Midnight Sun CTF 2018 Finalsに参加した感想と記録です。
まえがき
こんにちは、技術部の松隈です。
ネットワーク診断やバイナリ系のものが関わる診断に携わっています。すこしだけバイナリ系のexploitationに興味があります。
週末には業務兼趣味としてCTF(Capture The Flag、コンピュータセキュリティに関連するスキルの競技で、問題を解いて"flag"というキーワードを手に入れることで得点を得ます)に参加したりしていなかったりします。
競技ではWeb、バイナリ、暗号など様々なジャンルに渡って出題されますが、私は主にPwnというバイナリの脆弱性を攻撃するジャンルを担当しています。
先日、いつものようにCTFの参加に関する出張申請をしたためていたところ、
「いやさあ、、きみ何度も海外CTF参加してるし、、、さすがにそろそろブログ記事とかでアウトプットとかね…(。ŏ﹏ŏ)」
というお気持ちを頂戴してしまいました。
たしかにここ数年を振り返ってみると、幾度となく海外CTFの参加を出張にしてもらっていたにもかかわらず、ブログでのアウトプットはゼロでした。記事のネタがあるのなら書くべきですね。。
…というわけで、肩書きが「技術部 穀潰し」になってしまう前に先日参加したMidnight Sun CTF 2018 Finalsの参加記を投稿したいと思います。
Midnight Sun CTF 2018 Finals
6月16日(現地時間)から2日間、スウェーデン/ストックホルムで 開催された Midnight Sun CTF 2018 Finals に、チーム TokyoWesterns として参加しました。
学生の部と社会人の部の二部構成となっており(ただし問題は共通)、わたしたちは社会人枠として出場しました。
最終的なランキングは下のようになります。TokyoWesterns は 🥉3 位 でした。
日本から参加していた scryptos は、惜しくも 🏆1 位 の LC↯BC と僅差で 🥈2 位 でした。終了1分前の逆転劇は本当にすごかったです…!
Place | Team Name | Score | Student Team? |
---|---|---|---|
1 | LC↯BC | 6687 | ✘ |
2 | scryptos | 6660 | ✘ |
3 | TokyoWesterns | 5239 | ✘ |
4 | p4 | 4874 | ✘ |
5 | Made In MIM | 3260 | ✔ |
6 | RedRocket | 2582 | ✔ |
7 | dcua | 2517 | ✘ |
8 | GYG | 2244 | ✘ |
9 | /upb/hack | 2098 | ✔ |
10 | Perfect Blue | 1892 | ✘ |
11 | KTHCTF0x1 | 1214 | ✔ |
12 | showeremoji | 1005 | ✔ |
13 | VSS | 883 | ✔ |
14 | LiUhack | 870 | ✔ |
解いた問題の解説
競技中にバグを探すなどの手伝いをしたものが以下の通りで、
- Gissa (Pwn: 468pts)
- Flitbip (Pwn: 485pts)
- Onion browser (Pwn: 468pts)
個人としては次の問題が解けました。
- Onion browser (Pwn: 468pts)
Onion browser (Pwn: 468pts)
まずは、CTFで問題を解くときの基本の問題文です(Pwnの場合はあまり関係ないですが。。)
Onion Browser is a minimal and super secure browser! Fortunately, our boys at FRA/NSA left a backdoor in the source code. If only we knew someone skilled enough to exploit it :(
Solves: 4
Service: nc pwn.midnightsunctf.se 31337 | nc 34.254.34.57 31337
Download: onionbrowser.tar.gz
Author: likvidera
ミニマルなブラウザをexploitするチャレンジみたいですね。
詳細なwrite upは別の記事で載せるため(あくまでも「つもり」です、強靭な気力があればいつかきっと。。。)、ここでは簡単な流れの解説のみを行います。
概要
Onion browserは、JavaScriptエンジン(以下、「JSエンジン」とします)に埋め込まれた脆弱性をexploitするタイプのPwn問題(以下、「js pwn」と呼びます)です。js pwnの問題は、ここ最近の大きめのCTFではちょくちょく出題されている形式で、大抵は主要ブラウザの大規模JSエンジン(V8やSpiderMonkeyなど)がターゲットになります。今回のターゲットはWebブラウザを模したx86 ELFバイナリで、JSエンジンにはduktapeが採用されていました。
このバイナリの挙動は、
- ユーザーが指定したURLに対して
libcurl
でアクセスしてリソースを取得 - アクセス先のURLの拡張子によって次の処理を行う
.html
の場合: 取得したリソースをレンダリングをせずに表示.js
の場合: 取得したリソースをduktapeで実行した結果を表示- それ以外の場合: 対応していない旨のエラーを表示
というものでした。
つぎに、ターゲットのバイナリに施されている保護機構を示します。
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
No RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 21 (censored)
続いて、問題の脆弱性の解析とそのexploitについて軽く解説します。
解説
js pwnの問題には、脆弱性を埋め込むためのパッチファイルが添付されています。題意としては「脆弱性を作り込むパッチが当たった状態のJSエンジンをexploitしなさい」というものです。Onion browserにも次のようなパッチが用意されていました。
diff --git a/duktape/src-input/duk_bi_buffer.c b/duktape_patch/src-input/duk_bi_buffer.c
index 9b2f6d7..ac78ea2 100644
--- a/duktape/src-input/duk_bi_buffer.c
+++ b/duktape_patch/src-input/duk_bi_buffer.c
@@ -1591,6 +1591,24 @@ DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_copy(duk_hthread *thr) {
* See test-bi-typedarray-proto-set.js.
*/
+#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
+DUK_INTERNAL duk_ret_t duk_bi_typedarray_midnight(duk_hthread *thr) {
+ duk_hbufobj *h_this;
+ h_this = duk__require_bufobj_this(thr);
+ DUK_ASSERT(h_this != NULL);
+ DUK_ASSERT_HBUFOBJ_VALID(h_this);
+
+ if (h_this->buf == NULL) {
+ DUK_DDD(DUK_DDDPRINT("source neutered, skip copy"));
+ return 0;
+ }
+ h_this->length = 31337;
+ duk_hbuffer * buf = h_this->buf;
+ buf->size = 31337;
+ return 0;
+}
+#endif
+
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
DUK_INTERNAL duk_ret_t duk_bi_typedarray_set(duk_hthread *thr) {
duk_hbufobj *h_this;
diff --git a/duktape/src-input/builtins.yaml b/duktape_patch/src-input/builtins.yaml
index d4c4225..0e79be8 100644
--- a/duktape/src-input/builtins.yaml
+++ b/duktape_patch/src-input/builtins.yaml
@@ -974,6 +974,7 @@ objects:
id: bi_array_constructor
attributes: "wc"
present_if: DUK_USE_ARRAY_BUILTIN
+
- key: "toString"
value:
type: function
@@ -4071,6 +4072,12 @@ objects:
typedarray: true
es6: true
present_if: DUK_USE_BUFFEROBJECT_SUPPORT
+ - key: "midnight"
+ value:
+ type: function
+ native: duk_bi_typedarray_midnight
+ length: 0
+ present_if: DUK_USE_BUFFEROBJECT_SUPPORT
- key: "subarray"
value:
type: function
さて、このパッチを読むとターゲットのduktapeでは、
- TypedArrayに対して
midnight()
というメソッドが実装されている- 対象のTypedArrayの
size
、これに対応するArrayBufferのlength
フィールドをそれぞれ31337
に設定する
- 対象のTypedArrayの
ということがわかります。
ちなみにTypedArrayによる値の読み書きは、内部的に対象のTypedArrayに対応するArrayBufferのindex
番目に対してta.BYTES_PER_ELEMENT
分の読み書きを行います。
midnight()
を呼び出した場合、実際に確保されているサイズはそのままの状態で、対象のTypedArrayのsize
と当該ArrayBuffer
のlength
が書き換わってしまいます。例えばそのArrayBuffer
の元々のlength
が更新後の値よりも小さい場合は、領域外のアクセス(Out-of-bound Read/Write: OOB R/W)が可能となります。Onion browserで問われている脆弱性はこれでした。
以下に脆弱性を再現するためのPoCを示します。
let ta = new Uint32Array(1); // Any type is Ok :)
console.log(ta.byteLength); // => 4
ta.midnight(); // Update the length of `ta`'s internal buffer to 31337
console.log(ta.byteLength); // => 31337
とても意図的でシンプルな脆弱性ですね。このようなArrayBuffer
に関するOOB R/Wは強力なものですが、exploitationの目的である"/bin/sh"の起動の実現をするにはまだすこし弱いです。
さて、オーバーフローの発生する箇所の後方に任意のオブジェクトを配置可能な場合の攻略方法として、TypedArrayのサイズに関するフィールドを上書きして任意アドレスの読み書き(Arbitrary Address Read/Write: AAR/AAW)を実現するというものがあります。
この脆弱性をexploitして、OOB WriteのあるArrayBuffer
に隣接する他のTypedArrayと当該ArrayBuffer
のlength
を(1<<32) / ta.BYTES_PER_ELEMENT
に上書きすることで、当該プロセスの全メモリ空間で読み書きをすることが可能となります。
実際にAAR/AAWを実現するためには、書き込み対象のアドレス(targetAddress
)が上書き対象のTypedArrayのArrayBuffer
の存在するアドレス(addressOfArrayBuffer
、OOB Readにより取得・計算可能)よりも小さい場合は、addressOfArrayBuffer
をオフセットとして考えてtargetAddress += (1<<32) - addressOfArrayBuffer
を行ってから書き込む必要があります。
以上でAAR/AAWが手に入りました。あとは書き込み先を考えるだけです。先に述べた保護機構の情報から以下のことがわかります。
- RELRO: No RELRO
- RELRO(RELocation Read-Only)がないためGOTの領域が書き込み可能である。GOT OverwriteによりEIPの奪取が容易に実現可能である
- PIE: No PIE
- PIE(Position Independed Executable; 位置独立実行形式)ではない。バイナリのロードされるアドレスは固定であるため、書き込み対象のアドレスをリークする必要はない
今回は次の手順で攻略しました。
- 脆弱性の発火により得たOOB R/Wを利用して、AAR/AAWを実現する
- AARよりGOTのエントリから適当なlibc上のアドレスをリークして、
__libc_system
のアドレスを計算する - AAWより、第1引数が制御可能なlibc関数のGOTのエントリを(1)で求めた
__libc_system
のアドレスに上書きする - 第1引数に
"/bin/sh"
を設定した状態で上書きした関数を呼ぶことでシェルを起動する
あとがき
まずは個人的な感想ですが、久々の決勝参加と初のヨーロッパで純粋によかったです!また、最近はCTFでflagのサブミットがめっきりできていなかったのもあって、Onion browserに時間はかけてしまったものの解けた点に関しては安心しました。
チームとしては、上位に入ることができたのでよかったなあと思っています。ただ、Onion browserが解けたあとで集中が切れてしまい、結果として1問だけしか解けていないという残念な結果になったため個人としての得点への貢献は薄くなってしまいました。次のCTFではうまいことテンポよく解いていきたいと思います。
また、Onion browserの詳細なwrite upも近々(…🤔?)投稿するつもりですので、どうぞお楽しみに!