LoginSignup
26
33

独学リバースエンジニアリング バイナリファイルの解析

Last updated at Posted at 2022-04-19

はじめに

バイナリファイルの解析はアプリケーションやファームウェアのデバッグ、マルウェアの解析等目的に応じて手段となるツールが異なります。

本記事はバイナリファイルの解析を行うために必要となる基本的な知識や、リバースエンジニアリングツールの概要について記載しています。

リバースエンジニアリングツールはIDA Proなど有料のプロダクトがありますが、本記事ではフリーで利用可能なツールに着目しています。

バイナリファイルとは

コンピュータが処理する情報(データ)は基本的にテキストファイルと、バイナリファイルに大別できます。

テキストファイルはテキストエディタなどを使用して何らかの文字コードでエンコードされたテキストファイルのことです。テキストファイルに書かれた内容は人間にしか理解できません。

バイナリファイルは2進数の0と1が並んだビット列で表現されたファイルです。画像、動画、音声、実行形式のアプリケーションファイル等、テキストファイル以外は全てバイナリファイルに区別できます。

バイナリファイル.png

バイナリファイルの解析

バイナリファイルの操作コマンド

バイナリファイルを操作するための手段として、OS毎にバイナルファイルの操作コマンドが用意されいます。

本記事ではLinuxを例に、デバッグなどの調査で使われることが多いコマンドについて一部解説します。

objdump

高級言語であるC言語で記述されたソースコードだけでは、プログラムとして実行することができません。何故ならば、コンピュータは機械語(マシン語)1しか理解できないためです。

コンピュータが命令の記述されたプログラムを実行するためには、CPUが命令を理解できるように機械語である必要があります。そのため、命令を記述したソースコードはコンパイラを用いてコンパイルを行ない、機械語に翻訳します。従って、コンパイル実行後、ソースコードはCPUが理解できるオブジェクトコードの形式に変換されます。

また、オブジェクトコードだけではプログラムが実行可能な状態ではないため、更にオブジェクトコードに対して、必要なライブラリ群をリンクさせることにより、Windowsで言うexeファイルなど実行可能なプログラム形式に変換されます。

objdumpコマンドはオブジェクトファイルの情報を出力するためのコマンドです。
ここでは、下記のサンプルプログラムがどの様にコンパイルされいてるか確認します。
このソースコードはHello, worldを10回出力するだけの単純なプログラムです。

#include <stdio.h>

int main()
{
    int i;
    for (i = 0;i < 10; i++)
    {
        printf("Hello, world\n");
    }
    return 0;
}

Linux環境で上記ソースコードをGCC2でコンパイルすると、a.outファイルが生成されます。
なお、出力後のファイル名については-o 任意のファイル名で変更することができます。

a.outフォーマットの形式は2019年にLinus Torvaldsよりa.out: remove core dumping supportでサポート廃止のアナウンスが行われている通りに、現在は非推奨になっています。本記事ではバイナリの学習目的のため題材として取り扱っています。

以下のコマンドを実行すると、バイナリファイルの中身を表示します。また、以下の例ではオプションを付けて、Intelのシンタックス形式で見やすいように出力しています。

$ objdump -M intel -D a.out | grep -A20 main.:

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 10                	jmp    116e <main+0x25>
    115e:	48 8d 3d 9f 0e 00 00 	lea    rdi,[rip+0xe9f]        # 2004 <_IO_stdin_used+0x4>
    1165:	e8 e6 fe ff ff       	call   1050 <puts@plt>
    116a:	83 45 fc 01          	add    DWORD PTR [rbp-0x4],0x1
    116e:	83 7d fc 09          	cmp    DWORD PTR [rbp-0x4],0x9
    1172:	7e ea                	jle    115e <main+0x15>
    1174:	b8 00 00 00 00       	mov    eax,0x0
    1179:	c9                   	leave  
    117a:	c3                   	ret    
    117b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]

0000000000001180 <__libc_csu_init>:
    1180:	f3 0f 1e fa          	endbr64 
    1184:	41 57                	push   r15
    1186:	4c 8d 3d 2b 2c 00 00 	lea    r15,[rip+0x2c2b]        # 3db8 <__frame_dummy_init_array_entry>

見方として左から、メモリ空間の位置情報を表すアドレス、16進数のバイト郡がCPUに対する機械語の命令、右側にアセンブリ言語で表記した命令が出力されます。

