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里边的函数地址替换目标进程地址空间里原有的函数地址(老的函数地址一般也需要保存起来)。

源码目录

android  hook 框架 ADBI 简介、编译、运行

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

android  hook 框架 ADBI 简介、编译、运行

android  hook 框架 ADBI 简介、编译、运行

5. 启动模拟器

我使用android studio avd 管理器创建了一个模拟器,参数如下

android  hook 框架 ADBI 简介、编译、运行

使用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已经被注入

android  hook 框架 ADBI 简介、编译、运行

第三个shell多次执行 ./testclient 127.0.0.1 , 向服务端发送tcp请求,建立连接,触发服务端的epoll_wait返回

第四个shell执行 /data/local/tmp/target, 即启动服务端,它用epoll_wait监听5000端口,如果有一个请求到来,就分配一个socket去服务

android  hook 框架 ADBI 简介、编译、运行

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 ;
}
上一篇:What is a fully qualified domain name (FQDN)?


下一篇:即时通信系统Openfire分析之一:Openfire与XMPP协议