《加密与解密》读书笔记1

1、常用断点包括INT3断点、硬件断点、内存断点和消息断点。

        1.1 INT断点:一个常用的断点,在OD和x64dbg中按F2快捷键来设置/取消断点。当执行一个INT3断点时,该地址处的内容就被调试器使用INT3指令替换掉了,此时OD和x64dbg将INT3隐藏,显示出来的仍然是中断前的指令。这个INT3指令,因其机器码是0xCC,也常被称为CC指令。当被调试进程执行INT3指令导致一个异常时,调试器就会捕获这个异常,从而停在断点处,然后将断点处的指令恢复成原来的指令。INT3断点的优点是可以设置无数个断点,缺点是改变了原程序机器码,容易被软件检测到。

        1.2硬件断点:硬件断点和DRx调试寄存器有关,如下图所示:

        DRx调试寄存器总共有8个(DR0~DR7),每个寄存器的特性如下:

                A) DR0~DR3:调试地址寄存器,用于保存需要捡尸的地址,例如设置硬件断点;

                B)  DR4~DR5:保留,未公开具体作用;

                C)DR6:调试寄存器组状态寄存器

                D)  DR7:调试寄存器组控制寄存器

       硬件断点的原理就是使用DR0、DR1、DR2、DR3设定地址,并使用DR7设定状态,因此最多设置4各个断点。硬件执行断点与CC断点的作用一样,但因为硬件执行断点不会讲指令首字节修改为CC,所以更难检测

        1.3内存断点:分为内存访问断点和内存写入断点,原理是对所设的地址赋予不可方访问/不可写属性,当访问/写入的时候就会产生异常。调试器补货异常后,比较异常地址是不是断点地址,如果是就中断,让用户继续操作。因为每次出现异常是都要通过比较来确定是否该中断,所哟内存断点会降低调试器的执行速度,因此OD考虑到执行速度才规定只能下一个内存断点。需要注意的是:硬件访问/写入断点是在触发硬件断点的下一条指令处下断,而内存断点是在触发断点处的指令处下断内存断点不修改原始代码,不会像INT3断点那样因为修改代码而被程序校验导致下断失败。因此,在遇到代码校验且硬件断点失灵的情况,可以使用内存断点。

        1.4消息断点:当某个特定窗口函数接收到某个特定断点时,消息断点使程序中断。消息断点与INT3断点的区别在于:INT3断点可以在程序启动之前设置,消息断点只有在窗口被创建之后才能设置并拦截消息。

        1.5条件断点:在调试过程中,我们希望断点在满足一定条件时才会中断,这类断点称为条件断点。OD的条件断点可以按寄存器、存储器和消息的等设断。条件断点是一个带有条件表达式的普通INT3断点。当调试器遇到这类断点时,断将点计算表达式的值,如果结果非零或者表达式有效,则断点生效,被调试程序将会暂停

2、PE文件中的模块:当PE文件通过Windows加载器载入内存之后,内存中的版本称为模块。映射文件的起始地址称为模块句柄,也称为基地址。如下图所示:

