Android Hook看这篇就够了!(建议收藏)
大家好,我是皇叔,最近开了一个安卓进阶涨薪训练营,可以帮助大家突破技术&职场瓶颈,从而度过难关,进入心仪的公司。
详情见文章:没错!皇叔开了个训练营
来源:看雪论坛
看雪论坛作者ID:随风而行aa
一
前言
二
编译原理
1.编译过程

预处理阶段:预处理器(cpp)根据以字符#开头的命令修给原始的C程序,结果得到另一个C程序,通常以.i作为文件扩展名。主要是进行文本替换、宏展开、删除注释这类简单工作。
命令行:gcc -E hello.c hello.i
编译阶段:将文本文件hello.i翻译成hello.s,包含相应的汇编语言程序。
汇编阶段:将.S文件翻译成机器指令,然后把这些指令打包成一种可重定位目标程序的格式,并把结果保存在目标文件.o中(汇编——>机器)。
命令行:gcc -c hello.c hello.o
链接阶段:hello程序调用了printf函数,链接器(Id)就把printf.o文件并入hello.o文件中,得到hello可执行文件,然后加载到存储器中由系统执行。
函数库包括静态库和动态库
静态库:编译链接时,把库文件代码全部加入可执行文件中,运行时不需要库文件,后缀为.a。
动态库:编译链接时,不加入,在程序执行时,由运行时链接文件加载库,这样节省开销,后缀为.so。(gcc编译时默认使用动态库)
再经过汇编器和连接器的作用后输出一个目标文件,这个目标文件为可执行文件。
(1)链接方式

(2)链接库
命名规范为libXXX.a库函数会被连接进可执行程序,可执行文件体积较大可执行文件运行时,不需要从磁盘载入库函数,执行效率较高库函数更新后,需要重新编译可执行程序
命名规范为libXXX.so库函数不被连接进可执行程序,可执行文件体积较小可执行文件运行时,库函数动态载入使用灵活,库函数更新后,不需要重新编译可执行程序
2.可执行文件(ELF)
(1)ELF文件结构




(2)GOT和PLT
(1)如果是在其他中间文件中已经定义了的函数,链接阶段可以直接重定位到函数地址,比如我们从头文件访问另一个函数。
(2)如果是在动态库中定义了的函数,链接阶段无法直接重定位到函数地址,只能生成额外的小片段代码,也就是PLT表,然后重定位到该代码片段。




三
NDK基础知识
1.Android so文件的类型

adb shellcat /proc/cpuinfo

