お詫び
執筆中インフルエンザにり患してダウンしていたため、書ききれませんでした。
アドカレ中には書き切りますので温かい目で見守っていただけますと幸いです。
はじめに
普段はVR企業でバックエンドエンジニアをやっています。
弊社では今までバックエンドのAPIを主にRuby/Railsで実装していたのですが、最近ではGoで実装することが増えてきております。
そこで今回はGoの内部実装の理解を進めるため、サイバーセキュリティでは有名なGhidraでGoの実行ファイルを逆アセンブルしてみます。
Ghidraとは
Ghidra
リバースエンジニアリングツールです。
アメリカのNSA(国家安全保障局)が開発したもので、2019年にOSSとして公開されました。
開発自体は2000年前半から始まっていて、十数年の時を経て誰でも利用できるようになりました。
今回はこのGhidra最新バージョン 10.4
にてGoバージョン対応が入り、1.17
~ 1.20
がサポートされるようになりました。
そこで今回はGoの 1.20
で構造体を表現したソースコードをコンパイルしてバイナリコード(=実行ファイル)を作成したのち、リバースエンジニアリングで元の構造体に復元できるか見てみたいと思います。
なぜ逆アセンブル、デコンパイルするのか
以前私がCTFに参加していたときの記事があります。
このCTFというのは簡単に言えばハッキングコンテストで、いろいろなセキュリティのジャンルの問題を解いて点数を競う競技です。
このジャンルには reversing
というものもあり、問題として実行ファイルが与えられて、表層解析や静的解析、動的解析などの手法を駆使して、必要な情報を奪取します。
そしてその中でも静的解析に利用されるのが逆アセンブルやデコンパイルという方法になります。
つまり0と1で表現されているだけの実行ファイルからそれがどのようなことをするものなのか分析できるようになることを目的とします。
実行ファイルならそのまま実行すればいいじゃないかと思うかもしれませんが、そのファイルに悪意のあるコードが含まれている場合、実行環境がウイルス等に汚染される可能性があります。
例えば作成元が不明な実行ファイルや、インターネットで不特定多数の人が入手できる実行ファイルなどは当然のこと、実行ファイルである以上、リスクはつきものです。
そのため、事前にどういうファイルなのかをファイルを実行することなく調査、解析できることにはとても意義があります。
なぜGoなのか
Goはクロスプラットフォーム対応で、各種OSの実行ファイルを同じソースファイルから作成することができます。
マルウェア開発者は少ない労力で各プラットフォーム向けの実行ファイルを作成することができるのでGoによるマルウェアが増えてきています。
そのためバイナリ解析者はGoによるマルウェア解析ができることが今後より期待されます。
Goの実行ファイルの特徴
- String reference to "Go Build ID:"
- Tons of references to "vendor/golang.org" in strings
- PDB path references to "/go/src/"
- Symbol table names starting with "main.", "runtime.", "os."
- URLs referencing "go.org" or "golang.org"
- OSX binaries referencing Go Github repository
- Has section names like .gosymtab, .gopcnltab, .go.buildinfo
事前準備
では早速バイナリ解析をするための準備を始めます。
Ghidraのホスト環境
- Windows 11 Pro
- バージョン:21H2
- OSビルド:22000.2538
- プロセッサ:Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz 2.11 GHz
- 実装RAM:16.0 GB (15.7 GB 使用可能)
- システムの種類:64 ビット オペレーティング システム、x64 ベース プロセッサ
Go build環境
- Linux(WSL2)
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
- Go
$ go version
go version go1.21.1 linux/amd64
OpenJDKインストール
リンク先のバージョンは 17
ですが、たぶん最新でもいいと思います。
JAVA_HOME
と Path
にzipを展開したフォルダのルートとbinフォルダを指定します。
Ghidraインストール
以下からzipをダウンロードして展開します。
ghidraRun.bat
を実行します。
goの実行ファイルをインポートする
今回利用するGoの実行ファイルは以下となります(事前にbuildしています)。
$ file hello_world
hello_world: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=njE5p-SYvX81Coej0Ddq/MXkw_O-x2LoHvERX2vcL/b60xbcwo3xlyP-JDfLsN/8OUi4FSpBEys1yPjYGKa, with debug_info, not stripped
重要なのは静的リンクであり、ストリップされていないという部分です。
静的リンクとはライブラリが外部リンクではなくファイル内にハードコードされているという意味で、実際に呼ばれるライブラリの関数名が明示的に書かれていないため解析の難易度を高めます。
ストリップとは解析のヒントにつながるシンボル情報が残っているかどうかを指していて、 not stripped
はシンボル情報が残っている状態を意味しています。
シンボル情報はあればあるほど解析の難易度は下がります。
ちなみに unpacked
っぽそうです。
実行ファイルは linuxでの実行ファイルである ELF
という形式になっています。
CPUアーキテクチャは 64-bit
です。
少し遠回りになりますが、まずはGhidraに何も設定をしていない状態でGoの実行ファイルをインポートします。
警告が出ますが、アクセスを許可します。
----- Loading /C:/Users/USER/Downloads/hello_world -----
Skipping section [.gosymtab] with invalid size 0x0
Unsupported symbol name has been escaped: "type:.eq.struct { runtime.gList; runtime.n int32 }"
Error creating symbol: type:.eq.struct { runtime.gList; runtime.n int32 } - Symbol name contains invalid characters: type:.eq.struct { runtime.gList; runtime.n int32 }
Unsupported symbol name has been escaped: "type:.eq.sync/atomic.Pointer[interface {}]"
Error creating symbol: type:.eq.sync/atomic.Pointer[interface {}] - Symbol name contains invalid characters: type:.eq.sync/atomic.Pointer[interface {}]
ここでAdditional Informationのセクションを見ると、一部のシンボルが取得できないとあるので、より情報を得るために gotools
をインポートします。
gotools
をインポートする
逆アセンブルする
main.main
を見つける
.gopclntab
で関数名を解決できるようにする
参考にした
Go Basics
- https://qiita.com/t-yama-3/items/1b6e7e816aa07884378e
- https://qiita.com/tutuz/items/455a56ce7ca1131aa513
- https://stackoverflow.com/questions/23789951/easy-to-read-golang-assembly-output
- https://go101.org/article/type-system-overview.html
- https://go.dev/dl/
Ghidra
- https://blogs.trellix.jp/feeding-gophers-to-ghidra
- https://vblocalhost.com/uploads/2021/09/VB2021-04.pdf
- https://github.com/getCUJO/ThreatIntel/blob/master/Research_materials/Golang_reversing/hacktivity2020.pdf
- https://cujo.com/blog/reverse-engineering-go-binaries-with-ghidra/
- https://cujo.com/blog/reverse-engineering-go-binaries-with-ghidra-part-2-type-extraction-windows-pe-files-and-golang-versions/
Reversing Tips
- https://jsac.jpcert.or.jp/archive/2023/pdf/JSAC2023_2_1_kuwabara_jp.pdf
- https://engineers.ffri.jp/entry/2022/04/11/141131
- https://engineers.ffri.jp/entry/2023/02/09/100318
Assembly Language
OSS(ghidra Extensions)
- https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension
- https://github.com/goretk/redress
- https://github.com/felberj/gotools