Skip to content

Commit 5fc7995

Browse files
committed
initial version
0 parents  commit 5fc7995

File tree

9 files changed

+941
-0
lines changed

9 files changed

+941
-0
lines changed

LICENCE

Lines changed: 438 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Go-win-process-injector
2+
3+
## Description
4+
During my researches on process injection in Go, i have only found shellcode injections. But, in my case, I needed to include complex code in the process i was injecting into without instability linked to my action.
5+
6+
This complex code was compiled in a Go DLL. However, Go does not incorporate logic similar to DllMain to allow direct execution of a function once the code has been injected. This program therefore takes care of finding the address of the target function and then executing it in the context of the process where the injection took place.
7+
8+
## Instruction, example and sequence of the injection
9+
10+
Quick and easy way:
11+
* Use injectInProcess() to inject in the specified PID and call any selected function inside the memory space of this process
12+
* Use GetInjectedLibraryModuleHandle() to check if your library already is injected to avoid multiple useless injection
13+
14+
If you want more details on how it works:
15+
* It first OpenProcess() your target PID
16+
* Then, injectDLL() create a remote thread an load your DLL inside it
17+
* findSymbolRVA() identify the relative virtual address of your function in the dll
18+
* Finally, callRemoteFunction() execute it in a new thread of your target PID
19+
20+
Process output:
21+
```
22+
[INFO] Starting process injection...
23+
[INFO] Found process: Notepad.exe (PID: 17472)
24+
[DEBUG] PID: 17472 - Opening process Notepad.exe with 0x2a access...
25+
[DEBUG] PID: 17472 - Process Handle: 0x228
26+
[DEBUG] Loading DLL: C:\Temp\MyDll.dll
27+
[DEBUG] PID: 17472 - DLL Path Length: 40
28+
[INFO] PID: 17472 - VirtualAllocEx...
29+
[DEBUG] PID: 17472 - Allocating memory at: 0x21a60370000
30+
[DEBUG] PID: 17472 - Bytes written: 82
31+
[DEBUG] PID: 17472 - CreateRemoteThread...
32+
[DEBUG] PID: 17472 - Thread Handle: 556
33+
[DEBUG] PID: 17472 - Waiting for thread to finish...
34+
[DEBUG] PID: 17472 - DLL address in the remote process: 0x7ffe1eab0000
35+
[INFO] PID: 17472 - DLL injected successfully.
36+
[DEBUG] PID: 17472 - Function 'MyInjectFunction' RVA: 0xb2e60
37+
[INFO] PID: 17472 - Function 'MyInjectFunction' successfully called.
38+
```

