Skip to content

Instantly share code, notes, and snippets.

@k1R4
Created December 19, 2021 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save k1R4/1af1be1c8e9220257f3802908e4986df to your computer and use it in GitHub Desktop.
Save k1R4/1af1be1c8e9220257f3802908e4986df to your computer and use it in GitHub Desktop.
FastCars - InCTF Nationals 2021

FastCars

tl;dr - use UAF in sell_car to fastbin attack and overwrite __malloc_hook with one_gadget.

Description

I heard glibc malloc is fast. But not as fast as these cars.

Challenge author: k1R4

handout: FastCars.zip

Write-up

Initial Analysis

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;
}

Exploitation

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 with one_gadget by getting an allocation on it.

Conclusion

Final exploit can be found here

flag: inctf{g0tta_g0_f4st!}

Hope you enjoyed the challenge as much as I did making it :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment