obsidian-notes/MCU/逆向/一个简单的STM32固件分析.md
CSSC-WORK\murmur 3e6078442b init version
2024-04-15 11:19:57 +08:00

10 KiB
Raw Permalink Blame History

前言

- 一个名为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进入即可

  • 直接以Binary file打开可以看到IDA没有识别出任何函数

  • 习惯性的把前几个字节"D"三下如下图可以大胆的猜测加载基地址为0x8000000事实上也确实如此当然可以根据MCU型号stm32f103RCT6去查datasheet。不过更多确定基地址的方法请参考论坛的这个帖子 固件安全之加载地址分析 ,里面详细介绍了多种方法来确定基地址,这里就不再赘述了

  • Edit > Segments > Rebase program... 重设基地址为0x8000000设置好基地址后就已经往成功路上迈向一大步了

  • 接下来Alt + L从前面几个地址之后开始选择一直到末尾右键选择"Analyze selected area"

  • 出现下面提示框选择Analyze然后静待IDA分析过程结束

  • 这时候可以看到IDA已经识别出许多函数了

  • 接下来使用Findcrypt插件搜索加密常量看看有没有什么发现Search > Find crypto constants如下图可以看到有个TEA的delta加密常量这正好对应前面提到的MCU通信时使用了XTEA加密

  • 跟踪查看引用该常量的sub_800E288函数F5结果如下图所示

  • 对比网上的XTEA加密C代码可以知道sub_800E288函数的参数从左到右为加密密钥、加密输入、加密输出

  • 对照上面XTEA加密C代码就可以对一些变量重命名了如下图所示可以看到加密时的密钥输入并不直接作为XTEA加密的密钥而是经过了一些运算进行了变换

  • 接下来详细分析加密过程,首先时最前面的这部分,存在大量的 左移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

  • 忽略左移24、左移16和左移8这些端序转换的部分我们能发现其实对于输入in在正式XTEA加密前没有做任何特殊处理而对于密钥的每一个字节则与一些常量进行了异或处理。同样地看到"0x66、0x6F ..."这些值熟悉的情况下很容易反应过来这些可能是ASCII码

  • TAB键切换查看反汇编代码如下图可以看到这些常量都是基于地址0x8030A30开始的偏移量

  • 查看0x8030A30地址"A"一下,可以得到一个字符串,如下图所示,看得出来这是个有故事的字符串

  • 回到反编译代码窗口重新F5一下可以看到反编译代码更清晰明了了密钥在用于XTEA加密之前每个字节会与上面的字符串相对应的字节进行异或处理

  • 再来看后面这部分如下图后面的其实就是标准XTEA加密只不过对于加密结果的输出同样有大小端的转换"HIBYTE(x) BYTE2(x) BYTE1(x)"与上面的"<<24 <<16 <<8"一样,常见于数据大小端转换

  • 到这里就结束了吗?- 答案是还没有求助贴里面提到的MCU注册密钥是"BA2F96A9"只有4个字节大小的密钥但是这个加密函数的密钥输入却有16个字节所以从MCU注册密钥"BA2F96A9"到加密密钥userKey中间还有一些过程需要去分析

  • 对 sub_800E288 "X"一下查看sub_800E288函数的交叉引用如下图可以看到只有一处调用

  • 查看该调用如下图可以看到第一个参数加密密钥是以地址0x20000104传入的所以加密密钥就储存在0x20000104地址处接下来只要查看哪些函数有往0x20000104地址写数据即可找到密钥变换的函数

  • 接下来Text Serach搜索0x20000104看哪些函数使用了0x20000104地址

  • 如下图搜索的结果并不多挨个查看后可以看到只有sub_800F3C8函数中有往0x20000104地址写数据的代码而且sub_800F3C8函数参数为unsigned int类型刚好是4个字节大小所以基本上可以确定这个函数就是MCU注册时的密钥变换为加密密钥的函数

  • 接下来将sub_800F3C8和sub_800E288这两个函数反编译伪代码提取出来用VS Code简单写个程序验证下代码如下

#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加密过程

最后

  • 如您觉得本文内容侵犯了您的利益,请联系作者或