picoCTF writeup
自由に書いてOK
t6o_o6t.icon
始める前の状態
解説なしAC
Writeup
ダウンロードしたファイルが34Bのバイナリだった
$ mkdir picoctf
$ cd picoctf
$ xxd flag
フラグ以外の文字列はなかった
$ strings flag
xxdは16バイトで1行なのでこれで良いと分かった
感想
文字列を読みたい → strings
専門のコマンドを使ったほうが直接的にフラグを見られる
解説なしAC
Writeup
ROT13が、13を鍵とした換字式暗号であることは暗号技術入門で知っていた 書き方がわからなかった
数字や記号はどのように変換しているのか?
これは暗号文ではないか?
暗号化したらフラグが出てくると思っていた
書けた
code:ROT13.py
s = input()
def isupper(c):
return ord('A') <= ord(c) <= ord('Z')
def islower(c):
return ord('a') <= ord(c) <= ord('z')
for c in s:
x = c
if isupper(c):
x = chr(((ord(c) - ord('A') - 13) % 26) + ord('A'))
elif islower(c):
x = chr(((ord(c) - ord('a') - 13) % 26) + ord('a'))
print(x, end='')
print()
要件
大文字と小文字は区別しているっぽい
{が8文字目にあり、}で終わっていることから、これはフラグそのものと推測
フラグはpicoCTFで始まるはずだが、この文字列はcvrbPGSで始まる
ROT13で暗号化された文字列っぽい
普通に13文字戻すと上手くいかない
code:bad_rot13.py
s = input()
for c in s:
print(chr(ord(c) - 13), end='')
print()
VicUC:FnTe^ZRZimeRIllRZX_R%RXU[TdYRUfRXUZ$&Rh=WFYg`[p
この文字列、最初にic、C:Fといった文字列がある
picoCTFが部分的に抜け落ちて失敗しているっぽい
失敗する要因 → 大文字、小文字を区別していないから、英文字の範囲を超えている
元の文字列で大文字の部分は大文字の範囲でROT13復号する
暗号文がcvrbPGSとなっており、PGSはCTFを暗号化したものだろう
小文字の部分は小文字の範囲でROT13復号する
英文字でない部分は何もしない
暗号文に{}が残っていないから
最近C++を使うことが多いので、Pythonの関数は覚えていなかった
感想
時間かかった~
解説なしAC
3つのファイルがある
フラグ
パスワード
ファイル
以下のように実行
$ cat pw.txt | python3 ende.py -e flag.txt.en
Please enter the password:gAAAAABk50ggHSvLQNGG3IxOUY8rJYV6a4wA65LA0LUm0x-sSGOkRXvnQw8nQI1gGaE9ik_0-D8v_lcvBcAYynTbdemqn-Ko_9i9Lgciy63UtQa4LCyXg09znz664Qei5mzNsgkhiXtsLsSCokM13iMC4LYBqbB_Ps7N6FQqa3AviKmEvK_25NoWKumf1dEL_RG0QvIJ_xggogCtCXs9-t_HoWFDgXRHaPcg93qfp0JSZU9vFCf5bsQft4dy8Jd3ExmwQ5AbJ8xb
dオプションもあったから試してみよう
出来た
Please enter the passwordを抜く(オプション)
$ cat pw.txt | python3 ende.py -d flag.txt.en | grep -E "picoCTF\{.+\}"
マッチさせることはできたが、その行にある他の文字を表示したくない
$ cat pw.txt | python3 ende.py -d flag.txt.en | grep -E "picoCTF\{.+\}" -o
OK!!
感想
正規表現パートはしなくても良かった
ターミナルからコピーペーストするだけだから
解説なしAC
ファイルを実行するだけ?
Writeup
実行形式っぽいが、具体的には?
$ file warm
warm: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildIDsha1=7b3da2efd83a2b9154697b6c7f6474042e1fd033, with debug_info, not stripped
ELF
$ ./warm
-bash: ./warm: Permission denied
$ chmod u+x warm
$ ./warm
Hello user! Pass me a -h to learn what I can do!
-hオプションを使ってヘルプを見ろということ?
$ ./warm -h
なんかフラグもらえる(;o;)
Writeup
画像の中に文字がある? -> 表示する、strings
そもそも画像なのか?
$ file cat.jpg
表示する
特に何もなかった
stringsを実行
$ strings cat.jpg
バイナリなので、何行もランダムな文字列が出てきた
方針としては確信している状態
これ以外に方法を持っていない
問題文にある「ファイルはいつでも秘密の方法で変更できます」(Google翻訳)という部分にも一致する
文字列を探す
$ strings cat.jpg | grep "pico"
空振りだった
出力なし
picoCTFが含まれていると思っていた
$ strings cat.jpg | grep "CTF"
PicoCTF
<rdf:li xml:lang='x-default'>PicoCTF</rdf:li>
何かありそう、なんだけど
XML?
分からない
Writeupを読む
惜しかったかもしれないな
自分の試したかったことが書いてあった
たしかに、grepすれば「PicoCTF」の存在は分かる
その先は?
文字列「PicoCTF」が存在する部分の周りを探索する
1. stringsコマンドで周りを読みたい
$ strings cat.jpg -s "" | grep "PicoCTF"
行をまたいで検索することはできたが、出力が氾濫した
2. xxdコマンドで周りを読む
まず、行を確認
$ xxd cat.jpg | grep "PicoCTF"
00000030: 0013 1c02 7400 0750 6963 6f43 5446 1c02 ....t..PicoCTF..
最初の100行くらいを読めば良さそうだとわかった
$ xxd cat.jpg | head -n 50
3. stringsコマンドで周りを読む
$ strings cat.jpg | head -n 25
code:cat.xml
JFIF
0Photoshop 3.0
8BIM
PicoCTF
<?xpacket begin='
' id='W5M0MpCehiHzreSzNTczkc9d'?>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 10.80'>
<rdf:Description rdf:about=''
<cc:license rdf:resource='cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9'/>
</rdf:Description>
<rdf:Description rdf:about=''
<dc:rights>
<rdf:Alt>
<rdf:li xml:lang='x-default'>PicoCTF</rdf:li>
</rdf:Alt>
</dc:rights>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
$ echo W5M0MpCehiHzreSzNTczkc9d | base64 -d
$ echo cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9 | base64 -d
感想
確信できる方法を持っておくことが必要
例
多分Writeupを書いた人は、最初から「Base64すれば分かる」根拠をもって確信していたわけではない いくつか方法が頭に浮かんでいて、それを試したらたまたま当たった
ヒントありAC
感想
ASCIIは1つの方法
解説なしAC
感想
リバースエンジニアリング問題は、ソースコードを読んで、フラグを復元する方法を考えられると良い
未AC
実際、printf(user_buf)の形を見たら、以下のような攻撃を考えることはできた
%s%s%s%s... → スタックをさかのぼって文字列として見ていく
%x%x%x%x... → 16進数として
今回は1バイトごとに読んでいくのが良かったらしい
つまりそれは。。char
bytesで読む方法がポイント
bytes = char*
どうやって読むのか
解説AC
解説を読んだら理解できた
CTFは脆弱性を突くゲームではない。旗とりゲームだけが本質
注意点
と思う
GETやPOSTでは問題ないが、HEADでは飛ばない
EはDに対して、Lを法として逆数の関係
c が分からないので、Wikipediaを読んでみる
cは暗号文
n = p * q
l = lcd(p - 1, q - 1)
e =
Writeupを読む
t6o_o6t.iconは、大小の肌感覚が無いので、とりあえずfactordbに掛けたほうが良さそう。
1461849912200000206276283741896701133693 × 431899300006243611356963607089521499045809
ということがわかった。
P, Qが判明したから、Lが分かる。
Lを求める
code:find_L.py
l = math.lcm(1461849912200000206276283741896701133693 - 1, 431899300006243611356963607089521499045809 - 1)
52614329482780730983714227324740595059505144147429586505725945111325944153346928
Lが分かったので、Eと組み合わせて復号化鍵Dを入手する。
Dを求める
逆数の求め方が分からない
自分で考えてみよう
逆数とは
積が1になる数。
$ E×D\mod L = 1
Eが小さいから、上手くいくまで探索してみよう
E * D < Lのとき、絶対に積は1にならないような?
E * D = L のときは0になる
E * D > L、すなわち$ D > \frac L Eが必要条件
以下のようなコードを実行してみた(PythonのREPLインタプリタで)
code:bad.py
>> e = 65537
>> d = (l // e) + 1
>> while True:
... if (e * d) % l == 1:
... print(d)
... d += 1
びくともしない
RSA暗号の実装は最近もやってみて、逆数の実装が分からなかった
このブログではCryptoというパッケージを利用しているみたい
読んでみよう
RSAの小さな実装を読んでみたい
解説なし
最初は何をさせたいのかわからなかったが、ディレクトリを開いてみて分かった。
ディレクトリの奥にあるファイルを実行できればOK。
想定解(おそらく)
.
別解
treeで一番下のファイルまで見れるんじゃなかろうか?
注:通常の解法を見た後に考えたものです。天下り的に降りてきたものではないのでご安心
$ sudo apt install tree
$ tree
└── Almurbalarammi
└── Ashalmimilkala
└── Assurnabitashpi
└── Maelkashishi
└── Onnissiralis
└── Ularradallaku
└── fang-of-haynekhtnamet
$ tree -f
あとは、このツリー表示を上手く無くせれば.
$ tree -f -i
いい感じ。
$ tree -f -i | grep "fang" | /bin/sh | grep -E "picoCTF\{.+\}" -o
OK!
最後のgrepは、実行ファイルの標準出力からフラグを抽出している
解説なしAC
有料のフルバージョンを持つプログラムということで、それを不正に起動する問題だと推測。
実際にはこの推測は若干間違っていて、起動するためのライセンスキーを取れればそれがフラグになる問題だった。
まずは適当に動かしてみるが、分からない
ソースコードを読むことにした
とりあえず処理の流れを読むと、aを選択した場合は単純に計算をしているだけだった
bの実体はコード中には見つからない
暗号化されたものがハードコードされている
以下の流れのようだ
ライセンスキーを入力
ライセンスキーの検証を通過
暗号化されたフルバージョンのコードを、ライセンスキーで復号化
フルバージョンのコードを実行する
ライセンスキーの検証を見ることにした
usernameはANDERSONで、SHA-256によるusernameのハッシュ値を元に、ライセンスキーが決まっている 実際の決め方はソースコードを参照
code:REPL.py
>> username_trial = b"ANDERSON"
>> hashlib.sha256(username_trial)
<sha256 _hashlib.HASH object @ 0x7fc63a309690>
>> hashlib.sha256(username_trial).hexdigest()
'31250184996c31741ab6ae8452c205deb7dbf431c5bdba21dea5f1289b646bfa'
>> s = hashlib.sha256(username_trial).hexdigest()
>> def f(n):
'01582419'
これでライセンスキーのdynamicな部分を取れた。
ライセンスキーを手動で連結して、フラグを取得完了
初手
$ file dolls.jpg
この初手は、当たり前になった
dolls.jpg: PNG image data, 594 x 1104, 8-bit/color RGBA, non-interlaced
PNG..?
$ strings dolls.jpg
$ strings dolls.jpg | grep "pico"
$ strings dolls.jpg | head -n 50
特に何もない
ヒントを読む
待ってください、ファイルの中にファイルを隠すことはできますか? しかし、どうやってそれらを見つけるのでしょうか?
(Google翻訳)
思い出した
$ binwalk dolls.jpg
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 PNG image, 594 x 1104, 8-bit/color RGBA, non-interlaced
3226 0xC9A TIFF image data, big-endian, offset of first image directory: 8
272492 0x4286C Zip archive data, at least v2.0 to extract, compressed size: 378954, uncompressed size: 383938, name: base_images/2_c.jpg
651612 0x9F15C End of Zip archive, footer length: 22
このいずれかを見れば良さそう.
どうやって見るのかわからない
ファイルのスライスを得る方法があれば..
$ tail -c +272492 dolls.jpg > output.zip
$ unzip output.zip
感想
とても良い問題だった
もうちょっと良い方法がありそう
解説なし
Writeup
2分くらいいろいろな値を入力して遊んでみたが、分からず
ソースコードを読んでみる
ここまで触ったReverse Engineering問題、ソースコードを読まないと全部不可能だ
必ず読もう
読んでみると、入力値とは一切関係のないロジックとして、暗号化されたsecret変数と、復号化する関数があった
ソースコードを書き換えて、この関数にsecretを渡して実行しよう
実行したらフラグが出てきた
ここで終わるとは思わなかった
??