ReactOS-Freeldr镜像加载3

 上一次篇我们留下了WinLdrpScanImportAddressTable函数。

当WinLdrScanImportDescriptorTable调用这个函数时,当前需要处理的DLL已经加载到内存而且已经初始化完毕。现在我们需要调用WinLdrpScanImportAddressTable来填写模块本身的IAT。
这个函数有三个参数,WinLdrBlock是Freeldr的LOADER_PARAMETER_BLOCK结构。DllBase是刚刚加载的模块的基地址。ImageBase是需要处理IAT的模块的基地址。ThunkData是IAT表。

WinLdrScanImportDescriptorTable -> WinLdrpScanImportAddressTable (freeldr/freeldr/peloader.c)
  1. BOOLEAN WinLdrpScanImportAddressTable(IN OUT PLOADER_PARAMETER_BLOCK WinLdrBlock,
  2.                                       IN PVOID DllBase,
  3.                                       IN PVOID ImageBase,
  4.                                       IN PIMAGE_THUNK_DATA ThunkData)
  5. {
  6.     PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
  7.     BOOLEAN Status;
  8.     ULONG ExportSize;
  9.     /* 获得新加载DLL的导出表 */
  10.     if (DllBase == NULL)
  11.         return FALSE;
  12.     else
  13.         ExportDirectory =
  14.             (PIMAGE_EXPORT_DIRECTORY)RtlImageDirectoryEntryToData(VaToPa(DllBase),
  15.                 TRUE,
  16.                 IMAGE_DIRECTORY_ENTRY_EXPORT,
  17.                 &ExportSize);
  18.     if (ExportDirectory == NULL)
  19.         return FALSE;
  20.     /* 每个IAT表项循环一次, 调用WinLdrpBindImportName进行IAT地址填充 */
  21.     while (((PIMAGE_THUNK_DATA)VaToPa(ThunkData))->u1.AddressOfData != 0)
  22.     {
  23.         /* 填写IAT */
  24.         Status = WinLdrpBindImportName(
  25.             WinLdrBlock,
  26.             DllBase,
  27.             ImageBase,
  28.             ThunkData,
  29.             ExportDirectory,
  30.             ExportSize,
  31.             FALSE);
  32.         /* 下一个IAT表项 */
  33.         ThunkData++;
  34.         if (!Status)
  35.             return Status;
  36.     }
  37.     /* Return success */
  38.     return TRUE;
  39. }
获得刚刚加载的DLL的导出表后,为每个IAT表项进行一次循环,使用WinLdrpBindImportName填写该IAT表项。
WinLdrpBindImportName的作用是根据IAT存储的内容,在DLL的导出表中搜索到函数地址,并将地址覆盖IAT的原始值。
WinLdrpBindImportName有7个参数:
WinLdrBlock —— Freeldr的LOADER_PARAMETER_BLOCK结构
DllBase —— 拥有导出函数的DLL的基地址
ImageBase —— 需要处理IAT的模块的基地址
ThunkData —— 当前处理的IAT表项指针
ExportDirectory —— DllBase模块的导出表指针
ExportSize —— 导出表大小
ProcessForwards —— 是否在处理DLL Forwards (我们后面会说)

