⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 漫谈兼容内核之十一:windows dll的装入和连接.txt

📁 漫谈系统内核内幕 收集得很辛苦 呵呵 大家快下在吧
💻 TXT
📖 第 1 页 / 共 5 页
字号:
}[/code]

    注意调用LdrFixupImports()的参数之一Module是LDR_MODULE数据结构的间接指针(PLDR_MODULE本身就已经是指针),它最终指向目标EXE映像的LDR_MODULE数据结构,因此LdrFixupImports()是以EXE映像为起点的。下面读者就会看到,LdrFixupImports()是个递归的过程,处理的是一棵动态连接的调用树(实际上不是树,因为多个父节点可以通向同一个子节点,但为叙述方便我们暂且称之为树),而EXE映像就是这棵树的根。这棵树中除根节点以外的所有节点都是DLL,但是“叶节点”通常只有一个(不过并无限制),那就是ntdll.dll。
    PE映像的NtHeader中有个指针,指向一个OptionalHeader。说是“Optional”,实际上却是关键性的。在OptionalHeader中有个字段ImageBase,是具体映像建议、或者说希望被装入的地址,我们不妨称之为“愿望地址”。在装入一个映像时,只要相应的区间(取决于它的愿望地址和大小),就总是会遂其所愿。但是如果与已经被占用的区间相冲突,就只好易地安置。下面是一些常见.exe映像和DLL映像的愿望地址:
[code]notepad.exe         0x01000000    00013000
cmd.exe            0x4ad00000    0005e000
csrss.exe           0x4a680000    00004000
winword.exe         0x30000000    00836000
powerpnt.exe         0x30000000    00420000
excel.exe            0x30000000    006d6000
outlook.exe          0x30000000    0000e000
iexplore.exe         0x00400000    00019000
WinDVD.exe        0x00400000    00031000

ntdll.dll            0x77f50000    000a7000
kernel32.dll         0x77e60000    000e7ed3
gdi32.dll           0x77c70000    00040000
user32.dll           0x77d40000    0008c000
advapi32.dll          0x77dd0000    0008d000
csrsrv.dll            0x75b40000    0000a000
crtdll.dll             0x73d90000    00027000
mfc40.dll            0x61a90000    000e7000
ole32.dll            0x771b0000    00121000[/code]

    可见,无论是EXE映像还是DLL映像,都没有一个统一的愿望地址。在Windows的空间结构中,用户空间与系统空间的分界线是0x80000000、即2GB处,分界线以下为用户空间。如前所述,PEB的位置是0x7FFDF000(即分界线以下68KB处),PEB以下是TEB,每个TEB占4KB、即一个页面。系统DLL ntdll.dll的愿望地址是0x77f50000,离分界线的距离约128MB,而映像大小是0xa7000,小于1MB。Winword.exe的愿望地址是0x30000000,离分界线的距离大于1GB,映像大小是0x836000,稍大于8MB。注意EXE映像的大小未必反映应用软件的实际大小,因为许多应用软件都是由EXE和DLL共同实现的。例如,csrss.exe的映像大小是0x4000,即16KB,但是它的许多函数都在csrsrv.dll中,其大小倒反而是0xa000、即40KB。
    那么映像的愿望地址到底是怎么一回事,有着什么物理的或者逻辑的意义呢?我们知道,软件在编译以后有个连接的过程,即为函数的调用者落实被调用函数的入口地址、为全局变量(按绝对地址)的引用(读/写)者落实变量地址的过程。连接有静态和动态两种,静态连接是在“制造”软件时进行的,而动态连接则是在使用软件时进行的。尽管EXE模块和DLL模块之间的连接是动态连接,但是EXE或DLL模块内部的连接却是静态连接。既是静态连接,就必须为模块的映像提供一个假定的起点地址。如果以此假定地址为基础进行连接以后就不可变更,使用时必须装入到这个地址上,那么这个地址就是固定的“指定地址”了。早期的静态连接往往都是使用指定地址的。但是,如果允许按假定地址连接的映像在实际使用时进行“重定位”,那么这假定地址就是可浮动的“愿望地址”了。可“重定位”的静态连接当然比固定的静态连接灵活。事实上,要是没有可“重定位”的静态连接技术,DLL的使用根本就不现实,因为根本就不可能事先为所有可能的DLL划定它们的装入位置和大小。至于可“重定位”静态连接的实现,则一般都是采用间接寻址,通过指针来实现。
    一般而言,目标EXE映像的装入(映射)地址就是其愿望地址,因为一般而言这是最早映射的,没有理由要改变其位置。但是万一改变了就要通过LdrPerformRelocations()实行重定位,这是由LdrPerformRelocations()实现的:

[code][__true_LdrInitializeThunk > LdrPEStartup() > LdrPerformRelocations()]

static NTSTATUS
LdrPerformRelocations(PIMAGE_NT_HEADERS NTHeaders, PVOID ImageBase)
{
  . . . . . .

  RelocationDDir =
&NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
  . . . . . .

  ProtectSize = PAGE_SIZE;
  Delta = (ULONG_PTR)ImageBase - NTHeaders->OptionalHeader.ImageBase;
  RelocationDir = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase +
                  RelocationDDir->VirtualAddress);
  RelocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase +
                  RelocationDDir->VirtualAddress + RelocationDDir->Size);

  while (RelocationDir < RelocationEnd && RelocationDir->SizeOfBlock > 0)
    {
      Count = (RelocationDir->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) /
              sizeof(USHORT);
      Page = ImageBase + RelocationDir->VirtualAddress;
      TypeOffset = (PUSHORT)(RelocationDir + 1);

      /* Unprotect the page(s) we're about to relocate. */
      ProtectPage = Page;
      Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage,
                              &ProtectSize, PAGE_READWRITE, &OldProtect);
      . . . . . .

      if (RelocationDir->VirtualAddress + PAGE_SIZE <
          NTHeaders->OptionalHeader.SizeOfImage)
        {
          ProtectPage2 = ProtectPage + PAGE_SIZE;
          Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2,
                              &ProtectSize, PAGE_READWRITE, &OldProtect2);
          . . . . . .
        }
      else
        {
          ProtectPage2 = NULL;
        }

      RelocationDir = LdrProcessRelocationBlock(Page, Count, TypeOffset, Delta);
      . . . . . .

      /* Restore old page protection. */
      NtProtectVirtualMemory(NtCurrentProcess(),&ProtectPage,
                             &ProtectSize, OldProtect, &OldProtect);

      if (ProtectPage2 != NULL)
        {
          NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2,
                                 &ProtectSize, OldProtect2, &OldProtect2);
        }
    }

  return STATUS_SUCCESS;
}[/code]

    PE映像的OptionalHeader中有个大小为16的数组DataDirectory[],其元素都是“数据目录”、即IMAGE_DATA_DIRECTORY数据结构:

[code]typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;[/code]

    显然,数组中的每一个元素都说明了一个数据目录(在映像中)的位置及其大小(以32位长字为单位),用于各种不同的目的。其中之一(下标为5)就是“重定位目录”,这是一个IMAGE_BASE_RELOCATION结构数组。

[code]typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION,*PIMAGE_BASE_RELOCATION;[/code]

    每个IMAGE_BASE_RELOCATION数据结构代表着一个“重定位块”,每个重定位块的(容器)大小是两个页面(8KB),而SizeOfBlock则说明具体重定位块的实际大小。这实际的大小中包括了这IMAGE_BASE_RELOCATION数据结构本身。
    于是,所谓重定位,就是计算出实际装入地址与建议装入地址间的位移Delta,然后调整每个重定位块中的每一个重定位项、即指针,具体就是在指针上加Delta。而映像中使用的所有绝对地址(包括函数入口、全局量数据的位置)实际上用的都是间接寻址,每个这样的地址都有个指针存在于某个重定位块中。可见,这与ELF格式中的PLT实质上是一样的。具体的指针调整是由LdrProcessRelocationBlock()完成的,此前和此后的NtProtectVirtualMemory()只是为了先去除这些指针所在页面的写保护,而事后加以恢复。
    完成了可能需要的EXE映像重定位以后,下一个主要的操作就是LdrFixupImports()了。实际上这才是关键所在,它所处理的就是当前模块所需DLL模块的装入(如果尚未装入的话)和连接。如前所述,这个函数递归地施行于所有的模块,直至最底层的“叶节点”ntdll.dll为止。
    最后,LdrPEStartup()返回的是根模块即EXE映像的程序入口。相比之下,LdrFixupImports()则并不返回各个模块的程序入口,各DLL的程序入口纪录在它们的LDR_MODULE数据结构中(但是ntdll.dll的入口已经不再需要,因为现在已经在这个模块里面了),借助InInitializationOrderModuleList队列就可依次调用所有DLL的初始化函数。
    下面我们看LdrFixupImports()的代码:

[code][__true_LdrInitializeThunk > LdrPEStartup() > LdrFixupImports()]

static NTSTATUS
LdrFixupImports(IN PWSTR SearchPath OPTIONAL, IN PLDR_MODULE Module)
{
  . . . . . .
  /*
   * Process each import module.
   */
  ImportModuleDirectory = (PIMAGE_IMPORT_MODULE_DIRECTORY)
                        RtlImageDirectoryEntryToData(Module->BaseAddress, TRUE,
                        IMAGE_DIRECTORY_ENTRY_IMPORT, NULL);

  BoundImportDescriptor = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)
                        RtlImageDirectoryEntryToData(Module->BaseAddress, TRUE,
                        IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, NULL);

  if (BoundImportDescriptor != NULL && ImportModuleDirectory == NULL)
  {
       DPRINT1("%wZ has only a bound import directory\n", &Module->BaseDllName);
       return STATUS_UNSUCCESSFUL;
  }

  if (BoundImportDescriptor)    /*  绑定引入  */
  {
    DPRINT("BoundImportDescriptor %x\n", BoundImportDescriptor);

    BoundImportDescriptorCurrent = BoundImportDescriptor;
    while (BoundImportDescriptorCurrent->OffsetModuleName)
    {
       ImportedName = (PCHAR)BoundImportDescriptor +
BoundImportDescriptorCurrent->OffsetModuleName;
       TRACE_LDR("%wZ bound to %s\n", &Module->BaseDllName, ImportedName);
       Status = LdrpGetOrLoadModule(SearchPath, ImportedName,
                                          &ImportedModule, TRUE);
       . . . . . .
       if (ImportedModule->TimeDateStamp !=
                     BoundImportDescriptorCurrent->TimeDateStamp)
       {
         TRACE_LDR("%wZ has stale binding to %wZ\n",
                         &Module->BaseDllName, &ImportedModule->BaseDllName);
         Status = LdrpProcessImportDirectory(Module, ImportedModule, ImportedName);
         . . . . . .
       }
       else
       {
         BOOLEAN WrongForwarder;
         WrongForwarder = FALSE;
         . . . . . .
         if (BoundImportDescriptorCurrent->NumberOfModuleForwarderRefs)
         {
            PIMAGE_BOUND_FORWARDER_REF BoundForwarderRef;
            ULONG i;
            PLDR_MODULE ForwarderModule;
            PCHAR ForwarderName;

            BoundForwarderRef = (PIMAGE_BOUND_FORWARDER_REF)
(BoundImportDescriptorCurrent + 1);
            for (i = 0;
                i < BoundImportDescriptorCurrent->NumberOfModuleForwarderRefs;
                i++, BoundForwarderRef++)
            {
              ForwarderName = (PCHAR)BoundImportDescriptor +
                                BoundForwarderRef->OffsetModuleName;
              TRACE_LDR("%wZ bound to %s via forwardes from %s\n",
                           &Module->BaseDllName, ForwarderName, ImportedName);
              Status = LdrpGetOrLoadModule(SearchPath,
                          ForwarderName, &ForwarderModule, TRUE);
              . . . . . .
              if (ForwarderModule->TimeDateStamp !=
BoundForwarderRef->TimeDateStamp  ||
                           ForwarderModule->Flags & IMAGE_NOT_AT_BASE)
              {
                           TRACE_LDR("%wZ has stale binding to %s\n",
                                     &Module->BaseDllName, ForwarderName);
                           WrongForwarder = TRUE;
              }
               else
               {
                           TRACE_LDR("%wZ has correct binding to %s\n",
                                     &Module->BaseDllName, ForwarderName);
                         }
             } //end for
           }

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -