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 }