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  }