はじめに
ソフトウェア解析ツールである IDA を用いて,実行ファイルのアセンブリ命令を書き換えてみます.
簡単なプログラムについて命令の書き換えを行い,プログラムの振る舞いを変えることを目標とします.
実行環境
- IDA Freeware 7.7
- MinGW W64 builds 5.0.0
- Windows 11 23H2
解析対象プログラム
チュートリアルとして以下のプログラムを使用します.
- 「abc」が入力されたら「Hello!」を出力する
- 「abc」以外が入力されたら「wrong」を出力する
#include <stdio.h>
#include <string.h>
int main(void) {
char password[30];
printf("Password: ");
scanf("%s", password);
if (strcmp(password, "abc") == 0) {
printf("Hello!\n");
} else {
printf("wrong\n");
}
return 0;
}
MinGWにバンドルされている gcc を用いて,PE形式の実行ファイルにコンパイルします.
$ gcc main.c -o main.exe
IDAで解析
実行ファイルの読み込み
IDA Freewareを起動し,コンパイルしたプログラムをIDAで読み込むと次のウィンドウが表示されます.
ここでは実行ファイルがPE形式なので,Portable executable for AMD64
が選択されていることを確認して,OKを押します.
マッピングが終了すると,次の画面が表示されます.
ここでは main
関数をターゲットとしたいため,画面左側の Function name
から main
を選びます.
Graph View を確認すると,最上部のブロックから「Hello!」が出力されるブロックと,「wrong」が出力されるブロックに枝分かれされていることがわかります.
枝分かれする直前の命令を見ると,jnz short loc_140001552
が実行されており,これはステータスフラグ上のゼロフラグが0のとき,loc_140001552
(「wrong」が出力されるブロックの先頭アドレス)にジャンプすることを意味します.
入力された値によらずジャンプをさせないため,この命令をnopに書き換えることを考えます.
命令の書き換え
書き換える命令をクリックし,命令が黄色くハイライトされていることを確認します.
Edit → Patch program → Change word...
を実行します.
命令の16進データがリトルエンディアンで表示されるので,これをnop(0x90)に書き換えます.
元の命令が4バイトの大きさであることから,ここでは0x9090(2つ分のnop)に設定します.
書き換えが完了すると,次のグラフに変化します.
グラフから入力値に関わらず必ず「Hello!」に到達することが確認できます.
また,「wrong」が出力されるブロックには到達不能になります.
最後に Edit → Patch program → Apply patches to input file...
を実行し,書き換えた命令を元の実行ファイルに反映します.
書き換えたプログラムを実行
前のセクションで書き換えた実行ファイルを実際に実行してみます.
$ main.exe
Password: a
Hello!
$ main.exe
Password: abc
Hello!
$ main.exe
Password: password
Hello!
入力した値に関係なく,「Hello!」が出力されています.
おわりに
この記事では,ごく簡単なプログラムについて実行ファイルの書き換えを行いました.
今回はIDAを用いて静的解析を行いましたが,実際に動作を確認しながら解析を行いたい場合には,IDAに付属しているデバッガを利用することで動的解析も可能です.
また,今回のような入力によって顕著に分岐するプログラムでは,angr などのシンボリック実行ツールも適しています.
シンボリック実行についても後々記事を書こうと思っていますので,良ければ見ていただけると嬉しいです.