本文博客地址: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}platformsandroid-19arch-armusrinclude
${NDKROOT}sourcescxx-stlgnu-libstdc++4.8include
${NDKROOT}sourcescxx-stlgnu-libstdc++4.8libsarmeabiinclude
${NDKROOT} oolchainsarm-Linux-androideabi-4.8prebuiltwindowslibgccarm-linux-androideabi4.8include
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 ",(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 xxxxxAndroidProject_Poison-masteruse_poison
adb 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.androiddecod
cat /proc/17569/maps
/data/local/tmp/poison /data/local/tmp/libmobisec.so 17569
cat /proc/17569/maps | grep libmobisec.so
adb 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]);
// 获取注入的目标进程的pid
process_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
");
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
", process_hook.pid, get_process_name(process_hook.pid));
// 读取此时目标进程中所有的寄存器的状态信息
if (ptrace_getregs(process_hook.pid, ®s) < 0) {
LOGE("[-] Can't get regs %d
", 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.
");
// goto DETACH;
// }
//
// 打印目标进程的函数dlopen和dlsym的调用地址
LOGI("[+] remote_dlopen address %p
", remote_dlopen_addr);
LOGI("[+] remote_dlsym address %p
", remote_dlsym_addr);
// 调用目标pid进程的dlopen函数加载指定的so库文件,获取返回的加载的模块的基址
if(ptrace_dlopen(process_hook.pid, remote_dlopen_addr, process_hook.dso) == NULL){
LOGE("[-] Ptrace dlopen fail. %s
", dlerror());
}
// 针对此时不同的模式,设置目标pid进程的CPSR寄存器的值
if (regs.ARM_pc & 1 ) {
// thumb
regs.ARM_pc &= (~1u);
regs.ARM_cpsr |= CPSR_T_MASK;
} else {
// arm
regs.ARM_cpsr &= ~CPSR_T_MASK;
}
// 恢复目标pid进程的寄存器的状态即恢复到注入前的运行状态
if (ptrace_setregs(process_hook.pid, ®s) == -1) {
LOGE("[-] Set regs fail. %s
", strerror(errno));
// 失败进行跳转
goto DETACH;
}
// 打印注入成功的消息
LOGI("[+] Inject success!
");
DETACH:
// 结束对目标pid进程的附加
ptrace_detach(process_hook.pid);
// 打印注入工作完成的消息
LOGI("[+] Inject done!
");
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.
");
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.
", 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,
", 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.
");
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.
");
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,
", 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; // addr
parameters[1] = size; // size
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // prot
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // flags
parameters[4] = 0; //fd
parameters[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.
", remote_mmap_addr);
if (remote_mmap_addr == NULL) {
LOGE("[-] Get Remote mmap address fails.
");
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,
", 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) {
// 以" "为基准分解字符串,将原字符串中第一个" "替换为' '
// 第一个" "前面的字符串返回在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 proms
nexttok(&ptr); // skip offset
nexttok(&ptr); // skip dev
nexttok(&ptr); // skip inode
// ptr中最终保存的是加载的内存模块的路径字符串
while (*ptr != ' ') {
if (*ptr == ' ')
ptr++;
else
break;
}
// 查找目标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-afe3a000
if (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/maps
fp = fopen( statline, "r" );
if ( fp != NULL ) {
// 每次一行,读取文件/proc/pid/maps中内容
while ( fgets( statline, sizeof(statline), fp ) ) {
// 解析读取为一行字符串信息
ptr = statline;
// 获取模块的起始和结束地址
address = nexttok(&ptr); // skip address
proms = nexttok(&ptr); // skip proms
nexttok(&ptr); // skip offset
nexttok(&ptr); // skip dev
nexttok(&ptr); // skip inode
while(*ptr != ' ') {
if(*ptr == ' ')
ptr++;
else
break;
}
p = ptr;
while(*p != ' ') {
if(*p == '
')
*p = ' ';
p++;
}
// 4016a000-4016b000
if(strlen(address) == 17) {
address[8] = ' ';
// 获取内存加载模块的起始地址
*start = (void*)strtoul(address, NULL, 16);
// 获取内存加载模块的结束地址
*end = (void*)strtoul(address+9, NULL, 16);
// printf("[%p-%p] %s | %p
", *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 address
proms = nexttok(&ptr); // skip proms
nexttok(&ptr); // skip offset
nexttok(&ptr); // skip dev
nexttok(&ptr); // skip inode
while(*ptr != ' ') {
if(*ptr == ' ')
ptr++;
else
break;
}
p = ptr;
while(*p != ' ') {
if(*p == '
')
*p = ' ';
p++;
}
// 4016a000-4016b000
if(strlen(address) == 17) {
address[8] = ' ';
*start = (void*)strtoul(address, NULL, 16);
*end = (void*)strtoul(address+9, NULL, 16);
// printf("[%p-%p] %s | %p
", *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;
// 获取当前进程中指定系统函数所在的模块的文件路径字符串buf
if(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