Golangバイナリをより簡単かつ正確に解析するGhidraスクリプトを公開

記事で取り上げたスクリプトは、CUJOaiDorka Palotay氏の壮大な作品に基づくものです。

Golangマルウェアはますます蔓延しており、アナリストは無数のウサギの穴に飛び込むことなく、これらのファイルを効果的に分析する方法を知っておく必要があります。このブログでは、Golangバイナリをより簡単かつ正確に解析し、アナリストがこのようなバイナリに費やす時間を大幅に短縮するためのいくつかのGhidraスクリプトについて説明します。最近、NSAはGhidra 10.3を公開しましたが、これにはGolang 1.18の初期サポートが含まれています。これらのスクリプトは、より多くのバージョンのGolangをサポートし、ELFファイルの包含など、より広い範囲をサポートしています。

NSAが無償で提供するオープンソースのGhidraは、広く採用されている業界標準の分析ツールです。これを使用すると、攻撃者にコストを課しながら、防御側がツール全体を皆のために改善する方法を見つけることによって効果的に協力することができます。

Trellixは、可能な限りコミュニティプロジェクトをサポートし、貢献することを強く支持しています。これらのGhidraスクリプトをコミュニティと共有することで、私たちが説く「生きたセキュリティ」を実践していることを証明します。このブログは、GhidraにGophersを送り込み、最終的に複数のJavaベースのヘルパースクリプト(JavaScriptスクリプトと誤解されないように)を完成させるまでの私たちの旅を記録しています。


旅のはじまり

2022年4月、フランスのナントで開催された第9回Botconfで、Dorka PalotayはGolang研究に関するプレゼンテーションを行い、彼女の同僚 György LuptákはSysrvマイニングボットネットに潜入しました。Dorkaが作成したGhidraスクリプトは、静的および動的な文字列、元の名前と一緒に関数、およびGolang型を回復することができます。これらのスクリプトはPython2で書かれており、GhidraはJythonインタプリタ経由で実行します。私たちのチームは、Dorkaの研究について議論し、それをさらに発展させたいと考えました。元のスクリプトでは、Golangのバイナリを扱う際に発生するすべての問題を解決することができなかったので、Dorkaの基礎的な研究を改良して、研究者にとってさらに有用なものにできると考えました。


Gophersとの出会い

Golangバイナリの解析方法を完全に理解するためには、言語そのものに熟達することが最も重要です。Golangのソースコードと、Dorkaが 書いた2つの注目すべき ブログは、Golangの内部を理解するための基礎となりました。

Golang の積極的な開発により、最終的にはマイナーな (場合によっては重大な) 方法でスクリプトが破壊されることは避けられません。そのため、Golangのバージョン検知に重点を置いています。これにより、スクリプトは必要に応じてバージョン固有のアプローチを使用できるようになります。異なるアーキテクチャ(IntelやARMなど)やアーキテクチャの異なるバージョン(x86やx86_64など)は、それぞれ異なるアプローチを必要としますが、これもきめ細かく処理されます。


Ghidraの内部を理解する

Ghidraを拡張するためには、ローダー、アナライザー、スクリプトを書くことができます。このブログの範囲内で(過度に)単純化すると、ローダーはバイナリをロードするために使用され、アナライザーとスクリプトはバイナリがロードされた後に使用されます。アナライザーは(自動)解析中にいつでも実行でき、スクリプトは特定の瞬間にユーザーによって呼び出されます。

また、Ghidraは内部で使用する世界共通言語であるP-codeを採用しています。各対応アーキテクチャをPコードにマッピングし、デコンパイラなどの内部ツールはそのPコードを利用します。これにより、デコンパイラを変更することなく、どのようなアーキテクチャにも対応することができるようになります。P-codeには、ノーマルP-codeとハイP-codeの2種類があることに注意する必要があります。ノーマルPコードにはすべての情報が含まれていますが、トラバースが困難です。一方、ハイPコードには必要な情報が含まれていながら、トラバースが容易です。デコンパイラでは、このハイPコードを使って、変数とコードを結びつけていますが、これにはもっと多くの意味があります。

Dorkaによるオリジナルの作品では、4つの台本が作成されました。Dorkaの作品をレビューすると、スクリプトの数やその形式を変えるメリットはありませんでした。ローダーは、オリジナルのローダーがすでに適切であるため、過剰になります。アナライザーまたはスクリプトは、プリコンパイルされたアナライザーとは異なり、実行時にコンパイルされるため、アナライザーに対するスクリプトの移植性以外に違いはありません。さらに、アナライザーは多くの場合、重大な変更が存在しない場合でもバージョンに依存しており、再度機能させるにはバージョンのバンプと再コンパイルが必要です。

このように、移植性に優れていることは有用な利点であり、デメリットは特にありません。4つのスクリプトの実行を容易にするため、5つ目のスクリプトとして、他の4つのスクリプトを順次呼び出すラッパを作成しました。Ghidraの高いPコードを利用するためにスクリプトの一部を変更することで、どのアーキテクチャでも独立して動作させることが可能ですが、Ghidraの(高)Pコードやそのメリット・デメリットが不明だったため、実行には至っていません。これは、将来のアップデートで改善される可能性があります。


ドラゴンに餌をやる

Golang の内部構造の基本を理解した上で、スクリプトの Java への移植が始まりました。いくつかの特殊な点がなければ、これにはそれほど時間はかかりませんでした。完成したら、スクリプトを強化します。さらに、スクリプトのドキュメントを完全に記述する必要がありました。ドキュメントに重点を置くことで、コードを変更する前に適切な理解を確実に行うことができました。理解しやすいレイアウトと概念を考慮すると、Golang や同様の目的でスクリプトを簡単に編集できます。

Javaベースのスクリプトが完成し、ドキュメントも完成したところで、コードベースの改良を行いました。元のコードは、あまり効率的でないケースもありましたし、エッジケースも欠けていました。この2点を修正・改良することで、より早く、より効果的な結果を得ることができるようになりました。

テストは、まずDorkaがブログで言及したGolangサンプルで行われ、その後、私たちのチームが野生で遭遇した他のサンプルに移行しました。チームは、デモデータとして以下のような2つのサンプルを取り上げています。2023年1月下旬から2月上旬に遡り、ESETがSandworm、SymantecはUAC-0056と結び付けています。以下、ファイルのハッシュが複数のフォーマットで表示されています。なお、Trellix製品群は、「検出名」の行に示された名前でこれらのファイルを検出しています。

Table 1

Ghidraの活発な開発に基づいて、新しいバージョンにはバグフィックスと新機能が含まれていることに注意することが重要です。テスト中は、2022年9月22日時点で利用可能な公開Ghidraソースから構築されたGhidraバージョン10.2-DEVを使用しました。

Ghidraのバージョンが異なると、追加された機能や更新された機能により、以下の数値は異なるかもしれません。しかし、このスクリプトによってGolangバイナリの解析がどのように強化されるかを示すことができます。スクリプトを実行する前に、すべてのアナライザーを “Select All “ボタンで選択しました。

Figure 1 - The applied Ghidra analyzers, all of which are selected
図 1 – 適用された Ghidra アナライザー (すべてが選択されている)

Ghidra のバージョン、サンプル、使用する Ghidra アナライザーを確認した上で、スクリプトの結果を解釈することができます。以下の表は、前述の3つのサンプルについて、スクリプトの実行による違いを示しています。

Table 2

結果が正しく解釈されるように、この表の値について簡単な説明が必要です。すべてのパーセンテージは、スクリプトの実行が完了した後の Ghidra 内の関数または文字列の数に基づいています。

“Ghidra命名関数 “とは、前述の自動解析後にGhidraが見つけた関数のうち、”FUN_”や “thunk_”で始まる関数を除いた総数として定義されます。

「新しく作成された関数」の値は、関数リカバリースクリプトが作成した関数の数を表します。同様に、「新しく見つかった文字列」の値は、文字列回復スクリプトが作成した文字列の数です。

「新しく名前が付けられた関数」とは、「FUN_」や「thunk_」という名前の関数ではなく、コードの解析を容易にするために意味のある名前を付けた関数のことです。復元処理により、この値は、これらのバイナリがストリップされている場合でも、元の関数名がほぼ完全に復元されていることを表しています。Ghidraには現在、Golang関数の関数IDデータベースがなく、仮にあったとしても、ユーザが作成した関数は復元されませんが、本手法では復元が可能になっています。

