Emotet vs EmoCheck
Emotet開発者との戦い
NTT
JPCERT/CC
JPCERT/CC

社会情報研究所
インシデントレスポンスグループ
インシデントレスポンスグループ

谷 知亮
喜野 孝太
佐條 研

$whoami

Tomoaki Tani

Kota Kino

Ken Sajo

NTT 社会情報研究所 社会理論
研究プロジェクトに所属。主にマルウェア分析技術や脆弱性分析
技術の研究を行っている。以前
はJPCERT/CCでマルウェア分
析やインシデント対応に従事していた。Virus Bulletin,
CODEBLUE, BotConf,
BsidesLV, BlackHat USA
Arsenalなどで講演。

国内ITベンダーでのメールセキュ
リティ製品の導入支援業務を経て、2019年よりJPCERT/CC。 現
在は、標的型攻撃への対応も含
んだ、日本国内におけるさまざまなインシデントの分析業務に従事。 CODE BLUE、Botconfなどで講演。

金融系企業でのセキュリティ監
視業務を経て、現在はインシデン
ト対応やマルウェア分析、脅威情
報分析の業務に従事。傍ら、個人の活動としてばらまきメールの情報発信・分析も行っている。
JSAC2020スピーカー、
JSAC2021ベストスピーカー
2

本日の目的- 最新のEmotetアップデート状況の共有- Emotet検知ツールEmocheckの機能解説- 攻撃者が使う難読化技術とその実装例の紹介- マルウェア検知ツールを作る意義

3

Emotetとは？ (~2021/1)(引用) https://jsac.jpcert.or.jp/archive/2021/pdf/JSAC2021_104_sajo-sasada_jp.pdf , p9
4

アジェンダ

1. 復活したEmotet(2021/11~)
2. EmoCheck概要
3. Emotet vs EmoCheck
4. Emotetのバイナリ難読化処理
5. EmoCheckのバイナリ難読化の実装手法
6. まとめ5復活したEmotet（2021/11 ~)Emotet本体の変更点◦ 利用する公開鍵暗号方式の変更(RSAからECC)◦ 利用する共通鍵暗号方式の変更(AES128からAES256)◦ 通信プロトコルの変更(HTTPからHTTPS)◦ 本体のコマンドセットの追加◦ C&Cサーバーのリストの格納方式の変更

7

Emotet本体のコマンドセット
コマンド

内容

1

自身のアップデート

2

モジュールをロードし、実行

3

EXEファイルをダウンロードし、実行

4

EXEファイルをダウンロードし、（特定のユーザで）実行

5

DLLファイルをメモリ内にインジェクションし、実行

6

DLLファイルをダウンロードし、実行（ regsvr32.exe）

7

New

DLLファイルをダウンロードし、実行（ rundll32.exe）8モジュールの追加
モジュール
プロセスリスト

内容

New

プロセス一覧の取得

メール PassView

メールクライアントのパスワード窃取

Webブラウザ PassView

Webブラウザのパスワード窃取

Outlookアカウントスティーラー

Outlookに保存されている連絡先の窃取

Outlookメールスティーラー

Outlookに保存されているメールの窃取ThunderbirdアカウントスティーラーNew

Thunderbirdに保存されている連絡先の窃取ThunderbirdメールスティーラーNew

Thunderbirdに保存されているメールの窃取

メール送信モジュール

スパムメールの送信

返信型メール送信モジュール

窃取したメールから返信を装ったスパムメールの送信

ネットワーク拡散モジュール

ローカルネットワーク内をブルートフォース

9

Emotetが配信するマルウェア● 再開前（~ 2021/1)
○
○
○
○
○

Trickbot (-> Cobalt Strike beacon)
QakbotIcedID
Ursnif(日本のみ)
ZLoader (〃)● 再開後（2021/11 ~)○ Cobalt Strike beacon
10国内のEmotet感染状況 (~2022/01/13)
窃取され悪用された国内ドメイン(.jp)のメールアカウント数

takedown

11

EmoCheck概要

EmoCheckとは？

◦Emotetのプロセスを検知するツール
https://github.com/JPCERTCC/EmoCheck

◦
◦
◦
◦
◦

初版リリース: 2020/2/3
最新バージョン: 2.0
ダウンロード回数: > 60万回
多言語対応: 日/仏/英
想定利用者:▫ すべてのWindowsユーザー▫ EDRなどを導入していない人

13

私、Emotetに感染してますか？

14

なぜEmoCheckは作られたのか？

そんな疑問にお答えするためのツール（インシデント対応を楽にする）

15

要求仕様

1. 簡単に利用できる
a.
b.
c.
d.
e.

Windows NT6.1以上で動作
ポータビリティがある（no setup)
最小限のユーザー操作
ファイルサイズが小さい
1秒以内に結果を出力

2. 確実に感染を検知する
a. Emotetローダーのプロセスが動作 == 感染
b. MalDocの実行 != 感染

3. 極力フォレンジックの阻害しない16

開発言語の選定 (2019年12月時点)
C++ (MSVC)
環境依存❍

ポータビリティ❍

ファイルサイズ

❍

実行速度

.NET

Go

△

❍

.Net Core依存△.Net Core依存

PowerShell

Python△✗外部スクリプト実行

要インタープリタ

✗

❍

△
❍

❍

要インタープリタ✗△.Net Core含む

肥大化しやすい

❍

❍

❍

?

✗

フォレンジックへの
影響

❍

❍

❍

△

✗

難読化の容易さ

△

❍

?

❍

❍

Undocumented
APIの利用❍？

△△ConfuseEx❍

17

Emotet vs EmoCheck(検知ロジックについて）

検知の基本方針

Emotetは必ず永続化をする★ Malware-as-a-ServiceであるEmotetのビ
ジネスは感染端末数の維持が必須
EmoCheckは永続化手法の再現し、痕跡を探す
19

“

戦いの始まり20

ドライブシリアルによるキーワード選択型

2020.1.21 -● 独自のキーワードリストを保持● ドライブシリアルからキーワードを複数選択しファイル名設定
キーワードリスト

キーワード選択ロジック
seed = GetSystemDriveSerial();q,r = divmod(seed/sizeof(keywords));
select_keyword(q,r);

環境ごとに固定のキーワードの組
み合わせが選択される
21

EmoCheck Scanner 1

ver.0.0.1

2020.2.3

1.

ドライブのシリアル番号を取得（例. 0x2EA4B3F0）

2.

シリアル番号をキーワードリストの文字列長で除算し、剰余の値をポインタとして使用（例. 0x2EA4B3F0 % 18 = 8）

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

a

p

p

l

e

,

o

r

a

n

g

e

,

l

e

m

o

n

3.

ポインタの位置にあるキーワードを選択（例. orange）

4.

1 ～ 3. で選択した２個のキーワードを結合した、[keyword1+keyword2].exeのプロセス名が存在すれば検知（例. orangeapple.exe）22３日後に永続化方式が置き換えられ、
EmoCheckで検知できなくなる。
※Emotet 開発モードへ移行

23

Emotet Update

BeforeAfter

24システムファイル名+レジストリにファイル名保存2020.2.6 -1. システムディレクトリ配下で拡張子がdllまたはexeのファイル名
をランダムに選択し、実行ファイル名へ設定
2. 実行ファイル名を特定のレジストリへエンコードして保存（マーカー）
レジストリパス
HKLM(HKCU)\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer
キー
ドライブシリアル番号値
key = GetSystemDriveSerial();
RegValue = XOR(FileNameOfEmotet,key);

25

EmoCheck Scanner 2
1.

ドライブのシリアル番号を取得（例. 0x2EA4B3F0）2.

以下のレジストリに格納されている値を取得

ver.0.0.2

2020.2.10

レジストリパス
HKLM(HKCU)\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer
キー
ドライブシリアル番号3.

2. で取得した値をドライブシリアルでXORデコードしEmotetのファイ
ル名を取得

4.

Emotetのファイル名と同じプロセスが存在すれば検知
26

Twitterでの声(超意訳）
EmoCheckみたいなツールをオープンソースで公開するのってどうなのよ？
Emotetの難読化がさらにひどくなる未来しか見えない。

27

“

EmoCheckに難読化を！

28

2020年4月 Emotet更新※ばらまきメール配信停止中※Emotet 開発モード中

29

システムファイル名からのランダム選択

2020.4.20 -◦ システムディレクトリ配下で拡張子がdllまたはexeのファイ
ル名[A]、[B]を選択（ランダム）◦ %LOCALAPPDATA%\[A]\[B].exeにEmotetを保存◦ Runキーまたはサービスへ永続化設定

30

EmoCheck Scanner 3

ver.1.0

2020.8.11

1. システムディレクトリ配下で拡張子がdllまたはexeのファイル名を
全抽出
2. %LOCALAPPDATA%\[A]\[B].exe に該当するファイルを検索
a.

[A] == 1で取得したファイル名b.

[B] == 1で取得したファイル名

3. 同一名の正規の実行ファイルと同一であれば除外
4. 実行中の全プロセスのコマンドラインを取得
5. 2のパスを含むプロセスを検知
31

2020年11月 Emotet活動停止。
※Emotet 開発モードへ移行

32

Emotet Update

BeforeAfter33ランダム文字列 + rundll32経由での実行

2020.12.21 -◦ 以下のパターンでEmotetを設置%LOCALAPPDATA%\\[%s]{4,16}\\[%s]{3,15}.[%s]{3}◦ 以下のコマンドラインで起動▫ rundll32.exe [Emotetのパス], Control_RunDLL◦ コマンドラインをRunキーまたはサービスへ永続化設定

34

EmoCheck Scanner 4

ver.2.0

2021.1.27

1. 以下のパターンのファイルパスを検索
a. %LOCALAPPDATA%\\[%s]{4,16}\\[%s]{3,15}.[%s]{3}2. 実行中のプロセスのコマンドラインを取得
3. 以下の正規表現と一致するコマンドラインを検知a.

^.*rundll32\\.exe.*,.*RunDLL.*$b.

^.*rundll32\\.exe.*,.*ShowDialogA.*$c.

^.*rundll32\\.exe.*,.*[A-Za-z]{4,15}.*$2021.11以降のEmotetも検知可能
35

“

正規表現での検知....?
（False Positive/False Negative)36

変わらないEmotetの挙動

特徴的なローダー実行判定を行う。
コピーを起動

自身の実行ファイルのタイムスタンプ確認

システム時刻と
の差が
1週間未満

NO

YES

永続化用の
ディレクトリ作成

自身のコピー作成

lastWritetime
システム時刻

lastWritetime
システム時刻 - 604800 sec

ローダーの実行37

FILETIME構造体

Windowsは時刻情報を100ナノ秒単位で管理

https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-ﬁletime38検証1: 環境差による差分時間を範囲

FILETIME構造体による時刻情報の差はどうなるか？
永続化用のディレクトリ作成

自身のコピー作成

lastWritetime
システム時刻

lastWritetime
システム時刻 - 604800 sec

検証環境： 物理環境(CPU: Intel Core i7 3770) / 仮想環境 (VMWare)
検証結果：[Emotetが作成するディレクトリのlastWritetime] - [本体コピーのlastWritetime] ≒604800.000sec ± 0.090 sec に収束。（10回ずつ実行した平均値）
39

EmoCheck Scanner 5ver.2.0

2021.1.27

1. 以下格納フォルダとファイルのLastWriteTimeの差分が604800.000sec ± 0.100 sec未満のファイルを列挙
a. %LOCALAPPDATA%
b. %WINDIR%\system32

2. 実行中のプロセスをコマンドラインを取得
3. 列挙したファイルを実行するプロセスを検知
2021.11以降のEmotetも検知可能

40

Emotetの難読化処理一般的なC/C++プログラムの難読化の流れ
各抽象レイヤーでそれぞれ難読化処理（ &最適化処理）が施される。

Abstruction

Source CodeIntermediate
Representation

Binary MachineCodeobfuscation

obfuscation

obfuscation

42

Emotet（Loader）に施されている難読化

2020.12.21 -MethodAbstruction

Unit

TargetString Obfuscation

maybe
Source CodeInstruction

Data Constant

Mixed Boolean Arithmetic

Intermediate
Representation

Basic Block

Data ConstantControl Flow Flattening

Intermediate
Representation

Function

Code Logic

Win32API Hashing
Obfuscation

Source CodeFunction

Code
Abstraction

Function Argument
Randomization

Intermediate
Representation

Function

Code
Abstraction
43

String Obfuscation
文字列をXORエンコードし、バイナリ内に格納。文字列の利用時のみに Heap領域へ復号。
XOR-keyは文字列ごと /検体ごとに異なる 。
難読化前（ pseudo-code)

