バイナリファイルの解析、調査に使えるコマンドメモ
下記のコマンド実行例の環境はこちら
[rso@localhost log]$ cat /etc/issue
CentOS release 6.3 (Final)
Kernel \r on an \m
バイナリファイルの読み書き
ファイルタイプを調べたい
file
コマンドで簡単に調べられる。
[rso@localhost log]$ file /bin/ls /bin/sh /var/log/messages /dev/sda /dev/tty11 /tmp /tmp/test.pl /tmp/hoge
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
/bin/sh: symbolic link to `bash'
/var/log/messages: ASCII English text
/dev/sda: block special
/dev/tty11: character special
/tmp: sticky directory
/tmp/test.pl: ASCII text
/tmp/hoge: data
バイナリファイルに含まれる文字列を抽出したい。
strings
で可能。バイナリファイルの文字列を抜き出すには一番手っ取り早い。
[rso@localhost log]$ strings /var/log/wtmp | head
reboot
2.6.32-279.el6.x86_64
runlevel
2.6.32-279.el6.x86_64
tty1
LOGIN
tty2
LOGIN
tty3
LOGIN
ちなみに、対象となるバイナリファイルがオブジェクトファイル(ELFファイル)の場合、
初期化・ロードされたセクション内の文字列のみ抽出する。ファイル全体から抽出したい場合、
strings -a
を使う。
バイナリファイルを16進ダンプしたい
od
で可能。
[rso@localhost log]$ od -Ax -tx1z /var/log/wtmp |head
000000 02 00 00 00 00 00 00 00 7e 00 00 00 00 00 00 00 >........~.......<
000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
000020 00 00 00 00 00 00 00 00 7e 7e 00 00 72 65 62 6f >........~~..rebo<
000030 6f 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ot..............<
000040 00 00 00 00 00 00 00 00 00 00 00 00 32 2e 36 2e >............2.6.<
000050 33 32 2d 35 37 33 2e 33 2e 31 2e 65 6c 36 2e 78 >32-573.3.1.el6.x<
000060 38 36 5f 36 34 00 00 00 00 00 00 00 00 00 00 00 >86_64...........<
000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
*
000150 00 00 00 00 ba 75 ff 55 84 37 09 00 00 00 00 00 >.....u.U.7......<```
hexdump
でも可能。どちらを使うかは好き好きで。
個人的にはレトロな環境ではhexdump
が使えないところもあり、od
のほうがどこでも使える感じがする。
[rso@localhost log]$ hexdump -C /var/log/wtmp | head
00000000 02 00 00 00 00 00 00 00 7e 00 00 00 00 00 00 00 |........~.......|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 7e 7e 00 00 72 65 62 6f |........~~..rebo|
00000030 6f 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |ot..............|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 32 2e 36 2e |............2.6.|
00000050 33 32 2d 32 37 39 2e 65 6c 36 2e 78 38 36 5f 36 |32-279.el6.x86_6|
00000060 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |4...............|
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000150 00 00 00 00 33 8d fc 4f 34 e5 01 00 00 00 00 00 |....3..O4.......|
また、objdump
でも可能。
こちらは後述のELFファイルを扱う時に使用する。
[rso@localhost log]$ objdump -s -b binary /var/log/wtmp | head
/var/log/wtmp: file format binary
Contents of section .data:
00000 02000000 00000000 7e000000 00000000 ........~.......
00010 00000000 00000000 00000000 00000000 ................
00020 00000000 00000000 7e7e0000 7265626f ........~~..rebo
00030 6f740000 00000000 00000000 00000000 ot..............
00040 00000000 00000000 00000000 322e362e ............2.6.
00050 33322d32 37392e65 6c362e78 38365f36 32-279.el6.x86_6
バイナリファイルを編集したい
vim
でバイナリモードで開いた後、コマンド%!xxd
でバイナリ表示にできる。
編集が終わったら、:%!xxd -r
で戻す。
[rso@localhost log]$ vim -b binfile
(省略)
:%!xxd
(16進表示になる。編集する)
:%!xxd -r
(元に戻る)
:wq
また、vim(xxd)
が入ってないような前時代的なマシンで作業している場合や、vim
で開くまでもない、開くと重過ぎる、という場合にはdd
でも以下のように編集はできる。
[rso@localhost log]$ hexdump binfile2 -C
00000000 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 |bbbbbbbbbbbbbbbb|
00000010 62 62 62 62 62 62 62 62 62 62 0a |bbbbbbbbbb.|
[rso@localhost log]$ echo -en '\x5A\x5A' | dd of=binfile2 bs=1 se
ek=16 conv=notrunc
2+0 records in
2+0 records out
2 bytes (2 B) copied, 6.0608e-05 s, 33.0 kB/s
[rso@localhost log]$ hexdump binfile2 -C
00000000 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 |bbbbbbbbbbbbbbbb|
00000010 5a 5a 62 62 62 62 62 62 62 62 0a |ZZbbbbbbbb.|
ELFファイル入門
ELF (Exectable and Linking Format) とは、Linux 上で実行可能なプログラムやそれにリンクされる共有ライブラリなどのバイナリファイルのファイル形式。
ELFヘッダの表示
以下のような簡単なHello worldバイナリを対象とする。
[rso@localhost log]$ cat main.c
#include <stdio.h>
int main(){
printf("hello, world\n");
}
[rso@localhost log]$ gcc main.c
readelf
コマンドを使用する。
[rso@localhost log]$ 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
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: 0x4003e0
Start of program headers: 64 (bytes into file)
Start of section headers: 2472 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 27
64bitバイナリ、エンディアンなどが記載されている。
プログラムヘッダの表示
[rso@localhost log]$ readelf -l a.out
(省略)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001c0 0x00000000000001c0 R E 8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000068c 0x000000000000068c R E 200000
LOAD 0x0000000000000690 0x0000000000600690 0x0000000000600690
0x00000000000001ec 0x0000000000000200 RW 200000
(省略)
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
(省略)
各セグメントの開始オフセットのメモリ読み書きに関する権限、セグメント内に含まれるセクションの情報が表示される。
例えば、セクション.rodata は権限RE(読み込み、実行)のLOADセグメント内にあることが分かる。
セクションヘッダの表示
[rso@localhost log]$ cat main.c
#include <stdio.h>
int main(){
printf("hello, world\n");
}
[rso@localhost log]$ gcc main.c
[rso@localhost log]$ readelf -S a.out
There are 30 section headers, starting at offset 0x9a8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
(省略)
[13] .text PROGBITS 00000000004003e0 000003e0
00000000000001d8 0000000000000000 AX 0 0 16
(省略)
[15] .rodata PROGBITS 00000000004005c8 000005c8
000000000000001d 0000000000000000 A 0 0 8
(省略)
[24] .data PROGBITS 0000000000600878 00000878
0000000000000004 0000000000000000 WA 0 0 4
(省略)
各セクションヘッダの種類と位置が表示される。Offset表示部分がプログラムの先頭からの距離で、セクションヘッダの種類をいくつかメモしておく。
-
.text
・・・テキストセグメント。実行プログラムが配置されている領域 -
.rodata
・・・読み込み専用の定数値等が配置されている領域 -
.data
・・・変更可能な初期化済みデータの配置されている領域
試しに .rodata セグメントの内容をダンプしてみる。
[rso@localhost log]$ hexdump a.out -s 0x5c8 -n $((0x01d)) -C
000005c8 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000005d8 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 00 |hello, world.|
上記のプログラムの文字列定数が含まれていることがわかる。
セクション名が分かっていてそれを抜き出したいときは、objdump
でも可能。
[rso@localhost log]$ objdump -s -j .rodata a.out
a.out: file format elf64-x86-64
Contents of section .rodata:
4005c8 01000200 00000000 00000000 00000000 ................
4005d8 68656c6c 6f2c2077 6f726c64 00 hello, world.
ちなみに、strings
コマンドでも以下のように調べられる。
[rso@localhost log]$ strings -tx a.out
200 /lib64/ld-linux-x86-64.so.2
(省略)
5d8 hello, world
ELFファイルからシンボルを表示させる。
シンボルとは、関数、変数、アドレスに紐付けられた識別子のようなもの。
プログラムの実行時やリンク時に使用される。
nm
コマンドを使用すれば、シンボルを表示できる。
[rso@localhost ~]$ gcc -o main -c main.o
[rso@localhost ~]$ nm main.o
0000000000000000 T main
U puts
シンボルの種類についてはここでは詳しく書かないが、Uは未定義のシンボル。
実行可能なファイルに対しても使用できる。
[rso@localhost ~]$ nm a.out
00000000006006b8 d _DYNAMIC
(省略)
00000000004004c4 T main
U puts@@GLIBC_2.2.5
実行ファイルから使用される共有ライブラリの表示
ldd
を使えば便利。
実行時に読み込まれるライブラリ一覧が表示される。
[rso@localhost log]$ ldd a.out
linux-vdso.so.1 => (0x00007ffe39db9000)
libc.so.6 => /lib64/libc.so.6 (0x0000003bc3e00000)
/lib64/ld-linux-x86-64.so.2 (0x0000003bc3600000)
参考文献
主に以下の資料から自分なりに理解したものを記載した。
-
ELF入門 ・・・ IBMのマシン用の解説資料ですが、Linuxでも十分通用します。
-
Binary Hacks ―ハッカー秘伝のテクニック100選 ・・・ バイナリ職人を目指す人のために。。
// 記載内容に間違いなどありましたらご指摘いただけると幸いです。