首页
社区
课程
招聘
[原创]一个简单的STM32固件分析
2022-10-19 23:25 21377

[原创]一个简单的STM32固件分析

2022-10-19 23:25
21377

前言

  • 看完论坛的 STM32固件逆向 帖子后,又在评论区发现这个求助贴 stm32芯片程序有xtea加密算法,但是数据排序的问题研究不明白 ,于是想着分析分析练练手

  • 根据求助贴内容,概括下有用的信息

    • 一个名为stm32f103RCT6.bin的固件,从固件名可以得知MCU型号为stm32f103RCT6

    • MCU与设备A通信时用到了XTEA加密

    • MCU注册时用到的密钥为 BA2F96A9

    • MCU与设备A通信时的两个加密样例如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      明文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简单写个程序验证下,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    #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加密过程

最后

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

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-10-21 18:55 被烧板侠编辑 ,原因:
上传的附件:
收藏
点赞9
打赏
分享
最新回复 (5)
雪    币: 199
活跃值: (1332)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_一叶_733 2022-10-20 22:50
2
0

代码复制到VS studiow 2022,生成解决方案时下面错误,请教下怎样解决。百度回来的解决方法没效。


严重性 代码 说明 项目 文件 禁止显示状态

错误(活动) E1696 无法打开 源 文件 "defs.h" XETA C:\Users\Lhm\source\repos\XETA\XETA\xete.c 2


错误(活动) E0020 未定义标识符 "_BYTE" XETA C:\Users\Lhm\source\repos\XETA\XETA\xete.c 3


错误 C1083 无法打开包括文件: “defs.h”: No such file or directory XETA C:\Users\Lhm\source\repos\XETA\XETA\xete.c 2


最后于 2022-10-21 12:11 被wx_一叶_733编辑 ,原因:
雪    币: 2027
活跃值: (1258)
能力值: ( LV7,RANK:101 )
在线值:
发帖
回帖
粉丝
烧板侠 2 2022-10-21 18:53
3
0
wx_一叶_733 代码复制到VS studiow 2022,生成解决方案时下面错误,请教下怎样解决。百度回来的解决方法没效。严重性 代码 说明 项目 文件 行 禁止显示状态错误(活动) E1696 无法打开 源 文件 ...
defs.h是IDA定义的头文件可以在IDA安装目录的plugins\hexrays_sdk\include下找到,或者下载附件就可以找到
雪    币: 199
活跃值: (1332)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_一叶_733 2022-10-21 19:41
4
0
烧板侠 defs.h是IDA定义的头文件可以在IDA安装目录的plugins\hexrays_sdk\include下找到,或者下载附件就可以找到

谢谢大佬的回复。加了defs.h后,生成解决方案报C4146错误:
错误        C4146        一元负运算符应用于无符号类型,结果仍为无符号类型。  对应代码   v5 -= -0x9E3779B9;

尝试关闭SDL检查重新生成时,报以下错误:        
错误        LNK2019        无法解析的外部符号 _WinMain@16,函数 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) 中引用了该符号        
错误        LNK1120        1 个无法解析的外部命令        

最后于 2022-10-21 19:42 被wx_一叶_733编辑 ,原因:
雪    币: 2027
活跃值: (1258)
能力值: ( LV7,RANK:101 )
在线值:
发帖
回帖
粉丝
烧板侠 2 2022-10-21 19:57
5
0
wx_一叶_733 烧板侠 defs.h是IDA定义的头文件可以在IDA安装目录的plugins\hexrays_sdk\include下找到,或者下载附件就可以找到 ...
第一个错你把v5 -= -0x9E3779B9;改成v5 += 0x9E3779B9;试试,后面两个我也不知道什么错,你有没有试过直接编译我附件的代码,也是报一样的错?mac和linux下编译是正常的,要不你换个编译器试试,试试直接用gcc命令编译?
雪    币: 199
活跃值: (1332)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_一叶_733 2022-10-21 21:46
6
0
谢谢,直接用你附件的代码也报同样的错误。直接用gcc命令编译成功,没有报错。
游客
登录 | 注册 方可回帖
返回