二进制文件是我们几乎每天都要打交道的一类文件,但很少有人知道它们是如何工作的。这里所说的二进制文件是指一些可执行文件,包括大家日常使用的Linux命令,也是二进制文件的一种。Linux系统为我们提供了很多分析二进制文件的工具。无论您在Linux下从事何种工作,了解这些工具也会让您更好地了解您的系统。在本文中,我们将介绍一些最常用的分析二进制文件的工具和命令。这些工具可以在大多数发行版中直接使用。如果你不能直接使用它们,你可以自己安装它们。filefile命令用于分析文件的类型。如果需要分析二进制文件,可以先使用file命令切入。我们知道,在Linux下,一切皆文件,但并不是所有的文件都是可执行的。我们还有各种文件,比如:文本文件、管道文件、链接文件、socket文件等等。在分析一个文件之前,我们可以先用file命令分析一下它们的类型。当然,除此之外,我们还可以看到一些其他的信息。$file/bin/pwd/bin/pwd:ELF64-bitLSBexecutable,x86-64,version1(SYSV),dynamicallylinked(usesharedlibs),forGNU/Linux2.6.32,BuildID[sha1]=0d264bacf2adc568f0e21cbcc9576df434c44380,strippedlddldd可用于分析可执行命令文件依赖。我们在使用file命令分析一个可执行文件的时候,有时会在输出中看到动态链接的字样。这是什么意思?大多数程序都会使用第三方库,这样就不用重新发明轮子,节省很多时间。最简单的,如果我们写C程序代码,肯定会用到libc或者glibc库。当然,除此之外还可以使用其他库。那么什么情况下我们需要分析程序的依赖库呢?有一个场景,想必每个人都经历过。你去找你的同事把他写的程序拷贝过来,在你自己的环境下运行,有时候可能运行不起来。当然不能运行的原因可能有很多,但其中一个原因可能是缺少相应的依赖库。这时候ldd就派上用场了。它可以分析出程序需要哪些依赖库,你只需要将相应的库放在相应的位置即可。$ldd/bin/pwdlinux-vdso.so.1=>(0x00007ffeb73e5000)libc.so.6=>/lib64/libc.so.6(0x00007f908b321000)/lib64/ld-linux-x86-64.so.2(0x00007f908b6ef000)ltraceltrace的作用是能够跟踪进程的库函数调用。我们可以使用ldd命令来查找程序的依赖库。然而,库中的函数少则几个,多则数千。我们如何知道程序现在正在调用什么函数?ltrace命令用于执行此操作。在下面的例子中,我们可以看到程序调用的函数和传入的参数,也可以看到函数调用的输出。$ltrace/bin/pwd__libc_start_main(0x401760,1,0x7ffff6524cc8,0x404a00<未完成...>getenv("POSIXLY_CORRECT")=nilstrrchr("/bin/pwd",'/')="/pwd"setlocale(LC_ALL,"")="en_US.utf8"bindtextdomain("coreutils","/usr/share/locale")="/usr/share/locale"textdomain("coreutils")="coreutils"__cxa_atexit(0x4022f0,0,0,0x736c6974756572)=0getopt_long(1,0x7ffff6524cc8,"LP",0x606d00,nil)=-1getcwd(nil,0)=""puts("/home/alvin"/home/alvin)=12free(0x22bc030)=exit(0__fpending(0x7f3048865400,0,64,0x7f3048865eb0)=0fileno(0x7f3048865400)=1__freading(0x7f3048865400,0,64,0x7f3048865eb0)=0__freading(0x7f3048865400,0,2052,0x7f3048865eb0)=0fflush(0x7f3048865400)=0fclose(0x7f3048865400)=0__fpending(0x7f30488651c0,0,3328,0xfbad000c)=0fileno(0x7f30488651c0)=2__freading(0x7f30488651c0,0,3328,0xfbad000c)=0x7freading(0x7freading30488651c0,0,4,0xfbad000c)=0fflush(0x7f30488651c0)=0fclose(0x7f30488651c0)=0++++exited(status0)+++stracestrace命令可以用来跟踪程序运行过程中的系统调用和信号通过上面的介绍,我们知道ltrace命令是用来跟踪函数调用的。strace命令类似,但它跟踪系统调用。什么是系统调用?简单的说,我们可以通过系统调用与内核进行交互,完成我们想要的任务。比如我们要在屏幕上打印某些字符,可以使用printf或者puts函数,这两个函数都是libc库函数,在更底层,都调用了write系统调用。$strace-f/bin/pwdexecve("/bin/pwd",["/bin/pwd"],[/*24vars*/])=0brk(NULL)=0xbc9000mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7f918ba69000access("/etc/ld.so.preload",R_OK)=-1ENOENT(Nosuchfileordirectory)open("/etc/ld.so.cache",O_RDONLY|O_CLOEXEC)=3fstat(3,{st_mode=S_IFREG|0644,st_size=38684,...})=0mmap(NULL,38684,PROT_READ,MAP_PRIVATE,3,0)=0x7f918ba5f000close(3)=0open("/lib64/libc.so.6",O_RDONLY|O_CLOEXEC)=3read(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)=832fstat(3,{st_mode=S_IFREG|0755,st_size=2156160,...})=0mmap(NULL,3985888,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_DENYWRITE,3,0)=0x7f918b47b000mprotect(0x7f918b63e000,2097152,PROT_NONE)=0mmap(0x7f918b83e000,24576,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE,3,0x1c3000)=0x7f918b83e000mmap(0x7f918b844000,16864,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,-1,0)=0x7f918b844000close(3)…………+++exitedwith0+++hexdumphexdump命令用于查看二进制文件的十六进制编码,但实际上它可以查看任何文件,不限于二进制文件。如果一个二进制文件,直接用文本编辑器打开,会看到一堆乱码。此时可以使用hexdump命令查看其内容。hexdump的显示格式是:左边是字节序号,中间是文件的十六进制编码,如果是可打印字符,就显示在右边。通过使用这个命令,我们可以大致知道二进制文件里面有什么,以后做什么操作会比较方便。$Hexdump-C/Bin/PWD|头000000007F454C4602010000000000000|.lf...............0000002003e000100000017194000000000|..>.........@...|000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000来........@.@.....|00000060f8010000000000000f801000000000000|...0000007008000000000000000304000...0.........|00000080380200000000000038024000000@0.........|0000009038024000000000001c00000000000000|8.@............|stringsstrings命令可用于打印二进制文件中的可显示字符。什么是可显示角色?简单的说,你在显示器上看到的字符都是可以显示的字符,比如:abcABC,.:。我们知道,二进制文件的很多内容都是不可显示的字符,所以不能直接用文本处理器打开。在程序开发的时候,我们经常会添加一些调试信息,比如:debuglog、warnlog、errorlog等,我们可以使用strings命令来查看这些信息。$strings/bin/pwd|head/lib64/ld-linux-x86-64.so.2libc.so.6fflushstrcpy__printf_chkreaddirsetlocalembrtowcstrncmpopptindreadelfreadelf一般用于查看ELF格式的文件信息。ELF(ExecutableandLinkableFormat)是一种可执行链接文件格式,是一种相对复杂的文件格式,但应用广泛。当你使用file命令发现一个文件是ELF文件时,可以使用readelf命令读取这个文件的信息。$readelf-h/bin/pwdELFHeader:Magic:7f454c46020101000000000000000000Class:ELF64Data:2'scomplement,littleendianVersion:1(current)OS/ABI:UNIX-SystemVABIVersion:0Type:EXEC(Executablefile)Machine:AdvancedMicroDevicesX86-64Version:0x1Entrypointaddress:0x401917Startofprogramheaders:64(bytesintofile)Startofsectionheaders:31312(bytesintofile)Flags:0x0Sizeofthisheader:64(bytes)Sizeofprogramheaders:56(bytes)Numberofprogramheaders:9Sizeofsectionheaders:64(bytes)Numberofsectionheaders查看可执行目标文件或可执行目标文件jobtablempindex:29objduGCC工具。我们知道,程序开发出来之后,需要经过编译才能生成计算机可以识别的二进制文件。我们编写的代码不能被计算机直接执行。它需要被编译成汇编程序,以便计算机可以顺序执行它。objdump命令可以读取可执行文件并打印出汇编指令。所以想要看懂objdump的结果,需要具备一些基本的汇编技巧。$objdump-d/bin/pwd|head/bin/pwd:fileformatelf64-x86-64Disassemblyofsection.init:0000000000401350<.init>:401350:4883ec08sub$0x8,%rsp401354:488b056d5c2000mov0x205c6d(%ripctype6fc_0),%rax#6@plt+0x205878>40135b:4885c0test%rax,%raxnmnm命令主要列出目标文件的符号(说白了就是一些函数和全局变量等)。如果你的编译程序没有被剥离,nm命令可以挖掘出隐藏在可执行文件中的大秘密。它可以帮助你列出文件中的变量和函数,这对我们的逆向操作有很大的意义。让我们通过一个简短的程序来解释nm命令的用途。在编译这个程序的时候,我们加入了-g选项,可以让编译后的文件包含更多的有效信息。$cathello.c#includeintmain(){printf("Helloworld!");return0;}$$gcc-ghello.c-ohello$$filehellohello:ELF64-bitLSBexecutable,x86-64,version1(SYSV),dynamicallylinked(usessharedlibs),forGNU/Linux2.6.32,BuildID[sha1]=3de46c8efb98bce4ad525d3328121568ba3d8a5d,notstripped$$./helloHelloworld!$$$nmhello|tail0000000000600e20d__JCR_END__0000000000600e20d__JCR_LIST__00000000004005b0T__libc_csu_fini0000000000400540T__libc_csu_initU__libc_start_main@@GLIBC_2.2.5000000000040051dTmainUprintf@@GLIBC_2.2.50000000000400490tregister_tm_clones0000000000400430T_start0000000000601030D__TMC_END__$gdbgdb就是所谓的GNU调试器。大家或多或少都听说过gdb。我们在使用一些IDE写代码的时候,可以通过断点、步进、查看变量值等方式进行调试,其实这些IDE的底层调用也是gdb。关于gdb的用法大家可以写很多,本文暂时不深入。我们先来一小段演示gdb最基本的功能。$gdb-q./helloReadingsymbolsfrom/home/flash/hello...done.(gdb)breakmainBreakpoint1at0x400521:filehello.c,line4.(gdb)infobreakNumTypeDispEnbAddressWhat1breakpointkeepy0x000000000400521inmainathello.c:4(gdb)run/reakhome1/Startingprog,main()athello.c:44printf("Helloworld!");Missingseparatedebuginfos,use:debuginfo-installglibc-2.17-260.el7_6.6.x86_64(gdb)bt#0main()athello.c:4(gdb)cContinuing.Helloworldq$总结如果在Linux下开发程序,不可避免地要和二进制文件打交道。熟练使用上面介绍的10条命令,对你的工作会有很大的帮助。本文经授权转载自公众号“良墟Linux”。世界500强外企Linux开发工程师梁旭,在公众号分享大量Linux干货,欢迎关注!