「この世界には、バイナリを理解している人と理解していない人の10種類があります。」
その他のLinuxリソース
- Linuxコマンドのチートシート
- 高度なLinuxコマンドのチートシート
- 無料のオンラインコース:RHELの技術概要
- Linuxネットワーキングのチートシート
- SELinuxチートシート
- Linuxの一般的なコマンドのチートシート
- Linuxコンテナとは何ですか?
- 最新のLinux記事
私たちは毎日バイナリを処理していますが、バイナリについてはほとんど理解していません。バイナリとは、コマンドラインツールから本格的なアプリケーションまで、毎日実行する実行可能ファイルを意味します。
Linuxは、バイナリの分析を簡単にする豊富なツールセットを提供します。 Linuxで作業している場合は、職務が何であれ、これらのツールの基本を理解しておくと、システムをよりよく理解するのに役立ちます。
この記事では、これらのLinuxツールとコマンドの中で最も人気のあるもののいくつかを取り上げます。これらのほとんどは、Linuxディストリビューションの一部としてネイティブに利用できます。そうでない場合は、いつでもパッケージマネージャーを使用してインストールし、探索することができます。覚えておいてください:適切なツールを適切な機会に使用することを学ぶには、十分な忍耐と練習が必要です。
ファイル
内容:ファイルの種類を判断するのに役立ちます。
これがバイナリ分析の開始点になります。私たちは毎日ファイルを処理します。すべてが実行可能タイプであるとは限りません。そこにはさまざまな種類のファイルがあります。始める前に、分析されているファイルのタイプを理解する必要があります。バイナリファイル、ライブラリファイル、ASCIIテキストファイル、ビデオファイル、画像ファイル、PDF、データファイルなどですか?
ファイル コマンドは、処理している正確なファイルタイプを識別するのに役立ちます。
$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=94943a89d17e9d373b2794dcb1f7e38c95b66c86, stripped
$
$ file /etc/passwd
/etc/passwd: ASCII text
$
ldd
機能:共有オブジェクトの依存関係を出力します。
すでにファイルを使用している場合 実行可能バイナリで上記のコマンドを実行すると、出力で「動的にリンクされた」メッセージを見逃すことはできません。どういう意味ですか?
ソフトウェアが開発されているとき、私たちは車輪の再発明をしないように努めています。ほとんどのソフトウェアプログラムに必要な一連の一般的なタスクがあります。たとえば、出力の印刷や標準からの読み取り、ファイルのオープンなどです。これらの一般的なタスクはすべて、書き込みの代わりに誰もが使用できる一連の一般的な機能に抽象化されています。独自のバリアント。これらの一般的な関数は、 libcというライブラリに配置されています またはglibc 。
実行可能ファイルが依存しているライブラリをどのように見つけるのですか?そこでldd コマンドが登場します。動的にリンクされたバイナリに対して実行すると、すべての依存ライブラリとそのパスが表示されます。
$ ldd /bin/ls
linux-vdso.so.1 => (0x00007ffef5ba1000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fea9f854000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007fea9f64f000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007fea9f446000)
libc.so.6 => /lib64/libc.so.6 (0x00007fea9f079000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fea9ee17000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fea9ec13000)
/lib64/ld-linux-x86-64.so.2 (0x00007fea9fa7b000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007fea9ea0e000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fea9e7f2000)
$
ltrace
内容:ライブラリ呼び出しトレーサー。
これで、実行可能プログラムが lddの使用に依存しているライブラリを見つける方法がわかりました。 指図。ただし、ライブラリには何百もの関数を含めることができます。それらの数百のうち、バイナリで使用されている実際の関数はどれですか?
ltrace コマンドは、実行時にライブラリから呼び出されているすべての関数を表示します。次の例では、呼び出されている関数名と、その関数に渡されている引数を確認できます。また、出力の右端にあるこれらの関数によって何が返されたかを確認できます。
$ ltrace ls
__libc_start_main(0x4028c0, 1, 0x7ffd94023b88, 0x412950 <unfinished ...>
strrchr("ls", '/') = nil
setlocale(LC_ALL, "") = "en_US.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
__cxa_atexit(0x40a930, 0, 0, 0x736c6974756572) = 0
isatty(1) = 1
getenv("QUOTING_STYLE") = nil
getenv("COLUMNS") = nil
ioctl(1, 21523, 0x7ffd94023a50) = 0
<< snip >>
fflush(0x7ff7baae61c0) = 0
fclose(0x7ff7baae61c0) = 0
+++ exited (status 0) +++
$
16進ダンプ
機能:ファイルの内容をASCII、10進数、16進数、または8進数で表示します。
多くの場合、そのファイルをどう処理するかを知らないアプリケーションでファイルを開くことがあります。 vimを使用して実行可能ファイルまたはビデオファイルを開いてみてください。表示されるのは、画面に投げられたぎこちないものだけです。
16進ダンプで不明なファイルを開くと、ファイルに何が含まれているかを正確に確認できます。いくつかのコマンドラインオプションを使用して、ファイルに存在するデータのASCII表現を表示するように選択することもできます。これは、それがどのような種類のファイルであるかについての手がかりを与えるのに役立つかもしれません。
$ hexdump -C /bin/ls | head
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 d4 42 40 00 00 00 00 00 |..>......B@.....|
00000020 40 00 00 00 00 00 00 00 f0 c3 01 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1f 00 1e 00 |[email protected]...@.....|
00000040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000050 40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00 |@.@.....@.@.....|
00000060 f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00 |................|
00000070 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 |................|
00000080 38 02 00 00 00 00 00 00 38 02 40 00 00 00 00 00 |8.......8.@.....|
00000090 38 02 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 |8.@.............|
$
文字列
機能:印刷可能な文字列をファイルに印刷します。
16進ダンプがユースケースにとってやややり過ぎのようで、バイナリ内で印刷可能な文字を探しているだけの場合は、文字列を使用できます。 コマンド。
ソフトウェアの開発中は、情報メッセージの印刷、情報のデバッグ、ヘルプメッセージ、エラーなど、さまざまなテキスト/ASCIIメッセージがソフトウェアに追加されます。このすべての情報がバイナリに存在する場合、文字列を使用して画面にダンプされます 。
$ strings /bin/ls
readelf
機能:ELFファイルに関する情報を表示します。
ELF(Executable and Linkable File Format)は、Linuxだけでなく、さまざまなUNIXシステムでも、実行可能ファイルまたはバイナリの主要なファイル形式です。ファイルがELF形式であることを通知するファイルコマンドなどのツールを使用したことがある場合、次の論理的な手順は、 readelfを使用することです。 コマンドと、ファイルをさらに分析するためのさまざまなオプション。
readelfを使用するときに実際のELF仕様のリファレンスを手元に用意する 非常に便利です。仕様はここにあります。
$ readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4042d4
Start of program headers: 64 (bytes into file)
Start of section headers: 115696 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
$
objdump
機能:オブジェクトファイルからの情報を表示します。
バイナリは、当然のことながらコンパイラと呼ばれるツールを使用してコンパイルされるソースコードを作成するときに作成されます。このコンパイラは、ソースコードと同等の機械語命令を生成します。この命令は、CPUによって実行され、特定のタスクを実行できます。この機械語コードは、アセンブリ言語と呼ばれるニーモニックを介して解釈できます。アセンブリ言語は、プログラムによって実行され、最終的にCPUで実行される操作を理解するのに役立つ一連の命令です。
objdump ユーティリティは、バイナリファイルまたは実行可能ファイルを読み取り、アセンブリ言語の指示を画面にダンプします。 objdump の出力を理解するには、アセンブリの知識が重要です。 コマンド。
注意:アセンブリ言語はアーキテクチャ固有です。
$ objdump -d /bin/ls | head
/bin/ls: file format elf64-x86-64
Disassembly of section .init:
0000000000402150 <_init@@Base>:
402150: 48 83 ec 08 sub $0x8,%rsp
402154: 48 8b 05 6d 8e 21 00 mov 0x218e6d(%rip),%rax # 61afc8 <__gmon_start__>
40215b: 48 85 c0 test %rax,%rax
$
strace
機能:システムコールとシグナルをトレースします。
ltraceを使用したことがある場合 、前述のとおり、 straceについて考えてみてください。 似ている。唯一の違いは、ライブラリを呼び出す代わりに、 strace ユーティリティはシステムコールをトレースします。システムコールは、カーネルとインターフェイスして作業を行う方法です。
たとえば、画面に何かを印刷する場合は、 printfを使用します。 またはプット 標準ライブラリの関数libc;ただし、内部的には、最終的には、 writeという名前のシステムコールが使用されます。 実際に画面に何かを印刷するように作られます。
$ strace -f /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 17 vars */]) = 0
brk(NULL) = 0x686000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f967956a000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=40661, ...}) = 0
mmap(NULL, 40661, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9679560000
close(3) = 0
<< snip >>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9679569000
write(1, "R2 RH\n", 7R2 RH
) = 7
close(1) = 0
munmap(0x7f9679569000, 4096) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
$
nm
機能:オブジェクトファイルからシンボルを一覧表示します。
削除されていないバイナリを使用している場合は、 nm コマンドは、コンパイル中にバイナリに埋め込まれた貴重な情報を提供します。 nm バイナリから変数と関数を識別するのに役立ちます。分析対象のバイナリのソースコードにアクセスできない場合、これがどれほど役立つか想像できます。
nmを紹介する 、小さなプログラムをすばやく作成し、 -gでコンパイルします オプションを選択すると、fileコマンドを使用してもバイナリが削除されないこともわかります。
$ cat hello.c
#include <stdio.h>
int main() {
printf("Hello world!");
return 0;
}
$
$ gcc -g hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3de46c8efb98bce4ad525d3328121568ba3d8a5d, not stripped
$
$ ./hello
Hello world!$
$
$ nm hello | tail
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
00000000004005b0 T __libc_csu_fini
0000000000400540 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
000000000040051d T main
U printf@@GLIBC_2.2.5
0000000000400490 t register_tm_clones
0000000000400430 T _start
0000000000601030 D __TMC_END__
$
gdb
内容:GNUデバッガー。
まあ、バイナリのすべてが静的に分析できるわけではありません。 ltrace など、バイナリを実行するいくつかのコマンドを実行しました およびstrace;ただし、ソフトウェアはさまざまな条件で構成されているため、さまざまな代替パスが実行される可能性があります。
これらのパスを分析する唯一の方法は、実行時に任意の場所でプログラムを停止または一時停止し、情報を分析してさらに下に移動できるようにすることです。
ここで、デバッガーが登場します。写真に、そしてLinuxでは gdb 事実上のデバッガーです。プログラムのロード、特定の場所でのブレークポイントの設定、メモリとCPUレジスタの分析など、さまざまなことを行うのに役立ちます。上記の他のツールを補完し、より多くのランタイム分析を実行できるようにします。
注意すべき点の1つは、 gdbを使用してプログラムをロードした後です。 、独自の(gdb)が表示されます 促す。以降のすべてのコマンドは、この gdbで実行されます 終了するまでコマンドプロンプト。
以前にコンパイルした「hello」プログラムを使用し、 gdbを使用します それがどのように機能するかを確認します。
$ gdb -q ./hello
Reading symbols from /home/flash/hello...done.
(gdb) break main
Breakpoint 1 at 0x400521: file hello.c, line 4.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400521 in main at hello.c:4
(gdb) run
Starting program: /home/flash/./hello
Breakpoint 1, main () at hello.c:4
4 printf("Hello world!");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) bt
#0 main () at hello.c:4
(gdb) c
Continuing.
Hello world![Inferior 1 (process 29620) exited normally]
(gdb) q
$
結論
これらのネイティブLinuxバイナリ分析ツールの使用に慣れ、それらが提供する出力を理解したら、radare2などのより高度でプロフェッショナルなオープンソースのバイナリ分析ツールに進むことができます。