回収されたタイプは絶対値で表示されます。これは、以前に特定されたタイプとの比較ができないため、不正確な結果を含みやすいからです。

その違いを説明するために、「ギドラ命名機能」と「新規命名機能」を比較したのが次のグラフです。

Figure 2 - The originally recognized and labelled functions compared to the script’s result
図 2 – 元々認識されラベル付けされた関数とスクリプトの結果の比較

ギドラのデフォルトの解析結果と、スクリプトを実行した結果の違いは、下図を参照してください。左がデフォルトの解析結果、右がスクリプトを実行した後の出力です。これまで認識されていなかった関数(”FUN_”と “thunk_”で始まるもの)は、元のGolangパッケージを含む名前に変更されています。これは、ライブラリコード(”os/exec.Command “関数など)とユーザが書いたコード(”main.walkFunc “など)の両方に対して行われます。

ライブラリ関数の復元は関数署名でも可能ですが、時間の経過とともにコードが変化するため、関数ごとに数十の署名を必要とし、適切な代替手段ではありません。さらに、ユーザが書いた関数のファイル名を復元することはできません。基本的には、シンボルが最初に存在していたかのようにGolangファイルを分析することができます。

Figure 3 - SwiftSlicer's final few lines of code of the main.main function (offset of the first line is 0x004afc60)
図3 – SwiftSlicerによるmain.main関数の最後の数行のコード(最初の行のオフセットは0x004afc60)

上図では、スクリーンショットの1行目で、関数の第1引数が、未知のデータ型(「DAT_」と表示)から、正しい長さで認識される文字列に変更されていることがわかります。これは、文字列復元スクリプトの効果で、ギドラがデータを正しい型に表示することができるようになったのです。WMICコマンドが1行目で実行されることは、従来はあいまいでしたが、クリックしなくても理解できるようになりました。

上の図ではシンボルがほぼ完全に復元されていますが、これを数枚のスクリーンショットで表現するのは困難です。ここで重要なのは、サンプルのリバースエンジニアリング中に分析者が持つ追加のコンテキストであり、これにより時間を節約し、不必要な仮定を回避することができます。

Golangの内部構造やスクリプトの内部構造に興味がない人、あるいは単にスクリプトを自分で試すのが待てない人のために:それらはここで見つけることができます。次のセクションでは、Golangの内部とスクリプトがそれらをどのように使っているかについての詳細な概要を説明します。


Gopherを消化する

スクリプトを完全に理解するためには、いくつかのGolangの概念と構造を理解する必要があります。このセクションでは、使用されているいくつかのパターンと、Golangのpclntabとmoduledata構造体について説明します。ここでは、これらの構造を徹底的に深く掘り下げるというよりは、その目的を伝えることを目的としています。Golang の内部に詳しい人は、簡潔にするためにこのセクションで省略した部分がいくつかあることに気づくかもしれません。

Golangのバージョン管理について簡単に書いておきます。バージョン番号の前にある “1. “を除いて読むと、一番簡単です。例えば、バージョン1.2はバージョン1.18より前です。正確には、この2つのバージョンの間に16のバージョンがあり、9年の間にリリースされています。スクリプトを読むとき、そしてGolangのバージョン管理を扱うときは、このことに留意してください。

繰り返されるパターン

高度な P コードを使用しないことの影響は、Golang コードと構造の一部がアセンブリ命令のパターンに基づいていることです。新しい Golang バージョンではこれらのパターンから逸脱する可能性があるため、メンテナンスが必要です。サポートするアーキテクチャが増えると、維持費も増加します。動的文字列回復スクリプトの抜粋を次の図に示します。この抜粋では、有効アドレスのロード (LEA) 命令の後に移動 (MOV) 命令が続き、その後パターンが再開されます。完全なパターンが一致すると、Ghidra で文字列を復元して作成できます。

Figure 4 - Matching instructions in a sequence to find a pattern
図4 – シーケンス内の命令をマッチングさせてパターンを見つける

pclntab

2013年にRuss Coxによって文書化されたように、pclntabはGolang 1.2 .で導入されました。この名前は「program counter line number table」の略で、バイナリ内の関数とその元のソースコードの行番号に関する情報を含んでいます。このデータは、エラーが発生したときに意味のあるスタックトレースを作成するために使用されます。Cox によるリンク先の資料から引用したこの表の形式は次のとおりです。

N pc0 func0 pc1 func1 pc2 func2 ... pc(N-1) func(N-1) pcN

この表記では、”N “はテーブルのカウント、またはサイズです。各「funcN」エントリーは、「Func」構造体が存在する関数シンボルテーブルからのオフセット値です。この構造体には、関数の名前とそのオフセットなどの情報が含まれています。「pcN」は、ソースファイルの行番号です。

pclntabは、LinuxとMacOSベースのバイナリでは、それぞれ「.gopclntab」または「_gopclntab」という名前でセクションとして区別されています。Windowsや前述のプラットフォームの場合、構造体のマジックバリューを検索することができます。このマジックバリューは、バイナリのGolangのバージョンによって異なります

関数名はバイナリから削除できるので、完全に削除されると思われがちですが、そうではありません。しかし、リンクされている “Func “構造体には、まだ関数名が残っています。この構造体は、適切なパース処理によって取得することができ、そこから関数を作成したり名前を変更したりすることができます。Ghidraが見逃した関数もこの方法で作成され、解析時にバイナリのより完全な概観を確保することができることに注意してください。

モデュールデータ

moduledata構造体内には、pclntabなど他の構造体がいくつか存在します。また、バイナリ内の型に関する情報を含むtypelinksフィールドも含まれています。moduledataにはpclntabがそのまま入っているので(ポインタではなく、構造体内のバイト配列フィールドです)、pclntabのマジック値を検索するとmoduledataにたどり着きますが、Golangのバージョンによっては、構造体の先頭にあるとは限りません。

typelinks フィールドは、型名と型内のフィールド名を復元するために使用されます。含まれる型やネストされた可能性のある型に応じて、これらはGhidra内で解析され、名前を変更することができます。これにより、元のフィールド名とともに、作成者が使用した構造についての直接的な洞察が得られます。


ドラゴンを使ってGopherを倒す

提供されたスクリプトを使用するには、すべての Java ファイルを Ghidra scripts フォルダに転送するだけです。Ghidraが使用しているフォルダを確認したり、フォルダを追加したりするには、下図のようにコードブラウザ内のウィンドウ→スクリプトマネージャでスクリプトマネージャを開くか、アイコンバーの緑の再生ボタンを押します。スクリプトマネージャでは、箇条書きのリストをクリックするとフォルダビューが表示され、フォルダを確認、追加、削除することができます。

Figure 5 - Ghidra's script manager
図 5 – Ghidra のスクリプト マネージャー

スクリプトを追加したら、スクリプトマネージャの2つの緑の矢印を押して、読み込んだスクリプトを更新し、(部分的な)スクリプト名を検索してください。また、スクリプトマネージャの左側にあるツリービューでGolangタブを開くと、タグ付けされたスクリプトをすべて表示することができます。


まとめ

Ghidraは、マルウェアを分析する際の救世主です。無料かつオープンソースであることは、2019年にツールを公開する際にNSAが意図した通り、コミュニティがツールを採用し、学習し、拡張することを可能にします。これらの目標は、アナリストやマルウェア研究者が防御能力を強化するのを支援するというTrellixの目標と一致しています。Ghidra ユーザーのために再利用可能なスクリプトを使用してツールに貢献することだけが私たちの唯一の目標ではありません。私たちはまた、防御側がそのような活動から多大な利益を得るように、他の人がそのような拡張機能を作成し、公開リリースすることを促したいと考えています。

最後に、これらのスクリプトのベースとなる初期スクリプトを制作してくれたDorkaに感謝します。

この文書およびここに含まれる情報は、教育目的およびTrellixのお客様の利便性を目的としたコンピュータセキュリティリサーチを記述しています。

※本ページの内容は2023年6月6日(US時間)更新の以下のTrellix Storiesの内容です。
原文:Feeding Gophers to Ghidra

著者:Max Kersten