逆向工程 BLE 设备 - 校验和?

Reverse Engineering BLE device - Checksum?

我正在尝试对 BLE 设备 (Gimbal) 进行逆向工程。在嗅探 btsnoop_hci.log 之后,我已经成功地复制了精确的命令,这里是其中的一些:

* AF: a55a030232200001 00 03 bd03
* TF: a55a030232200001 00 02 9c13
* HF: a55a030232200001 00 01 ff23
* LK: a55a030232200001 00 00 de33

这些 命令 改变了 操作模式 ,我猜它是用 HEX 编码的,从 0103。有四个,所以这是有道理的。但是,末尾有四个字符,恕我直言,这是某种校验和,但我不知道是哪种。试过了 online tool 但没有成功。

为什么我需要知道如何计算校验和?因为我也想通过模拟操纵杆控制电机,我不能只复制粘贴数千个值并映射它们。

此外,这里还有更多电机本身的值:

                      (Speed 1) (Dir 1)  (Speed 2) (Dir 2) (CRC???)
a55a03000e0000050000      6f      00        00       00     f0c8   (Goes Left)
a55a03000e0000050000      ff      00        00       00     6f0e   (Goes Left Fast)
a55a03000e0000050000      96      ff        00       00     a96b   (Goes Right)

a55a03000e0000050000      01      ff        00       00     1bfc   (Goes Right Fast)
a55a03000e0000050000      00      00        01       ff     0d68   (Goes Up)
a55a03000e0000050000      00      00        ff       00     3346   (Goes Down)

更新: 我用 reveng 来暴力破解 POLY 和 INIT:

第 1 步: 运行 命令(电机命令中的最后两个字节被反转):

reveng -w 16 -s a55a03000e00000500006f000000c8f0 a55a03000e0000050000ff0000000e6f a55a03000e000005000096ff00006ba9 a55a03000e000005000001ff0000fc1b

步骤 1 - 结果:

width=16  poly=0x1021  init=0xa55a  refin=false  refout=false  xorout=0x0000  check=0x0459  residue=0x0000  name=(none)

第 2 步: 运行 命令(模式命令中的最后两个字节被反转):

reveng -w 16 -s a55a030232200001000303bd a55a0302322000010002139c a55a030232200001000123ff a55a030232200001000033de

步骤 2 - 结果:

width=16  poly=0x1021  init=0xa55a  refin=false  refout=false  xorout=0x0000  check=0x0459  residue=0x0000  name=(none)
width=16  poly=0x4dd7  init=0xd565  refin=true  refout=true  xorout=0x0000  check=0x39bd  residue=0x0000  name=(none)

所以,很明显 poly (0x1021) & init (0xa55a) 匹配 在不同类型的消息中。

但是,如果我使用函数:

#define POLY (0x1021)
#define INIT (0xa55a)

uint16_t crc16(uint8_t * bfr, size_t size)
{
uint16_t crc = INIT;
int i;
    while(size--){
        crc ^= *bfr++;
        for(i = 0; i < 8; i++)
            /* assumes two's complement */
            crc = (crc>>1)^((0-(crc&1))&POLY);
    }
    return(crc);
}

CRC 值仍然与原始值不匹配。据我所知,我缺少一些东西。

示例:

uint8_t bfr[] = { 0xa5, 0x5a, 0x03, 0x02, 0x32, 0x20, 0x00, 0x01, 0x00, 0x02 };
uint16_t crc = crc16(bfr, 10);

应该导致 139c(从原来的交换),但我得到:1fb1 这是 poly & init 生成的实际值 (a55a030232200001 00 02 9c13)...

更新: 重新检查所有 14 字节(电机)值(交换最后一个字节)以确保:

a55a03000e00000500006f000000c8f0
a55a03000e0000050000ff0000000e6f
a55a03000e000005000096ff00006ba9
a55a03000e000005000001ff0000fc1b
a55a03000e0000050000000001ff680d
a55a03000e00000500000000ff004633

调用的命令:

reveng -w 16 -s a55a03000e00000500006f000000c8f0 a55a03000e0000050000ff0000000e6f a55a03000e000005000096ff00006ba9 a55a03000e000005000001ff0000fc1b a55a03000e0000050000000001ff680d a55a03000e00000500000000ff004633