dllutils.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"debug/pe"
6+
"encoding/binary"
7+
"fmt"
8+
"os"
9+
)
10+
11+
func findSymbolRVA(PEpath string, symbolName string) (uint32, error) {
12+
f, err := os.Open(PEpath)
13+
if err != nil {
14+
return 0, err
15+
}
16+
defer f.Close()
17+
18+
peFile, err := pe.NewFile(f)
19+
if err != nil {
20+
return 0, err
21+
}
22+
defer peFile.Close()
23+
24+
exportDir := peFile.OptionalHeader.(*pe.OptionalHeader64).DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT]
25+
if exportDir.VirtualAddress == 0 {
26+
exportDir = peFile.OptionalHeader.(*pe.OptionalHeader32).DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT]
27+
if exportDir.VirtualAddress == 0 {
28+
return 0, fmt.Errorf("no export directory found")
29+
}
30+
}
31+
32+
var exportTable *pe.Section
33+
for _, section := range peFile.Sections {
34+
if uint32(exportDir.VirtualAddress) >= section.VirtualAddress &&
35+
uint32(exportDir.VirtualAddress) < section.VirtualAddress+section.Size {
36+
exportTable = section
37+
break
38+
}
39+
}
40+
if exportTable == nil {
41+
return 0, fmt.Errorf("could not find export section")
42+
}
43+
44+
exportData, err := exportTable.Data()
45+
if err != nil {
46+
return 0, fmt.Errorf("could not read export data: %w", err)
47+
}
48+
49+
exportDirRVA := uint32(exportDir.VirtualAddress)
50+
exportDirOffset := exportDirRVA - exportTable.VirtualAddress
51+
52+
exportDirectory := struct {
53+
Characteristics uint32
54+
TimeDateStamp uint32
55+
MajorVersion uint16
56+
MinorVersion uint16
57+
Name uint32
58+
Base uint32
59+
NumberOfFunctions uint32
60+
NumberOfNames uint32
61+
AddressOfFunctions uint32
62+
AddressOfNames uint32
63+
AddressOfNameOrdinals uint32
64+
}{}
65+
66+
err = binary.Read(bytes.NewReader(exportData[exportDirOffset:]), binary.LittleEndian, &exportDirectory)
67+
if err != nil {
68+
return 0, fmt.Errorf("could not read export directory: %w", err)
69+
}
70+
71+
namesOffset := exportDirectory.AddressOfNames - exportTable.VirtualAddress
72+
ordinalsOffset := exportDirectory.AddressOfNameOrdinals - exportTable.VirtualAddress
73+
74+
for i := 0; i < int(exportDirectory.NumberOfNames); i++ {
75+
var nameRVA uint32
76+
err = binary.Read(bytes.NewReader(exportData[namesOffset+uint32(i)*4:]), binary.LittleEndian, &nameRVA)
77+
if err != nil {
78+
return 0, fmt.Errorf("could not read name RVA: %w", err)
79+
}
80+
81+
nameOffset := nameRVA - exportTable.VirtualAddress
82+
name := ""
83+
for j := nameOffset; j < uint32(len(exportData)); j++ {
84+
if exportData[j] == 0 {
85+
break
86+
}
87+
name += string(exportData[j])
88+
}
89+
90+
if name == symbolName {
91+
var ordinal uint16
92+
err = binary.Read(bytes.NewReader(exportData[ordinalsOffset+uint32(i)*2:]), binary.LittleEndian, &ordinal)
93+
if err != nil {
94+
return 0, fmt.Errorf("could not read ordinal: %w", err)
95+
}
96+
97+
functionsTableOffsetInData := (exportDirectory.AddressOfFunctions) - (exportTable.VirtualAddress)
98+
functionAddressOffset := functionsTableOffsetInData + uint32(ordinal)*4
99+
100+
if int(functionAddressOffset)+4 > len(exportData) {
101+
return 0, fmt.Errorf("reading outside limit of exportedData")
102+
}
103+
104+
var rawFunctionAddress uint32
105+
err = binary.Read(bytes.NewReader(exportData[functionAddressOffset:]), binary.LittleEndian, &rawFunctionAddress)
106+
if err != nil {
107+
return 0, fmt.Errorf("could not read raw function address: %w", err)
108+
}
109+
110+
return rawFunctionAddress, nil
111+
}
112+
}
113+
114+
return 0, fmt.Errorf("symbol %s not found in export table", symbolName)
115+
}

go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module go-win-process-injector
2+
3+
go 1.23.6
4+
5+
require (
6+
github.com/shirou/gopsutil v3.21.11+incompatible
7+
golang.org/x/sys v0.32.0
8+
)
9+
10+
require (
11+
github.com/go-ole/go-ole v1.2.6 // indirect
12+
github.com/stretchr/testify v1.10.0 // indirect
13+
github.com/tklauser/go-sysconf v0.3.15 // indirect
14+
github.com/tklauser/numcpus v0.10.0 // indirect
15+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
16+
)

go.sum

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
4+
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
8+
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
9+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
10+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11+
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
12+
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
13+
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
14+
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
15+
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
16+
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
17+
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
18+
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
19+
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
20+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
21+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

