banner
lca

lca

真正的不自由,是在自己的心中设下牢笼。

《ゼロから始めるIDA逆アセンブリ》学習ノート-12(プログラム登録の逆アナリシス)

image

12.1 コマンドライン引数による main 関数の特定#

本章のプログラム TEST_REVERSER.exe は、この演習から静的逆向きとデバッグの新しい知識を学ぶものです。

まずプログラムの構造を見て、IDE で確認します。

image

上の図から、このプログラムは 32 ビットアーキテクチャで、vc++ 2015 でコンパイルされています。

次に、プログラムを実行し、ユーザー名とパスワードの入力を促されます。適当にユーザー名とパスワードを入力すると、bad reverser と表示されます。

image

次に、IDA を開いてターゲットプログラムをロードします。

image

重要な部分を探す方法の一つは、文字列を検索することです。argc、argv などのコマンドライン引数を検索します。C++ で書かれたプログラムなので、関数のプロトタイプは以下の通りです:

int main(int argc, char *argv[])

name から arg などの引数を検索し、ctrl + F で呼び出し検索ボックスを開きます。

image

上の図の_p_argc_をダブルクリックすると、内容は以下の通りです:

image

X キーを押して参照を検索します。

image

上の図のプログラムは_p_argc__p_argv関数を呼び出し、値を main 関数に渡します。

ダブルクリックして main 関数に入ります。

image

main 関数の 3 つの引数

image

main 関数の参照(符号付き)

12.2 main 関数のスタック分析#

任意の関数の引数やローカル変数をダブルクリックして、静的スタックビューに移動します。

image

上の図から、最下部には関数の引数があり、常に戻りアドレス (return address (r)) の下にあります。関数を呼び出す前に、引数はまず push 命令を通じてスタックに渡され、その後戻りアドレスが渡されます。

その上には main 関数を呼び出す上位関数の ebp 値があります。

image

上の図で、main 関数が最初に実行する命令push ebpは、それをスタックに保存し、次に esp の値を ebp に渡し、ebp を下の関数引数と上のローカル変数参照の基準アドレスとして使用します。最後に sub esp,94h、0x94 で esp をローカル変数とバッファのためのスペースを作ります。このプログラムでは移動距離は 0x94 で、コンパイラはソースコードに基づいてローカル変数が占めるスペースを計算します。

esp の値はローカル変数の上に指し、ebp は基準アドレスを指し、基準アドレスの上はローカル変数、下は戻りアドレスと関数引数です。以下の図のようになります。

image

したがって、ebp を基準とする関数内では、前の関数の ebp 値がpush ebpを通じてスタックに保存された後、esp の値が ebp に渡されます。00000000 は基準線として、上のアドレスは負(-)、下のアドレスは正(+)です。

上の図で、var_4 の相対アドレスは - 00000004 で、ebp の値を基準にすると、var_4 の実際のアドレスは ebp-4 です。

逆アセンブルビューで、var_4 を使用している任意の場所を右クリックすると、上記の内容を確認できます。

image

var_4 の上には変数がない空白の領域があり、これはバッファである可能性があります。

image

ビューを上に移動すると、空白の領域の上に最初の変数 Buf が見えます。以下の図のようになります。

image

右クリックして ARRAY を選択すると、以下のウィンドウがポップアップし、配列は 120 個の 1 バイト要素で構成されていることがわかります。したがって、配列のサイズは 120 です。

image

image

関数スタックビュー

上の図は ebp 基準を示しており、mov ebp、esp 命令を指した後、esp が 0x94 減少し、最終的にローカル変数領域の上部を指します。以下の図のように、sub esp、0x94 を実行した後の esp の値です。

image

上の図で、左側の 00000094 は esp=ebp-0x94 を表しており、関数内部で他の関数を呼び出す際、esp はさらに上に移動します。main 関数内部では、main 関数を退出するまで - 0x94 以前のローカル変数に対して操作を行います。

12.3 main 関数のローカル変数#

次に、静的スタックビューからローカル変数を逆向きに分析します。main 関数の引数は既知です。

image

ローカル変数

上の図で、プログラムはある値を読み取り、ebp 上の値と排他的論理和を取り、演算後に var_4 に保存します。これはスタックオーバーフローを防ぐためのものです。

ダブルクリックしてsub_4011B0に入ると、sub_401040関数が見えます。

image

image

sub_401040 関数内部には printf 関数があり、これによりこの関数は文字を印刷するためのものであると判断できます。

image

その後、size 変数に 8 が代入され、参照から 2 つの参照があることがわかりますが、内容を読み取るだけで変更はされていません。

image

image

次に、gets_s 関数があり、gets_s 関数はユーザー入力を制限します。上の図では最大入力が 8 文字であることが示されており、push eax で引数を渡し、lea で変数 buf、つまりバッファのアドレスを取得します。

ユーザーが 8 文字未満を入力して Enter を押すと、関数は入力を中断して戻ります。したがって、Buf バッファには最大 8 文字が含まれます。

その後、プログラムはPUSH EDXを通じてバッファのアドレスをstrlen()という API 関数に引数として渡し、strlen()は Buf 内の文字列の長さを取得し、その結果を var_90 変数に保存します。

12.4 ループとコードブロックのグループ化#

image

上の図で、青い矢印が指す戻りジャンプはループである可能性があり、var_84変数がこのループのカウンタとして使用されます。0x4019f5で条件付きジャンプがあり、条件が満たされるとループが終了します。カウンタは 0 から始まり、var_90変数以上になるまで加算され、ループが終了します。

image

カウンタを 1 加算

カウンタ変数の値が EAX に渡され、EAX が 1 加算された後、再びカウンタ変数に戻されます。

image

上の図で、プログラムはEBP+EDX+BUFからBUFFERの最初のバイトを取得します。EBP+BUFとカウンタを加算し、カウンタは最初は 0 で、ループごとに 1 加算され、次のバイトを読み取ります。このようにして、ループはBUFFERの各バイトの 16 進数をvar_88(初期値0)変数に加算します。

このループの内容は文字の加算です。

image

同じ色にマークし、最下部のコードブロックをドラッグして近づけます。

これらの 3 つのブロックをグループ化することができ、ctrl キーを押しながらタブの上でクリックして、各ブロックを順に選択すると、上の色がシアンに変わります。

image

その後、右クリックしてノードをグループ化します。

image

最終的な効果

image

具体的な内容を見たい場合は、右クリックしてノードのグループ化を解除します。

image

12.5 登録アルゴリズム分析#

ループ内容の下のコード分析を続けます。

image

上の図で、ユーザーにユーザー名とパスワードの入力を求め、下の sub_4011b0 は printf 関数であり、その後 gets_s 関数が呼び出され、ユーザー名とパスワードは同じバッファ Buf と最大文字制限 Size を使用します。

プログラムはすでにユーザー名の各文字の合計を計算しているため、ユーザー名の文字列はもはや使用されず、パスワードは同じバッファを使用できます。

image

次に、atoi 関数が呼び出され、入力された内容が 10 進数に変換され、変数 var_94 に保存されます。これがパスワード変数です。

その後、プログラムは push edx を通じて var_94 パスワード変数を渡し、push eax を通じて var_88 変数を渡し、これら 2 つの変数が 0x401010 関数の引数として渡されます。

image

0x401010 関数内部に入ると、2 つの引数があり、arg_4 はパスワード変数であるべきです。なぜなら、パスワード変数が最初にスタックに渡され、上の arg_0 引数は var_88 変数の値だからです。

では、0x401010 関数はこれら 2 つの引数をどのように使用するのでしょうか?

cmp 比較を行う前に、プログラムはパスワード変数 arg_4 を eax に渡し、shl eax,1 を実行します。

shl は eax 内のビットを左にシフトし、右側の低位を 0 で埋めます。特例として、shl reg,1 は 2 倍に相当します。

したがって、プログラムはパスワード変数を 2 倍にし、arg_0 と比較します。

Python の ord 関数を使用して ASCII に対応する 10 進数値を計算します。

image

pepe をユーザー名として使用すると、pepe 文字の合計は以下の通りです:

image

結果は 0x1aa で、入力されたパスワードは 2 倍にされて 0x1aa と比較されます。したがって、正しいパスワードは 2 倍にした結果が 0x1aa になる必要があります。結果は以下の通りです:

image

結果は 213 です。

この時、プログラムを開いてユーザー名 pepe、パスワード 213 を入力できます。

image

成功メッセージが表示されます。

image

上の図からわかるように、これら 2 つの値が等しくない場合は赤いコードブロックに移動し、0 を返します。等しい場合は緑のコードブロックに移動し、返り値は 1 です。

返り値の役割は何でしょうか?

image

上の図からわかるように、返り値は var_7D 変数に渡されます。

image

上の図からわかるように、返り値が 0 の場合、bad reverser にジャンプし、1 の場合は good reverser にジャンプします。


この章では、登録を回避するための逆向き分析の方法について主に説明しました。主な内容には、関数スタック、ローカル変数、登録アルゴリズム分析などが含まれます。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。