9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【バイナリ解析】実行ファイルの挙動を変えてみよう

Last updated at Posted at 2023-12-03

はじめに

この記事ではUbuntu上で生成した実行ファイルを逆アセンブルします。
ついでに実行ファイルを直接修正して挙動を変えます。

環境: Ubuntu 23.04

サンプルコード1

Hello world!を5回出力するプログラムです。

sample1.c
#include <stdio.h>

int main(void) {
    for(int i = 0; i < 5; i++) {
        printf("Hello world!\n");
    }
    return 0;
}
$ gcc sample1.c -o sample1
$ ./sample1 
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!

この実行ファイルをobjdumpコマンドで逆アセンブルします。
main関数の部分は以下の通りです。

$ objdump -d -M intel sample1
...
0000000000001149 <main>:
    1149:	f3 0f 1e fa          	endbr64
    114d:	55                   	push   rbp
    114e:	48 89 e5             	mov    rbp,rsp
    1151:	48 83 ec 10          	sub    rsp,0x10
    1155:	c7 45 fc 00 00 00 00 	mov    DWORD PTR [rbp-0x4],0x0
    115c:	eb 13                	jmp    1171 <main+0x28>
    115e:	48 8d 05 9f 0e 00 00 	lea    rax,[rip+0xe9f]        # 2004 <_IO_stdin_used+0x4>
    1165:	48 89 c7             	mov    rdi,rax
    1168:	e8 e3 fe ff ff       	call   1050 <puts@plt>
    116d:	83 45 fc 01          	add    DWORD PTR [rbp-0x4],0x1
    1171:	83 7d fc 04          	cmp    DWORD PTR [rbp-0x4],0x4
    1175:	7e e7                	jle    115e <main+0x15>
    1177:	b8 00 00 00 00       	mov    eax,0x0
    117c:	c9                   	leave
    117d:	c3                   	ret
...

出力する回数を変える

まず出力する回数を変えます。
ループカウンタに1加算して条件式を評価しているは以下の部分です。

    116d:	83 45 fc 01          	add    DWORD PTR [rbp-0x4],0x1
    1171:	83 7d fc 04          	cmp    DWORD PTR [rbp-0x4],0x4
    1175:	7e e7                	jle    115e <main+0x15>

rbp-0x4番地の値に1加算してその値が4以下なら文字列出力処理へジャンプしています。
0x1171番地の比較命令83 7d fc 0404が終了条件のリテラル値の様です。
この値を変更してループ回数が変化するかを確認します。

ddコマンドを使って実行ファイルを書き換えます。

$ echo -en '\x09' | dd of=sample1 bs=1 seek=$((0x1174)) count=1 conv=notrunc
1+0 records in
1+0 records out
1 byte copied, 0.000391533 s, 2.6 kB/s
$ objdump -d -M intel sample1 | grep 1171:
    1171:	83 7d fc 09          	cmp    DWORD PTR [rbp-0x4],0x9

リテラル値が0x4から0x9に変わっています。
修正した実行ファイルを動かします。

$ ./sample1
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!

文字列が10回出力されるようになりました。

出力する文字列を変える

次に文字列出力をしている部分を読みます。

    115e:	48 8d 05 9f 0e 00 00 	lea    rax,[rip+0xe9f]        # 2004 <_IO_stdin_used+0x4>
    1165:	48 89 c7             	mov    rdi,rax
    1168:	e8 e3 fe ff ff       	call   1050 <puts@plt>

rdiレジスタにrip + 0xe9fのアドレスを代入してputs関数を呼び出しています。
ripレジスタには次の命令のアドレスが格納されているのでrip + 0xe9f
0x1165 + 0xe9f = 0x2004となります。

実行ファイルの0x2004番地のデータをhexdumpコマンドで確認します。

$ hexdump -C sample1 | grep 00002000
00002000  01 00 02 00 48 65 6c 6c  6f 20 77 6f 72 6c 64 21  |....Hello world!|

ここには画面に出力される文字列がありました。
world!の部分をbinaryに書き換えて実行します。

$ echo -en '\x62\x69\x6e\x61\x72\x79' | dd of=sample1 bs=1 seek=$((0x200a)) count=6 conv=notrunc
6+0 records in
6+0 records out
6 bytes copied, 0.00053478 s, 11.2 kB/s
$ hexdump -C sample1 | grep 00002000
00002000  01 00 02 00 48 65 6c 6c  6f 20 62 69 6e 61 72 79  |....Hello binary|
$ ./sample1
Hello binary
Hello binary
Hello binary
Hello binary
Hello binary
Hello binary
Hello binary
Hello binary
Hello binary
Hello binary

