So, we can allocate a chunk and specify content for it. If a chunk was allocated, we can free it again.
Modify will let us update some “user information” (not modifying the allocated chunk).
Let’s take a short glance at the main function:
We can only have one allocated chunk at a time (ALLOCATED will be set/reset on malloc/free)
The size for the allocated chunk must be between 512 and 4095
On the first allocation of a chunk, a chunk for the “user information” will be allocated (USER_INFO)
On Free the pointer to the freed chunk won’t be reset, so it can be used for a double-free
By entering 201527 we can access the secret menu and add a comment (though comment isn’t needed at all, the read on SECRET_CODE will be useful later on)
With modify we can update the user information chunk
It’s eye-catching, that in the modify function most times read is used to get the user input, but for reading the name it’s fgets.
This has an important side effect, as fgets allocates a chunk on the heap (size 0x1000) for reading user input.
So, after allocating a chunk, our variables in the bss will look like this:
If we were able to overwrite the pointer to USER_INFO, we could use modify to get an arbitrary write.
Thus, the plan is to force the binary to allocate a chunk overlapping this pointer. To do so, we’ll first let this area look like a valid chunk by misusing the secret menu, creating a valid size field:
Since we passed a wrong SECRET_INPUT, it won’t ask us for leaving a comment. But we weren’t interested in writing a comment at all :)
After this, the bss area will look like this:
The allocated chunks on the heap:
Now, we’ll abuse the fact, that fgets allocates a chunk on the heap for its user input (with size 0x1000).
Since we allocated our chunk with a size around that, when we free it and then call modify, the chunk for fgets will be allocated exactly on top of our previously allocated chunk. Since the original chunk pointer doesn’t get resetted on Free, we’ll have a pointer to the freed chunk, which then also is the chunk for fgets.
Let’s split this up:
After freeing up the allocated chunk:
The modify function will now allocate a chunk with size 0x1000 for fgets. Since malloc tries to reuse the top chunk in the unsorted bin list, it will discover our just freed chunk (0x603000) and serve it to the fgets allocation:
So, fgets took the chunk from the unsorted bin list and stored the user input at the same position, the first chunk was allocated at. Since we chose to update the name, it was then copied to the USER_INFO chunk.
After freeing the first chunk again (remember, we still have the dangling pointer from our first allocation, which points also to the fgets chunk), this chunk will be freed and moved to the unsorted bin list again.
Now, we’re at the starting point again, so what did we win by freeing the chunk again?
The trick here is, that fgets now has an internal pointer to that chunk, since it thinks, it already allocated it. Thus, everytime fgets gets called from now on, it will store its input at this address (overwriting our first chunk).
So, we can now allocate this chunk again, writing to it or we can use fgets in modify to write to this chunk, regardless if the chunk is allocated or not, resulting in a use-after-free.
We’ll use this now, to overwrite the BK pointer in the freed chunk. On the next allocation, malloc will try to take this chunk and remove it from the unsorted bin list. To do so, it has to link the previous and follow-up chunk:
When removing the chunk from the unsorted bin list, malloc doesn’t check, if the BK pointer points to a valid chunk, so we can specify any address here. Malloc will then overwrite address+0x10 (FD pointer) with a pointer to the unsorted bin list.
Since the FD pointer is chunk offset + 0x10, faking 0x6020b0 as the BK pointer in the chunk will result in overwriting 0x6020c0 with the pointer to the unsorted bin list. 0x6020c0 also happens to be our pointer to the USER_INFO struct :)
Though, we now f*cked up the unsorted bin list and trying to free the chunk, we just allocated, would result in a crash, since free tries to check the sanity of the unsorted bin list:
Ok, this will absolutely fail. It first gets a pointer to the unsorted chunks and stores it in bck (0x7ffff7dd3b58). It then gets the FD pointer from that address (bck+0x10 = 0x7ffff7dd3b68 ==> 0x603000). So fwd will be 0x603000 and then it tries to get the BK pointer from there (fwd + 0x18 = 0x60200a).
So, the comparison will be 0x7ffff7dd3b58 == 0x60200a, which obviously fails and thus crashes the application.
But well, we have an editable object pointing straight into the unsorted bin list… The USER_INFO object (which was previously overwritten with a pointer to main_arena). This should help us fixing this mess :)
This will write the pointers to our BSS fake chunk into the heap, and then copy it to the address USER_INFO points to (which happens to be main_arena).
So, let’s take another look at this after the modification:
So… bck will still be 0x7ffff7dd3b58, since it’s the pointer to unsorted bins. But bck->fd now changed to 0x6020a8, which points to our fake chunk in the bss. When it now tries to get the FD pointer, it will be (bck+0x10 = 0x7ffff7dd3b68 ==> 0x6020a8). So fwdis now 0x6020a8and when free dereferences it and reads the BK pointer, it will result in fwd+0x18 = 0x6020a8 + 0x18 = 0x6020c0 ==> 0x00007ffff7dd3b58.
So the comparison will now be 0x00007ffff7dd3b58 == 0x00007ffff7dd3b58 resulting in free happily freeing our chunk again.
We now have successfully freed the chunk again, which resets ALLOCATED, allowing us to allocate new chunks again.
Though our initial chunk got on top of unsorted bin list, we also managed to write the address from our bss fake chunk into the unsorted bin list. We’re nearly at the finish line :)
Remember, that we abused the secret menu to write a fake chunk size? This will pay off now…
When we now allocate a chunk with a size around 0x210, malloc will try to get a matching chunk from the unsorted bin list, discovering our fake chunk in the bss with a matching chunk size, just waiting there to get allocated :)
Finally a chunk overlapping the USER_INFO pointer and we have absolute control over the content, which will overwrite it.
So, we’ll be using it to overwrite it with the adress of ATOI got (small padding there).
Now, we should be able to overwrite atoi with the help of the modify function. Though, we have an arbitrary write now, what can we do with it, since ASLR is active and we didn’t found a leak by now to get the libc address?
Well, let’s create a format string vulnerability by overwriting atoi got with printf plt. Thus, everytime atoi gets called, the binary will call printf instead (with the arguments, that should have been passed to atoi).
The binary uses readInteger to determine which menu option we selected (which then uses atoi to convert our input to a number):
This means we have control over the arguments, which will be passed to printf also, so let’s make good use of it:
So, we’ve got a libc address and calculated the address of system.
If we now overwrite atoi got again, but this time with the address of system, we should be able to trigger a shell.
We just have to remember, that we already changed atoi, so the menu handler won’t be able anymore to convert our input to a number. Entering 3 won’t result in calling modify, but just crash the application instead.
Well, but printf returns the number of characters, which were printed out. Soooo, we just have to print out 3 characters, and the menu handler will interpret this, as if we would have selected 3.
This will overwrite atoi got with the address to system. Now everything, we enter at the menu, will call system and pass our input as an argument.