ReactOS-Freeldr镜像加载

该博客详细解析了ReactOS中WinLdrLoadImage函数加载PE文件的过程,包括加载PE头、分配内存、处理节表、重定位镜像等步骤,确保x86可执行文件正确执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

Freeldr中镜像加载相关的操作在Freeldr/Freeldr/windows/Peloader.c文件中。
首先来看看如何加载一个PE镜像。这个操作在WinLdrLoadImage函数中。
这个函数的三个参数分别为镜像的Arc路径FileName, 镜像所处内存的内存属性MemoryType, 加载后镜像的基地址由ImageBasePA返回。
  1. BOOLEAN
  2. WinLdrLoadImage(IN PCHAR FileName,
  3.                 TYPE_OF_MEMORY MemoryType,
  4.                 OUT PVOID *ImageBasePA)
  5. {
  6.     ......
  7.     /* 在屏幕中央显示 Loading xxx... */
  8.     sprintf(ProgressString, "Loading %s...", strchr(FileName, '//') + 1);
  9.     UiDrawBackdrop();
  10.     UiDrawProgressBarCenter(1, 100, ProgressString);
  11.     /*打卡Arc路径的文件,并读取前两个sector。用来读取PE头*/
  12.     Status = ArcOpen(FileName, OpenReadOnly, &FileId);
  13.     if (Status != ESUCCESS)
  14.     {
  15.         //UiMessageBox("Can not open the file");
  16.         return FALSE;
  17.     }
  18.     Status = ArcRead(FileId, HeadersBuffer, SECTOR_SIZE * 2, &BytesRead);
  19.     if (Status != ESUCCESS)
  20.     {
  21.         UiMessageBox("Error reading from file");
  22.         ArcClose(FileId);
  23.         return FALSE;
  24.     }
  25.     /* 获得NT头指针 */
  26.     NtHeaders = RtlImageNtHeader(HeadersBuffer);
  27.     if (!NtHeaders)
  28.     {
  29.         UiMessageBox("Error - no NT header found");
  30.         ArcClose(FileId);
  31.         return FALSE;
  32.     }
  33.     /* 检测是否是可执行镜像 */
  34.     if (((NtHeaders->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0))
  35.     {
  36.         UiMessageBox("Not an executable image");
  37.         ArcClose(FileId);
  38.         return FALSE;
  39.     }
  40.     /* 从PE头中获得section数量, 并且获得节表的首地址 */
  41.     NumberOfSections = NtHeaders->FileHeader.NumberOfSections;
  42.     SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
  43.     /* 首先以PE头中的ImageBase为基地址申请存放镜像的内存, 如果失败就从任意地址申请内存 */
  44.     PhysicalBase = MmAllocateMemoryAtAddress(NtHeaders->OptionalHeader.SizeOfImage,
  45.                        (PVOID)((ULONG)NtHeaders->OptionalHeader.ImageBase & (KSEG0_BASE - 1)),
  46.                        MemoryType);
  47.     if (PhysicalBase == NULL)
  48.     {
  49.         /* It's ok, we don't panic - let's allocate again at any other "low" place */
  50.         PhysicalBase = MmAllocateMemoryWithType(NtHeaders->OptionalHeader.SizeOfImage, MemoryType);
  51.         if (PhysicalBase == NULL)
  52.         {
  53.             //Print(L"Failed to alloc pages for image %s/n", FileName);
  54.             UiMessageBox("Failed to alloc pages for image");
  55.             ArcClose(FileId);
  56.             return FALSE;
  57.         }
  58.     }
  59.     /* 对于x86而言PaToVa直接返回PhysicalBase */
  60.     VirtualBase = PaToVa(PhysicalBase);
  61.     /* 把PE头全部读入申请的内存 */
  62.     Position.HighPart = Position.LowPart = 0;
  63.     Status = ArcSeek(FileId, &Position, SeekAbsolute);
  64.     if (Status != ESUCCESS)
  65.     {
  66.         UiMessageBox("Error seeking to start of file");
  67.         ArcClose(FileId);
  68.         return FALSE;
  69.     }
  70.     Status = ArcRead(FileId, PhysicalBase, NtHeaders->OptionalHeader.SizeOfHeaders, &BytesRead);
  71.     if (Status != ESUCCESS)
  72.     {
  73.         //Print(L"Error reading headers %s/n", FileName);
  74.         UiMessageBox("Error reading headers");
  75.         ArcClose(FileId);
  76.         return FALSE;
  77.     }
  78.     /* 获得刚刚读入的完整的PE头 */
  79.     NtHeaders = RtlImageNtHeader(PhysicalBase);
  80.     /* 获得节表 */
  81.     SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
  82.     /* 输出PysicalBase地址 */
  83.     *ImageBasePA = PhysicalBase;
  84.     /* 按照节表要求, 把节读入物理内存 */
  85.     for (= 0; i < NumberOfSections; i++)
  86.     {
  87.         VirtualSize = SectionHeader->Misc.VirtualSize;
  88.         SizeOfRawData = SectionHeader->SizeOfRawData;
  89.         /* Handle a case when VirtualSize equals 0 */
  90.         if (VirtualSize == 0)
  91.             VirtualSize = SizeOfRawData;
  92.         /* If PointerToRawData is 0, then force its size to be also 0 */
  93.         if (SectionHeader->PointerToRawData == 0)
  94.         {
  95.             SizeOfRawData = 0;
  96.         }
  97.         else
  98.         {
  99.             /* Cut the loaded size to the VirtualSize extents */
  100.             if (SizeOfRawData > VirtualSize)
  101.                 SizeOfRawData = VirtualSize;
  102.         }
  103.         /* Actually read the section (if its size is not 0) */
  104.         if (SizeOfRawData != 0)
  105.         {
  106.             /* Seek to the correct position */
  107.             Position.LowPart = SectionHeader->PointerToRawData;
  108.             Status = ArcSeek(FileId, &Position, SeekAbsolute);
  109.             DPRINTM(DPRINT_PELOADER, "SH->VA: 0x%X/n", SectionHeader->VirtualAddress);
  110.             /* Read this section from the file, size = SizeOfRawData */
  111.             Status = ArcRead(FileId, (PUCHAR)PhysicalBase + SectionHeader->VirtualAddress, SizeOfRawData, &BytesRead);
  112.             if (Status != ESUCCESS)
  113.             {
  114.                 DPRINTM(DPRINT_PELOADER, "WinLdrLoadImage(): Error reading section from file!/n");
  115.                 break;
  116.             }
  117.         }
  118.         /* Size of data is less than the virtual size - fill up the remainder with zeroes */
  119.         if (SizeOfRawData < VirtualSize)
  120.         {
  121.             DPRINTM(DPRINT_PELOADER, "WinLdrLoadImage(): SORD %d < VS %d/n", SizeOfRawData, VirtualSize);
  122.             RtlZeroMemory((PVOID)(SectionHeader->VirtualAddress + (ULONG_PTR)PhysicalBase + SizeOfRawData), VirtualSize - SizeOfRawData);
  123.         }
  124.         SectionHeader++;
  125.     }
  126.     /* We are done with the file - close it */
  127.     ArcClose(FileId);
  128.     /* If loading failed - return right now */
  129.     if (Status != ESUCCESS)
  130.         return FALSE;
  131.     /* 如果内存镜像的基地址和PE头记录的ImageBase不同则需要重定位 */
  132.     if (NtHeaders->OptionalHeader.ImageBase != (ULONG_PTR)VirtualBase)
  133.     {
  134.         DPRINTM(DPRINT_PELOADER, "Relocating %p -> %p/n",
  135.             NtHeaders->OptionalHeader.ImageBase, VirtualBase);
  136.         return (BOOLEAN)LdrRelocateImageWithBias(PhysicalBase,
  137.             (ULONG_PTR)VirtualBase - (ULONG_PTR)PhysicalBase,
  138.             "FreeLdr",
  139.             TRUE,
  140.             TRUE/* in case of conflict still return success */
  141.             FALSE);
  142.     }
  143.     return TRUE;
  144. }

通过读取PE头获得需要的内存总量。申请SizeOfImage大小的空间, 这里并不要求内存的基地址和PE头中的ImageBase相同。申请完空间后按照PE格式要求镜像文件读入内存。

最后如果内存基地址如果和PE头中记录的ImageBase不能则需要调用LdrRelocateImageWithBias对镜像进行重定位。

函数中使用的RtlImageNtHeader函数用来根据镜像基址获得PE头, 在lib/rtl/Image.c文件中, 非常简单就不列出代码了。而ArcXxx系列的文件操作函数我们之前已经看过了。

现在重点看看Freeldr的PE重定位。重定位表指针可以从NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]获得。它实际是一个变长结构IMAGE_BASE_RELOCATION的数组
  1. typedef struct _IMAGE_BASE_RELOCATION {
  2.   ULONG VirtualAddress;
  3.   ULONG SizeOfBlock;
  4. USHORT TypeOffset[(SizeOfBlock - 2 * sizeof(ULONG)) / sizeof(USHORT)]
  5. } IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
