Hint for Final Project

First, if you’re not familiar with House of force (HoF) attack, please download ch22.pptx and read it. Make sure you fully understand the HoF attack and know how to trigger it.

A few tips:

  1. You can hack it locally first instead of remotely, change
    p = remote(....) to p = process("./final")
  2. You can turn on the “DEBUG” mode of pwntools by adding the follow line on the top of your Python script (before p = proces(...)):

    context.log_level = "debug"

Step 1 – Modify top chunk’s size into -1

When you execute the vulnerable program, it will ask you to input name, and then log, name, and log are both arrays which will store on stack. Then, the program will start to allocate the first heap chunk p0, and the return address will be copied to array p0_a, and print the data inside the name and ask you to input content into p0.

Based on the disassembled result, the stack structure (at runtime) is like:

Fig 1. stack structure
Fig 1. stack structure

According to the figure, we can overflow name to get p0’s address. If we use GDB, we can find that the actual length between name and p0_a is 208. So, if we input 208-byte dummy characters, the program will also print out p0’s address (because there is no \0 to stop it).

Base on p0’s address, we can calculate the address for the top chunk. We can use the following equations:

top_chunk_address = p0_address + p0_length  

Fig 2. heap chunk structure
Fig 2. heap chunk structure

The result is the top chunk’s previous chunk size address. Because this a 64-bit program, so previous chunk size will occupy 8 bytes.

In order to launch house of force attack, we need to modify the top chunk’s chunk size to -1 (0xFFFFFFFFFFFFFFFF) – aka. the maximum size (otherwise if the required size is greater than top chunk size, it will call mmap instead of allocating it from the top chunk). Top chunk’s chunk size address can be calculated by the following equation:

top_chunk_size_address = top_chunk_address + 8  

Step 2 Calculate the evil size to overwrite free@got

In step 1, we find the top_chunk address and overflowed top_chunk_size to -1. Now, when the vulnerable program is asking for p1’s size, we can input an evil_size, so the top_chunk can jump to the target address. In this attack, our target address is free@got –> to hijack the free() function. Because each heap chunk has 8 + 8 = 16 = 0x10 byte header data, so the top_chunk pointer should be pointing to free@got - 0x10 after allocate the evil_size. Thus, after the third malloc, p2 is pointing to free@got.

the evil_size can be calculated based on the following code:

evil_size = addr_free_got - 0x10 - top_chunk_address - 0x10  

Note that you may need to further adjust the evil_size (+- 0x8 byte) because of the heap memory address alignment.

Step 3 Launch GOT overwrite attack using ROP chain

Now, if we are writing anything into p2, it will overwrite free@got. Note that our ultimate goal is to spawn a shell, which means we need to find a way to let this vulnerable program to execute system("/bin/sh"). Because there is no system() function in the original program, so there is no system@plt. Moreover, the ASLR is enabled, which means the system()functions' address is random. So what we can do now is to create an ROP call chain (like we did in lab 4!) and store it into log.

  1. the rop call chain calls puts() function (puts@plt) to print out real gets() address (gets@libc) which stores inside gets@got.

  2. we use the offset of each function inside libc[link] to calculate the address for system@libc.

  3. we can call gets() function again (gets@plt) to put system@libc into free@got.

  4. we need to call gets() function (gets@plt) again to write the string “/bin/sh” into a free memory address.

  5. we can call free(‘/bin/sh’) (free@plt)to execute system() function and get the shell.

The overall shellcode structure is like:

Fig 3. ROP call chain
Fig 3. ROP call chain

Note that the ROPgadget in this figure is an ROP gadget generated by the helper() function.

Fig 4. ROPgadget
Fig 4. ROPgadget

You can check this link to find it’s address, or simply put 0x40091e :)

The tricky part of this project is to figure out how to get this ROP call chain executed. Based on Fig 1, We know that log array is at the top of the stack, which is very close to the current RSP. So if we can find a ROP gadget that can either do a few pop operation or directly increases the RSP register, it will let the RSP reach the log array.

Luckily, we can find such assembly code in __libc_csu_init:

ROPSP code
ROPSP code

Starting from 0x400986, the RSP keeps increasing. And when the ret function is called, the RSP is just pointing to the beginning of the log array!

Question 1: How to get the system@libc?

Answer:

system_libc = gets_libc - gets_offset + system_offset  

The gets@libc will be printed when the ROP chain calls the puts() function. The offset is available here link

Question 2: How to find a free memory address to write “/bin/sh”

Answer:

You can try the following command: objdump -h heap_overflow
It will show you all the headers, and the corresponding memory address

headers' info
headers' info

If there is no READONLY, it means that the address range is writable. We can choose the “.data” section (0x601060) to write “/bin/sh”.