mcpField
A
analyzing-go-binaries

analyzing-go-binaries

github.com/trilwuセキュリティ17750 回インストール12 週間前に更新
リポジトリ

analyzing-go-binaries について

コンパイル済みGoプログラムを、シンボルが削除されたバイナリも含めて分析するリバースエンジニアリング向けスキルです。セキュリティ分析者やリバースエンジニアがGo固有の手法でシンボル、型、依存関係を復元し、動作を調べられます。

主な機能

  1. 01Goバイナリとバージョンの特定
  2. 02シンボルと関数名の復元
  3. 03型メタデータと構造体タグの抽出
  4. 04依存関係とビルド設定の確認
  5. 05Go固有のセキュリティリスクの点検
  6. 06Goマルウェア痕跡の分析

活用シーン

  1. 01未知のGo実行ファイルの初期調査
  2. 02サービスのAPI構造の復元
  3. 03Goマルウェアサンプルの調査
  4. 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

コメント