逆向工程基础
什么是逆向工程?
程序接受一个输入,然后经过一系列计算最后提示成功,那个输入就是flag,这需要有一定的算法能力,和思维能力。
什么是可执行文件?
逆向工程分析的对象就是一个程序,即一个可执行文件。
可执行文件的形成过程
用户将用高级语言编写的源代码输入,然后编译器产生相对应的汇编代码,汇编器接受汇编代码后继续执行汇编操作,将生成的机器码临时存放于各对象文件中,这时候,链接器参与其中,将各对象文件连接起来,融合成完整的程序,最后按照可执行文件的格式,填入各环境指定的参数,形成一个可执行文件。
不同格式的可执行文件
PE格式的可执行文件
PE文件由DOS头、PE文件头、节表及各级数据组成,可以使用导入库引用外部的链接库,使用导出库提供函数给其他程序来动态链接
ELF格式的可执行文件
ELF文件由ELF头、各节数据、节表、字符串段、符号表组成。
什么是节?
节是程序各部分的逻辑划分,,可执行文件在运行时,各节被加载到内存的各位置,一个或多个节会被映射到一个段中,如果在段中进行非法操作,则会造成段错误。
汇编基础
寄存器、内存、和寻址
寄存器是CPU的一部分,用来暂存指令、数据和地址
X86架构的处理器包含以下寄存器:
- 通用寄存器:EAX/EBX/ECX/EDX/ESI/EDI
- 栈顶指针寄存器:ESP
- 栈底指针寄存器:EBP
- 指令计数器(保存下一条执行的指令的地址):EIP
- 段寄存器:CS/DS/SS/ES/FS/GS、
X86-64架构的处理器则是在上述的寄存器的基础下,将前缀改为R,用来标记64位,并且增加了R8-R15这8个通用寄存器
标志寄存器:
- AF:辅助进位标志,当运算结果在第三位进位的时候置一
- PF:奇偶校验标志,当运算结果的最低有效字节的1为偶数个时置1
- SF:符号标志,有符号整型的符号位为1时置1,代表是一个负数
- ZF:零标志,当运算结果为全零时置一
- OF:溢出标志,数有符号位且溢出时置一
- CF:进位标志,运算结果向最高位进一时置一,用来判断无符号位数的溢出
CPU还可以对内存单元进行操作,存在着多种不同的寻址方式:立即寻址、直接寻址、寄存器寻址、寄存器间接寻址、基址寻址、变址寻址、基址加变址寻址
X86/X64汇编语言
什么是机器码?机器码是直接执行在CPU上的二进制指令,什么是汇编语言?汇编语言是机器语言的一种助记符,两者一一对应
汇编指令的基本格式:操作码 [操作数1] [操作数2]
常见汇编指令:
指令类型 | 操作码 | 指令示例 | 对应作用 |
---|---|---|---|
数据传送指令 | mov | mov rax,rbx | rax = rbx |
mov qword ptr [rdi],rax | *(rdi) = rax | ||
取地址指令 | lea | lea rax,[rsi] | rax = &*(rsi) |
算数运算指令 | add | add rax,rbx | rax += rbx |
add | add qword ptr [rdi],rax | *(rdi) += rax | |
sub | sub rax,rbx | rax -= rbx | |
逻辑运算指令 | and | and rax,rbx | rax &= rbx |
xor | xor rax,rbx | rax ^= rbx | |
函数调用指令 | call | call 0x100001 | 执行地址0x100001地址的函数 |
函数返回指令 | ret | ret | 函数的返回 |
比较指令 | cmp | cmp rax,rbx | 根据rax,rbx的比较结果跟换标志位 |
无条件跳转指令 | jmp | jmp 0x100001 | 跳到 0x100001地址执行 |
栈操作指令 | push | push rax | 把rax的值压入栈中 |
pop | pop rax | 从栈上弹出一个元素放入rax |
反汇编
汇编过程是将汇编语句翻译为对应的机器代码,然后将各条相邻的语句放在一起,反汇编就是将机器代码翻译回汇编语言。
在代码节中可能会穿插着跳转表、常量池(ARM)、普通常量数据,甚至还有恶意数据,所以我们需要用一个东西来表示程序的一个位置,方便跳转和取地址,这样可以指引反汇编工具正确解析代码
两种反汇编算法:线性扫描反汇编算法和递归下降反汇编算法
线性扫描反汇编算法是从代码段的初始位置一个接着一个解析指令,但是如果中途有数据插入,则后续的反汇编结构都是错误的
递归下降反汇编算法是尝试推测每条指令被执行后,程序会如何运行
调用约定
规定各函数之间的参数传递的约定称为调用约定
X86 32为架构的调用约定
__cdecl:参数从右向左依次压入栈中,操作完成后,参数被清理,返回值置于EAX中
__stdcall:参数从右向左依次压入栈中,操作完成后,参数被清理,返回值置于EAX中
__thiscall:将类方法的this指针放在ECX寄存器中,将其余参数压入栈中,为类方法专门优化的调用约定
__fastcall:将第一个参数放在ECX中,将最后一个参数放在EDX中,然后将后续的参数从右到左一个个压入栈中,加快调用的速度
__x86 64位架构的调用约定
Microsoft x64 调用约定:在windows上使用,从左至右将前四个参数放入RCX/RDX/R8/R9这四个寄存器中,然后将后续的参数从右向左依次压入栈中
SystemV x64 调用约定:在Linux、MacOS上使用,使用RDI/RSI/RDX/RCX/R8/R9这6个寄存器传递前6个参数,然后将后续的参数从右向左依次压入栈中
局部变量
在每次函数被调用的时候,程序从栈上分配一定的空间用来存储局部变量,栈的内容随着进栈和出栈会一直变化,但是每个函数中的局部变量相当于该栈帧的偏移量是一定的,可以引入一个寄存器来专门存储栈帧的位置(ebp)
常用逆向工具
DIA Pro
IDA可以对x86/x64、ARM、MIPS等架构,PE、ELF等格式的可执行文件进行静态分析和动态调试,具有从汇编语言到c语言伪代码的反编译功能
OllyDbg和x64dbg
OllyDbg是windows32位下的调试器,扩展性很强,x64dbg是windows64位下的调试器
GNU Binary Utilities
二进制文件分析工具链,包含很多工具
命令 | 功能 | 命令 | 功能 |
---|---|---|---|
as | 汇编器 | nm | 显示目标文件内的符号 |
ld | 链接器 | objcopy | 复制目标文件,过程中可以修改 |
gprof | 性能分析工具程序 | objdump | 显示目标文件的相关信息,也可以反汇编 |
addr2line | 从目标文件的虚拟机地址获取文件的行号和符号 | ranlib | 产生静态库的索引 |
ar | 可以对静态库进行创建、修改和取出操作 | readelf | 显示ELF文件的内容 |
c++filt | 解码c++语言的符号 | size | 列出总体和Section的大小 |
dlltool | 创建windows动态库 | strings | 列出任何二进制的可显示的字符串 |
gold | 另一种链接器 | strip | 从目标文件中移除符号 |
nlmconv | 可以转换为NetWare Loadable Module目标文件格式 | windmc/windres | 产生windows消息资源/windows资源编译器 |
GDB
这是GNU的一款命令行调试器,支持源码级调试,支持python语言编写扩展