ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

バイナリであそぼう 〜バイナリエディタの窓からのぞく世界〜

ESM Advent Calendar 2022 15日目の記事です🎅🦌🎄

こんにちは。永和システムマネジメントの内角低め担当、はたけやまです。

毎日お世話になっている実行ファイル、中身は謎のバイナリデータが詰まっていてなんだかよく分からない...。今回はそんなミステリアスな実行ファイルをいじくりまわして遊んでみようと思います。

Hello World

まずは実行ファイルを用意します。以下のC言語のソースファイルを用意して、

// hello.c
#include <stdio.h>

int main() {
  printf("Hello World !!\n");
}

コンパイルして実行します。ハローワールドが元気よく表示されます。

$ gcc hello.c -o hello 
$ ./hello
Hello World !!

この実行ファイルをバイナリエディタで直接編集して、出力される文字列を変更してみます。

まずはバイナリエディタの hexedit を起動します。

$ hexedit hello

hexeditを起動すると 以下のような画面が表示されます。画面の左側がアドレスが表示される「アドレスエリア」、画面中央がバイナリデータを16進数形式で表示する「HEX エリア」画面右側がバイナリデータを ASCII 形式で表示する「ASCII エリア」になります。

起動直後は「HEX エリア」にカーソルが当たっているので、TABキーを押して「ASCII エリア」にカーソルを移動します。

ASCII エリアに移動したら「Ctrl-S」を押して前方検索モードに入り「Hello」と入力してエンターキーを押すと、実行ファイル中の「Hello World !!」が格納されている場所へ移動します。

キーボードのカーソルキー(矢印キー)を使ってカーソルを移動できるので「Hello World !!」を「Hello World ??」に書き換えて、Ctrl-X でセーブして終了します。

それでは、バイナリエディタで書き換えた実行ファイル hello を実行してみましょう。「Hello World ??」が表示されれば成功です。

【おまけ】hexedit の使い方

  • TAB
    • HEX エリアと ASCII エリアを行き来する
  • Ctrl-S
    • 前方検索
  • Ctrl-R
    • 後方検索
  • Ctrl-X
    • セーブして終了
  • Ctrl-C
    • セーブせず終了

return する値を変更してみる

// return255.c
 int main() {
   return 255;
 }

上記のコードをコンパイルして実行すると、main で return したリターンコード「255」が出力されます。

 $ gcc return255.c -o return255
 $ ./return255
 $ echo $?
 255
 $

次は return する値を「255」から「254」へ変更してみましょう。

return で返る値を変更するため、hexedit return255 してみましたが、どこを直せばいいのかさっぱりわかりません...

今度はバイナリエディタの代わりに、objdump コマンドを使って実行ファイルを逆アセンブルしてみます。

 $ objdump -D return255
 
 return255: file format mach-o 64-bit x86-64
 
 
 Disassembly of section __TEXT,__text:
 
 0000000100003fa0 <_main>:
 100003fa0: 55                              pushq   %rbp
 100003fa1: 48 89 e5                        movq    %rsp, %rbp
 100003fa4: c7 45 fc 00 00 00 00            movl    $0, -4(%rbp)
 100003fab: b8 ff 00 00 00                  movl    $255, %eax
 100003fb0: 5d                              popq    %rbp
 100003fb1: c3                              retq

逆アセンブルしたアセンブリコードを眺めてみると、レジスタ「eax」に値「255」をセットしています。ここが怪しそうです。 「movl $255, %eax」を機械語に変換すると「b8 ff 00 00 00」になるのですが、「ff」が「255」を表していそうです。これをバイナリエディタで「ff (255)」から「fe (254)」へ書き換えてみましょう。

書き換えの手順は以下のとおりです。

  • hexedit return255 でバイナリエディタを起動し、
  • Ctrl-S で 「b8 ff 00 00 00」を検索し、
  • 「b8 ff 00 00 00」を「b8 fe 00 00 00」に書き換え、
  • Ctrl-X で保存して終了

書き換えた実行ファイルを実行してみます。「254」が返れば成功です。

 $ ./return255
 $ echo $?
 254
 $

加算→減算

今度は加算を減算に変えてみましょう。

// plus.c
 #include <stdio.h>
 
 int main() {
   int a = 20;
   int b = 10;
   int c = a + b;
   printf("c = %d\n", c);
 }

上記のコードをコンパイルして実行すると、a + b の結果「c = 30」が出力されます。

 $ gcc plus.c -o plus
 $ ./plus
 c = 30

「c = a + b」を「c = a - b」に変更したいので、まずは「c = a + b」の機械語を確認しましょう。先ほど作成した実行ファイル plus を逆アセンブルします。

 $ objdump -D plus
 plus:   file format mach-o 64-bit x86-64
 
 
 Disassembly of section __TEXT,__text:
 
 0000000100003f50 <_main>:
 100003f50: 55                           pushq   %rbp
 100003f51: 48 89 e5                     movq    %rsp, %rbp
 100003f54: 48 83 ec 10                  subq    $16, %rsp
 100003f58: c7 45 fc 14 00 00 00         movl    $20, -4(%rbp)
 100003f5f: c7 45 f8 0a 00 00 00         movl    $10, -8(%rbp)
 100003f66: 8b 45 fc                     movl    -4(%rbp), %eax
 100003f69: 03 45 f8                     addl    -8(%rbp), %eax
 100003f6c: 89 45 f4                     movl    %eax, -12(%rbp)
 100003f6f: 8b 75 f4                     movl    -12(%rbp), %esi
 100003f72: 48 8d 3d 31 00 00 00         leaq    49(%rip), %rdi  # 100003faa <dyld_stub_binder+0x100003faa>
 100003f79: b0 00                        movb    $0, %al
 100003f7b: e8 08 00 00 00               callq   0x100003f88 <dyld_stub_binder+0x100003f88>
 100003f80: 31 c0                        xorl    %eax, %eax
 100003f82: 48 83 c4 10                  addq    $16, %rsp
 100003f86: 5d                           popq    %rbp
 100003f87: c3                           retq

逆アセンブルした結果を見ると「03 45 f8」が加算命令のようです。これを減算命令にすると良さそうです。

減算の命令コードが分からない

加算命令を減算命令に置き換えたいものの、減算命令の命令コードが分かりません...

インテルの命令セットマニュアル( https://pdos.csail.mit.edu/archive/6.097/readings/intelv2.pdf )を読むのもいいですが、今回はC言語のソースから生成した実行ファイルを逆アセンブルして命令コードを確認してみます。

// minus.c
 #include <stdio.h>
 
 int main() {
   int a = 20;
   int b = 10;
   int c = a - b;
   printf("c = %d\n", c);
 }

上記のコードをコンパイル & 逆アセンブルした結果がこちらです。「2b 45 f8」が減算を行う SUB 命令のようです。

 $ gcc minus.c -o minus
 $ objdump -D minus
 minus: file format mach-o 64-bit x86-64
 
 
 Disassembly of section __TEXT,__text:
 
 0000000100003f50 <_main>:
 100003f50: 55                              pushq   %rbp
 100003f51: 48 89 e5                        movq    %rsp, %rbp
 100003f54: 48 83 ec 10                     subq    $16, %rsp
 100003f58: c7 45 fc 14 00 00 00            movl    $20, -4(%rbp)
 100003f5f: c7 45 f8 0a 00 00 00            movl    $10, -8(%rbp)
 100003f66: 8b 45 fc                        movl    -4(%rbp), %eax
 100003f69: 2b 45 f8                        subl    -8(%rbp), %eax
 100003f6c: 89 45 f4                        movl    %eax, -12(%rbp)
 100003f6f: 8b 75 f4                        movl    -12(%rbp), %esi
 100003f72: 48 8d 3d 31 00 00 00            leaq    49(%rip), %rdi  # 100003faa <dyld_stub_binder+0x100003faa>
 100003f79: b0 00                           movb    $0, %al
 100003f7b: e8 08 00 00 00                  callq   0x100003f88 <dyld_stub_binder+0x100003f88>
 100003f80: 31 c0                           xorl    %eax, %eax
 100003f82: 48 83 c4 10                     addq    $16, %rsp
 100003f86: 5d                              popq    %rbp
 100003f87: c3                              retq
 
 (省略)

バイナリエディタを起動して、ADD 命令( 03 45 f8 )を SUB 命令 ( 2b 45 f8 ) に書き換えます。

$ hexedit plus

書き換え完了したら、書き換えたコマンドを実行します。「c = 10」が出力されればOKです。

 $ ./plus 
 c = 10

まとめ

今日は以下のテクニックを使って実行ファイルのバイナリをいじって遊んでみました。

  • hexedit でバイナリ操作
  • objdump -D で逆アセンブル

みなさんもミステリアスなバイナリファイルと仲良くなってみませんか?