首先每一个IMAGE_BASE_RELOCATION至多管理4KB内存的重定位工作。如果需要重定位的内存超过4KB那么将有多个IMAGE_BASE_RELOCATION结构。
这段内存的基地址的RVA就是VirtualAddress, 使用PysicalBase + VirtualAddress可以找到这段内存。
IMAGE_BASE_RELOCATION的结构的大小是SizeOfBlock。
结构最后是一个变成的TypeOffset数组,数组的每一个数据是一个WORD。这个WORD的低12位是着4KB内存中需要重定位地址的偏移, 所以通过PysicalBase + VirtualAddress + (TypeOffset[n] & 0x0fff)可以找到需要重定位的地址。
WORD的高4位是一个标志。
    IMAGE_REL_BASED_ABSOLUTE (0) 该地址需要按照32位对其
    IMAGE_REL_BASED_HIGH (1) 表示这个一个DWORD的高16位
    IMAGE_REL_BASED_LOW (2) 一个DWORD的低16位
    IMAGE_REL_BASED_HIGHLOW (3) 表示一个DWORD
    IMAGE_REL_BASED_HIGHADJ (4) 表示一个QWORD
    IMAGE_REL_BASED_MIPS_JMPADDR (5)        Unknown
    IMAGE_REL_BASED_SECTION (6)        Unknown
    IMAGE_REL_BASED_REL32 (7)        Unknown
