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 }