You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

反射加载CGO生成的Go DLL失败问题求助

反射加载Go生成的CGO DLL时.rdata段被置空导致崩溃的问题

问题场景

我正在尝试进行反射DLL加载实验,编写了一个简单的带MessageBox功能的Go代码,通过CGO生成DLL:

package main
import "C"
import (
    "unsafe"
    "syscall"
)

//export OnProcessAttach
func OnProcessAttach() {
    const (
        NULL  = 0
        MB_OK = 0
    )
    caption := "Hola"
    title := "desdegoo"
    ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
        uintptr(NULL),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
        uintptr(MB_OK))

    if ret != 1 {
        return
    }
    return
}

func main() {}

使用命令go build --buildmode=c-shared main.go生成DLL后,用LoadLibrary()加载并调用导出函数OnProcessAttach可正常弹出消息框,但尝试通过处理基址重定位和解析导入地址表实现反射加载时却失败。

在处理重定位与IAT后,发现用于Go运行时初始化的.rdata段部分内容被置空,该运行时初始化逻辑位于NT头指定的入口点中。我使用的重定位与IAT解析代码如下:

// resolve base relocations
IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)dllBase;
DWORD relocationsProcessed = 0;

while (relocationsProcessed < relocations.Size)
{
    PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);
    relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);
    DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
    PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);
    for (DWORD i = 0; i < relocationsCount; i++)
    {
        relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);
        if (relocationEntries[i].Type == 0)
        {
            continue;
        }

        DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset;
        DWORD_PTR addressToPatch = 0;
        ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD_PTR)dllBase, relocationRVA), &addressToPatch, sizeof(DWORD_PTR), NULL);
        addressToPatch += deltaImageBase;
        memcpy((PVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR));
    }
}

// resolve IAT
PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;
IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dllBase);
LPCSTR libraryName = "";
HMODULE library = NULL;

while (importDescriptor->Name != NULL)
{
    libraryName = (LPCSTR)importDescriptor->Name + (DWORD_PTR)dllBase;
    library = LoadLibraryA(libraryName);
    if (library)
    {
        PIMAGE_THUNK_DATA thunk = NULL;
        thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dllBase + importDescriptor->FirstThunk);
        while (thunk->u1.AddressOfData != NULL)
        {
            if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
            {
                LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
                thunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal);
            }
            else {
                PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dllBase + thunk->u1.AddressOfData);
                DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name);
                thunk->u1.Function = functionAddress;
            }
            ++thunk;
        }
    }
    importDescriptor++;
}

完成上述操作后,解析导出地址表找到OnProcessAttach函数,直接调用因Go运行时未初始化无法工作,尝试初始化运行时时程序崩溃,抛出EXCEPTION_ACCESS_VIOLATION异常,原因是尝试读取被置空的内存块。

入口点反汇编代码:

mov rax, qdword ptr ds:[address]
mov dword ptr ds:[rax]

查看内存转储发现该地址内容全为0,而原DLL中该地址有有效数值。我确认是处理重定位时导致.rdata段内容被置空,但无法确定原因,请问是Go运行时不支持此类反射加载,还是我的代码存在问题?


问题分析与修复

直接原因:重定位代码的语法错误

你的重定位处理代码存在致命错误:

  • 在ReadProcessMemory的参数中,(LPCVOID)((DWORD_PTR)dllBase, relocationRVA)使用了逗号运算符,这会导致表达式的结果是最后一个值relocationRVA,完全没有加上dllBase的基址。这意味着你读取的是进程内存中relocationRVA对应的低地址(通常是空内存,值为0),然后将这个0加上deltaImageBase后写回dllBase+relocationRVA,直接覆盖了.rdata段中原本有效的内容,这就是.rdata被置空的根源。
  • 此外,ReadProcessMemory在这里完全多余——你已经将DLL映射到当前进程的内存空间,直接通过指针访问即可,不需要跨进程读取。

修正重定位代码

将重定位循环中的读取逻辑替换为直接内存访问:

// 替换原来的ReadProcessMemory行
addressToPatch = *(DWORD_PTR*)((DWORD_PTR)dllBase + relocationRVA);

额外注意事项

  1. Go运行时初始化:CGO生成的DLL必须先调用NT头指定的入口点(Go runtime的初始化函数)才能正常使用,修复重定位错误后,需要先调用该入口点完成初始化,再调用导出的OnProcessAttach函数。
  2. 重定位类型匹配:x64架构下的重定位类型是IMAGE_REL_BASED_DIR64,你的代码当前只跳过Type=0的条目,这在x64下是可行的,但如果是x86架构需要对应处理IMAGE_REL_BASED_HIGHLOW等类型。
  3. 延迟导入表处理:Go生成的DLL可能包含延迟导入表(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT),如果你的反射加载代码没有处理这部分,后续调用延迟导入的函数时可能仍会出错,需要补充对应的处理逻辑。

内容的提问来源于stack exchange,提问作者Cuervo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:10:41