FFRI Security, Inc.

進化するGo言語製マルウェアとどう戦うか？:
解析能力向上に向けての実践的テクニック
JSAC 2023
株式会社FFRIセキュリティ
桑原翼

FFRI Security, Inc.

自己紹介
桑原翼
• 株式会社FFRIセキュリティ– マルウェア解析– その他セキュリティサービスの提供• Ghidraやx64dbgのプラグイン開発– Go言語製バイナリを解析するためのプラグインなども開発• SecHack365、セキュリティ・キャンプ全国大会2019修了生• セキュリティ・キャンプ全国大会2021講師

2FFRI Security, Inc.

目次
進化するGo言語製マルウェアとどう戦うか？: 解析能力向上に向けての実践的テクニック• 現状と問題点• 解析の基礎– Go言語製マルウェア解析の経験が少ない方に向けて• 発展的な解析– Go言語製マルウェア解析の経験がある方に向けて

3

FFRI Security, Inc.

GO言語製マルウェア
現状と問題点4

FFRI Security, Inc.

マルウェア開発におけるGo言語の利点• Go言語について– 記述が容易– ライブラリが豊富– クロスコンパイル• マルウェア開発者にとっての利点– 開発しやすい• マルウェアでもOSSライブラリが良く利用される– 複数プラットフォームを攻撃しやすい

5

FFRI Security, Inc.