内存中模块代表进程将这个可执行文件所需要的代码、数据、资源、输入表和输出表以及其他有用的数据结构所使用的内存都放在一个连续的内存块中。程序员只需要知道装在程序映像到内存后的基址,PE文件的剩余部分可以被读入,但可能无法映射。基地址的值是由PE文件本身设定的。

        2.1、虚拟地址VA:每个程序都有自己的虚拟空间,这个虚拟地址空间中的内存地址称为虚拟地址VA。

        2.2、相对虚拟地址VA:。一个进程被操作系统加载到虚拟内存空间后,其相关的动态链接库也会被加载,这些同时加载到进程地址空间的文件被称为模块。每个模块在加载时都会有一个基地址,也就是预先告诉操作系统,它会占用4GB空间中的哪个部分(即从哪里开始存储该模块),不同模块的基址一般是不同的,但如果两个模块的基地址相同,就需要操作系统来决定这两个模块在虚拟空间中的具体位置。相对虚拟内存地址RVA是相对于模块基地址的偏移,即RVA是虚拟内存中用来定位某个特定位置的地址,该地址的值是这个特定位置距离某个模块基地址的偏移量,所以说RVA是针对某个模块而存在的RAV与具体模块相关,它有一个范围,该范围从模块的开始到模块的结束,脱离开这个范围的RVA是无效的,称为越界。越界的RVA地址没有任何意义。VA是相对于整个进程的地址空间而言的,RVA是相对于模块而言的

        2.3文件偏移地址:当PE文件存储在磁盘时,某个数据相对于文件头的偏移量称为文件偏移地址FOA,FOA和内存无关。文件偏移地址从PE文件的第1个字节开始计数,起始值为0。用十六进制工具打开文件时所显示的地址就是文件偏移地址。

        分页机制的基本原理:32位环境下,操作系统假设一个进程拥有4GB的独立内存空间,按照某个固定大小将这4GB空间分成N个页面(如果固定大小是4KB,则页面个数N为1M)。物理内存(即真实存在的内存条)也按照固定大小被划分成多个页框,在64位环境中,大部分页面都是处于未分配状态。在某一时刻,已分配的页面中只有一部分和物理内存中的页框是一一对应的。已经被分配给进程但没有页框对应的且页面被标记为脏的页面,一般存储在一个名为“交换文件”的磁盘文件中。当系统需要读取未在内存中的数据时,这部分数据会将不经常读写的页交换出内存,而把要读取的、位于交换文件中的页换进内存。

        PE文件的基本结构:从DOS头(DOS header)到节区头(Section header)是PE头部分,剩下的节区合称PE体。文件中是偏移,内存中使用VA来表示位置。文件加载到内存时,情况就会发生变化(节区的大小、位置等)。PE文件的内容(即PE文件体)一般分为代码节、数据节、资源节,分别保存。各个节区对应的节区头定义了各个节区在文件或者内存中的大小、位置、属性等

        2.4 PE头:PE Header是PE相关结构NT头(IMAGE_NT_HEADER)的简称。当执行体在支持PE文件结构的操作系统中执行时,PE装载器从IMAGE_DOS_HEADER结构的e_lfanew字段找到PE HEADER的起始偏移量,用其加上基址,得到PE文件头的指针:

                PNTHEADER = ImageBase + dosHeader->e_lfanew

     NT头由PE头标识、PE文件头、PE可选头(又称为扩展头)组成。NT头下的文件头中的一个字段指出了PE可选头的大小,如下图所示(图中偏移量是相对于PE文件头而言的):

        需要注意的是NT头下的PE文件头中,使用了NumberOfSections来指出文件中存在的节区数量

        PE可选头虽然名称带有可选二字,但是PE文件头结构不足以定义PE文件的属性,但实际上完全不必考虑文件头和可选头之间的区别,二者连起来才是一个完整的PE文件头结构。PE可选头的定义提如下图(图中偏移量也是相对于PE文件头而言的):

        需要特别说明的是NT头下可选头中的ImageBase:PE文件被加载到虚拟内存中时,ImageBase指出文件的优先载入地址。EXE、DLL文件被载入到用户内存的(0~2^31-1)中,SYS文件被载入到内核内存中的(2^31~2^32-1)中。一般而言,使用开发工具创建好EXE之后,ImageBase的值为0x00400000,DLL文件的ImageBase值为0x10000000(当然也可以指定为其他值)。执行PE文件时,PE装载器先创建进程再将PE文件载入内存,然后把EIP寄存器的值设置为ImageBase+AddressOfEntryPoint

        2.5、区块表(节区表,节区头):紧跟在IMAGE_NT_HEADERS之后,它是一个IMAGE_SECTION_HEADER结构体的数组。每个区块表包含了它所关联的区块的信息,如位置、长度、属性。该数组的长度由NT下的PE文件头中的NumberOfSection字段指出。节表后面就是节的内容。在PE文件中创建多个节区的好处是:可以保证程序的安全性。若把code与data放在一个节区中相互纠缠很容易引发安全问题,即使忽略过程的烦琐。PE文件格式的设计者将具有相同属性的数据统一保存在一个被称为节区的地方,然后把各个节区的属性记录在节区头中(节区属性中有文件/内存的起始位置、大小、访问权限等)。不同节区的特性和访问权限如下所示:

        PE文件中“映像”的概念:PE加载到内存时,文件不会原封不动地加载,而要根据节区头中定义的节区起始地址、节区大小等加载。因此磁盘文件中的PE与内存中的PE具有不同的形态。将装载到内存中的形态称为“映像”以示区别

       2.6区块(节区):无论是结构化程序设计还是面向对象程序设计,都提倡程序与数据的独立性(主要是为了安全性),因此,程序中的代码和数据通常是分开的。为了保证程序执行的安全和内核的稳定,Windows操作系统通常对不同用途的数据设置不同的访问权限,如代码段中的字节码在程序运行期间只能读,不允许写,数据段在程序运行过程中允许读和写,常量段也只允许读。Windows操作系统在加载可执行文件时,会为这些具有不同属性的数据分配标记有有不同属性的页面,以保证程序运行时的安全性。因此,PE中出现了节区的概念。节就是存放不同类型数据(如代码、数据、常量和资源)的地方,不同的节具有不同的访问权限。节是PE文件中存放代码或者数据的基本单元。一个节中的所有原始数据必须被加载到连续的内存空间中。从操作系统加载的角度来看,节是相同属性数据的组合。与数据目录不同的是,尽管有些数据类型不同,分别属于不同的数据目录,但由于其访问属性相同,便被归类到同一个节中。这些节最终可能会占用一个或者多个页面,但无论占用多少个页面,所有相关页面均会被赋予相同的页面属性(只读,只写,可读可写等)。Windows操作系统在装载PE文件时会对这些数据进行抛弃、合并、新增、复制等操作,这些不同操作的交叉组合导致了内存中的节和文件中的节会出现很大的不同

        PE文件中一般至少有两个区块,一个是代码块一个是数据块。每个区块都有特定的名字,用于表示这个区块的用途。区块在影像中是按照起始地址RVA排列的,而非按字母顺序排列。区块并非全部在链接时形成,更准确的说,它们一般是从Obj文件开始被编译器放置的。链接器的工作就是合并所有Obj和库中需要的块,使其成为一个更合适的区块。合并区块的优点是节省磁盘和内存空间。区块的大小是需要对齐的,有两种对齐值:文件对齐和内存对齐。在PE文件中,FileAlignment定义了磁盘区块的对齐值。每一个区块从对齐值的倍数的偏移位置开始,但区块实际上使用的长度不一定是这么多,在不足的地方一般用00h来填充,这就是区块的间隙。同样,SectionAlignment定义了PE文件在内存中的对齐值,当PE文件映射到内存中时,区块总是至少从一个页边界处开始。也就是说,当一个PE文件被映射到内存中时,每个区块的第一个字节对应于某个内存页面。在x86系列CPU中,内存是按照4KB(0x1000)排列的,在x64中,内存是按照8Kb(0x2000)排列的。

        2.7、文件偏移与虚拟地址的转换:一些PE文见为减少体积,磁盘对齐值不是一个内存页0x1000,而是0x200,当这类文件映射到内存之后,统一数据相对于文件头的偏移量在内存和磁盘文件中是不同的。这样就出现了文件偏移地址又虚拟内存地址的转换问题。而那些磁盘对齐值0x1000与内存页相同的区块,同一数据在磁盘文件中的偏移与内存中的偏移相同,因此不需要转换。下图中显示了实例文件在磁盘中华与内存中各个区块的地址、大小等信息。虚拟地址和虚拟大小是指该区块在虚拟地址空间中的地址和大小,物理地址和物理大小是指该区块在磁盘文件中的地址和大小。(图中磁盘对齐值为0x200, 与内存对齐值不同,故磁盘映像和内存影像不同)

        PE文件被映射到内存中时,MS-DOS头部、PE文件头和块表的偏移位置与大小均没有发生变化(即PE文件头在磁盘中和内存中是没有变化的),而当各区块被映射到内存中,其偏移位置就发生了变化。因此文头部数据结构中的所有RVA字段都可以通过比较方法将该RVA定位到某个具体的节,有了这个节的内存起始RVA0,就可以求出指定RVA距离节头的偏移offset,而这个偏移在磁盘文件中和内存中都是一样的(因为对齐时是在节的数据后面补0而不是前面)。根据RVA求FOA的步骤:1)判断RVA落在哪个节中;2)求出该节的起始RVA0=IMAGE_SECTION_HEADER.VirtualAddress;3)求出偏移量offset=RVA- RVA0;4)FOA = IMAGE_SECTION_HEADER.PointerToRawData + offset

        2.8、数据目录项IMAGE_DATA_DIRECTORY:该字段为NT头的最后一个字段,它定义了PE文件中出现的所有不同类型的数据的目录信息。应用程序中的数据按照用途被分成很多种类,如导入表、导出表、资源、重定位等。在内存中这些数据被操作系统以页为单位组织起来,并赋予不同的访问属性;在文件中,这些数据也同样被组织起来,按照不同类别分别存放在文件的指定位置。数据目录项就是用来描述这些不同类别的数据在文件(和内存中)的位置以及大小的。该结构体只有两个字段,如下图所示:

        2.9、为了避免内存浪费,Windows OS设计者引入了DLL,其设计思想如下:1)不要把库包含到程序中,单独组成DLL文件,需要时使用;2)内存映射技术使加载后的DLL代码、资源在多个进程中实现共享;3)更新库时只要替换相关DLL文件即可,简便易行。加载DLL的方式有两种:显示链接(程序使用DLL时加载,使用完之后释放内存)和隐式链接(程序开始时即一同加载DLL,程序结束时再释放占用的内存)。IAT提供的机制与隐式链接有关。实际操作过程无法保证DLL一定会被加载到PE头内指定的ImageBase处。但是EXE文件(生成进程的主体)却能准确加载到自身的ImageBase中,因为EXE文件拥有自己的虚拟空间。DLL重定位使得我们无法对实际地址进行硬编码,另一个无法硬编码的原因是PE头中表示地址时不使用VA而是RVA。

        2.10、导入表:可执行文件使用来自其他DLL的代码或者数据的动作称为输入。当PE文件被载入时,Windows加载器的工作之一就是定位所有被输入的函数和数据,并让正在载入的文件可以使用那些地址,这项工作是通过PE文件的导入表Import Table完成的。导入表中保存的是函数和其驻留的DLL等动态链接所需的信息。输入函数就是被程序调用但其执行代码不在程序中的函数,这些函数的代码位于相关的DLL文件中,在调用程序中只保留相关的函数信息,如函数名和DLL文件名等。对于磁盘上的PE文件来说,它无法得知这些输入函数在内存中的地址。只有当PE文件载入内存之后,Windows加载器才将相关DLL载入,并将调用输入函数的指令和函数所处的地址联系起来。如果一个动态库在一个进程被加载过,而且在其他进程中也引用了该链接库的函数,操作系统不会再次加载这个动态链接库,而是用过页面调度基址使两个进程同时访问一个动态链接库,为了节约内存资源,操作系统只保证一份代码存在于物理内存中,不同进程中加载地址不同的相同动态链接库,其实只是页面存取机制下的一个映射而已。执行一个普通程序时往往需要导入多个库,一个导入库对应于一个IDD,导入多少库就存在多少个IMAGE_IMPORT_DESCRIPTOR(IDD)结构体,这些结构体形成了数组,且结构体数组最后以NULL结构体结束。

        OriginalFirstChunk包含了指向导入名称表INT的RVA,INT是一个IMAGE_THUNK_DATA结构的数组,数组中的每个IMAGE_THUNK_DATA结构都指向IMAGE_IMPORT_BY_NAME结构,数组以一个内容为0的IMAGE_THUNK_DATA结构结束。FirstThunk包含指向导入地址表IAT的RVA,IAT也是一个IMAGE_THUNK_DATA结构的数组

         注意:PE文件中的表指的是数组,INT和IAT是长整型(4个字节数据类型)数组,以NULL指出。(未另外明确指出大小),INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值),INT与IAT的大小应相同。OriginalFirstThunk与FirstThunk相似,它们分别指向两个本质上相同的IMAGE_THUNK_DATA结构的数组,如下图所示:

        IMAGE_THUNK_DATA结构体实际上是一个双字,但在不同的时候拥有不同的解释,包括如下两种:1)双字最高位0,表示导入符号是一个数值,该数值是一个RVA;2)双字最高为1,表示导入符号是一个名称

        每一个结构IMAGE_DATA_DESCRIPTOR都对应于一个唯一的DLL文件,以及引用了该动态链接库的多个函数,每个函数的最终“值-名称”描述都可以沿着如下图所示的桥1或者桥2找到,这种导入表结构称为双桥结构。

        双桥结构的导入表在文件中存在着两份完全相同的地址列表,一般情况下,桥2指向的地址列表对应于IAT,桥1指向的地址列表对应于INT。PE文件中所有导入函数jmp指令操作数的集合组成了导入函数地址表IAT,该表是数据目录的第13个数据目录项。导入表与导入函数地址表IAT的关系如上图所示。IAT是一个双字的数组,该数组中的每个元素代表的是一个导入函数的VA(导入函数地址IA)。用户指令可以jmp指令跳转到VA指定处,便可以运行导入函数的指令。由于IAT中定义了不止一个DLL库的函数,为了进行区分,规定所有导入函数按照链接库进行分类:相邻链接库的函数地址排列在一起,最后以一个双字的0表示IAT的结束。通过上图中的桥2即可定位IAT。在内存中,桥1可以让我们找到调用函数名称或者函数的索引编号,桥2可以帮助我们找到该函数指令代码在内存空间中的地址。但是当PE文件被加载到虚拟地址空间中后,IAT的内容会被操作系统更改为函数的VA,这个修改最终会导致桥2中IAT通向“值-名称”的连接发生断裂,如下图所示:

        当桥2发生断裂以后,如果没有桥1作为参照(桥1和桥2维护了两个一一对应的函数RVA),我们就无法找重新找到该地址对应的函数名(根据IAT和INT中的数组下标(或数组索引)进行对应),这就是为什么会在导入表数据结构中存在两个桥的原因,也是为什么单桥导入表结构无法实施绑定的原因。

参考链接:

        1、《加密与解密 第4版》

        2、《Windows PE文件权威指南》

        3、《逆向工程核心原理》

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值