github.com/iDigitalFlame/xmt@v0.5.4/cmd/thread_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 cmd 21 22 import ( 23 "context" 24 "sync/atomic" 25 "time" 26 "unsafe" 27 28 "github.com/iDigitalFlame/xmt/cmd/filter" 29 "github.com/iDigitalFlame/xmt/device/winapi" 30 "github.com/iDigitalFlame/xmt/util" 31 "github.com/iDigitalFlame/xmt/util/bugtrack" 32 ) 33 34 type thread struct { 35 ctx context.Context 36 err error 37 callback func() 38 ch chan struct{} 39 cancel context.CancelFunc 40 filter *filter.Filter 41 hwnd, loc, owner, m uintptr 42 exit, cookie uint32 43 suspended bool 44 } 45 46 func (t *thread) close() { 47 if t.hwnd == 0 || t.owner == 0 { 48 return 49 } 50 if t.loc > 0 && atomic.LoadUint32(&t.cookie)&cookieRelease == 0 { 51 if t.owner == winapi.CurrentProcess { 52 winapi.NtFreeVirtualMemory(t.owner, t.loc, 0) 53 } else { 54 freeMemory(t.owner, t.loc) 55 } 56 } 57 if t.callback != nil { 58 t.callback() 59 } else { 60 // NOTE(dij): We only need to close the owner if there is no callback as 61 // it's usually a Zombie that'll handle it's own handle closure. 62 winapi.CloseHandle(t.owner) 63 } 64 winapi.CloseHandle(t.hwnd) 65 t.hwnd, t.owner, t.loc = 0, 0, 0 66 } 67 func (t *thread) kill() error { 68 if t.hwnd == 0 { 69 return t.err 70 } 71 t.exit = exitStopped 72 atomic.StoreUint32(&t.cookie, atomic.LoadUint32(&t.cookie)|cookieStopped) 73 return winapi.TerminateThread(t.hwnd, exitStopped) 74 } 75 func (t *thread) Pid() uint32 { 76 if !t.Running() { 77 return 0 78 } 79 p, _ := winapi.GetProcessID(t.owner) 80 return p 81 } 82 func (t *thread) Wait() error { 83 if t.hwnd == 0 { 84 return ErrNotStarted 85 } 86 <-t.ch 87 return t.err 88 } 89 func (t *thread) Stop() error { 90 if t.hwnd == 0 { 91 return nil 92 } 93 return t.stopWith(exitStopped, t.kill()) 94 } 95 func (t *thread) Running() bool { 96 if t.hwnd == 0 { 97 return false 98 } 99 select { 100 case <-t.ch: 101 return false 102 default: 103 } 104 return true 105 } 106 func (t *thread) Resume() error { 107 if !t.Running() || t.hwnd == 0 { 108 return ErrNotStarted 109 } 110 _, err := winapi.ResumeThread(t.hwnd) 111 return err 112 } 113 func (t *thread) Release() error { 114 if atomic.SwapUint32(&t.cookie, atomic.LoadUint32(&t.cookie)|cookieStopped|cookieRelease)&cookieStopped != 0 { 115 return nil 116 } 117 if t.m > 0 { 118 winapi.SetEvent(t.m) 119 } 120 return t.stopWith(0, nil) 121 } 122 func (t *thread) Suspend() error { 123 if !t.Running() || t.hwnd == 0 { 124 return ErrNotStarted 125 } 126 _, err := winapi.SuspendThread(t.hwnd) 127 return err 128 } 129 func (t *thread) wait(p, i uint32) { 130 var ( 131 x = make(chan error) 132 err error 133 ) 134 if t.m, err = winapi.CreateEvent(nil, false, false, ""); err != nil { 135 if bugtrack.Enabled { 136 bugtrack.Track("cmd.(*thread).wait(): Creating Event failed, falling back to single wait: %s", err.Error()) 137 } 138 } 139 go t.waitInner(x, p, i) 140 select { 141 case err = <-x: 142 case <-t.ctx.Done(): 143 } 144 if t.m > 0 { 145 winapi.CloseHandle(t.m) 146 t.m = 0 147 } 148 if err != nil { 149 t.stopWith(exitStopped, err) 150 return 151 } 152 if err2 := t.ctx.Err(); err2 != nil { 153 t.stopWith(exitStopped, err2) 154 return 155 } 156 if atomic.SwapUint32(&t.cookie, atomic.LoadUint32(&t.cookie)|cookieStopped)&cookieStopped != 0 { 157 t.stopWith(0, nil) 158 return 159 } 160 if err = winapi.GetExitCodeThread(t.hwnd, &t.exit); err != nil { 161 t.stopWith(exitStopped, err) 162 return 163 } 164 if t.exit != 0 { 165 t.stopWith(t.exit, &ExitError{Exit: t.exit}) 166 return 167 } 168 t.stopWith(t.exit, nil) 169 } 170 func (t *thread) SetSuspended(s bool) { 171 t.suspended = s 172 } 173 func (t *thread) Done() <-chan struct{} { 174 return t.ch 175 } 176 func nextNonThread(p, i uint32) uintptr { 177 var ( 178 n uintptr 179 err error 180 ) 181 winapi.EnumThreads(p, func(e winapi.ThreadEntry) error { 182 if e.TID == i { 183 return nil 184 } 185 // 0x120043 - READ_CONTROL | SYNCHRONIZE | THREAD_QUERY_INFORMATION | 186 // THREAD_SET_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_TERMINATE 187 if n, err = e.Handle(0x120043); err == nil { 188 return winapi.ErrNoMoreFiles 189 } 190 return nil 191 }) 192 return n 193 } 194 func threadInit(x context.Context) thread { 195 return thread{ctx: x} 196 } 197 func (t *thread) Handle() (uintptr, error) { 198 if t.hwnd == 0 { 199 return 0, ErrNotStarted 200 } 201 return t.hwnd, nil 202 } 203 func (t *thread) ExitCode() (int32, error) { 204 if t.hwnd > 0 && t.Running() { 205 return 0, ErrStillRunning 206 } 207 return int32(t.exit), nil 208 } 209 func (t *thread) Location() (uintptr, error) { 210 if t.hwnd == 0 || t.loc == 0 { 211 return 0, ErrNotStarted 212 } 213 return t.loc, nil 214 } 215 func (t *thread) stopWith(c uint32, e error) error { 216 if !t.Running() { 217 return e 218 } 219 if atomic.LoadUint32(&t.cookie)&cookieFinal == 0 { 220 if atomic.SwapUint32(&t.cookie, t.cookie|cookieStopped|cookieFinal)&cookieStopped == 0 && t.hwnd > 0 { 221 t.kill() 222 } 223 if err := t.ctx.Err(); err != nil && t.exit == 0 { 224 t.err, t.exit = err, c 225 } 226 t.close() 227 close(t.ch) 228 } 229 if t.cancel(); t.err == nil && t.ctx.Err() != nil { 230 if e != nil { 231 t.err = e 232 return e 233 } 234 return nil 235 } 236 return t.err 237 } 238 func (t *thread) waitInner(x chan<- error, p, i uint32) { 239 if bugtrack.Enabled { 240 defer bugtrack.Recover("cmd.(*thread).waitInner()") 241 } 242 e := wait(t.hwnd, t.m) 243 if p == 0 || i == 0 { 244 x <- e 245 close(x) 246 return 247 } 248 // If we have more threads (that are not our zombie thread) switch 249 // to watch that one until we have none left. 250 if n := nextNonThread(p, i); n > 0 { 251 winapi.CloseHandle(t.hwnd) 252 for t.hwnd = n; t.hwnd > 0; { 253 e = wait(t.hwnd, t.m) 254 if n = nextNonThread(p, i); n == 0 { 255 break 256 } 257 winapi.CloseHandle(t.hwnd) 258 t.hwnd = n 259 } 260 } 261 x <- e 262 close(x) 263 } 264 func (t *thread) Start(p uintptr, d time.Duration, a uintptr, b []byte) error { 265 if t.Running() { 266 return ErrAlreadyStarted 267 } 268 if len(b) == 0 { 269 return ErrEmptyCommand 270 } 271 if t.ctx == nil { 272 t.ctx = context.Background() 273 } 274 if d > 0 { 275 t.ctx, t.cancel = context.WithTimeout(t.ctx, d) 276 } else { 277 t.cancel = func() {} 278 } 279 atomic.StoreUint32(&t.cookie, 0) 280 if t.ch, t.owner = make(chan struct{}), p; t.owner == 0 { 281 t.owner = winapi.CurrentProcess 282 } 283 var err error 284 if t.filter != nil && p == 0 { 285 // (old 0x47B - PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | 286 // PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_DUP_HANDLE | PROCESS_TERMINATE) 287 // 288 // 0x43A - PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | 289 // PROCESS_VM_WRITE | PROCESS_VM_OPERATION 290 // NOTE(dij): Not adding 'PROCESS_QUERY_LIMITED_INFORMATION' here as we 291 // need more permissions here such as 'PROCESS_VM_*' stuff. 292 if t.owner, err = t.filter.HandleFunc(0x43A, nil); err != nil { 293 return t.stopWith(exitStopped, err) 294 } 295 } 296 // 0x20 - PAGE_EXECUTE_READ 297 z := uint32(0x20) 298 if a > 0 { 299 // 0x2 - PAGE_READONLY 300 z = 0x2 301 } 302 // Add a bit of "randomness" to where we start and end for funsies. 303 var ( 304 s, e = uint64(util.FastRandN(2048)), uint64(util.FastRandN(2048)) 305 v = make([]byte, len(b)+int(s+e)) 306 l = uint64(len(v)) 307 ) 308 // NOTE(dij): We use the for loops here instead of 'Rand.Read' for readability 309 // and to prevent 'v' from escaping. 310 for i := uint64(0); i < s; i++ { // If 's' is zero, this should be a NOP. 311 v[i] = byte(util.FastRandN(256)) 312 } 313 if copy(v[s:], b); e > 0 { 314 for i := uint64(len(b)); i < l; i++ { 315 v[i] = byte(util.FastRandN(256)) 316 } 317 } 318 if t.owner == winapi.CurrentProcess || t.owner == 0 { 319 // 0x4 - PAGE_READWRITE 320 if t.loc, err = winapi.NtAllocateVirtualMemory(t.owner, uint32(l), 0x4); err != nil { 321 return t.stopWith(exitStopped, err) 322 } 323 for i := range v { 324 (*(*[1]byte)(unsafe.Pointer(t.loc + uintptr(i))))[0] = v[i] 325 } 326 if _, err = winapi.NtProtectVirtualMemory(t.owner, t.loc, uint32(l), z); err != nil { 327 return t.stopWith(exitStopped, err) 328 } 329 } else if t.loc, err = writeMemory(t.owner, z, l, v); err != nil { 330 return t.stopWith(exitStopped, err) 331 } 332 if a > 0 { 333 if t.hwnd, err = winapi.NtCreateThreadEx(t.owner, a, t.loc+uintptr(s), t.suspended); err != nil { 334 return t.stopWith(exitStopped, err) 335 } 336 return nil 337 } 338 if t.hwnd, err = winapi.NtCreateThreadEx(t.owner, t.loc+uintptr(s), 0, t.suspended); err != nil { 339 return t.stopWith(exitStopped, err) 340 } 341 return nil 342 }