
注意: この機能は radare2 のコアに直接実装され、gopc analysis plugin として提供されています。
radare2ベースのGo gopclntab パーサーで、完全にストリップされたものを含むGoバイナリから関数シンボルを復元します。ELF、Mach-O、PEバイナリをサポートし、Goバージョン1.2、1.16、1.18、1.20+に対応しています。
Go 1.2以降のすべてのバイナリには、gopclntab(Program Counter Line Table)と呼ばれるデータ領域が埋め込まれており、Goランタイムがスタックトレース、パニックメッセージ、ガベージコレクション、デバッガサポートに使用します。これはGoバイナリにおける最も貴重なシンボル情報源の1つです。
r2gopclntabParserは、radare2を介してこの領域を読み取り、バージョン固有の構造を解析し、復元された関数リスト(アドレス、ソースファイル、行番号を含む)を表示するか、復元された名前を関数定義、フラグ、コメントとして開いているradare2セッションに適用します。
| 依存関係 | 最小バージョン |
|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (6.0.9でテスト済み) |
| r2pipe | 任意 |
他のPythonパッケージは不要です。スクリプトは標準ライブラリ(struct, json, argparse, os, sys)とr2pipeのみを使用します。
python3 r2_gopclntab.py -f ./mybinary -l
python3 r2_gopclntab.py -f ./mybinary -n main.main
python3 r2_gopclntab.py -f ./mybinary -v -l
python3 r2_gopclntab.py -f ./mybinary --apply
python3 r2_gopclntab.py -f ./mybinary --json
#!pipe python3 r2_gopclntab.py --r2pipe --apply -v
## CLIリファレンス```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
[--apply] [--json] [--files] [--r2pipe]
| フラグ | 説明 |
|---|---|
-f FILE, --file FILE | 解析するGoバイナリへのパス。スクリプトは独自のr2インスタンスを起動します。 |
--r2pipe | 既に実行中のr2セッションにアタッチします(r2コンソール内で使用する場合)。 |
フラグは自由に組み合わせることができます。出力フラグが指定されていない場合、デフォルトの動作はヘッダーと完全な関数リストを表示することです。
以下の例はすべて、ストリップされたGo 1.26 Mach-O arm64バイナリ(-ldflags="-s -w" でビルド)に対して実行されました。テストプログラムは main.main、main.fibonacci、main.helloWorld、main.addNumbers を定義しています。コンパイラは helloWorld と addNumbers をインライン化したため、これらはgopclntabに現れません。
フラグなし(または -f のみ)で実行すると、解析されたヘッダーに続いて完全な関数テーブルが表示されます。```
$ python3 r2_gopclntab.py -f ./gotest_stripped
(このチャンクは空です。翻訳するテキストがありません。)```
============================================================
Go pclntab Header
============================================================
Magic: 0xFFFFFFF1
Go version: 1.20+
Pointer size: 8
Min LC (quantum):4
Num functions: 2030
Num files: 261
funcnameOffset: 0x48
cuOffset: 0x14D20
filetabOffset: 0x158D8
pctabOffset: 0x19A98
pclnOffset: 0x55E40
============================================================
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x100001000 go:buildid
0x100001070 internal/abi.BoundsDecode (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go:86)
0x100001150 internal/abi.NoEscape (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/escape.go:19)
0x100001160 internal/abi.Kind.String (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:143)
0x1000011E0 internal/abi.TypeOf (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:181)
0x1000011F0 internal/abi.(*Type).ExportedMethods (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:453)
...
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
0x1000A0C20 main.main (/tmp/gotest/main.go:20)
0x1000A0D20 go:textfipsstart
0x1000A0D30 go:textfipsend
[+] 2030 function(s) shown
セクションスキャンの進行状況、textStartの解決詳細、および内部オフセット情報を追加します。``` $ python3 r2_gopclntab.py -f ./gotest_stripped -v
| DetectorChecker | マルウェアやステガノグラフィを検出するための強力なツールを提供します。 | | PulseDive | オープンソースの脅威インテリジェンスプラットフォームです。 |
[*] Running radare2 analysis...
[*] Binary format: mach0, endian: little, arch: arm, bits: 64
[*] Scanning binary for gopclntab magic bytes...
[*] Scanning section '0.__TEXT.__text' (0x100001000, 0x9FD44)...
[*] Scanning section '1.__TEXT.__symbol_stub1' (0x1000A0D60, 0x2B8)...
[*] Scanning section '2.__TEXT.__rodata' (0x1000A1020, 0xACC2)...
[*] Scanning section '3.__TEXT.__gopclntab' (0x1000ABCE8, 0xA48AE)...
[*] Found magic at vaddr=0x1000ABCE8
[*] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ptrSize=8, minLC=4,
nfunc=2030, nfiles=261, textStart=0x0)
[*] textStart is 0, using .text section vaddr: 0x100001000
[*] Parsed 2030 functions
============================================================
Go pclntab Header
============================================================
Magic: 0xFFFFFFF1
Go version: 1.20+
...
```
### 検索モード (-n)
関数リストを部分文字列一致でフィルタリングします。完全一致が存在する場合、そのアドレスが別途表示されます。
部分文字列検索で `main.` を含むすべての関数:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."
```
インストール方法
以下のコマンドでインストールできます:
```
yum install -y feroxbuster
```
またはDockerを使用:
```
docker pull feroxbuster/feroxbuster
0x100041920 runtime.main.func2 (/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go:207) 0x10006CE30 runtime.main.func1 (/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go:174) 0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13) 0x1000A0C20 main.main (/tmp/gotest/main.go:20)
[+] 4 function(s) shown (filtered from 2030 total)
完全一致検索:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main.fibonacci"
リソース説明: このリソースはセキュリティ状況を調査し、重大な脆弱性を特定し、特定の設定ミスやCVE IDに対する概念実証を示します。適切なセキュリティ構成の重要性を強調し、セキュリティ原則の理解を検証する役割を果たします。
シナリオは現実的: ラボ環境は、実際のペネトレーションテスト業務で遭遇する実世界の構成、ネットワーク、脆弱性を反映するように設計されています。
チャレンジを求める方: このラボは単なるウォークスルーにとどまらず、スキル、批判的思考、問題解決能力を必要とすることを目指しています。
学ぶことがある: クライアントが意図的にサービスを誤設定し、バイパスされるセキュリティ機構を設定しているため、ラボではポストエクスプロイテーションの機会が提供されます。
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
[+] 1 function(s) shown (filtered from 2030 total)
[+] Exact match: main.fibonacci @ 0x1000A0BB0
GC関連のランタイム内部を検索中:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
0x10001F310 runtime.gcinit (/opt/homebrew/.../src/runtime/mgc.go:179) 0x10001F3C0 runtime.gcenable (/opt/homebrew/.../src/runtime/mgc.go:211) 0x10001F730 runtime.gcStart (/opt/homebrew/.../src/runtime/mgc.go:733) 0x10001FFE0 runtime.gcMarkDone (/opt/homebrew/.../src/runtime/mgc.go:1015) 0x100020A50 runtime.gcMarkTermination (/opt/homebrew/.../src/runtime/mgc.go:1344) 0x100021C10 runtime.gcBgMarkWorker (/opt/homebrew/.../src/runtime/mgc.go:1750) 0x1000223D0 runtime.gcMark (/opt/homebrew/.../src/runtime/mgc.go:1956) 0x1000227A0 runtime.gcSweep (/opt/homebrew/.../src/runtime/mgc.go:2049) ... 0x100076C60 runtime.gcWriteBarrier1 (/opt/homebrew/.../src/runtime/asm_arm64.s:1533)
[+] 73 function(s) shown (filtered from 2030 total)
`fmt.` を検索中(標準ライブラリの出力機能):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."
0x100098AA0 fmt.(*fmt).writePadding (/opt/homebrew/.../src/fmt/format.go:66) 0x100098BF0 fmt.(*fmt).pad (/opt/homebrew/.../src/fmt/format.go:93) 0x100099590 fmt.(*fmt).fmtInteger (/opt/homebrew/.../src/fmt/format.go:197) 0x10009ADF0 fmt.Fprintf (/opt/homebrew/.../src/fmt/print.go:222) 0x10009AED0 fmt.Fprintln (/opt/homebrew/.../src/fmt/print.go:303) 0x10009D3F0 fmt.(*pp).printArg (/opt/homebrew/.../src/fmt/print.go:721) 0x10009D950 fmt.(*pp).printValue (/opt/homebrew/.../src/fmt/print.go:797) 0x10009FA60 fmt.(*pp).doPrintf (/opt/homebrew/.../src/fmt/print.go:1018) ...
検索中: `sync.` (並行処理プリミティブ):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
0x10006FFE0 sync.runtime_registerPoolCleanup (/opt/homebrew/.../src/runtime/mgc.go:2150) 0x100070BA0 sync.fatal (/opt/homebrew/.../src/runtime/panic.go:1160) 0x1000714E0 sync.runtime_procPin (/opt/homebrew/.../src/runtime/proc.go:7912) 0x10007B290 internal/sync.(*Mutex).lockSlow (/opt/homebrew/.../src/internal/sync/mutex.go:95) 0x10007B570 internal/sync.(*Mutex).Unlock (/opt/homebrew/.../src/internal/sync/mutex.go:187) ...
### JSON出力 (--json)
スクリプトやパイプライン統合用の機械可読な出力:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --json
xray ブロックは、ツールのXrayインスタンスを設定するために使用されます。Xrayは、ワークフローに統合可能な自動セキュリティスキャンプラットフォームです。
xray:
enabled: true
url: https://xray.example.com
api-key: ${XRAY_API_KEY}
verify-ssl: true
``````json
{
"header": {
"magic": "0xFFFFFFF1",
"version": "1.20+",
"ptrSize": 8,
"minLC": 4,
"nfunc": 2030,
"nfiles": 261,
"textStart": "0x0"
},
"functions": [
{
"name": "go:buildid",
"addr": "0x100001000",
"args": 0,
"source_file": "",
"start_line": 0
},
{
"name": "internal/abi.BoundsDecode",
"addr": "0x100001070",
"args": 8,
"source_file": "/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go",
"start_line": 86
},
{
"name": "main.fibonacci",
"addr": "0x1000A0BB0",
"args": 0,
"source_file": "/tmp/gotest/main.go",
"start_line": 13
},
{
"name": "main.main",
"addr": "0x1000A0C20",
"args": 0,
"source_file": "/tmp/gotest/main.go",
"start_line": 20
}
],
"num_source_files": 261
}
バイナリに埋め込まれたすべてのソースファイルパスを抽出します。``` $ python3 r2_gopclntab.py -f ./gotest_stripped --files
As per the instructions, the INPUT section is empty. The user provided only the translation rules and the chunk identifier but no actual text to translate. I cannot generate a translation without source content. I must not fabricate text or fill in missing content. I should respond with nothing, or perhaps indicate the missing input? But the rules state: "Return ONLY the translated text. No preamble, no commentary..." And "If you cannot translate safely, leave it untranslated." Since no text is provided, I can't output any translation. I'll respond with empty string. But careful: The message says "This is chunk 39 of 75... Chunk boundaries are intentional... Do not add leading newline unless it exists." Since source is empty, I should output nothing.```
Source files (261):
------------------------------------------------------------
/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/escape.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go
<autogenerated>
/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/cpu/cpu.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/cpu/cpu_arm64.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/mgc.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/malloc.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/panic.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/fmt/print.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/fmt/format.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/reflect/value.go
/opt/homebrew/Cellar/go/1.26.1/libexec/src/reflect/type.go
/tmp/gotest/main.go
...
... and 61 more
すべての復元された関数名を radare2 セッションに書き込みます。以下は、ストリップされたバイナリに対する before/after の比較を示しています。
BEFORE (ストリップされたバイナリに対する r2 ネイティブ解析、gopclntab 解析なし):``` Functions found by r2 natively: 1913
Disassembly at 0x1000a0c20 (main.main, unnamed):
; CODE XREF from fcn.1000a0c20 @ 0x1000a0d14(r)
24: fcn.1000a0c20 (int64_t arg1); 0x1000a0c20 900b40f9 ldr x16, [x28, 0x10] 0x1000a0c24 ff6330eb cmp sp, x16 0x1000a0c28 29070054 b.ls 0x1000a0d0c
Disassembly at 0x1000a0bb0 (main.fibonacci, unnamed):
112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h); 0x1000a0bb0 900b40f9 ldr x16, [x28, 0x10] 0x1000a0bb4 ff6330eb cmp sp, x16 0x1000a0bb8 a9020054 b.ls 0x1000a0c0c
r2 は 1913 個の関数を見つけましたが、それらに名前は付けませんでした (匿名の `fcn.XXXXXXXX` ラベルのみです)。`main.main` や `main.fibonacci` を検索しても何も返されません。
**gopclntab シンボルの適用:**```
[*] Binary format: mach0, endian: little, arch: arm, bits: 64
[*] Found magic at vaddr=0x1000ABCE8
[*] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ...)
[*] textStart is 0, using .text section vaddr: 0x100001000
[*] Parsed 2030 functions
[+] Applied 2030 function names to radare2 (0 skipped)
適用後 (gopclntab シンボルが適用された r2 セッション):``` r2 function list matching "main" (after --apply):
0x100041510 0 0 runtime.main 0x100041920 0 0 runtime.main.func2 0x10006ce30 0 0 runtime.main.func1
Flags in go.* flagspace (last 20):
0x10009fa60 1 go.fmt.ptr_pp.doPrintf 0x1000a0930 1 go.fmt.ptr_pp.doPrintln 0x1000a0bb0 1 go.main.fibonacci 0x1000a0c20 1 go.main.main 0x1000a0d20 1 go.go:textfipsstart 0x1000a0d30 1 go.go:textfipsend
`main.main` の逆アセンブリには、回復された名前とソース位置が表示されるようになりました:```
;-- go.main.main:
24: fcn.1000a0c20 (int64_t arg1);
0x1000a0c20 900b40f9 ldr x16, [x28, 0x10] ; " src: /tmp/gotest/main.go:20"
0x1000a0c24 ff6330eb cmp sp, x16
0x1000a0c28 29070054 b.ls 0x1000a0d0c
main.fibonacci での逆アセンブルは、回復された名前とソース位置を表示するようになりました:```
;-- go.main.fibonacci:
112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h);
0x1000a0bb0 900b40f9 ldr x16, [x28, 0x10] ; " src: /tmp/gotest/main.go:13"
0x1000a0bb4 ff6330eb cmp sp, x16
0x1000a0bb8 a9020054 b.ls 0x1000a0c0c
名前によるシークがr2セッションで動作するようになりました:```
go.main.main resolves to: 0x1000a0c20
go.main.fibonacci resolves to: 0x1000a0bb0
パーサーは実在のPEバイナリであるGreenblood(greenblood_1)に対してテストされました。これはPE32+ x86-64 Windows実行可能ファイルとしてコンパイルされたGo製ランサムウェアサンプルです。PEバイナリには専用の .gopclntab セクションがないため、これはマジックバイトスキャンのフォールバックパスを実行します。
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
| --profile | 使用するプロファイル(例:extreme)。 |
| --max-run-duration | アクションが実行できる最大時間(例:600)。 |
| --max-concurrent | 同時に実行できるテストの最大数(例:5)。 |
| --cpu-limit | コンテナのCPU制限(例:0.4)。 |
| --mem-limit | コンテナのメモリ制限(例:256m)。 |
[*] Binary format: pe, endian: little, arch: x86, bits: 64
[*] Scanning binary for gopclntab magic bytes...
[*] Scanning section '.text' (0x401000, 0xF4000)...
[*] Scanning section '.rdata' (0x4F5000, 0x127000)...
[*] Found magic at vaddr=0x568C00
[*] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ptrSize=8,
minLC=1, nfunc=2596, nfiles=345, textStart=0x401000)
[*] textStart from header: 0x401000
[*] Parsed 2596 functions
```
スキャナーは `.rdata` セクション内の `0x568C00` で gopclntab を発見しました。
これは標準のPE(PIEではない)であるため、`textStart` は `0x401000`(非ゼロ)であり、ヘッダー値がアドレス計算に直接使用されます。
| Field | Value |
|---|---|
| 形式 | PE32+ x86-64 |
| gopclntab の場所 | `.rdata` at `0x568C00` (found by magic scan) |
| マジック | `0xFFFFFFF1` (Go 1.20+) |
| ポインタサイズ | 8 |
| Quantum (minLC) | 1 (x86) |
| textStart | `0x401000` (from header) |
| 復元された関数数 | 2596 |
| ソースファイル数 | 345 |
### 復元されたマルウェア関数
マルウェア自身のコード (`main.`) を検索中:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."
```
[](https://github.com/fnk0c/cangibrina/actions/workflows/github_board.yml)```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x4D91E0 main.init (<autogenerated>:1)
0x4D9200 main.map.init.0 (/root/victims/ransom/daf/enc.go:59)
0x4D92C0 main.map.init.1 (/root/victims/ransom/daf/enc.go:126)
0x4D95C0 main.NewKeyManager (/root/victims/ransom/daf/enc.go:146)
0x4D97E0 main.getMachineFingerprint (/root/victims/ransom/daf/enc.go:173)
0x4DA2C0 main.getBIOSUUID (/root/victims/ransom/daf/enc.go:249)
0x4DA3C0 main.NewEncryptionEngine (/root/victims/ransom/daf/enc.go:287)
0x4DA560 main.(*EncryptionEngine).fileWorker (/root/victims/ransom/daf/enc.go:303)
0x4DA660 main.(*EncryptionEngine).processFile (/root/victims/ransom/daf/enc.go:319)
0x4DA740 main.(*EncryptionEngine).encryptFile (/root/victims/ransom/daf/enc.go:334)
0x4DB2A0 main.(*EncryptionEngine).EncryptPath (/root/victims/ransom/daf/enc.go:446)
0x4DB620 main.(*EncryptionEngine).shouldSkipDirectory (/root/victims/ransom/daf/enc.go:495)
0x4DB7C0 main.(*EncryptionEngine).shouldEncryptFile (/root/victims/ransom/daf/enc.go:522)
0x4DB9A0 main.(*EncryptionEngine).placeRansomNote (/root/victims/ransom/daf/enc.go:560)
0x4DBB60 main.(*EncryptionEngine).recordSuccess (/root/victims/ransom/daf/enc.go:636)
0x4DBFA0 main.(*EncryptionEngine).Wait (/root/victims/ransom/daf/enc.go:664)
0x4DC3C0 main.formatBytes (/root/victims/ransom/daf/enc.go:689)
0x4DC500 main.disableRecovery (/root/victims/ransom/daf/enc.go:706)
0x4DC720 main.isAdmin (/root/victims/ransom/daf/enc.go:732)
0x4DC8C0 main.main (/root/victims/ransom/daf/enc.go:760)
0x4DD260 main.getLogicalDrives (/root/victims/ransom/daf/enc.go:863)
0x4DD4A0 main.isAlreadyRunning (/root/victims/ransom/daf/enc.go:892)
0x4DD660 main.getDesktopPath (/root/victims/ransom/daf/enc.go:911)
0x4DD780 main.removeExecutable (/root/victims/ransom/daf/enc.go:932)
...
[+] 43 function(s) shown (filtered from 2596 total)
```
43個のユーザー関数はすべて、単一のソースファイルである`/root/victims/ransom/daf/enc.go`から復元されました。関数名から即座にランサムウェアの機能が明らかになります。鍵管理、マシンフィンガープリンティング、パストラバーサルを用いたファイル暗号化、身代金メモの配置、リカバリ無効化、権限確認、ミューテックスによる単一インスタンス制御、ドライブ列挙、自己削除です。
### 非標準ライブラリの依存関係
Go標準ライブラリに含まれないソースファイルを抽出する:```
/root/go/pkg/mod/golang.org/x/[email protected]/windows/dll_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/registry/key.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/registry/value.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/security_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/str.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/syscall.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/syscall_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/zsyscall_windows.go
/root/victims/ransom/daf/enc.go
```
唯一の外部依存関係は、Windows固有のシステムコール(レジストリアクセス、セキュリティトークン、DLLロード)のための`golang.org/x/[email protected]`です。
### 暗号関数
`crypto`を検索すると、137の暗号関連関数が見つかります。その一部は次のとおりです:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
```
[No content provided to translate.]```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x4AEC00 crypto/cipher.NewCTR (/usr/local/go/src/crypto/cipher/ctr.go:41)
0x4AF520 crypto/cipher.StreamWriter.Write (/usr/local/go/src/crypto/cipher/io.go:36)
0x4AF840 crypto/aes.NewCipher (/usr/local/go/src/crypto/aes/aes.go:36)
0x4C3760 crypto/rand.(*reader).Read (/usr/local/go/src/crypto/rand/rand.go:45)
0x4DE6C0 crypto/internal/fips140/sha256.New (.../sha256/sha256.go:138)
0x4E3EC0 crypto/internal/fips140/sha3.NewCShake128 (.../sha3/shake.go:134)
0x4EED60 crypto/internal/fips140/hmac.New (.../hmac/hmac.go:131)
0x4EF800 crypto/internal/fips140/aes.newBlock (.../aes/aes_asm.go:59)
0x4F0800 crypto/internal/fips140/aes.(*CBCEncrypter).CryptBlocks (.../aes/cbc.go:26)
0x4F0FE0 crypto/internal/fips140/aes.(*CTR).XORKeyStream (.../aes/ctr.go:41)
...
[+] 137 function(s) shown (filtered from 2596 total)
```
暗号化プロファイル: AES(CBCおよびCTRモード)、SHA-256、SHA-512、HMAC、
CSHAKE128、DRBG(決定論的ランダムビットジェネレータ)。これは、
マシンフィンガープリントから派生したマシンごとの暗号化キーを生成し、AES-CTRでファイルを暗号化し、HMACを使用して整合性を保護するランサムウェアと一致しています。
### 適用モード```
$ python3 r2_gopclntab.py -f ./greenblood_1 --apply
[+] Applied 2596 function names to radare2 (0 skipped)
[+] Function names applied. Use 'afl' in r2 to see them.
```
すべての2596個の関数が正常に適用され、スキップされたものはありません。
---
## リバースエンジニアリングのユースケース
### 1. トリアージと識別
バイナリがGoで書かれているか、どのバージョンでビルドされたか、どのパッケージを使用しているかを瞬時に判断します。`--files`出力は、Goツールチェーンのバージョン(`/usr/local/go/1.26.1/...`のようなファイルパスから)と、サードパーティライブラリを含むすべてのソースファイルパスを明らかにします。マルウェアの場合、サンプルが`crypto/tls`、`net/http`、`os/exec`などの関心のあるパッケージを使用しているかどうかを即座に明らかにします。
### 2. ストリップされたバイナリにおけるシンボル復元
コアユースケース。`-ldflags="-s -w"`でストリップされたGoバイナリはシンボルテーブルを失いますが、gopclntabは残ります。このツールはすべての関数名を復元し、匿名の`fcn.1000a0c20`を`main.main`に戻します。これは、マルウェアサンプル、CTFチャレンジ、本番バイナリ、およびストリップされたGo実行ファイルに適用されます。
### 3. Goランタイムのナビゲーション
Goバイナリはランタイム全体(通常1500~2000以上の関数)を埋め込みます。名前がない場合、ランタイムは匿名関数の難攻不落の壁となります。名前があれば、`runtime.mallocgc`、`runtime.gopanic`、`runtime.newproc`、`runtime.gcStart`を瞬時に特定し、各コールサイトでバイナリが何をしているかを理解できます。
### 4. ユーザーコードとランタイムの分離
`main.`またはアプリケーションのパッケージパスを検索することで、ランタイムからユーザーコードだけを分離できます。上記の例では、`main.`でフィルタリングすると、2030個の全関数の中から`main.main`と`main.fibonacci`が即座に明らかになります。サードパーティのパッケージ名(例:`-n "github.com/user/repo"`)で検索して、外部依存関係を特定することもできます。
### 5. ソースレベルのコンテキスト
各関数には、そのソースファイルのパスと開始行番号が付属しています。つまり、ストリップされたバイナリを解析する場合でも、逆アセンブリをGo標準ライブラリのソースコード(オープンソース)と相互参照できます。関数が`runtime/mgc.go`の733行目で始まることがわかれば、逆アセンブリと並行して元のソースを読むことができます。
### 6. パイプラインと自動化
`--json`モードによりスクリプト処理が可能になります。出力をIDA/Ghidraインポーター、差分ツール、YARAルールジェネレーター、または任意の解析パイプラインに取り込めます。例えば、すべての暗号関連関数を抽出するには:```bash
python3 r2_gopclntab.py -f sample.exe --json \
| jq '.functions[] | select(.name | contains("crypto"))'
```
### 7. 対話型 radare2 ワークフロー
`--apply` の後、r2 セッション全体が Go の名前でナビゲート可能になります。関数にシーク (`s go.main.main`)、関数リストの検索 (`afl~runtime.gc`)、クロスリファレンスの検査 (`axf go.main.fibonacci`)、逆アセンブリ出力内のインラインのソース位置コメントの表示 (`pd`) が可能です。これにより、r2 は汎用の逆アセンブラから Go 対応の解析環境に変わります。
---
## サポートされているプラットフォームと Go バージョン
### バイナリ形式
| 形式 | セクション発見方法 | テスト済み |
|---|---|---|
| ELF (Linux) | セクション名 `.gopclntab` または `.data.rel.ro.gopclntab` | はい |
| Mach-O (macOS) | セクション名 `__gopclntab` (`__TEXT` セグメント内) | はい |
| PE (Windows) | マジックバイトスキャン(専用セクションなし) | はい(Go ランサムウェア PE64 である Greenblood でテスト済み) |
PE バイナリやセクションヘッダを欠く積極的にストリップされた ELF/Mach-O バイナリの場合、ツールはフォールバックとして全セクションをスキャンし、4 バイトのマジックとそれに続く検証バイト(pad=0、ptrSize ∈ {4,8}、minLC ∈ {1,2,4})を探します。
### Go バージョン
| マジック | Go バージョン | ヘッダ内の `textStart` | `functab.entry` の型 | `startLine` フィールド | 状態 |
|---|---|---|---|---|---|
| `0xFFFFFFFB` | 1.2 | いいえ | `uintptr` (絶対) | いいえ | サポート済み |
| `0xFFFFFFFA` | 1.16 | いいえ | `uintptr` (絶対) | いいえ | サポート済み |
| `0xFFFFFFF0` | 1.18 - 1.19 | はい | `uint32` (相対) | いいえ | サポート済み |
| `0xFFFFFFF1` | 1.20+ | はい(0 の場合あり) | `uint32` (相対) | はい | サポート済み |
`0xFFFFFFF1` マジックは Go 1.20 から少なくとも Go 1.26 まで使用されています。
---
## 方法論
### セクション位置特定戦略
パーサーは 2 段階のアプローチで gopclntab データを見つけます。
**フェーズ 1 (ELF/Mach-O):** radare2 のセクションリスト (`iSj`) をクエリし、`.gopclntab`、`.data.rel.ro.gopclntab`、または `__gopclntab` という名前のセクションを探します。
**フェーズ 2 (PE/フォールバック):** 名前付きセクションが見つからない場合、全セクションをスキャンして 4 バイトのマジックバイトを探します。各候補は、バイト 4-7 が期待されるパターン(2 つのゼロパッドバイト、有効なポインタサイズ(4 または 8)、有効な命令量子(1、2、または 4))と一致するか検証されます。これにより、偶然のバイトパターンによる誤検出が排除されます。
### textStart と .text セクション
Go >= 1.18 では、functab 内の関数エントリポイントは相対オフセットとして格納されます。絶対仮想アドレスを計算するにはベースが必要です。```
absolute_addr = base + entryoff
```
ベースは次のロジックで解決されます。```
Is magic 0xFFFFFFFB (Go 1.2)?
YES -> base = 0 (entries are absolute addresses)
NO -> Is magic 0xFFFFFFFA (Go 1.16)?
YES -> base = .text section vaddr (entries are absolute)
NO -> (Go 1.18 / 1.20+)
Is header.textStart != 0?
YES -> base = header.textStart
NO -> base = .text section vaddr
```
`textStart == 0` のケースは、Go >= 1.22 の Mach-O および PIE バイナリで発生します。
この場合、`entryoff` の値は `.text` セクションの先頭からの相対値となるため、パーサーは r2 に `.text` の仮想アドレスを問い合わせ、それをベースとして使用します。
これは Go 1.26 Mach-O arm64 バイナリで検証済みです:
- ヘッダ内の `textStart`: `0x0`
- `.text` セクション vaddr: `0x100001000`
- `functab[1].entryoff`: `0x70`
- 計算後のアドレス: `0x100001000 + 0x70 = 0x100001070`
- r2 のネイティブ解析により、`internal/abi.BoundsDecode` が `0x100001070` にあることを確認
### バージョン対応の構造体解析
`_func` 構造体のレイアウトは Go 1.18 と Go 1.20+ で異なります。
唯一の変更点は、Go 1.20+ においてオフセット 36 に 4 バイトの `startLine` フィールドが挿入されたことで、これにより `funcID`、`flag`、`nfuncdata` が 4 バイトずつずれます:
| フィールド | Go 1.18 のオフセット | Go 1.20+ のオフセット |
|---|---|---|
| `entryOff` | 0 | 0 |
| `nameOff` | 4 | 4 |
| `args` | 8 | 8 |
| `cuOffset` | 32 | 32 |
| `startLine` | (なし) | 36 |
| `funcID` | 36 | 40 |
| `flag` | 37 | 41 |
| `nfuncdata` | 39 | 43 |
パーサーはマジックナンバーを確認し、使用するレイアウトを判断します。
### PC データのデコード
ソースファイルインデックスと行番号は、pctab 領域にコンパクトな「PC データプログラム」として格納されています。
各プログラムは、符号付き値にジグザグエンコーディングを用いた可変長整数を使用して、`(value_delta, pc_delta)` ペアのシーケンスをエンコードします。パーサーはこれらをデコードして次の情報を解決します:
- ソースファイル: `_func.pcfile` -> pctab プログラム -> ファイルインデックス -> cutab -> filetab -> ファイルパス文字列
- 行番号: `_func.pcln` -> pctab プログラム -> 行番号 (Go 1.20+ では `startLine` のオフセットを加算)
---
## 制限
1. **インライン化された関数** はトップレベルの関数テーブルに出現しません。
これらは `FUNCDATA_InlTree` / `PCDATA_InlTreeIndex` 構造体にエンコードされており、本ツールは現在これらをデコードしません。テストバイナリでは、`main.helloWorld` と `main.addNumbers` がコンパイラによってインライン化されたため、出力に含まれません。
2. **Go 1.2 のサポート** はベストエフォートです。Go 1.2 のフォーマットは大幅に異なり(独立した funcnametab や cutab がなく、functab に絶対ポインタが使われる)、実際に遭遇することはほとんどありません。
3. **ビッグエンディアン** アーキテクチャは、原理的には対応しています(エンディアンは radare2 のバイナリ情報から検出され、すべての構造体読み取りに使用されます)が、テストは行われていません。
4. `--apply` モードは `af+` を使用して関数スタブを作成しますが、これが r2 の自動解析と競合する可能性があります。クリーンなセッション(`aaa` の前、またはその代わり)で実行すると、場合によってはより良い結果が得られることがあります。
---
## 追加ドキュメント
詳細な技術ドキュメントは `documentation/` ディレクトリにあります:
- [DOCUMENTATION.md](https://github.com/asherdll/r2gopclntabparser/blob/main/documentation/DOCUMENTATION.md) - すべての使用モード、出力形式、フラグの組み合わせを含む完全なユーザードキュメント。
- [METHODOLOGY.md](https://github.com/asherdll/r2gopclntabparser/blob/main/documentation/METHODOLOGY.md) - 設計上の決定とアルゴリズム: textStart と .text の解決、バージョン対応の解析、PE スキャン戦略、PC データのデコード、r2 統合の詳細。
- [GOPCLNTAB_FORMAT.md](https://github.com/asherdll/r2gopclntabparser/blob/main/documentation/GOPCLNTAB_FORMAT.md) - Go のバージョン間における gopclntab バイナリフォーマット: バイトレベルの構造体レイアウト、メモリモデル、オフセットチェーン、varint エンコーディング、バージョン差分の概要。
---
## 参考文献
- Go ランタイムソース (pcHeader): [go1.20.6/src/runtime/symtab.go#L414](https://github.com/golang/go/blob/go1.20.6/src/runtime/symtab.go#L414)
- Go リンカ (フォーマットを書き込む): [go1.20.6/src/cmd/link/internal/ld/pcln.go](https://github.com/golang/go/blob/go1.20.6/src/cmd/link/internal/ld/pcln.go)
- Mandiant - Golang Internals Symbol Recovery: [mandiant.com/resources/blog/golang-internals-symbol-recovery](https://www.mandiant.com/resources/blog/golang-internals-symbol-recovery)
- Go 1.2 シンボルテーブルの設計文書: [docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o](https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub)
| フラグ | 説明 |
|---|
-l, --list | 復元されたすべての関数をそのアドレスとともに表示します。 |
-n NAME, --funcname NAME | 名前に NAME を含む関数のみを表示します(部分一致)。完全一致がある場合、そのアドレスは個別に表示されます。 |
-v, --verbose | 進行状況メッセージ、解析済みヘッダーフィールド、内部オフセットを表示します。 |
--apply | 復元された関数名をradare2セッションに関数定義(af+)として書き込み、go.フラグスペースにフラグを追加し、元のGo名とソース位置をコメントとして追加します。 |
--json | ヘッダーと完全な関数リストをJSONとしてstdoutに出力します。 |
--files | ファイルテーブルから抽出されたソースファイルパスのリストを表示します。 |
-h, --help | ヘルプメッセージを表示します。 |
| 用語 | 説明 |
|---|
| PoC | 概念実証; 脆弱性が悪用可能であることを示す実証。 |
| RCE | リモートコード実行; 攻撃者が標的のマシン上で任意のコードを実行できる能力。 |
| LFI | ローカルファイルインクルージョン; 攻撃者がサーバー上のファイルをインクルードできる脆弱性。 |
| RFI | リモートファイルインクルージョン; 攻撃者がリモートファイルをインクルードできる脆弱性。 |
| SQLi | SQLインジェクション; データベースの脆弱性を悪用する攻撃手法。 |
| XSS | クロスサイトスクリプティング; Webアプリケーションに悪意のあるスクリプトを注入する攻撃。 |
| CSRF | クロスサイトリクエストフォージェリ; ユーザーを騙して意図しない操作を実行させる攻撃。 |
| SSRF | サーバーサイドリクエストフォージェリ; サーバーに攻撃者の代わりにリクエストを実行させる攻撃。 |
| LPE | ローカル特権昇格; システム上でより高い権限を取得するために脆弱性を悪用する行為。 |
| EoP | 特権昇格; LPEと同義。 |
| CTF | キャプチャー・ザ・フラッグ; 様々な課題を含むサイバーセキュリティ競技。 |
| OSINT | オープンソースインテリジェンス; 公開情報源から情報を収集すること。 |
| C2 | コマンド&コントロール; 攻撃者が侵害したシステムとの通信を維持するために使用するインフラ。 |
| ADDRESS FUNCTION NAME |
| 指標 | r2 ネイティブ (stripped) | r2_gopclntab.py適用後 |
|---|
| 検出された関数 | 1913 (匿名の fcn.XXXX ラベル) | 2030 (完全なGoパッケージ修飾名) |
| 識別されたユーザー関数 | 0 | main.main, main.fibonacci (ソース+行番号付き) |
| 復元されたソースファイル | 0 | 261 (完全な絶対パス) |
| 名前付きシンボル | Cインポートスタブのみ (sym.imp.mmap など) | すべてのGo関数にラベル付き (go.main.main, go.runtime.gcStart など) |
| ソースコメント | なし | 逆アセンブリ内にインラインで src: /tmp/gotest/main.go:20 |
| 名前によるナビゲーション | いいえ | はい (s go.main.main, afl~runtime.gc) |