加载配置信息

加载配置信息

加载配置信息最初只用在Windows NT操作系统中,作为文件头部的延伸部分,后来被用做异常处理
加载配置信息表中红存放了基于结构化异常处理(Structureed Exception Handling,SEH)技术的各种异常句柄。当程序运行发生异常后,操作系统会根据异常类别对异常进行分发处理,并根据这些句柄实施程序流程的转向,保证系统能从异常中全身而退

定义

PE中的加载配置信息储存在一个名为加载配置结构(Load Configuration Structure)的数据结构中。加载配置结构是PE中定义的一种基本数据类型。因为信息太大或信息类型太复杂,所以被单独定义。

如果PE中的该部分表中没有对应的异常处理函数句柄,操作系统将会调用其内核模式的异常分发函数终止应用程序的运行。这种安全设置主要是为了阻止因异常句柄导致的溢出被恶意程序利用,从而造成对系统的破坏。

通常情况下,链接器会提供一个默认的加载配置结构,该结构包含了预留的SEH数据。用户代码提供该结构则必须由用户来完成设置新的预留SEH字段,否则链接器不会将SEH数据加入到加载配置信息中。这些处理程序的地址记录在一个被称为中断描述符表

Windows 结构化异常处理

DOS时代,中断被设计为操作系统的核心。所有的中断最后以“int 功能号“的形式调用。系统会根据中断向量表查找系统中定义的中断入口,并跳转到此处执行该中断的相关代码。
在Windows操作系统中,异常(Exception)和中断(Interrupt)提供了类似的功能;中断通常是由外部的条件引发的,如按下一个键;而异常则是代码或者数据中的条件导致处理器生成的,异常与正在执行的指令有直接联系。从宏观上讲,异常是一种内部中断,当异常发生时,CPU会中断当前进程转到异常处理程序。这些处理程序的地址记录在一个被称为中断描述符(IDT)的数据结构中,对应的处理程序存储在 ntoskrnl.exe 文件中。

SEH

为了方便开发者处理异常,在程序内部异常的处理是基于块的。程序中的任何一个函数都可以定义异常处理模块,程序代码实现和异常处理被不同的伪操作语句隔离开。
这种在操作系统和程序级别上采用的,基于一块一块的结构化的异常处理方式称为结构化异常处理。

Windows 异常分类

SEH使用与硬件相关的数据指针,硬件平台不同,SEH实现方式不同。

x86平台上,SEH处理框架把异常分为两大类:硬异常(系统异常)和软异常(程序自己抛出的异常)

1.硬异常,可以细分为以下三类:

(1)故障(Fault)异常
因执行指令失败而引起的。此类异常有一个共同点:发生异常时自动压入栈的是失败指令的地址,而不是它的下一条指令的地址(注意:这与call指令是不一样的)。这样做的原因:当从异常遍历过程返回时,可以重新执行一遍这条指令。

(2)陷阱(Trap)异常

通常是因为执行了自陷指令。这一类异常的返回地址是自陷指令的下一条指令所在的地址。

(3)终止(Abort)异常

这类异常专指那些已经无法恢复的严重出错,如硬件故障引发的异常,或者系统表中出现了错误值引发的异常

在这里插入图片描述

Windows操作系统使用了自己定义的一套代码表示各种异常。操作系统会把相应的CPU异常代码映射到一个或者多个通用win32异常代码上。到底异常映射到哪个win32异常上由Win32异常上由底层的硬件异常决定。

2.软异常

软异常是以函数调用的手段来模拟一次异常,即通过Windows提供的API函数RaiseException,执行函数引发软异常,它的声明如下:

VOID RaiseException(
     DWORD dwExcept ionCode,    //异常代码
     DWORD dwExceptionFlags,    //继续执行标志
     DWORD nNumberOfArguments,  //参数个数
     CONST DWORD *lpArguments   // 指向参数缓冲区的指针
);

实际上,在高级语言的异常处理模型中的大部分抛出异常的操作,最终都是对 RaiseException函数的调用。

