I'm working on a disassembler for ARM Thumb2 (stripped) binaries. I can already recover the CFG of Basic Blocks (BB) that use direct jumps. My next goal is to handle indirect jumps. I'm currently working on identifying targets of switch statements which I'll discuss based on the following example:
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;
}
When compiling the above example using:
arm-linux-gnueabihf-gcc-4.8 -o3 -mthumb main.c -o main.out
Disassembling main
using objdump
show that the switch table data words start at 0x83d8
as can be seen here:
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
Observations:
- Data words store the absolute target addresses of the switch table not offsets.
- The indirect branch is implemented using
ldr.w pc, [base, index, lsl #2]
where base (here r2) stores the address of the beginning of data words and index (here r3) is used to calculate the offset.
Questions:
- Can the aforementioned observations be generalized? in other words, can I assume the this is the (de-facto) standard way for (most) ARM compilers to implement switch statements?
- Why are the addresses stored in data words odd? I can't see any mode switching between ARM/Thumb here. For example, the default case can be found at
0x8428
but the corresponding address is stored as0x00008429
.