2.so文件加载
//加载的是libnative-lib.so,注意的是这边只需要传入"native-lib"System.loadLibrary("native-lib");//传入的是so文件完整的绝对路径System.load("/data/data/应用包名/lib/libnative-lib.so")
public static void load(String pathName) {Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());}public static void loadLibrary(String libName) {Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());}
void load(String absolutePath, ClassLoader loader) {if (absolutePath == null) {throw new NullPointerException("absolutePath == null");}String error = doLoad(absolutePath, loader);if (error != null) {throw new UnsatisfiedLinkError(error);}}public void loadLibrary(String nickname) {loadLibrary(nickname, VMStack.getCallingClassLoader());}void loadLibrary(String libraryName, ClassLoader loader) {if (loader != null) {String filename = loader.findLibrary(libraryName);if (filename == null) {...
public static void load(String filename) {Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);}public static void loadLibrary(String libname) {Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);}
synchronized void load0(Class fromClass, String filename) {if (!(new File(filename).isAbsolute())) {throw new UnsatisfiedLinkError("Expecting an absolute path of the library: " + filename); }if (filename == null) {throw new NullPointerException("filename == null");}String error = doLoad(filename, fromClass.getClassLoader());if (error != null) {throw new UnsatisfiedLinkError(error);}}public void loadLibrary(String libname, ClassLoader classLoader) {java.lang.System.logE("java.lang.Runtime#loadLibrary(String, ClassLoader)" +" is private and will be removed in a future Android release");loadLibrary0(classLoader, libname);}
四
各类hook技术原理分析
1.Xposed hook技术


2.Frida hook技术

accessflags = nativeentry_point_fromjni = 自定义代码的入口entry_point_from_quick_compiledcode = art_quick_generic_jni_trampoline函数的地址entry_point_frominterpreter = artInterpreterToCompiledCodeBridge函数地址
3.inlinehook 技术
(1)基本原理
(2)inlineHook组成
(3)inlineHook实现


(4)Android-Inline-Hook和SandHook 技术
4.PLT/GOT hook技术
5.Unicorn hook技术
五
各类hook技术实操
1.Xposed hook实操
(1)环境安装
(1) 4.4以下Android版本安装比较简单,只需要两步即可1.对需要安装Xposed的手机进行root2.下载并安装xposedInstaller,之后授权其root权限,进入app点击安装即可但是由于官网不在维护,导致无法直接通过xposedinstaller下载补丁包(2)Android 5.0-8.0 由于5.0后出现ART,所以安装步骤分成两个部分:xposed.zip 和XposedInstaller.apk,zip文件是框架主体,需要进入Recovery后刷入,apk文件用于Xposed管理1.完成对手机的root,并刷入reconvery(比如twrp),使用Superroot2.下载你对应的zip补丁包,并进入recovery刷入3.重启手机,安装xposedInstaller并授予root权限即可官网地址:https://dl-xda.xposed.info/framework/(3)由于Android 8.0后,Xposed官方作者没有再对其更新,我们一般就使用国内大佬riyu的Edxposed框架Magisk + riyu + Edxposed
asop镜像:https://developers.google.com/android/ota#hammerheadtwrp: https://twrp.me/xposed: https://dl-xda.xposed.info/framework/xposed installer https://repo.xposed.info/module/de.robv.android.xposed.installer
fastboot flash recovery twrp-3.4.0-0-hammerhead.img




(2)Xposed插件编写

<meta-dataandroid:name="xposedmodule" //是否配置为Xposed插件,设置为trueandroid:value="true"/><meta-dataandroid:name="xposeddescription" //模块名称android:value="模块描述"/><meta-dataandroid:name="xposedminversion" //最低版本号android:value="54"/>
进入app目录下的build.gradle文件,compile fileTree(includes:['*.jar'],dir:'libs')替换成provided fileTree(includes:['*.jar'],dir:'libs')现在provided变为 compileOnly如果使用compile,可以正常编译生成插件apk,但是当安装到手机上后,xposed会报错,无法正常工作
public class Xposed01 implements IXposedHookLoadPackage {public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {if(loadPackageParam.packageName.equals("com.example.xposedlesson2")){ //判断目标包名XposedBridge.log("XLZH"+loadPackageParam.packageName); //打出包名的信息Log.i("Xposed01",loadPackageParam.packageName);}}}



2.frida hook实操
(1)环境安装
pip install frida==12.8.0pip install frida-tools==5.3.0pip install objection==1.8.4
frida --versionobjection --help

(2)frida使用
常见的hook命令:objection -g com.android.settings explore //注入设置应用android hooking list activities //查看Activity,service相同android intent launch_activity com.android.settings.DisplaySettings //实现Activity跳转android heap search instances com.android.settings.DisplaySettings //搜索类的实例android heap execute 0x2526 getPreferenceScreenResId //主动调用实例android hooking list classes //列出内存中所有类android hooking search methods display //列出内存中所有的方法android hooking watch class android.bluetooth.BluetoothDevice //hook相关类的所有方法android hooking watch class_method android.bluetooth.BluetoothDevice.getName --dump-args --dump-return --dump-backtrace //打印具体方法的参数、返回值、堆栈信息

attach方式 frida -U com.example.test -l hook.jsspwan启动 frida -U -f com.example.test -l demo1.js --no-pause
3.inlinehook实操
(1)Android-lnine-Hook
<1>编写目标函数so文件

<2>导入文件


<3>修改配置文件

<4>编写hook代码






源码解析:(1)dlopen:该函数将打开一个新库,并把它装入内存void *dlopen(const char *filename, int flag);参数1:文件名就是一个动态库so文件,标志位:RTLD_NOW 的话,则立刻计算;设置的是 RTLD_LAZY,则在需要的时候才计算libc.so是一个共享库======================参数中的 libname 一般是库的全路径,这样 dlopen 会直接装载该文件;如果只是指定了库名称,在 dlopen 会按照下面的机制去搜寻:根据环境变量 LD_LIBRARY_PATH 查找根据 /etc/ld.so.cache 查找查找依次在 /lib 和 /usr/lib 目录查找。flag 参数表示处理未定义函数的方式,可以使用 RTLD_LAZY 或 RTLD_NOW 。RTLD_LAZY 表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW 表示马上检查是否存在未定义的函数,若存在,则 dlopen 以失败告终。参考链接:https://blog.nowcoder.net/n/5b2c04bbcccf431e9f1ab34aa02717fe=======================(2)dlsym:在 dlopen 之后,库被装载到内存。dlsym 可以获得指定函数( symbol )在内存中的位置(指针)。void *dlsym(void *handle,const char *symbol);参数1:文件句柄 参数2:函数名
我们对一个目标so文件hook步骤如下:(1)我们获取so的handler,使用dlopen函数void* libhandler = dlopen("libc.so",RTLD_NOW);(2)我们获取hook目标函数的地址,使用dlsym函数void* strstr_addr = dlsym(libhandler,函数名);(3)声明原来的函数void* (*oldmethod)(char*,char*); //这个格式需要参考hook的函数声明现在的函数void* newmethod(char* a,char* b){return (void *)oldmethod(a,b);}(3)使用registerInlinehook进行重定向,将hook函数地址重定向我们编写的新函数上(registerInlineHook((uint32_t) strstr_addr, (uint32_t) new_strstr, (uint32_t **) &old_strstr) != ELE7EN_OK//参数一:hook函数的地址 参数二:替换函数的地址 参数3:用来保存原来函数的地址(5)我们判断我们的hook操作是否成功,并且再次调用实现hook(inlineHook((uint32_t) strstr_addr) == ELE7EN_OK)
(2)SandHook实操
<1>导入文件

<2>配置环境



cmake {arguments '-DBUILD_TESTING=OFF'cppFlags "-frtti -fexceptions -Wpointer-arith"abiFilters 'armeabi-v7a', 'arm64-v8a'}
<3>编写hook代码



(1)导包,将SandHook中cpp文件夹下的包全部导入到项目中,并修改CMakeLists.txt中添加native.cpp, 修改java层导入so库为sandHook-native(2)配置相关的环境在配置文件build.gradle中配置externalNativeBuild {cmake {arguments '-DBUILD_TESTING=OFF'cppFlags "-frtti -fexceptions -Wpointer-arith"abiFilters 'armeabi-v7a', 'arm64-v8a'}}(3)编译可以成功通过(4)使用const char * libc = "/system/lib64/libc.so";old_fopen = reinterpret_cast<void *(*)(char *, char *)>(SandInlineHookSym(libc, "fopen",reinterpret_cast<void *>(new_fopen)));参数2:hook的函数 参数3:新的函数添加原理hook旧函数的声明void* (*old_fopen)(char*,char*);实现新的函数功能void* new_fopen(char* a,char* b){__android_log_print(6,"windaa","I am from new open %s",a);return old_fopen(a,b);}(5)运行测试是否成功启动
4.PLT/GOT hook实操






<1>获得so模块的加载地址
char line[1024];int *start;int *end;int n=1;//1.拿到so的起始地址// 749e5d7000-749e5db000 r--p 000f4000 103:09 441 /system/bin/linker64// 749e5db000-749e5dc000 rw-p 000f8000 103:09 441 /system/bin/linker64FILE *fd = fopen("/proc/self/maps","r");while (fgets(line,sizeof(line),fd)){if(strstr(line,"libnative-lib.so")){__android_log_print(6,"windaa","%s",line);if(n==1){start = reinterpret_cast<int *>(strtoul(strtok(line, "-"),NULL,16));end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "),NULL,16));}else{strtok(line,"-");end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "),NULL,16));}n++;}}
<2>找到got表的位置



<3>定位到节表的地址
//读取elf文件Elf64_Ehdr ehd;int fp =open("/data/local/tmp/libnative-lib.so", O_RDONLY);if(fp == -1){__android_log_print(4,"windaa","%s","error");}//读取elf文件的文件头read(fp,&ehd,sizeof(Elf64_Ehdr));//读取节表的地址unsigned long shof = ehd.e_shoff;//读取节表的数量int shnum = ehd.e_shnum;//读取每个节表的大小int shsize = ehd.e_ehsize;//记录一下str表的偏移,主要是获取后面got的字符串值int shstr = ehd.e_shstrndx;

<4>定位到got表的位置和函数位置
//2.拿到字符串表Elf64_Shdr shdr;//定位字符串,节表地址加字符串表偏移×节表个数lseek(fp,shof+shstr*shsize,SEEK_SET);//此时节表就定位到字符串表开头read(fp,&shdr,shsize);//分配一个字符串表大小char* strtable = (char *)malloc(shdr.sh_size);__android_log_print(6,"windaa","shdrsize %p",shdr.sh_offset);//将字符串片指针移动到0x34104上lseek(fp,shdr.sh_offset,SEEK_SET);read(fp,strtable,shdr.sh_size);//将指针移动到节表开头lseek(fp,shof,SEEK_SET);//遍历查找到gotfor(int i=0;i<shnum;i++){//从节表开头开始读取字符串,每次读取一个节表read(fp,&shdr,shsize);//通过节表的索引找到字符串表中对应的值if(strcmp(&strtable[shdr.sh_name], ".got")==0){//定位到got表的地址int* saddr = start+shdr.sh_addr/4;//整个got表的大小int size = shdr.sh_size;//遍历got表中的函数for(int j=0;j<size;j=j+8){uint64_t value = *(uint64_t *)(saddr + j / 4);//找到mywind的地址if(reinterpret_cast<uint64_t>(mywin0) == value){__android_log_print(6,"windaa","value %p",value);//替换mywind地址// 获取当前内存分页的大小uint64_t page_size = getpagesize();// 获取内存分页的起始地址(需要内存对齐)//page要保护的是函数的绝对地址,而不是相对地址uint64_t entry_page_start = (uint64_t)(saddr+j/4) & (~(page_size - 1));// 修改内存属性为可读可写可执行if(mprotect((uint64_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){__android_log_print(6,"windaa","%s","mprotect failed");}value = (uint64_t)mywin1;//将mywind0函数的地址换成mywind1函数的地址memcpy((saddr+j/4),&value,16);}}}}

5.Unicorn hook使用


六
实验总结
参考文献
参考书目:《程序员的自我修养——链接、装载与库》https://www.geek-share.com/detail/2774116640.htmlhttps://www.jianshu.com/p/0ac63c3744ddhttps://www.zhihu.com/question/21249496https://www.codeleading.com/article/37234101170/
https://zhuanlan.zhihu.com/p/389889716https://mabin004.github.io/2018/07/31/Mac%E4%B8%8A%E7%BC%96%E8%AF%91Frida/https://zhuanlan.zhihu.com/p/269441842https://blog.csdn.net/sdoyuxuan/article/details/78481239https://www.cnblogs.com/codingmengmeng/p/6046481.htmlhttps://blog.csdn.net/sssssuuuuu666/article/details/78788369https://www.malwaretech.com/2015/01/inline-hooking-for-programmers-part-1.htmlhttps://juejin.cn/post/6844903993668272141
https://www.likecs.com/show-203321775.htmlhttps://www.lmlphp.com/user/65342/article/item/709806/
为了防止失联,欢迎关注我防备的小号
微信改了推送机制,真爱请星标本公号👇



