Malware Analysis at Scale~ Defeating EMOTET by Ghidra ~株式会社サイバーディフェンス研究所

中島将太

トレンドマイクロ株式会社

原弘明

2

> whoamiØØ

Shota Nakajima

Ø

Ø

Ø

Hiroaki Hara

株式会社サイバーディフェンス研究所でマルウ
ェア解析、インシデントレスポンス業務、脅威情報の収集・分析業務に従事。
JSAC、HITCON CMT、AVAR、CPRCon、BlackHat EUROPE Arsenal、CodeBlue BlueBoxなど
で発表経験あり。
技術系同⼈サークルAllsafeのプロデューサー。

トレンドマイクロ株式会社にて、マルウェア
解析やインシデントレスポンス、スレットリ
サーチなどに従事。
技術系同⼈サークルAllsafeの澤村・スペンサ
ー・英梨々 (アートディレクター) 。

3

Malware Analysis at Scale
テーマ: いかにして楽に解析を終わらせるか
解析者の現状として、次のような課題があるnü

静的解析は時間がかかり、根本的につらいü

膨⼤な数のマルウェアを解析する必要があるü

⼿動での解析は現実的に不可能

解析の⾃動化（=スケールする解析）をめざす

4

Targetnn

マルウェア解析経験者○

C⾔語、アセンブリ⾔語を理解している○

Windowsのプログラミングについて知識がある○

IDA, Ghidraを使ってマルウェアの静的解析をしたことがある

Pythonでのプログラミング経験を有する者○

Pythonの基本的な⽂法がわかる

TABLE OF CONTENTS
01

Basic of Ghidran Ghidra Usagen Ghidra Script

02

Automated Analysis of EMOTETn Surface Analysis
n API Hashing and Dynamic Calln Decrypt stringsn Config Structure Analysis

01

Basic of Ghidra

7What is Ghidra?
ソフトウェアリバースエンジニアリングツールセット■

2019年にNSAによってOpen Source Softwareとして公開○■

https://github.com/NationalSecurityAgency/ghidra

フリーでありながら、豊富な機能を提供

8

Ghidra's Basic Features

Disassembler/Decompiler

Ghidra Script

Ghidra Extension

多様なアーキテクチャに対応した
ディスアセンブラに加え、
擬似C⾔語へ変換するデコンパイラ機能

Ghidraの各種機能をスクリプト(Java/Python)から利⽤可能

⾃作プラグインによる機能拡張をサポート

Headless Analyzer

Ghidra Server

Debugger

CLI経由での操作

複数ユーザによる共同作業をサポート

GDB/WinDBGをバックエンドとしたデバッグ機能を提供

Ghidra Usage

10

Code Browser
Listing View

Decompile View

• ディスアセンブル・データを表⽰• Listing Viewのデコンパイル結果を表⽰

Program Tree
• セクション名などを表⽰

Source Tree
• インポート/エクス
ポート関数、関数
名、クラス名などなどを列挙

Data Type Manager• 独⾃構造体などのデータ型を管理

Console• ヘッダファイルなどの読み込み• スクリプトの標準出⼒結果を表⽰

11Other WindowsnBookmarks○n

特定のアドレスに対し、名前をつけてブックマークとして保存

Bytes
○

n

HEXエディタ

Defined Strings○nプログラム内で定義された可読⽂字列

Function Graph○n

ディスアセンブル結果をグラフ形式で表⽰

Python○nPythonインタプリタの起動

Script Manager○

Ghidra Scriptを管理するマネージャの起動

12

Name
Ghidraではバイナリのロード時に⾃動解析して関数や変数に名前を付ける

n•関数(FUN_*)•

⽂字列(s_*)•

ローカル変数(local_*)•

変数(*Var*)13

Change Namen

リネームして解析結果を反映することで、Ghidraを読みやすくしていくn右クリックメニューから選択14

Change Function Signaturen関数の定義を変更する

15

XREF(Cross References)n

データの参照元の表⽰

16

Change Data Typen関数や変数の型の変更○

右クリックメニューから選択Ghidra Script

18

Ghidra Script
Ghidraの各種操作を⾃動化・効率化する機能n

こんなことができる
ü

バイト列の検索やコメントの付与など、⼿動作業の⾃動化

ü

⼤量のファイルのインポート・解析処理など、反復作業の⾃動化

ü

暗号化されたデータや⽂字列の復号など、解析の⾃動化ü

エミュレータによる実⾏

19

Ghidra APIn

スクリプトからGhidraの機能にアクセスするためのAPIn

JavaとPython (Jython2.7) で実装が可能
Python実装の例

20Type of Ghidra API
Ghidra APIは⼤きく3つに分類でき、次のような階層構造になっているn•

ほとんどの場合で 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

Low Layer

Program API
https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Program.html

21

Script Manager
nGhidra Scriptの実⾏や管理を⾏う機能
Script Managerの起動⼿順

22

Built-in Python Interpretern

ビルトインのPythonインタプリタが提供されている•メニューの「Window」 > 「Python」で起動

23

Why Interpreter?
検証のためのトライアンドエラーに便利

Test
AnalysisWrite Code24

Why Interpreter? Pt.1
n

変数内部のデータを都度確認しながら処理を記述できる

25

Why Interpreter? Pt.2
n

コード補完してくれる•

⼊⼒中にTabを押すと利⽤可能なメソッドを表⽰してくれる26

Why Interpreter? Pt.3
n

APIのヘルプが参照できる•← Listingインター
フェイスの実装
クラスだとわかる

help に参照したいオブジェクトを渡す

インターフェイス名がわかれば検索できる← 公開メソッドの
シグネチャ

27

Interpreter Tips ~ import ~n

複数⾏にわたるペーストができないので、関数などがコピー＆ペーストできないnなので、使いたい関数やクラスをスクリプト内で定義しておき、import する•

import で読み込めるファイルはScript Directories配下に存在しているファイルのみなので、今回はC:¥Ghidra¥ghidra_scripts配下に配置する•

Script Directoriesに登録された別フォルダに同名のファイルが存在するとエラーの原因になるので、ユニークにする← get_all_functions関数が現在の名前空間に存在しないのでエラー← snipetts.pyn内のget_all_functions関数が呼び出せるようになるので便利28Interpreter Tips ~ import ~n

ただし、そのままだとインポートされたスクリプト側からGhidra APIを参照できないので、次のimport⽂を必ずスクリプトの最初に記述しておく！（重要）29Interpreter Tips ~ reload ~nすでにimport済みのスクリプトを編集した後再度importするには、reload を使う○ 例えば、⼀度importしたがエラーが出たのでスクリプト内の関数を書き換えた場合など○

Pythonは⼀度importしたコードをコンパイルしてキャッシュしているので、importだけでは更新が反映されないのが原因編集された関数← この後のタイミングで編集← reloadで編集を反映

30

Interpreter Tips ~ reload ~n

ただし、from name import * でimportしている場合、↓のようにsys.module['name']を
reloadしてから再インポートする必要がある

編集された関数← この後のタイミングで編集← reload← 再インポート

31

Headless Analyzer
CLIからGhidra Scriptを操作するための機能n

ユーザインタラクションが不要なので、バッチ処理が可能

n

バッチ処理により、ファイルのインポートや解析処理の⾃動化が可能

Ghidra API
Quick Reference

33

FlatProgramAPI Class
Program APIのラッパークラス•

https://ghidra.re/ghidra_docs/api/ghidra/program/flatapi/FlatProgramAPI.html

Fieldsn

protected Program currentProgram°

現在読み込んでいるプログラムに関する様々な情報を保持するフィールド, Program APIを提供する

MethodsnAddress[] findBytes(Address start, java.lang.String byteString,int matchLimit)
°

n

n

n

°

°

指定したaddressの命令を取得

n指定したaddressを含む関数を取得

Reference[] getReferencesTo(Address address)

現在読み込んでいるプログラムの最初の関数を取得

Instruction getInstructionAt(Address address)

指定したaddressの⼀つ⼿前（低位アドレス）の命令を取得, ⼀つ先（⾼位アドレス）の命令を取得するgetInstructionAfterもある

Function getFunctionContaining(Address address)°

Function getFirstFunction()°n

指定したaddressから指定したlength分をバイト列として取得

Instruction getInstructionBefore(Address address)°

byte[] getBytes(Address address, int length)°n指定したバイト列（Python2では⽂字列と同じ）を検索してヒットしたアドレス⼀覧を返すn

指定したaddressの参照元（=addressを参照しているオブジェクト）⼀覧を取得

Address toAddr(int offset)°

指定した値をAddressオブジェクトに変換

FlatProgramAPI Class Use-Case:
01. Address ManipulationnAddress toAddr(int offset)nAddress toAddr(long offset)nAddress toAddr(String address)°

指定した値をAddressオブジェクトに変換

数値や⽂字列をAddressオブジェクトに変換し、⽐較

34

35

Address Interface
アドレスを表現するインターフェイス•

https://ghidra.re/ghidra_docs/api/ghidra/program/model/address/Address.html

MethodsnAddress add(long displacement)°nboolean equals(java.lang.Object o)°n

引数oとアドレスを⽐較

long getOffset()°n

アドレスを引数displacementのサイズ分加算し、新しいAddressを返す

アドレスのオフセットをlongで返す

Address next()°

次のアドレスを返す

オフセット値の取得

FlatProgramAPI Class Use-Case:
02. Binary Manipulationnbyte[] getBytes(Address address, int length)°n指定したaddressから指定したlength分をバイト列として取得

int getInt(Address address)°

指定したaddressから4バイトをintとして取得

特定のアドレスから、指定したサイズ分バイト列の読み出し

36

37

FlatProgramAPI Class Use-Case:
03. SearchnAddress find(java.lang.String text)°°nプログラム内のコメントやラベル、コードユニットのやオペランドなどから、引数のtextで与えられた⽂字列を検索
最初にマッチした結果のアドレスが返される

Address find(Address start, byte[] values)°°

第1引数にAddressオブジェクトが渡されると、引数startで指定したアドレス以降のプログラムのメモリからvalueで指定したバイト列を検索し、最初にマッチした結果のアドレスを1つだけ返す
正規表現はサポートされていないレジスタへの
MOV命令を検索

nAddress[] findBytes(Address start,java.lang.String byteString, int matchLimit)°°

引数startで指定したアドレス以降のメモリから、byteStringで指定された値を検索
このメソッ ドは、byteStringで正規表現を使うことも可能、例えば
任意の4バイトをマッチさせたい場合「.{4}」 といった表現を使⽤可能

38

FlatProgramAPI Class Use-Case:
04. Function ManipulationnFunction getFirstFunction()°n

現在のプログラムの最初の関数オブジェクトを取得

引数entryPointで指定したアドレスから始まる関数オブジェクト
を取得

Function getFunctionAfter(Address address)°

引数addressで指定したアドレスで始まる関数の次の関数オブジ
ェクトを取得プログラム内の全関数を列挙Function getFunctionBefore(Address address)°

Function getFunctionAt(Address entryPoint)
°

n

n

n

引数addressで指定したアドレスで始まる関数の前の関数オブジ
ェクトを取得Function getFunctionContaining(Address address)°

引数addressで指定したアドレスが関数のアドレスレンジに含まれる場合、その関数オブジェクトを取得

39

Function Interface
関数を表現するインターフェイス•

https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Function.html

MethodsnAddress getEntryPoint()°njava.lang.String getName()°n関数のエントリーポイント（先頭アドレス）を取得

関数の名前を取得

Parameter[] getParameters()°

関数の引数をParameterオブジェクトで取得

40

FlatProgramAPI Class Use-Case:
05. Instruction ManipulationnInstruction getFirstInstruction(Function function)°nInstruction getInstructionAt(Address address)°n

引数addressで指定したアドレスの命令を取得

Instruction getInstructionAfter(Instruction
instruction)°n

引数functionで指定した関数オブジェクトに含まれる、最初の命
令を取得

引数instructionで指定した命令の次（⾼位アドレス）の命令を取得Instruction getInstructionBefore(Instruction
instruction)°

引数instructionで指定した命令の1つ前（低位アドレス）の命令を取得°

関数呼び出しを⾏うCALL 命令に対する引数をたどる際などに利⽤可能特定の関数内の全ての命令を取得

41

Instruction Interface
命令を表現するインターフェイス•

https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Function.html

Methodsnjava.lang.Object[] getOpObjects(int opIndex)°nnjava.lang.String getMnemonicString()°

正確にはCodeUnitインターフェイスのメソッド°

命令のニーモニックを⽂字列として取得

PcodeOp[] getPcode()°n

命令に渡されているオペランドのうち、opIndexで指定されたインデックスのオブジェクト（Address, Scalarなど）⼀覧を取得

命令をP-Codeという中間表現に変換

Register getRegister(int opIndex)°

命令に渡されているオペランドのうち、opIndexで指定されたインデックスのレジスタオブジェクトを取得

FlatProgramAPI Class Use-Case:
06. Reference Manipulation
Ghidraはデータやコードなどのオブジェクト間の関係性を「参照」という情報として保持

後方参照

n

n

Reference[] getReferencesTo(Address address)°

引数addressで指定したアドレスへの参照を持つコードユニッ
トのアドレス⼀覧を取得°

つまり、 後⽅参照（クロスリファレンス）を取得

Reference[] getReferencesFrom(Address address)°

引数addressで指定したコードユニットのアドレスが参照している先のアドレス、つまり前⽅参照の⼀覧 を取得

42

43

Reference Interface
参照を表現するインターフェイス•

https://ghidra.re/ghidra_docs/api/ghidra/program/model/symbol/Reference.html

MethodsnAddress getFromAddress()°nAddress getToAddress()°n参照しているコードのアドレス、つまり後⽅参照のアドレスを取得

参照の「宛先」アドレス、つまり前⽅参照のアドレスを取得

RefType getReferenceType()°

参照のタイプを取得°

参照タイプ: https://ghidra.re/ghidra_docs/api/ghidra/program/model/symbol/RefType.html

02

Automated Analysis of EMOTET

45

Goals of Malware Analysis「解析」といっても⽬的（期待されるアウトプット）はさまざま

Detection

不正かどうかの判定、およびマルウェアのファミリ名やカテゴリを特定する。この作業により、マルウェアの⽬的や関連する攻撃者を認識することができる（可能性がある）。

Indicator ofCompromise

ネットワークやホスト内において、侵害の有無を判断するための情報を特定する。作成されるファイル名やレジストリ名、通信先情報などが該当する。

Reportマルウェアの挙動・機能を含む解析レポート。Detection/IoCを含み、マルウェアの⽬的や背後の攻撃者について⾔及する。主に顧客向けに⽂章で記述される。

今回は通信先情報の抽出を⽬的とする

46

EMOTETnn

近年猛威をふるう、情報窃取兼ダウンローダー型マルウェア○

⼤量のスパム経由で配布されることが知られている○

Human Operated Ransomwareの起点となることもある

NICTによるEMOTETスパムの着信数推移

とにかく数が多い○

⼤量の亜種が短時間で配布される○かつそれぞれの検体が⼤量のC&C通信先をもつ

https://blog.nicter.jp/2020/10/emotet-mail-202007-202009/

47

How EMOTET works

Spam

メール経由で侵⼊

MacroPowerShell

マクロ付き
ドキュメントPowerShellが実⾏され、EMOTET (Loader) をDL

EMOTET(Loader)

EMOTET(Payload)

Rundll32.exe
経由で実⾏される

C&Cサーバから
ペイロードを取得し、オンメモリで実⾏される

48

Analysis Steps
以下のような点を解析によって明らかにし、スクリプトに落とし込む

Packed?

ü パックされているか？
ü パッカーのロジック

AntiAnalysis?

ConfigEmbeded?

ü ⽂字列は難読化・暗号化されているか？

ü C&Cサーバなどの情報はどこに/どのような形式で格納されているか？

ü 鍵は？
ü API呼び出しは難読化されているか？

ü どうやって⾃動抽出するか？

Step 0:
Determine Packing Logic and Do Unpack
他の多くのマルウェアと同じく、EMOTETもパックされており、メモリ上でペイロードが復号されて実⾏される（ある時期の）EMOTETのアンパックロジック

https://www.bromium.com/wp-content/uploads/2019/07/Bromium-Emotet-Technical-Analysis-Report.pdf

49Step 0:
Determine Packing Logic and Do Unpack
アンパックの⼿法はさまざま
nn

⼿動アンパック○

デバッガーを使ってアンパック○

実⾏後、マルウェア⾃⾝によってアンパックされた領域をダンプ

ツールによるアンパック○

マルウェアを実⾏、メモリ上からアンパック後の領域を機械的に抽出ü PE-SIEVE / Hollows Huntern

⾃動アンパック○

ツールの⾃動実⾏
ü tknk_scanner
ü CAPE

50

Quick Unpack with Hollows
Huntern

パックされたEMOTETを実⾏し、Hollows Hunterでアンパック後のEMOTETをダンプn

具体的な⼿順はAppendixを参照

51

52

Unpacked EMOTETn

本ワークショップでは、アンパック済みのEMOTETを使⽤します

メイン解析⽤⽐較⽤

Step 1:
Surface Analysis

54

Resourcesnn

使⽤するサンプル等はJSAC参加者にのみSlackで共有済み共有されたzipを展開し、以下⼿順を実施
scripts: 配下のファイルをC:¥Ghidra¥ghidra_scripts配下に
コピー

1.
•

scripts¥answers: 演習の回答なので、極⼒⾒ない

2.

emotet.dll.gzf: Ghidraにドラッグ＆ドロップでインポート

3.

emotet-2.dll.gzf: Ghidraにドラッグ＆ドロップでインポ

4.

winapi_32.gdt: C:¥Ghidra直下にコピー

C:¥Ghidra¥ghidra_scripts配下が以下のような構成になっていればOK

55

Setup (sample)n

配布したemotet.dll.gzfをGhidraの既存プロジェクトにドラッグ&ドロップしてインポート○

途中ポップアップが出たら「OK」

56

Setup (GDT)nGhidraの標準型データにはwininet.h、winhttp.hのAPI情報がない○

Data Type Managerでwinapi_32.gdtをロード配布したファイルに含まれる
winapi_32.gdtをロード▼ボタンを押してOpen File Archive

ロード完了後、winapi_32を
右クリックしてApply Function Data Types

57

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: 演習⽤⽳あきスクリプト

58

Exports Functionsn

DLL verのEmotetはrundll32.exeを使ってエクスポートされた関数から実⾏される

59

Brief Analysis (code)nGarbage CodenControl Flow Flatting○

実⾏に影響を及ぼさない無駄なコード○

Control Flow Obfuscationの⼿法○

Ghidraのデコンパイラがいい感じに端折
ってくれる

○

直感的に命令フローが識別しづらい60

Brief Analysis (API/strings)n○

アンパック後のEMOTETはImport Tableが空動的にAPIを解決している可能性

n○

APIに関連する⽂字列もなし
API含む⽂字列が暗号化・難読化されている
可能性 🥺

61

Where to start?
n

APIの動的解決や⽂字列の復号に使⽤されている関数
は実⾏時に何度も呼び出される

n

そのため、まずは各関数が呼び出されている回数を調べて、呼び出し回数が多い関数をみていくことにする

utils.pyに定義済みのget_func_xref_count関数を実⾏し、呼び出し回数の多い関数を列挙

62

FUN_10017e12n

最も多く呼び出されている関数

nRETするだけのジャンク関数n「 _ 」 など適当な名前に変えておく63

FUN_10007d5bn

2番⽬に多く呼び出され
ている関数

n

4つの引数を受け取るが、実際は第⼆引数は未使⽤

nDAT_1001fa20はグロー
バル変数の配列

FUN_10007d5bの呼び出し例 (@0x10017e02)

各要素が4バイトの配列？
第⼀引数は配列のインデックス

64

FUN_10007d5bn

変数名・型名の変更
少し⾒やすくした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Step 2:
API Hashing and
Dynamic Call

66

How Windows APIs are resolvednn

暗黙的な解決○コンパイル時に、必要なAPIがインポートテーブルに定義され、実⾏時にOSが暗黙的にDLLをロードし、APIを解決する○

普通にコンパイルするとこの⽅法で解決される

明⽰的な解決（動的API解決）○

LoadLibrary / GetProcAddress を⽤いて実⾏時に動的にAPIを呼び出す○

API名から簡単に利⽤するAPIが識別されてしまう
MessageBoxAを明⽰的に解決して呼び出す例

FARPROC MessageBoxA = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
MessageBoxA(NULL, "Hello world", "Hello", MB_OK);

67

API Hashingn

動的にWindows APIを解決する際に⽤いられる耐解析⼿法の⼀つ

n既知、もしくは独⾃実装のハッシュ関数でAPI名をハッシュ化して保持しておき、実⾏時に、エクスポートされたAPI名を列挙してハッシュ関数の計算結果が同じAPIを動的に呼び出す
例: CRC32を使⽤してAPI名をハッシュ化し、実⾏時に⽐較して動的に解決

コンパイル時実⾏時

CRC32
CreateFileA

0x553B5C78

擬似コード

CreateFileA = resolve_api(0x553B5C78);
CreateFileA(...);

kernel32.dll

CRC32

ReadFile

0xD0B7641A

WriteFile

0x3E5BC8E3

CreateFileA

0x553B5C78
動的に解決

68

FUN_10004ae6nFUN_10002b48の呼び出し○

in_FS_OFFSET (=FSレジスタ)
へのアクセス○x86 の場合、FSレジスタには
TEB (Thread Environment
Block) へのポインタが格納されている

n

FUN_100180eaの呼び出し

69

Thread Environment Block /Process Environment BlocknnThread Environment Block (TEB)○

現在のスレッドに関する情報を保持する構造体○

FSセグメントレジスタからアクセス可能(x86 Windowsにおいて)Process Environment Block (PEB)○

現在のプロセスに関する情報を保持する構造体Ø プロセスに読み込まれているDLLや、ヒープの状態、実⾏ファイルの情報など○

TEB構造体のオフセット0x30からアクセス可能FSレジスタ経由でTEBにアクセスし、そこからPEBにアクセスしているThread Environment Block /Process Environment Block

70

71

Apply Structuren

PEB構造体を事前定義したヘッダファイルをGhidraにインポート(すでに適⽤済み)○File > 「Parse C Source」 > win_internal.hを選択 >「Parse to Program」

72

FUN_10002b48
n

PEBのポインタを取得する関数○

in_FS_OFFSETをAuto Create Structure

BEFORE

AFTER(name)

AFTER(type)

FUN_10002b48

get_peb

PEB *in_FS_OFFSET

teb

TEB *

73

FUN_10004ae6n

読み込まれているDLLのベースネームを列挙し、FUN_100180eaに渡している

n

FUN_100180eaの戻り値を0x1fc325daとXOR

n

その結果を引数の値と⽐較

BEFOREAFTER (name)

AFTER (type)

iVar1

peb

PEB *

puVar3current_module_entry

LDR_DATA_TABLE_ENTRY *puVar4

next_module_entry

LDR_DATA_TABLE_ENTRY *プロセスに読み込まれているDLLの列挙

DLLのベースネーム（ファイル名）を渡している
戻り値をXORして引数と⽐較

74

FUN_100180ea1.

この関数はハッシュ関数です、
アルゴリズムを解析してくだ
さい

2.

実際はFUN_100180eaの戻り値を0x1fc325daとXORして⽐較していました。よって、次のように、 DLLベースネームとXOR鍵の⼊⼒を受け付けて適切なハッシュ値を返す
関数(calc_hash)をGhidra
Scriptで実装してください

演習

75

FUN_100180ean

BaseDllName.Bufferの型はwchar_t *(=unsingned short *)n

1バイトずつ以下処理

n○⼤⽂字ASCII（0x40 < x <0x5b）の場合、⼩⽂字(+0x20)に変更○

結果を状態(iVar2)に加算
し、0x1003fを乗算

結果のint値を返す

ハッシュ関数であると判断できる(「calc_hash_case_insensitive」に変更)

76

FUN_100180ean

DLLベースネームとXOR鍵を受け取り、ハッシュ値を返す関数

77

FUN_10004ae6ncalc_hash_case_insensit
ive関数の呼び出し元に戻るn

これまでの結果から、DLL名をハッシュ化した値を受け取り、ロード済みDLLの中から該当するDLLを探してベースアドレスを返す関数だとわかる○n

ベースアドレス: メモリにロードされているDLLのPE
ヘッダへのポインタ

関数名を「find_lib」に変えておく

BEFOREAFTER (name)

AFTER (type)param_1

hashed_dllname

N/A

uVar1

hashedN/A

FUN_10004ae6

find_lib

N/A

78

PE Header Overview
Windows実⾏可能形式(PE)のファイルフォーマット
n

実⾏ファイルに関する様々な情報を保持している○

Export TableØ○

エクスポート関数⼀覧

Import TableØ

使⽤するAPI⼀覧

https://tech-zealots.com/malware-analysis/pe-portable-executable-structure-malware-analysis-part-2/

79

FUN_10012794

なにもわからない80

FUN_10012794
n

エクスポート関数の⼀覧を保持する
エクスポートテーブルにアクセスしている

DLLのベースアドレス、つまりIMAGE_DOS_HEADERへのポインタが引数に渡されているOptionalHeader.DataDirectoryはさまざまなデータを格納する連想配列でインデックス0にはExport Tableへのオフセットが格納されている型を適⽤すると都度変数名が変わってしまうので、変更”前”の変数名で表記

BEFOREAFTER (name)

AFTER (type)param_2

base_address

N/A

param_4

hashed_api

N/A

iVar1

nt_headers

IMAGE_NT_HEADER32 *pcVar8export_table

IMAGE_EXPORT_DIRECTORY *iVar2functions_rva

N/A

iVar3

names_rva

N/A

iVar4

ordinals_rva

N/A

uVar5

value

N/A

uVar6

i

N/A

pcVar7

export_func

LPVOID

エクスポート関数のRVAの配列
エクスポート関数名のRVAの配列
エクスポート関数の序数のRVAの配列

エクスポート関数名を列挙

エクスポート関数のアドレスを取得

エクスポート関数のアドレスがフォワードされたものか確認

81

FUN_10012794
n

列挙したエクスポート関数
をFUN_1000df50に渡す

n

戻り値を0x5a80eaeとXOR

n

XORした結果を第四引数と⽐較

n

合致した場合、対象APIのアドレスを返すnExport ForwardされたAPIは
別途APIを解決※export forwardとはhttps://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table
https://devblogs.microsoft.com/oldnewthing/20060719-24/?p=30473

82

FUN_1000df50n

API名が渡される関数nほぼ
calc_hash_case_insen
sitiveと同じ（⼩⽂字変換処理がない）83

Calcurate Hashn

FUN_10012794関数内にハードコードされたXOR鍵

Pythonで実装

呼び出し元の例 (@0x10017e02)

合致している84(Advanced) Calcurate Hash by Emulatorn

アルゴリズムの実装がわからなくても、Emulatorで実⾏してしまうことも可能

1.

（エミュレータ内の）ECXに⽂字列を格納（=引数に渡す）

2.

0x1001285aから0x10012864まで実⾏

3.

EAXに格納されている値（=ハッシュ化済みの⽂字列）を取得

85

emulator.pynGhidraが提供している
EmulatorHelperクラス
を使⽤し、エミュレータ
を実装

86

(Advanced) Calcurate Hash by Emulator
GhidraのEmulatorでハッシュ関数をエミュレート

実際に渡されているハッシュ値

87

FUN_10007d5b
引数のハッシュ値をもとにAPIを動的解決し、グローバ
ル変数にキャッシュ

関数

変数
BEFOREAFTER (name)

BEFOREAFTER (name)param_1

index

FUN_10007d5b

resolve_api

param_2

hashed_dll_name

FUN_10004ae6

find_lib

param_3

hashed_api_name

FUN_10012794

find_api

g_arrayg_api_table

pcVar1api

88Anti-API Hashing
プログラム内のハッシュ値⼀覧を取得し、API名を逆解決してコメントをつけたい
1.

2.

3.

Hash DBの作成
1.

API Hashに使⽤するXOR鍵を特定

2.

System32配下の主要なDLLのエクスポート関数名をハッシュ化

ハッシュ値とDBを突合
1.

スタックに積まれているスカラー値を全て取得

2.

Hash DBに登録済みのハッシュ値と突合

コメントをつける
1.

呼び出されるAPI名をコード上にコメント

89

Create Hash DB
make_hash_db.pyn%windir%¥System32¥*.dll(今回は簡略化のため⼀部
DLLのみ)に定義されている
エクスポート関数を列挙

n

検体内にハードコードされていたXOR鍵で関数名をエンコードし、↓形式のjsonで格納○<HASH>: <ORIG_NAME>

90

Create Hash DB
make_hash_db.pyn

出⼒されたdb.json

91

Lookup Hashes
Decompile View at FUN_10017d5an

DLL名とAPI名のハッシ
ュ値はresolve_api関数の引数に渡されている

n

値はスタック経由で渡されている

n

雑だが、PUSH命令の引数に渡されているスカラ
ー値が取得できればよさ
そうListing View at FUN_10017d5a

92

Lookup Hashesnプログラム内の全命令を列挙

n

PUSH命令の引数に渡されたスカラー値を取得

n

値をDB内のハッシュ値と⽐較n合致したらコメント・ブック
マークをつける

93

Lookup Hashesn

演習

与えられた命令がスカラー値をPUSHするものかチェックする関数を実装し、search_hash_not_impl.pyを完成させてください

search_hash_not_impl.py

94

Lookup Hashes

演習n

与えられた命令がスカラー値をPUSHするものかチェックする関数を実装し、search_hash_not_impl.pyを完成させてください

n

ヒント:
○

inst.getMnemonicString()○

Inst.getOpObjects()

search_hash_not_impl.py

95

Lookup Hashesn

演習

与えられた命令がスカラー値をPUSHするものかチェックする関数を実装し、search_hash_not_impl.pyを完成させてください

実装したsearch_hash.py

96

Run Ghidra Scriptn

Script Managerで実⾏

１. Script Manager起動
３. 参照するDBを指定（make_hash_db.pyで作成したjson）
２. スクリプト名を検索して実⾏97Run Ghidra Script
コンソールに実⾏結果が表⽰されている

API名・DLL名のコメントがついている※コメントが表⽰されない場合、
Edit > Tool Options > Decompiler > Display の「Display EOL Comments」が有効化されていない可能性

98

Other API Hash Search Scriptn

EMOTETは、ハッシュ関数に使⽤されるXOR鍵が検体ごとに異なるため、検体ごとにDBを作成する必要があるが、既知のアルゴリズムが使⽤されている場合は、既成のDBを使⽤してルックアップすれば、ハッシュ関数の詳細をしらなくてもAPI Hashを解決できる場合がある○

shellcode_hash_search.py (Ghidra)■○

https://github.com/AllsafeCyberSecurity/ghidra_scripts/blob/master/shellcode_hash_search.
pyshellcode_hashes_search_plugin.py (IDA)■

https://github.com/fireeye/flare-ida/tree/master/shellcode_hashes

Step 3:
Decrypt Strings

100

Defined Stringsn

アンパック後のEMOTETには、不正活動に関連する可読⽂字列が存在しないn

⽂字列も暗号化・難読化されている可能性がある
Window > Defined Strings からプログラム内の可読⽂字列を表⽰できる

101

Where is Decryption Function?
n

⽂字列が暗号化されている場合、実⾏時に復号関数が呼び出されているはずn

復号関数は何度も呼び出されているはず

なにもしない関数
resolve_api関数
メモリ確保関数
メモリ解放関数
メモリ解放関数のラッパー
？？？

102

FUN_1000732d
⽐較的複雑な関数1.

ロジックを解析してくださいü

2.

17~24、35~50⾏に集中するといいかも

復号スクリプト
decrypt_string_stage1_not_impl.pyのdecrypt_string関数を実装してください

演習

103

FUN_1000732d
⾒やすくするヒント ①n

オフセットでアクセスされている場合、構造体を使⽤している可能性がある

nGhidraは独⾃構造体を⾃動で識別できないnAuto Create Structureで構造体を⾃動定義し、指定した変数に適⽤できる

param_2を右クリック > Auto Create Structure

変更後FUN_1000732d

uVar8は途中で0で初期化され、別の⽤途で再利⽤されている

⾒やすくするヒント ②n

同じレジスタが異なる⽤途で使いまわされている場合、変数名が付けづらい問題

nSplit Out As New Variable
で変数を分割すると⾒やす
くなる

uVar8を右クリック >Split Out As New Variable

後者がuVar9として再定義された

104

105

FUN_1000732d❶❷

1. 第２引数に、「暗号化された⽂字列に関す❸る情報を保持する構造体」へのポインタが渡されている•••

オフセット+0: XOR鍵オフセット+4: XOR鍵でエンコードされた、復号後⽂字列のサイズ
オフセット+8: 暗号データ

2.

4バイトで割り切れるサイズに調整

3.

復号⽂字列を格納するために、②のサイズ*2バイト分のバッファ確保

4.

enc_dataがemptyでないかチェック

5.

復号処理

1.
2.
3.
4.
5.
6.

❹

❺-❶
❺-❷
❺-❸
❺-❹

4バイトを1チャンクとして処理
4バイト分ポインタを進める
XOR鍵でデコード
下位バイトから順にバッファにコピー
4バイト分ポインタを進める

復号後のデータ末尾にNULLを追記し返す

❺-❺

❻

106

String Decryption Algorithmn

先頭4バイトがXOR鍵（keyとする）

n

オフセット+4から4バイトがXORされた（平⽂の）サイズ（xored_lengthとする）

n

key ^ xored_length が平⽂のサイズとなる（orig_lengthとする）n

オフセット+8以降から
orig_length分が暗号⽂

107

Decrypt Strings: Logic
暗号⽂字列のデータはEDX経由で復号関数 (FUN_1000732d) に渡されている解析結果をもとにGhidra Scriptで復号処理を実装

key
xored_length
enc

108

Decrypt Strings: Logicn

⽳埋め部分を実装

実装したdecrypt_string_stage1.pyのdecrypt_string関数

109

Decrypt Strings: How to get arguments?
n

ここまでで、暗号データのアドレスがわかれば⽂字列を復号できることがわかった

n

暗号データのアドレスは
EDXレジスタ経由で復号
関数（FUN_1000732d）の第２引数として渡されている

FUN_1000732dの参照元を辿ると、EDX経由でアドレスが渡されている

110

Decrypt Strings: How to get arguments?
ただし、例外もあるn

FUN_1000732dの「呼び出し元の関数の引数」としてアドレスが渡されているケースn

本来は対応すべきだが、今回は無視することにする

暗号データのアドレスが呼び出し元関数の引数として渡されている

111

Decrypt Strings: How to get arguments?
n

復号関数（FUN_1000732d）の呼び出し元の直前の命令を取得し、EDXにMOVされているアドレスを取得n

特定のアドレスの直前の命令を取得するためのユーティリ
ティ関数
get_instructions_beforeがutils.pyに定義済み

EDXにMOVしてる命令だけフィルター

112

Decrypt Strings: Assemble codesn

復号関数のアドレスはとりあえずハードコードしておく
decrypt_string_stage1.pyのmain関数

113

Decrypt Strings: Runn

復号後の⽂字列がコメントされている

Script Manager経由でdecrypt_string_stage1.pyを実⾏

実⾏結果はコンソールに出⼒される

Window > Bookmark からもコメントを確認できる

114

Decrypt Strings Stage2n復号関数のアドレスは常に固定とは限らない

n

復号関数を都度⼿動で解析して特定していては⾃動化の意味がない

複数サンプルを解析し、復号関数内の共通する特徴的な命令列を⾒つけ出し、それをもとに復号関数を⾃動特定する
本演習は自主学習にします。
Appendixに課題と解法が書いてあるので興味のある方はトライしてみてください。

Step 4:
Config Structure
Analysis

116

Config Structure Analysisn

EMOTETは通信先を検体内にハードコードしているn

通信関連のAPIを辿り、ハードコードされた通信先情報の取得を試みる
Bookmarksから、search_hash.pyでみつけた通信関連のAPIを確認できる

InternetConnectWの呼び出しもと

Find Hostname & PortnInternetConnectW API

pcVar1をRetype VariableでInternetConnectW *型に変更した後、引数名や型を⼿動で変更する

nInternetConnectWを呼び出しているFUN_10002b4fの第七引数にホスト名、第４引数にポート番号が渡されている

117

118

Find Hostname & Port
FUN_1000f70c内でcall_internetconnectwを呼び出している部分

ncall_internetconnectw（FUN_10002b4f）の呼び出し元を辿ると、FUN_1000f70cが呼び出していることがわかる

n

ホスト名が渡されている第七引
数は、FUN_1000f70cの第引数から渡されているn

ポート番号が渡されている第４引数には、in_stack_00000024という変数が渡されている○

実際はポート番号も
FUN_1000f70cの引数として渡されているのだが、Ghidraが関数の解析に失敗していて、解釈がおかしくなっている↓ポート番号
ホスト名→

call_internetconnectwに渡されているホスト名はFUN_1000f70cの第⼆引数から渡される

119

Fix Function: FUN_1000f70cnFUN_1000f70cの引数の解釈にGhidraが失敗している

n

そのため、Edit Function Signatureで引数の数を調整する
FUN_1000f70cの呼び出し元を⾒てみるとECX, EDX, PUSH*9で引数を渡している

FUN_1000f70cを右クリック > Edit Function Signatureで編集
Add Parameterで引数の数が合計11になるまで追加

120

Fix Function: FUN_1000f70cn

正しく引数の数を調整すると、FUN_1000f70cの第１１引数にポート番号が渡され、
call_internetconnectwの第４引数に渡されていることがわかる

n

FUN_1000f70cの呼び出し元でも引数が反映されている（名前は⼿動で変更）

121

FUN_10007e95nFUN_1000f70cを呼び出している関数がFUN_10007e95n

portへのアサインは⾒えるが、hostnameがどこからアサインされているかわからないn

ポート番号はDAT_1001fa08
からのオフセット0x24+0x4に格納されている○→ 構造体として定義しておく

portがアサインされている箇所

DAT_1001fa08に対し、Create Auto Structureで構造体定義

122

FUN_10017f0fn

フォーマット⽂字列を第１１引数として受け取る○n

IPアドレスのようなフォーマット

FUN_10017f0fは、実質_snwprintfを呼び出すだけ

IPアドレスのようなフォーマット⽂字列が渡される

123

Fix Function: FUN_10017f0fn

⼀⾒問題ないようだが、実は呼び出し
規約と引数の数が間違っている○__cdecl -> __fastcall○

引数の数 -> 14

Edit Function Signatureで修正関数シグネチャ修正後、変数名・型を変更した結果（param_2とextraout_EDXは実際同⼀だが、変数名が同期されず、今回は無視）

124

Config Structurencall_snwprintfでホスト名（IP）が⽣成されることがわかった

n

IPアドレスの各オクテットがcall_snwprintfの引数に渡されているn

ここまでの情報から、0x1001fa08には次のような構造体へのポインタが存在していると推測できるü

オフセット+0x24にはIPアドレスへのポインタが格納されているはずüIPアドレスはリトルエンディアンの4バイト（1オクテット1バイト）

ü

また、オフセット+0x24+0x4にポート番号が格納されているはず
第２オクテット
第３オクテット

第１オクテット
第４オクテット

ポート番号

125

Config Structuren

ここまでの情報をまとめて、
次のような構造体を定義
擬似構造体

typedef struct {byte ip_address[4];WORD port;} C2Info;
typedef struct {...
// at offset 0x24
C2Info *c2;
} Config;

Data Type Manager > emotet.dll 右クリック > New > Structure でC2Infoを新規作成し、次にPTR_1001fa08を右クリック > Edit Data Type からオフセット36をC2Info*に変更

126

Config Structuren構造体が適⽤されると⾒やすくなるn

ここで⽣成されたIPアドレスとポート番号が、先ほど解析したFUN_1000f70cに渡され、通信が発⽣する

n

つまり、これらのIPアドレスとポート番号がC&Cサーバの情報となることがわかる

127

Config Structureng_config(PTR_1001fa08)
はポインタを保持するだけのグローバル変数なので、Config構造体の実態へのポインタは実⾏時に格納されるnでは、いつ・どこで初期化されるのか？

g_configの参照元⼀覧を確認すると、WRITEしているのは0x100e258の⼀箇所のみ

128

FUN_1000e10b

pcVar1を右クリック > Auto Fill in Structure して
Config構造体をアップデートした後のコード

Config構造体を初期化する関数
1.

2.

3.

Config構造体⽤のバッファを確保し、g_configへポイ
ンタをコピー
DAT_1001f000からC2Info
構造体のポインタをコピー
DAT_1001f000には、1要素あたり8バイトの配列が格納されている

❶
❷
❸

129

FUN_1000e10b
編集後のFUN_1000e10bn

現在C2Info構造体は6バイトなので、（使⽤⽤途は不明だが）2バイト分追加しておく

nDAT_1001f000(g_c2info_array)をRetype
Globalし、C2Info構造体の配列として定義

130

Array of C2Infonn

DAT_1001f000(g_c2inf
o_array) にはIPアドレ
ス、ポート番号が平⽂で格納されている

C2Info

このデータをパースすれば通信先情報を取得できそう

ip_address
port
C2Infoip_address
port

C2Infoip_address
port

131

Protocol of Connection
IPとポートは判明したが、プロトコル(HTTP/HTTPS)は？
n

HTTPSを使う場合、WinInet系APIではHttpOpenRequestのdwFlagsでINTERNET_FLAG_SECURE(0x800000)が指定される
HttpOpenRequestの関数シグネチャ

https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-httpopenrequestw

132

Protocol of Connection
HttpOpenRequestWの呼び出し元を辿ると、INTERNET_FLAG_SECURE(0x800000)のビットは⽴っていないので、プロトコルはHTTPが使⽤されることがわかる

133

演習

Extract C2 in Action
extract_c2_stage1_not_impl.pyのparse_single_configを実装してくださいnC2Info

アドレス0x1001f000以降に存在しているIPアドレスとポートを以下形式で出⼒する○

http://<ip-address>:<port>

ip_address
port
C2Infoip_address
port

C2Infoip_address
port

134

Extract C2 in Action
1. g_c2info_arrayの先頭アドレスから処理を進める2.

先頭4バイトをIPアドレス、オフセッ
ト+4から2バイトをポート番号とし
てパース

3.

8バイト(C2Info構造体のサイズ)分
アドレスを進める

実装したextract_c2_stage1.py

135Extract C2 Stage2nコンフィグのアドレスは常に固定とは限らない

nコンフィグのアドレスを都度⼿動で解析・特定していては⾃動化の意味がない

複数サンプルを解析し、コンフィグ初期化関数内で共通する特徴的な命令列を⾒つけ出し、それをもとにコンフィグのアドレスを⾃動特定する

136

How to compare samples?
Version Trackingn

2つのバイナリの差分を解析するGhidraの機能○

パッチの差分解析○

共通する命令列の可視化

差分表⽰の例

137

Version Tracking: Main WindownVersion Trackingで、関数を⽐較したりする際に使⽤するウィンドウn

最初はなにも表⽰されない

Ghidraのプロジェクト画⾯から開く

Version Tracking Windowの初期画⾯

138

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]を実⾏

❶
❷

❸

139

Version Tracking: Compare Functions
Version Tracking Functionsを使うと、⽐較したい関数を⼊⼒すると差分が可視化できる(emotet.dllの0x1000e10bとemotet-2.dllの0x1001a094の⽐較例)

ブルー: ⼀致していない
コードユニット
グレー: 部分的に⼀致していないコードユニット
グリーン: バイト列、ニーモニック、オペランドのいずれかが異なっている箇所
ホワイト: 完全⼀致しているコードユニット

140

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

演習

141

Decrypt Strings Stage2: Find FunctionnVersion Trackingで複数サンプル内の命令列を⽐較

n

共通する特徴的な命令列を特定する
たとえばこのあたり

デコンパイル結果

142

Decrypt Strings Stage2: Find Functionn

特徴的な命令列を検索し、それの命令の引数に渡されているアドレスを取得○

Address[] findBytes(Address start, java.lang.String byteString, int matchLimit)Ø○

正規表現をつかった検索が可能

Instruction getInstructionAt(Address address)Ø

引数addressで指定したアドレスの命令を取得

レジスタや変数など、コンパイラ等によって可変なものは「.」(任意の⼀⽂字) にして⼀般化する143

Decrypt Strings Stage2: Fully Automatedfind_config_addressを実装したextract_c2_stage2.pyとして保存して実⾏

emotet.dllおよびemotet-2.dllで⾃動抽出が可能に

144

Summary

145

Ghidra Script for EMOTET
1.

APIハッシュの突合○

2.

⽂字列の復号○

3.

search_hash.py

decrypt_string_stage1.py

C&C情報の抽出○

extract_c2_stage1.py○

extract_c2_stage2.py

146(Advanced) It’s Automatic!
n

extract_c2_stage2.pyをHeadless対応したanalyzer.pyをHeadless Analyzerで実⾏すればバッチ処理も可能

Headless AnalyzerでCLI経由でanalyzer.pyを実⾏出⼒されたresolt.json (抜粋)147

Conclusionn

解析⾃体を⽬的とするのではなく、⾃動化を⽬的にするとスケールする

n

解析コードの共有により、知識をサイロ化させない・再利⽤可能にする○解析結果だけでなく、再利⽤可能なコードとして共有する（Analysis as a Code）○

Ghidra Scriptは誰でも再利⽤可能なので、有効な選択肢の⼀つ○

解析者にもGithubificationが広がると、コミュニティ全体の能⼒が向上する
Ø

The Githubification of InfoSec: https://medium.com/@johnlatwc/the-githubification-of-infosecafbdbfaad1d1

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

152

Decrypt Strings Stage2n復号関数のアドレスは常に固定とは限らない

n

復号関数を都度⼿動で解析して特定していては⾃動化の意味がない

複数サンプルを解析し、復号関数内の共通する特徴的な命令列を⾒つけ出し、それをもとに復号関数を⾃動特定する

153

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

演習

154

Decrypt Strings Stage2: Find FunctionnVersion Trackingで複数サンプル内の命令列を⽐較

n

共通する特徴的な命令列を特定する
たとえばこのあたり

デコンパイル結果

155

Decrypt Strings Stage2: Find Functionn

特徴的な命令列を検索し、それらの命令列を含んでいる関数⼀覧を取得○

Address[] findBytes(Address start, java.lang.String byteString, int matchLimit)Ø○

正規表現をつかった検索が可能

Function getFunctionContaining(Address address)Ø

指定したアドレスを含む関数を取得

レジスタや変数など、コンパイラ等によって可変なものは「.」(任意の⼀⽂字) にして⼀般化する

156

Decrypt Strings Stage2: Fully Automated
実装したスクリプトをdecrypt_string_stage2.pyとして保存して実⾏

複数の復号関数を⾒つけ、引数に渡されている⽂字列を復号している

How to get
unpacked samples
by yourself

How to get unpacked
samples by yourself
n

必要なもの
○

仮想環境（VMWare/VirtualBox/Hyper-V等）○

Windows OS（ゲストOS）○

ANY.RUNアカウント○

Process Explorer / Process Monitor (なくても可)○Hollow Hunter■n

https://github.com/hasherezade/hollows_hunter/release

以後の作業は全て構築した仮想環境内でおこなう

158

How to get unpacked
samples by yourself
以下ANY.RUNのページを開き、実⾏されているDLLをダウンロードnurcwzowo.xck (=packed emotet.dll) MD5:19b0124f2e4f223113bb11a84765a6c3○nhttps://app.any.run/tasks/4060b18e-8132-42c0-a0bc-65f398d6bfb2/

eclh.gfk (=packed emotet-2.dll) MD5:714cdae2b20896e72d92e28dc831b81b○

https://app.any.run/tasks/b9be3b5e-368d-4248-87c2-e06b3d66769e/

159

How to get unpacked
samples by yourself
仮想環境を外部接続不可な状態にし、EMOTETを適切な引数で実⾏

このとき、Process ExplorerやProcess Hackerなどで実⾏された
rundll32.exeのプロセスシーケンスを確認しておく

160

How to get unpacked
samples by yourself
実⾏後5-10秒ほど待機し、⼦プロセスのPIDをHollows Hunterに渡して実⾏
n

成功すると実⾏フォルダ配下にprocess_<PID>フォルダが作成され、中にアンパック後のEMOTETのDLLが保存される

161