This is an old writeup I wrote for myself but never published for cookbook which was a binary exploitation challenge in Boston Key Party CTF 2015
There are several vulnerabilities in this program. First, there is a use-after-free vulnerability.
cur_recipe is not set to NULL after the object is freed.
This leads to unexpected system behavior when the user deletes/frees their recipe and later prints out information that references the same pointer.
In this case, if the user also adds an ingredient to the recipe, it allows the user to leak the address of a heap chunk, which can then be used the calculate the base address of the heap.
There are also several heap overflow vulnerabilities that exist in the program.
cur_recipe is calloc()’d with size
0x40C when a new recipe is created.
When a name is give to this recipe,
0x40C of user input, but it is written to offset
0x8C into the
cur_recipe buffer, allowing the user to overflow into the next heap chunk.
I also found a couple double free() vulnerabilities but those are not really pertinent to the exploit I ended up writing so I will not mention those.
Essentially, what I ended up doing was I used the UAF vulnerabilities to defeat ASLR and leak the address of the heap chunk as well as the address of a libc function in the global offset table (GOT).
I did the former to calculate the base address of the heap as well as the address of the wilderness chunk, and I did the latter to calculate the base address of libc which allowed me to calculate the addresses of all the other functions in libc, including
I was also able to leverage the first leak to determine the address of the ingredient struct which I used to craft a “fake” chunk, which I requested
0x40C bytes for.
By requesting a heap chunk of size
0x40C bytes, I was able to allocate the same chunk of memory that got freed, to this “fake” chunk because memory allocators are deterministic in the way they allocate chunks of memory: If a chunk of around the same requested size is free/available, the memory allocator will return a pointer to that free chunk.
The first DWORD in this “fake” chunk’s data is a pointer to 8 bytes ahead of where the “fake” chunk data starts. In turn, this DWORD contains a pointer to the address of
puts() in the GOT. The reason being, I wanted to leak out the actual address of
puts() later when I would print out the recipe. In between these two DWORDS I just placed the same pointer that was in the original recipe, which pointed to the number of the specified ingredient to be used. Without it, the program kept segfault-ing.
previously freed chunk that has just been overwritten w/a “fake” chunk:
After crafting this fake chunk, I simply had to go back to the
create recipe menu and print out the recipe to leak out the address of
For the second stage of this exploit I used the House of Force heap exploitation technique detailed in Malloc Maleficarum to gain a write-what-where primitive and eventually spawn a shell. This technique requries 3 memory allocations:
1) must be able to overflow into the wilderness chunk with a
malloc() to corrupt its size field
2) must be able to specify the size of this second
3) must be able to copy data into the 3rd
First, I leveraged the heap overflow vulnerability to corrupt the wilderness chunk.
I created a new recipe in order to allocate a new heap chunk next to the wilderness chunk.
I then gave the new recipe a name that was long enough to overwrite the
size field of the wilderness chunk with
When the size of the wilderness chunk is
-1, it tricks the memory allocator into never thinking that it is low on memory, allowing the user to continue to allocate more chunks on the heap, even if they extend past the allocated area for the heap arena, and into other segments of the process’s virtual address space.
Then, I went back to the main menu and gave my cookbook a name of a size I calculated would bring me to precisely 8 bytes before the address I wanted to overwrite.
In my exploit, I chose to overwrite the pointer stored in
strtoul@GOT and replace it with the address of
Strtoul was an ideal candidate to be overwritten because the program pushed user controlled input onto the stack before calling it, and the second argument passed in was 0.
For my third
malloc(), I simply gave my cookbook another new name of 5 bytes and set the name to be the calculated address of
system() in libc.
Finally, I gave my cookbook another name and set the size to be
"/bin/sh\n" to gain a shell.