injector.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"syscall"
6+
"unsafe"
7+
8+
"golang.org/x/sys/windows"
9+
)
10+
11+
var (
12+
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
13+
loadLibraryW = kernel32DLL.NewProc("LoadLibraryW")
14+
virtualAllocEx = kernel32DLL.NewProc("VirtualAllocEx")
15+
writeProcessMemory = kernel32DLL.NewProc("WriteProcessMemory")
16+
createRemoteThread = kernel32DLL.NewProc("CreateRemoteThread")
17+
createToolhelp32Snapshot = kernel32DLL.NewProc("CreateToolhelp32Snapshot")
18+
process32FirstW = kernel32DLL.NewProc("Process32FirstW")
19+
process32NextW = kernel32DLL.NewProc("Process32NextW")
20+
)
21+
22+
type PROCESSENTRY32 struct {
23+
DwSize uint32
24+
CntUsage uint32
25+
Th32ProcessID uint32
26+
Th32DefaultHeapID uintptr
27+
Th32ModuleID uint32
28+
CntThreads uint32
29+
Th32ParentProcessID uint32
30+
PcPriClassBase int32
31+
DwFlags uint32
32+
SzExeFile [syscall.MAX_PATH]uint8
33+
}
34+
35+
const (
36+
MEM_RESERVE = 0x00002000
37+
MEM_COMMIT = 0x00001000
38+
TH32CS_SNAPPROCESS = 0x00000002
39+
)
40+
41+
func injectDLL(processID uint32, processHandle windows.Handle, dllPath string) (uintptr, error) {
42+
dllPathPtr, err := windows.UTF16PtrFromString(dllPath)
43+
if err != nil {
44+
return 0, err
45+
}
46+
47+
remoteAlloc, _, err := virtualAllocEx.Call(
48+
uintptr(processHandle),
49+
0,
50+
uintptr(len(dllPath)*2+2),
51+
uintptr(MEM_RESERVE|MEM_COMMIT),
52+
uintptr(windows.PAGE_READWRITE),
53+
)
54+
if remoteAlloc == 0 {
55+
return 0, fmt.Errorf("VirtualAllocEx failed: %v", err)
56+
}
57+
logMessage(LOGLEVEL_INFO, fmt.Sprintf("PID: %d - VirtualAllocEx...\n", processID))
58+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Allocating memory at: 0x%x\n", processID, remoteAlloc))
59+
60+
bytesWritten := uint(0)
61+
_, _, err = writeProcessMemory.Call(
62+
uintptr(processHandle),
63+
remoteAlloc,
64+
uintptr(unsafe.Pointer(dllPathPtr)),
65+
uintptr(len(dllPath)*2+2),
66+
uintptr(unsafe.Pointer(&bytesWritten)),
67+
)
68+
if bytesWritten == 0 {
69+
return 0, fmt.Errorf("PID: %d WriteProcessMemory failed: %v", processID, err)
70+
}
71+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Bytes written: %d\n", processID, bytesWritten))
72+
73+
threadHandle, _, err := createRemoteThread.Call(
74+
uintptr(processHandle),
75+
0,
76+
0,
77+
uintptr(loadLibraryW.Addr()),
78+
remoteAlloc,
79+
0,
80+
0,
81+
)
82+
if threadHandle == 0 {
83+
return 0, fmt.Errorf("PID: %d - CreateRemoteThread failed: %v", processID, err)
84+
}
85+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - CreateRemoteThread...\n", processID))
86+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Thread Handle: %d\n", processID, threadHandle))
87+
defer syscall.CloseHandle(syscall.Handle(threadHandle))
88+
89+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Waiting for thread to finish...\n", processID))
90+
_, err = syscall.WaitForSingleObject(syscall.Handle(threadHandle), syscall.INFINITE)
91+
if err != nil {
92+
return 0, fmt.Errorf("PID: %d - WaitForSingleObject failed: %v", processID, err)
93+
}
94+
95+
// Récupérer l'adresse de la DLL chargée dans le processus distant
96+
remoteDLLHandle, err := GetInjectedLibraryModuleHandle(processID, dllPath)
97+
if err != nil {
98+
return 0, fmt.Errorf("PID: %d - GetModuleHandle failed: %v", processID, err)
99+
}
100+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - DLL address in the remote process: 0x%x\n", processID, remoteDLLHandle))
101+
102+
return remoteDLLHandle, nil
103+
}
104+
105+
func GetInjectedLibraryModuleHandle(processID uint32, injectedDllPath string) (uintptr, error) {
106+
handle, err := syscall.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, processID)
107+
if err != nil {
108+
return 0, fmt.Errorf("PID: %d - error opening process: %w", processID, err)
109+
}
110+
defer syscall.CloseHandle(syscall.Handle(handle))
111+
112+
var modules [1024]windows.Handle
113+
var needed uint32
114+
err = windows.EnumProcessModules(windows.Handle(handle), &modules[0], uint32(unsafe.Sizeof(modules)), &needed)
115+
if err != nil {
116+
return 0, fmt.Errorf("PID: %d - error enumerating process modules: %v", processID, err)
117+
}
118+
119+
numModules := needed / uint32(unsafe.Sizeof(windows.Handle(0)))
120+
for i := uint32(0); i < numModules; i++ {
121+
var filename [windows.MAX_PATH]uint16
122+
err = windows.GetModuleFileNameEx(windows.Handle(handle), modules[i], &filename[0], windows.MAX_PATH)
123+
if err == nil && windows.UTF16ToString(filename[:]) == injectedDllPath {
124+
return uintptr(modules[i]), nil
125+
}
126+
}
127+
return 0, nil
128+
}
129+
130+
func callRemoteFunction(processID uint32, dllBaseAddress uintptr, functionName string, functionRVA uintptr) error {
131+
handle, err := syscall.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, processID)
132+
if err != nil {
133+
return fmt.Errorf("PID: %d - error opening process: %w", processID, err)
134+
}
135+
defer syscall.CloseHandle(syscall.Handle(handle))
136+
137+
remoteFunctionAddress := dllBaseAddress + functionRVA
138+
139+
threadHandle, _, err := createRemoteThread.Call(
140+
uintptr(handle),
141+
0,
142+
0,
143+
remoteFunctionAddress,
144+
0,
145+
0,
146+
0,
147+
)
148+
if threadHandle == 0 {
149+
return fmt.Errorf("PID: %d - CreateRemoteThread failed while calling '%s'- %v", processID, functionName, err)
150+
}
151+
defer syscall.CloseHandle(syscall.Handle(threadHandle))
152+
153+
return nil
154+
}
155+
156+
func injectInProcess(processID uint32, processName string, dllPath string, dllFunction string) error {
157+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Opening process %s with 0x%x access...\n", processID, processName, windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_OPERATION))
158+
processHandle, err := syscall.OpenProcess(windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_OPERATION, false, processID)
159+
if err != nil {
160+
return fmt.Errorf("PID: %d - OpenProcess failed: %v", processID, err)
161+
}
162+
defer syscall.CloseHandle(processHandle)
163+
164+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Process Handle: 0x%x\n", processID, processHandle))
165+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Loading DLL: %s\n", processID, dllPath))
166+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - DLL Path Length: %d\n", processID, len(dllPath)))
167+
168+
dllBaseAddress, err := injectDLL(processID, windows.Handle(processHandle), dllPath)
169+
if err != nil {
170+
return fmt.Errorf("PID: %d - DLL injection failed: %v", processID, err)
171+
}
172+
logMessage(LOGLEVEL_INFO, fmt.Sprintf("PID: %d - DLL injected successfully.\n", processID))
173+
174+
FunctionRVA, err := findSymbolRVA(dllPath, dllFunction)
175+
if err != nil {
176+
return fmt.Errorf("PID: %d - Error finding symbol RVA: %v", processID, err)
177+
}
178+
logMessage(LOGLEVEL_DEBUG, fmt.Sprintf("PID: %d - Function '%s' RVA: 0x%x\n", processID, dllFunction, FunctionRVA))
179+
180+
err = callRemoteFunction(processID, dllBaseAddress, dllFunction, uintptr(FunctionRVA))
181+
if err != nil {
182+
183+
return fmt.Errorf("PID: %d - Error calling remote function %v", processID, err)
184+
}
185+
logMessage(LOGLEVEL_INFO, fmt.Sprintf("PID: %d - Function '%s' successfully called.\n", processID, dllFunction))
186+
187+
return nil
188+
}

0 commit comments

Comments
 (0)