Go言語製マルウェアの今後• 複数プラットフォームへの攻撃– ElectroRAT *1• デジタルウォレットの鍵などを盗むRAT• Windows/Linux/macOS向けに活動– Chaos *2• 脆弱性の悪用などを行う多機能なマルウェア• ARM/Intel/MIPS/PowerPC向けが存在• 今後について– 2022年にも多く登場している• Chaos、Nerbian、Agenda、etc– 今後もGo言語製マルウェアが継続しそう*1 https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/*2 https://blog.lumen.com/chaos-is-a-go-based-swiss-army-knife-of-malware/

6

FFRI Security, Inc.

Go言語製マルウェア解析の問題点• 既存のマルウェアはC/C++製のバイナリが多い

Hello Worldの関数
実際には1400程度だが解決できていない• Go言語製バイナリとC/C++製バイナリは大きく異なる– 関数量が膨大• Hello Worldでも関数が1000以上ある• Windows APIなどは動的に呼び出される– 関数の機能が分かりづらい– 独自のデータ構造• 文字列がヌル終端ではなく構造体で表現される• interface{}などの独自の型が利用される– 独自の呼び出し規約• 引数や戻り値に使用されるレジスタ等が異なる

ElectroRATだと8000以上

Hello Worldの文字列
Hello Worldの関数呼び出し

7

FFRI Security, Inc.

ツールによる対応と問題点• 既存ツールの機能– 関数名の解決• これが分からないとかなり解析が困難– 文字列抽出、etc• 問題点– 既存ツールを長い間使い回せない• Go言語はバージョンアップで構造が変化することがある– 新たなバージョンでは関数名などが取り出せない場合も– マルウェアChaChiなどの難読化が施されたマルウェアが存在する• Go言語用の難読化ツールが利用される• 既存ツールをそのまま使用するだけでは解決が難しい– 適宜改造などが必要になる

8

FFRI Security, Inc.

紹介する内容• Go言語製マルウェア 解析の基礎– Go言語特有の構造– 解析フロー– マルウェアを用いた事例• Go言語製マルウェア 発展的な解析– 既存ツールが参照するGo言語製バイナリのデータ– Go言語のバージョンアップへの既存ツールの改造による対応– 難読化対策

9

FFRI Security, Inc.

GO言語製マルウェア
解析の基礎
10

FFRI Security, Inc.

Go言語特有の構造• 解析方法紹介の前にGo言語特有の構造について紹介• データ構造関連– string– interface{}– slice– map• 関数関連– 呼び出し規約

11

FFRI Security, Inc.

string & interface{}• string: 文字列– 構造• ̲̲data：文字列へのポインター– ヌル終端ではない• ̲̲len：文字列長– サイズはポインターと同等

struct string {char* ̲̲data;
int32 or int64 ̲̲len;}• interface{}：複数のデータ型を許容– 構造• tab：データの型情報へのポインター• data：データへのポインター

12

FFRI Security, Inc.

slice
•
•

slice：動的にサイズを変更可能な配列
構造– array：配列へのポインター– len：配列の長さ• サイズはポインターと同等– cap：メモリ確保されている配列の長さ• サイズはポインターと同等•

操作関数– func growslice(et *̲type, old slice, cap int) slice *1• 第2引数に指定したsliceを第3引数で指定したサイズ以上のメモリを保持するsliceにコピーする• sliceへの値の追加はこのgrowsliceの戻り値に対して行われる• Go 1.20からは引数の構成が異なる•

可変長引数もsliceと同様の構造で表現– func Command(name string, arg ...string) *Cmd *2• Command( cmd , /C , bin ) == Command( cmd , [2]string{ /C , bin })struct slice {void* array;
int32 or int64 len;
int32 or int64 cap;}

*1 https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/slice.go#L178*2 https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/os/exec/exec.go#L271

13

FFRI Security, Inc.

map
• map：連想配列• 操作関数（キーが文字列の場合）– func mapaccess2̲faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) *1• 第1引数にmapの型、第2引数にアクセス先map、第3引数にキーを指定する• キーに対応する値と成否が返る– func mapassign̲faststr(t *maptype, h *hmap, s string) unsafe.Pointer *2• 第1引数にmapの型、第2引数に操作対象map、第3引数にキーを指定する• 戻り値に対して値を割り当てる*1 https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/map̲faststr.go#L108*2 https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/map̲faststr.go#L203

14

FFRI Security, Inc.

呼び出し規約• Windowsのamd64での既存との差– i386であればスタックが利用される
Microsoft x64 Go言語(>=Go 1.17)
呼び出し規約

Go言語(<Go 1.17)

引数

rcx,rdx,r8,r9,
スタック

rax,rbx,rcx,rdi,rsi,r8,r9,r10,r11,ス スタック
タック

戻り値

rax

同上

同上

• 引数・戻り値の割り当て– Go 1.17以上 & amd64: 引数も戻り値もraxから順番に使用される– Go 1.16以下 ¦ i386: 戻り値は引数が使用したスタックの末尾から使用される• Go言語のソースコードでの定義– 以下の「paramIntReg＜アーキテクチャ名＞」に定義されている• https://raw.githubusercontent.com/golang/go/go1.19.1/src/cmd/compile/internal/ssa/opGen.go

15

FFRI Security, Inc.

ツールによる対応• https://github.com/mooncat-greenpy/Ghidra̲GolangAnalyzerExtension– 関数名の解決– 引数・戻り値で利用されるバイト数やレジスタの解決– データ型名やフィールド情報の抽出
ツールなし– 文字列の抽出– アセンブリに対応するソースコードファイル名– アセンブリに対応するソースコード行番号
ツールあり• 以降のGhidraの画面ではGhidraAnalyzerExtensionを利用した結果を表示
16

FFRI Security, Inc.

解析フロー
• main (main.main) 関数を確認– アセンブリとデコンパイル結果を俯瞰する程度• 呼び出される関数など– 存在するなら初期化に利用されるinit関数も確認• 関数名・ファイル名・構造体列確認– 明らかに怪しい名前– 利用しているOSSライブラリ• 上記情報から機能推定及び推定内容の確認– 利用有無や推定通りの機能であるか• 詳細解析

補足
main関数が分からない場合はエントリーポイントから辿る
ここでは解説しないので以下のブログを参考にして欲しい
https://engineers.ffri.jp/entry/2022/04/11/141131
init関数についてはAppendixで説明

17

FFRI Security, Inc.

事例：ElectroRAT• Windows、Linux、macOSで活動していたRAT• 解析フロー– GolangAnalyzerExtensionをGhidraにインストール・実行– main.mainの確認– 関数名・ファイル名リストから保有機能を推測• 構造体の確認は割愛– 上記推測を簡易的に検証– 詳細解析

18

FFRI Security, Inc.

ElectroRAT：main.mainの確認• 注目すべき関数呼び出し抜粋– net/http.(*Client).Get– io/ioutil.readAll– main.registerUser– main.setAutostart– main.StartKeyLogger– main.socketConnect• 推測– 自動実行機能があり、主な機能は
C2との通信をベースに行う

main.mainのデコンパイル結果（ツール適用済み、手動での修正なし）
省略

省略

省略

標準ライブラリ開発者が実装• main.initは割愛– mapの作成などが行われている

19

FFRI Security, Inc.

ElectroRAT：関数名・ファイル名確認••予想されるマルウェアの機能– 情報窃取• github.com/gorilla/websocket• main.uploadFile• main.uploadFolder– 自動起動設定• github.com/ProtonMail/go-autostart• main.HideFile• main.copyAppToStartDir– 端末情報取得– キーロガー– スクリーンショット取得– etc

ファイル名リスト（Window → GolangAnalyzerExtension）

関数名リスト推定した機能の呼び出し元と推定が正しいかを簡易的に確認する– 今回は上記の太字のみ確認

20

FFRI Security, Inc.

ElectroRAT：情報窃取の呼び出し元• gorilla/websocket.(*Dialer).Dial– (*Dialer).Dialが最初に呼ばれる– 呼び出し元：main.socketConnect• main.mainから呼ばれる• main.upload(File¦Folder)– 呼び出し元：両方ともmain.socketConnect.func1• main.socketConnect.func1– Call Treesでは分からない• 並行処理を実行するGoroutineが使われているため– 実行される関数は引数を経由して渡される– 呼び出し元：main.socketConnect• アドレス参照などから調査

main.socketConnectのCall Trees

main.uploadFileのCall Trees

main.socketConnect関数

Goroutineを実行する関数

内部で呼び出される

21

FFRI Security, Inc.

ElectroRAT：情報窃取処理• mapに以下が追加される– キー： file
– 値：main.uploadFileの引数• アップロードするファイルパス
• ファイルの送信による情報窃取– SetFilesでファイルパスを含むmapを指定• 送信ファイル設定– Executeで外部に送信• POSTコマンド

main.uploadFileのデコンパイル結果
mapの型

mapのオブジェクト

キー

第3、4引数が代入される
おそらくファイルパス
指定した型のオブジェクトを生成する関数

resty.Request

通信を行うオブジェクトの型

mapのオブジェクト

POST 文字列22

FFRI Security, Inc.

ElectroRAT：自動起動設定• main.copyAppToStartDirで作成されたファイルに自動起動設定– OSSが利用されている• https://github.com/ProtonMail/go-autostart
main.setAutostartのデコンパイル結果• go-autostart• cgoを利用している

IsEnabledの成否確認

go-autostartのソースコード• Go言語からC言語を使用• COMを利用してスタートアップフォルダにショートカットファイルが作成される

23

FFRI Security, Inc.

ElectroRATで利用されるOSSライブラリ抜粋• github.com/ProtonMail/go-autostart– 自動起動設定• github.com/gorilla/websocket– ソケット通信• github.com/matishsiao/goInfo– 端末の情報を取得• github.com/mitchellh/go-ps– プロセスリストを取得• etc• どれもWindows、Linux、macOSに対応• OSSライブラリの処理はソースコードを読めばよい

24

FFRI Security, Inc.

良く利用されるOSSライブラリ• 複数のプラットフォームを対象としたライブラリ– github.com/denisbrodbeck/machineid• 端末を識別するIDを取得– github.com/shirou/gopsutil• プロセス・システム関連の情報を取得– github.com/gorilla/websocket• ソケット通信– github.com/kardianos/service• サービスの操作を提供• 特定のプラットフォームのみ対象だがGo言語での実装に工数がかかる処理のライブラリ– github.com/go-ole/go-ole• Windows COMのラッパー– github.com/lxn/win• Windows APIのラッパー

25

FFRI Security, Inc.

詳細
• これまでの調査でマルウェアの機能が大雑把に把握できた• これらの情報を基により詳細な解析を行う– ここでは以下のみを扱う• C2との通信– main.socketConnectでマルウェアのメインの処理を行っていると思われる• main.registerUserによるC2への感染通知やmain.setAutostartでの自動起動設定などの前準備の後に、main.socketConnectでソケット通信が行われるため

26

FFRI Security, Inc.

main.socketConnectの解析• 主な処理– 通信の確立• 通信に必要なConnオブジェクトを作成する– main.socketConnect.func1の呼び出し• 第2引数にConnオブジェクトを渡す
Connオブジェクトが作成される
省略

Connオブジェクトを第2引数に設定

Goroutineで実行される関数の引数についてはAppendixで説明

IPアドレス文字列（main.socketConnectの引数）
Goroutineを実行する関数

27

FFRI Security, Inc.

main.socketConnect.func1• 処理の流れ– C2から命令受信– 命令のパース– 命令の実行– C2に結果を送信

28

FFRI Security, Inc.

命令受信 ＆ 命令のパース• 受信したデータはmain.Commandに変換– Typeフィールドが命令として扱われる

main.socketConnect.func1関数
引数のConnを利用
受信データを取得

省略

Decoderオブジェクト
受信データを保持&main.Commandの型&main.Commandのオブジェクトmain.Command構造体（Data Type Manager）

Type文字列
省略

命令判定

Run command

29

FFRI Security, Inc.

命令の実行 ＆ 結果の送信• main.Command構造体からCommand文字列を取り出して実行• 結果をJSON形式に変換して外部へ送信

main.socketConnect.func1関数
RAX=＆main.Command
オフセット：0x58

Commandフィールドを取得

main.Command構造体
Commandを実行し結果を返す
省略

戻り値の実行結果

JSON形式Dataフィールドの構造体

main.Commandの先頭からのオフセット：0x58

30

FFRI Security, Inc.

検体の比較による解析の効率化• 解析対象の検体と既存の検体や同一インシデント内で観測された検体との比較について紹介する• 同一処理が存在– 解析を短縮可能• 類似しているが異なる処理が存在– 攻撃の動向把握– 既存検体が防御できている場合に新規検体に感染してしまった原因調査• Go言語のバイナリではソースコードのファイル名や行番号が取得できるため、詳細に差分を追える

31

FFRI Security, Inc.

新旧検体の比較• 以下の検体を比較– 左：e9b83d5cdefd4486b32a927d7505cdeebb43e6977759ba069d9373e46ca7d0f2（新）– 右：170cb5ea1a6b4af3c27358ba267a1309ed5118481619fc874f717262cb91fb77（旧）検体の対象環境ごとにファイルが容易されている

⊝

⊕

⊝
⊝

いくつかファイルが追加・削除されている次ページでコマンドの差を確認する

32

FFRI Security, Inc.

検体間のコマンドの変化• 変化があった場所を主に解析すれば良い
コマンド

コマンドが実装されているsocket.goファイルの行数を比較
e9b83d…の行数（新）170cb5…の行数（旧）Get folder content

130˜ 133

100˜ 103

Keylogger

135˜ 149

105˜ 118

Screenshot

151˜ 164

119˜ 128

Camera photo

keylogger̲windows.goが削除されていたがコマンドは存在

129˜ 134

Processes list

166˜ 169135˜ 137

Download file

171˜ 179

138˜ 145

Download folder

181˜ 189

147˜ 153

Add file

191˜ 205

Delete file

207˜ 215

154˜ 161

Kill process

217˜ 225

162˜ 169

Chrome passwords

170˜ 176

Run service

227˜ 235

177˜ 202Run command

237˜ 245

203˜ 210

追加されたdownloadFile.goに関連するコマンドが追加削除されたchrome̲windows.goに関連するコマンドが削除

33

FFRI Security, Inc.

GO言語製マルウェア
発展的な解析
34

FFRI Security, Inc.

Goバージョンアップへの対応• 今まで利用できていたツールが突然上手く動作しなくなる– Go言語のバージョンアップによってツールが参照するデータが変更される– 例：ElectroRATは解析できるが、より新しいChaosは解析できないなど• 説明の流れ– Go言語製バイナリのメタデータ構造説明• 既存ツールが利用する情報• 過去のバージョンアップでの変更点– ツールの改造事例紹介

35

FFRI Security, Inc.

Go言語製バイナリのメタデータ紹介• ツールが良く参照するデータ– 関数情報• 関数名などの情報を含む• スタックトレースの表示などで使用される– データ型情報• データ型名やフィールド情報を含む• オブジェクト生成やinterface{}、mapでの型指定に使用される

36

FFRI Security, Inc.

メタデータの構成概要• 紹介するメタデータを管理する構造体– moduledata• pcHeaderとデータ型情報へのリンクを保持– pcHeader• 関数情報へのリンクを保持

64bit向けバイナリでの一部フィールドのみ表示している、以降の図も同様

37

FFRI Security, Inc.

メタデータの構成要素• moduledata *– pcHeader• pcHeaderへのポインター– text• コードへのポインター• 基本的に.textセクションと同一– types• 型情報のベースアドレス– typelinks• typesから型情報へのオフセットの配列(slice)* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/symtab.go#L415-L457

38

FFRI Security, Inc.

メタデータの構成要素• moduledata *– pcHeader
フィールド追加によるオフセットの変化• pcHeaderへのポインター
Go 1.7 、 Go 1.8 、 Go 1.16 、 Go 1.17 、 Go 1.18 、 Go 1.20
– text• コードへのポインター• 基本的に.textセクションと同一
typesの追加
Go 1.7– types• 型情報のベースアドレス– typelinks• typesから型情報へのオフセットの配列(slice)
ポインターからオフセットに変更
Go 1.7* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/symtab.go#L415-L457

39

FFRI Security, Inc.

メタデータの構成要素• pcHeader *– magic、pad1、pad2• 固定値– ptrSize• ポインターのサイズ– nfunc• 関数の数– textStart• moduledata.textと同様– funcnameOffset• pcHeaderから関数名列へのオフセット– pclnOffset• pcHeaderから関数情報へのリンクを含む配列へのオフセット* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/symtab.go#L395-L408

40

FFRI Security, Inc.

メタデータの構成要素

Magicの変更
Go 1.16, Go 1.18, Go 1.20

• pcHeader *– magic、pad1、pad2• 固定値– ptrSize• ポインターのサイズ– nfunc• 関数の数フィールド追加によるオフセットの変化– textStart
Go 1.16 、 Go 1.18
• moduledata.textと同様– funcnameOffset
参照方法変更• pcHeaderから関数名列へのオフセット
Go 1.16– pclnOffset• pcHeaderから関数情報へのリンクを含む配列へのオフセット* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/symtab.go#L395-L408

41

FFRI Security, Inc.

メタデータの位置特定• セクションを使う– pcHeaderは.gopclntabセクションの先頭に配置されている– .gopclntabセクションはWindowsでは存在しない場合もある• 「-ldflags= -w –s 」オプション使用時
Go 1.19• pcHeaderのmagicを使う– 先頭のmagicフィールドには「0xfffffff0」という値がある• 書き換えるとエラーが発生– pclnOffsetなどは書き換え可能– この値を探索する• moduledataはpcHeaderへのポインターから検索する

42

FFRI Security, Inc.

関数情報の構成• pcHeaderから以下の情報を取り出す流れを示す– 関数アドレス– 関数名– 引数・戻り値バイト数
関数情報を保持する̲funcを取得する流れ

̲funcから関数情報を取得する流れ

43

FFRI Security, Inc.

関数情報の構成要素• functab *– entryoff• 関数の先頭アドレスへのオフセット• ベースはpcHeader.textStart– funcoff• 関数情報へのオフセット• ベースはfunctab配列の先頭* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/symtab.go#L562-L565

44

FFRI Security, Inc.

関数情報の構成要素• functab *– entryoff• 関数の先頭アドレスへのオフセット• ベースはpcHeader.textStart– funcoff• 関数情報へのオフセット• ベースはfunctab配列の先頭

ポインターからオフセットに変更
Go 1.18

サイズの変更Go 1.18
ベースアドレスの変更
Go 1.16* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/symtab.go#L562-L565

45

FFRI Security, Inc.

関数情報の構成要素• ̲func *– entryoff• 関数の先頭アドレスへのオフセット• ベースはpcHeader.textStart– nameoff• 関数名へのオフセット• pcHeaderのアドレス + pcHeader.funcnameOffset+ ̲func.nameoff– args• 引数・戻り値で利用されるバイト数– pcsp、pcfile、pcln• アセンブリに対応するスタックサイズ、ファイル名、
行番号を取り出すための情報• 情報の取り出し方については割愛するが後日弊社ブログにて解説予定* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/runtime2.go#L869-L885

46

FFRI Security, Inc.

関数情報の構成要素• ̲func *– entryoff• 関数の先頭アドレスへのオフセット• ベースはpcHeader.textStart– nameoff• 関数名へのオフセット• pcHeaderのアドレス + pcHeader.funcnameOffset+ ̲func.nameoff– args• 引数・戻り値で利用されるバイト数– pcsp、pcfile、pcln• アセンブリに対応するスタックサイズ、ファイル名、
行番号を取り出すための情報

ポインターからオフセットに変更
Go 1.18

nameoffのベースアドレスの変更
Go 1.16• 情報の取り出し方については割愛するが後日弊社ブログにて解説予定* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/runtime2.go#L869-L885

47

FFRI Security, Inc.

関数情報取り出しfunctab[nfunc]• Ghidraで例示
＋

＋
̲func

＋

＋

関数名列

48

FFRI Security, Inc.

データ型情報の構成• moduledataから各データ型情報を含む̲type構造体を取得するための流れを下図に示す• ̲type– データ型のサイズや種類などの情報を保持する– それぞれの種類ごとに固有の情報は̲typeの末尾に追加される– オブジェクト生成やmapへのアクセス関数で使用される

49

FFRI Security, Inc.

データ型情報の構成要素• ̲type *– size• データ型のバイト数– kind• データ型の種類• 値と種類の関係一部抜粋– 1: Bool、2: Int、17: Array、21: Map、
22: Pointer、25、Struct– str• データ型名へのオフセット• ベースはmoduledata.types* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/type.go#L35-L52

50

FFRI Security, Inc.

データ型情報の構成要素• ̲type *– size• データ型のバイト数– kind• データ型の種類• 値と種類の関係一部抜粋– 1: Bool、2: Int、17: Array、21: Map、
22: Pointer、25、Struct– str• データ型名へのオフセット• ベースはmoduledata.types

ポインターからオフセットに変更
Go 1.7

文字列の取得方法変更
Go 1.17* https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/runtime/type.go#L35-L52

51

FFRI Security, Inc.

データ型情報取り出し• Ghidraで例示
typelinksの配列

＋
̲type

0x36&0x1f=22

52

FFRI Security, Inc.

Goバージョンアップ対応例• 対象検体– Chaos• ハッシュ値：
ebe0f9855eb8f6bd980ed60c26e3a877dc1ace5d664e248bb0558996fe0bd06f– Goバージョン：Go 1.18.1– OS：Linux– Arch：x86• ツール– https://github.com/f0rki/r2-go-helpers• radare2のスクリプト• 機能：関数名を解決

53

FFRI Security, Inc.

現状の対応範囲の確認• Go 1.15でビルドしたバイナリへの適用結果– 関数列挙（afl）
一部修正
vaddrに修正– go.main.mainのアセンブリ表示（pdf）

引数に4を追加

54

FFRI Security, Inc.

現状の対応範囲の確認• Chaosへの適用結果– スクリプトを実行

関数のアドレス
明らかに違う値が表示されている– 関数列挙（afl）
失敗している

55

FFRI Security, Inc.

バージョンアップによる影響• pcHeader → ̲func– magicの値変更– 関数情報へのリンクを含む配列の参照方法変更– functab.funcoffのベースアドレス変更– functabのフィールドのサイズがポインターサイズから0x4に変更
Go 1.15以前
pcHeaderの直後にfunctab[nfunc]が続く

0xfffffffb

0xfffffff0

Go 1.18以降
pclnOffsetを利用してfunctab[nfunc]の位置特定

56

FFRI Security, Inc.

バージョンアップによる影響• ̲func → 関数情報
– ̲func.entryoffがポインターからオフセットに変更– ̲func.nameoffのベースアドレス変更

Go 1.15以前

Go 1.18以降

57

FFRI Security, Inc.

Chaosに向けたツール改造• 抽出できない原因– Go 1.15〜Go 1.18間の変化に対応できていない• 修正すべき箇所 *functab配列位置の取得方法が異なる
functabのサイズが異なる
アドレスではなくオフセットになる
ベースアドレスが異なる* https://github.com/f0rki/r2-go-helpers/blob/d1167e4b96ba0e3c33ee8c5e578bb3cde930324e/gohelper.py#L165-L177

58

FFRI Security, Inc.

改造したツールをChaosに適用• Chaosへの適用結果– 関数列挙（afl）– go.main.mainのアセンブリ表示（pdf）

59

FFRI Security, Inc.

gobfuscateによる難読化への対応• 説明の流れ– gobfuscateによる難読化手法の紹介– マルウェアの文字列難読化解除– 難読化されたマルウェアでの機能推定• 対象検体– ChaChi• ハッシュ値：
8a9205709c6a1e5923c66b63addc1f833461df2c7e26d9176993f14de2a39d5b

60

FFRI Security, Inc.

gobfuscateによる難読化• gobfuscateとは– 機能：文字列や関数の難読化等– ソースコードの修正等により難読化を実施– https://github.com/unixpickle/gobfuscate

文字列難読化の様子

難読化された関数

文字列のはず文字列のはず• ここでは文字列難読化を扱う

func Bool(name string, value bool, usage string) *bool ** https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/flag/flag.go#L734

61

FFRI Security, Inc.

gobfuscateの文字列難読化• gobfuscateによる文字列の難読化手法– 無名関数で文字列を生成する– 文字列はXORによって暗号化されている「golang」をgobfuscateで難読化した際に生成される関数
無名関数が利用されているバイト列を文字列に変換

62

FFRI Security, Inc.

ChaChiの文字列難読化

バイト列取り出し• 関数：main.main.func1

バイト列をXOR
0x41fbfb281bda8257

0x2888894d6bf7ed3

0xeb50

0x9f23

XOR

XOR

0x69737265702d6f6e

0x7473

no-persist

ループの回数

バイト列を文字列へ変換し戻り値に設置

63

FFRI Security, Inc.

補足：無名関数• Go言語の無名関数– 例：f := func(arg string) string { return arg:

+ arg }• アセンブリでの表現– 関数名は「<実装元の関数名>.func%d」という形式• この形式の関数名がすべて開発者が実装した無名関数というわけではない

64

FFRI Security, Inc.

ChaChiの文字列難読化解除• コード案– 無名関数かつruntime.slicebytetostringのみ呼び出される関数を対象• 関数自身やruntime.morestack̲noctxtなどの例外あり
main.main.func1関数– 関数の2行目と3行目でスタックに格納されるデータを取得– 取得したデータをXORし、文字列に変換– 取得した文字列で関数名をリネーム
2行目・3行目

省略

65

FFRI Security, Inc.

ChaChiの文字列難読化解除

ChaChiのmain.init関数（格納先メモリのラベルは手動で設定）• 難読化された文字列が複数存在• 復号後にメモリに格納される

難読化解除

難読化解除

難読化解除

66

FFRI Security, Inc.

難読化されたマルウェアでの機能推定• ChaChiの難読化された文字列は解決したが、まだ難読化された情報があり解析しづらい• 難読化された検体を効率的に解析するために、機能と利用しているOSSライブラリの推定について紹介– 機能推定• 表層情報の収集– 利用しているOSSライブラリの推定• ファイル名や行番号などから推定
難読化された関数名からは何をしているかがわからない

67

FFRI Security, Inc.

機能の推測
• ファイル名列を確認する• サービスの実行機能があると思われる– service.goファイルなどが存在• 内部の処理でOpenServiceを呼び出す
ファイル名列68FFRI Security, Inc.

OSSライブラリの推定• heimaoplnhkdaeiflhmp/lpdkaklidghllnacngmd/lmmbpplgmfkpanjncdffについて調査する– おそらくgithub.comで公開されているOSSライブラリ• 上記パスを特定できれば、ソースコードを読んで内部の処理と使われ方が分かり解析が捗る• 以下の難読化解除した文字列で検索を試みる

SYSTEM¥CurrentControlSet¥Control

WaitToKillServiceTimeout

69

FFRI Security, Inc.

ライブラリの特定• 検索：「site:github.com golang WaitToKillServiceTimeout
SYSTEM¥CurrentControlSet¥Control」– https://github.com/kardianos/service/blob/master/service̲windows.go• ファイル名が一致• 行番号もある程度一致– 文字列難読化により行数がずれる– https://github.com/takama/daemon/blob/master/daemon̲windows.go• ファイル名が一致しない• 「github.com/kardianos/service」が利用されているOSSライブラリ

70

FFRI Security, Inc.

補足：ChaChiで利用されているOSSライブラリ• github.com/rs/xid– 一意なIDを取得• github.com/fasthttp/websocket– fasthttpをサポートするgorilla/websocketのフォーク• github.com/armon/go-socks5– SOCKS5サーバー• github.com/fsnotify/fsnotify– ファイルシステム通知• github.com/jpillora/backoff– 指数バックオフカウンタ• github.com/Jeffail/tunny– Goroutineプールを生成し管理するためのライブラリ
• etc
71

FFRI Security, Inc.

まとめ

72

FFRI Security, Inc.

振り返りと課題• 問題– Go言語製マルウェアには解析を困難にする課題がいくつか存在• 解析能力向上のために紹介した内容– 基本的な解析フローとTips– Go言語製バイナリ内のメタデータとそれを利用するツールの改造方法– Go言語のバージョンアップへの対応– 難読化された検体への対策• 今後の課題– Ghidraなどのデコンパイル性能が良くない– 動的解析ツールの不足

73

FFRI Security, Inc.

利用したツール等• https://github.com/mooncat-greenpy/Ghidra̲GolangAnalyzerExtension– 本発表で主に利用したGo言語製バイナリ解析用のGhidraプラグイン• https://github.com/FFRI/JSAC2023-GolangMalwareAnalysis– バージョンアップへの対応で修正したradare2のスクリプト– gobfuscateによる文字列難読化を解除するGhidraのスクリプト

74

FFRI Security, Inc.

Thank you for listening!

FFRI Security, Inc.

APPENDIX76

FFRI Security, Inc.

Appendix：init関数• init関数– 初期化などに使用される関数である– main関数よりも前に呼び出される– 複数定義できる• アセンブリでの表現– 関数名は「<モジュール名>.init.%d」または「<モジュール名>.init」という形式になる• 例：main.init.0– init関数はruntime.doInit関数によって呼び出される• runtime.doInit関数はmain関数を呼び出すruntime.main関数から呼ばれる

77

FFRI Security, Inc.

Appendix ：Goroutine• Goroutine– Go独自の軽量なスレッド– 例：go sub̲func(0x1, 0x10, 0x100, 0x1000) // sub̲func関数を実行• アセンブリでの表現– runtime.newproc関数によりGoroutineが起動– バージョンによる引数の違い• Go 1.17以下– 第２引数に「呼び出し関数ポインター、引数1、引数2、…」が設定されているメモリへのポインターを渡す• Go 1.17以上– newprocに対象関数のラッパー関数が渡される» いくつかの引数はラッパー関数内で用意される• Go 1.18以上– 第1引数に上述のポインターを渡す

78

FFRI Security, Inc.

Appendix：バージョンの判定• Go言語製バイナリのビルドに使用されたGo言語のバージョンを取り出す方法を以下に示す
このバイト列を検索

文字列へのポインターバージョン文字列Go 1.13.5でビルドされたとわかる

ポインターのサイズ
文字列長バージョン情報へのポインター
Go 1.18以降ではこのバイト列の後方にバージョン情報が存在79