reg_path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
難読化後 (pseudo-code)

reg_path = mal_string_decrypt(&enc_registry_path);&enc_registry_path(0x100014F8)

100014F0 00 00 00 00 00 00 00 00
10001500 0C B1 0B 66 08 BF 1F 77
10001510 39 8A 11 65 36 90 29 5D
10001520 31 8A 1B 57 2D 8D 24 5D
10001530 97 DB FB 1E 33 ED 59 B7

XOR-Key

XORed Size(=45)

5F FE 4D 32 72 FE 4D 32
03 B3 24 51 2D 91 3E 5D
XORed String
28 8D 11 71 2A 8C 3F 57
31 A2 1F 47 31 60 5A 72
93 AD B5 39 38 04 AE 32

44

Mixed Boolean Arithmetic
定数値/変数の初期値と同値となるような算術演算とビット演算の組み合わせ(XOR,ShiftLeft,ShiftRight,Addなど）に置換し、データを難読化。
難読化前

mov

[esp+84h+regsam], 02h; KEY_SET_VALUE

難読化後

mov
shl
shr
add
xor

[esp+84h+regsam], 0D4B91Fh[esp+84h+regsam], 0Eh[esp+84h+regsam], 0Dh[esp+84h+regsam], 0FFFF4A0Bh[esp+84h+regsam], 0BC4Bh; [esp+84h+regsam] => 00D4B91Fh; [esp+84h+regsam] => 2E47C000h; [esp+84h+regsam] => 0001723Eh; [esp+84h+regsam] => 0000BC49h; [esp+84h+regsam] => 00000002h((((((0xD4B91F << 0xE) & 0xFFFFFFFF) >> 0xD) & 0xFFFFFFFF) + 0xFFFF4A0B) & 0xFFFFFFFF) ^ 0xBC4B = 2

45

Control Flow Flattening
ジャンプテーブルを作成し、制御フローを平坦化し、 BasicBlockの実行順を難読化。
実行順の制御には Routing変数を用いる。

Prologue

PrologueA

check routing variable

Transform

routingA
B

C

init routing variable

update routing variable

B

C

Epilogueupdate routing variable update routing variable

Epilogue46

Control Flow Flattening
Emotetでは単一のRouting変数によって Basic Blockの実行順を難読化。
Emotetの関数 CFF解除（pseudo-code)

