对x86平台更熟悉一些,所以就写一下x86的吧,至于arm平台,只是寄存器有些不同,其他的思路应该还是差不多的。
其实主要是对了解的知识做一下记录,方便以后翻出来看看
Linux函数hook一般是使用ptrace函数,对指定进程使用ptrace,可以暂停进程,并可以读/写目标进程指定内存/寄存器,整个hook的流程其实很简单,
假设我们要替换运行进程P中的某个函数调用M_org,大致的思路如下:
1,生成一个动态库libInject.so,里面包含了用来替换M_org的函数M_replace;另外还有一个hook_work函数用来做替换操作;
2,获得目标进程P的进程号tPid;
3,分别获得加载到目标进程P中的dlopen,dlsym,dlclose,dlerror函数地址;
4,通过上一步获得的目标进程dlopen函数地址,在目标进程里加载libInject.so,并取得返回的so句柄;
5,调用dlsym获得目标进程中hook_work函数(刚才已经加载libInject.so了,这个函数位于这个模块里)地址,并调用它;
hook_work主要做以下工作:
w1,通过解析包含M_org函数的动态库,假设叫libM.so(如果这个函数直接位于目标程序里,则解析目标binary文件),找到libM.so在被加载模块中对应的GOT表相对于libM.so被加载位置偏移量;
w2,获得libM.so在目标进程P中加载位置,再加上一步获得的GOT偏移量,可获得libM.so的GOT表在目标进程中的地址;
w3,获得M_org的地址,并遍历第w2步获得的GOT表项,如果有一项对应的地址和M_org地址相同,则将这个表项里的地址替换成M_replace函数地址
整个步骤完成之后,以后每次调用M_org函数时,在GOT表项里找到的实际是M_replace函数地址,也就是说实际执行的是M_replace函数!
注意android的链接加载器和linux的链接加载器稍微有点不同,而正是这点不同之处可以使得w1~w3能正常工作:
标准Linux链接器是ld.so,支持Lazy绑定,引用模块A在真正开始调用被应用模块B中符号时,才将该符号地址写到A中于B对应的GOT表项中,也就是说,模块A在编译期间生成的调用模块B的原始代码,流程是从调用代码到PLT表到链接器。运行期第一次调模块乙时,首先进入链接器,链接器根据调用信息加载模块B搜寻其符号并将找到的函数地址填入GOT表,之后的后续调用流程就直接走PLT/GOT表了。这种机制能减少加载时的开销。
Android虽然内核基于Linux,但其动态链接机制却不是ld.so而是自带的linker,不支持Lazy绑定。也就是说,上述模块AB如果在Android平台上,则是模块A加载时,linker就会根据模块A中的.rel.plt表和字符串表中的内容加载模块B并搜索其所需