致谢:
感谢 简行之旅的这篇blog:http://blog.csdn.net/l173864930/article/details/38455951,这篇文章是参考这篇blog的进行一步一步操作的,如果没有这篇好文章的话,貌似我这篇文章的诞生可能就有点难度了。
今天周日,昨天花了一天的时间总算是搞定了,问题还是想对应用程序的行为进行拦截的操作,就是像小米手机一样,哪些应用在获取你什么权限的信息。在之前写过对应用程序的行为进行拦截的方式(C层)实现的博客,在写完这篇之后,本来想是尽快的把Java层拦截的文章结束的,但是由于各种原因吧,所以一直没有时间去弄这些了。今天算是有空,就总结一下吧。下面进入正题:
一、摘要
我们知道现在一些安全软件都会有一个功能就是能够拦截应用的行为(比如地理位置信息,通讯录等),所以这里就来实现以下这样的功能,当然实现这样的功能有两种方式,一种是从底层进行拦截,这个我在之前的博客中已经讲解过了。没看过的同学可以转战:
http://blog.csdn.net/jiangwei0910410003/article/details/39346151
还有一种方式就是从上层进行拦截,也就是我们今天所要说的内容,这种方式都是可以的,当然很多人更多的偏向上层,因为底层拦截需要熟知Binder协议和Binder的数据格式的。上层拦截就简单点了。
二、知识点概要
首先我们需要了解一点知识就是不管是底层拦截还是上层拦截,都需要一个技术支持:进程注入,关于这个知识点,这里就不作解释了,不了解的同学可以转战:http://blog.csdn.net/jiangwei0910410003/article/details/39293635
了解了进程注入之后,这篇文章主要讲解三点知识:
1、如何动态加载so,并且执行其中的函数
2、如何在C层执行Java方法(NDK一般是指Java中调用C层函数)
3、如何修改系统服务(Context.getSystemService(String...)其实返回来的就是Binder对象)对象
当然我们还需要一些预备知识:知道如何使用NDK进行编译项目,不了解的同学可以转战:
http://blog.csdn.net/jiangwei0910410003/article/details/17710243
这篇文章编译环境是Window下的,个人感觉还是不方便,还是在Ubuntu环境下操作比较方便
还有一点需要声明:就是拦截行为是需要root权限的
三、例子
第一个例子:简单的进程注入功能
目的:希望将我们自己的功能模块(so文件)注入到目标进程中,然后修改目标进程中的某个函数的执行过程
文件:注入功能可执行文件poison、目标进程可执行文件demo1、需要注入的模块libmyso.so
注入功能的可执行文件核心代码poison.c,这个功能模块在后面讲到的例子中也会用到,所以他是公用的
#include <unistd.h>#include <errno.h>#include <stdlib.h>#include <dlfcn.h>#include <sys/mman.h>#include <sys/ptrace.h>#include <sys/wait.h>#include "ptrace_utils.h"#include "elf_utils.h"#include "log.h"#include "tools.h"struct process_hook {
pid_t pid; char *dso;} process_hook = {
0, ""};int main(int argc, char* argv[]) { LOGI("argv len:"+argc); if(argc < 2) exit(0); struct pt_regs regs; process_hook.dso = strdup(argv[1]); process_hook.pid = atoi(argv[2]); if (access(process_hook.dso, R_OK|X_OK) < 0) { LOGE("[-] so file must chmod rx\n"); return 1; } const char* process_name = get_process_name(process_hook.pid); ptrace_attach(process_hook.pid, strstr(process_name,"zygote")); LOGI("[+] ptrace attach to [%d] %s\n", process_hook.pid, get_process_name(process_hook.pid)); if (ptrace_getregs(process_hook.pid, ®s) < 0) { LOGE("[-] Can't get regs %d\n", errno); goto DETACH; } LOGI("[+] pc: %x, r7: %d", regs.ARM_pc, regs.ARM_r7); void* remote_dlsym_addr = get_remote_address(process_hook.pid, (void *)dlsym); void* remote_dlopen_addr = get_remote_address(process_hook.pid, (void *)dlopen); LOGI("[+] remote_dlopen address %p\n", remote_dlopen_addr); LOGI("[+] remote_dlsym address %p\n", remote_dlsym_addr); if(ptrace_dlopen(process_hook.pid, remote_dlopen_addr, process_hook.dso) == NULL){ LOGE("[-] Ptrace dlopen fail. %s\n", dlerror()); } if (regs.ARM_pc & 1 ) { regs.ARM_pc &= (~1u); regs.ARM_cpsr |= CPSR_T_MASK; } else { regs.ARM_cpsr &= ~CPSR_T_MASK; } if (ptrace_setregs(process_hook.pid, ®s) == -1) { LOGE("[-] Set regs fail. %s\n", strerror(errno)); goto DETACH; } LOGI("[+] Inject success!\n");DETACH: ptrace_detach(process_hook.pid); LOGI("[+] Inject done!\n"); return 0;}
我们看到,这个注入功能的代码和我们之前说的从底层进行拦截的那篇文章中的注入代码(inject.c)不太一样呀?这个是有人在网上从新改写了一下,其实功能上没什么区别的,我们从main函数可以看到,有两个入口参数:
第一个是:需要注入so文件的全路径
第二个是:需要注入进程的pid
也就是说,我们在执行poison程序的时候需要传递这两个值。在之前说道的注入代码(inject.c)中,其实这两个参数是在代码中写死的,如果忘记的同学可以回去看一下,就是前面提到的从底层进行拦截的那篇文章。
那么这样修改之后,貌似灵活性更高了。
当然注入功能的代码不止这一个,其实是一个工程,这里由于篇幅的原因就不做介绍了,工程的下载地址:
http://download.csdn.net/detail/jiangwei0910410003/8138061
使用NDK编译一下,生成可执行文件就OK了。
第一部分:代码实现
1)目标进程依赖的so文件inso.h和inso.c
__attribute__ ((visibility ("default"))) void setA(int i);__attribute__ ((visibility ("default"))) int getA();
inso.c代码
#include <stdio.h>#include "inso.h"static int gA = 1;void setA(int i){ gA = i;}int getA(){ return gA;}
编译成so文件即可,项目下载:http://download.csdn.net/detail/jiangwei0910410003/8138107
2)目标进程的可执行文件demo1.c
这个就简单了,就是非常简单的代码,起一个循环每个一段时间打印数值,这个项目需要引用上面编译的inso.so文件
头文件inso.h(和上面的头文件是一样的)
__attribute__ ((visibility ("default"))) void setA(int i);__attribute__ ((visibility ("default"))) int getA();
demo1.c文件
#include <stdio.h>#include <unistd.h>#include "inso.h"#include "log.h"int main(){ LOGI("DEMO1 start."); while(1){ LOGI("%d", getA()); setA(getA() + 1); sleep(2); } return 0;}
代码简单吧,就是执行循环打印数值,这里使用的是底层的log方法,在log.h文件中定义了,篇幅原因,这里就不列举了,项目下载地址:
http://download.csdn.net/detail/jiangwei0910410003/8138071
3)注入的模块功能源文件myso.c
#include <stdio.h>#include <stddef.h>#include <dlfcn.h>#include <pthread.h>#include <stddef.h>#include "log.h"__attribute__ ((__constructor__))void Main() { LOGI(">>>>>>>>>>>>>Inject Success!!!!<<<<<<<<<<<<<<"); void (*setA_func)(int); void* handle = dlopen("libinso.so", RTLD_NOW); LOGI("Handle:%p",handle); //void (*setA_func)(int) = (void (*)(int))dlsym(handle, "setA"); setA_func = (void (*)(int))dlsym(handle,"setA"); LOGI("Func:%p",setA_func); if (setA_func) { LOGI("setA is Executing!!!"); (*setA_func)(999); } dlclose(handle);}
说明:
这段代码需要解释一下,首先来看一下:
__attribute__ ((__constructor__))
gcc为函数提供了几种类型的属性,其中包含:构造函数(constructors)和析构函数(destructors)。
程序员应当使用类似下面的方式来指定这些属性:
static void start(void) __attribute__ ((constructor));static void stop(void) __attribute__ ((destructor));
带有"构造函数"属性的函数将在main()函数之前被执行,而声明为"析构函数"属性的函数则将在main()退出时执行。
用法举例:
#include <iostream>