簡単なバイナリの書き換え

デバッグ方法を説明した内容は数多く見かけますが、バイナリの書き換え方についてまとまったものがなかったので簡単にまとめてみます。ただ、実用性があるかは微妙です。

C言語で確認します。以下のコードで Cygwin を使ってコンパイルを行います。


hello.c

                                                                • -

#include
int main(void) {
int a=1;
if ( a != 1 ){
printf("Hello, World\n");
}
}

                                                                • -

これをコンパイルして実行します。当然何も出力されません。

$ gcc -o hello hello.c
$ ./hello

今回はバイナリ上で if 文を書き換え Hello World を出力させます。そのために先ず、hello のアプリケーションを objdump で逆アセンブルします。

$ objdump -d hello >> hello.assemble

main 関数の逆アセンブルについては以下になります。

                                                                          • -

00401050 <_main>:
401050: 55 push %ebp
401051: 89 e5 mov %esp,%ebp
401053: 83 ec 18 sub $0x18,%esp
401056: 83 e4 f0 and $0xfffffff0,%esp
401059: b8 00 00 00 00 mov $0x0,%eax
40105e: 83 c0 0f add $0xf,%eax
401061: 83 c0 0f add $0xf,%eax
401064: c1 e8 04 shr $0x4,%eax
401067: c1 e0 04 shl $0x4,%eax
40106a: 89 45 f8 mov %eax,-0x8(%ebp)
40106d: 8b 45 f8 mov -0x8(%ebp),%eax
401070: e8 23 00 00 00 call 401098 <___chkstk>
401075: e8 ae 00 00 00 call 401128 <___main>
40107a: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
401081: 83 7d fc 01 cmpl $0x1,-0x4(%ebp)
401085: 74 0c je 401093 <_main+0x43>
401087: c7 04 24 00 20 40 00 movl $0x402000,(%esp)
40108e: e8 a5 00 00 00 call 401138 <_printf>
401093: c9 leave
401094: c3 ret
401095: 90 nop
401096: 90 nop
401097: 90 nop

                                                                          • -

少し取得したアセンブルについて説明します。簡単なソースなので主な処理は以下の箇所のみです。

                                                                          • -

40107a: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
401081: 83 7d fc 01 cmpl $0x1,-0x4(%ebp)
401085: 74 0c je 401093 <_main+0x43>
401087: c7 04 24 00 20 40 00 movl $0x402000,(%esp)
40108e: e8 a5 00 00 00 call 401138 <_printf>
401093: c9 leave

                                                                          • -

40107a movl では、ebp レジスタに 0x1 の値を代入します。
401081 cmpl では、0x1 と ebp レジスタの値を比較します。
401085 je では、401081 の cmpl の比較が == ならば 401093 に飛び、!= ならば処理を継続します。
401093 は、プログラムの終了です。


今回のソースコードと逆アセンブルを比較して a に 1 を代入しているため、ここに該当するアセンブラは、40107a となります。また if 文で判定を行っている箇所は、401085 です。つまり、401085 の je を jne などに変更できれば Hello World は出力されるようになります。

バイナリで該当箇所を確認します。(さくっと変更するには bvi が便利。以下が該当箇所になります。
* objdump の結果に出力されているバイナリの値で検索かけるのが楽。

                                                                          • -

00000480 00 83 7D FC 01 74 0C C7 04 24 00 20 40 00 E8 A5
cmpl --> ^^^^^^^^^^^ ^^^^^ <-- je

                                                                          • -


今回は if の書き換えなので je を jne に書き換えます。そのためには je の 74 0c を 75 0c にします。以下が書き換えた後の objdump

                                                                          • -

40107a: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
401081: 83 7d fc 01 cmpl $0x1,-0x4(%ebp)
401085: 75 0c jne 401093 <_main+0x43> <--- ★
401087: c7 04 24 00 20 40 00 movl $0x402000,(%esp)
40108e: e8 a5 00 00 00 call 401138 <_printf>

                                                                          • -

jne となっています。これで cmpl の比較の結果が != ならば leave に飛ぶように変更できました。つまり、ソースコードでいうと以下のように変更したということになります。

                                                                • -

#include
int main(void) {
int a=1;
if ( a == 1 ){ <----------- ★
printf("Hello, World\n");
}
}

                                                                • -

これで hello を動作させると Hello World が出力されます。

$ ./hello
Hello, World

できました。コンパイルを必要とする言語でデバッグするには、バイナリやアセンブラについても把握しているとかなり有利だと思うので引き続きアセンブラについて調べていきたいです。