Emotetの関数: CFF (pseudo-code)

BOOL mal_DeletePersistentRegistry(){
LPCWSTR lpValueName;
const WCHAR *reg_path;
HKEY phkResult;
BOOL status;
lpValueName = NULL;

BOOL mal_DeletePersistentReg(){
int route;
LPCWSTR lpValueName;
const WCHAR *reg_path;
HKEY phkResult;
BOOL status;
lpValueName = NULL;
route = 0xC62CE70; // init routing variablewhile ( 1 ) {while ( 1 ){while ( 1 ) {while ( route == 0x6EF1DF9 ){reg_path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"route = 0x9084733; // update routing variable
if(!RegCreateKeyExW(hKey, lpSubKey, 0,0,0, regsam,0, phkResult,0))route = 0x7FE3D14; // update routing variable
HeapFree(reg_path);
if ( route == 0x9084733 ) return status;}
if ( route != 0x7FE3D14 ) break;route = 0xE13E5E7;// update routing variable
status = RegDeleteValueW(phkResult, lpValueName) == 0;}
if ( route != 0xC62CE70 ) break;…(snip)// Block 1: Get registry value name from global data section.
for (int i = pGlobalData + 564; *i != '\\'; i += 2 );lpValueName = (i + 2);// Block 2: Open Windows registry key handle
reg_path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"res = RegCreateKeyExW(0,lpSubKey,0,0,0,regsam,0,phkResult,0);
HeapFree(reg_path);
if (res == ERROR_SUCCESS)// Block 3: Delete registry value
status = RegDeleteValueW(phkResult, lpValueName) == 0;// Block4: Close Windows registry key handle
RegCloseKey(phkResult);
return status;}

47

Win32API Hashing Obfuscation
Win32API名のHash値を使い、PEBから関数ポインタを探し、 API呼び出しを行うことで、プログラムが使用するAPIを難読化。
難読化前（ pseudo-code)

RegDeleteValueW(hKey, lpValueName);

難読化後（ pseudo-code)

sub_1001BCBC(hKey, lpValueName);
sub_1001BCBC(HKEY hKey, LPCWSTR lpValueName){
LPVOID func;
DWORD ApiHash = 0xCDA8113B;
DWORD LibHash = 0x004E81E0;
func = GetFunctionPtrByHash(idx, dwLibHash, dwApiHash);return func(hKey,LPCWSTR lpValueName);}48

Function Argument Randomization
関数コールの引数に不必要な定数と追加し、引数の順番を入れ替える。追加された引数はプログラ
ムの動作には無関係であり、呼び出し元の関数のスタックに保持される。

難読化前（ pseudo-code)

reg_path = mal_DecryptString(&enc_registry_path);// エンコードされた文字列を復号

難読化後（ pseudo-code)

reg_path = mal_DecryptString(732570, 117490, 388602, &enc_registry_path);49

Emotetの難読化処理
それぞれの難読化が各抽象化レイヤーで適用されている。

Source CodeIntermediate
Representation