下面我们来看代码
LdrRelocateImageWithBias有6个参数,BaseAddress是PE内存镜像的基地址,AdditionalBias是PE头开始的偏移,在X86建构下这个值为0。LoaderName没有使用,看名字应该是输出DEBUG输出信息时的模块名。
Success/Conflict/Invalid分别代表成功、冲突和不成功的返回值。
lib/rtl/Image.c
  1. ULONG
  2. NTAPI
  3. LdrRelocateImageWithBias(
  4.     IN PVOID BaseAddress,
  5.     IN LONGLONG AdditionalBias,
  6.     IN PCCH  LoaderName,
  7.     IN ULONG Success,
  8.     IN ULONG Conflict,
  9.     IN ULONG Invalid)
  10. {
  11.     ....
  12.     /*获得nt头*/
  13.     NtHeaders = RtlImageNtHeader(BaseAddress);
  14.     if (NtHeaders == NULL)
  15.         return Invalid;
  16.     /*如果没有重定位表, 返回Conflict*/
  17.     if (SWAPW(NtHeaders->FileHeader.Characteristics) & IMAGE_FILE_RELOCS_STRIPPED)
  18.     {
  19.         return Conflict;
  20.     }
  21.     /× 获得重定位表的起始和结束地址 ×/
  22.     RelocationDDir = &NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
  23.     if (SWAPD(RelocationDDir->VirtualAddress) == 0 || SWAPD(RelocationDDir->Size) == 0)
  24.     {
  25.         return Success;
  26.     }
  27.     Delta = (ULONG_PTR)BaseAddress - SWAPD(NtHeaders->OptionalHeader.ImageBase) + AdditionalBias;
  28.     RelocationDir = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)BaseAddress + SWAPD(RelocationDDir->VirtualAddress));
  29.     RelocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)RelocationDir + SWAPD(RelocationDDir->Size));
  30.     /* 每个IMAGE_BASE_RELOCATION表项循环一次 */
  31.     while (RelocationDir < RelocationEnd &&
  32.             SWAPW(RelocationDir->SizeOfBlock) > 0)
  33.     {
  34.         Count = (SWAPW(RelocationDir->SizeOfBlock) - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
  35.         Address = (ULONG_PTR)RVA(BaseAddress, SWAPD(RelocationDir->VirtualAddress));
  36.         TypeOffset = (PUSHORT)(RelocationDir + 1);
  37.         
  38.         /* 为表里的每一个TypeOffset进行重定位 */
  39.         RelocationDir = LdrProcessRelocationBlockLongLong(Address,
  40.                         Count,
  41.                         TypeOffset,
  42.                         Delta);
  43.         if (RelocationDir == NULL)
  44.         {
  45.             DPRINT1("Error during call to LdrProcessRelocationBlockLongLong()!/n");
  46.             return Invalid;
  47.         }
  48.     }
  49.     return Success;
  50. }
