# Malware Analysis at Scale #### ~ Defeating EMOTET by Ghidra ~ ###### 株式会社サイバーディフェンス研究所 中島将太 トレンドマイクロ株式会社 原弘明 ----- #### > whoami ###### Shota Nakajima Hiroaki Hara Ø 株式会社サイバーディフェンス研究所でマルウ ェア解析、インシデントレスポンス業務、脅威 情報の収集・分析業務に従事。 Ø JSAC、HITCON CMT、AVAR、CPRCon、Black Hat EUROPE Arsenal、CodeBlue BlueBoxなど で発表経験あり。 ###### Shota Nakajima Ø 技術系同⼈サークルAllsafeのプロデューサー。 Ø トレンドマイクロ株式会社にて、マルウェア 解析やインシデントレスポンス、スレットリ サーチなどに従事。 Ø 技術系同⼈サークルAllsafeの澤村・スペンサ ー・英梨々(アートディレクター) 。 ###### Hiroaki Hara ----- #### Malware Analysis at Scale ###### テーマ: いかにして楽に解析を終わらせるか n 解析者の現状として、次のような課題がある ü 静的解析は時間がかかり、根本的につらい ü 膨⼤な数のマルウェアを解析する必要がある ü ⼿動での解析は現実的に不可能 ###### 解析の⾃動化(スケ ルする解析)をめざす ###### テーマ: いかにして楽に解析を終わらせるか n 解析者の現状として、次のような課題がある ü 静的解析は時間がかかり、根本的につらい ü 膨⼤な数のマルウェアを解析する必要がある ü ⼿動での解析は現実的に不可能 ----- #### Target n マルウェア解析経験者 - C⾔語、アセンブリ⾔語を理解している - Windowsのプログラミングについて知識がある - IDA, Ghidraを使ってマルウェアの静的解析をしたことがある n Pythonでのプログラミング経験を有する者 - Pythonの基本的な⽂法がわかる ----- ###### TABLE OF CONTENTS ###### TABLE OF CONTENTS 01 Basic of Ghidra n Ghidra Usage n Ghidra Script ###### 02 Automated Analysis of EMOTET n Surface Analysis n API Hashing and Dynamic Call n Decrypt strings n Config Structure Analysis ----- ## 01 # Basic of Ghidra ----- #### What is Ghidra? ###### ソフトウェアリバースエンジニアリングツールセット - 2019年にNSAによってOpen Source Softwareとして公開 - https://github.com/NationalSecurityAgency/ghidra - フリーでありながら、豊富な機能を提供 ----- #### Ghidra's Basic Features 複数ユーザによる共同作業を サポ ト GDB/WinDBGをバックエンド としたデバ グ機能を提供 #### Ghidra's Basic Features ###### Disassembler/Decompiler Ghidra Script Ghidra Extension 多様なアーキテクチャに対応した Ghidraの各種機能をスクリプト ⾃作プラグインによる ディスアセンブラに加え、 (Java/Python)から利⽤可能 機能拡張をサポート 擬似C⾔語へ変換するデコンパイラ機能 ###### Headless Analyzer Ghidra Server Debugger CLI経由での操作 複数ユーザによる共同作業を GDB/WinDBGをバックエンド ----- # Ghidra Usage ----- #### Code Browser Program Tree - セクション名など を表⽰ Source Tree - インポート/エクス ポート関数、関数 名、クラス名など などを列挙 Data Type Manager - 独⾃構造体などの データ型を管理 ###### Console Listing View Decompile View - ディスアセンブル・データを表⽰ - Listing Viewのデコンパイル結果を表⽰ Program Tree - セクション名など を表⽰ Source Tree - インポート/エクス ポート関数、関数 名、クラス名など などを列挙 Data Type Manager - 独⾃構造体などの ###### Console ----- #### Other Windows n Bookmarks n Bytes - HEXエディタ n Defined Strings n Function Graph n Python - Pythonインタプリタの起動 n Script Manager n Bookmarks - 特定のアドレスに対し、名前をつけてブックマークとして保存 n Bytes - HEXエディタ n Defined Strings - プログラム内で定義された可読⽂字列 n Function Graph - ディスアセンブル結果をグラフ形式で表⽰ n Python - Pythonインタプリタの起動 n Script Manager ----- #### Name n Ghidraではバイナリのロード時に⾃動解析して関数や変数に名前を付ける - 関数(FUN_*) - ⽂字列(s_*) - ローカル変数(local_*) - 変数(*Var*) ----- #### Change Name n リネームして解析結果を反映することで、Ghidraを読みやすくしていく n 右クリックメニューから選択 ----- #### Change Function Signature n 関数の定義を変更する ----- #### XREF(Cross References) n データの参照元の表⽰ ----- #### Change Data Type n 関数や変数の型の変更 - 右クリックメニューから選択 ----- # Ghidra Script ----- #### Ghidra Script ###### Ghidraの各種操作を⾃動化・効率化する機能 n こんなことができる ü バイト列の検索やコメントの付与など、⼿動作業の⾃動化 ü ⼤量のファイルのインポート・解析処理など、反復作業の⾃動化 ü 暗号化されたデータや⽂字列の復号など、解析の⾃動化 ü エミュレータによる実⾏ ----- #### Ghidra API n スクリプトからGhidraの機能にアクセスするためのAPI n JavaとPython (Jython2.7) で実装が可能 ###### Python実装の例 ----- #### Type of Ghidra API n Ghidra APIは⼤きく3つに分類でき、次のような階層構造になっている - ほとんどの場合でFlatProgram API/Script API さえ理解していれば事⾜りる - なにか必要な機能があったらまずはFlatProgramAPIのドキュメントを検索する ###### High Layer Script API https://ghidra.re/ghidra_docs/api/ghidra/app/script/GhidraScript.html ###### FlatProgram API https://ghidra.re/ghidra_docs/api/ghidra/program/flatapi/FlatProgramAPI.html ###### Program API Low Layer https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Program.html ----- #### Script Manager n Ghidra Scriptの実⾏や管理を⾏う機能 Script Managerの起動⼿順 ----- #### Built in Python Interpreter n ビルトインのPythonインタプリタが提供されている - メニューの「Window」> 「Python」で起動 ----- #### Why Interpreter? ###### 検証のためのトライアンドエラーに便利 #### Test Write Code Analysis ----- #### Why Interpreter? Pt.1 n 変数内部のデータを都度確認しながら処理を記述できる ----- #### Why Interpreter? Pt.2 n コード補完してくれる - ⼊⼒中にTabを押すと利⽤可能なメソッドを表⽰してくれる ----- #### Why Interpreter? Pt.3 **← Listingインター** **フェイスの実装** n APIのヘルプが参照できる **クラスだとわかる** - help に参照したいオブジェクトを渡す インターフェイス名がわかれば検索できる **←公開メソッドの** **シグネチャ** ----- #### Interpreter Tips import n 複数⾏にわたるペーストができないので、関数などがコピー&ペーストできない n なので、使いたい関数やクラスをスクリプト内で定義しておき、import する - import で読み込めるファイルはScript Directories配下に存在しているファイルのみなので、 今回はC:¥Ghidra¥ghidra_scripts配下に配置する - Script Directoriesに登録された別フォルダに同名のファイルが存在するとエラーの原因になるので、 ユニークにする **←get_all_functions関数が** **現在の名前空間に存在しない** **のでエラー** ----- #### Interpreter Tips import n ただし、そのままだとインポートされたスクリプト側からGhidra APIを参照できないので、次の ###### import⽂を必ずスクリプトの最初に記述しておく!(重要) ----- #### Interpreter Tips reload n すでにimport済みのスクリプトを編集した後再度importするには、reload を使う - 例えば、⼀度importしたがエラーが出たのでスクリプト内の関数を書き換えた場合など - Pythonは⼀度importしたコードをコンパイルしてキャッシュしているので、importだけでは更新が反映さ れないのが原因 ###### 編集された関数 ←この後のタイミングで編集 ←reloadで編集を反映 ----- #### Interpreter Tips reload n ただし、from name import * でimportしている場合、↓のようにsys.module['name']を ###### reloadしてから再インポートする必要がある 編集された関数 ←この後のタイミングで編集 ←reload ←再インポート ----- #### Headless Analyzer ###### CLIからGhidra Scriptを操作するための機能 n ユーザインタラクションが不要なので、バッチ処理が可能 n バッチ処理により、ファイルのインポートや解析処理の⾃動化が可能 ----- # Ghidra API Quick Reference ----- #### FlatProgramAPI Class ###### Program APIのラッパークラス - https://ghidra.re/ghidra_docs/api/ghidra/program/flatapi/FlatProgramAPI.html ###### Fields n protected Program currentProgram ° 現在読み込んでいるプログラムに関する様々な情報を保持 するフィールド, Program APIを提供する Methods n Address[] findBytes(Address start, java.lang.String byteString, n Instruction getInstructionBefore(Address address) int matchLimit) ° 指定したaddressの⼀つ⼿前(低位アドレス)の命令を取得, ⼀つ先(⾼位 アドレス)の命令を取得するgetInstructionAfterもある ° 指定したバイト列(Python2では⽂字列と同じ)を検索してヒットしたアド レス⼀覧を返す n Function getFunctionContaining(Address address) n byte[] getBytes(Address address, int length) ° 指定したaddressを含む関数を取得 ° 指定したaddressから指定したlength分をバイト列として取得 n Reference[] getReferencesTo(Address address) n Function getFirstFunction() ° 指定したaddressの参照元(=addressを参照しているオブジェクト)⼀覧を 取得 ----- ##### 01. Address Manipulation ##### 01. Address Manipulation n Address toAddr(int offset) n Address toAddr(long offset) n Address toAddr(String address) ° 指定した値をAddressオブジェクトに変換 ###### 数値や⽂字列をAddressオブジェクトに変換し、⽐較 ----- #### Address Interface ###### アドレスを表現するインターフェイス - https://ghidra.re/ghidra_docs/api/ghidra/program/model/address/Address.html ###### Methods n Address add(long displacement) ° アドレスを引数displacementのサイズ分加算し、 新しいAddressを返す オフセット値の取得 n boolean equals(java.lang.Object o) ° 引数oとアドレスを⽐較 n long getOffset() ° アドレスのオフセットをlongで返す n Address next() ----- ##### 02. Binary Manipulation ##### 02. Binary Manipulation n byte[] getBytes(Address address, int length) ° 指定したaddressから指定したlength分をバイト列として取得 n int getInt(Address address) ° 指定したaddressから4バイトをintとして取得 ###### 特定のアドレスから、指定したサイズ分バイト列の読み出し ----- ##### 03. Search ##### 03. Search n Address find(java.lang.String text) n Address[] findBytes(Address start, java.lang.String byteString, int matchLimit) ° プログラム内のコメントやラベル、コードユニットのやオペラン ドなどから、引数のtextで与えられた⽂字列を検索 ° 引数startで指定したアドレス以降のメモリから、byteStringで指定 ° 最初にマッチした結果のアドレスが返される された値を検索 ° このメソッドは、byteStringで正規表現を使うことも可能、例えば n Address find(Address start, byte[] values) 任意の4バイトをマッチさせたい場合「.{4}」といった表現を使⽤可能 ° 第1引数にAddressオブジェクトが渡されると、引数startで指定し たアドレス以降のプログラムのメモリからvalueで指定したバイト 列を検索し、最初にマッチした結果のアドレスを1つだけ返す ° 正規表現はサポートされていない ###### レジスタへの MOV命令を検索 ----- ##### 04. Function Manipulation ##### 04. Function Manipulation n Function getFirstFunction() n Function getFunctionBefore(Address address) ° 現在のプログラムの最初の関数オブジェクトを取得 ° 引数addressで指定したアドレスで始まる関数の前の関数オブジ ェクトを取得 n Function getFunctionAt(Address entryPoint) n Function getFunctionContaining(Address address) ° 引数entryPointで指定したアドレスから始まる関数オブジェクト を取得 ° 引数addressで指定したアドレスが関数のアドレスレンジに含ま れる場合、その関数オブジェクトを取得 n Function getFunctionAfter(Address address) ° 引数addressで指定したアドレスで始まる関数の次の関数オブジ ェクトを取得 ###### プログラム内の 全関数を列挙 ----- #### Function Interface ###### 関数を表現するインターフェイス - https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Function.html ###### Methods n Address getEntryPoint() ° 関数のエントリーポイント(先頭アドレス)を取得 n java.lang.String getName() ° 関数の名前を取得 n Parameter[] getParameters() ° 関数の引数をParameterオブジェクトで取得 ----- ##### 05. Instruction Manipulation ##### 05. Instruction Manipulation n Instruction getFirstInstruction(Function function) ° 引数functionで指定した関数オブジェクトに含まれる、最初の命 令を取得 n Instruction getInstructionAt(Address address) ###### 特定の関数内の全ての命令を取得 ° 引数addressで指定したアドレスの命令を取得 n Instruction getInstructionAfter(Instruction instruction) ° 引数instructionで指定した命令の次(⾼位アドレス)の命令を取 得 n Instruction getInstructionBefore(Instruction instruction) ° 引数instructionで指定した命令の1つ前(低位アドレス)の命令 を取得 ° 関数呼び出しを⾏うCALL 命令に対する引数をたどる際などに利 ⽤可能 ----- #### Instruction Interface ###### 命令を表現するインターフェイス - https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Function.html ###### Methods n java.lang.Object[] getOpObjects(int opIndex) ° 命令に渡されているオペランドのうち、opIndexで指定されたインデックスのオブジェクト(Address, Scalar など)⼀覧を取得 n java.lang.String getMnemonicString() ° 正確にはCodeUnitインターフェイスのメソッド ° 命令のニーモニックを⽂字列として取得 n PcodeOp[] getPcode() ° 命令をP-Codeという中間表現に変換 n Register getRegister(int opIndex) ----- ##### 06. Reference Manipulation ##### 06. Reference Manipulation ###### Ghidraはデータやコードなどのオブジェクト間の関係性を「参照」という情報として保持 後方参照 n Reference[] getReferencesTo(Address address) ° 引数addressで指定したアドレスへの参照を持つコードユニッ トのアドレス⼀覧を取得 ° つまり、後⽅参照(クロスリファレンス)を取得 n Reference[] getReferencesFrom(Address address) ----- #### Reference Interface ###### 参照を表現するインターフェイス - https://ghidra.re/ghidra_docs/api/ghidra/program/model/symbol/Reference.html ###### Methods n Address getFromAddress() ° 参照しているコードのアドレス、つまり後⽅参照のアドレスを取得 n Address getToAddress() ° 参照の「宛先」アドレス、つまり前⽅参照のアドレスを取得 n RefType getReferenceType() ° 参照のタイプを取得 ° 参照タイプ: https://ghidra.re/ghidra_docs/api/ghidra/program/model/symbol/RefType.html ----- ## 02 # Automated Analysis of EMOTET ----- #### Goals of Malware Analysis ###### 「解析」といっても⽬的(期待されるアウトプット)はさまざま 不正かどうかの判定、およびマルウェアのファミリ名やカテゴリを ###### Detection 特定する。この作業により、マルウェアの⽬的や関連する攻撃者を 認識することができる(可能性がある)。 ###### Indicator of ネットワークやホスト内において、侵害の有無を判断するための情 報を特定する。作成されるファイル名やレジストリ名、通信先情報 今回は通信先情報 ###### Compromise などが該当する。 **の抽出を⽬的とする** マルウェアの挙動・機能を含む解析レポート。Detection/IoCを含 ###### Report み、マルウェアの⽬的や背後の攻撃者について⾔及する。主に顧客 向けに⽂章で記述される。 ----- #### EMOTET n 近年猛威をふるう、情報窃取兼 ###### ダウンローダー型マルウェア - ⼤量のスパム経由で配布されるこ とが知られている NICTによるEMOTETスパムの着信数推移 - Human Operated Ransomware の起点となることもある n とにかく数が多い - ⼤量の亜種が短時間で配布される - かつそれぞれの検体が⼤量の C&C通信先をもつ https://blog.nicter.jp/2020/10/emotet-mail-202007-202009/ ----- #### How EMOTET works ###### EMOTET EMOTET Macro PowerShell Spam (Loader) (Payload) メール経由で侵⼊ マクロ付き PowerShellが Rundll32.exe C&Cサーバから ドキュメント 実⾏され、 経由で実⾏される ペイロードを取得し、 EMOTET (Loader) をDL オンメモリで実⾏される ----- #### Analysis Steps ###### ü どうや て⾃動抽出するか? ###### 以下のような点を解析によって明らかにし、スクリプトに落とし込む Anti- Config Packed? Analysis? Embeded? ü パックされているか? ü ⽂字列は難読化・暗号化 ü C&Cサーバなどの情報は されているか? どこに/どのような形式で ü パッカーのロジック 格納されているか? ü 鍵は? ----- ##### Determine Packing Logic and Do Unpack ##### Determine Packing Logic and Do Unpack ###### 他の多くのマルウェアと同じく、EMOTETもパックされており、メモリ上で ペイロードが復号されて実⾏される (ある時期の)EMOTETのアンパックロジック ----- ##### Determine Packing Logic and Do Unpack ###### アンパックの⼿法はさまざま n **⼿動アンパック** - デバッガーを使ってアンパック - 実⾏後、マルウェア⾃⾝によってアンパックされた ###### 領域をダンプ n **ツールによるアンパック** - マルウェアを実⾏、メモリ上からアンパック後の領 ###### 域を機械的に抽出 ü PE-SIEVE / Hollows Hunter n **⾃動アンパック** - ツールの⾃動実⾏ ##### Determine Packing Logic and Do Unpack ###### アンパックの⼿法はさまざま n **⼿動アンパック** - デバッガーを使ってアンパック - 実⾏後、マルウェア⾃⾝によってアンパックされた ###### 領域をダンプ n **ツールによるアンパック** - マルウェアを実⾏、メモリ上からアンパック後の領 ###### 域を機械的に抽出 ü PE-SIEVE / Hollows Hunter n **⾃動アンパック** - ツールの⾃動実⾏ ----- #### Hunter #### Hunter n パックされたEMOTETを実⾏し、Hollows Hunterでアンパック後のEMOTETをダンプ n 具体的な⼿順はAppendixを参照 ----- #### Unpacked EMOTET n 本ワークショップでは、アンパック済みのEMOTETを使⽤します ###### メイン解析⽤ ⽐較⽤ ----- # Step 1: Surface Analysis ----- #### Resources n 使⽤するサンプル等はJSAC参加者にのみSlackで共有済み C:¥Ghidra¥ghidra_scripts配下が 以下のような構成になっていればOK n 共有されたzipを展開し、以下⼿順を実施 **1.** **scripts: 配下のファイルをC:¥Ghidra¥ghidra_scripts配下に** コピー - scripts¥answers: 演習の回答なので、極⼒⾒ない **2.** **emotet.dll.gzf: Ghidraにドラッグ&ドロップでインポート** **3.** **emotet-2.dll.gzf: Ghidraにドラッグ&ドロップでインポ** **4.** **winapi_32.gdt: C:¥Ghidra直下にコピー** ----- #### Setup (sample) n 配布したemotet.dll.gzfをGhidraの既存プロジェクトにドラッグ&ドロップしてインポート - 途中ポップアップが出たら「OK」 ----- #### Setup (GDT) ロード完了後、winapi_32を 右クリックして n Ghidraの標準型データにはwininet.h、winhttp.hのAPI情報がない Apply Function Data Types - Data Type Managerでwinapi_32.gdtをロード 配布したファイルに含まれる winapi_32.gdtをロード - タンを押してOpen File Archive ----- #### Setup (scripts) ###### スクリプトの設置 n 以下スクリプトがC:¥Ghidra¥ghidra_scripts配下にコピーされているか念のため確認 1. utils.py: 演習等で再利⽤可能なユーティリティ関数群 2. search_hash_not_impl.py: 演習⽤⽳あきスクリプト 3. make_hash_db.py: APIハッシュ⽤DB作成スクリプト 4. db.json: APIハッシュ⽤DB 5. emulator.py: エミュレータを使ったハッシュ計算スクリプト 6. decrypt_string_stage1_not_impl.py: 演習⽤⽳あきスクリプト 7. decrypt_string_stage2_not_impl.py: 演習⽤⽳あきスクリプト 8. extract_c2_stage1_not_impl.py: 演習⽤⽳あきスクリプト 9. extract_c2_stage2_not_impl.py: 演習⽤⽳あきスクリプト ----- #### Exports Functions n DLL verのEmotetはrundll32.exeを使ってエクスポートされた関数から実⾏される ----- #### Brief Analysis (code) n Garbage Code n Control Flow Flatting - 実⾏に影響を及ぼさない無駄なコード - Control Flow Obfuscationの⼿法 - Ghidraのデコンパイラがいい感じに端折 - 直感的に命令フローが識別しづらい ってくれる ----- #### Brief Analysis (API/strings) n アンパック後のEMOTETは n APIに関連する⽂字列もなし ###### Import Tableが空 ○ API含む⽂字列が暗号化・難読化されている - 動的にAPIを解決している可能性 可能性 🥺 ----- #### Where to start? utils.pyに定義済みのget_func_xref_count関数を実⾏し、 呼び出し回数の多い関数を列挙 n APIの動的解決や⽂字列の ###### 復号に使⽤されている関数 は実⾏時に何度も呼び出さ れる n そのため、まずは各関数 ###### が呼び出されている回数を 調べて、呼び出し回数が多 い関数をみていくことにす る ----- ###### FUN_10017e12 n 最も多く呼び出されてい ###### る関数 n RETするだけのジャンク ###### 関数 n 「 _ 」など適当な名前 ###### に変えておく ----- ###### FUN_10007d5b ###### FUN_10007d5bの呼び出し例 (@0x10017e02) n 2番⽬に多く呼び出され ###### ている関数 n 4つの引数を受け取るが、 ###### 実際は第⼆引数は未使⽤ n DAT_1001fa20はグロー ###### バル変数の配列 **各要素が4バイトの配列?** **第⼀引数は配列のインデックス** ----- ###### FUN_10007d5b |BEFORE|AFTER (name)|AFTER (type)| |---|---|---| |DAT_1001fa20|g_array|int[0x64]| |param_1|index|N/A| |param_2|_|N/A| |param_3|hex_value1|N/A| |param_4|hex_value2|N/A| n 変数名・型名の変更 ###### 少し⾒やすくしたFUN_10007d5b BEFORE AFTER AFTER (name) (type) DAT_1001fa20 g_array int[0x64] param_1 index N/A param_2 _ N/A param_3 hex_value1 N/A param_4 hex_value2 N/A ----- # Step 2: API Hashing and Dynamic Call ----- #### How Windows APIs are resolved FARPROC MessageBoxA = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA"); M B A(NULL "H ll ld" "H ll " MB OK) n **暗黙的な解決** - コンパイル時に、必要なAPIがインポートテーブルに定義され、実⾏時にOSが暗黙的にDLLをロードし、 APIを解決する - 普通にコンパイルするとこの⽅法で解決される n **明⽰的な解決(動的API解決)** - LoadLibrary / GetProcAddress を⽤いて実⾏時に動的にAPIを呼び出す - API名から簡単に利⽤するAPIが識別されてしまう MessageBoxAを明⽰的に解決して呼び出す例 FARPROC MessageBoxA = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA"); ----- #### API Hashing n 動的にWindows APIを解決する際に⽤いられる耐解析⼿法の⼀つ n 既知、もしくは独⾃実装のハッシュ関数でAPI名をハッシュ化して保持しておき、実⾏時に、 ###### エクスポートされたAPI名を列挙してハッシュ関数の計算結果が同じAPIを動的に呼び出す 例: CRC32を使⽤してAPI名をハッシュ化し、実⾏時に⽐較して動的に解決 ###### コンパイル時 実⾏時 CRC32 CRC32 ReadFile 0xD0B7641A CreateFileA 0x553B5C78 kernel32.dll WriteFile 0x3E5BC8E3 擬似コード CreateFileA **0x553B5C78** ----- ###### FUN_10004ae6 n FUN_10002b48の呼び出し - in_FS_OFFSET (=FSレジスタ) へのアクセス - x86 の場合、FSレジスタには TEB (Thread Environment Block) へのポインタが格納さ れている n FUN_100180eaの呼び出し ----- ##### Process Environment Block ##### Process Environment Block n **Thread Environment Block (TEB)** - 現在のスレッドに関する情報を保持する構造体 FSレジスタ経由でTEBにアクセスし、 そこからPEBにアクセスしている - FSセグメントレジスタからアクセス可能 (x86 Windowsにおいて) n **Process Environment Block (PEB)** - 現在のプロセスに関する情報を保持する構造体 Ø プロセスに読み込まれているDLLや、 ヒープの状態、実⾏ファイルの情報など - TEB構造体のオフセット0x30からアクセス可能 ----- ##### Process Environment Block ##### Process Environment Block ----- #### Apply Structure n PEB構造体を事前定義したヘッダファイルをGhidraにインポート(すでに適⽤済み) - File > 「Parse C Source」> win_internal.hを選択>「Parse to Program」 ----- ###### FUN_10002b48 |BEFORE|AFTER (name)|AFTER (type)| |---|---|---| |FUN_10002b48|get_peb|PEB *| |in_FS_OFFSET|teb|TEB *| n PEBのポインタを取得する関数 - in_FS_OFFSETをAuto Create Structure ###### BEFORE AFTER AFTER (name) (type) FUN_10002b48 get_peb PEB * in_FS_OFFSET teb TEB * ----- ###### FUN_10004ae6 |iVar1|peb|PEB *| |---|---|---| |puVar3|current_module_entry|LDR_DATA_TABLE_ENTRY *| |puVar4|next_module_entry|LDR_DATA_TABLE_ENTRY *| |Col1|Col2| |---|---| ||戻り値をXORして引数と⽐較| iVar1 peb PEB * puVar3 current_module_entry LDR_DATA_TABLE_ENTRY * puVar4 next_module_entry LDR_DATA_TABLE_ENTRY * n 読み込まれているDLLの ###### ベースネームを列挙し、 FUN_100180eaに渡して いる n FUN_100180eaの戻り値 ###### を0x1fc325daとXOR **プロセスに読み込まれているDLLの列挙** n その結果を引数の値と ###### ⽐較 **DLLのベースネーム(ファイル名)を渡している** **戻り値をXORして引数と⽐較** ----- ###### FUN_100180ea 1. この関数はハッシュ関数です、 アルゴリズムを解析してくだ さい 2. 実際はFUN_100180eaの戻り 値を0x1fc325daとXORして ⽐較していました。よって、 次のように、DLLベースネ ームとXOR鍵の⼊⼒を受け付 けて適切なハッシュ値を返す 関数(calc_hash)をGhidra Scriptで実装してください ----- ###### FUN_100180ea n BaseDllName.Bufferの ###### 型はwchar_t * (=unsingned short *) n 1バイトずつ以下処理 - ⼤⽂字ASCII(0x40 < x < 0x5b)の場合、⼩⽂字 (+0x20)に変更 - 結果を状態(iVar2)に加算 し、0x1003fを乗算 n 結果のint値を返す ----- ###### FUN_100180ea n DLLベースネームとXOR鍵を受け取り、ハッシュ値を返す関数 ----- ###### FUN_10004ae6 |param_1|hashed_dllname|N/A| |---|---|---| |uVar1|hashed|N/A| |FUN_10004ae6|find_lib|N/A| param_1 hashed_dllname N/A uVar1 hashed N/A n calc_hash_case_insensit FUN_10004ae6 find_lib N/A ive関数の呼び出し元に戻る n これまでの結果から、DLL名 をハッシュ化した値を受け取 り、ロード済みDLLの中から 該当するDLLを探してベース アドレスを返す関数だとわか る - ベースアドレス: メモリに ロードされているDLLのPE ヘッダへのポインタ n 関数名を「find_lib」に ###### 変えておく ----- #### PE Header Overview ###### Windows実⾏可能形式(PE)の ファイルフォーマット n 実⾏ファイルに関する様々な 情報を保持している - **Export Table** Ø エクスポート関数⼀覧 - **Import Table** Ø 使⽤するAPI⼀覧 ----- ###### FUN_10012794 ###### なにもわからない ----- ###### FUN_10012794 n エクスポート関数の⼀覧を保持する エクスポートテーブルにアクセスしている 型を適⽤すると都度変数名が変わってしまうので、変更”前” BEFORE AFTER (name) AFTER (type) param_2 base_address N/A param_4 hashed_api N/A iVar1 nt_headers IMAGE_NT_HEADER32 * pcVar8 export_table IMAGE_EXPORT_DIRECTORY * iVar2 functions_rva N/A iVar3 names_rva N/A iVar4 ordinals_rva N/A uVar5 value N/A uVar6 i N/A |BEFORE|AFTER (name)|AFTER (type)| |---|---|---| |param_2|base_address|N/A| |param_4|hashed_api|N/A| |iVar1|nt_headers|IMAGE_NT_HEADER32 *| |pcVar8|export_table|IMAGE_EXPORT_DIRECTORY *| |iVar2|functions_rva|N/A| |iVar3|names_rva|N/A| |iVar4|ordinals_rva|N/A| |uVar5|value|N/A| **DLLのベースアドレス、つまり** **IMAGE_DOS_HEADERへの** **OptionalHeader.DataDirectoryは** n エクスポート関数の⼀覧を保持する **ポインタが引数に渡されている** **さまざまなデータを格納する連想配列で** エクスポートテーブルにアクセスしている **インデックス0にはExport Tableへの** **オフセットが格納されている** 型を適⽤すると都度変数名が変わってしまうので、変更”前”の変数名で表記 **エクスポート関数のRVAの配列** BEFORE AFTER (name) AFTER (type) **エクスポート関数名のRVAの配列** **エクスポート関数の序数のRVAの配列** param_2 base_address N/A **エクスポート関数名を列挙** param_4 hashed_api N/A iVar1 nt_headers IMAGE_NT_HEADER32 * **エクスポート関数のアドレスを取得** pcVar8 export_table IMAGE_EXPORT_DIRECTORY * **エクスポート関数のアドレスが** iVar2 functions_rva N/A **フォワードされたものか確認** iVar3 names_rva N/A iVar4 ordinals_rva N/A uVar5 value N/A ----- ###### FUN_10012794 n 列挙したエクスポート関数 ###### をFUN_1000df50に渡す n 戻り値を0x5a80eaeとXOR n XORした結果を第四引数と ###### ⽐較 n 合致した場合、対象APIの ###### アドレスを返す n Export ForwardされたAPIは ###### 別途APIを解決 ※ f dと n 列挙したエクスポート関数 ###### をFUN_1000df50に渡す n 戻り値を0x5a80eaeとXOR n XORした結果を第四引数と ###### ⽐較 n 合致した場合、対象APIの ###### アドレスを返す n Export ForwardされたAPIは ###### 別途APIを解決 ----- ###### FUN_1000df50 n API名が渡される関数 n ほぼ ###### calc_hash_case_insen sitiveと同じ(⼩⽂字 変換処理がない) ----- #### Calcurate Hash FUN_10012794関数内にハードコードされたXOR鍵 n Pythonで実装 呼び出し元の例 (@0x10017e02) 合致している ----- #### (Advanced) Calcurate Hash by Emulator n アルゴリズムの実装がわからなくても、Emulatorで実⾏してしまうことも可能 ###### 1. (エミュレータ内の)ECXに⽂字列を格納(=引数に渡す) 2. 0x1001285aから0x10012864まで実⾏ 3. EAXに格納されている値(=ハッシュ化済みの⽂字列)を取得 n アルゴリズムの実装がわからなくても、Emulatorで実⾏してしまうことも可能 ###### 1. (エミュレータ内の)ECXに⽂字列を格納(=引数に渡す) 2. 0x1001285aから0x10012864まで実⾏ 3. EAXに格納されている値(=ハッシュ化済みの⽂字列)を取得 ----- ###### emulator.py n Ghidraが提供している ###### EmulatorHelperクラス を使⽤し、エミュレータ を実装 ----- #### (Advanced) Calcurate Hash by Emulator ###### GhidraのEmulatorでハッシュ関数をエミュレート 実際に渡されているハッシュ値 ----- ###### FUN_10007d5b |BEFORE|AFTER (name)| |---|---| |FUN_10007d5b|resolve_api| |FUN_10004ae6|find_lib| |FUN_10012794|find_api| |BEFORE|AFTER (name)| |---|---| |param_1|index| |param_2|hashed_dll_name| |param_3|hashed_api_name| |g_array|g_api_table| |pcVar1|api| ###### BEFORE AFTER (name) BEFORE AFTER (name) param_1 index FUN_10007d5b resolve_api ###### 引数のハッシュ値をもとに param_2 hashed_dll_name FUN_10004ae6 find_lib APIを動的解決し、グローバ param_3 hashed_api_name FUN_10012794 find_api ル変数にキャッシュ g_array g_api_table pcVar1 api ----- #### Anti API Hashing ###### プログラム内のハッシュ値⼀覧を取得し、API名を逆解決してコメントをつけたい 1. Hash DBの作成 1. API Hashに使⽤するXOR鍵を特定 2. System32配下の主要なDLLのエクスポート関数名をハッシュ化 2. ハッシュ値とDBを突合 1. スタックに積まれているスカラー値を全て取得 2. Hash DBに登録済みのハッシュ値と突合 3. コメントをつける 1 呼び出されるAPI名をコ ド上にコメント ###### プログラム内のハッシュ値⼀覧を取得し、API名を逆解決してコメントをつけたい 1. Hash DBの作成 1. API Hashに使⽤するXOR鍵を特定 2. System32配下の主要なDLLのエクスポート関数名をハッシュ化 2. ハッシュ値とDBを突合 1. スタックに積まれているスカラー値を全て取得 2. Hash DBに登録済みのハッシュ値と突合 3. コメントをつける ----- #### Create Hash DB ###### make_hash_db.py n %windir%¥System32¥*.dll ###### (今回は簡略化のため⼀部 DLLのみ)に定義されている エクスポート関数を列挙 n 検体内にハードコードされて ###### いたXOR鍵で関数名をエンコ ードし、↓形式のjsonで格納 - _: _ ----- #### Create Hash DB ###### make_hash_db.py n 出⼒されたdb.json ----- #### Lookup Hashes ###### Decompile View at FUN_10017d5a n DLL名とAPI名のハッシ ###### ュ値はresolve_api関数 の引数に渡されている n 値はスタック経由で渡さ ###### れている n 雑だが、PUSH命令の引 Listing View at FUN_10017d5a ###### 数に渡されているスカラ ー値が取得できればよさ そう ----- #### Lookup Hashes n プログラム内の全命令を列挙 n PUSH命令の引数に渡された スカラー値を取得 n 値をDB内のハッシュ値と⽐ 較 n 合致したらコメント・ブック マークをつける ----- #### Lookup Hashes ###### 演習 n 与えられた命令がスカラー値をPUSHするものかチェックする関数を実装し、 search_hash_not_impl.pyを完成させてください ###### search_hash_not_impl.py ----- #### Lookup Hashes ###### 演習 n 与えられた命令がスカラー値をPUSHするものかチェックする関数を実装し、 search_hash_not_impl.pyを完成させてください n ヒント: - inst.getMnemonicString() - Inst.getOpObjects() ###### search_hash_not_impl.py ----- #### Lookup Hashes ###### 演習 n 与えられた命令がスカラー値をPUSHするものかチェックする関数を実装し、 search_hash_not_impl.pyを完成させてください ###### 実装したsearch_hash.py ----- #### Run Ghidra Script n Script Managerで実⾏ 1. Script Manager起動 3. 参照するDBを指定(make_hash_db.pyで作成したjson) 2. スクリプト名を検索して実⾏ ----- #### Run Ghidra Script コンソールに実⾏結果が表⽰されている ※コメントが表⽰されない場合、 Edit > Tool Options > Decompiler > Display の API名・DLL名のコメントがついている 「Display EOL Comments」が有効化されていない可能性 ----- #### Other API Hash Search Script n EMOTETは、ハッシュ関数に使⽤されるXOR鍵が検体ごとに異なるため、検体ごとにDBを作成 ###### する必要があるが、既知のアルゴリズムが使⽤されている場合は、既成のDBを使⽤してルック アップすれば、ハッシュ関数の詳細をしらなくてもAPI Hashを解決できる場合がある - shellcode_hash_search.py (Ghidra) - https://github.com/AllsafeCyberSecurity/ghidra_scripts/blob/master/shellcode_hash_search. py - shellcode_hashes_search_plugin.py (IDA) - [https://github.com/fireeye/flare-ida/tree/master/shellcode_hashes](https://github.com/fireeye/flare-ida/tree/master/shellcode_hashes) ----- # Step 3: Decrypt Strings ----- #### Defined Strings n アンパック後のEMOTETには、不正活動に関連する可読⽂字列が存在しない n ⽂字列も暗号化・難読化されている可能性がある Window > Defined Strings から プログラム内の可読⽂字列を表⽰できる ----- #### Where is Decryption Function? n ⽂字列が暗号化されている場合、実⾏時に復号関数が呼び出されているはず n 復号関数は何度も呼び出されているはず **なにもしない関数** **resolve_api関数** **メモリ確保関数** **メモリ解放関数** **メモリ解放関数のラッパー** **???** ----- ###### FUN_1000732d ###### 演習 ###### ⽐較的複雑な関数 1. ロジックを解析してください ü 17~24、35~50⾏に集中する といいかも 2. 復号スクリプト decrypt_string_stage1_not _impl.pyのdecrypt_string関 数を実装してください ----- ###### FUN_1000732d ###### ⾒やすくするヒント① n オフセットでアクセスされ ###### ている場合、構造体を使⽤ param_2を右クリック> Auto Create Structure している可能性がある n Ghidraは独⾃構造体を⾃動 ###### で識別できない n Auto Create Structureで ###### 構造体を⾃動定義し、指定 変更後 ###### した変数に適⽤できる ----- ###### FUN_1000732d ###### ⾒やすくするヒント② n 同じレジスタが異なる⽤途 **uVar8を右クリック>** **Split Out As New Variable** ###### で使いまわされている場合、 変数名が付けづらい問題 n Split Out As New Variable ###### で変数を分割すると⾒やす くなる **後者がuVar9として再定義された** ----- ###### FUN_1000732d 1. 第2引数に、「暗号化された⽂字列に関す る情報を保持する構造体」へのポインタが 渡されている - オフセット+0: XOR鍵 - オフセット+4: XOR鍵でエンコードされた、 復号後⽂字列のサイズ - オフセット+8: 暗号データ 2. 4バイトで割り切れるサイズに調整 3. 復号⽂字列を格納するために、 ②のサイズ*2バイト分のバッファ確保 4. enc_dataがemptyでないかチェック 5. 復号処理 1. 4バイトを1チャンクとして処理 2. 4バイト分ポインタを進める 3. XOR鍵でデコード 4. 下位バイトから順にバッファにコピー ###### ❶ ❷ 1. 第2引数に、「暗号化された⽂字列に関す る情報を保持する構造体」へのポインタが **❸** 渡されている - オフセット+0: XOR鍵 - オフセット+4: XOR鍵でエンコードされた、 復号後⽂字列のサイズ **❹** - オフセット+8: 暗号データ 2. 4バイトで割り切れるサイズに調整 3. 復号⽂字列を格納するために、 **❺-❶** **❺-❷** ②のサイズ*2バイト分のバッファ確保 **❺-❸** 4. enc_dataがemptyでないかチェック 5. 復号処理 **❺-❹** 1. 4バイトを1チャンクとして処理 2. 4バイト分ポインタを進める 3. XOR鍵でデコード **❺-❺** 4. 下位バイトから順にバッファにコピー ----- #### String Decryption Algorithm n 先頭4バイトがXOR鍵(keyと する) n オフセット+4から4バイトが XORされた(平⽂の)サイズ (xored_lengthとする) n key ^ xored_length が平⽂のサ イズとなる(orig_lengthとす る) n オフセット+8以降から orig_length分が暗号⽂ ----- #### Decrypt Strings: Logic |Col1|key|Col3| |---|---|---| |||| |xored_length||| ||enc|| 暗号⽂字列のデータはEDX経由で復号関数 (FUN_1000732d) に渡されている 解析結果をもとにGhidra Scriptで復号処理を実装 **key** **xored_length** **enc** ----- #### Decrypt Strings: Logic n ⽳埋め部分を実装 実装したdecrypt_string_stage1.pyのdecrypt_string関数 ----- #### Decrypt Strings: How to get arguments? n ここまでで、暗号データ FUN_1000732dの参照元を辿ると、EDX経由でアドレスが渡されている ###### のアドレスがわかれば⽂ 字列を復号できることが わかった n 暗号データのアドレスは ###### EDXレジスタ経由で復号 関数(FUN_1000732d) の第2引数として渡され ている ----- #### Decrypt Strings: How to get arguments? ###### ただし、例外もある n FUN_1000732dの「呼び出し元の関数の引数」として ###### アドレスが渡されているケース n 本来は対応すべきだが、今回は無視することにする **暗号データのアドレスが呼び出し元関数の** **引数として渡されている** ----- #### Decrypt Strings: How to get arguments? n 復号関数(FUN_1000732d)の ###### 呼び出し元の直前の命令を取 得し、EDXにMOVされている アドレスを取得 n 特定のアドレスの直前の命令 ###### を取得するためのユーティリ ティ関数 EDXにMOVしてる命令だけフィルター ###### get_instructions_beforeが utils.pyに定義済み ----- #### Decrypt Strings: Assemble codes n 復号関数のアドレスはとりあえずハードコードしておく decrypt_string_stage1.pyのmain関数 ----- #### Decrypt Strings: Run 復号後の⽂字列がコメントされている n Script Manager経由で ###### decrypt_string_stage1.pyを実⾏ 実⾏結果はコンソールに出⼒される Window > Bookmark からもコメントを確認できる ----- #### Decrypt Strings Stage2 n 復号関数のアドレスは常に固定とは限らない n 復号関数を都度⼿動で解析して特定していては⾃動化の意味がない ###### 複数サンプルを解析し、 復号関数内の共通する特徴的な命令列を⾒つけ出し、 それをもとに復号関数を⾃動特定する 本演習は自主学習にします。 A di に課題と解法が書いてあるので興味のある方はトライしてみてください n 復号関数のアドレスは常に固定とは限らない n 復号関数を都度⼿動で解析して特定していては⾃動化の意味がない ###### 複数サンプルを解析し、 復号関数内の共通する特徴的な命令列を⾒つけ出し、 それをもとに復号関数を⾃動特定する 本演習は自主学習にします。 ----- # Step 4: Config Structure Analysis ----- #### Config Structure Analysis n EMOTETは通信先を検体内にハードコードしている n 通信関連のAPIを辿り、ハードコードされた通信先情報の取得を試みる Bookmarksから、search_hash.pyでみつけた 通信関連のAPIを確認できる ----- #### Find Hostname & Port n InternetConnectW API n InternetConnectWを呼び出 ###### しているFUN_10002b4fの第 七引数にホスト名、第4引数 にポート番号が渡されている n InternetConnectW API pcVar1をRetype Variableで InternetConnectW *型に変更した後、 引数名や型を⼿動で変更する n InternetConnectWを呼び出 ###### しているFUN_10002b4fの第 七引数にホスト名、第4引数 ----- #### Find Hostname & Port n call_internetconnectw (FUN_10002b4f)の呼び出し 元を辿ると、FUN_1000f70cが 呼び出していることがわかる n ホスト名が渡されている第七引 数は、FUN_1000f70cの第引数 から渡されている n ポート番号が渡されている第4 引数には、in_stack_00000024 という変数が渡されている - 実際はポート番号も FUN_1000f70cの引数として渡さ れているのだが、Ghidraが関数の 解析に失敗していて、解釈がおか しくな ている FUN_1000f70c内でcall_internetconnectwを呼び出している部分 n call_internetconnectw **↓ポート番号** (FUN_10002b4f)の呼び出し **ホスト名→** 元を辿ると、FUN_1000f70cが 呼び出していることがわかる call_internetconnectwに渡されているホスト名はFUN_1000f70cの第⼆引数から渡される n ホスト名が渡されている第七引 数は、FUN_1000f70cの第引数 から渡されている n ポート番号が渡されている第4 引数には、in_stack_00000024 という変数が渡されている - 実際はポート番号も FUN_1000f70cの引数として渡さ れているのだが、Ghidraが関数の 解析に失敗していて、解釈がおか ----- #### Fix Function: FUN_1000f70c n FUN_1000f70cの引数の解釈にGhidraが失敗している n そのため、Edit Function Signatureで引数の数を調整する FUN_1000f70cの呼び出し元を⾒てみると FUN_1000f70cを右クリック> Edit Function Signatureで編集 ECX, EDX, PUSH*9で引数を渡している Add Parameterで引数の数が合計11になるまで追加 ----- #### Fix Function: FUN_1000f70c n 正しく引数の数を調整すると、FUN_1000f70cの第11引数にポート番号が渡され、 ###### call_internetconnectwの第4引数に渡されていることがわかる n FUN_1000f70cの呼び出し元でも引数が反映されている(名前は⼿動で変更) ----- ###### FUN_10007e95 portがアサインされている箇所 n FUN_1000f70cを呼び出して ###### いる関数がFUN_10007e95 n portへのアサインは⾒える ###### が、hostnameがどこからア サインされているかわから ない n ポート番号はDAT_1001fa08 DAT_1001fa08に対し、Create Auto Structureで構造体定義 ###### からのオフセット0x24+0x4 に格納されている - → 構造体として定義してお く ----- ###### FUN_10017f0f IPアドレスのようなフォ マット⽂字列が渡される n フォーマット⽂字列を第11 引数として受け取る - IPアドレスのようなフォーマ ット n FUN_10017f0fは、実質 _snwprintfを呼び出すだけ ----- ###### Fix Function: FUN_10017f0f n ⼀⾒問題ないようだが、実は呼び出し 関数シグネチャ修正後、変数名・型を変更した結果 (param_2とextraout_EDXは実際同⼀だが、変数名が同期されず、今回は無視) 規約と引数の数が間違っている - __cdecl -> __fastcall - 引数の数-> 14 Edit Function Signatureで修正 ----- #### Config Structure |第2オクテット|Col2| |---|---| |第3オクテット|| ||| |クテット|| n call_snwprintfでホスト名(IP)が⽣成されることがわかった n IPアドレスの各オクテットがcall_snwprintfの引数に渡されている n ここまでの情報から、0x1001fa08には次のような構造体へのポインタが存在していると推測できる ü オフセット+0x24にはIPアドレスへのポインタが格納されているはず ü IPアドレスはリトルエンディアンの4バイト(1オクテット1バイト) ü また、オフセット+0x24+0x4にポート番号が格納されているはず **第2オクテット** **第3オクテット** **第1オクテット** **第4オクテット** ----- #### Config Structure Data Type Manager > emotet.dll 右クリック> New > Structure でC2Infoを新規作成し、 次にPTR_1001fa08を右クリック> Edit Data Type からオフセット36をC2Info*に変更 ###### typedef struct { ... // at offset 0x24 C2Info *c2; } Config; Data Type Manager > emotet.dll 右クリック> New > Structure でC2Infoを新規作成し、 次にPTR_1001fa08を右クリック> Edit Data Type からオフセット36をC2Info*に変更 n ここまでの情報をまとめて、 ###### 次のような構造体を定義 擬似構造体 ###### typedef struct { byte ip_address[4]; WORD port; } C2Info; typedef struct { ... // at offset 0x24 C2Info *c2; } Config; ----- #### Config Structure n 構造体が適⽤されると⾒やすくなる n ここで⽣成されたIPアドレスとポート番号が、先ほど解析したFUN_1000f70cに渡され、通信が ###### 発⽣する n つまり、これらのIPアドレスとポート番号がC&Cサーバの情報となることがわかる ----- #### Config Structure g_configの参照元⼀覧を確認すると、 n g_config(PTR_1001fa08) WRITEしているのは0x100e258の⼀箇所のみ ###### はポインタを保持するだけ のグローバル変数なので、 Config構造体の実態への ポインタは実⾏時に格納さ れる n では、いつ・どこで初期化 ###### されるのか? ----- ###### FUN_1000e10b Config構造体をアップデ トした後のコ ド Config ###### Config構造体を初期化する関数 1. Config構造体⽤のバッファ ###### を確保し、g_configへポイ ンタをコピー ❶ 2. DAT_1001f000からC2Info ###### ❷ 構造体のポインタをコピー ❸ 3. DAT_1001f000には、1要素 ###### あたり8バイトの配列が格 納されている ----- ###### FUN_1000e10b 編集後のFUN_1000e10b n 現在C2Info構造体は6バイトなので、(使⽤⽤ 途は不明だが)2バイト分追加しておく n DAT_1001f000(g_c2info_array)をRetype Globalし、C2Info構造体の配列として定義 ----- #### Array of C2Info n DAT_1001f000(g_c2inf ###### o_array) にはIPアドレ C2Info ス、ポート番号が平⽂で 格納されている n このデータをパースすれ ###### ば通信先情報を取得でき そう **ip_address** **port** **C2Info** **ip_address** **port** **C2Info** **ip_address** ----- #### Protocol of Connection ###### IPとポートは判明したが、プロトコル(HTTP/HTTPS)は? n HTTPSを使う場合、WinInet系APIではHttpOpenRequestのdwFlagsで ###### INTERNET_FLAG_SECURE(0x800000)が指定される HttpOpenRequestの関数シグネチャ ----- #### Protocol of Connection ###### HttpOpenRequestWの呼び出し元を辿ると、INTERNET_FLAG_SECURE(0x800000)のビットは ⽴っていないので、プロトコルはHTTPが使⽤されることがわかる ----- #### Extract C2 in Action extract_c2_stage1_not_impl.py のparse_single_configを実装し **C2Info** てください n アドレス0x1001f000以降に存 在しているIPアドレスとポート を以下形式で出⼒する - http://: **ip_address** **port** **C2Info** **ip_address** **port** **C2Info** **ip_address** ----- #### Extract C2 in Action 1. g_c2info_arrayの先頭アドレスか ら処理を進める 2. 先頭4バイトをIPアドレス、オフセッ ト+4から2バイトをポート番号とし てパース 3. 8バイト(C2Info構造体のサイズ)分 アドレスを進める ----- #### Extract C2 Stage2 n コンフィグのアドレスは常に固定とは限らない n コンフィグのアドレスを都度⼿動で解析・特定していては⾃動化の意味がない ###### 複数サンプルを解析し、コンフィグ初期化関数内で 共通する特徴的な命令列を⾒つけ出し、 それをもとにコンフィグのアドレスを⾃動特定する ----- #### How to compare samples? ###### Version Tracking n 2つのバイナリの差分を解析するGhidraの機能 - パッチの差分解析 - 共通する命令列の可視化 差分表⽰の例 ----- #### Version Tracking: Main Window n Version Trackingで、関数を⽐較したりする際に使⽤するウィンドウ n 最初はなにも表⽰されない Ghidraのプロジェクト画⾯から開く Version Tracking Windowの初期画⾯ ----- #### Version Tracking: Session ###### バイナリの⽐較結果をSessionという情報として保持 n Sessionの作成⼿順 1. Version Tracking Window > [Create a new Version Tracking Session] 2. Source ProgramとDestination Programを指定 3. [Run Precondition Checks]を実⾏ ###### ❶ ❷ ❸ ----- #### Version Tracking: Compare Functions Version Tracking Functionsを使うと、⽐較したい関数を⼊⼒すると差分が可視化できる (emotet.dllの0x1000e10bとemotet-2.dllの0x1001a094の⽐較例) **ブルー: ⼀致していない** コードユニット **グレー: 部分的に⼀致し** ていないコードユニット **グリーン: バイト列、ニ** ーモニック、オペランド のいずれかが異なってい る箇所 ----- #### Decrypt Strings Stage2: Find Function ###### 演習 ###### extract_c2_stage2_not_impl.pyのfind_config_addrssを実装する 1. Version Trackingで複数サンプルの命令列を⽐較 - Source -> emotet.dll: 0x1000e10b (Version Tracking Functionsで検索するときは0xをとる) - Destination -> emotet-2.dll: 0x1001a094 (Version Tracking Functionsで検索するときは0xをとる) 2. 共通する“特徴的な”命令列を特定 3. 特徴的な命令列を含む関数⼀覧をスクリプトで取得 extract_c2_stage2_not_impl.pyのfind_config_addrss ----- #### Decrypt Strings Stage2: Find Function n Version Trackingで複数サンプル内の命令列を⽐較 n 共通する特徴的な命令列を特定する たとえばこのあたり デコンパイル結果 ----- #### Decrypt Strings Stage2: Find Function n 特徴的な命令列を検索し、それの命令の引数に渡されているアドレスを取得 - Address[] findBytes(Address start, java.lang.String byteString, int matchLimit) Ø 正規表現をつかった検索が可能 - Instruction getInstructionAt(Address address) Ø 引数addressで指定したアドレスの命令を取得 **レジスタや変数など、コンパイラ等によ** **って可変なものは「.」(任意の⼀⽂字) に** **して⼀般化する** ----- #### Decrypt Strings Stage2: Fully Automated find_config_addressを実装したextract_c2_stage2.pyとして emotet.dllおよびemotet-2.dllで⾃動抽出が可能に 保存して実⾏ ----- # Summary ----- #### Ghidra Script for EMOTET 1. APIハッシュの突合 - search_hash.py 2. ⽂字列の復号 - decrypt_string_stage1.py 3. C&C情報の抽出 - extract_c2_stage1.py - extract_c2_stage2.py ----- #### (Advanced) It s Automatic! n extract_c2_stage2.pyをHeadless対応したanalyzer.pyを ###### Headless Analyzerで実⾏すればバッチ処理も可能 出⼒されたresolt.json (抜粋) Headless AnalyzerでCLI経由でanalyzer.pyを実⾏ ----- #### Conclusion n 解析⾃体を⽬的とするのではなく、⾃動化を⽬的にするとスケールする n 解析コードの共有により、知識をサイロ化させない・再利⽤可能にする - 解析結果だけでなく、再利⽤可能なコードとして共有する(Analysis as a Code) - Ghidra Scriptは誰でも再利⽤可能なので、有効な選択肢の⼀つ - 解析者にもGithubificationが広がると、コミュニティ全体の能⼒が向上する Ø The Githubification of InfoSec: https://medium.com/@johnlatwc/the-githubification-of-infosec- afbdbfaad1d1 ----- contact: @PINKSAWTOOTH ### THANKS! if you have any, please let us know, friend. contact: @PINKSAWTOOTH ----- ###### CREDITS - Ghidra Logo by NSA - Presentation template by Slidesgo - Icons by Flaticon - Twemoji by Twitter, Inc and other contributors is licensed under CC-BY 4.0 - Allsafe Logo inspired by Mr.Robot ----- # Appendix ----- # Homework: Decrypt Strings Stage 2 ----- #### Decrypt Strings Stage2 n 復号関数のアドレスは常に固定とは限らない n 復号関数を都度⼿動で解析して特定していては⾃動化の意味がない ###### 複数サンプルを解析し、 復号関数内の共通する特徴的な命令列を⾒つけ出し、 それをもとに復号関数を⾃動特定する ----- #### Decrypt Strings Stage2: Find Function ###### 演習 ###### decrypt_string_stage2_not_impl.pyのfind_decrypt_string_funcを実装する 1. Version Trackingで複数サンプルの命令列を⽐較 - Source -> emotet.dll: 0x1000732d (Version Tracking Functionsで検索するときは0xをとる) - Destination -> emotet-2.dll: 0x10006aba (Version Tracking Functionsで検索するときは0xをとる) 2. 共通する“特徴的な”命令列を特定 3. 特徴的な命令列を含む関数⼀覧をスクリプトで取得 decrypt_string_stage2_not_impl.pyのfind_decrypt_string_func ----- #### Decrypt Strings Stage2: Find Function n Version Trackingで複数サンプル内の命令列を⽐較 n 共通する特徴的な命令列を特定する たとえばこのあたり デコンパイル結果 ----- #### Decrypt Strings Stage2: Find Function n 特徴的な命令列を検索し、それらの命令列を含んでいる関数⼀覧を取得 - Address[] findBytes(Address start, java.lang.String byteString, int matchLimit) Ø 正規表現をつかった検索が可能 - Function getFunctionContaining(Address address) Ø 指定したアドレスを含む関数を取得 **レジスタや変数など、コンパイラ等によって** **可変なものは「.」(任意の⼀⽂字) にして⼀般化する** ----- #### Decrypt Strings Stage2: Fully Automated 実装したスクリプトをdecrypt_string_stage2.pyとして保存して実⾏ 複数の復号関数を⾒つけ、 引数に渡されている⽂字列を復号している ----- # How to get unpacked samples by yourself ----- ##### samples by yourself ##### samples by yourself n 必要なもの - 仮想環境(VMWare/VirtualBox/Hyper-V等) - Windows OS(ゲストOS) - ANY.RUNアカウント - Process Explorer / Process Monitor (なくても可) - Hollow Hunter - [https://github.com/hasherezade/hollows_hunter/release](https://github.com/hasherezade/hollows_hunter/release) n **以後の作業は全て構築した仮想環境内でおこなう** ----- ##### samples by yourself ##### samples by yourself ###### 以下ANY.RUNのページを開き、実⾏されているDLLをダウンロード n urcwzowo.xck (=packed emotet.dll) MD5:19b0124f2e4f223113bb11a84765a6c3 - [https://app.any.run/tasks/4060b18e-8132-42c0-a0bc-65f398d6bfb2/](https://app.any.run/tasks/4060b18e-8132-42c0-a0bc-65f398d6bfb2/) n eclh.gfk (=packed emotet-2.dll) MD5:714cdae2b20896e72d92e28dc831b81b - [https://app.any.run/tasks/b9be3b5e-368d-4248-87c2-e06b3d66769e/](https://app.any.run/tasks/b9be3b5e-368d-4248-87c2-e06b3d66769e/) ----- ##### samples by yourself ##### samples by yourself ###### 仮想環境を外部接続不可な状態にし、EMOTETを適切な引数で実⾏ このとき、Process ExplorerやProcess Hackerなどで実⾏された rundll32.exeのプロセスシーケンスを確認しておく ----- ##### samples by yourself ##### samples by yourself ###### 実⾏後5-10秒ほど待機し、⼦プロセスのPIDをHollows Hunterに渡して実⾏ n 成功すると実⾏フォルダ配下にprocess_フォルダが作成され、中にアンパック後のEMOTETのDLLが 保存される -----