duckが送るBeginnersCTF2019 BitFlip(Crypto)の解説 Not初心者向け
どうもduckです。最近は仕事とctfしかしてません。ブログタイトル「CTFと共に生きる」とかにしません?
今回はCryptoのラスボスBitFlipです。いろんな方のwriteupを読ませていただきましたが、使われている数学について解説しているwriteupはないんじゃないか?
私がやるしかないな!(需要?なにそれおいしいの?)
まずはもらったコードからこれを解析すると以下の操作が行われていることがわかります。
まず最初に思いつく方法はブルートフォースです。
が、美しくないうえ、サーバーから全てのReturnの情報を得なければならないので割愛します。
(ついでに言えば途中で運営側からBitFlipのサーバーにアクセスしすぎだよ自重しろというお達しもありました。つまりはブルートフォース以外の方法があるはず)
気になる方はこちらの方のWriteupを見るとよいです。
SECCON Beginners CTF 2019 - Qiita
さてここから数学のお話。
今回私の記事の中では
(ただし1番下位のbitを0bitとします)
(サーバーから返ってくる値)
として話を進めます。
となることにも注意しておきます。(Flipが0→1のときは+、1→0のときは-)
また、書くのが面倒なのでこれ以降と書くのをやめます。適宜置き換えてください。
まず、サーバーにアクセスして
となるをゲットしましょう。よほど運が悪くなければ2回でゲットできます。
もちろんこの段階ではどのビットが反転されたのかわかりませんのでn,mは不明です。
ーーーーー
目標は、平文が線形関係であるときに用いることができるFranklin-Reiter Related Message Attackを行うことです。
と表せるとき、
の暗号文と公開鍵がわかれば、平文を求められることを使います。
まあ後でもう少しだけ解説します。
ーーーーー
1.Coppersmith’s Short Pad Attackする
でありこれをとおきます。
そうすると
...①
...②
と表せます。
このsさえ求まればFranklin-Reiter Related Message Attackが使える形になります。
ここで
の多項式を考えます。(を未知数xに置き換えただけ)
の解はを含むことは明らかです。(つまり共通解をもつ)
この性質から、sを求めるのに終結式を使います。(終結式の定義については割愛します。終結式の定義といくつかの性質 | 高校数学の美しい物語)
終結式は共通解をもつ二つの多項式に対しては0になるという性質を持ちます。
つまりから終結式を計算して、それが0になることからsを求めます。
終結式の計算にはシルベスター行列の行列式を使います。(終結式の定義といくつかの性質 | 高校数学の美しい物語)
今回の場合は以下のようになります。
こっから先は手計算では無理だ。
アホなので手計算でシルベスターの行列式計算しました。
sの大きさに注意しつつプログラムに投げましょう。後でやります。(おい)Sagemathがpythonライクでおすすめだった気がします。
mathematicaとかフリーで使えたっけ?使えるならそっちでも。
sのサイズが大きすぎるため、このままプログラムに投げるにはとしていい値が来ないと計算無理です。
何回も問題サーバーにアクセスして解けるパターンを待ってもいいですが、今回はわかってる情報からsがある程度絞り込めるのでそれを利用します。
の形なのがわかっているので、これを使い回くらい終結式を計算すればよいことがわかります。
今回はmax(n)として250くらいを取りました。
このmax(n)は実験中にサーバーサクセスを繰り返してたときに推測した値です。
これでSagemathをつかわなくともsが求まります。
以下くそコード。
N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331 e = 3 #max(n) nm = 250 C1 = 19668512534871703818804721058464184088540192186216654188589585883263154008964228350399514299852538811337835790238510703474647742023573370430287373841543674974547734434937124159068497046659147350241364684829208188803795307462087810637640642350674397153687260268727800582499681256385837706667398671154929189579 C2 = 44939803625275977251911534717835431979049524856461188120868461571121976859707514547338914366715426490130522458001402249990220314314873155256383447002643583335186960138112746521599509022925369612324695641201278318699732590955718931066187237603506202339345820308234883558809220466506231185260966657450242854347 #2べきを先にリストに格納 list2 = [] t = 1 ans = 0 i = 0 while i <= nm: list2.append(t) t = t * 2 t = t % N i += 1 #sを表示する def result(s,n,m,l): print(s,n,m,l) ans = s #ブルートフォース for n in range(nm): for m in range(nm)[n+1:nm]: #2^m + 2^n or 2^m - 2^n s = (list2[m] + list2[n]) % N if solve(s): result(s,n,m,0) elif solve(-s): result(s,n,m,1) #2^m - 2^n or 2^n - 2^m s = (list2[m] - list2[n]) % N if solve(s): result(s,n,m,2) elif solve(-s): result(s,n,m,3)
solve関数が消えていますが、うえで示した終結式に代入して、=ならTrue ,ならFalseを返すだけの関数です。
結果はこちら
383123885216472214589586756787577295904680382499389440 42 178 3
今回のケースでは
FLAGの42bit目が0→1反転したものと、178bit目が1→0反転したものであることがわかりました。
まあさえ知ってれば大体OKですが。
2.Franklin-Reiter Related Message Attackする
...①
...②
のsが1で求まったとします。
です。
この時はで求まります。(雑)
もうちょっとだけ解説を加えます。
は多項式版ユークリッドの互除法をして、最大公約多項式を求めろということです。
整数のユークリッドの互除法とおんなじです。そしたら答えがわかります。(雑)
例を挙げます。
の最大公約単項式を求めてみましょう。
頑張ってユークリッドの互除法をすると以下のようになります。
ということは
となっていることを意味します。
つまり共通因数がわかったってことだ!!
これ見たらの共通解はどう見ても1やんけ!ってなわけですね。
は共通解を持つことがわかっているので、は必ず1ではない多項式です。
ほとんどの場合で、それは1次多項式でありとなりますからがわかります。
(まれにが2次式以上になりますが、それを解いてを求めるのはかなりめんどくさいのでおとなしくの選択からやり直すのをおすすめします。)
実際にやってみた結果。(Sagemathを使いました。sympyだと整数環上でしかできなかったためあきらめた。)
PRx.<x> = PolynomialRing(Zmod(N)) g1 = x^e - c1 g2 = (x+s)^e - c2 while g2: g1, g2 = g2, g1 % g2 print(g1.monic())
結果。
つまり
bit反転させるのが筋ですが、もうめんどいのでこのまま復号します。
from Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime,long_to_bytes N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331 m = -82212154592315489750110187598968636841905821953623851870053845076997511568524415687534405445330110037385522700226033733612223434284079949332614038131717774815337863135195292470488899872185912270098336585556230191588353421626879687269516794206695695261382777258681677254870347368455087826235419500816586773161 m = m % N print(long_to_bytes(m))
ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMIYDUMMYDUMMYDUMMYDUMMY\n
反転さぼった影響で赤い部分の文字が変わってますね。(気になる人は178bit目を反転させましょう)まあもうFLAG出たしいいでしょう。
お疲れさまでした。