Skip to content

Instantly share code, notes, and snippets.

@qwerty12
Created October 27, 2021 23:02
Show Gist options
  • Save qwerty12/de1c6bad9bc9db9530ee2ed2791f297b to your computer and use it in GitHub Desktop.
Save qwerty12/de1c6bad9bc9db9530ee2ed2791f297b to your computer and use it in GitHub Desktop.
// +build windows
/*
Parts derived from
https://github.com/hallazzang/go-windows-programming
https://github.com/GameXG/gowindows
https://stackoverflow.com/a/37911789
*/
package main
import (
"fmt"
"regexp"
"unsafe"
"os"
"golang.org/x/sys/windows"
"github.com/godbus/dbus/v5"
)
const (
WTS_CURRENT_SERVER_HANDLE = 0
WTS_CURRENT_SESSION = 0xFFFFFFFF
WTSTypeProcessInfoLevel0 = 0
MEM_PRIVATE = 0x20000
)
type WTS_PROCESS_INFO struct {
SessionId uint32
ProcessId uint32
pProcessName *uint16
pUserSid uintptr // incomplete: avoid defining a struct for something unneeded
}
var (
libwtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll")
procWTSEnumerateProcessesExW = libwtsapi32.NewProc("WTSEnumerateProcessesExW")
procWTSFreeMemoryExW = libwtsapi32.NewProc("WTSFreeMemoryExW")
cachedDBusConnAddr = ""
)
func WTSFreeMemoryExW(WTSTypeClass uint32, pMemory unsafe.Pointer, NumberOfEntries uint32) {
procWTSFreeMemoryExW.Call(uintptr(WTSTypeClass), uintptr(pMemory), uintptr(NumberOfEntries))
}
func WTSEnumerateProcessesExW(hServer windows.Handle, pLevel *uint32, SessionId uint32, ppProcessInfo unsafe.Pointer, pCount *uint32) (ret bool, lastErr error) {
r1, _, lastErr := procWTSEnumerateProcessesExW.Call(uintptr(hServer), uintptr(unsafe.Pointer(pLevel)), uintptr(SessionId), uintptr(unsafe.Pointer(ppProcessInfo)), uintptr(unsafe.Pointer(pCount)))
return r1 != 0, lastErr
}
func DBusSessionAddressFromProcess(ProcessId uint32) string {
// assumes a 64-bit process reading another 64-bit process' memory
// could be sped up a little by using offset magic to only remotely copy certain struct members instead of the entire thing
const sizeofWchar uintptr = unsafe.Sizeof(uint16(0))
reTarget, _ := regexp.Compile(`^DBUS_SESSION_BUS_ADDRESS=(tcp:host=localhost,port=\d+,family=ipv4,guid=.+)`) // Naïve check
hProcess, lastErr := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_VM_READ, false, ProcessId)
if (hProcess == 0) {
fmt.Println(fmt.Errorf("OpenProcess (%v): %v", ProcessId, lastErr))
return ""
}
defer windows.CloseHandle(hProcess)
pbi := windows.PROCESS_BASIC_INFORMATION{}
lastErr = windows.NtQueryInformationProcess(hProcess, windows.ProcessBasicInformation, unsafe.Pointer(&pbi), uint32(unsafe.Sizeof(pbi)), nil)
if lastErr != nil {
fmt.Println(fmt.Errorf("NtQueryInformationProcess (%v): %v", ProcessId, lastErr))
return ""
}
peb := windows.PEB{}
lastErr = windows.ReadProcessMemory(hProcess, uintptr(unsafe.Pointer(pbi.PebBaseAddress)), (*byte)(unsafe.Pointer(&peb)), uintptr(unsafe.Sizeof(peb)), nil)
if lastErr != nil {
fmt.Println(fmt.Errorf("ReadProcessMemory(PebBaseAddress) (%v): %v", ProcessId, lastErr))
return ""
}
processParameters := windows.RTL_USER_PROCESS_PARAMETERS{}
lastErr = windows.ReadProcessMemory(hProcess, uintptr(unsafe.Pointer(peb.ProcessParameters)), (*byte)(unsafe.Pointer(&processParameters)), uintptr(unsafe.Sizeof(processParameters)), nil)
if lastErr != nil {
fmt.Println(fmt.Errorf("ReadProcessMemory(ProcessParameters) (%v): %v", ProcessId, lastErr))
return ""
}
/*
I considered checking to see ImagePathName started with "C:\Program Files\WindowsApps\KDEe.V.KDEConnect_" (Windows Store install) or
"C:\Program Files\KDE Connect" (Standalone installer), but considered it pointless because:
* a process can modify its PEB
* the KDE Connect standalone installer lets the user install it to an arbitrary directory
imagePathNameBuf := make([]uint16, process_parameters.ImagePathName.Length)
lastErr = windows.ReadProcessMemory(hProcess, uintptr(unsafe.Pointer(process_parameters.ImagePathName.Buffer)), (*byte)(unsafe.Pointer(&imagePathNameBuf[0])), uintptr(process_parameters.ImagePathName.Length), nil)
if lastErr != nil {
fmt.Println(fmt.Errorf("ReadProcessMemory(ImagePathName) (%v): %v", ProcessId, lastErr))
return ""
}
imagePathName := windows.UTF16ToString(imagePathNameBuf)
*/
Environment := processParameters.Environment
mbi := windows.MemoryBasicInformation{}
lastErr = windows.VirtualQueryEx(hProcess, uintptr(Environment), &mbi, unsafe.Sizeof(mbi))
if lastErr != nil {
fmt.Println(fmt.Errorf("VirtualQueryEx(Environment) (%v): %v", ProcessId, lastErr))
return ""
}
if (mbi.State != windows.MEM_COMMIT || mbi.Type != MEM_PRIVATE) {
fmt.Println(fmt.Errorf("Environment (%v): not commited", ProcessId))
return ""
}
if (uintptr(Environment) & (unsafe.Alignof(uint16(0)) - 1) != 0) {
fmt.Println(fmt.Errorf("Environment (%v): not WCHAR aligned", ProcessId))
return ""
}
envSize := mbi.RegionSize - (uintptr(Environment) - uintptr(mbi.BaseAddress))
if (envSize < 2 * sizeofWchar) {
fmt.Println(fmt.Errorf("Environment (%v): too small", ProcessId))
return ""
}
if (envSize > 0x10000) {
envSize = 0x10000 // cap to 64Kb
}
var nBytesRead uintptr
envBuf := make([]uint16, envSize / 2)
lastErr = windows.ReadProcessMemory(hProcess, uintptr(Environment), (*byte)(unsafe.Pointer(&envBuf[0])), envSize, &nBytesRead)
if lastErr != nil {
fmt.Println(fmt.Errorf("ReadProcessMemory(Environment) (%v): %v", ProcessId, lastErr))
return ""
}
if nBytesRead != envSize {
fmt.Println(fmt.Errorf("ReadProcessMemory(Environment) (%v): short read", ProcessId))
return ""
}
envBuf[envSize / sizeofWchar - 2] = 0
envBuf[envSize / sizeofWchar - 1] = 0
dbusSessionBufAddr := ""
// I'm sure there's a Go way to read double-null terminated strings, but I went for the tried and true option
envBufPtr := unsafe.Pointer(&envBuf[0])
for (*(*uint16)(envBufPtr) != 0) {
envvar := windows.UTF16PtrToString((*uint16)(envBufPtr))
if (reTarget.MatchString(envvar)) {
dbusSessionBufAddr = reTarget.FindStringSubmatch(envvar)[1]
break
}
/* essentially wcslen() copied from syscall_windows.go */
var len uintptr = 0
for ptr := envBufPtr; *(*uint16)(ptr) != 0; len++ {
ptr = unsafe.Pointer(uintptr(ptr) + sizeofWchar)
}
envBufPtr = unsafe.Add(envBufPtr, (len + 1) * sizeofWchar)
}
return dbusSessionBufAddr
}
func DBusSessionBusForPlatform() (conn *dbus.Conn, err error) {
if (cachedDBusConnAddr != "") {
conn, err := dbus.Connect(cachedDBusConnAddr)
if conn != nil {
return conn, err
}
cachedDBusConnAddr = ""
}
if this_env_sess_addr := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); this_env_sess_addr != "" {
return dbus.SessionBus()
}
var Level uint32 = WTSTypeProcessInfoLevel0
var pProcessInfo *WTS_PROCESS_INFO
var count uint32
var hostSessionId uint32 = WTS_CURRENT_SESSION
windows.ProcessIdToSessionId(windows.GetCurrentProcessId(), &hostSessionId)
r1, lastErr := WTSEnumerateProcessesExW(WTS_CURRENT_SERVER_HANDLE, &Level, hostSessionId, unsafe.Pointer(&pProcessInfo), &count)
if (!r1) {
fmt.Println(fmt.Errorf("WTSEnumerateProcessesExW failed: %v", lastErr))
return
}
defer WTSFreeMemoryExW(Level, unsafe.Pointer(pProcessInfo), count)
size := unsafe.Sizeof(WTS_PROCESS_INFO{})
for i := uint32(0); i < count; i++ {
p := *(*WTS_PROCESS_INFO)(unsafe.Pointer(uintptr(unsafe.Pointer(pProcessInfo)) + (uintptr(size) * uintptr(i))))
procName := windows.UTF16PtrToString(p.pProcessName)
if (procName == "kdeconnect-indicator.exe" || procName == "kdeconnectd.exe") {
addr := DBusSessionAddressFromProcess(p.ProcessId)
if (addr != "") {
conn, err := dbus.Connect(addr)
if conn != nil && err != nil {
cachedDBusConnAddr = addr
}
return conn, err
}
}
}
return dbus.SessionBus()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment