224 lines
10 KiB
Markdown
224 lines
10 KiB
Markdown
|
## 前言
|
|||
|
|
|||
|
- 看完论坛的 [STM32固件逆向](https://bbs.pediy.com/thread-272811.htm) 帖子后,又在评论区发现这个求助贴 [stm32芯片程序有xtea加密算法,但是数据排序的问题研究不明白](https://bbs.pediy.com/thread-272872.htm) ,于是想着分析分析练练手
|
|||
|
|
|||
|
![](/_resources/855195_EEXH7ERQQA4MU5J.png)
|
|||
|
|
|||
|
- 根据求助贴内容,概括下有用的信息
|
|||
|
```
|
|||
|
- 一个名为stm32f103RCT6.bin的固件,从固件名可以得知MCU型号为stm32f103RCT6
|
|||
|
- MCU与设备A通信时用到了XTEA加密
|
|||
|
- MCU注册时用到的密钥为 BA2F96A9
|
|||
|
- MCU与设备A通信时的两个加密样例如下
|
|||
|
|
|||
|
明文1
|
|||
|
10 BE 62 F8 E8 DC 34 46
|
|||
|
密文1
|
|||
|
8C 79 F5 D1 5E A9 46 2D
|
|||
|
|
|||
|
明文2
|
|||
|
0E 77 50 C8 C6 27 E1 BF
|
|||
|
密文2
|
|||
|
36 0A 1A 6E 6E FE F0 84
|
|||
|
````
|
|||
|
|
|||
|
|
|||
|
- 接下来目标就是根据上面信息,找到加密函数,还原加密过程
|
|||
|
|
|||
|
|
|||
|
## 开始
|
|||
|
|
|||
|
- 首先用IDA打开stm32f103RCT6.bin文件,选择ARM小端序,然OK进入即可
|
|||
|
|
|||
|
![](/_resources/855195_N5KQ4J3XP5XU7KJ.png)
|
|||
|
|
|||
|
- 直接以Binary file打开,可以看到,IDA没有识别出任何函数
|
|||
|
|
|||
|
![](/_resources/855195_Q36XQYB4QSVHQAF.png)
|
|||
|
|
|||
|
- 习惯性的把前几个字节"D"三下,如下图,可以大胆的猜测加载基地址为0x8000000,事实上也确实如此,当然可以根据MCU型号stm32f103RCT6去查datasheet。不过更多确定基地址的方法请参考论坛的这个帖子 [固件安全之加载地址分析](https://bbs.pediy.com/thread-267719.htm) ,里面详细介绍了多种方法来确定基地址,这里就不再赘述了
|
|||
|
|
|||
|
![](/_resources/855195_CBVA6ARP3BB9EKB.png)
|
|||
|
|
|||
|
- Edit > Segments > Rebase program... 重设基地址为0x8000000,设置好基地址后,就已经往成功路上迈向一大步了
|
|||
|
|
|||
|
![](/_resources/855195_R33AGSFEKP2BYQG.png)
|
|||
|
|
|||
|
- 接下来,Alt + L从前面几个地址之后开始选择,一直到末尾,右键选择"Analyze selected area"
|
|||
|
|
|||
|
![](/_resources/855195_RPZUKFWGHPW5ZBX.png)
|
|||
|
|
|||
|
![](/_resources/855195_C5FV5R36DNDDH99.png)
|
|||
|
|
|||
|
- 出现下面提示框,选择Analyze,然后静待IDA分析过程结束
|
|||
|
|
|||
|
![](/_resources/855195_8KGJ6BY4Z4VE7MD.png)
|
|||
|
|
|||
|
- 这时候,可以看到,IDA已经识别出许多函数了
|
|||
|
|
|||
|
![](/_resources/855195_WYQSUKGKHVHNC2U.png)
|
|||
|
|
|||
|
- 接下来使用Findcrypt插件搜索加密常量,看看有没有什么发现,Search > Find crypto constants,如下图,可以看到,有个TEA的delta加密常量,这正好对应前面提到的MCU通信时使用了XTEA加密
|
|||
|
|
|||
|
![](/_resources/855195_ZPMKFT24PWUZBBU.png)
|
|||
|
|
|||
|
- 跟踪查看引用该常量的sub_800E288函数,F5结果如下图所示
|
|||
|
|
|||
|
![](/_resources/855195_969WJTGJAQMSAWE.png)
|
|||
|
|
|||
|
- 对比网上的XTEA加密C代码,可以知道,sub_800E288函数的参数从左到右为,加密密钥、加密输入、加密输出
|
|||
|
|
|||
|
![](/_resources/855195_79E38SUZ572VYDN.png)
|
|||
|
|
|||
|
- 对照上面XTEA加密C代码,就可以对一些变量重命名了,如下图所示,可以看到,加密时的密钥输入并不直接作为XTEA加密的密钥,而是经过了一些运算进行了变换
|
|||
|
|
|||
|
![](/_resources/855195_Q5DSQ7UJTYEJC2P.png)
|
|||
|
|
|||
|
- 接下来详细分析加密过程,首先时最前面的这部分,存在大量的 左移24、左移16和左移8,熟悉的朋友可能很快就反应过来,这个是大小端转换。所谓大小端指的是"大端序"(BigEndian)和"小端序"(LittleEndian)。用一句话来说"大端序"就是高位在前,低位在后,"小端序"就是低位在前,高位在后。举个简单例子对于同一个4字节的byte数组"12 34 56 78",在大端序里面把它当成4字节的int的话它就是0x12345678,而在小端序里面把它当成4字节的int的话它就是0x78563412了。下面的"<<24 <<16 <<8"作用就是用来将4个byte的数组转为大端序的整数(MCU是小端序的,直接用*(int *)类型强转的话得到的结果就是一个小端序的int)
|
|||
|
|
|||
|
![](/_resources/855195_Q37GH5R5DDY9PY2.png)
|
|||
|
|
|||
|
- 忽略左移24、左移16和左移8这些端序转换的部分,我们能发现,其实对于输入in在正式XTEA加密前没有做任何特殊处理,而对于密钥的每一个字节则与一些常量进行了异或处理。同样地,看到"0x66、0x6F ...",这些值,熟悉的情况下,很容易反应过来,这些可能是ASCII码
|
|||
|
|
|||
|
![](/_resources/855195_Q37GH5R5DDY9PY2.png)
|
|||
|
|
|||
|
- TAB键切换查看反汇编代码,如下图,可以看到这些常量都是基于地址0x8030A30开始的偏移量
|
|||
|
|
|||
|
![](/_resources/855195_34CVYXN7WE24CH7.png)
|
|||
|
|
|||
|
- 查看0x8030A30地址,"A"一下,可以得到一个字符串,如下图所示,看得出来这是个有故事的字符串
|
|||
|
|
|||
|
![](/_resources/855195_JC25E8J7DKV4YD3.png)
|
|||
|
|
|||
|
- 回到反编译代码窗口,重新F5一下,可以看到,反编译代码更清晰明了了,密钥在用于XTEA加密之前,每个字节会与上面的字符串相对应的字节进行异或处理
|
|||
|
|
|||
|
![](/_resources/855195_3CK99UYKVG7ZWC9.png)
|
|||
|
|
|||
|
- 再来看后面这部分,如下图,后面的其实就是标准XTEA加密,只不过对于加密结果的输出,同样有大小端的转换,"HIBYTE(x) BYTE2(x) BYTE1(x)"与上面的"<<24 <<16 <<8"一样,常见于数据大小端转换
|
|||
|
|
|||
|
![](/_resources/855195_J6YYW7BD9C3VBPJ.png)
|
|||
|
|
|||
|
- 到这里就结束了吗?- 答案是还没有,求助贴里面提到的MCU注册密钥是"BA2F96A9",只有4个字节大小的密钥,但是这个加密函数的密钥输入却有16个字节,所以从MCU注册密钥"BA2F96A9"到加密密钥userKey中间还有一些过程需要去分析
|
|||
|
|
|||
|
- 对 sub_800E288 "X"一下,查看sub_800E288函数的交叉引用,如下图,可以看到只有一处调用
|
|||
|
|
|||
|
![](/_resources/855195_XRD7MRZEW57XKAZ.png)
|
|||
|
|
|||
|
- 查看该调用,如下图,可以看到第一个参数加密密钥是以地址0x20000104传入的,所以加密密钥就储存在0x20000104地址处,接下来只要查看哪些函数有往0x20000104地址写数据,即可找到密钥变换的函数
|
|||
|
|
|||
|
![](/_resources/855195_9C2CX4SKYKK5Q3S.png)
|
|||
|
|
|||
|
- 接下来Text Serach搜索0x20000104,看哪些函数使用了0x20000104地址
|
|||
|
|
|||
|
![](/_resources/855195_7DRQSR433ERP3QU.png)
|
|||
|
|
|||
|
- 如下图,搜索的结果并不多,挨个查看后,可以看到只有sub_800F3C8函数中有往0x20000104地址写数据的代码,而且sub_800F3C8函数参数为unsigned int类型(刚好是4个字节大小),所以基本上可以确定这个函数就是MCU注册时的密钥变换为加密密钥的函数
|
|||
|
|
|||
|
![](/_resources/855195_P35AQH74WFKCSCY.png)
|
|||
|
|
|||
|
![](/_resources/855195_FSR36AQRNH3AQ8E.png)
|
|||
|
|
|||
|
- 接下来将sub_800F3C8和sub_800E288这两个函数反编译伪代码提取出来,用VS Code简单写个程序验证下,代码如下
|
|||
|
|
|||
|
```c
|
|||
|
#include <stdio.h>
|
|||
|
#include "defs.h"
|
|||
|
void keyExpand(unsigned int key, _BYTE *outKey);
|
|||
|
void XTEA(_BYTE *userkey, _BYTE *in, _BYTE *out);
|
|||
|
|
|||
|
void keyExpand(unsigned int key, _BYTE *outKey)
|
|||
|
{
|
|||
|
int i; // r0
|
|||
|
|
|||
|
for ( i = 0; i < 16; i = (i + 1) )
|
|||
|
*(i + outKey) = key >> (8 * (3 - i % 4));
|
|||
|
}
|
|||
|
|
|||
|
void XTEA(_BYTE *userkey, _BYTE *in, _BYTE *out)
|
|||
|
{
|
|||
|
unsigned int v3; // r3
|
|||
|
unsigned int v4; // r4
|
|||
|
unsigned int v5; // r5
|
|||
|
unsigned int i; // r6
|
|||
|
int v7[4]; // [sp+0h] [bp-34h]
|
|||
|
unsigned int v8; // [sp+10h] [bp-24h]
|
|||
|
unsigned int v9; // [sp+14h] [bp-20h]
|
|||
|
|
|||
|
char aStefanlovesmay[] = "StefanLovesMaya!";
|
|||
|
|
|||
|
if ( userkey !=0 && in!=0 && out!=0 )
|
|||
|
{
|
|||
|
v8 = (*in << 24) + (in[1] << 16) + (in[2] << 8) + in[3];
|
|||
|
v9 = (in[4] << 24) + (in[5] << 16) + (in[6] << 8) + in[7];
|
|||
|
v7[0] = (userkey[3] ^ aStefanlovesmay[3])
|
|||
|
+ ((*userkey ^ aStefanlovesmay[0]) << 24)
|
|||
|
+ ((userkey[1] ^ aStefanlovesmay[1]) << 16)
|
|||
|
+ ((userkey[2] ^ aStefanlovesmay[2]) << 8);
|
|||
|
v7[1] = (userkey[7] ^ aStefanlovesmay[7])
|
|||
|
+ ((userkey[4] ^ aStefanlovesmay[4]) << 24)
|
|||
|
+ ((userkey[5] ^ aStefanlovesmay[5]) << 16)
|
|||
|
+ ((userkey[6] ^ aStefanlovesmay[6]) << 8);
|
|||
|
v7[2] = (userkey[11] ^ aStefanlovesmay[11])
|
|||
|
+ ((userkey[8] ^ aStefanlovesmay[8]) << 24)
|
|||
|
+ ((userkey[9] ^ aStefanlovesmay[9]) << 16)
|
|||
|
+ ((userkey[10] ^ aStefanlovesmay[10]) << 8);
|
|||
|
v7[3] = (userkey[15] ^ aStefanlovesmay[15])
|
|||
|
+ ((userkey[12] ^ aStefanlovesmay[12]) << 24)
|
|||
|
+ ((userkey[13] ^ aStefanlovesmay[13]) << 16)
|
|||
|
+ ((userkey[14] ^ aStefanlovesmay[14]) << 8);
|
|||
|
v3 = v8;
|
|||
|
v4 = v9;
|
|||
|
v5 = 0;
|
|||
|
for ( i = 0; i < 0x20; ++i )
|
|||
|
{
|
|||
|
v3 += (((16 * v4) ^ (v4 >> 5)) + v4) ^ (v7[v5 & 3] + v5);
|
|||
|
v5 -= -0x9E3779B9;
|
|||
|
v4 += (((16 * v3) ^ (v3 >> 5)) + v3) ^ (v7[(v5 >> 11) & 3] + v5);
|
|||
|
}
|
|||
|
*out = HIBYTE(v3);
|
|||
|
out[1] = BYTE2(v3);
|
|||
|
out[2] = BYTE1(v3);
|
|||
|
out[3] = v3;
|
|||
|
out[4] = HIBYTE(v4);
|
|||
|
out[5] = BYTE2(v4);
|
|||
|
out[6] = BYTE1(v4);
|
|||
|
out[7] = v4;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
int main() {
|
|||
|
unsigned int key=0xBA2F96A9;
|
|||
|
_BYTE outKey[16];
|
|||
|
|
|||
|
keyExpand(key, outKey);
|
|||
|
printf("outKey: ");
|
|||
|
for (int i=0; i<16; i++) {
|
|||
|
printf("%02x ", outKey[i]);
|
|||
|
}
|
|||
|
printf("\n");
|
|||
|
// _BYTE in[] = {0x10, 0xBE, 0x62, 0xF8, 0xE8, 0xDC, 0x34, 0x46};
|
|||
|
_BYTE in[] ={0x0E, 0x77, 0x50, 0xC8, 0xC6, 0x27, 0xE1, 0xBF};
|
|||
|
_BYTE out[8];
|
|||
|
printf("in: ");
|
|||
|
for (int i=0; i<8; i++) {
|
|||
|
printf("%02x ", in[i]);
|
|||
|
}
|
|||
|
printf("\n");
|
|||
|
XTEA(outKey, in, out);
|
|||
|
|
|||
|
printf("out: ");
|
|||
|
for (int i=0; i<8; i++) {
|
|||
|
printf("%02x ", out[i]);
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
- 输出结果,如下图所示,下面输出结果用到例子为上面样例的第2组数据(使用第1组效果也一样),可以看到加密结果与预期一致,至此,成功还原了整个XTEA加密过程
|
|||
|
|
|||
|
![](/_resources/855195_E7JK3DTEBX2WDD4.png)
|
|||
|
|
|||
|
|
|||
|
### 最后
|
|||
|
|
|||
|
- 如您觉得本文内容侵犯了您的利益,请联系作者或
|