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; }