Tamil CTF 2021 crypto writeup
平日開催だったのですが、思わずやってしまいました。前回のCTFのwriteup見たいなとCTFtimeを開いたのが敗因です…。おかげで平日にやるべきことをどんどん後回しにしています。
今回のCTF、最後に時間を延長することになってそのおかげで追加で1問解けたのですが、その後FLAGをtwitterに載せる人がいて強制終了となりました。本当に良くない。
Result
Writeup
Triple Dimple Easy Squish 200pt
The Author is addicted to reels , find out what he is doing!!
ファイル:chall
challを見ると、ciphertext, key, ivがあります。keyは24バイトでivは8バイトでした。慣れてる方は8バイトのivでピンと来るかもしれません。ピンと来なかった私は、とりあえずAESだろう、keyが読める文字だな、筆者はリールが好きとのことだからivを繰り返して16バイトにするのかな?と色々考えていました。しかし、どのAES暗号のモードを使ってもダメ…。
問題名より、Triple DES に気付けるかどうかの問題でした。DESはIVを8バイトにしがちなのか…。
from Crypto.Cipher import DES3 from Crypto.Util.number import * f = open("chall") a = f.readlines() ct = a[0].split()[-1] key = a[1].split()[-1] iv = a[2].split()[-1] ct = long_to_bytes(int(ct,16)) key = long_to_bytes(int(key,16)) iv = long_to_bytes(int(iv,16)) cipher = DES3.new(key, DES3.MODE_CBC,iv) print(cipher.decrypt(ct)) print() cipher = DES3.new(key, DES3.MODE_CFB,iv) print(cipher.decrypt(ct)) print() cipher = DES3.new(key, DES3.MODE_OFB,iv) print(cipher.decrypt(ct)) print()
OFBモードでうまくいきました。
TamilCTF{Triplee_DES_iss_quitee_samee_as_DES_isnt_it???}
PJ-JP 238pt
You know something Pj and Jp are friends and CTF contributers... go ahead
ファイル:pj_jp.txt
いつもvimでファイルを見ているのですが、今回catで見たのでファイルの下の方の文字にすぐ気付けました。
最後にある、"jp", "pj"を2進数に変換すればOKです。8文字毎に空白が2つあることや、8文字毎の先頭が毎回"jp"であることから推測できると思います。"jp"を0, "pj"を1としてASCIIコードとすればOKです。
f = open("pj_jp.txt") a = f.readlines() enc = a[-3].split() for i in range(0,len(enc),8): temp = enc[i:i+8] val = 0 val = "" for j in range(len(temp)): if temp[j] == "pj": val += "1" else: val += "0" print(chr(int(val,2)),end="") print()
TamilCTF{wh4t_th3_b!4ry}
AEXOR 293pt
The title itself enough ig?
ファイル:data.txt, enc.py
正直この問題はあまり納得いっていないです。
Triple Dimple Easy Squishと同様、data.txtを見るとciphertext, key, ivがあります。ivが16バイトであることや問題名からAES暗号でしょう。enc.pyでAESであることは分かりますがモードは分かりません。
問題は、encです。encはgetkey関数とgetsubkey関数で取得したバイト列を1バイト毎XORしています。getkey関数で得られたバイト列はkeyとなり、暗号化時に使われています。getsubkeyはそのkeyの部分文字列であろう、だからrep_keyという変数名(repeat)なのだろうと推測。どのバイト列が部分文字列ならうまくいくかすべて検証しましたが、どれもうまくいかず…。
結局、data.txtの最後にあった"okay"がgetsubkey関数で得られた文字列とのこと。Discordで質問したらよくdata.txtを見ろと言われました。元から"okay"には気付いていましたが、まさかこれがという感じです。しかも、別にkeyの部分文字列ではないです。うーん…
from Crypto.Util.number import * from Crypto.Cipher import AES ct = "68e934aa25be2c5f1674e101b31c25672400d69f9cf910a9f64071cea79f2de01d01bcf140105e5f7a3db66fffe64694" enc = "030e150a0b0415110618111c001b0d1c" iv = "1cb7942bf4ae14947150f9f196f92b2c" ct = long_to_bytes(int(ct,16)) iv = long_to_bytes(int(iv,16)) ok = "okay" key = b"" for i in range(0,len(enc),2): v = int(enc[i:i+2],16)^ord(ok[(i//2)%4]) key += long_to_bytes(v) cipher = AES.new(key,AES.MODE_CBC,iv) flag = cipher.decrypt(ct) if b"Tamil" in flag: print("CBC",flag) cipher = AES.new(key,AES.MODE_CFB,iv) flag = cipher.decrypt(ct) if b"Tamil" in flag: print("CFB",flag) cipher = AES.new(key,AES.MODE_OFB,iv) flag = cipher.decrypt(ct) if b"Tamil" in flag: print("OFB",flag) cipher = AES.new(key,AES.MODE_OPENPGP,iv) flag = cipher.decrypt(ct) if b"Tamil" in flag: print("OPENPGP",flag) cipher = AES.new(key,AES.MODE_EAX,iv) flag = cipher.decrypt(ct) if b"Tamil" in flag: print("EAX",flag) cipher = AES.new(key,AES.MODE_GCM,iv) flag = cipher.decrypt(ct) if b"Tamil" in flag: print("GCM",flag)
CBCモードでした。
TamilCTF{AESS+XORR_issss_W3irdd_Combinationn???}
Secret Sharing 294pt
Jo , Pj and Shamir communicating with each other in group, jo ask for a flag, Shamir send's junk of data, did you decrypt this data?
ファイル:file
問題名をググると、「Shamirの秘密分散法」がヒットします。以下のサイトが分かりやすかったです。
Shamir's Secret Sharing Step-By-Step - Qvault
つまり、f(x) = a_n × xn + a_(n-1) × xn-1 + ... + a_1 × x + a_0 mod p という式があり、a_0が秘匿情報、つまりFLAGとなります。f(1) ~ f(5)の値が与えられています。また、required_sharesが2であることから、上記の式のnが2ではないかとエスパーします。
f(x) = a1*x+a0 mod p (a0 = flag) f(1) = a1+a0 = shares[0] mod p f(2) = 2*a1+a0 = shares[1] mod p よって a1 = f(2) - f(1) = shares[1] - shares[0] mod p flag = a0 = f(1) - a1 mod p
import base64 from Crypto.Util.number import * p = b"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEp" p = bytes_to_long(base64.b64decode(p)) rs = 2 shares = [b"MEwx7cz+C01rL8H0Hhz2EIgHjWYXVcL81uITmRha674=",b"YJhj22+ntS1s80CT9b6Y7ayc52baTFGNRpPUyLxtaf8=",b"kOSVyRJRXw1utr8zzWA7ytEyQWedQuAdtkWV+GB/6EA=",b"wTDHtrT7CO1wej3TpQHep/XHm2hgOW6uJfdXKASSZoE=",b"8Xz5pFekss1yPbxzfKOBhRpc9WkjL/0+lakYV6ik5MI="] #f(1) = a1+a0 mod p #f(2) = 2*a1+a0 mod p f1 = bytes_to_long(base64.b64decode(shares[0])) f2 = bytes_to_long(base64.b64decode(shares[1])) a1 = (f2-f1)%p flag = (f1-a1)%p print(long_to_bytes(flag))
TamilCTF{S3cr3eT_4lg0RitHm}
Break The RSA 296pt
Megan Foxx sent a mssg regarding her age..Did you decrypt message only using public keys?
Note: the flag is seprated by two parts
Name+age enc Second one Rsa
ファイル:message.enc, message.pub
この問題もあまり好みではありませんでした…。
1. 1つ目の暗号
公開鍵が、「BEGING PUBLIC KEY」と少し変です。ちゃんとしたものではないのでしょう。問題文より、どうやらRSA暗号ではないようです。
作問者に聞くと、Meganの年齢が重要だと言っていました。Megan Foxxという人が実在しているとは思わず放置していました。調べてみると35歳でした。なんと、megan35 という符号化処理があるんですね。調べてみると、base64でencodeされた文字列を別の文字に置き換える処理がmegan35のようです。
このソースコードを参考にして自作megan32 decodeプログラムを作りました。decode時にpaddingが合わないと出てきたので最後の文字だけのけると、URLエンコードされた文字列となりASCII文字列にするとこのような文章が得られました。
Congrats You Got it ! n = 667 d = 1027 e = 3 decrypt it
message.encの1行目の整数たちをこの値で復号すると、flagの前半部分が得られました。
import base64 from Crypto.Util.number import * import urllib.parse megan35 = b"3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5" b = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" dec_dict = dict(zip(megan35, b)) # megan 35 decode enc = b"bwDVjxOXnMR1RZGjlxe1RZGMlxb1RZG0nHesRHesRceqbgy1RZ31Rub1RZ3wSZm1RJK/OdNqOdSJOdNqRd3sSseqbge1RZ31Rub1RZ3tOdGGjLfZm+1qnHesRL1u=" dec = b"" for e in enc: v = dec_dict[e] dec += long_to_bytes(v) msg = base64.b64decode(dec[:-1]) print(urllib.parse.unquote(msg.decode())) # decode RSA n = 667 d = 1027 e = 3 ct = [408,217,382,380,416,613,408,162,604,9,537,146,280] for c in ct: m = pow(c,d,n) print(chr(m),end="") print()
TamilCTF{y0u_
2. 2つ目の暗号
DownUnder CTFでもあったPKCS#1方式のRSAです。モジュール使って公開鍵を見てみると、nの値がそこまで大きくなかったのでfactorDBに入れると素因数分解できました。復号したのですが、なんか変な文字列に…。
b'\x02\x90\xa9\x14\x93l\xe2\x9f\x8a?-\xa1\xf4\x01b\xbbD\xa8\x00br34k3d}\n'
後ろの方にギリギリ読めそうな文字列がありました。いつかのCTFで復号したらこれと似た文字列が出てきたことがありました。その時は、m1, m2が得られてm2をlong_to_bytesするとこのように後ろの方に読める文字列があり、答えはlong_to_bytes(m1+m2)でした。
今回もこんな感じになるのだろうと思っていたのですが、1つ目の暗号は綺麗にもう出ています。これからどうするんだと悩んでいたら、作問者からそのまま読めるとこ引っ付ければいいよと言われます。
公式writeupではopensslでやると綺麗になるそうですが、なんかもやっとしています。
from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA from Crypto.Util.number import * import base64 b2 = b"MDgwDQYJKoZIhvcNAQEBBQADJwAwJAIdDVZLl4+dIzUElY7ti3RDcyge0UGLKfHs+oCT2M8CAwEAAQ==" v2 = base64.b64decode(b2) cipher = RSA.importKey(v2) print(cipher) # RsaKey(n=359567260516027240236814314071842368703501656647819140843316303878351, e=65537) # nを素因数分解すると、p,q= 17963604736595708916714953362445519,20016431322579245244930631426505729 p,q= 17963604736595708916714953362445519,20016431322579245244930631426505729 n = 359567260516027240236814314071842368703501656647819140843316303878351 assert n == p*q e = 65537 ct2 = b"C1qKLBtrUwLkebPf+JKX6ie1bKEdUGmzkYwBJWQ=" ct2 = bytes_to_long(base64.b64decode(ct2)) phi = (p-1)*(q-1) d = inverse(e,phi) m = pow(ct2,d,n) print(long_to_bytes(m))
TamilCTF{y0u_br34k3d}
Code Book of Bases 482pt
Blocks and Blocks of data in Cipher Books
ファイル:cipher, key
keyに書いてあるものが読みにくかったので、fileコマンド叩くと見慣れないものが出てきました。
$ file key key: ISO-8859 text
これでutf-8に変換できました。
不正なマルチバイト文字ですと表示された時の対処法 - Qiita
$ iconv -f sjis -t utf8 key モ]tラMuモ]tモ]5モ]uラM5モ]uモ]4モ]tモM5モ]tラ]5モ]tラM5モ]tラ]4モ]tモMuモ]uモ]4モ]tモ]tモMuモMtモMuモM4モMuモMtモMuモM5モMtモM5
バイナリデータとして読み込んでみると、なんとなく周期がありそうで、5バイト毎のようです。
b'\xef\xbe\x93]t' b'\xef\xbe\x97Mu' b'\xef\xbe\x93]t' b'\xef\xbe\x93]5' b'\xef\xbe\x93]u' (以下略)
2進数で見てみましょう。
1110111110111110100100110101110101110100 1110111110111110100101110100110101110101 1110111110111110100100110101110101110100 1110111110111110100100110101110100110101 1110111110111110100100110101110101110101 (以下略)
21, 27, 33, 37bit目以外は毎回同じです。ここの部分だけ抜き取ってみると、ASCIIコードのようです。ここのエスパー要素ちょっと強め?
b'keytamilctf2021!'
どうやらkeyが得られたようです。問題名よりブロック暗号のようなので、IVの要らないAESのECBモードでcipherにあった暗号文らしきbase64encodeを復号してみたら、FLAGになりました。
TamilCTF{bL0ckS_ar3_Br34kabL3!!}
Bases 485pt
Its going to complex as usual, but if you know random bases very well it wouldn't be. Play with them
ファイル:cipher.txt
時間延長のおかげで解けた問題です。作問者にめっちゃDM送りました。
どうやら65536がヒントだぞと教えてもらったのですが、問題名より65536進数にするのかと思いやってみるがうまくいかず…。
結局、base65536 → base91 → base62 → base32 → base85とdecodeすればFLAGになりました。base65536は初耳です。情弱には難しいです…。
復号ツールはこちらです。
base65536 : Base65536 Decoding Tool Online Free
base91 : Base91 Encoding - Base 91 Online Decoder, Encoder, Translator
残り : CyberChef
TamilCTF{B4s3_C1ph3r_4r3_re4lly_c00l!!}
Vai Raja Vai 984pt
Karthick(you) and Panda are friends.. You have a unique power to see the future and panda knows the gambling world . Panda is in debt of 10 lakhs to Rande(a local gangster) as one of his gambling incident went wrong. Use your ability to help panda. As his fate depends on your ability and time.
nc 3.99.48.161 8002
ファイル:vai_raja_rai_client
crypto要素はそれほどなく、reversing問のような気がします。reversingにしてもそれほど難しいわけではなく、コスパがいい問題でした。
Ghidraに与えられたファイルを突っ込んでみると、乱数を生成しておりシードはそのプログラムが実行された時のUNIXTIMEです。
順序としては、mod 7の乱数を6回生成し、その合計値を配列に格納すること50回行います。その配列の値を50個すべて当てればFLAGがもらえます。
そのアルゴリズムをそのまんま自分でも作ります。ここでシードは現在のUNIXTIMEから10足した値にします。自分で実行してから10秒後にncすると、シードが同じ値になるので、実行したプログラムでの配列の値をそのまま送ればFLAGがもらえます。
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(void) { unsigned int nowtime = (unsigned int) time( NULL ); srand(nowtime+10); int a,b,x; a = 0; b = 1; int val[53]; while(b < 301) { if(b%6 == 0) { val[b/6-1] = a; a = 0; x = rand(); a += x%7; } else { x = rand(); a += x%7; } b++; } for(int i=0;i<53;i++) { printf("%d %d\n",i,val[i]); } printf("%u\n",nowtime+10); }
TamilCTF{Af73r_m4nY_1nt3r3st1ng_tuRn_0f_3v3nts_R4nd3_tri3s_t0_k1ll_k4rth1ck_ but_k4rth1ck_g0t_s4v3d_by_KumAru_uh_K0kki_kumAru_uh}