Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support win64 #1

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions cmd/injgo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"fmt"
"os"
"path/filepath"
"strconv"

"go.zoe.im/injgo"
Expand Down Expand Up @@ -44,7 +43,6 @@ func main() {
fmt.Println("injector ", pid)
for _, name := range os.Args[2:] {
// check if file exits
name, _ = filepath.Abs(name)
err = injgo.Inject(pid, name, false)
if err != nil {
fmt.Println("inject ", name, "error:", err)
Expand Down
29 changes: 29 additions & 0 deletions inject.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
// Package injgo is a package for injecting in golang.
package injgo

import (
"syscall"
"unsafe"
)

func ptr(val interface{}) uintptr {
switch val.(type) {
case byte:
return uintptr(val.(byte))
case bool:
isTrue := val.(bool)
if isTrue {
return uintptr(1)
}
return uintptr(0)
case string:
bytePtr, _ := syscall.BytePtrFromString(val.(string))
return uintptr(unsafe.Pointer(bytePtr))
case int:
return uintptr(val.(int))
case uint:
return uintptr(val.(uint))
case uintptr:
return val.(uintptr)
default:
return uintptr(0)
}
}
52 changes: 33 additions & 19 deletions inject_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package injgo

import (
"errors"
"math"
"os"
"path/filepath"
"unsafe"

"go.zoe.im/injgo/pkg/w32"
Expand All @@ -15,65 +18,76 @@ var (

// WARNING: only 386 arch works well.
//
// Inject is the function inject dynamic library to a process
// # Inject is the function inject dynamic library to a process
//
// In windows, name is a file with dll extion.If the file
// name exits, we will return error.
// The workflow of injection in windows is:
// 0. load kernel32.dll in current process.
// 1. open target process T.
// 2. malloc memory in T to store the name of the library.
// 3. get address of function LoadLibraryA from kernel32.dll
// in T.
// 4. call CreateRemoteThread method in kernel32.dll to execute
// LoadLibraryA in T.
// 0. load kernel32.dll in current process.
// 1. open target process T.
// 2. malloc memory in T to store the name of the library.
// 3. get address of function LoadLibraryA from kernel32.dll
// in T.
// 4. call CreateRemoteThread method in kernel32.dll to execute
// LoadLibraryA in T.
func Inject(pid int, dllname string, replace bool) error {

dllname, _ = filepath.Abs(dllname)
_, err := os.Stat(dllname)
if os.IsNotExist(err) {
return err
}
// check is already injected
if !replace && IsInjected(pid, dllname) {
return ErrAlreadyInjected
}

// open process
hdlr, err := w32.OpenProcess(w32.PROCESS_ALL_ACCESS, false, uint32(pid))
hdlr, err := w32.OpenProcess(w32.PROCESS_ALL_ACCESS, true, ptr(pid))
if err != nil {
return err
}
defer w32.CloseHandle(hdlr)

// malloc space to write dll name
dlllen := len(dllname)
dllnameaddr, err := w32.VirtualAllocEx(hdlr, 0, dlllen, w32.MEM_COMMIT, w32.PAGE_EXECUTE_READWRITE)
dlllen := len(dllname) + 1
dllnameaddr, err := w32.VirtualAllocEx(hdlr, 0, ptr(dlllen), ptr(w32.MEM_RESERVE_AND_COMMIT), ptr(w32.PAGE_READWRITE))
if err != nil {
return err
}

// write dll name
err = w32.WriteProcessMemory(hdlr, uint32(dllnameaddr), []byte(dllname), uint(dlllen))
err = w32.WriteProcessMemory(hdlr, dllnameaddr, ptr(dllname), ptr(dlllen))
if err != nil {
return err
}

// test
tecase, _ := w32.ReadProcessMemory(hdlr, uint32(dllnameaddr), uint(dlllen))
if string(tecase) != dllname {
tecase, _ := w32.ReadProcessMemory(hdlr, dllnameaddr, ptr(dlllen))
if string(tecase[:len(tecase)-1]) != dllname {
return errors.New("write dll name error")
}

// get LoadLibraryA address in target process
// TODO: can we get the address at from this process?
lddladdr, err := w32.GetProcAddress(w32.GetModuleHandleA("kernel32.dll"), "LoadLibraryA")
lddladdr, err := w32.LoadLibraryAddress(ptr("LoadLibraryA"))
if err != nil {
return err
}

// call remote process
dllthread, _, err := w32.CreateRemoteThread(hdlr, nil, 0, uint32(lddladdr), dllnameaddr, 0)
dllthread, _, err := w32.CreateRemoteThread(hdlr, nil, ptr(0), ptr(lddladdr), dllnameaddr, ptr(0))
if err != nil {
return err
}
defer w32.CloseHandle(dllthread)
err = w32.WaitForSingleObj(dllthread, math.MaxInt)
if err != nil {
return err
}
_, err = w32.VirtualFreeEx(hdlr, dllnameaddr, ptr(0), w32.MEM_RELEASE)
if err != nil {
return err
}

w32.CloseHandle(dllthread)

return nil
}
Expand Down
18 changes: 14 additions & 4 deletions pkg/w32/constants.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package w32

//go:build windows
// +build windows

package w32

const (
NO_ERROR = 0
ERROR_SUCCESS = 0
Expand Down Expand Up @@ -31,6 +32,8 @@ const (
MEM_RESET = 0x00080000
MEM_RESET_UNDO = 0x1000000

MEM_RESERVE_AND_COMMIT = MEM_COMMIT | MEM_RESERVE

MEM_LARGE_PAGES = 0x20000000
MEM_PHYSICAL = 0x00400000
MEM_TOP_DOWN = 0x00100000
Expand All @@ -52,8 +55,8 @@ const (
PAGE_TARGETS_NO_UPDATE = 0x40000000
)

//Process Access Rights
//https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
// Process Access Rights
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
const (
PROCESS_CREATE_PROCESS = 0x0080 //Required to create a process.
PROCESS_CREATE_THREAD = 0x0002 //Required to create a thread.
Expand All @@ -70,3 +73,10 @@ const (
PROCESS_ALL_ACCESS = 2035711 //This is not recommended.
SYNCHRONIZE = 0x00100000
)

const (
infinite = 0xFFFFFFFF
waitObject0 = 0x00000000
waitAbandoned = 0x00000080
waitFailed = 0xFFFFFFFF
)
93 changes: 57 additions & 36 deletions pkg/w32/kernel32.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package w32

//go:build windows
// +build windows

package w32

import (
"encoding/binary"
"syscall"
Expand Down Expand Up @@ -30,9 +31,11 @@ var (
procProcess32Next = modkernel32.NewProc("Process32NextW")
procModule32First = modkernel32.NewProc("Module32FirstW")
procModule32Next = modkernel32.NewProc("Module32NextW")
procGetProcAddress = modkernel32.NewProc("GetProcAddress")
procWaitForSingleObj = modkernel32.NewProc("WaitForSingleObject")
)

func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (handle HANDLE, err error) {
func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uintptr) (handle HANDLE, err error) {
inherit := 0
if inheritHandle {
inherit = 1
Expand All @@ -41,7 +44,7 @@ func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (ha
ret, _, err := procOpenProcess.Call(
uintptr(desiredAccess),
uintptr(inherit),
uintptr(processId))
processId)
if err != nil && IsErrSuccess(err) {
err = nil
}
Expand Down Expand Up @@ -155,13 +158,13 @@ func CreateProcessA(lpApplicationName *string,
}

// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366890(v=vs.85).aspx
func VirtualAllocEx(hProcess HANDLE, lpAddress int, dwSize int, flAllocationType int, flProtect int) (addr uintptr, err error) {
func VirtualAllocEx(hProcess HANDLE, lpAddress uintptr, dwSize uintptr, flAllocationType uintptr, flProtect uintptr) (addr uintptr, err error) {
ret, _, err := procVirtualAllocEx.Call(
uintptr(hProcess), // The handle to a process.
uintptr(lpAddress), // The pointer that specifies a desired starting address for the region of pages that you want to allocate.
uintptr(dwSize), // The size of the region of memory to allocate, in bytes.
uintptr(flAllocationType),
uintptr(flProtect))
uintptr(hProcess), // The handle to a process.
lpAddress, // The pointer that specifies a desired starting address for the region of pages that you want to allocate.
dwSize, // The size of the region of memory to allocate, in bytes.
flAllocationType,
flProtect)
if int(ret) == 0 {
return ret, err
}
Expand All @@ -182,33 +185,43 @@ func VirtualAlloc(lpAddress int, dwSize int, flAllocationType int, flProtect int
}

// https://github.com/AllenDang/w32/pull/62/commits/08a52ff508c6b2b9b9bf5ee476109b903dcf219d
func VirtualFreeEx(hProcess HANDLE, lpAddress, dwSize uintptr, dwFreeType uint32) bool {
ret, _, _ := procVirtualFreeEx.Call(
func VirtualFreeEx(hProcess HANDLE, lpAddress, dwSize uintptr, dwFreeType uint32) (uintptr, error) {
ret, _, err := procVirtualFreeEx.Call(
uintptr(hProcess),
lpAddress,
dwSize,
uintptr(dwFreeType),
)

return ret != 0
if IsErrSuccess(err) {
return ret, nil
}
return ret, err
}

func GetProcAddress(h uintptr, name string) (uintptr, error) {
return syscall.GetProcAddress(syscall.Handle(h), name)
}

func LoadLibraryAddress(libraryPtr uintptr) (uintptr, error) {
loadLibraryAddress, _, err := procGetProcAddress.Call(modkernel32.Handle(), libraryPtr)
if !IsErrSuccess(err) {
return loadLibraryAddress, err
}
return loadLibraryAddress, nil
}

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682437(v=vs.85).aspx
// Credit: https://github.com/contester/runlib/blob/master/win32/win32_windows.go#L577
func CreateRemoteThread(hprocess HANDLE, sa *syscall.SecurityAttributes,
stackSize uint32, startAddress uint32, parameter uintptr, creationFlags uint32) (HANDLE, uint32, error) {
var threadId uint32
stackSize uintptr, startAddress uintptr, parameter uintptr, creationFlags uintptr) (HANDLE, int, error) {
var threadId int
r1, _, e1 := procCreateRemoteThread.Call(
uintptr(hprocess),
uintptr(unsafe.Pointer(sa)),
uintptr(stackSize),
uintptr(startAddress),
uintptr(parameter),
uintptr(creationFlags),
stackSize,
startAddress,
parameter,
creationFlags,
uintptr(unsafe.Pointer(&threadId)))

if int(r1) == 0 {
Expand All @@ -217,15 +230,24 @@ func CreateRemoteThread(hprocess HANDLE, sa *syscall.SecurityAttributes,
return HANDLE(r1), threadId, nil
}

//Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or the operation fails.
//https://msdn.microsoft.com/en-us/library/windows/desktop/ms681674(v=vs.85).aspx
func WriteProcessMemory(hProcess HANDLE, lpBaseAddress uint32, data []byte, size uint) (err error) {
// https://learn.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
func WaitForSingleObj(hprocess HANDLE, milliseconds int) error {
_, _, err := procWaitForSingleObj.Call(uintptr(hprocess), uintptr(milliseconds))
if IsErrSuccess(err) {
return nil
}
return err
}

// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or the operation fails.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681674(v=vs.85).aspx
func WriteProcessMemory(hProcess HANDLE, lpBaseAddress uintptr, data uintptr, size uintptr) (err error) {
var numBytesRead uintptr

_, _, err = procWriteProcessMemory.Call(uintptr(hProcess),
uintptr(lpBaseAddress),
uintptr(unsafe.Pointer(&data[0])),
uintptr(size),
lpBaseAddress,
data,
size,
uintptr(unsafe.Pointer(&numBytesRead)))
if !IsErrSuccess(err) {
return
Expand All @@ -234,28 +256,27 @@ func WriteProcessMemory(hProcess HANDLE, lpBaseAddress uint32, data []byte, size
return
}

//Write process memory with a source of uint32
func WriteProcessMemoryAsUint32(hProcess HANDLE, lpBaseAddress uint32, data uint32) (err error) {

// Write process memory with a source of uint32
func WriteProcessMemoryAsUint32(hProcess HANDLE, lpBaseAddress uintptr, data uint32) (err error) {
bData := make([]byte, 4)
binary.LittleEndian.PutUint32(bData, data)
err = WriteProcessMemory(hProcess, lpBaseAddress, bData, 4)
err = WriteProcessMemory(hProcess, lpBaseAddress, uintptr(unsafe.Pointer(&bData[0])), 4)
if err != nil {
return
}
return
}

//Reads data from an area of memory in a specified process. The entire area to be read must be accessible or the operation fails.
//https://msdn.microsoft.com/en-us/library/windows/desktop/ms680553(v=vs.85).aspx
func ReadProcessMemory(hProcess HANDLE, lpBaseAddress uint32, size uint) (data []byte, err error) {
// Reads data from an area of memory in a specified process. The entire area to be read must be accessible or the operation fails.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680553(v=vs.85).aspx
func ReadProcessMemory(hProcess HANDLE, lpBaseAddress uintptr, size uintptr) (data []byte, err error) {
var numBytesRead uintptr
data = make([]byte, size)

_, _, err = procReadProcessMemory.Call(uintptr(hProcess),
uintptr(lpBaseAddress),
lpBaseAddress,
uintptr(unsafe.Pointer(&data[0])),
uintptr(size),
size,
uintptr(unsafe.Pointer(&numBytesRead)))
if !IsErrSuccess(err) {
return
Expand All @@ -265,7 +286,7 @@ func ReadProcessMemory(hProcess HANDLE, lpBaseAddress uint32, size uint) (data [
}

// Read process memory and convert the returned data to uint32
func ReadProcessMemoryAsUint32(hProcess HANDLE, lpBaseAddress uint32) (buffer uint32, err error) {
func ReadProcessMemoryAsUint32(hProcess HANDLE, lpBaseAddress uintptr) (buffer uint32, err error) {
data, err := ReadProcessMemory(hProcess, lpBaseAddress, 4)
if err != nil {
return
Expand Down
3 changes: 2 additions & 1 deletion process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package injgo

import (
"errors"
"strings"
"syscall"
"unsafe"

Expand Down Expand Up @@ -40,7 +41,7 @@ func FindProcessByName(name string) (*Process, error) {
}

_exeFile := w32.UTF16PtrToString(&entry.ExeFile[0])
if name == _exeFile {
if strings.ToLower(name) == strings.ToLower(_exeFile) {
process.Name = _exeFile
process.ProcessID = int(entry.ProcessID)
// TODO: 找到路径
Expand Down