├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── arch_util.go ├── azure-pipelines.yml ├── bug.sh ├── elf_helper.go ├── example ├── example.go ├── example2.go ├── example3.go ├── main.go └── unwind.go ├── func_stack_test.go ├── go.mod ├── go.sum ├── hook.go ├── hook_test.go ├── inplace_fix_test.go ├── inst_fix_test.go ├── small_func_test.go ├── utility.go ├── utility_darwin.go ├── utility_linux.go ├── utility_unix.go └── utility_windows.go /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.log 3 | *.swo 4 | *.swn 5 | .idea/* 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "github.com/brahma-adshonor/gohook" 54 | "os" 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 | -------------------------------------------------------------------------------- /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 // applied to instructions that are copied to new localtion(trampoline), false mean the fix is for instruction in the original function body 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 | // fix instruction operand. 276 | // the instruction to be fixed will be copied to function started at 'to' 277 | // so instruction operand might need to adjust, currently only offset is fixed. 278 | func FixOneInstruction(mode int, fix_recursive_call bool, startAddr, curAddr uintptr, code []byte, to uintptr, to_sz int) (int, int, []byte) { 279 | nc := make([]byte, len(code)) 280 | copy(nc, code) 281 | 282 | if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) { 283 | // two byte condition jump, two byte jmp 284 | nc = nc[:2] 285 | off := calcOffset(2, startAddr, curAddr, to, to_sz, int32(int8(code[1]))) 286 | if off != int64(int8(nc[1])) { 287 | if isByteOverflow(int32(off)) { 288 | // overfloat, cannot fix this with one byte operand 289 | return 2, FT_OVERFLOW, nc 290 | } 291 | nc[1] = byte(off) 292 | return 2, FT_CondJmp, nc 293 | } 294 | return 2, FT_SKIP, nc 295 | } 296 | 297 | if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) { 298 | // six byte condition jump 299 | nc = nc[:6] 300 | off1 := (uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24)) 301 | off2 := uint64(calcOffset(6, startAddr, curAddr, to, to_sz, int32(off1))) 302 | if uint64(int32(off1)) != off2 { 303 | if isIntOverflow(int64(off2)) { 304 | // overfloat, cannot fix this with four byte operand 305 | return 6, FT_OVERFLOW, nc 306 | } 307 | nc[2] = byte(off2) 308 | nc[3] = byte(off2 >> 8) 309 | nc[4] = byte(off2 >> 16) 310 | nc[5] = byte(off2 >> 24) 311 | return 6, FT_CondJmp, nc 312 | } 313 | return 6, FT_SKIP, nc 314 | } 315 | 316 | if code[0] == 0xe9 || code[0] == 0xe8 { 317 | // five byte jmp, five byte call 318 | nc = nc[:5] 319 | off1 := (uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24)) 320 | 321 | off2 := uint64(0) 322 | if !fix_recursive_call && code[0] == 0xe8 && startAddr == (curAddr+uintptr(5)+uintptr(int32(off1))) { 323 | // don't fix recursive call 324 | off2 = uint64(int32(off1)) 325 | } else { 326 | off2 = uint64(calcOffset(5, startAddr, curAddr, to, to_sz, int32(off1))) 327 | } 328 | 329 | if uint64(int32(off1)) != off2 { 330 | if isIntOverflow(int64(off2)) { 331 | // overfloat, cannot fix this with four byte operand 332 | return 5, FT_OVERFLOW, nc 333 | } 334 | nc[1] = byte(off2) 335 | nc[2] = byte(off2 >> 8) 336 | nc[3] = byte(off2 >> 16) 337 | nc[4] = byte(off2 >> 24) 338 | return 5, FT_JMP, nc 339 | } 340 | return 5, FT_SKIP, nc 341 | } 342 | 343 | // ret instruction just return, no fix is needed. 344 | if code[0] == 0xc3 || code[0] == 0xcb { 345 | // one byte ret 346 | nc = nc[:1] 347 | return 1, FT_RET, nc 348 | } 349 | 350 | if code[0] == 0xc2 || code[0] == 0xca { 351 | // three byte ret 352 | nc = nc[:3] 353 | return 3, FT_RET, nc 354 | } 355 | 356 | inst, err := x86asm.Decode(code, mode) 357 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) { 358 | return 0, FT_INVALID, nc 359 | } 360 | 361 | if inst.Len == 1 && code[0] == 0xcc { 362 | return 0, FT_INVALID, nc 363 | } 364 | 365 | sz := inst.Len 366 | nc = nc[:sz] 367 | return sz, FT_OTHER, nc 368 | } 369 | 370 | // instructions from target function will be copied to new location started at 'to' 371 | // those copied instruction thus might need to be adjusted, eg, the offset operand. 372 | // here we try to generate a fix to the instruction so that it can be copied to the new location correctly 373 | func doFixTargetFuncCode(all bool, mode int, start uintptr, funcSz int, to uintptr, move_sz int, inst []CodeFix) ([]CodeFix, error) { 374 | fix := make([]CodeFix, 0, 64) 375 | 376 | curSz := 0 377 | curAddr := start 378 | 379 | i := 0 380 | for i = 0; i < len(inst); i++ { 381 | if curSz >= move_sz { 382 | break 383 | } 384 | 385 | code := inst[i].Code 386 | sz, ft, nc := FixOneInstruction(mode, false, start, curAddr, code, to, move_sz) 387 | 388 | if sz == 0 && ft == FT_INVALID { 389 | // the end or unrecognized instruction 390 | return nil, errors.New(fmt.Sprintf("invalid instruction scanned, addr:0x%x", curAddr)) 391 | } else if sz == 5 && nc[0] == 0xe8 { 392 | // call instruction is not allowed to move. 393 | // this will mess up with golang stack reallocation. 394 | return nil, fmt.Errorf("call instruction is not allowed to move, addr:0x%x", curAddr) 395 | } 396 | 397 | if ft == FT_RET { 398 | return nil, errors.New(fmt.Sprintf("ret instruction in patching erea is not allowed, addr:0x%x", curAddr)) 399 | } 400 | 401 | if ft == FT_OVERFLOW { 402 | return nil, errors.New(fmt.Sprintf("jmp instruction in patching erea overflow, addr:0x%x", curAddr)) 403 | } 404 | 405 | if ft != FT_OTHER && ft != FT_SKIP { 406 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: true}) 407 | } else if all { 408 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: true}) 409 | } 410 | 411 | curSz += sz 412 | curAddr = start + uintptr(curSz) 413 | } 414 | 415 | for ; i < len(inst); i++ { 416 | if funcSz > 0 && int(curAddr-start) >= funcSz { 417 | break 418 | } 419 | 420 | code := inst[i].Code 421 | sz, ft, nc := FixOneInstruction(mode, false, start, curAddr, code, to, move_sz) 422 | 423 | if sz == 0 && ft == FT_INVALID { 424 | // the end or unrecognized instruction 425 | break 426 | } 427 | 428 | if ft == FT_OVERFLOW { 429 | return nil, errors.New(fmt.Sprintf("jmp instruction in body overflow, addr:0x%x", curAddr)) 430 | } 431 | 432 | if ft != FT_OTHER && ft != FT_RET && ft != FT_SKIP { 433 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: false}) 434 | } else if all { 435 | fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: false}) 436 | } 437 | 438 | curSz += sz 439 | curAddr = start + uintptr(curSz) 440 | } 441 | 442 | return fix, nil 443 | } 444 | 445 | // FixTargetFuncCode trys to fix function code starting at address [start] 446 | // parameter 'funcSz' may not specify, in which case, we need to find out the end by scanning next prologue or finding invalid instruction. 447 | // 'to' specifys a new location, to which 'move_sz' bytes of instruction will be copied 448 | // since move_sz bytes of instructions will be copied, those relative jump instructions need to be fixed(adjusted). 449 | func FixTargetFuncCode(mode int, start uintptr, funcSz uint32, to uintptr, move_sz int) ([]CodeFix, error) { 450 | inst, _ := parseInstruction(mode, start, int(funcSz), false) 451 | return doFixTargetFuncCode(false, mode, start, int(funcSz), to, move_sz, inst) 452 | } 453 | 454 | func GetFuncSizeByGuess(mode int, start uintptr, minimal bool) (uint32, error) { 455 | funcPrologue := funcPrologue64 456 | if mode == 32 { 457 | funcPrologue = funcPrologue32 458 | } 459 | 460 | prologueLen := len(funcPrologue) 461 | code := makeSliceFromPointer(start, 16) // instruction takes at most 16 bytes 462 | 463 | /* prologue is not required 464 | if !bytes.Equal(funcPrologue, code[:prologueLen]) { // not valid function start or invalid prologue 465 | return 0, errors.New(fmt.Sprintf("no func prologue, addr:0x%x", start)) 466 | } 467 | */ 468 | 469 | int3_found := false 470 | curLen := uint32(0) 471 | 472 | for { 473 | inst, err := x86asm.Decode(code, mode) 474 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) { 475 | break 476 | } 477 | 478 | if inst.Len == 1 && code[0] == 0xcc { 479 | // 0xcc -> int3, trap to debugger, padding to function end 480 | if minimal { 481 | break 482 | } 483 | int3_found = true 484 | } else if int3_found { 485 | break 486 | } 487 | 488 | curLen = curLen + uint32(inst.Len) 489 | code = makeSliceFromPointer(start+uintptr(curLen), 16) // instruction takes at most 16 bytes 490 | 491 | if bytes.Equal(funcPrologue, code[:prologueLen]) { 492 | break 493 | } 494 | } 495 | 496 | return curLen, nil 497 | } 498 | 499 | // sz size of source function 500 | // WARNING: copy function won't work in copystack(since go 1.3). 501 | // runtime will copy stack to new area and fix those weird stuff(pointer/rbp etc), this will crash trampoline function. 502 | // since copying function makes trampoline a completely different function, with completely different stack layout which is 503 | // not known to runtime. 504 | // solution to this is, we should just copy those non-call instructions to trampoline. in this way we don't mess up with runtime. 505 | // TODO/FIXME 506 | func copyFuncInstruction(mode int, from, to uintptr, sz int, allowCall bool) ([]CodeFix, error) { 507 | curSz := 0 508 | curAddr := from 509 | fix := make([]CodeFix, 0, 256) 510 | 511 | for { 512 | if curSz >= sz { 513 | break 514 | } 515 | 516 | code := makeSliceFromPointer(curAddr, 16) // instruction takes at most 16 bytes 517 | sz, ft, nc := FixOneInstruction(mode, true, from, curAddr, code, to, sz) 518 | 519 | if sz == 0 && ft == FT_INVALID { 520 | // the end or unrecognized instruction 521 | break 522 | } else if !allowCall && sz == 5 && nc[0] == 0xe8 { 523 | // call instruction is not allowed to move. 524 | // this will mess up with golang stack reallocation. 525 | return nil, fmt.Errorf("call instruction is not allowed to copy") 526 | } 527 | 528 | if ft == FT_OVERFLOW { 529 | return nil, fmt.Errorf("overflow instruction in copying function, addr:0x%x", curAddr) 530 | } 531 | 532 | to_addr := (to + (curAddr - from)) 533 | fix = append(fix, CodeFix{Code: nc, Addr: to_addr}) 534 | 535 | curSz += sz 536 | curAddr = from + uintptr(curSz) 537 | } 538 | 539 | to_addr := (to + (curAddr - from)) 540 | fix = append(fix, CodeFix{Code: []byte{0xcc}, Addr: to_addr}) 541 | return fix, nil 542 | } 543 | 544 | func adjustJmpOffset(mode int, start, delem uintptr, funcSize, moveSize int, inst []CodeFix) error { 545 | funcEnd := start + uintptr(funcSize) 546 | for i := range inst { 547 | code := inst[i].Code 548 | curAddr := inst[i].Addr 549 | absAddr := calcJumpToAbsAddr(mode, curAddr, code) 550 | 551 | if curAddr > delem && curAddr < funcEnd { 552 | inst[i].Addr = curAddr + uintptr(moveSize) 553 | } 554 | 555 | if absAddr != uintptr(0) { 556 | delta := absAddr - curAddr - uintptr(len(code)) 557 | off := int64(delta) 558 | if unsafe.Sizeof(uintptr(0)) == unsafe.Sizeof(int32(0)) { 559 | off = int64(int32(delta)) 560 | } 561 | 562 | // 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)) 563 | 564 | if (curAddr < delem || curAddr >= funcEnd) && absAddr > delem && absAddr < funcEnd { 565 | off += int64(moveSize) 566 | } else if (curAddr >= delem && curAddr < funcEnd) && (absAddr <= delem || absAddr >= funcEnd) { 567 | off -= int64(moveSize) 568 | } else { 569 | // do nothing 570 | } 571 | 572 | c, err := adjustInstructionOffset(code, off) 573 | if err != nil { 574 | return fmt.Errorf("err occurs adjusting inst, addr:%x,off:%x,err:%s", curAddr, off, err.Error()) 575 | } 576 | 577 | inst[i].Code = c 578 | // absAddr = calcJumpToAbsAddr(mode, inst[i].Addr, code) 579 | // fmt.Printf("after adjust inst, old addr:%x, new addr:%x, target:%x\n", curAddr, inst[i].Addr, absAddr) 580 | } 581 | } 582 | 583 | return nil 584 | } 585 | 586 | // trys to translate short jump that is not reachable when moved to new location. 587 | func translateShortJump(mode int, addr, to uintptr, inst []CodeFix, funcSz, move_sz, jumpSize int) (int, []CodeFix, error) { 588 | newSz := 0 589 | fix := make([]CodeFix, 0, 256) 590 | 591 | for i := range inst { 592 | code := inst[i].Code 593 | curAddr := inst[i].Addr 594 | sz, ft, _ := FixOneInstruction(mode, false, addr, curAddr, code, to, move_sz) 595 | 596 | if sz == 0 && ft == FT_INVALID { 597 | // the end or unrecognized instruction 598 | break 599 | } 600 | 601 | // whether current instruction will be copied to new location 602 | foreign := false 603 | if curAddr < addr+uintptr(move_sz) { 604 | foreign = true 605 | } 606 | 607 | if ft == FT_OVERFLOW { 608 | if sz != 2 { 609 | return 0, nil, fmt.Errorf("inst overflow with size != 2") 610 | } 611 | 612 | nc, err := translateJump(int64(int8(code[1])), code) 613 | if err != nil { 614 | return 0, nil, err 615 | } 616 | 617 | delta := len(nc) - len(code) 618 | if curAddr < addr+uintptr(move_sz) { 619 | move_sz += delta 620 | } 621 | 622 | inst[i].Code = nc 623 | // fmt.Printf("extent overflow inst at:%x, sz:%d, move sz:%d\n", curAddr, len(nc), move_sz) 624 | 625 | err = adjustJmpOffset(mode, addr, curAddr, funcSz, delta, inst[i:]) 626 | if err != nil { 627 | return 0, nil, err 628 | } 629 | 630 | err = adjustJmpOffset(mode, addr, curAddr, funcSz, delta, fix) 631 | if err != nil { 632 | return 0, nil, err 633 | } 634 | } 635 | 636 | newSz += len(inst[i].Code) 637 | fix = append(fix, CodeFix{Code: inst[i].Code, Addr: inst[i].Addr, Foreign: foreign}) 638 | } 639 | 640 | if newSz-move_sz > funcSz-jumpSize { 641 | return move_sz, fix, errInplaceFixSizeNotEnough 642 | } 643 | 644 | return move_sz, fix, nil 645 | } 646 | 647 | func parseInstruction(mode int, addr uintptr, funcSz int, minimal bool) ([]CodeFix, error) { 648 | funcPrologue := funcPrologue64 649 | if mode == 32 { 650 | funcPrologue = funcPrologue32 651 | } 652 | 653 | prologueLen := len(funcPrologue) 654 | code := makeSliceFromPointer(addr, 16) // instruction takes at most 16 bytes 655 | 656 | curLen := 0 657 | int3_found := false 658 | 659 | ret := make([]CodeFix, 0, 258) 660 | 661 | for { 662 | if funcSz > 0 && curLen >= funcSz { 663 | break 664 | } 665 | 666 | inst, err := x86asm.Decode(code, mode) 667 | if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) { 668 | break 669 | } 670 | 671 | if inst.Len == 1 && code[0] == 0xcc { 672 | // 0xcc -> int3, trap to debugger, padding to function end 673 | if minimal { 674 | break 675 | } 676 | int3_found = true 677 | } else if int3_found { 678 | break 679 | } 680 | 681 | c := make([]byte, inst.Len) 682 | copy(c, code) 683 | cf := CodeFix{Addr: addr + uintptr(curLen), Code: c} 684 | ret = append(ret, cf) 685 | 686 | curLen = curLen + inst.Len 687 | code = makeSliceFromPointer(addr+uintptr(curLen), 16) 688 | 689 | if bytes.Equal(funcPrologue, code[:prologueLen]) { 690 | break 691 | } 692 | } 693 | 694 | return ret, nil 695 | } 696 | 697 | // fixFuncInstructionInplace trys to modify instruction directly in the original function by extending its instruction size if necessary. 698 | // this is impossible sometime, since origin function size may not be large enough 699 | func fixFuncInstructionInplace(mode int, addr, to uintptr, funcSz int, move_sz int, jumpSize int) ([]CodeFix, error) { 700 | /* 701 | trail := makeSliceFromPointer(addr+uintptr(funcSz), 1024) 702 | for i := 0; i < len(trail); i++ { 703 | if trail[i] != 0xcc { 704 | break 705 | } 706 | funcSz++ 707 | } 708 | */ 709 | 710 | code, _ := parseInstruction(mode, addr, funcSz, false) 711 | move_sz, fix, err := translateShortJump(mode, addr, to, code, funcSz, move_sz, jumpSize) 712 | 713 | if err != nil { 714 | return nil, err 715 | } 716 | 717 | fix, err1 := doFixTargetFuncCode(true, mode, addr, funcSz, to, move_sz, fix) 718 | 719 | if err1 != nil { 720 | return fix, err1 721 | } 722 | 723 | curAddr := to 724 | firstBody := addr 725 | for i := range fix { 726 | if !fix[i].Foreign { 727 | firstBody = fix[i].Addr 728 | break 729 | } 730 | 731 | // fmt.Printf("foreign addr:%x, sz:%d\n", curAddr, len(fix[i].Code)) 732 | 733 | fix[i].Addr = curAddr 734 | curAddr += uintptr(len(fix[i].Code)) 735 | } 736 | 737 | mvAddr := addr + uintptr(jumpSize) 738 | msz := -int(firstBody - mvAddr) 739 | 740 | if msz != 0 { 741 | // fmt.Printf("now move to the front, msz:%d\n", msz) 742 | err2 := adjustJmpOffset(mode, addr, mvAddr, funcSz, msz, fix) 743 | if err2 != nil { 744 | // fmt.Printf("error in fixing inplace\n") 745 | return nil, err2 746 | } 747 | } 748 | 749 | // fmt.Printf("done fixing inplace\n") 750 | return fix, nil 751 | } 752 | 753 | func genJumpCode(mode int, rdxIndirect bool, to, from uintptr) []byte { 754 | // 1. use relaive jump if |from-to| < 2G 755 | // 2. otherwise, push target, then ret 756 | 757 | var code []byte 758 | 759 | if rdxIndirect { 760 | // rdx indirect jump. 761 | // 'to' :data pointer from reflect.Value, pointed to a funcValue, and the first field of funcval is a pointer to the real func. 762 | // 'from': this is the instruction code addr of the target function. 763 | 764 | // by convention, rdx is the context register pointed to a funcval. 765 | // funcval of a closure function contains extra information used by compiler and runtime. 766 | // so using indirect jmp by rdx makes it possible to hook closure func and func created by reflect.MakeFunc 767 | 768 | // caution: 'to' funcval must stay alive after hook is installed. 769 | if mode == 32 { 770 | code = []byte{ 771 | 0xBA, 772 | byte(to), 773 | byte(to >> 8), 774 | byte(to >> 16), 775 | byte(to >> 24), // mov edx,to 776 | 0xFF, 0x22, // jmp DWORD PTR [edx] 777 | } 778 | } else { 779 | code = []byte{ 780 | 0x48, 0xBA, 781 | byte(to), 782 | byte(to >> 8), 783 | byte(to >> 16), 784 | byte(to >> 24), 785 | byte(to >> 32), 786 | byte(to >> 40), 787 | byte(to >> 48), 788 | byte(to >> 56), // movabs rdx,to 789 | 0xFF, 0x22, // jmp QWORD PTR [rdx] 790 | } 791 | } 792 | } else { 793 | delta := int64(from - to) 794 | if unsafe.Sizeof(uintptr(0)) == unsafe.Sizeof(int32(0)) { 795 | delta = int64(int32(from - to)) 796 | } 797 | 798 | relative := (delta <= 0x7fffffff) 799 | 800 | if delta < 0 { 801 | delta = -delta 802 | relative = (delta <= 0x80000000) 803 | } 804 | 805 | // relative = false 806 | 807 | if relative { 808 | var dis uint32 809 | if to > from { 810 | dis = uint32(int32(to-from) - 5) 811 | } else { 812 | dis = uint32(-int32(from-to) - 5) 813 | } 814 | code = []byte{ 815 | 0xe9, 816 | byte(dis), 817 | byte(dis >> 8), 818 | byte(dis >> 16), 819 | byte(dis >> 24), 820 | } 821 | } else if mode == 32 { 822 | code = []byte{ 823 | 0x68, // push 824 | byte(to), 825 | byte(to >> 8), 826 | byte(to >> 16), 827 | byte(to >> 24), 828 | 0xc3, // retn 829 | } 830 | } else if mode == 64 { 831 | // push does not operate on 64bit imm, workarounds are: 832 | // 1. move to register(eg, %rdx), then push %rdx, however, overwriting register may cause problem if not handled carefully. 833 | // 2. push twice, preferred. 834 | /* 835 | code = []byte{ 836 | 0x48, // prefix 837 | 0xba, // mov to %rdx 838 | byte(to), byte(to >> 8), byte(to >> 16), byte(to >> 24), 839 | byte(to >> 32), byte(to >> 40), byte(to >> 48), byte(to >> 56), 840 | 0x52, // push %rdx 841 | 0xc3, // retn 842 | } 843 | */ 844 | code = []byte{ 845 | 0x68, //push 846 | byte(to), byte(to >> 8), byte(to >> 16), byte(to >> 24), 847 | 0xc7, 0x44, 0x24, // mov $value, 4%rsp 848 | 0x04, // rsp + 4 849 | byte(to >> 32), byte(to >> 40), byte(to >> 48), byte(to >> 56), 850 | 0xc3, // retn 851 | } 852 | } else { 853 | panic("invalid mode") 854 | } 855 | } 856 | 857 | sz := len(code) 858 | if minJmpCodeSize > 0 && sz < minJmpCodeSize { 859 | nop := make([]byte, 0, minJmpCodeSize-sz) 860 | for { 861 | if len(nop) >= minJmpCodeSize-sz { 862 | break 863 | } 864 | nop = append(nop, 0x90) 865 | } 866 | 867 | code = append(code, nop...) 868 | } 869 | 870 | return code 871 | } 872 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // try to extend jump instructions inplace of the original function. 181 | // so that we can move part of its instructions later. 182 | // note: first attempt above will fail only when jump instruction overflow. 183 | err1 := doFixFuncInplace(mode, target, trampoline, int(sz1), insLen, info, len(jumpcode)) 184 | if err1 != nil { 185 | // inplace-fix fails. 186 | // next we try to copy the whole target function to trampline. 187 | // this will succ only when original function is too small to carry out a inplace fix. 188 | info.How = "copy" 189 | origin, err2 := doCopyFunction(mode, false, target, trampoline, sz1, sz2, info) 190 | if err2 != nil { 191 | return nil, fmt.Errorf("both fix/fix2/copy failed, fix:%s, fix2:%s, copy:%s", err.Error(), err1.Error(), err2.Error()) 192 | } 193 | info.TrampolineOrig = origin 194 | } else { 195 | info.How = "adjust" 196 | /* 197 | ts = makeSliceFromPointer(target, len(jumpcode)+65) 198 | insLen = GetInsLenGreaterThan(mode, ts, len(jumpcode)) 199 | ts = makeSliceFromPointer(target, insLen) 200 | */ 201 | } 202 | } else { 203 | info.How = "fix" 204 | for _, v := range fix { 205 | origin := makeSliceFromPointer(v.Addr, len(v.Code)) 206 | f := make([]byte, len(v.Code)) 207 | copy(f, origin) 208 | CopyInstruction(v.Addr, v.Code) 209 | v.Code = f 210 | info.Fix = append(info.Fix, v) 211 | } 212 | 213 | jumpcode2 := genJumpCode(mode, false, target+uintptr(target_body_off), trampoline+uintptr(insLen)) 214 | f2 := makeSliceFromPointer(trampoline, insLen+len(jumpcode2)*2) 215 | insLen2 := GetInsLenGreaterThan(mode, f2, insLen+len(jumpcode2)) 216 | info.TrampolineOrig = make([]byte, insLen2) 217 | ts2 := makeSliceFromPointer(trampoline, insLen2) 218 | copy(info.TrampolineOrig, ts2) 219 | CopyInstruction(trampoline, ts) 220 | CopyInstruction(trampoline+uintptr(insLen), jumpcode2) 221 | } 222 | } 223 | 224 | CopyInstruction(target, jumpcode) 225 | return info, nil 226 | } 227 | 228 | func printInstructionFix(v CodeFix, origin []byte) { 229 | for _, c := range v.Code { 230 | fmt.Printf(" %x", c) 231 | } 232 | 233 | fmt.Printf(", origin:") 234 | for _, c := range origin { 235 | fmt.Printf(" %x", c) 236 | } 237 | fmt.Printf("\n") 238 | } 239 | 240 | func GetFuncAddr(f interface{}) uintptr { 241 | fv := reflect.ValueOf(f) 242 | return fv.Pointer() 243 | } 244 | -------------------------------------------------------------------------------- /utility_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package gohook 5 | 6 | var ( 7 | defaultFuncPrologue64 = []byte{0x65, 0x48, 0x8b, 0x0c, 0x25, 0x30, 0x00, 0x00, 0x00, 0x48} 8 | defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff} 9 | ) 10 | -------------------------------------------------------------------------------- /utility_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package gohook 5 | 6 | var ( 7 | defaultFuncPrologue64 = []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48} 8 | defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff} 9 | ) 10 | -------------------------------------------------------------------------------- /utility_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package gohook 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | func getPageAddr(ptr uintptr) uintptr { 11 | return ptr & ^(uintptr(syscall.Getpagesize() - 1)) 12 | } 13 | 14 | func setPageWritable(addr uintptr, length int, prot int) { 15 | pageSize := syscall.Getpagesize() 16 | for p := getPageAddr(addr); p < addr+uintptr(length); p += uintptr(pageSize) { 17 | page := makeSliceFromPointer(p, pageSize) 18 | err := syscall.Mprotect(page, prot) 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | } 24 | 25 | func CopyInstruction(location uintptr, data []byte) { 26 | f := makeSliceFromPointer(location, len(data)) 27 | setPageWritable(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC) 28 | sz := copy(f, data[:]) 29 | setPageWritable(location, len(data), syscall.PROT_READ|syscall.PROT_EXEC) 30 | if sz != len(data) { 31 | panic("copy instruction to target failed") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /utility_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package gohook 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var ( 12 | defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff} 13 | defaultFuncPrologue64 = []byte{0x65, 0x48, 0x8B, 0x0C, 0x25, 0x28, 0x00, 0x00, 0x00} 14 | ) 15 | 16 | const PAGE_EXECUTE_READWRITE = 0x40 17 | 18 | var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect") 19 | 20 | func virtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error { 21 | ret, _, _ := procVirtualProtect.Call( 22 | lpAddress, 23 | uintptr(dwSize), 24 | uintptr(flNewProtect), 25 | uintptr(lpflOldProtect)) 26 | if ret == 0 { 27 | return syscall.GetLastError() 28 | } 29 | return nil 30 | } 31 | 32 | func CopyInstruction(location uintptr, data []byte) { 33 | f := makeSliceFromPointer(location, len(data)) 34 | 35 | var oldPerms uint32 36 | err := virtualProtect(location, len(data), PAGE_EXECUTE_READWRITE, unsafe.Pointer(&oldPerms)) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | copy(f, data[:]) 42 | 43 | var tmp uint32 44 | err = virtualProtect(location, len(data), oldPerms, unsafe.Pointer(&tmp)) 45 | if err != nil { 46 | panic(err) 47 | } 48 | } 49 | --------------------------------------------------------------------------------