github.com/iDigitalFlame/xmt@v0.5.4/cmd/filter/filter_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 filter
    21  
    22  import (
    23  	"strings"
    24  
    25  	"github.com/iDigitalFlame/xmt/device/winapi"
    26  	"github.com/iDigitalFlame/xmt/util"
    27  	"github.com/iDigitalFlame/xmt/util/bugtrack"
    28  	"github.com/iDigitalFlame/xmt/util/xerr"
    29  )
    30  
    31  func isTokenElevated(h uintptr) bool {
    32  	if !winapi.IsTokenElevated(h) {
    33  		return false
    34  	}
    35  	switch u, err := winapi.GetTokenUser(h); {
    36  	case err != nil:
    37  		fallthrough
    38  	case u.User.Sid.IsWellKnown(0x17): // 0x17 - WinLocalServiceSid
    39  		fallthrough
    40  	case u.User.Sid.IsWellKnown(0x18): // 0x18 - WinNetworkServiceSid
    41  		return false
    42  	}
    43  	return true
    44  }
    45  
    46  // Select will attempt to find a process with the specified Filter options.
    47  // If a suitable process is found, the Process ID will be returned.
    48  //
    49  // An 'ErrNoProcessFound' error will be returned if no processes that match the
    50  // Filter are found.
    51  //
    52  // This function returns 'ErrNoWindows' on non-Windows devices if a PID is not set.
    53  func (f Filter) Select() (uint32, error) {
    54  	return f.SelectFunc(nil)
    55  }
    56  func inStrList(s string, l []string) bool {
    57  	for i := range l {
    58  		if strings.EqualFold(s, l[i]) {
    59  			return true
    60  		}
    61  	}
    62  	return false
    63  }
    64  
    65  // Token will attempt to find a process with the specified Filter options.
    66  // If a suitable process is found, the Process Token Handle will be returned.
    67  // The first argument is the access rights requested, expressed as an uint32.
    68  //
    69  // An 'ErrNoProcessFound' error will be returned if no processes that match the
    70  // Filter are found.
    71  //
    72  // This function returns 'ErrNoWindows' on non-Windows devices.
    73  func (f Filter) Token(a uint32) (uintptr, error) {
    74  	return f.TokenFunc(a, nil)
    75  }
    76  
    77  // Thread will attempt to find a process with the specified Filter options.
    78  // If a suitable process is found, a handle to the first Thread in the Process
    79  // will be returned. The first argument is the access rights requested, expressed
    80  // as an uint32.
    81  //
    82  // An 'ErrNoProcessFound' error will be returned if no processes that match the
    83  // Filter are found.
    84  //
    85  // This function returns 'ErrNoWindows' on non-Windows devices.
    86  func (f Filter) Thread(a uint32) (uintptr, error) {
    87  	return f.ThreadFunc(a, nil)
    88  }
    89  
    90  // Handle will attempt to find a process with the specified Filter options.
    91  // If a suitable process is found, the Process Handle will be returned.
    92  // The first argument is the access rights requested, expressed as an uint32.
    93  //
    94  // An'ErrNoProcessFound' error will be returned if no processes that match the
    95  // Filter are found.
    96  //
    97  // This function returns 'ErrNoWindows' on non-Windows devices.
    98  func (f Filter) Handle(a uint32) (uintptr, error) {
    99  	return f.HandleFunc(a, nil)
   100  }
   101  
   102  // SelectFunc will attempt to find a process with the specified Filter options.
   103  // If a suitable process is found, the Process ID will be returned.
   104  //
   105  // This function allows for a filtering function to be passed along that will be
   106  // supplied with the ProcessID, if the process is elevated, the process name
   107  // and process handle. The function supplied should return true if the process
   108  // passes the filter. The function argument may be nil.
   109  //
   110  // An 'ErrNoProcessFound' error will be returned if no processes that match the
   111  // Filter are found.
   112  //
   113  // This function returns 'ErrNoWindows' on non-Windows devices if a PID is not set.
   114  func (f Filter) SelectFunc(x filter) (uint32, error) {
   115  	// NOTE(dij): Process ID values in Windows technically begin at 5, [0-4] are
   116  	//            System, we can't really use them or get handles to them.
   117  	if f.PID > 4 && x == nil {
   118  		// NOTE(dij): No need to check info (we have no extra filter)
   119  		return f.PID, nil
   120  	}
   121  	// 0x400 - PROCESS_QUERY_LIMITED_INFORMATION
   122  	h, err := f.open(0x1000, false, x)
   123  	if err != nil {
   124  		return 0, err
   125  	}
   126  	return h.PID, nil
   127  }
   128  
   129  // TokenFunc will attempt to find a process with the specified Filter options.
   130  // If a suitable process is found, the Process Token Handle will be returned.
   131  // The first argument is the access rights requested, expressed as an uint32.
   132  //
   133  // This function allows for a filtering function to be passed along that will be
   134  // supplied with the ProcessID, if the process is elevated, the process name
   135  // and process handle. The function supplied should return true if the process
   136  // passes the filter. The function argument may be nil.
   137  //
   138  // An 'ErrNoProcessFound' error will be returned if no processes that match the
   139  // Filter are found.
   140  //
   141  // This function returns 'ErrNoWindows' on non-Windows devices.
   142  func (f Filter) TokenFunc(a uint32, x filter) (uintptr, error) {
   143  	// 0x400 - PROCESS_QUERY_INFORMATION
   144  	h, err := f.HandleFunc(0x400, x)
   145  	if err != nil {
   146  		return 0, err
   147  	}
   148  	var t uintptr
   149  	err = winapi.OpenProcessToken(h, a, &t)
   150  	winapi.CloseHandle(h)
   151  	return t, err
   152  }
   153  
   154  // HandleFunc will attempt to find a process with the specified Filter options.
   155  // If a suitable process is found, the Process Handle will be returned.
   156  // The first argument is the access rights requested, expressed as an uint32.
   157  //
   158  // This function allows for a filtering function to be passed along that will be
   159  // supplied with the ProcessID, if the process is elevated, the process name
   160  // and process handle. The function supplied should return true if the process
   161  // passes the filter. The function argument may be nil.
   162  //
   163  // An 'ErrNoProcessFound' error will be returned if no processes that match the
   164  // Filter are found.
   165  //
   166  // This function returns 'ErrNoWindows' on non-Windows devices.
   167  func (f Filter) HandleFunc(a uint32, x filter) (uintptr, error) {
   168  	// If we have a specific PID in mind that's valid.
   169  	if f.PID > 4 {
   170  		if err := winapi.GetDebugPrivilege(); err != nil {
   171  			if bugtrack.Enabled {
   172  				bugtrack.Track("filter.(Filter).open(): GetDebugPrivilege failed with err=%s", err.Error())
   173  			}
   174  		}
   175  		h, err := winapi.OpenProcess(a, false, f.PID)
   176  		if h == 0 || err != nil {
   177  			return 0, xerr.Wrap("OpenProcess", err)
   178  		}
   179  		if x == nil {
   180  			// NOTE(dij): Quick path since we don't have a filter func.
   181  			return h, nil
   182  		}
   183  		var n string
   184  		if n, err = winapi.GetProcessFileName(h); err != nil {
   185  			winapi.CloseHandle(h)
   186  			return 0, xerr.Wrap("GetProcessFileName", err)
   187  		}
   188  		var t uintptr
   189  		// (old 0x8 - TOKEN_QUERY)
   190  		// 0x20008 - TOKEN_READ | TOKEN_QUERY
   191  		if err = winapi.OpenProcessToken(h, 0x20008, &t); err != nil {
   192  			winapi.CloseHandle(h)
   193  			return 0, xerr.Wrap("OpenProcessToken", err)
   194  		}
   195  		r := x(f.PID, isTokenElevated(t), n, h)
   196  		if winapi.CloseHandle(t); r {
   197  			return h, nil
   198  		}
   199  		winapi.CloseHandle(h)
   200  		return 0, ErrNoProcessFound
   201  	}
   202  	h, err := f.open(a, false, x)
   203  	if err != nil {
   204  		return 0, err
   205  	}
   206  	return h.Handle(a)
   207  }
   208  
   209  // ThreadFunc will attempt to find a process with the specified Filter options.
   210  // If a suitable process is found, a handle to the first Thread in the Process
   211  // will be returned. The first argument is the access rights requested, expressed
   212  // as an uint32.
   213  //
   214  // This function allows for a filtering function to be passed along that will be
   215  // supplied with the ProcessID, if the process is elevated, the process name
   216  // and process handle. The function supplied should return true if the process
   217  // passes the filter. The function argument may be nil.
   218  //
   219  // An'ErrNoProcessFound' error will be returned if no processes that match the
   220  // Filter are found.
   221  //
   222  // This function returns 'ErrNoWindows' on non-Windows devices.
   223  func (f Filter) ThreadFunc(a uint32, x filter) (uintptr, error) {
   224  	i, err := f.SelectFunc(x)
   225  	if err != nil {
   226  		return 0, err
   227  	}
   228  	if err = winapi.GetDebugPrivilege(); err != nil {
   229  		if bugtrack.Enabled {
   230  			bugtrack.Track("filter.(Filter).open(): GetDebugPrivilege failed with err=%s", err.Error())
   231  		}
   232  	}
   233  	var v uintptr
   234  	err = winapi.EnumThreads(i, func(e winapi.ThreadEntry) error {
   235  		if v, err = e.Handle(a); err == nil {
   236  			return winapi.ErrNoMoreFiles
   237  		}
   238  		return nil
   239  	})
   240  	if err != nil {
   241  		return 0, err
   242  	}
   243  	if v == 0 {
   244  		return 0, ErrNoProcessFound
   245  	}
   246  	return v, nil
   247  }
   248  func (f Filter) open(a uint32, r bool, x filter) (winapi.ProcessEntry, error) {
   249  	var (
   250  		z = make([]winapi.ProcessEntry, 0, 64)
   251  		p = winapi.GetCurrentProcessID()
   252  		s = f.Session > Empty
   253  		v = f.Elevated > Empty || x != nil
   254  	)
   255  	if err := winapi.GetDebugPrivilege(); err != nil {
   256  		if bugtrack.Enabled {
   257  			bugtrack.Track("filter.(Filter).open(): GetDebugPrivilege failed with err=%s", err.Error())
   258  		}
   259  	}
   260  	err := winapi.EnumProcesses(func(e winapi.ProcessEntry) error {
   261  		if e.PID == p || e.PID < 5 || len(e.Name) == 0 || (f.PID > 0 && f.PID != e.PID) {
   262  			return nil
   263  		}
   264  		if (len(f.Exclude) > 0 && inStrList(e.Name, f.Exclude)) || (len(f.Include) > 0 && !inStrList(e.Name, f.Include)) {
   265  			return nil
   266  		}
   267  		if (x == nil && !s && !v) || r {
   268  			h, err := e.Handle(a)
   269  			if err != nil {
   270  				return nil
   271  			}
   272  			if winapi.CloseHandle(h); bugtrack.Enabled {
   273  				bugtrack.Track("filter.(Filter).open(): Added process e.Name=%s, e.PID=%d for eval.", e.Name, e.PID)
   274  			}
   275  			z = append(z, e)
   276  			// NOTE(dij): Left this commented to be un-commented if you want a
   277  			//            fast-path to select. However, this prevents selecting
   278  			//            a random process and grabs the first one instead, but
   279  			//            also produces fewer handles opened. YMMV
   280  			//
   281  			//	if len(f.Include) == 1 {
   282  			//	    return false
   283  			//	}
   284  			//
   285  			return nil
   286  		}
   287  		h, k, i, err := e.InfoEx(a, v, s, x != nil)
   288  		if err != nil {
   289  			return nil
   290  		}
   291  		if v && ((k && f.Elevated == False) || (!k && f.Elevated == True)) {
   292  			return nil
   293  		}
   294  		if s && ((i > 0 && f.Session == False) || (i == 0 && f.Session == True)) {
   295  			return nil
   296  		}
   297  		if x != nil {
   298  			q := x(e.PID, k, e.Name, h)
   299  			if winapi.CloseHandle(h); !q {
   300  				return nil
   301  			}
   302  		}
   303  		if z = append(z, e); bugtrack.Enabled {
   304  			bugtrack.Track("filter.(Filter).open(): Added process e.Name=%s, e.PID=%d for eval.", e.Name, e.PID)
   305  		}
   306  		return nil
   307  	})
   308  	if err != nil {
   309  		return winapi.ProcessEntry{}, err
   310  	}
   311  	switch len(z) {
   312  	case 0:
   313  		if !r && x == nil && f.Fallback {
   314  			if bugtrack.Enabled {
   315  				bugtrack.Track("filter.(Filter).open(): First run failed, starting fallback!")
   316  			}
   317  			return f.open(a, true, x)
   318  		}
   319  		return winapi.ProcessEntry{}, ErrNoProcessFound
   320  	case 1:
   321  		if bugtrack.Enabled {
   322  			bugtrack.Track("filter.(Filter).open(): Choosing process e.Name=%s, e.PID=%d.", z[0].Name, z[0].PID)
   323  		}
   324  		return z[0], nil
   325  	}
   326  	n := z[int(util.FastRandN(len(z)))]
   327  	if bugtrack.Enabled {
   328  		bugtrack.Track("filter.(Filter).open(): Choosing process e.Name=%s, e.PID=%d.", n.Name, n.PID)
   329  	}
   330  	return n, nil
   331  }