内核模式下的异常处理

在用户模式下,fs:[0x00]指向了线程的环境块(TEB)地址。该地址的第一个成分是线程信息块(TIB)结构,而ExceptionList则是这个结构的第一个字段,该字段是用户模式下异常处理链表的指针。当进入内核模式的异常处理程序后,fs被赋予了另外的含义,代码如下:

;Save FS and set it to PCR
push fs
mov ebx, KGDT_R0_PCR ;KGDT_R0_PCR=0x30
mov fs, bx

在代码中,系统将用户模式下的原始fs值保存到栈,然后将fs指向了内核处理器控制域 (Kernel Processor Control Region, KPCR)数据结构。可以看到,内核模式下和用户模式的fs 概念基本是一样的。在内核模式下,fs:[0x00]也是一个异常处理链表的指针。KPCR结构中的 第一个成分是KPCR_TIB数据结构,ExceptionList则是KPCR_TIB结构中的第一个字段。

以下是内核模式下两个数据结构的完整定义:

typedef struct _KPCR_TIB{
  PVOID ExceptionList                               //0000h -指向 _EXCEPTION_REGISTRATION_RECORD 指针
  PVOID StackBase                                   //0004h -栈基地址
  PVOID StackLimit                                  //0008h -栈大小
  PVOID SubSystemTib                                //000C-
  _ANONYMOUS_UNION union{
     PVOID FiberData                                //0010h-
     DWORD Version                                  //0010h-
}DUMMYUNIONNAME 
  PVOID ArbitraryUserPointer,                      //0014h-
  struct _NT_TIB *Self                              //0018h-指向本体结构 
typedef struct _EXCEPTION_REGISTRATION_RECORD{ 
  struct _EXCEPTION_REGISTRATION_RECORD *Next       //0000h - 指向下一个相同结构 
  PEXCEPTION_ROUTINE Handler	                    //0004h - SEH异常处理回调函数指针
}

内核模式下的异常处理程序首先根据异常的类型构造一个陷阱框架(KTrapFrame)。该框 架是一个数据结构,里面记录了当异常发生时的系统环境,如各寄存器的当前值、调试信息、 错误代码、异常处理函数链表等。框架构造完成后,调用公共的_KiTrapHandIer异常处理函数。该函数接收两个参数,一个是异常号,另外一个就是陷阱框架。

;Call the C exception handler
push 0	//异常号
push ebp //KTrapFrame起始指针
call _KiTrapHandler
add esp, 8

认为 _KiTrapHandler是公共的异常处理函数,是因为大部分的异常处理都是把它当成内核异常的入口。当然,也有例外,比如14号异常(页异常),该异常的处理入口为函数 _KiPageFaultHandler。公共的异常处理函数最终会根据CPU在发生异常时所处的地址空间而定:如果是用户层,调用函数 _KiUserTrapHandler ;如果是内核层,调用函数 KiKemelTrapHandlcr。

以下是内核函数 _KiKemelTrapHandler 的代码:

ULONG
KiKemelTrapHandler (PKTRAP_FRAME Tf,ULONG ExceptionNr, PVOID Cr2) 
{
    
    
    EXCEPTION_RECORD Er;			 		
    Er.ExceptionFlags = 0;
    Er.ExceptionRecord = NULL;       
    Er.ExceptionAddress = (PVOID)Tf->Eip; 
    if (ExceptionNr == 14)	//页异常需要单独处理
    {
    
    
      Er.Exceptioncode = STATUS_ACCESS_VIOLATION;    
      Er.Numberparameters = 2;          
      Er.Exceptioninformation[0] = Tf->ErrCode & 0x1; 
      Er.Exceptioninformation[1] = (ULONG)Cr2; 
    } 
    else
    {
    
     
        if (ExceptionNr<ARRAY_SIZE(ExceptionToNtStatus))         {
    
    
            Er.Exceptioncode = ExceptionToNtStatus[ExceptionNr];
        }
    else
    {
    
    
        Er.Exceptioncode = STATUS_ACCESS_VIOLATION;
    } 
     Er.Numberparameters = 0; 
    }
    /* FIXME: Which exceptions are noncontinuab1e? */      
    Er.ExceptionFlags = 0;
    KiDispatchException(&Er,NULL,Tfz KerneIMode,TRUE);
    return(0);
}

该函数构造了异常记录块 EXCEPTION_RECORD ,然后调用 KiDispatchException。

异常记录块的完整定义如下:

typedef struct _EXCEPTION_RECORD{ 
    DWORD Exceptioncode//异常代码
    DWORD ExceptionFlags//异常的状态标志位
    struct EXCEPTIONRECORD * Except ionRecord,
//指向另一个异常记录块
    PVOID ExceptionAddress//本次异常的返回地址
    DWORD Numberparameters//数组 Exceptioninformation[]中有效数据个数
    DWORD Exceptioninformation[EXCEPTION_MAXIMUM_PARAMETERS];
}

异常记录块用来记录一个异常所对应的相关信息,其中含有异常的代码、发生异常时的系统状况、异常之间的联系等。每一个异常发生时,系统都会传递这样一个数据结构。

函数 KiDispatchException 的原型为:

NTAPI
KiDispatchException(
    PEXCEPTION_RECORD ExceptionRecord// 相向 Except ionRecord 的指针 
    PKEXCEPTION_FRAME ExceptionFrame // 对x86,为 NULL
    PKTRAP_FRAME TrapFrame陷阱枢架指针
    KPROCESSOR_MODE PreviousMode //用户模式还是内核模式
    BOOLEAN FirstChance//是否为进行的第一次努力
);

该函数不仅是内核模式下异常处理最后调用的函数,也是用户模式下异常处理函数 KiUserTrapHandler最后调用的函数。

该函数试图通过执行三次尝试来处理异常:
第一次尝试,即 FirstChance=1。异常会先提交给调试程序,如调试程序不存在或调试程序也不能解决该异常,就调用函数 RtlDispatchException 进行实质性的SEH处理。SEH机制对异常的处理有以下三种可能:
1.如果异常被某个SEH框架所接受,并实施了长程跳转(跳转到异常处理程序执行异常处理),程序就不再返回了。
2.如果异常被某个SEH框架所接受,但是程序认为对该异常的处理只需要执行善后函数即可,这样,程序就会从 RtlDispatchException 返回,并且其返回的值是TRUE。
3.如果异常被所有的SEH框架都拒绝接受,那就意味着处理异常的第一次尝试失败。 第二次尝试和第一次尝试失败后,程序将再次提交给调试程序。通过调用其他的调试 支持判断是否可以处理该异常。如果这一次取得了成功,问题解决了,那么返回值是常数 kdContinue;否则进人第三次尝试。
第三次尝试时,表示系统已经没有办法处理这个故障了,所以系统会显示出错信息,并将出错信息转储(Dump)到文件中以备事后分析,然后使CPU进入停机状态。通常情况下, 此时系统将显示蓝屏,并显示类似于下面的一些信息:

…STOP 0x0000001E0xC0000005,0XFDE38AF9,0x0000001,0X7E8B0EB4) KMODE_EXCEPTION_NOT_HANDLED ***

SEH处理的核心就是对 ExceptionList(异常处理链表)的扫描处理,这是由函数 RtlDispatchException来完成的,对该函数的调用位于函数KiDispatchException中。
事实上,绝大多数的异常都可以通过这个函数得到妥善的处理。函数首先通过RtlpGetExceptionList找到异常处理链表,即当前CPU的KPCR结构中的指针ExceptionList; 然后,通过一个while循环来依次搜寻处理ExceptionList链表中的每一个节点,由 RtlpExecuteHandlerForException加以尝试处理。链表中的毎个节点都代表一个局部SEH框架。
由于异常处理链表是个后进先出的队列,里面的第一个节点代表最近进入的SEH框架;如果链表中有不止一个的节点,就说明有了 SEH框架嵌套。在嵌套的情况下,队列中的第一个节点代表最底层的那个保护域;如果这个节点(执行过滤函数以后)拒绝接受本次异常处理,就说明并非这个SEH域所针对的异常,那就应该往栈顶再“跑“ 一层,看是否为上一 层SEH域所针对的异常;如此反复,直到在栈上找到某个节点接受本次异常为止。当一个节点接受了对异常的处理,通常会执行预先规定的长程跳转,直接“跑”到那个框架中的相关语句部分,即由字段 _EXCEPTION_REGISTRATION_RECORD.Handler 指定的代码。在长程跳转之前还需要有个“展开”(Unwinding)的过程,那就是调用所有被跨越的SEH框架的善后函数,这就是为什么在有的错误处理中会看到出现两次相同的错误提示的原因了。

