逆向分析基础 0x01-0x0C
本笔记使用汇编指令为x86架构下汇编指令,ARM架构汇编指令不做介绍
0x01. 关于RE
- 逆向工程(Reverse Engineering RE)
- 逆向分析方法:
- 静态分析法:观察代码文件的外部特征、获取文件的类型(EXE、DLI、DOC、ZIP等)、大小、PE头信息、Import/Export API、内部字符串、是否运行时解压缩、注册信息、调试信息、数字证书等多种信息,使用反汇编查看内部代码
- 动态分析法:通过调试来分析代码流,获取内存状态,可以在观察文件、注册表(Registry)、网络等的
- 打补丁与破解:对应用程序文件或进程内存内容进行更改(破解是非法的)。
0x02. 分析Hello World
- 关键词
- PE (Portal Executive, 可移植、可执行)
- BP (Break Point)
- EP (Entry Point, 程序入口点)
- VA (Virtual Address, 虚拟地址),地址:进程的虚拟内存地址
- OP code (操作码)
- Win32中API的参数通过栈传递; VC++中默认字符串使用Unicode码表示,Unicode占用2byte
- OllyDbg:代码窗口、寄存器窗口、数据窗口、栈窗口
- 修改字符串的两种方法:
- 直接修改字符串缓冲区:可保存执行(新字符串长度不应比原字符串长)
- 在其他内存区域新建字符串并传递给消息函数:保存后不一定能执行,可能由文件offset造成
- 应用程序被加载到内存时有一个最小的内存分配大小,一般为1000。
- 启动函数(Stub code):不是用户编写的代码,而是由编译器任意添加的。编译程序时,不同的编译器会根据自身特点添加不同的启动函数。
0x03. 小端序标记法(字符串以NULL结尾0x0000)
- 大端序(Big endian):内存低地址存储高位,如UNIX服务的RISC系列、网络协议
- 小端序(Little endian):内存低地址存储低位(本书默认所有数据采用),如Intel x86 CPU
0x04. IA-32寄存器
(Intel Architecture 32 位, IA-32)
- 寄存器(Register):CPU内部用来存放数据的小型存储区域,与RAM(Random Access Memory,随机存储器、内存)不同,CPU访问RAM数据时要经过漫长的物理路径,所以花费时间长一些。
- 通用寄存器(8个,32bits):保存常量和地址
- EAX:(针对操作数和结果数据的)累加器
- EBX:(DS段中的数据指针)基址寄存器
- ECX:(字符串和循环操作)计数器
- EDX:(I/O指针)数据寄存器
注:以上Register主要用于算术运算(ADD、SUB、XOR、OR等)指令中,常保存常量和变量的值。特殊地,在LOOP中,ECX控制循环计数,每执行一次-1;EAX一般保存返回值,所有Win32 API函数中都会先把返回值保存到EAX再返回。 - EBP:(SS段中栈内数据指针)基址指针寄存器,行使栈帧指针的职能
- ESI:(字符串操作源指针)源变址寄存器
- EDI:(字符串操作目标指针)目的变址寄存器
- ESP:(SS段中指针)栈指针寄存器,指示栈区域的栈顶指针。
- 段寄存器(6个,16bits):
- CS:代码段寄存器,存储应用程序代码所在段的段基址
- SS:栈段寄存器
- DS:数据段寄存器
- ES:附加数据段寄存器
- FS:数据段寄存器
- GS:数据段寄存器
- 程序状态与控制寄存器(1个,32bits):
- EFLAGS:标志寄存器(CF-0:进位标志,无符号整数溢出时标记为1; ZF-6:零标志,运算结果为0,标记为1; OF-11:溢出标志,有符号整数溢出时标记为1,另外MSB(Most Significant Bit)改变时标记为1)
- 指令指针寄存器(1个,32bits):
- EIP:指令指针寄存器,保存着CPU要执行的指令地址,程序运行时,CPU读取EIP中一条指令,传送到指令缓冲区后,EIP的值会自动增加,增加大小即是指令的字节大小。另外我们不能直接修改EIP的值,只能通过间接指令修改,这些指令包括JMP、jcc、CALL、RET,此外还可以通过中断或异常来修改EIP的值。
0x05. 栈
- 用途:用于存储局部变量、函数参数、保存函数返回地址
- 特征:(栈是逆向拓展的)栈是一种高地址向低地址拓展的数据结构,即PUSH时栈顶指针减小向低地址移动、POP时栈顶指针增加向高地址移动。
0x06. 分析abex'crackme #1'
“crackme-破解我”
- 打补丁(patch):逆向分析中,将已有代码(或数据)覆盖为其他代码的行为。
- OllyDbg修改数据内容时在“数据窗口”使用“Ctrl+E”修改,修改汇编代码在“代码窗口”使用“空格”修改。
- 垃圾代码:故意在汇编代码中插入垃圾代码来迷惑逆向分析人员。
- NOP指令:(空指令)什么都不做,但会占用一个时钟周期;当指令需要延时是可以使用;也可用于指令的对齐(nop指令一个字节),使指令按字对齐,从而减少取指令时的内存访问次数。(一般用来内存地址偶数对齐,比如有一条指令,占3字节,这时候使用nop指令,cpu 就可以从第四个字节处读取指令了)。
0x07. 栈帧(Stack Frame)
栈帧用于在程序中申明局部变量、调用函数
- 利用栈帧指针(EBP寄存器)
PUSH EBP ; 函数开始
MOV EBP, ESP ; 保存当前ESP到EBP中
... ; 函数体
... ; 无论ESP值如何变化,EBP都保持不变,可以安全地访问函数的局部变量、参数
MOV ESP, EBP ; 将函数的起始地址返回到ESP,局部变量不在有效
POP EBP ; 函数返回前弹出保存在栈中的EBP值
RETN ; 函数终止,返回CALL指令的下一条指令
- 在栈中保存返回地址存在安全隐患,攻击者可利用缓冲区溢出技术把保存在栈中的返回地址更改为其他地址。
- 最新的编译器中带有“优化(Optimization)”选项,使用该选项编译简单函数时不会生成栈帧。
- 汇编与C的指针格式:
DWORD PTR SS:[EBP-4], 1
-->*(DWORD*)(EBP-4) = 1
(即:*(EBP-4) = (DWORD*)1
) - 执行
CALL
命令进入被调函数之前,CPU会先把函数的返回地址(即CALL
下一条指令的地址)压入栈中,用作函数执行完毕后的返回地址。 - POP:数据出栈之后只是不能访问使用,原来对应内存地址上保存的内容并没有删除。
- OllyDbg选项:Alt+O打开对话框,选择
-Disasm
可以设置显示“默认段”和“内存大小”;-Analysis 1
可以设置显示“局部变量”、“函数参数”。
0x08. 分析abex'crackme #2
- VB(主要用来编写GUI程序)文件可以编译为本机代码(N code)与伪代码(P code),本机代码一般使用易于调试器解析的IA-32指令;而伪代码是一种解释器(Interpreter)语言(类似于JVM、Python专用引擎等)。
- 汇编--C语言
TEST AX, Ax
JE 403408
if(AX == 0)
goto 403408
- 寻找函数起始点,一般从生成栈帧位置开始。
0x09. Process Explorer --最优秀的进程管理工具
- 左侧以Parent/Childern结构显示当前运行的进程;右侧显示各进程的PID、CPU占用率、注册信息等;下方显示加载到所选进程中的DLL信息,或所选进程的所有对象的句柄。
0x0A. 函数调用约定(Calling Convention)
- 栈就是定义在进程内部的一段内存空间,向下(低地址方向)扩展,且其大小被记录在PE头中;即进程运行时确定栈内存的大小(与malloc/new动态分配内存不同)。
- 函数调用约定的目的:解决函数调用之后应该如何处理ESP,约定主要包括三种:cdecl(主要在C语言中使用)、stdcall(常用于Win32 API)、fastcall
- cdecl:ESP由调用者处理,如
CALL xxxx
之后会有ADD ESP, 8
来调整栈顶指针位置。 - stdcall:ESP由被调用者处理,如在函数返回时
RETN 8
即RETN + POP 8
- fastcall:类似于stdcall,但通常使用寄存器来传递函数的部分参数(前2个,如ECX、EDX)
- cdecl:ESP由调用者处理,如
0x0B. 视频讲座
Lena在[http://www.tuts4you.com/]公示板上贴了40多个crackme讲座
- 确定参数个数:记录调用
CALL
指令之前的地址start和调用结束之后的地址end(可以在栈中查看),则参数个数为(end-start)/4
(因为是32bit,所以4bytes为一个参数)。
0x0C. 究竟如何学习代码逆向分析
- 自信与坚持多一点,就OK了
(下一部分为:RE-2 PE文件格式)