第一章 基础知识
什么是加密与解密
加密: 开发者通过一些方法保护自己的程序中的代码逻辑。目的是使代码逻辑不易被拥有黑客技术的人获取,从而保护自己的程序不被盗版或是破解。
解密: 通过逆向分析技术,还原程序的代码逻辑,目的是突破程序限制,或是开发功能相似的程序。
加密与解密的过程,外至用户层,深至内核层,涉及范围之广,知识量之庞大。经过这些年的发展,加密与解密的对抗过程,宛如战场上攻守双方的不断拼杀。
逆向工程: 根据现有程序,还原代码逻辑的过程。从软件的视角出发,“可执行程序 —-> 反编译 —-> 源码”的过程就是逆向工程。
逆向工程可实现的目的大致可分为三类:
- 突破程序的功能限制,或添加软件功能(如破解软件)
- 获取程序源代码/还原出相似的代码逻辑(研究他人的开发方法,或者是盗版)
- 硬件的复制和模拟(IC卡复制等)
除此之外,逆向工程还可以帮助安全人员分析对抗木马病毒,帮助开发者寻找BUG或是学习他人的开发经验。
这里笔者需要指出,由于编程语言的不同,对程序的逆向也会有不同的特点。部分编程语言所写的程序可以近乎完美地还原出源代码,如:java、打包成pyc的python脚本。而部分编程语言则无法直接还原出真正的源代码,只能做到相似,如:C/C++。
逆向工程所涉及的知识往往枯燥而庞杂,因此,笔者建议各位同学:
- 请带着热情与好奇来探索逆向的世界。单从学习角度出发,我不否定利益驱动型的同学,只要你能保持住学习的主动性,而不是因想捞钱而急功近利。
- 保持勤奋和毅力。逆向的学习是枯燥的,并且经常长时间没有正反馈。当然,经过这些年各路大佬的贡献,出现了如160个CrackMe等梯度难度题库,能在一定程度上将你的知识付诸实践。
- 精通至少一门编程语言。笔者在这里推荐C/C++。
- 编程经验、系统编程功底扎实。当然,你也可以在学习逆向的过程中,同步进行编程知识的学习。
- 用有一定的汇编语言基础。
逆向分析技术
逆向分析技术总体上分为两类:静态分析技术、动态分析技术。二者各有利弊。
除开这些技术层面的分析,如果你能够厘清程序各部分的功能的作用,做到使用层面的分析,对你技术分析是有一定程度帮助的。
静态分析技术:
使用如:IDA、Ghrida等静态分析工具,对程序进行反汇编/反编译,得到程序的汇编代码/源码,通过阅读代码的方式,搞清代码逻辑的过程,称为静态分析。静态分析工具往往将程序以片段的形式呈现,能有效地将各模块分开,并显示各模块之间的关系。静态分析时,程序是没有运行的。而有些时候,程序的功能是需要运行起来才能够正确分析,这是静态分析无法做到的,也是其缺点。
动态分析技术:
虽然静态分析技术能够帮助我们了解程序各模块的功能以及它们之间的关系,但是具体的技术细节我们却无法得到。比如有这么一个程序,它每次运行都会生成一个随机数,而你必须输入这个随机数才可以进一步使用这个软件。那么很显然,静态分析是无法得到每次运行的随机数的。这时候,就需要运用动态分析技术。
以下有三点我们需要动态分析技术的原因:
- 程序模块与模块之间的调用,往往存在一个中间结果(比如上文的随机数),而我们想要得到这个中间结果,必须使用动态分析技术。
- 有些程序在运行时,会先进行初始化操作,初始化完毕后,才是拥有完整功能的程序。而静态分析由于没办法进行初始化操作,故需要动态分析技术。
- 开发者为了保护自己的程序,会对代码进行加密。加密后的程序,是按照逐块解密,逐块执行的方式运行,如果不进行动态跟踪,逆向过程将停滞不前。
文本字符
计算机中存储的信息都是用二进制数表示的。屏幕上显示的文本也不例外。如果要处理文本,就必领先把文本转换为相应的二进制数。在学习过程中,我们会与各类字符打交道。这些字符在Windows里扮演着重要的⻆色。
ASCII与Unicode字符集
ASCII: 简单来说,就是使用一个字节的二进制数来表示字符。
Unicode: 使用两个字节来表示字符。
下图为常见ASCII表。
字节存储顺序
计算机界使用两种字节存储顺序:大端(Big-endian)、小端(Little-endian)
大端: 高位字节存入低地址,低位字节存入高地址
小端: 高位字节存入高地址,低位字节存入低地址
一个字节拥有八个比特位。 其中,前四个比特位称为高位字节。后四个比特位称为低位字节。比如12345678,1234位于高字节, 5678位于低字节。
将12345678h写入以1000h为开头的内存中,两种存储方式的表现如下图:
这两种顺序并无优劣之分。而计算机实际使用什么存储顺序,取决于CPU。Intel的CPU一般采用小端序
Windows操作系统
Win32 API函数
API: 全称”Application Programing Interface“(应用程序编程接口)。是开发者调用Windows系统服务的一个接口。
Windows系统自带的Kernel.dll、User.dll、GDI.dll提供了大量重要的API
除上述三个外,还有其他的提供很多重要功能的dll。这里不列举。
由于ASCII和Unicode字符集使用的字节数不同,Windows为了兼容,在实现同一个功能时,可以根据字符集的不同而选择不同的函数。比如MessageBox函数,它拥有两个版本,一个是MessageBoxA(ANSI版),另一个是MessageBoxW(Unicode版)。一般来说,Windows都是以结尾的A/W来表明函数适用的字符集。
WOW64: 全称“Windows-on-Windows-64-bit“。是64位Windows操作系统的子系统,它的存在使得32位程序得以在64位操作系统中运行。
WOW64勾住了所有从32位代码转变至原生64位系统的代码路径,也勾住了64位原生系统需要调用至32位用户模式代码的所有路径。在进程创建的过程中,进程管理器(process manager)将原生的64位Ntdll.dll和针对WOW64进程的32位Ntdll.dll映射到进程地址空间中。当加载器的初始化过程被调用时,它调用WOW64.dll内部的WOW64初始化代码。然后WOW64建立起32位Ntdll所要求的启动环境,将CPU模式切换至32位下,并开始执行32位加载器。从这个点开始,执行过程继续执行,就如同该进程运行在原生的32位系统之上。
Ntdll.dll,User32.dll和Gdi32.dll的特殊32位版本位于\Windows\Syswow64文件夹下。它们调用到WOW64.dll中,而不是发出原生的32位系统调用指令。WOW64转变到原生的64位模式下,并捕获到与系统调用有关的参数(将32位指针转化为64位指针),并发出对应的原生64位系统调用。当原生的系统调用返回时,WOW64把任何输出参数,如果有必要的话,在返回至32位模式之前从64位转换成32位格式。
简单来说,比如:64位系统中存着的32位Ntdll.dll想和内核层沟通时,无法直接发送32位的系统调用指令,而会经过WOW64转换成64位系统调用指令后再发送,之后接收到的64位的结果会经由WOW64转回32位。
Windows消息机制
Windows系统中有两种消息队列:一种是系统消息队列;另一种是应用程序消息队列。计算机的所有输人设备由Windows监控。当一个事件发生时,Windows 先将输人的消息放人系统消息队列,
再将输人的消息复制到相应的应用程序队列中,应用程序中的消息循环在它的消息以列中检素每个 消息并发送给相应的窗口两数。 一个事件从发生到到达处理它的窗口⻄数必须经历上述过程。x
因为Windows本身是由消息驱动的,所以在调试程成中我们会得到相当底层的答案。因此我们需要了解这一机制。
值得注意的是消息的非抢先性,即不论事件的急与缓,总是按到达的先后排队(一些系统消息除外), 而这可能导致一些外部实时事件得不到及时的处理。
虚拟内存
32位操作系统最高支持4GB的物理内存。程序的数据和代码都在同一个地址空间中,不必区分代码段和数据段。但是,为了防止出错,程序员们引入了”虚拟内存“。这一机制通过映射(map)的方式实现。简单地说,虚拟内存的实现方法和过程如下:
- 当一个应用程序启动时,操作系统就创建一个进程,并给该进程分配2GB的虚拟地址(不是内存,只是地址)。另外2GB系统自用。
- 虚拟内存管理器将应用程序的代码映射到那个应用程序的虛拟地址中的某个位置,并把当前需要的代码读人物理地址(注意:虚拟地址与应用程序代码在物理内存中的位置是没有关系的)。
- 如果使用 DLL,DLL 也会被映射到进程的虚拟地址空间中,在需要的时候才会被读人物理 内存。
- 其他项目(数据、堆栈符)的空间是从物理内存中分配的,并被映射到虚拟地址空间中。
- 应用程序通过使用其虚拟地址空间中的地址开始执行。然后,雕拟内存管理器把每次内存 访问映射到物理位置。
Windows是一个分时多任务操作系统。CPU时间会被分为一片片,供不同的应用程序使用。在属于程序A的时间片里,虚拟地址空间不会存在与程序A无关的内容。
看不明白上面的步骤也没关系,但需要明白以下几点:
- 应用程序不会直接访问物理地址。
- 虚拟内存管理器通过虚拟地址的访问请求来控制所有的物理地址空间。
- 在32位系统中,每个应用程序拥有独立的4GB寻址空间,不同的程序的寻址空间是彼此隔离的。
- DLL没有“私有”空间,它们总是被映射到其他应用程序的虚拟地址空间中,因为不在一起的话,程序就无法调用它。
64位操作系统也采用虚拟内存机制。只不过是寻址空间大小不止4GB,而是16TB。