SECCON Beginners CTF 2020 に参加しました
CTF は何回かやったことある程度の初心者です。
会場はサーバーが動いてる限り(2020/06/15まで) → ここ
解けた問題は以下。
解けた人が増えるにつれてリアルタイムに問題のスコアが減っていくのですが、きれいに最終的なスコアが100未満のは全て解けて、それ以上の問題は1問も解けなかったですね。。
Pwnが1問も解けなかったの悔しい。
解法 (解けた問題)
Crypto
R&B
ダウンロードしたzipを展開すると
- encoded_flag
- problem.py
の二つのファイルが手に入ります。
encoded_flag はファイル名の通り、エンコードされたフラグですね。これをデコードすれば良さそうです。
そして問題の problem.py
ぼくは始めなぜかこの problem.py に加筆してこれを使ってデコードするものだと勘違いして、
def rot13(s)
def base64(s)
にデコードするコード書いて実行するなどして、割と時間使ってしまいました。
その後、落ち着いてから見返して気がつきました。
実際には逆でこの problem.py を使ってエンコードされています。
for t in FORMAT:
if t == "R":
FLAG = "R" + rot13(FLAG)
if t == "B":
FLAG = "B" + base64(FLAG)
rot13 でエンコードした場合は先頭に R
が、base64 でエンコードした場合は先頭に B
が連結され、その繰り返しになっています。
そして、encoded_flag の先頭の文字は B
です。
なので、最初の一文字を切り取って base64 デコードしてみます。
# cat encoded_flag | cut -b 2- | base64 -d
BUk9IeDlWclF5RnB4dTVySEU0cUdJbEZSeVZvd09hSTB1NHJLSVJGVEFHRTFXU0FITVZIMXFsSEh1ZUV5RGtFMjl1R3pnV1p4eWVGVXFXSDBNWEgwQVdyUVNLcFJ1S24zV0VHMk1TWjNJWXBLeXdJeGpscEljbEhheWJFR1NPSDBNVkZHTVZaUjFoSmFjYXJScUhIM3FScndIMUZRT2FIUk1WSDJ1aVpUcGtGSHUxTTBxNk0xV2xGMUE1RTB5R0kwcVdMbUFrRTAxTEVHTjVGVU9ZcklxQVpUQTFFUnVXblNiakFKSVRIYXlLRElXQUF4RVdJMXFTb0g0NQ==base64: invalid input
ちゃんと文字列が返ってきました!よさげです。
※なぜか base64: invalid input
と出てきてしまいましたがとりあえず無視します。echo <一文字目を切り取った文字列> | base64 -d
だと base64: invalid input
と出ずに、それ以外は同じ結果だったので問題無いです。
また、頭文字が B
だったので、同様にbase64デコードしてみます。
# cat encoded_flag | cut -b 2- | base64 -d | cut -b 2- | base64 -d
base64: invalid input
ROHx9VrQyFpxu5rHE4qGIlFRyVowOaI0u4rKIRFTAGE1WSAHMVH1qlHHueEyDkE29uGzgWZxyeFUqWH0MXH0AWrQSKpRuKn3WEG2MSZ3IYpKywIxjlpIclHaybEGSOH0MVFGMVZR1hJacarRqHH3qRrwH1FQOaHRMVH2uiZTpkFHu1M0q6M1WlF1A5E0yGI0qWLmAkE01LEGN5FUOYrIqAZTA1ERuWnSbjAJITHayKDIWAAxEWI1qSoH45#
次は頭文字が R
なので、今度は一文字目を切り取ってrot13にかけてみます
※ 参照: Wikipedia - ROT13
# cat encoded_flag | cut -b 2- | base64 -d | cut -b 2- | base64 -d | cut -b 2- | tr a-zA-Z n-za-mN-ZA-M
base64: invalid input
BUk9IeDlSckh5eUR4dTVySElIbjBnV0h4eXVESGNTR1JFNUZIU1dyUUhrRlQxR29hTmtJMklrSHdJU0ZKU0NJeDFXcEhXa3JRT2ZFM3VLcXljVkwycVpyUnloRTFBU0ZISTZIME1uWnpneEdUU3dEejU1SDBnUEZIU2hvMGcxSUh1Z0d6Z1JyS1N5R0lTV0dJYzNxR01YRTA5SHBLeVdNMGN1REhJaFowNWVGUnlXQVJNNkRJV1dFbU45
また、頭文字がちゃんと B
になりました。
これを繰り返していけば良さそうです!
なので、スクリプトにします。
s = 'BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ=='
import codecs
while True:
print(s)
print()
if s[0] == 'B':
s = codecs.decode(s[1:].encode(), 'base64').decode()
elif s[0] == 'R':
s = codecs.decode(s[1:], 'rot13')
else:
break
print(s)
ファイル読み込むのがめんどくさかったので encoded_flag はべた書きしてます。
これを実行すると
# python3 solve.py
BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==
BUk9IeDlWclF5RnB4dTVySEU0cUdJbEZSeVZvd09hSTB1NHJLSVJGVEFHRTFXU0FITVZIMXFsSEh1ZUV5RGtFMjl1R3pnV1p4eWVGVXFXSDBNWEgwQVdyUVNLcFJ1S24zV0VHMk1TWjNJWXBLeXdJeGpscEljbEhheWJFR1NPSDBNVkZHTVZaUjFoSmFjYXJScUhIM3FScndIMUZRT2FIUk1WSDJ1aVpUcGtGSHUxTTBxNk0xV2xGMUE1RTB5R0kwcVdMbUFrRTAxTEVHTjVGVU9ZcklxQVpUQTFFUnVXblNiakFKSVRIYXlLRElXQUF4RVdJMXFTb0g0NQ==
ROHx9VrQyFpxu5rHE4qGIlFRyVowOaI0u4rKIRFTAGE1WSAHMVH1qlHHueEyDkE29uGzgWZxyeFUqWH0MXH0AWrQSKpRuKn3WEG2MSZ3IYpKywIxjlpIclHaybEGSOH0MVFGMVZR1hJacarRqHH3qRrwH1FQOaHRMVH2uiZTpkFHu1M0q6M1WlF1A5E0yGI0qWLmAkE01LEGN5FUOYrIqAZTA1ERuWnSbjAJITHayKDIWAAxEWI1qSoH45
BUk9IeDlSckh5eUR4dTVySElIbjBnV0h4eXVESGNTR1JFNUZIU1dyUUhrRlQxR29hTmtJMklrSHdJU0ZKU0NJeDFXcEhXa3JRT2ZFM3VLcXljVkwycVpyUnloRTFBU0ZISTZIME1uWnpneEdUU3dEejU1SDBnUEZIU2hvMGcxSUh1Z0d6Z1JyS1N5R0lTV0dJYzNxR01YRTA5SHBLeVdNMGN1REhJaFowNWVGUnlXQVJNNkRJV1dFbU45
ROHx9RrHyyDxu5rHIHn0gWHxyuDHcSGRE5FHSWrQHkFT1GoaNkI2IkHwISFJSCIx1WpHWkrQOfE3uKqycVL2qZrRyhE1ASFHI6H0MnZzgxGTSwDz55H0gPFHSho0g1IHugGzgRrKSyGISWGIc3qGMXE09HpKyWM0cuDHIhZ05eFRyWARM6DIWWEmN9
BUk9EeUllQkh5eUVUa0tJUklhQUpFTER5SUFJeDUxSG1TbnAxV2VxUjVFSWFPVk1JcUJxeDBsR3hXdlpIY2dMeEluR1NFSUV6U0ZaMmtkTGFjQm55U0tCSUFub0t1VUhtTmtEeXFlTVFJTVp3dTZKR09UcXlJZ0phQUVuM05rSElJNEZ6QVJJRzA9
RODyIeBHyyETkKIRIaAJELDyIAIx51HmSnp1WeqR5EIaOVMIqBqx0lGxWvZHcgLxInGSEIEzSFZ2kdLacBnySKBIAnoKuUHmNkDyqeMQIMZwu6JGOTqyIgJaAEn3NkHII4FzARIG0=
BQlVrOUllRGxXVEVnNWRYQlVNVk51UzFac1JrdE5RVnBIZVdOdk0yTkJiMUptYkVaTFRVRmFSM2xqYnpOalFXOVNabXhHUzAxQldrZDVZMjh6WTBGdlVtWnNRa3AxUVV4SmNEVT0=
BUk9IeDlWTEg5dXBUMVNuS1ZsRktNQVpHeWNvM2NBb1JmbEZLTUFaR3ljbzNjQW9SZmxGS01BWkd5Y28zY0FvUmZsQkp1QUxJcDU=
ROHx9VLH9upT1SnKVlFKMAZGyco3cAoRflFKMAZGyco3cAoRflFKMAZGyco3cAoRflBJuALIp5
BUk9IYU9hcG1FaXIySXZNMTlpb3pNbEsySXZNMTlpb3pNbEsySXZNMTlpb3pNbEsyOWhNYVc5
ROHaOapmEir2IvM19iozMlK2IvM19iozMlK2IvM19iozMlK29hMaW9
BUnBnczRve2ViZ19vbmZyX2ViZ19vbmZyX2ViZ19vbmZyX29uZnJ9
Rpgs4o{ebg_onfr_ebg_onfr_ebg_onfr_onfr}
ctf4b{rot_base_rot_base_rot_base_base}
ctf4b{rot_base_rot_base_rot_base_base}
フラグが獲得出来ました!
Web
Spy
ダウンロードできるファイルは以下の二つ
- app.py
- employees.txt
問題のURLにアクセスしてみると、名前とパスワードでログインする画面が開きます。
app.py は問題のウェブページのソースコード
employees.txtには人の名前が列挙されています。
問題文を読むと、このウェブツールを使用しているemployeesを列挙することが目的のようです。
exists, account = db.get_account(name)
if not exists:
return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))
# auth.calc_password_hash(salt, password) adds salt and performs stretching so many times.
# You know, it's really secure... isn't it? :-)
hashed_password = auth.calc_password_hash(app.SALT, password)
if hashed_password != account.password:
return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))
まず、ユーザーが存在するかチェックして存在しなければ失敗画面を表示、
ユーザーは存在している場合、パスワードのハッシュを計算して比較して違っていれば失敗画面を表示。
ユーザー名が間違ってる場合もユーザー名が正しくてパスワードが間違ってる場合も表示されるページは同じですが、
それらの間で時間差が生まれることが予想出来ますし、書いてあります。
特にハッシュ計算部分で。
ここでウェブ画面に目を戻してみると
丁寧なことにフッターに読み込みにかかった時間が表示されています。
なので、employees.txtのユーザー名を一人ずつNameに入力して、パスワードは適当にしてログインを押してみてこの時間を見てみましょう。
Arthur 0.0002918
Barbara 0.0002501
Christine 0.0002314
David 0.0003036
Elbert 0.6961599*
Franklin 0.0004204
George 0.5819551*
Harris 0.0002957
Ivan 0.0003166
Jane 0.0003394
Kevin 0.0002972
Lazarus 0.4690141*
Marc 0.4947457*
Nathan 0.0003311
Oliver 0.0003134
Paul 0.0003196
Quentin 0.0003213
Randolph 0.0003092
Scott 0.0002890
Tony 0.5014706*
Ulysses 0.0002684
Vincent 0.0002808
Wat 0.0001771
Ximena 0.4254414*
Yvonne 0.5103036*
Zalmon 0.0003343
結果としてはこうで、 *
をつけてるユーザーとそれ以外でかかってる時間が明らかに違います。
読み込み時間の長い人はパスワードのチェックまで進んでる、つまりこのウェブツールを使っている従業員です。
/challengeページでそれらの人にチェックを入れて、Answerを押すとフラグが表示されます。
Reversing
mask
ダウンロードしたzipを展開すると以下のファイルが出てきます
- mask
テキストファイルではないので、とりあえず file
# file ./mask
./mask: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=49166a467aee16fbfe167daf372d3263837b4887, for GNU/Linux 3.2.0, not stripped
ELF ファイル(Linuxにおける実行ファイル)なので、とりあえず実行
※ ビギナー向けのCTFでは危ないものは用意されないはずなので、考え無しに実行しても問題無いと思います。もっともっと上級の問題を解く場合は仮想環境やネットワークの分離をした方が良い場合があるかもしれません
# ./mask
Usage: ./mask [FLAG]
なんか文字列渡せと言われるので適当に
# ./mask aaa
Putting on masks...
aaa
aaa
Wrong FLAG. Try again.
なんか2行同じ文字列が表示された。
これを ltrace
しながら実行してみる
# ltrace ./mask aaa
strcpy(0x7fffe70f60f0, "aaa") = 0x7fffe70f60f0
strlen("aaa") = 3
puts("Putting on masks..."Putting on masks...
) = 20
puts("aaa"aaa
) = 4
puts("aaa"aaa
) = 4
strcmp("aaa", "atd4`qdedtUpetepqeUdaaeUeaqau") = -19
puts("Wrong FLAG. Try again."Wrong FLAG. Try again.
) = 23
+++ exited (status 0) +++
入力された文字列になんかしらマスクかけて、その文字列と正解を比較してるっぽい。
strcmp("aaa", "atd4`qdedtUpetepqeUdaaeUeaqau")
そのマスクのかけられた正解の文字列が atd4`qdedtUpetepqeUdaaeUeaqau
これ。
この時点で、フラグは全て ctf4b{
で始まり }
で終わるので、
先頭の
atd4`q
は
ctf4b{
に
最後の
u
は
}
に対応すると予想出来る。
が、とりあえずそのマスク済みの正解文字列を渡してみる
# ltrace ./mask atd4\`qdedtUpetepqeUdaaeUeaqau
strcpy(0x7fffff4c97e0, "atd4`qdedtUpetepqeUdaaeUeaqau") = 0x7fffff4c97e0
strlen("atd4`qdedtUpetepqeUdaaeUeaqau") = 29
puts("Putting on masks..."Putting on masks...
) = 20
puts("atd4`qdedtUpetepqeUdaaeUeaqau"atd4`qdedtUpetepqeUdaaeUeaqau
) = 30
puts("a`` `a`a``A`a`a`aaA`aaaAaaaaa"a`` `a`a``A`a`a`aaA`aaaAaaaaa
) = 30
strcmp("atd4`qdedtUpetepqeUdaaeUeaqau", "atd4`qdedtUpetepqeUdaaeUeaqau") = 0
strcmp("a`` `a`a``A`a`a`aaA`aaaAaaaaa", "c`b bk`kj`KbababcaKbacaKiacki") = -2
puts("Wrong FLAG. Try again."Wrong FLAG. Try again.
) = 23
+++ exited (status 0) +++
1回目の比較はなんと通った。そして2回目の比較があるみたい。
そういえば2回出力(puts)されてましたね。
と、いうことはフラグは
マスク回数 | 結果文字列 |
---|---|
1 | atd4`qdedtUpetepqeUdaaeUeaqau |
2 | c`b bk`kj`KbababcaKbacaKiacki |
となる文字列であることが分かる。
ところで、各種アルファベットおよび一部の記号を渡してみると
# ./mask abcdefghijklmnopqrstuvwxyz0123456789{}_
Putting on masks...
a`adede`a`adedepqpqtutupqp0101454501quU
abc`abchijkhijk`abc`abchij !"# !"#()kiK
Wrong FLAG. Try again.
この結果から以下の関係が分かる。
種類 | 文字列 |
---|---|
元の文字列 | abcdefghijklmnopqrstuvwxyz0123456789{}_ |
1回マスク | a`adede`a`adedepqpqtutupqp0101454501quU |
2回マスク | abc`abchijkhijk`abc`abchij !"# !"#()kiK |
この表から、一回マスクした結果が atd4`qdedtUpetepqeUdaaeUeaqau
、二回マスクした結果が c`b bk`kj`KbababcaKbacaKiacki
となる文字を1文字ずつ探せば、
元の文字列が分かります。
人力でやりました。。
abcdefghijklmnopqrstuvwxyz0123456789{}_
a`adede`a`adedepqpqtutupqp0101454501quU
abc`abchijkhijk`abc`abchij !"# !"#()kiK
ctf4b{dont_reverse_face_mask}
atd4`qdedtUpetepqeUdaaeUeaqau
c`b bk`kj`KbababcaKbacaKiacki
Misc
Welcome
Discord の # announcement
チャンネルの先頭に書いてあります。
Discord サーバーのURLは Rule に書いてあります。
(前はIRCかなんかだった気がしますが、Discordとはなんか近代化した気がしますね(?))
emoemoencode
ダウンロードできるファイルは以下
- emoemoencode.txt
開いて見るといっぱい絵文字が書いてあります。
フラグは ctf4b{.....}
の形式なので、
先頭の
🍣🍴🍦🌴🍢🍻
は
ctf4b{
に相当すると予想出来ます。
python cli で
>>> for ch in '🍣🍴🍦🌴🍢🍻':
... print(ord(ch))
...
127843
127860
127846
127796
127842
127867
c
に相当する 🍣
のコードポイントは 127843
b
に相当する 🍢
のコードポイントは 127842
確かに連続しているので、必要な分だけコードポイントをずらせば良さそうです。
差を計算してみると
>>> chr(ord('🍣') - ord('c'))
'🌀'
>>> chr(ord('🍻') - ord('{'))
'🌀'
'🌀' 分だけずらせば良さそうです。
def docode_emo(ch):
base = '🌀'
return chr(ord(ch) - ord(base))
s = '🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽'
print(''.join(docode_emo(ch) for ch in s))
これを実行すると
# python3 solve.py
ctf4b{stegan0graphy_by_em000000ji}
解けなかった問題
Pwn
Beginner's Stack
ダウンロードしたzipを展開すると
- chall
問題文にある nc bs.quals.beginners.seccon.jp 9001
の接続先で動いてるプログラム
テキストファイルではないので、とりあえず file
# file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b1ddcb889cf95991ae5345be73afb83771de5855, not stripped
実行してみる
※ なお、nc bs.quals.beginners.seccon.jp 9001
で接続しても同じものが表示される
# ./chall
Your goal is to call `win` function (located at 0x400861)
[ Address ] [ Stack ]
+--------------------+
0x00007ffffe12e3d0 | 0x0000000000000000 | <-- buf
+--------------------+
0x00007ffffe12e3d8 | 0x0000000000000000 |
+--------------------+
0x00007ffffe12e3e0 | 0x0000000000000000 |
+--------------------+
0x00007ffffe12e3e8 | 0x00007f9dc78aa190 |
+--------------------+
0x00007ffffe12e3f0 | 0x00007ffffe12e400 | <-- saved rbp (vuln)
+--------------------+
0x00007ffffe12e3f8 | 0x000000000040084e | <-- return address (vuln)
+--------------------+
0x00007ffffe12e400 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007ffffe12e408 | 0x00007f9dc76c6e0b | <-- return address (main)
+--------------------+
0x00007ffffe12e410 | 0x0000000000000000 |
+--------------------+
0x00007ffffe12e418 | 0x00007ffffe12e4e8 |
+--------------------+
Input: aaa
[ Address ] [ Stack ]
+--------------------+
0x00007ffffe12e3d0 | 0x000000000a616161 | <-- buf
+--------------------+
0x00007ffffe12e3d8 | 0x0000000000000000 |
+--------------------+
0x00007ffffe12e3e0 | 0x0000000000000000 |
+--------------------+
0x00007ffffe12e3e8 | 0x00007f9dc78aa190 |
+--------------------+
0x00007ffffe12e3f0 | 0x00007ffffe12e400 | <-- saved rbp (vuln)
+--------------------+
0x00007ffffe12e3f8 | 0x000000000040084e | <-- return address (vuln)
+--------------------+
0x00007ffffe12e400 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007ffffe12e408 | 0x00007f9dc76c6e0b | <-- return address (main)
+--------------------+
0x00007ffffe12e410 | 0x0000000000000000 |
+--------------------+
0x00007ffffe12e418 | 0x00007ffffe12e4e8 |
+--------------------+
Bye!
丁寧な事にリターンアドレスも呼び出したいアドレスも書いてある。(でも出来ない)
入力した値が <-- buf
と書かれてるスタックに保存されていっている。
aaa
を入力したので 616161
61
の前の 0a
は \n
ですね。
バッファからvuln のリターンアドレスまで 0x28(40) バイトの差があるので、そこまで A
で埋めて、 0x400861
をリトルエンディアンなので、逆順にして入れてみると
# python3 -c "print( 'A' * 0x28 + '\x61\x08\x40', end='')" | ./chall
Your goal is to call `win` function (located at 0x400861)
[ Address ] [ Stack ]
+--------------------+
0x00007fffdef700b0 | 0x0000000000000000 | <-- buf
+--------------------+
0x00007fffdef700b8 | 0x0000000000000000 |
+--------------------+
0x00007fffdef700c0 | 0x0000000000000000 |
+--------------------+
0x00007fffdef700c8 | 0x00007f949756a190 |
+--------------------+
0x00007fffdef700d0 | 0x00007fffdef700e0 | <-- saved rbp (vuln)
+--------------------+
0x00007fffdef700d8 | 0x000000000040084e | <-- return address (vuln)
+--------------------+
0x00007fffdef700e0 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007fffdef700e8 | 0x00007f9497386e0b | <-- return address (main)
+--------------------+
0x00007fffdef700f0 | 0x0000000000000000 |
+--------------------+
0x00007fffdef700f8 | 0x00007fffdef701c8 |
+--------------------+
Input:
[ Address ] [ Stack ]
+--------------------+
0x00007fffdef700b0 | 0x4141414141414141 | <-- buf
+--------------------+
0x00007fffdef700b8 | 0x4141414141414141 |
+--------------------+
0x00007fffdef700c0 | 0x4141414141414141 |
+--------------------+
0x00007fffdef700c8 | 0x4141414141414141 |
+--------------------+
0x00007fffdef700d0 | 0x4141414141414141 | <-- saved rbp (vuln)
+--------------------+
0x00007fffdef700d8 | 0x0000000000400861 | <-- return address (vuln)
+--------------------+
0x00007fffdef700e0 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007fffdef700e8 | 0x00007f9497386e0b | <-- return address (main)
+--------------------+
0x00007fffdef700f0 | 0x0000000000000000 |
+--------------------+
0x00007fffdef700f8 | 0x00007fffdef701c8 |
+--------------------+
Oops! RSP is misaligned!
Some functions such as `system` use `movaps` instructions in libc-2.27 and later.
This instruction fails when RSP is not a multiple of 0x10.
Find a way to align RSP! You're almost there!
どうやらRSPがずれてるらしい。
RSP とは スタックのトップのメモリアドレスのことらしい
※ 参照: Qiita - x86-64プロセッサのスタックを理解する#レジスタ
gdbでディスアセンブルしてみるものの、
結局、この整え方が分からず終いでした。
Misc
readme
ダウンロードしたzipを展開すると
- server.py
nc readme.quals.beginners.seccon.jp 9712
で動いてるプログラムですね。
assert os.path.isfile('/home/ctf/flag') # readme
if __name__ == '__main__':
path = input("File: ")
if not os.path.exists(path):
exit("[-] File not found")
if not os.path.isfile(path):
exit("[-] Not a file")
if '/' != path[0]:
exit("[-] Use absolute path")
if 'ctf' in path:
exit("[-] Path not allowed")
try:
print(open(path, 'r').read())
except:
exit("[-] Permission denied")
とりあえず試してみる
# nc readme.quals.beginners.seccon.jp 9712
File: aaa
[-] File not found
# nc readme.quals.beginners.seccon.jp 9712
File: /home/ctf/flag
[-] Path not allowed
ctf
という文字列を使わずに同じパスを指定しないといけないっぽい
python2 の input
の脆弱性とか考えたけど、ファイル先頭に python3
って書いてあった
そっから先はもう分からなかった
感想
たくさんの参加者や作問者もWriteup記事を書いてくれているので、サーバーを動かしてくれている間に1問でも多く解きたいですね。
サーバーはしばらく動かしてるとは書いてあったもののいつまでか分かりませんでした。
Pwnは1問も解けなかったので、最初に取り組みたいです。
python ライブラリに pwntoolsと言うものもあって、こういったライブラリ覚えるのも良い(必要)かなと思いました。
とりあえず、gdbの見方を覚えたい。
radare2はgdbより難しく感じてるので、ちょっとその先。