0ctf19 Finals

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:

  1. All the chunks are preallocated with random values, and all of them were fastbin heap chunks
  2. The entire binary was loaded with PIE; in addition, the heap metadata was loaded on a randomized address page (using urandom).
  3. We had a read leak, could only leak the size of the chunk, so it was useless.
  4. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void free(void* mem)
{

...

check_inuse_chunk(p);

/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/

if ((unsigned long)(size) <= (unsigned long)(av->max_fast)

#if TRIM_FASTBINS
/* If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins */
&& (chunk_at_offset(p, size) != av->top)
#endif
) {

set_fastchunks(av);
fb = &(av->fastbins[fastbin_index(size)]);
p->fd = *fb;
*fb = p;
}

...

}

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 */
#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2)

...

/*
----------- 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:

  1. 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.
  2. 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).

Share