Created
October 27, 2021 23:02
-
-
Save qwerty12/de1c6bad9bc9db9530ee2ed2791f297b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// +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