yoshikingのがんばる日記

あたまよくないけどがんばります

yoshi-camp 参加記

はじめに

 2日間行われたyoshi-campを無事終了したので、参加記を書きます. 内容としては、angr講座、マルウェア解析・フォレンジック講座を主にしました. 2日目には少々早く終わったので、pwnを解く時間もありました.

事の発端

 僕がセキュキャンに落ちたんですが、なんとptr-yudaiも(チューターに)落ちてました.

 こんな感じから、どっかでやるか!となりました. その会場が運悪く用意できなくて困り果てていたところを、阪大の教授、学生の方々のご厚意という形で部屋を貸していただきました.(ありがとうございますっっっっっ) yoshi-campは9時~18時だったのですが、家が少々遠くて早起きがしんどかったです. 早起きもセキュキャンっぽさがあって、また一興ということでよいとします.

day1 angr講座(シンボリック実行入門)

 yoshi-campの開幕を飾るのは、ふるつきが講師のangr講座でした. この講義では、主にCTFの問題をangrで解ける範囲を増やそうという目的で行われました. 基本的には、angrの機能解説→問題解くの流れでした. 問題はharekazeCTF, InterKosenCTF, angr_ctfの問題を用いて練習しました.

 簡単な使い方や制約のつけ方、アドレスや関数に対してフックする方法を学びました. angrは得意な形かどうかを見極めて使ってあげることが大切ですね.

 今回の講義で生み出された簡単なanti angrをptr-yudaiが公開しているので、そちらもぜひ.

ptr-yudai.hatenablog.com

day1, 2 マルウェア解析・フォレンジック講座

 1日目の後半ちょっと+2日目にかけて行われたのは、ptr-yudaiが講師のマルウェア解析・フォレンジック講座でした. (実際に使われた)マルウェアに感染した疑いのあるマシンのディスクダンプがあるので、重要ファイルの復元、マルウェア抽出、マルウェア挙動解析をすることを目的に行われました.

 vmの容量が足りなくて、10Gのディスクダンプが取れなかったりしましたが、メインのマルウェアの挙動解析は一通りできました.

 ディスクダンプは先頭0x20e00バイト壊されていて、謎の文字(PRINCPES)やごみで埋められています. ファイルの復元には、先頭だけ壊されているので、ファイルの真ん中や後ろにあるメタデータを用いるMetadata Carvingで復元します. その後、マルウェアを抽出する作業に移るのですが、今回は謎の文字でgrepするとマルウェアっぽいexeファイルを見つけることができます.(ここまで1時間くらい)

 このマルウェアを静的解析していくのですが、ひたすらidaさんで読んでいきます. これがまた、CTFのrevと違って読みにくい...(号泣)関数の呼び出しとかが気持ち悪くて、アドレスで呼んでいてidaが認識できないです. そのため、アドレスの先を計算して、ジャンプ、idaに認識させる、という作業をします. これで、idaは関数を認識することができましたが、callが死ぬほど読みにくくて何の関数を読んでるか(ぱっと見では)わからないです. これを解決するため、構造体を定義してidaに定義した名前を認識できるようにしてあげます. idaで構造体を定義して読みやすくするのは、とても勉強になりました.

 そんな感じで、ひたすら変数のrenameやアドレス計算、コメントをつけたりをたくさんしました(白目). 頑張っていくと、MBRパーティションをPRINCPESで埋めたりしているところを見つけ、シャットダウン処理までたどり着きました.

 あと1時間多く解析していれば、頭がもげるところでした.あぶなかった...

extra pwnタイム

 2日目終了の18時まで時間があまったので、pwnの問題を解く会が急遽開催されました. 3,4問あったのですが、そのうち典型的なbuffer overflowとropを使う問題を(ヒントをちょいちょいもらいつつ)解きました.

 ptr-yudaiがいた時期の身内CTF感があって、楽しかったです(マルウェア解析地獄から抜けたので楽しさ100倍).

さいごに

 元同期やWaniHackaseの方( Hi120ki (@hi120ki) | Twitter )、阪大の教授の方、ありがとうございました.また、講師の2人も楽しい(鬼畜)の講義、ありがとうございます. やっぱり、リアルで集まって集中的にやる分には、鬼畜講義の方が盛り上がりますね.

 次回開催も十分可能性としてあるので、楽しみです(次回はzer0ptsでやりたいな~なんて言ってますが...). 来年は、セキュキャン最後の年なので何としても行きたいですが、院死もあるので何とも言えないですね.

 2日間本当にありがとうございました.お疲れ様です.

InterKosenCTF 2019 で作問した

 チームinsecureとして、InterKosenCTFを8月11~12日に開催しました. 作問は主にptr-yudaiふるつきがしていたのですが、僕も2問作問して出題したので、想定解を簡単に書きたいと思います.

運営メンバーの記事やwirteup集など

ptr-yudai: InterKosenCTF 2019で作問しました - CTFするぞ

ふるつき: InterKosenCTF2019 開催記 - ふるつき

writeup集: InterKosenCTF2019 writeups - HackMD

問題: GitHub - theoldmoon0602/InterKosenCTF2019-challenges-public

[Forensics] Hugtto!

 easyなステガノ問題として出題しました. この問題は、身内CTFで僕が作った超絶簡単ステガノを見たふるつきが助言をくれて作られたものです. 想定解としては、randomのseedをtarやpngの生成時間から予想できるため、どこに埋め込まれているかわかるというものでした. 非想定解もありましたが、解くのがとても簡単になるようなものではなかったのでセーフです. ただ、出題者の意図としては、randomのseedがわかるとrandomを再現できるよーといったところです.

