💻
バイナリプログラムの解析 振り返り
#school
#tech
2024-2-23
現在の所属先の1つである,筑波大学情報学群情報科学類では卒業したいのであれば3年次に主専攻実験と呼ばれる講義を受講する必要がある.
これはいくつかの実験の中から自分の興味などに合わせて春学期と秋学期に独立した2つを選択する形式になっている.春学期にはカーネルハックを選択したが,秋学期には「バイナリプログラムの解析」を選択し,受講したので感想を記す.
カーネルハックはかなり前[1]から存在する実験であるが履修者が例年少ないという特徴があった.一方バイナリプログラムの解析については,担当教員が筑波大学に赴任したのが数年前ということもあり,開講された年度も少ない[2]うえに履修者もそこまでいるわけではないと思うので,感想を投下しておくことには意味が多少なりともあると考えている.
2023年度の実施内容は講義の個別ページにまとまっている.受講者には,これとは別に各回で内容が詳しく説明されている資料が渡される.
尚,各回における課題について述べている部分では,答えが一意に定まっているようなものの解答はこういった記事に書かないでほしいと担当教員から言われているので,そのような内容は避けることにする.また,こういった事情と書くのが面倒という気持ちから,全ての課題の内容については触れずに,主に個人的にやっていて面白かったものについて述べる.
この講義はバイナリ解析について広く触れる講義であり,原則2週に1回のペースで課題が課されて,それらについてのレポートを提出するというものだった.課題は必須課題と発展課題とに分かれており,発展課題は任意で行う.多分普通に考えてやるだけ加点になると思う.課題間で点数について重みなどが付いているのかは不明.
第1回ということで簡単なガイダンスと,ELFやPE,Mach-Oが持つ構造の概観について扱った.演習では基礎的なバイナリ解析に用いるコマンドを用いて情報を取得したり,実際にプログラムヘッダを解析して表示するツールを作成するなどの課題が存在した.
特に個人的に面白いと思ったのは,バイナリファイルの静的解析結果と,マルウェアか否かが記録されたデータセットであるEMBER dataset[3]に対して,静的解析結果からマルウェアかどうかを予測するプログラムを書くものであった.普通にやると機械学習だなあという気持ちになるので雑にOptunaでチューニングしたLightGBM[4]を投げた.
逆アセンブルと逆コンパイルについて扱った.実際に逆アセンブルや逆コンパイルを行えるツールを触って各ツール間での差異を比較するなども行った.
課題については,特に小間使い的なツールを多く作成する印象を受けた.例えば逆アセンブル結果からCPU命令の出現回数ランキングを作成するツールだったり,バイナリプログラム中の関数呼び出しにおける依存関係を可視化するツール[5]を作るなどした.
マルウェアを解析できるWebサービスについて扱った.例えばVirusTotalやHybrid Analysisといったサービスにおいて実際にマルウェアの性質を調べるなどの課題があった.
適当なマルウェアを1つ選んで解析してみるという課題では,SNAKEという,ある日本企業の内部に被害を齎したと考えられているマルウェアが面白そうだったので選んだ.これはファイアウォールの設定を弄るという特徴を持っており,any.runで動かしてみると楽しい.あとGolang製PEなのでタイムスタンプがUNIX Timeで0.
パッカーについて扱った.
パッカーというのは,バイナリプログラムの実行時に見える動作を変えることはないが,実行されるプログラムが圧縮されており,実行時にこれをプログラムが自分で解凍して実行する(自己解凍)ことにより,ファイルサイズを小さくしたり,静的解析に強くしたりする目的を持つソフトウェア.しばしばマルウェアにも用いられる.
演習では著名なパッカーの1つであるUPXで遊んだり,適当に選んだパッカーが用いているアルゴリズムをコードリーディングして説明したりした.
シグネチャマッチングと難読化がテーマ.
演習ではClamAVのCLIツールであるclamscanのクローン版を作ったり,簡単なobfuscatorやdeobfuscatorを作ったりした.このときに作ったx86_64のELFに対するobfuscatorについては,ここに書いてある.
動的解析について扱った.
straceやltraceを用いて既存のプログラムから面白い性質を発見するという課題では,何個かのプログラムについて挙げたのだが,中でもtokeiはrayonクレートを使っているからcloneしていそうだなの気持ちになってstraceすると実際clone3
を発行していて面白かった.
あと簡単なCTFが3問あった.年賀状かと思った.
ファジングとコードカバレッジがテーマだった.
ファジングというのはプログラムに様々な入力を与えて脆弱性などの欠陥が無いかを探索するテスト手法.コードカバレッジは対象となるプログラムのうち,一般に検査できたコード部分のことを指す.
適当なコードカバレッジツールを用いて簡単なプログラムに対してカバレッジを測定してみようみたいな課題が存在したので,「簡単なプログラムかあ~」となって,気が狂ってそのままRubyでWhitespaceのインタプリタを書いて,SimpleCovで測定した.
あと簡単なファザーを作ろうという課題があったが,どの程度のクオリティーを求められているのかがよく分からない.
解析回避と環境fingerprintingについて扱った.
サンドボックス検出ツールを実際の環境で用いてみて実環境と解析環境間の差異を見たり,簡単なサンドボックス,デバッガ,仮想マシン,コンテナなどで動作しているかを判定するツールを作ったりした.
各回で課される課題とは別に,実験を通して得た知識を使ったり使わなかったりして,バイナリプログラムの解析に関するツールを実装して発表し,レポートを書くことが求められる.とはいえ直接バイナリプログラムに関係している必要は無くて,講義で扱った各テーマの延長線上で何かやることが求められている.他の人間だと例えばPythonでPythonコード難読化をやっていたり,自作straceをやっていたりした.
まずはテーマ案を出せと言われたタイミングがあったので,雑に次の3つからどれかを選ぶ方針でいるというのを投げた:
- Linuxカーネルをよりデバッグしやすくするデバッガの実装
- ELFに対するタイムトラベルデバッガの実装
- ブラックボックス最適化技術を組み込んだファザーの実装
一番目は,例えばeBPFなどで遊べる[6]と面白そう,二番目は,当時FireDBGが発表されたばかりであり,TTDについて知ったので面白そう,三番目は,ファジングが面白そうで,大課題として作成するのにちょうど良さそうな実装量をしていそうという理由で投げた.
投げた結果,教員からは「どれでも良いんちゃう?」的なことを言われたのでRustをガリガリ書けそうな二番目を選択することにした.
通常のデバッガは,バイナリファイルを処理フローの通りにしか辿ることができない.すなわち,過去の任意の状態に立ち返って,そこからの実行を再現できない.これがどのようなときに困るかというのを,次のような簡単なC言語のプログラムを用いて考えてみる:
#include <stdio.h>
#include <stdlib.h>
void foo(int *val) { *val = 1; }
int main() {
int x = 0;
foo(&x);
if (x == 0) {
puts("zero");
} else {
exit(-1);
}
}
これは途中で変数x
の値が変更されているプログラムであるが,当然これをコンパイルしたものを実行するとexit(-1);
で異常終了する.これをどうにかしたい(原因を探りたい,動作を変えたいなど)気持ちがあって,手元にこれをコンパイルしたバイナリだけがある場合,gdb等のデバッガを用いて解析するのが一般的であろう.
こうした解析を行う際には,このケースではどのタイミングで変数x
が書き変わったのかを知りたい.もちろん,この程度のプログラムであればデバッガを使えばほぼ自明だし,逆アセンブルしたものを睨むだけでも事足りるかもしれない.
しかし現実に解析対象となるプログラムは往々にしてもっと複雑だし,実行時に必ず同じ現象が発生するとは限らない.そういったときに既に発生した現象まで戻りたくなる気持ちがある.
こういった際に,ファイル操作などの副作用が途中で生じなければ過去の地点におけるメモリとレジスタの値を保存しておいて,これを復元することでその地点に立ち返って同じ現象を再現できる.これがタイムトラベルデバッグの基本的な考え方であり,一部のデバッガについてはこういった機能が実装され始めている.この観点で様々なデバッガについて眺めてみると,
- gdb:rrというプラグインを併用することでタイムトラベルデバッグを行える.がgdbはApple Siliconをサポートしていない.
- FireDBG:先に述べた.Rust製のRustバイナリに対するタイムトラベルデバッガなので,汎用的な使途は無さそう.
- 他数多のデバッガ:タイムトラベルできない
といった感じである.このため汎用的に用いることのできる可能性があるタイムトラベルデバッガを作成することに意味はありそうという気持ちになったため作った.この実装などについてまでここで触れていると分量が大変長くなりそうなので割愛する.
発表してから一週間で全てを書く必要があった.まあ実装をもっと前に終わらせていれば話は別だが...
なんやかんやで思想などをダンプしていたらA4で25ページくらいになったのでまあええ感じの分量やねとなってそのまま提出した.思想を出したのは,主にRust周りについての話で,担当教員がRustをそこまで知らないらしいので書いたというのが大きい.
今年度に関しては発表で用いたスライドをそのまま投げても良いという条件が存在したので,何人かはそうしたらしい.例年あるかは不明.
今年度の講義受講者は,自分を含めて6人(上限)いたが,自分以外の受講者5人の内,3人が自分と知り合いであった.このため,個人的には後述するDiscordサーバーも含めて大変やりやすかったが,知り合いがいないと,完全オンラインで自分のペースに合わせて進行するという形式なので,万一詰まった場合には辛そうだなと思う.このことは講義の感想として教員に投げてあるので来年以降これを取り入れてくれるかもしれないし,何も無いかもしれない.
また,主専攻実験は,落とすと即留年であるというのもあってか,殊この実験については担当教員が毎回の履修者たちの進捗を見て,適宜必須課題の量を調整しているように思えた(というか調整していると言っていた)[7].このため,必須課題もこなせるかどうか不安であるような人についてもそこまで心配はいらないと考える.
また,発展課題まで含めると自分でガリガリプログラムを書く課題が多めなので,手を動かしたい人でテーマに興味がある人間にはおすすめできると思う.
春学期に受講した「カーネルハック」では,受講者全員の知り合い関係におけるホップ数のmaxが2であったので,開始直後から仲良くなり,Discordサーバー[8]が作られていた.ここで進捗を共有したり雑談をするなどの活動が行われており[9],有意義であると感じたので,この実験においても作成した.
基本的に知り合いである履修者を入れ,外部で興味を持った人間は,カーネルハックのサーバーと同様,"モグリ"というロールを付けて権限管理を行った.
モグリ・ロール
ここでは各回の詰まりどころなどを解消する様子が観測された.また,大課題の実装などを行う時期には,勝手に一人までしか入れないVCチャンネルである「味集中カウンター」を設置した[10].たまに一人で入っては喚いていた.やはり一人でも言語化すると見えてくるものがあると感じる.
プログラムの味に集中する
実験ページのURLを睨み,年度が書かれている部分を書き換えると過年度のページに飛ぶことができるから調べたところ,少なくとも2003年からやっている ↩︎
主専攻実験のページについてWayback Machineでアーカイブを見ると,多分2023年度の講義で4年目 ↩︎
Pythonという言語はエコシステムがカスなので,このEMBER datasetを手元で構築する際に,Python3.9以下を求められる ↩︎
最初はRandomForestとか使えばええやんとなったが,よく考えたらscikit-learnはGPU対応に渋い顔をしていることを思い出した.今でも実験的な対応しかしていないし,う~んという気持ち ↩︎
普通に
f: printf, fopen
みたいなノリで出力を作れば良いと言われたがすぐできて面白くなかったのでMermaid記法で吐き出せるオプションを付けた ↩︎とはいえeBPFって4096命令以上のコードは拒否されるので遊ぶのは難しそう ↩︎
まあ別に必須課題から外れたものは発展課題とすれば良いだけで,多分そうなっているので,教員とこなせる人間からすると何も変わらないし,こなせない人間からすると助かるというのでwin-winっぽい. ↩︎
このサーバー名は,「対xx決戦サーバ」である(xxには担当教員の通称が入っている).当初は"サーバー"であったが,B2の頃に履修した専門英語基礎の教員が"サーバーではなくサーバです"と繰り返し言っていたのでこれを取り入れた. ↩︎
現在でも雑談をするサーバーとして運用されている ↩︎