UMD CTF
Last updated
Last updated
I'm quite busy during the competition and so does my teammates, so we didn't put too much time into this, but I did take a glance on some of the challenges and they pique my interest so I tried to solve them myself after the ctf had over to learn one or more things :D
Challenge | Category | Points | Solves |
---|---|---|---|
Help! Baron Harkonnen is forcing me to make a statue in the light of the scorching black sun.
Lend me a helping hand, will you?
nc challs.umdctf.io 31447
we're given attachments as follows:
running the binary, we're greeted with the following options:
decompiling the binary in ghidra, it is quite small and only have a main()
function
note:
we can only keep track of chunk at a time of any sizes
data given to the chunk is limited to 8 bytes.
there's an UAF, since the pointer is not deleted upon free
we can trigger malloc(0x4f8)
without interacting with the chunk, this means we are unable to free or edit the chunk returned by said chunk
to exploit this, I'm going to delve into an area I'm not familiar with and that is chunk consolidation, chunk splitting, small bins and large bins.
in this challenge, utilizing internal implementation of malloc, we'll see how we can maneuver a free chunk from the large bin, into the tcache.
first with the UAF, let's grab a quick heap address leak.
for the next subsequent request we wanted to fill in the large bins, note that since we're adjacent to the top chunk, every malloc immediate to free will consolidate the chunk to the wilderness instead of linking it to the bins.
and since, we can only keep track of one chunk at a time, in the middle of malloc and free, we would want to call chisel()
to create a buffer between the allocated chunk and the wilderness to prevent it from consolidating.
now you might ask, what's with the request size and why we need to do such, as of now, take this for granted cause it will be clearer and much easier to explain and understand in later part of the exploitation.
to understand this better, lets start slowly with this:
now, lets take a look at the heap state in GDB:
as you can see, as the request size doesn't match either of the fastbin nor tcache, it will go to the unsorted bin.
now if we continue the program with this:
and examining the heap state in GDB:
we can see the previous chunk is now linked to the large bin and the recent one is in the unsorted bin.
recall from the heap implementation:
The heap manager improves this basic algorithm one step further using an optimizing cache layer called the “unsorted bin”. This optimization is based on the observation that often frees are clustered together, and frees are often immediately followed by allocations of similarly sized chunks.
During malloc, each item on the unsorted bin is checked to see if it “fits” the request. If it does, malloc can use it immediately. If it does not, malloc then puts the chunk into its corresponding small or large bin.
and so since the 2nd request has a larger size than what the unsorted currently holds, it takes a new space from the top to return to the 2nd request and then throws the current unsorted chunk to its respective bins, in this case judging from its size, it gets linked into the large bin.
also recall that unlike the other bins, large bins doesn't contain a chunk with a fixed size, rather a range of size. in the screenshot above, you can see that our chunk gets linked into a large bin that can contain a chunk with a size starting from 0x440 up until 0x470.
that is the reason specifically why we request malloc with different sizes starting from the lowest to the highest but still will fall within the same large bin size range.
with this in mind, we are essentially linking 3 chunks into the large bin.
with the chunks previously goes through the unsorted bin, we have a libc address within it, using UAF we can read the addresses and calculate the offset to gain the base address.
for this part, now we will performing a chunk split. we will request another 3 with sizes the same as before but -0x20
in order.
if previously we requested with sizes (in order):
0x438
0x458
0x478
now we requesting with sizes (in order):
0x418
0x438
0x458
as follows:
when malloc is encountered with this request, all of the bins are empty but the large bins. and so it will traverse the large bin to find chunks that are less or equal to the requested size.
and in this case when we're request a chunk of size 0x420 (0x418 of request size) it finds a suitable chunk in the large bin of size 0x440 (0x438 of request size)
but the suitable chunk is larger by 0x20 bytes, in this case heap will split the chunk into two of size 0x420 and 0x20 respectfully. and the remainder chunk of size 0x20 will be put into its respective bin, in this case the small bin.
note that as always, it will be linked into the unsorted bin first before to the small bin for performance reasons as explained in the Azeria-Labs blog linked above.
we can examine this in verbosity if I change the loop as follow:
on the first pause:
on the second pause:
on the third pause:
as you can observe in the screenshots above, the chunks are linked in to small bins.
we can even further confirm this by refreshing the performance cache on unsorted bin by simply allocating another big chunk to see it is linked to the small bins:
from the same Azeria-Labs blog mentioned above, quote:
Try the fastbin/smallbin recycling strategy
If a corresponding fast bin exists, try and find a chunk from there (and also opportunistically prefill the tcache with entries from the fast bin).
Otherwise, if a corresponding small bin exists, allocate from there (opportunistically prefilling the tcache as we go).
which means, upon malloc (not sure whether this is being done before or after the memory is allocated), it will check if fastbin or small bin exist, if it exist but tcache is empty, it will move all of the chunks there to tcache until its respective size are full.
to see this in action we can simply do an allocation of size 0x10 (will be 0x20 because of metadata, which is also the size of the small bin):
initially we had 3 chunks in the small bins, but since we already requested one, the remaining two goes into the tcache. this is also exactly why we requested 3 chunk at the start.
note: if we requested of size that doesn't match of that particular small bin size, it will not do the linking as shown below
with UAF and tcache in our control, the next step is quite self explanatory, do tcache poisoning into one of the hook with system and spawn a shell.
Below is the full exploit script:
Flag: UMDCTF{a_glorious_statue_for_a_glorious_baron}
chisel
Binary Exploitation
495 pts
7