线性扫描( linear sweep)和递归下降( recursive descent)是两种最主要的反汇编算法。
线性扫描反汇编
线性扫描反汇编算法采用一种非常简单的方法来确定需要反汇编的指令的位置:一条指令结束、另一条指令开始的地方。
因此,确定起始位置最为困难。常用的解决办法是,假设程序中标注为代码(通常由程序文件的头部指定)的节所包含的全部是机器语言指令。反汇编从一个代码段的第一个字节开始,以线性模式扫描整个代码段,逐条反汇编每条指令,直到完成整个代码段。这种算法并不会通过识别分支等非线性指令来了解程序的控制流。
进行反汇编时,可以维护一个指针来标注当前正在反汇编的指令的起始位置。在反汇编过程中,每一条指令的长度都被计算出来,并用来确定下一条将要反汇编的指令的位置。为此,对由长度固定的指令构成的指令集(如 MIPS)进行反汇编有时会更加容易,因为这时可轻松定位随后的指令。
线性扫描算法的主要优点,在于它能够完全覆盖程序的所有代码段。线性扫描方法的一个主要缺点,是它没有考虑到代码中可能混有数据。
GNU 调试器( gdb)、微软公司的 WinDbg 调试器和 objdump ...
逆向与反汇编工具
1 分类工具
绝不要根据文件的扩展名来确定文件的类型。
1.1 file
file 命令是一个标准的实用工具,大多数*NIX 风格的操作系统和 Windows 下的 Cygwin①或MinGw②工具都带有这个实用工具。file 试图通过检查文件中的某些特定字段来确认文件的类型。
file 能够识别常见的字符串,如#!/bin/sh( shell 脚本文件)或( HTML 文档)。但是,识别那些包含非 ASCII 内容的文件要困难得多,在这种情况下, file 会设法判断该文件的结构是否符合某种已知的文件格式。多数情况下,它会搜索某些文件类型所特有的标签值(通常称为幻数③)。下面的十六进制表列出了几个用于判断常见文件类型的幻数。
幻数是一些文件格式规范所要求的特殊标签值,它表示文件符合这种规范。
1.2 PE Tools
PE Tools①是一组用于分析 Windows 系统中正在运行的进程和可执行文件的工具。
1.3 PEiD
PEiD①是另一款 Windows 工具,它主要用于识别构建某一特定 Windows PE 二进制文件所使用的编译器,并确定任何用于模糊 ...
编译过程从你要编译的各种源文件开始(可能只有一个源文件,但大型程序通常由许多文件组成)。这不仅使得项目更易于管理,而且加快了编译速度,因为如果一个文件发生了更改,只需重新编译该文件而不是所有代码。
C的源文件包含宏(用#define表示)和#include指令。可以使用#include指令包含源文件所依赖的头文件(扩展名为.h)。
预处理阶段扩展了源文件中的所有#define和#include指令,因此剩下的就是准备编译的纯C代码。
默认情况下,GCC会自动执行所有的编译阶段,因此必须明确告诉它在预处理后停止,并显示中间输出。
对GCC来说,这可以使用命令gcc -E -P来完成,其中-E告诉GCC在预处理后停止,-P使GCC忽略调试信息,以便输出更清晰。
==stdio.h头文件全部包含在内,其所有的类型定义、全局变量及函数原型都被“复制”到源文件中。==因为每个#include指令都会发生这种情况,所以预处理器输出可能非常冗长。==预处理器还完整地扩展了#define定义的任何宏的所有用法。==在示例中,这意味着对printf(FORMAT_STRING❶和MESSAGE❷ ...
汇编阶段
汇编阶段将得到真正的机器代码,汇编阶段的输入是在编译阶段生成的汇编语言集,输出是一组对象文件,有时简称为模块。
对象文件原则上包含可由处理器执行的机器指令。
通常情况下,每个源文件对应一个汇编文件,每个汇编文件对应一个对象文件。
生成对象文件,传递-c标志给gcc,
可以使用file工具来确认生成的compilation_example.o文件确实是对象文件。
file输出的第一部分显示了该文件符合二进制可执行文件的ELF规范。具体地,是一个64位的ELF二进制文件(因为在这个示例中编译的是x86_64),并且是最低有效位(Least Significant Bit,LSB),这意味着数在内存中的排序是以最低有效字节优先的。但最重要的是,可以看到该文件是可重定位的。
可重定位文件不依赖于放置在内存中的任何特定地址,相反,它们可以随意移动,而不会破坏代码中的任何假设。当在文件输出中看到术语“可重定位”时,表示正在处理的是对象文件而不是可执行文件。
对象文件相互独立编译,因此汇编程序在组装对象文件时无法知道其他对象文件的内存地址。这就是对象文件需要可重定位的原因,这样就可 ...
二进制分析
未读目标是解析flag,为一个序列号
查看PE文件,可见是一个win32程序,32位exe,并且加壳了,加壳工具是tElock,首先脱壳得到脱壳后的文件。
用IDA反编译:
分析DialogFunc,获取最终的flag;
此处应该是获取了两个输入,用户名和序列号。
DialogFunc中可分析出由一个子函数控制着序列号正确与否,接着去分析sub_401610,该函数接受的参数是lpString,也是输入的序列号,尝试去找到该序列号的正确匹配结果;
查看sub_401610函数的伪代码,其中又调用了其他函数过程,挨个分析吧;
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131 ...
链接阶段
链接阶段是编译过程的最后阶段。顾名思义,此阶段将所有对象文件链接到一个二进制可执行文件中。在现代系统中,链接阶段有时会包含额外的优化过程,被称为链接时优化(Link-Time Optimization, LTO)。
执行链接阶段的程序被称为链接器或者链接编辑器。通常链接器与编译器相互独立,编译器通常实现前面所有的步骤。
对象文件是可重定位的,它们是相互独立编译的,这使得编译器无法假设对象最终会出现在任意特定的基址上。
对象文件可以引用其他对象文件或程序外部库中的函数或者变量。在链接阶段之前,引用代码和数据的地址尚不清楚,因此对象文件只包含重定位符号,这些符号指定最终如何解析函数和变量引用。在链接上下文中,依赖于重定位符号的引用称为符号引用。
当一个对象文件通过绝对地址引用自己的函数或变量时,该引用也会被符号化。
链接器的工作是获取属于程序的所有对象文件,并将它们合并为一个连贯的可执行文件,然后加载到特定的内存地址。既然现在已经知道可执行文件中所有模块的排列,链接器也就可以解析大多数的符号引用了。
根据库文件的类型,对库文件的引用可能会、也可能不会完全解析。 ...
二进制分析
未读反汇编二进制文件
已经了解了如何编译二进制文件,那么让我们来看一下编译汇编阶段生成的对象文件内容。之后,我将会反汇编二进制可执行文 件,显示可执行文件内容与对象文件的内容有何不同。
查看对象文件
使用objdump实用程序来展示如何进行反汇编。objdump是一个简单、易用的反汇编程序,包含在大多数Linux发行版中,非常适合快速了解二进制文件中包含的代码和数据。
此处,调用了objdump两次。
第一次,在 ❶处,调用objdump显示.rodata节的内容。.rodata节代表的是“只 读数据”,二进制文件中所有的常量都存储在该节,包括“Hello, world!”字符串。注意,.rodata节的内容是由 ASCII编码的字符串组成的,显示在左侧的输出中。在右侧,你可以看到相同字节的可读表示。
第二次在❷处调用objdump,以Intel语法反汇编对象文件的所有代码。正如你所看到的,结果仅包含main函数❸的代码,因为这是源文件中定义的唯一函数。大多数情况下,输出与先前由编译阶段生成的汇编 代码非常接近,它采用了一些汇编级宏。有趣的是,指向“Hello, world!”字符串的 ...
二进制分析
未读符号和剥离的二进制文件
符号信息
高级源代码(如C代码)均以有意义的、人类可读的函数和变量命名为中心。编译程序时,编译器会翻译符号,这些符号会跟踪其名称,并记录哪些二进制代码和数据对应哪个符号。
用到了 readelf来显示符号❶。
请注意,在许多不熟悉的符号中,main函数❷有一个符号。你可以看到它指定了当二进制文件加载到内存时main将驻留的地址(0x400526)。输出还显示main的代码大小(32字节),并指出你正在处理一个函数符号(类型为 FUNC)。
符号信息可以作为二进制文件的一部分,或者以单独的符号文件形式转译,它有各种风格。链接器只需要基本符号,但为了调试,可以转译出更广泛的信息。
调试符号提供了源 代码行和二进制指令之间的完整映射关系,甚至描述了函数的参数、堆栈帧信息等。对于ELF二进制文件,调试符号通常以DWARF格式生成,而PE二进制文件通常使用专有的Microsoft可移植调试(如PDB)格式。DWARF信息通常嵌在二进制文件中,而PDB则以单独的符号文件的形式存在。
符号信息对于二进制分析非常有用。一组定义良好的函数符号可以使反汇编更加容易,这是因为可 ...
二进制分析
未读123456789101112131415161718 5 touch a.c 6 gcc -E -P a.c 7 gcc -S -masm=intel a.c 8 cat a.s 9 gcc -c a.c10 file a.o11 gcc a.c12 file a.out13 ls14 ./a.out15 readelf --syms a.out16 strip --strip-all a.out17 file a.out18 objdump -sj .rodata a.o19 objdump -M intel -d a.o20 readelf --relocs a.o21 ls22 objdump -M intel -d ./a.out.stripped
加载并执行二进制文件
加载二进制文件是一个复杂的过程,涉及操作系统的大量工作。同样重要的是,内存中二进制文件的表示不一定与磁盘上二进制文件的表示一一对应。
图1-2显示了如何在Linux操作系统上 加载ELF二进制文件,如刚编译的二进制文件。从高层次上讲,这与在Windows操作系统上加载PE ...