メモリ空間から指定されたアドレスのデータを読み込むためには、アドレスの指定が必要になります。CPUはメモリアドレスを指定しながら逐次、命令を処理します。なお、アドレス空間の大きさはCPUアーキテクチャによって異なります。

16進数のバイト郡では人間から見て分かりにくいため、右側のアセンブリ言語で表記した命令が出力されています。そのため、アセンブリ言語もCPUアーキテクチャによって異なります。

gdb

CPUはデータの読み書きなどの処理を行うために、一時的な記憶領域としてレジスタを使用します。また、レジスタは特別な機能を持たない汎用レジスタ専用レジスタなどが存在し、それぞれ役割が異なります。

レジスタの構成を理解することは、CPUがどの様に命令を実行しているかを理解するのに役立ちます。GNU開発ツールであるGDBデバッガはレジスタの内容を表示することができます。

以下のコマンドを実行すると、main()関数に対してブレークポイントを設定し、main()関数が実行される直前で処理が停止します。また、run実行後、レジスタの情報を出力します。

$ gdb -q ./a.out

ubuntu@ubuntu:~$ gdb -q ./a.out
Reading symbols from ./a.out...
(gdb) break main
Breakpoint 1 at 0x1149: file firstprog.c, line 4.
(gdb) run
Starting program: /home/ubuntu/a.out 

