ソフトウェアのソースコードにアクセスできないが、ソフトウェアがどのように実装されているかを理解し、その脆弱性を見つけ、さらにはバグを修正できると想像してみてください。これらはすべてバイナリ形式です。超能力を持っているようですね。
あなたもそのような超能力を持つことができ、GNUバイナリユーティリティ(binutils)は良い出発点です。 GNU binutilsは、すべてのLinuxディストリビューションにデフォルトでインストールされるバイナリツールのコレクションです。
バイナリ分析は、コンピュータ業界で最も過小評価されているスキルです。これは主にマルウェアアナリスト、リバースエンジニア、および
低レベルのソフトウェアに取り組んでいる人々によって利用されています。
この記事では、binutilsで利用できるツールのいくつかについて説明します。私はRHELを使用していますが、これらの例はすべてのLinuxディストリビューションで実行する必要があります。
[~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.6 (Maipo)
[~]#
[~]# uname -r
3.10.0-957.el7.x86_64
[~]#
一部のパッケージ化コマンド( rpm など)に注意してください )Debianベースのディストリビューションでは利用できない可能性があるため、同等の dpkgを使用してください 該当する場合はコマンド。
オープンソースの世界では、私たちの多くはソース形式のソフトウェアに焦点を合わせています。ソフトウェアのソースコードがすぐに利用できるようになったら、ソースコードのコピーを入手し、お気に入りのエディタを開いて、コーヒーを飲み、探索を開始するのは簡単です。
ただし、ソースコードはCPUで実行されるものではありません。 CPUで実行されるのはバイナリまたは機械語の命令です。バイナリファイルまたは実行可能ファイルは、ソースコードをコンパイルするときに取得するものです。デバッグに熟練した人は、この違いを理解することで優位に立つことがよくあります。
binutilsパッケージ自体を掘り下げる前に、コンパイルの基本を理解しておくことをお勧めします。
コンパイルとは、プログラムを特定のプログラミング言語(C / C ++)のソースまたはテキスト形式からマシンコードに変換するプロセスです。
マシンコードは、CPU(または一般にハードウェア)によって理解される1と0のシーケンスであるため、CPUによって実行または実行できます。このマシンコードは、実行可能ファイルまたはバイナリファイルと呼ばれることが多い特定の形式のファイルに保存されます。 Linux(およびLinuxバイナリ互換性を使用する場合はBSD)では、これはELF(Executable and Linkable Format)と呼ばれます。
コンパイルプロセスは、特定のソースファイルの実行可能ファイルまたはバイナリファイルを表示する前に、一連の複雑な手順を実行します。例として、このソースプログラム(Cコード)を考えてみましょう。お気に入りのエディターを開き、次のプログラムを入力します:
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
ステップ1:cppによる前処理
Cプリプロセッサ( cpp )は、すべてのマクロを展開し、ヘッダーファイルをインクルードするために使用されます。この例では、ヘッダーファイル stdio.h ソースコードに含まれます。 stdio.h printfに関する情報を含むヘッダーファイルです プログラム内で使用される関数。 cpp ソースコードで実行され、結果の命令は hello.iというファイルに保存されます 。テキストエディタでファイルを開いて、その内容を確認します。 hello worldを印刷するためのソースコード ファイルの一番下にあります。
[testdir]# cat hello.c
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
[testdir]#
[testdir]# cpp hello.c > hello.i
[testdir]#
[testdir]# ls -lrt
total 24
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
[testdir]#
ステップ2:gccを使用したコンパイル
これは、ステップ1で前処理されたソースコードが、オブジェクトファイルを作成せずにアセンブリ言語命令に変換される段階です。 GNUコンパイラコレクション( gcc )を使用します )。 gccを実行した後 - Sを使用したコマンド hello.iのオプション ファイルの場合、 hello.sという名前の新しいファイルが作成されます 。このファイルには、Cプログラムのアセンブリ言語命令が含まれています。
任意のエディターまたは猫を使用してコンテンツを表示できます コマンド。
[testdir]#
[testdir]# gcc -Wall -S hello.i
[testdir]#
[testdir]# ls -l
total 28
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
[testdir]# cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
.section .note.GNU-stack,"",@progbits
[testdir]#
ステップ3:asでアセンブル
アセンブラの目的は、アセンブリ言語命令を機械語コードに変換し、 .oを持つオブジェクトファイルを生成することです。 拡大。 GNUアセンブラをとして使用します これは、すべてのLinuxプラットフォームでデフォルトで使用できます。
[testdir]# as hello.s -o hello.o
[testdir]#
[testdir]# ls -l
total 32
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
これで、ELF形式の最初のファイルができました。ただし、まだ実行できません。後で、オブジェクトファイルの違いがわかります および実行可能ファイル 。
[testdir]# file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
ステップ4:ldとのリンク
これは、オブジェクトファイルをリンクして実行可能ファイルを作成するコンパイルの最終段階です。実行可能ファイルには通常、システムライブラリ( libc )に由来することが多い外部関数が必要です。 。
ldを使用してリンカーを直接呼び出すことができます 指図;ただし、このコマンドはやや複雑です。代わりに、 gccを使用できます -vを使用するコンパイラ リンクがどのように発生するかを理解するための(詳細)フラグ。 ( ldを使用 リンクするためのコマンドは、探索するために残された演習です。)
[testdir]# gcc -v hello.o
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man [...] --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:[...]:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu [...]/../../../../lib64/crtn.o
[testdir]#
このコマンドを実行すると、 a.outという名前の実行可能ファイルが表示されます。 :
[testdir]# ls -l
total 44
-rwxr-xr-x. 1 root root 8440 Sep 13 03:45 a.out
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
ファイルの実行 a.outのコマンド は、それが実際にELF実行可能ファイルであることを示しています:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=48e4c11901d54d4bf1b6e3826baf18215e4255e5, not stripped
実行可能ファイルを実行して、ソースコードの指示どおりに実行されるかどうかを確認します。
[testdir]# ./a.out
Hello World
します! Hello World を印刷するためだけに、舞台裏で多くのことが起こります 画面上。より複雑なプログラムで何が起こるか想像してみてください。
binutilsツールを探索する
この演習は、binutilsパッケージに含まれているツールを利用するための良い背景を提供しました。私のシステムにはbinutilsバージョン2.27-34があります。 Linuxディストリビューションによってはバージョンが異なる場合があります。
[~]# rpm -qa | grep binutils
binutils-2.27-34.base.el7.x86_64
次のツールがbinutilsパッケージで利用できます:
[~]# rpm -ql binutils-2.27-34.base.el7.x86_64 | grep bin/
/usr/bin/addr2line
/usr/bin/ar
/usr/bin/as
/usr/bin/c++filt
/usr/bin/dwp
/usr/bin/elfedit
/usr/bin/gprof
/usr/bin/ld
/usr/bin/ld.bfd
/usr/bin/ld.gold
/usr/bin/nm
/usr/bin/objcopy
/usr/bin/objdump
/usr/bin/ranlib
/usr/bin/readelf
/usr/bin/size
/usr/bin/strings
/usr/bin/strip
上記のコンパイル演習では、これらのツールのうち2つをすでに検討しました。 as コマンドはアセンブラとして使用され、 ld コマンドがリンカーとして使用されました。上で太字で強調表示されている他の7つのGNUbinutilsパッケージツールについて学ぶために読んでください。
readelf:ELFファイルに関する情報を表示します
上記の演習では、オブジェクトファイルという用語について説明しました。 および実行可能ファイル 。その演習のファイルを使用して、 readelfと入力します -hを使用する (ヘッダー)ファイルのELFヘッダーを画面にダンプするオプション。 .oで終わるオブジェクトファイルに注意してください 拡張子はタイプ:REL(再配置可能ファイル)として表示されます :
[testdir]# readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 [...]
[...]
Type: REL (Relocatable file)
[...]
このファイルを実行しようとすると、実行できないというエラーが表示されます。これは単に、CPUで実行するために必要な情報がまだないことを意味します。
xを追加する必要があることを忘れないでください または実行可能ビット 最初にchmodを使用してオブジェクトファイルを作成します コマンドを実行しないと、アクセスが拒否されましたが表示されます エラー。
[testdir]# ./hello.o
bash: ./hello.o: Permission denied
[testdir]# chmod +x ./hello.o
[testdir]#
[testdir]# ./hello.o
bash: ./hello.o: cannot execute binary file
a.outで同じコマンドを試した場合 ファイルの場合、そのタイプが EXEC(実行可能ファイル)であることがわかります。 。
[testdir]# readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
[...]
Type: EXEC (Executable file)
前に見たように、このファイルはCPUによって直接実行できます:
[testdir]# ./a.out
Hello World
readelf コマンドは、バイナリに関する豊富な情報を提供します。ここでは、ELF64ビット形式であることが示されています。つまり、64ビットCPUでのみ実行でき、32ビットCPUでは機能しません。また、X86-64(Intel / AMD)アーキテクチャで実行されることを意図していることも示しています。バイナリへのエントリポイントはアドレス0x400430にあります。これは、メインのアドレスです。 Cソースプログラム内で機能します。
readelfをお試しください ls など、知っている他のシステムバイナリに対するコマンド 。出力に注意してください(特にタイプ: )セキュリティ上の理由から位置独立実行可能ファイル(PIE)の変更が行われたため、RHEL8またはFedora30以降のシステムでは異なる場合があります。
[testdir]# 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)
システムライブラリについて学ぶ ls コマンドはlddの使用に依存しています 次のようにコマンド:
[testdir]# ldd /bin/ls
linux-vdso.so.1 => (0x00007ffd7d746000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f060daca000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f060d8c5000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007f060d6bc000)
libc.so.6 => /lib64/libc.so.6 (0x00007f060d2ef000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f060d08d000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f060ce89000)
/lib64/ld-linux-x86-64.so.2 (0x00007f060dcf1000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f060cc84000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f060ca68000)
readelfを実行します libc ライブラリファイルを使用して、ファイルの種類を確認します。ご指摘のとおり、これは DYN(共有オブジェクトファイル)です。 、つまり、単独で直接実行することはできません。ライブラリによって利用可能になった関数を内部的に使用する実行可能ファイルで使用する必要があります。
[testdir]# readelf -h /lib64/libc.so.6
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: DYN (Shared object file)
サイズ:セクションサイズと合計サイズを一覧表示します
サイズ コマンドはオブジェクトファイルと実行可能ファイルでのみ機能するため、単純なASCIIファイルで実行しようとすると、ファイル形式が認識されないというエラーがスローされます。 。
[testdir]# echo "test" > file1
[testdir]# cat file1
test
[testdir]# file file1
file1: ASCII text
[testdir]# size file1
size: file1: File format not recognized
次に、サイズを実行します オブジェクトファイル および実行可能ファイル 上記の演習から。実行可能ファイル( a.out )には、オブジェクトファイル( hello.o )よりもかなり多くの情報があります )、sizeコマンドの出力に基づく:
[testdir]# size hello.o
text data bss dec hex filename
89 0 0 89 59 hello.o
[testdir]# size a.out
text data bss dec hex filename
1194 540 4 1738 6ca a.out
しかし、テキストは何をしますか 、データ 、および bss セクションの意味は?
テキスト セクションは、すべての実行可能命令を含むバイナリのコードセクションを参照します。 データ セクションは、初期化されたすべてのデータがある場所であり、 bss 初期化されていないすべてのデータが保存される場所です。
サイズを比較する 他の利用可能なシステムバイナリのいくつかと一緒に。
lsの場合 コマンド:
[testdir]# size /bin/ls
text data bss dec hex filename
103119 4768 3360 111247 1b28f /bin/ls
あなたはそのgccを見ることができます およびgdb lsよりもはるかに大きなプログラムです サイズの出力を見るだけで コマンド:
[testdir]# size /bin/gcc
text data bss dec hex filename
755549 8464 81856 845869 ce82d /bin/gcc
[testdir]# size /bin/gdb
text data bss dec hex filename
6650433 90842 152280 6893555 692ff3 /bin/gdb
文字列:印刷可能な文字の文字列をファイルに印刷します
-dを追加すると便利なことがよくあります 文字列にフラグを立てる データセクションの印刷可能な文字のみを表示するコマンド。
hello.o テキストを印刷するための指示を含むオブジェクトファイルですHelloWorld 。したがって、文字列からの唯一の出力 コマンドはHelloWorld 。
[testdir]# strings -d hello.o
Hello World
文字列の実行 a.out 一方、(実行可能ファイル)は、リンクフェーズ中にバイナリに含まれていた追加情報を示します。
[testdir]# strings -d a.out
/lib64/ld-linux-x86-64.so.2
!^BU
libc.so.6
puts
__libc_start_main
__gmon_start__
GLIBC_2.2.5
UH-0
UH-0
=(
[]A\A]A^A_
Hello World
;*3$"
コンパイルは、ソースコード命令をマシンコードに変換するプロセスであることを思い出してください。機械語は1と0のみで構成されており、人間が読むのは困難です。したがって、マシンコードをアセンブリ言語の命令として提示すると便利です。アセンブリ言語はどのように見えますか?アセンブリ言語はアーキテクチャ固有であることを忘れないでください。 Intelまたはx86-64アーキテクチャを使用しているため、同じプログラムをコンパイルするためにARMアーキテクチャを使用している場合は手順が異なります。
objdump:オブジェクトファイルからの情報を表示します
バイナリから機械語命令をダンプできる別のbinutilsツールは、 objdumpと呼ばれます。 。
-dを使用します オプション。バイナリからすべてのアセンブリ命令を分解します。
[testdir]# objdump -d hello.o
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov $0x0,%edi
9: e8 00 00 00 00 callq e
e: b8 00 00 00 00 mov $0x0,%eax
13: 5d pop %rbp
14: c3 retq
この出力は最初は恐ろしいように見えますが、先に進む前に少し時間を取って理解してください。 .textを思い出してください セクションには、すべてのマシンコードの説明があります。組み立て手順は4列目にあります(つまり、プッシュ 、 mov 、 callq 、ポップ 、 retq )。これらの命令は、CPUに組み込まれているメモリ位置であるレジスタに作用します。この例のレジスタはrbpです。 、 rsp 、 edi 、 eax 、など、各レジスタには特別な意味があります。
次に、 objdumpを実行します 実行可能ファイル( a.out )そしてあなたが得るものを見てください。 objdumpの出力 実行可能ファイルは大きくなる可能性があるため、メインに絞り込みました。 grepを使用して機能する コマンド:
[testdir]# objdump -d a.out | grep -A 9 main\>
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
400521: bf d0 05 40 00 mov $0x4005d0,%edi
400526: e8 d5 fe ff ff callq 400400
40052b: b8 00 00 00 00 mov $0x0,%eax
400530: 5d pop %rbp
400531: c3 retq
手順はオブジェクトファイルhello.oに似ていることに注意してください。 、ただし、いくつかの追加情報が含まれています:
- オブジェクトファイルhello.o 次の命令があります:
callq e
- 実行可能ファイルa.out アドレスと関数を含む次の命令で構成されます:
callq 400400 <puts@plt>
上記のアセンブリ命令は、 putsを呼び出しています。 働き。 printfを使用したことを忘れないでください ソースコードで機能します。コンパイラはputsへの呼び出しを挿入しました Hello Worldを出力するライブラリ関数 画面に。
putsの上の行の説明を見てください :
- オブジェクトファイルhello.o movの指示があります :
mov $0x0,%edi
- 命令mov 実行可能ファイルの場合a.out 実際のアドレスがあります( $ 0x4005d0 ) $ 0x0の代わりに :
mov $0x4005d0,%edi
この命令は、アドレス $ 0x4005d0に存在するものをすべて移動します ediという名前のレジスタへのバイナリ内 。
その記憶場所の内容には他に何がありますか?はい、あなたはそれを正しく推測しました:それはテキスト Hello、Worldに他なりません 。どうすれば確信できますか?
readelf コマンドを使用すると、バイナリファイルの任意のセクションをダンプできます( a.out )画面に。以下は、 .rodataをダンプするように要求します 、読み取り専用データであり、画面に表示されます:
[testdir]# readelf -x .rodata a.out
Hex dump of section '.rodata':
0x004005c0 01000200 00000000 00000000 00000000 ....
0x004005d0 48656c6c 6f20576f 726c6400 Hello World.
Hello Worldというテキストが表示されます 右側にアドレス、左側にバイナリのアドレスがあります。 movで見たアドレスと一致しますか 上記の指示?はい、そうです。
strip:オブジェクトファイルからシンボルを破棄します
このコマンドは、顧客に出荷する前にバイナリのサイズを縮小するためによく使用されます。
重要な情報がバイナリから削除されるため、デバッグのプロセスが妨げられることに注意してください。それでも、バイナリは問題なく実行されます。
a.outで実行します 実行可能ファイルと何が起こるかに注意してください。まず、バイナリが削除されていないことを確認します 次のコマンドを実行します:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] not stripped
また、ストリップを実行する前に、元々バイナリに含まれていたバイト数を追跡します。 コマンド:
[testdir]# du -b a.out
8440 a.out
次に、ストリップを実行します 実行可能ファイルでコマンドを実行し、ファイルを使用して実行可能ファイルが機能することを確認します コマンド:
[testdir]# strip a.out
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] stripped
バイナリを削除した後、そのサイズは 6296に減少しました 以前の8440から この小さなプログラムのバイト。小さなプログラムでこれだけの節約ができるので、大きなプログラムが頻繁に削除されるのも不思議ではありません。
[testdir]# du -b a.out
6296 a.out
addr2line:アドレスをファイル名と行番号に変換します
addr2line ツールは、バイナリファイル内のアドレスを検索し、それらをCソースコードプログラム内の行と照合するだけです。かなりかっこいいですね。
このための別のテストプログラムを作成します。今回のみ、 -gを使用してコンパイルしてください。 gccのフラグ 、バイナリの追加のデバッグ情報を追加し、行番号(ここのソースコードで提供)を含めることもできます:
[testdir]# cat -n atest.c
1 #include <stdio.h>
2
3 int globalvar = 100;
4
5 int function1(void)
6 {
7 printf("Within function1\n");
8 return 0;
9 }
10
11 int function2(void)
12 {
13 printf("Within function2\n");
14 return 0;
15 }
16
17 int main(void)
18 {
19 function1();
20 function2();
21 printf("Within main\n");
22 return 0;
23 }
-gでコンパイルします フラグを立てて実行します。ここに驚きはありません:
[testdir]# gcc -g atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
ここで、 objdumpを使用します 関数が始まるメモリアドレスを特定します。 grepを使用できます 必要な特定の行を除外するコマンド。関数のアドレスは以下で強調表示されています:
[testdir]# objdump -d a.out | grep -A 2 -E 'main>:|function1>:|function2>:'
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
--
0000000000400532 :
400532: 55 push %rbp
400533: 48 89 e5 mov %rsp,%rbp
--
0000000000400547 :
400547: 55 push %rbp
400548: 48 89 e5 mov %rsp,%rbp
次に、 addr2lineを使用します これらのアドレスをバイナリからマップして、Cソースコードのアドレスと一致させるツール:
[testdir]# addr2line -e a.out 40051d
/tmp/testdir/atest.c:6
[testdir]#
[testdir]# addr2line -e a.out 400532
/tmp/testdir/atest.c:12
[testdir]#
[testdir]# addr2line -e a.out 400547
/tmp/testdir/atest.c:18
40051d ソースファイルatest.cの6行目から始まります 、これは開始中括弧( { ) function1の場合 開始します。 function2の出力を一致させます およびメイン 。
nm:オブジェクトファイルからシンボルを一覧表示します
上記のCプログラムを使用して、 nmをテストします 道具。 gccを使用してすばやくコンパイルします 実行します。
[testdir]# gcc atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
次に、 nmを実行します およびgrep 関数と変数についての情報:
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
000000000040051d T function1
0000000000400532 T function2
000000000060102c D globalvar
U __libc_start_main@@GLIBC_2.2.5
0000000000400547 T main
関数がTとマークされていることがわかります 、テキストの記号を表します セクション、変数は Dとしてマークされています 、初期化されたデータのシンボルを表します セクション。
ソースコードがないバイナリでこのコマンドを実行するとどれほど便利か想像してみてください。これにより、内部を覗いて、どの関数と変数が使用されているかを理解できます。もちろん、バイナリが削除されていない限り、その場合はシンボルが含まれていないため、 nm ここに表示されているように、コマンドはあまり役に立ちません:
[testdir]# strip a.out
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
nm: a.out: no symbols
Conclusion
The GNU binutils tools offer many options for anyone interested in analyzing binaries, and this has only been a glimpse of what they can do for you. Read the man pages for each tool to understand more about them and how to use them.