├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── patcher.go ├── patcher.s ├── patcher_arm64.go ├── patcher_test.go ├── patcher_unix.go ├── patcher_unsupported.go ├── patcher_windows.go ├── patcher_x32.go └── patcher_x64.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | test: 5 | name: Test 6 | strategy: 7 | matrix: 8 | go_version: [1.7, 1.8, 1.9, "1.10", 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, "1.20", "1.21"] 9 | os: [ubuntu-latest, windows-latest, macos-11] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - name: Set up Go ${{ matrix.go_version }} 13 | uses: actions/setup-go@v3 14 | with: 15 | go-version: ${{ matrix.go_version }} 16 | id: go 17 | 18 | - name: Check out code into the Go module directory 19 | uses: actions/checkout@v1 20 | 21 | - name: Get dependencies 22 | run: go get -v -t -d ./... 23 | 24 | - name: Test 25 | run: go test -v --race ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Undefined Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-mpatch 2 | Go library for monkey patching 3 | 4 | ## Compatibility 5 | 6 | - **Go version:** tested from `go1.7` to `go1.21` 7 | - **Architectures:** `x86`, `amd64`, `arm64` (ARM64 not supported on macos) 8 | - **Operating systems:** tested in `macos`, `linux` and `windows`. 9 | 10 | ### ARM64 support 11 | The support for ARM64 have some caveats. For example: 12 | - On Windows 11 ARM64 works like any other platform, we test it with tiny methods and worked correctly. 13 | - On Linux ARM64 the minimum target and source method size must be > 24 bytes. This means a simple 1 liner method will silently fail. In our tests we have methods at least with 3 lines and works correctly (beware of the compiler optimizations). 14 | This doesn't happen in any other platform because the assembly code we emit is really short (x64: 12 bytes / x86: 7 bytes), but for ARM64 is exactly 24 bytes. 15 | - On MacOS ARM64 the patching fails with `EACCES: permission denied` when calling `syscall.Mprotect`. There's no current workaround for this issue, if you use an Apple Silicon Mac you can use a docker container or docker dev environment. 16 | 17 | ## Features 18 | 19 | - Can patch package functions, instance functions (by pointer or by value), and create new functions from scratch. 20 | 21 | ## Limitations 22 | 23 | - Target functions could be inlined, making those functions unpatcheables. You can use `//go:noinline` directive or build with the `gcflags=-l` 24 | to disable inlining at compiler level. 25 | 26 | - Write permission to memory pages containing executable code is needed, some operating systems could restrict this access. 27 | 28 | - Not thread safe. 29 | 30 | ## Usage 31 | 32 | ### Patching a func 33 | ```go 34 | //go:noinline 35 | func methodA() int { return 1 } 36 | 37 | //go:noinline 38 | func methodB() int { return 2 } 39 | 40 | func TestPatcher(t *testing.T) { 41 | patch, err := mpatch.PatchMethod(methodA, methodB) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if methodA() != 2 { 46 | t.Fatal("The patch did not work") 47 | } 48 | 49 | err = patch.Unpatch() 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if methodA() != 1 { 54 | t.Fatal("The unpatch did not work") 55 | } 56 | } 57 | ``` 58 | 59 | ### Patching using `reflect.ValueOf` 60 | ```go 61 | //go:noinline 62 | func methodA() int { return 1 } 63 | 64 | //go:noinline 65 | func methodB() int { return 2 } 66 | 67 | func TestPatcherUsingReflect(t *testing.T) { 68 | reflectA := reflect.ValueOf(methodA) 69 | patch, err := mPatch.PatchMethodByReflectValue(reflectA, methodB) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | if methodA() != 2 { 74 | t.Fatal("The patch did not work") 75 | } 76 | 77 | err = patch.Unpatch() 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | if methodA() != 1 { 82 | t.Fatal("The unpatch did not work") 83 | } 84 | } 85 | ``` 86 | 87 | ### Patching creating a new func at runtime 88 | ```go 89 | //go:noinline 90 | func methodA() int { return 1 } 91 | 92 | func TestPatcherUsingMakeFunc(t *testing.T) { 93 | reflectA := reflect.ValueOf(methodA) 94 | patch, err := PatchMethodWithMakeFuncValue(reflectA, 95 | func(args []reflect.Value) (results []reflect.Value) { 96 | return []reflect.Value{reflect.ValueOf(42)} 97 | }) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | if methodA() != 42 { 102 | t.Fatal("The patch did not work") 103 | } 104 | 105 | err = patch.Unpatch() 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | if methodA() != 1 { 110 | t.Fatal("The unpatch did not work") 111 | } 112 | } 113 | ``` 114 | 115 | ### Patching an instance func 116 | ```go 117 | type myStruct struct { 118 | } 119 | 120 | //go:noinline 121 | func (s *myStruct) Method() int { 122 | return 1 123 | } 124 | 125 | func TestInstancePatcher(t *testing.T) { 126 | mStruct := myStruct{} 127 | 128 | var patch *Patch 129 | var err error 130 | patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "Method", func(m *myStruct) int { 131 | patch.Unpatch() 132 | defer patch.Patch() 133 | return 41 + m.Method() 134 | }) 135 | if err != nil { 136 | t.Fatal(err) 137 | } 138 | 139 | if mStruct.Method() != 42 { 140 | t.Fatal("The patch did not work") 141 | } 142 | err = patch.Unpatch() 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | if mStruct.Method() != 1 { 147 | t.Fatal("The unpatch did not work") 148 | } 149 | } 150 | ``` 151 | 152 | ### Patching an instance func by Value 153 | ```go 154 | type myStruct struct { 155 | } 156 | 157 | //go:noinline 158 | func (s myStruct) ValueMethod() int { 159 | return 1 160 | } 161 | 162 | func TestInstanceValuePatcher(t *testing.T) { 163 | mStruct := myStruct{} 164 | 165 | var patch *Patch 166 | var err error 167 | patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "ValueMethod", func(m myStruct) int { 168 | patch.Unpatch() 169 | defer patch.Patch() 170 | return 41 + m.Method() 171 | }) 172 | if err != nil { 173 | t.Fatal(err) 174 | } 175 | 176 | if mStruct.ValueMethod() != 42 { 177 | t.Fatal("The patch did not work") 178 | } 179 | err = patch.Unpatch() 180 | if err != nil { 181 | t.Fatal(err) 182 | } 183 | if mStruct.ValueMethod() != 1 { 184 | t.Fatal("The unpatch did not work") 185 | } 186 | } 187 | ``` 188 | 189 | > Library inspired by the blog post: https://bou.ke/blog/monkey-patching-in-go/ 190 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/undefinedlabs/go-mpatch 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /patcher.go: -------------------------------------------------------------------------------- 1 | package mpatch // import "github.com/undefinedlabs/go-mpatch" 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | type ( 13 | Patch struct { 14 | targetBytes []byte 15 | target *reflect.Value 16 | redirection *reflect.Value 17 | } 18 | sliceHeader struct { 19 | Data unsafe.Pointer 20 | Len int 21 | Cap int 22 | } 23 | ) 24 | 25 | //go:linkname getInternalPtrFromValue reflect.(*Value).pointer 26 | func getInternalPtrFromValue(v *reflect.Value) unsafe.Pointer 27 | 28 | var ( 29 | patchLock = sync.Mutex{} 30 | patches = make(map[unsafe.Pointer]*Patch) 31 | pageSize = syscall.Getpagesize() 32 | ) 33 | 34 | // Patches a target func to redirect calls to "redirection" func. Both function must have same arguments and return types. 35 | func PatchMethod(target, redirection interface{}) (*Patch, error) { 36 | tValue := getValueFrom(target) 37 | rValue := getValueFrom(redirection) 38 | if err := isPatchable(&tValue, &rValue); err != nil { 39 | return nil, err 40 | } 41 | patch := &Patch{target: &tValue, redirection: &rValue} 42 | if err := applyPatch(patch); err != nil { 43 | return nil, err 44 | } 45 | return patch, nil 46 | } 47 | 48 | // Patches an instance func by using two parameters, the target struct type and the method name inside that type, 49 | // this func will be redirected to the "redirection" func. Note: The first parameter of the redirection func must be the object instance. 50 | func PatchInstanceMethodByName(target reflect.Type, methodName string, redirection interface{}) (*Patch, error) { 51 | method, ok := target.MethodByName(methodName) 52 | if !ok && target.Kind() == reflect.Struct { 53 | target = reflect.PtrTo(target) 54 | method, ok = target.MethodByName(methodName) 55 | } 56 | if !ok { 57 | return nil, errors.New(fmt.Sprintf("Method '%v' not found", methodName)) 58 | } 59 | return PatchMethodByReflect(method, redirection) 60 | } 61 | 62 | // Patches a target func by passing the reflect.Method of the func. The target func will be redirected to the "redirection" func. 63 | // Both function must have same arguments and return types. 64 | func PatchMethodByReflect(target reflect.Method, redirection interface{}) (*Patch, error) { 65 | return PatchMethodByReflectValue(target.Func, redirection) 66 | } 67 | 68 | // Patches a target func with a "redirection" function created at runtime by using "reflect.MakeFunc". 69 | func PatchMethodWithMakeFunc(target reflect.Method, fn func(args []reflect.Value) (results []reflect.Value)) (*Patch, error) { 70 | return PatchMethodByReflect(target, reflect.MakeFunc(target.Type, fn)) 71 | } 72 | 73 | // Patches a target func by passing the reflect.ValueOf of the func. The target func will be redirected to the "redirection" func. 74 | // Both function must have same arguments and return types. 75 | func PatchMethodByReflectValue(target reflect.Value, redirection interface{}) (*Patch, error) { 76 | tValue := &target 77 | rValue := getValueFrom(redirection) 78 | if err := isPatchable(tValue, &rValue); err != nil { 79 | return nil, err 80 | } 81 | patch := &Patch{target: tValue, redirection: &rValue} 82 | if err := applyPatch(patch); err != nil { 83 | return nil, err 84 | } 85 | return patch, nil 86 | } 87 | 88 | // Patches a target func with a "redirection" function created at runtime by using "reflect.MakeFunc". 89 | func PatchMethodWithMakeFuncValue(target reflect.Value, fn func(args []reflect.Value) (results []reflect.Value)) (*Patch, error) { 90 | return PatchMethodByReflectValue(target, reflect.MakeFunc(target.Type(), fn)) 91 | } 92 | 93 | // Patch the target func with the redirection func. 94 | func (p *Patch) Patch() error { 95 | if p == nil { 96 | return errors.New("patch is nil") 97 | } 98 | if err := isPatchable(p.target, p.redirection); err != nil { 99 | return err 100 | } 101 | if err := applyPatch(p); err != nil { 102 | return err 103 | } 104 | return nil 105 | } 106 | 107 | // Unpatch the target func and recover the original func. 108 | func (p *Patch) Unpatch() error { 109 | if p == nil { 110 | return errors.New("patch is nil") 111 | } 112 | return applyUnpatch(p) 113 | } 114 | 115 | func isPatchable(target, redirection *reflect.Value) error { 116 | patchLock.Lock() 117 | defer patchLock.Unlock() 118 | if target.Kind() != reflect.Func || redirection.Kind() != reflect.Func { 119 | return errors.New("the target and/or redirection is not a Func") 120 | } 121 | if target.Type() != redirection.Type() { 122 | return errors.New(fmt.Sprintf("the target and/or redirection doesn't have the same type: %s != %s", target.Type(), redirection.Type())) 123 | } 124 | if _, ok := patches[getCodePointer(target)]; ok { 125 | return errors.New("the target is already patched") 126 | } 127 | return nil 128 | } 129 | 130 | func applyPatch(patch *Patch) error { 131 | patchLock.Lock() 132 | defer patchLock.Unlock() 133 | tPointer := getCodePointer(patch.target) 134 | rPointer := getInternalPtrFromValue(patch.redirection) 135 | rPointerJumpBytes, err := getJumpFuncBytes(rPointer) 136 | if err != nil { 137 | return err 138 | } 139 | tPointerBytes := getMemorySliceFromPointer(tPointer, len(rPointerJumpBytes)) 140 | targetBytes := make([]byte, len(tPointerBytes)) 141 | copy(targetBytes, tPointerBytes) 142 | if err := writeDataToPointer(tPointer, rPointerJumpBytes); err != nil { 143 | return err 144 | } 145 | patch.targetBytes = targetBytes 146 | patches[tPointer] = patch 147 | return nil 148 | } 149 | 150 | func applyUnpatch(patch *Patch) error { 151 | patchLock.Lock() 152 | defer patchLock.Unlock() 153 | if patch.targetBytes == nil || len(patch.targetBytes) == 0 { 154 | return errors.New("the target is not patched") 155 | } 156 | tPointer := getCodePointer(patch.target) 157 | if _, ok := patches[tPointer]; !ok { 158 | return errors.New("the target is not patched") 159 | } 160 | delete(patches, tPointer) 161 | err := writeDataToPointer(tPointer, patch.targetBytes) 162 | if err != nil { 163 | return err 164 | } 165 | return nil 166 | } 167 | 168 | func getValueFrom(data interface{}) reflect.Value { 169 | if cValue, ok := data.(reflect.Value); ok { 170 | return cValue 171 | } else { 172 | return reflect.ValueOf(data) 173 | } 174 | } 175 | 176 | // Extracts a memory slice from a pointer 177 | func getMemorySliceFromPointer(p unsafe.Pointer, length int) []byte { 178 | return *(*[]byte)(unsafe.Pointer(&sliceHeader{ 179 | Data: p, 180 | Len: length, 181 | Cap: length, 182 | })) 183 | } 184 | 185 | // Gets the code pointer of a func 186 | func getCodePointer(value *reflect.Value) unsafe.Pointer { 187 | p := getInternalPtrFromValue(value) 188 | if p != nil { 189 | p = *(*unsafe.Pointer)(p) 190 | } 191 | return p 192 | } 193 | -------------------------------------------------------------------------------- /patcher.s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undefinedlabs/go-mpatch/fbac8a0d7853c06daf75d5b335bbb336722a25a9/patcher.s -------------------------------------------------------------------------------- /patcher_arm64.go: -------------------------------------------------------------------------------- 1 | //go:build arm64 2 | // +build arm64 3 | 4 | package mpatch 5 | 6 | import "unsafe" 7 | 8 | // Code from: https://github.com/agiledragon/gomonkey/blob/master/jmp_arm64.go 9 | 10 | // Gets the jump function rewrite bytes 11 | // 12 | //go:nosplit 13 | func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) { 14 | res := make([]byte, 0, 24) 15 | d0d1 := uintptr(to) & 0xFFFF 16 | d2d3 := uintptr(to) >> 16 & 0xFFFF 17 | d4d5 := uintptr(to) >> 32 & 0xFFFF 18 | d6d7 := uintptr(to) >> 48 & 0xFFFF 19 | 20 | res = append(res, movImm(0b10, 0, d0d1)...) // MOVZ x26, double[16:0] 21 | res = append(res, movImm(0b11, 1, d2d3)...) // MOVK x26, double[32:16] 22 | res = append(res, movImm(0b11, 2, d4d5)...) // MOVK x26, double[48:32] 23 | res = append(res, movImm(0b11, 3, d6d7)...) // MOVK x26, double[64:48] 24 | res = append(res, []byte{0x4A, 0x03, 0x40, 0xF9}...) // LDR x10, [x26] 25 | res = append(res, []byte{0x40, 0x01, 0x1F, 0xD6}...) // BR x10 26 | 27 | return res, nil 28 | } 29 | 30 | func movImm(opc, shift int, val uintptr) []byte { 31 | var m uint32 = 26 // rd 32 | m |= uint32(val) << 5 // imm16 33 | m |= uint32(shift&3) << 21 // hw 34 | m |= 0b100101 << 23 // const 35 | m |= uint32(opc&0x3) << 29 // opc 36 | m |= 0b1 << 31 // sf 37 | 38 | res := make([]byte, 4) 39 | *(*uint32)(unsafe.Pointer(&res[0])) = m 40 | 41 | return res 42 | } 43 | -------------------------------------------------------------------------------- /patcher_test.go: -------------------------------------------------------------------------------- 1 | package mpatch 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | //go:noinline 10 | func methodA() int { 11 | x := rand.Int() >> 48 12 | y := rand.Int() >> 48 13 | return x + y 14 | } 15 | 16 | //go:noinline 17 | func methodB() int { 18 | x := rand.Int() >> 48 19 | y := rand.Int() >> 48 20 | return -(x + y) 21 | } 22 | 23 | type myStruct struct { 24 | } 25 | 26 | //go:noinline 27 | func (s *myStruct) Method() int { 28 | return 1 29 | } 30 | 31 | //go:noinline 32 | func (s myStruct) ValueMethod() int { 33 | return 1 34 | } 35 | 36 | func TestPatcher(t *testing.T) { 37 | patch, err := PatchMethod(methodA, methodB) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | if methodA() > 0 { 42 | t.Fatal("The patch did not work") 43 | } 44 | err = patch.Unpatch() 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | if methodA() < 0 { 49 | t.Fatal("The unpatch did not work") 50 | } 51 | } 52 | 53 | func TestPatcherUsingReflect(t *testing.T) { 54 | reflectA := reflect.ValueOf(methodA) 55 | patch, err := PatchMethodByReflectValue(reflectA, methodB) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | if methodA() > 0 { 60 | t.Fatal("The patch did not work") 61 | } 62 | 63 | err = patch.Unpatch() 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if methodA() < 0 { 68 | t.Fatal("The unpatch did not work") 69 | } 70 | } 71 | 72 | func TestPatcherUsingMakeFunc(t *testing.T) { 73 | reflectA := reflect.ValueOf(methodA) 74 | patch, err := PatchMethodWithMakeFuncValue(reflectA, 75 | func(args []reflect.Value) (results []reflect.Value) { 76 | return []reflect.Value{reflect.ValueOf(42)} 77 | }) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | if methodA() != 42 { 82 | t.Fatal("The patch did not work") 83 | } 84 | 85 | err = patch.Unpatch() 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | if methodA() < 0 { 90 | t.Fatal("The unpatch did not work") 91 | } 92 | } 93 | 94 | func TestInstancePatcher(t *testing.T) { 95 | mStruct := myStruct{} 96 | 97 | var patch *Patch 98 | var err error 99 | patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "Method", func(m *myStruct) int { 100 | patch.Unpatch() 101 | defer patch.Patch() 102 | return 41 + m.Method() 103 | }) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | if mStruct.Method() != 42 { 109 | t.Fatal("The patch did not work") 110 | } 111 | err = patch.Unpatch() 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | if mStruct.Method() != 1 { 116 | t.Fatal("The unpatch did not work") 117 | } 118 | } 119 | 120 | func TestInstanceValuePatcher(t *testing.T) { 121 | mStruct := myStruct{} 122 | 123 | var patch *Patch 124 | var err error 125 | patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "ValueMethod", func(m myStruct) int { 126 | patch.Unpatch() 127 | defer patch.Patch() 128 | return 41 + m.Method() 129 | }) 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | 134 | if mStruct.ValueMethod() != 42 { 135 | t.Fatal("The patch did not work") 136 | } 137 | err = patch.Unpatch() 138 | if err != nil { 139 | t.Fatal(err) 140 | } 141 | if mStruct.ValueMethod() != 1 { 142 | t.Fatal("The unpatch did not work") 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /patcher_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package mpatch 5 | 6 | import ( 7 | "reflect" 8 | "syscall" 9 | "time" 10 | "unsafe" 11 | ) 12 | 13 | var writeAccess = syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC 14 | var readAccess = syscall.PROT_READ | syscall.PROT_EXEC 15 | 16 | //go:nosplit 17 | func getMemorySliceFromUintptr(p uintptr, length int) []byte { 18 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 19 | Data: p, 20 | Len: length, 21 | Cap: length, 22 | })) 23 | } 24 | 25 | //go:nosplit 26 | func callMProtect(addr unsafe.Pointer, length int, prot int) error { 27 | for p := uintptr(addr) & ^(uintptr(pageSize - 1)); p < uintptr(addr)+uintptr(length); p += uintptr(pageSize) { 28 | page := getMemorySliceFromUintptr(p, pageSize) 29 | if err := syscall.Mprotect(page, prot); err != nil { 30 | return err 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | func writeDataToPointer(ptr unsafe.Pointer, data []byte) error { 37 | dataLength := len(data) 38 | ptrByteSlice := getMemorySliceFromPointer(ptr, len(data)) 39 | if err := callMProtect(ptr, dataLength, writeAccess); err != nil { 40 | return err 41 | } 42 | copy(ptrByteSlice, data[:]) 43 | if err := callMProtect(ptr, dataLength, readAccess); err != nil { 44 | return err 45 | } 46 | <-time.After(100 * time.Microsecond) // If we remove this line then it fails to unpatch on ARM64 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /patcher_unsupported.go: -------------------------------------------------------------------------------- 1 | //go:build !386 && !amd64 && !arm64 2 | // +build !386,!amd64,!arm64 3 | 4 | package mpatch 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "runtime" 10 | "unsafe" 11 | ) 12 | 13 | // Gets the jump function rewrite bytes 14 | // 15 | //go:nosplit 16 | func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) { 17 | return nil, errors.New(fmt.Sprintf("unsupported architecture: %s", runtime.GOARCH)) 18 | } 19 | -------------------------------------------------------------------------------- /patcher_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package mpatch 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const pageExecuteReadAndWrite = 0x40 12 | 13 | var virtualProtectProc = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect") 14 | 15 | func callVirtualProtect(lpAddress unsafe.Pointer, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error { 16 | ret, _, _ := virtualProtectProc.Call(uintptr(lpAddress), uintptr(dwSize), uintptr(flNewProtect), uintptr(lpflOldProtect)) 17 | if ret == 0 { 18 | return syscall.GetLastError() 19 | } 20 | return nil 21 | } 22 | 23 | func writeDataToPointer(ptr unsafe.Pointer, data []byte) error { 24 | var oldPerms, tmp uint32 25 | dataLength := len(data) 26 | ptrByteSlice := getMemorySliceFromPointer(ptr, len(data)) 27 | if err := callVirtualProtect(ptr, dataLength, pageExecuteReadAndWrite, unsafe.Pointer(&oldPerms)); err != nil { 28 | return err 29 | } 30 | copy(ptrByteSlice, data[:]) 31 | if err := callVirtualProtect(ptr, dataLength, oldPerms, unsafe.Pointer(&tmp)); err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /patcher_x32.go: -------------------------------------------------------------------------------- 1 | //go:build 386 2 | // +build 386 3 | 4 | package mpatch 5 | 6 | import "unsafe" 7 | 8 | // Gets the jump function rewrite bytes 9 | // 10 | //go:nosplit 11 | func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) { 12 | return []byte{ 13 | 0xBA, 14 | byte(uintptr(to)), 15 | byte(uintptr(to) >> 8), 16 | byte(uintptr(to) >> 16), 17 | byte(uintptr(to) >> 24), 18 | 0xFF, 0x22, 19 | }, nil 20 | } 21 | -------------------------------------------------------------------------------- /patcher_x64.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 2 | // +build amd64 3 | 4 | package mpatch 5 | 6 | import "unsafe" 7 | 8 | // Gets the jump function rewrite bytes 9 | // 10 | //go:nosplit 11 | func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) { 12 | return []byte{ 13 | 0x48, 0xBA, 14 | byte(uintptr(to)), 15 | byte(uintptr(to) >> 8), 16 | byte(uintptr(to) >> 16), 17 | byte(uintptr(to) >> 24), 18 | byte(uintptr(to) >> 32), 19 | byte(uintptr(to) >> 40), 20 | byte(uintptr(to) >> 48), 21 | byte(uintptr(to) >> 56), 22 | 0xFF, 0x22, 23 | }, nil 24 | } 25 | --------------------------------------------------------------------------------