10問解いて233位だった。数時間しか参加できなかった割にはよく解けたと思うが、惜しいところで詰まった問題も多く技術不足を感じた。
crypto
beginners_rsa (beginner)
nが5つの素数の積として定義されている。こうした問題はMulti-Prime RSAと呼ばれるらしい。
n自体は96桁の巨大な数だが、その素因数は64bitほどの大きさしかない。そのため簡単に素因数分解できる。
ref: https://www.alpertron.com.ar/ECM.HTM
素因数分解の結果を以下に示す。
n = 317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347 primes = [ 9953162929836910171, 11771834931016130837, 12109985960354612149, 13079524394617385153, 17129880600534041513, ]
p, q, r, s, a
が求まったので、ここからphi(n) -> d -> m
の順に計算することができる。
Malti-Prime RSAにおいてphi(n)
は(p - 1)(q - 1)(r - 1)(s - 1)(a - 1)
と求まるから、以降は通常のRSAと同じようにd = e^(-1) mod phi(n)
, m = c ^ d mod n
に沿って計算すればよい。
ref: https://www.ochappa.net/posts/multi-prime-rsa
※なお、計算が遅い場合は中国剰余定理を利用した高速化手法が使えるらしいが、今回は爆速で解読できたため利用しなかった。
以下は計算に使用したソースコードである。
from Crypto.Util.number import * n = 317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347 e = 65537 enc = 127075137729897107295787718796341877071536678034322988535029776806418266591167534816788125330265 # https://www.alpertron.com.ar/ECM.HTM primes = [ 9953162929836910171, 11771834931016130837, 12109985960354612149, 13079524394617385153, 17129880600534041513, ] phi = ( (primes[0] - 1) * (primes[1] - 1) * (primes[2] - 1) * (primes[3] - 1) * (primes[4] - 1) ) d = pow(e, -1, phi) m = pow(enc, d, n) flag = long_to_bytes(m) print(flag)
以下は実行結果。
$ python3 dec.py b'FLAG{S0_3a5y_1254!!}'
付録:RSA問題で役に立つリンク集
- https://zenn.dev/anko/articles/ctf-crypto-rsa#%E6%94%BB%E6%92%83
- https://furutsuki.hatenablog.com/entry/2021/03/16/095021
beginners_aes (beginner)
AESに関する問題だが、AESの仕組みを知らずとも解ける。
keyがb"the_enc_key_is_" + (ランダムな1バイト)
、ivがb"my_great_iv_is_" + (ランダムな1バイト)
と定義されているから、考えられるkey, ivの候補を全探索すればよい。
その後、得られたflagの候補に対して、
FLAG{
という文字列から始まるか- (パディングを外したうえで)ハッシュ値が与えられたものと等しいか
を調べることで、正しいflagが求まる。
以下は作成した解読用スクリプトである。
from Crypto.Util.Padding import unpad from Crypto.Cipher import AES import hashlib enc = b'\x16\x97,\xa7\xfb_\xf3\x15.\x87jKRaF&"\xb6\xc4x\xf4.K\xd77j\xe5MLI_y\xd96\xf1$\xc5\xa3\x03\x990Q^\xc0\x17M2\x18' flag_hash = "6a96111d69e015a07e96dcd141d31e7fc81c4420dbbef75aef5201809093210e" key = b'the_enc_key_is_' iv = b'my_great_iv_is_' for c_key in range(255): new_key = key + c_key.to_bytes() for c_iv in range(255): new_iv = iv + c_iv.to_bytes() cipher = AES.new(new_key, AES.MODE_CBC, new_iv) m_pad = cipher.decrypt(enc) if (not m_pad.startswith(b"FLAG{")): continue m = unpad(m_pad, 16) m_hash = hashlib.sha256(m).hexdigest() if (m_hash != flag_hash): continue print(m) # exit()
FLAG{7h3_f1r57_5t3p_t0_Crypt0!!}
replacement (easy)
1文字ごとにmd5ハッシュ値を求めているので、これも全探索すればよさそう。 全文字のmd5ハッシュ値を求め、与えられた配列の中に同一のハッシュ値があればその文字で置き換える、という処理を行えばよい。
import hashlib # 配布された"my_diary_11_8_Wednesday.txt"の中身。長いので省略 enc = [265685380796387128074260337556987156845, ... , 301648155472379285594517050531127483548] for c in range(256): x = hashlib.md5(str(c).encode()).hexdigest() x = int(x, 16) enc = [chr(c) if e == x else e for e in enc] m = "".join(enc) print(m)
以下は実行結果。
$ python3 dec.py Wednesday, 11/8, clear skies. This morning, I had breakfast at my favorite cafe. Drinking the freshly brewed coffee and savoring the warm buttery toast is the best. Changing the subject, I received an email today with something rather peculiar in it. It contained a mysterious message that said "This is a secret code, so please don't tell anyone. FLAG{13epl4cem3nt}". How strange! Gureisya
forensics
tiny-usb (beginner)
AutoPsyでisoファイルを開くと、FLAG.PNGという画像ファイルを見つけた。そこにflagが書かれていた。
FLAG{hey_i_just_bought_a_usb}
※ちなみにstringsコマンドで調べても画像ファイルの存在自体はわかる。
ImgBurn v2.5.8.0 CD001 FLAG.PNG;1 IHDR
Surveillance-of-sus (Normal)
fileコマンドで調べてもdata
としか言われず、ファイルタイプは判明しなかった。
そこでstringsコマンドを使うと先頭にRDP8bmp
という文字列が見つかり、それで調べるとRDP Bitmap Cache
というファイルであることがわかった。
RDP(リモートデスクトッププロトコル)使用時、通信量軽減のために画像をキャッシュする仕組みがあるらしく、その際に作成されるファイルとのこと。
さらに検索すると解析方法から何まで書いてあったため、これに沿って解析していく。
https://jpn.nec.com/cybersecurity/blog/231006/index.html
簡単に説明すると、まずbmc-tools
でキャッシュから画像ファイル(複数のタイル)を得て、
$ wget https://raw.githubusercontent.com/ANSSI-FR/bmc-tools/master/bmc-tools.py $ mkdir result $ ls bmc-tools.py Cache_chal.bin result $ python3 bmc-tools.py -s Cache_chal.bin -d result [+++] Processing a single file: 'Cache_chal.bin'. [===] 650 tiles successfully extracted in the end. [===] Successfully exported 650 files. [===] Successfully exported collage file.
それらのタイルをRdpCacheStitcher
というツールで並び替えることで、もとの画像を復元する。
https://github.com/BSI-Bund/RdpCacheStitcher/releases/
並び替えによって得た画像を以下に示す。flagが書かれているのがわかると思う。
FLAG{RDP_is_useful_yipeee}
codebreaker (beginner)
以下のサイトを使い、与えられた画像とにらめっこしてQRコードを作っていく。見えない部分については塗らないままでよい。
※参考までに、この問題ではQR-code versionは29x29(ver.3)、青いタイルをクリックすると表示されるFormat Info PatternはH/7だった。
塗り終えたら右側の""Editor mode"をクリックするとテキストベースで表示してくれるので、これをコピーする。
#######_#?#???#?#???#_####### #_____#_##??#???????#_#_____# #_###_#__##?#?##?#???_#_###_# #_###_#_##??##?##?##?_#_###_# #_###_#_#????##??##??_#_###_# #_____#_##????#??###?_#_____# #######_#_#_#_#_#_#_#_####### _________???#?#??????________ ___#__#__???####?#???__###_## ###??#_?#??????#????#?#???##? ?#???##?##??????????#?##??##? #?#???_?##???????????#?##?### #####?#??##????????#?##?#??## ?#??#?_#####?????#####???###? ##?#####????????????###?????# ?##???_?##??????????#??##??## ##??#?###????#???????#???#### ?#?###_??????##?#?????##??##? ?####?##?????#?#?#??????#?### ##??##_???????###???????##??# ?#??###????#?????########?### ________##???#?###??#___##?## #######__##??####??##_#_#?##? #_____#__#?##????#??#___#???? #_###_#__#??#?##?#??#####???# #_###_#_#?#??#??#??##?#?#???# #_###_#__##??###?###?#??##??? #_____#__#?##???#?#?##??#???? #######__#?#?##??##?######???
塗っていない部分はすべて?
と表記されているため、白マスについては_
に置き換える。メモ帳の置換機能を使って1行ずつ見ていった。
#######_#_#___#_#___#_####### #_____#_##__#_______#_#_____# #_###_#__##_#_##_#____#_###_# #_###_#_##__##_##_##__#_###_# #_###_#_#____##__##___#_###_# #_____#_##____#__###__#_____# #######_#_#_#_#_#_#_#_####### _________??_#_#____??________ ___#__#__???####_#???__###_## ###__#__#????__#_???#_#___##_ _#___##_##?????????_#_##__##_ #_#_____##_#??????___#_##_### #####_#__##_?????__#_##_#__## _#__#__#####_????#####___###_ ##_#####____??????__###_____# _##_____##_????????_#__##__## ##__#_###_???#__?????#___#### _#_###____?__##_#????_##__##_ _####_##???__#_#_#????__#_### ##__##__??#___###__????_##__# _#__###??__#_____########_### ________##___#_###__#___##_## #######__##__####__##_#_#?##_ #_____#__#_##____#__#___#??__ #_###_#__#__#_##_#__#####??_# #_###_#_#_#__#__#__##_#_#???# #_###_#__##__###_###_#__##??? #_____#__#_##___#_#_##__#_??? #######__#_#_##__##_######_??
これをtxtファイルとして保存し、再度QRazyBoxに食わせてみる(New -> Import from text)。右上のTools -> Extract QR Informationを選択して解読を試みるが、以下の通りまだ読むことができない。
QR version : 3 (29x29) Error correction level : H Mask pattern : 7 Number of missing bytes (erasures) : 27 bytes (38.57%) Data blocks : ["??????0?","00010110","01010100","11100010","01100100","11010110","11000100","01000110","000?????","???1?110","01110111","11000110","10110100","11110111","10000110","01010111","1???0111","00110111","01110101","1???????","?1110111","111011??","???10110","00010001","00110110","11101?0?","???0?101","01??????","??101000","01001110","10011010","01111011","10111101","0001111?","0???????","?????001","11101001","01111000","00010000","00110110","1???????","?????100","01110000","00011000","11101001","11011010","01110011","0011000?","????100?","0?1????0","?1100101","10111100","01001000","000?????","??100010","01000?0?","1??0?100","01100110","1010001?","??0?1110","01101111","00101110","01000001","00010011","10000000","01010010","11110101","01001110","10111101","01000000"] ----------------Block 1---------------- Reed-Solomon Block : [0,84,100,196,0,119,180,134,0,117,0,0,54,0,0,154,189,0,233,16,0,112,233,115,0,0,72,0,0,0,111,65,128,245,189] ----------------Block 2---------------- Reed-Solomon Block : [22,226,214,70,0,198,247,87,55,0,0,17,0,0,78,123,0,0,120,54,0,24,218,0,0,188,0,0,102,0,46,19,82,78,64] Final data bits : 0000000001010100011001001100010000000000011101111011010010000110000000000111010100000000000000000011011000010110111000101101011001000110000000001100011011110111010101110011011100000000000000000001000100000000 Final Decoded string : Error : - Too much missing bits
これ以上の復元は無理そうで諦めかけたが、ここで"reed-solomon decoder"を使うとデコードできる場合があると知った。
ref: https://merri.cx/qrazybox/help/examples/basic-example.html
そこでTools -> reed-solomon decoderを選択し、Decodeを押すと、見事flagを得ることができた。
FLAG{How_scan-dalous}
I_wanna_be_a_streamer (easy):解けなかった
RTPストリームから怪しげな音源を見つけるところまでは行けたがそこからがわからなかった
pwnable
nc (beginner)
16進数で計算の答えを述べる問題。
$ nc chal-lz56g6.wanictf.org 9003 15+1=0x10 FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}
home (easy):解けなかった
現在いるパスにService
という文字列が含まれており、なおかつデバッガを使用していない場合にflag作成処理constructFlag()
が実行される。
ただし、2つ目の条件「デバッガを使用していない」に関してはcmp
命令で判定がされており、$rax == 0xffffffffffffffff
のときにデバッガを使っていると判断される。そのため、デバッガを使用していたとしても、print $rax=1
(gdbの例)のようにraxを適当な値に変えてやれば突破できる。
さて、このconstructFlag()
の結果がどこに格納されるかがわからない。
reversing
lambda
ソースコードに書いてあった内包表記を組み合わせたら解けてしまった…
s = "16_10_13_x_6t_4_1o_9_1j_7_9_1j_1o_3_6_c_1o_6r" s = [chr(int(c,36) + 10) for c in s.split('_')] s = [chr(123^ord(c)) for c in s] s = [chr(ord(c) + 3) for c in s] s = [chr(ord(c) - 12) for c in s] print("".join(s))
web
Bad_Worker (beginner)
"Fetch"ボタンを押した際の遷移先をBurpSuiteでdummy.txt
からflag.txt
に書き換えるだけ。
FLAG{pr0gr3ssiv3_w3b_4pp_1s_us3fu1}
pow (easy)
あるWebページが与えられる。そこでは一定間隔でsend
関数が実行されており、send
では/api/pow
に引数array
をPOSTする処理が行われている。
scriptタグ内のコードを以下に示す。
function hash(input) { let result = input; for (let i = 0; i < 10; i++) { result = CryptoJS.SHA256(result); } return (result.words[0] & 0xFFFFFF00) === 0; } async function send(array) { document.getElementById("server-response").innerText = await fetch( "/api/pow", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(array), } ).then((r) => r.text()); } let i = BigInt(localStorage.getItem("pow_progress") || "0"); async function main() { await send([]); async function loop() { document.getElementById( "client-status" ).innerText = `Checking ${i.toString()}...`; localStorage.setItem("pow_progress", i.toString()); for (let j = 0; j < 1000; j++) { i++; if (hash(i.toString())) { await send([i.toString()]); } } requestAnimationFrame(loop); } loop(); } main();
一定の間隔でsend
が呼び出されており、そのたびにページ内のServer response
の数字が1ずつ増えていく。表記からしてこの数字を1000000
にすればflagが手に入りそうだ。
とりあえず適当な値でsend
を呼び出してみたが、特定の値でないとBad Requestとなってしまう。そのため、一度正規の通信が行われるまで待ち、そこで送信された値2862152
*1を用いて呼び出したところ、Server response
が1増えたことが確認できた。
さて、send
を使えば数値が増やせることがわかったものの、愚直に100万回リクエストを飛ばすとDoS攻撃になりかねない。
そこでこのsend
関数が引数にstringsのarrayをとることから、複数の値を一気に渡せないかと考えた。実際に["2862152", "2862152"]
をsendしたところ、想定通りServer response
が2増えた。
これを利用して、「一気に10万要素のarrayを送信する」処理を10回繰り返すことで、DoSせずにflagを得ることができる。具体的にはDevtoolで以下のコードを実行すればよい(省略しているが、send(arr)
は10回実行する)。
arr = new Array(1000000).fill("2862152") send(arr) send(arr) (略) send(arr)
これでServer response
の部分にflagが表示された(flagの文面からして嘘解法っぽいが気にしない)。
FLAG{N0nCE_reusE_i$_FUn}
*1:この値はburpsuiteで通信内容を見ることで得た