函数从PE头中找到重定位表,计算出重定位的偏移(内存中PE基地址和PE头中基地址的差), 为每一个IMAGE_BASE_RELOCATION结构调用一次LdrProcessRelocationBlockLongLong,这个函数里实现重定位地址的改写。
调用这个函数时Address为管理的4KB内存的开始地址,Count是TypeOffset的个数,TypeOffset为TypeOffset数组的起始地址,Delta是重定位的偏移。
lib/rtl/image.c
  1. PIMAGE_BASE_RELOCATION
  2. NTAPI
  3. LdrProcessRelocationBlockLongLong(
  4.     IN ULONG_PTR Address,
  5.     IN ULONG Count,
  6.     IN PUSHORT TypeOffset,
  7.     IN LONGLONG Delta)
  8. {
  9.     ......
  10.     for (= 0; i < Count; i++)
  11.     {
  12.         /*TypeOffset中的低12位是偏移,高4位是类型。ShortPtr是要重定位的地址*/
  13.         Offset = SWAPW(*TypeOffset) & 0xFFF;
  14.         Type = SWAPW(*TypeOffset) >> 12;
  15.         ShortPtr = (PUSHORT)(RVA(Address, Offset));
  16.         /*不同类型有不同的处理方式*/
  17.         switch (Type)
  18.         {
  19.         case IMAGE_REL_BASED_ABSOLUTE:
  20.             break;
  21.         case IMAGE_REL_BASED_HIGH:          /*ShortPtr指向一个DWORD的高16位*/
  22.             *ShortPtr = HIWORD(MAKELONG(0, *ShortPtr) + (LONG)Delta);
  23.             break;
  24.         case IMAGE_REL_BASED_LOW:           /*ShortPtr指向DWORD的低16位*/
  25.             *ShortPtr = SWAPW(*ShortPtr) + LOWORD(Delta);
  26.             break;
  27.         case IMAGE_REL_BASED_HIGHLOW:       /*指向DWORD*/
  28.             LongPtr = (PULONG)RVA(Address, Offset);
  29.             *LongPtr = SWAPD(*LongPtr) + (ULONG)Delta;
  30.             break;
  31.         case IMAGE_REL_BASED_DIR64:         /*指向QWORD*/
  32.             LongLongPtr = (PUINT64)RVA(Address, Offset);
  33.             *LongLongPtr = SWAPQ(*LongLongPtr) + Delta;
  34.             break;
  35.         case IMAGE_REL_BASED_HIGHADJ:
  36.         case IMAGE_REL_BASED_MIPS_JMPADDR:
  37.         default:
  38.             return (PIMAGE_BASE_RELOCATION)NULL;
  39.         }
  40.         /*下一个TypeOffset*/
  41.         TypeOffset++;
  42.     }
  43.     /*返回一下个IMAGE_BASE_RELOCATION结构指针*/
  44.     return (PIMAGE_BASE_RELOCATION)TypeOffset;
  45. }
LdrProcessRelocationBlockLongLong处理了当前IMAGE_BASE_RELOCATION中的每一个TypeOffset域,将它的低12位分解为偏移,高4位分解为类型。
偏移 + 基地址Address 就是需要重定位的内存地址。每种类型的重定位方式不一样。如IMAGE_REL_BASED_HIGHLOW表示该地址是个DWORD,所以使用LongPtr = (PULONG)RVA(Address, Offset)转化为DWORD来进行处理。
最后当所有的TypeOffset都处理完成后,函数返回下一个IMAGE_BASE_RELOCATION的地址。

到此为止镜像重定位完毕,同时也加载完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值