analyzing-go-binaries について
コンパイル済みGoプログラムを、シンボルが削除されたバイナリも含めて分析するリバースエンジニアリング向けスキルです。セキュリティ分析者やリバースエンジニアがGo固有の手法でシンボル、型、依存関係を復元し、動作を調べられます。
主な機能
- 01Goバイナリとバージョンの特定
- 02シンボルと関数名の復元
- 03型メタデータと構造体タグの抽出
- 04依存関係とビルド設定の確認
- 05Go固有のセキュリティリスクの点検
- 06Goマルウェア痕跡の分析
活用シーン
- 01未知のGo実行ファイルの初期調査
- 02サービスのAPI構造の復元
- 03Goマルウェアサンプルの調査
- 04バイナリ内のセキュリティ問題の発見
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill analyzing-go-binariesインストール後、Claude Code で/analyzing-go-binariesを入力して読み込まれたか確認します。
詳細
Go バイナリの解析
Go バイナリは厄介に見えます。静的リンクされ、数十メガバイトもあり、見覚えのあるインポートはなく、「ストリップ」されているため、ツールには sub_ 関数が何千個も表示されます。しかし、実際は違います。Go にはランタイムのリフレクションとパニックトレース用の独自のシンボルテーブルがあり、strip でも削除されません。これを復元すれば、バイナリは最も解析しやすい対象のひとつになります。
使用する場面
stringsにruntime.main、go:buildid、go.buildinfo、またはruntime.gopanicが表示される- 「ストリップ」されたバイナリが 5~50 MB で、動的インポートがほとんどない
- 逆アセンブラーに名前のない関数や判読不能な文字列が何千個も表示される
- Go マルウェア、Go CLI ツール、またはコンパイル済み Go サービスを解析する
使用しない場面
- Rust バイナリ —
analyzing-rust-binariesを使用してください。シンボルの復元方法がまったく異なります - .NET アセンブリ —
analyzing-dotnet-assembliesを使用してください - 封じ込め前のマルウェア疑い — まず環境の分析に
analyzing-malwareを使用し、その後ここに戻ってください - 一般的なネイティブ RE —
analyzing-binariesを使用してください
Go であることとバージョンの確認
strings -n 6 target | rg -m5 'go1\.[0-9]+|go:buildid|runtime\.main|GOROOT'
go version target # works on unstripped and many stripped builds
go version -m target # module list and build settings — free SBOM
最初に実行するコマンドとして最も有用なのは go version -m です。バージョン付きのモジュール依存関係グラフが出力されるため、逆アセンブルを始める前に使用中のサードパーティライブラリを確認できます。これだけで疑問が解決することもよくあります(使用している HTTP ライブラリ、暗号ライブラリ、C2 フレームワークなど)。
Go のバージョンは重要です。pclntab のレイアウトは 1.2、1.16、1.18、1.20 で変更されました。ツールが失敗する場合、強化されたバイナリではなく、バージョンの不一致が原因であることがほとんどです。
シンボルの復元
# GoReSym — extracts pclntab, moduledata, types, and build info
GoReSym -t -d -p target > syms.json
# -t user type metadata
# -d include standard library
# -p paths
# redress — Go-aware analysis, works well when GoReSym struggles
redress info target
redress symbols target
redress types target
# Load into the disassembler
# IDA: AlphaGolang, golang_loader_assist, or the GoReSym IDA script
# Ghidra: GolangAnalyzerExtension, or gotools
# Binja: the Golang loader plugin
シンボルを適用すると、関数には本来の名前が付与されます。
main.processRequest、crypto/tls.(*Conn).Handshake、
github.com/vendor/pkg.Function などです。main.* とサードパーティのモジュールパスに絞り込んでください。 runtime.、internal/、標準ライブラリに含まれるものはすべて標準のものであり、関数数の 90%以上を占めます。
jq -r '.UserFunctions[].FunctionName' syms.json | rg -v '^(runtime|internal|reflect|sync)\.' | head -40
逆アセンブラーで Go コードを読む
Go のリスト表示を理解するうえで、知っておくべき点は 4 つあります。
文字列には終端文字がありません。 Go の文字列はポインターと長さの組み合わせであるため、strings の出力では隣接するリテラルが連結され、逆アセンブラーではポインターの読み込みに続いて長さの定数が表示されます。この 2 つを探してください。ポインターの隣にある定数が長さであり、これを使ってブロブから正しい部分文字列を切り出します。
呼び出し規約。 Go 1.17 より前は、引数と戻り値はすべてレジスターではなくスタックに渡されていました。1.17 以降は amd64/arm64 でレジスター ABI が適用されます。C の呼び出し規約で設定されたデコンパイラーでは、引数が誤って表示されます。Go 対応プラグインを使えば修正できます。デコンパイラーの出力が意味不明に見える主な理由はこれです。
インターフェースのディスパッチ。 インターフェース経由の呼び出しは itab を通じて行われます。itab は具象型とそのメソッドポインターを保持するテーブルです。呼び出し先を特定するには、読み込まれている itab を見つけ、次に具象型を確認します。型の復元ツールを使うとこれらに名前が付与され、間接呼び出しが読みやすくなります。
ゴルーチンと defer。 go f() は f を引数として runtime.newproc にコンパイルされるため、並行処理のロジックは直接の呼び出しとして現れません。defer は runtime.deferproc/deferreturn になり、クリーンアップコードが記述された場所から離れた位置に散らばります。制御フローを追うときは、見落とす可能性のある処理を見つけるため、newproc の呼び出し箇所を確認してください。
型の復元
Go にはリフレクション用の完全な型記述子が埋め込まれています。そのため、構造体のフィールド名とレイアウトを復元できます。ワイヤープロトコルにそのまま対応する JSON タグも含まれます。
redress types target | rg -A10 'type main\.'
# Struct tags like `json:"api_key"` recover the exact protocol field names
Go サービスの API サーフェスや Go インプラントの C2 メッセージ形式を把握する最速の方法です。リクエストとレスポンスの構造体を復元すれば、命令をひとつも読まずにプロトコルが分かります。
Go 固有のセキュリティレビュー
動作を理解するのではなく、バグを探す場合に優先して調査すべき Go 固有の項目は次のとおりです。
- 無視されたエラー。 エラーを返す関数に対する
_ =。特に認証、暗号処理、ファイル操作に関わる箇所に注意します。 - セキュリティ値に
math/randを使用。 トークン、セッション ID、ノンスの生成に、暗号用途ではない乱数生成器を使用していないか確認します。 tls.ConfigのInsecureSkipVerify: true。fmt.Sprintfによる SQL、シェルコマンド、URL の組み立て。- 共有マップや構造体のデータ競合。 負荷がかかった際の認可バグの原因になることがよくあります。
- シェルを使った
os/exec、または入力から組み立てられた引数を使うos/exec。
ソースコードを利用できる場合は、auditing-code-for-vulnerabilities と
govulncheck を使用します。上記はバイナリしかない場合を対象としています。
Go マルウェアに関するメモ
Go はクロスプラットフォームのインプラントでよく使われており、有用な痕跡が残ります。
go version -mで取得できるモジュール一覧から、使用されているフレームワークを特定できます。ネットワークライブラリ、暗号ライブラリ、場合によっては C2 プロジェクト自体も判明します。- シンボルテーブル内のビルドパスから、開発者のユーザー名、プロジェクト名、ディレクトリ構成が漏れることがあります。
main.パッケージの関数名は、ストリップしてもpclntabが削除されないため、多くの場合残ります。そのため、機能の一覧を手早く把握できます。- 一部のサンプルでは
pclntabが意図的にストリップされていたり、moduledataを改変するツールが使われていたりします。それ以外の点では明らかに Go であるサンプルを GoReSym と redress の両方が解析できない場合は、報告すべき回避の兆候として扱い、型記述子を直接スキャンしてください。
IOC と検出結果を analyzing-malware と
engineering-detections に引き継ぎます。
受け入れてはいけない決めつけ
- 「ストリップされているから、シンボルはありません。」
stripはpclntabを削除しません。結論を出す前に GoReSym を実行してください。 - 「逆コンパイラーの出力がめちゃくちゃです。」 呼び出し規約が間違っています。Go 対応プラグインを適用してください。
- 「関数が何千もあり、何週間もかかります。」 ほとんどはランタイムと標準ライブラリです。
main.*とベンダーモジュールに絞り込んでください。 - 「文字列がすべて連結されています。」 Go の文字列はポインターと長さで構成されます。長さの定数に従って切り出してください。
- 「ツールが失敗したので、このバイナリは保護されています。」 まず Go のバージョンが、ツールの対応する
pclntabのバージョンに含まれているか確認してください。 - 「すべてのゴルーチンを追跡する必要があります。」
runtime.newprocの呼び出し箇所を見つけ、渡される関数を読んでください。
参考資料
analyzing-binaries— 一般的なトリアージと動的解析analyzing-malware— Go サンプルの封じ込めと IOC 抽出auditing-code-for-vulnerabilities— ソースコードがある場合に使う、Go のバグ種別チェックリスト- GoReSym、redress、AlphaGolang、GolangAnalyzerExtension、
go version -m