github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/m_funcmap.go (about)

     1  //go:build windows && funcmap
     2  // +build windows,funcmap
     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 winapi
    21  
    22  import (
    23  	"os"
    24  	"sync"
    25  	"sync/atomic"
    26  	"syscall"
    27  	"unsafe"
    28  
    29  	"github.com/iDigitalFlame/xmt/data"
    30  )
    31  
    32  var (
    33  	funcLock   sync.Mutex
    34  	funcMapper map[uint32]*funcMap
    35  )
    36  
    37  type funcMap struct {
    38  	_    [0]func()
    39  	proc *lazyProc
    40  	bak  uintptr
    41  	swap uintptr
    42  	len  uint32
    43  }
    44  
    45  // FuncEntry is a simple struct that is used to describe the current status of
    46  // function mappings. This struct is returned by a call to 'FuncRemaps' in a
    47  // slice of current remaps.
    48  type FuncEntry struct {
    49  	_        [0]func()
    50  	Hash     uint32
    51  	Swapped  uintptr
    52  	Original uintptr
    53  }
    54  
    55  // FuncUnmapAll attempts to call 'FuncUnmap' on all currently mapped functions.
    56  // If any error occurs during unmapping, this function will stop and return an
    57  // error. Errors will stop any pending unmap calls from occuring.
    58  func FuncUnmapAll() error {
    59  	funcLock.Lock()
    60  	for _, v := range funcMapper {
    61  		if err := v.unmap(); err != nil {
    62  			funcLock.Unlock()
    63  			return err
    64  		}
    65  	}
    66  	funcLock.Unlock()
    67  	return nil
    68  }
    69  
    70  // FuncUnmap will attempt to unmap the ntdll.dll function by name. If successful
    71  // all calls to the affected function will work normally and the allocated memory
    72  // region will be freed.
    73  //
    74  // This function returns ErrNotExist if the function name is not a recognized
    75  // ntdll.dll function that does a direct syscall.
    76  //
    77  // This function returns nil even if the function was not previously remapped.
    78  //
    79  // If this function returns any errors do not assume the call site was fixed
    80  // to behave normally.
    81  func FuncUnmap(f string) error {
    82  	if len(f) == 0 {
    83  		return os.ErrNotExist
    84  	}
    85  	return FuncUnmapHash(FnvHash(f))
    86  }
    87  func (v *funcMap) unmap() error {
    88  	if v.bak == 0 || v.swap == 0 || v.len == 0 {
    89  		return nil
    90  	}
    91  	atomic.SwapUintptr(&v.proc.addr, v.bak)
    92  	err := NtFreeVirtualMemory(CurrentProcess, v.swap, 0)
    93  	v.swap, v.len = 0, 0
    94  	return err
    95  }
    96  
    97  // FuncRemapList returns a list of all current remapped functions. This includes
    98  // the old and new addresses and the function name hash.
    99  //
   100  // If no functions are remapped, this function returns nil.
   101  func FuncRemapList() []FuncEntry {
   102  	if funcLock.Lock(); len(funcMapper) == 0 {
   103  		funcLock.Unlock()
   104  		return nil
   105  	}
   106  	r := make([]FuncEntry, 0, len(funcMapper))
   107  	for k, v := range funcMapper {
   108  		if v.bak == 0 || v.swap == 0 {
   109  			continue
   110  		}
   111  		r = append(r, FuncEntry{Hash: k, Swapped: v.swap, Original: v.bak})
   112  	}
   113  	funcLock.Unlock()
   114  	return r
   115  }
   116  
   117  // FuncUnmapHash will attempt to unmap the ntdll.dll by its function hash. If
   118  // successful all calls to the affected function will work normally and the
   119  // allocated memory region will be freed.
   120  //
   121  // This function returns ErrNotExist if the function name is not a recognized
   122  // ntdll.dll function that does a direct syscall.
   123  //
   124  // This function returns nil even if the function was not previously remapped.
   125  //
   126  // If this function returns any errors do not assume the call site was fixed
   127  // to behave normally.
   128  func FuncUnmapHash(h uint32) error {
   129  	if h == 0 {
   130  		return os.ErrNotExist
   131  	}
   132  	funcLock.Lock()
   133  	v, ok := funcMapper[h]
   134  	if funcLock.Unlock(); !ok {
   135  		return os.ErrNotExist
   136  	}
   137  	return v.unmap()
   138  }
   139  
   140  // FuncRemap attempts to remap the raw ntdll.dll function name with the supplied
   141  // machine-code bytes. If successful, this will point all function calls in the
   142  // runtime to that allocated byte array in memory, bypassing any hooked calls
   143  // without overriting any existing memory.
   144  //
   145  // This function returns EINVAL if the byte slice is empty or ErrNotExist if the
   146  // function name is not a recognized ntdll.dll function that does a direct syscall.
   147  //
   148  // It is recommended to call 'FuncUnmap(name)' or 'FuncUnmapAll' once complete
   149  // to release the memory space.
   150  //
   151  // The 'Func*' functions only work of the build tag "funcmap" is used during
   152  // buildtime, otherwise these functions return EINVAL.
   153  func FuncRemap(f string, b []byte) error {
   154  	if len(f) == 0 {
   155  		return os.ErrNotExist
   156  	}
   157  	return FuncRemapHash(FnvHash(f), b)
   158  }
   159  
   160  // FuncRemapHash attempts to remap the raw ntdll.dll function hash with the supplied
   161  // machine-code bytes. If successful, this will point all function calls in the
   162  // runtime to that allocated byte array in memory, bypassing any hooked calls
   163  // without overriting any existing memory.
   164  //
   165  // This function returns EINVAL if the byte slice is empty or ErrNotExist if the
   166  // function hash is not a recognized ntdll.dll function that does a direct syscall.
   167  //
   168  // It is recommended to call 'FuncUnmap(name)' or 'FuncUnmapAll' once complete
   169  // to release the memory space.
   170  //
   171  // The 'Func*' functions only work of the build tag "funcmap" is used during
   172  // buildtime, otherwise these functions return EINVAL.
   173  func FuncRemapHash(h uint32, b []byte) error {
   174  	if h == 0 {
   175  		return os.ErrNotExist
   176  	}
   177  	if len(b) == 0 {
   178  		return syscall.EINVAL
   179  	}
   180  	funcLock.Lock()
   181  	v, ok := funcMapper[h]
   182  	if funcLock.Unlock(); !ok {
   183  		return os.ErrNotExist
   184  	}
   185  	if err := v.proc.find(); err != nil {
   186  		return err
   187  	}
   188  	if v.swap != 0 {
   189  		return syscall.EADDRINUSE
   190  	}
   191  	atomic.CompareAndSwapUintptr(&v.bak, 0, v.proc.addr) // Single swap to prevent lock
   192  	var (
   193  		n      = uint32(len(b))
   194  		a, err = NtAllocateVirtualMemory(CurrentProcess, n, 0x4)
   195  		// 0x4 - PAGE_READWRITE
   196  	)
   197  	if err != nil {
   198  		return err
   199  	}
   200  	for i := range b {
   201  		(*(*[1]byte)(unsafe.Pointer(a + uintptr(i))))[0] = b[i]
   202  	}
   203  	// 0x20 - PAGE_EXECUTE_READ
   204  	if _, err = NtProtectVirtualMemory(CurrentProcess, a, n, 0x20); err == nil {
   205  		v.swap, v.len = a, n
   206  		atomic.SwapUintptr(&v.proc.addr, a)
   207  		return nil
   208  	}
   209  	NtFreeVirtualMemory(CurrentProcess, a, 0)
   210  	return err
   211  }
   212  func registerSyscall(p *lazyProc, n string, h uint32) {
   213  	if funcLock.Lock(); funcMapper == nil {
   214  		funcMapper = make(map[uint32]*funcMap, 8)
   215  	}
   216  	switch {
   217  	case h == 0 && len(n) == 0:
   218  	case h > 0:
   219  		funcMapper[h] = &funcMap{proc: p}
   220  	case len(n) > 0:
   221  		funcMapper[FnvHash(n)] = &funcMap{proc: p}
   222  	}
   223  	funcLock.Unlock()
   224  }
   225  
   226  // MarshalStream transforms this struct into a binary format and writes to the
   227  // supplied data.Writer.
   228  func (e FuncEntry) MarshalStream(w data.Writer) error {
   229  	if err := w.WriteUint32(e.Hash); err != nil {
   230  		return err
   231  	}
   232  	if err := w.WriteUint64(uint64(e.Original)); err != nil {
   233  		return err
   234  	}
   235  	return w.WriteUint64(uint64(e.Swapped))
   236  }