reveng -w 16 -s a55a030232200001000303bd a55a0302322000010002139c a55a03000e00000500006f000000c8f0 a55a03000e0000050000ff0000000e6f

结果:

width=16  poly=0x1021  init=0xa55a  refin=false  refout=false  xorout=0x0000  check=0x0459  residue=0x0000  name=(none)

因此(有两个 10 字节值和两个 14 字节值):

width=16  poly=0x1021  init=0xa55a  refin=false  refout=false  xorout=0x0000  check=0x0459  residue=0x0000  name=(none)
width=16  poly=0x1021  init=0x5545  refin=false  refout=false  xorout=0xf01f  check=0x0459  residue=0xf01f  name=(none)

Poly & Init 一定是对的。但是,在生成第一个的 CRC 时:

//                 a5    5a    03    00    0e    00    00    05    00    00    6f    00   00     00     f0c8 (swapped c8f0)
uint8_t bfr[] = { 0xa5, 0x5a, 0x03, 0x00, 0x0e, 0x00, 0x00, 0x05, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00 };



uint16_t crc = crc16(bfr, 14);

我得到十六进制的输出:532。我不明白。为什么我从 returns 错误的十六进制生成 Poly & Init 的代码?

您可以对样本对进行异或以减少变量的数量,因为这会消除任何初始值或最终异或(就好像两者都是 0),并且搜索只需要查找多项式,如果它是非反射(左移)或反射(右移)。对于 CRC16,左移只有 64k 循环,右移只有 64k 循环。有可能得到多个看似有效的多项式,因此需要更多样本来确认。

* AF: a55a030232200001 00 03 bd03
* TF: a55a030232200001 00 02 9c13
      0000000000000000 00 01 2110

* HF: a55a030232200001 00 01 ff23
* LK: a55a030232200001 00 00 de33
                          01 2110

起初我假设最后 2 个字节被交换,所以 01 2110 将是 01 10 21,一个常见的左移(未反映)CRC,但由于错误我没有让它工作就我而言(其中一张支票的尺寸有误)。

然后我假设最后 2 个字节是大端字节序 0x2110,对于单个数据字节 0x01 的左移 CRC,CRC 与多项式相同。然而,多项式的最低有效位(位 0)需要为 1,而 0x2110 的位 0 为 0,所以我尝试了右移 CRC。

对单个数据字节 = 0x01 的 CRC 为 0x2110 的反射多项式进行强力搜索,有 3 种情况,但其中只有一种具有位 15 == 1, 0xEBB2,因此是假定的多项式。

然后对初始值和最终 xor = 0x0000 或 0xffff 进行暴力搜索,以匹配所有 4 个示例,初始值 = 0xa6ab,最终 xor = 0x0000。

示例位级代码:

typedef unsigned char   uint8_t;
typedef unsigned short uint16_t;

#define POLY (0xebb2)    /* polynomial */
#define INIT (0xa6ab)    /* initial value */
#define FXOR (0x0000)    /* final xor == xor out */

uint16_t crc16(uint8_t * bfr, size_t size)
{
uint16_t crc = INIT;
int i;
    while(size--){
        crc ^= *bfr++;
        for(i = 0; i < 8; i++)
            /* assumes two's complement */
            crc = (crc>>1)^((0-(crc&1))&POLY);
    }
    return(crc^FXOR);
}

如果将 CRC 与交换字节进行比较,则生成的 CRC 将匹配 4 个示例:

crc = crc16(bfr, 10);
if((bfr[10] == crc>>8) && (bfr[11] == crc&0xff))
    printf("match\n");

虽然这适用于这 4 种情况,但我无法确定这是不是更多情况下使用的实际 CRC。


然后我建议交换 12 字节和 16 字节消息的最后 2 个字节,并使用 reveng。这表明 0x1021 的左移和 POLY 是一个可能的多项式,所以我重复了我的测试并最终得到与 reveng 相同的结果:

对于左移CRC,代码不同:

#define POLY (0x1021)    /* polynomial */
#define INIT (0xa55a)    /* initial value */
#define FXOR (0x0000)    /* final xor == xor out */

uint16_t crc16(uint8_t * bfr, size_t size)
{
uint16_t crc = INIT;
int i;
    while(size--){
        crc ^= ((uint16_t)(*bfr++))<<8;
        for(i = 0; i < 8; i++)
            /* assumes two's complement */
            crc = (crc<<1)^((0-(crc>>15))&POLY);
    }
    return(crc^FXOR);
}

