安卓hook技术
http://www.epubit.com.cn/book/onlinechapter/33620 阅读总结
两个层面上的hook: java层:需要了解虚拟机的特性与java上反射的使用; native层:难点在于理解elf文件。关键在于如何找到函数的入口点、替换函数。
对于进程附着,Android的内核中有一个函数叫ptrace,它能够动态地attach(跟踪一个目标进程)、detach(结束跟踪一个目标进程)、peektext(获取内存字节)、poketext(向内存写入地址)等,它能够满足我们的需求。而Android中的另一个内核函数dlopen,能够以指定模式打开指定的动态链接库文件。对于程序的指向流程,我们可以调用ptrace让PC指向LR堆栈。最后调用,对目标进程调用dlopen则能够将我们希望注入的动态库注入至目标进程中。
对于代码的注入,我们可以使用mmap函数分配一段临时的内存来完成代码的存放。对于目标进程中的mmap函数地址的寻找与Hook API函数地址的寻找都需要通过目标进程的虚拟地址空间解析与ELF文件解析来完成,具体算法如下:
(1)通过读取/proc/PID/maps文件找到链接库的基地址。
(2)读取动态库,解析elf文件,找到目标函数相对elf文件开始的偏移。
(3)计算目标函数的绝对地址 (= 函数在elf中的偏移+ 动态库基地址)
总之,向目标进程中注入代码分为以下几步:
(1)用ptrace attach上目标进程 (2)发现装载共享库so函数。(3)装载指定的so (4)让目标进程的执行流程跳转到注入的代码执行 (5)使用ptrace函数的detach释放目标进程。

ptrace提供了一种使父进程得以监视和控制其他进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。使用ptrace,你可以在用户层拦截和修改系统调用(这个和Hook所要达到的目的类似),父进程还可以使子进程继续执行,并选择是否忽略引起终止的信号。
ptrace函数定义如下所示:
int ptrace(int request, int pid, int addr, int data);
- request是请求ptrace执行的操作。
- pid是目标进程的ID。
- addr是目标进程的地址值。
- data是作用的数据。
ptrace允许的操作见 http://www.epubit.com.cn/book/onlinechapter/33620
常用的hook工具: XPosed框架,CydiaSubstrate框架,ADBI/DDI框架等。
使用这些框架可以hook 系统api, 用户自定义的函数,以及nativce code的执行,实现广告注入、登陆劫持、改变系统表现(如系统颜色)等功能。
hook检测:
首先得到该app的pid,之后用/proc/PID/maps查看在其进程空间中的信息,看其加载了哪些第三方的库文件,看是否包含有substrate等怀疑为hook框架的库。
hook修复:
我们希望除了自身包名(com.example.testndklib)下的其他动态链接全都给删除关闭,且关闭后的应用程序还能够正常地运行。因为所有的第三方库都是通过dlopen后注入的方式附加到应用程序进程中的,这里我们很容易想到我们直接使用 dlclose 将其中的第三方函数挨个卸载关闭即可。
这样一个程序思路就来了,首先扫描/proc/<pid>/maps目录下的所有so库文件,并将自身的动态库文件排除,对于非自身的动态链接库我们全都卸载关闭。对于Java我们无法使用dlclose,所以这里我们还是采用了JNI的方式来完成,具体的操作函数如下所示。
/**
* 根据包名与进程pid,删除非包名下的动态库
* @parampid 进程pid
* @parampkg 包名
* @return
*/
public List<String> removeHooks(intpid, String pkg) {
List<String> hookLibFile = newArrayList<>();
// 找到对应进程的虚拟地址空间文件
File file = newFile("/proc/" + pid + "/maps");
if(!file.exists()) {
returnhookLibFile;
}
try{
BufferedReader bufferedReader = newBufferedReader(newInputStreamReader
(newFileInputStream(file)));
String lineString = null;
while((lineString = bufferedReader.readLine()) != null) {
String tempString = lineString.trim();
// 被hook注入的so动态库
if(tempString.contains("/data/data") && !tempString.contains("/data/data/" + pkg)) {
intindex = tempString.indexOf("/data/data");
String soPath = tempString.substring(index);
hookLibFile.add(soPath);
// 调用native方法删除so动态库
removeHookSo(soPath);
}
}
bufferedReader.close();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
return hookLibFile;
}
/**
* 卸载加载的so库
* @paramsoPath so库地址路径
*/
public native void removeHookSo(String soPath);
// JNI中的removeHookSo卸载一个so的加载
void Java_com_example_testndklib_MainActivity_removeHookSo(JNIEnv* env,
jobject thiz, jstring path) {
constchar