《基于Windows进程状态提取与恢复的研究毕业论文.doc》由会员分享,可在线阅读,更多相关《基于Windows进程状态提取与恢复的研究毕业论文.doc(54页珍藏版)》请在三一办公上搜索。
1、毕 业 论 文论文题目: 基于Windows进程状态提取与恢复的研究姓 名 学 号 学 院 计算机科学与技术学院 专 业 计算机科学与技术 年 级 2007级 指导教师 2011年 6月 5日目录摘 要5ABSTRACT6第1章 绪论71.1课题研究背景71.2 进程检查点系统的研究现状81.3 本文主要工作8第2章 WINDOWS系统内存管理机制102.1 CPU工作方式102.2 进程地址空间102.2.1 Windows地址空间基础102.2.2 用户地址空间分布112.2.3 系统地址空间分布132.3 内存映射132.3.1 内存映射基础132.3.2 虚拟内存的使用152.3.3
2、内存映射的可执行文件和DLL文件172.3.4 在DLL的多个实例之间共享静态数据18第3章 DLL注入193.1 DLL基础193.2 DLL的运行机制 203.3 DLL的创建233.4 注入技术的分析和比较243.4.1. 利用注册表注入243.4.2. 建立系统范围的Windows钩子253.4.3 使用 CreateRemoteThread函数263.4.4 通过BHO来注入DLL283.5 编码实现283.5.1 钩子的挂接283.5.2 挂起和恢复进程313.5.3 获取节数据313.5.4 保存和恢复进程状态343.6 PE文件36第4章 程序功能演示和结语384.1程序功能演
3、示384.2 结语39致谢41参考文献42附录1.英文原文43附录2.中文翻译51基于Windows用户级进程状态提取与恢复摘 要 基于用户级Windows进程状态的提取与恢复是在进程正常运行的适当时刻注入DLL文件,在用户级实现进程的停止、启动和将进程状态保存到稳定存储器中。从磁盘文件中读出保存的进程状态,实现进程重启后的状态恢复。 Windows内核分为16位的Win95和98、32位的WinNT以及用于嵌入式开发的WinCE。本文针对32位的WinNT内核实现,支持Windows 2003、XP以上版本。本文重点讨论了基于用户级Windows进程状态的提取与恢复实现的基本原理和步骤,并对
4、其进行了编码实现。文章首先对基于用户级Windows进程状态的提取与恢复实现的理论基础和实现机制进行了阐述,然后分析和编码实现了动链接库注入,最后对完成实例程序的功能进行了介绍。本文对不同的动态链接库注入技术进行了分析和比较,并采用钩子技术实现,给出了相应的重点代码,详细阐述了实现机制。关键字:动态链接库、PE文件结构、容错技术ABSTRACTThe extraction and recovery user-level process state in windows is to inject the DLL file at the appropriate time; while the ap
5、plication is running and you can stop or start it on the user-level .Process status can also be saved to stable storage. When restarted, the saved information can be read to restart the execution of the application. Windows kernel can be divided into 16-bit Win95 and Win98、32-bit WinNT and embedded
6、used Wince. In the following, if not named in detail Windows refer to 32-bit WinNT. This article focuses on the basic principles and steps of extraction and recovery user-level process state in windows and implements its encoded. At first we describe the theoretical basis and implementation mechanis
7、ms of extraction and recovery user-level process state in Windows, then analyze and implement the injections of dynamic link library. Finally, we give a brief introduction to the functions of the application that we completed.In this paper, we take an analysis and comparison among different techniqu
8、es of injection of the dynamic link library and implement it with hook technique. We present corresponding code implementation to elaborate the realization of mechanism described in detail.Keywords: dynamic link library, PE file structure, fault-tolerance第1章 绪论1.1课题研究背景随着Windows操作系统的不断普及,基于该操作系统下的应用
9、也越来越广泛。Windows软件的容错性越来越受到重视,由于Windows是不公开内核的操作系统,许多研究通过实现用户级进程检查点设置与回卷恢复1,在不修改系统内核的前提下提高Windows软件的容错性。本文研究基于WinNT内核下的用户级进程状态提取与恢复是基于后向恢复的异构分布式系统容错技术的研究与实现的子课题,基于检查点技术实现分布式计算系统故障检测,能够保存和恢复程序的运行状态,因此在许多相邻领域都有重要的应用2-4。1. 进程迁移 目前大多数操作系统还不能提供进程迁移功能,利用检查点可以保存进程在某台机器的运行状态,然后在其他机器上恢复进程的运行以实现进程迁移。进程迁移可以使集群系统
10、负载平衡,从而提高计算速度和集群的利用率。2. 容错 分布式系统的故障率随系统机器数的增加而增加,长时间运行的作业若在每次出现机器故障都从头开始执行,该作业很难被执行完毕。因此利用检查点实现多机器系统容错称为人们日益关心的热点。3. 卷回调试 在程序调试过程中,利用检查点保存程序在某个时刻的运行状态。当错误发生时,把程序卷回到保存前的某一个时刻的状态重新向下运行,以再次产生相同的错误来查找错误发生条件的调试方法称为卷回调试。分布式程序包含较多的不确定成分,当进程发生运行错误重新运行程序查找错误原因时,同样的错误可能很难再次出现。采用卷回调试在很大程度上提高错误再次发生的概率。1.2 进程检查点
11、系统的研究现状 进程检查点可以分别在操作系统级、用户级或者应用程序级实现,但各自有其优缺点。操作系统级实现的进程检查点5对用户程序透明,容易得到进程的内核数据结构,但需要修改系统内核,因此对于基于封闭操作系统的进程检查点难以实现,并且可以配置性和移植性不高,检查点开销很大。 用户级的进程检查点6-11,将检查点功能编译为一个库连接到应用程序,可以实现对应用程序透明,易于配置且开销较小,但其实现机制与操作系统平台密切相关,无法实现不同系统平台的迁移。应用程序级的进程检查点12优点是能够实现平台无关,可在不同的操作系统间移植;缺点是只限于几种有限的编程语言,目前仅有基于JAVA虚拟机的应用程序级检
12、查点。由于用户级的进程检查点具有对应用程序透明,易于配置、开销较小且实用等特点,因此大部分检查点系统都选择在用户级实现。而Unix和WinNT操作系统都是非常普及的操作系统,因此,大部分进程检查点系统都是基于Unix或者WinNT实现的。1.3 本文主要工作随着PC机使用范围的不断扩大,Windows系统已经成为一个日益流行的桌面平台,尤其在中低端市场,运行Unix系统的高级工作站逐渐被价格低廉、使用方便且符合行业开放性标准的WinNT的PC机所取代。与此同时,基于WinNT操作系统的集群系统也开始出现。典型的由美国Illinois大学研制的HPVM及由Rice大学开发的Brazosl20l。
13、由于WinNT操作系统在集群系统中的运用,随故障负载和隶属关系的影响,具有变化特性。特别是随着系统规模的不断扩大,其在计算过程中发生故障的几率会以指数级增长,对于大规模科学工程计算任务来说,任务的计算时间比较长,一旦发生异常,可视为故障,会导致并行计算的彻底失败,此前的大量计算不再可用,尤其是与Unix系统相比较,WinNT系统是一个相对不太健壮的操作系统。因此研究WinNT系统的容错性越来越受到重视。为提高WinNT系统的鲁棒性,一般有两种方法提高系统可靠性。一是在硬件配置上或操作系统内部采取容错措施,如硬件冗余、文件备份等。对于大型系统而言这种方法是高效且实用的;但对于中低端市场而言,这种
14、措施的代价过于昂贵。第二种方法是在应用层完全用软件实现。即在不修改操作系统的前提下,通过提供库文件或高可用性运行环境来实现,其中的重要措施就是进程检查点设置与回卷恢复(checkpoint and rollback recoveryCRR)。本文主要工作是基于Windows用户级进程状态提取与恢复的实现原理和编码。代理进程(Test_hook_api)将动态库(hooklib.dll)注入目标进程(例 XX.exe),使得动态库hooklib.dll与目标进程位于同一进程虚拟地址空间。Hook lib库中的服务线程定时(使用可等待定时器内核对象,避免占用CPU时间)停止进程,读取节数据,保存节
15、数据至检查点文件。故障时,由故障检测模块通知代理进程,代理进程经共享内存通知动态库中的服务线程,服务线程停止进程,重启进程且处于挂起状态,把检查点文件中的节数据写入进程的各个节,然后启动进程。该系统由两个模块组成:(1) 注入动态链接库模块Test_hook_api。使用该模块对应用程序进行预处理,在不需要修改应用程序源代码,不对应用程序进行重新编译或者链接的前提下。透明地将动态链接库hooklib.dll插入到可执行文件中。(2) 动态链接库hooklib.dll。该库是系统的主要模块,实现API函数截获。进程状态的提取、保存和恢复。通过对WinNT进程机制的研究,本文提出了将进程状态的保存
16、和恢复到用户地址空间的实现思想。本文分为六章,第一章介绍了进程检查点的现状、基于用户级进程提取与恢复相关的领域。第二章介绍了Windows虚拟内存分配机制,CPU的工作方式和Windows内存模型。接下来三章分别详细介绍了基于用户级的Windows进程状态提取与恢复实现的关键技术DLL的注入、钩子的实现、PE文件等技术。着重介绍了基于Windows用户级进程提取与恢复的原理和编码实现。最后是全文的总结、致谢、参考文献和附录。第2章 Windows系统内存管理机制2.1 CPU工作方式首先,我们应该了解Windows系统内存管理机制,这是我们注入被拦截应用程序地址空间的基础。Windows系统能
17、够寻址空间随工作方式不同而不同,下面简要介绍其工作方式。CPU有三种工作方式13:虚拟8086模式、实模式和保护模式。只有在刚刚启动的时候是实模式,等操作系统运行起来以后就运行在保护模式。 实模式也称为实地址模式,只能访问地址小于1MB 内存称为常规内存,我们把地址在1M以上的内存称为扩展内存。由于CPU寄存器的地址仅有16位,这意味着应用程序可访问的连续线性地址空间仅有64KB,通过16位段寄存器的帮助,这个64KB大小的内存窗口就可以在整个物理空间中上下移动,64KB逻辑空间中的线性地址作为偏移量和基地址(由16位段寄存器给出)相加,从而构成有效的20位地址。保护模式,而叫做受保护的虚拟地
18、址模式是80286 CPU的工作方式。该模式全部32条地址线有效,可寻址4G物理地址空间,扩充的存储器分段管理机制和分页管理机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持;支持多任务,快速任务切换和保护任务环境,4个特权级和完善的特权检查机制。提供了选择器,该选择器由一个描述符表的索引构成。该描述符表的每一项都定义了一个24位的物理地址,允许访问16MB RAM,但是线性地址空间仍然被限制在64KB。虚拟8086模式时运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序。它不是一个真正的CPU模式,还属于保护模式。本文研究保护模式下的Windows进
19、程信息提取与恢复。2.2 进程地址空间2.2.1 Windows地址空间基础CPU寻址引入了3个术语,下面将进行简要介绍。逻辑地址:这是内存地址的精确描述,通常表示为16进制:xxxx: YYYY YYYY,这里xxxx为选择器,而YYYYYYYY是针对选择器所选段地址的线性偏移量。这里xxxx可以用段寄存器名字代替,如CS(代码段),DS(数据段),ES(扩展段),FS(附加数据段#1),GS(附加数据段#2)和SS(堆栈段)。线性地址:大多数应用程序和内核驱动程序都忽略虚拟地址。他们只对虚拟地址的偏移量部分感兴趣。而这一部分通常称为线性地址。此种类型的地址假定了一种默认的分段模型,这种模型
20、由CPU的当前段寄存器确定。物理地址:仅当CPU工作于分页模式时,这种地址才会变得非常有趣。本质上,一个物理地址是CPU插脚上可测量的电压。操作系统通过设立页表将线性地址映射为物理地址。本文研究内容采用保护模式下的虚拟内存管理机制。由 32 位的 Intel CPU 提供的 4GB 虚拟内存空间被分割为相等的两部分。低于 0x80000000 的内存地址由用户模式下的模块使用,这包括 Win32 子系统,剩余的 2GB 保留给了系统内核。表2-1显示了32位Windows系统中采用的虚拟地址空间的划分方法。表2-2 2GB Windows 2000/XP用户进程地址空间分布分区名称范围用途NU
21、LL指针分区0x0000 0000-0x0000 FFFF保护内存非法访问独享用户区0x0001 0000-0x7FFE FFFF进程只能读取或访问这个范围的虚拟地址;超越这个范围的行为都会产生违规退出。共享内核区0x8000 0000-0xFFFF FFFF这个空间提供操作系统内核代码、设备驱动程序、设备I/O高速缓存、页表等2.2.2 用户地址空间分布表2-2详细描述了2GB Windows 2000/XP用户进程地址空间的布局。表2-3显示的系统变量定义了用户地址空间的范围。表2-4列出了性能计数器提供的有关全部系统虚拟内存使用的信息。表2-5列出了进程性能计数器得到的单个进程地址空间的
22、使用情况。表2-2 2GB Windows 2000/XP用户进程地址空间分布范围大小功能0x0000 0000-0x0000 FFFF64KB 拒绝访问区域,避免程序员不正确的指针引用 用于帮助查找错误0x0001 0000-0x7FFE FFFF2GB减去至少192KB进程私有地址空间0x7FFD E000-0x7FFD EFFF4KB第一个线程的线程环境块(TEB)0x7FFD F000-0x7FFD FFFF4KB进程环境块(PEB)0x7FFE 0000-0x7FFE 0FFF4KB共享的用户数据页面 该只读数据页面映射到系统空间中包括系统时间、时钟计数、版本号信息,该页面存在使这些
23、信息可以直接从用户态读出0x7FFE 1000-0x7FFE FFFF60KB拒绝访问区域0x7FFF 0000-0x7FFF FFFF60KB拒绝访问区域 阻止线程跨过系统 用户边界传送那个缓冲区 MmUserProbeAddress中包含此区的起始地址表2-3 windows用户地址空间系统变量系统变量描述用户空间MmHighestUserAddress最高用户地址0x7FFE FFFFMmUserProbeAddress最高用户地址+10x7FFF 0000表2-4 Windows虚拟内存使用性能计数器性能计数器系统变量描述Memory:Committed BytesMmTotalCom
24、mittedPages提交的私有地址空间数量Memory:Commited LimitMmTotalCommitLimit在不增加页文件大小的情况下,可以提交的内存字节数Memory:%Committed Bytes in UseMmTotalCommittedPages/ MmTotalCommitLimit提交字节和提交限制之比表2-4 Windows虚拟内存使用性能计数器性能计数器描述Process:Virtual Bytes进程地址空间全部大小Process:Private Bytes私有地址空间占用地址大小Process:Page File Bytes私有地址空间占用地址大小Proc
25、ess:Peak Page File Bytes私有地址空间占用地址大小的峰值2.2.3 系统地址空间分布本段简要描述了系统地址空间的布局和内容。表2-6显示了x86系统上的2GB系统空间的全部结构。表2-6 x86系统地址空间布局系统代码和系统中一些初始的未分页缓冲池系统映射图或者会话空间附加系统PTS如高速缓存可以扩展到此进程的页表和页目录超空间和进城工作集表没有使用不可访问系统工作集列表系统高速缓存分页缓冲池系统PTE未分页缓冲池扩充故障转储信息HAL使用通过以上分析,可以知道,如果系统有n个进程,它所需的虚拟空间是:2G*n+2G (内核只需2G的共享空间)。 独立用户分区是进程的私有
26、地址空间,进程不能够以任何方式读、写其他进程此部分空间中的数据。对每个进程来说,它自己的、未被共享的保存数据都被保存在这块空间里。共享内核分区放置操作系统的代码,包括内核代码、设备驱动代码、设备I/0缓冲区等。系统空间部分在所有的进程是共享的。2.3 内存映射2.3.1 内存映射基础内存块指的是地址空间中的一片连续地址,大小必须是64k的整数倍,大部分是64k。内存块的状态有:空闲、私有、映射、映像。在创建进程并赋予进程地址空间时,这些可用地址空间的大部分是空闲的,即未分配。要使用这些地址空间的各个部分,必须调用VirtualAlloc14函数分配。对一个地址空间进行分配的操作称为保留操作。删
27、除空间的过程为释放,可以VirtualFree函数。在程序里预订了地址空间以后,仍然不可以存取数据,因为没有真实的RAM和它关联。这时候的区域状态是私有;默认情况下,区域状态是空闲;当exe或DLL文件被映射进了进程空间后,区域状态变成映像;当一般数据文件被映射进了进程空间后,区域状态变成映射。当系统访问一个字节的内存时,有两种情况。如图2-1所示。图2-1 虚拟地址与物理存储器地址转换流程图在第一种情况下,线程试图访问的数据是在RAM中。在这种情况下, CPU将数据的虚拟内存地址映射到内存的物理地址中,然后执行需要的访问。在第二种情况中,线程试图访问的数据不在RAM中,而是存放在页文件中的某
28、个地方。这时,试图访问就称为页面失效, CPU将把试图进行的访问通知操作系统。这时操作系统就寻找RAM中的一个内存空页。如果找不到空页,系统必须释放一个空页。如果一个页面尚未被修改,系统就可以释放该页面。但是,如果系统需要释放一个已经修改的页面,那么它必须首先将该页面从RAM拷贝到页交换文件中,然后系统进入该页文件,找出需要访问的数据块,并将数据加载到空闲的内存页面。然后,操作系统更新它的用于指明数据的虚拟内存地址现在已经映射到RAM中的相应的物理存储器地址中的表。这时CPU重新运行生成初始页面失效的指令,但是这次CPU能够将虚拟内存地址映射到一个物理RAM地址,并访问该数据块。已经分配的物理
29、存储器各个页面可以被赋予不同保护属性。表2-7显示了这些属性。表2-7页面的保护属性保护属性描述PAGE_NOACCESS如果试图在该页面上读取、写入或执行代码,就会引发访问违规PAGE_READONLY如果试图在该页面上写入或执行代码,就会引发访问违规PAGE_READWRITE如果试图在该页面上执行代码,就会引发访问违规PAGE_EXECUTE如果试图在该页面上对内存进行读取或写入操作,就会引发访问违规PAGE_EXECUTE_READ如果试图在该页面上对内存进行写入操作,就会引发访问违规PAGE_EXECUTE_READWRITE对于该页面不管执行什么操作,都不会引发访问违规PAGE_W
30、RITECOPY如果试图在该页面上写入内存,就会导致系统将它自己的私有页面(受页文件的支持)拷贝赋予该进程PAGE_EXECUTE_WRITECOPY该页面执行所有操作内存映射文件可以用于三个不同的目的: 1.系统使用内存映射文件,以便加载和执行.exe和DLL文件。这可以大大节省页文件空间和应用程序启动运行所需的时间。 2. 可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I/O操作,并且可以不必对文件内容进行缓存。 3. 可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。Windows确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方
31、法都是使用内存映射文件来实现的,这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。 2.3.2 虚拟内存的使用使用管理虚拟内存的函数,可以直接保留一个地址空间区域,将物理存储器提交到该区域,并且可以设置自己的保护属性。1. 在地址空间保留一个区域通过使用VirtualAlloc函数,可以在进程的地址空间保留一个区域:PVOID VirtualAlloc(PVOID pvAddress,SIZE_T dwSize,DWORD dwSize,DWORD fdwAllocationType.DWORD fdwProtect) 第一个参数pvAddress包含一个内存地址,用于设
32、定想让系统将地址空间保留在什么地方。NULL表示地址由系统分配。如果VirtualAlloc函数能够满足你的要求,那么它就返回一个指明保留区域的基地址值。 第二个参数dwSize用于设置想保留区域的大小,第三个参数表明分配的是保留区域还是物理存储器,最后一个参数设置文件属性(参考表2-7)。2. 提交物理存储器 若要提交物理存储器,必须再次调用VirtualAlloc函数。不过这次为fdwAllocationType参数传递的是MEM_COMMIT标志,而不MEM_RESERVE标志。3. 同时进行保留区域和提交内存 只需要fdwAllocationType参数传递的是MEM_COMMIT|M
33、EM_RESERVE标志即可。可以采用以下四种方法确定何时提交物理存储器:总是提交、通过VirtualQuery函数、保留提交记录、使用结构化异常处理(SEH)。4. 物理存储器的回收和释放 若要回收物理存储器可以调用VirtualFree函数。参数和Virtual Alloc一样,由于系统知道释放的内存大小故应设为0,第三个参数设为MEM_REALSE则释放内存,MEM_DECOMMIT则回收内存。决定回收时间可以使用结构来记录、无用单元收集函数或者将每个结构设计为一个页面。当创建一个线程时,系统就会为线程的堆栈保留一个1MB堆栈空间区域,将一些物理存储器提交给这个已保留的区域。但是,当调用
34、CreateThread或_begin ThreadEx函数时,可以重载原先提交的内存数量。这两个函数都有一个参数,可以用来重载原先提交给堆栈的地址空间的内存数量。如果设定这个参数为0,那么系统将使用/STACK开关指明的已提交的堆栈大小值。表2-2显示了在页面大小为4KB的计算机上的一个堆栈区域。表2-3 堆栈区域内存地址页面状态0x080F F000堆栈顶部 已提交页面0x080F DOOO带有保护属性标志的已提交页面0x0800 2000 0x0800 1000 0x0800 3000保留页面0x0800 0000堆栈底部:保留页面2.3.3 内存映射的可执行文件和DLL文件当线程调用C
35、reateProcess时,系统将执行下列操作步骤: 1) 系统找出在调用CreateProcess时设定的.exe文件。如果找不到这个.exe文件,进程将无法创建,CreateProcess将返回FALSE。 2) 系统创建一个新进程内核对象。 3) 系统为这个新进程创建一个私有地址空间。 4) 系统保留一个足够大的地址空间区域,用于存放该.exe文件。该区域需要的位置在.exe文件本身中设定。按照默认设置,.exe文件的基地址是0x0040 0000。 5) 系统注意到支持已保留区域的物理存储器是在磁盘上的.exe文件中,而不是在系统的页文件中。 当.exe文件被映射到进程的地址空间中之后
36、,系统将访问.exe文件的一个部分,该部分列出了包含.exe文件中的代码要调用的函数的DLL文件。然后,系统为每个DLL文件调用LoadLibrary函数,如果任何一个DLL需要更多的DLL,那么系统将调用LoadLibrary函数,以便加载这些DLL。每当调用LoadLibrary来加载一个DLL时,系统将执行下列操作步骤,它们均类似上面的第4和第5个步骤: 1) 系统保留一个足够大的地址空间区域,用于存放该DLL文件。该区域需要的位置在DLL文件本身中设定。按照默认设置, Microsoft的Visual C+ 建立的DLL文件基地址是0x1000 0000但是,你可以在创建DLL文件时重
37、载这个地址,方法是使用链接程序的/BASE选项。Windows提供的所有标准系统DLL都拥有不同的基地址,这样,如果加载到单个地址空间,它们就不会重叠。 2) 如果系统无法在该DLL的首选基地址上保留一个区域,其原因可能是该区域已经被另一个DLL或.exe占用,也可能是因为该区域不够大,此时系统将设法寻找另一个地址空间的区域来保留该DLL。如果一个DLL无法加载到它的首选基地址,这将是非常不利的,原因有二。首先,如果系统没有再定位信息,它就无法加载该DLL(可以在DLL创建时,使用链接程序的/ F I X E D开关,从DLL中删除再定位信息,这能够使DLL变得比较小,但是这也意味着该DLL必
38、须加载到它的首选地址中,否则它就根本无法加载)。第二,系统必须在DLL中执行某些再定位操作。在Windows 98中,系统可以在页面被转入RAM时执行再定位操作。在Windows 2000中,这些再定位操作需要由系统的页文件提供更多的存储器,它们也增加了加载DLL所需要的时间量。 3) 系统会注意到支持已保留区域的物理存储器位于磁盘上的DLL文件中,而不是在系统的页文件中。如果由DLL无法加载到它的首选基地址,Windows必须执行再定位操作,那么系统也将注意到DLL的某些物理存储器已经被映射到页文件中。 如果由于某个原因系统无法映射.exe和所有必要的DLL文件,那么系统就会向用户显示一个消
39、息框,并且释放进程的地址空间和进程对象。CreateProcess函数将向调用者返回FALSE,调用者可以调用GetLastError函数,以便更好地了解为什么无法创建该进程。 当所有的.exe和DLL文件都被映射到进程的地址空间之后,系统就可以开始执行.exe文件的启动代码。当.exe文件被映射后,系统将负责所有的分页、缓冲和高速缓存的处理。例如,如果.exe文件中的代码使它跳到一个尚未加载到内存的指令地址,那么就会出现一个错误。系统能够发现这个错误,并且自动将这页代码从该文件的映像加载到一个RAM页面。然后,系统将这个RAM页面映射到进程的地址空间中的相应位置,并且让线程继续运行,就像这页
40、代码已经加载了一样。当然,这一切是应用程序看不见的。当进程中的线程每次试图访问尚未加载到RAM的代码或数据时,该进程就会重复执行。2.3.4 在DLL的多个实例之间共享静态数据全局数据和静态数据不能被同一个.exe或DLL文件的多个映像共享,这是个安全的默认设置。但是,在某些情况下,让一个.exe文件的多个映像共享一个变量的实例是非常有用和方便的。本节将介绍一种方法,它允许你共享.exe或DLL文件的所有实例的变量。不过在介绍这个方法之前,首先让我们介绍一些背景知识。 每个.exe或DLL文件的映像都由许多节组成。按照规定,每个标准节的名字均以圆点开头。例如,当编译你的程序时,编译器会将所有代
41、码放入一个名叫. text的节中。该编译器还将所有未经初始化的数据放入一个. bss节,而已经初始化的所有数据则放入.data节中。 每一节都拥有与其相关的一组属性,这些属性如表2 - 3所示。表2-9 DLL文件各节的属性属性含义READ该节中的字节可以读取WRITE该节中的字节可以写入EXECUTE该节中的字节可以执行SHARE该节中的字节可以被多个实例共享(本属性能够有效地关闭c o p y - o n - w r i t e机制)除了编译器和链接程序创建的标准节外,也可以在使用下面的命令进行编译时创建自己的节:#pragma data_seg(“sectionname”) 定义全局变量
42、#pragma data_seg() 常见的节名和作用如表2-4所示:表2-10常见的节名和作用节名作用.text.exe和.dll文件的代码.data已经初始化的数据.bss未初始化的数据.reloc重定位表(装载进程的进程地址空间).rdata运行期只读数据.CRTC运行期只读数据.debug调试信息.xdata异常处理表.tls线程的本地化存储.idata输入文件名表.edata输出文件名表.rsrc资源表.didata延迟输入文件名表第3章 DLL注入3.1 DLL基础动态链接库(DLL)15是Dynamic Link Library 的缩写形式,DLL 是一个包含可由多个程序同时使用
43、的代码和数据的库。 (1)动态链接库是应用程序的一部分,它的任何操作都是代表应用程序进行的。所以动态链接库在本质上与可执行文件没有区别,都是作为模块被进程加载到自己的地址空间的。 (2)动态链接库在程序编译时并不会被插入到可执行文件中,在程序运行时整个库的代码才会调入内存,这就是所谓的“动态链接”。 (3)如果有多个程序用到同一个动态链接库,Windows在物理内存中只保留一份库的代码,仅通过分页机制将这份代码映射到不同的进程中。这样,不管有多少程序同时使用一个库,库代码实际占用的物理内存永远只有一份。 由于动态链接库的“动态链接”特性使得我们可以将用于Windows API 拦截的DLL在被
44、拦截程序运行过程中加载到它的进程地址空间中,也就是DLL注入。 另外,当多个应用程序是用同一个DLL时,该DLL只会被载入一次,所有应用程序都共享该DLL的页面,这样就可以在这些应用程序之间共享数据及其资源了。这是拦截进程如何得到获取的被拦截进程通信信息问题的基础,将在下文中进行详细的阐述。 Windows API 中的所有函数都包含在DLL中。3个最重要的DLL是Kernel32. DLL,它包含用于管理内存、进程和线程的各个函数; User32. DLL,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数; GDI32. DLL,它包含用于画图和显示文本的各个函数。3.2 D
45、LL的运行机制 为了全面理解DLL是如何运行的以及你和系统如何使用DLL,让我们首先观察一下DLL的整个运行情况。图3 - 1综合说明了它的所有组件一道配合运行的情况。 现在要重点介绍可执行模块和DLL模块之间是如何隐含地互相链接的。 在图3 - 1中你可以看到,当一个模块(比如一个可执行文件)使用DLL中的函数或变量时,将有若干个文件和组件参与发挥作用。图3-1应用程序如何创建和隐含链接DLL的示意图创建DLL:1) 建立带有输出原型/结构/符号的头文件。2) 建立实现输出函数/变量的C/C+源文件。3) 编译器为每个C/C+源文件生成.obj模块。4) 链接程序将生成DLL的.obj模块链
46、接起来。5) 如果至少输出一个函数/变量,那么链接程序也生成lib 文件。创造EXE:6) 建立带有输入原型/结构/符号的头文件。7) 建立引用输入函数/变量的C/C+源文件。8) 编译器为每个C/C+源文件生成.obj源文件。9) 链接程序将各个.obj模块链接起来,产生一个.exe文件 (它包含了所需要DLL模块的名字和输入符号的列表)。运行应用程序:10) 加载程序为.exe 创建地址空间。11) 加载程序将需要的DLL加载到地址空间中进程的主线程开始执行; 应用程序启动运行。若要创建一个从DLL模块输入函数和变量的可执行模块,必须首先创建一个DLL模块。然后就可以创建可执行模块。 若要创建DLL模块,必须执行下列操作步骤: 1) 首先必须创建一个头文件,它包含你想要从DLL输出的函数原型、结构和符号。DLL的 所有源代码模块均包含该头文件,以帮助创建DLL。后面将会看到,当创建需要使用DLL中