跳转至

Crackle学习

crackle学习

GitHub - mikeryan/crackle: Crack and decrypt BLE encryption

crackle 是研究蓝牙安全的 mikeryan 大佬写的一款能够解密 BLE 传统配对(legacy pairing)后的流量包的工具,本文将通过 crackle 源码的阅读,学习 SMP 协议、学习如何解密传统配对的流量

通过 TK 计算 STK

这里用到了几个函数,copy_reverse 是用来逆序的

void copy_reverse(const u_char *bytes, uint8_t *dest, size_t len) {
    unsigned i;
    for (i = 0; i < len; ++i)
        dest[i] = bytes[len - 1 - i];
}
计算 iv
void calc_iv(connection_state_t *state) {
    assert(state != NULL);

    copy_reverse(state->ivm, state->iv + 0, 4);
    copy_reverse(state->ivs, state->iv + 4, 4);
}

iv 是在数据包中直接能找到的,在 LL_ENC_REQ 报文中是 ivm,意思是 master 的 iv

1663663800841-ff4eccdd-8b38-4944-8fb2-ebd70a8d8541.png

在 LL_ENC_RSP 报文中是 ivs,意思是 slave 的 iv

1663664210061-c32db721-5a0b-46ef-80ec-9f4dbe503bf0.png

计算 iv 这里只需要把他们反转一下即可,因为在内存里放着的是逆序的,看一下 GDB 调试的

GDB 调试的方法,gdb crackle

然后 b 源码第几行,比如b 608下个断点,set args -i Legacy_pairing.pcapng,然后点击运行即可

gdb-peda$ p &state->ivm
$1 = (uint8_t (*)[4]) 0x55555555f7c0
gdb-peda$ x/gx 0x55555555f7c0
0x55555555f7c0: 0x816400f1fda34aa8    //fda34aa8
gdb-peda$ p &state->ivs
$2 = (uint8_t (*)[4]) 0x55555555f7cc
gdb-peda$ x/gx 0x55555555f7cc
0x55555555f7cc: 0x00000000483dea50    //483dea50
gdb-peda$ p &state->iv
$3 = (uint8_t (*)[8]) 0x55555555f800
gdb-peda$ x/gx 0x55555555f800
0x55555555f800: 0x0000000000000000    //此时iv是空的

等执行完两个反转就得到了真实的 iv 值

gdb-peda$ x/gx 0x55555555f800
0x55555555f800: 0x50ea3d48a84aa3fd   //50ea3d48 ivs   a84aa3fd ivm
计算STK

STK 的生成方式在蓝牙的规范中用的是一个叫做 s1 的函数,实际上是 AES-128-CCM 加密

void calc_stk(connection_state_t *state, uint32_t numeric_key) {
    uint8_t rand[16];

    assert(state != NULL);

    // calculate TK
    numeric_key = htobe32(numeric_key);
    memcpy(&state->tk[12], &numeric_key, 4);

    // STK = s1(TK, Srand, Mrand) [pg 1971]
    // concatenate the lower 8 octets of Srand and MRand
    memcpy(rand + 0, state->srand + 8, 8);
    memcpy(rand + 8, state->mrand + 8, 8);

    aes_block(state->tk, rand, state->stk);
}

s1 这个函数需要 TK 的值和两个 random 的值,TK 需要转成小端序放在内存里,用 htobe32(numeric_key) 即可

1663722963069-620848cd-fe90-4a03-bea6-0485531a52b0.png

gdb-peda$ p &numeric_key
$1 = (uint32_t *) 0x7fffffffe174
gdb-peda$ x/2gx 0x7fffffffe174
0x7fffffffe174: 0x5556072000031b16      0x5555524000005555    #1b160300
....执行后....
gdb-peda$ x/2gx 0x7fffffffe174
0x7fffffffe174: 0x55560720161b0300      0x5555524000005555    #00031b16

然后拷贝到 state->tk[12]

gdb-peda$ p &state->tk[12]
$2 = (uint8_t *) 0x5555555607dc ""
gdb-peda$ x/gx 0x5555555607dc
0x5555555607dc: 0x0000000000000000
....执行后....
gdb-peda$ x/gx 0x5555555607dc
0x5555555607dc: 0x00000000161b0300

两个 random 分别取高 8 字节拼接起来

1663723311412-280c3809-01c1-40ef-907e-1e7df72477cb.png

1663723390273-1cc1ee7c-595c-4768-9444-12d112e46af6.png

看一下标准里咋说:For example if the 128-bit value r1 is 0x000F0E0D0C0B0A091122334455667788 then r1’is 0x1122334455667788. If the 128-bit value r2 is 0x010203040506070899AABBCCDDEEFF00 then r2’is 0x99AABBCCDDEEFF00。(r1 为 Srand,r2 为 Mrand,最后 r' = r1' || r2')

这明显是拿低位拼起来的啊,为啥代码这里是拿高位拼起来?还是说 wireshark 显示的是小端序?我理解不了了

gdb-peda$ p &state->mrand
$5 = (uint8_t (*)[16]) 0x55555555f76e     
gdb-peda$ p &state->srand
$6 = (uint8_t (*)[16]) 0x55555555f77e
gdb-peda$ x/4gx 0x55555555f76e
0x55555555f76e: 0xec977c9fe63591dc      0x1a10975ba476bc02   //这是mrand
0x55555555f77e: 0xa20ea327c9897c01      0x575852f181e9db17   //这是srand
gdb-peda$ p &rand
$7 = (uint8_t (*)[16]) 0x7fffffffe160
gdb-peda$ x/2gx 0x7fffffffe160
0x7fffffffe160: 0x575852f181e9db17      0x1a10975ba476bc02   //这是拼起来后的rand
0x7fffffffe0e0: 0x9bf4b0e8f0e11f9a      0x62a06d79ae16425b    //stk
0x7fffffffe0f0: 0x090a0b0c0d0e0f00      0x8877665544332211    //srand
0x7fffffffe100: 0x0807060504030201      0x00ffeeddccbbaa99    //mrand

原文: https://www.yuque.com/hxfqg9/iot/ze45bw