下記のオフセット値0xe9fも修正します。
文字列のアドレスを6バイトずらしてみます。

    115e:	48 8d 05 9f 0e 00 00 	lea    rax,[rip+0xe9f]        # 2004 
$ echo -en '\xa5' | dd of=sample1 bs=1 seek=$((0x1161)) count=1 conv=notrunc
1+0 records in
1+0 records out
1 byte copied, 0.000426931 s, 2.3 kB/s$ objdump -d -M intel sample1 | grep 115e:
    115e:	48 8d 05 a5 0e 00 00 	lea    rax,[rip+0xea5]        # 200a <_IO_stdin_used+0xa>
$ ./sample1
binary
binary
binary
binary
binary
binary
binary
binary
binary
binary

文字列が6文字ずれて出力されました。

サンプルコード2

8192分の1の確率でSucessが、それ以外の場合はFailureが出力されるプログラムです。

sample2.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));
    
    if (rand() % 8192 != 0) {
        printf("Failure\n");
        return 1;
    }
    printf("Success\n");
    return 0;
}

強運がないとSuccessを出力させるのは難しそうです。。

$ gcc sample2.c -o sample2
$ ./sample2
Failure
$ ./sample2
Failure
$ ./sample2
Failure
$ ./sample2
Failure
$ ./sample2
Failure

常にSuccessを出力させる

このプログラムを逆アセンブルすると以下の様に出力されます。

$ objdump -d -M intel sample2
...
00000000000011a9 <main>:
    11a9:	f3 0f 1e fa          	endbr64
    11ad:	55                   	push   rbp
    11ae:	48 89 e5             	mov    rbp,rsp
    11b1:	bf 00 00 00 00       	mov    edi,0x0
    11b6:	e8 e5 fe ff ff       	call   10a0 <time@plt>
    11bb:	89 c7                	mov    edi,eax
    11bd:	e8 ce fe ff ff       	call   1090 <srand@plt>
    11c2:	e8 e9 fe ff ff       	call   10b0 <rand@plt>
    11c7:	25 ff 1f 00 00       	and    eax,0x1fff
    11cc:	85 c0                	test   eax,eax
    11ce:	74 16                	je     11e6 <main+0x3d>
    11d0:	48 8d 05 2d 0e 00 00 	lea    rax,[rip+0xe2d]        # 2004 <_IO_stdin_used+0x4>
    11d7:	48 89 c7             	mov    rdi,rax
    11da:	e8 a1 fe ff ff       	call   1080 <puts@plt>
    11df:	b8 01 00 00 00       	mov    eax,0x1
    11e4:	eb 14                	jmp    11fa <main+0x51>
    11e6:	48 8d 05 1f 0e 00 00 	lea    rax,[rip+0xe1f]        # 200c <_IO_stdin_used+0xc>
    11ed:	48 89 c7             	mov    rdi,rax
    11f0:	e8 8b fe ff ff       	call   1080 <puts@plt>
    11f5:	b8 00 00 00 00       	mov    eax,0x0
    11fa:	5d                   	pop    rbp
    11fb:	c3                   	ret
...

乱数が0かどうかを確認しているのは以下の部分です。

    11c2:	e8 e9 fe ff ff       	call   10b0 <rand@plt>
    11c7:	25 ff 1f 00 00       	and    eax,0x1fff
    11cc:	85 c0                	test   eax,eax
    11ce:	74 16                	je     11e6 <main+0x3d>

0x11ce番地のje命令をjmp命令に書き換えます。
これで乱数に関わらず常にSuccessの出力処理を行うようになります。

$ echo -en '\xeb' | dd of=sample2 bs=1 seek=$((0x11ce)) count=1 conv=notrunc
1+0 records in
1+0 records out
1 byte copied, 0.000151262 s, 6.6 kB/s
$ objdump -d -M intel sample2 | grep 11ce:
    11ce:	eb 16                	jmp    11e6 <main+0x3d>
$ ./sample2
Success
$ ./sample2
Success
$ ./sample2
Success
$ ./sample2
Success
$ ./sample2
Success

おわりに

実行ファイルを解析して直接修正するのは面白かったです。
アセンブリ言語を読むのは得意ではないので、徐々に慣れていきたいです。

参考文献

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?