arrow_backUS Cyber Games 2026
REmedium

Smart Lock

by Pavel Knespl (capyplivl)

A BLE smart lock sends its 8-digit PIN in cleartext over an unencrypted GATT characteristic. Sniffing the unlock capture leaks the PIN directly, and the remote lock service only needs that PIN.

Solve

  1. Read the BLE capture for ATT writes and notifications:
tshark -r unlock_capture.pcapng -Y btatt -T fields -e btatt.handle -e btatt.opcode -e btatt.value

The interesting frames (all hex ASCII):

  • handle 0x0003 write 3437313933383230 = 47193820 (the PIN)
  • handle 0x0005 notify 63...6a = c3d8f2a1-7e4b-4d9c-b6f0-1a2e3c4d5f6a (server challenge)
  • handle 0x0008 write d87d6883829e0bfece6a7c1950681762 (16-byte response token)
  • handle 0x000a notify 554e4c4f434b4544 = UNLOCKED
  1. Confirm the PIN by reproducing the response token. The token is MD5(pin + challenge):
python3 -c "import hashlib;print(hashlib.md5(('47193820'+'c3d8f2a1-7e4b-4d9c-b6f0-1a2e3c4d5f6a').encode()).hexdigest())"

This matches the captured token exactly, proving the PIN is 47193820. 3. (Optional) Verify the algorithm in the app. jadx/apktool are not required; unzip the APK and inspect the dex strings:

unzip -o SmartLock.apk -d apk_unzip
strings apk_unzip/classes.dex | grep -E 'AuthUtils|computeResponse|computeSHA256Digest|^MD5$|MessageDigest'

com/smartlock/app/AuthUtils exposes computeResponse. The method named computeSHA256Digest is a red herring: the only digest-algorithm literal in the dex is MD5 (no SHA-256 string exists), and the code calls MessageDigest.getInstance("MD5"). So the response is MD5(pin || challengeUuid), consistent with step 2. 4. The remote lock just checks the PIN. Run against the live instance:

from pwn import *
r = remote(HOST, PORT)
r.recvuntil(b'PIN:')
r.sendline(b'47193820')
print(r.recvall().decode())

Flag: SVIUSCG{1b3aa3e7895dfb275c671ef059b6f696}