这是我使用的暴力搜索代码。它可能需要多达 40 亿次循环,但这只需要几分钟,所以我没有费心去优化它。可以使用基于多项式的 table 查找或使用带有 SSSE3 xmm 寄存器的汇编代码对其进行优化(大约是 table 查找的 8 倍)。

#include <stdio.h>

typedef unsigned char   uint8_t;
typedef unsigned short uint16_t;

#define POLY (0x1021)           /* polynomial */
static uint16_t INIT;           /* initial value */
static uint16_t FXOR;           /* final xor == xor out */

/* left shifting crc using POLY == 0x1021 */

uint16_t crc16(uint8_t * bfr, size_t size)
{
uint16_t crc = INIT;
int i;
    while(size--){
        crc ^= (uint16_t)(*bfr++)<<8;
        for(i = 0; i < 8; i++)
            /* assumes two's complement */
            crc = (crc<<1)^((0-(crc>>15))&POLY);
    }
    return(crc^FXOR);
}

/* assume last 2 bytes are swapped versus nomral CRC */

int main(int argc, char**argv)
{
uint16_t crc;
uint8_t bfr0[] = {0xa5,0x5a,0x03,0x02,0x32,0x20,0x00,0x01,0x00,0x00,0xde,0x33};
uint8_t bfr1[] = {0xa5,0x5a,0x03,0x02,0x32,0x20,0x00,0x01,0x00,0x01,0xff,0x23};
uint8_t bfr2[] = {0xa5,0x5a,0x03,0x02,0x32,0x20,0x00,0x01,0x00,0x02,0x9c,0x13};
uint8_t bfr3[] = {0xa5,0x5a,0x03,0x02,0x32,0x20,0x00,0x01,0x00,0x03,0xbd,0x03};
uint8_t bfr4[] = {0xa5,0x5a,0x03,0x00,0x0e,0x00,0x00,0x05,0x00,0x00,0x96,0xff,0x00,0x00,0xa9,0x6b};
uint8_t bfr5[] = {0xa5,0x5a,0x03,0x00,0x0e,0x00,0x00,0x05,0x00,0x00,0x6f,0x00,0x00,0x00,0xf0,0xc8};
uint8_t bfr6[] = {0xa5,0x5a,0x03,0x00,0x0e,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0xff,0x00,0x33,0x46};
uint8_t bfr7[] = {0xa5,0x5a,0x03,0x00,0x0e,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x01,0xff,0x0d,0x68};
    FXOR = 0;
    do{
        INIT = 0;
        do{
            crc = crc16(bfr0, 10);
            if(crc != 0x33de)
                continue;
            crc = crc16(bfr1, 10);
            if(crc != 0x23ff)
                continue;
            crc = crc16(bfr2, 10);
            if(crc != 0x139c)
                continue;
            crc = crc16(bfr3, 10);
            if(crc != 0x03bd)
                continue;
            crc = crc16(bfr4, 14);
            if(crc != 0x6ba9)
                continue;
            crc = crc16(bfr5, 14);
            if(crc != 0xc8f0)
                continue;
            crc = crc16(bfr6, 14);
            if(crc != 0x4633)
                continue;
            crc = crc16(bfr7, 14);
            if(crc != 0x680d)
                continue;
            goto match0;
        }while(++INIT != 0);
    }while(++FXOR != 0);
match0:
    printf("%04x %04x\n", INIT, FXOR);
    crc = crc16(bfr0, 10);
    printf("%04x\n", crc);
    crc = crc16(bfr1, 10);
    printf("%04x\n", crc);
    crc = crc16(bfr2, 10);
    printf("%04x\n", crc);
    crc = crc16(bfr3, 10);
    printf("%04x\n", crc);
    crc = crc16(bfr4, 14);
    printf("%04x\n", crc);
    crc = crc16(bfr5, 14);
    printf("%04x\n", crc);
    crc = crc16(bfr6, 14);
    printf("%04x\n", crc);
    crc = crc16(bfr7, 14);
    printf("%04x\n", crc);
    return(0);
}

假设左移 POLY == 0x1021,代码确定 INIT == 0xa55a 和 FXOR == 0x0000 适用于我测试的 8 个案例。由于 XFOR == 0x0000,它只需要 运行 64k 循环。