[Crypto] pascal homomorphicity

 hardとして出題しましたが、midiumのFlag Ticketより解かれたし、hardではなくmidiumで出すべきだったと反省しています. まあ、このtag付けをミスっても問題ないのがdynamicスコアのいいところですね!

 当初、ふるつきがpaillier暗号の面白い原理 c=(1+n)^ {m} \bmod{n^ 2}=1 + m\ast n \bmod{n^ 2}を使った問題を出す予定だったのですが、その問題をテストしたときに僕が思いつき改変した問題です. ふるつきの時は c=(1+m)^ {flag} \bmod{m^ 2}(m.bits > flag.bits) mを入力できる問題でした. これだと、原理さえわかれば秒で解けるし、paillier暗号の形ではなかったのでいじりました.

 想定解としては、

  1. 原理に気付く(2項定理や問題文でググるpascal paillierさんが出てくるのでなんとかpaillier暗号にたどり着く)
  2. 暗号文を2つ用意して、原理より gcd(c_1 - 1 , c_2 - 1)=gcd(m_1\ast n, m_2\ast n)=n nを求める
  3. 初めに暗号化されたフラグがあるので、 nがわかっているとやるだけ

でした. 非想定解ではテストのとき、ptr-yudaiが二部探索して解いてました.

 個人的にはフラグ(KosenCTF{Th15_15_t00_we4k_p41ll1er_crypt05y5tem})が絶望的に読みにくいのが残念でした.(無理にリートにしなくてよかったな...) しかし、楽しんでもらえたようでよかったです.

さいごに

 前回のInterKosenCTFよりはお手伝いできたので良かったです. でも、運営中のログ管理、スコアサーバ周りはふるつきとptr-yudaiが2人でしているので、僕も手伝えるぐらいにならないといけないですね(今僕が触ると壊すし、なんもわからん). てか、彼らは一からツールを作ったりしていて、マジですごいのでもっと褒めてあげてください.

 今回がopenなCTFで初作問だったのですが、アンケートで面白いと答えてくださりとてもうれしいのと、ほっとしています. 次回はWinterKosenCTFがあるのですが、こちらは難易度が高めになっているので、作問できるかなといった感じですが、頑張っていきます.

 最後になりましたが、運営メンバー、参加者の皆様、スポンサーの皆様、ありがとうございました.

CyBRICS CTF Quals 2019 Writeup

CyBRICS CTFが7月20~21日に開催されていたので、zer0ptsとして参加しました. 結果は69位で、解いた問題は3つでした.

チームメンバーのWriteup

st98: CyBRICS CTF Quals 2019 の write-up - st98 の日記帳

ptr-yudai: CyBRICS CTF 2019 Writeup - CTFするぞ

[Rev 10pts] Oldman Reverse

以下のアセンブリファイルが渡されます.

.MCALL  .TTYOUT,.EXIT
START:
    mov   #MSG r1 
    mov #0d r2
    mov #32d r3
loop:       
    mov   #MSG r1 
    add r2 r1
    movb    (r1) r0
    .TTYOUT
    sub #1d r3
    cmp #0 r3
    beq     DONE
    add #33d r2
    swab r2
    clrb r2
    swab r2    
    br      loop      
DONE: 
    .EXIT

MSG:
    .ascii "cp33AI9~p78f8h1UcspOtKMQbxSKdq~^0yANxbnN)d}k&6eUNr66UK7Hsk_uFSb5#9b&PjV5_8phe7C#CLc#<QSr0sb6{%NC8G|ra!YJyaG_~RfV3sw_&SW~}((_1>rh0dMzi><i6)wPgxiCzJJVd8CsGkT^p>_KXGxv1cIs1q(QwpnONOU9PtP35JJ5<hlsThB{uCs4knEJxGgzpI&u)1d{4<098KpXrLko{Tn{gY<|EjH_ez{z)j)_3t(|13Y}"
.end START

r3はループ回数に使われてそうで、r1, r2はMSGをごちゃごちゃしてるな~という認識です. 33をr2に足しているので、1文字目、34文字目、67文字目、....とMSGを参照してそうです. あとは、文字列の長さを伸ばしてあげて、pythonでやってあげます.

>>> msg = "cp33AI9~p78f8h1UcspOtKMQbxSKdq~^0yANxbnN)d}k&6eUNr66UK7Hsk_uFSb5#9b&PjV5_8phe7C#CLc#<QSr0sb6{%NC8G|ra!YJyaG_~RfV3sw_&SW~}((_1>rh0dMzi><i6)wPgxiCzJJVd8CsGkT^p>_KXGxv1cIs1q(QwpnONOU9PtP35JJ5<hlsThB{uCs4knEJxGgzpI&u)1d{4<098KpXrLko{Tn{gY<|EjH_ez{z)j)_3t(|13Y}"
>>> (msg*5)[::33]
'cybrics{pdp_gpg_crc_dtd_bkb_php}09|z1Cn'

フラグはcybrics{pdp_gpg_crc_dtd_bkb_php}でした.

[Cyber 10pts] Zakukozh

This image containing flag is encrypted with affine cipher. Scrape it

イメージファイルがアフィン暗号で暗号化されているみたいです. 軽くアフィン暗号について説明すると、


Enc(x) = (ax + b) \bmod{p}

\begin{eqnarray}
Dec(x) &=& a^{-1}(x - b) \bmod{p}
\end{eqnarray}

のような、暗号化、復号が行われます.a, b, pがパラメータとなり、これを見つけると復号できます. アフィン暗号は既知の平文があると、そこからパラメータを推測できるので、イメージファイルのマジックナンバーから推測します. イメージファイルといっても、いくつかあるのですが、暗号化データを見る感じではpngっぽいです.

マジックナンバーに0x00の部分があるので、そこを見るとbが0x59であることがわかります. また、暗号化データの最大値からpも推測でき、pが256であることがわかります. 残るパラメータはaのみです. aはbとqがわかっているので、適当に小さめな値から求めることができます. ここでは、マジックナンバーの0x0aに注目したとすると、暗号化データでは0xefになっていました. そのため、