WinLdrScanImportDescriptorTable -> WinLdrpScanImportAddressTable -> WinLdrpBindImportName(freeldr/freeldr/peloader.c)
  1. BOOLEAN WinLdrpBindImportName(IN OUT PLOADER_PARAMETER_BLOCK WinLdrBlock,
  2.                               IN PVOID DllBase,
  3.                               IN PVOID ImageBase,
  4.                               IN PIMAGE_THUNK_DATA ThunkData,
  5.                               IN PIMAGE_EXPORT_DIRECTORY ExportDirectory,
  6.                               IN ULONG ExportSize,
  7.                               IN BOOLEAN ProcessForwards)
  8. {
  9.     ULONG Ordinal;
  10.     PULONG NameTable, FunctionTable;
  11.     PUSHORT OrdinalTable;
  12.     LONG High, Low, Middle, Result;
  13.     ULONG Hint;
  14.     if(DllBase == NULL)
  15.     {
  16.         return FALSE;
  17.     }
  18.     ThunkData = VaToPa(ThunkData);
  19.     /* 使用序号导入函数, 并且没有处理Forward */
  20.     if (IMAGE_SNAP_BY_ORDINAL(ThunkData->u1.Ordinal) && !ProcessForwards)
  21.     {
  22.         /* 直接获得函数在ExportTable中的索引 */
  23.         Ordinal = (ULONG)(IMAGE_ORDINAL(ThunkData->u1.Ordinal) - (UINT32)ExportDirectory->Base);
  24.     }
  25.     else
  26.     {
  27.         /* 按照名称导入函数, 如果不是处理Forward请求, 需要先把IMAGE_IMPORT_BY_NAME的RVA转化成物理地址 */
  28.         if (!ProcessForwards)
  29.         {
  30.             ThunkData->u1.AddressOfData =
  31.                 (ULONG_PTR)RVA(ImageBase, ThunkData->u1.AddressOfData);
  32.         }
  33.         /* 获得导出表的AddressOfNames和AddressOfNameOrdinals的物理地址 */
  34.         NameTable = (PULONG)VaToPa(RVA(DllBase, ExportDirectory->AddressOfNames));
  35.         OrdinalTable = (PUSHORT)VaToPa(RVA(DllBase, ExportDirectory->AddressOfNameOrdinals));
  36.         /* IMAGE_IMPORT_BY_NAME的第一个元素是Hint值, 这个值是个搜索建议, 可以加快搜索速度 */
  37.         Hint = ((PIMAGE_IMPORT_BY_NAME)VaToPa((PVOID)ThunkData->u1.AddressOfData))->Hint;
  38.         /* 首先查看AddressOfName中Hint个名称, 是否是需要查询的函数名。如果是,直接获得函数的序号 */
  39.         if (
  40.             (Hint < ExportDirectory->NumberOfNames) &&
  41.             (
  42.             strcmp(VaToPa(&((PIMAGE_IMPORT_BY_NAME)VaToPa((PVOID)ThunkData->u1.AddressOfData))->Name[0]),
  43.                    (PCHAR)VaToPa( RVA(DllBase, NameTable[Hint])) ) == 0
  44.             )
  45.             )
  46.         {
  47.             Ordinal = OrdinalTable[Hint];
  48.         }
  49.         else
  50.         {
  51.             /* Hint值不正确, 使用二分查找在NameTable中查找函数名 */
  52.             Low = 0;
  53.             High = ExportDirectory->NumberOfNames - 1;
  54.             /* 二分查找 */
  55.             while (High >= Low)
  56.             {
  57.                 Middle = (Low + High) >> 1;
  58.                 Result = strcmp(VaToPa(&((PIMAGE_IMPORT_BY_NAME)VaToPa((PVOID)ThunkData->u1.AddressOfData))->Name[0]),
  59.                     (PCHAR)VaToPa(RVA(DllBase, NameTable[Middle])));
  60.                 if (Result < 0)
  61.                     High = Middle - 1;
  62.                 else if (Result > 0)
  63.                     Low = Middle + 1;
  64.                 else
  65.                     break;
  66.             }
  67.             /* 没找到 */
  68.             if (High < Low)
  69.                 return FALSE;
  70.             /* 获得函数序号 */
  71.             Ordinal = OrdinalTable[Middle];
  72.         }
  73.     }
  74.     /* 序号不合法*/
  75.     if (Ordinal >= ExportDirectory->NumberOfFunctions)
  76.         return FALSE;
  77.     /* 获得导出函数表 */
  78.     FunctionTable = (PULONG)VaToPa(RVA(DllBase, ExportDirectory->AddressOfFunctions));
  79.     /* 获得函数的实际地址或者Forward字符串 */
  80.     ThunkData->u1.Function = (ULONG_PTR)RVA(DllBase, FunctionTable[Ordinal]);
  81.     /* 如果函数地址在导出表的范围内, 那它实际是Forwarder字符串 (DLLNAME.FUNCNAME形式) */
  82.     if (((ULONG_PTR)VaToPa((PVOID)ThunkData->u1.Function) > (ULONG_PTR)ExportDirectory) &&
  83.         ((ULONG_PTR)VaToPa((PVOID)ThunkData->u1.Function) < ((ULONG_PTR)ExportDirectory + ExportSize)))
  84.     {
  85.         PLDR_DATA_TABLE_ENTRY DataTableEntry;
  86.         CHAR ForwardDllName[255];
  87.         PIMAGE_EXPORT_DIRECTORY RefExportDirectory;
  88.         ULONG RefExportSize;
  89.         /* 分离出DLLNAME */
  90.         RtlCopyMemory(ForwardDllName, (PCHAR)VaToPa((PVOID)ThunkData->u1.Function)sizeof(ForwardDllName));
  91.         *strchr(ForwardDllName,'.') = '/0';
  92.         /* 如果ForwardDllName没有加载, 出错 */
  93.         if (!WinLdrCheckForLoadedDll(WinLdrBlock, ForwardDllName, &DataTableEntry))
  94.             return FALSE;
  95.         /* 获得ForwardDllName模块的导出表RefExportDirectory */
  96.         RefExportDirectory = (PIMAGE_EXPORT_DIRECTORY)
  97.             RtlImageDirectoryEntryToData(VaToPa(DataTableEntry->DllBase),
  98.             TRUE,
  99.             IMAGE_DIRECTORY_ENTRY_EXPORT,
  100.             &RefExportSize);
  101.         if (RefExportDirectory)
  102.         {
  103.             UCHAR Buffer[128];
  104.             IMAGE_THUNK_DATA RefThunkData;
  105.             PIMAGE_IMPORT_BY_NAME ImportByName;
  106.             PCHAR ImportName;
  107.             BOOLEAN Status;
  108.             /* 分离出FUNCNAME */
  109.             ImportName = strchr((PCHAR)VaToPa((PVOID)ThunkData->u1.Function), '.') + 1;
  110.             /* 做一个假的IMAGE_IMPORT_BY_NAME和IMAGE_THUNK_DATA */
  111.             ImportByName = (PIMAGE_IMPORT_BY_NAME)Buffer;
  112.             RtlCopyMemory(ImportByName->Name, ImportName, strlen(ImportName)+1);
  113.             ImportByName->Hint = 0;
  114.             RefThunkData.u1.AddressOfData = (ULONG_PTR)ImportByName;
  115.             /* 调用WinLdrpBindImportName填充RefThunkData, 注意最后一个参数 */
  116.             Status = WinLdrpBindImportName(
  117.                 WinLdrBlock,
  118.                 DataTableEntry->DllBase,
  119.                 ImageBase,
  120.                 &RefThunkData,
  121.                 RefExportDirectory,
  122.                 RefExportSize,
  123.                 TRUE);
  124.             /* 填写真正的ThunkData */
  125.             ThunkData->u1 = RefThunkData.u1;
  126.             return Status;
  127.         }
  128.         else
  129.         {
  130.             return FALSE;
  131.         }
  132.     }
  133.     return TRUE;
  134. }
这里导入表导出表的结构就不说了,网上有不少教程。
这个函数的最后一个参数很有意思。因为导出表有一种Forwarder机制, 可以把导出的函数传递给其他模块。比如A模块导出的A函数是个Forwarder指向B模块的B函数,那么在exe程序导入A!A时实际上导入的是B!B。最后一个参数就表明了当前的调用是否是在处理一个Forwarder。下面会有说明。

如果ThunkData的最高位是1说明按照序号方式导入函数,直接减去Export表中的Base值就可以得到实际的函数序号(20-24行)。
如果最高位不是1说明按照名称导入,则需要在导出表中查找所需的函数名,之后得到函数序号。
28 - 32行处理了Forwarder与普通ThunkData绑定的唯一区别,处理Forwarder时ThunkData里面存的不是IMAGE_IMPORT_BY_NAME的RVA,而是真实的内存地址。这里将RVA转化成内存地址。
之后首先使用IMAGE_IMPORT_BY_NAME中的Hind值查看导出表是不是需要找的函数,如果是直接得到函数序号Ordinal(39 - 48)。
如果Hint不正确则使用二分查找导出表的NameTable,找到函数后从OrdinalTable中获得函数索引(49 - 72)。

来到74行, 这时已经获得函数的Ordinal, 根据Ordinal在导出表中找到函数地址, 并把这个地址赋值给ThunkData。如果地址不是一个Forwarder字符串则函数完成。
而如果这个函数地址指向的是导出表的内存空间,说明它并不是真正地址,而是一个Forwarder字符串。形如KERNEL32.GetProcAddress。82 - 126就是处理这种情况的,它把这个字符串分解为KERNEL32和GetProcAddress,并且重新调用WinLdrpBindImportName来绑定ThunkData, 这时这个ThunkData是函数伪造的, 里面存储的不是RVA, 而是指向IMAGE_IMPORT_BY_NAME地址。所以这次调用时最后一个参数为TRUE。调用完成后把伪造的ThunkData的值赋值给正式的ThunkData,函数完成。

至此镜像加载所有的关键点都说完了。
Freeldr的这组函数设计有一个比较不好的地方,就是没有将加载(WinLdrLoadImage)、生成DTE(WinLdrAllocateDataTableEntry)和处理导入表(WinLdrScanImportDescriptorTable)三个函数综合起来。每次手工加载一个模块的时候都要一次调用这三个函数,其实WinLdrScanImportDescriptorTable中的辅助函数WinLdrpLoadAndScanReferencedDll就是做这件事的,Freeldr完全可以把它改写成一个API接口,这样加载镜像时就更加方便了。

你可能感兴趣的:(ReactOS代码精读)