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

如何通过Golang将本地DLL注入远程进程(CreateRemoteThread方式)

问题:Golang下CreateRemoteThread注入DLL失败(路径乱码)

我是编程新手,正在学习Golang,当前项目是通过CreateRemoteThread将本地的test.dll注入远程进程。程序需要生成傀儡进程notepad.exe,并注入该DLL(DLL包含唯一导出函数Engage)。

我参考了Shellcode注入的Golang代码和DLL注入的C语言代码,但对DLL注入、Windows API运作机制及内存相关知识均不熟悉,代码基于Shellcode注入框架修改。目前程序能正常运行但注入失败,通过Process Monitor观察到只有指向DLL目录的“create file”操作,且DLL名称乱码(非ASCII,如SHƒì0èf.DLL)。我怀疑两个问题:

  • VirtualAllocEx的内存分配参数是否正确,参考C代码通常只传DLL路径字符串长度
  • 获取kernel32.dll的LoadLibraryA地址的方式是否正确

我的代码如下:

package main

import (
    "fmt"
    "log"
    "os/exec"
    "syscall"

    "golang.org/x/sys/windows"
)

func main() {
    dllPath := "C:\\Users\\Documents\\GoCode\\src\\test.dll"
    /*
       Spawn process to inject to
    */

    cmd := exec.Command("notepad.exe")
    cmd.Start()

    cmdPid := cmd.Process.Pid
    fmt.Println("PID of notepad.exe:", cmdPid)

    /*
        Process Injction
    */
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")
    VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
    WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
    CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx")

    //OpenProcess(desired access, inherite handle, PID)
    //Get a handle to the remote process with the proper secutiry attributes to create a remote thread
    proc, errOpenProcess := windows.OpenProcess(windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_OPERATION|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_READ|windows.PROCESS_QUERY_INFORMATION, false, uint32(cmdPid))
    if errOpenProcess != nil {
        panic(fmt.Sprintf("[!]Error calling OpenProcess:\r\n%s", errOpenProcess.Error()))
    }

    //VirtualAllocEx(handle to process/must have PROCESS_VM_OPERATION, optional starting address for alloc, size of mem region to alloc in bytes, type of mem alloc, mem protect)
    //Allocate memory in the remote process for the DLL
    addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(proc), 0, uintptr(len(dllPath)), windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
    if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {
        panic(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error()))
    }
    //Get uintptr to dll being injected, maybe?
    dll, errLoadLib := syscall.LoadLibrary(dllPath)
    if errLoadLib != nil {
        log.Fatal(errLoadLib)
    }

    dllH, getProcAddErr := syscall.GetProcAddress(syscall.Handle(dll), "Engage")
    if getProcAddErr != nil {
        log.Fatal(errLoadLib)
    }
    loadLibA, err := syscall.LoadLibrary("kernel32.dll")
    if err != nil {
        log.Fatal(errLoadLib)
    }
    loadLibAHandle, err := syscall.GetProcAddress(loadLibA, "LoadLibraryA")
    if err != nil {
        log.Fatal(errLoadLib)
    }

    //WriteProcessMemory(handle to process, base addr to where data is written, pointer to buffer that contains data, num of bytes to be written, optional num of bytes transferred)
    _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(proc), addr, (uintptr)(dllH), uintptr(len(dllPath)))
    if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." {
        panic(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error()))
    }

    //CreateRemoteThreadEx(handle to process, security attributes, ).
    _, _, errCreateRemoteThreadEx := CreateRemoteThreadEx.Call(uintptr(proc), 0, 0, loadLibAHandle, addr, 0, 0)
    if errCreateRemoteThreadEx != nil && errCreateRemoteThreadEx.Error() != "The operation completed successfully." {
        panic(fmt.Sprintf("[!]Error calling CreateRemoteThreadEx:\r\n%s", errCreateRemoteThreadEx.Error()))
    }
    /*
       Close handle to process
    */
    closeHandle := windows.CloseHandle(proc)
    if closeHandle != nil {
        fmt.Println("Error closing process:", closeHandle)
    } else {
        fmt.Println("Closed handle successfully.")
    }

}

核心问题分析

