手动实现Windows Dll加载与内存数据修复
手动实现Windows Dll加载与内存数据修复
加载dll文件到内存中 (std.heap.GeneralPurposeAllocator)
解析Dos头和Nt头, 获取
ntHeaders.OptionalHeader.SizeOfImage大小, 并分配内存 (std.heap.page_allocator)复制PE头, 大小
ntHeaders.OptionalHeader.SizeOfHeaders复制节表到对应内存中
处理基址重定位表
处理导入表
配置对应内存属性
调用Dll入口函数 (DllMain)
返回模块基地址
1. 复制PE头和节表数据到指定内存
ZIG
// 复制PE头
const headerSize = ntHeaders.OptionalHeader.SizeOfHeaders;
mem.memcpy(peMemory, peFile, headerSize);
var sectionProtectList: std.ArrayList(SectionProtectValue) = .empty;
defer sectionProtectList.deinit(allocator);
// 复制节区数据
const sectionHeaders: [*]const windows.IMAGE_SECTION_HEADER = @ptrCast(@alignCast(peFile.ptr + ntHeadersOffset + @sizeOf(windows.IMAGE_NT_HEADERS)));
const sectionHeadersSlice: []const windows.IMAGE_SECTION_HEADER = sectionHeaders[0..ntHeaders.FileHeader.NumberOfSections];
for (sectionHeadersSlice) |section| {
const fileOffset = section.PointerToRawData;
const memoryOffset = section.VirtualAddress;
const dataSize = section.SizeOfRawData;
const memSize = section.Misc.VirtualSize;
if (dataSize > 0) {
mem.memcpy(&peMemory[memoryOffset], &peFile[fileOffset], dataSize);
}
if (memSize > dataSize) {
mem.memset(&peMemory[memoryOffset + dataSize], 0, memSize - dataSize);
}
// 设置节区保护, 先添加入列表
const protect = getPageProtectFlags(section.Characteristics);
sectionProtectList.append(allocator, SectionProtectValue{
.protect = protect,
.address = peMemory.ptr + memoryOffset,
.size = memSize,
}) catch return LoadDllError.OutOfMemory;
}2. 修补重定位表
重定位表位于
重定位表中每一项都是一个 WORD(uint16_t) 值, 其中高4位位类型, 低12位为偏移量
其定义如下:
ZIG
const RelocItem = union {
value: u16,
item: packed struct {
offset: u12,
type: u4,
},
};修复的公式如下
delta = 实际分配的地址 - ntHeaders.OptionalHeader.ImageBase targetAddr = 实际分配的地址 + relocSection.VirtualAddress + RelocItem.offset targetAddr.* += delta
示例代码:
ZIG
const delta: i64 = @intCast(@intFromPtr(peMemory.ptr) - ntHeaders.OptionalHeader.ImageBase);
const relocAddress = ntHeaders.OptionalHeader.DataDirectory[windows.IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
var relocOffset: usize = 0;
while (true) {
const relocBaseData: *const windows.IMAGE_BASE_RELOCATION = @ptrCast(@alignCast(peMemory.ptr + relocAddress + relocOffset));
if (relocBaseData.SizeOfBlock == 0) break;
relocOffset += relocBaseData.SizeOfBlock;
const itemCount = (relocBaseData.SizeOfBlock - @sizeOf(windows.IMAGE_BASE_RELOCATION)) / @sizeOf(windows.WORD);
const relocItems: [*]windows.WORD = @ptrFromInt(@intFromPtr(relocBaseData) + @sizeOf(windows.IMAGE_BASE_RELOCATION));
const relocItemsSlice: []windows.WORD = relocItems[0..itemCount];
for (relocItemsSlice) |relocWord| {
const relocItem: *const RelocItem = @ptrCast(&relocWord);
const patchAddr = peMemory.ptr + relocBaseData.VirtualAddress + relocItem.item.offset;
if (relocItem.item.type == windows.IMAGE_REL_BASED_DIR64) { // 64位修复
const ptr: *u64 = @ptrCast(@alignCast(patchAddr));
const newAddr = @as(i128, ptr.*) + delta;
const asUnsigned: u128 = @bitCast(newAddr);
ptr.* = @truncate(asUnsigned);
} else if (relocItem.item.type == windows.IMAGE_REL_BASED_HIGHLOW and ntHeaders.OptionalHeader.Magic == windows.IMAGE_NT_OPTIONAL_HDR32_MAGIC) { // 32位修复
const ptr: *u32 = @ptrCast(@alignCast(patchAddr));
const newAddr = @as(i64, ptr.*) + delta;
const asUnsigned: u64 = @bitCast(newAddr);
ptr.* = @truncate(asUnsigned);
}
}
}3. 修复导入表
导入表中每一项都是一个 IMAGE_IMPORT_DESCRIPTOR 结构体, 其中 OriginalFirstThunk 和 FirstThunk 都是RVA, 需要转换为实际地址
其中 OriginalFirstThunk 为INT表, FirstThunk 为IAT表, 在修补的时候需要同时遍历两个表, 从INT表中取数据, 然后修补到IAT表中
示例代码:
ZIG
const importAddress = ntHeaders.OptionalHeader.DataDirectory[windows.IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
var importDescPtr: [*]const pe.ImageImportDescriptor = @ptrCast(@alignCast(peMemory.ptr + importAddress));
while (true) : (importDescPtr += 1) {
const importDesc = importDescPtr[0];
if (importDesc.OriginalFirstThunk == 0) break;
const name: [*:0]u8 = @ptrCast(@alignCast(peMemory.ptr + importDesc.Name));
std.debug.print("Import name: {s}\n", .{name});
const libHandle: windows.HMODULE = windows.LoadLibraryA(name);
if (libHandle == null) {
const lastError = windows.GetLastError();
std.debug.print("LoadLibraryA failed: 0x{x}\n", .{lastError});
return LoadDllError.LoadLibraryFailed;
}
const intRva: windows.DWORD = if (importDesc.OriginalFirstThunk != 0)
importDesc.OriginalFirstThunk
else
importDesc.FirstThunk;
const intThunkArray: [*]const pe.ImageThunkData = @ptrCast(@alignCast(peMemory.ptr + intRva));
const iatThunkArray: [*]pe.ImageThunkData = @ptrCast(@alignCast(peMemory.ptr + importDesc.FirstThunk));
var thunkArrayIndex: usize = 0;
while (true) : (thunkArrayIndex += 1) {
const intThunkItem: *const pe.ImageThunkData = &intThunkArray[thunkArrayIndex];
const iatThunkItem: *pe.ImageThunkData = &iatThunkArray[thunkArrayIndex];
if (intThunkItem.Function == 0) break;
var funcAddr: windows.ULONGLONG = 0;
if (windows.IMAGE_SNAP_BY_ORDINAL(intThunkItem.Function)) {
const ordinal: windows.ULONGLONG = windows.IMAGE_ORDINAL(intThunkItem.Function);
const ordinalWord: windows.WORD = @truncate(ordinal);
const procAddr = windows.GetProcAddress(libHandle, ordinalWord);
funcAddr = @intFromPtr(procAddr);
std.debug.print(" Import by ordinal: {} -> 0x{x}\n", .{ ordinalWord, funcAddr });
} else {
const rva: usize = @intCast(intThunkItem.AddressOfData & 0xFFFF_FFFF);
const importByName: *const pe.ImageImportByName = @ptrCast(@alignCast(peMemory.ptr + rva));
const namePtr: [*:0]const u8 = @ptrCast(&importByName.Name);
const procAddr = windows.GetProcAddress(libHandle, namePtr);
funcAddr = @intFromPtr(procAddr);
std.debug.print(" Import by name: {s} original hint: {} -> 0x{x}\n", .{ namePtr, importByName.Hint, funcAddr });
}
if (funcAddr != 0) {
iatThunkItem.Function = funcAddr;
}
}
}4. 配置对应内存属性
根据节表的Characteristics字段, 获取对应的内存属性
ZIG
fn getPageProtectFlags(characteristics: windows.DWORD) windows.DWORD {
const exec = characteristics & windows.IMAGE_SCN_MEM_EXECUTE != 0;
const read = characteristics & windows.IMAGE_SCN_MEM_READ != 0;
const write = characteristics & windows.IMAGE_SCN_MEM_WRITE != 0;
var flags: u3 = 0;
if (exec) flags |= 0b100;
if (read) flags |= 0b010;
if (write) flags |= 0b001;
var protect: windows.DWORD = switch (flags) {
0b111 => windows.PAGE_EXECUTE_READWRITE,
0b110 => windows.PAGE_EXECUTE_READ,
0b101 => windows.PAGE_EXECUTE_WRITECOPY,
0b100 => windows.PAGE_EXECUTE,
0b011 => windows.PAGE_READWRITE,
0b010 => windows.PAGE_READONLY,
0b001 => windows.PAGE_WRITECOPY,
0b000 => windows.PAGE_NOACCESS,
};
if (characteristics & windows.IMAGE_SCN_MEM_NOT_CACHED != 0) {
protect |= windows.PAGE_NOCACHE;
}
return protect;
}
for (sectionProtectList.items) |sectionProtect| {
var oldProtect: windows.DWORD = 0;
const result = windows.VirtualProtect(sectionProtect.address, sectionProtect.size, sectionProtect.protect, &oldProtect);
if (result != windows.TRUE) {
const lastError = windows.GetLastError();
std.debug.print("VirtualProtect failed: 0x{x}\n", .{lastError});
return LoadDllError.VirtualProtectFailed;
}
}手动实现Windows Dll加载与内存数据修复
https://simonkimi.githubio.io/posts/20260305011026/