逆向工程核心原理
逆向工程核心原理
那么,读者应该如何使用本书学习逆向分析技术呢?对此,我给出如下几点建议,供各位参考。 第一,技术书不是装饰书架的道具,它们是提高各位技术水平的工具。所以阅读时要勾画出重要部分,在书页空白处写下自己的想法与心得等。阅读时,在书页上记录相关技术、注意事项、技术优缺点、与作者的不同见解等,让它成为只属于你的书。读完这样一本逆向分析技术书后,不知不觉间就构建出自己独特的逆向分析世界,最终成为代码逆向分析专家。
第二,拥有积极乐观的心态。逆向分析是一项深奥的技术,会涉及OS底层知识。要学的内容很多,并且大部分内容需要亲自测试并确认才能最终理解。必须用积极乐观的心态对待这一过程,学习逆向技术无关聪明与否,只跟投入时间的多少有关。学习时,不要太急躁,请保持轻松的心态。
第三,不断挑战。逆向分析不尽如人意时,不要停下来,要尝试其他方法,不断挑战。要相信一定会有解决的方法,可能几年前早已有人成功过了。搜索相关资料并不断尝试,不仅能提高自身技术水平,解决问题后,心里还能感受到一种成就感。这样的成功经验一点点积累起来,自信心就会大大增强,自身的逆向分析水平也会得到明显提高。这种从经验中获得的自信会不知不觉地对逆向分析过程产生积极影响,让逆向分析往更好的方向发展。
希望本书能够帮助各位把“心愿表”上的愿望一一实现,也希望各位把本书讲解的知识、技术广泛应用到逆向分析过程中,发挥更大的作用。谢谢。
第一部分 代码逆向技术基础
第1章 关于逆向工程
1.1 逆向工程
1.2 代码逆向工程
1.2.1 逆向分析法
1.2.2 源代码、十六进制代码、汇编代码
1.2.3 “打补丁”与“破解”
1.3 代码逆向准备 1.3.1 目标 1.3.2 激情 1.3.3 谷歌
1.4 学习逆向分析技术的禁忌 1.4.1 贪心 1.4.2 急躁 1.5 逆向分析技术的乐趣
第2章 逆向分析Hello World!程序
2.1 Hello World!程序
2.2 调试HelloWorld. exe程序 2.2.1 调试目标 2.2.2 开始调试 2.2.3 入口点 2.2.4 跟踪40270C函数 2.2.5 跟踪40104F跳转语句 2.2.6 查找main()函数
2.3 进一步熟悉调试器 2.3.1 调试器指令 2.3.2 “大本营” 2.3.3 设置“大本营”的四种方法
2.4 快速查找指定代码的四种方法 2.4.1 代码执行法 2.4.2 字符串检索法 2.4.3 API检索法 (1): 在调用代码中设置断点 2.4.4 API检索法 (2): 在API代码中设置断点
2.5 使用“打补丁”方式修改“HelloWorld!”字符串 2.5.1 “打补丁” 2.5.2 修改字符串的两种方法
2.6 小结
第3章 小端序标记法
3.1 字节序 3.1.1 大端序与小端序 3.1.2 在OllyDbg中查看小端序
第4章 IA-32寄存器基本讲解
4.1 什么是CPU寄存器 4.2 IA-32寄存器
- 通用寄存器(General Purpose Registers,32位)
- 各寄存器的名称如下所示。
- EAX:(针对操作数和结果数据的)累加器
- EBX:(DS段中的数据指针)基址寄存器
- ECX:(字符串和循环操作的)计数器
- EDX:(I/O指针)数据寄存器
- 此外,ECX与EAX也可以用于特殊用途。循环命令(LOOP)中,ECX用来循环计数(loop count),每执行一次循环,ECX都会减1.EAX一般用在函数返回值中,所有Win32API函数都会先把返回值保存到EAX再返回。
- EBP:(SS段中栈内数据指针)扩展基址指针寄存器
- ESI:(字符串操作源指针)源变址寄存器
- EDI:(字符串操作目标指针)目的变址寄存器
- ESP:(SS段中栈指针)栈指针寄存器
- 以上4个寄存器主要用作保存内存地址的指针。
- ESP指示栈区域的栈顶地址,某些指令(PUSH、POP、CALL、RET)可以直接用来操作ESP(栈区域管理是程序中相当重要的部分,请不要把ESP用作其他用途)。
- EBP表示栈区域的基地址,函数被调用时保存ESP的值,函数返回时再把值返回ESP,保证栈不会崩溃(这称为栈帧(StackFrame)技术,它是代码逆向分析技术中的一个重要概念,后面会详细讲解)。
- ESI和EDI与特定指令(LODS、STOS、REP、MOVS等)一起使用,主要用于内存复制。
- 各寄存器的名称如下所示。
- 段寄存器(Segment Registers,16位,6个)
- CS: Code Segment, 代码段寄存器
- SS: Stack Segment, 栈段寄存器
- DS: Data Segment,数据段寄存器
- ES: Extra(Data) Segment, 附加(数据)段寄存器
- FS: Data Segment, 数据段寄存器
- GS: Data Segment, 数据段寄存器
- 程序状态与控制寄存器(Program Status and Control Registers,32位,1个)
- EFLAGS: Flag Register, 标志寄存器
- 学习代码逆向分析技术的初级阶段,只要掌握3个与程序调试相关的标志即可,分别为ZF(ZeroFlag,零标志)、OF (Overflow Flag, 溢出标志)、CF(Carry Flag, 进位标志)。
- ZF 若运算结果为0, 则其值为1(True), 否则其值为0(False).
- OF 有符号整数(signed integer) 溢出时, OF值被置为1。此外, MSB(Most Significant Bit,最高有效位)改变时,其值也被设为1.
- CF 无符号整数(unsigned integer)溢出时,其值也被置为1.
- 指令指针寄存器(Instruction Pointer,32位,1个)
- EIP: Instruction Pointer, 指令指针寄存器
- 指令指针寄存器保存着CPU要执行的指令地址,其大小为32位(4个字节),由原16位IP寄存器扩展而来。程序运行时,CPU会读取EIP中一条指令的地址,传送指令到指令缓冲区后,EIP寄存器的值自动增加,增加的大小即是读取指令的字节大小。这样,CPU每次执行完一条指令,就会通过EIP寄存器读取并执行下一条指令。
- 与通用寄存器不同,我们不能直接修改EIP的值,只能通过其他指令间接修改,这些特定指令包括JMP、Jcc、CALL、RET.此外,我们还可以通过中断或异常来修改EIP的值。
Intel® 64 和 IA-32 架构软件开发人员手册合并卷:1、2A、2B、2C、2D、3A、3B、3C、3D 和 4
- Volume 1: Basic Architecture
- CHAPTER 3 BASIC EXECUTION ENVIRONMENT
- 3.4 BASIC PROGRAM EXECUTION REGISTERS
- 3.4.1 General-Purpose Registers
- 3.4.2 Segment Registers
- 3.4.3 EFLAGS Register
- 3.4.3.1 Status Flags
- 3.4 BASIC PROGRAM EXECUTION REGISTERS
- CHAPTER 3 BASIC EXECUTION ENVIRONMENT
4.3 小结
第5章 栈
5.1 栈
5.1.1 栈的特征
5.1.2 栈操作示例
向栈压入数据时,栈顶指针减小,向低地址移动;从栈中弹出数据时,栈顶指针增加,向高地址移动。
第6章 分析abex' crackme#1
6.1 abex' crackme #1 6.1.1 开始调试 6.1.2 分析代码
PUSH 入栈指令
CALL 调用指定位置的函数
INC 值加1
DEC 值减1
JMP 跳转到指定地址
CMP
比较给定的两个操作数 *与SUB命令类似,但操作数的值不会改变,仅改变EFLAGS寄存器(若2个操作数的值一致,SUB结果为0,ZF被置为1)
JE 条件跳转指令 (Jump if equal) *若ZF为1, 则跳转
6.2 破解
6.3 将参数压入栈
6.4 小结
第7章 栈帧
7.1 栈帧
栈帧(StackFrame)相关知识,栈帧在程序中用于声明局部变量、调用函数。
简言之,栈帧就是利用EBP(栈帧指针,请注意不是ESP)寄存器访问栈内局部变量、参数、函数返回地址等的手段。
为什么使用EBP而不是ESP? 程序运行中,ESP寄存器的值随时变化,访问栈中函数的局部变量、参数时,若以ESP值为基准编写程序会十分困难,并且也很难使CPU引用到准确的地址。 所以,调用某函数时,先要把用作基准点(函数起始地址)的ESP值保存到EBP,并维持在函数内部。 这样,无论ESP的值如何变化,以EBP的值为基准(base)能够安全访问到相关函数的局部变量、参数、返回地址,这就是EBP寄存器作为栈帧指针的作用。
栈帧结构
PUSH EBP ;函数开始(使用EBP前先把已有值保存到栈中)
MOV EBP, ESP ;保存当前ESP到EBP中
;函数体
;无论ESP值如何变化,EBP都保持不变,可以安全访问函数的局部变量、参数
MOV ESP, EBP ;将函数的起始地址返回到ESP中
POP EBP ;函数返回前弹出保存在栈中的EBP值
RETN ;函数终止
借助栈帧技术管理函数调用时,无论函数调用的深度有多深、多复杂,调用栈都能得到很好的管理与维护。
提示
- 最新的编译器中都带有一个“优化”(Optimization)选项,使用该选项编译简单的函数将不会生成栈帧。
- 在栈中保存函数返回地址是系统安全隐患之一,攻击者使用缓冲区溢出技术能够把保存在栈内存的返回地址更改为其他地址。
7.2 调试示例:stackframe. exe
7.2.1 StackFrame.cpp
#include "stdio.h"
long add(long a, long b)
{
long x = a, y = b;
return (x+y);
}
int main(int argc, char* argv[])
{
long a = 1, b = 2;
long c = add(a, b);
printf("Sum of %ld and %ld is %ld\n", a, b, c);
return 0;
}
7.2.2 开始执行main()函数&生成栈帧
004D18D0 <stackfram | 55 | push ebp | StackFrame.c:12
004D18D1 | 8BEC | mov ebp,esp |
004D18D3 | 81EC E4000000 | sub esp,E4 | 生成栈空间
004D18D9 | 53 | push ebx |
004D18DA | 56 | push esi |
004D18DB | 57 | push edi |
004D18DC <stackfram | 8D7D DC | lea edi,dword ptr ss:[ebp-24] | 初始化局部变量空间
004D18DF | B9 09000000 | mov ecx,9 |
004D18E4 | B8 CCCCCCCC | mov eax,CCCCCCCC |
004D18E9 | F3:AB | rep stosd |
7.2.3 设置局部变量
004D18F6 | C745 F8 01000000 | mov dword ptr ss:[ebp-8],1 | 把数据1保存到[EBP-8]中,[EBP-8]代表局部变量a
004D18FD | C745 EC 02000000 | mov dword ptr ss:[ebp-14],2 | 把数据1保存到[EBP-14]中,[EBP-14]代表局部变量b
提示 DWORD PTR SS:[EBP-4]语句中, SS是Stack Segment的缩写,表示栈段。由于Windows中使用的是段内存模型(Segment Memory Model), 使用时需要指出相关内存属于哪一个区段。其实,32位的Windows OS中, SS、DS、ES的值皆为0, 所以采用这种方式附上区段并没有什么意义。因EBP与ESP是指向栈的寄存器,所以添加上了SS寄存器。请注意,“DWORD PTR”与“SS:”等字符串可以通过设置OllyDbg的相应选项来隐藏。
7.2.4 add()函数参数传递与调用
004D1904 | 8B45 EC | mov eax,dword ptr ss:[ebp-14] |
004D1907 | 50 | push eax | arg2
004D1908 | 8B4D F8 | mov ecx,dword ptr ss:[ebp-8] |
004D190B | 51 | push ecx | arg1
004D190C | E8 12F7FFFF | call stackframe.4D1023 | call的同时会push下一个指令的地址004D1911,也叫做返回地址
返回地址 执行CALL命令进入被调用的函数之前,CPU会先把函数的返回地址压入栈,用作函数执行完毕后的返回地址。
0041FA90 004D1911 return to stackframe.__$EncStackInitEnd+26 from stackframe.__enc$textbss$end+23
0041FA94 00000001
0041FA98 00000002
7.2.5 开始执行add()函数&生成栈帧
004D18 | 55 | push ebp | StackFrame.c:6
004D18 | 8BEC | mov ebp,esp |
004D18 | 81EC D8000000 | sub esp,D8 |
可以看到,main()函数使用的EBP值被备份到栈中,然后EBP的值被设置为一个新值。
7.2.6 设置add()函数的局部变量(x,y)
004D18 | 8B45 08 | mov eax,dword ptr ss:[ebp+8] | 取出参数a
004D18 | 8945 F8 | mov dword ptr ss:[ebp-8],eax | 设置局部变量值
004D18 | 8B45 0C | mov eax,dword ptr ss:[ebp+C] | 取出参数b
004D18 | 8945 EC | mov dword ptr ss:[ebp-14],eax | 设置局部变量值
0040FCC8 CCCCCCCC
0040FCCC 00000002
0040FCD0 CCCCCCCC
0040FCD4 CCCCCCCC
0040FCD8 00000001
0040FCDC CCCCCCCC
0040FCE0 0040FDE0 EBP
0040FCE4 004D1911 return to stackframe.__$EncStackInitEnd+26 from stackframe.__enc$textbss$end+23
0040FCE8 00000001
0040FCEC 00000002
7.2.7 ADD运算
004D18A2 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] | 使用局部变量计算
004D18A5 | 0345 EC | add eax,dword ptr ss:[ebp-14] |
7.2.8 删除函数add()的栈帧&函数执行完毕(返回)
004D18B8 | 8BE5 | mov esp,ebp |
004D18BA | 5D | pop ebp |
004D18BB | C3 | ret |
上面这条命令用于恢复函数add()开始执行时备份到栈中的EBP值,它与401000地址处的PUSH EBP命令对应。EBP值恢复为12FF40, 它是main()函数的EBP值。到此, add()函数的栈帧就被删除了。
0040FCE4 004D1911 return to stackframe.__$EncStackInitEnd+26 from stackframe.__enc$textbss$end+23
0040FCE8 00000001
0040FCEC 00000002
7.2.9 从栈中删除函数add()的参数(整理栈)
004D1904 | 8B45 EC | mov eax,dword ptr ss:[ebp-14] | StackFrame.c:14
004D1907 | 50 | push eax |
004D1908 | 8B4D F8 | mov ecx,dword ptr ss:[ebp-8] | ecx:_2339CD9D_StackFrame@c
004D190B | 51 | push ecx | ecx:_2339CD9D_StackFrame@c
004D190C | E8 12F7FFFF | call stackframe.4D1023 |
004D1911 | 83C4 08 | add esp,8 |
0040FCE8 00000001
0040FCEC 00000002
0040FCF0 <&EntryP 004D1028 ESP
0040FCF4 <&EntryP 004D1028 stackframe.__enc$textbss$end+28
提示
- 被调函数执行完毕后,函数的调用者(Caller)负责清理存储在栈中的参数,这种方式称为cdecl方式;
- 反之,被调用者(Callee)负责清理保存在栈中的参数,这种方式称为stdcall方式。这些函数调用规则统称为调用约定(Calling Convention), 这在程序开发与分析中是一个非常重要的概念,第10章将进一步讲解相关内容。
7.2.10 调用printf()函数
004D1914 | 8945 E0 | mov dword ptr ss:[ebp-20],eax |
004D1917 | 8B45 E0 | mov eax,dword ptr ss:[ebp-20] | StackFrame.c:15
004D191A | 50 | push eax |
004D191B | 8B4D EC | mov ecx,dword ptr ss:[ebp-14] |
004D191E | 51 | push ecx |
004D191F | 8B55 F8 | mov edx,dword ptr ss:[ebp-8] |
004D1922 | 52 | push edx |
004D1923 | 68 307B4D00 | push stackframe.4D7B30 | 4D7B30:"Sum of %ld and %ld is %ld\n"
004D1928 | E8 AAF7FFFF | call stackframe.4D10D7 |
004D192D | 83C4 10 | add esp,10 |
7.2.11 设置返回值
004D1930 | 33C0 | xor eax,eax | StackFrame.c:16
XOR命令用来进行Exclusive OR bit(异或)运算,其特点为“2个相同的值进行XOR运算,结果为0”.XOR命令比MOV EAX,0命令执行速度快,常用于寄存器的初始化操作。
7.2.12 删除栈帧& main()函数终止
004D1942 | 8BE5 | mov esp,ebp |
004D1944 | 5D | pop ebp |
004D1945 | C3 | ret |
7.3 设置OllyDbg选项 7.3.1 Disasm选项 7.3.2 Analysis1选项
7.4 小结
第8章 abex' crackme#2
8.1 运行abex' crackme#2
8.2 Visual Basic文件的特征 8.2.1 VB专用引擎 8.2.2 本地代码和伪代码 8.2.3 事件处理程序 8.2.4 未文档化的结构体
8.3 开始调试
00401238 | 68 141E4000 | push abex' crackme #2.401E14 |
0040123D | E8 F0FFFFFF | call <JMP.&ThunRTMain> |
8.3.1 间接调用
8.3.2 RT MainStruct结构体
8.3.3 ThunRTMain()函数
8.4 分析crackme
8.4.1 检索字符串
TEST: 逻辑比较(Logical Compare) 与bit-wise logical`AND'一样(仅改变EFLAGS寄存器而不改变操作数的值)若2个操作数中一个为0,则AND运算结果被置为0 -> ZF = 1 。 JE: 条件转移指令(Jump if equal) 若ZF=1, 则跳转。
8.4.2 查找字符串地址
00403321 | 8D55 BC | lea edx,dword ptr ss:[ebp-44] |
00403324 | 8D45 CC | lea eax,dword ptr ss:[ebp-34] |
00403327 | 52 | push edx |
00403328 | 50 | push eax |
00403329 | FF15 58104000 | call dword ptr ds:[<&__vbaVarTstEq>] |
0040332F | 66:85C0 | test ax,ax |
00403332 | 0F84 D0000000 | je abex' crackme #2.403408 |
8.4.3 生成Serial的算法
00402ED0 | 55 | push ebp |
00402ED1 | 8BEC | mov ebp,esp |
NOP: No Operation, 不执行任何动作的指令(只消耗CPU时钟)。
8.4.4 预测代码
00402F8E | 8D95 78FFFFFF | lea edx,dword ptr ss:[ebp-88] | [ebp-88]:L"BIEZHIHUA"
00402F94 | 52 | push edx |
00402F95 | 56 | push esi |
00402F96 | 8B0E | mov ecx,dword ptr ds:[esi] |
00402F98 | FF91 A0000000 | call dword ptr ds:[ecx+A0] |
8.4.5 读取Name字符串的代码
8.4.6 加密循环
00403102 | BB 04000000 | mov ebx,4 |
0040318B | FF15 30104000 | call dword ptr ds:[<&__vbaVarForInit>] |
00403191 | 8B1D 4C104000 | mov ebx,dword ptr ds:[<&rtcMidCharVar>] |
00403197 | 85C0 | test eax,eax |
00403199 | 0F84 06010000 | je abex' crackme #2.4032A5 |
0040329A | FF15 C0104000 | call dword ptr ds:[<&__vbaVarForNext>] |
004032A0 | E9 F2FEFFFF | jmp abex' crackme #2.403197 |
004032A5 | 8B45 08 | mov eax,dword ptr ss:[ebp+8] |
8.4.7 加密方法
8.5 小结
第9章 Process Explorer———最优秀的进程管理工具
9.1 Process Explorer 9.2 具体有哪些优点呢 9.3 sysinternals
第10章 函数调用约定
10.1 函数调用约定
函数调用约定(Calling Convention) |
主要的函数调用约定如下。
- cdecl
- stdcall
- fastcall
术语说明
- 调用者————调用函数的一方。
- 被调用者————被调用的函数。
- 比如在main()函数中调用printf()函数时,调用者为main(),被调用者为printf().
10.1.1 cdecl
cdecl是主要在C语言中使用的方式,调用者负责处理栈。
int add(int a, int b)
{
return a + b;
}
int main(int argc, char* argv[])
{
return add(1, 2);
}
00EF17F2 | 6A 02 | push 2 | cdecl.c:12
00EF17F4 | 6A 01 | push 1 |
00EF17F6 | E8 28F8FFFF | call cdecl.EF1023 |
00EF17FB | 83C4 08 | add esp,8 |
00EF1F10 | 55 | push ebp | exe_common.inl:77
00EF1F11 | 8BEC | mov ebp,esp |
00EF1F13 | 83EC 0C | sub esp,C |
00EF1F16 | E8 83F3FFFF | call cdecl.EF129E | exe_common.inl:78
00EF1F1B | 8945 FC | mov dword ptr ss:[ebp-4],eax | [ebp-4]:&"ALLUSERSPROFILE=C:\\ProgramData"
00EF1F1E | E8 85F3FFFF | call cdecl.EF12A8 |
00EF1F23 | 8B00 | mov eax,dword ptr ds:[eax] |
00EF1F25 | 8945 F8 | mov dword ptr ss:[ebp-8],eax | [ebp-8]:&"E:\\Projects\\VisualStudioProjects\\cdecl\\Debug\\cdecl.exe"
00EF1F28 | E8 3CF1FFFF | call cdecl.EF1069 |
00EF1F2D | 8B08 | mov ecx,dword ptr ds:[eax] | ecx:_E28DA64E_cdecl@c
00EF1F2F | 894D F4 | mov dword ptr ss:[ebp-C],ecx | ecx:_E28DA64E_cdecl@c
00EF1F32 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] | [ebp-4]:&"ALLUSERSPROFILE=C:\\ProgramData"
00EF1F35 | 52 | push edx |
00EF1F36 | 8B45 F8 | mov eax,dword ptr ss:[ebp-8] | [ebp-8]:&"E:\\Projects\\VisualStudioProjects\\cdecl\\Debug\\cdecl.exe"
00EF1F39 | 50 | push eax |
00EF1F3A | 8B4D F4 | mov ecx,dword ptr ss:[ebp-C] | ecx:_E28DA64E_cdecl@c
00EF1F3D | 51 | push ecx | ecx:_E28DA64E_cdecl@c
00EF1F3E | E8 92F3FFFF | call cdecl.EF12D5 |
00EF1F43 | 83C4 0C | add esp,C |
00EF1F46 | 8BE5 | mov esp,ebp | exe_common.inl:79
00EF1F48 | 5D | pop ebp |
00EF1F49 | C3 | ret |
10.1.2 stdcall
00F41780 | 55 | push ebp | cdecl.c:6
00F41781 | 8BEC | mov ebp,esp |
00F41783 | 81EC C0000000 | sub esp,C0 |
00F41789 | 53 | push ebx |
00F4178A | 56 | push esi | esi:__enc$textbss$end+23
00F4178B | 57 | push edi |
00F4178C | 8BFD | mov edi,ebp |
00F4178E | 33C9 | xor ecx,ecx | ecx:_E28DA64E_cdecl@c
00F41790 | B8 CCCCCCCC | mov eax,CCCCCCCC | eax:_E28DA64E_cdecl@c
00F41795 | F3:AB | rep stosd |
00F41797 | B9 00C0F400 | mov ecx,<cdecl._E28DA64E_cdecl@c> | cdecl.c:15732480, ecx:_E28DA64E_cdecl@c
00F4179C | E8 7FFBFFFF | call cdecl.F41320 |
00F417A1 | 90 | nop |
00F417A2 | 8B45 08 | mov eax,dword ptr ss:[ebp+8] | cdecl.c:7, eax:_E28DA64E_cdecl@c
00F417A5 | 0345 0C | add eax,dword ptr ss:[ebp+C] | eax:_E28DA64E_cdecl@c, [ebp+C]:&"E:\\Projects\\VisualStudioProjects\\cdecl\\Debug\\cdecl.exe"
00F417A8 | 5F | pop edi | cdecl.c:8
00F417A9 | 5E | pop esi | esi:__enc$textbss$end+23
00F417AA | 5B | pop ebx |
00F417AB | 81C4 C0000000 | add esp,C0 |
00F417B1 | 3BEC | cmp ebp,esp |
00F417B3 | E8 8CFAFFFF | call cdecl.F41244 |
00F417B8 | 8BE5 | mov esp,ebp |
00F417BA | 5D | pop ebp |
00F417BB | C2 0800 | ret 8 |
10.1.3 fastcall
第11章 视频讲座
11.1 运行 11.2 分析 11.2.1 目标 (1): 去除消息框 11.2.2 打补丁 (1): 去除消息框 11.2.3 目标 (2): 查找注册码 11.3 小结
第12章 究竟应当如何学习代码逆向分析
12.1 逆向工程 12.1.1 任何学习都应当有目标 12.1.2 拥有积极心态 12.1.3 要感受其中的乐趣 12.1.4 让检索成为日常生活的一部分 12.1.5 最重要的是实践 12.1.6 请保持平和的心态
代码逆向分析技术的初学者最容易犯的毛病是急躁。总想快速出成果,结果学习却不见起色,技术水平原地踏步。 自己究竟还有多少不懂、能不能顺利进行下去,这都让人一头雾水、心烦不已。 汇编、Windows内部结构、PE文件格式、API钩取等都不是易学的内容,仅汇编一项就学无止境。 此时心浮气躁就很容易放弃目标。
第二部分 PE文件格式
第13章 PE文件格式 90
13.1 介绍 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯90
PE文件是Windows操作系统下使用的可执行文件格式。 它是微软在UNIX平台的COFF(Common Object File Format, 通用对象文件格式)基础上制作而成的。
13.2 PE文件格式 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯90
如何加载到内存、从何处开始运行、运行中需要的DLL有哪些、需要多大的栈/堆内存等,大量信息以结构体形式存储在PE头中。 换言之,学习PE文件格式就是学习PE头中的结构体。
13.2.1 基本结构 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯91
https://www.openrce.org/reference_library/files/reference/PE%20Format.pdf
从DOS头(DOS header)到节区头(Section header)是PE头部分,其下的节区合称PE体。 文件中使用偏移(offset), 内存中使用VA(Virtual Address, 虚拟地址)来表示位置。 文件加载到内存时,情况就会发生变化(节区的大小、位置等)。 文件的内容一般可分为代码(.text)、数据(. data)、资源 (. rsrc) 节,分别保存。
各节区头定义了各节区在文件或内存中的大小、位置、属性等。
PE头与各节区的尾部存在一个区域,称为NULL填充(NULL padding)。
计算机中,为了提高处理文件、内存、网络包的效率,使用“最小基本单位”这一概念,PE文件中也类似。
文件/内存中节区的起始位置应该在各文件/内存最小单位的倍数位置上,空白区域将用NULL填充,可以看到各节区起始地址的截断都遵循一定规则)。
13.2.2 VA&RVA ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯92
基准位置(ImageBase)。
VA指的是进程虚拟内存的绝对地址, RVA(Relative Virtual Address, 相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址。
VA与RVA满足下面的换算关系:
- RVA+ImageBase=VA
PE头内部信息大多以RVA形式存在。原因在于,PE文件(主要是DLL)加载到进程虚拟内存的特定位置时,该位置可能已经加载了其他PE文件(DLL)。 此时必须通过重定位(Relocation)将其加载到其他空白的位置,若PE头信息使用的是VA,则无法正常访问。因此使用RVA来定位信息,即使发生了重定位,只要相对于基准位置的相对地址没有变化,就能正常访问到指定信息,不会出现任何问题。
提示
- 32位WindowsOS中,各进程分配有4GB的虚拟内存,因此进程中VA值的范围是00000000~FFFFFFFF.
13.3 PE头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯92
13.3.1 DOS头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯93
// D:\Windows Kits\10\Include\10.0.26100.0\um\winnt.h
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_DOS_HEADER结构体的大小为64个字节。
64/16=4
在该结构体中必须知道2个重要成员:e_magic与e_lfanew.
- e_magic: DOS签名(signature,4D5A=>ASCII值“MZ”).
- e_lfanew:指示NT头的偏移(根据不同文件拥有可变值)。
文件中offset: 0x00000000 内存中offset: 0x00340000
13.3.2 DOS存根⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯94
16*10
00340040~003400D0
13.3.3 NT头 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯94
#define IMAGE_SIZEOF_FILE_HEADER 20
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_ROM_HEADERS {
IMAGE_FILE_HEADER FileHeader;
IMAGE_ROM_OPTIONAL_HEADER OptionalHeader;
} IMAGE_ROM_HEADERS, *PIMAGE_ROM_HEADERS;
IMAGE_NT_HEADERS结构体由3个成员组成:
- 第一个成员为签名(Signature)结构体,其值为50450000h("PE"00)。
- 文件头(File Header)
- 可选头(Optional Header)结构体。
13.3.4 NT头:文件头 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯95
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_FILE_HEADER 20个字节
4C 01 05 00 B2 9C D3 67 00 00 00 00 00 00 00 00 E0 00 02 01
014C Machine
0005 NumberOfSections
67D39CB2 TimeDateStamp
00000000 PointerToSymbolTable
00000000 NumberOfSymbols
00EO SizeOfOptionalHeader
0102 Characteristics
IMAGE_FILE_HEADER结构体中有如下4种重要成员(若它们设置不正确,将导致文法正常运行)。
#1. Machine 每个CPU都拥有唯一的Machine码,兼容32位Intel x86芯片的Machine码为14C.以下是定义在winnt. h文件中的Machine码。
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_TARGET_HOST 0x0001 // Useful for indicating we want to interact with the host and not a WoW guest.
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
#2. NumberOfSections 前面提到过,PE文件把代码、数据、资源等依据属性分类到各节区中存储。 NumberOfSections用来指出文件中存在的节区数量。该值一定要大于0,且当定义的节区数量与实际节区不同时,将发生运行错误。
#3. SizeOfOptionalHeader IMAGE_NT_HEADERS结构体的最后一个成员为IMAGE_OPTIONAL_HEADER32结构体。SizeOfOptionalHeader成员用来指出IMAGE_OPTIONAL_HEADER32结构体的长度。IMAGE_OPTIONAL_HEADER32结构体由C语言编写而成,故其大小已经确定。但是Windows的PE装载器需要查看IMAGE_FILE_HEADER的SizeOfOptionalHeader值,从而识别出IMAGE_OPTIONAL_HEADER32结构体的大小。 PE32+格式的文件中使用的是IMAGE_OPTIONAL_HEADER64结构体,而不是IMAGE_OPTIONAL_HEADER32结构体。2个结构体的尺寸是不同的,所以需要在SizeOfOptionalHeader成员中明确指出结构体的大小。
#4. Characteristics 该字段用于标识文件的属性,文件是否是可运行的形态、是否为DLL文件等信息,以bit OR形式组合起来。 以下是定义在winnt.h文件中的Characteristics值(请记住0002h与2000h这两个值)。
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved external references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Aggressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
#5 TimeDateStamp 该成员的值不影响文件运行,用来记录编译器创建此文件的时间。
13.3.5 NT头:可选头 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯97
//
// Directory format.
//
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
//
// Optional header format.
//
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_ROM_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD BaseOfBss;
DWORD GprMask;
DWORD CprMask[4];
DWORD GpValue;
} IMAGE_ROM_OPTIONAL_HEADER, *PIMAGE_ROM_OPTIONAL_HEADER;
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107
#ifdef _WIN64
typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif
32位 224字节 224/16=14
64位 240字节 240/16=15
0B 01 0E 2B | 00 10 00 00 | 00 14 00 00 | 00 00 00 00
AB 12 00 00 | 00 10 00 00 | 00 20 00 00 | 00 00 34 00
00 10 00 00 | 00 02 00 00 | 06 00 00 00 | 00 00 00 00
06 00 00 00 | 00 00 00 00 | 00 60 00 00 | 00 04 00 00
00 00 00 00 | 03 00 40 81 | 00 00 10 00 | 00 10 00 00
00 00 10 00 | 00 10 00 00 | 00 00 00 00 | 10 00 00 00
00 00 00 00 | 00 00 00 00 | 1C 26 00 00 | A0 00 00 00
00 40 00 00 | E0 01 00 00 | 00 00 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 50 00 00 | 8C 01 00 00
D8 21 00 00 | 70 00 00 00 | 00 00 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00
18 21 00 00 | 40 00 00 00 | 00 00 00 00 | 00 00 00 00
00 20 00 00 | C8 00 00 00 | 00 00 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00
010B Magic
0E MajorLinkerVersion
2B MinorLinkerVersion
00001000 SizeOfCode
00001400 SizeOfInitializedData
00000000 SizeOfUninitializedData
000012AB AddressOfEntryPoint
00001000 BaseOfCode
00002000 BaseOfData
00340000 ImageBase
00001000 SectionAlignment
00000200 FileAlignment
0006 MajorOperatingSystemVersion
0000 MinorOperatingSystemVersion
0000 MajorImageVersion
0000 MinorImageVersion
0006 MajorSubsystemVersion
0000 MinorSubsystemVersion
00000000 Win32VersionValue
00006000 SizeOfImage
00000400 SizeOfHeaders
00000000 CheckSum
0003 Subsystem
8140 DllCharacteristics
00100000 SizeOfStackReserve
00001000 SizeOfStackCommit
00100000 SizeOfHeapReserve
00001000 SizeOfHeapCommit
00000000 LoaderFlags
00000010 NumberOfRvaAndSizes
00000000 00000000 DataDirectory0 RVA SIZE EXPORT
0000261C 000000A0 DataDirectory1 RVA SIZE IMPORT VirtualAddress Size
00400000 E0010000 DataDirectory2 RVA SIZE RESOURCE
00000000 00000000 DataDirectory3 RVA SIZE EXCEPTION
00000000 00000000 DataDirectory4 RVA SIZE SECURITY
00500000 8C010000 DataDirectory5 RVA SIZE BASERELOC
D8210000 70000000 DataDirectory6 RVA SIZE DEBUG
00000000 00000000 DataDirectory7 RVA SIZE COPYRIGHT
00000000 00000000 DataDirectory8 RVA SIZE GLOBALPTR
00000000 00000000 DataDirectory9 RVA SIZE TLS
18210000 40000000 DataDirectory10 RVA SIZE LOAD_CONFIG
00000000 00000000 DataDirectory11 RVA SIZE BOUND_IMPORT
00200000 C8000000 DataDirectory12 RVA SIZE IAT
00000000 00000000 DataDirectory13 RVA SIZE DELAY_IMPORT
00000000 00000000 DataDirectory14 RVA SIZE COM_DESCRIPTOR
00000000 00000000 DataDirectory15 RVA SIZE RESERVED
在IMAGE_OPTIONAL_HEADER32结构体中需要关注下列成员。这些值是文件运行必需的,设置错误将导致文件无法正常运行。
#1. Magic 为IMAGE_OPTIONAL_HEADER32结构体时, Magic码为010B; 为IMAGE_OPTIONAL_HEADER64结构体时, Magic码为020B.
#2. AddressOfEntryPoint AddressOfEntryPoint持有EP的RVA值。该值指出程序最先执行的代码起始地址,相当重要。
#3. ImageBase 进程虚拟内存的范围是0~FFFFFFFF(32位系统)。PE文件被加载到如此大的内存中时,ImageBase指出文件的优先装入地址。 EXE、DLL文件被装载到用户内存的0~7FFFFFFF中,SYS文件被载入内核内存的80000000~FFFFFFFF中。一般而言,使用开发工具((V B / V C + \rightarrow / D e l p h i)创建好EXE文件后,其ImageBase的值为00400000, DLL文件的ImageBase值为10000000(当然也可以指定为其他值)。执行PE文件时,PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为ImageBase+AddressOfEntryPoint.
#4. SectionAlignment, FileAlignment PE文件的Body部分划分为若干节区,这些节存储着不同类别的数据。FileAlignment指定了节区在磁盘文件中的最小单位,而SectionAlignment则指定了节区在内存中的最小单位(一个文件中, FileAlignment与SectionAlignment的值可能相同,也可能不同)。磁盘文件或内存的节区大小必定为FileAlignment或SectionAlignment值的整数倍。
#5. SizeOfImage 加载PE文件到内存时,SizeOfImage指定了PE Image在虚拟内存中所占空间的大小。一般而言,文件的大小与加载到内存中的大小是不同的(节区头中定义了各节装载的位置与占有内存的大小,后面会讲到)。
#6. SizeOfHeader SizeOfHeader用来指出整个PE头的大小。该值也必须是FileAlignment的整数倍。第一节区所在位置与SizeOfHeader距文件开始偏移的量相同。
#7. Subsystem 该Subsystem值用来区分系统驱动文件(. sys)与普通的可执行文件(. exe,*. dll).S
#8. NumberOfRvaAndSizes NumberOfRvaAndSizes用来指定DataDirectory(IMAGE OPTIONAL HEADER32结构体的最后一个成员)数组的个数。虽然结构体定义中明确指出了数组个数为IMAGE NUMBEROF DIRECTORY ENTRIES(16), 但是PE装载器通过查看NumberOfRvaAndSizes值来识别数组大小,换言之,数组大小也可能不是16.
#9. DataDirectory DataDirectory是由 IMAGE_DATA_DIRECTORY 结构体组成的数组,数组的每项都有被定义的值。代码13-7列出了各数组项。
DataDirectory[θ] = EXPORT Directory
DataDirectory[1] = IMPORT Directory
DataDirectory[2] = RESOURCE Directory
DataDirectory[3] = EXCEPTION Directory
DataDirectory[4] = SECURITY Directory
DataDirectory[5] = BASERELOC Directory
DataDirectory[6] = DEBUG Directory
DataDirectory[7] = COPYRIGHT Directory
DataDirectory[8] = GLOBALPTR Directory
DataDirectory[9] = TLS Directory
DataDirectory[A] = LOAD CONFIG Directory
DataDirectory[B] = BOUND IMPORT Directory
DataDirectory[C] = IAT Directory
DataDirectory[D] = DELAY IMPORT Directory
DataDirectory[E] = COM DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory
13.3.6 节区头 ………………………………………………101
PE文件格式的设计者们决定把具有相似属性的数据统一保存在一个被称为“节区”的地方,然后需要把各节区属性记录在节区头中(节区属性中有文件/内存的起始位置、大小、访问权限等)。
//
// Section header format.
//
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER 40
//
// Section characteristics.
//
// IMAGE_SCN_TYPE_REG 0x00000000 // Reserved.
// IMAGE_SCN_TYPE_DSECT 0x00000001 // Reserved.
// IMAGE_SCN_TYPE_NOLOAD 0x00000002 // Reserved.
// IMAGE_SCN_TYPE_GROUP 0x00000004 // Reserved.
#define IMAGE_SCN_TYPE_NO_PAD 0x00000008 // Reserved.
// IMAGE_SCN_TYPE_COPY 0x00000010 // Reserved.
#define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // Section contains uninitialized data.
#define IMAGE_SCN_LNK_OTHER 0x00000100 // Reserved.
#define IMAGE_SCN_LNK_INFO 0x00000200 // Section contains comments or some other type of information.
// IMAGE_SCN_TYPE_OVER 0x00000400 // Reserved.
#define IMAGE_SCN_LNK_REMOVE 0x00000800 // Section contents will not become part of image.
#define IMAGE_SCN_LNK_COMDAT 0x00001000 // Section contents comdat.
// 0x00002000 // Reserved.
// IMAGE_SCN_MEM_PROTECTED - Obsolete 0x00004000
#define IMAGE_SCN_NO_DEFER_SPEC_EXC 0x00004000 // Reset speculative exceptions handling bits in the TLB entries for this section.
#define IMAGE_SCN_GPREL 0x00008000 // Section content can be accessed relative to GP
#define IMAGE_SCN_MEM_FARDATA 0x00008000
// IMAGE_SCN_MEM_SYSHEAP - Obsolete 0x00010000
#define IMAGE_SCN_MEM_PURGEABLE 0x00020000
#define IMAGE_SCN_MEM_16BIT 0x00020000
#define IMAGE_SCN_MEM_LOCKED 0x00040000
#define IMAGE_SCN_MEM_PRELOAD 0x00080000
#define IMAGE_SCN_ALIGN_1BYTES 0x00100000 //
#define IMAGE_SCN_ALIGN_2BYTES 0x00200000 //
#define IMAGE_SCN_ALIGN_4BYTES 0x00300000 //
#define IMAGE_SCN_ALIGN_8BYTES 0x00400000 //
#define IMAGE_SCN_ALIGN_16BYTES 0x00500000 // Default alignment if no others are specified.
#define IMAGE_SCN_ALIGN_32BYTES 0x00600000 //
#define IMAGE_SCN_ALIGN_64BYTES 0x00700000 //
#define IMAGE_SCN_ALIGN_128BYTES 0x00800000 //
#define IMAGE_SCN_ALIGN_256BYTES 0x00900000 //
#define IMAGE_SCN_ALIGN_512BYTES 0x00A00000 //
#define IMAGE_SCN_ALIGN_1024BYTES 0x00B00000 //
#define IMAGE_SCN_ALIGN_2048BYTES 0x00C00000 //
#define IMAGE_SCN_ALIGN_4096BYTES 0x00D00000 //
#define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000 //
// Unused 0x00F00000
#define IMAGE_SCN_ALIGN_MASK 0x00F00000
#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discarded.
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Section is not cachable.
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // Section is not pageable.
#define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // Section is executable.
#define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable.
#define IMAGE_SCN_MEM_WRITE 000000A0 0x80000000 // Section is writeable.
IMAGE_SECTION_HEADER 40个字节
2E 74 65 78 74 00 00 00 Name
00 00 0E 2C VirtualSize 内存中节区所占大小
00 00 10 00 VirtualAddress 内存中节区起始地址
00 00 10 00 SizeOfRawData 磁盘文件中节区所占大小
00 00 04 00 PointerToRawData 磁盘文件中节区起始位置
00 00 00 00 PointerToRelocations
00 00 00 00 PointerToLinenumbers
00 00 NumberOfRelocations
00 00 NumberOfLinenumbers
60 00 00 20 Characteristics
2E 72 64 61 74 61 00 00
B4 0B 00 00
00 20 00 00 => 00 00 20 00
00 0C 00 00
00 14 00 00 => 00 00 14 00
00 00 00 00
00 00 00 00
00 00
00 00
40 00 00 40
2E 64 61 74 61 00 00 00
D0 03 00 00
00 30 00 00 => 00 00 30 00
00 02 00 00
00 20 00 00
00 00 00 00
00 00 00 00
00 00
00 00
40 00 00 C0
IMAGE_SECTION_HEADER结构体的重要成员
- VirtualSize 内存中节区所占大小
- VirtualAddress 内存中节区起始地址 (RVA)
- SizeOfRawData 磁盘文件中节区所占大小
- PointerToRawData 磁盘文件中节区起始位置
- Charateristics 节区属性 (bit OR)
VirtualAddress与PointerToRawData不带有任何值,分别由(定义在IMAGE OPTIONAL HEADER32中的) SectionAlignment与FileAlignment确定。
VirtualSize与SizeOfRawData一般具有不同的值,即磁盘文件中节区的大小与加载到内存中的节区大小是不同的。
Characterisitics由代码13-10中显示的值组合(bit OR) 而成。
Name字段,Name成员不像C语言中的字符串一样以NULL结束,并且没有“必须使用ASCII值”的限制。PE规范未明确规定节区的Name,所以可以向其中放入任何值,甚至可以填充NULL值。所以节区的Name仅供参考,不能保证其百分之百地被用作某种信息(数据节区的名称也可叫做. code).
提示
- 讲解PE文件时经常出现“映像”(Image)这一术语,希望各位牢记。PE文件加载到内存时,文件不会原封不动地加载,而要根据节区头中定义的节区起始地址、节区大小等加载。因此,磁盘文件中的PE与内存中的PE具有不同形态。将装载到内存中的形态称为“映像”以示区别,使用这一术语能够很好地区分二者。
13.4 RVA to RAW ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯104
PE文件加载到内存时,每个节区都要能准确完成内存地址与文件偏移间的映射。 这种映射一般称为RVA to RAW,方法如下。
- (1)查找RVA所在节区。
- (2)使用简单的公式计算文件偏移 (RAW).
根据IMAGE SECTION HEADER结构体,换算公式如下:
- RAW - PointerToRawData = RVA - VirtualAddress
- RAW = RVA - VirtualAddress + PointerToRawData
接下来将继续学习PE头的核心内容:
- IAT(Import Address Table, 导入地址表)
- EAT (Export Address Table, 导出地址表)
13.5 IAT ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯105
IAT (Import Address Table, 导入地址表)。
刚开始学习PE头时,最难过的一关就是IAT (Import Address Table, 导入地址表)。IAT保存的内容与Windows操作系统的核心进程、内存、DLL结构等有关。换句话说,只要理解了IAT,就掌握了Windows操作系统的根基。
简言之,IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。
13.5.1 DLL …………………………………………………105
Windows OS设计者们根据需要引入了DLL这一概念,描述如下。
- 不要把库包含到程序中,单独组成DLL文件,需要时调用即可。
- 内存映射技术使加载后的DLL代码、资源在多个进程中实现共享。
- 更新库时只要替换相关DLL文件即可,简便易行。
13.5.2 IMAGE_IMPORT_DESCRIPTOR ………………………………107
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
执行一个普通程序时往往需要导入多个库,导入多少库就存在多少个IMAGE_IMPORT_DESCRIPTOR结构体,这些结构体形成了数组,且结构体数组最后以NULL结构体结束。
IMAGE_IMPORT_DESCRIPTOR中的重要成员如表所示(拥有全部RVA值)。
- OriginalFirstThunk INT的地址(RVA)
- Name 库名称字符串的地址 (RVA)
- FirstThunk IAT的地址(RVA)
下面了解一下PE装载器把导入函数输入至IAT的顺序。IAT输入顺序:
- 读取IID(IMAGE_IMPORT_DESCRIPTOR)的Name成员,获取库名称字符串 ("kernel32. dll").
- 装载相应库。 → LoadLibrary("kernel32. dll")
- 读取IID的OriginalFirstThunk成员,获取INT地址。
- 逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址 (RVA).
- 使用IMAGE_IMPORT_BY_NAME的Hint (ordinal) 或Name项,获取相应函数的起始地址。 → GetProcAddress("GetCurrentThreadld")
- 读取IID的FirstThunk (IAT) 成员,获得IAT地址。
- 将上面获得的函数地址输入相应IAT数组值。
- 重复以上步骤4~7, 直到INT结束 (遇到NULL时)。
13.5.3 使用notepad. exe练习⋯⋯⋯⋯⋯⋯108
IB 00 3C 00 00
IMPORT 00 00 26 1C 00 00 00 A0
00 3C 26 1C RVA
.text 00 00 10 00
00 3C 10 00
.rdata 00 00 20 00
00 3C 20 00 VA
00 00 14 00 PTRD
.data 00 00 30 00
00 3C 30 00
FILE RAW OFFSET = 00 3C 26 1C - 00 3C 20 00 + 00 00 14 00
1A1C
1A1C+A0=1ABC
IMAGE_IMPORT_DESCRIPTOR FILE 1A1C ~ 1ABC
F0 26 00 00 INT
00 00 00 00 TDS
00 00 00 00 FC
DC 27 00 00 NAME 00 00 DC 27 =>
34 20 00 00 IAT
00 00 20 34 6C 27 00 00 00 00 00 00 00 00 00 00 E6 29 00 00 B0 20 00 00 1C 27 00 00 00 00 00 00 00 00 00 00 06 2A 00 00 60 20 00 00 14 27 00 00 00 00 00 00 00 00 00 00 28 2A 00 00 58 20 00 00 0C 27 00 00 00 00 00 00 00 00 00 00 48 2A 00 00 50 20 00 00 04 27 00 00 00 00 00 00 00 00 00 00 6A 2A 00 00 48 20 00 00 BC 26 00 00 00 00 00 00 00 00 00 00 A6 2B 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
IMAGE_IMPORT_DESCRIPTOR MEMORY 3C261C~3C26BB
F0 26 00 00 INT => 00 00 26 F0 => 00 3C 26 F0
00 00 00 00
00 00 00 00
DC 27 00 00 NAME => 00 00 27 DC => 00 3C 27 DC
34 20 00 00
6C 27 00 00 00 00 00 00 00 00 00 00 E6 29 00 00 B0 20 00 00 1C 27 00 00 00 00 00 00 00 00 00 00 06 2A 00 00 60 20 00 00 14 27 00 00 00 00 00 00 00 00 00 00 28 2A 00 00 58 20 00 00 0C 27 00 00 00 00 00 00 00 00 00 00 48 2A 00 00 50 20 00 00 04 27 00 00 00 00 00 00 00 00 00 00 6A 2A 00 00 48 20 00 00 BC 26 00 00 00 00 00 00 00 00 00 00 A6 2B 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
NAME FILE RAW OFFSET = 00 3C 27 DC - 00 3C 20 00 + 00 00 14 00 => 1BDC
NAME MEMORY:
00 3C 27 DC => 56 43 52 55 4E 54 49 4D 45 31 34 30 2E 64 6C 6C => VCRUNTIME140.dll
NAME FILE RAW:
1BDC => 56 43 52 55 4E 54 49 4D 45 31 34 30 2E 64 6C 6C => VCRUNTIME140.dll
INT 00 3C 26 F0
9A 27 00 00 84 27 00 00 B8 27 00 00 C2 27 00 00 00 00 00 00
9A 27 00 00 => 00 00 27 9A => 00 3C 27 9A
84 27 00 00 => 00 00 27 84
B8 27 00 00 => 00 00 27 B8
C2 27 00 00 => 00 00 27 C2
00 00 00 00
MEMORY 00 3C 27 9A
1D 00 5F 5F 63 75 72 72 65 6E 74 5F 65 78 63 65 70 74 69 6F 6E 5F
1D 00 => HINT
5F 5F 63 75 72 72 65 6E 74 5F 65 78 63 65 70 74 69 6F 6E 5F => __current_exception_
INT,由地址数组形式组成(数组尾部以NULL结束)。每个地址值分别指向IMAGE IMPORT BY NAME结构体(参考图13-11)。跟踪数组的第一个值7A7A(RVA), 进入该地址,可以看到导入的API函数的名称字符串。
13.6 EAT ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯112
Windows操作系统中,“库”是为了方便其他程序调用而集中包含相关函数的文件(DLL/SYS)。
Win32API是最具代表性的库,其中的kernel32. dll文件被称为最核心的库文件。
EAT是一种核心机制,它使不同的应用程序可以调用库文件中提供的函数。 也就是说,只有通过EAT才能准确求得从相应库中导出函数的起始地址。
与前面讲解的IAT一样,PE文件内的特定结构体(IMAGE_EXPORT_DIRECTORY)保存着导出信息,且PE文件中仅有一个用来说明库EAT的IMAGE EXPORT DIRECTORY结构体。
13.6.1 IMAGE_EXPORT_DIRECTORY ………………………………113
//@[comment("MVI_tracked")]
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
- NumberOfFunctions 实际Export函数的个数
- NumberOfNames Export函数中具名的函数个数
- AddressOfFunctions Export函数地址数组 (数组元素个数=NumberOfFunctions)
- AddressOfNames 函数名称地址数组 (数组元素个数=NumberOfNames)
- AddressOfNameOrdinals Ordinal地址数组 (数组元素个数=NumberOfNames)
GetProcAddress()操作原理:
- (1) 利用AddressOfNames成员转到“函数名称数组”。
- (2) “函数名称数组”中存储着字符串地址。通过比较(strcmp)字符串,查找指定的函数名称(此时数组的索引称为name index).
- (3) 利用AddressOfNameOrdinals成员,转到orinal数组。
- (4) 在ordinal数组中通过name index查找相应ordinal值。
- (5) 利用AddressOfFunctions成员转到“函数地址数组”(EAT).
- (6) 在“函数地址数组”中将刚刚求得的ordinal用作数组索引,获得指定函数的起始地址。
13.6.2 使用kernel32. dll练习………………114
0B 01
0E
1E
00 C0 06 00
00 40 03 00
00 00 00 00
C0 77 01 00
00 00 01 00
00 00 08 00
00 00 80 6B
00 00 01 00
00 10 00 00
0A 00
00 00
0A 00
00 00
0A 00
00 00
00 00 00 00
00 00 0F 00
00 10 00 00
A0 CB 0A 00
03 00
40 41
00 00 04 00
00 10 00 00
00 00 10 00
00 10 00 00
00 00 00 00
10 00 00 00 80 3E 09 00 DataDirectory0 RVA SIZE EXPORT
B8 E5 00 00 38 24 0A 00
D0 07 00 00 00 00 0D 00
20 05 00 00 00 00 00 00
00 00 00 00 00 10 0A 00
88 3C 00 00 00 00 0E 00
88 4B 00 00 5C 88 08 00
70 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 40 02 08 00
C0 00 00 00 00 00 00 00
00 00 00 00 00 0D 08 00
38 15 00 00 2C 3B 09 00
80 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
010B Magic
0E MajorLinkerVersion
2B MinorLinkerVersion
00001000 SizeOfCode
00001400 SizeOfInitializedData
00000000 SizeOfUninitializedData
000012AB AddressOfEntryPoint
00001000 BaseOfCode
00002000 BaseOfData
00340000 ImageBase
00001000 SectionAlignment
00000200 FileAlignment
0006 MajorOperatingSystemVersion
0000 MinorOperatingSystemVersion
0000 MajorImageVersion
0000 MinorImageVersion
0006 MajorSubsystemVersion
0000 MinorSubsystemVersion
00000000 Win32VersionValue
00006000 SizeOfImage
00000400 SizeOfHeaders
00000000 CheckSum
0003 Subsystem
8140 DllCharacteristics
00100000 SizeOfStackReserve
00001000 SizeOfStackCommit
00100000 SizeOfHeapReserve
00001000 SizeOfHeapCommit
00000000 LoaderFlags
00000010 NumberOfRvaAndSizes
00000000 00000000 DataDirectory0 RVA SIZE EXPORT
0000261C 000000A0 DataDirectory1 RVA SIZE IMPORT VirtualAddress Size
00400000 E0010000 DataDirectory2 RVA SIZE RESOURCE
00000000 00000000 DataDirectory3 RVA SIZE EXCEPTION
00000000 00000000 DataDirectory4 RVA SIZE SECURITY
00500000 8C010000 DataDirectory5 RVA SIZE BASERELOC
D8210000 70000000 DataDirectory6 RVA SIZE DEBUG
00000000 00000000 DataDirectory7 RVA SIZE COPYRIGHT
00000000 00000000 DataDirectory8 RVA SIZE GLOBALPTR
00000000 00000000 DataDirectory9 RVA SIZE TLS
18210000 40000000 DataDirectory10 RVA SIZE LOAD_CONFIG
00000000 00000000 DataDirectory11 RVA SIZE BOUND_IMPORT
00200000 C8000000 DataDirectory12 RVA SIZE IAT
00000000 00000000 DataDirectory13 RVA SIZE DELAY_IMPORT
00000000 00000000 DataDirectory14 RVA SIZE COM_DESCRIPTOR
00000000 00000000 DataDirectory15 RVA SIZE RESERVED
00 00 80 6B => 6B 80 00 00 ImageBase
10 00 00 00 80 3E 09 00 DataDirectory0 RVA SIZE EXPORT
10 00 00 00 => 00 00 00 10 RVA
80 3E 09 00 => 00 09 3E 80 SIZE
6B 80 00 00
13.7 高级PE⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯116
13.7.1 PEView.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 116
13.7.2 Patched PE⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯117
13.8 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯118
第14章 运行时压缩 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯121
14.1 数据压缩 ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯121
不论哪种形态的文件(数据)都是由二进制(0或1)组成的,只要使用合适的压缩算法,就能缩减其大小。经过压缩的文件若能100%恢复,则称该压缩为“无损压缩”(Lossless Data Compression); 若不能恢复原状,则称该压缩为“有损压缩”(Loss Data Compression).
14.1.1 无损压缩 …………………………………………121 14.1.2 有损压缩 ……………………………………………121
14.2 运行时压缩器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯122
顾名思义,运行时压缩器是针对可执行(PE, Portable Executable)文件而言的,可执行文件内部含有解压缩代码,文件在运行瞬间于内存中解压缩后执行。
运行时压缩文件也是PE文件,内部含有原PE文件与解码程序。在程序的EP代码中执行解码程序,同时在内存中解压缩后执行。
把普通PE文件创建成运行时压缩文件的实用程序称为“压缩器”(Packer),经反逆向(Anti-Reversing)技术特别处理的压缩器称为保护器(Protector).
14.2.1 压缩器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯122
PE压缩器是指可执行文件的压缩器,准确一点应该称为“运行时压缩器”,它是PE文件的专用压缩器。
14.2.2 保护器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯123
PE保护器是一类保护PE文件免受代码逆向分析的实用程序。它们不像普通的压缩器一样仅对PE文件进行运行时压缩,而应用了多种防止代码逆向分析的技术(反调试、反模拟、代码混乱、多态代码、垃圾代码、调试器监视等)。这类保护器使压缩后的PE文件尺寸反而比源文件要大一些,调试起来非常难。
14.3 运行时压缩测试⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯123
第15章 调试UPX压缩的notepad 程序⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯127
15.1 notepad. exe的EP代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯127 15.2 notepad upx. exe的EP代码⋯⋯⋯⋯⋯⋯⋯⋯127 15.3 跟踪UPX文件⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯129 15.3.1 OllyDbg的跟踪命令⋯⋯⋯⋯⋯⋯⋯129 15.3.2 循环#1⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯129 15.3.3 循环#2⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯130 15.3.4 循环#3………………………………………………131 15.3.5 循环#4⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯131 15.4 快速查找UPX OEP的方法⋯⋯⋯⋯⋯⋯⋯⋯132 15.4.1 在POPAD指令后的JMP指令处设置断点⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯132 15.4.2 在栈中设置硬件断点⋯⋯⋯⋯⋯⋯⋯133 15.5 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯133
第16章 基址重定位表⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯135
16.1 PE重定位⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯135 16.1.1 DLL/SYS⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯135 16.1.2 EXE⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯136 16.2 PE重定位时执行的操作⋯⋯⋯⋯⋯⋯⋯⋯⋯136 16.3 PE重定位操作原理⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯138 16.3.1 基址重定位表⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯138 16.3.2 IMAGE BASE RELOCATION 结构体⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯139 16.3.3 基址重定位表的分析方法……139 16.3.4 练习⋯⋯⋯⋯⋯⋯⋯⋯141
第17章 从可执行文件中删除. reloc
节区⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯142 17.1 . reloc节区⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯142 17.2 reloc.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 142 17.2.1 删除. reloc节区头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯142 17.2.2 删除. reloc节区⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯143 17.2.3 修改IMAGE FILE HEADER…………………………………………143 17.2.4修改IMAGE OPTIONAL HEADER⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯144 17.3 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯145
第18章 UPack PE文件头详细分析⋯⋯⋯146
18.1 UPack说明⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯146 18.2 使用UPack压缩notepad. exe…………………146 18.3 使用Stud PE工具⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯148 18.4 比较PE文件头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯148 18.4.1 原notepad. exe的PE文件头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯149 18.4.2 notepad upack. exe运行时压缩的PE文件头⋯⋯⋯⋯⋯⋯⋯⋯⋯ 149 18.5 分析UPack的PE文件头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯150 18.5.1 重叠文件头⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯150 18.5.2 IMAGE FILE HEADER.SizeOfOptionalHeader⋯⋯⋯⋯⋯⋯ 150 18.5.3 IMAGE OPTIONAL HEADER. NumberOfRvaAndSizes……………………………………152 18.5.4 IMAGE SECTION HEADER⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯153 18.5.5 重叠节区⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯155 18.5.6 RVA to RAW⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯156 18.5.7 导入表 (IMAGE_IMPORT_DESCRIPTOR array)…………………158 18.5.8 导入地址表⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯160 18.6 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯161
第19章 UPack调试-查找OEP⋯⋯⋯⋯⋯⋯162
19.1 OllyDbg运行错误⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯162 19.2 解码循环⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯163 19.3 设置IAT⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 165 19.4 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯166
第20章 “内嵌补丁”练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯167
20.1 内嵌补丁⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯167 20.2 练习:Patchme………………………………………………168 20.3 调试:查看代码流……………………………………………168 20.4 代码结构⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯172 20.5 “内嵌补丁”练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯173 20.5.1 补丁代码要设置在何处呢⋯⋯⋯173 20.5.2 制作补丁代码……………………………………175 20.5.3 执行补丁代码…………………………………176 20.5.4 结果确认……………………………………………177
第三部分 DLL注入
第21章 Windows消息钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯180
21.1 钩子⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯180 21.2 消息钩子⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯180 21.3 SetWindowsHookEx()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯181 21.4 键盘消息钩取练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯182 21.4.1 练习示例HookMain. exe⋯⋯⋯⋯182 21.4.2 分析源代码………………………………………185 21.5 调试练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯187 21.5.1 调试HookMain. exe⋯⋯⋯⋯⋯⋯⋯⋯188 21.5.2 调试Notepad. exe进程内的 KeyHook. dll⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 190 21.6 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯192
第22章 恶意键盘记录器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯194
22.1 恶意键盘记录器的目标⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯194 22.1.1 在线游戏⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯194 22.1.2 网上银行⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯194 22.1.3 商业机密泄露⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯194 22.2 键盘记录器的种类与发展趋势⋯⋯⋯⋯⋯⋯195 22.3 防范恶意键盘记录器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯195 22.4 个人信息⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯195
第23章 DLL注入⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯197
23.1 DLL注入⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯197 23.2 DLL注入示例⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯198 23.2.1 改善功能与修复Bug⋯⋯⋯⋯⋯⋯⋯ 198 23.2.2 消息钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯198 23.2.3 API钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯198 23.2.4 其他应用程序⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯199 23.2.5 恶意代码……………………………………………199 23.3 DLL注入的实现方法⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯199 23.4 CreateRemoteThread()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯199 23.4.1 练习示例myhack. dll……………………199 23.4.2 分析示例源代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯203 23.4.3 调试方法⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯208 23.5 AppInit DLLs⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯210 23.5.1 分析示例源码…………………………………211 23.5.2 练习示例myhack2. dll………………212 23.6 SetWindowsHookEx()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯214 23.7 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯214
第24章 DLL卸载………………………………………………216
24.1 DLL卸载的工作原理⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯216 24.2 实现DLL卸载⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯216 24.2.1 获取进程中加载的DLL信息…219 24.2.2 获取目标进程的句柄…………………220 24.2.3 获取FreeLibrary()API地址……220 24.2.4 在目标进程中运行线程……………220 24.3 DLL卸载练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯220 24.3.1 复制文件及运行notepad. exe…220 24.3.2 注入myhack. dll……………………………221 24.3.3 卸载myhack.dll⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 222
第25章 通过修改PE加载DLL…………………224
25.1 练习文件⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯224 25.1.1 TextView.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 224 25.1.2 TextView patched.exe⋯⋯⋯⋯⋯⋯⋯ 225 25.2 源代码-myhack3. cpp…………………………………227 25.2.1 DllMain()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯227 25.2.2 DownloadURL()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯228 25.2.3 DropFile()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯229 25.2.4 dummy()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯230 25.3 修改TextView. exe文件的准备工作⋯⋯231 25.3.1 修改思路⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯231 25.3.2 查看IDT是否有足够空间……231 25.3.3 移动IDT⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯233 25.4 修改TextView.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 235 25.4.1 修改导入表的RVA值⋯⋯⋯⋯⋯⋯235 25.4.2 删除绑定导入表……………………………235 25.4.3 创建新IDT⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯235 25.4.4 设置Name、INT、IAT⋯⋯⋯⋯⋯236 25.4.5 修改IAT节区的属性值……………238 25.5 检测验证⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯240 25.6 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯241
第26章 PE Tools· …………………………………………242
26.1 PE Tools⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯242 26.1.1 进程内存转储⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯243 26.1.2 PE编辑器…………………………………………245 26.2 小结……………………………………………………………………245
第27章 代码注入……………………………………………247
27.1 代码注入⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯247 27.2 DLL注入与代码注入⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯247 27.3 练习示例⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯249 27.3.1 运行notepad.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 249 27.3.2 运行CodeInjection.exe⋯⋯⋯⋯⋯ 249 27.3.3 弹出消息框⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯250 27.4 CodeInjection. cpp⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯250 27.4.1 main()函数…………………………………………251 27.4.2 ThreadProc()函数⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯251 27.4.3 InjectCode()函数⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯254 27.5 代码注入调试练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯256 27.5.1 调试notepad. exe……………………………256 27.5.2 设置OllyDbg选项………………………256 27.5.3 运行CodeInjection.exe⋯⋯⋯⋯⋯⋯ 257 27.5.4 线程开始代码……………………………………258 27.6 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯259
第第28章 使用汇编语言编写注入代码……260
28.1 目标⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯260 28.2 汇编编程⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯260 28.3 OllyDbg的汇编命令………………………………………260 28.3.1 编写ThreadProc()函数…………………262 28.3.2 保存文件……………………………………………265 28.4 编写代码注入程序⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯266 28.4.1 获取ThreadProc()函数的二进制代码………………………………………266 28.4.2 CodeInjection2. cpp…………………………267 28.5 调试练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯270 28.5.1 调试notepad.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 270 28.5.2 设置OllyDbg选项⋯⋯⋯⋯⋯⋯⋯⋯⋯270 28.5.3 运行CodeInjection2.exe⋯⋯⋯⋯⋯ 271 28.5.4 线程起始代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯272 28.6 详细分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯272 28.6.1 生成栈帧………………………………………………272 28.6.2 THREAD PARAM结构体指针…………………………………………………………273 28.6.3 "User32. dll"字符串⋯⋯⋯⋯⋯⋯⋯⋯274 28.6.4 压入“user32. dll”字符串参数……………………………………………………274 28.6.5 调用LoadLibraryA("user32. dll")………………………………………275 28.6.6 "MessageBoxA"字符串⋯⋯⋯⋯276 28.6.7 调用 GetProcAddress(hMod,"MessageBoxA")…………………………276 28.6.8 压入MessageBoxA()函数的参数1-MB OK……………………………277 28.6.9 压入MessageBoxA()函数的参数2- "ReverseCore"…………277 28.6.10 压入MessageBoxA()函数的参数3-"www.reversecore.com"……………………………………………278 28.6.11 压入MessageBoxA()函数的参数4-NULL………………………………279 28.6.12 调用MessageBoxA()………………279 28.6.13 设置ThreadProc()函数的 返回值………………………………………………280 28.6.14 删除栈帧及函数返回⋯⋯⋯⋯⋯⋯280 28.7 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯280
第四部分 API钩取
第29章 API钩取:逆向分析之“花”…282
29.1 钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯282 29.2 API是什么⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯282 29.3 API钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯283 29.3.1 正常调用API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯283 29.3.2 钩取API调用⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯284 29.4 技术图表⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯284 29.4.1 方法对象(是什么)…………………285 29.4.2 位置(何处)⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯285 29.4.3 技术(如何)⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯286 29.4.4 API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯286
第30章 记事本WriteFile()API钩取……288
30.1 技术图表-调试技术⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯288 30.2 关于调试器的说明⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯289 30.2.1 术语⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯289 30.2.2 调试器功能⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯289 30.2.3 调试器的工作原理⋯⋯⋯⋯⋯⋯⋯⋯⋯289 30.2.4 调试事件…………………………………………289 30.3 调试技术流程⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯290 30.4 练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯291 30.5 工作原理⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯293 30.5.1 栈⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯293 30.5.2 执行流⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯295 30.5.3 “脱钩”&“钩子”⋯⋯⋯⋯⋯⋯⋯⋯⋯295 30.6 源代码分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯295 30.6.1 main()…………………………………………………296 30.6.2 DebugLoop()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯296 30.6.3 EXIT PROCESS DEBUG EVENT⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯298 30.6.4 CREATE PROCESS DEBUG EVENT-OnCreateProcessDebugEvent()·· ………………………298 30.6.5 EXCEPTION DEBUG EVENT-OnExceptionDebugEvent()·· …………………300
第31章 关于调试器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯305
31.1 OllyDbg⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯305 31.2 IDA Pro⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 305 31.3 WinDbg⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯306
第32章 计算器显示中文数字⋯⋯⋯⋯⋯⋯⋯⋯308
32.1 技术图表⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯308 32.2 选定目标API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯309 32.3 IAT钩取工作原理⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯312 32.4 练习示例⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯314 32.5 源代码分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯316 32.5.1 DllMain()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯316 32.5.2 MySetWindowTextW()………………317 32.5.3 hook iat()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯319 32.6 调试被注入的DLL文件⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯322 32.6.1 DllMain()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯325 32.6.2 hook iat()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯325 32.6.3 MySetWindowTextW()⋯⋯⋯⋯⋯⋯327 32.7 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯328
第33章 隐藏进程⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯329
33.1 技术图表⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯329 33.2 API代码修改技术的原理⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯329 33.2.1 钩取之前⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯330 33.2.2 钩取之后⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯330 33.3 进程隐藏⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯332 33.3.1 进程隐藏工作原理⋯⋯⋯⋯⋯⋯⋯⋯332 33.3.2 相关API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯332 33.3.3 隐藏技术的问题……………………………333 33.4 练习#1(HideProc. exe, stealth. dll)…333 33.4.1 运行notepad. exe、procexp. exe、taskmgr.exe⋯⋯ 334 33.4.2 运行HideProc.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 334 33.4.3 确认stealth. dll注入成功……………334 33.4.4 查看notepad. exe进程是否隐藏成功⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯335 33.4.5 取消notepad. exe进程隐藏……336 33.5 源代码分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯336 33.5.1 HideProc. cpp⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯336 33.5.2 stealth. cpp……………………………………………338 33.6 全局API钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯344 33.6.1 Kernel32. CreateProcess()API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯344 33.6.2 Ntdll. ZwResumeThread()API………………………………………………………………345 33.7 练习#2(HideProc2. exe,Stealth2. dll)……345 33.7.1 复制stealth2. dll文件到%SYSTEM%文件夹中…………………345 33.7.2 运行HideProc2. exe-hide⋯⋯⋯⋯346 33.7.3 运行ProcExp. exe¬epad.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 346 33.7.4 运行HideProc2. exe-show…………347 33.8 源代码分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯348 33.8.1 HideProc2. cpp……………………………………348 33.8.2 stealth2. cpp…………………………………………348 33.9 利用“热补丁”技术钩取API⋯⋯⋯⋯⋯⋯⋯350 33.9.1 API代码修改技术的问题⋯⋯⋯350 33.9.2 “热补丁”(修改7个字节代码)⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯350 33.10 练习#3: stealth3. dll……………………………………353 33.11 源代码分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯353 33.12 使用“热补丁”API钩取技术时需要考虑的问题⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯356 33.13 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯357
第34章 高级全局API钩取:IE连接控制⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯359
34.1 目标API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯359 34.2 IE进程结构⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯361 34.3 关于全局API钩取的概念⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯362 34.3.1 常规API钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯363 34.3.2 全局API钩取⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯363 34.4 ntdll!ZwResumeThread()API⋯⋯⋯⋯⋯⋯⋯⋯364 34.5 练习示例:控制IE网络连接⋯⋯⋯⋯⋯⋯⋯⋯368 34.5.1 运行IE⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯368 34.5.2 注入DLL………………………………………369 34.5.3 创建新选项卡⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯369 34.5.4 尝试连接网站………………………………370 34.5.5 卸载DLL………………………………………371 34.5.6 课外练习⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯372 34.6 示例源代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯372 34.6.1 DllMain()⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯372 34.6.2 NewInternetConnectW()⋯⋯⋯⋯⋯373 34.6.3 NewZwResumeThread()……………374 34.7 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯375
第35章 优秀分析工具的五种标准⋯⋯⋯⋯376
35.1 工具⋯376 35.2 代码逆向分析工程师⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯376 35.3 优秀分析工具的五种标准⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯376 35.3.1 精简工具数量⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯377 35.3.2 工具功能简单、使用方便……377
35.3.3 完全掌握各种功能⋯⋯⋯⋯⋯⋯⋯⋯377 35.3.4 不断升级更新………………………………377 35.3.5 理解工具的核心工作原理……377 35.4 熟练程度的重要性⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯377
第五部分 64位& Windows内核6
第36章 64位计算⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯380
36.1 64位计算环境⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯380 36.1.1 64位CPU⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯380 36.1.2 64位OS⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯381 36.1.3 Win32 API⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯381 36.1.4 WOW64⋯⋯⋯⋯⋯⋯⋯381 36.1.5 练习:WOW64Test⋯⋯⋯⋯⋯⋯⋯384 36.2 编译64位文件⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯385 36.2.1 Microsoft Windows SDK(Software Development Kit)……386 36.2.2 设置Visual C++2010 Express环境………………………………………………………386
第37章 x64处理器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯389
37.1 x64中新增或变更的项目⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯389 37.1.1 64位⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯389 37.1.2 内存⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯389 37.1.3 通用寄存器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯389 37.1.4 CALL/JMP指令⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯390 37.1.5 函数调用约定⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯391 37.1.6 栈&栈帧⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯392 37.2 练习:Stack32. exe& Stack64.exe⋯⋯⋯ 392 37.2.1 Stack32.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 392 37.2.2 Stack64.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 394 37.3 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯397
第38章 PE32+ ………………398
38.1PE32+ (PE+、PE64)⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯398 38.1.1 IMAGE NT HEADERS⋯⋯⋯⋯398 38.1.2 IMAGE FILE HEADER⋯⋯⋯398 38.1.3 IMAGE OPTIONAL HEADER⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯399 38.1.4 IMAGE THUNK DATA⋯⋯⋯401 38.1.5 IMAGE TLS DIRECTORY…403
第39章 WinDbg· ……………405
39.1 WinDbg· ………………………………………405 39.1.1 WinDbg的特征⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯405 39.1.2 运行WinDbg⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯406
39.1.3 内核调试⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯407 39.1.4 WinDbg基本指令⋯⋯⋯⋯⋯⋯⋯⋯⋯409
第40章 64位调试⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯411
40.1 x64环境下的调试器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯411 40.2 64位调试⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯411 40.3 PE32: WOW64Test x86. exe⋯⋯⋯⋯⋯⋯⋯413 40.3.1 EP代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯414 40.3.2 Startup代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯414 40.3.3 main()函数⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯415 40.4 PE32+: WOW64Test x64.exe⋯⋯⋯⋯⋯⋯ 416 40.4.1 系统断点⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯416 40.4.2 EP代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯417 40.4.3 Startup代码⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯418 40.4.4 main()函数………………………………………420 40.5 小结⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯423
第41章 ASLR⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯424
41.1 Windows内核版本⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯424 41.2 ASLR⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯424 41.3 Visual C++⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯424 41.4 ASLR.exe⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯ 425 41.4.1 节区信息⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯426 41.4.2 IMAGE FILE HEADER\Characteristics⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯427 41.4.3 IMAGE OPTIONAL HEADER\DLL Characteristics⋯⋯⋯⋯⋯⋯⋯⋯⋯428 41.5 练习:删除ASLR功能⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯428
第42章 内核6中的会话………………………………430
42.1 会话⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯430 42.2 会话0隔离机制⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯432 42.3 增强安全性⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯432
第43章 内核6中的DLL注入⋯⋯⋯⋯⋯⋯⋯⋯433
43.1 再现DLL注入失败⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯433 43.1.1 源代码…………………………………………………433 43.1.2 注入测试⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯435 43.2 原因分析⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯436 43.2.1 调试#1⋯⋯⋯⋯⋯⋯⋯436 43.2.2 调试#2⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯438 43.3 练习:使CreateRemoteThread()正常工作⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯440 43.3.1 方法#1: 修改CreateSuspended参数值⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯440 43.3.2 方法#2: 操纵条件分支……………441
Windows Visual Studio 的常用命令,例如:编译、连接、生成可执行文件等等
在 Windows Visual Studio 环境中,常用的命令涉及编译、链接、构建项目等操作。这些命令可以通过 Visual Studio 命令行工具(如 Developer Command Prompt
或 Developer PowerShell
)执行,也可以集成到脚本中实现自动化构建。以下是核心命令及其用途和示例:
一、核心命令行工具
1. MSBuild
(项目构建工具)
- 作用:用于构建
.sln
解决方案文件或.vcxproj
项目文件。 - 常用命令:
# 构建整个解决方案(默认配置) MSBuild MySolution.sln # 指定配置(Debug/Release)和平台(x86/x64) MSBuild MySolution.sln /p:Configuration=Release /p:Platform=x64 # 仅构建指定项目 MSBuild MyProject.vcxproj /t:Build
2. cl.exe
(C/C++ 编译器)
- 作用:编译 C/C++ 源文件(
.cpp
、.c
)生成对象文件(.obj
)。 - 常用命令:
# 编译单个文件(默认生成同名 .obj) cl /c main.cpp # 指定输出文件名和优化选项 cl /c /O2 /Fomain.obj main.cpp # 多文件编译(生成多个 .obj) cl /c file1.cpp file2.cpp
3. link.exe
(链接器)
- 作用:将对象文件(
.obj
)和库文件(.lib
)链接为可执行文件(.exe
)或动态库(.dll
)。 - 常用命令:
# 链接生成可执行文件 link main.obj utils.obj /OUT:MyApp.exe # 链接动态库(生成 .dll) link /DLL math.obj /OUT:MathLibrary.dll # 指定依赖库(如 Windows API) link main.obj user32.lib gdi32.lib /OUT:MyApp.exe
二、常用编译和链接参数
1. 编译器参数(cl.exe
)
参数 | 说明 |
---|---|
/c | 仅编译,不链接(生成 .obj 文件) |
/O1 , /O2 , /Ox | 优化等级(最小体积、最大速度、全优化) |
/I<目录> | 添加头文件搜索目录(如 /I"C:\MyLib\include" ) |
/D<宏> | 定义预处理器宏(如 /DDEBUG ) |
/EHsc | 启用 C++ 异常处理(必须用于包含异常的代码) |
2. 链接器参数(link.exe
)
参数 | 说明 |
---|---|
/OUT:<文件名> | 指定输出文件名(默认基于第一个输入文件) |
/LIBPATH:<目录> | 添加库文件搜索目录(如 /LIBPATH:"C:\MyLib\lib" ) |
/SUBSYSTEM:CONSOLE | 控制台应用程序(显示命令行窗口) |
/SUBSYSTEM:WINDOWS | GUI 应用程序(不显示命令行窗口) |
/DEBUG | 生成调试信息(需与 /Zi 编译选项配合) |
三、完整构建流程示例
1. 手动编译链接
# 编译源文件
cl /c /EHsc /Iinclude /O2 src\main.cpp src\utils.cpp
# 链接生成可执行文件
link main.obj utils.obj /OUT:MyApp.exe /LIBPATH:lib user32.lib
2. 使用 devenv
命令(集成 IDE 功能)
# 构建解决方案(不打开 IDE)
devenv MySolution.sln /Build "Release|x64"
# 清理项目
devenv MySolution.sln /Clean
四、实用环境配置
1. 设置环境变量
- 运行
Developer Command Prompt
或Developer PowerShell
,它们已自动配置以下路径:- 编译器/链接器路径:如
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.30.30705\bin\Hostx64\x64
。 - 库和头文件路径:自动包含 Windows SDK 和 CRT 路径。
- 编译器/链接器路径:如
2. 查看工具版本
# 查看 cl.exe 版本
cl /?
# 查看 link.exe 版本
link /?
五、进阶工具与场景
1. nmake
(Makefile 构建)
- 作用:通过
Makefile
文件管理构建流程。 - 示例:
# 生成 Makefile(需编写规则) nmake /f MyMakefile.mak
2. vcpkg
(包管理)
- 作用:集成第三方库(如 OpenSSL、Boost)。
- 示例:
# 安装库并集成到项目 vcpkg install zlib:x64-windows
3. dumpbin.exe
(查看二进制信息)
- 作用:分析可执行文件或库的符号、依赖项。
- 示例:
# 查看 DLL 的导出函数 dumpbin /exports MyLibrary.dll
总结
通过上述命令,可以在 Visual Studio 环境中实现高效的项目构建与管理。掌握 MSBuild
、cl.exe
和 link.exe
的核心用法,能够灵活应对从简单编译到复杂项目构建的需求,同时结合 Developer Command Prompt
的环境配置,确保编译链接过程无缝衔接。
windows下 ULONGLONG DWORD WORD BYTE 分别是多少字节?
在 Windows 平台下,以下数据类型的大小如下:
数据类型 | 字节数 | 位数 | 说明 |
---|---|---|---|
BYTE | 1 字节 | 8 位 | 无符号字符类型,等同于 unsigned char 。 |
WORD | 2 字节 | 16 位 | 无符号短整数类型,等同于 unsigned short 。 |
DWORD | 4 字节 | 32 位 | "双字"(Double Word),等同于 unsigned long (注意:在 Windows 中始终为 4 字节)。 |
ULONGLONG | 8 字节 | 64 位 | 无符号长长整数类型,等同于 unsigned long long (C++11 中的 uint64_t )。 |
关键说明:
固定大小:
这些类型在 Windows SDK 中明确定义,与编译器和系统架构(32/64位)无关,保证兼容性。用途:
BYTE
:处理二进制数据(如文件、网络流)。WORD
/DWORD
:与 Windows API 交互(如GetLastError()
返回DWORD
)。ULONGLONG
:表示大范围数值(如文件大小、时间戳)。
跨平台注意:
非 Windows 系统(如 Linux)需使用<stdint.h>
的类型(如uint32_t
),但 Windows 下优先使用上述类型以保证与 API 兼容。
验证方式:
可通过 sizeof
运算符直接验证(示例代码):
#include <Windows.h>
#include <stdio.h>
int main() {
printf("BYTE: %zu\n", sizeof(BYTE)); // 输出 1
printf("WORD: %zu\n", sizeof(WORD)); // 输出 2
printf("DWORD: %zu\n", sizeof(DWORD)); // 输出 4
printf("ULONGLONG: %zu\n", sizeof(ULONGLONG)); // 输出 8
return 0;
}