Points: 125 Solves: 18 Category: Exploitation Description: Can you exploit the following binary?
Another House of Force exploitation challenge. This one is quite involved, so I decided to do a write-up on it. The binary is PIE enabled for ARM64 architecture.
The initialization of main includes
setvbuf to disable buffering and a call to
A function pointer is placed in the malloced buffer. Depending on if env variable
DEBUG is set, this helping function will print some debugging information. If the
DEBUG env variable doesnt exist this function will do nothing.
Note: The minimum size of malloced buffer on 64bit system is 0x20 bytes. 0x10 bytes for the metadata and 0x10 bytes for the actual buffer.
Next we have
print_menu(). We also have a call to
malloc(0x800) that gets executed only once. A ptr to this buffer is placed in
root variable in the .bss section. In this buffer we will place our input for all functions. After that there’s a call to
read(stdin, root, 0x800).
The main menu prints:
The IDA graph for this function is not as descriptive as the one for
main() so I will just explain the process for each case/switch option.
So, input goes in the 0x800 bytes buffer on the heap and is processed by
3. Quit calls
2. Print entries prints the content of the entries structure used to hold the ptrs and data of our buffers. It uses the following format string for each member.
The structure used to hold the data looks something like:
This structure gets allocated on the heap by
malloc(0x100) everytime we chose option
1. Add an entry from the main menu.
So far the heap will look like this:
Add entry menu
1. Set namedoes
strcpy(entries->name, root);. With the root buffer 0x800 bytes and
entries->nameonly 0x88 bytes, we have an obvious overflow here.
2. Set smalldoes
strdup(root);. As we know strdup uses malloc and strcpy internally.
3. Set largedoes
malloc(strtol(root));after it asks us for size with “What size should large be?”. Strangely it doesn’t do anything else and there’s no buffer allocated for it.
4. cd ..returns is to the main menu.
As we can see these functions satisfy our requirements to perform House of Force and exploit the heap. However, because the binary is PIE enabled the distance between the different segments like the heap and the .data/.bss is randomized which I consider the main constrain.
Stage 1 - Leaking the heap address
We can easily get the address of the
entries->large because the format string
%p is used. This will provide us with an address on the heap, calculating relative offsets from it we can get the location on all the buffers located on the heap. Because of PIE we can’t calculate the distance to the other sections.
Stage 2 - Leaking the heap stored
To leak the stored function pointer at the beginning of the heap, I needed to overwrite any of the pointers stored in the
entries structure. Because they are placed before the
entries->name buffer we need to use the House of Force method. So by overwriting the wilderness’s metadata by using option number
1. Set name we can set the size of the wilderness to
(or 0xFFFFFFFFFFFFFFFF). Then we need to allocate a huge chunk via option
3. Set large which will do something like
malloc( -0x960 ); which will wrap around the x64 bit address space and end up somewhere on the heap in the root buffer. Once we are there we can go back to the main menu and choose
Set an entry again to cause another entries structure to be allocated right on top of the
root buffer. Both structures are linked and we can control the pointers on the second entries structure.
Since we control everything in the root buffer not only we can control the pointers to leak the
helper ptr but also we can overflow again the metadata of the new wilderness :). After we allocated a negative buffer, we wrapped around 2**64-1 address space the size of the wilderness will be rather small.
Stage 3 - Leak an entry from the GOT
Now that we have a pointer to the .data section, we can calculate the distance to the GOT and leak an entry from there using the same method as above. Since everything we need to manipulate is still in the root buffer it will be rather easy.
Once we are done with that we can calculate the distance to
So far the heap looks like this:
Stage 4 - Overflowing the helper function with
helper_func_ptr is still placed before whatever we can manipulate. So to overflow it I’m going to wrap around the whole address space again and end up right in the beginning of the heap. But before doing that I need to set the argument for
The helper function is called with
blr X1 instruction and the address of the first entries structure that currently holds
entries->large. So to reach that location I will just allocate a large enough (positive value this time :) buffer using
Set large and store ‘/bin/sh’ there via
Set small (remember it’s using strdup() so we gotta account for metadata overhead).
After that we can perform another negative allocation to wrap around the 64bit address space and end up right at the beginning of the heap. Once we are there we can just use
Set small to overwrite the helper function with the address of system and we are done.
And the flag