From 8d35ed7a5e2df5dbd63810cb30f0b424a23edf44 Mon Sep 17 00:00:00 2001 From: Adrian Serrano Date: Thu, 2 Aug 2018 12:33:37 +0200 Subject: [PATCH] Add APIs to fetch processes information - ntdll.NtQueryInformationProcess (internal/unsupported API!) PROCESS_BASIC_INFORMATION struct UNICODE_STRING struct RTL_USER_PROCESS_PARAMETERS struct - kernel32.ReadProcessMemory - psapi.GetProcessImageFileNameA - psapi.EnumProcesses --- .ci/scripts/check_lint.go | 1 + CHANGELOG.md | 6 + constants.go | 32 +++++ doc.go | 2 +- kernel32.go | 18 +++ ntdll.go | 129 ++++++++++++++++++ psapi.go | 38 ++++++ vendor/github.com/elastic/go-windows/ntdll.go | 95 +++++++++++++ zsyscall_windows.go | 66 +++++++-- 9 files changed, 377 insertions(+), 10 deletions(-) create mode 100644 constants.go create mode 100644 ntdll.go create mode 100644 vendor/github.com/elastic/go-windows/ntdll.go diff --git a/.ci/scripts/check_lint.go b/.ci/scripts/check_lint.go index a2eaa42..8ca3620 100644 --- a/.ci/scripts/check_lint.go +++ b/.ci/scripts/check_lint.go @@ -30,6 +30,7 @@ import ( var ignoreWarnings = []string{ `don't use underscores in Go names`, + `don't use ALL_CAPS in Go names`, } var ignoreWarningsRe = regexp.MustCompile(strings.Join(ignoreWarnings, "|")) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8efb5..973f716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ # Changelog * Add GetProcessMemoryInfo: [#2](https://github.com/elastic/go-windows/pull/2) +* Add APIs to fetch process information: + - NtQueryInformationProcess + - ReadProcessMemory + - GetProcessImageFileName + - EnumProcesses + [#6](https://github.com/elastic/go-windows/pull/6) diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..dabc425 --- /dev/null +++ b/constants.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build windows + +package windows + +const ( + // This process access rights are missing from Go's syscall package as of 1.10.3 + + // PROCESS_VM_READ right allows to read memory from the target process. + PROCESS_VM_READ = 0x10 + + // PROCESS_QUERY_LIMITED_INFORMATION right allows to access a subset of the + // information granted by PROCESS_QUERY_INFORMATION. Not available in XP + // and Server 2003. + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 +) diff --git a/doc.go b/doc.go index 2a13ced..03e106f 100644 --- a/doc.go +++ b/doc.go @@ -20,4 +20,4 @@ package windows // Use "GOOS=windows go generate -v -x" to generate the sources. // Add -trace to enable debug prints around syscalls. -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -systemdll=false -output zsyscall_windows.go kernel32.go version.go psapi.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -systemdll=false -output zsyscall_windows.go kernel32.go version.go psapi.go ntdll.go diff --git a/kernel32.go b/kernel32.go index 9dfff23..35e1b3e 100644 --- a/kernel32.go +++ b/kernel32.go @@ -33,6 +33,7 @@ import ( //sys _GetTickCount64() (millis uint64, err error) = kernel32.GetTickCount64 //sys _GetSystemTimes(idleTime *syscall.Filetime, kernelTime *syscall.Filetime, userTime *syscall.Filetime) (err error) = kernel32.GetSystemTimes //sys _GlobalMemoryStatusEx(buffer *MemoryStatusEx) (err error) = kernel32.GlobalMemoryStatusEx +//sys _ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, buffer uintptr, size uintptr, numRead *uintptr) (err error) = kernel32.ReadProcessMemory var ( sizeofMemoryStatusEx = uint32(unsafe.Sizeof(MemoryStatusEx{})) @@ -67,6 +68,9 @@ const ( ProcessorArchitectureUnknown ProcessorArchitecture = 0xFFFF ) +// ErrReadFailed is returned by ReadProcessMemory on failure +var ErrReadFailed = errors.New("ReadProcessMemory failed") + func (a ProcessorArchitecture) String() string { names := map[ProcessorArchitecture]string{ ProcessorArchitectureAMD64: "x86_64", @@ -218,3 +222,17 @@ func GlobalMemoryStatusEx() (MemoryStatusEx, error) { return memoryStatusEx, nil } + +// ReadProcessMemory reads from another process memory. The Handle needs to have +// the PROCESS_VM_READ right. +// A zero-byte read is a no-op, no error is returned. +func ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, dest []byte) (numRead uintptr, err error) { + n := len(dest) + if n == 0 { + return 0, nil + } + if err = _ReadProcessMemory(handle, baseAddress, uintptr(unsafe.Pointer(&dest[0])), uintptr(n), &numRead); err != nil { + return 0, err + } + return numRead, nil +} diff --git a/ntdll.go b/ntdll.go new file mode 100644 index 0000000..ececdc5 --- /dev/null +++ b/ntdll.go @@ -0,0 +1,129 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build windows + +package windows + +import ( + "fmt" + "syscall" + "unsafe" +) + +const ( + // SizeOfProcessBasicInformationStruct gives the size + // of the ProcessBasicInformationStruct struct. + SizeOfProcessBasicInformationStruct = unsafe.Sizeof(ProcessBasicInformationStruct{}) + + // SizeOfRtlUserProcessParameters gives the size + // of the RtlUserProcessParameters struct. + SizeOfRtlUserProcessParameters = unsafe.Sizeof(RtlUserProcessParameters{}) +) + +// NTStatus is an error wrapper for NTSTATUS values, 32bit error-codes returned +// by the NT Kernel. +type NTStatus uint32 + +// ProcessInfoClass is Go's counterpart for the PROCESSINFOCLASS enumeration +// defined in ntdll.h. +type ProcessInfoClass uint32 + +const ( + // ProcessInfoClass enumeration values that can be used as arguments to + // NtQueryInformationProcess + + // ProcessBasicInformation returns a pointer to + // the Process Environment Block (PEB) structure. + ProcessBasicInformation ProcessInfoClass = 0 + + // ProcessDebugPort returns a uint32 that is the port number for the + // debugger of the process. + ProcessDebugPort = 7 + + // ProcessWow64Information returns whether a process is running under + // WOW64. + ProcessWow64Information = 26 + + // ProcessImageFileName returns the image file name for the process, as a + // UnicodeString struct. + ProcessImageFileName = 27 + + // ProcessBreakOnTermination returns a uintptr that tells if the process + // is critical. + ProcessBreakOnTermination = 29 + + // ProcessSubsystemInformation returns the subsystem type of the process. + ProcessSubsystemInformation = 75 +) + +// ProcessBasicInformationStruct is Go's counterpart of the +// PROCESS_BASIC_INFORMATION struct, returned by NtQueryInformationProcess +// when ProcessBasicInformation is requested. +type ProcessBasicInformationStruct struct { + Reserved1 uintptr + PebBaseAddress uintptr + Reserved2 [2]uintptr + UniqueProcessID uintptr + // Undocumented: + InheritedFromUniqueProcessID uintptr +} + +// UnicodeString is Go's equivalent for the _UNICODE_STRING struct. +type UnicodeString struct { + Size uint16 + MaximumLength uint16 + Buffer uintptr +} + +// RtlUserProcessParameters is Go's equivalent for the +// _RTL_USER_PROCESS_PARAMETERS struct. +// A few undocumented fields are exposed. +type RtlUserProcessParameters struct { + Reserved1 [16]byte + Reserved2 [5]uintptr + + // + CurrentDirectoryPath UnicodeString + CurrentDirectoryHandle uintptr + DllPath UnicodeString + // + + ImagePathName UnicodeString + CommandLine UnicodeString +} + +// Syscalls +// Warning: NtQueryInformationProcess is an unsupported API that can change +// in future versions of Windows. Available from XP to Windows 10. +//sys _NtQueryInformationProcess(handle syscall.Handle, infoClass uint32, info uintptr, infoLen uint32, returnLen *uint32) (ntStatus uint32) = ntdll.NtQueryInformationProcess + +// NtQueryInformationProcess is a wrapper for ntdll.NtQueryInformationProcess. +// The handle must have the PROCESS_QUERY_INFORMATION access right. +// Returns an error of type NTStatus. +func NtQueryInformationProcess(handle syscall.Handle, infoClass ProcessInfoClass, info unsafe.Pointer, infoLen uint32) (returnedLen uint32, err error) { + status := _NtQueryInformationProcess(handle, uint32(infoClass), uintptr(info), infoLen, &returnedLen) + if status != 0 { + return returnedLen, NTStatus(status) + } + return returnedLen, nil +} + +// Error prints the wrapped NTSTATUS in hex form. +func (status NTStatus) Error() string { + return fmt.Sprintf("ntstatus=%x", uint32(status)) +} diff --git a/psapi.go b/psapi.go index 19e2383..ef62536 100644 --- a/psapi.go +++ b/psapi.go @@ -28,6 +28,8 @@ import ( // Syscalls //sys _GetProcessMemoryInfo(handle syscall.Handle, psmemCounters *ProcessMemoryCountersEx, cb uint32) (err error) = psapi.GetProcessMemoryInfo +//sys _GetProcessImageFileNameA(handle syscall.Handle, imageFileName *byte, nSize uint32) (len uint32, err error) = psapi.GetProcessImageFileNameA +//sys _EnumProcesses(lpidProcess *uint32, cb uint32, lpcbNeeded *uint32) (err error) = psapi.EnumProcesses var ( sizeofProcessMemoryCountersEx = uint32(unsafe.Sizeof(ProcessMemoryCountersEx{})) @@ -60,3 +62,39 @@ func GetProcessMemoryInfo(process syscall.Handle) (ProcessMemoryCountersEx, erro } return info, nil } + +// GetProcessImageFileName retrieves the process main executable. +// The returned path is a device path, that is: +// "\Device\HardDisk0Volume1\Windows\notepad.exe" +// instead of +// "C:\Windows\notepad.exe" +// Use QueryDosDevice or equivalent to convert to a drive path. +// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/nf-psapi-getprocessimagefilenamea +func GetProcessImageFileName(handle syscall.Handle) (string, error) { + for bufLen, limit := syscall.MAX_PATH, syscall.MAX_PATH*4; bufLen <= limit; bufLen *= 2 { + buf := make([]byte, bufLen) + nameLen, err := _GetProcessImageFileNameA(handle, &buf[0], uint32(len(buf))) + if err == nil { + buf = buf[:nameLen] + return string(buf), nil + } + if err != syscall.ERROR_INSUFFICIENT_BUFFER { + return "", err + } + } + return "", syscall.ERROR_INSUFFICIENT_BUFFER +} + +// EnumProcesses returns a list of running processes. +// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/nf-psapi-enumprocesses +func EnumProcesses() (pids []uint32, err error) { + for nAlloc, nGot := uint32(128), uint32(0); ; nAlloc *= 2 { + pids = make([]uint32, nAlloc) + if err = _EnumProcesses(&pids[0], nAlloc*4, &nGot); err != nil { + return nil, err + } + if nGot/4 < nAlloc { + return pids, nil + } + } +} diff --git a/vendor/github.com/elastic/go-windows/ntdll.go b/vendor/github.com/elastic/go-windows/ntdll.go new file mode 100644 index 0000000..fc6e22a --- /dev/null +++ b/vendor/github.com/elastic/go-windows/ntdll.go @@ -0,0 +1,95 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build windows + +package windows + +import ( + "fmt" + "syscall" + "unsafe" +) + +const ( + // PROCESS_BASIC_INFORMATION ProcessInformationClass parameter for + // NtQueryInformationProcess. + ProcessBasicInformation = 0 + + SizeOfProcessBasicInformationStruct = unsafe.Sizeof(ProcessBasicInformationStruct{}) + SizeOfRtlUserProcessParameters = unsafe.Sizeof(RtlUserProcessParameters{}) +) + +// NTStatus is an error wrapper for NTSTATUS values, 32bit error-codes returned +// by the NT Kernel. +type NTStatus uint32 + +// ProcessBasicInformationStruct is Go's counterpart of the +// PROCESS_BASIC_INFORMATION struct, returned by NtQueryInformationProcess +// when ProcessBasicInformation is requested. +type ProcessBasicInformationStruct struct { + Reserved1 uintptr + PebBaseAddress uintptr + Reserved2 [2]uintptr + UniqueProcessId uintptr + Reserved3 uintptr +} + +// UnicodeString is Go's equivalent for the _UNICODE_STRING struct. +type UnicodeString struct { + Size uint16 + MaximumLength uint16 + Buffer uintptr +} + +// RtlUserProcessParameters is Go's equivalent for the +// _RTL_USER_PROCESS_PARAMETERS struct. +// A few undocumented fields are exposed. +type RtlUserProcessParameters struct { + Reserved1 [16]byte + Reserved2 [5]uintptr + + // + CurrentDirectoryPath UnicodeString + CurrentDirectoryHandle uintptr + DllPath UnicodeString + // + + ImagePathName UnicodeString + CommandLine UnicodeString +} + +// Syscalls +// Warning: NtQueryInformationProcess is an unsupported API that can change +// in future versions of Windows. Available from XP to Windows 10. +//sys _NtQueryInformationProcess(handle syscall.Handle, infoClass uint32, info uintptr, infoLen uint32, returnLen *uint32) (ntStatus uint32) = ntdll.NtQueryInformationProcess + +// NtQueryInformationProcess is a wrapper for ntdll.NtQueryInformationProcess. +// The handle must have the PROCESS_QUERY_INFORMATION access right. +// Returns an error that can be cast to a NTStatus type. +func NtQueryInformationProcess(handle syscall.Handle, infoClass uint32, info unsafe.Pointer, infoLen uint32, returnLen *uint32) error { + status := _NtQueryInformationProcess(handle, infoClass, uintptr(info), infoLen, returnLen) + if status != 0 { + return NTStatus(status) + } + return nil +} + +// Error prints the wrapped NTSTATUS in hex form. +func (status NTStatus) Error() string { + return fmt.Sprintf("ntstatus=%x", uint32(status)) +} diff --git a/zsyscall_windows.go b/zsyscall_windows.go index a0c5676..c861d3a 100644 --- a/zsyscall_windows.go +++ b/zsyscall_windows.go @@ -38,15 +38,20 @@ var ( modkernel32 = syscall.NewLazyDLL("kernel32.dll") modversion = syscall.NewLazyDLL("version.dll") modpsapi = syscall.NewLazyDLL("psapi.dll") - - procGetNativeSystemInfo = modkernel32.NewProc("GetNativeSystemInfo") - procGetTickCount64 = modkernel32.NewProc("GetTickCount64") - procGetSystemTimes = modkernel32.NewProc("GetSystemTimes") - procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx") - procGetFileVersionInfoW = modversion.NewProc("GetFileVersionInfoW") - procGetFileVersionInfoSizeW = modversion.NewProc("GetFileVersionInfoSizeW") - procVerQueryValueW = modversion.NewProc("VerQueryValueW") - procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + modntdll = syscall.NewLazyDLL("ntdll.dll") + + procGetNativeSystemInfo = modkernel32.NewProc("GetNativeSystemInfo") + procGetTickCount64 = modkernel32.NewProc("GetTickCount64") + procGetSystemTimes = modkernel32.NewProc("GetSystemTimes") + procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx") + procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") + procGetFileVersionInfoW = modversion.NewProc("GetFileVersionInfoW") + procGetFileVersionInfoSizeW = modversion.NewProc("GetFileVersionInfoSizeW") + procVerQueryValueW = modversion.NewProc("VerQueryValueW") + procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + procGetProcessImageFileNameA = modpsapi.NewProc("GetProcessImageFileNameA") + procEnumProcesses = modpsapi.NewProc("EnumProcesses") + procNtQueryInformationProcess = modntdll.NewProc("NtQueryInformationProcess") ) func _GetNativeSystemInfo(systemInfo *SystemInfo) { @@ -91,6 +96,18 @@ func _GlobalMemoryStatusEx(buffer *MemoryStatusEx) (err error) { return } +func _ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, buffer uintptr, size uintptr, numRead *uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procReadProcessMemory.Addr(), 5, uintptr(handle), uintptr(baseAddress), uintptr(buffer), uintptr(size), uintptr(unsafe.Pointer(numRead)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + func _GetFileVersionInfo(filename string, reserved uint32, dataLen uint32, data *byte) (success bool, err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(filename) @@ -168,3 +185,34 @@ func _GetProcessMemoryInfo(handle syscall.Handle, psmemCounters *ProcessMemoryCo } return } + +func _GetProcessImageFileNameA(handle syscall.Handle, imageFileName *byte, nSize uint32) (len uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetProcessImageFileNameA.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(imageFileName)), uintptr(nSize)) + len = uint32(r0) + if len == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _EnumProcesses(lpidProcess *uint32, cb uint32, lpcbNeeded *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(lpidProcess)), uintptr(cb), uintptr(unsafe.Pointer(lpcbNeeded))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _NtQueryInformationProcess(handle syscall.Handle, infoClass uint32, info uintptr, infoLen uint32, returnLen *uint32) (ntStatus uint32) { + r0, _, _ := syscall.Syscall6(procNtQueryInformationProcess.Addr(), 5, uintptr(handle), uintptr(infoClass), uintptr(info), uintptr(infoLen), uintptr(unsafe.Pointer(returnLen)), 0) + ntStatus = uint32(r0) + return +}