Issue
I'm hoping someone can help me determine why this binary won't execute.
It is a closed-source, stripped ARM binary. That said, it is freely downloadable on the internet so there is a link to it at the bottom of this post.
The target is an ARM binary pulled from a firmware image. I have set up an ARM VM, but have also tried running the binary on a Pi with the same result.
Here is what I'm seeing:
root@debian-armel:/tmp/squashfs-root/usr/bin# ./my_arm_bin
Illegal instruction
That "Illegal instruction" error is not super helpful... so I dug in a little deeper.
My VM
I don't think the problem is my VM. It is a pretty standard ARM VM setup. From: https://people.debian.org/~aurel32/qemu/armel/
Using debian_squeeze_armel_standard.qcow2
, initrd.img-2.6.32-5-versatile
, and vmlinuz-2.6.32-5-versatile
. It is launched with QEMU with a few ports forwarded (ssh,http,31337 for gdb stuff). I am able to execute other ARM binaries on the system without issue, including other binaries pulled from the same firmware image.
Also, as mentioned before I've tried dropping the binary onto a Pi with no luck. I tried both as root on the pi, as well as in a chroot'ed environment with the rootfs of the extracted firmware image, same result: Illegal Instruction
.
r2 Info
rabin2 -I my_arm_bin
:
Warning: Cannot initialize dynamic strings
arch arm
binsz 44831825
bintype elf
bits 32
canary false
class ELF32
crypto false
endian little
havecode true
lang c
linenum false
lsyms false
machine ARM
maxopsz 16
minopsz 1
nx false
os linux
pcalign 0
pic false
relocs false
rpath NONE
static true
stripped true
subsys linux
va true
Additional Debugging (gdb w/ gef on Pi)
So, time to attach a debugger and see exactly what instruction is actually throwing that error. This was done on a Pi.
After starting up and breaking on entry, using ni
to single step, I see:
gef> x/20i $pc
=> 0x796a0: mov r11, #0
0x796a4: mov lr, #0
0x796a8: pop {r1} ; (ldr r1, [sp], #4)
0x796ac: mov r2, sp
0x796b0: push {r2} ; (str r2, [sp, #-4]!)
0x796b4: push {r0} ; (str r0, [sp, #-4]!)
0x796b8: ldr r12, [pc, #16] ; 0x796d0
0x796bc: push {r12} ; (str r12, [sp, #-4]!)
0x796c0: ldr r0, [pc, #12] ; 0x796d4
0x796c4: ldr r3, [pc, #12] ; 0x796d8
0x796c8: bl 0x4021a0
0x796cc: bl 0x401fa0
0x796d0: andeq r2, r12, #200, 2 ; 0x32
0x796d4: andeq r10, r1, r12, lsl #11
0x796d8: andeq r2, r12, #40, 2
0x796dc: ldr r3, [pc, #20] ; 0x796f8
0x796e0: ldr r2, [pc, #20] ; 0x796fc
0x796e4: add r3, pc, r3
0x796e8: ldr r2, [r3, r2]
0x796ec: cmp r2, #0
gef>
It all looks like valid ARM instructions.
In case it's relevant - All ldr instructions (0x796b8, 0x796c0, 0x796c4) in gdb are giving this message when executed: Cannot access memory at address 0x0
. Some mov instructions throw this too.
At 0x796c8: bl 0x4021a0
:
-> 0x796c8 bl 0x4021a0
\-> 0x4021a0 ldr pc, [pc, #-4] ; 0x4021a4
And eventually we get here:
gef> x/20i $pc
=> 0x20c1b30: push {r4, r5, r6, r7, lr}
0x20c1b34: sub sp, sp, #300 ; 0x12c
0x20c1b38: movw r12, #0
0x20c1b3c: mov r5, r3 --> Here is our illegal instruction
0x20c1b40: movt r12, #0
0x20c1b44: str r1, [sp, #4]
0x20c1b48: movw r1, #65336 ; 0xff38
0x20c1b4c: cmp r12, #0
0x20c1b50: ldr r3, [sp, #4]
0x20c1b54: str r2, [sp, #8]
0x20c1b58: ldrne r12, [r12]
0x20c1b5c: add r2, r3, #1
0x20c1b60: str r0, [sp, #12]
0x20c1b64: movw r3, #56376 ; 0xdc38
0x20c1b68: ldr r7, [sp, #8]
0x20c1b6c: movw r0, #3092 ; 0xc14
0x20c1b70: ldr lr, [sp, #328] ; 0x148
0x20c1b74: clzne r12, r12
0x20c1b78: movt r0, #685 ; 0x2ad
0x20c1b7c: movt r1, #827 ; 0x33b
And of course a copy of the binary can be found here: https://mega.nz/#!CKxBQKaI!T__d9pjpOn_rPtfvPNkkPsFWHTjg7u-vDt5AK6610ug
So are the registers just not initialized to the right values? How can that be possible?
To paraphrase from Archer: What am I not getting?... I think the core concept.
I am probably missing a critical idea here. I'm hoping someone can help fill in the blank(s).
UPDATE 1:
At @0xC0000022L suggestion I looked into making sure the ARM revision of my VM/Pi matches that of the binary. As far as I can tell they do. Just comparing a binary from the VM to my my ARM binary I'm trying to get running their ABIs match (32-bit ARMv5):
$ file my_arm_bin
my_arm_bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, stripped
$ file /usr/bin/id
/usr/bin/id: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, stripped
UPDATE 2:
At @perror suggestion I tried forcing the binary to execute in thumb mode. For context here is the disassembly when running in "arm" (non-thumb) mode:
gef> x/10i $pc
=> 0x20c1b38: movw r12, #0
0x20c1b3c: mov r5, r3
0x20c1b40: movt r12, #0
0x20c1b44: str r1, [sp, #4]
0x20c1b48: movw r1, #65336 ; 0xff38
0x20c1b4c: cmp r12, #0
0x20c1b50: ldr r3, [sp, #4]
0x20c1b54: str r2, [sp, #8]
0x20c1b58: ldrne r12, [r12]
0x20c1b5c: add r2, r3, #1
Forcing thumb mode, I now see:
gef> set arm force-mode thumb
gef> x/10i $pc
=> 0x20c1b38: stmia r0!, {}
0x20c1b3a: b.n 0x20c213e
0x20c1b3c: str r3, [r0, r0]
0x20c1b3e: b.n 0x20c1e82
0x20c1b40: stmia r0!, {}
0x20c1b42: b.n 0x20c21c6
0x20c1b44: asrs r4, r0, #32
0x20c1b46: b.n 0x20c1664
0x20c1b48: subs r0, r7, #4
0x20c1b4a: b.n 0x20c216c
Forcing thumb mode prior to execution, the program exits immediately with the following error:
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6ac
Starting the executable up, breaking and switching modes exits with an Illegal instruction error.
gdb
. But, what if the CPU was executing in thumb mode ? To check, you might also simply execute the program throughsi
(stepi
) commands until crash. It might give you a more insightful error message.ni
to step through instructions. Thank you @perror and @0xC0000022L - Your insight is much appreciated. I'm going to dig into thumb mode and ARM revision later today and will post and update. I was usingni
to skip function calls and keep it simple, butsi
may give me some more context.