\begin{eqnarray}
239 &=& (10a + 89) \bmod{256} \\
150 &=& 10a \\
a &=& 15
\end{eqnarray}

となり、aがわかります(modは超えていないだろうという推測で無視しています...). あとは、復号するだけです.

from Crypto.Util.number import inverse

with open("./zakukozh.bin", "rb") as f:
    data = f.read()

a = 15
b = 89
q = 256
inv_a = inverse(a, q)

m = bytearray([])
for c in data:
    m.append(inv_a * (c - b) % q)

with open('./flag.png', 'wb') as f:
    f.write(m)

flag.pngを表示すると、フラグが書いてました. cybrics{W311_C0M3_2_CY13R1C5}

[Forensic 10pts] Tone

youtubeに電話を掛けるときのボタン音が投稿されていました. はじめは、気合で読もうとしたのですが、4,7が低音、2,5,8が中音、3,6,9が高音までしか分からなくて、細かい音の違いが分からなくて泣いていました. そこで、ptr-yudaiがスペクトル解析をしてくれました. スペクトル解析すると、おおよそ正しい数字がわかるので、あとは文字にあてはめます(おおよそなので単語になってなさそうなところをエスパー).

cybrics{secrettonalflag}

録音して波形を見る、という考えになる前に泣いてしまったのが負け

さいごに

はやく夏休みなんねーかな

InnoCTF International 2019 Writeup

 7月13~14日にInnoCTFが開かれていたので、zer0ptsとして参加しました. チームとしては、(多分)9位でした. 終了後スコアサーバーがすぐ落ちたので(は???)、昼頃に見たときの順位です. 僕はwelcom問題含む3問を解きました. 今回Cryptoはguessingばかりだったので、つらかったです.

チームメンバーのWriteup

st98: InnoCTF International 2019 の write-up - st98 の日記帳

[Crypto] One Hundred Time RSA

 nとcが渡されます. 情報はこれだけで、ソースコードはないです.

 nが大きくないので、簡単に素因数分解できます. これでpとqが手に入ったのですが、eがわかりません. 0x10001や3、100(問題名から)を試してもうまくいきません.

 ここで一旦ロシア人になると、eを総当たりしつつ、100回復号を繰り返すという発想に至ります. たしかeが34800とかだった気がします. 結構時間がかかりました.

from Crypto.Util.number import inverse
from binascii import unhexlify

p = 37975227936943673922808872755445627854565536638199
q = 40094690950920881030683735292761468389214899724061
n = p*q
phi = (p-1) * (q-1)

e = 1
while True:
    c = 594147643758126272722748149715320287571901225730250492908477114410071694555274921111773337859009576
    if e % 100 == 0:
        print("[+] now public exponents is :", e)
    d = inverse(e, phi)
    e += 1
    for i in range(100):
        c = pow(c, d, n)
        try:
            m = unhexlify(hex(c)[2:])
            if b"InnoCTF" in m:
                print(m)
        except:
            pass

InnoCTF{cr4ck_rs4_4g41n_7bca}

