手动实现Windows Dll加载与内存数据修复

手动实现Windows Dll加载与内存数据修复

  1. 加载dll文件到内存中 (std.heap.GeneralPurposeAllocator)

  2. 解析Dos头和Nt头, 获取 ntHeaders.OptionalHeader.SizeOfImage 大小, 并分配内存 (std.heap.page_allocator)

  3. 复制PE头, 大小 ntHeaders.OptionalHeader.SizeOfHeaders

  4. 复制节表到对应内存中

  5. 处理基址重定位表

  6. 处理导入表

  7. 配置对应内存属性

  8. 调用Dll入口函数 (DllMain)

  9. 返回模块基地址

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 结构体, 其中 OriginalFirstThunkFirstThunk 都是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/
作者
simonkimi
发布于
2026年3月5日
许可协议