├── .github └── workflows │ └── go.yml ├── LICENSE.md ├── README.md ├── examples ├── bleep.go ├── instance_example.go └── no_http.go ├── go.mod ├── monkey.go ├── monkey_386.go ├── monkey_amd64.go ├── monkey_test.go ├── replace.go ├── replace_unix.go ├── replace_windows.go └── script └── test /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 1.13 9 | uses: actions/setup-go@v1 10 | with: 11 | go-version: 1.13 12 | id: go 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@v1 15 | - name: Test 16 | run: script/test 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright Bouke van der Bijl 2 | 3 | I do not give anyone permissions to use this tool for any purpose. Don't use it. 4 | 5 | I’m not interested in changing this license. Please don’t ask. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go monkeypatching :monkey_face: :monkey: 2 | 3 | Actual arbitrary monkeypatching for Go. Yes really. 4 | 5 | Read this blogpost for an explanation on how it works: https://bou.ke/blog/monkey-patching-in-go/ 6 | 7 | ## I thought that monkeypatching in Go is impossible? 8 | 9 | It's not possible through regular language constructs, but we can always bend computers to our will! Monkey implements monkeypatching by rewriting the running executable at runtime and inserting a jump to the function you want called instead. **This is as unsafe as it sounds and I don't recommend anyone do it outside of a testing environment.** 10 | 11 | Make sure you read the notes at the bottom of the README if you intend to use this library. 12 | 13 | ## Using monkey 14 | 15 | Monkey's API is very simple and straightfoward. Call `monkey.Patch(, )` to replace a function. For example: 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "strings" 24 | 25 | "bou.ke/monkey" 26 | ) 27 | 28 | func main() { 29 | monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) { 30 | s := make([]interface{}, len(a)) 31 | for i, v := range a { 32 | s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1) 33 | } 34 | return fmt.Fprintln(os.Stdout, s...) 35 | }) 36 | fmt.Println("what the hell?") // what the *bleep*? 37 | } 38 | ``` 39 | 40 | You can then call `monkey.Unpatch()` to unpatch the method again. The replacement function can be any function value, whether it's anonymous, bound or otherwise. 41 | 42 | If you want to patch an instance method you need to use `monkey.PatchInstanceMethod(, , )`. You get the type by using `reflect.TypeOf`, and your replacement function simply takes the instance as the first argument. To disable all network connections, you can do as follows for example: 43 | 44 | ```go 45 | package main 46 | 47 | import ( 48 | "fmt" 49 | "net" 50 | "net/http" 51 | "reflect" 52 | 53 | "bou.ke/monkey" 54 | ) 55 | 56 | func main() { 57 | var d *net.Dialer // Has to be a pointer to because `Dial` has a pointer receiver 58 | monkey.PatchInstanceMethod(reflect.TypeOf(d), "Dial", func(_ *net.Dialer, _, _ string) (net.Conn, error) { 59 | return nil, fmt.Errorf("no dialing allowed") 60 | }) 61 | _, err := http.Get("http://google.com") 62 | fmt.Println(err) // Get http://google.com: no dialing allowed 63 | } 64 | 65 | ``` 66 | 67 | Note that patching the method for just one instance is currently not possible, `PatchInstanceMethod` will patch it for all instances. Don't bother trying `monkey.Patch(instance.Method, replacement)`, it won't work. `monkey.UnpatchInstanceMethod(, )` will undo `PatchInstanceMethod`. 68 | 69 | If you want to remove all currently applied monkeypatches simply call `monkey.UnpatchAll`. This could be useful in a test teardown function. 70 | 71 | If you want to call the original function from within the replacement you need to use a `monkey.PatchGuard`. A patchguard allows you to easily remove and restore the patch so you can call the original function. For example: 72 | 73 | ```go 74 | package main 75 | 76 | import ( 77 | "fmt" 78 | "net/http" 79 | "reflect" 80 | "strings" 81 | 82 | "bou.ke/monkey" 83 | ) 84 | 85 | func main() { 86 | var guard *monkey.PatchGuard 87 | guard = monkey.PatchInstanceMethod(reflect.TypeOf(http.DefaultClient), "Get", func(c *http.Client, url string) (*http.Response, error) { 88 | guard.Unpatch() 89 | defer guard.Restore() 90 | 91 | if !strings.HasPrefix(url, "https://") { 92 | return nil, fmt.Errorf("only https requests allowed") 93 | } 94 | 95 | return c.Get(url) 96 | }) 97 | 98 | _, err := http.Get("http://google.com") 99 | fmt.Println(err) // only https requests allowed 100 | resp, err := http.Get("https://google.com") 101 | fmt.Println(resp.Status, err) // 200 OK 102 | } 103 | ``` 104 | 105 | ## Notes 106 | 107 | 1. Monkey sometimes fails to patch a function if inlining is enabled. Try running your tests with inlining disabled, for example: `go test -gcflags=-l`. The same command line argument can also be used for build. 108 | 2. Monkey won't work on some security-oriented operating system that don't allow memory pages to be both write and execute at the same time. With the current approach there's not really a reliable fix for this. 109 | 3. Monkey is not threadsafe. Or any kind of safe. 110 | 4. I've tested monkey on OSX 10.10.2 and Ubuntu 14.04. It should work on any unix-based x86 or x86-64 system. 111 | 112 | © Bouke van der Bijl 113 | -------------------------------------------------------------------------------- /examples/bleep.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "bou.ke/monkey" 9 | ) 10 | 11 | func main() { 12 | monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) { 13 | s := make([]interface{}, len(a)) 14 | for i, v := range a { 15 | s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1) 16 | } 17 | return fmt.Fprintln(os.Stdout, s...) 18 | }) 19 | fmt.Println("what the hell?") 20 | } 21 | -------------------------------------------------------------------------------- /examples/instance_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "reflect" 8 | 9 | "bou.ke/monkey" 10 | ) 11 | 12 | func main() { 13 | var d *net.Dialer 14 | monkey.PatchInstanceMethod(reflect.TypeOf(d), "Dial", func(_ *net.Dialer, _, _ string) (net.Conn, error) { 15 | return nil, fmt.Errorf("no dialing allowed") 16 | }) 17 | _, err := http.Get("http://google.com") 18 | fmt.Println(err) // Get http://google.com: no dialing allowed 19 | } 20 | -------------------------------------------------------------------------------- /examples/no_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strings" 8 | 9 | "bou.ke/monkey" 10 | ) 11 | 12 | func main() { 13 | var guard *monkey.PatchGuard 14 | guard = monkey.PatchInstanceMethod(reflect.TypeOf(http.DefaultClient), "Get", func(c *http.Client, url string) (*http.Response, error) { 15 | guard.Unpatch() 16 | defer guard.Restore() 17 | 18 | if !strings.HasPrefix(url, "https://") { 19 | return nil, fmt.Errorf("only https requests allowed") 20 | } 21 | 22 | return c.Get(url) 23 | }) 24 | 25 | _, err := http.Get("http://google.com") 26 | fmt.Println(err) // only https requests allowed 27 | resp, err := http.Get("https://google.com") 28 | fmt.Println(resp.Status, err) // 200 OK 29 | } 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module bou.ke/monkey 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /monkey.go: -------------------------------------------------------------------------------- 1 | package monkey // import "bou.ke/monkey" 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | "unsafe" 8 | ) 9 | 10 | // patch is an applied patch 11 | // needed to undo a patch 12 | type patch struct { 13 | originalBytes []byte 14 | replacement *reflect.Value 15 | } 16 | 17 | var ( 18 | lock = sync.Mutex{} 19 | 20 | patches = make(map[uintptr]patch) 21 | ) 22 | 23 | type value struct { 24 | _ uintptr 25 | ptr unsafe.Pointer 26 | } 27 | 28 | func getPtr(v reflect.Value) unsafe.Pointer { 29 | return (*value)(unsafe.Pointer(&v)).ptr 30 | } 31 | 32 | type PatchGuard struct { 33 | target reflect.Value 34 | replacement reflect.Value 35 | } 36 | 37 | func (g *PatchGuard) Unpatch() { 38 | unpatchValue(g.target) 39 | } 40 | 41 | func (g *PatchGuard) Restore() { 42 | patchValue(g.target, g.replacement) 43 | } 44 | 45 | // Patch replaces a function with another 46 | func Patch(target, replacement interface{}) *PatchGuard { 47 | t := reflect.ValueOf(target) 48 | r := reflect.ValueOf(replacement) 49 | patchValue(t, r) 50 | 51 | return &PatchGuard{t, r} 52 | } 53 | 54 | // PatchInstanceMethod replaces an instance method methodName for the type target with replacement 55 | // Replacement should expect the receiver (of type target) as the first argument 56 | func PatchInstanceMethod(target reflect.Type, methodName string, replacement interface{}) *PatchGuard { 57 | m, ok := target.MethodByName(methodName) 58 | if !ok { 59 | panic(fmt.Sprintf("unknown method %s", methodName)) 60 | } 61 | r := reflect.ValueOf(replacement) 62 | patchValue(m.Func, r) 63 | 64 | return &PatchGuard{m.Func, r} 65 | } 66 | 67 | func patchValue(target, replacement reflect.Value) { 68 | lock.Lock() 69 | defer lock.Unlock() 70 | 71 | if target.Kind() != reflect.Func { 72 | panic("target has to be a Func") 73 | } 74 | 75 | if replacement.Kind() != reflect.Func { 76 | panic("replacement has to be a Func") 77 | } 78 | 79 | if target.Type() != replacement.Type() { 80 | panic(fmt.Sprintf("target and replacement have to have the same type %s != %s", target.Type(), replacement.Type())) 81 | } 82 | 83 | if patch, ok := patches[target.Pointer()]; ok { 84 | unpatch(target.Pointer(), patch) 85 | } 86 | 87 | bytes := replaceFunction(target.Pointer(), (uintptr)(getPtr(replacement))) 88 | patches[target.Pointer()] = patch{bytes, &replacement} 89 | } 90 | 91 | // Unpatch removes any monkey patches on target 92 | // returns whether target was patched in the first place 93 | func Unpatch(target interface{}) bool { 94 | return unpatchValue(reflect.ValueOf(target)) 95 | } 96 | 97 | // UnpatchInstanceMethod removes the patch on methodName of the target 98 | // returns whether it was patched in the first place 99 | func UnpatchInstanceMethod(target reflect.Type, methodName string) bool { 100 | m, ok := target.MethodByName(methodName) 101 | if !ok { 102 | panic(fmt.Sprintf("unknown method %s", methodName)) 103 | } 104 | return unpatchValue(m.Func) 105 | } 106 | 107 | // UnpatchAll removes all applied monkeypatches 108 | func UnpatchAll() { 109 | lock.Lock() 110 | defer lock.Unlock() 111 | for target, p := range patches { 112 | unpatch(target, p) 113 | delete(patches, target) 114 | } 115 | } 116 | 117 | // Unpatch removes a monkeypatch from the specified function 118 | // returns whether the function was patched in the first place 119 | func unpatchValue(target reflect.Value) bool { 120 | lock.Lock() 121 | defer lock.Unlock() 122 | patch, ok := patches[target.Pointer()] 123 | if !ok { 124 | return false 125 | } 126 | unpatch(target.Pointer(), patch) 127 | delete(patches, target.Pointer()) 128 | return true 129 | } 130 | 131 | func unpatch(target uintptr, p patch) { 132 | copyToLocation(target, p.originalBytes) 133 | } 134 | -------------------------------------------------------------------------------- /monkey_386.go: -------------------------------------------------------------------------------- 1 | package monkey 2 | 3 | // Assembles a jump to a function value 4 | func jmpToFunctionValue(to uintptr) []byte { 5 | return []byte{ 6 | 0xBA, 7 | byte(to), 8 | byte(to >> 8), 9 | byte(to >> 16), 10 | byte(to >> 24), // mov edx,to 11 | 0xFF, 0x22, // jmp DWORD PTR [edx] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /monkey_amd64.go: -------------------------------------------------------------------------------- 1 | package monkey 2 | 3 | // Assembles a jump to a function value 4 | func jmpToFunctionValue(to uintptr) []byte { 5 | return []byte{ 6 | 0x48, 0xBA, 7 | byte(to), 8 | byte(to >> 8), 9 | byte(to >> 16), 10 | byte(to >> 24), 11 | byte(to >> 32), 12 | byte(to >> 40), 13 | byte(to >> 48), 14 | byte(to >> 56), // movabs rdx,to 15 | 0xFF, 0x22, // jmp QWORD PTR [rdx] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /monkey_test.go: -------------------------------------------------------------------------------- 1 | package monkey_test 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "testing" 7 | "time" 8 | 9 | "bou.ke/monkey" 10 | ) 11 | 12 | func no() bool { return false } 13 | func yes() bool { return true } 14 | 15 | func TestTimePatch(t *testing.T) { 16 | before := time.Now() 17 | monkey.Patch(time.Now, func() time.Time { 18 | return time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) 19 | }) 20 | during := time.Now() 21 | assert(t, monkey.Unpatch(time.Now)) 22 | after := time.Now() 23 | 24 | assert(t, time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) == during) 25 | assert(t, before != during) 26 | assert(t, during != after) 27 | } 28 | 29 | func TestGC(t *testing.T) { 30 | value := true 31 | monkey.Patch(no, func() bool { 32 | return value 33 | }) 34 | defer monkey.UnpatchAll() 35 | runtime.GC() 36 | assert(t, no()) 37 | } 38 | 39 | func TestSimple(t *testing.T) { 40 | assert(t, !no()) 41 | monkey.Patch(no, yes) 42 | assert(t, no()) 43 | assert(t, monkey.Unpatch(no)) 44 | assert(t, !no()) 45 | assert(t, !monkey.Unpatch(no)) 46 | } 47 | 48 | func TestGuard(t *testing.T) { 49 | var guard *monkey.PatchGuard 50 | guard = monkey.Patch(no, func() bool { 51 | guard.Unpatch() 52 | defer guard.Restore() 53 | return !no() 54 | }) 55 | for i := 0; i < 100; i++ { 56 | assert(t, no()) 57 | } 58 | monkey.Unpatch(no) 59 | } 60 | 61 | func TestUnpatchAll(t *testing.T) { 62 | assert(t, !no()) 63 | monkey.Patch(no, yes) 64 | assert(t, no()) 65 | monkey.UnpatchAll() 66 | assert(t, !no()) 67 | } 68 | 69 | type s struct{} 70 | 71 | func (s *s) yes() bool { return true } 72 | 73 | func TestWithInstanceMethod(t *testing.T) { 74 | i := &s{} 75 | 76 | assert(t, !no()) 77 | monkey.Patch(no, i.yes) 78 | assert(t, no()) 79 | monkey.Unpatch(no) 80 | assert(t, !no()) 81 | } 82 | 83 | type f struct{} 84 | 85 | func (f *f) No() bool { return false } 86 | 87 | func TestOnInstanceMethod(t *testing.T) { 88 | i := &f{} 89 | assert(t, !i.No()) 90 | monkey.PatchInstanceMethod(reflect.TypeOf(i), "No", func(_ *f) bool { return true }) 91 | assert(t, i.No()) 92 | assert(t, monkey.UnpatchInstanceMethod(reflect.TypeOf(i), "No")) 93 | assert(t, !i.No()) 94 | } 95 | 96 | func TestNotFunction(t *testing.T) { 97 | panics(t, func() { 98 | monkey.Patch(no, 1) 99 | }) 100 | panics(t, func() { 101 | monkey.Patch(1, yes) 102 | }) 103 | } 104 | 105 | func TestNotCompatible(t *testing.T) { 106 | panics(t, func() { 107 | monkey.Patch(no, func() {}) 108 | }) 109 | } 110 | 111 | func assert(t *testing.T, b bool, args ...interface{}) { 112 | t.Helper() 113 | if !b { 114 | t.Fatal(append([]interface{}{"assertion failed"}, args...)) 115 | } 116 | } 117 | 118 | func panics(t *testing.T, f func()) { 119 | t.Helper() 120 | defer func() { 121 | t.Helper() 122 | if v := recover(); v == nil { 123 | t.Fatal("expected panic") 124 | } 125 | }() 126 | f() 127 | } 128 | -------------------------------------------------------------------------------- /replace.go: -------------------------------------------------------------------------------- 1 | package monkey 2 | 3 | import ( 4 | "reflect" 5 | "syscall" 6 | "unsafe" 7 | ) 8 | 9 | func rawMemoryAccess(p uintptr, length int) []byte { 10 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 11 | Data: p, 12 | Len: length, 13 | Cap: length, 14 | })) 15 | } 16 | 17 | func pageStart(ptr uintptr) uintptr { 18 | return ptr & ^(uintptr(syscall.Getpagesize() - 1)) 19 | } 20 | 21 | // from is a pointer to the actual function 22 | // to is a pointer to a go funcvalue 23 | func replaceFunction(from, to uintptr) (original []byte) { 24 | jumpData := jmpToFunctionValue(to) 25 | f := rawMemoryAccess(from, len(jumpData)) 26 | original = make([]byte, len(f)) 27 | copy(original, f) 28 | 29 | copyToLocation(from, jumpData) 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /replace_unix.go: -------------------------------------------------------------------------------- 1 | //+build !windows 2 | 3 | package monkey 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func mprotectCrossPage(addr uintptr, length int, prot int) { 10 | pageSize := syscall.Getpagesize() 11 | for p := pageStart(addr); p < addr + uintptr(length); p += uintptr(pageSize) { 12 | page := rawMemoryAccess(p, pageSize) 13 | err := syscall.Mprotect(page, prot) 14 | if err != nil { 15 | panic(err) 16 | } 17 | } 18 | } 19 | 20 | // this function is super unsafe 21 | // aww yeah 22 | // It copies a slice to a raw memory location, disabling all memory protection before doing so. 23 | func copyToLocation(location uintptr, data []byte) { 24 | f := rawMemoryAccess(location, len(data)) 25 | 26 | mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC) 27 | copy(f, data[:]) 28 | mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_EXEC) 29 | } 30 | -------------------------------------------------------------------------------- /replace_windows.go: -------------------------------------------------------------------------------- 1 | package monkey 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | const PAGE_EXECUTE_READWRITE = 0x40 9 | 10 | var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect") 11 | 12 | func virtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error { 13 | ret, _, _ := procVirtualProtect.Call( 14 | lpAddress, 15 | uintptr(dwSize), 16 | uintptr(flNewProtect), 17 | uintptr(lpflOldProtect)) 18 | if ret == 0 { 19 | return syscall.GetLastError() 20 | } 21 | return nil 22 | } 23 | 24 | // this function is super unsafe 25 | // aww yeah 26 | // It copies a slice to a raw memory location, disabling all memory protection before doing so. 27 | func copyToLocation(location uintptr, data []byte) { 28 | f := rawMemoryAccess(location, len(data)) 29 | 30 | var oldPerms uint32 31 | err := virtualProtect(location, len(data), PAGE_EXECUTE_READWRITE, unsafe.Pointer(&oldPerms)) 32 | if err != nil { 33 | panic(err) 34 | } 35 | copy(f, data[:]) 36 | 37 | // VirtualProtect requires you to pass in a pointer which it can write the 38 | // current memory protection permissions to, even if you don't want them. 39 | var tmp uint32 40 | err = virtualProtect(location, len(data), oldPerms, unsafe.Pointer(&tmp)) 41 | if err != nil { 42 | panic(err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | go test -v -gcflags=-l 3 | --------------------------------------------------------------------------------