バイナリファイルの解析、調査に使えるコマンドメモ
下記のコマンド実行例の環境はこちら
[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選 ・・・ バイナリ職人を目指す人のために。。
 
// 記載内容に間違いなどありましたらご指摘いただけると幸いです。