Skip to content

Commit

Permalink
Significantly reworked Windows architecture handling in Process
Browse files Browse the repository at this point in the history
  - Fixed several issues around struct and pointer sizing for 64-bit queries
  - Process handling now functions as expected on ARM64 Windows hosts
  • Loading branch information
s-m-martin committed Mar 21, 2023
1 parent c036597 commit 67b6689
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 73 deletions.
70 changes: 36 additions & 34 deletions process/process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -89,7 +90,7 @@ type ioCounters struct {

type processBasicInformation32 struct {
Reserved1 uint32
PebBaseAddress uint32
PebBaseAddress uintptr
Reserved2 uint32
Reserved3 uint32
UniqueProcessId uint32
Expand All @@ -98,7 +99,7 @@ type processBasicInformation32 struct {

type processBasicInformation64 struct {
Reserved1 uint64
PebBaseAddress uint64
PebBaseAddress uintptr
Reserved2 uint64
Reserved3 uint64
UniqueProcessId uint64
Expand All @@ -111,7 +112,7 @@ type processEnvironmentBlock32 struct {
Reserved2 uint8
Reserved3 [2]uint32
Ldr uint32
ProcessParameters uint32
ProcessParameters uintptr
// More fields which we don't use so far
}

Expand All @@ -122,7 +123,7 @@ type processEnvironmentBlock64 struct {
_ [4]uint8 // padding, since we are 64 bit, the next pointer is 64 bit aligned (when compiling for 32 bit, this is not the case without manual padding)
Reserved3 [2]uint64
Ldr uint64
ProcessParameters uint64
ProcessParameters uintptr
// More fields which we don't use so far
}

Expand All @@ -135,18 +136,18 @@ type rtlUserProcessParameters32 struct {
StdErrorHandle uint32
CurrentDirectoryPathNameLength uint16
_ uint16 // Max Length
CurrentDirectoryPathAddress uint32
CurrentDirectoryPathAddress uintptr
CurrentDirectoryHandle uint32
DllPathNameLength uint16
_ uint16 // Max Length
DllPathAddress uint32
DllPathAddress uintptr
ImagePathNameLength uint16
_ uint16 // Max Length
ImagePathAddress uint32
ImagePathAddress uintptr
CommandLineLength uint16
_ uint16 // Max Length
CommandLineAddress uint32
EnvironmentAddress uint32
CommandLineAddress uintptr
EnvironmentAddress uintptr
// More fields which we don't use so far
}

Expand All @@ -160,21 +161,21 @@ type rtlUserProcessParameters64 struct {
CurrentDirectoryPathNameLength uint16
_ uint16 // Max Length
_ uint32 // Padding
CurrentDirectoryPathAddress uint64
CurrentDirectoryPathAddress uintptr
CurrentDirectoryHandle uint64
DllPathNameLength uint16
_ uint16 // Max Length
_ uint32 // Padding
DllPathAddress uint64
DllPathAddress uintptr
ImagePathNameLength uint16
_ uint16 // Max Length
_ uint32 // Padding
ImagePathAddress uint64
ImagePathAddress uintptr
CommandLineLength uint16
_ uint16 // Max Length
_ uint32 // Padding
CommandLineAddress uint64
EnvironmentAddress uint64
CommandLineAddress uintptr
EnvironmentAddress uintptr
// More fields which we don't use so far
}

Expand Down Expand Up @@ -403,13 +404,13 @@ func (p *Process) CwdWithContext(_ context.Context) (string, error) {

procIs32Bits := is32BitProcess(h)

if procIs32Bits {
if procIs32Bits && !strings.Contains(runtime.GOARCH, "64") {
userProcParams, err := getUserProcessParams32(h)
if err != nil {
return "", err
}
if userProcParams.CurrentDirectoryPathNameLength > 0 {
cwd := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CurrentDirectoryPathAddress), uint(userProcParams.CurrentDirectoryPathNameLength))
cwd := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams.CurrentDirectoryPathAddress, uint(userProcParams.CurrentDirectoryPathNameLength))
if len(cwd) != int(userProcParams.CurrentDirectoryPathNameLength) {
return "", errors.New("cannot read current working directory")
}
Expand Down Expand Up @@ -941,39 +942,39 @@ func getProcessCPUTimes(pid int32) (SYSTEM_TIMES, error) {
}

func getUserProcessParams32(handle windows.Handle) (rtlUserProcessParameters32, error) {
pebAddress, err := queryPebAddress(syscall.Handle(handle), true)
pebAddress, err, queryFrom64Bit := queryPebAddress(syscall.Handle(handle), true)
if err != nil {
return rtlUserProcessParameters32{}, fmt.Errorf("cannot locate process PEB: %w", err)
return rtlUserProcessParameters32{}, fmt.Errorf("cannot locate process PEB for 64-bit-initiator: %t, 32-bit-proc: %t : %w", queryFrom64Bit, true, err)
}

buf := readProcessMemory(syscall.Handle(handle), true, pebAddress, uint(unsafe.Sizeof(processEnvironmentBlock32{})))
if len(buf) != int(unsafe.Sizeof(processEnvironmentBlock32{})) {
return rtlUserProcessParameters32{}, fmt.Errorf("cannot read process PEB")
return rtlUserProcessParameters32{}, fmt.Errorf("cannot read process PEB for 64-bit-initiator: %t, 32-bit-proc: %t", queryFrom64Bit, true)
}
peb := (*processEnvironmentBlock32)(unsafe.Pointer(&buf[0]))
userProcessAddress := uint64(peb.ProcessParameters)
userProcessAddress := peb.ProcessParameters
buf = readProcessMemory(syscall.Handle(handle), true, userProcessAddress, uint(unsafe.Sizeof(rtlUserProcessParameters32{})))
if len(buf) != int(unsafe.Sizeof(rtlUserProcessParameters32{})) {
return rtlUserProcessParameters32{}, fmt.Errorf("cannot read user process parameters")
return rtlUserProcessParameters32{}, fmt.Errorf("cannot read user process parameters for 64-bit-initiator: %t, 32-bit-proc: %t", queryFrom64Bit, true)
}
return *(*rtlUserProcessParameters32)(unsafe.Pointer(&buf[0])), nil
}

func getUserProcessParams64(handle windows.Handle) (rtlUserProcessParameters64, error) {
pebAddress, err := queryPebAddress(syscall.Handle(handle), false)
pebAddress, err, queryFrom64Bit := queryPebAddress(syscall.Handle(handle), false)
if err != nil {
return rtlUserProcessParameters64{}, fmt.Errorf("cannot locate process PEB: %w", err)
return rtlUserProcessParameters64{}, fmt.Errorf("cannot locate process PEB for 64-bit-initiator: %t, 32-bit-proc: %t : %w", queryFrom64Bit, false, err)
}

buf := readProcessMemory(syscall.Handle(handle), false, pebAddress, uint(unsafe.Sizeof(processEnvironmentBlock64{})))
if len(buf) != int(unsafe.Sizeof(processEnvironmentBlock64{})) {
return rtlUserProcessParameters64{}, fmt.Errorf("cannot read process PEB")
return rtlUserProcessParameters64{}, fmt.Errorf("cannot read process PEB for 64-bit-initiator: %t, 32-bit-proc: %t", queryFrom64Bit, false)
}
peb := (*processEnvironmentBlock64)(unsafe.Pointer(&buf[0]))
userProcessAddress := peb.ProcessParameters
buf = readProcessMemory(syscall.Handle(handle), false, userProcessAddress, uint(unsafe.Sizeof(rtlUserProcessParameters64{})))
if len(buf) != int(unsafe.Sizeof(rtlUserProcessParameters64{})) {
return rtlUserProcessParameters64{}, fmt.Errorf("cannot read user process parameters")
return rtlUserProcessParameters64{}, fmt.Errorf("cannot read user process parameters for 64-bit-initiator: %t, 32-bit-proc: %t", queryFrom64Bit, false)
}
return *(*rtlUserProcessParameters64)(unsafe.Pointer(&buf[0])), nil
}
Expand Down Expand Up @@ -1035,21 +1036,22 @@ func getProcessEnvironmentVariables(pid int32, ctx context.Context) ([]string, e

procIs32Bits := is32BitProcess(h)

var processParameterBlockAddress uint64
var processParameterBlockAddress uintptr

if procIs32Bits {
if procIs32Bits && !strings.Contains(runtime.GOARCH, "64") {
peb, err := getUserProcessParams32(h)
if err != nil {
return nil, err
}
processParameterBlockAddress = uint64(peb.EnvironmentAddress)
processParameterBlockAddress = peb.EnvironmentAddress
} else {
peb, err := getUserProcessParams64(h)
if err != nil {
return nil, err
}
processParameterBlockAddress = peb.EnvironmentAddress
}

envvarScanner := bufio.NewScanner(&processReader{
processHandle: h,
is32BitProcess: procIs32Bits,
Expand Down Expand Up @@ -1094,7 +1096,7 @@ func getProcessEnvironmentVariables(pid int32, ctx context.Context) ([]string, e
type processReader struct {
processHandle windows.Handle
is32BitProcess bool
offset uint64
offset uintptr
}

func (p *processReader) Read(buf []byte) (int, error) {
Expand All @@ -1103,7 +1105,7 @@ func (p *processReader) Read(buf []byte) (int, error) {
return 0, io.EOF
}
copy(buf, processMemory)
p.offset += uint64(len(processMemory))
p.offset += uintptr(len(processMemory))
return len(processMemory), nil
}

Expand All @@ -1119,15 +1121,15 @@ func getProcessCommandLine(pid int32) (string, error) {

procIs32Bits := is32BitProcess(h)

if procIs32Bits {
if procIs32Bits && !strings.Contains(runtime.GOARCH, "64") {
userProcParams, err := getUserProcessParams32(h)
if err != nil {
return "", err
}
if userProcParams.CommandLineLength > 0 {
cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CommandLineAddress), uint(userProcParams.CommandLineLength))
cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams.CommandLineAddress, uint(userProcParams.CommandLineLength))
if len(cmdLine) != int(userProcParams.CommandLineLength) {
return "", errors.New("cannot read cmdline")
return "", fmt.Errorf("cannot read cmdline for 32-bit-proc: %t, len: %d and %d", procIs32Bits, len(cmdLine), int(userProcParams.CommandLineLength))
}

return convertUTF16ToString(cmdLine), nil
Expand All @@ -1140,7 +1142,7 @@ func getProcessCommandLine(pid int32) (string, error) {
if userProcParams.CommandLineLength > 0 {
cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams.CommandLineAddress, uint(userProcParams.CommandLineLength))
if len(cmdLine) != int(userProcParams.CommandLineLength) {
return "", errors.New("cannot read cmdline")
return "", fmt.Errorf("cannot read cmdline for 32-bit-proc: %t, len: %d and %d", procIs32Bits, len(cmdLine), int(userProcParams.CommandLineLength))
}

return convertUTF16ToString(cmdLine), nil
Expand Down
15 changes: 8 additions & 7 deletions process/process_windows_32bit.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ type PROCESS_MEMORY_COUNTERS struct {
PeakPagefileUsage uint32
}

func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) (uint64, error) {
func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) (uintptr, error, bool) {
var queryFrom64Bit bool
if is32BitProcess {
// we are on a 32-bit process reading an external 32-bit process
var info processBasicInformation32
Expand All @@ -38,9 +39,9 @@ func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) (uint64, er
uintptr(0),
)
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
return uint64(info.PebBaseAddress), nil
return info.PebBaseAddress, nil, queryFrom64Bit
} else {
return 0, windows.NTStatus(ret)
return 0, windows.NTStatus(ret), queryFrom64Bit
}
} else {
// we are on a 32-bit process reading an external 64-bit process
Expand All @@ -55,17 +56,17 @@ func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) (uint64, er
uintptr(0),
)
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
return info.PebBaseAddress, nil
return info.PebBaseAddress, nil, queryFrom64Bit
} else {
return 0, windows.NTStatus(ret)
return 0, windows.NTStatus(ret), queryFrom64Bit
}
} else {
return 0, errors.New("can't find API to query 64 bit process from 32 bit")
return 0, errors.New("can't find API to query 64 bit process from 32 bit"), queryFrom64Bit
}
}
}

func readProcessMemory(h syscall.Handle, is32BitProcess bool, address uint64, size uint) []byte {
func readProcessMemory(h syscall.Handle, is32BitProcess bool, address uintptr, size uint) []byte {
if is32BitProcess {
var read uint

Expand Down
48 changes: 16 additions & 32 deletions process/process_windows_64bit.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,43 +24,27 @@ type PROCESS_MEMORY_COUNTERS struct {
PeakPagefileUsage uint64
}

func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) (uint64, error) {
if is32BitProcess {
// we are on a 64-bit process reading an external 32-bit process
var wow64 uint
func queryPebAddress(procHandle syscall.Handle, _ bool) (uintptr, error, bool) {
var queryFrom64Bit bool = true

ret, _, _ := common.ProcNtQueryInformationProcess.Call(
uintptr(procHandle),
uintptr(common.ProcessWow64Information),
uintptr(unsafe.Pointer(&wow64)),
uintptr(unsafe.Sizeof(wow64)),
uintptr(0),
)
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
return uint64(wow64), nil
} else {
return 0, windows.NTStatus(ret)
}
} else {
// we are on a 64-bit process reading an external 64-bit process
var info processBasicInformation64
// we are in a 64-bit process
var info processBasicInformation64

ret, _, _ := common.ProcNtQueryInformationProcess.Call(
uintptr(procHandle),
uintptr(common.ProcessBasicInformation),
uintptr(unsafe.Pointer(&info)),
uintptr(unsafe.Sizeof(info)),
uintptr(0),
)
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
return info.PebBaseAddress, nil
} else {
return 0, windows.NTStatus(ret)
}
ret, _, _ := common.ProcNtQueryInformationProcess.Call(
uintptr(procHandle),
uintptr(common.ProcessBasicInformation),
uintptr(unsafe.Pointer(&info)),
uintptr(unsafe.Sizeof(info)),
uintptr(0),
)
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
return info.PebBaseAddress, nil, queryFrom64Bit
} else {
return 0, windows.NTStatus(ret), queryFrom64Bit
}
}

func readProcessMemory(procHandle syscall.Handle, _ bool, address uint64, size uint) []byte {
func readProcessMemory(procHandle syscall.Handle, _ bool, address uintptr, size uint) []byte {
var read uint

buffer := make([]byte, size)
Expand Down

0 comments on commit 67b6689

Please sign in to comment.