WaniCTF writeup(Crypto,Forensics)
25問解いて187人中24位でした。
ForensicsとCryptoに加えてMiscも全完出来たので嬉しかったです。
RevとPwnは精進します。
Crypto
Veni, vidi
SYNT{fvzcyr_pynffvpny_pvcure}
ROT13で復号する。
FLAG{simple_classical_cipher}
exclusive
XORを使った暗号です🔐
encrypt.pyとoutput.pyが与えられる。
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="")
コード中の
assert key[0:3] * 19 == key
から鍵が3文字と分かり、フラグの形式がFLAG{
で始まるので既知平文攻撃が出来る。
with open('output.txt') as c: strings = c.read() known = 'FLAG{' known = known[0:5]*len(strings) key=[] f=[] for c1,c2 in zip(strings,known): key.append(chr(ord(c1)^ord(c2))) key = key[0:3]*19 for c,m in zip(strings,key): f.append(chr(ord(c)^ord(m))) flag = ''.join(f) print(flag)
FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}
Basic RSA
RSA暗号の基本的な演算ができますか?
接続するとRSAに関する計算を求められる。
p : 素数1 , q : 素数2 , N : 公開鍵1(p*q) , e : 公開鍵2 , m : 平文 , c : 暗号文
として、
・p,qからN
・m,e,nからc
・p,q,e,cからm
の計算を行って順番に投げつけるとフラグが返ってくる。
最後のp,q,e,cからmを導出するには
phi = (p-1)*(q-1)
d = e^-1 (mod phi)
が必要なので適宜計算する。dに関してはpow(e,-1,phi)で出せば早い。
ターミナルからPythonを起動して計算したのでコードは残ってません。
FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}
LCG crack
安全な暗号は安全な乱数から
server.pyが配布される。
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
choiceが1の時に次の乱数を取得、2の時に次の乱数を当てるモードになる。
seed,a,b,mが固定されてそうなので逆算すればいけるかと予想したら実装に困ってしまった...。
色々調べてたら次の記事を見つけた。
今回のケースは記事の「A (multiplier) と B ( increment) と M (modulus) が未知である場合」と同じ。
記事からコードを引用させていただきソルバを書いた。
#https://satto.hatenadiary.com/entry/solve-LCG from Crypto.Util.number import inverse, GCD, getRandomNBitInteger from functools import reduce from pwn import * def solve_unknown_modulus(states): diffs = [X_1 - X_0 for X_0, X_1 in zip(states, states[1:])] multiples_of_M = [T_2 * T_0 - T_1 ** 2 for T_0, T_1, T_2, in zip(diffs, diffs[1:], diffs[2:])] M = reduce(GCD, multiples_of_M) return M def solve_unknown_multiplier(states, M): A = (states[2] - states[1]) * inverse((states[1] - states[0]), M) return A def solve_unknown_increment(states, A, M): B = (states[1] - A * states[0]) % M return B state = [] io = remote('lcg.wanictf.org',50001) i=0 while(i<30): io.sendlineafter('> ',"1") app = int(io.readline(),10) print(app) state.append(app) i+=1 print('------------------------') M = solve_unknown_modulus(state) A = solve_unknown_multiplier(state, M) B = solve_unknown_increment(state, A, M) next_value = (A * state[-1] + B) % M print(next_value) io.sendlineafter('> ',"2") io.sendlineafter('> ',str(next_value)) print(io.recvline()) for j in range(9): next_value = (A * next_value + B) % M print(next_value) io.sendlineafter('> ',str(next_value)) print(io.recvline()) print(io.recvline())
FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}
l0g0n
🕵️♂️
server.pyが与えられる。
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()
AES_CFB8と問題名の「l0g0n」からZerologonを思い出す。
動画によると、AES_CFB8ではivを0で固定すると暗号文の先頭バイトが256分の1の確率で0となる。
また、0を暗号化すると0になるため、256回の鍵生成したうち1回は平文と同じ0となる。
ChallengeとCredentialを暗号化した結果を比較して同じであればフラグが取得出来るので、上記の脆弱性にしたがって8Byte分の0をループで送り続けるというソルバを書いた。
from pwn import * io = remote('l0g0n.wanictf.org',50002) while(True): io.sendlineafter('Challenge (hex) > ','0000000000000000') io.sendlineafter('Credential (hex) >','0000000000000000') rcv = io.recvline() if(b'OK!' in rcv): print(rcv) break print('not')
FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}
Forensics
logged_flag
ワニ博士が問題を作っていたので、作っているところをキーロガーで勝手に記録してみました。
先に公開してしまいたいと思います。
(ワニ博士は英字配列のキーボードを使っています)
key_log.txtとsecret.jpgが渡される。
key_log.txtを見る限りechoでフラグをflag.txtに書き込んでそれをsteghideでjpgファイルに埋め込んでいる。
echoのコマンド文がそのまま見れるので頑張ってフラグを取り出すか、「steghide embed」で打ち込んでるパスワードmachikanetamachikanesai
を抜き出して、「steghide extract -sf test.jpg」からパスワードを入力してflag.txtを抜き出す。
11:50:15 [E] 11:50:15 [C] 11:50:15 [H] 11:50:15 [O] 11:50:16 [Space] 11:50:23 [Shift] 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}
chunk_eater
pngの必須チャンクをワニ博士が食べてしまいました!
eaten.pngとPNGフォーマットの解説サイトが渡される。
eaten.jpgをバイナリエディタで開くとPNGの必須チャンクと思われるIHDR
IDAT
IEND
が全てWANI
に書き換わっているので、これらを全て元に戻してやる。最初と最後以外のWANI
は全てIDAT
に書き換えた。
正確に修復出来るとフラグの画像が出てくる。
FLAG{chunk_is_so_yummy!}
zero_size_png
この画像のサイズは本当に0×0ですか?
バイナリエディタで開くと画像の幅(赤)と画像の高さ(青)が0になっている。
ただChunk Type(茶)とCRC(緑)は元から存在している。
CRCはChunk Typeと幅,高さから導出出来るので、CRCから幅と高さを逆算するっぽいな~と思い色々調べてたら見つけた。
zlibのcrc32を使えばxorの実装せずに計算してくれるので楽っぽい。
上記のサイトのコードを引用させていただき幅と高さを求める。
#https://www.programmersought.com/article/80384546662/ import zlib import struct filename = 'dyson.png' with open(filename, 'rb') as f: all_b = f.read() crc32key = int(all_b[29:33].hex(),16) data = bytearray(all_b[12:29]) n = 4095 for w in range(n): width = bytearray(struct.pack('>i', w)) for h in range(n): height = bytearray(struct.pack('>i', h)) for x in range(4): data[x+4] = width[x] data[x+8] = height[x] crc32result = zlib.crc32(data) if crc32result == crc32key: print("Wide is:",end="") print(width.hex()) print("High as:",end="") print(height.hex()) exit(0)
Wide is:00000257 High as:0000030d
後は出力通りに幅と高さを書き換える。
FLAG{Cyclic_Redundancy_CAT}
ALLIGATOR_01
ワニ博士のPCでは,悪意のあるプロセスが実行されているみたいです。
取得したメモリダンプから、”evil.exe”が実行された日時を報告してください。
(注意: スペースはすべて半角のアンダースコアにしてください)
example: FLAG{1234-56-78_99:99:99_UTC+0000}
Memory Forensics三部作。
ALLIGATOR.zipを展開するとALLIGATOR.rawが出てくる。
Volatilityが推奨ツールとして紹介されていたので導入した。
使い方等は下記サイトを参照した。
初動調査としてOSのプロファイルを調べる必要があるので下記コマンドで調べる。
volatility -f ALLIGATOR.raw imageinfo
Volatility Foundation Volatility Framework 2.6 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 (/root/Documents/wanictf/for/alligator/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
Win7SP0x86で良さそう。pstree
でメモリダンプ取得時に動いていたプロセスが確認出来る。
volatility -f ALLIGATOR.raw --profile=Win7SP0x86 pstree
省略 . 0x84dd6b28:evil.exe 3632 2964 1 21 2020-10-26 03:01:55 UTC+0000 省略
日時が載っているのでこれをフラグ形式にしたらフラグ完成。
FLAG{2020-10-26_03:01:55_UTC+0000}
ALLIGATOR_02
コマンドプロンプトの実行履歴からFLAGを見つけてください。
(ALLIGATOR_01で配布されているファイルを使ってください)
consoles
でcmd.exe で実行されたコマンドの出力を表示出来るらしい。
volatility -f ALLIGATOR.raw --profile=Win7SP0x86 consoles
省略 C:\Users\ALLIGATOR>type C:\Users\ALLIGATOR\Desktop\flag.txt FLAG{y0u_4re_c0n50les_master} C:\Users\ALLIGATOR>
FLAG{y0u_4re_c0n50les_master}
ALLIGATOR_03
Dr.WANIはいつも同じパスワードを使うらしいです。
Dr.WANIのパソコンから入手したパス付のzipファイルを開けて、博士の秘密を暴いてしまいましょう。
(ALLIGATOR_01で配布されているファイルを使ってください)
hashdump
でメモリ上のパスワードハッシュをダンプできる。
SYSTEMとSAMのアドレスがなくてもALLIGATORのパスワードハッシュが出てきたけど本来ならば指定するっぽい。
volatility -f ALLIGATOR.raw --profile=Win7SP0x86 hashdump
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:::
5e7a211fee4f7249f9db23e4a07d7590
をhashes.txtに移したらJohn the Ripperなどで復号する。
john hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=NT
Loaded 1 password hash (NT [MD4 256/256 AVX2 8x3]) Warning: no OpenMP support for this hash type, consider --fork=4 Press 'q' or Ctrl-C to abort, almost any other key for status ilovewani (?) 1g 0:00:00:00 DONE (2020-11-25 06:35) 4.166g/s 7149Kp/s 7149Kc/s 7149KC/s iloveyoh2..iloveus5 Use the "--show --format=NT" options to display all of the cracked passwords reliably Session completed
ilovewani
をパスワードとしてwani_secret.zipを解凍するとflag.txtが出てきた。
大阪大学 公式マスコットキャラクター「ワニ博士」 【プロフィール】 名前: ワニ博士(わにはかせ) 誕生日: 5 月 3 日 性別: オス 出身地: 大阪府 豊中市 待兼山町 【性格】 温厚,好奇心旺盛,努力型,お茶目,社交的,たまに天然,賢い 【趣味】 ・阪大キャンパスでコーヒーを飲みながら学生としゃべる ・粉もん屋めぐり ・化石集め。(いつか自分の仲間に会うために) ・CTF: FLAG{The_Machikane_Crocodylidae}
FLAG{The_Machikane_Crocodylidae}