当前位置: 首页 > 科技观察

在Linux上分析二进制文件的10种方法

时间:2023-03-21 12:06:01 科技观察

“这个世界上有10种人:懂二进制的和不懂二进制的。”我们每天都与二进制文件打交道,但我们对它们知之甚少。二进制文件是指您每天运行的可执行文件,从命令行工具到成熟的应用程序。Linux提供了一组丰富的工具,使分析二进制文件变得轻而易举。无论您的工作角色如何,如果您在Linux上工作,了解这些工具的基础知识将有助于您更好地了解您的系统。在本文中,我们将介绍一些最流行的Linux工具和命令,其中大部分是Linux发行版的一部分。如果没有找到,您可以随时使用您的包管理器来安装和探索它们。请记住:学习在正确的情况下使用正确的工具需要大量的耐心和练习。file它的作用:帮助确定文件类型。这将是您进行二进制分析的起点。我们每天都在和文件打交道,并不是所有的文件都是可执行类型,除此之外还有各种各样的文件类型。在开始之前,您需要了解要分析的文件类型。是二进制文件、库文件、ASCII文本文件、视频文件、图像文件、PDF、数据文件等?文件命令将帮助您确定您正在处理的文件类型。$file/bin/ls/bin/ls:ELF64-bitLSBexecutable,x86-64,version1(SYSV),动态链接(usesharedlibs),forGNU/Linux2.6.32,BuildID[sha1]=94943a89d17e9d373b2794dcb1f7e38c95b66c86,stripped$$filewd/etc//etc/passwd:ASCIItext$ldd其作用:打印共享对象的依赖关系。如果您对可执行二进制文件使用了上述文件命令,您肯定会在输出中看到“动态链接”消息。这是什么意思?在开发软件时,我们尽量不重新发明轮子。大多数软件程序都需要一组通用任务,例如打印输出或从标准输入/打开文件中读取等。所有这些通用任务都被抽象为一组通用函数,每个人都可以使用这些函数而不是自己编写变体。这些常用函数都放在一个名为libc或glibc的库中。如何找到可执行程序所依赖的库?这就是ldd命令的作用。在动态链接的二进制文件上运行此命令将显示所有依赖库及其路径。$ldd/bin/lslinux-vdso.so.1=>(0x00007ffef5ba1000)libselinux.so.1=>/lib64/libselinux.so.1(0x00007fea9f854000)libcap.so.2=>/lib64/libcap.so.2(0x00007fea9f64f000)libacl.so.1=>/lib64/libacl.so.1(0x00007fea9f446000)libc.so.6=>/lib64/libc.so.6(0x00007fea9f079000)libpcre.so.1=>/lib64/libpcre.so.1(0x00007fea9ee17000)libdl.so.2=>/lib64/libdl.so.2(0x00007fea9ec13000)/lib64/ld-linux-x86-64.so.2(0x00007fea9fa7b000)libattr.so.1=>/lib64/libattr.so.1(0x00007fea9ea0e000)libpthread.so.0=>/lib64/libpthread.so.0(0x00007fea9e7f2000)$ltrace它的作用:库调用跟踪器。我们现在知道如何使用ldd命令来查找可执行程序所依赖的库。但是,一个库可以包含数百个函数。在这数百个函数中,哪些是我们的二进制程序实际使用的函数?ltrace命令可以显示运行时从库中调用的所有函数。在下面的示例中,您可以看到被调用函数的名称,以及传递给该函数的参数。您还可以在输出的最右侧看到这些函数返回的内容。$ltracels__libc_start_main(0x4028c0,1,0x7ffd94023b88,0x412950<未完成...>strrchr("ls",'/')=nilsetlocale(LC_ALL,"")="en_US.UTF-8"bindtextdomain("coreutils","/usr/share/locale")="/usr/share/locale"textdomain("coreutils")="coreutils"__cxa_atexit(0x40a930,0,0,0x736c6974756572)=0isatty(1)=1getenv("QUOTING_STYLE")=nilgetenv("COLUMNS")=nilioctl(1,21523,0x7ffd94023a50)=0<>fflush(0x7ff7baae61c0)=0fclose(0x7ff7baae61c0)=0+++exited(status0)+++$hexdump它的作用:在ASCII中,十进制、十六进制或八进制来显示文件内容。通常,当你用应用程序打开一个文件,它不知道如何处理它时,会发生这种情况。尝试用vim打开一个文件,执行一个文件或视频文件,和所有你会看到屏幕上是乱码。在hexdump中打开未知文件可以帮助你看到文件的具体内容。你也可以选择使用一些命令行选项来查看ASCII表示的文件数据。这可能有助于你了解什么类型的文件是。$Hexdump-C/Bin/LS|头000000007F454C460201000000000000000|.lf...............0000001002003e000100000000d4424000000000|......B@............|000000300000000040003800090040001f001e00|....@.8...@......|0000004006000000050000004000000000000000|......@.。....|0000009038024000000000001c00000000000000|8.@............|$strings它的作用:在文件中打印一串可打印字符。如果您只是在二进制中寻找可打印字符,那么hexdump对于您的用例来说似乎有点过分,您可以改用strings命令。开发软件时,会在其中添加各种文本/ASCII信息,如打印信息、调试信息、帮助信息、错误等。只要二进制文件中存在这些信息,就可以通过字符串命令。$strings/bin/lsreadelf作用:显示有关ELF文件的信息。ELF(ExecutableandLinkableFileFormat)是可执行文件或二进制文件的主流格式,不仅是Linux系统,也是各种UNIX系统的主流文件格式。如果您使用过file命令之类的工具,它告诉您该文件是ELF格式,那么下一步就是使用readelf命令及其各种选项进一步分析该文件。使用readelf命令时,参考实际的ELF规范非常有用。您可以在此处找到规范。$readelf-h/bin/lselfheader:魔术:7f454c4602010100000000000000000000000000000000000000000000000000000000000000000000000000000000来:ELF64DATA:2'SCOMPLEMENT,LITTLEENDIANVERMEN:1(当前)OS/ABI:os/abi:unix-systemvabiversion:unix-systempevirsion:0type:0type:0typoint:exec(execiutablebypointry:execiptable):(bytesintofile)Startofsectionheaders:115696(bytesintofile)Flags:0x0Sizeofthisheader:64(bytes)Sizeofprogramheaders:56(bytes)Numberofprogramheaders:9Sizeofsectionheaders:64(bytes)Numberofsectionheaders:31Sectionheaderstringmptableindex:30$obj二进制文件是根据你编写的源代码创建的,它是编译的通过一个叫做编译器的工具。编译器生成相对于源代码的机器语言指令,然后由CPU执行以完成特定的任务。这些机器语言代码可以通过称为汇编语言的助记术语来破译。汇编语言是一组指令,可帮助您了解程序正在做什么并最终在CPU上执行。objdump实用程序读取二进制文件或可执行文件并将汇编语言指令转储到屏幕上。了解汇编语言对于理解objdump命令的输出至关重要。请记住:汇编语言是特定于体系结构的。$objdump-d/bin/ls|head/bin/ls:fileformatelf64-x86-64Disassemblyofsection.init:0000000000402150<_init@@Base>:402150:4883ec08sub$0x8,%rsp402154:488b056d8e2100mov0x218e6p)18<__%caf(%__%caf)>40215b:4885c0test%rax,%rax$strace它的作用:跟踪系统调用和信号。如果您使用过上述ltrace,请将strace视为类似的东西。唯一的区别是它不是跟踪调用的库,strace工具跟踪系统调用。系统调用是您与内核交互以完成工作的方式。例如,如果您想将某些内容打印到屏幕上,您可以使用标准库libc中的printf或puts函数;但是,在引擎盖下,最终会有一个名为write的系统调用,用于实际将某些内容打印到屏幕上级。$strace-f/bin/lsexecve("/bin/ls",["/bin/ls"],[/*17vars*/])=0brk(NULL)=0x686000mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7f967956a000access("/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=40661,...})=0mmap(NULL,40661,PROT_READ,MAP_PRIVATE,3,0)=0x7f9679560000close(3)=0<>fstat(1,{st_mode=S_IFCHR|0620,st_rdev=makedev(136,1),...})=0mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7f9679569000write(1,"R2RH\n",7R2RH)=7close(1)=0munmap(0x7f9679569000,4096)=0close(2)=0exit_group(0)=?+++exitedwith0+++$nm作用:列出目标文件中的符号。如果您使用的二进制文件没有被剥离,nm命令将在编译期间为您提供嵌入到二进制文件中的有价值的信息。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__$gdb它的作用:GNU调试器。好吧,并非二进制文件中的所有内容都可以进行静态分析。我们确实执行了一些运行二进制文件的命令(用于分析),例如ltrace和strace;但是,该软件包含各种条件,这些条件可能导致执行不同的备选路径。分析这些路径的唯一方法是在运行时环境中的任何给定点停止或暂停程序,并能够在进一步处理之前分析信息。这就是调试器的用途,在Linux上,gdb是调试器的实际标准。它可以帮助您加载程序、在特定位置设置断点、分析内存和CPU寄存器等等。它补充了上面提到的其他工具,让您可以进行更多的运行时分析。需要注意的一件事是,一旦您使用gdb加载程序,您将看到它自己的(gdb)提示符。所有进一步的命令都将在此gdb命令提示符下运行,直到您退出。我们将采用我们之前编译的hello程序并使用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.Helloworld![Inferior1(process29620)exitednormally](gdb)q$Epilogue一旦你习惯了使用这些本地Linux二进制分析工具并理解了它们提供的输出,你就可以转向更高级、更专业的开源二进制分析工具,例如radare2。