Gohook
gohook有以下优点:
- 跳转效率更高
- 更安全可靠
- 支持回调旧函数(最大优点)
- 不依赖 runtime 内部实现
gohook 实现了对函数的暴力拦截,无论是普通函数,还是成员函数都可以强行拦截替换,并支持回调原来的旧函数。
gohook 有以下几个方法 :
func Hook(target, replace, trampoline interface{}) error;
func UnHook(target interface{}) error;
func HookMethod(instance interface{}, method string, replace, trampoline interface{}) error;
func UnHookMethod(instance interface{}, method string) error;
func HookByIndirectJmp(target, replace, trampoline interface{});
一般来说我们使用前面四种方法就已经可以满足日常需求了:
对于 Hook
方法,其接受三个参数,第一个参数是要 hook 的目标原函数,第二个参数是用来替换的函数,第三个参数用来支持回调旧函数。当 hook 完成后,会调用 trampoline,其相当于调用旧的目标函数(target),第三个参数可以传入 nil,此时表示不需要支持回调旧函数。
func originFunc() {
str := "Hi, origin func"
fmt.Println(str)
}
func monkeyFunc() {
str := "Hi, monkey func"
fmt.Println(str)
}
func main() {
gohook.Hook(originFunc, monkeyFunc, nil)
originFunc()
}
运行结果是:
Hi, monkey func
替换后回调原函数:
func originFunc() {
str := "Hi, origin func"
fmt.Println(str)
}
func monkeyFunc() {
str := "Hi, monkey func"
fmt.Println(str)
trampolineFunc()
}
func trampolineFunc() {
}
func main() {
gohook.Hook(originFunc, monkeyFunc, trampolineFunc)
originFunc()
}
运行结果:
Hi, monkey func
Hi, origin func
这里的 trampoline
函数内的内容是什么并不重要,只是为了给原函数申请空间。
除了hook普通过程函数外,还可以使用 HookMethod
方法 hook 成员函数。
一个实用的场景:比如我们想替换 time.Now() 函数,使其在每次调用时,返回一个固定时间:
func myTime() time.Time {
return time.Date(2022, 1, 1, 0, 0, 0, 0, &time.Location{})
}
func main() {
fmt.Println(time.Now())
gohook.Hook(time.Now, myTime, nil)
fmt.Println(time.Now())
}
运行结果:
2023-03-09 12:32:17.547981 +0800 CST m=+0.004697201
2022-01-01 00:00:00 +0000 UTC
注意事项
- gohook 项目主要是用来辅助作测试,最好不要用于生产环境。
- 过小的函数有可能会变成内联函数,在编译期间被优化掉,这样在运行时就无法 hook了。这也是上面例子中将
fmt.Println(str)
和str := "Hi, origin func"
拆开的原因。(编译时加上-gcflags='-m'
选项可以查看哪些函数被 inline,另外也可以通过// go:noline
或-gcflags=all='-l'
来告诉编译器不要对其进行 inline)。 - 跳转指令取决于硬件平台,该实现只支持 x86/x64 架构。