[Crypto] RF

 暗号文I3_naseincamno_r15Ct_t0T07_}Fnhs{1が渡されます.

 たしか、問題文が「フェンスの上で見つけた」みたいな感じでした. フラグのフォーマットや問題文から推測するに、Rail fence cipherですね. 適当なソルバーに投げてやると、見つかります.

InnoCTF{n0t_ca3sar_7h1s_t1me_in50}

 keyは8でした.

さいごに

 えっっ、guessing過ぎません!? Cryptoはたいていの問題みたんですけど、guessing祭りでした.

 チームメンバー各位お疲れ様でした.

HSCTF 6 Writeup

 6月3~8日にHSCTFが開催されていたので、参加しました. といっても、存在に気付いたのが7日の昼だったので今回はyoshikingdomというチームで参加しました. zer0ptsのメンバーにregister情報投げると、案外みんな参加してくれました. その結果、

f:id:y05h1k1ng:20190608123849p:plain

でした. プロがぽんぽこ解いていって怖かったです. クソみたいな名前じゃなくて、結果zer0ptsでもよかったなと思います.

 登録した張本人はというと、easyなCryptoを2問解いて、寝てました(午後授業フル+バイトだったから許して☆). その2問のWriteupを書いていこうと思います.

チームメンバーのWriteup

theoldmoon0602: HSCTF 6 writeup - ふるつき

ptr-yudai: HSCTF 6 Writeup - CTFするぞ

st98: HSCTF 6 の write-up - st98 の日記帳

[Crypto 130pts] Reverse Search Algorithm

n, e, cが渡されます.

n = 561985565696052620466091856149686893774419565625295691069663316673425409620917583731032457879432617979438142137
e = 65537
c = 328055279212128616898203809983039708787490384650725890748576927208883055381430000756624369636820903704775835777

nが大きくないので、素因数分解してみると、

n = 29 * 19378812610208711050554891591368513578428260883630885898953907471497427917962675301070084754463193723428901453

でした. あとは、やるだけです.

Python 3.5.2 (default, Nov 12 2018, 13:43:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from binascii import unhexlify
>>> from Crypto.Util.number import inverse
>>> p = 29
>>> q = 19378812610208711050554891591368513578428260883630885898953907471497427917962675301070084754463193723428901453
>>> e = 0x10001
>>> n = 561985565696052620466091856149686893774419565625295691069663316673425409620917583731032457879432617979438142137
>>> n == p*q
True
>>> c = 328055279212128616898203809983039708787490384650725890748576927208883055381430000756624369636820903704775835777
>>> d = inverse(e, (p-1)*(q-1))
>>> m = pow(c, d, n)
>>> unhexlify(hex(m)[2:])
b'hsctf{y3s_rsa_1s_s0lved_10823704961253}'

hsctf{y3s_rsa_1s_s0lved_10823704961253}

[Crypto 190pts] Super Secure System

 メッセージを投げると、暗号化されたメッセージが返ってくるシステムです.

$ nc crypto.hsctf.com 8111
* * * SUPER SECURE SYSTEM * * *
My encryption system is impossible to crack if used once!
You can use this system to encrypt any of your messages with my super special key!!!
Here is my super secret message: 20235d3e4e162a732e35177c22044e013179111e42530b3b124f31490b753f5c6c7b47347a5f5a112e74772b2c0a776f6c1b680630

Enter the message you want to encrypt: aaa 

Encrypted: 29315f

Enter the message you want to encrypt: bbb

Encrypted: 2a325c

Enter the message you want to encrypt: a

Encrypted: 29

 ちなみに、暗号化は何回でもできます. xorぽいので確認すると、

>>> ord('a')^0x29
72
>>> ord('b')^0x2a
72

 あたりです.また、2文字目、3文字目は別の数字でxorされていることがわかります. 方針としては、フラグの長さ分aを送り、返ってきた値とaでxorを取り復号していきます.

from ptrlib import *

sock = Socket("crypto.hsctf.com", 8111)

sock.recvuntil("Here is my super secret message: ")
c = int(sock.recvline().strip(), 16)

flag_len = len(hex(c)[2:]) // 2
sock.recvuntil("Enter the message you want to encrypt: ")
sock.sendline(b"a"*flag_len)

sock.recvuntil("Encrypted: ")
recv = int(sock.recvline().strip(), 16)
flag = ""
for i in range(0, len(hex(c)[2:]) - 1, 2):
    a = ord('a') ^ int(hex(recv)[2:][i:i+2], 16)
    flag += chr(a ^ int(hex(c)[2:][i:i+2], 16))
print(flag)

hsctf{h0w_d3d_y3u_de3cryP4_th3_s1p3R_s3cuR3_m355a9e?}

 初めxorに気付けなかった...

終わりに

 全然仕事してないじゃん!一応Crypto担当としているので、クビになる前に強くなります. チームメンバー、運営・参加された方々お疲れさまでした.

SECCON Beginners CTF 2019 Writeup

 5月25~26日にSECCON Beginners CTFが開催されてました. チームzer0ptsで参加し、見事1位でした. チームメンバーのスピード感にあたふたしながらも、何とか数問通したのでWriteupを書きます. f:id:y05h1k1ng:20190526150254p:plain

チームメンバーのWriteup

st98: Beginners CTF 2019 の write-up - st98 の日記帳

theoldmoon0602: SECCON Beginners CTF 2019 writeup - ふるつき

ptr-yudai: SECCON Beginners CTF 2019のWriteup - CTFするぞ

[Crypto 363pts] Go RSA

Nだけなくしちゃったんだよなあ……。

 とりあえず、ncしてみます.

$ nc 133.242.17.175 1337
Encrypted flag is: 6344991311564553615862579427487315421208055634102890395723986371495652609153052697621268167305282339617530377087384855394685474792773022115680540686568501138450681688735527617827708821485497498334547179214866381804619441945049231222689303859605328356743173601997078352973035701358512748861024520717173066208645110545270222021655297034426926498311814336661104067007494662082645957734853033496598300417493177589227972753784432427753861158326507152049357862231911441535352804653810629357318640120796228696711675902539564632797542696587638137206651220915784909017728259256279217121403410183718987376151252076850594986072
> 0 
0
> 1
1
> 2
2862334794876318566324057598241408128109425191172133981033760448232814429877972087723511977245286558645814682054000700802565238541629306735791660770705209092054679971683689343593450904080873595244990490120345136595441331711791642822309784005850295255019360549477080451991452042485726954377888628611850312169956235832476482473468997735379482200368532387100203433537314371261639774070163736423425627779689468867325590663448606913878199332557603250135695359944006132143818989945004067114026488158760762115016145779669731641814525334761633123615342046269898802364403424297888529318077961694410182680067006220851010487693
The D was 3108589354283999881366977377816211791659702282566657492492997508648885943813044323050780847180267937096874217339239440259051280553557374969326192368619436355830441614153601856008410159305941711348272515678361389635638005453913489251292139406829869123083572863219302494814837811525563431399822598189396464260524809835126230568531180443134269028794046517626742952434891439389309802663719183434506260830073832328429505252129371886238807490334805430087834068647490477751392145402285002619521810094785728754166749922294644025726513925553921296219135200362480519946364396480504261544279372784914298890836180406341120859329

 数字を与えたら何か返ってきます.また、Encrypted flagDがもらえます.RSAと言われているので、返ってくる数字は


c = (input\ number)^e \bmod{n}

と考えられます.

 残るnさえ見つければ、復号できます. となると、mod演算の力の見せ所です...! mod演算は大雑把に説明すると、余りのことでした. 具体的に小さな値で説明すると、


10 \bmod{8} = 2

となります.つまり、0~7の値ですべての数字を表します.ではマイナスの値はどのように表せられるでしょうか. なんと、


-1 \bmod{8} = 7

となります!!! 詳細な説明はGooglemod 数学とかでググってください.(説明めんどう...)

 ここまでで、-1がn-1になることがわかりました.あとは、Encrypted flag, D, nから復号するだけです.

$ nc 133.242.17.175 1337
Encrypted flag is: 8574265495326376630715344864118702269699156118364751300483334371118983366068335837878430692194643907630947760218074636257962213901840959279695171778233952421546225414267130705377041788556040425374281913702704126363017674911378922866975795108524827197944518379304446564136766368478442131132639926373420800112390754225415097241757980287552353646752237359721950559438294762549429323083064086759103892170016208312683627723372030448129192438463592881852579263558462628295396255591387527275622328216850681535962245066287633466061728308482282796879985854086150426339364856915080205049371087944085742585047633265078786916717
> -1
15542669559227225429825537324581227774031891481936836236023168265768560509534175413897898039939875309341780656581069256476153063968087844810314599683067761887287458414581491790878409323665908229753209802327406269329874867875899296307778465317869542704169327492799825878469046825297855569668076456765118516134730535748246251497708000239072261510836367562449392851385927337215223745929318252139726812616575352609129732978704075275309551532205839627157499897379612485885734926354947463132399491683701371211067204644701324764499164101507557096214031191342098432721010724037945643454866504810914319842027997822183833426928
> 
Bye
The D was 11294445810590008451256715894011877130636049863981565968908667859208720657125830750743450207578873334636815264492070773310043387985660245681773997822701971285230906503746417520618785810004504532306435504005987399035139855466710836433795285598947451084782032325512289357709536384149184028104314710422845174670617305707095050004395521134314125412593577026243869778911744982261668051428824513145971177432162816223685859285472240021048696838191250769292549473790421401957500586651833875804831485999431831427601391163986595878453821597916628888747151831289612576792460488564806552328452459557394329005245982294646297165825
$ python3
Python 3.5.2 (default, Nov 12 2018, 13:43:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from binascii import unhexlify
>>> enc_flag = 8574265495326376630715344864118702269699156118364751300483334371118983366068335837878430692194643907630947760218074636257962213901840959279695171778233952421546225414267130705377041788556040425374281913702704126363017674911378922866975795108524827197944518379304446564136766368478442131132639926373420800112390754225415097241757980287552353646752237359721950559438294762549429323083064086759103892170016208312683627723372030448129192438463592881852579263558462628295396255591387527275622328216850681535962245066287633466061728308482282796879985854086150426339364856915080205049371087944085742585047633265078786916717
>>> n_minus_1 = 15542669559227225429825537324581227774031891481936836236023168265768560509534175413897898039939875309341780656581069256476153063968087844810314599683067761887287458414581491790878409323665908229753209802327406269329874867875899296307778465317869542704169327492799825878469046825297855569668076456765118516134730535748246251497708000239072261510836367562449392851385927337215223745929318252139726812616575352609129732978704075275309551532205839627157499897379612485885734926354947463132399491683701371211067204644701324764499164101507557096214031191342098432721010724037945643454866504810914319842027997822183833426928
>>> n = n_minus_1 + 1
>>> d = 11294445810590008451256715894011877130636049863981565968908667859208720657125830750743450207578873334636815264492070773310043387985660245681773997822701971285230906503746417520618785810004504532306435504005987399035139855466710836433795285598947451084782032325512289357709536384149184028104314710422845174670617305707095050004395521134314125412593577026243869778911744982261668051428824513145971177432162816223685859285472240021048696838191250769292549473790421401957500586651833875804831485999431831427601391163986595878453821597916628888747151831289612576792460488564806552328452459557394329005245982294646297165825
>>> flag = pow(enc_flag, d, n)
>>> unhexlify(hex(flag)[2:])
b'ctf4b{f1nd_7he_p4ramet3rs}'

フラグゲット!!!ctf4b{f1nd_7he_p4ramet3rs}

[Crypto 393pts] Bit Flip

平文を1ビットランダムで反転させる能力を手に入れた!

 ソースコードも渡されます.ソースコードを読むと、問題文通りのRSAです. e = 3 は明らかにおかしい(通常はe = 0x10001)ので、low public exponet attack等をやってみますが、条件に合わないのでうまくいきません.

 うーん、これは困りました... いったんe = 3から離れてみましょう.

 ncしたとき、

$ nc 133.242.17.175 31337
11545705109146803081154196731091553481494291928457261149212388528068058378823269333768320005182187108983022445099553663343177812738205610210357260446050457617731702932090378720710803576264258475457572522292489169532629163257089936346472604268088528122978005890263575400712331754280140566518723346561592384959

というふうに、平文をランダムで1ビット反転したものの暗号文がもらえます.再度ncすると、違う場所が反転されたものの暗号文です. となると、1ビット反転後の平文をm'としたとき、m'1m'2はほとんど同じ平文であることがわかります.

 RSAでbitがわかっている(今回はわかっているとは言えない)ときの攻撃といえば、Coppersmith's Attackです. なんか、今回の条件に合う攻撃なかったかな~と調べていると、

inaz2.hatenablog.com

 ありました!!!!Coppersmith’s Short Pad Attack & Franklin-Reiter Related Message Attack(長い)みたいです!!! 攻撃条件は

二つの暗号文について平文の上位bitがnのbit数の (1-1/e2) 程度共通する場合、これらからそれぞれの平文を求めることができる。

みたいです.今回はe = 3、nのbit数が1023bitなので、平文の上位bitが967bit同じ平文m'1m'2を用意しなければなりません. これはあまり難しくなくて、1bitしか変わらないので、いくつか暗号文を用意すれば上位bitがほとんど同じペアがあります. ただ、暗号文からどこのbitが反転しているかはわからないです. そのため10個程度暗号文を用意し、数値が近いもの同士で攻撃してみます.

def short_pad_attack(c1, c2, e, n):
    PRxy.<x,y> = PolynomialRing(Zmod(n))
    PRx.<xn> = PolynomialRing(Zmod(n))
    PRZZ.<xz,yz> = PolynomialRing(Zmod(n))

    g1 = x^e - c1
    g2 = (x+y)^e - c2

    q1 = g1.change_ring(PRZZ)
    q2 = g2.change_ring(PRZZ)

    h = q2.resultant(q1)
    h = h.univariate_polynomial()
    h = h.change_ring(PRx).subs(y=xn)
    h = h.monic()

    kbits = n.nbits()//(2*e*e)
    diff = h.small_roots(X=2^kbits, beta=0.5)[0]  # find root < 2^kbits with factor >= n^0.5

    return diff

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]


if __name__ == '__main__':
    n = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331
    e = 3

    nbits = n.nbits()
    kbits = nbits//(2*e*e)
    print "upper %d bits (of %d bits) is same" % (nbits-kbits, nbits)

    # ^^ = bit-wise XOR
    # http://doc.sagemath.org/html/en/faq/faq-usage.html#how-do-i-use-the-bitwise-xor-operator-in-sage
    cs = [77806872332913639919230209971392238784617993613323608240759820467908361781292069393342282547351581556259586998986092213607990761433134090374312820694026772204652782705492033343505430039323127158238332166350733150021228707945218465461966055458055161585955918733260995686334094638272681504698713633622376505308, 56989538367235158070306311186976292179966897528478291741508067866076558453602047302097966694208295312681095728980453463065836403911062093169223760418437322965770764311509640241287759696024373113722137027468951246866660964543912817862578633166333098878995510302628735250428938293637106737039139535160299108113, 38904877077171263737628178288521508189937409452080551772429415381447030724554764139002760866443078904692197281533309765344183894882142747076974754239070945859011211448240209079404207383248263468302174482960469183459600520739134108565937096269114702664072319462367117303477702692144066942049118080262294258198, 18583489730966619030615979926193288458958202730544958951381254155250349562324597989826437296289617404778761642054183547194844953317251480423608095521453337512728025268903153005408483464432086619556754592849674435698214322639172504375579000690270700946296005084794493004273617912360198409988034062432904201443, 81779690030003209255009995980102852322918673284543406574532267172257453562542775941820716471921905724303767155906053394193722593095884543297184318525481682039383393411599467185668595266012433381398851647598176014943130868710424785320701066749455904911769968456600407617618999332917394004530335428836578511038, 32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587, 74515903266795492985898471025229269878024376884435233684330662967167738131179553721410985213658229983853042260704757838616925556197183464709236532593823846170499630895374282114622487171170087091856753282020369482187351762243468042340933214874709271883018014631321412057529269400452647011517326283977015327189, 50884193811664497731364349812345616105175266569747833161903539901598152530392727464246077616701699644190582339965644325919604108036097223534586827791663822181563764715514302285487273629970687924833993670021557569304506460830014722740930334310623881775573664519736313757433300737631028708325665330938763782184, 32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587, 32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587, 70349274946614957759744006616813137783981786796010483575643371633588262711107346355531982812397035546281065385276548490062296856185344260769924218825995217554937441693054306002310733683884013698358767818115213188542062367075206824218550649564035135625128570873259781498669780078490598700442802349177736369712, 14980967781796819388041767887889903216468297576583006138007297644760691492596305457996725733919525577152256022483859011858574079640555374613247881143820771398254213825665880164126364722955374272148086280616213121922743671360167002526131898166108921720900263044330025216410582216554968042705705297554547422768, 20084571405797864822888728924121156713191530288959443915037367599696984039639335435370481551716134617000168582476618945013400480262866044903717028607042509827672584798897176555204805945259386570000183369487279952176022447143620009290796257006795499399194469035408992268017445449883256724883866657842304577341, 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331]
    cs.sort()
    for i in range(len(cs)-1):
        c1 = cs[i]
        c2 = cs[i+1]
    try:
            diff = short_pad_attack(c1, c2, e, n)
            print "difference of two messages is %d" % diff

            m1 = related_message_attack(c1, c2, diff, e, n)
            print m1
            print m1 + diff
    except:
        pass
$ ./sage attack.sage
upper 967 bits (of 1023 bits) is same
difference of two messages is 0
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
difference of two messages is 0
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
difference of two messages is 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831589264307
16260765149986038884145173876068642724013617302097779293079362876653494069932815072038851668676222848467504538570853507159925860036819304291732134150397319327193122637750054910716746167965635612837962028769149915298230040116567157454495798898178036434538204980608594381468821524975316356796051708170
16260765149986038884145173876068642724013617302097779293079362876653494069932815072038851668676222848467504538570853507159925860036819304291732134150397319327193122637750054910716746167965635612837962028769149915298230040116567157454495798898178036434538204980608594381468821524975316356795816827146

 m'1m'2が同じになってしまうペアがあったようですが、1つうまくいってます. ただ、これはどこかのbitが反転されたものなので、雑プログラムで復号します.

from binascii import unhexlify

m = 16260765149986038884145173876068642724013617302097779293079362876653494069932815072038851668676222848467504538570853507159925860036819304291732134150397319327193122637750054910716746167965635612837962028769149915298230040116567157454495798898178036434538204980608594381468821524975316356796051708170

for i in range(0, 30):
    r = 1 << i
    flag = m ^ r
    try:
        print("[+] {} : {}".format(i, unhexlify(hex(flag)[2:])))
    except:
        pass

 0~30(適当)で雑に調べてますが、特にエラーなく出力されたのでその中で1番うまくいっているのが、

[+] 28 : b'ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMY\n'

でした.後ろにDUMMYが入っていたんですね. これでフラグがとれました!ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge}

終わりに

 初めにCryptoのWarmupからやったのですが、全然解けなくて死んでました... theoldmoonにヘルプをだして解いてもらいました... そのときは、Warmupすら解けないの終わりでは...?と思いましたが、結果、きちんと仕事を果たせてよかったです(ほんまか).

 去年はCTFの存在を知っている程度のbabyで、linuxわからない!って感じで何も解けなかった記憶があります(笑). そう考えるとCTFを初めて約1年でよくここまで成長したな~と思います.

 まだまだ、わからないことがたくさんあるので、今後も精進していきたいです.

 最後になりましたが、 チームメンバー、運営・参加者の皆様お疲れさまでした. ここまで、読んでくださってありがとうございます.

angstromCTF 2019 Writeup

 4月20~25日にangstromCTFが開催されてました. チームzer0ptsで参加し、3730点で8位でした. 僕個人は、MiscとCryptoしかやってないです.

 今回は結構たくさんの問題を解くことができたので、うれしいです. Miscの簡単な問題を除いて以下に、Writeupを書いていきます.

チームメンバーのWriteup

theoldmoon0602 : ångstromCTF 2019 writeup - ふるつき

ptr-yudai : angstromCTF 2019 Writeup - CTFするぞ

st98 : ちょいまち angstromCTF 2019 の write-up - st98 の日記帳

[Misc 40] Paper bin

defund accidentally deleted all of his math papers! Help recover them from his computer's raw data.

 paper_bin.datというファイルが渡されます.

 recoverしてくれと言われているので、拡張子をpdfに変更してみます. 普通にpdfが見れるのですが、pdfの中を検索してもフラグが一向に見つかりません. しかし、foremostをするとpdfの中に20個ほどpdfが隠されていることがわかりました.

$ foremost paper_bin.dat  (pdfからでも取れます)
Processing: paper_bin.dat
|*|
$ cd output/pdf/
$ evince *

 雑にpdfを開けて、1つずつactfで検索するとフラグが取れます.

actf{proof_by_triviality}

 この問題は、10ページのpdfに対してサイズがやけに大きいことに感づくべきで、初めは全然気づかなかったです. 実ははじめforemostをpdfに対してしたけど、cd output/を見てpdf以外なんもないじゃんって放置してました... 気づくまで、ddとかで空白を消したりしてました(ddコマンドの練習にはなった...).

[Misc 60] Lithp

My friend gave me this program but I couldn't understand what he was saying - what was he trying to tell me?

 lithp.lispというファイルが渡されます.

;LITHP

(defparameter *encrypted* '(8930 15006 8930 10302 11772 13806 13340 11556 12432 13340 10712 10100 11556 12432 9312 10712 10100 10100 8930 10920 8930 5256 9312 9702 8930 10712 15500 9312))
(defparameter *flag* '(redacted))
(defparameter *reorder* '(19 4 14 3 10 17 24 22 8 2 5 11 7 26 0 25 18 6 21 23 9 13 16 1 12 15 27 20))

(defun enc (plain)
    (setf uwuth (multh plain))
    (setf uwuth (owo uwuth))
    (setf out nil)
    (dotimes (ind (length plain) out)
        (setq out (append out (list (/ (nth ind uwuth) -1))))))
    
(defun multh (plain)
    (cond
        ((null plain) nil)
        (t (cons (whats-this (- 1 (car plain)) (car plain)) (multh (cdr plain))))))

(defun owo (inpth)
    (setf out nil)
    (do ((redth *reorder* (cdr redth)))
        ((null redth) out)
        (setq out (append out (list (nth (car redth) inpth))))))

(defun whats-this (x y)
    (cond
        ((equal y 0) 0)
        (t (+ (whats-this x (- y 1)) x))))

;flag was encrypted with (enc *flag*) to give *encrypted*

 僕はlispわからないマンなので、condとかをググりながら読み解きます. すると.whats-thisがただ単にx * yをしてるだけだったり、oworeorderの順に並び変えているだけだということがわかります.

 ここまでわかったので、自分の書きやすい言語で調べていきます(僕はpython). owoを日本語でうまく説明できないですが(すいません)、

for i in range(len(encrypted)):
    encrypted[reorder.index(i)]

こんな感じで、encryptedを参照すると元の位置のものがわかります.

 ここで、encryptedが何か(lisp読むと平文っぽい)がwhats-thisに入っているのを思い出します. そのため、encryptedは平文actf{と何かを掛けたものだと考えられます. やってみると、

f = 'actf{'
for i, c in enumerate(f):
    a = encrypted[reorder.index(i)] / ord(c)
    print(a)
96.0          <- * ord('a')
98.0          <- * ord('c')
115.0        <- * ord('t')
101.0        <- * ord('f')
122.0        <- * ord('{')

きれいに割れてます.ord('a') = 97であることを考えると、1引いたものだとわかります(lispをきちんと読むとわかる).

 となると、encryptedに格納されている数字はフラグの文字 * (フラグの文字 - 1)とわかります. ここまでわかると、1文字ずつ全探索をするとフラグが得られます.

import string

encrypted = [8930, 15006, 8930, 10302, 11772, 13806, 13340, 11556, 12432, 13340, 10712, 10100, 11556, 12432, 9312, 10712, 10100, 10100, 8930, 10920, 8930, 5256, 9312, 9702, 8930, 10712, 15500, 9312]
reorder = [19, 4, 14, 3, 10, 17, 24, 22, 8, 2, 5, 11, 7, 26, 0, 25, 18, 6, 21, 23, 9, 13, 16, 1, 12, 15, 27, 20]

attack = string.printable

flag = ''
for i in range(len(encrypted)):
    for c in attack:
        if encrypted[reorder.index(i)] == ord(c)*(ord(c)-1):
            print('[+]find : {}'.format(c))
            flag += c

print('[!]flag : {}'.format(flag))

actf{help_me_I_have_a_lithp}

 lispを軽く理解して、あとはエスパー?で補って解いていく感じが楽しかったです. lispを完璧に読むとこんなに手順を踏まなくてもいいですが、CTF感があって楽しかったです.

[Misc 60] Just Letters

Hope you’ve learned the alphabet!

AlphaBeta - Esolang こういう頭がおかしくなる系の言語っぽいです.

$ nc 54.159.113.26 19600
Welcome to the AlphaBeta interpreter! The flag is at the start of memory. You get one line:
> 

 メモリのはじめにフラグがあるみたいです. いろいろ遊んでると、outputの命令を呼ぶとregister3にある値が参照されるっぽいです. なのでこの辺りを使います.

C sets register 3 to the value of register 1
G sets register 1 to the memory at the memory pointer
L outputs a character to the screen
S adds 1 to the register
Y sets the register to 0

 YGCLを送るとaが返ってきます.次の文字からはSGCLで求められます. フラグの長さはわからないので、YGCLSGCLSGCLSGCLSGCL...SGCLをたくさん繰り返したものを送るとフラグを得られます.

actf{esolangs_sure_are_fun!}

[Crypto 50] Half and Half

Mm, coffee. Best served with half and half!

 フラグを半分にしたものをxorしただけです.

from secret import flag

def xor(x, y):
    o = ''
    for i in range(len(x)):
        o += chr(ord(x[i])^ord(y[i]))
    return o

assert len(flag) % 2 == 0

half = len(flag)//2
milk = flag[:half]
cream = flag[half:]

assert xor(milk, cream) == '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

 フラグの形式からmilkの初めがactf{であるので、creamの5文字がtasteであることがわかります. しかしこれでは

milk  = actf{xxxxxx_
cream = tastexxxxxx}

 ここで、残りの文字をエスパーします.

import string

def xor(x, y):
    o = ''
    for i in range(len(x)):
        o += chr(ord(x[i]) ^ ord(y[i]))
    return o

a = '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

b = 'coffee'
for i in range(len(a)):
    print(xor(b, a[i:i+len(b)]))

 これお行儀の悪いプログラムなので、当然怒られますが、bの文字列がいい感じの文字になるか調べられます. 初めはbmilkとかcreamとかを適当に入れてみますが駄目でした. 問題文よりcoffeeを入れてみると、

vmat{u
ahtxuU
d}xvUd
qqvVdl
}Vglo
s_good
SnoldG
おこ

s_goodがいい感じなので、フラグが

milk  = actf{coffee_
cream = tastes_good}

だとわかります.

[Crypto 150] Lattice ZKP

I tried to make a zero knowledge proof, but something isn't right.

 ncすると、なにやら大きな値のA*r + eが得られます. そのあとに、rr + sのどちらかを選べる想定の問題だと思います. しかし、複数回ncしても毎回A*r + eの値は変わりません. そのため、rr + sを得ることができるので、鍵であるs(各自プログラムをよんでほしい)を簡単に求めることができます.

 あとは、暗号文を平文に戻すことを考えるだけです. といっても、sのハッシュとxorしているだけなので、同じことをします. 注意点としては、行列で値を持っていて、それをpackしたものが見えているので、packを戻すこと(unpack)を考えなければならないです. さらにもう一つ注意することがあって、Cryptoモジュールはpycryptoではなくpycryptodomeのものを使いましょう(ややこしい).

from Crypto.Util.strxor import strxor
from Crypto.Hash import SHAKE256
from Crypto.Util.asn1 import DerSequence
import binascii
import numpy as np


pack = lambda x: binascii.hexlify(DerSequence(x.tolist()).encode())
unpack = lambda x: np.array(DerSequence().decode(binascii.unhexlify(x)))

def sub(x, y):
    return np.mod(x-y, q)

with open('./r.txt') as f:
    r = f.read().strip()

with open('./r_plus_s.txt') as f:
    r_plus_s = f.read().strip()

q = 1 << 15
s = sub(unpack(r_plus_s), unpack(r))

print('[+]s : {}'.format(s))

with open('./flag.enc', 'rb') as f:
    enc_flag = f.read().strip()

raw = bytes(np.mod(s, 256).tolist())
shake = SHAKE256.new()
shake.update(raw)
pad = shake.read(len(enc_flag))
flag = strxor(enc_flag, pad)
print('[!]flag : {}'.format(flag))

actf{deep_into_that_darkness_learning_with_errors_goes}

 僕が手を動かして書いていたのですが、theoldmoon0602にunpack等の助言を得ながら解きました. ややこしいところすべてtheoldmoon0602によって解決したので、ほとんど彼が解いた感じです...

[Crypto 200] Powerball

Introducing ångstromCTF Powerball, where the Grand Prize is a flag! All you need to do is guess 6 ball values, ranging from 0 to 4095. But don't worry, we'll give one for free!

 ptr-yudaiからOblivious TransferでRSAっぽいからやってみて!と言われてやってみた問題です. 申し訳ないですけど、Oblivious Transferが何なのかよく知らないです... が、あふれる想像力でなんとかしました(嘘です).

 neが渡されています.ランダムで生成された6つのxが毎回見れます. 次に好きな数vを入力することができ、各i=0, 1, ... ,6について


k = (v - x_i)^{d} \bmod{n}

m_i = balls_i + k

によって、mが計算され、表示されます.

 これらの情報から、ballsを6つ予想し、すべて合えばフラグが得られるいう問題でした.

 これはRSAが元ということを考えるとうまく解けます. kを求める際、RSAっぽさを感じないですか?? 感じれば勝ちです. つまり、


v - x_i = k^{e} \bmod n

が成り立ちそうです. ここでm_i = balls_i + kよりk = balls_i - m_iなので上式に代入すると、


v - x_i = (m_i - balls_i)^{e} \bmod n

この式で、わからないのはballsだけなので、全探索します(ballsは0~4095の値でしかない).

for m, x in zip(ms, xs):
    for i in range(4096):
        if (max_x - x) == pow(m - i, e, n):
            print('[!]find bull : {}'.format(i))

 vの値として、xの中で一番大きい値を選んでいます(特にこれといった理由はないが、よさそうな感じがしたので...).

 実は、僕がしたのはここまでで、ncでやり取り云々はptr-yudaiに任せました. (僕はすぐに書けないので、pwnで手慣れている彼に投げた.) ここには、自分でやり取りする部分も書いて載せようと思ったのですが、こんなに長いWriteupを書くのは初??なので疲れてしまいました. 回復したら、更新するかもしれないです(しないやつ).

actf{no_more_free_oblivious_transfers}

終わりに

 チームメンバー、運営の皆さん、参加者の皆さんお疲れさまでした. 長期間CTFだったので、いい感じにぼちぼちできて楽しかったです. 最近はASISやPlaidでズタボロにされていたので、この難易度と問題量がよかったと感じました.

 長いWriteupとなりましたが、最後まで読んでくださりありがとうございました.