你的代码存在两个致命错误,直接导致注入失败和路径乱码:

  1. 写入远程进程的内容错误:你把本地进程中test.dll导出函数Engage的地址(dllH)写入了远程进程内存,而非DLL的路径字符串。远程进程无法识别本地进程的内存地址,会把这些二进制数据当成乱码路径解析。
  2. 内存分配长度遗漏终止符:Windows的C风格字符串需要以\0(空字符)结尾,你用len(dllPath)计算长度时未包含这个终止符,导致远程进程读取路径时越界,出现乱码。
  3. LoadLibraryA地址获取冗余:kernel32.dll是系统核心DLL,在所有进程中的加载基址固定(ASLR不影响其地址一致性),直接通过windows.NewLazySystemDLL获取即可,无需重复调用syscall.LoadLibrary。

修正后的代码

package main

import (
    "fmt"
    "log"
    "os/exec"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

func main() {
    dllPath := "C:\\Users\\Documents\\GoCode\\src\\test.dll"
    // 添加字符串终止符,确保远程进程能正确识别字符串结束位置
    dllPathWithNull := dllPath + "\x00"

    /* 启动傀儡进程 */
    cmd := exec.Command("notepad.exe")
    if err := cmd.Start(); err != nil {
        log.Fatalf("启动notepad失败: %v", err)
    }
    cmdPid := cmd.Process.Pid
    fmt.Println("notepad.exe的PID:", cmdPid)

    /* 初始化Windows API函数 */
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")
    VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
    WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
    CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx")
    // 直接获取LoadLibraryA的地址,kernel32在所有进程中地址一致
    loadLibraryA := kernel32.NewProc("LoadLibraryA")

    /* 打开远程进程 */
    proc, err := windows.OpenProcess(
        windows.PROCESS_CREATE_THREAD|
            windows.PROCESS_VM_OPERATION|
            windows.PROCESS_VM_WRITE|
            windows.PROCESS_VM_READ,
        false,
        uint32(cmdPid),
    )
    if err != nil {
        panic(fmt.Sprintf("调用OpenProcess失败:\n%s", err.Error()))
    }
    defer windows.CloseHandle(proc) // 用defer确保句柄自动关闭,避免资源泄漏

    /* 在远程进程中分配内存 */
    addr, _, err := VirtualAllocEx.Call(
        uintptr(proc),
        0,
        uintptr(len(dllPathWithNull)), // 包含空终止符的完整长度
        windows.MEM_RESERVE|windows.MEM_COMMIT,
        windows.PAGE_READWRITE,
    )
    if err != nil && err.Error() != "The operation completed successfully." {
        panic(fmt.Sprintf("调用VirtualAllocEx失败:\n%s", err.Error()))
    }

    /* 将DLL路径写入远程进程内存 */
    // 把Go字符串转为C风格的字节指针,作为WriteProcessMemory的缓冲区参数
    dllPathPtr := unsafe.Pointer(syscall.StringBytePtr(dllPathWithNull))
    _, _, err = WriteProcessMemory.Call(
        uintptr(proc),
        addr,
        uintptr(dllPathPtr),
        uintptr(len(dllPathWithNull)),
        0, // 忽略返回的写入字节数
    )
    if err != nil && err.Error() != "The operation completed successfully." {
        panic(fmt.Sprintf("调用WriteProcessMemory失败:\n%s", err.Error()))
    }

    /* 创建远程线程执行LoadLibraryA */
    _, _, err = CreateRemoteThreadEx.Call(
        uintptr(proc),
        0,
        0,
        loadLibraryA.Addr(), // 直接用Proc的Addr()获取函数地址
        addr,                // 远程内存中DLL路径的地址
        0,
        0,
    )
    if err != nil && err.Error() != "The operation completed successfully." {
        panic(fmt.Sprintf("调用CreateRemoteThreadEx失败:\n%s", err.Error()))
    }

    fmt.Println("注入操作已执行,请检查notepad进程是否加载test.dll")
}

关键修改点说明

  • 添加字符串终止符:构造dllPathWithNull,确保远程进程能正确识别字符串边界
  • 写入正确内容:将DLL路径字符串(而非本地的函数地址)写入远程进程内存
  • 简化LoadLibraryA地址获取:直接通过kernel32.NewProc("LoadLibraryA").Addr()获取,避免冗余操作
  • 使用defer自动关闭句柄:防止进程句柄泄漏
  • 修正WriteProcessMemory的缓冲区参数:用syscall.StringBytePtr将Go字符串转为符合Windows API要求的C风格字节指针

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 00:25:20