Breakpoint 1, main () at firstprog.c:4
4	{
(gdb) info registers
rax            0x555555555149      93824992235849
rbx            0x555555555180      93824992235904
rcx            0x555555555180      93824992235904
rdx            0x7fffffffe528      140737488348456
rsi            0x7fffffffe518      140737488348440
rdi            0x1                 1
rbp            0x0                 0x0
rsp            0x7fffffffe428      0x7fffffffe428
r8             0x0                 0
r9             0x7ffff7fe0d50      140737354009936
r10            0x0                 0
r11            0x0                 0
r12            0x555555555060      93824992235616
r13            0x7fffffffe510      140737488348432
r14            0x0                 0
r15            0x0                 0
rip            0x555555555149      0x555555555149 <main>
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
(gdb) quit
A debugging session is active.

	Inferior 1 [process 11722] will be killed.

Quit anyway? (y or n) y

次の例では、CPUに対する次に実行する命令(Instruction Pointer)を格納しているレジスタの情報を出力しています。info registerコマンドの引数に、レジスタ名を指定することで実行中のアドレスの情報を表示できます。なお、レジスタ名はCPUのアーキテクチャによって異なります。

(gdb) info register rip
rip            0x555555555149      0x555555555149 <main>

また、コマンドは省略可能なため、次のコマンドは同じ情報を出力します。

(gdb) i r rip
rip            0x555555555149      0x555555555149 <main>

GDBデバッガにはメモリの内容をチェックするためのexamineコマンド(省略形:x)が用意されています。

また、メモリにデータを格納する際にバイトの並べ方として、エンディアンという仕組みが存在します。現在のデバッグツールやコンパイラは自動的に考慮してくれいることが多いですが、メモリを直接操作する場合は、このような仕組みにも注意が必要です。

strings

stringsコマンドはバイナリファイルを指定して実行すると、読み取った文字列を出力します。デフォルトでは、4文字以上連続している文字列を出力します。

  • 実行例
    $ strings <ファイル名>

cmp

cmpコマンドは指定したファイルをバイト単位で比較することができます。用途としてバイナリファイルの比較に利用できます。

  • 実行例
    $ cmp <ファイル名A> <ファイル名B>

ltrace

ltraceコマンドはコマンドラインの診断およびデバッグのツールです。ELFを指定して実行すると、呼び出しされた共有ライブラリーを出力することができます。使用方法はstraceコマンドと良く似ていて、呼び出された動的ライブラリ3を確認することができます。そのため、プログラムのデバッグにも利用できます。

  • 実行例
    $ ltrace <ファイル名>
kali@kali:~$ ltrace ./a.out 
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
puts("Hello, world"Hello, world
)                                      = 13
+++ exited (status 0) +++

上記の場合、Hello, worldを10回出力するために、puts関数が10回呼びされていることが分かります。

readelf

readelfコマンドはELFを指定して実行すると、ELF形式のオブジェクトファイルに関する情報を表示します。

  • 実行例
    $ readelf -a <ファイル名>

リバースエンジニアリングツール

リバースエンジニアリングはハードウェア及び、ソフトウェアの動作を解析するなどして、製品の構造を分析して明らかにすることが目的です。

ソフトウェアの場合はバイナリファイルを基に、人間が理解しやすいアセンブリ言語による変換する逆アセンブルや、開発時に用いられたプログラミング言語に戻して変換するデコンパイルなどの手法が存在します。

Ghidra

GhidraはNSAの研究局によって開発されたリバースエンジニアリングツールです。Ghidraのリポジトリより入手できます。

リポジトリのREADME.mdを参照し、事前にJDK 11 64-bitをインストールます。
また、環境変数のPATHを設定し、既存のjavaが存在し、バージョンが違う場合はインストールしたJDKが存在する場合は先に通すように設定します。

準備完了後、以下のコマンドを実行して起動します。なお、本記事のMac環境では/usr/local/bin/ghidraRunをシンボリックリンクを作成し、/opt/配下に格納したファイルにリンクさせて起動しています。

$ ghidraRun

スクリーンショット 2022-04-14 1.28.21.png

Ghidra起動後、Projectフォルダーを作成し、バイリナファイルを開くことで解析を始めることができます。

スクリーンショット 2022-04-14 1.29.10.png

逆コンパイル実行時にDecompiler: Unable to initialize the DecompilerInterface: Could not find decompiler executableの様なメッセージが出力される場合、逆コンパイルで使用されるコンポーネントが見つからないため、出力されています。原因としてGhidraの初回起動時に、macOSのセキュリティ機構によってブロックされた際に削除した可能性があります。

JADX

JADXはAndroidのApkファイルなどをデコンパイルしてJavaソースコードを生成するためのコマンドラインおよびGUIツールです。下記の手順でビルドして使用します。

  • ソースのダウンロード及びビルド
    $ git clone https://github.com/skylot/jadx
    $ cd jadx/
    $ ./gradlew dist
Downloading https://services.gradle.org/distributions/gradle-7.4.1-bin.zip
...........10%...........20%...........30%...........40%...........50%...........60%...........70%...........80%...........90%...........100%

Welcome to Gradle 7.4.1!

Here are the highlights of this release:
 - Aggregated test and JaCoCo reports
 - Marking additional test source directories as tests in IntelliJ
 - Support for Adoptium JDKs in Java toolchains

For more details see https://docs.gradle.org/7.4.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Configure project :
jadx version: dev
The CreateStartScripts.mainClassName property has been deprecated. This is scheduled to be removed in Gradle 8.0. Please use the mainClass property instead. See https://docs.gradle.org/7.4.1/dsl/org.gradle.jvm.application.tasks.CreateStartScripts.html#org.gradle.jvm.application.tasks.CreateStartScripts:mainClassName for more details.

> Task :jadx-core:compileJava
注意:一部の入力ファイルは推奨されないAPIを使用またはオーバーライドしています。
注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。

> Task :jadx-gui:compileJava
注意:/Users/kenichikato/Documents/Code/GitHub/jadx/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.javaは推奨されないAPIを使用またはオーバーライドしています。
注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。
注意:/Users/kenichikato/Documents/Code/GitHub/jadx/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.javaの操作は、未チェックまたは安全ではありま
BUILD SUCCESSFUL in 2m 23skedオプションを指定して再コンパイルしてください。

ビルド成功後、以下の実行ファイルを実行すると、GUIのツールが起動します。ツール起動後、デコンパイルしたいファイルを選択するとデコンパイルされたJavaのソースコードが表示されます。以下の例ではapkファイルを基にデコンパイルされたソースコードが確認できます。

$ build/jadx/bin/jadx-gui

スクリーンショット 2022-03-30 23.10.17.png

radare2

radare2コマンドはバイナリコードの逆アセンブルやデバッグを行うことができます。
radare2のリポジトリから取得できます。

以下の例では分析と書き込みモードを指定して開いています。

  • 実行例
    $ r2 -A -w <バイナリファイル名>
kali@kali:~$ r2 -A -w ./a.out 
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.

?を入力するとヘルプを表示します。

[0x00001050]> ?
Usage: [.][times][cmd][~grep][@[@iter]addr!size][|>pipe] ; ...   
Append '?' to any char command to get detailed help
Prefix with number to repeat command N times (f.ex: 3x)
| %var=value              alias for 'env' command
| *[?] off[=[0x]value]    pointer read/write data/values (see ?v, wx, wv)
| (macro arg0 arg1)       manage scripting macros
| .[?] [-|(m)|f|!sh|cmd]  Define macro or load r2, cparse or rlang file
| _[?]                    Print last output
| =[?] [cmd]              send/listen for remote commands (rap://, raps://, udp://, http://, <fd>)                                                                                    
| <[...]                  push escaped string into the RCons.readChar buffer
| /[?]                    search for bytes, regexps, patterns, ..
| ![?] [cmd]              run given command as in system(3)
| #[?] !lang [..]         Hashbang to run an rlang script
| a[?]                    analysis commands
| b[?]                    display or change the block size
| c[?] [arg]              compare block with given data
| C[?]                    code metadata (comments, format, hints, ..)
| d[?]                    debugger commands
| e[?] [a[=b]]            list/get/set config evaluable vars
| f[?] [name][sz][at]     add flag at current address
| g[?] [arg]              generate shellcodes with r_egg
| i[?] [file]             get info about opened file from r_bin
| k[?] [sdb-query]        run sdb-query. see k? for help, 'k *', 'k **' ...
| l [filepattern]         list files and directories
| L[?] [-] [plugin]       list, unload load r2 plugins
| m[?]                    mountpoints commands
| o[?] [file] ([offset])  open file at optional address
| p[?] [len]              print current block with format and length
| P[?]                    project management utilities
| q[?] [ret]              quit program with a return value
| r[?] [len]              resize file
| s[?] [addr]             seek to address (also for '0x', '0x1' == 's 0x1')
| t[?]                    types, noreturn, signatures, C parser and more
| T[?] [-] [num|msg]      Text log utility (used to chat, sync, log, ...)
| u[?]                    uname/undo seek/write
| v                       visual mode (v! = panels, vv = fcnview, vV = fcngraph, vVV = callgraph)                                                                                     
| w[?] [str]              multiple write operations
| x[?] [len]              alias for 'px' (print hexadecimal)
| y[?] [len] [[[@]addr    Yank/paste bytes from/to memory
| z[?]                    zignatures management
| ?[??][expr]             Help or evaluate math expression
| ?$?                     show available '$' variables and aliases
| ?@?                     misc help for '@' (seek), '~' (grep) (see ~??)
| ?>?                     output redirection
| ?|?                     help for '|' (pipe)

aflを入力すると、バイナリファイルで用意されている関数のリストを表示します。

[0x00001050]> afl
0x00001050    1 42           entry0
0x00001080    4 41   -> 34   sym.deregister_tm_clones
0x000010b0    4 57   -> 51   sym.register_tm_clones
0x000010f0    5 57   -> 50   entry.fini0
0x00001130    1 5            entry.init0
0x00001000    3 23           sym._init
0x000011d0    1 1            sym.__libc_csu_fini
0x000011d4    1 9            sym._fini
0x00001170    4 93           sym.__libc_csu_init
0x00001135    4 46           main
0x00001030    1 6            sym.imp.puts

s mainを入力し、main関数に移動後、pdfを入力すると逆アセンブルして表示することができます。

[0x00001050]> s main
[0x00001135]> pdf
            ; DATA XREF from entry0 @ 0x106d
┌ 46: int main (int argc, char **argv, char **envp);
│           ; var signed int64_t var_4h @ rbp-0x4
│           0x00001135      55             push rbp
│           0x00001136      4889e5         mov rbp, rsp
│           0x00001139      4883ec10       sub rsp, 0x10
│           0x0000113d      c745fc000000.  mov dword [var_4h], 0
│       ┌─< 0x00001144      eb10           jmp 0x1156
│       │   ; CODE XREF from main @ 0x115a
│      ┌──> 0x00001146      488d3db70e00.  lea rdi, qword str.Hello__world ; 0x2004 ; "Hello, world" ; const char *s
│      ╎│   0x0000114d      e8defeffff     call sym.imp.puts           ; int puts(const char *s)
│      ╎│   0x00001152      8345fc01       add dword [var_4h], 1
│      ╎│   ; CODE XREF from main @ 0x1144
│      ╎└─> 0x00001156      837dfc09       cmp dword [var_4h], 9
│      └──< 0x0000115a      7eea           jle 0x1146
│           0x0000115c      b800000000     mov eax, 0
│           0x00001161      c9             leave
└           0x00001162      c3             ret

vを入力するとVisualモードで表示させることができます。以下はGUI環境で実行している場合の例です。

スクリーンショット 2022-04-14 22.39.12.png

おわりに

リバースエンジニアリングを知ることは世界が広がります。

  1. 書籍やドキュメントの文脈によっては機械語とバイナリは同じ表現で扱われることがある

  2. GNU Compiler Collection

  3. Windowsの場合、DLL

26
33
7

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
26
33