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  }