Hello so recently I have competed in 0ctf alongside a huge collab group of
Perfect Blue, Sice Squad, Galhacktic Trendsenders, and RPISEC on team U+1F914
.
This was a pretty fun collab, and for the most of the time I was working with
the kernel Fast&Furious challenge, which was pretty cool. Since there is a
write-up with this one, I won't spend time over that.
There was also another challenge called embeded heap, which I spent a good amount of time during the CTF but ended up not solving because I had to look at Fast&Furious.
Embeded heap
They give us a MIPS binary that is linked with uClibc. This is actually a drop in replacement for the standard glibc used primarly for embedded devices since the glibc library is so fat.
So first thing, I needed a MIPS image to run with. So I decided to use
arm_now
, to fetch a pre-built MIPS image. Unfortunately, it did not include an
instance of gdb, so I decided to build my own gdb/gdbserver. I ended up using a
gdbserver + gdb-multiarch on my host computer to try to debug it with my
favorite plugin gef. (I had to also install python on the guest MIPS computer
to debug it locally.)
The MIPS binary had a simple heap overflow bug, allowing us to input a size and some data that we can write. The problem is we are actually pretty limited in what we can do for the exploit:
- All the chunks are preallocated with random values, and all of them were fastbin heap chunks
- The entire binary was loaded with PIE; in addition, the heap metadata was loaded on a randomized address page (using urandom).
- We had a read leak, could only leak the size of the chunk, so it was useless.
- After a set of modifications, we get to free, free, then update again. Then the program just exits, so we essentially only get two free's.
Fortunately, the uClibc implementation of the heap is an older dlmalloc implementation, and many of the glibc checks were not there, i.e. double free was not checked. Also another thing was that the heap chunk size was NOT checked, so we could modify heap size to whatever we want. This comes into importance later on.
Because PIE was enabled, we had no way of doing a unlink attack, so then the only possible attack was a fastbin attack. In addition, we were unable to leak because once we free'd, we cannot issue a leak.
So I decided to go into the source code of uClibc. I was looking at the
uClibc-ng version 1.0.30 (I thought that these two uClibc versions should be the
same code-ish) because I already had that for a different challenge. Here I am
at the directory /libc/stdlib/malloc-standard/free.c
:
1 | void free(void* mem) |
So it seems like it confirms that it does not check the fastbin size to see if
index is out of bounds. Then it looks like the fastbin at that index is set to
the chunk that we freed. Another interesting to note is that we enter this
segment of code if the size of the chunk is les than max_fast
value in the
arena. Then an idea came to me, what if I could set max_fast
to a large number
(the pointer value), and then I could write a pointer to my heap to some offset
from fastbin, potentially into the ld linker!
Checking from the malloc_state
struct and fastbin_index
macro, both defined in
malloc.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* offset 2 to use otherwise unindexable first 2 bins */
...
/*
----------- Internal state representation and initialization -----------
*/
struct malloc_state {
/* The maximum chunk size to be eligible for fastbin */
size_t max_fast; /* low 2 bits used as flags */
/* Fastbins */
mfastbinptr fastbins[NFASTBINS];
...
It seems like the max_fast
value is defined before the fastbins! Which means
if we past a size to fastbin_index
that will make it return -1 (it turns out
to be sz = 8
) we will be able to overwrite max_fast
. Then the next free, we
will be able to overwrite a function pointer in the linker!
Now I had two issues:
- I still don't know if a function pointer is called at this point, since FULL RELRO was enabled, so the linker would never be invoked for libc function.
- I dunno if NX is enabled on the remote MIPS; it turns out my local MIPS had no NX enabled (except for the stack, which was RW only), but I wasn't for sure on the remote, since my local MIPS instance also had ASLR disabled.
Then I got called to continue to work on the other Fast&Furious challenge, so I never got to get back to this challenge until after the CTF.
Afterwards, I found that the OP's solution was pretty much what I have already found and was pretty sad I could not have gotten this during the CTF. Overall, only one team solved it, we could've boosted our own team by a few places, potentially.
The OP then said this afterwards:
MIPS doesn't actually support NX
At that point, I was actually kinda disappointed at the lack of security on MIPS :(
So after that I decided to go back and solve this. In the end, I found that on
exit, the libc will actually call _dl_app_fini_array
when then calls a set of
finalizing functions. The _dl_app_fini_array
will then surprisingly use the
plt.got section of the linker (surprisingly RW) to call _dl_run_fini_array
,
which is where I could easily overwrite that to point to our heap pointer. Then
we simply just write shellcode on the heap and get profit!
Here's my script for the final writeup. I also had some code to run a qemu to test local.
Well overall, 0ctf had some cool, hard challenges, and this surprise last-minute collab was very fun to do! We ended up 11th place out of 12 teams (though still better than getting last :P).