├── .gitignore ├── go.mod ├── bug.sh ├── utility_darwin.go ├── utility_linux.go ├── .github └── workflows │ └── go.yml ├── example ├── unwind.go ├── example2.go ├── example.go ├── example3.go └── main.go ├── go.sum ├── utility_unix.go ├── LICENSE ├── utility_windows.go ├── azure-pipelines.yml ├── elf_helper.go ├── small_func_test.go ├── README.md ├── hook.go ├── hook_test.go ├── utility.go ├── func_stack_test.go ├── inplace_fix_test.go ├── inst_fix_test.go └── arch_util.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.log 3 | *.swo 4 | *.swn 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/brahma-adshonor/gohook 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/stretchr/testify v1.3.0 7 | golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c 8 | ) 9 | -------------------------------------------------------------------------------- /bug.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | for i in {1..10000};do 4 | go test -gcflags=all='-l' -cover -o t 5 | if [[ "$?" != "0" ]]; then 6 | exit 233 7 | fi 8 | echo "@@@@@@@@@@@@@ ${i}th run done @@@@@@@@@@@@@@@" 9 | done 10 | -------------------------------------------------------------------------------- /utility_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | package gohook 3 | 4 | var ( 5 | defaultFuncPrologue64 = []byte{0x65, 0x48, 0x8b, 0x0c, 0x25, 0x30, 0x00, 0x00, 0x00, 0x48} 6 | defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff} 7 | ) 8 | -------------------------------------------------------------------------------- /utility_linux.go: -------------------------------------------------------------------------------- 1 | //+build linux 2 | 3 | package gohook 4 | 5 | var ( 6 | defaultFuncPrologue64 = []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48} 7 | defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff} 8 | ) 9 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.12 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.12 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | if [ -f Gopkg.toml ]; then 23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 24 | dep ensure 25 | fi 26 | 27 | - name: Build 28 | run: go build -v . 29 | 30 | - name: Test 31 | run: go test -v . 32 | -------------------------------------------------------------------------------- /example/unwind.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/brahma-adshonor/gohook" 6 | ) 7 | 8 | //go:noinline 9 | func bad_func(k int) int { 10 | if k <= 0 { 11 | panic("bad func vomit\n") 12 | } 13 | 14 | return 23 + k 15 | } 16 | 17 | //go:noinline 18 | func foo(k int) int { 19 | return k + bad_func(k) 20 | } 21 | 22 | //go:noinline 23 | func goo(k int) int { 24 | k = -1000 + k 25 | return foo_trampoline(k) 26 | } 27 | 28 | func main() { 29 | defer func() { 30 | if err := recover(); err != nil { 31 | fmt.Printf("recover from panic, err:%s\n", err) 32 | } 33 | }() 34 | 35 | gohook.Hook(foo, goo, foo_trampoline) 36 | fmt.Printf("hook info:%s\n", gohook.ShowDebugInfo()) 37 | foo(-22) 38 | } 39 | 40 | //go:noinline 41 | func foo_trampoline(k int) int { 42 | defer func(k int) { fmt.Printf("k:%d\n", k)}(k) 43 | 44 | k = -1000 + k 45 | return foo_trampoline(k) 46 | } 47 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 7 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 8 | golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c h1:Rx/HTKi09myZ25t1SOlDHmHOy/mKxNAcu0hP1oPX9qM= 9 | golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 10 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 11 | -------------------------------------------------------------------------------- /example/example2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "github.com/brahma-adshonor/gohook" 7 | ) 8 | 9 | func myPrintln(a ...interface{}) (n int, err error) { 10 | fmt.Fprintln(os.Stdout, "before real Printfln") 11 | return myPrintlnTramp(a...) 12 | } 13 | 14 | //go:noinline 15 | func myPrintlnTramp(a ...interface{}) (n int, err error) { 16 | // a dummy function to make room for a shadow copy of the original function. 17 | // it doesn't matter what we do here, just to create an adequate size function. 18 | myPrintlnTramp(a...) 19 | myPrintlnTramp(a...) 20 | myPrintlnTramp(a...) 21 | 22 | for { 23 | fmt.Printf("hello") 24 | } 25 | 26 | return 0, nil 27 | } 28 | 29 | func main() { 30 | gohook.Hook(fmt.Println, myPrintln, myPrintlnTramp) 31 | fn := fmt.Println 32 | fn("hello world!") 33 | fmt.Println("hello world!") 34 | 35 | fmt.Printf("debug info:\n%s\n", gohook.ShowDebugInfo()) 36 | } 37 | -------------------------------------------------------------------------------- /utility_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package gohook 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func getPageAddr(ptr uintptr) uintptr { 10 | return ptr & ^(uintptr(syscall.Getpagesize() - 1)) 11 | } 12 | 13 | func setPageWritable(addr uintptr, length int, prot int) { 14 | pageSize := syscall.Getpagesize() 15 | for p := getPageAddr(addr); p < addr+uintptr(length); p += uintptr(pageSize) { 16 | page := makeSliceFromPointer(p, pageSize) 17 | err := syscall.Mprotect(page, prot) 18 | if err != nil { 19 | panic(err) 20 | } 21 | } 22 | } 23 | 24 | func CopyInstruction(location uintptr, data []byte) { 25 | f := makeSliceFromPointer(location, len(data)) 26 | setPageWritable(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC) 27 | sz := copy(f, data[:]) 28 | setPageWritable(location, len(data), syscall.PROT_READ|syscall.PROT_EXEC) 29 | if sz != len(data) { 30 | panic("copy instruction to target failed") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 brahma-adshonor 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 | -------------------------------------------------------------------------------- /utility_windows.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | var ( 9 | defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff} 10 | defaultFuncPrologue64 = []byte{0x65, 0x48, 0x8B, 0x0C, 0x25, 0x28, 0x00, 0x00, 0x00} 11 | ) 12 | 13 | const PAGE_EXECUTE_READWRITE = 0x40 14 | 15 | var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect") 16 | 17 | func virtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error { 18 | ret, _, _ := procVirtualProtect.Call( 19 | lpAddress, 20 | uintptr(dwSize), 21 | uintptr(flNewProtect), 22 | uintptr(lpflOldProtect)) 23 | if ret == 0 { 24 | return syscall.GetLastError() 25 | } 26 | return nil 27 | } 28 | 29 | func CopyInstruction(location uintptr, data []byte) { 30 | f := makeSliceFromPointer(location, len(data)) 31 | 32 | var oldPerms uint32 33 | err := virtualProtect(location, len(data), PAGE_EXECUTE_READWRITE, unsafe.Pointer(&oldPerms)) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | copy(f, data[:]) 39 | 40 | var tmp uint32 41 | err = virtualProtect(location, len(data), oldPerms, unsafe.Pointer(&tmp)) 42 | if err != nil { 43 | panic(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Go 2 | # Build your Go project. 3 | # Add steps that test, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/go 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | variables: 13 | GOBIN: '$(GOPATH)/bin' # Go binaries path 14 | GOROOT: '/usr/local/go1.11' # Go installation path 15 | GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path 16 | modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code 17 | 18 | steps: 19 | - script: | 20 | mkdir -p '$(GOBIN)' 21 | mkdir -p '$(GOPATH)/pkg' 22 | mkdir -p '$(modulePath)' 23 | shopt -s extglob 24 | shopt -s dotglob 25 | mv !(gopath) '$(modulePath)' 26 | echo '##vso[task.prependpath]$(GOBIN)' 27 | echo '##vso[task.prependpath]$(GOROOT)/bin' 28 | displayName: 'Set up the Go workspace' 29 | 30 | - script: | 31 | go version 32 | go get -v -t -d ./... 33 | if [ -f Gopkg.toml ]; then 34 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 35 | dep ensure 36 | fi 37 | go build -v . 38 | workingDirectory: '$(modulePath)' 39 | displayName: 'Get dependencies, then build' 40 | 41 | - script: | 42 | go test -v . 43 | GOARCH=386 go test -v . 44 | workingDirectory: '$(modulePath)' 45 | displayName: 'run unittest' 46 | 47 | - script: | 48 | GO111MODULE=on go run example/main.go && 49 | GO111MODULE=on go run example/unwind.go && 50 | GO111MODULE=on go run example/example.go && 51 | GO111MODULE=on go run example/example2.go && 52 | GO111MODULE=on go run example/example3.go 53 | workingDirectory: '$(modulePath)' 54 | displayName: 'run example' 55 | -------------------------------------------------------------------------------- /elf_helper.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "debug/elf" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | "sort" 9 | ) 10 | 11 | var ( 12 | curExecutable, _ = filepath.Abs(os.Args[0]) 13 | ) 14 | 15 | type SymbolSlice []elf.Symbol 16 | 17 | func (a SymbolSlice) Len() int { return len(a) } 18 | func (a SymbolSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 19 | func (a SymbolSlice) Less(i, j int) bool { return a[i].Value < a[j].Value } 20 | 21 | type ElfInfo struct { 22 | CurFile string 23 | Symbol SymbolSlice 24 | } 25 | 26 | func NewElfInfo() (*ElfInfo, error) { 27 | ei := &ElfInfo{CurFile: curExecutable} 28 | err := ei.init() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return ei, nil 34 | } 35 | 36 | func (ei *ElfInfo) init() error { 37 | f, err := elf.Open(ei.CurFile) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | defer f.Close() 43 | 44 | var sym []elf.Symbol 45 | sym, err = f.Symbols() 46 | ei.Symbol = make(SymbolSlice, 0, len(sym)) 47 | 48 | for _, v := range sym { 49 | if v.Size > 0 { 50 | ei.Symbol = append(ei.Symbol, v) 51 | } 52 | } 53 | 54 | if err != nil { 55 | return err 56 | } 57 | 58 | sort.Sort(ei.Symbol) 59 | return nil 60 | } 61 | 62 | func (ei *ElfInfo) GetFuncSize(addr uintptr) (uint32, error) { 63 | if ei.Symbol == nil { 64 | return 0, errors.New("no symbol") 65 | } 66 | 67 | i := sort.Search(len(ei.Symbol), func(i int) bool { return ei.Symbol[i].Value >= uint64(addr) }) 68 | if i < len(ei.Symbol) && ei.Symbol[i].Value == uint64(addr) { 69 | //fmt.Printf("addr:0x%x,value:0x%x, sz:%d\n", addr, ei.Symbol[i].Value, ei.Symbol[i].Size) 70 | return uint32(ei.Symbol[i].Size), nil 71 | } 72 | 73 | //fmt.Printf("not find elf\n") 74 | return 0, errors.New("can not find func") 75 | } 76 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/brahma-adshonor/gohook" 7 | ) 8 | 9 | //go:noinline 10 | func foo1(v1 int, v2 string) int { 11 | fmt.Printf("foo1:%d(%s)\n", v1, v2) 12 | return v1 + 42 13 | } 14 | 15 | func foo2(v1 int, v2 string) int { 16 | fmt.Printf("foo2:%d(%s)\n", v1, v2) 17 | v1 = foo3(100, "not calling foo3") 18 | return v1 + 4200 19 | } 20 | 21 | //go:noinline 22 | func foo3(v1 int, v2 string) int { 23 | fmt.Printf("foo3:%d(%s)\n", v1, v2) 24 | return v1 + 10000 25 | } 26 | 27 | func hookFunc() { 28 | ret1 := foo1(23, "miliao for foo1 before hook") 29 | 30 | err := gohook.Hook(foo1, foo2, foo3) 31 | 32 | fmt.Printf("hook done\n") 33 | if err != nil { 34 | fmt.Printf("err:%s\n", err.Error()) 35 | return 36 | } 37 | 38 | ret2 := foo1(23, "miliao for foo1 after hook") 39 | 40 | fmt.Printf("r1:%d, r2:%d\n", ret1, ret2) 41 | } 42 | 43 | // hook instance method 44 | func myBuffWriteString(b *bytes.Buffer, s string) (int, error) { 45 | fmt.Printf("fake buffer WriteString func, s:%s\n", s) 46 | l, _ := myBuffWriteStringTramp(b, s) 47 | return 1000 + l, nil 48 | } 49 | 50 | //go:noinline 51 | func myBuffWriteStringTramp(b *bytes.Buffer, s string) (int, error) { 52 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 53 | return 0, nil 54 | } 55 | 56 | func myBuffLen(b *bytes.Buffer) int { 57 | fmt.Println("calling myBuffLen") 58 | return 233 + myBuffLenTramp(b) 59 | } 60 | 61 | //go:noinline 62 | func myBuffLenTramp(b *bytes.Buffer) int { 63 | fmt.Println("calling myBuffLenTramp1") 64 | fmt.Println("calling myBuffLenTramp2") 65 | fmt.Println("calling myBuffLenTramp3") 66 | fmt.Println("calling myBuffLenTramp4") 67 | return 1000 68 | } 69 | 70 | func hookMethod() { 71 | buff := bytes.NewBufferString("abcd:") 72 | err1 := gohook.HookMethod(buff, "WriteString", myBuffWriteString, myBuffWriteStringTramp) 73 | if err1 != nil { 74 | fmt.Printf("hook WriteString() fail, err:%s\n", err1.Error()) 75 | return 76 | } 77 | buff.WriteString("hook by miliao") 78 | fmt.Printf("value of buff:%s\n", buff.String()) 79 | 80 | sz1 := buff.Len() 81 | 82 | fmt.Printf("try hook bytes.Buffer.Len()\n") 83 | err2 := gohook.HookMethod(buff, "Len", myBuffLen, myBuffLenTramp) 84 | if err2 != nil { 85 | fmt.Printf("hook Len() fail, err:%s\n", err2.Error()) 86 | return 87 | } 88 | 89 | sz2 := buff.Len() 90 | sz3 := myBuffLenTramp(buff) 91 | 92 | gohook.UnHookMethod(buff, "Len") 93 | 94 | sz4 := myBuffLen(buff) 95 | fmt.Printf("old sz:%d, new sz:%d, copy func:%d, recover:%d\n", sz1, sz2, sz3, sz4) 96 | } 97 | 98 | func main() { 99 | fmt.Printf("start testing...\n") 100 | 101 | hookFunc() 102 | hookMethod() 103 | 104 | fmt.Printf("debug info:\n%s\n", gohook.ShowDebugInfo()) 105 | } 106 | -------------------------------------------------------------------------------- /small_func_test.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | //go:noinline 12 | func foo_short_call(a int) (int, error) { 13 | //fmt.Printf("calling short call origin func\n") 14 | return 3 + foo_short_call2(a), nil 15 | } 16 | 17 | //go:noinline 18 | func foo_short_call2(a int) int { 19 | fmt.Printf("in short call2\n") 20 | return 3 + a 21 | } 22 | 23 | //go:noinline 24 | func foo_short_call_replace(a int) (int, error) { 25 | fmt.Printf("calling short call replace func\n") 26 | r, _ := foo_short_call_trampoline(a) 27 | return a + 1000 + r, nil 28 | } 29 | 30 | func dummy_delimiter(id string) string { 31 | for { 32 | fmt.Printf("calling victim trampoline") 33 | if id == "miliao" { 34 | return "done" 35 | } 36 | break 37 | } 38 | 39 | ret := "miliao" 40 | ret += foo_for_inplace_fix("test") 41 | ret += foo_for_inplace_fix("test") 42 | ret += foo_for_inplace_fix("test") 43 | ret += foo_for_inplace_fix("test") 44 | 45 | fmt.Printf("len1:%d\n", len(id)) 46 | fmt.Printf("len2:%d\n", len(ret)) 47 | 48 | ret += foo_for_inplace_fix_delimiter(id) 49 | 50 | return id + ret 51 | } 52 | 53 | //go:noinline 54 | func foo_short_call_trampoline(a int) (int, error) { 55 | for { 56 | fmt.Printf("printing a:%d\n", a) 57 | a++ 58 | if a > 233 { 59 | fmt.Printf("done printing a:%d\n", a) 60 | break 61 | } 62 | } 63 | 64 | dummy_delimiter("miliao") 65 | 66 | return a + 233, nil 67 | } 68 | 69 | func TestShortCall(t *testing.T) { 70 | r, _ := foo_short_call(32) 71 | assert.Equal(t, 38, r) 72 | 73 | addr := GetFuncAddr(foo_short_call) 74 | sz1 := GetFuncInstSize(foo_short_call) 75 | addr2 := addr + uintptr(sz1) 76 | fmt.Printf("start hook real short call func, start:%x, end:%x\n", addr, addr2) 77 | 78 | err := Hook(foo_short_call, foo_short_call_replace, foo_short_call_trampoline) 79 | assert.Nil(t, err) 80 | 81 | r1, _ := foo_short_call(22) 82 | assert.Equal(t, 1050, r1) 83 | 84 | UnHook(foo_short_call) 85 | 86 | r2, _ := foo_short_call(32) 87 | assert.Equal(t, 38, r2) 88 | 89 | code := make([]byte, 0, sz1) 90 | for i := 0; i < int(sz1); i++ { 91 | code = append(code, 0x90) 92 | } 93 | 94 | code1 := []byte{0xeb, 0x4} 95 | code2 := []byte{0xeb, 0x5} 96 | 97 | copy(code, code1) 98 | copy(code[2:], code2) 99 | 100 | ret := sz1 - 5 101 | jmp1 := sz1 - 4 102 | jmp2 := sz1 - 2 103 | 104 | if sz1 > 0x7f { 105 | ret = 0x70 - 5 106 | jmp1 = 0x70 - 4 107 | jmp2 = 0x70 - 2 108 | } 109 | 110 | code[ret] = byte(0xc3) 111 | 112 | code3 := []byte{0xeb, byte(-jmp1 - 2)} 113 | code4 := []byte{0xeb, byte(-jmp2 - 2)} 114 | 115 | copy(code[jmp1:], code3) 116 | copy(code[jmp2:], code4) 117 | 118 | assert.Equal(t, code[:4], append(code1, code2...)) 119 | 120 | CopyInstruction(addr, code) 121 | 122 | err = Hook(foo_short_call, foo_short_call_replace, foo_short_call_trampoline) 123 | assert.Nil(t, err) 124 | 125 | fmt.Printf("fix code for foo_short_call:\n%s\n", ShowDebugInfo()) 126 | 127 | foo_short_call(22) 128 | 129 | addr3 := addr2 + uintptr(2) 130 | fc := runtime.FuncForPC(addr3) 131 | 132 | assert.NotNil(t, fc) 133 | 134 | fmt.Printf("func name get from addr beyond scope:%s\n", fc.Name()) 135 | assert.Equal(t, addr, fc.Entry()) 136 | 137 | f, l := fc.FileLine(addr2 + uintptr(3)) 138 | assert.Equal(t, 0, l) 139 | assert.Equal(t, "?", f) 140 | fmt.Printf("file:%s, line:%d\n", f, l) 141 | } 142 | -------------------------------------------------------------------------------- /example/example3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/brahma-adshonor/gohook" 6 | ) 7 | 8 | type base struct { 9 | id int 10 | } 11 | 12 | type advance struct { 13 | base 14 | name string 15 | } 16 | 17 | type who interface { 18 | Id() int 19 | } 20 | 21 | //go:noinline 22 | func (b *base) Id() int { 23 | fmt.Printf("calling base.Id()\n") 24 | return b.id 25 | } 26 | 27 | //go:noinline 28 | func (a *advance) Name() string { 29 | return fmt.Sprintf("%d@%s", a.Id(), a.name) 30 | } 31 | 32 | func MyId(a *base) int { 33 | fmt.Printf("in fake MyId() base\n") 34 | return MyIdTrampoline(a) + 1000 35 | } 36 | 37 | //go:noinline 38 | func MyIdTrampoline(a *base) int { 39 | fmt.Printf("abccccc") 40 | fmt.Printf("abccccc") 41 | fmt.Printf("abccccc") 42 | fmt.Printf("abccccc") 43 | 44 | for { 45 | if a != nil { 46 | fmt.Printf("bbbbbb") 47 | } else { 48 | fmt.Printf("cbbcbb") 49 | } 50 | } 51 | 52 | return 233 53 | } 54 | 55 | func MyId2(a *advance) int { 56 | fmt.Printf("in fake MyId() advance\n") 57 | return MyIdTrampoline2(a) + 5000 58 | } 59 | 60 | //go:noinline 61 | func MyIdTrampoline2(a *advance) int { 62 | fmt.Printf("abccccc") 63 | fmt.Printf("abccccc") 64 | fmt.Printf("abccccc") 65 | fmt.Printf("abccccc") 66 | 67 | for { 68 | if a != nil { 69 | fmt.Printf("bbbbbb") 70 | } else { 71 | fmt.Printf("cbbcbb") 72 | } 73 | } 74 | 75 | return 233 76 | } 77 | 78 | func get_id_from(v who) int { 79 | return v.Id() 80 | } 81 | 82 | func main() { 83 | a := &advance{base:base{id:23},name:"miliao"} 84 | fmt.Printf("before hook advance, id:%d\n", a.Id()) 85 | fmt.Printf("before hook advance, id from interface:%d\n", get_id_from(a)) 86 | 87 | err := gohook.HookMethod(a, "Id", MyId2, MyIdTrampoline2) 88 | if err != nil { 89 | panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error())) 90 | } 91 | 92 | fmt.Printf("after hook advance, id:%d\n", a.Id()) 93 | fmt.Printf("after hook advance, id from interface:%d\n", get_id_from(a)) 94 | 95 | b := &base{id:333} 96 | err2 := gohook.HookMethod(b, "Id", MyId, MyIdTrampoline) 97 | if err2 != nil { 98 | panic(fmt.Sprintf("Hook base instance method failed:%s", err2.Error())) 99 | } 100 | 101 | fmt.Printf("after hook base, id:%d\n", a.Id()) 102 | fmt.Printf("after hook base, id from interface:%d\n", get_id_from(a)) 103 | 104 | fmt.Printf("debug info:\n%s\n", gohook.ShowDebugInfo()) 105 | 106 | fmt.Printf("method value by value:%v, by type:%v\n", a.Id, (*advance).Id) 107 | 108 | gohook.UnHookMethod(a, "Id") 109 | gohook.UnHookMethod(b, "Id") 110 | 111 | // (*advance.Id has the type of func(*advance)()) 112 | // (a.Id has the type of func()()) 113 | // so a.Id is a closure wrappiing 'a' as the first argument to function the advance.Id() 114 | 115 | err3 := gohook.Hook((*advance).Id, MyId2, MyIdTrampoline2) 116 | if err3 != nil { 117 | panic(fmt.Sprintf("hook method by method type failed\n", err3.Error())) 118 | } 119 | 120 | fmt.Printf("after hook advance by method type, id:%d\n", a.Id()) 121 | 122 | err4 := gohook.Hook(a.Id, MyId2, MyIdTrampoline2) 123 | if err4 != nil { 124 | fmt.Printf("hook method by method value failed:%s\n", err4.Error()) 125 | } 126 | 127 | fmt.Printf("debug info:\n%s\n", gohook.ShowDebugInfo()) 128 | 129 | call_base_id(b) 130 | call_advance_id(a) 131 | } 132 | 133 | func call_advance_id(a *advance) { 134 | a.Id() 135 | } 136 | 137 | func call_base_id(b *base) { 138 | b.Id() 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://kmalloc.visualstudio.com/ink/_apis/build/status/kmalloc.gohook?branchName=master)](https://kmalloc.visualstudio.com/ink/_build/latest?definitionId=1&branchName=master) 2 | 3 | ## Gohook 4 | 5 | A funny library to hook golang function dynamically at runtime, enabling functionality like patching in dynamic language. 6 | 7 | The most significant feature this library provided that makes it distinguished from others is that it supports calling back to the original function. 8 | 9 | Read following blogpost for further explanation of the implementation detail: [1](https://www.cnblogs.com/catch/p/10973611.html),[2](https://onedrive.live.com/View.aspx?resid=7804A3BDAEB13A9F!58083&authkey=!AKVlLS9s9KYh07s) 10 | 11 | ## How it works 12 | 13 | The general idea of this library is that gohook will find out the address of a go function and then insert a few jump instructions to redirect execution flow to the new function. 14 | 15 | there are a few steps to perform a hook: 16 | 17 | 1. find out the address of a function, this can be accomplished by standard reflect library. 18 | 2. inject jump code into target function, with carefully crafted binary instruction. 19 | 3. implement trampoline function to enable calling back to the original function. 20 | 21 | It may seem risky and dangerous to perform operations like these at first glance, but this is actually common practice in c/c++ though, you can google it, search for "hot patching" something like that for more information. 22 | 23 | ## Using gohook 24 | 25 | 5 api are exported from this library, the signatures are simple as illustrated following: 26 | 27 | 1. `func Hook(target, replace, trampoline interface{}) error;` 28 | 2. `func UnHook(target interface{}) error;` 29 | 3. `func HookByIndirectJmp(target, replace, trampoline interface{});` 30 | 4. `func HookMethod(instance interface{}, method string, replace, trampoline interface{}) error;` 31 | 5. `func UnHookMethod(instance interface{}, method string) error;` 32 | 33 | The first 3 functions are used to hook/unhook regular functions, the rest are for instance method, as the naming implies(essentially, HookMethod(obj,x,y,z) is the same as Hook(ObjType.x,y,z)). 34 | 35 | Basically, you can just call `gohook.Hook(fmt.Printf, myPrintf, myPrintfTramp)` to hook the fmt.Printf in the standard library. 36 | 37 | Trampolines here serves as a shadow function after the target function is hooked, think of it as a copy of the original target function. 38 | 39 | In situation where calling back to the original function is not needed, trampoline can be passed a nil value. 40 | 41 | HookByIndirectJmp() differs from Hook() in that it uses rdx to perform an indirect jump from a funcval, and: 42 | 43 | 1. `rdx is the context register used by compiler to access funcval.` 44 | 2. `funcval contains extra information for a closure, which is used by compiler and runtime.` 45 | 46 | this makes it possible to hook closure function and function created by reflect.MakeFunc(), **in a less compatible way**, since the implementaion of this hook has to guess the memory layout of a reflect.Value object, which may vary from different version of runtime. 47 | 48 | ```go 49 | package main 50 | 51 | import ( 52 | "fmt" 53 | "os" 54 | "github.com/kmalloc/gohook" 55 | ) 56 | 57 | func myPrintln(a ...interface{}) (n int, err error) { 58 | fmt.Fprintln(os.Stdout, "before real Printfln") 59 | return myPrintlnTramp(a...) 60 | } 61 | 62 | func myPrintlnTramp(a ...interface{}) (n int, err error) { 63 | // a dummy function to make room for a shadow copy of the original function. 64 | // it doesn't matter what we do here, just to create an addressable function with adequate size. 65 | myPrintlnTramp(a...) 66 | myPrintlnTramp(a...) 67 | myPrintlnTramp(a...) 68 | 69 | for { 70 | fmt.Printf("hello") 71 | } 72 | 73 | return 0, nil 74 | } 75 | 76 | func main() { 77 | gohook.Hook(fmt.Println, myPrintln, myPrintlnTramp) 78 | fmt.Println("hello world!") 79 | } 80 | ``` 81 | 82 | For more usage example, please refer to the example folder. 83 | 84 | ## Notes 85 | 86 | 1. 32 bit mode may not work, far jump is not handled. 87 | 2. trampoline is used to make room for the original function, it will be overwrited. 88 | 3. in case of small function which may be inlined, gohook may fail: 89 | - disable inlining by passig -gcflags=-l to build cmd. 90 | 4. this library is created for integrated testing, and not fully tested in production(yet), user discretion is advised. 91 | 5. escape analysis may be influenced: 92 | - deep copy arguments if you need to copy argument from replacement function(see func_stack_test.go). 93 | - escape those arguments from trampoline(by passing it to a goroutine or to other function that can escape it) 94 | if that argument is allocated from the replacement function. 95 | -------------------------------------------------------------------------------- /hook.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "unsafe" 9 | ) 10 | 11 | type HookInfo struct { 12 | Mode int 13 | Info *CodeInfo 14 | Target reflect.Value 15 | Replacement reflect.Value 16 | Trampoline reflect.Value 17 | } 18 | 19 | var ( 20 | archMode = 64 21 | g_all = make(map[uintptr]HookInfo) 22 | ) 23 | 24 | func init() { 25 | sz := unsafe.Sizeof(uintptr(0)) 26 | if sz == 4 { 27 | archMode = 32 28 | } 29 | } 30 | 31 | func GetArchMode() int { 32 | return archMode 33 | } 34 | 35 | // valueStruct is taken from runtime source code. 36 | // it may be changed in later release 37 | type valueStruct struct { 38 | typ uintptr 39 | ptr uintptr 40 | } 41 | 42 | func getDataPtrFromValue(v reflect.Value) uintptr { 43 | return (uintptr)((*valueStruct)(unsafe.Pointer(&v)).ptr) 44 | } 45 | 46 | func ShowDebugInfo() string { 47 | buff := bytes.NewBuffer(make([]byte, 0, 256)) 48 | for k, v := range g_all { 49 | s := fmt.Sprintf("hook function at addr:%x, how:%s, num of instruction fixed:%d\n", k, v.Info.How, len(v.Info.Fix)) 50 | 51 | buff.WriteString(s) 52 | for _, f := range v.Info.Fix { 53 | s = fmt.Sprintf("==@%08x new inst:", f.Addr) 54 | buff.WriteString(s) 55 | for _, c := range f.Code { 56 | s = fmt.Sprintf("%02x ", c) 57 | buff.WriteString(s) 58 | } 59 | s = fmt.Sprintf("\n") 60 | buff.WriteString(s) 61 | } 62 | } 63 | 64 | return string(buff.Bytes()) 65 | } 66 | 67 | func Hook(target, replacement, trampoline interface{}) error { 68 | t := reflect.ValueOf(target) 69 | r := reflect.ValueOf(replacement) 70 | t2 := reflect.ValueOf(trampoline) 71 | return doHook(archMode, false, t, r, t2) 72 | } 73 | 74 | func HookByIndirectJmp(target, replacement, trampoline interface{}) error { 75 | t := reflect.ValueOf(target) 76 | r := reflect.ValueOf(replacement) 77 | t2 := reflect.ValueOf(trampoline) 78 | return doHook(archMode, true, t, r, t2) 79 | } 80 | 81 | func UnHook(target interface{}) error { 82 | t := reflect.ValueOf(target) 83 | return doUnHook(t.Pointer()) 84 | } 85 | 86 | func HookMethod(instance interface{}, method string, replacement, trampoline interface{}) error { 87 | target := reflect.TypeOf(instance) 88 | m, ok := target.MethodByName(method) 89 | if !ok { 90 | return fmt.Errorf("unknown method %s.%s()", target.Name(), method) 91 | } 92 | r := reflect.ValueOf(replacement) 93 | t := reflect.ValueOf(trampoline) 94 | return doHook(archMode, false, m.Func, r, t) 95 | } 96 | 97 | func UnHookMethod(instance interface{}, methodName string) error { 98 | target := reflect.TypeOf(instance) 99 | m, ok := target.MethodByName(methodName) 100 | if !ok { 101 | return errors.New(fmt.Sprintf("unknown method %s", methodName)) 102 | } 103 | 104 | return UnHook(m.Func.Interface()) 105 | } 106 | 107 | func doUnHook(target uintptr) error { 108 | info, ok := g_all[target] 109 | if !ok { 110 | return errors.New("target not exist") 111 | } 112 | 113 | CopyInstruction(target, info.Info.Origin) 114 | 115 | if info.Info.How == "fix" { 116 | for _, v := range info.Info.Fix { 117 | CopyInstruction(v.Addr, v.Code) 118 | } 119 | } 120 | 121 | if info.Trampoline.IsValid() { 122 | CopyInstruction(info.Trampoline.Pointer(), info.Info.TrampolineOrig) 123 | } 124 | 125 | delete(g_all, target) 126 | 127 | return nil 128 | } 129 | 130 | func doHook(mode int, rdxIndirect bool, target, replacement, trampoline reflect.Value) error { 131 | if target.Kind() != reflect.Func { 132 | return fmt.Errorf("target must be a Func") 133 | } 134 | 135 | if replacement.Kind() != reflect.Func { 136 | return fmt.Errorf("replacement must be a Func") 137 | } 138 | 139 | if target.Type() != replacement.Type() { 140 | return fmt.Errorf("target and replacement must have the same type %s != %s", target.Type().Name(), replacement.Type().Name()) 141 | } 142 | 143 | tp := uintptr(0) 144 | if trampoline.IsValid() { 145 | if trampoline.Kind() != reflect.Func { 146 | return fmt.Errorf("replacement must be a Func") 147 | } 148 | 149 | if target.Type() != trampoline.Type() { 150 | return fmt.Errorf("target and trampoline must have the same type %s != %s", target.Type().Name(), trampoline.Type().Name()) 151 | } 152 | 153 | tp = trampoline.Pointer() 154 | } 155 | 156 | doUnHook(target.Pointer()) 157 | 158 | replaceAddr := replacement.Pointer() 159 | if rdxIndirect { 160 | // get data ptr out of a reflect value. 161 | replaceAddr = getDataPtrFromValue(replacement) 162 | } 163 | 164 | info, err := hookFunction(mode, rdxIndirect, target.Pointer(), replaceAddr, tp) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | g_all[target.Pointer()] = HookInfo{Mode: mode, Info: info, Target: target, Replacement: replacement, Trampoline: trampoline} 170 | 171 | return nil 172 | } 173 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "golang.org/x/arch/x86/x86asm" 7 | "github.com/brahma-adshonor/gohook" 8 | ) 9 | 10 | //go:noinline 11 | func foo1(v1 int, v2 string) int { 12 | fmt.Printf("foo1:%d(%s)\n", v1, v2) 13 | return v1 + 42 14 | } 15 | 16 | func foo2(v1 int, v2 string) int { 17 | fmt.Printf("foo2:%d(%s)\n", v1, v2) 18 | v1 = foo3(100, "not calling foo3") 19 | return v1 + 4200 20 | } 21 | 22 | //go:noinline 23 | func foo3(v1 int, v2 string) int { 24 | fmt.Printf("foo3:%d(%s)\n", v1, v2) 25 | return v1 + 10000 26 | } 27 | 28 | func TestAsm() { 29 | fmt.Printf("start testing...\n") 30 | 31 | ret1 := foo1(23, "sval for foo1") 32 | 33 | gohook.Hook(foo1, foo2, foo3) 34 | 35 | ret2 := foo1(23, "sval for foo1") 36 | 37 | fmt.Printf("r1:%d, r2:%d\n", ret1, ret2) 38 | } 39 | 40 | func myBuffLen(b *bytes.Buffer) int { 41 | return 233 42 | } 43 | 44 | //go:noinline 45 | func myBuffLenTramp(b *bytes.Buffer) int { 46 | fmt.Printf("start testing...\n") 47 | fmt.Printf("start testing...\n") 48 | fmt.Printf("start testing...\n") 49 | fmt.Printf("start testing...\n") 50 | fmt.Printf("start testing...\n") 51 | fmt.Printf("start testing...\n") 52 | return 1000 53 | } 54 | 55 | func main() { 56 | TestStackGrowth() 57 | buff := bytes.NewBufferString("abcd") 58 | fmt.Printf("len(buff):%d\n", buff.Len()) 59 | 60 | err1 := gohook.HookMethod(buff, "Len", myBuffLen, myBuffLenTramp) 61 | if err1 != nil { 62 | fmt.Printf("errors:%s\n", err1.Error()) 63 | } 64 | 65 | info := gohook.ShowDebugInfo() 66 | fmt.Printf("show debug info:\n%s\n", info) 67 | 68 | TestAsm() 69 | 70 | //code := []byte {0x64,0x48,0x8b,0x0c,0x25,0xf8,0xff,0xff,0xff,0x48,0x3b,0x61} 71 | //code := []byte {0x48,0x3b,0x61,0x10} 72 | //code := []byte {0x8d,0x6c,0x24,0x10} 73 | //code := []byte{0x64, 0x48, 0x8b, 0xc, 0x25, 0xf8, 0xff, 0xff, 0xff} 74 | code := []byte{0xC7, 0x44, 0x24, 0xFC, 0x01, 0x02, 0x03, 0x04} 75 | //code := []byte {0x0f,0x86,0xa1,0x00, 0x00,0x00,0x00,0x00} 76 | //code := []byte {0xe8,0x8f,0x89,0xe0,0xff} 77 | inst, err := x86asm.Decode(code, 64) 78 | if err != nil { 79 | fmt.Printf("decode failed\n") 80 | return 81 | } 82 | 83 | fmt.Printf("op:%s,code:%x,len:%d,prefix:", inst.Op.String(), inst.Opcode, inst.Len) 84 | for _, v := range inst.Prefix { 85 | if v == 0 { 86 | break 87 | } 88 | fmt.Printf(" %s", v.String()) 89 | } 90 | fmt.Printf(",args:") 91 | for _, v := range inst.Args { 92 | if v == nil { 93 | break 94 | } 95 | fmt.Printf(" %s", v.String()) 96 | } 97 | 98 | fmt.Printf("\n") 99 | 100 | fullInstLen := gohook.GetInsLenGreaterThan(gohook.GetArchMode(), code, 11) 101 | fmt.Printf("full inst len:%d\n", fullInstLen) 102 | } 103 | 104 | func victim(a, b, c int, e, f, g string) int { 105 | if a > 100 { 106 | return 42 107 | } 108 | 109 | var someBigStackArray [4096]byte // to occupy stack, don't let it escape 110 | for i := 0; i < len(someBigStackArray); i++ { 111 | someBigStackArray[i] = byte((a ^ b) & (i ^ c)) 112 | } 113 | 114 | if (a % 2) != 0 { 115 | someBigStackArray[200] = 0xe9 116 | } 117 | 118 | fmt.Printf("calling real victim() (%s,%s,%s,%x):%dth\n", e, f, g, someBigStackArray[200], a) 119 | 120 | return 1 + victim(a+1, b-1, c-1, e, f, g) 121 | } 122 | 123 | //go:noinline 124 | func victimTrampoline(a, b, c int, e, f, g string) int { 125 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 126 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 127 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 128 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 129 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 130 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 131 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 132 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 133 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 134 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 135 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 136 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", e, f, g, a, 0x23) 137 | 138 | for { 139 | if (a % 2) != 0 { 140 | fmt.Printf("calling victim()(%s,%s,%s,%x):%dth\n", a, e, f, g, 0x23) 141 | } else { 142 | a++ 143 | } 144 | 145 | if a+b > 100 { 146 | break 147 | } 148 | 149 | buff := bytes.NewBufferString("something weird") 150 | fmt.Printf("len:%d\n", buff.Len()) 151 | } 152 | 153 | return 1 154 | } 155 | 156 | func victimReplace(a, b, c int, e, f, g string) int { 157 | fmt.Printf("victimReplace sends its regard\n") 158 | ret := 0 159 | if a > 100 { 160 | ret = 100000 161 | } 162 | 163 | return ret + victimTrampoline(a, b, c, e, f, g) 164 | } 165 | 166 | func TestStackGrowth() { 167 | gohook.SetMinJmpCodeSize(64) 168 | defer gohook.SetMinJmpCodeSize(0) 169 | 170 | gohook.Hook(victim, victimReplace, victimTrampoline) 171 | 172 | victim(0, 1000, 100000, "ab", "miliao", "see") 173 | } 174 | -------------------------------------------------------------------------------- /hook_test.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func myPrintf(f string, a ...interface{}) (n int, err error) { 14 | myPrintfTramp("prefixed by miliao -- ") 15 | return myPrintfTramp(f, a...) 16 | } 17 | 18 | //go:noinline 19 | func myPrintfTramp(f string, a ...interface{}) (n int, err error) { 20 | fmt.Printf("hello") 21 | fmt.Printf("hello") 22 | fmt.Printf("hello") 23 | fmt.Printf("hello") 24 | fmt.Printf("hello") 25 | return fmt.Printf("hello") 26 | } 27 | 28 | func init() { 29 | fmt.Printf("test file init()\n") 30 | err := Hook(fmt.Printf, myPrintf, myPrintfTramp) 31 | if err != nil { 32 | fmt.Printf("err:%s\n", err.Error()) 33 | } else { 34 | fmt.Printf("hook fmt.Printf() done\n") 35 | } 36 | 37 | fmt.Printf("debug info for init():%s\n", ShowDebugInfo()) 38 | } 39 | 40 | //go:noinline 41 | func foo1(v1 int, v2 string) int { 42 | 43 | fmt.Printf("foo1:%d(%s)\n", v1, v2) 44 | return v1 + 42 45 | } 46 | 47 | func foo2(v1 int, v2 string) int { 48 | fmt.Printf("foo2:%d(%s)\n", v1, v2) 49 | v1 = foo3(100, "not calling foo3") 50 | return v1 + 4200 51 | } 52 | 53 | //go:noinline 54 | func foo3(v1 int, v2 string) int { 55 | fmt.Printf("foo3:%d(%s)\n", v1, v2) 56 | return v1 + 10000 57 | } 58 | 59 | func myByteContain(a, b []byte) bool { 60 | fmt.Printf("calling fake bytes.Contain()\n") 61 | return false 62 | } 63 | 64 | func TestHook(t *testing.T) { 65 | ResetFuncPrologue() 66 | 67 | fmt.Printf("start testing...\n") 68 | 69 | ret1 := foo1(23, "sval for foo1") 70 | assert.Equal(t, 65, ret1) 71 | 72 | err := Hook(foo1, foo2, foo3) 73 | assert.Nil(t, err) 74 | 75 | ret2 := foo1(23, "sval for foo1") 76 | assert.Equal(t, 4342, ret2) 77 | 78 | ret4 := foo3(100, "vvv") 79 | assert.Equal(t, 142, ret4) 80 | 81 | UnHook(foo1) 82 | ret3 := foo1(23, "sval for foo1") 83 | assert.Equal(t, 65, ret3) 84 | 85 | ret5 := foo3(100, "vvv") 86 | assert.Equal(t, 10100, ret5) 87 | 88 | ret6 := bytes.Contains([]byte{1, 2, 3}, []byte{2, 3}) 89 | assert.Equal(t, true, ret6) 90 | err = Hook(bytes.Contains, myByteContain, nil) 91 | assert.Nil(t, err) 92 | 93 | fun := bytes.Contains // prevent inline 94 | ret7 := fun([]byte{1, 2, 3}, []byte{2, 3}) 95 | 96 | assert.Equal(t, false, ret7) 97 | UnHook(bytes.Contains) 98 | ret8 := bytes.Contains([]byte{1, 2, 3}, []byte{2, 3}) 99 | assert.Equal(t, true, ret8) 100 | } 101 | 102 | func myBuffLen(b *bytes.Buffer) int { 103 | fmt.Println("calling myBuffLen") 104 | return 0 + myBuffLenTramp(b) 105 | } 106 | 107 | //go:noinline 108 | func myBuffLenTramp(b *bytes.Buffer) int { 109 | fmt.Println("calling myBuffLenTramp") 110 | return 1000 111 | } 112 | 113 | func myBuffGrow(b *bytes.Buffer, n int) { 114 | fmt.Println("fake buffer grow func") 115 | } 116 | 117 | func myBuffWriteString(b *bytes.Buffer, s string) (int, error) { 118 | fmt.Printf("fake buffer WriteString func, s:%s\n", s) 119 | 120 | l, _ := myBuffWriteStringTramp(b, s) 121 | return 1000 + l, nil 122 | } 123 | 124 | func myBuffWriteStringTramp(b *bytes.Buffer, s string) (int, error) { 125 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 126 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 127 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 128 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 129 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 130 | fmt.Printf("fake buffer WriteString tramp, s:%s\n", s) 131 | return 0, nil 132 | } 133 | 134 | func TestInstanceHook(t *testing.T) { 135 | ResetFuncPrologue() 136 | buff1 := bytes.NewBufferString("abcd") 137 | assert.Equal(t, 4, buff1.Len()) 138 | 139 | err1 := HookMethod(buff1, "Grow", myBuffGrow, nil) 140 | err2 := HookMethod(buff1, "Len", myBuffLen, myBuffLenTramp) 141 | 142 | assert.Nil(t, err1) 143 | assert.Nil(t, err2) 144 | 145 | assert.Equal(t, 4, buff1.Len()) // Len() is inlined 146 | buff1.Grow(233) // no grow 147 | assert.Equal(t, 4, buff1.Len()) // Len() is inlined 148 | 149 | err3 := HookMethod(buff1, "WriteString", myBuffWriteString, myBuffWriteStringTramp) 150 | assert.Nil(t, err3) 151 | 152 | sz1, _ := buff1.WriteString("miliao") 153 | assert.Equal(t, 1006, sz1) 154 | assert.Equal(t, 10, buff1.Len()) // Len() is inlined 155 | 156 | err4 := UnHookMethod(buff1, "WriteString") 157 | assert.Nil(t, err4) 158 | 159 | flen := buff1.Len 160 | 161 | sz2, _ := buff1.WriteString("miliao") 162 | assert.Equal(t, 6, sz2) 163 | assert.Equal(t, 16, flen()) // Len() is inlined 164 | 165 | sz3, _ := myBuffWriteStringTramp(nil, "sssssss") 166 | assert.Equal(t, 0, sz3) 167 | } 168 | 169 | //go:noinline 170 | func foov(v int) int { 171 | v2 := v*v + 2*v 172 | return v + v2 173 | } 174 | 175 | func foor(v int) int { 176 | if v%2 == 0 { 177 | fmt.Printf("vvvvvvv:%d\n", v) 178 | } 179 | 180 | return v + 1 + foot(v) 181 | } 182 | 183 | //go:noinline 184 | func foot(v int) int { 185 | fmt.Printf("fake func hold:%+v\n", v) 186 | fmt.Printf("fake func hold:%+v\n", v) 187 | fmt.Printf("fake func hold:%+v\n", v) 188 | if v%3 == 0 { 189 | panic("vvvv") 190 | } else { 191 | v = v*v + 23 192 | } 193 | 194 | return v 195 | } 196 | 197 | func TestHookByIndirectJmp(t *testing.T) { 198 | ResetFuncPrologue() 199 | 200 | v := foov(3) 201 | assert.Equal(t, 3*3+2*3+3, v) 202 | 203 | err := HookByIndirectJmp(foov, foor, foot) 204 | assert.Nil(t, err) 205 | 206 | v2 := foov(3) 207 | assert.Equal(t, 3+1+v, v2) 208 | } 209 | 210 | type cfunc func(int) int 211 | 212 | func getClosureFunc(v int) (cfunc, cfunc) { 213 | vv := v + 1 214 | 215 | f1 := func(v2 int) int { 216 | fmt.Printf("f111111, v:%d\n", v2) // prevent inline 217 | v3 := v2*v2 + vv*v + v 218 | return v3 219 | } 220 | 221 | vv2 := v + 10 222 | f2 := func(v2 int) int { 223 | return v2 + vv2 + v 224 | } 225 | 226 | return f1, f2 227 | } 228 | 229 | func TestClosure(t *testing.T) { 230 | ResetFuncPrologue() 231 | 232 | f1, f2 := getClosureFunc(200) 233 | 234 | // use Hook() will fail 235 | err := HookByIndirectJmp(f1, f2, nil) 236 | assert.Nil(t, err) 237 | 238 | var wg sync.WaitGroup 239 | 240 | wg.Add(1) 241 | go func() { 242 | v1 := f1(2) 243 | assert.Equal(t, 2+200+10+200, v1) 244 | wg.Done() 245 | }() 246 | 247 | wg.Wait() 248 | } 249 | 250 | //go:noinline 251 | func tfunc(v int) int { 252 | fmt.Printf("vvvvv:%v\n", v) 253 | return v*v*100 + 123 + v 254 | } 255 | 256 | func TestMakeFunc(t *testing.T) { 257 | ResetFuncPrologue() 258 | 259 | replace := func(in []reflect.Value) []reflect.Value { 260 | v := int(in[0].Int()) 261 | ret := reflect.ValueOf(v + 1000) 262 | return []reflect.Value{ret} 263 | } 264 | 265 | f := reflect.MakeFunc(reflect.ValueOf(tfunc).Type(), replace) 266 | 267 | // use Hook() will panic 268 | err := HookByIndirectJmp(tfunc, f.Interface(), nil) 269 | assert.Nil(t, err) 270 | 271 | v := tfunc(int(3)) 272 | assert.Equal(t, 3+1000, v) 273 | } 274 | -------------------------------------------------------------------------------- /utility.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | ) 8 | 9 | func dummy(v int) string { 10 | return fmt.Sprintf("some text:%d", v) 11 | } 12 | 13 | type CodeInfo struct { 14 | How string 15 | Origin []byte 16 | Fix []CodeFix 17 | TrampolineOrig []byte 18 | } 19 | 20 | func makeSliceFromPointer(p uintptr, length int) []byte { 21 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 22 | Data: p, 23 | Len: length, 24 | Cap: length, 25 | })) 26 | } 27 | 28 | func GetFuncInstSize(f interface{}) uint32 { 29 | sz := uint32(0) 30 | ptr := reflect.ValueOf(f).Pointer() 31 | if elfInfo != nil { 32 | sz, _ = elfInfo.GetFuncSize(ptr) 33 | } 34 | 35 | if sz == 0 { 36 | sz, _ = GetFuncSizeByGuess(GetArchMode(), ptr, true) 37 | } 38 | 39 | return sz 40 | } 41 | 42 | func CopyFunction(allowCall bool, from, to interface{}, info *CodeInfo) ([]byte, error) { 43 | s := reflect.ValueOf(from).Pointer() 44 | d := reflect.ValueOf(to).Pointer() 45 | 46 | mode := GetArchMode() 47 | sz1 := getFuncSize(mode, s, true) 48 | sz2 := getFuncSize(mode, d, true) 49 | return doCopyFunction(mode, allowCall, s, d, sz1, sz2, info) 50 | } 51 | 52 | func getFuncSize(mode int, addr uintptr, minimal bool) uint32 { 53 | sz := uint32(0) 54 | if elfInfo != nil { 55 | sz, _ = elfInfo.GetFuncSize(addr) 56 | } 57 | 58 | var err error 59 | if sz == 0 { 60 | sz, err = GetFuncSizeByGuess(mode, addr, minimal) 61 | if err != nil { 62 | return 0 63 | } 64 | } 65 | 66 | return sz 67 | } 68 | 69 | func doFixFuncInplace(mode int, addr, to uintptr, funcSz, to_sz int, info *CodeInfo, jumpSize int) error { 70 | fix, err := fixFuncInstructionInplace(mode, addr, to, funcSz, to_sz, jumpSize) 71 | for retry := 0; err != nil && retry < 8 && to_sz < funcSz/2; retry++ { 72 | if err != errInplaceFixSizeNotEnough { 73 | break 74 | } 75 | 76 | to_sz += 16 77 | f := makeSliceFromPointer(addr, to_sz+16) 78 | to_sz = GetInsLenGreaterThan(mode, f, to_sz) 79 | 80 | // fmt.Printf("retry fix inplace by extending move size, move sz:%d\n", to_sz) 81 | 82 | fix, err = fixFuncInstructionInplace(mode, addr, to, funcSz, to_sz, jumpSize) 83 | } 84 | 85 | if err != nil { 86 | return err 87 | } 88 | 89 | size2 := 0 90 | total_len := 0 91 | for _, f := range fix { 92 | total_len += len(f.Code) 93 | if f.Foreign { 94 | // fmt.Printf("foreign code:%x, sz:%d\n", f.Addr, len(f.Code)) 95 | size2 += len(f.Code) 96 | } 97 | } 98 | 99 | //jump from trampoline back to origin func 100 | jumpcode2 := genJumpCode(mode, false, addr+uintptr(jumpSize), to+uintptr(size2)) 101 | 102 | origin := makeSliceFromPointer(addr, int(total_len)) 103 | sf := make([]byte, total_len) 104 | copy(sf, origin) 105 | 106 | tramp_origin := makeSliceFromPointer(to, size2+64) 107 | tf := make([]byte, len(tramp_origin)) 108 | copy(tf, tramp_origin) 109 | 110 | for _, f := range fix { 111 | CopyInstruction(f.Addr, f.Code) 112 | } 113 | 114 | CopyInstruction(to+uintptr(size2), jumpcode2) 115 | 116 | info.Fix = fix 117 | info.Origin = sf 118 | info.TrampolineOrig = tf 119 | return nil 120 | } 121 | 122 | func doCopyFunction(mode int, allowCall bool, from, to uintptr, sz1, sz2 uint32, info *CodeInfo) ([]byte, error) { 123 | if sz1 > sz2+1 { // add trailing int3 to the end 124 | return nil, fmt.Errorf("source addr:%x, target addr:%x, sizeof source func(%d) > sizeof of target func(%d)", from, to, sz1, sz2) 125 | } 126 | 127 | fix, err2 := copyFuncInstruction(mode, from, to, int(sz1), allowCall) 128 | if err2 != nil { 129 | return nil, err2 130 | } 131 | 132 | origin := makeSliceFromPointer(to, int(sz2)) 133 | sf := make([]byte, sz2) 134 | copy(sf, origin) 135 | 136 | curAddr := to 137 | for _, f := range fix { 138 | CopyInstruction(curAddr, f.Code) 139 | f.Addr = curAddr 140 | curAddr += uintptr(len(f.Code)) 141 | } 142 | 143 | info.Fix = fix 144 | return sf, nil 145 | } 146 | 147 | func hookFunction(mode int, rdxIndirect bool, target, replace, trampoline uintptr) (*CodeInfo, error) { 148 | info := &CodeInfo{} 149 | jumpcode := genJumpCode(mode, rdxIndirect, replace, target) 150 | 151 | insLen := len(jumpcode) 152 | if trampoline != uintptr(0) { 153 | f := makeSliceFromPointer(target, len(jumpcode)+65) 154 | insLen = GetInsLenGreaterThan(mode, f, len(jumpcode)) 155 | } 156 | 157 | // target slice 158 | ts := makeSliceFromPointer(target, insLen) 159 | info.Origin = make([]byte, len(ts)) 160 | copy(info.Origin, ts) 161 | 162 | info.How = "jump" 163 | target_body_off := insLen 164 | 165 | if trampoline != uintptr(0) { 166 | sz := uint32(0) 167 | if elfInfo != nil { 168 | sz, _ = elfInfo.GetFuncSize(target) 169 | } 170 | 171 | fix, err := FixTargetFuncCode(mode, target, sz, trampoline, insLen) 172 | 173 | if err != nil { 174 | sz1 := getFuncSize(mode, target, false) 175 | sz2 := getFuncSize(mode, trampoline, false) 176 | if sz1 <= 0 || sz2 <= 0 { 177 | return nil, fmt.Errorf("failed calc func size") 178 | } 179 | 180 | err1 := doFixFuncInplace(mode, target, trampoline, int(sz1), insLen, info, len(jumpcode)) 181 | if err1 != nil { 182 | info.How = "copy" 183 | origin, err2 := doCopyFunction(mode, false, target, trampoline, sz1, sz2, info) 184 | if err2 != nil { 185 | return nil, fmt.Errorf("both fix/fix2/copy failed, fix:%s, fix2:%s, copy:%s", err.Error(), err1.Error(), err2.Error()) 186 | } 187 | info.TrampolineOrig = origin 188 | } else { 189 | info.How = "adjust" 190 | /* 191 | ts = makeSliceFromPointer(target, len(jumpcode)+65) 192 | insLen = GetInsLenGreaterThan(mode, ts, len(jumpcode)) 193 | ts = makeSliceFromPointer(target, insLen) 194 | */ 195 | } 196 | } else { 197 | info.How = "fix" 198 | for _, v := range fix { 199 | origin := makeSliceFromPointer(v.Addr, len(v.Code)) 200 | f := make([]byte, len(v.Code)) 201 | copy(f, origin) 202 | CopyInstruction(v.Addr, v.Code) 203 | v.Code = f 204 | info.Fix = append(info.Fix, v) 205 | } 206 | 207 | jumpcode2 := genJumpCode(mode, false, target+uintptr(target_body_off), trampoline+uintptr(insLen)) 208 | f2 := makeSliceFromPointer(trampoline, insLen+len(jumpcode2)*2) 209 | insLen2 := GetInsLenGreaterThan(mode, f2, insLen+len(jumpcode2)) 210 | info.TrampolineOrig = make([]byte, insLen2) 211 | ts2 := makeSliceFromPointer(trampoline, insLen2) 212 | copy(info.TrampolineOrig, ts2) 213 | CopyInstruction(trampoline, ts) 214 | CopyInstruction(trampoline+uintptr(insLen), jumpcode2) 215 | } 216 | } 217 | 218 | CopyInstruction(target, jumpcode) 219 | return info, nil 220 | } 221 | 222 | func printInstructionFix(v CodeFix, origin []byte) { 223 | for _, c := range v.Code { 224 | fmt.Printf(" %x", c) 225 | } 226 | 227 | fmt.Printf(", origin:") 228 | for _, c := range origin { 229 | fmt.Printf(" %x", c) 230 | } 231 | fmt.Printf("\n") 232 | } 233 | 234 | func GetFuncAddr(f interface{}) uintptr { 235 | fv := reflect.ValueOf(f) 236 | return fv.Pointer() 237 | } 238 | -------------------------------------------------------------------------------- /func_stack_test.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func victim(a, b, c int, e, f, g string) int { 13 | if a > 100 { 14 | return 42 15 | } 16 | 17 | var someBigStackArray [4096]byte // to occupy stack, don't let it escape 18 | for i := 0; i < len(someBigStackArray); i++ { 19 | someBigStackArray[i] = byte((a ^ b) & (i ^ c)) 20 | } 21 | 22 | ch := make(chan int, 2) 23 | 24 | if (a % 2) != 0 { 25 | someBigStackArray[200] = 0xe9 26 | } 27 | 28 | ch <- 2 29 | fmt.Printf("calling real victim() (%s,%s,%s,%x):%dth\n", e, f, g, someBigStackArray[200], a) 30 | 31 | runtime.GC() 32 | 33 | return victim(a+1, b-1, c-1, e, f, g) 34 | } 35 | 36 | func victimTrampoline(a, b, c int, e, f, g string) int { 37 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 38 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 39 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 40 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 41 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 42 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 43 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 44 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 45 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 46 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 47 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 48 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 49 | 50 | for { 51 | if (a % 2) != 0 { 52 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 53 | } else { 54 | a++ 55 | } 56 | 57 | if a+b > 100 { 58 | break 59 | } 60 | 61 | buff := bytes.NewBufferString("something weird") 62 | fmt.Printf("len:%d\n", buff.Len()) 63 | } 64 | 65 | return 1 66 | } 67 | 68 | func victimReplace(a, b, c int, e, f, g string) int { 69 | fmt.Printf("victimReplace sends its regard\n") 70 | ret := 0 71 | if a > 100 { 72 | ret = 100000 73 | } 74 | 75 | return ret + victimTrampoline(a, b, c, e, f, g) 76 | } 77 | 78 | func TestStackGrowth(t *testing.T) { 79 | SetMinJmpCodeSize(64) 80 | defer SetMinJmpCodeSize(0) 81 | 82 | ResetFuncPrologue() 83 | 84 | err := Hook(victim, victimReplace, victimTrampoline) 85 | assert.Nil(t, err) 86 | 87 | ret := victim(0, 1000, 100000, "ab", "miliao", "see") 88 | 89 | runtime.GC() 90 | runtime.GC() 91 | runtime.GC() 92 | 93 | assert.Equal(t, 100042, ret) 94 | 95 | UnHook(victim) 96 | 97 | fmt.Printf("after unHook\n") 98 | victimReplace(98, 2, 3, "ab", "ef", "g") 99 | } 100 | 101 | func TestFuncSize(t *testing.T) { 102 | ResetFuncPrologue() 103 | 104 | addr1 := GetFuncAddr(victim) 105 | addr2 := GetFuncAddr(victimReplace) 106 | addr3 := GetFuncAddr(victimTrampoline) 107 | 108 | elf, err := NewElfInfo() 109 | hasElf := (err == nil) 110 | 111 | sz11, err11 := GetFuncSizeByGuess(GetArchMode(), addr1, true) 112 | assert.Nil(t, err11) 113 | 114 | if hasElf { 115 | sz1, err1 := elf.GetFuncSize(addr1) 116 | assert.Nil(t, err1) 117 | assert.Equal(t, sz1, sz11) 118 | } else { 119 | assert.True(t, sz11 > 0) 120 | } 121 | 122 | sz21, err21 := GetFuncSizeByGuess(GetArchMode(), addr2, true) 123 | assert.Nil(t, err21) 124 | 125 | if hasElf { 126 | sz2, err2 := elf.GetFuncSize(addr2) 127 | assert.Nil(t, err2) 128 | assert.Equal(t, sz2, sz21) 129 | } 130 | 131 | sz31, err31 := GetFuncSizeByGuess(GetArchMode(), addr3, true) 132 | assert.Nil(t, err31) 133 | 134 | if hasElf { 135 | sz3, err3 := elf.GetFuncSize(addr3) 136 | assert.Nil(t, err3) 137 | 138 | assert.Equal(t, sz3, sz31) 139 | } 140 | } 141 | 142 | func mySprintf(format string, a ...interface{}) string { 143 | addr1 := GetFuncAddr(victim) 144 | addr2 := GetFuncAddr(victimReplace) 145 | addr3 := GetFuncAddr(victimTrampoline) 146 | 147 | elf, err := NewElfInfo() 148 | fmt.Println("show:", elf, err) 149 | 150 | sz1, err1 := elf.GetFuncSize(addr1) 151 | fmt.Println("show:", sz1, err1) 152 | 153 | sz11, err11 := GetFuncSizeByGuess(GetArchMode(), addr1, false) 154 | fmt.Println("show:", sz11, err11) 155 | 156 | sz2, err2 := elf.GetFuncSize(addr2) 157 | fmt.Println("show:", sz2, err2) 158 | sz21, err21 := GetFuncSizeByGuess(GetArchMode(), addr2, false) 159 | fmt.Println("show:", sz21, err21) 160 | 161 | sz3, err3 := elf.GetFuncSize(addr3) 162 | fmt.Println("show:", sz3, err3) 163 | sz31, err31 := GetFuncSizeByGuess(GetArchMode(), addr3, false) 164 | fmt.Println("show:", sz31, err31) 165 | 166 | return "" 167 | } 168 | 169 | func TestCopyFunc(t *testing.T) { 170 | ResetFuncPrologue() 171 | 172 | addr := GetFuncAddr(mySprintf) 173 | sz := GetFuncInstSize(mySprintf) 174 | 175 | tp := makeSliceFromPointer(addr, int(sz)) 176 | txt := make([]byte, int(sz)) 177 | copy(txt, tp) 178 | 179 | fs := "some random text, from %d,%S,%T" 180 | s1 := fmt.Sprintf(fs, 233, "miliao test sprintf", addr) 181 | 182 | info := &CodeInfo{} 183 | origin, err := CopyFunction(true, fmt.Sprintf, mySprintf, info) 184 | 185 | assert.Nil(t, err) 186 | assert.Equal(t, len(txt), len(origin)) 187 | assert.Equal(t, txt, origin) 188 | 189 | s2 := mySprintf(fs, 233, "miliao test sprintf", addr) 190 | 191 | assert.Equal(t, s1, s2) 192 | 193 | addr2 := GetFuncAddr(fmt.Sprintf) 194 | sz2, _ := GetFuncSizeByGuess(GetArchMode(), addr2, true) 195 | sz3, _ := GetFuncSizeByGuess(GetArchMode(), addr, true) 196 | 197 | assert.Equal(t, sz2, sz3) 198 | } 199 | 200 | type DataHolder struct { 201 | size int 202 | addr []string 203 | close chan int 204 | channel chan *DataHolder 205 | } 206 | 207 | func foo_return_orig(from *DataHolder, addr []string) *DataHolder { 208 | dh := &DataHolder{ 209 | size: from.size + 8, 210 | close: make(chan int, from.size+2), 211 | channel: make(chan *DataHolder, from.size+2), 212 | } 213 | 214 | // origin func doesn't store argument 'addr' 215 | 216 | runtime.GC() 217 | runtime.GC() 218 | from.close = make(chan int, from.size+3) 219 | fmt.Printf("done foo_return_orig\n") 220 | return dh 221 | } 222 | 223 | func foo_return_replace(from *DataHolder, addr []string) *DataHolder { 224 | dummy := make([]int, 10*1024*1024) 225 | fmt.Printf("dummy data, size:%d\n", len(dummy)) 226 | 227 | // replace func DOES store argument 'addr' 228 | /// this will trick golang escape analysis to make wrong decisions regarding whether to heap allocate 'addr' 229 | 230 | dh := &DataHolder{ 231 | addr: addr, 232 | size: from.size, 233 | close: make(chan int, from.size), 234 | channel: make(chan *DataHolder, from.size+1), 235 | } 236 | 237 | // fixing escape analysis by always deep copy slice. 238 | dh.addr = make([]string, len(addr)) 239 | copy(dh.addr, addr) 240 | 241 | origin := foo_return_trampoline(from, addr) 242 | from.close = make(chan int, origin.size) 243 | 244 | go func() { 245 | select { 246 | case <-dh.close: 247 | return 248 | } 249 | }() 250 | 251 | runtime.GC() 252 | from.channel = make(chan *DataHolder, 22) 253 | runtime.GC() 254 | runtime.GC() 255 | runtime.GC() 256 | runtime.GC() 257 | fmt.Printf("done foo_return_replace\n") 258 | return dh 259 | } 260 | 261 | func foo_return_trampoline(from *DataHolder, addr []string) *DataHolder { 262 | for { 263 | if (from.size % 2) != 0 { 264 | fmt.Printf("even from size:%d\n", from.size) 265 | } else { 266 | from.size++ 267 | } 268 | 269 | if from.size > 100 { 270 | break 271 | } 272 | 273 | buff := bytes.NewBufferString("something weird") 274 | fmt.Printf("len:%d\n", buff.Len()) 275 | } 276 | 277 | from.close = make(chan int, 11) 278 | return from 279 | } 280 | 281 | func TestGarbageCollection(t *testing.T) { 282 | err := Hook(foo_return_orig, foo_return_replace, foo_return_trampoline) 283 | assert.Nil(t, err) 284 | 285 | addr := []string{"mm11111111111vvvvvvvv", "=ggslsllllllllllllllll"} 286 | 287 | dh := &DataHolder{size: 32} 288 | dh2 := foo_return_orig(dh, addr) 289 | 290 | for i := 0; i < 10; i++ { 291 | runtime.GC() 292 | runtime.GC() 293 | assert.NotNil(t, dh2) 294 | runtime.GC() 295 | runtime.GC() 296 | } 297 | 298 | dh.close <- 1 299 | } 300 | -------------------------------------------------------------------------------- /inplace_fix_test.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func inplaceFix(a, b, c int, e, f, g string) int { 12 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 13 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 14 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 15 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 16 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 17 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 18 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 19 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 20 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 21 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 22 | 23 | for { 24 | if (a % 2) != 0 { 25 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 26 | } else { 27 | a++ 28 | } 29 | 30 | if a+b > 100 { 31 | break 32 | } 33 | 34 | buff := bytes.NewBufferString("something weird") 35 | fmt.Printf("len:%d\n", buff.Len()) 36 | } 37 | 38 | return 1 39 | } 40 | 41 | func TestRipRelativeAddr(t *testing.T) { 42 | toAddr := uintptr(0x10101042) 43 | code := []byte{0x48, 0x8b, 0x05, 0x11, 0x22, 0x33, 0x44} 44 | addr := calcJumpToAbsAddr(GetArchMode(), toAddr, code) 45 | 46 | assert.Equal(t, toAddr+7+0x44332211, addr) 47 | } 48 | 49 | func TestFixInplace(t *testing.T) { 50 | d0 := int32(0x01b1) 51 | d1 := byte(0xb2) // byte(-78) 52 | d2 := byte(0xa8) // byte(-88) 53 | 54 | prefix1 := []byte{ 55 | 0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, // 9 56 | 0x48, 0x3b, 0x61, 0x10, // 4 57 | } 58 | 59 | jc0 := []byte{ 60 | 0x0f, 0x86, byte(d0), byte(d0 >> 8), 0x00, 0x00, // jmp 61 | } 62 | 63 | prefix2 := []byte{ 64 | 0x48, 0x83, 0xec, 0x58, // 4 65 | 0x48, 0x89, 0x6c, 0x24, 0x50, // 5 66 | 0x48, 0x8d, 0x6c, 0x24, 0x50, // 5 67 | 0x90, // 1 68 | } 69 | 70 | prefix3rip := []byte{ 71 | 0x48, 0x8b, 0x05, 0xc7, 0x5f, 0x16, 0x00, // 7 mov $rip offset 72 | } 73 | 74 | prefix4rip := []byte{ 75 | 0x48, 0x8d, 0x0d, 0x50, 0x85, 0x07, 0x00, // 7 lea $rip offset 76 | } 77 | 78 | prefix5 := []byte{ 79 | 0x48, 0x89, 0x0c, 0x24, // 4 80 | 0x48, 0x89, 0x44, 0x24, 0x08, // 5 81 | } 82 | 83 | prefix6rip := []byte{ 84 | 0x48, 0x8d, 0x05, 0x9f, 0xe0, 0x04, 0x00, // 7 lea $rip offset 85 | } 86 | 87 | prefix7 := []byte{ 88 | 0x48, 0x89, 0x44, 0x24, 0x10, // 5 89 | 0x48, 0xc7, 0x44, 0x24, 0x18, 0x07, 0x00, 0x00, 0x00, // 9 90 | } 91 | // totoal 78 bytes 92 | 93 | // short jump 94 | jc1 := []byte{0xeb, d1} // 2 95 | 96 | mid := []byte{ 97 | 0x0f, 0x57, 0xc0, // 3 98 | 0x0f, 0x11, 0x44, 0x24, 0x28, // 5 99 | } 100 | 101 | jc2 := []byte{ 102 | // condition jump 103 | 0x77, d2, // 2 104 | } 105 | 106 | posfix := []byte{ 107 | // trailing 108 | 0xcc, 0xcc, 0xcc, 0xcc, 109 | 0xcc, 0xcc, 0xcc, 0xcc, 110 | 0xcc, 0xcc, 0xcc, 0xcc, 111 | 0xcc, 0xcc, 0xcc, 0xcc, 112 | } 113 | 114 | var fc []byte 115 | all := [][]byte{prefix1, jc0, prefix2, prefix3rip, prefix4rip, prefix5, prefix6rip, prefix7, jc1, mid, jc2, posfix} 116 | for k := range all { 117 | fc = append(fc, all[k]...) 118 | } 119 | 120 | info := &CodeInfo{} 121 | addr := GetFuncAddr(inplaceFix) 122 | size := len(fc) 123 | mvSize := 0x09 124 | toAddr := GetFuncAddr(inplaceFix2) 125 | 126 | curAddr1 := addr + uintptr(78) 127 | curAddr2 := addr + uintptr(78) + uintptr(10) 128 | 129 | CopyInstruction(addr, fc) 130 | 131 | fs := makeSliceFromPointer(addr, len(fc)) 132 | raw := make([]byte, len(fc)) 133 | copy(raw, fs) 134 | 135 | fmt.Printf("src func:%x, target func:%x\n", addr, toAddr) 136 | 137 | err := doFixFuncInplace(64, addr, toAddr, int(size), mvSize, info, 5) 138 | 139 | assert.Nil(t, err) 140 | assert.True(t, len(raw) >= len(info.Origin)) 141 | raw = raw[:len(info.Origin)] 142 | assert.Equal(t, raw, info.Origin) 143 | assert.Equal(t, 18, len(info.Fix)) 144 | assert.Equal(t, prefix1[:5], fs[:5]) 145 | 146 | off0 := d0 + 4 147 | fix0, _ := adjustInstructionOffset(jc0, int64(off0)) 148 | fmt.Printf("inplace fix, off0:%x, sz:%d\n", off0, len(fix0)) 149 | 150 | rip3Addr := addr + uintptr(len(prefix1)+len(jc0)+len(prefix2)) 151 | ripOff3 := calcJumpToAbsAddr(GetArchMode(), rip3Addr, prefix3rip) - uintptr(len(prefix3rip)) - rip3Addr + uintptr(4) 152 | prefix3ripFix, _ := adjustInstructionOffset(prefix3rip, int64(ripOff3)) 153 | 154 | rip4Addr := addr + uintptr(len(prefix1)+len(jc0)+len(prefix2)+len(prefix3rip)) 155 | ripOff4 := calcJumpToAbsAddr(GetArchMode(), rip4Addr, prefix4rip) - uintptr(len(prefix4rip)) - rip4Addr + uintptr(4) 156 | prefix4ripFix, _ := adjustInstructionOffset(prefix4rip, int64(ripOff4)) 157 | 158 | rip6Addr := addr + uintptr(len(prefix1)+len(jc0)+len(prefix2)+len(prefix3rip)+len(prefix4rip)+len(prefix5)) 159 | ripOff6 := calcJumpToAbsAddr(GetArchMode(), rip6Addr, prefix6rip) - uintptr(len(prefix6rip)) - rip6Addr + uintptr(4) 160 | prefix6ripFix, _ := adjustInstructionOffset(prefix6rip, int64(ripOff6)) 161 | 162 | to1 := curAddr1 + uintptr(2) + uintptr(int32(int8(d1))) 163 | newTo1 := toAddr + to1 - addr 164 | off1 := int64(newTo1 - (curAddr1 - uintptr(4)) - 5) 165 | fix1, _ := translateJump(off1, jc1) 166 | fmt.Printf("inplace fix, off1:%x, sz:%d\n", off1, len(fix1)) 167 | 168 | to2 := curAddr2 + uintptr(2) + uintptr(int32(int8(d2))) 169 | newTo2 := toAddr + to2 - addr 170 | off2 := int64(newTo2 - (curAddr2 + uintptr(3) - uintptr(4)) - 6) 171 | fix2, _ := translateJump(off2, jc2) 172 | fmt.Printf("inplace fix, off2:%x, sz:%d\n", off2, len(fix2)) 173 | 174 | all2 := [][]byte{prefix1[:5], prefix1[9:], fix0, prefix2, prefix3ripFix, prefix4ripFix, prefix5, prefix6ripFix, prefix7, fix1, mid, fix2, posfix} 175 | var fc2 []byte 176 | for k := range all2 { 177 | fc2 = append(fc2, all2[k]...) 178 | } 179 | assert.Equal(t, len(fc)-4+3+4, len(fc2)) 180 | 181 | fs = makeSliceFromPointer(addr, len(fc2)-len(posfix)) 182 | assert.Equal(t, fc2[:len(fc2)-len(posfix)], fs) 183 | } 184 | 185 | func inplaceFix2(a, b, c int, e, f, g string) int { 186 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 187 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 188 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 189 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 190 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 191 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 192 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 193 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 194 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 195 | fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 196 | 197 | for { 198 | if (a % 2) != 0 { 199 | fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23) 200 | } else { 201 | a++ 202 | } 203 | 204 | if a+b > 100 { 205 | break 206 | } 207 | 208 | buff := bytes.NewBufferString("something weird") 209 | fmt.Printf("len:%d\n", buff.Len()) 210 | } 211 | 212 | return 1 213 | } 214 | 215 | func foo_for_inplace_fix(id string) string { 216 | c := 0 217 | for { 218 | fmt.Printf("calling victim\n") 219 | if id == "miliao" { 220 | return "done" 221 | } 222 | 223 | c++ 224 | if c > len(id) { 225 | break 226 | } 227 | } 228 | 229 | fmt.Printf("len:%d\n", len(id)) 230 | return id + "xxx" 231 | } 232 | 233 | func foo_for_inplace_fix_delimiter(id string) string { 234 | for { 235 | fmt.Printf("calling victim trampoline") 236 | if id == "miliao" { 237 | return "done" 238 | } 239 | break 240 | } 241 | 242 | ret := "miliao" 243 | ret += foo_for_inplace_fix("test") 244 | ret += foo_for_inplace_fix("test") 245 | ret += foo_for_inplace_fix("test") 246 | ret += foo_for_inplace_fix("test") 247 | 248 | fmt.Printf("len1:%d\n", len(id)) 249 | fmt.Printf("len2:%d\n", len(ret)) 250 | 251 | return id + ret 252 | } 253 | 254 | func foo_for_inplace_fix_replace(id string) string { 255 | c := 0 256 | for { 257 | fmt.Printf("calling foo_for_inplace_fix_replace\n") 258 | if id == "miliao" { 259 | return "done" 260 | } 261 | c++ 262 | if c > len(id) { 263 | break 264 | } 265 | } 266 | 267 | // TODO uncomment following 268 | foo_for_inplace_fix_trampoline("miliao") 269 | 270 | fmt.Printf("len:%d\n", len(id)) 271 | return id + "xxx2" 272 | } 273 | 274 | func foo_for_inplace_fix_trampoline(id string) string { 275 | c := 0 276 | for { 277 | fmt.Printf("calling foo_for_inplace_fix_trampoline\n") 278 | if id == "miliao" { 279 | return "done" 280 | } 281 | c++ 282 | if c > len(id) { 283 | break 284 | } 285 | } 286 | 287 | fmt.Printf("len:%d\n", len(id)) 288 | return id + "xxx3" 289 | } 290 | 291 | func TestInplaceFixAtMoveArea(t *testing.T) { 292 | code := []byte{ 293 | /* 294 | 0x48, 0x8b, 0x48, 0x08, // mov 0x8(%rax),%rcx 295 | 0x74, 0x4, // jbe 296 | 0x48, 0x8b, 0x48, 0x18, // sub 0x18(%rax), %rcx 297 | 0x48, 0x89, 0x4c, 0x24, 0x10, // %rcx, 0x10(%rsp) 298 | 0xc3, // retq 299 | 0xcc, 0xcc, 300 | */ 301 | 0x90, 0x90, 302 | 0xeb, 0x04, // jmp 4 303 | 0x90, 0x90, 0x90, 0x90, 0x90, 304 | 0x90, 0x90, 0x90, 0x90, 0x90, 305 | 0xc3, 306 | 0x74, 0xf0, // jbe -16 307 | 0xcc, 0xcc, 0xcc, 0xcc, 308 | 0xcc, 0xcc, 0xcc, 0xcc, 309 | } 310 | 311 | target := GetFuncAddr(foo_for_inplace_fix) 312 | replace := GetFuncAddr(foo_for_inplace_fix_replace) 313 | trampoline := GetFuncAddr(foo_for_inplace_fix_trampoline) 314 | 315 | assert.True(t, isByteOverflow((int32)(trampoline-target))) 316 | 317 | CopyInstruction(target, code) 318 | 319 | fmt.Printf("short call target:%x, replace:%x, trampoline:%x\n", target, replace, trampoline) 320 | err1 := Hook(foo_for_inplace_fix, foo_for_inplace_fix_replace, foo_for_inplace_fix_trampoline) 321 | assert.Nil(t, err1) 322 | 323 | fmt.Printf("debug info:%s\n", ShowDebugInfo()) 324 | 325 | msg1 := foo_for_inplace_fix("txt") 326 | 327 | fmt.Printf("calling foo inplace fix func\n") 328 | 329 | assert.Equal(t, "txtxxx2", msg1) 330 | 331 | sz1 := 5 332 | na1 := trampoline + uintptr(2) 333 | ta1 := target + uintptr(2+5+4-3) 334 | off1 := ta1 - (na1 + uintptr(sz1)) 335 | 336 | sz2 := 6 337 | na2 := target + uintptr(15+3-3) 338 | ta2 := trampoline + uintptr(1) 339 | off2 := ta2 - (na2 + uintptr(sz2)) 340 | 341 | fmt.Printf("off1:%x, off2:%x\n", off1, off2) 342 | 343 | ret := []byte{ 344 | 0x90, 0x90, 345 | 0xe9, 0x74, 0xfc, 0xff, 0xff, 346 | 0x90, 0x90, 0x90, 0x90, 0x90, 347 | 0x90, 0x90, 0x90, 0x90, 0x90, 348 | 0xc3, 349 | 0x0f, 0x84, 0x80, 0x03, 0x00, 0x00, 350 | 0xcc, 0xcc, 0xcc, 351 | } 352 | 353 | ret[3] = byte(off1) 354 | ret[4] = byte(off1 >> 8) 355 | ret[5] = byte(off1 >> 16) 356 | ret[6] = byte(off1 >> 24) 357 | 358 | ret[20] = byte(off2) 359 | ret[21] = byte(off2 >> 8) 360 | ret[22] = byte(off2 >> 16) 361 | ret[23] = byte(off2 >> 24) 362 | 363 | fc1 := makeSliceFromPointer(target, len(ret)) 364 | fc2 := makeSliceFromPointer(trampoline, len(ret)) 365 | 366 | assert.Equal(t, ret[:8], fc2[:8]) 367 | assert.Equal(t, byte(0xe9), fc2[8]) 368 | assert.Equal(t, ret[8:], fc1[5:len(ret)-3]) 369 | 370 | code2 := []byte{ 371 | 0x90, 0x90, 0x90, 0x90, 372 | 0x74, 0x04, 373 | 0x90, 0x90, 0x90, 0x90, 374 | 0x90, 0x90, 0x90, 0x90, 0x90, 375 | 0xc3, 0xcc, 0x90, 376 | } 377 | 378 | err2 := UnHook(foo_for_inplace_fix) 379 | assert.Nil(t, err2) 380 | 381 | msg2 := foo_for_inplace_fix_trampoline("txt") 382 | assert.Equal(t, "txtxxx3", msg2) 383 | 384 | msg3 := foo_for_inplace_fix_replace("txt2") 385 | assert.Equal(t, "txt2xxx2", msg3) 386 | 387 | CopyInstruction(target, code2) 388 | 389 | fsz, _ := GetFuncSizeByGuess(GetArchMode(), target, false) 390 | assert.Equal(t, len(code2)-1, int(fsz)) 391 | } 392 | -------------------------------------------------------------------------------- /inst_fix_test.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "unsafe" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetInsLenGreaterThan(t *testing.T) { 12 | ResetFuncPrologue() 13 | c1 := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8} 14 | c2 := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff} 15 | 16 | r1 := GetInsLenGreaterThan(64, c1, len(c1)-2) 17 | assert.Equal(t, 0, r1) 18 | r2 := GetInsLenGreaterThan(64, c2, len(c2)-2) 19 | assert.Equal(t, len(c2), r2) 20 | r22 := GetInsLenGreaterThan(64, c2, len(c2)) 21 | assert.Equal(t, len(c2), r22) 22 | r23 := GetInsLenGreaterThan(64, c2, len(c2)+2) 23 | assert.Equal(t, 0, r23) 24 | 25 | c3 := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48, 0x3b, 0x41, 0x10} 26 | r3 := GetInsLenGreaterThan(64, c3, len(c2)+2) 27 | assert.Equal(t, len(c3), r3) 28 | r32 := GetInsLenGreaterThan(64, c3, len(c2)-2) 29 | assert.Equal(t, len(c2), r32) 30 | } 31 | 32 | func TestFixOneInstructionForTwoByteJmp(t *testing.T) { 33 | ResetFuncPrologue() 34 | // jump from within patching erea to outside, negative fix 35 | c1 := []byte{0x75, 0x40} // jne 64 36 | 37 | l1, t1, r1 := FixOneInstruction(64, false, 10, 12, c1, 100, 8) 38 | 39 | assert.Equal(t, 2, l1) 40 | assert.Equal(t, FT_CondJmp, t1) 41 | assert.Equal(t, c1[0], r1[0]) 42 | assert.Equal(t, int8(-26), int8(r1[1])) 43 | 44 | // jump from within patching erea to outside, positive fix 45 | l2, t2, r2 := FixOneInstruction(64, false, 10, 12, c1, 26, 8) 46 | 47 | assert.Equal(t, 2, l2) 48 | assert.Equal(t, FT_CondJmp, t2) 49 | assert.Equal(t, c1[0], r2[0]) 50 | assert.Equal(t, int8(48), int8(r2[1])) 51 | 52 | //overflow test 53 | l3, t3, r3 := FixOneInstruction(64, false, 10, 12, c1, 1000, 8) 54 | 55 | assert.Equal(t, 2, l3) 56 | assert.Equal(t, FT_OVERFLOW, t3) 57 | assert.Equal(t, c1[0], r3[0]) 58 | assert.Equal(t, c1[1], r3[1]) 59 | 60 | // overflow test2 61 | c32 := []byte{0x75, 0x7e} // jne 0x7e 62 | l32, t32, r32 := FixOneInstruction(64, false, 30, 32, c32, 10, 8) 63 | 64 | assert.Equal(t, 2, l32) 65 | assert.Equal(t, FT_OVERFLOW, t32) 66 | assert.Equal(t, c32[0], r32[0]) 67 | assert.Equal(t, c32[1], r32[1]) 68 | 69 | // jump from outside patching erea to outside of patching erea 70 | l4, t4, r4 := FixOneInstruction(64, false, 10, 18, c1, 100, 4) 71 | 72 | assert.Equal(t, 2, l4) 73 | assert.Equal(t, FT_SKIP, t4) 74 | assert.Equal(t, c1[0], r4[0]) 75 | assert.Equal(t, c1[1], r4[1]) 76 | 77 | // jump from outside patching erea to within patching erea 78 | c2 := []byte{0x75, 0xe6} // jne -26 79 | l5, t5, r5 := FixOneInstruction(64, false, 10, 38, c2, 100, 8) 80 | 81 | assert.Equal(t, 2, l5) 82 | assert.Equal(t, FT_CondJmp, t5) 83 | assert.Equal(t, c2[0], r5[0]) 84 | assert.Equal(t, 64, int(r5[1])) 85 | 86 | // jump within patching erea 87 | c3 := []byte{0x75, 0x06} // jne 6 88 | l6, t6, r6 := FixOneInstruction(64, false, 10, 12, c3, 100, 11) 89 | 90 | assert.Equal(t, 2, l6) 91 | assert.Equal(t, FT_SKIP, t6) 92 | assert.Equal(t, c3[0], r6[0]) 93 | assert.Equal(t, c3[1], r6[1]) 94 | 95 | // sign test, from outside to outside 96 | c4 := []byte{0x7c, 0xcd} // jne -51 97 | l7, t7, r7 := FixOneInstruction(64, false, 10, 83, c4, 1000, 10) 98 | 99 | assert.Equal(t, 2, l7) 100 | assert.Equal(t, FT_SKIP, t7) 101 | assert.Equal(t, c4[0], r7[0]) 102 | assert.Equal(t, c4[1], r7[1]) 103 | } 104 | 105 | func byteToInt32(d []byte) int32 { 106 | v := int32(uint32(d[0]) | (uint32(d[1]) << 8) | (uint32(d[2]) << 16) | (uint32(d[3]) << 24)) 107 | return v 108 | } 109 | 110 | func TestFixOneInstructionForSixByteJmp(t *testing.T) { 111 | ResetFuncPrologue() 112 | // jump from within patching erea to outside, negative fix 113 | c1 := []byte{0x0f, 0x8d, 0x10, 0x00, 0x00, 0x00} // jge 16 114 | 115 | l1, t1, r1 := FixOneInstruction(64, false, 20, 22, c1, 100, 8) 116 | assert.Equal(t, 6, l1) 117 | assert.Equal(t, FT_CondJmp, t1) 118 | assert.Equal(t, c1[0], r1[0]) 119 | assert.Equal(t, c1[1], r1[1]) 120 | 121 | assert.Equal(t, int32(-64), byteToInt32(r1[2:])) 122 | 123 | // jump from within patching erea to outside, positive fix 124 | c2 := []byte{0x0f, 0x8d, 0x40, 0x00, 0x00, 0x00} // jge 64 125 | 126 | l2, t2, r2 := FixOneInstruction(64, false, 2, 4, c2, 32, 9) 127 | assert.Equal(t, 6, l2) 128 | assert.Equal(t, FT_CondJmp, t2) 129 | assert.Equal(t, c2[0], r2[0]) 130 | assert.Equal(t, c2[1], r2[1]) 131 | 132 | assert.Equal(t, int32(34), byteToInt32(r2[2:])) 133 | 134 | // overflow test 135 | c3 := []byte{0x0f, 0x8d, 0xfe, 0xff, 0xff, 0x7f} // jge 64 136 | 137 | l3, t3, r3 := FixOneInstruction(64, false, 10000, 10004, c3, 100, 16) 138 | assert.Equal(t, 6, l3) 139 | assert.Equal(t, FT_OVERFLOW, t3) 140 | assert.Equal(t, c3[0], r3[0]) 141 | assert.Equal(t, c3[1], r3[1]) 142 | assert.Equal(t, c3[2], r3[2]) 143 | assert.Equal(t, c3[3], r3[3]) 144 | assert.Equal(t, c3[4], r3[4]) 145 | assert.Equal(t, c3[5], r3[5]) 146 | 147 | // jump from outside patching erea to outside of patching erea 148 | c4 := []byte{0x0f, 0x8d, 0x40, 0x00, 0x00, 0x00} // jge 64 149 | 150 | l4, t4, r4 := FixOneInstruction(64, false, 10, 33, c4, 22, 9) 151 | assert.Equal(t, 6, l4) 152 | assert.Equal(t, FT_SKIP, t4) 153 | assert.Equal(t, c4[0], r4[0]) 154 | assert.Equal(t, c4[1], r4[1]) 155 | assert.Equal(t, c4[2], r4[2]) 156 | assert.Equal(t, c4[3], r4[3]) 157 | assert.Equal(t, c4[4], r4[4]) 158 | assert.Equal(t, c4[5], r4[5]) 159 | 160 | // jump from outside patching erea to within patching erea 161 | c5 := []byte{0x0f, 0x85, 0xce, 0xff, 0xff, 0xff} // jne -50 162 | 163 | l5, t5, r5 := FixOneInstruction(64, false, 10, 60, c5, 1000, 9) 164 | assert.Equal(t, 6, l5) 165 | assert.Equal(t, FT_CondJmp, t5) 166 | assert.Equal(t, c5[0], r5[0]) 167 | assert.Equal(t, c5[1], r5[1]) 168 | 169 | assert.Equal(t, int32(940), byteToInt32(r5[2:])) 170 | 171 | // jump within patching erea 172 | c6 := []byte{0x0f, 0x85, 0x10, 0x00, 0x00, 0x00} // jne 16 173 | 174 | l6, t6, r6 := FixOneInstruction(64, false, 10, 12, c6, 1000, 30) 175 | assert.Equal(t, 6, l6) 176 | assert.Equal(t, FT_SKIP, t6) 177 | assert.Equal(t, c6[0], r6[0]) 178 | assert.Equal(t, c6[1], r6[1]) 179 | assert.Equal(t, c6[2], r6[2]) 180 | assert.Equal(t, c6[3], r6[3]) 181 | assert.Equal(t, c6[4], r6[4]) 182 | assert.Equal(t, c6[5], r6[5]) 183 | } 184 | 185 | func TestFixOneInstructionForFixByteJmp(t *testing.T) { 186 | // jump from within patching erea to outside, negative fix 187 | c1 := []byte{0xe9, 0x10, 0x00, 0x00, 0x00} // jmp 16 188 | 189 | l1, t1, r1 := FixOneInstruction(64, false, 20, 22, c1, 100, 8) 190 | assert.Equal(t, 5, l1) 191 | assert.Equal(t, FT_JMP, t1) 192 | assert.Equal(t, c1[0], r1[0]) 193 | assert.Equal(t, int32(-64), byteToInt32(r1[1:])) 194 | 195 | // jump from within patching erea to outside, positive fix 196 | c2 := []byte{0xe9, 0x40, 0x00, 0x00, 0x00} // jmp 64 197 | 198 | l2, t2, r2 := FixOneInstruction(64, false, 2, 4, c2, 32, 9) 199 | assert.Equal(t, 5, l2) 200 | assert.Equal(t, FT_JMP, t2) 201 | assert.Equal(t, c2[0], r2[0]) 202 | assert.Equal(t, int32(34), byteToInt32(r2[1:])) 203 | 204 | // overflow test 205 | c3 := []byte{0xe9, 0xfe, 0xff, 0xff, 0x7f} // jmp 64 206 | 207 | l3, t3, r3 := FixOneInstruction(64, false, 10000, 10004, c3, 100, 16) 208 | assert.Equal(t, 5, l3) 209 | assert.Equal(t, FT_OVERFLOW, t3) 210 | assert.Equal(t, c3[0], r3[0]) 211 | assert.Equal(t, c3[1], r3[1]) 212 | assert.Equal(t, c3[2], r3[2]) 213 | assert.Equal(t, c3[3], r3[3]) 214 | assert.Equal(t, c3[4], r3[4]) 215 | 216 | // jump from outside patching erea to outside of patching erea 217 | c4 := []byte{0xe9, 0x40, 0x00, 0x00, 0x00} // jmp 64 218 | 219 | l4, t4, r4 := FixOneInstruction(64, false, 10, 33, c4, 22, 9) 220 | assert.Equal(t, 5, l4) 221 | assert.Equal(t, FT_SKIP, t4) 222 | assert.Equal(t, c4[0], r4[0]) 223 | assert.Equal(t, c4[1], r4[1]) 224 | assert.Equal(t, c4[2], r4[2]) 225 | assert.Equal(t, c4[3], r4[3]) 226 | assert.Equal(t, c4[4], r4[4]) 227 | 228 | // jump from outside patching erea to within patching erea 229 | c5 := []byte{0xe9, 0xce, 0xff, 0xff, 0xff} // jmp -50 230 | 231 | l5, t5, r5 := FixOneInstruction(64, false, 10, 60, c5, 1000, 9) 232 | assert.Equal(t, 5, l5) 233 | assert.Equal(t, FT_JMP, t5) 234 | assert.Equal(t, c5[0], r5[0]) 235 | assert.Equal(t, int32(940), byteToInt32(r5[1:])) 236 | 237 | // jump within patching erea 238 | c6 := []byte{0xe9, 0x10, 0x00, 0x00, 0x00} // jmp 16 239 | 240 | l6, t6, r6 := FixOneInstruction(64, false, 10, 12, c6, 1000, 30) 241 | assert.Equal(t, 5, l6) 242 | assert.Equal(t, FT_SKIP, t6) 243 | assert.Equal(t, c6[0], r6[0]) 244 | assert.Equal(t, c6[1], r6[1]) 245 | assert.Equal(t, c6[2], r6[2]) 246 | assert.Equal(t, c6[3], r6[3]) 247 | assert.Equal(t, c6[4], r6[4]) 248 | 249 | // jump from outside to outside, sign test 250 | c7 := []byte{0xe8, 0xdc, 0xfb, 0xff, 0xff} // jmp -1060 251 | l7, t7, r7 := FixOneInstruction(64, false, 2000, 4100, c7, 10000, 30) 252 | assert.Equal(t, 5, l7) 253 | assert.Equal(t, FT_SKIP, t7) 254 | assert.Equal(t, c7[0], r7[0]) 255 | assert.Equal(t, c7[1], r7[1]) 256 | assert.Equal(t, c7[2], r7[2]) 257 | assert.Equal(t, c7[3], r7[3]) 258 | assert.Equal(t, c7[4], r7[4]) 259 | } 260 | 261 | func TestFixFuncCode(t *testing.T) { 262 | p := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff} // move %fs:0xfffffffffffffff8, %rcx 263 | c1 := []byte{ 264 | /*0:*/ 0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, // move %fs:0xfffffffffffffff8, %rcx sz:9 265 | /*9:*/ 0x48, 0x8d, 0x44, 0x24, 0xe0, // lea -0x20(%rsp),%rax sz:5 266 | /*14:*/ 0x48, 0x3b, 0x41, 0x10, // cmp 0x10(%rcx),%rax sz:4 267 | /*18:*/ 0x0f, 0x86, 0xc3, 0x01, 0x00, 0x00, // jbe 451 sz:6 268 | /*24:*/ 0x48, 0x81, 0xec, 0xa0, 0x00, 0x00, 0x00, // sub $0xa0,%rsp sz:7 269 | /*31:*/ 0x48, 0x8b, 0x9c, 0x24, 0xa8, 0x00, 0x00, 0x00, // mov 0xa8(%rsp),%rbx sz:8 270 | /*39:*/ 0xe3, 0x02, // jmp 02 sz:2 271 | /*41:*/ 0x90, // nop sz:1 272 | /*42:*/ 0x90, // nop sz:1 273 | /*43:*/ 0x90, // nop sz:1 274 | /*44:*/ 0x90, // nop sz:1 275 | //////////patching erea end: 45 bytes///////////////////////////////////////// 276 | /*45:*/ 0x48, 0x89, 0x5c, 0x24, 0x40, // mov %rbx,0x40(%rsp) sz:5 277 | /*50:*/ 0xe9, 0xd2, 0xff, 0xff, 0xff, // jmp -46 sz:5 278 | /*55:*/ 0x90, // nop sz:1 279 | /*56:*/ 0x90, // nop sz:1 280 | /*57:*/ 0x90, // nop sz:1 281 | /*58:*/ 0x90, // nop sz:1 282 | } 283 | 284 | SetFuncPrologue(64, []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48}) 285 | sh1 := (*reflect.SliceHeader)((unsafe.Pointer(&c1))) 286 | 287 | move_sz := 45 288 | startAddr := sh1.Data 289 | toAddr := startAddr + 100000 290 | 291 | fix1, err1 := FixTargetFuncCode(64, startAddr, uint32(len(c1)), toAddr, move_sz) 292 | 293 | assert.Nil(t, err1) 294 | assert.Equal(t, 2, len(fix1)) 295 | 296 | assert.Equal(t, startAddr+uintptr(18), fix1[0].Addr) 297 | assert.Equal(t, startAddr+uintptr(50), fix1[1].Addr) 298 | 299 | assert.Equal(t, 6, len(fix1[0].Code)) 300 | assert.Equal(t, byte(0x0f), fix1[0].Code[0]) 301 | assert.Equal(t, byte(0x86), fix1[0].Code[1]) 302 | assert.Equal(t, int32(startAddr+451-toAddr), byteToInt32(fix1[0].Code[2:])) 303 | 304 | assert.Equal(t, 5, len(fix1[1].Code)) 305 | assert.Equal(t, byte(0xe9), fix1[1].Code[0]) 306 | assert.Equal(t, int32(toAddr+9-startAddr-50-5), byteToInt32(fix1[1].Code[1:])) 307 | 308 | c2 := append(c1, p...) 309 | sh2 := (*reflect.SliceHeader)((unsafe.Pointer(&c2))) 310 | startAddr = sh2.Data 311 | toAddr = startAddr + 100000 312 | 313 | fix2, err2 := FixTargetFuncCode(64, startAddr, 0, toAddr, move_sz) 314 | 315 | assert.Nil(t, err2) 316 | assert.Equal(t, 2, len(fix2)) 317 | 318 | assert.Equal(t, startAddr+uintptr(18), fix2[0].Addr) 319 | assert.Equal(t, startAddr+uintptr(50), fix2[1].Addr) 320 | 321 | assert.Equal(t, 6, len(fix2[0].Code)) 322 | assert.Equal(t, byte(0x0f), fix2[0].Code[0]) 323 | assert.Equal(t, byte(0x86), fix2[0].Code[1]) 324 | assert.Equal(t, int32(startAddr+451-toAddr), byteToInt32(fix2[0].Code[2:])) 325 | 326 | assert.Equal(t, 5, len(fix2[1].Code)) 327 | assert.Equal(t, byte(0xe9), fix2[1].Code[0]) 328 | assert.Equal(t, int32(toAddr+9-startAddr-50-5), byteToInt32(fix2[1].Code[1:])) 329 | } 330 | -------------------------------------------------------------------------------- /arch_util.go: -------------------------------------------------------------------------------- 1 | package gohook 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "unsafe" 9 | 10 | "golang.org/x/arch/x86/x86asm" 11 | ) 12 | 13 | type CodeFix struct { 14 | Code []byte 15 | Addr uintptr 16 | Foreign bool 17 | } 18 | 19 | var ( 20 | minJmpCodeSize = 0 21 | elfInfo, _ = NewElfInfo() 22 | 23 | errInplaceFixSizeNotEnough = fmt.Errorf("func size exceed during inplace fix") 24 | 25 | funcPrologue32 = defaultFuncPrologue32 26 | funcPrologue64 = defaultFuncPrologue64 27 | 28 | // ======================condition jump instruction======================== 29 | // JA JAE JB JBE JCXZ JE JECXZ JG JGE JL JLE JMP JNE JNO JNP JNS JO JP JRCXZ JS 30 | 31 | // one byte opcode, one byte relative offset 32 | twoByteCondJmp = []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0xe3} 33 | // two byte opcode, four byte relative offset 34 | sixByteCondJmp = []uint16{0x0f80, 0x0f81, 0x0f82, 0x0f83, 0x0f84, 0x0f85, 0x0f86, 0x0f87, 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c, 0x0f8d, 0x0f8e, 0x0f8f} 35 | 36 | // ====================== jump instruction======================== 37 | // one byte opcode, one byte relative offset 38 | twoByteJmp = []byte{0xeb} 39 | // one byte opcode, four byte relative offset 40 | fiveByteJmp = []byte{0xe9} 41 | 42 | // ====================== call instruction======================== 43 | // one byte opcode, 4 byte relative offset 44 | fiveByteCall = []byte{0xe8} 45 | 46 | // ====================== ret instruction======================== 47 | // return instruction, no operand 48 | oneByteRet = []byte{0xc3, 0xcb} 49 | // return instruction, one byte opcode, 2 byte operand 50 | threeByteRet = []byte{0xc2, 0xca} 51 | ) 52 | 53 | const ( 54 | FT_CondJmp = 1 55 | FT_JMP = 2 56 | FT_CALL = 3 57 | FT_RET = 4 58 | FT_OTHER = 5 59 | FT_INVALID = 6 60 | FT_SKIP = 7 61 | FT_OVERFLOW = 8 62 | ) 63 | 64 | func SetMinJmpCodeSize(sz int) { 65 | minJmpCodeSize = sz 66 | } 67 | 68 | func ResetFuncPrologue() { 69 | funcPrologue32 = defaultFuncPrologue32 70 | funcPrologue64 = defaultFuncPrologue64 71 | } 72 | 73 | func SetFuncPrologue(mode int, data []byte) { 74 | if mode == 32 { 75 | funcPrologue32 = make([]byte, len(data)) 76 | copy(funcPrologue32, data) 77 | } else { 78 | funcPrologue64 = make([]byte, len(data)) 79 | copy(funcPrologue64, data) 80 | } 81 | } 82 | 83 | func GetInsLenGreaterThan(mode int, data []byte, least int) int { 84 | if len(data) < least { 85 | return 0 86 | } 87 | 88 | curLen := 0 89 | d := data[curLen:] 90 | for { 91 | if len(d) <= 0 { 92 | break 93 | } 94 | 95 | if curLen >= least { 96 | break 97 | } 98 | 99 | inst, err := x86asm.Decode(d, mode) 100 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(d[0])) { 101 | break 102 | } 103 | 104 | if inst.Len == 1 && d[0] == 0xcc { 105 | // 0xcc -> int3, trap to debugger, padding to function end 106 | break 107 | } 108 | 109 | curLen = curLen + inst.Len 110 | d = data[curLen:] 111 | } 112 | 113 | return curLen 114 | } 115 | 116 | func isByteOverflow(v int32) bool { 117 | if v > 0 { 118 | if v > math.MaxInt8 { 119 | return true 120 | } 121 | } else { 122 | if v < math.MinInt8 { 123 | return true 124 | } 125 | } 126 | 127 | return false 128 | } 129 | 130 | func isIntOverflow(v int64) bool { 131 | if v > 0 { 132 | if v > math.MaxInt32 { 133 | return true 134 | } 135 | } else { 136 | if v < math.MinInt32 { 137 | return true 138 | } 139 | } 140 | 141 | return false 142 | } 143 | 144 | func calcOffset(insSz int, startAddr, curAddr, to uintptr, to_sz int, offset int32) int64 { 145 | newAddr := curAddr 146 | absAddr := curAddr + uintptr(insSz) + uintptr(offset) 147 | 148 | if curAddr < startAddr+uintptr(to_sz) { 149 | newAddr = to + (curAddr - startAddr) 150 | } 151 | 152 | if absAddr >= startAddr && absAddr < startAddr+uintptr(to_sz) { 153 | absAddr = to + (absAddr - startAddr) 154 | } 155 | 156 | return int64(uint64(absAddr) - uint64(newAddr) - uint64(insSz)) 157 | } 158 | 159 | func translateJump(off int64, code []byte) ([]byte, error) { 160 | if code[0] == 0xe3 { 161 | return nil, errors.New("not supported JCXZ instruction(0xe3)") 162 | } 163 | 164 | if code[0] == 0xeb { 165 | ret := make([]byte, 5) 166 | ret[0] = 0xe9 167 | 168 | ret[1] = byte(off) 169 | ret[2] = byte(off >> 8) 170 | ret[3] = byte(off >> 16) 171 | ret[4] = byte(off >> 24) 172 | return ret, nil 173 | } else if code[0] >= 0x70 && code[0] <= 0x7f { 174 | ret := make([]byte, 6) 175 | ret[0] = 0x0f 176 | ret[1] = 0x80 + code[0] - 0x70 177 | 178 | ret[2] = byte(off) 179 | ret[3] = byte(off >> 8) 180 | ret[4] = byte(off >> 16) 181 | ret[5] = byte(off >> 24) 182 | return ret, nil 183 | } else { 184 | return nil, errors.New("cannot fix unsupported jump instruction inplace") 185 | } 186 | } 187 | 188 | func adjustInstructionOffset(code []byte, off int64) ([]byte, error) { 189 | if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) { 190 | offset := int(int8(code[1])) 191 | if offset == int(off) { 192 | return code, nil 193 | } 194 | if isByteOverflow(int32(off)) { 195 | return nil, fmt.Errorf("byte overflow in adjusting offset") 196 | } 197 | code[1] = byte(int8(off)) 198 | } else if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) { 199 | offset := int(int32(uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24))) 200 | if offset == int(off) { 201 | return code, nil 202 | } 203 | if isIntOverflow(off) { 204 | return nil, fmt.Errorf("int overflow in adjusting 6-bytes inst offset") 205 | } 206 | code[2] = byte(off) 207 | code[3] = byte(off >> 8) 208 | code[4] = byte(off >> 16) 209 | code[5] = byte(off >> 24) 210 | } else if code[0] == 0xe9 || code[0] == 0xe8 { 211 | offset := int(int32(uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24))) 212 | if offset == int(off) { 213 | return code, nil 214 | } 215 | if isIntOverflow(off) { 216 | return nil, fmt.Errorf("int overflow in adjusting 5-bytes inst offset") 217 | } 218 | code[1] = byte(off) 219 | code[2] = byte(off >> 8) 220 | code[3] = byte(off >> 16) 221 | code[4] = byte(off >> 24) 222 | } else if code[0] == 0x48 && (code[1] == 0x8b || code[1] == 0x8d) && (code[2]&0x05) == 0x05 { // mod == 00 r/m == 101 223 | // rip relative addressing: mov/lea 224 | // intel software development manual: `Addressing-Mode Encoding of ModR/M and SIB Bytes` && `RIP-Relative Addressing` 225 | // or https://www.cs.uaf.edu/2016/fall/cs301/lecture/09_28_machinecode.html 226 | offset := int(int32(uint32(code[3]) | (uint32(code[4]) << 8) | (uint32(code[5]) << 16) | (uint32(code[6]) << 24))) 227 | if offset == int(off) { 228 | return code, nil 229 | } 230 | code[3] = byte(off) 231 | code[4] = byte(off >> 8) 232 | code[5] = byte(off >> 16) 233 | code[6] = byte(off >> 24) 234 | } else { 235 | return nil, fmt.Errorf("not jump instruction") 236 | } 237 | 238 | return code, nil 239 | } 240 | 241 | func calcJumpToAbsAddr(mode int, addr uintptr, code []byte) uintptr { 242 | sz := 0 243 | offset := 0 244 | 245 | if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) { 246 | sz = 2 247 | offset = int(int8(code[1])) 248 | } 249 | 250 | if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) { 251 | sz = 6 252 | offset = int(int32(uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24))) 253 | } 254 | 255 | if code[0] == 0xe9 || code[0] == 0xe8 { 256 | sz = 5 257 | offset = int(int32(uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24))) 258 | } 259 | 260 | if code[0] == 0x48 && (code[1] == 0x8b || code[1] == 0x8d) && (code[2]&0x05) == 0x05 { // mod == 00 r/m == 101 261 | // rip relative addressing: mov/lea 262 | // intel software development manual: `Addressing-Mode Encoding of ModR/M and SIB Bytes` && `RIP-Relative Addressing` 263 | // or https://www.cs.uaf.edu/2016/fall/cs301/lecture/09_28_machinecode.html 264 | sz = 7 265 | offset = int(int32(uint32(code[3]) | (uint32(code[4]) << 8) | (uint32(code[5]) << 16) | (uint32(code[6]) << 24))) 266 | } 267 | 268 | if sz == 0 { 269 | return uintptr(0) 270 | } 271 | 272 | return addr + uintptr(sz) + uintptr(offset) 273 | } 274 | 275 | func FixOneInstruction(mode int, fix_recursive_call bool, startAddr, curAddr uintptr, code []byte, to uintptr, to_sz int) (int, int, []byte) { 276 | nc := make([]byte, len(code)) 277 | copy(nc, code) 278 | 279 | if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) { 280 | // two byte condition jump, two byte jmp 281 | nc = nc[:2] 282 | off := calcOffset(2, startAddr, curAddr, to, to_sz, int32(int8(code[1]))) 283 | if off != int64(int8(nc[1])) { 284 | if isByteOverflow(int32(off)) { 285 | // overfloat, cannot fix this with one byte operand 286 | return 2, FT_OVERFLOW, nc 287 | } 288 | nc[1] = byte(off) 289 | return 2, FT_CondJmp, nc 290 | } 291 | return 2, FT_SKIP, nc 292 | } 293 | 294 | if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) { 295 | // six byte condition jump 296 | nc = nc[:6] 297 | off1 := (uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24)) 298 | off2 := uint64(calcOffset(6, startAddr, curAddr, to, to_sz, int32(off1))) 299 | if uint64(int32(off1)) != off2 { 300 | if isIntOverflow(int64(off2)) { 301 | // overfloat, cannot fix this with four byte operand 302 | return 6, FT_OVERFLOW, nc 303 | } 304 | nc[2] = byte(off2) 305 | nc[3] = byte(off2 >> 8) 306 | nc[4] = byte(off2 >> 16) 307 | nc[5] = byte(off2 >> 24) 308 | return 6, FT_CondJmp, nc 309 | } 310 | return 6, FT_SKIP, nc 311 | } 312 | 313 | if code[0] == 0xe9 || code[0] == 0xe8 { 314 | // five byte jmp, five byte call 315 | nc = nc[:5] 316 | off1 := (uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24)) 317 | 318 | off2 := uint64(0) 319 | if !fix_recursive_call && code[0] == 0xe8 && startAddr == (curAddr+uintptr(5)+uintptr(int32(off1))) { 320 | // don't fix recursive call 321 | off2 = uint64(int32(off1)) 322 | } else { 323 | off2 = uint64(calcOffset(5, startAddr, curAddr, to, to_sz, int32(off1))) 324 | } 325 | 326 | if uint64(int32(off1)) != off2 { 327 | if isIntOverflow(int64(off2)) { 328 | // overfloat, cannot fix this with four byte operand 329 | return 5, FT_OVERFLOW, nc 330 | } 331 | nc[1] = byte(off2) 332 | nc[2] = byte(off2 >> 8) 333 | nc[3] = byte(off2 >> 16) 334 | nc[4] = byte(off2 >> 24) 335 | return 5, FT_JMP, nc 336 | } 337 | return 5, FT_SKIP, nc 338 | } 339 | 340 | // ret instruction just return, no fix is needed. 341 | if code[0] == 0xc3 || code[0] == 0xcb { 342 | // one byte ret 343 | nc = nc[:1] 344 | return 1, FT_RET, nc 345 | } 346 | 347 | if code[0] == 0xc2 || code[0] == 0xca { 348 | // three byte ret 349 | nc = nc[:3] 350 | return 3, FT_RET, nc 351 | } 352 | 353 | inst, err := x86asm.Decode(code, mode) 354 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) { 355 | return 0, FT_INVALID, nc 356 | } 357 | 358 | if inst.Len == 1 && code[0] == 0xcc { 359 | return 0, FT_INVALID, nc 360 | } 361 | 362 | sz := inst.Len 363 | nc = nc[:sz] 364 | return sz, FT_OTHER, nc 365 | } 366 | 367 | func doFixTargetFuncCode(all bool, mode int, start uintptr, funcSz int, to uintptr, move_sz int, inst []CodeFix) ([]CodeFix, error) { 368 | fix := make([]CodeFix, 0, 64) 369 | 370 | curSz := 0 371 | curAddr := start 372 | 373 | i := 0 374 | for i = 0; i < len(inst); i++ { 375 | if curSz >= move_sz { 376 | break 377 | } 378 | 379 | code := inst[i].Code 380 | sz, ft, nc := FixOneInstruction(mode, false, start, curAddr, code, to, move_sz) 381 | 382 | if sz == 0 && ft == FT_INVALID { 383 | // the end or unrecognized instruction 384 | return nil, errors.New(fmt.Sprintf("invalid instruction scanned, addr:0x%x", curAddr)) 385 | } else if sz == 5 && nc[0] == 0xe8 { 386 | // call instruction is not allowed to move. 387 | // this will mess up with golang stack reallocation. 388 | return nil, fmt.Errorf("call instruction is not allowed to move") 389 | } 390 | 391 | if ft == FT_RET { 392 | return nil, errors.New(fmt.Sprintf("ret instruction in patching erea is not allowed, addr:0x%x", curAddr)) 393 | } 394 | 395 | if ft == FT_OVERFLOW { 396 | return nil, errors.New(fmt.Sprintf("jmp instruction in patching erea overflow, addr:0x%x", curAddr)) 397 | } 398 | 399 | if ft != FT_OTHER && ft != FT_SKIP { 400 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: true}) 401 | } else if all { 402 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: true}) 403 | } 404 | 405 | curSz += sz 406 | curAddr = start + uintptr(curSz) 407 | } 408 | 409 | for ; i < len(inst); i++ { 410 | if funcSz > 0 && int(curAddr-start) >= funcSz { 411 | break 412 | } 413 | 414 | code := inst[i].Code 415 | sz, ft, nc := FixOneInstruction(mode, false, start, curAddr, code, to, move_sz) 416 | 417 | if sz == 0 && ft == FT_INVALID { 418 | // the end or unrecognized instruction 419 | break 420 | } 421 | 422 | if ft == FT_OVERFLOW { 423 | return nil, errors.New(fmt.Sprintf("jmp instruction in body overflow, addr:0x%x", curAddr)) 424 | } 425 | 426 | if ft != FT_OTHER && ft != FT_RET && ft != FT_SKIP { 427 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: false}) 428 | } else if all { 429 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: false}) 430 | } 431 | 432 | curSz += sz 433 | curAddr = start + uintptr(curSz) 434 | } 435 | 436 | return fix, nil 437 | } 438 | 439 | // FixTargetFuncCode fix function code starting at address [start] 440 | // parameter 'funcSz' may not specify, in which case, we need to find out the end by scanning next prologue or finding invalid instruction. 441 | // 'to' specifys a new location, to which 'move_sz' bytes instruction will be copied 442 | // since move_sz byte instructions will be copied, those relative jump instruction need to be fixed. 443 | func FixTargetFuncCode(mode int, start uintptr, funcSz uint32, to uintptr, move_sz int) ([]CodeFix, error) { 444 | inst, _ := parseInstruction(mode, start, int(funcSz), false) 445 | return doFixTargetFuncCode(false, mode, start, int(funcSz), to, move_sz, inst) 446 | } 447 | 448 | func GetFuncSizeByGuess(mode int, start uintptr, minimal bool) (uint32, error) { 449 | funcPrologue := funcPrologue64 450 | if mode == 32 { 451 | funcPrologue = funcPrologue32 452 | } 453 | 454 | prologueLen := len(funcPrologue) 455 | code := makeSliceFromPointer(start, 16) // instruction takes at most 16 bytes 456 | 457 | /* prologue is not required 458 | if !bytes.Equal(funcPrologue, code[:prologueLen]) { // not valid function start or invalid prologue 459 | return 0, errors.New(fmt.Sprintf("no func prologue, addr:0x%x", start)) 460 | } 461 | */ 462 | 463 | int3_found := false 464 | curLen := uint32(0) 465 | 466 | for { 467 | inst, err := x86asm.Decode(code, mode) 468 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) { 469 | break 470 | } 471 | 472 | if inst.Len == 1 && code[0] == 0xcc { 473 | // 0xcc -> int3, trap to debugger, padding to function end 474 | if minimal { 475 | break 476 | } 477 | int3_found = true 478 | } else if int3_found { 479 | break 480 | } 481 | 482 | curLen = curLen + uint32(inst.Len) 483 | code = makeSliceFromPointer(start+uintptr(curLen), 16) // instruction takes at most 16 bytes 484 | 485 | if bytes.Equal(funcPrologue, code[:prologueLen]) { 486 | break 487 | } 488 | } 489 | 490 | return curLen, nil 491 | } 492 | 493 | // sz size of source function 494 | // WARNING: copy function won't work in copystack(since go 1.3). 495 | // runtime will copy stack to new area and fix those weird stuff(pointer/rbp etc), this will crash trampoline function. 496 | // since copying function makes trampoline a completely different function, with completely different stack layout which is 497 | // not known to runtime. 498 | // solution to this is, we should just copy those non-call instructions to trampoline. in this way we don't mess up with runtime. 499 | // TODO/FIXME 500 | func copyFuncInstruction(mode int, from, to uintptr, sz int, allowCall bool) ([]CodeFix, error) { 501 | curSz := 0 502 | curAddr := from 503 | fix := make([]CodeFix, 0, 256) 504 | 505 | for { 506 | if curSz >= sz { 507 | break 508 | } 509 | 510 | code := makeSliceFromPointer(curAddr, 16) // instruction takes at most 16 bytes 511 | sz, ft, nc := FixOneInstruction(mode, true, from, curAddr, code, to, sz) 512 | 513 | if sz == 0 && ft == FT_INVALID { 514 | // the end or unrecognized instruction 515 | break 516 | } else if !allowCall && sz == 5 && nc[0] == 0xe8 { 517 | // call instruction is not allowed to move. 518 | // this will mess up with golang stack reallocation. 519 | return nil, fmt.Errorf("call instruction is not allowed to copy") 520 | } 521 | 522 | if ft == FT_OVERFLOW { 523 | return nil, fmt.Errorf("overflow instruction in copying function, addr:0x%x", curAddr) 524 | } 525 | 526 | to_addr := (to + (curAddr - from)) 527 | fix = append(fix, CodeFix{Code: nc, Addr: to_addr}) 528 | 529 | curSz += sz 530 | curAddr = from + uintptr(curSz) 531 | } 532 | 533 | to_addr := (to + (curAddr - from)) 534 | fix = append(fix, CodeFix{Code: []byte{0xcc}, Addr: to_addr}) 535 | return fix, nil 536 | } 537 | 538 | func adjustJmpOffset(mode int, start, delem uintptr, funcSize, moveSize int, inst []CodeFix) error { 539 | funcEnd := start + uintptr(funcSize) 540 | for i := range inst { 541 | code := inst[i].Code 542 | curAddr := inst[i].Addr 543 | absAddr := calcJumpToAbsAddr(mode, curAddr, code) 544 | 545 | if curAddr > delem && curAddr < funcEnd { 546 | inst[i].Addr = curAddr + uintptr(moveSize) 547 | } 548 | 549 | if absAddr != uintptr(0) { 550 | delta := absAddr - curAddr - uintptr(len(code)) 551 | off := int64(delta) 552 | if unsafe.Sizeof(uintptr(0)) == unsafe.Sizeof(int32(0)) { 553 | off = int64(int32(delta)) 554 | } 555 | 556 | // fmt.Printf("adjust inst at:%x, sz:%d, delem:%x, target:%x, funcEnd:%x, off:%x\n", curAddr, len(code), delem, absAddr, funcEnd, uintptr(off)) 557 | 558 | if (curAddr < delem || curAddr >= funcEnd) && absAddr > delem && absAddr < funcEnd { 559 | off += int64(moveSize) 560 | } else if (curAddr >= delem && curAddr < funcEnd) && (absAddr <= delem || absAddr >= funcEnd) { 561 | off -= int64(moveSize) 562 | } else { 563 | // do nothing 564 | } 565 | 566 | c, err := adjustInstructionOffset(code, off) 567 | if err != nil { 568 | return fmt.Errorf("err occurs adjusting inst, addr:%x,off:%x,err:%s", curAddr, off, err.Error()) 569 | } 570 | 571 | inst[i].Code = c 572 | // absAddr = calcJumpToAbsAddr(mode, inst[i].Addr, code) 573 | // fmt.Printf("after adjust inst, old addr:%x, new addr:%x, target:%x\n", curAddr, inst[i].Addr, absAddr) 574 | } 575 | } 576 | 577 | return nil 578 | } 579 | 580 | func translateShortJump(mode int, addr, to uintptr, inst []CodeFix, funcSz, move_sz, jumpSize int) (int, []CodeFix, error) { 581 | newSz := 0 582 | fix := make([]CodeFix, 0, 256) 583 | 584 | for i := range inst { 585 | code := inst[i].Code 586 | curAddr := inst[i].Addr 587 | sz, ft, _ := FixOneInstruction(mode, false, addr, curAddr, code, to, move_sz) 588 | 589 | if sz == 0 && ft == FT_INVALID { 590 | // the end or unrecognized instruction 591 | break 592 | } 593 | 594 | foreign := false 595 | if curAddr < addr+uintptr(move_sz) { 596 | foreign = true 597 | } 598 | 599 | if ft == FT_OVERFLOW { 600 | if sz != 2 { 601 | return 0, nil, fmt.Errorf("inst overflow with size != 2") 602 | } 603 | 604 | nc, err := translateJump(int64(int8(code[1])), code) 605 | if err != nil { 606 | return 0, nil, err 607 | } 608 | 609 | delta := len(nc) - len(code) 610 | if curAddr < addr+uintptr(move_sz) { 611 | move_sz += delta 612 | } 613 | 614 | inst[i].Code = nc 615 | // fmt.Printf("extent overflow inst at:%x, sz:%d, move sz:%d\n", curAddr, len(nc), move_sz) 616 | 617 | err = adjustJmpOffset(mode, addr, curAddr, funcSz, delta, inst[i:]) 618 | if err != nil { 619 | return 0, nil, err 620 | } 621 | 622 | err = adjustJmpOffset(mode, addr, curAddr, funcSz, delta, fix) 623 | if err != nil { 624 | return 0, nil, err 625 | } 626 | } 627 | 628 | newSz += len(inst[i].Code) 629 | fix = append(fix, CodeFix{Code: inst[i].Code, Addr: inst[i].Addr, Foreign: foreign}) 630 | } 631 | 632 | if newSz-move_sz > funcSz-jumpSize { 633 | return move_sz, fix, errInplaceFixSizeNotEnough 634 | } 635 | 636 | return move_sz, fix, nil 637 | } 638 | 639 | func parseInstruction(mode int, addr uintptr, funcSz int, minimal bool) ([]CodeFix, error) { 640 | funcPrologue := funcPrologue64 641 | if mode == 32 { 642 | funcPrologue = funcPrologue32 643 | } 644 | 645 | prologueLen := len(funcPrologue) 646 | code := makeSliceFromPointer(addr, 16) // instruction takes at most 16 bytes 647 | 648 | curLen := 0 649 | int3_found := false 650 | 651 | ret := make([]CodeFix, 0, 258) 652 | 653 | for { 654 | if funcSz > 0 && curLen >= funcSz { 655 | break 656 | } 657 | 658 | inst, err := x86asm.Decode(code, mode) 659 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) { 660 | break 661 | } 662 | 663 | if inst.Len == 1 && code[0] == 0xcc { 664 | // 0xcc -> int3, trap to debugger, padding to function end 665 | if minimal { 666 | break 667 | } 668 | int3_found = true 669 | } else if int3_found { 670 | break 671 | } 672 | 673 | c := make([]byte, inst.Len) 674 | copy(c, code) 675 | cf := CodeFix{Addr: addr + uintptr(curLen), Code: c} 676 | ret = append(ret, cf) 677 | 678 | curLen = curLen + inst.Len 679 | code = makeSliceFromPointer(addr+uintptr(curLen), 16) 680 | 681 | if bytes.Equal(funcPrologue, code[:prologueLen]) { 682 | break 683 | } 684 | } 685 | 686 | return ret, nil 687 | } 688 | 689 | func fixFuncInstructionInplace(mode int, addr, to uintptr, funcSz int, move_sz int, jumpSize int) ([]CodeFix, error) { 690 | /* 691 | trail := makeSliceFromPointer(addr+uintptr(funcSz), 1024) 692 | for i := 0; i < len(trail); i++ { 693 | if trail[i] != 0xcc { 694 | break 695 | } 696 | funcSz++ 697 | } 698 | */ 699 | 700 | code, _ := parseInstruction(mode, addr, funcSz, false) 701 | move_sz, fix, err := translateShortJump(mode, addr, to, code, funcSz, move_sz, jumpSize) 702 | 703 | if err != nil { 704 | return nil, err 705 | } 706 | 707 | fix, err1 := doFixTargetFuncCode(true, mode, addr, funcSz, to, move_sz, fix) 708 | 709 | if err1 != nil { 710 | return fix, err1 711 | } 712 | 713 | curAddr := to 714 | firstBody := addr 715 | for i := range fix { 716 | if !fix[i].Foreign { 717 | firstBody = fix[i].Addr 718 | break 719 | } 720 | 721 | // fmt.Printf("foreign addr:%x, sz:%d\n", curAddr, len(fix[i].Code)) 722 | 723 | fix[i].Addr = curAddr 724 | curAddr += uintptr(len(fix[i].Code)) 725 | } 726 | 727 | mvAddr := addr + uintptr(jumpSize) 728 | msz := -int(firstBody - mvAddr) 729 | 730 | if msz != 0 { 731 | // fmt.Printf("now move to the front, msz:%d\n", msz) 732 | err2 := adjustJmpOffset(mode, addr, mvAddr, funcSz, msz, fix) 733 | if err2 != nil { 734 | // fmt.Printf("error in fixing inplace\n") 735 | return nil, err2 736 | } 737 | } 738 | 739 | // fmt.Printf("done fixing inplace\n") 740 | return fix, nil 741 | } 742 | 743 | func genJumpCode(mode int, rdxIndirect bool, to, from uintptr) []byte { 744 | // 1. use relaive jump if |from-to| < 2G 745 | // 2. otherwise, push target, then ret 746 | 747 | var code []byte 748 | 749 | if rdxIndirect { 750 | // rdx indirect jump. 751 | // 'to' :data pointer from reflect.Value, pointed to a funcValue, and the first field of funcval is a pointer to the real func. 752 | // 'from': this is the instruction code addr of the target function. 753 | 754 | // by convention, rdx is the context register pointed to a funcval. 755 | // funcval of a closure function contains extra information used by compiler and runtime. 756 | // so using indirect jmp by rdx makes it possible to hook closure func and func created by reflect.MakeFunc 757 | 758 | // caution: 'to' funcval must stay alive after hook is installed. 759 | if mode == 32 { 760 | code = []byte{ 761 | 0xBA, 762 | byte(to), 763 | byte(to >> 8), 764 | byte(to >> 16), 765 | byte(to >> 24), // mov edx,to 766 | 0xFF, 0x22, // jmp DWORD PTR [edx] 767 | } 768 | } else { 769 | code = []byte{ 770 | 0x48, 0xBA, 771 | byte(to), 772 | byte(to >> 8), 773 | byte(to >> 16), 774 | byte(to >> 24), 775 | byte(to >> 32), 776 | byte(to >> 40), 777 | byte(to >> 48), 778 | byte(to >> 56), // movabs rdx,to 779 | 0xFF, 0x22, // jmp QWORD PTR [rdx] 780 | } 781 | } 782 | } else { 783 | delta := int64(from - to) 784 | if unsafe.Sizeof(uintptr(0)) == unsafe.Sizeof(int32(0)) { 785 | delta = int64(int32(from - to)) 786 | } 787 | 788 | relative := (delta <= 0x7fffffff) 789 | 790 | if delta < 0 { 791 | delta = -delta 792 | relative = (delta <= 0x80000000) 793 | } 794 | 795 | // relative = false 796 | 797 | if relative { 798 | var dis uint32 799 | if to > from { 800 | dis = uint32(int32(to-from) - 5) 801 | } else { 802 | dis = uint32(-int32(from-to) - 5) 803 | } 804 | code = []byte{ 805 | 0xe9, 806 | byte(dis), 807 | byte(dis >> 8), 808 | byte(dis >> 16), 809 | byte(dis >> 24), 810 | } 811 | } else if mode == 32 { 812 | code = []byte{ 813 | 0x68, // push 814 | byte(to), 815 | byte(to >> 8), 816 | byte(to >> 16), 817 | byte(to >> 24), 818 | 0xc3, // retn 819 | } 820 | } else if mode == 64 { 821 | // push does not operate on 64bit imm, workarounds are: 822 | // 1. move to register(eg, %rdx), then push %rdx, however, overwriting register may cause problem if not handled carefully. 823 | // 2. push twice, preferred. 824 | /* 825 | code = []byte{ 826 | 0x48, // prefix 827 | 0xba, // mov to %rdx 828 | byte(to), byte(to >> 8), byte(to >> 16), byte(to >> 24), 829 | byte(to >> 32), byte(to >> 40), byte(to >> 48), byte(to >> 56), 830 | 0x52, // push %rdx 831 | 0xc3, // retn 832 | } 833 | */ 834 | code = []byte{ 835 | 0x68, //push 836 | byte(to), byte(to >> 8), byte(to >> 16), byte(to >> 24), 837 | 0xc7, 0x44, 0x24, // mov $value, 4%rsp 838 | 0x04, // rsp + 4 839 | byte(to >> 32), byte(to >> 40), byte(to >> 48), byte(to >> 56), 840 | 0xc3, // retn 841 | } 842 | } else { 843 | panic("invalid mode") 844 | } 845 | } 846 | 847 | sz := len(code) 848 | if minJmpCodeSize > 0 && sz < minJmpCodeSize { 849 | nop := make([]byte, 0, minJmpCodeSize-sz) 850 | for { 851 | if len(nop) >= minJmpCodeSize-sz { 852 | break 853 | } 854 | nop = append(nop, 0x90) 855 | } 856 | 857 | code = append(code, nop...) 858 | } 859 | 860 | return code 861 | } 862 | --------------------------------------------------------------------------------