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, )|O_NONBLOCK) == -)
{
return -;
}
return ;
} /*
handle_message - 处理每个 socket 上的消息收发
*/
int handle_message(int new_fd)
{
char buf[MAXBUF + ];
int len; /* 开始处理每个新连接上的数据收发 */
bzero(buf, MAXBUF + ); /* 接收客户端的消息 */
len = recv(new_fd, buf, MAXBUF, );
if (len > )
{
printf("%d :'%s',共%d个字节的数据/n",new_fd, buf, len);
}
else
{
if (len < )
printf("消息接收失败!错误代码是%d,错误信息是'%s'/n", errno, strerror(errno));
close(new_fd);
return -;
}
/* 处理每个新连接上的数据收发结束 */
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 = ;
lisnum = ; /* 设置每个进程允许打开的最大文件数 */
rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
if (setrlimit(RLIMIT_NOFILE, &rt) == -)
{
perror("setrlimit");
exit();
}
else
{
printf("setrlimit success \n");
} /* 开启 socket 监听 */
if ((listener = socket(PF_INET, SOCK_STREAM, )) == -)
{
perror("socket");
exit();
}
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)) == -)
{
perror("bind");
exit();
}
else
{
printf("ip bind succ\n");
}
if (listen(listener, lisnum) == -)
{
perror("listen");
exit();
}
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) < )
{
fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);
return -;
}
else
{
printf("add socket to epoll succ\n");
}
curfds = ;
while ()
{
/* 等待有事件发生 */
nfds = epoll_wait(kdpfd, events, curfds, -);
if (nfds == -)
{
if(errno==EINTR) continue;
perror("epoll_wait");
break;
}
/* 处理所有事件 */
for (n = ; n < nfds; ++n)
{
if (events[n].data.fd == listener)
{
new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);
if (new_fd < )
{
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) < )
{
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 < && errno != )
{
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[]);
#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 ;
}
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 != )
{
printf("Please input the IP address of the server \n", argv[]);
exit();
} //设置一个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表示让系统自动分配一个空闲端口
//创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket
int client_socket = socket(AF_INET, SOCK_STREAM, );
if (client_socket < )
{
printf("Create Socket Failed!\n");
exit();
}
//把客户机的socket和客户机的socket地址结构联系起来
if (bind(client_socket, (struct sockaddr*) &client_addr,
sizeof(client_addr)))
{
printf("Client Bind Port Failed!\n");
exit();
} //设置一个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[], &server_addr.sin_addr) == ) //服务器的IP地址来自程序的参数
{
printf("Server IP Address Error! \n");
exit();
} 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) < )
{
printf("Can Not Connect To %s!\n", argv[]);
exit();
} sleep();
close(client_socket);
return ;
}