这世界有10种人,一种人懂二进制,另一种人不懂二进制。 ——
大家好,
二进制文件是我们几乎每天都需要打交道的文件类型,但很少人知道他们的工作原理。这里所讲的二进制文件,是指一些可执行文件,包括你天天要使用的 Linux 命令,也是二进制文件的一种。
Linux 给我们提供了非常多用于二进制文件的工具,不管你在 Linux 下从事的是何种工作,知道这些工具也会让你对你的更加了解。
在本文中,将介绍几种常用的用于二进制文件的工具及命令,这些工具在大部分发行版里可以直接使用,如果不能直接用的话,可以自行安装。
file
file 命令用于文件的类型。
如果你需要二进制文件,可以首先使用 file 命令来切入。我们知道,在 Linux 下,皆文件,但并不是所有的文件都具有可执行,我们还有各种各样的文件,比如:文本文件,管道文件,链接文件,socket文件,等等。
在对一个文件进行之前,我们可以首先使用 file 命令来它们的类型。当然除此之外,我们还可以看到一些其它信息。
$ file /bin/pwd /bin/pwd: ELF -bit L executable, x86-, version 1 (SYSV), dynamically linked (uses shared libs), for U/Linux 2.6.32, BuildID[sha1]=0d2ba2adc568f0e21cc9576df434c, stpped
ldd
ldd 命令可以用于可执行文件的依赖。
我们使用 file 命令来一个可执行文件的时候,有时候可以看到输出中有 dynamically linked 这样的字眼。这个是啥呢?
大部分程序,都会使用到第三方库,这样就可以不用重复造子,节约大量时间。简单的,我们写C程序代码的话,肯定会使用到 li 或者 gli 库。当然,除此之外,还可能使用其它的库。
那我们在什么情况下需要程序的依赖库呢?有一个场景大家肯定经历过。你去你同事那边拷备他写好的程序放到自己的环境下运行,有时候可能会跑不起来。当然跑不起来的原因可能很多,但其中一个原因可能就是缺少对应的依赖库。
这时候,ldd 就派上用场了。它可以程序需要一些什么依赖库,你只要把对应的库放在对应的位置就可以了。
$ ldd /bin/pwd > (0x00007ffeb73e5000) > /lib(0x00007f908b) /lib(0x00007f908b6ef000)
ltrace
ltrace的能是能够跟踪进程的库函数调用。
我们可以使用 ldd 命令来找到程序的依赖库,但是,一个库里少则几个,多则几千个函数,怎么知道现在程序调用的是什么函数呢?
ltrace 命令就是用来做这个事的。在下面的例子里,我们可以看到程序调用的函数,以及传递进去的参数,同时你也可以看到函数调用的输出。
$ ltrace /bin/pwd __li_start_in(0x, 1, 0x7ffff6524cc8, 0x404a00 <unfinished > getenv("POSIX_CORRECT") = nil strrchr("/bin/pwd", '/') = "/pwd" setlocale(LC_ALL, "") = "" bindtextdoin("coreutils", "/usr/share/locale") = "/usr/share/locale" textdoin("coreutils") = "coreutils" __cxa_atexit(0x4022f0, 0, 0, 0x736c) = 0 getopt_long(1, 0x7ffff6524cc8, "LP", 0x606d00, nil) = -1 getcwd(nil, 0) = "" puts("/home/alvin"/home/alvin ) = 12 free(0x22030) = <void> exit(0 <unfinished > __fpending(0x7f, 0, , 0x7feb0) = 0 fileno(0x7f) = 1 __freading(0x7f, 0, , 0x7feb0) = 0 __freading(0x7f, 0, 2052, 0x7feb0) = 0 fflush(0x7f) = 0 fclose(0x7f) = 0 __fpending(0x7fc0, 0, 3328, 0xfbad000c) = 0 fileno(0x7fc0) = 2 __freading(0x7fc0, 0, 3328, 0xfbad000c) = 0 __freading(0x7fc0, 0, 4, 0xfbad000c) = 0 fflush(0x7fc0) = 0 fclose(0x7fc0) = 0 +++ exited (status 0) +++
strace
strace 命令可以用于追踪程序运行过程中的调用及。
通过上面的介绍,我们知道 ltrace 命令是用来追踪函数调用的。strace 命令类似,但它追踪的是调用。何为调用?简单说就是我们可以通过调用与内核进行交互,完成我们想要的任务。
例如,如果我们想在屏幕上打印某些字符,可以使用 pntf 或 puts 函数,而这两个都是 li 的库函数,在更底层,他们都是调用 wte 这个调用。
$ strace -f /bin/pwd execve("/bin/pwd", ["/bin/pwd"], [/* 24 vars */]) = 0 brk(NULL) = 0x9000 mp(NULL, 40, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f918ba access("/etc", R_OK) = -1 ENOENT (No such file or directory) open("/etc", O_RDON|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|04, st_size=, }) = 0 mp(NULL, , PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f918ba5f000 close(3) = 0 open("/lib", O_RDON|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20&\2\0\0\0\0\0", 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=, }) = 0 mp(NULL, , PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f918b47b000 mptect(0x7f918b63e000, , PROT_NONE) = 0 mp(0x7f918b83e000, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f918b83e000 mp(0x7f918b, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f918b close(3) ………… +++ exited with 0 +++
hexdump
hexdump 命令用来查看二进制文件的 16 进制编码,但实际它能查看文件,而不限于二进制文件。
一个二进制文件,如果你直接使用文本编辑器打开的话,将看到一堆乱码。这时候,你就可以使用 hexdump 命令来查看它的内容了。
hexdump 的显示格式是:左边是字节序,中间是文件的 16 进制编码,如果是可打印字符的话就会显示在右边。
通过使用这个命令,我们就可以大概知道这个二进制文件里面有什么内容,后面要做什么处理就比较方便了。
$ hexdump -C /bin/pwd | head 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 00000010 02 00 3e 00 01 00 00 00 17 19 40 00 00 00 00 00 |..>@ 00000020 40 00 00 00 00 00 00 00 50 7a 00 00 00 00 00 00 |@ 00000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00 @@ 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 @ 00000090 38 02 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 |8.@
stngs
stngs 命令可以用来打印二进制文件中可显示的字符。
什么是可显示字符?简单说你在显示器上看到的字符都是可显示字符,比如:aA,.:。
我们知道,一个二进制文件里面的内容很多是非显示字符,所以无法直接用文本处理器打开。程序在被的时候,我们经常会加一些调试信息,比如:debug log, warn log, err log,等等。这些信息我们就可以使用 stngs 命令看得到。
$ stngs /bin/pwd | head /lib fflush strcpy __pntf_chk readdir setlocale mbrtowc strncmp optind
readelf
readelf 一般用于查看 ELF 格式的文件信息。
ELF(Executable and Linkable Fort)即可执行连接文件格式,是一种比较复杂的文件格式,但其应用广泛。当你使用 file 命令发现某个文件是 ELF 文件时,你就可以使用 readelf 命令来读取这个文件的信息。
$ readelf -h /bin/pwd ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF Data: 2's complement, little endian Version: 1 (crent) OS/A: UNIX - V A Version: 0 Type: EXEC (Executable file) Machine: Advanced Mic Devices X86- Version: 0x1 Entry point address: 0x Start of pgram headers: (bytes into file) Start of section headers: (bytes into file) Flags: 0x0 Size of this header: (bytes) Size of pgram headers: 56 (bytes) Number of pgram headers: 9 Size of section headers: (bytes) Number of section headers: 30 Section header stng table index: 29
objdump
objdump是用查看目标文件或者可执行的目标文件的构成的GCC工具。
我们知道,程序在完成之后,需要经过编译,才可以生成计算机可以识别的二进制文件。我们写的代码计算机不能直接执行,需要编译成汇编程序,计算机才能依次执行。
objdump 命令可以读取可执行文件,然后将汇编指令打印出来。所以如果你想看懂 objdump 的结果,你就需要有一些汇编基才可以。
$ objdump -d /bin/pwd | head /bin/pwd: file fort elf-x86- Disassembly of section .init: 0000000000 <.init>: : 48 83 ec 08 sub $0x8,%rsp : 48 8b 05 6d 5c 20 00 mov 0x205c6d(%p),%rax # 606fc8 <__ctype_b_loc@plt+0x> b: 48 85 c0 %rax,%rax
nm
nm命令主要是列出目标文件的符(说白了就是一些函数和全变量等)。
如果你编译出来的程序没有经过 stp ,那么 nm 命令可以挖掘出隐含在可执行文件中的重大秘密。它可以帮你列出文件中的变量及函数,这对于我们进行反向操作具有重大意义。
下面我们通过一小段简单的程序来讲解 nm 命令的用途。在编译这个程序时,我们加上了 -g 选项,这个选项可以使编译出来的文件包含更多有效信息。
$ cat #include int in { pntf("Hello world!"); retn 0; } $ $ gcc -g $ $ file hello hello: ELF -bit L executable, x86-, version 1 (SYSV), dynamically linked (uses shared libs), for U/Linux 2.6.32, BuildID[sha1]=3de46c8efb98e4ad525dba3d8a5d, not stpped $ $ ./hello Hello world!$ $ $ nm hello | tail 0000000000600e20 d __JCR_END__ 0000000000600e20 d __JCR_LIST__ 00000000004005b0 T __li_u_fini 0000000000 T __li_u_init U __li_start_in@@GLI_2.2.5 0000000000d T in U pntf@@GLI_2.2.5 0000000000 t register_tm_clones 0000000000 T _start 0000000000 D __TMC_END__ $
gdb
gdb 就是所谓的 U debugger。
gdb 大家或多或少都有听说过。我们在使用一些 IDE 写代码的时候,可以进行打断点、步进、查看变量值等方式调试,其实这些 IDE 底层调用的也是 gdb 。
对于 gdb 的用法,可以写很多,本文就暂且不深入了。下面先演示一小段 gdb 基的能。
$ gdb -q ./hello Reading symbols fm /home/fla (gdb) break in Breakpoint 1 at 0x: file , line 4. (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000 in in at (gdb) run Starting pgram: /home/flash/./hello Breakpoint 1, in at 4 pntf("Hello world!"); Missing separate debuginfos, use: debuginfo-tall (gdb) #0 in at (gdb) c Continuing. Hello world![Infeor 1 (pcess ) exited norlly] (gdb) q $
小结
如果你在 Linux 下进行程序的时候,那么你免不了跟二进制文件打交道。熟练使用以上介绍的 10 个命令,将会对你的工作产生很大的帮助。