ARM逆向工程ROM转储

时间:2019-02-07 14:34:37

标签: c pointers arm reverse rom

我有一个老式的嵌入式系统,带有ARM cpu内核,我转储了ROM,用binwalk解压缩,并加载到IDA pro上。 我发现在某个例行程序中,BX不带LR,而是带有通用寄存器(R2,R3等),在伪代码中,它就像这样的“ memory [0xCC1232](a3,v4)”一样。我以为这就像ARM编译器构建C指针函数一样。可以吗 无论如何,我的主要问题是,如何仅通过ROM转储找到该子例程(memoryx ..)?因为该地址是ram地址,所以在没有ram dump的情况下,我找不到该子例程的引用?将值写入该特定地址,但这几乎是不可能的 谢谢

2 个答案:

答案 0 :(得分:1)

取决于编译器设置和不带lr的bx目标是非常普遍的,如果不需要,因为lr的较早的核心pop(通常为ldmia)不适用于在arm和thumb模式之间切换,因此编译器将生成pop到r3,然后是bx r3。

extern unsigned int more_fun ( void );
unsigned int fun ( void )
{
    return(more_fun()+1);
}

00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   f7ff fffe   bl  0 <more_fun>
   6:   3001        adds    r0, #1
   8:   bc10        pop {r4}
   a:   bc02        pop {r1}
   c:   4708        bx  r1
   e:   46c0        nop         ; (mov r8, r8)

vs

00000000 <fun>:
   0:   b508        push    {r3, lr}
   2:   f7ff fffe   bl  0 <more_fun>
   6:   3001        adds    r0, #1
   8:   bd08        pop {r3, pc}
   a:   bf00        nop

r4 vs r3作为虚拟寄存器在这里并不重要,有趣的是编译器可以这样做,但是它们只是用来使堆栈对齐,而不是保留寄存器。

这当然是拇指模式,手臂模式

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   ebfffffe    bl  0 <more_fun>
   8:   e8bd4010    pop {r4, lr}
   c:   e2800001    add r0, r0, #1
  10:   e12fff1e    bx  lr

您可能希望看到lr与bx lr一起使用

或更新版本,但gcc至少默认为拇指模式,您必须强制它生成像这样的手臂代码

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   ebfffffe    bl  0 <more_fun>
   8:   e2800001    add r0, r0, #1
   c:   e8bd8010    pop {r4, pc}

就找到ram中的内容而言,您需要做更多的工作,这可能是不可能的。如果这是一个嵌入式系统,并且所有内容都在非易失性存储器(闪存/ ROM /等)中,则在某些情况下(如果代码在ram中),则在分支之前将其复制或解压缩或以其他方式放入ram中。另一方面,您可能已经分解了一些代码,可以在ram中调用代码,但是ram中的代码可能来自下载,基本上是一些最终不在板上/芯片上的外部源。就像具有代码的引导程序,该引导程序可以通过uart下载程序然后将其分支到该程序一样,这并不意味着该代码可以正常使用,并且当然意味着您无法预测或知道该代码将是什么,更不用说找到并反汇编它了。

如果我下载到0x20000000并可以手动使用非lb的bx来启动此类代码,并且以某种方式知道这不是拇指代码,那么我需要以某种方式将地址或1或br改为or,有时您需要注册,有时不需要。

您还将看到链接器使用bx修补远处的东西或更改模式:

unsigned int more_fun ( void )
{
    return(3);
}

链接器为您添加蹦床:

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   eb000003    bl  18 <__more_fun_from_arm>
   8:   e8bd4010    pop {r4, lr}
   c:   e2800001    add r0, r0, #1
  10:   e12fff1e    bx  lr

00000014 <more_fun>:
  14:   2003        movs    r0, #3
  16:   4770        bx  lr

00000018 <__more_fun_from_arm>:
  18:   e59fc000    ldr r12, [pc]   ; 20 <__more_fun_from_arm+0x8>
  1c:   e12fff1c    bx  r12
  20:   00000015    andeq   r0, r0, r5, lsl r0
  24:   00000000    andeq   r0, r0, r0

,在这种情况下使用r12。

答案 1 :(得分:0)

通常情况下,不拆卸和分析代码就无法确定实际的跳转地址。但是,可能有几种技巧。

  1. 通常在非常早的启动时就将ROM的一部分复制到RAM。尝试在初始化中找到该复制例程。如果幸运的话,您将能够获得感兴趣的RAM位置的初始内容。如果非常幸运的话,该位置不会在运行时中重写,而是实际数据。 (请参见下文)
  2. 尝试先反汇编部分代码。在大多数情况下,地址是从ROM中的某个表中获取的,然后存储在本地变量中,并且该表的地址在那里。
  3. 在大多数情况下,子例程代码体始终位于ROM的开头(从入口点开始并以BX LR结尾)。您可以通过扫描ROM来查找所有或大多数子例程入口点,因为怀疑下一个子例程的入口点位于上一个“ BX LR”的旁边。要确定是否间接调用了特定的子例程,请尝试搜索其子地址是否存在于ROM中,或者是否装有“ MOVW” +“ MOVT”对。这使您知道可以在哪里使用它。
  4. 在某些情况下,子例程地址作为参数传递给子例程调用。因此,请尝试搜索是否有任何调用者在代码中引用某个地址。
  5. 可能存在过程表,看起来像是长行单词,指向ROM代码区(可能与null交错)。尝试找到它们,并确定可以使用哪种情况。

从GNAT的运行时获取的示例RAM初始化代码:

        movw    r0,#:lower16:__data_start ; __data_start points to RAM
        movt    r0,#:upper16:__data_start ; (often it is start of RAM)
        movw    r1,#:lower16:__data_words
        movw    r2,#:lower16:__data_load  ; __data_load points to ROM
        movt    r2,#:upper16:__data_load  ; right after code and readonly data
        cbz     r1,1f
0:      ldr     r4,[r2],#4
        str     r4,[r0],#4
        subs    r1,r1,#1
        bne     0b
1: