python中普通方法与c函数的hook挂钩

前言

java可以在不改用户代码的前提下,hook一些方法,做一些扩展,在python下虽然也可以hook方法,但是需要用户插入一行代码用于触发。

普通方法hook

python中的hook可玩性高,他hook方法比较简单,下面看一个例子,这个例子最早可在stackoverflow中找到,虽然这里还有几个不明白的地方,但是秉着一句话,能跑就行。

python 复制代码
import ctypes
import user
def magic_get_dict(o):
    dict_addr = id(o) + type(o).__dictoffset__
    dict_ptr = ctypes.cast(dict_addr, ctypes.POINTER(ctypes.py_object))
    return dict_ptr.contents.value

def magic_flush_mro_cache():
    ctypes.PyDLL(None).PyType_Modified(ctypes.cast(id(object), ctypes.py_object))

class Wrapper:
    def __init__(self,origin):
        self.origin_method=origin
    def __call__(self, *args, **kwargs):
        origin_value=self.origin_method(*args,*kwargs)
        if origin_value=="user1":
            return "user2"
        return origin_value

def wrapper_invoke(s,b,origin):
    origin(s, b)

origin_method =getattr(user,"get_name")

dct = magic_get_dict(user)
dct['get_name'] =Wrapper(origin_method)

magic_flush_mro_cache()

print(user.get_name())

主要用到的是ctypes模块,这个模块是用于和C沟通的一个桥梁,比起java来,python调用dll、so函数方便的要很多,实现hook的思想是:对象中有函数,函数会有一个地址,我们只要替换这个函数地址指向新的函数即可,并且保存原地址,在新函数中以便在调用原函数。

而上面的magic_get_dict方法就是获取对象中这个函数的信息,结果是一个字典,通过dct['get_name']=xx就可以替换原来的函数了。

最后需要执行刷新操作。

虽然上述能完成我们想要的需要,但是有两个问题,获取字典可以通过__dict__,为什么还要通过那么复杂的方法呢,是因为用ctypes这种方法,可以做到修改内置类型中的方法,比如str,你可以替换str.count()的实现,而使用str.__dict__获取的字典,是不允许被修改的。

而最后需要执行flush的操作,看样子意思应该是把新函数地址刷新一下,但是在linux上测试,好像有没有都一样,或者是,在某些场景下不flush真的会失效,暂时没见过这种场景。

但上面代码还有一个问题,magic_get_dict函数需要传一个对象,我们作为hook者只知道函数完整的路径,我们不能import他,解决方法是使用__import__函数,他可以动态地导入模块,允许我们在运行时根据字符串的内容导入一个模块,而不是在代码中直接指定模块名。

下面是修改了的代码

python 复制代码
import ctypes
import user


def magic_get_dict(o):
    dict_addr = id(o) + type(o).__dictoffset__
    dict_ptr = ctypes.cast(dict_addr, ctypes.POINTER(ctypes.py_object))
    return dict_ptr.contents.value

def magic_flush_mro_cache():
    ctypes.PyDLL(None).PyType_Modified(ctypes.cast(id(object), ctypes.py_object))

class Wrapper:
    def __init__(self,origin):
        self.origin_method=origin
    def __call__(self, *args, **kwargs):
        origin_value=self.origin_method(*args,*kwargs)
        if origin_value=="user1":
            return "user2"
        return origin_value

def wrapper_invoke(s,b,origin):
    origin(s, b)

user_module=__import__("user",globals(),locals(),[],0)
origin_method =getattr(user_module,"get_name")

dct = magic_get_dict(user_module)
dct['get_name'] =Wrapper(origin_method)

magic_flush_mro_cache()

print(user.get_name())

操作系统函数hook

python还可以hook c层的函数,由于python调用windll等非常方便,可能有人使用window/linux的函数,你也可以hook住window的函数,这个实现需要借助funchook这个库,github地址是https://github.com/kubo/funchook

下载下来进行编译,以下是linux中的例子。

java 复制代码
$ git clone --recursive https://github.com/kubo/funchook.git
$ mkdir build
$ cd build
$ cmake ..
$ make
$ make install

写一点点测试代码,首先同样是获取原函数的指针(hook最重要的一步是获取原函数地址),然后在调用funchook安装挂钩即可。

c 复制代码
#include <funchook.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static int  (*getpid_func)();

static int getpid_hook() {
    printf("origin getpid==%d\n",getpid_func());
    return 0;
}

int main() {
    funchook_t *funchook = funchook_create();

    getpid_func = getpid;
    funchook_prepare(funchook, (void**)&getpid_func, getpid_hook);
    funchook_install(funchook, 0);
    printf("%d\n",getpid());
    return 0;
}

输出如下,可以看到在调用getpid时,先进入了我们的hook函数,然后通过getpid_func指针就可以调用原函数了,想做你想做的事。

c 复制代码
origin getpid==8809  
0

那接下来看python的hook,下面这段代码是调用getpid,输出没有任何问题。

python 复制代码
import ctypes
libc = ctypes.CDLL("libc.so.6")
pid = libc.getpid()
print("ID:", pid)

然后在看hook,python使用funchook还是有一点点麻烦的。

python 复制代码
import ctypes
fh_lib = ctypes.cdll.LoadLibrary('/usr/local/lib/libfunchook.so')
libc = ctypes.CDLL("libc.so.6")
funchook_create = fh_lib.funchook_create
funchook_create.restype = ctypes.c_void_p
funchook_create.argtypes = []

funchook_prepare = fh_lib.funchook_prepare
funchook_prepare.restype = ctypes.c_ssize_t
funchook_prepare.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]

funchook_install = fh_lib.funchook_install
funchook_install.restype = ctypes.c_ssize_t
funchook_install.argtypes = [ctypes.c_void_p, ctypes.c_int]

funchook_libc_getpid = libc.getpid
funchook_libc_getpid.restype = ctypes.c_int
funchook_libc_getpid.argtypes=[]

global origin_getpid_func, hook, origin_getpid_func_ptr

hook_type = ctypes.PYFUNCTYPE(ctypes.c_int )
origin_getpid_func = None
def hook_impl():
    print("origin",origin_getpid_func())
    return  1

hook = hook_type(hook_impl)

fh = funchook_create()
origin_getpid_func_ptr = ctypes.c_void_p(ctypes.c_void_p.from_address(ctypes.addressof(funchook_libc_getpid)).value)
ret = funchook_prepare(fh, ctypes.addressof(origin_getpid_func_ptr), hook)
ret = funchook_install(fh, 0)
origin_getpid_func = hook_type.from_address(ctypes.addressof(origin_getpid_func_ptr))
pid = libc.getpid()

print("ID:", pid)

首先是定义要调用函数的返回值类型和参数,如下面这段是定义getpid的参数类型和返回值类型。

python 复制代码
funchook_libc_getpid = libc.getpid 
funchook_libc_getpid.restype = ctypes.c_int 
funchook_libc_getpid.argtypes=[]

在通过一系列的地址指针转换操作,安装挂钩即可,在调用libc.getpid()时,首先会进入hook_impl方法,origin_getpid_func变量保存着原来函数地址指针,可以进行调用,上面代码我们只hook一下返回一个常量1。

funchook和ctypes这部分知识用到的还蛮多的,在下一篇继续讨论。

相关推荐
孤雪心殇3 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
小突突突4 小时前
模拟实现Java中的计时器
java·开发语言·后端·java-ee
web137656076434 小时前
Scala的宝藏库:探索常用的第三方库及其应用
开发语言·后端·scala
闲猫5 小时前
go 反射 interface{} 判断类型 获取值 设置值 指针才可以设置值
开发语言·后端·golang·反射
LUCIAZZZ6 小时前
EasyExcel快速入门
java·数据库·后端·mysql·spring·spring cloud·easyexcel
Asthenia04126 小时前
依托IOC容器提供的Bean生命周期,我们能在Bean中做些什么?又能测些什么?
后端
Ase5gqe6 小时前
Spring中的IOC详解
java·后端·spring
小万编程6 小时前
基于SpringBoot+Vue奖学金评比系统(高质量源码,可定制,提供文档,免费部署到本地)
java·spring boot·后端·毕业设计·计算机毕业设计·项目源码
南雨北斗7 小时前
ThinkPHP6控制器方法返回的 Content-Type类型
后端
CryptoRzz7 小时前
springboot接入方式对接股票数据源API接口
后端