如何通过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.") } }
核心问题分析
你的代码存在两个致命错误,直接导致注入失败和路径乱码:
- 写入远程进程的内容错误:你把本地进程中
test.dll导出函数Engage的地址(dllH)写入了远程进程内存,而非DLL的路径字符串。远程进程无法识别本地进程的内存地址,会把这些二进制数据当成乱码路径解析。 - 内存分配长度遗漏终止符:Windows的C风格字符串需要以
\0(空字符)结尾,你用len(dllPath)计算长度时未包含这个终止符,导致远程进程读取路径时越界,出现乱码。 - 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
相关产品推荐
相关产品推荐