函数 RtlpExecuteHandlerForException 的返回值

字 符 描 述
ExccpiionContinueExecution 0 已接受处理异常
ExceplionContinueSearch 1 不接受.继续査找下一个SEH处理框架
ExceptionNestedException 2 本次异常为嵌套异常
ExcepiionCollidedUnwind 3 发生了严重的错误

如果while循环结束,说明异常处理链表中所有的节点都不是为本类异常准备的。也就是 说,程序事先并没有估计到本类异常的发生,也没有为此做出更多的安排,所以返回FALSE, 让上一层KiDispatchException采取其第二步措施。
内核模式下异常处理函数的调用关系如下:

_KiTrapHandler
   _KiKernelTrapHandler 
      _KiDispatchException
        _RtlDispatchException//三次尝试
          _RtlpGetExceptionList
          _RtlpExecuteHandlerForException//尝试循环执行异常处理

用户模式下的异常处理

异常发生进入内核处理程序后,程序就会顺着内核中的KPCR数据结构中的“异常处理 队列”即 ExceptionList,依次让各个节点认领。如果异常被某个节点接受,那么程序就会通过 SEHLongJmp长程跳转到节点给定的异常处理代码中。

数据结构主要指线程信息块NT_TIB,以下是该数据结构的完整定义:

NT_TIB STRUCT
       ExceptionList dd ? //000h,栈指向SEH链入口
       StackBase dd ? //0004h,栈基地址
       StackLimit dd ? //0008h,栈大小
       SubSystemTib dd ? //000Ch
       FiberData dd ? //0010h
       ArbitraryUserPointer dd ? //0014h
       Self dd ? //0018h,NT_TIB结构自身的线性地址
NT_TIB ENDS

其中,ExceptionList指向一个EXCEPTION_REGISTRATION结构,该结构完整定义如下:

EXCEPTION_REGISTRATION STRUCT
       Prev dd ?	//000h -前一个 EXCEPTION_REGISTRATION 结构的地址
       Handler dd ?	//0004h -昇常处理回调函数地址
EXCEPTION_REGISTRATION ENDS

当异常发生的时候,系统从NT_TIB中取出第一个字段,然后依据该字段获取第一个异常处理程序的句柄Handler,并根据其中的地址调用该回调函数。

异常发生于用户空间,在内核中异常处理程序就会有内核态函数KiKernelTrapHandler改变为用户态函数KidispatchException。该函数构造异常记录块,然后转交给函数KiKernelTrapHandler来处理。
用户模式下只有一个类似于内核空间的KiDispatchException函数,它就是动态链接库ntdll.dll中的KiUserExceptionDispatcher,它是用户模式下SEH异常处理的总人口。尽管是发生于用户空间的异常,对异常的初期响应和处理还都是在内核中进行的。

内核中涉及用户空间异常的处理也分三步:

