本文主要是介绍Android进程的so注入--Poison(稳定注入版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53869796
Android进程的so注入已经是老技术了,网上能用的Android注入的工程也有很多,虽然分享代码的作者在测试的时候能注入成功,但是其他的同学使用这些代码的时候总是出现这样或者那样的问题。在Android逆向学习的这段时间里,我也陆续测试了几个作者给出的Android的注入的代码,但是总是效果不明显,今天就学习一下大牛boyliang分享的Android的so注入的代码框架Poison,作者boyliang的注入代码也是基于大牛古河分享的Andorid注入的代码修改过来的,做了一些改进和优化,后面的文章中就对注入代码进行学习一下。
一、注入工程Poison-master代码的下载
1)作者boyliang的注入代码的原下载地址已经失效了,但是从github上还是可以查找的,下载地址为:https://github.com/matrixhawk/Poison(缺少编译的Android.mk文件和Application.mk配置文件,需要自己编写)。
windows环境下,NDK编译需要添加的include头文件(根据编译的版本需要进行修改):
右击项目 --> Properties --> 左侧C/C++ General --> Paths and Symbols --> 右侧Includes --> GNU C++(.cpp) --> Add
${NDKROOT}\platforms\android-19\arch-arm\usr\include
${NDKROOT}\sources\cxx-stl\gnu-libstdc++\4.8\include
${NDKROOT}\sources\cxx-stl\gnu-libstdc++\4.8\libs\armeabi\include
${NDKROOT}\toolchains\arm-Linux-androideabi-4.8\prebuilt\windows\lib\gcc\arm-linux-androideabi\4.8\include
2)为Poison-master工程添加编译需要的Android.mk文件和Application.mk文件如下:
Android.mk文件:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 编译生成的模块的名称
LOCAL_MODULE := poison # 需要被编译的源码文件
LOCAL_SRC_FILES := poison.c \elf_utils.c \ptrace_utils.c \tools.c # 支持log日志打印android/log.h里函数调用的需要
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog # 编译模块生成可执行文件
include $(BUILD_EXECUTABLE)
Application.mk文件:
# 编译生成的模块运行支持的平台
APP_ABI := armeabi-v7a
# 设置编译连接的工具的版本
#NDK_TOOLCHAIN_VERSION = 4.9
4)哈哈,编译成功了,Android进程so注入的工具就有了。
二、注入工程代码Poison的说明
1)关于Android的so注入的详细的原理和细节,可以参考博文http://blog.csdn.net/qq1084283172/article/details/46859931,这篇博文里已经把Android的so注入的代码分析的很清楚了,基本把古河大牛的LibInject都说明白了。古河大牛的LibInject中涉及到了Android函数的Hook部分的模板的编写,作者boyliang的代码中没有涉及到Android函数的Hook部分的代码,这部分后面再研究,大牛boyliang和古河的so注入部分的思路是一样的,只不过boyliang的so注入代码中考虑到了"zygote"进程注入的特殊情况,在对进程目标pid进程ptrace时,有着特殊的处理。
2)Andorid的so注入时,针对"zygote"进程注入的特殊处理的原因,可以参考《Android so注入》这篇博文给出的解释原因。
A.针对"zygote"进程注入时,so库文件必须存放在“/system/lib/“路径下。
B.针对"zygote"进程注入时,ptrace操作"zygote"进程时的特殊处理(直接搬过来)。
可以看到ptrace_attach只是对ptrace(PTRACE_ATTACH,…)做了一个封装,但是在attach还做了一系列的waitpid和ptrace(PTRACE_SYSCALL,…)的操作,这是为什么呢。这里我们需要复习一下ptrace的执行过程,一旦对某个进程执行了ptrace操作,那么当目标进程执行系统调用,也就是把执行的控制权交给内核的时候,内核会检查当前进程是否被标记为”traced”,如果是,那么内核就会把控制权转交给跟踪进程。而此时跟踪进程正调用了wait函数在等待内核函数的信号,当接受到信号后跟踪进程就能继续执行。但是有时候会遇到被跟踪进程执行的系统调用是一个阻塞函数,比如recv,read,这样当目标进程系统调用开始的时候(PTRACE_ATTACH在系统调用开始暂停目标进程),它就会被暂停,而跟踪进程会被唤醒,一般这个时候跟踪进程会执行ptrace(PTRACE_GETREGS,…)等操作,这需要目标进程从系统调用返回,但是目标进程这个时候已经阻塞在系统调用里面了,无法返回,ptrace就会产生错误。知道这个情况,我们就很容易理解这段代码了。首先使用PTRACE_ATTACH标记目标进程,然后等待目标进程返回,这里的WUNTRACED表示目标进程暂停后就立即返回,而不是等待目标进程结束。当目标进程进入系统调用后,通知跟踪进程,跟踪进程再调用ptrace(PTRACE_SYSCALL,…)然后等待(PTRACE_SYSCALL在目标进程进入/退出系统调用的时候暂停目标进程),表示等待目标进程进入系统调用,然后再调用一次ptrace(PTRACE_SYSCALL,…)再等待,表示等待目标进程从系统调用返回,等第三次的wait返回后((可能会被阻塞),就可以进行系统调用了。
这种做法还是有可能会被阻塞,就是第三次wait会等不到信号,也就是目标进程进入系统调用后一直不返回。什么时候会发生这种情况呢?其实zygote就是个很好的例子,这需要对zygote进程有一些了解,这里只简单的分析一下。zygote启动后会进入一个死循环,用来接收AMS的请求连接,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | * Runs the zygote process's select loop. Accepts new connections as * they happen, and reads commands from connections one spawn-request's * worth at a time. * * @throws MethodAndArgsCaller in a child process when a main() should * be executed. */ private static void runSelectLoopMode() throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList(); ArrayList <ZygoteConnection> peers = new ArrayList(); FileDescriptro[] fdArray = new FileDescriptor[4]; ...... while (true) {//死循环 ...... if (index < 0) { throw new RuntimeException("Error in select()"); } else if (index == 0) {//index==0表示selcet接收到的是Zygote的socket的事件 ZygoteConnection newPeer = acceptCommandPeer(); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else {//调用ZygoteConnection对象的runOnce方法,ZygoteConnection是在index == 0时被添加到peers的 boolean done; done = peers.get(index).runOnce(); if (done) { peers.remove(index); fds.remove(index); } } } } |
index变量表示此时和zygote进程通信的个数,当index=0时也就是说没有socket连接,此时zygote调用acceptCommandPeer函数,该函数等待一个连接并返回一个ZygoteConnection对象。
1 2 3 4 5 6 7 8 9 10 11 | /* * Waits for and accepts a single command connection. Throws * RuntimeException on failure. */ private static ZygoteConnection acceptCommandPeer() { try { return new ZygoteConnection(sServerSocket.accept()); } catch (IOException ex) { throw new RuntimeException("IOException during accept()", ex); } } |
也就是说,当没有应用启动时,zygote进程一直处于阻塞状态。所以我们上面代码中的第三次wait会无法返回,解决办法也很简单,就是主动发起一个zygote的连接。我们看到第二个waitpid后面调用了一个connect_to_zygote函数,下面是它的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static void* connect_to_zygote(void* arg){ int s, len; struct sockaddr_un remote; //zygote进程接收socket连接的时间间隔是500ms,2s足以保证此socket连接能连接到zygote socket LOGI("[+] wait 2s..."); sleep(2); //sleep(0.5); if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) { remote.sun_family = AF_UNIX; strcpy(remote.sun_path, "/dev/socket/zygote"); len = strlen(remote.sun_path) + sizeof(remote.sun_family); LOGI("[+] start to connect zygote socket"); connect(s, (struct sockaddr *) &remote, len); LOGI("[+] close socket"); close(s); } return NULL ; } |
这个函数的功能很简单,先发起socket连接,然后再关闭连接。看上去没有做什么有用的事情,但是它却非常重要,通过连接zygote,它使zygote进程解除了阻塞状态,我们才得以注入进zygote进程。
说明:暂时对zygote这一块不是很熟悉,参考大牛http://zke1ev3n.me/2015/12/02/Android-so注入/的分析理由。
C.针对调用目标pid进程的函数时,对于等待目标pid进程完成so注入需要注意的地方,大牛zke1ev3n也做了深入的说明和代码的补充。
ptrace_dlopen函数构造dlopen函数的参数,然后调用ptrace_call开始加载so的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | int ptrace_call(pid_t pid, uint32_t addr, long *params, int num_params, struct pt_regs* regs) { uint32_t i; for (i = 0; i < num_params && i < 4; i++) { regs->uregs[i] = params[i]; } if (i < num_params) { regs->ARM_sp-= (num_params - i) * sizeof(long); ptrace_write(pid, (uint8_t *) regs->ARM_sp, (uint8_t *) ¶ms[i], (num_params - i) * sizeof(long)); } regs->ARM_pc= addr; if (regs->ARM_pc& 1) { /* thumb */ regs->ARM_pc &= (~1u); regs->ARM_cpsr |= CPSR_T_MASK; } else { /* arm */ regs->ARM_cpsr &= ~CPSR_T_MASK; } regs->ARM_lr= 0; //置子程序的返回地址为空,以便函数执行完后,返回到null地址,产生SIGSEGV错误 if (ptrace_setregs(pid, regs) == -1 || ptrace_continue(pid) == -1) { return -1; } // waitpid(pid, NULL, WUNTRACED); int status = 0; // waitpid(pid,&stat,WUNTRACED); pid_t res; waitpid(pid, NULL, WUNTRACED); /* * Restarts the stopped child as for PTRACE_CONT, but arranges for * the child to be stopped at the next entry to or exit from a sys‐ * tem call, or after execution of a single instruction, respec‐ * tively. */ if (ptrace(PTRACE_SYSCALL, pid, NULL, 0) < 0) { LOGE("ptrace_syscall"); return -1; } waitpid(pid, NULL, WUNTRACED); if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL ) < 0) { LOGE("ptrace_syscall"); return -1; } res = waitpid(pid, NULL, WUNTRACED); LOGI("[+] status is %x",status); if (res != pid || !WIFSTOPPED (status))//WIFSTOPPED(status) 若为当前暂停子进程返回的状态,则为真 return 0; LOGI("[+]done %d\n",(WSTOPSIG (status) == SIGSEGV)?1:0); //设置siginal 11信号处理函数 /* if(signal(SIGSEGV,handler) == SIG_ERR){ LOGE("[-]can not set handler for SIGSEGV"); }*/ return 0; } |
WUNTRACED告诉waitpid,如果子进程进入暂停状态,那么就立即返回。如果是被ptrace的子进程,那么即使不提供WUNTRACED参数,也会在子进程进入暂停状态的时候立即返回。对于使用PTRACE_CONT运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发生错误。这里的0xb7f就表示子进程进入了暂停状态,且发送的错误信号为11(SIGSEGV),它表示试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。那么什么时候会发生这种错误呢?显然,当子进程执行完注入的函数后,由于我们在前面设置了regs->ARM_lr = 0,它就会返回到0地址处继续执行,这样就会产生SIGSEGV。
这里还需要了解下arm架构的相关知识。首先是函数参数传递,在arm中,函数的前4个参数分别保存在r0-r3中,当参数大于4个,就依次压入栈中。此外,arm处理器实际上支持两套指令集,即arm和thumb。thumb为16位,arm为32位。这里通过判断pc的最后一位是否是1来确定指令集,这是因为编译器在用thmub指令集编译一个函数时,会将函数的符号地址设置成真正的映射地址+1,实现arm和thumb混编。此外,在切换arm和thumb指令时,还会修改CPSR处理器。在arm中,出了r0-r15这16个处理器,还有状态寄存器CPSR。关于CPSR的其他位这里先不讨论,我们只要知道CPSR寄存器的第低5位T标识了当前的指令集(T=0表示执行arm指令,T=1表示执行Thumb指令),所以在切换指令集时需要修改这一位。
Arm与Thumb之间的状态切换是通过专用的转移交换指令BX来实现。BX指令以通用寄存器(R0~R15)为操作数,通过拷贝Rn到PC实现绝对跳转。BX利用Rn寄存器中目的地址值的最后一位判断跳转后的状态,如果为“1”表示跳转到Thumb指令集的函数中,如果为“0”表示跳转到Arm指令集的函数中。而Arm指令集的每条指令是32位,即4个字节,也就是说Arm指令的地址肯定是4的倍数,最后两位必定为“00”。所以,直接就可以将从符号表中获得的调用地址模4,看是否为0来判断要修改的函数是用Arm指令集还是Thumb指令集。
三、注入工程Poison-master代码的注入测试
用到的命令:
cd xxxxx\AndroidProject_Poison-master\use_poisonadb push poison /data/local/tmp
adb push libmobisec.so /data/local/tmp
adb shell chmod 0777 /data/local/tmp/poison
adb shell chmod 0777 /data/local/tmp/libmobisec.so
adb shell
su
ps | grep com.example.androiddecodcat /proc/17569/maps/data/local/tmp/poison /data/local/tmp/libmobisec.so 17569cat /proc/17569/maps | grep libmobisec.soadb logcat -s TTT
注入so库文件libmobisec.so到com.example.androiddecod进程中成功
能编译成功的项目工程下载地址:http://download.csdn.net/detail/qq1084283172/9721443
四、注入工程Poison-master代码的详细注释说明
整个Poison-master工程的代码的结构图:
整个Poison-master工程代码的详细分析注释:
主文件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;// 注入到目标进程中so文件的路径char *dso;
// void *dlopen_addr;
// void *dlsym_addr;
// void *mmap_addr;
} process_hook = {0, "", NULL, NULL, NULL};// 主函数
int main(int argc, char* argv[]) {// 对传入的参数的个数进行判断(要求3个参数)if(argc < 2)exit(0);// 保存寄存器的状态信息struct pt_regs regs;// 获取注入到目标进程中的so的文件的路径process_hook.dso = strdup(argv[1]);// 获取注入的目标进程的pidprocess_hook.pid = atoi(argv[2]);// process_hook.dlopen_addr = (void *)atol(argv[3]);
// process_hook.dlsym_addr = (void *)atol(argv[4]);
// process_hook.mmap_addr = (void *)atol(argv[5]);// 判断注入到目标进程中so是否存在并且具有可读可执行权限if (access(process_hook.dso, R_OK|X_OK) < 0) {LOGE("[-] so file must chmod rx\n");return 1;}// 获取指定pid进程的名称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;}// 打印目标进程的寄存器pc和R7的信息LOGI("[+] pc: %x, r7: %d", regs.ARM_pc, regs.ARM_r7);// dlsym参数为当前进程中的调用地址,获取目标pid进程中dlsy函数的调用地址void* remote_dlsym_addr = get_remote_address(process_hook.pid, (void *)dlsym);// 获取目标pid进程中dlopen函数的调用地址void* remote_dlopen_addr = get_remote_address(process_hook.pid, (void *)dlopen);// if(remote_dlopen_addr == NULL && remote_dlsym_addr != NULL){
// remote_dlopen_addr = (void *)((uint32_t)remote_dlsym_addr - (uint32_t)process_hook.dlsym_addr + (uint32_t)process_hook.dlopen_addr);
// }else if(remote_dlopen_addr != NULL && remote_dlsym_addr == NULL){
// remote_dlsym_addr = (void *)((uint32_t)remote_dlopen_addr - (uint32_t)process_hook.dlopen_addr + (uint32_t)process_hook.dlsym_addr);
// }else if(remote_dlopen_addr == NULL && remote_dlsym_addr == NULL){
// LOGE("[-] Can not found dlopen_addr & dlsym_addr.\n");
// goto DETACH;
// }
//// 打印目标进程的函数dlopen和dlsym的调用地址LOGI("[+] remote_dlopen address %p\n", remote_dlopen_addr);LOGI("[+] remote_dlsym address %p\n", remote_dlsym_addr);// 调用目标pid进程的dlopen函数加载指定的so库文件,获取返回的加载的模块的基址if(ptrace_dlopen(process_hook.pid, remote_dlopen_addr, process_hook.dso) == NULL){LOGE("[-] Ptrace dlopen fail. %s\n", dlerror());}// 针对此时不同的模式,设置目标pid进程的CPSR寄存器的值if (regs.ARM_pc & 1 ) {// thumbregs.ARM_pc &= (~1u);regs.ARM_cpsr |= CPSR_T_MASK;} else {// armregs.ARM_cpsr &= ~CPSR_T_MASK;}// 恢复目标pid进程的寄存器的状态即恢复到注入前的运行状态if (ptrace_setregs(process_hook.pid, ®s) == -1) {LOGE("[-] Set regs fail. %s\n", strerror(errno));// 失败进行跳转goto DETACH;}// 打印注入成功的消息LOGI("[+] Inject success!\n");DETACH:// 结束对目标pid进程的附加ptrace_detach(process_hook.pid);// 打印注入工作完成的消息LOGI("[+] Inject done!\n");return 0;
}
ptrace_utils.h文件
/** ptrace_utils.h** Created on: 2013-6-19* Author: boyliang*/#ifndef PTRACE_UTILS_H_
#define PTRACE_UTILS_H_#define CPSR_T_MASK ( 1u << 5 )int ptrace_getregs(pid_t pid, struct pt_regs* regs);int ptrace_setregs(pid_t pid, struct pt_regs* regs);int ptrace_attach( pid_t pid , int zygote);int ptrace_detach( pid_t pid );int ptrace_continue(pid_t pid);int ptrace_syscall(pid_t pid);int ptrace_write(pid_t pid, uint8_t *dest, uint8_t *data, size_t size);int ptrace_read( pid_t pid, uint8_t *src, uint8_t *buf, size_t size );int ptrace_call(pid_t pid, uint32_t addr, long *params, int num_params, struct pt_regs* regs);void* ptrace_dlopen(pid_t target_pid, void* remote_dlopen_addr, const char* filename);#endif /* PTRACE_UTILS_H_ */
ptrace_utils.c文件
/** ptrace_utils.c** Created on: 2013-6-26* Author: boyliang*/#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <cutils/sockets.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <pthread.h>#include "ptrace_utils.h"
#include "log.h"/*** read registers' status*/
int ptrace_getregs(pid_t pid, struct pt_regs* regs) {if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {perror("ptrace_getregs: Can not get register values");return -1;}return 0;
}/*** set registers' status*/
int ptrace_setregs(pid_t pid, struct pt_regs* regs) {if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {perror("ptrace_setregs: Can not set register values");return -1;}return 0;
}// 解除zygote进程的阻塞状态
static void* connect_to_zygote(void* arg){int s, len;struct sockaddr_un remote;LOGI("[+] wait 2s...");// 休眠一下sleep(2);/**** zygote启动后会进入一个死循环,用来接收AMS的请求连接.* 当没有应用启动时,zygote进程一直处于阻塞状态。* 所以我们后面代码中的第三次wait会无法返回,解决办法也很简单,就是主动发起一个zygote的连接。* 我们看到第二个waitpid后面调用了一个connect_to_zygote函数。*/// 创建socket套接字if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {// 设置连接的套接字的协议类型remote.sun_family = AF_UNIX;// 设置连接的套接字的目标strcpy(remote.sun_path, "/dev/socket/zygote");// 设置传递的参数的字节长度len = strlen(remote.sun_path) + sizeof(remote.sun_family);LOGI("[+] start to connect zygote socket");// 向"/dev/socket/zygote"目标套接字发起连接connect(s, (struct sockaddr *) &remote, len);LOGI("[+] close socket");// 关闭socket套接字close(s);}/**** 这个函数的功能很简单,先发起socket连接,然后再关闭连接。* 看上去没有做什么有用的事情,但是它却非常重要,* 通过连接zygote,它使zygote进程解除了阻塞状态,* 我们才得以注入进zygote进程。* 参考网址:http://zke1ev3n.me/2015/12/02/Android-so%E6%B3%A8%E5%85%A5/*/return NULL ;
}/*** attach to target process 附加目标进程*/
int ptrace_attach(pid_t pid, int zygote) {if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {LOGE("ptrace_attach");return -1;}waitpid(pid, NULL, WUNTRACED);/** Restarts the stopped child as for PTRACE_CONT, but arranges for* the child to be stopped at the next entry to or exit from a sys‐* tem call, or after execution of a single instruction, respec‐* tively.*/if (ptrace(PTRACE_SYSCALL, pid, NULL, 0) < 0) {LOGE("ptrace_syscall");return -1;}waitpid(pid, NULL, WUNTRACED);// 针对zygote进程的特殊处理if (zygote) {// 当进程为zygote时,需要考虑为zygote进程解除阻塞状态,使进程注入得以进行connect_to_zygote(NULL);}// 当目标进程在下次进/出系统调用时被附加调试if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL ) < 0) {LOGE("ptrace_syscall");return -1;}// 等待进程附加操作返回waitpid(pid, NULL, WUNTRACED);return 0;
}/*** detach from target process*/
int ptrace_detach( pid_t pid )
{if ( ptrace( PTRACE_DETACH, pid, NULL, 0 ) < 0 ){LOGE( "ptrace_detach" );return -1;}return 0;
}int ptrace_continue(pid_t pid) {if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {LOGE("ptrace_cont");return -1;}return 0;
}int ptrace_syscall(pid_t pid) {return ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}/*** write data to dest 向目标pid进程中写入数据(4字节对齐)*/
int ptrace_write(pid_t pid, uint8_t *dest, uint8_t *data, size_t size) {uint32_t i, j, remain;uint8_t *laddr;union u {long val;char chars[sizeof(long)];} d;j = size / 4;remain = size % 4;laddr = data;for (i = 0; i < j; i++) {memcpy(d.chars, laddr, 4);ptrace(PTRACE_POKETEXT, pid, (void *)dest, (void *)d.val);dest += 4;laddr += 4;}if (remain > 0) {d.val = ptrace(PTRACE_PEEKTEXT, pid, (void *)dest, NULL);for (i = 0; i < remain; i++) {d.chars[i] = *laddr++;}ptrace(PTRACE_POKETEXT, pid, (void *)dest, (void *)d.val);}return 0;
}// 从目标pid进程中读取数据(4字节对齐)
int ptrace_read( pid_t pid, uint8_t *src, uint8_t *buf, size_t size )
{uint32_t i, j, remain;uint8_t *laddr;union u {long val;char chars[sizeof(long)];} d;j = size / 4;remain = size % 4;laddr = buf;for ( i = 0; i < j; i ++ ){d.val = ptrace( PTRACE_PEEKTEXT, pid, src, 0 );memcpy( laddr, d.chars, 4 );src += 4;laddr += 4;}if ( remain > 0 ){d.val = ptrace( PTRACE_PEEKTEXT, pid, src, 0 );memcpy( laddr, d.chars, remain );}return 0;
}// 调用目标pid进程中的指定函数addr
int ptrace_call(pid_t pid, uint32_t addr, long *params, int num_params, struct pt_regs* regs) {uint32_t i;// 在arm中,函数的前4个参数使用r0-r4的寄存器传递for (i = 0; i < num_params && i < 4; i++) {// 设置调用目标pid进程中的函数需要的参数regs->uregs[i] = params[i];}// 当被调用的函数的参数个数超过4个时,其他的参数通过栈进行传递if (i < num_params) {// 抬高函数的栈顶regs->ARM_sp-= (num_params - i) * sizeof(long);// 向目标pid进程的内存中写入函数调用需要的超过4个的其他参数ptrace_write(pid, (uint8_t *) regs->ARM_sp, (uint8_t *) ¶ms[i], (num_params - i) * sizeof(long));}// 设置目标pid进程的pc为将被调用的函数的地址regs->ARM_pc= addr;// 针对当前进程所处的不同模式,进行不同的处理if (regs->ARM_pc& 1) {/* thumb模式 */regs->ARM_pc &= (~1u);regs->ARM_cpsr |= CPSR_T_MASK;} else {/* arm模式 */regs->ARM_cpsr &= ~CPSR_T_MASK;}// 设置函数调用的返回地址为0,调用的函数执行完,跳回到当前进程中regs->ARM_lr= 0;// 设置目标pid进程的寄存器的状态,并调用addr函数if (ptrace_setregs(pid, regs) == -1 || ptrace_continue(pid) == -1) {return -1;}// 等待函数的调用完成waitpid(pid, NULL, WUNTRACED);return 0;
}//static void* thread_connect_to_zygote(void* arg){
// int s, len;
// struct sockaddr_un remote;
//
// LOGI("[+] wait 2s...");
// sleep(2);
//
// if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {
// remote.sun_family = AF_UNIX;
// strcpy(remote.sun_path, "/dev/socket/zygote");
// len = strlen(remote.sun_path) + sizeof(remote.sun_family);
// LOGI("[+] start to connect zygote socket");
// connect(s, (struct sockaddr *) &remote, len);
// LOGI("[+] close socket");
// close(s);
// }
//
// return NULL ;
//}// 当目标pid进程为zygote时,加载so库文件之前,需要的测试处理
static int zygote_special_process(pid_t target_pid){LOGI("[+] zygote process should special take care. \n");struct pt_regs regs;// 获取目标pid进程的寄存器的状态值if (ptrace_getregs(target_pid, ®s) == -1)return -1;// 获取目标pid进程的getpid函数的调用地址void* remote_getpid_addr = get_remote_address(target_pid, getpid);LOGI("[+] Remote getpid addr %p.\n", remote_getpid_addr);// 判断获取目标pid进程的getpid函数的调用地址是否成功if(remote_getpid_addr == NULL){return -1;}pthread_t tid = 0;// 创建线程再次调用connect_to_zygote解除zygote进程的阻塞状态pthread_create(&tid, NULL, connect_to_zygote, NULL);// 释放线程pthread_detach(tid);// 调用目标pid进程中的getpid函数if (ptrace_call(target_pid, remote_getpid_addr, NULL, 0, ®s) == -1) {LOGE("[-] Call remote getpid fails");return -1;}// 获取上面的函数调用完后目标pid进程的寄存器的状态,主要是为了获取getpid函数的返回值if (ptrace_getregs(target_pid, ®s) == -1)return -1;// 打印调用getpid函数完后,目标pid进程的寄存器的状态LOGI("[+] Call remote getpid result r0=%x, r7=%x, pc=%x, \n", regs.ARM_r0, regs.ARM_r7, regs.ARM_pc);return 0;
}// 调用目标pid进程的dlopen函数加载指定的so库文件,并返回加载的模块的基址
void* ptrace_dlopen(pid_t target_pid, void* remote_dlopen_addr, const char* filename){struct pt_regs regs;// 获取目标pid进程的寄存器的状态值if (ptrace_getregs(target_pid, ®s) == -1)return NULL ;// 判断目标pid进程是否是zygote进程;如果是,加载so库文件之前,进行相应的测试处理if (strcmp("zygote", get_process_name(target_pid)) == 0 && zygote_special_process(target_pid) != 0) {return NULL ;}// 在目标pid进程中调用dlopen函数需要的参数long mmap_params[2];// filename为将要加载到目标pid进程中的so的路径字符串// 要将filename字符串写入到目标pid进程中,filename_len即为需要分配的内存空间的大小size_t filename_len = strlen(filename) + 1;// 调用目标pid进程的mmap函数申请内存空间,用以保存filename字符串(即将要加载的so文件的路径)void* filename_addr = find_space_by_mmap(target_pid, filename_len);// 判断在目标pid进程是否调用mmap函数分配内存空间成功if (filename_addr == NULL ) {LOGE("[-] Call Remote mmap fails.\n");return NULL ;}// 将filename字符串(即将要加载的so文件的路径)写入到目标pid进程的内存地址filename_addr中ptrace_write(target_pid, (uint8_t *)filename_addr, (uint8_t *)filename, filename_len);// dlopen函数的参数--需要加载的so文件的路径字符串mmap_params[0] = (long)filename_addr;// dlopen函数的参数--flag,加载的要求mmap_params[1] = RTLD_NOW | RTLD_GLOBAL;// 获取目标pid进程中的dlopen函数的调用地址(调用参数已经准备好)remote_dlopen_addr = (remote_dlopen_addr == NULL) ? get_remote_address(target_pid, (void *)dlopen) : remote_dlopen_addr;if (remote_dlopen_addr == NULL) {LOGE("[-] Get Remote dlopen address fails.\n");return NULL;}// 在目标pid进程调用dlopen函数,加载filename_addr指定的so库文件if (ptrace_call(target_pid, (uint32_t) remote_dlopen_addr, mmap_params, 2, ®s) == -1)return NULL;// 获取目标pid进程的寄存器的状态值,主要是为了获取上面 dlopen函数调用的返回值if (ptrace_getregs(target_pid, ®s) == -1)return NULL;LOGI("[+] Target process returned from dlopen, return r0=%x, r7=%x, pc=%x, \n", regs.ARM_r0, regs.ARM_r7, regs.ARM_pc);// 返回目标pid进程中调用dlopen函数的返回的内存加载的模块基址return regs.ARM_pc == 0 ? (void *) regs.ARM_r0 : NULL;
}
tool.h文件
/** tool.h** Created on: 2013-7-5* Author: boyliang*/#ifndef TOOL_H_
#define TOOL_H_#include <stdio.h>
#include <dlfcn.h>// 获取指定内存加载模块的导出函数的地址
void *get_method_address(const char *soname, const char *methodname);// 获取目标pid进程的名称字符串
const char* get_process_name(pid_t pid);#endif /* TOOL_H_ */
tool.c文件
/** tool.c** Created on: 2013-7-5* Author: boyliang*/#include <stdio.h>
#include <dlfcn.h>
#include <stddef.h>// 获取指定内存加载模块的导出函数的地址
void *get_method_address(const char *soname, const char *methodname) {void *handler = dlopen(soname, RTLD_NOW | RTLD_GLOBAL);return dlsym(handler, methodname);
}// 获取目标pid进程的名称字符串
const char* get_process_name(pid_t pid) {static char buffer[255];FILE* f;char path[255];// 格式化得到字符串"/proc/pid/cmdline"snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);// 读取文件"/proc/pid/cmdline"的内容,获取进程的命令行参数if ((f = fopen(path, "r")) == NULL) {return NULL;}// 读取文件"/proc/pid/cmdline"的第1行字符串内容--进程的名称if (fgets(buffer, sizeof(buffer), f) == NULL) {return NULL;}// 关闭文件fclose(f);return buffer;
}
log.h文件
/** log.h** Created on: 2013-6-25* Author: boyliang*/#ifndef LOG_H_
#define LOG_H_#include <android/log.h>// 主要用于消息的log打印#define LOG_TAG "TTT"#ifdef DEBUG
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#else
#define LOGI(...) while(0)
#define LOGE(...) while(0)
#endif#endif /* LOG_H_ */
elf_utils.h文件
/** elf_utils.h** Created on: 2013-6-19* Author: boyliang*/#ifndef ELF_UTILS_H_
#define ELF_UTILS_H_#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/mman.h>// 获取目标pid进程中指定so模块的加载基址
void* get_module_base(pid_t pid, const char* module_name);// 在目标pid进程的内存空间中申请内存,申请成功返回的内存地址保存在r0中
void* find_space_by_mmap(int target_pid, int size);// 在目标pid进程的"/system/lib/libc.so"的内存范围内(从内存结束地址往回的方向)查找内存空间
void* find_space_in_maps(int pid, int size);// 通过系统函数的地址查找到该函数所在的模块的名称
int find_module_info_by_address(pid_t pid, void* addr, char *module, void** start, void** end);// 通过指定的内存模块so的路径字符串,获取该内存模块的在目标进程pid中起始地址和结束地址
int find_module_info_by_name(pid_t pid, const char *module, void** start, void** end);// 获取目标pid进程中指定函数的调用地址
void* get_remote_address(pid_t pid, void *local_addr);#endif /* ELF_UTILS_H_ */
elf_utils.c文件
/** elf_utils.c** Created on: 2013-6-25* Author: boyliang*/#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ptrace.h>#include "tools.h"
#include "elf_utils.h"
#include "log.h"// 获取目标pid进程中指定so模块的加载基址
void* get_module_base(pid_t pid, const char* module_name) {FILE *fp;long addr = 0;char *pch;char filename[32];char line[1024];if (pid < 0) {/* self process */snprintf(filename, sizeof(filename), "/proc/self/maps");} else {snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);}fp = fopen(filename, "r");if (fp != NULL) {while (fgets(line, sizeof(line), fp)) {// 判断是否是在目标pid进程的内存中要查找到的so模块if (strstr(line, module_name)) {pch = strtok(line, "-");// 获取目标pid进程中指定模块的基址addr = strtoul(pch, NULL, 16);if (addr == 0x8000)addr = 0;break;}}fclose(fp);}return (void *) addr;
}// 在目标pid进程的内存空间中申请内存,申请成功返回的内存地址保存在r0中
void* find_space_by_mmap(int target_pid, int size) {struct pt_regs regs;// 获取目标pid进程的寄存器的状态if (ptrace_getregs(target_pid, ®s) == -1)return 0;long parameters[10];/* call mmap */parameters[0] = 0; // addrparameters[1] = size; // sizeparameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // protparameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // flagsparameters[4] = 0; //fdparameters[5] = 0; //offset// 获取目标pid进程中的mmap函数的调用地址void *remote_mmap_addr = get_remote_address(target_pid, get_method_address("/system/lib/libc.so", "mmap"));LOGI("[+] Calling mmap in target process. mmap addr %p.\n", remote_mmap_addr);if (remote_mmap_addr == NULL) {LOGE("[-] Get Remote mmap address fails.\n");return 0;}// 调用目标pid进程的mmap函数,在目标pid进程的内存中申请内存空间if (ptrace_call(target_pid, (uint32_t) remote_mmap_addr, parameters, 6, ®s) == -1)return 0;// 获取目标pid进程的寄存器的状态if (ptrace_getregs(target_pid, ®s) == -1)return 0;LOGI("[+] Target process returned from mmap, return r0=%x, r7=%x, pc=%x, \n", regs.ARM_r0, regs.ARM_r7, regs.ARM_pc);// arm中,函数的返回值保存在寄存器r0中,返回在目标pid进程中申请的内存空间的地址return regs.ARM_pc == 0 ? (void *) regs.ARM_r0 : 0;
}// 分割字符串
static char* nexttok(char **strp) {// 以" "为基准分解字符串,将原字符串中第一个" "替换为'\0'// 第一个" "前面的字符串返回在p中,第一个" "后面的字符串在strp中char *p = strsep(strp, " ");// 返回分割的字符串return p == NULL ? "" : p;
}// 在目标pid进程的"/system/lib/libc.so"的内存范围内(从内存结束地址往回的方向)查找内存空间
void* find_space_in_maps(int pid, int size) {char statline[1024];FILE * fp;uint32_t* addr = (uint32_t*) 0x40008000;char *address, *proms, *ptr;const char* tname = "/system/lib/libc.so";const char* tproms = "r-xp";// 获取字符串"/system/lib/libc.so"的长度int tnaem_size = strlen(tname);// 获取字符串"r-xp"的长度int tproms_size = strlen(tproms);// 内存以4字节对齐size = ((size / 4) + 1) * 4;// 格式化得到字符串"/proc/pid/maps"sprintf(statline, "/proc/%d/maps", pid);// 打开文件"/proc/pid/maps"fp = fopen(statline, "r");if (fp == 0)return 0;// 读取文件"/proc/pid/maps"中内容(每次读一行)while (fgets(statline, sizeof(statline), fp)) {// 分割字符串ptr = statline;// 得到内存模块的起始和结束地址address = nexttok(&ptr); // skip address// 内存模块的属性proms = nexttok(&ptr); // skip promsnexttok(&ptr); // skip offsetnexttok(&ptr); // skip devnexttok(&ptr); // skip inode// ptr中最终保存的是加载的内存模块的路径字符串while (*ptr != '\0') {if (*ptr == ' ')ptr++;elsebreak;}// 查找目标so模块if (ptr && proms && address) {// 判断是否是"r-xp"属性的模块if (strncmp(tproms, proms, tproms_size) == 0) {// 判断是否是"/system/lib/libc.so"模块if (strncmp(tname, ptr, tnaem_size) == 0) {// address like afe00000-afe3a000if (strlen(address) == 17) {// 获取内存加载模块/system/lib/libc.so的内存范围的结束地址(方便后面查找内存空间)addr = (uint32_t*) strtoul(address + 9, NULL, 16);// 在目标pid进程的/system/lib/libc.so的内存范围内查找到size大小内存空间addr -= size;printf("proms=%s address=%s name=%s", proms, address, ptr);break;}}}}}// 关闭文件fclose(fp);// 返回在目标进程中查找到的内存空间的地址return (void*) addr;
}// 通过系统函数的地址查找到该函数所在的模块的名称
int find_module_info_by_address(pid_t pid, void* addr, char *module, void** start, void** end) {char statline[1024];FILE *fp;char *address, *proms, *ptr, *p;// 格式化字符串得到"/proc/pid/maps"if ( pid < 0 ) {/* self process */snprintf( statline, sizeof(statline), "/proc/self/maps");} else {snprintf( statline, sizeof(statline), "/proc/%d/maps", pid );}// 打开文件 /proc/pid/mapsfp = fopen( statline, "r" );if ( fp != NULL ) {// 每次一行,读取文件/proc/pid/maps中内容while ( fgets( statline, sizeof(statline), fp ) ) {// 解析读取为一行字符串信息ptr = statline;// 获取模块的起始和结束地址address = nexttok(&ptr); // skip addressproms = nexttok(&ptr); // skip promsnexttok(&ptr); // skip offsetnexttok(&ptr); // skip devnexttok(&ptr); // skip inodewhile(*ptr != '\0') {if(*ptr == ' ')ptr++;elsebreak;}p = ptr;while(*p != '\0') {if(*p == '\n')*p = '\0';p++;}// 4016a000-4016b000if(strlen(address) == 17) {address[8] = '\0';// 获取内存加载模块的起始地址*start = (void*)strtoul(address, NULL, 16);// 获取内存加载模块的结束地址*end = (void*)strtoul(address+9, NULL, 16);// printf("[%p-%p] %s | %p\n", *start, *end, ptr, addr);// 判断该系统函数的地址是否在该模块的内存范围内if(addr > *start && addr < *end) {// 找到该系统函数所在的内存模块// 保存该内存加载的so模块的文件路径strcpy(module, ptr);fclose( fp );return 0;}}}fclose( fp ) ;}return -1;
}// 通过指定的内存模块so的路径字符串,获取该内存模块的在目标进程pid中起始地址和结束地址
int find_module_info_by_name(pid_t pid, const char *module, void** start, void** end) {char statline[1024];FILE *fp;char *address, *proms, *ptr, *p;if ( pid < 0 ) {/* self process */snprintf( statline, sizeof(statline), "/proc/self/maps");} else {snprintf( statline, sizeof(statline), "/proc/%d/maps", pid );}fp = fopen( statline, "r" );if ( fp != NULL ) {while ( fgets( statline, sizeof(statline), fp ) ) {ptr = statline;address = nexttok(&ptr); // skip addressproms = nexttok(&ptr); // skip promsnexttok(&ptr); // skip offsetnexttok(&ptr); // skip devnexttok(&ptr); // skip inodewhile(*ptr != '\0') {if(*ptr == ' ')ptr++;elsebreak;}p = ptr;while(*p != '\0') {if(*p == '\n')*p = '\0';p++;}// 4016a000-4016b000if(strlen(address) == 17) {address[8] = '\0';*start = (void*)strtoul(address, NULL, 16);*end = (void*)strtoul(address+9, NULL, 16);// printf("[%p-%p] %s | %p\n", *start, *end, ptr, addr);// 通过内存模块的路径字符串,判读是否是要查找的目标内存so模块if(strncmp(module, ptr, strlen(module)) == 0) {fclose( fp ) ;return 0;}}}fclose( fp ) ;}return -1;
}// 获取目标pid进程中指定函数的调用地址
void* get_remote_address(pid_t pid, void *local_addr) {// 保存加载的内存so模块的文件路径字符串char buf[256];// 当前进程中指定模块的起始地址void* local_start = 0;// 当前进程中指定模块的结束地址void* local_end = 0;// 目标pid进程中指定模块的起始地址void* remote_start = 0;// 目标pid进程中指定模块的结束地址void* remote_end = 0;// 获取当前进程中指定系统函数所在的模块的文件路径字符串bufif(find_module_info_by_address(-1, local_addr, buf, &local_start, &local_end) < 0) {LOGI("[-] find_module_info_by_address FAIL");return NULL;}LOGI("[+] the local module is %s", buf);// 通过指定的内存模块so的路径字符串,获取该内存模块的在目标进程pid中起始地址和结束地址if(find_module_info_by_name(pid, buf, &remote_start, &remote_end) < 0) {LOGI("[-] find_module_info_by_name FAIL");return NULL;}// 目标pid进程的local_addr函数的调用地址return (void *)( (uint32_t)local_addr + (uint32_t)remote_start - (uint32_t)local_start );
}
Android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 编译生成的模块的名称
LOCAL_MODULE := poison # 需要被编译的源码文件
LOCAL_SRC_FILES := poison.c \elf_utils.c \ptrace_utils.c \tools.c # 支持log日志打印android/log.h里函数调用的需要
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog # 编译模块生成可执行文件
include $(BUILD_EXECUTABLE)
Application.mk文件
# 编译生成的模块运行支持的平台
APP_ABI := armeabi-v7a
# 设置编译连接的工具的版本
#NDK_TOOLCHAIN_VERSION = 4.9
https://github.com/matrixhawk/Poison
https://github.com/boyliang/ndk-patch
http://zke1ev3n.me/2015/12/02/Android-so注入/
http://blog.csdn.net/qq1084283172/article/details/46859931
http://www.cnblogs.com/leaven/archive/2011/01/25/1944688.html
http://bbs.pediy.com/showthread.php?t=141355
http://blog.csdn.net/jinzhuojun/article/details/9900105
这篇关于Android进程的so注入--Poison(稳定注入版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!