Freeldr中镜像加载相关的操作在Freeldr/Freeldr/windows/Peloader.c文件中。
首先来看看如何加载一个PE镜像。这个操作在WinLdrLoadImage函数中。
这个函数的三个参数分别为镜像的Arc路径FileName, 镜像所处内存的内存属性MemoryType, 加载后镜像的基地址由ImageBasePA返回。
- BOOLEAN
- WinLdrLoadImage(IN PCHAR FileName,
- TYPE_OF_MEMORY MemoryType,
- OUT PVOID *ImageBasePA)
- {
- ......
- /* 在屏幕中央显示 Loading xxx... */
- sprintf(ProgressString, "Loading %s...", strchr(FileName, '//') + 1);
- UiDrawBackdrop();
- UiDrawProgressBarCenter(1, 100, ProgressString);
- /*打卡Arc路径的文件,并读取前两个sector。用来读取PE头*/
- Status = ArcOpen(FileName, OpenReadOnly, &FileId);
- if (Status != ESUCCESS)
- {
- //UiMessageBox("Can not open the file");
- return FALSE;
- }
- Status = ArcRead(FileId, HeadersBuffer, SECTOR_SIZE * 2, &BytesRead);
- if (Status != ESUCCESS)
- {
- UiMessageBox("Error reading from file");
- ArcClose(FileId);
- return FALSE;
- }
- /* 获得NT头指针 */
- NtHeaders = RtlImageNtHeader(HeadersBuffer);
- if (!NtHeaders)
- {
- UiMessageBox("Error - no NT header found");
- ArcClose(FileId);
- return FALSE;
- }
- /* 检测是否是可执行镜像 */
- if (((NtHeaders->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0))
- {
- UiMessageBox("Not an executable image");
- ArcClose(FileId);
- return FALSE;
- }
- /* 从PE头中获得section数量, 并且获得节表的首地址 */
- NumberOfSections = NtHeaders->FileHeader.NumberOfSections;
- SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
- /* 首先以PE头中的ImageBase为基地址申请存放镜像的内存, 如果失败就从任意地址申请内存 */
- PhysicalBase = MmAllocateMemoryAtAddress(NtHeaders->OptionalHeader.SizeOfImage,
- (PVOID)((ULONG)NtHeaders->OptionalHeader.ImageBase & (KSEG0_BASE - 1)),
- MemoryType);
- if (PhysicalBase == NULL)
- {
- /* It's ok, we don't panic - let's allocate again at any other "low" place */
- PhysicalBase = MmAllocateMemoryWithType(NtHeaders->OptionalHeader.SizeOfImage, MemoryType);
- if (PhysicalBase == NULL)
- {
- //Print(L"Failed to alloc pages for image %s/n", FileName);
- UiMessageBox("Failed to alloc pages for image");
- ArcClose(FileId);
- return FALSE;
- }
- }
- /* 对于x86而言PaToVa直接返回PhysicalBase */
- VirtualBase = PaToVa(PhysicalBase);
- /* 把PE头全部读入申请的内存 */
- Position.HighPart = Position.LowPart = 0;
- Status = ArcSeek(FileId, &Position, SeekAbsolute);
- if (Status != ESUCCESS)
- {
- UiMessageBox("Error seeking to start of file");
- ArcClose(FileId);
- return FALSE;
- }
- Status = ArcRead(FileId, PhysicalBase, NtHeaders->OptionalHeader.SizeOfHeaders, &BytesRead);
- if (Status != ESUCCESS)
- {
- //Print(L"Error reading headers %s/n", FileName);
- UiMessageBox("Error reading headers");
- ArcClose(FileId);
- return FALSE;
- }
- /* 获得刚刚读入的完整的PE头 */
- NtHeaders = RtlImageNtHeader(PhysicalBase);
- /* 获得节表 */
- SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
- /* 输出PysicalBase地址 */
- *ImageBasePA = PhysicalBase;
- /* 按照节表要求, 把节读入物理内存 */
- for (i = 0; i < NumberOfSections; i++)
- {
- VirtualSize = SectionHeader->Misc.VirtualSize;
- SizeOfRawData = SectionHeader->SizeOfRawData;
- /* Handle a case when VirtualSize equals 0 */
- if (VirtualSize == 0)
- VirtualSize = SizeOfRawData;
- /* If PointerToRawData is 0, then force its size to be also 0 */
- if (SectionHeader->PointerToRawData == 0)
- {
- SizeOfRawData = 0;
- }
- else
- {
- /* Cut the loaded size to the VirtualSize extents */
- if (SizeOfRawData > VirtualSize)
- SizeOfRawData = VirtualSize;
- }
- /* Actually read the section (if its size is not 0) */
- if (SizeOfRawData != 0)
- {
- /* Seek to the correct position */
- Position.LowPart = SectionHeader->PointerToRawData;
- Status = ArcSeek(FileId, &Position, SeekAbsolute);
- DPRINTM(DPRINT_PELOADER, "SH->VA: 0x%X/n", SectionHeader->VirtualAddress);
- /* Read this section from the file, size = SizeOfRawData */
- Status = ArcRead(FileId, (PUCHAR)PhysicalBase + SectionHeader->VirtualAddress, SizeOfRawData, &BytesRead);
- if (Status != ESUCCESS)
- {
- DPRINTM(DPRINT_PELOADER, "WinLdrLoadImage(): Error reading section from file!/n");
- break;
- }
- }
- /* Size of data is less than the virtual size - fill up the remainder with zeroes */
- if (SizeOfRawData < VirtualSize)
- {
- DPRINTM(DPRINT_PELOADER, "WinLdrLoadImage(): SORD %d < VS %d/n", SizeOfRawData, VirtualSize);
- RtlZeroMemory((PVOID)(SectionHeader->VirtualAddress + (ULONG_PTR)PhysicalBase + SizeOfRawData), VirtualSize - SizeOfRawData);
- }
- SectionHeader++;
- }
- /* We are done with the file - close it */
- ArcClose(FileId);
- /* If loading failed - return right now */
- if (Status != ESUCCESS)
- return FALSE;
- /* 如果内存镜像的基地址和PE头记录的ImageBase不同则需要重定位 */
- if (NtHeaders->OptionalHeader.ImageBase != (ULONG_PTR)VirtualBase)
- {
- DPRINTM(DPRINT_PELOADER, "Relocating %p -> %p/n",
- NtHeaders->OptionalHeader.ImageBase, VirtualBase);
- return (BOOLEAN)LdrRelocateImageWithBias(PhysicalBase,
- (ULONG_PTR)VirtualBase - (ULONG_PTR)PhysicalBase,
- "FreeLdr",
- TRUE,
- TRUE, /* in case of conflict still return success */
- FALSE);
- }
- return TRUE;
- }
通过读取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的数组
- typedef struct _IMAGE_BASE_RELOCATION {
- ULONG VirtualAddress;
- ULONG SizeOfBlock;
- USHORT TypeOffset[(SizeOfBlock - 2 * sizeof(ULONG)) / sizeof(USHORT)]
- } 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
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分别代表成功、冲突和不成功的返回值。
LdrRelocateImageWithBias有6个参数,BaseAddress是PE内存镜像的基地址,AdditionalBias是PE头开始的偏移,在X86建构下这个值为0。LoaderName没有使用,看名字应该是输出DEBUG输出信息时的模块名。
Success/Conflict/Invalid分别代表成功、冲突和不成功的返回值。
lib/rtl/Image.c
- ULONG
- NTAPI
- LdrRelocateImageWithBias(
- IN PVOID BaseAddress,
- IN LONGLONG AdditionalBias,
- IN PCCH LoaderName,
- IN ULONG Success,
- IN ULONG Conflict,
- IN ULONG Invalid)
- {
- ....
- /*获得nt头*/
- NtHeaders = RtlImageNtHeader(BaseAddress);
- if (NtHeaders == NULL)
- return Invalid;
- /*如果没有重定位表, 返回Conflict*/
- if (SWAPW(NtHeaders->FileHeader.Characteristics) & IMAGE_FILE_RELOCS_STRIPPED)
- {
- return Conflict;
- }
- /× 获得重定位表的起始和结束地址 ×/
- RelocationDDir = &NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
- if (SWAPD(RelocationDDir->VirtualAddress) == 0 || SWAPD(RelocationDDir->Size) == 0)
- {
- return Success;
- }
- Delta = (ULONG_PTR)BaseAddress - SWAPD(NtHeaders->OptionalHeader.ImageBase) + AdditionalBias;
- RelocationDir = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)BaseAddress + SWAPD(RelocationDDir->VirtualAddress));
- RelocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)RelocationDir + SWAPD(RelocationDDir->Size));
- /* 每个IMAGE_BASE_RELOCATION表项循环一次 */
- while (RelocationDir < RelocationEnd &&
- SWAPW(RelocationDir->SizeOfBlock) > 0)
- {
- Count = (SWAPW(RelocationDir->SizeOfBlock) - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
- Address = (ULONG_PTR)RVA(BaseAddress, SWAPD(RelocationDir->VirtualAddress));
- TypeOffset = (PUSHORT)(RelocationDir + 1);
- /* 为表里的每一个TypeOffset进行重定位 */
- RelocationDir = LdrProcessRelocationBlockLongLong(Address,
- Count,
- TypeOffset,
- Delta);
- if (RelocationDir == NULL)
- {
- DPRINT1("Error during call to LdrProcessRelocationBlockLongLong()!/n");
- return Invalid;
- }
- }
- return Success;
- }
函数从PE头中找到重定位表,计算出重定位的偏移(内存中PE基地址和PE头中基地址的差), 为每一个IMAGE_BASE_RELOCATION结构调用一次LdrProcessRelocationBlockLongLong,这个函数里实现重定位地址的改写。
调用这个函数时Address为管理的4KB内存的开始地址,Count是TypeOffset的个数,TypeOffset为TypeOffset数组的起始地址,Delta是重定位的偏移。
lib/rtl/image.c
- PIMAGE_BASE_RELOCATION
- NTAPI
- LdrProcessRelocationBlockLongLong(
- IN ULONG_PTR Address,
- IN ULONG Count,
- IN PUSHORT TypeOffset,
- IN LONGLONG Delta)
- {
- ......
- for (i = 0; i < Count; i++)
- {
- /*TypeOffset中的低12位是偏移,高4位是类型。ShortPtr是要重定位的地址*/
- Offset = SWAPW(*TypeOffset) & 0xFFF;
- Type = SWAPW(*TypeOffset) >> 12;
- ShortPtr = (PUSHORT)(RVA(Address, Offset));
- /*不同类型有不同的处理方式*/
- switch (Type)
- {
- case IMAGE_REL_BASED_ABSOLUTE:
- break;
- case IMAGE_REL_BASED_HIGH: /*ShortPtr指向一个DWORD的高16位*/
- *ShortPtr = HIWORD(MAKELONG(0, *ShortPtr) + (LONG)Delta);
- break;
- case IMAGE_REL_BASED_LOW: /*ShortPtr指向DWORD的低16位*/
- *ShortPtr = SWAPW(*ShortPtr) + LOWORD(Delta);
- break;
- case IMAGE_REL_BASED_HIGHLOW: /*指向DWORD*/
- LongPtr = (PULONG)RVA(Address, Offset);
- *LongPtr = SWAPD(*LongPtr) + (ULONG)Delta;
- break;
- case IMAGE_REL_BASED_DIR64: /*指向QWORD*/
- LongLongPtr = (PUINT64)RVA(Address, Offset);
- *LongLongPtr = SWAPQ(*LongLongPtr) + Delta;
- break;
- case IMAGE_REL_BASED_HIGHADJ:
- case IMAGE_REL_BASED_MIPS_JMPADDR:
- default:
- return (PIMAGE_BASE_RELOCATION)NULL;
- }
- /*下一个TypeOffset*/
- TypeOffset++;
- }
- /*返回一下个IMAGE_BASE_RELOCATION结构指针*/
- return (PIMAGE_BASE_RELOCATION)TypeOffset;
- }
LdrProcessRelocationBlockLongLong处理了当前IMAGE_BASE_RELOCATION中的每一个TypeOffset域,将它的低12位分解为偏移,高4位分解为类型。
偏移 + 基地址Address 就是需要重定位的内存地址。每种类型的重定位方式不一样。如IMAGE_REL_BASED_HIGHLOW表示该地址是个DWORD,所以使用LongPtr = (PULONG)RVA(Address, Offset)转化为DWORD来进行处理。
最后当所有的TypeOffset都处理完成后,函数返回下一个IMAGE_BASE_RELOCATION的地址。
到此为止镜像重定位完毕,同时也加载完毕。