Binary Machine
Code

Main function of Emotet

Win32API Hashing
Obfuscation
String Obfuscation

Control Flow Flatten
Function Argument
Randomization

PackerMixed Boolean Arithmetic

50

EmoCheckのバイナリ
難読化の実装手法

“

いたちごっこに歯止めを。

52

難読化実装の要件

1. Emotetと同等の難読化を含む (2020.2.6時点)
a.

Control Flow Flattenb.

Win32API Hashing Obfuscationc.

String Obfuscation

2. プロジェクト単位で難読化できる
3. 自動で難読化コンパイルa.

1コマンドで難読化コンパイルができる

4. 無償でできる
53

難読化関連の利用プロダクト

C++プロジェクト管理●●

CMake (>3.15)
Ninja build

コンパイラ

●
●

LLVM 9.0 （通常コンパイル用）
O-LLVMとObfuscator Hikari (難読化コンパイル用)[13] [14]

ライブラリ

●
●

API難読化ライブラリ（別途実装) 参考:[9] [10]
文字列難読化ライブラリ (別途実装) 参考:[9] [10]54

難読化の流れPhase1
ソースコード
難読化

Phase2
中間表現
難読化

C++プリプロセッサ
テンプレートメタプログラミング /constexpr● String Obfuscation● Win32API Hashing Obfuscation

コンパイラ機能 (LLVM-pass)● Control Flow Flattening● String Obfuscation● Split Basic Blocks etc

55

難読化の実装
ソースコードレイヤー

56

C++プリプロセッサとテンプレートメタプログラミング/constexpr
C++のソースコード難読化 [7][8]ではマクロなどのプリプロセッサ機能とメタプログラミング技法であるテンプレートメタプログラミング [5]とconstexpr[6]を利用。
C++プリプロセッサ

テンプレートメタプログラミング

文字列操作を利用して、コンパイル時にソースコードの変換 を行う。

テンプレートを利用して、コンパイル時に（チューリング完全な）演算処理を行い、ソースコードの生成と変換 を行う

57

メタプログラミングの例
例：階乗の計算（メタプログラミング なし）

main関数

factorial関数

int factorial(int n) {if (n == 0)
return 1;
return n * factorial(n-1)
int main(int, char**){int x = factorial(4); // == 4*3*2*1 == 24
int y = factorial(0); // == 1
}

コンパイル

58

メタプログラミングの例
例：階乗の計算（メタプログラミング あり）

main関数template <int N>
struct Factorial {enum {value = N * Factorial<N-1>::value};};
template <>struct Factorial<0> {enum {value = 1};};
int main(int, char**){int x = Factorial<4>::value;// 4*3*2*1 == 24

int y = Factorial<0>::value; // == 1
階乗の結果が即値になっている}

一時ソースコード
int main(int, char**){

コンパイル
コンパイル

int x = 24; // == 4*3*2*1 == 24
int y = 1; // == 1}

59Win32API Hashing Obfuscationの実装
ソースコード内の API文字列からハッシュ値を計算する

メタプログラミング

ソースコードの Win32APIの呼び出しをハッシュ値による呼び出しに置換

マクロ

ハッシュ値による Win32API呼び出しの実装

MSVC

PEBからAPIアドレステーブルと API名一覧取得
API名のハッシュ値を逐次計算し、一致する APIを探索一致したAPI名の関数ポインタを返す
60

Win32API Hashing Obfuscationの実装
FNV-1a ハッシュをメタプログラミング用コード// FNV hash parameters// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hashconstexpr uint32_t val_32_const = 0x811c9dc5;
constexpr uint32_t prime_32_const = 0x1000193;
constexpr uint64_t val_64_const = 0xcbf29ce484222325;
constexpr uint64_t prime_64_const = 0x100000001b3;
inline constexpr uint32_t Hash32Fnv1aConst(const char* const str, const uint32_t value = val_32_const) noexcept {return (str[0] == '\0') ? value : Hash32Fnv1aConst(&str[1], (value ^ uint32_t(str[0])) * prime_32_const);}

コンパイル時に API名がハッシュ値に変換される

MessageBoxA

fnv-1a0x23a979e4
61

Win32API Hashing Obfuscationの実装
ハッシュ値からAPIの関数ポインタを取得するテンプレート
template <uint32_t hash>static void* GetApiPtrByHash(const wchar_t* dll_name = NULL) {static void* function_ptr;
if (!function_ptr)
function_ptr = GetFuncByHash(hash, dll_name);
return function_ptr;}

Win32APIをハッシュ値によるAPI呼び出しに置換するマクロ#define OBF_CALL(dll_name, name)(reinterpret_cast<decltype(&name)>(obfuscation::GetApiPtrByHash<obfuscation::FnvHash(#name)>(dll_name)))62

Win32API Hashing Obfuscationの実装
メタプログラミングとプリプロセッサによるソースコード生成イメージ
MessageBoxA(0, "World!", "Hello", MB_OK);
マクロ適用（ソースコード書き換え）

OBF_CALL(L"user32.dll", MessageBoxA)(0, "World!", "Hello", MB_OK);
コンパイル（メタプログラミングによるソースコード生成）

static void* function_pointer = GetApiPtrByHash(0x23a979e4, L"user32.dll");function_pointer(0, "World!", "Hello", MB_OK);

63

Win32API Hashing Obfuscationの実装
コンパイル結果の比較
MessageBoxA(0, "World!", "Hello", MB_OK);

OBF_CALL(L"user32.dll", MessageBoxA)(0, "World!", "Hello", MB_OK);

API名が見える

ハッシュ値による
API解決
API名が見えない64

難読化の実装
中間表現レイヤー

65

LLVM-pass
LLVM-passはコンパイラの最適化機能で用いられる。[11]Intermediate Representationにおける難読化の実装ではLLVM-passの拡張が利用される。

C/C++ など

LLVM-IR

Assembly/Binary

Source CodeIntermediate
Representation

Binary Machine
Code

LLVM-pass
最適化（Optimization)
66

Control Flow Flattening
LLVM-passを利用したOSSからOLLVM[13]を選定して利用。
EmoCheck main関数(Control Flow Flatten)

EmoCheck main関数(難読化なし)Transform

67

Strings Obfuscation
Emotetではソースコードレイヤーでの難読化が採用されていたが、EmoCheckではHikari[14]のIntermediate Representationレイヤーでの難読化を採用。
難読化前

parm = &byte_1400D5774;

14000D5700// "quiet"
q

u

i

e

t \0

00 00 00 00 71 75 69 65 74 00 00 00 00 00 00 00

難読化後

byte_1400D5774 ^= 0xB0u;
byte_1400D5775 ^= 0x96u;
byte_1400D5776 ^= 0xDEu;
文字列を1byteずつでXORする復号する命令を関数の先頭に追加
byte_1400D5777 ^= 0x13u;
byte_1400D5778 ^= 0xDAu;
byte_1400D5779 ^= 0x3Du;
parm = &byte_1400D5774; // quiet

14000D5700

00 00 00 00 C1 E3 B7 76

AE 3D 00 00 00 00 00 00

元の文字列をランダムな1byteでXORしたデータに書き換え

68

Strings Obfuscation
最適化処理で削除することができない命令を多数追加することにより、他の中間言語レイヤの難読化処理の効
果の増加が見込める。
EmoCheck main関数(難読化なし)

EmoCheck main関数 (String Obfuscation)

Transform

文字列を復号する処理

69

Split Basic Blocks
EmoCheckではControl Flow Flattenをより複雑化するために、O-LLVM[13]のBasic Blockの分割を採用。
String Obfuscationで生成された多数の命令を分割することを狙っている。

A
Transform

Original

BC

70

Bogus Control Flow
EmoCheckではControl Flow Flattenをより複雑化するために、Hikari[14]の Bogus Control Flowを採用。
String Obfuscation + Split Basic Blocksで生成された多数のBasic Blockに一意に決まるかつ削除が困難
な条件分岐を追加しロジックを複雑化。

Headercondition branch Always T/F(Deﬁned at Compile time)

Transform

Original

Jump to Next basic block

False

True

Original +Junk code

Original

Jump to random basic block

Jump to Next basic block71難読化の実装
ビルドの自動化

72難読化ビルドのパイプライン
clang++

PythonPython

ソースコード作成（通常開発）

コンパイル（難読化なし）生成バイナリ解析（Win32API抽出)

ソースコード解析

コンパイル（難読化あり）

中間言語難読化（LLVM-IR）

中間言語生成（LLVM-IR）

難読化マクロ適用
自作ライブラリ追加

O-LLVM & Hikariclang++

Python

コードサイニング

リリース

clang++/lld

難読化
PEファイル生成

手動73

Step1: 利用するWin32APIの特定
ソースコードへ難読化マクロ適用を自動化するため、通常コンパイルしたPEファイルのIATから、利用している
Win32APIを特定。

ソースコード作成（通常開発）

コンパイル（難読化なし）生成バイナリ解析（Win32API抽出)

PE File74

Step2: ソースコードへの難読化マクロ適用
ソースコードへ難読化マクロ適用し、難読化ライブラリを読み込み設定を追加。
Pythonスクリプトによるソースコード変換前
HANDLE hFind;
WIN32_FIND_DATAA win32_find_data;
std::string search_path = path + "\\*";
hFind = FindFirstFileA(search_path.c_str(), &win32_find_data);/* snip */FindClose(hFind);/* snip */Pythonスクリプトによるソースコード変換後
HANDLE hFind;

WIN32_FIND_DATAA win32_find_data;
std::string search_path = path + "\\*";

マクロ適用

hFind = OBFCALL(OBFSTRW(L"kernel32.dll"),FindFirstFileA)(search_path.c_str(), &win32_find_data);/* snip */マクロ適用

OBFCALL(OBFSTRW(L"kernel32.dll"),FindClose)(hFind);
return keywords;/* snip */75

Step3: Intermediate Representationレイヤーの難読化

Source Codeclang++

Hikari/O-llvm

clang++/lld

中間言語生成（LLVM-IR）

中間言語難読化（LLVM-IR）

コンパイル（難読化あり）

Intermediate
Representation

Binary Machine
Code

LLVM-pass
Control Flow Flatten
String Obfuscation

PE File
難読化済み

Bogus Control Flow
Split Basic Blocks

76難読化の比較
EmoCheck main関数（難読化前）

EmoCheck main関数（難読化後）

77

EmoCheckの難読化

Emotetよりは複雑になった● max( 関数のBasic Block数) > 4000○○

IDA7.7(Hex-rays cloud decompiler)ではdecompile不可
Ghidraで最大15000行/関数のpseudo–code● ソースコードレイヤ/中間言語レイヤの難読化を適切に組み合わせによる難読化効果Up!
78

Take-awayTake-Away● 検知手法の紹介○○

マルウェアの目的にあわせて、検知手法を決める
インシデント調査やフォレンジックへ応用可● 検知ツールの開発○

簡単に使用できるツールがないと調査できない人も多い○ Human vs Humanのいたちごっこ● 難読化手法とその実装例の理解○○○○

マルウェアに利用される難読化の多くは既存手法の組み合わせ複数手法を組み合わせることで難読化は強化される
難読化を解除するために必要な知見
難読化を利用する敷居は下がっている80

Any Questions?

References
Emotetについて
K. Sajo and S. Sasada, "とあるEmotetの観測結果", JSAC 2021
https://jsac.jpcert.or.jp/archive/2021/pdf/JSAC2021_104_sajo-sasada_jp.pdf
Cryptolaemus - Pastedump (accessed 2022-01-16)
https://paste.cryptolaemus.com/
L.Nagy, "Exploring Emotet, an elaborate everyday enigma", VirusBulletin 2019https://www.virusbulletin.com/virusbulletin/2019/10/vb2019-paper-exploring-emotet-elaborate-everyday-enigma/
流行マルウェア「EMOTET」の内部構造を紐解く - MSBD Blog (accessed 2022-01-16)
https://www.mbsd.jp/blog/20181225_2.html
JPCERTCC/EmoCheck - GitHub (accessed 2022-01-16)
https://github.com/JPCERTCC/EmoCheck82

References
難読化の分類について[1] Collberg et al. A Taxonomy of Obfuscating Transformations. Jan, 1997
https://researchspace.auckland.ac.nz/bitstream/handle/2292/3491/TR148.pdf[2] Banescu. A Tutorial on Software Obfuscation. 2017https://mediatum.ub.tum.de/doc/1367533/ﬁle.pdf[3] Xu et al. Layered obfuscation: a taxonomy of software obfuscation techniques for layered security. Apr,2020
https://cybersecurity.springeropen.com/articles/10.1186/s42400-020-00049-3[4] Transformations -Tigress (accessed 2022-01-16)
https://tigress.wtf/transformations.html

83References
Metaprogramingによる難読化について[5] Template Metaprograming - Wikipedia (accessed 2022-01-16)
https://en.wikipedia.org/wiki/Template_metaprogramming[6] constexpr (C++) - Microsoft Technical documentation (accessed 2022-01-16)
https://docs.microsoft.com/en-us/cpp/cpp/constexpr-cpp[7] Samuel, N. and Filip, A., Binary code obfuscation through C++ templatemetaprogramming, Proceedings of the
4th INForum, 2012https://www.researchgate.net/publication/233997753_Binary_code_obfuscation_through_C_template_metapr
ogramming[8] S.Andrivet, "C++11 metaprogramming applied to software obfuscation", BlackHat EU 2014
https://www.blackhat.com/docs/eu-14/materials/eu-14-Andrivet-C-plus-plus11-Metaprogramming-Applied-To-so
ftware-Obfuscation-wp.pdf[9] andrivet / ADVobfuscator - GitHub (accessed 2022-01-16)
https://github.com/andrivet/ADVobfuscator[10] fritzone / obfy - GitHub (accessed 2022-01-16)
https://github.com/fritzone/obfy

84References
Intermediate Representationによる難読化について[11] Writing an LLVM Pass - LLVM (accessed 2022-01-16)
https://llvm.org/docs/WritingAnLLVMPass.html[12] Junod, P., Rinaldini, J., Wehrli, J. and Michielin, J.: Obfuscator-LLVM: Software Protection for the Masses,
Proceedings of the 1st International Workshop on Software Protection, SPRO'15, Piscataway, NJ, USA, IEEE Press,
pp. 3-9 (online), available from <http://dl.acm.org/citation.cfm?id=2821429.2821434> (2015).
[13] obfuscator-llvm / obfuscator -GitHub (accessed 2022-01-16)
https://github.com/obfuscator-llvm/obfuscator[14] HikariObfuscator / Hikari - GitHub (accessed 2022-01-16)
https://github.com/HikariObfuscator/Hikari[15] T. Haruyama. Defeating APT10 Compiler-level Obfuscations. Virus Bulletin Conference 2019https://www.virusbulletin.com/uploads/pdf/conference_slides/2019/VB2019-Haruyama.pdf

85Appendix: 一般的な難読化手法の分類
難読化処理を施す抽象レイヤ、単位、手法、対象によって分類される。 [1] [2] [3]
Abstruction

Unit

Dynamics

TargetSource CodeInstruction

StaticConstants

Basic Block
Variables
Intermediate
Representation

Loop
Function

Code Logic

ProgramBinary Machine
Code

SystemDynamic

Code
Abstraction
86Appendix: Emotet（Loader）に施されている難読化の分布
Emotetの難読化がカバーする範囲。 Dropper(e.g. デコイ文書)やPackerはスコープ外。
Abstruction

Unit

Dynamics

TargetSource CodeInstruction

StaticConstants

Basic Block
Variables
Loop

Intermediate
Representation

Function

Code Logic

PackerPackerBinary Machine
Code

ProgramPacker

SystemDynamic

Code
Abstraction
87Appendix: Control Flow Flattening
Emotetでは単一のRouting変数によって Basic Blockの実行順を難読化。
Emotetの関数 CFF解除（pseudo-code)

Emotetの関数 (Control Flow Graph)
prologue

BOOL mal_DeletePersistentRegistry(){
LPCWSTR lpValueName;
const WCHAR *reg_path;
HKEY phkResult;
BOOL status;
lpValueName = NULL;

3// Block 1: Get value name from global data section.
for (int i = pGlobalData + 564; *i != '\\'; i += 2 );lpValueName = (i + 2);

1// Block 2: Open Windows registry key handle
reg_path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"res = RegCreateKeyExW(0, lpSubKey, 0, 0, 0, regsam, 0, phkResult, 0);
HeapFree(reg_path);2

if (res == ERROR_SUCCESS)// Block 3: Delete registry value
status = RegDeleteValueW(phkResult, lpValueName) == 0;

1
4// Block4: Close Windows registry key handle
RegCloseKey(phkResult);
return status;

1

epilogue}88

Appendix: Function Argument Randomization(アセンブリ）
関数コールの引数に不必要な定数と追加し、引数の順番を入れ替える。追加された引数はプログラ
ムの動作には無関係であり、呼び出し元の関数のスタックに保持される。
難読化後
Junk引数を含む関数呼び出し

Junk local
variables for
FunctionArgument
Randamization

Junk
Aruguments

Junk
Aruguments

Junk
Aruguments

89Appendix: EmoCheckで採用しなかった難読化
Mixed Boolean Arithmetic
コンパイラ/デコンパイラの最適化処理で難読化が消失する可能性があるため。
IDA View

Hex-rays Decompiler

Function Argument Randomization
難読化実装当時にまだ Emotetの難読化に含まれていなかった。

90

Appendix: EmoCheckの難読化
EmoCheckの検知ロジックを保護する機能は果たせていると考えられる。

Abstruction

Unit

Dynamics

TargetSource CodeInstruction

StaticConstants

Basic Block
Variables
Intermediate
Representation

Loop
Function

Code Logic

ProgramBinary Machine
Code

SystemDynamic

Code
Abstraction91