步骤1 参数FirstChancc为1时,先通过KdpEnterDebuggerException交由内核调试程序处理。如果内核调试程序解决了该问题,或者它认为不需提交回用户空间,返回值就是常量 kdContinue;否则就要把异常提交给用户空间,由用户空间的程序加以处理。
步骤2 万一用户空间处理不了,例如ExceptionList中没有安排可以认领、处理本次异常的节点,就会通过调用函数RtlRaiseException,进而通过系统调用ZwRaiseException发起一次“软异常”,把问题重新交还给内核。此时,CPU再次进入KiDispatchException,但是此时的实际参数FirstChance被改为FALSE,所以直接进入第二步措施。在Windows内核中,第二次尝试是通过进程间通信向用户空间的调试程序发送一个报文,将其唤醒,由调试程序作进一步的处理。
步骤3 如果用户空间调试程序不存在,或者也不能解决,那就属于不可恢复的问题了。 于是就有第三步措施,第三步措施会调用ZwTerminateThread结束当前线程的运行。

内核模式下程序把异常提交给用户空间的步骤如下:
首先,把上下文数据结构Context和异常记录块ExceptionRecord复制到用户空间的栈 上去。
其次,在用户空间栈上压人两个指针,分别指向这两个数据结构的用户空间副本,并相 应调整异常框架中的用户空间栈指针。
最后,也是最关键的一步,把异常框架中的用户空间返回地址设置成函数指针 KeUserExceptionDispatcher 所指向的函数。
当CPU从异常返回,回到用户空间时就进入了函数KiUserExceptionDispatcher,该函数 就是用户模式下的异常响应/处理程序的入口。与内核中异常有多个入口不同,用户空间有且 仅有一个这样的入口。至于绑定到异常上的其他信息,则由异常记录块负责记录。
用户空间的异常机制是对系统空间的异常机制的模拟。在内核中,并非所有的异常都是一开始就进入“基于SEH框架” (Frame-Based)的异常处理,而是先进入类似向量中断的入口。用户空间也一样,在用户空间有一个函数,它负责遍历一个“向量式异常处理程序入口队列”。如果异常被这些队列中的某个处理程序认领,则不再进行基于SEH框 架的异常处理,该函数是全局性的,在RtlDispatchException执行前运行。以下是函数 KiUserExceptionDispatcher 的调用过程:

KiUserExceptionDispatcher()
   RtlDispatchException()
       RtlpIsValidHandler ()//昇常处理函致指针安全验证 
       RtlpExecuteHandlerForException()//尝试处理异常 
       ExecuteHandler()

函数RtlpIsValidHandler检査SEH handler的过程包括:
步骤1 检査handler是否在线程环境块TEB指定的Stack范围内(fs:[4]〜fs:[8]),如果是则拒绝执行。
步骤2 检査handler是否在已加载模块列表(exe和dll)中,如果handler不在这些模块地址范围内,则拒绝执行。
步骤3 如果handler在模块地址范围内,则检査已注册异常处理程序列表。
检査过程如下:
(1)if((DLLCharacteristics&0xFF00) == 0x0400),则拒绝执行(No SEH),否则继续检査。
(2)数据目录项中的加载配置地址为0,也就意味着不存在IMAGE_LOAD_CONFIG_ DIRECOTRY结构,说明编译时没有设置 -safeseh选项,则停止检查,执行。
如果IMAGE_LOAD_CONFIG_DIRECOTRY结构存在,则继续检査以下字段:

+0000h: directory_size。首先判断目录长度是否介于00x48之间,是则停止检査, 执行。
+0040h: handlers[]。其次检査 SEH Handler 的数组指针(元素是 SEH Handler RVA)。如果该指针指向0,if(handlers[]==0)成立,则停止检査,执行。
+0044h : handler_num。最后检査 SEH Handler 数组元素个数,if(handler_num==O),则停止检査,执行。	 	

(3)根据SEH Handler开始逐个匹配,发现匹配则调用:否则拒绝调用。
简言之,如果异常处理程序的地址在映像的VA范围之内,并且映像被标记为支持保留的SEH (也就是说,可选文件头中的DllCharacteristics字段没有设置IMAGE_ DLLCHARACTERISTICS_NO_SEH标志),那么这个异常处理程序必须在映像的已知安全异常处理程序列表中(加载配置信息的结构字段 SEHandleTable 指向该列表),否则操作系统将终止应用程序。对异常处理程序如此严格的限制主要目的是防止利用”x86异常处理程序劫持”来控制操作系统,这种技术在以前曾经被人利用过。
在用户空间执行完“向量式异常处理程序入口队列”遍历后,如果没有找到异常的认领处理程序,则继续运行函数RtlDispatchException。与内核模式中的同名函数不同,此处的 RtlDispatchException 函数所处理的ExceptionList是在用户空间,所使用的栈也是用户空间栈。 在内核模式部分fs:[0x00],无论是在内核还是在用户模式下,其取到的都是数据结构ExceptionList指针。

