android hook 框架 ADBI 简介、编译、运行
Android so注入-libinject2 简介、编译、运行
Android so注入-libinject2 如何实现so注入
Android so注入-Libinject 如何实现so注入
Android so注入挂钩-Adbi 框架简介、编译、运行
Android so注入挂钩-Adbi 框架如何实现so注入
Android so注入挂钩-Adbi 框架如何实现so函数挂钩
Android so注入挂钩-Adbi 框架如何实现dalvik函数挂钩
Android dalvik挂钩-Xposed框架如何实现注入
Android dalvik挂钩-Xposed框架如何实现挂钩
简介
adbi 是一个android平台(arm 32 )的so注入+挂钩框架,源码开放在github上 : ADBI 项目 。
从hook技术的分类来说,其属于应用层so注入+inline 挂钩, 这种方式的套路是:基于linux系统的ptrace机制,attach一个目标进程,注入一个动态链接库进入目标进程的地址空间,然后用so里边的函数地址替换目标进程地址空间里原有的函数地址(老的函数地址一般也需要保存起来)。
源码目录
hijack: 注入功能,用于注入一个so到目标进程并执行初始化函数,编译为一个可执行程序
libbase: 挂钩框架,用于hook指定so内部的指定函数,并提供unhook函数,编译为一个静态库
example: 一个hook例子,使用libbase,将一个自定义的my_epoll_wait函数编译成一个动态库libexample.so,由 hijack 注入目标进程,并用libexample.so里的my_epoll_wait 挂钩目标进程的 libc.so 库的函数 epoll_wait
编译运行
默认从github clone下面的编译文件 Android.mk 里目标体系架构是 arm ,所以要运行测试例子需要准备一个 Arm 模拟器,另外, README.md 给出的编译方式是 linux 系统的shell脚本,在Windows下不能直接使用,需要改成相应的批处理脚本。这里我直接手动进入相关目录执行ndk-build.
1. 下载ndk, 资源站 下载 ndk
2. 解压到某一目录,将ndk的路径设置进 PATH 环境变量
3. git clone adbi 项目
4. 进入hijack/jni, instruments/base/jni, instruments/base/example/jni 分别执行 ndk-build
5. 启动模拟器
我使用android studio avd 管理器创建了一个模拟器,参数如下
使用android studio 创建一个android项目(默认即可),启动模拟器
6. adb 传输文件到模拟器
adb push libexample.so /data/local/tmp/ adb push hijack /data/local/tmp adb push target /data/local/tmp adb push testclient /data/local/tmp
其中,target和testclient是从网上拷贝的 epoll 服务端和客户端代码(本文最后会给出代码),执行情况如下:
第一个shell 执行 adb logcat 用于查看日志,在本次测试里,目标进程是1490,从logcat 可以看出执行了3次挂钩函数
第二个shell执行 ./hijack -p 1490 -l /data/local/tmp/libexample.so -d , 用于注入 libexample.so 到进程 1490 并执行初始化函数(执行挂钩),执行完后,查看1490的maps文件,可以看到,libexample.so已经被注入
第三个shell多次执行 ./testclient 127.0.0.1 , 向服务端发送tcp请求,建立连接,触发服务端的epoll_wait返回
第四个shell执行 /data/local/tmp/target, 即启动服务端,它用epoll_wait监听5000端口,如果有一个请求到来,就分配一个socket去服务
target.c 的代码
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #include <fcntl.h> #include <sys/epoll.h> #include <dlfcn.h> #include <sys/mman.h> #include <sys/time.h> #include <sys/resource.h> #include <signal.h> #include <sys/ptrace.h> #define MAXBUF 1024 #define MAXEPOLLSIZE 10000 /* setnonblocking - 设置句柄为非阻塞方式 */ int setnonblocking(int sockfd) { if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) { return -1; } return 0; } /* handle_message - 处理每个 socket 上的消息收发 */ int handle_message(int new_fd) { char buf[MAXBUF + 1]; int len; /* 开始处理每个新连接上的数据收发 */ bzero(buf, MAXBUF + 1); /* 接收客户端的消息 */ len = recv(new_fd, buf, MAXBUF, 0); if (len > 0) { printf("%d :'%s',共%d个字节的数据/n",new_fd, buf, len); } else { if (len < 0) printf("消息接收失败!错误代码是%d,错误信息是'%s'/n", errno, strerror(errno)); close(new_fd); return -1; } /* 处理每个新连接上的数据收发结束 */ return len; } void sig_worker() { int listener, new_fd, kdpfd, nfds, n, ret, curfds; socklen_t len; struct sockaddr_in my_addr, their_addr; unsigned int myport, lisnum; struct epoll_event ev; struct epoll_event events[MAXEPOLLSIZE]; struct rlimit rt; myport = 5000; lisnum = 2; /* 设置每个进程允许打开的最大文件数 */ rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE; if (setrlimit(RLIMIT_NOFILE, &rt) == -1) { perror("setrlimit"); exit(1); } else { printf("setrlimit success \n"); } /* 开启 socket 监听 */ if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } else { printf("socket create success \n"); } setnonblocking(listener); bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = PF_INET; my_addr.sin_port = htons(myport); my_addr.sin_addr.s_addr = INADDR_ANY; if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } else { printf("ip bind succ\n"); } if (listen(listener, lisnum) == -1) { perror("listen"); exit(1); } else { printf("listen succ\n"); } /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */ kdpfd = epoll_create(MAXEPOLLSIZE); len = sizeof(struct sockaddr_in); ev.events = EPOLLIN | EPOLLET; ev.data.fd = listener; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d\n", listener); return -1; } else { printf("add socket to epoll succ\n"); } curfds = 1; while (1) { /* 等待有事件发生 */ nfds = epoll_wait(kdpfd, events, curfds, -1); if (nfds == -1) { if(errno==EINTR) continue; perror("epoll_wait"); break; } /* 处理所有事件 */ for (n = 0; n < nfds; ++n) { if (events[n].data.fd == listener) { new_fd = accept(listener, (struct sockaddr *) &their_addr,&len); if (new_fd < 0) { perror("accept"); continue; } else { printf("request from %d:%d, giving socket:%d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd); } setnonblocking(new_fd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = new_fd; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0) { fprintf(stderr, "giving %d to epoll %s fail \n", new_fd, strerror(errno)); return; } curfds++; } else { ret = handle_message(events[n].data.fd); if (ret < 1 && errno != 11) { epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev); curfds--; } } } } close(listener); } int main(int argc, char **argv) { printf("my pid is %d\n",getpid()); printf("cmd %s\n",argv[0]); #if 0 printf("mmap: 0x%x\n", mmap); printf("mprotect: 0x%x\n", mprotect); printf("dlopen: 0x%x\n", dlopen); printf("dlsym: 0x%x\n", dlsym); printf("dlerror: 0x%x\n", dlerror); #endif sig_worker(); return 0; }
testclient.c 的代码
#include <netinet/in.h> // for sockaddr_in #include <sys/types.h> // for socket #include <sys/socket.h> // for socket #include <stdio.h> // for printf #include <stdlib.h> // for exit #include <string.h> // for bzero /* #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> */ #define SERVER_PORT 5000 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main(int argc, char **argv) { if (argc != 2) { printf("Please input the IP address of the server \n", argv[0]); exit(1); } //设置一个socket地址结构client_addr,代表客户机internet地址, 端口 struct sockaddr_in client_addr; bzero(&client_addr, sizeof(client_addr)); //把一段内存区的内容全部设置为0 client_addr.sin_family = AF_INET; //internet协议族 client_addr.sin_addr.s_addr = htons(INADDR_ANY); //INADDR_ANY表示自动获取本机地址 client_addr.sin_port = htons(0); //0表示让系统自动分配一个空闲端口 //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } //把客户机的socket和客户机的socket地址结构联系起来 if (bind(client_socket, (struct sockaddr*) &client_addr, sizeof(client_addr))) { printf("Client Bind Port Failed!\n"); exit(1); } //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; if (inet_aton(argv[1], &server_addr.sin_addr) == 0) //服务器的IP地址来自程序的参数 { printf("Server IP Address Error! \n"); exit(1); } server_addr.sin_port = htons(SERVER_PORT); socklen_t server_addr_length = sizeof(server_addr); // 向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接 if (connect(client_socket, (struct sockaddr*) &server_addr, server_addr_length) < 0) { printf("Can Not Connect To %s!\n", argv[1]); exit(1); } sleep(10); close(client_socket); return 0; }