《逆向工程核心原理》笔记(1-16)
第一章/第二章 分析Hello World!程序
OD基本命令
-
Restart Ctrl+F2 重新开始调试
-
Step Into F7 执行语句会进入函数内部
-
Step Over F8 执行语句不会进入函数内部
-
Execute till Return Ctrl+F9 一直在函数代码内部运行,直到遇到retn跳出
(跳出该命令函数)
OD中右边注释中的红字部分是 代码调用的API函数名称 是VS为了保证程序正常运行自动添加的,源码中并没有,是VS的启动函数,可以暂时忽略。
RETN指令,它用于返回函数调用者的下一条命令,一般是被调用的函数的最后一句
●找到Helloworld!的main()主函数 未完成 没找到
OD调试器指令 | ||
---|---|---|
Go to | Ctrl+G | 移动到指定地址,用来查看代码或者内存,运行时不能用 |
Execute till Cursor | F4 | 执行到光标位置,即直接转到要调试的地址 |
Comment | ; | 添加注释 |
User-defined commet | 右键菜单Search for User-defined comment | |
Label | : | 添加标签 |
User-defined label | 右键菜单Search for User-defined label | |
Set/Reset BreakPoint | F2 | 设置或取消断点(BP)(BP就是断点 |
Run | F9 | 运行(若设置了断点,则执行至断点处) |
Show the current EIP | * | 显示当前EIP(命令指针)位置 |
Show the previous Cursor | - | 显示上一个光标的位置 |
Preview CALL/JMP address | Enter | 若光标处有CALL/JMP等指令,则跟踪并显示相关地址(运行时不可用) |
在菜单栏选择View-Breakpoints选项(快捷键ALT+B)打开Breakpoints窗口,该窗口列出代码中设置的断点。双击会跳到相应位置。
右键菜单Search for User-defined comment。可以看见所有添加的注释。双击会跳到相应位置。
找主函数的方法
<1>
1、 从头到尾地执行 直到消息窗口弹出 弹出消息窗口的就是主函数 F8
2、 第二遍 一直执行到弹出消息窗口的函数前 F7 进入 函数内部
<2>查找字符串
Tips
VC++中,static字符串会被默认保存为Unicode码形式,static字符串是指在程序内部被硬编码的字符串。
代码与数据所在的区域是彼此分开的。
<3>API检索
Helloworld!.exe这个程序会出弹窗
弹窗调用了user32.MessageBoxW()API
所以我们直接 右键 查找所有模块间的调用(All intermodular calls)该窗口会列出程序中调用的所有API
找 MessageBoxW()
<4>
上法的缺点: OD不能为所有.exe都列出API函数调用列表。使用压缩器/保护器工具对软件进行压缩或者保护后,文件结构机会改变。因此OD就不能列出所有API函数列表了。
Tips
压缩器:压缩软件的代码、数据、资源等,压缩后仍是.exe
保护器:不仅具有压缩功能还能反调试、反模拟、反储存..能够有效保护进程
DLL代码库被加载到进程内存后,可直接向DLL代码库添加断点。看不懂
API是操作系统对用户应用程序提供的一系列函数,实现于系统文件夹中的*.dll文件内部。
我们编写的应用程序执行某种操作时,必须使用OS提供的API向OS提出请求,然后与被调用API对应的系统DLL文件就会被加载到应用程序的进程内存。
该过程可以通过,OD菜单中的View-Memory(Alt+M),打开内存映射窗口,观察到。
若HelloWorld.exe应用程序中调用了MessageBoxW()API,则调用时程序运行到该处就会停止。
1、 OD中的Name in all modules(所有模块名称) 命令可以列出被加载的DLL文件中提供的所有API。名称排序。找到MessageBoxW。
2、 下断点 F2
3、 继续执行 F9 直到遇见MessageBoxW()代码的断点 自动停
4、 此时的ESP值对应一个返回地址 HelloWorld.exe的main()函数调用完MessageBoxW()之后,程序会回到该地址处。Ctrl+F9可以直接让MessageBoxW()到RETN处。F7也可以。
用打补丁的方式修改字符串
打补丁 可以修BUG 还可以加新功能
<1>直接修改字符串 缓冲区
右下 要选HEX/ASCII
直接智能搜索到 相应的字符串处 数据窗口跟随—>立即数
在ASCII区域选中 Ctrl+E 直接更改 保存到可执行文件 保存文件
此法的缺点在于不能比原字符串长,最好别
<2>程序中未被使用的NULL填充区域
找一块空地儿 Ctrl+E 修改
找到原字符串的地方修改汇编指令 空格键修改
把地址改成新地方的地址
保存
汇编语言基础指令 | |
---|---|
CALL XXXX | 调用XXXX地址处的函数 |
JMP XXXX | 跳转到XXXX地址处 |
PUSH XXXX | 保存XXXX到栈 |
RETN | 跳转到栈中保持的地址 |
术语和说明
VA | 进程的虚拟地址 |
---|---|
OP code | CPU指令 |
PE | windows可执行文件(EXE、DLL、SYS等) |
第三章 小端序标记法
字节序 是多字节数据在计算机内存中存放的字节顺序 主要分为 小端序 和 大端序
BYTE | b= | 0x12 |
---|---|---|
WOED | w= | 0x1234 |
DWORO | dw= | 0x12345678 |
char | str[]= | “abcde” |
大端序与小端序的不同比较
TYPE | NAME | SIZE | 大端序类型 | 小端序类型 |
---|---|---|---|---|
BYTE | b | 1 | [12] | [12] |
WOED | w | 2 | [12][34] | [34][12] |
DWORD | dw | 4 | [12][34][56][78] | [78][56][34][12] |
char[] | str | 6 | [61][62][63][64][65][00] | [61][62][63][64][65][00] |
Tip
字符串最后是以NULL结尾的
大端序存储数据时,内存地址低位 存储 数据的高位
小端序存储数据时,内存地址高位 存储 数据的高位
小端序是高位存高位 这是一直逆序存储的方式 保持的字节顺序被倒转
字符串在字符数组里存着,字符数组在内存中是连续的,所以这个时候无论是大小端序,存储的顺序都是一样的。
第四章IA-32寄存器基本讲解
寄存器是CPU内部用来存放数据的一些小型存储区域
基本程序运行寄存器
1、 通用寄存器8 通常用来保存常量与地址
2、 段寄存器6
3、 程序状态与控制寄存器1
4、 指令指针寄存器1 EIP
16-bit | 32-bit | |||
---|---|---|---|---|
AH | AL | AX | EAX | 累加器(针对操作数和结果数据) |
BH | BL | BX | EBX | 基址寄存器(DS段中的数据指针) |
CH | CL | CX | ECX | 计数器(字符串和循环操作) |
DH | DL | DX | EDX | 数据寄存器(I/O指针) |
BP | EBP | 拓展基址指针寄存器(SS段中栈内数据指针) | ||
SI | ESI | 源变址寄存器(字符串操作源指针) | ||
DI | EDI | 目的变址寄存器(字符串操作目标指针) | ||
SP | ESP | 栈指针寄存器(SS段中栈指针) |
<1>通用寄存器↓
ESP指示栈区域的栈顶地址,PUSH、POP、CALL、RET指令可以直接用来操作ESP
EBP表示栈区域的基地址,函数被调用时,保存ESP的值,函数返回时再把值返回ESP,保证栈不会崩溃。(栈帧技术)。
ESI和EDI与特定指令(LODS、STOS、REP、MOVS等)一起使用,主要用于内存复制。
装串 保存串 串传送
<2>段寄存器
段是一种内存保护技术,它把内存分段,保护内存。它还同
一起用于将虚拟内存变更为实际物理内存。段内存记录在SDT中,而段寄存器就持有这些SDT的索引。
段寄存器
CS | 代码段寄存器 | 存放应用程序代码所在段的段基址 |
---|---|---|
SS | 栈段寄存器 | 存放栈段的段基址 |
DS | 数据段寄存器 | 存放数据段的段基址 |
ES | 附加(数据)段寄存器 | 存放程序使用的附加数据段的段基址 |
FS | 数据段寄存器 | 同上 |
GS | 数据段寄存器 | 同上 |
FS常在程序调试中用到,用于计算SEH(结构化异常处理)、TEB(线程环境块)、PEB(进程环境块)等地址。
<3>标志寄存器EFLAGS
EFLAGS(32位)是由FLAGS(16位)扩展来的。
每位值都有意义。
初级阶段掌握 ZF(零标志)、OF(溢出标志)、CF(进位标志)即可。
ZF 运算结果0或1,True或False
OF 有符号整数溢出时,OF为1。MSB改变时,其值也为1。
CF 无符号整数溢出时,CF为1。
Tip
Jcc(条件跳转)指令要检查这3个标志的值,并根据其值决定是否执行某个动作。
<4>EIP指针指令寄存器
保存着CPU要执行的指令地址,由IP寄存器拓展的
EIP是不能直接修改的
程序运行时CPU读EIP中的一条指令的地址,EIP寄存器的值自己增加,CPU每次执行完一条指令,就会通过EIP寄存器读取并执行下一条指令。
第五章 栈
栈内存的作用
暂存函数内的局部变量、调用函数时传递函数参数、保存函数返回后的地址
栈是后进先出的
栈顶指针ESP初始状态指向栈底端
第六章 分析abex’crackme#1
大多数crackme小程序都让我们猜测序列号
如果直接用汇编语言写程序,汇编代码会直接变成反汇编代码
INC | 值加1 |
---|---|
DEC | 值减1 |
JMP | 跳转到指定地址 |
CMP | 比较给定的两个操作数*与SUB命令类似,但操作数的值不会改变,仅改变EFLAGS寄存器(若2个操作数的值一致,SUB结果为0,ZF被置为1) |
JE | 条件跳转*若ZF为1就跳转 |
第七章 栈帧
栈帧:利用EBP寄存器访问栈内局部变量、参数、函数返回地址等手段
调用函数时,先把用作基准点的ESP值保存到EBP,并维持在函数内部。所以是以EBP的值作为基准编写程序的。
在执行完
PUSH EBP
MOV EBP ESP
两条命令后函数main()的栈帧就生成好了(设置好EBP了)
调用某一个函数之前要先使用PUSH把参数a、b压入栈,要清理栈就是让ESP+参数的字节数,就能把它们从栈中清理掉。
Tips
被调函数执行完毕后,函数的调用者Caller负责清理存储在栈中的参数,这被称为cdecl方式
Caller复制清理保存在栈中的参数,这被称为stdcall方式
这些规则统称为调用约定Calling Convention
Return 0的汇编写法可以为XOR EAX,EAX
两个相同的值进行异或运算结果为0
这样写比MOV EAX,0更快,所以常用来初始化寄存器
第八章abex’crackme#2
VB文件的特点 VB文件使用名为MSVBVM60.dll的VB专用引擎
可以编译为本地代码(N code)和伪代码(P code)
VB主要用来编写GUI程序
VB使用的各种信息以结构体的形式保存在文件内部
VC++、VB编译器通常用间接调用的方法,调用ThunRTMain()函数,这个函数在它的专用引擎中
TEST:逻辑比较
与AND一样(仅改变EFLAGS寄存器而不改变操作数的值),若2个操作数中一个为0,则AND运算结果被置为0->ZF=1
TEST AX,AX 是为了检测AX是否为0,这是汇编语法的特征
例子:
TEST AX,AX
JE 403408
If(AX==0)
Goto 403408
JE:条件跳转
若ZF=1,则跳转
第九章 进程管理工具
Process explorer
Sysinternals 上面那个的迷你版
第十章 函数调用约定
函数调用约定:对函数调用时如何传递参数的一种约定。
调用函数前要先把参数压入栈再传递给函数,栈就是定义在进程中的一小段内存空间。进程运行时,栈内存大小才确定。
函数执行完毕后,栈中的参数不必管。因为存在栈里的是临时存的,下一次再用这个栈存东西的时候,会覆盖掉之前的参数。
函数执行完毕后,ESP的值要恢复到函数调用之前。ESP是用来指示栈当前的位置的,栈内存是固定的,所以ESP不恢复初始的话,这个栈就不能用了。
主要函数的约定有如下三种:
优点在于
Cdecl | 调用者处理栈 | 可以向被调用函数传递长度可变的参数 |
---|---|---|
Stdcall | 被调用者清理栈 | 被调用者函数内部存在着栈清理代码,使得代码尺寸更小 |
Fastcall | 与上类似,不同在于该方式通常会使用寄存器去传参 | 可以实现对函数的快速调用 |
C语言默认使用Cdecl的方式
Stdcall常用于Win32 API Win32 API虽然是c语言写的库但用的是Stdcall的方式,这可以使C语言之外的其他语言也能直接调用API
第十一章 视频讲座
www.tuts4you.com 的公示板上有40个crackme讲座可供练习
要去除消息框
可以清理栈 但要注意调用rtcMsgBox()函数后返回值要是1(表示确定的按钮)
找到跳出该弹窗的命令 CALL XXXX修改
正确命令如下
ADD ESP,14 14是传递参数的大小
MOV EAX,1
还可以从根头跳过rtcMsgBox()函数 直接改函数开头
PUSH EBP
MOV EBP,ESP
修改成
RETN 4
此处根据要传递的参数大小调整栈 RETN XX
__vbaStrCmp()API 是VB中比较字符串的函数
第十三章 PE文件格式
PE文件是Windows操作系统下使用的可执行文件格式。
PE文件种类
种类 | 主拓展名 | 种类 | 主拓展名 |
---|---|---|---|
可执行系列 | EXE、SCR(屏保文件) | 驱动程序系列 | SYS、VXD(虚拟驱动文件) |
库系列 | DLL、OCX、CPX、DRX | 对象文件系列 | OBJ(对象文件) |
除OBJ之外都是可执行的,DLL、SYS是用其他方法间接执行的。OBJ不能以任何形式执行,所以逆向分析中一般不管它。
PE头:从DOS头到 是由许多结构体构成的
PE体:其他
节区
文件加载到内存时,节区的大小和位置等都会发生变化
文件的内容一般可以分为代码(.text)、数据(.data)、资源节(.rsrc),分别保存。
用不同的开发工具与编译选项,节区的名称、大小、个数、存储的内容都是不同的。它们按不同的用途分类保存到不同的节中。
各节区的头就定义了各节区在文件活内存中的大小、位置、属性等。
PE头跟节区尾放在同一个地方,称为NULL填充。
重点 文件/内存中节区的起始位置应该在各文件/内存最小单位的倍数位置上
VA指的是进程虚拟内存的绝对地址
RVA指的是从某个基准位置开始的相对地址
RVA+imageBase=VA
PE头内部文件信息大多都是以RVA形式存在。用RVA的话即使发生了重定位,只要基准位置的相对地址没有变化,就能正常访问。
DOS头
IMAGE_DOS_HEADER结构体
用来拓展已有的DOS EXE头
该结构体大小为64字节
有两个重要成员
1、 e_magic DOS签名
2、e_lfanew 指示NT头的偏移(根据不同文件拥有可变值)
开始字节 4D5A
e_lfanew 000000E0(这里是用的小端序标识法)
DOS存根
在DOS头下方,大小不定,没它文件也能运行,由代码与数据混合而成
有这个存根就可以让DOS调试器(debug.exe)运行它,DOS调试器不认PE文件格式
NT头
IMAGE_NT_HEADERS
签名 值为50450000h
文件头:表现文件大致属性的IMAGE_FILE_HEADER结构体
1、Machine:每个CPU都有不同的此码
2、NumberOfSections:用来指出文件中的节区数量,此值必大于0,且与实际不符时会运行错误
3、SizeOfOptionalHeader:用来指出IMAGE_NT_HEADERS的最后一个成员IMAGE_OPTIONAL_HEADER32的长度
4、Characteristics:用于标识文件的属性,是否可运行,是否为DLL文件
IMAG_OPTIONAL_HEADER32
(可选头)其成员是文件运行所必需的
1、Magic:IMAG_OPTIONAL_HEADER32 时,Magic码为10B;IMAG_OPTIONAL_HEADER64时,MaGic码为20B
2、AddressOfEntryPoint持有EP的RVA值,指出代码的起始位置
3、ImageBase:指出文件的优先装入地址
4、SectionAlignment,FileAlignment前者指定了节区在内存中的最小单位后者指定了节区在磁盘文件中的最小单位
5、SizeOfImage指定了PEImage在虚拟内存中所占的空间的大小
6、SizeOfHeaders指出整个PE头大小。第一节区所在位置与SizeOfHeaders距文件开始偏移的量相同
7、Subsystem其值用来区分系统驱动文件(.sys)和普通可执行文件(.exe、*.dll)可选头
8、NumberOfRvaAndSize用来指定DateDirectory程序数组个数 (该程序是IMAGE_OPTIONAL_HEADER32J 结构体的最后一个成员)
9、DataDiretory是IMAGE_OPTIONAL_DIRECTORY结构 体组成的数组
节区头
记录了各节区的属性,节区头由IMAG_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区。
重要成员
项目 | 含义 |
---|---|
Virtual | 内存中节区所占大小 |
VirtualAddress | 内存中节区起始地址RVA |
SizeOfRawData | 磁盘文件中节区所占的大小 |
PointerToRawData | 磁盘文件中节选起始位置 |
Characteristics | 节区属性(bit OR) |
值 | 含义 | 备注 |
---|---|---|
1 | Driver文件 | 系统驱动 |
2 | GUI文件 | 窗口应用程序 |
3 | CUI文件 | 控制台应用程序 |
Tips
磁盘文件中的PE与内存中的PE不同,所以将装载到内存中的PE形态称为映像
PE文件装载到内存时,每个节区都准确完成内存地址与文件偏移间的映射,这种映射被称为RVA to RAW
该过程的方法是
1、查找RVA所在节区
2、使用简单的公式计算文件偏移(RAW)
RAW=RVA-VirtualAddress+PointerToRawData
P105不会算
IAT
IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。
DLL(动态链接库)
优点
不把库包含到程序中,组成单独的DLL文件,需要时调用,节省空间
内存映射技术使加载后的DLL代码、资源在多个进程中实现共享
更新库时只要替换相关DLL文件
加载方式
1、 显式链接:程序使用DLL时加载,用完后释放
2、 隐式链接:程序开始时就加载,程序终止时释放
DLL重定位 DLL文件的ImageBase的值一般为10000000。程序在使用a.dll和B.dll时,先往10000000装a.dll再尝试装b.dll,装不下的话就会找其他空白的内存空间装。这就导致了,我们无法对实际地址硬编码。无法保证DLL一定会被加载到ImageBase处。
IMAGE_IMPORT_DESCRIPTOR
(也称IMPORT Directory Table)(存在PE体中)
IMAGE_IMPORT_DESCRIPTOR结构体中记录着PE文件要导入哪些库文件。
Import:导入,向库提供服务(函数)
Export:导出从库向其他PE文件提供服务(函数)
执行一个程序的时候要导入多个库,导入多少库就存在多少各IMAGE_IMPORT_ DESCRIPTOR结构体,这些结构体组成数组,最后以NULL结构体结束。
重要成员
OriginalFirstThunk | INT的地址(RVA) |
---|---|
Name | 库名称字符串的地址(RVA) |
FirstThunk | IAT的地址(RVA) |
Tips
PE头中的Table即数组
INT和IAT都是长整型数组,以NULL结束
INT和IAT大小应该相同
IAT输入顺序
库名称Name
Name是一个字符串指针,指向导入函数所属的库文件名称。
文件偏移P110 RVA->RAW是怎么计算的?
OriginalFirstThunk-INT
INT是一个包含导入函数的信息的结构体指针数组。
IMAGE_IMPORT_BY_NAME
以000F(固有编号)为Ordinal打头,NULL结束。
FirstThunk-IAT
与INT类似,由结构体指针数组组成,以NULL结尾。IAT的第一个元素值是硬编码的(76324906),当文件载入内存时,这个地址会被取代。
Tips
Windows系统的DLL 文件不会发生重定位,它拥有自身固有的ImageBase
EAT
通过EAT才能准确得到相应库中导出函数的起始地址。
EAT与IAT一样,是PE文件内特定结构体保存着导出信息。唯一的说明EAT的结构体是IMAGE_EXPORT_DIRECTORY。这个结构体以数组形式存在,拥有多个成员,这样可以同时导入多个库。
RVA与文件偏移间的转换过程。怎么算?
IMAGE_EXPORT_DIRECTORY
NumberOfFunctions | 实际Export |
---|---|
NumberOfNames | Export函数中具名的函数个数 |
AddressOfFunctions | Export函数地址数组(数组元素个数= AddressOfFunctions) |
AddressOfNames | 函数名称地址数组(数组元素个数= AddressOfNames) |
AddressOfNameOrdinals | Ordinal地址数组(数组元素个数= AddressOfNameOrdinals) |
(重要成员)
Tips
没有函数名称的函数,通过Ordinal查找到它们的地址。
高级PE
推荐PEView.exe
Patched PE是符合PE规范的,非常规PE文件。
PE规范只是一种标准规范。
PE文件的填充只是为了让它对齐基本单位。
Ordinal相当于导出函数的固有编号
第十四章 运行时压缩
数据压缩
无损压缩(文件能百分百恢复)zip、rar
有损压缩 jpg、mp3、mp4 mp3的压缩核心算法是,通过删除超越人类听觉范围的波长区段来缩减数据大小。
运行时压缩器
普通压缩 | 运行时压缩 | |
---|---|---|
对象文件 | 所有文件 | PE文件(exe、dll、sys) |
压缩结果 | 压缩文件(zip、rar) | PE文件(exe、dll、sys) |
解压缩方式 | 用专门的解压缩程序 | 内部含义解码程序 |
文件是否可执行 | 本身不可执行 | 可以 |
运行时压缩器是针对PE文件的
压缩器 使用目的:使PE文件减小、隐藏PE文件内部代码与资源
保护器 保护PE文件免受代码逆向分析的程序
运行时压缩测试
第十五章 调试UPX压缩的notepad程序
快速找到UPX OEP的方法:UPX压缩器的特征是,其EP代码被包含在PUSHAD/POPAD之间,且跳转到OPE的代码的JMP紧接着POPAD出现。
所以只要在JMP指令处下断点,运行后就能直接找到OEP。
硬件断点,是执行完当前断点指令后才暂停调试,所以此时在JMP下硬件断点就可以直接跳到OPE了。
第十六章基址重定位表
PE重定位
PE重定位指的是 PE文件无法加载到ImageBase所指的位置,而是被加载到其他地址时发生的一系列处理的行为。
EXE
PE重定位时执行的操作
PE重定位时执行的原理
基址重定位表
IMAGE_BASE_RELOCATION结构体
基址重定位表的分析方法