识别 ARM 中开关表的目标

逆向工程 拆卸 手臂 拇指2
2021-06-22 12:35:25

我正在为 ARM Thumb2(剥离)二进制文件开发反汇编程序。我已经可以恢复使用直接跳转的基本块 (BB) 的 CFG。我的下一个目标是处理间接跳转。我目前正在确定 switch 语句的目标,我将根据以下示例进行讨论:

int main(int argc, char** argv){
  int i = 0;
  switch (argc) {
    case 0: i++; break;
    case 1: i+=2; break;
    case 3: i+=3; break;
    case 6: i+=6; break;
    case 5: i+=8; break;
    case 7: i+=10; break;
    default: i+=87; break;
  }
  return i;
}

使用以下方法编译上述示例时:

arm-linux-gnueabihf-gcc-4.8 -o3 -mthumb main.c -o main.out

反汇编main使用objdump显示开关表数据字开始于0x83d8如下所示:

83bc:       b480            push    {r7}
83be:       b085            sub     sp, #20
83c0:       af00            add     r7, sp, #0
83c2:       6078            str     r0, [r7, #4]
83c4:       6039            str     r1, [r7, #0]
83c6:       2300            movs    r3, #0
83c8:       60fb            str     r3, [r7, #12]
83ca:       687b            ldr     r3, [r7, #4]
83cc:       2b07            cmp     r3, #7
83ce:       d82b            bhi.n   8428 <main+0x6c>
83d0:       a201            add     r2, pc, #4      ; (adr r2, 83d8 <main+0x1c>)
83d2:       f852 f023       ldr.w   pc, [r2, r3, lsl #2]
83d6:       bf00            nop
83d8:       000083f9        .word   0x000083f9
83dc:       00008401        .word   0x00008401
83e0:       00008429        .word   0x00008429
83e4:       00008409        .word   0x00008409
83e8:       00008429        .word   0x00008429
83ec:       00008419        .word   0x00008419
83f0:       00008411        .word   0x00008411
83f4:       00008421        .word   0x00008421
83f8:       68fb            ldr     r3, [r7, #12]
83fa:       3301            adds    r3, #1
83fc:       60fb            str     r3, [r7, #12]
83fe:       e017            b.n     8430 <main+0x74>
8400:       68fb            ldr     r3, [r7, #12]
8402:       3302            adds    r3, #2
8404:       60fb            str     r3, [r7, #12]
8406:       e013            b.n     8430 <main+0x74>
8408:       68fb            ldr     r3, [r7, #12]
840a:       3303            adds    r3, #3
840c:       60fb            str     r3, [r7, #12]
840e:       e00f            b.n     8430 <main+0x74>
8410:       68fb            ldr     r3, [r7, #12]
8412:       3306            adds    r3, #6
8414:       60fb            str     r3, [r7, #12]
8416:       e00b            b.n     8430 <main+0x74>
8418:       68fb            ldr     r3, [r7, #12]
841a:       3308            adds    r3, #8
841c:       60fb            str     r3, [r7, #12]
841e:       e007            b.n     8430 <main+0x74>
8420:       68fb            ldr     r3, [r7, #12]
8422:       330a            adds    r3, #10
8424:       60fb            str     r3, [r7, #12]
8426:       e003            b.n     8430 <main+0x74>
8428:       68fb            ldr     r3, [r7, #12]
842a:       3357            adds    r3, #87 ; 0x57
842c:       60fb            str     r3, [r7, #12]
842e:       bf00            nop
8430:       68fb            ldr     r3, [r7, #12]
8432:       4618            mov     r0, r3
8434:       3714            adds    r7, #20
8436:       46bd            mov     sp, r7
8438:       f85d 7b04       ldr.w   r7, [sp], #4
843c:       4770            bx      lr
843e:       bf00            nop

观察:

  • 数据字存储开关表的绝对目标地址而不是偏移量。
  • 间接分支是使用ldr.w pc, [base, index, lsl #2] 其中 base(此处为 r2)存储数据字开头的地址和索引(此处为 r3)用于计算偏移量来实现的。

问题:

  1. 上述观察可以概括吗?换句话说,我可以假设这是(大多数)ARM 编译器实现 switch 语句的(事实上的)标准方式吗?
  2. 为什么存储在数据字中的地址是奇数?我在这里看不到 ARM/Thumb 之间的任何模式切换。例如,可以找到默认情况,0x8428但相应的地址存储为0x00008429
1个回答

不,您不能假设这是大多数 ARM 编译器实现 switch 语句的方式。例如,这是同一代码上的 gcc 5.2.1:

cosimo:~ moyix$ arm-none-eabi-gcc-5.2.1 -o3 -mthumb -c x.c -o x.o
cosimo:~ moyix$ arm-none-eabi-objdump -d x.o

x.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <main>:
   0:   b580        push    {r7, lr}
   2:   b084        sub sp, #16
   4:   af00        add r7, sp, #0
   6:   6078        str r0, [r7, #4]
   8:   6039        str r1, [r7, #0]
   a:   2300        movs    r3, #0
   c:   60fb        str r3, [r7, #12]
   e:   687b        ldr r3, [r7, #4]
  10:   2b07        cmp r3, #7
  12:   d81d        bhi.n   50 <main+0x50>
  14:   687b        ldr r3, [r7, #4]
  16:   009a        lsls    r2, r3, #2
  18:   4b13        ldr r3, [pc, #76]   ; (68 <main+0x68>)
  1a:   18d3        adds    r3, r2, r3
  1c:   681b        ldr r3, [r3, #0]
  1e:   469f        mov pc, r3
  20:   68fb        ldr r3, [r7, #12]
  22:   3301        adds    r3, #1
  24:   60fb        str r3, [r7, #12]
  26:   e017        b.n 58 <main+0x58>
  28:   68fb        ldr r3, [r7, #12]
  2a:   3302        adds    r3, #2
  2c:   60fb        str r3, [r7, #12]
  2e:   e013        b.n 58 <main+0x58>
  30:   68fb        ldr r3, [r7, #12]
  32:   3303        adds    r3, #3
  34:   60fb        str r3, [r7, #12]
  36:   e00f        b.n 58 <main+0x58>
  38:   68fb        ldr r3, [r7, #12]
  3a:   3306        adds    r3, #6
  3c:   60fb        str r3, [r7, #12]
  3e:   e00b        b.n 58 <main+0x58>
  40:   68fb        ldr r3, [r7, #12]
  42:   3308        adds    r3, #8
  44:   60fb        str r3, [r7, #12]
  46:   e007        b.n 58 <main+0x58>
  48:   68fb        ldr r3, [r7, #12]
  4a:   330a        adds    r3, #10
  4c:   60fb        str r3, [r7, #12]
  4e:   e003        b.n 58 <main+0x58>
  50:   68fb        ldr r3, [r7, #12]
  52:   3357        adds    r3, #87 ; 0x57
  54:   60fb        str r3, [r7, #12]
  56:   46c0        nop         ; (mov r8, r8)
  58:   68fb        ldr r3, [r7, #12]
  5a:   0018        movs    r0, r3
  5c:   46bd        mov sp, r7
  5e:   b004        add sp, #16
  60:   bc80        pop {r7}
  62:   bc02        pop {r1}
  64:   4708        bx  r1
  66:   46c0        nop         ; (mov r8, r8)
  68:   00000000    .word   0x00000000

还有更复杂的方案是可能的,包括对开关值进行二分搜索以找到正确的情况。通常,您需要进行更复杂的分析才能恢复 switch 语句。关于这个主题存在一些学术工作,例如:

http://www.cs.tufts.edu/~nr/cs257/archive/cristina-cifuentes/switch-proof.pdf

至于你的第二个问题,跳转使用ldr pc必须明确指定模式。由于您使用 编译了代码-mthumb,因此您处于拇指模式,并且要保持拇指模式,这些地址需要将位 0 ​​设置为 1(请参阅本页的注释 [a] )。