Windows SEH机制解析

通过以上对内核模式和用户模式下基于SEH技术的异常处理过程。
总结如下:
SEH机制就是在执行某一段代码的过程中,如果发生了特定种类的异常,就执行另一段指定代码。为实现结构化异常处理,Windows在系统空间和用户空间都有一个后进先出的异常处理队列ExceptionList。毎当程序进人一个SEH框架时,就把一个带有长程跳转目标地址的数据结构挂入相应空间的异常处理队列,成为其一个节点。在内核空间将节点挂入系统空间的队列,在用户空间则挂入用户空间的队列。
一般而言,当CPU运行于用户空间时,系统空间的异常处理队列应该是空的。除长程跳转目标地址外,挂入ExceptionList的数据结构中还可以有两个函数指针:
(1)过滤(Filter)函数的指针。这个函数判断所发生的异常是否就是本保护域所要处理的异常,如果是才加以接受,从而执行本SEH域的长程跳转,执行异常处理函数。
(2)善后(Final)函数的指针。在进行异常认领过程中,将会遍历并执行节点上的各异常函数;在执行过程中,这些异常处理函数(无论它是否接受了该异常的处理)可能会申请一些资源,这些资源必须得到释放。善后函数的主要目的通常是释放展开过程动态获取的资源。
当发生异常时,异常响应程序就依次考察相应 ExceptionList 中的各个节点,并执行其过滤函数(如果有的话)。如果过滤函数认为这就是本保护域所针对的异常,或者默认为相符不需要进行过滤,就执行本保护域的长程跳转,进入本SEH域的 _SEH_HANDLE{} 里面的代 码;而对于被跨越的各个内层SEH域,则执行其善后函数(如果有的话),即展开操作。
Windows对于段寄存器 fs 有特殊的设置和使用,当CPU运行于系统空间时,就使 fs:0 指向 当前CPU的KPCR数据结构。而KPCR结构的第一个成分是KPCR_TIB数据结构,KPCR_TIB 的第一个成分则是VOID指针ExceptionListo
当CPU运行于系统空间时,就使 fs:0 指向当前线程的TEB。TEB数据结构中的第一个成分是NT_TIB数据结构,这里面的第一个成分即指针ExceptionList。

异常代码为EXCEPTION_ACCESS_VIOLATION,其十六进制值0C0000005h,将这个异常代码值按位拆开,来分析它的各个bit位字段的含义:

C    0    0    0    0    0    0    5    (十六进制〉
1100 0000 0000 0000 0000 0000 0000 0101 (二进制〉

第30位和第31位都是1,表示该异常是一个严重的错误,线程可能不能够继续往下运行,必须及时处理恢复这个异常。第29位是0,表示系统中已经定义了异常代码。第28位是 0,留待后用。第16〜27位是0,表示是FACILITY_NULL设备类型,它代表存取异常可发生在系统中任何地方,不是使用特定设备才发生的异常。第0〜15位的值为5,表示异常错误的代码。真正的异常代码只是用了低16位。

10.3.2 加载配置目录 IMAGE_LOAD_CONFIG_DIRECTORY
加载配置信息的数据起始为数据结构IMAGE_LOAD_CONFIG_DIRECTORY。该结构的完整定义如下:
在这里插入图片描述
在这里插入图片描述

  1. IMAGE_LOAD_CONFIG_DIRECTORY. Characteristics
    +0000h,双字。标志字节,用来显示文件的属性,通常为0。如果存在加载配置信息,则大部分情况下被设置为48h。

  2. IMAGE_LOAD_CONFIG_DIRECTORY. TimeDateStamp
    +0004h,双字。时间戳,这个值表示从UTC时间1970年1月1日午夜(00:00:00)以来经过的总秒数,它是根据系统时钟算出的。可以用C运行时函数来获取它。

  3. IMAGE_LOAD_CONFI G_DIRECTORY. MajorVersion
    +0008h,单字。主版本。

  4. IMAGE LOAD CONFIG DIRECTORY. MinorVersion
    +000Ah,单字。次版本。

  5. IMAGE_LOAD_CONFIG_DIRECTORY. GlobalFlagsClear
    +000Ch,双字。当PE加载器加载该映像时需要清除的全局标志。

  6. IMAGE_LOAD_CONFIG_DIRECTORY. GlobalFlagsSet
    +0010h,双字。PE加载器加载该映像时需要设置的全局标志。

  7. IMAGE_LOAD_CONFIG_DIRECTORY. CriticalSectionDefaultTimeout
    +0014h,双字。用于这个进程处于无约束状态的临界区的默认超时值。

  8. IMAGE_LOAD_CONFIG_DIRECTORY. DeCommitFreeBlockThreshold
    +0018h,双字。返回到系统之前必须释放的内存数量(以字节计)。

  9. IMAGE_LOAD_CONFIG_DIRECTORY. DeCommitTotalFreeThreshold
    +001 Ch,双字。空闲内存总量(以字节计)。

  10. IMAGE_LOAD_CONFIG_DIRECTORY. LockPrefixTable
    +0020h,双字。该值是一个VA。该字段仅适用于x86平台。它指向一个地址列表,这个
    地址列表中保存的是使用lock前缀的指令的地址,这样便于在单处理器机器上将这些lock前缀替换为nop指令。

  11. IMAGE_LOAD_CONFIG_DIRECTORY. MaximumAllocationSize
    +0024h,双字。最大的分配粒度(以字节计)。

  12. IMAGE_LOAD_CONFlG_DIRECTORY. VirtualMemoryThreshold

    +0028h,双字。最大的虚拟内存大小(以字节计)。

  13. IMAGE一LOAD_CONFIG_DIRECTORY. ProcessAffinityMask
    +002Ch,双字。如果将该字段设置为非零值,则等效于在进程启动时将这个设定的值作为参数去调用函数SetProcessAffinityMask0

  14. IMAGEJ_OAD_CONFIG_DIRECTOR丫. ProcessHeapFlags
    +0030h,双字。进程堆的标志,相当于函数HeapCreate的第一个参数。这些标志用于在进程启动过程中创建的堆。

  15. IMAGE_LOAD_CONFIG_DIRECTOR丫. CSDVersion
    +0034h,单字。Service Pack 版本标识。

  16. IMAGE_LOAD_CONFIG_DIRECTORY. Reserved 1
    +0036h,单字。保留值。

  17. IMAGE_LOAD_CONFIG_DIRECTORY. EditList
    +0038h,双字。保留,供系统使用。

  18. I MAG E_LO AD_CON FI G_D I RECTO RY. SecurityCookie
    +003Ch,双字。指向 cookie 的指针。该 cookie 由 Visual C++ 编译器的“GS implementation”所使用。

  19. IMAGE_LOAD__CONFIG_DIRECTORY. SEHandlerTable
    +0040h,双字。该值为一个VA。与平台相关,指向一个地址列表。这个地址列表中保存的是映像中每个合法的、独一无二的、基于SEH框架的异常处理程序的RVA,并且它们已经按RVA从小到大的顺序排过序,最后以一个双字的0结尾。

  20. I MAG E_LO AD_CON FI G_D I RECTO RY. SEHandlerCount
    +0044h,双字。IMAGE_LOAD_CONFIG_DIRECTORY. SEHandlerTable 指向列表中双字的个数,最后一个0除外。

猜你喜欢

转载自blog.csdn.net/weixin_51732593/article/details/124671088