Though it’s the second level, the approach now is completely different.
Binary sections aren’t marked rwx anymore, but this time we’re allowed to create and edit bigger chunks. This looks way more like your proper heap challenge.
So, let’s check the differences in the code:
Nothing special here, add_note will check, if there’s a free entry in the NOTE_TABLE (max 11 entries), allocate a chunk and put it there, also increasing NOTE_COUNT.
delete_note will this time do a proper boundary check, but it fails to check, if we specified an existing note. Thus, if we give it an index of an unitialized note, it will call free(0), which will just do nothing, but decreases NOTE_COUNT afterwards, which enables us to decrease NOTE_COUNT by an arbitrary value, by just calling delete_note on a freed note multiple times. Might remember this for later ;)
No real bugs here, edit_note checks the index and also if we specified a note that actually contains content.
So, how to go on with this? Well, for this, we should take a closer look at the memory handling for the NOTE_TABLE and NOTE_COUNT.
After adding some notes, it will fill up NOTE_TABLE with the addresses of our note chunks and increase NOTE_COUNT
Uhm, the binary allows us to allocate and edit 10 notes, but the address for the 10th chunk is also the NOTE_COUNT itself. We should surely be able to do some mischief with that :)
Remember, that we are able to decrease NOTE_COUNT by arbitrary values by freeing an empty chunk, so we can use that to manipulate the address of the 10th chunk.
Let’s set up some chunks to start with:
The NOTE_TABLE will now look like this
Since the address at 0x602130 is != 0x0, add_note won’t allow us to create another note and complains with FULL~~.
Well, we can change this with abusing delete_note
This will decrease NOTE_COUNT until it’s -1. We need to be able to create another chunk, which will be stored in Note 1 before being able to create another chunk in Note 10, which will increase NOTE_COUNT by 1, resulting in a 0x0 in 0x602130.
Now we create two new chunks
We now have a note in the NOTE_COUNT address (see, how the address also got increased by 1 after adding it…).
And like before, we’re able to move the address around in the heap by multiple calls to delete_note and since it’s a valid note, we can use edit_note, giving us an arbitrary write on the heap.
Though, there’s nothing of interest inside the heap, we would like to have a note pointer pointing somewhere useful, like the bss…
Since no PIE is enabled, we know the address of NOTETABLE, which conveniently contains pointers to our chunks, so we can do an unlink attack to write the address of NOTETABLE into itself.
Though, some preparations for this needs to be taken.
Create a fake chunk on the heap containing FD/BK pointers to NOTETABLE (which needs to point back to our fake chunk to get around the libc checks)
Overwrite prev_size and size of a followup chunk, so that upon freeing the followup, free will think the previous chunk is also free
prev_size should be forged, so that it points to our fake chunk
Free the manipulated chunk, which will then unlink our fake chunk
With the overwritten note entry, we can now modify the NOTETABLE itself resulting in an arbitrary write
For this, we’ll change the note initialization a bit
We prepare our fake chunk in chunk 8 (size 0x120) with FD/BK pointing to the NOTETABLE entry for chunk 8 (which happens to point to the data portion of note 8, which is our fake chunk).
In our final NOTE_COUNT chunk we store a valid prev_size, which points to the fake chunk, we created in chunk 8.
We still need to overwrite prev_size for Note 9 and toggle the prev_inuse bit of Note 9, so free will think that the previous chunk is also freed.
Now is the time, for NOTE_COUNT to shine :). We’ll be freeing so many notes, decreasing NOTE_COUNT (and the address of Note 10) until Note 10 points to 0x603510.
Now we can overwrite prev_size and size of Note 9 via Note 10 and trigger unlink by deleting Note 9
After the edit, the heap will look like this
We now have everything prepared for unlink doing its magic :)
Deleting Note 9 will now letting free think the previous chunk is also free (0x603510 - 0x80 => 0x603490 (Fake chunk)) and unlink it.
EDIT (Adding a little bit more explanation on the unlink portion)
When note 9 gets freed, free will think the previous chunk is also free, since we set the prev_inuse bit to 0x0. So it will try to consolidate backwards and unlink the previous chunk from its binlist. free does this with the unlink macro
So, let’s check the state of FD/BK before unlink is triggered:
unlink will now execute FD->bk = BK with FD = 0x602108
It interprets 0x602108 as a chunk, and thus overwrites its BK (0x602120) with the BK from our fake chunk (0x602110), so it would look like this
Now, unlink will execute BK->fd = FD with BK = 0x602110 (from our fake chunk in the heap).
So, now unlink wants to set the FD value for this chunk, which also happens to be 0x602120, thus overwriting it with the FD value from our fake chunk (0x602108), resulting in
/EDIT
Note 8 now contains 0x602108 pointing back in our NOTETABLE, so we can now use Note 8 to write arbitrary addresses into the NOTETABLE and then edit those addresses to overwrite arbitrary addresses.
With this, it’s only a matter of overwriting got entries, leaking libc and call system("/bin/sh"), so let’s wrap this up
atoi is now printf, so any input in the menu will be executed by printf instead, so we can send some format strings as input to leak libc.
Knowing libc, we can now overwrite atoi again, but this time with system.
We just have to consider that for selecting a menu, atoi is no longer called, but printf (which returns the count of printed characters). So for selecting edit_note we have to send for example .. instead of 2.
Same game, having atoi now replaced with system, we just have to send another /bin/sh to trigger system("/bin/sh")