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

通过 TK 计算 STK

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

1
2
3
4
5
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

1
2
3
4
5
6
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

image.png
image.png

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

image.png
image.png

计算 iv 这里只需要把他们反转一下即可,因为在内存里放着的是逆序的,看一下 GDB 调试的
GDB 调试的方法,gdb crackle
然后 b 源码第几行,比如b 608下个断点,set args -i Legacy_pairing.pcapng,然后点击运行即可

1
2
3
4
5
6
7
8
9
10
11
12
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 值

1
2
gdb-peda$ x/gx 0x55555555f800
0x55555555f800: 0x50ea3d48a84aa3fd //50ea3d48 ivs a84aa3fd ivm

计算 STK

STK 的生成方式在蓝牙的规范中用的是一个叫做 s1 的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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) 即可

image.png
image.png

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

image.png
image.png
image.png
image.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。这明显是拿低位拼起来的啊,为啥这里是拿高位拼起来?还是说 wireshark 显示的是小端序?我理解不了了

1
2
3
4
5
6
7
8
9
10
11
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