前言:
注入技术形象点的比喻就像打针的注射器,注入代码就跟把药水通过注射器打入身体里一样的道理。
Android系统采用的是Linux内核,很多Linux系统上的技术都可以应用在Android系统上,Android系统上ptrace注入远程进程的技术就是其中一种。这篇文章将对ptrace注入的完整流程进行介绍。
一、ptrace函数介绍
ptrace注入技术的核心就是ptrace函数,在ptrace注入过程中,将多次调用ptrace函数。Linux的man文档(超链接至: http://man7.org/linux/man-pages/man2/ptrace.2.html)中提到,ptrace函数为一个进程提供了监视和控制其他进程的方法,在注入进程后,父进程还可以读取和修改子进程的内存空间以及寄存器值。
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
request参数取值较多,由于篇幅所限这里仅介绍一部分ptrace注入进程的过程中需要使用到的request参数。
PTRACE_ATTACH,表示附加到指定远程进程;
PTRACE_DETACH,表示从指定远程进程分离
PTRACE_GETREGS,表示读取远程进程当前寄存器环境
PTRACE_SETREGS,表示设置远程进程的寄存器环境
PTRACE_CONT,表示使远程进程继续运行
PTRACE_PEEKTEXT,从远程进程指定内存地址读取一个word大小的数据
PTRACE_POKETEXT,往远程进程指定内存地址写入一个word大小的数据
二、ptrace注入进程流程
2.1 ptrace注入综述
Ptrace注入的目的是为了将外部的模块注入到游戏进程中,然后执行被注入模块的代码,可以对游戏进程的代码和数据进行修改。目前有两种实现ptrace注入模块到远程进程的方法,其中一种是使用ptrace将shellcode注入到远程进程的内存空间中,然后通过执行shellcode加载远程进程模块;另外一种方法是直接远程调用dlopen\dlsym等函数加载被注入模块并执行指定的代码。两种方法的实现方式稍有差异,但总体来说都是通过ptrace函数在远程进程空间执行代码,本章仅就第二种方式进行详细介绍,附件中包含两种注入方法实现的代码,有兴趣的读者可以了解。
如下图所示,为ptrace注入远程进程的整体流程,整个注入过程并不复杂,但其中有一些细节需要注意,稍有不慎就会造成远程进程崩溃。下面小节将详细介绍流程中的每个步骤。
ptrace注入远程进程流程:
2.2 Attach到远程进程
ptrace注入的第一个步骤是先附加到远程进程上,如下所示,附加到远程进程是通过调用request参数为PTRACE_ATTACH的ptrace函数,pid为对应需要附加的远程进程的ID,addr参数和data参数为NULL。
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
在附加到远程进程后,远程进程的执行会被中断,此时父进程可以通过调用waitpid函数来判断子进程是否进入暂停状态。waitpid的函数原型如下所示,其中当options参数为WUNTRACED ,表示若对应pid的远程进程进入暂停状态,则马上返回,可用于等待远程进程进入暂停状态。
pid_t waitpid(pid_t pid,int * status,int options);
2.3 读取和写入寄存器值
在通过ptrace改变远程进程执行流程前,需要先读取远程进程的所有寄存器值进行保存,在detach时向远程进程写入保存的原寄存器值用于恢复远程进程原有的执行流程。
如下所示,为读取和写入寄存器值的ptrace调用,request参数分别为PTRACE_GETREGS和PTRACE_SETREGS,pid为对应进程的ID。
ptrace(PTRACE_GETREGS, pid, NULL, regs);
ptrace(PTRACE_SETREGS, pid, NULL, regs);
在ARM处理器下,data参数的regs为pt_regs结构的指针,从远程进程获取的寄存器值将存储到该结构中,pt_regs结构的定义如下所示,其中ARM_r0成员用于存储R0寄存器的值,函数调用后的返回值会存储在R0寄存器中,ARM_pc成员存储当前执行地址,ARM_sp成员存储当前栈顶地址,ARM_lr成员存储返回地址,ARM_cpsr成员存储状态寄存器的值。
struct pt_regs {
long uregs[18];
};
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
2.4 远程进程内存读取和写入数据
调用request参数为PTRACE_PEEKTEXT的ptrace函数可以从远程进程的内存空间中读取数据,一次读取一个word大小的数据。如下所示,其中addr参数为需读取数据的远程进程内存地址,返回值为读取出的数据。
ptrace(PTRACE_PEEKTEXT, pid, pCurSrcBuf, 0);
ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) ;
调用request参数为PTRACE_POKETEXT的ptrace函数可以将数据写入到远程进程的内存空间中,同样一次写入一个word大小的数据,ptrace函数的addr参数为要写入数据的远程进程内存地址,data参数为要写入的数据。
写入数据时需要注意,若写入数据长度不是一个word大小的倍数,写入最后一个不足word大小的数据时,要先保存原地址处的高位数据。
如下代码所示,首先通过request参数为PTRACE_PEEKTEXT的ptrace函数读取原内存中的一个word大小的数据,然后将要写入的数据复制到读取出的数据的低位,然后调用ptrace函数将修改后的数据写入远程进程的内存地址处。
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurDestBuf, NULL);
memcpy((void *)(&lTmpBuf), pCurSrcBuf, nRemainCount);
if (ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0)
{
LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);
return -1;
}
2.5 远程调用函数
在ARM处理器中,函数调用的前四个参数通过R0-R3寄存器来传递,剩余参数按从右到左的顺序压入栈中进行传递。如下代码所示,在远程调用函数前,需要先判断函数调用的参数个数,如果小于4个,则将参数按顺序分别写入R0-R3寄存器中,若大于4个,则首先调整SP寄存器在栈中分配空间,然后通过调用ptrace函数将剩余参数写入到栈中。
for (i = 0; i < num_params && i < 4; i ++) {
regs->uregs[i] = parameters[i];
}
if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ;
if (ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)parameters[i], (num_params - i) * sizeof(long)) == -1)
return -1;
}
在写入函数的参数后,修改进程的PC寄存器为需要执行的函数地址。这里有一点需要注意,在ARM架构下有ARM和Thumb两种指令,因此在调用函数前需要判断函数被解析成哪种指令,如下所示的代码就是通过地址的最低位是否为1来判断调用地址处指令为ARM或Thumb,若为Thumb指令,则需要将最低位重新设置为0,并且将CPSR寄存器的T标志位置位,若为ARM指令,则将CPSR寄存器的T标志位复位。
if (regs->ARM_pc & 1) { /* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
} else { /* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
}
在使远程进程恢复运行前,还需要设置远程进程的LR寄存器值为0,并且在在本地进程调用options参数为WUNTRACED的waitpid函数等待远程进程重新进入暂停状态。远程进程的函数调用结束后,会跳转到LR寄存器存储的地址处,但由于LR寄存器被设置为0,会导致远程进程执行出错,此时进程会进入暂停状态,本地进程等待结束,通过读取远程进程的R0寄存器可以获取远程函数调用的返回结果,以上就是一次完整地调用远程函数的过程。
在ptrace注入流程中需要多次调用函数,除了调用被注入模块的函数外,还需要调用mmap函数在远程进程地址空间内分配内存,调用dlopen函数来远程加载被注入模块,调用dlsym函数来获取被注入模块对应函数的地址,调用dlclose函数来关闭加载的模块。这些函数的原型如下所示
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
void * dlopen( const char * pathname, int mode);
void*dlsym(void*handle,constchar*symbol);
int dlclose (void *handle);
在调用这些函数前,需要首先获取到这些系统函数在远程进程中的地址,mmap函数是在”/system/lib/libc.so”模块中,dlopen、dlsym与dlclose函数均是在”/system/bin/linker”模块中。
读取”/proc/pid/maps”可以获取到系统模块在本地进程和远程进程的加载基地址,要获取远程进程内存空间中mmap等函数的虚拟地址,可通过计算本地进程中mmap等函数相对于模块的地址偏移,然后使用此地址偏移加上远程进程对应模块的基地址,这个地址就是远程进程内存空间中对应函数的虚拟地址。
2.6 恢复寄存器值
在从远程进程detach前,需要将远程进程的原寄存器环境恢复,保证远程进程原有的执行流程不被破坏,如果不恢复寄存器值,detach时会导致远程进程的崩溃。
2.7 Detach进程
从远程进程脱离是ptrace注入的最后一个步骤,在detach后被注入进程将继续运行。如下所示,从远程进程detach是调用request参数为PTRACE_DETACH的ptrace函数。
ptrace(PTRACE_DETACH, pid, NULL, 0);
三、完整代码
被注入模块(药水)
#include <stdio.h>
#include <stdlib.h>
#include <utils/PrintLog.h>
int Inject_entry()
{
LOGD("Inject_entry Func is called\n");
return 0;
}
注入模块(注射器)
/************************************************************
FileName: InjectModule.c
Description: ptrace注入
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <ptraceInject.h>
#include <utils/PrintLog.h>
/*************************************************
Description: 通过进程名称定位到进程的PID
Input: process_name为要定位的进程名称
Output: 无
Return: 返回定位到的进程PID,若为-1,表示定位失败
Others: 无
*************************************************/
pid_t FindPidByProcessName(const char *process_name)
{
int ProcessDirID = 0;
pid_t pid = -1;
FILE *fp = NULL;
char filename[MAX_PATH] = {0};
char cmdline[MAX_PATH] = {0};
struct dirent * entry = NULL;
if ( process_name == NULL )
return -1;
DIR* dir = opendir( "/proc" );
if ( dir == NULL )
return -1;
while( (entry = readdir(dir)) != NULL )
{
ProcessDirID = atoi( entry->d_name );
if ( ProcessDirID != 0 )
{
snprintf(filename, MAX_PATH, "/proc/%d/cmdline", ProcessDirID);
fp = fopen( filename, "r" );
if ( fp )
{
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (strncmp(process_name, cmdline, strlen(process_name)) == 0)
{
pid = ProcessDirID;
break;
}
}
}
}
closedir(dir);
return pid;
}
int main(int argc, char *argv[]) {
char InjectModuleName[MAX_PATH] = "/data/libInjectModule.so"; // 注入模块全路径
char RemoteCallFunc[MAX_PATH] = "Inject_entry"; // 注入模块后调用模块函数名称
char InjectProcessName[MAX_PATH] = "com.yiyou.org.mario.xyhy"; // 注入进程名称
// 当前设备环境判断
#if defined(__i386__)
LOGD("Current Environment x86");
return -1;
#elif defined(__arm__)
LOGD("Current Environment ARM");
#else
LOGD("other Environment");
return -1;
#endif
pid_t pid = FindPidByProcessName(InjectProcessName);
if (pid == -1)
{
printf("Get Pid Failed");
return;
}
printf("begin inject process, RemoteProcess pid:%d, InjectModuleName:%s, RemoteCallFunc:%s\n", pid, InjectModuleName, RemoteCallFunc);
int iRet = inject_remote_process(pid, InjectModuleName, RemoteCallFunc, NULL, 0);
//int iRet = inject_remote_process_shellcode(pid, InjectModuleName, RemoteCallFunc, NULL, 0);
if (iRet == 0)
{
printf("Inject Success\n");
}
else
{
printf("Inject Failed\n");
}
printf("end inject,%d\n", pid);
return 0;
}
/************************************************************
FileName: ptraceInject.c
Description: ptrace注入
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <utils/PrintLog.h>
#include <ptraceInject.h>
#define CPSR_T_MASK ( 1u << 5 )
#define REMOTE_ADDR( addr, local_base, remote_base ) ( (uint32_t)(addr) + (uint32_t)(remote_base) - (uint32_t)(local_base) )
const char *libc_path = "/system/lib/libc.so";
const char *linker_path = "/system/bin/linker";
/*************************************************
Description: ptrace使远程进程继续运行
Input: pid表示远程进程的ID
Output: 无
Return: 返回0表示continue成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_continue(pid_t pid)
{
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0)
{
LOGD("ptrace cont error, pid:%d", pid);
return -1;
}
return 0;
}
/*************************************************
Description: 使用ptrace Attach到指定进程
Input: pid表示远程进程的ID
Output: 无
Return: 返回0表示attach成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_attach(pid_t pid)
{
int status = 0;
if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {
LOGD("attach process error, pid:%d", pid);
return -1;
}
LOGD("attach process pid:%d", pid);
waitpid(pid, &status , WUNTRACED);
return 0;
}
/*************************************************
Description: 使用ptrace detach指定进程
Input: pid表示远程进程的ID
Output: 无
Return: 返回0表示detach成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_detach(pid_t pid)
{
if (ptrace(PTRACE_DETACH, pid, NULL, 0) < 0) {
LOGD("detach process error, pid:%d", pid);
return -1;
}
LOGD("detach process pid:%d", pid);
return 0;
}
/*************************************************
Description: 使用ptrace获取远程进程的寄存器值
Input: pid表示远程进程的ID,regs为pt_regs结构,存储了寄存器值
Output: 无
Return: 返回0表示获取寄存器成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_getregs(pid_t pid, struct pt_regs *regs)
{
if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0)
{
LOGD("Get Regs error, pid:%d", pid);
return -1;
}
return 0;
}
/*************************************************
Description: 使用ptrace设置远程进程的寄存器值
Input: pid表示远程进程的ID,regs为pt_regs结构,存储需要修改的寄存器值
Output: 无
Return: 返回0表示设置寄存器成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_setregs(pid_t pid, struct pt_regs *regs)
{
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0)
{
LOGD("Set Regs error, pid:%d", pid);
return -1;
}
return 0;
}
/*************************************************
Description: 获取返回值,ARM处理器中返回值存放在ARM_r0寄存器中
Input: regs存储远程进程当前的寄存器值
Output: 无
Return: 在ARM处理器下返回r0寄存器值
Others: 无
*************************************************/
long ptrace_getret(struct pt_regs * regs)
{
return regs->ARM_r0;
}
/*************************************************
Description: 获取当前执行代码的地址,ARM处理器下存放在ARM_pc中
Input: regs存储远程进程当前的寄存器值
Output: 无
Return: 在ARM处理器下返回pc寄存器值
Others: 无
*************************************************/
long ptrace_getpc(struct pt_regs * regs)
{
return regs->ARM_pc;
}
/*************************************************
Description: 使用ptrace从远程进程内存中读取数据
Input: pid表示远程进程的ID,pSrcBuf表示从远程进程读取数据的内存地址
pDestBuf表示用于存储读取出数据的地址,size表示读取数据的大小
Output: 无
Return: 返回0表示读取数据成功
Others: 无
*************************************************/
int ptrace_readdata(pid_t pid, uint8_t *pSrcBuf, uint8_t *pDestBuf, uint32_t size)
{
uint32_t nReadCount = 0;
uint32_t nRemainCount = 0;
uint8_t *pCurSrcBuf = pSrcBuf;
uint8_t *pCurDestBuf = pDestBuf;
long lTmpBuf = 0;
uint32_t i = 0;
nReadCount = size / sizeof(long);
nRemainCount = size % sizeof(long);
for (i = 0; i < nReadCount; i ++ )
{
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurSrcBuf, 0);
memcpy(pCurDestBuf, (char *)(&lTmpBuf), sizeof(long));
pCurSrcBuf += sizeof(long);
pCurDestBuf += sizeof(long);
}
if ( nRemainCount > 0 )
{
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurSrcBuf, 0);
memcpy(pCurDestBuf, (char *)(&lTmpBuf), nRemainCount);
}
return 0;
}
//
/*************************************************
Description: 使用ptrace将数据写入到远程进程空间中
Input: pid表示远程进程的ID,pWriteAddr表示写入数据到远程进程的内存地址
pWriteData用于存储写入数据的地址,size表示写入数据的大小
Output: 无
Return: 返回0表示写入数据成功,返回-1表示写入数据失败
Others: 无
*************************************************/
int ptrace_writedata(pid_t pid, uint8_t *pWriteAddr, uint8_t *pWriteData, uint32_t size)
{
uint32_t nWriteCount = 0;
uint32_t nRemainCount = 0;
uint8_t *pCurSrcBuf = pWriteData;
uint8_t *pCurDestBuf = pWriteAddr;
long lTmpBuf = 0;
uint32_t i = 0;
nWriteCount = size / sizeof(long);
nRemainCount = size % sizeof(long);
// 先讲数据以sizeof(long)字节大小为单位写入到远程进程内存空间中
for (i = 0; i < nWriteCount; i ++)
{
memcpy((void *)(&lTmpBuf), pCurSrcBuf, sizeof(long));
if (ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0) // PTRACE_POKETEXT表示从远程内存空间写入一个sizeof(long)大小的数据
{
LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);
return -1;
}
pCurSrcBuf += sizeof(long);
pCurDestBuf += sizeof(long);
}
// 将剩下的数据写入到远程进程内存空间中
if (nRemainCount > 0)
{
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurDestBuf, NULL); //先取出原内存中的数据,然后将要写入的数据以单字节形式填充到低字节处
memcpy((void *)(&lTmpBuf), pCurSrcBuf, nRemainCount);
if (ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0)
{
LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);
return -1;
}
}
return 0;
}
/*************************************************
Description: 使用ptrace远程call函数
Input: pid表示远程进程的ID,ExecuteAddr为远程进程函数的地址
parameters为函数参数的地址,regs为远程进程call函数前的寄存器环境
Output: 无
Return: 返回0表示call函数成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_call(pid_t pid, uint32_t ExecuteAddr, long *parameters, long num_params, struct pt_regs* regs)
{
int i = 0;
// ARM处理器,函数传递参数,将前四个参数放到r0-r3,剩下的参数压入栈中
for (i = 0; i < num_params && i < 4; i ++) {
regs->uregs[i] = parameters[i];
}
if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ; // 分配栈空间,栈的方向是从高地址到低地址
if (ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)¶meters[i], (num_params - i) * sizeof(long)) == -1)
return -1;
}
regs->ARM_pc = ExecuteAddr; //设置ARM_pc寄存器为需要调用的函数地址
// 与BX跳转指令类似,判断跳转的地址位[0]是否为1,如果为1,则将CPST寄存器的标志T置位,解释为Thumb代码
// 若为0,则将CPSR寄存器的标志T复位,解释为ARM代码
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;
if (ptrace_setregs(pid, regs) == -1 || ptrace_continue(pid) == -1) {
LOGD("ptrace set regs or continue error, pid:%d", pid);
return -1;
}
int stat = 0;
// 对于使用ptrace_cont运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发生错误。
// 参数WUNTRACED表示当进程进入暂停状态后,立即返回
// 将ARM_lr(存放返回地址)设置为0,会导致子进程执行发生错误,则子进程进入暂停状态
waitpid(pid, &stat, WUNTRACED);
// 判断是否成功执行函数
LOGD("ptrace call ret status is %d\n", stat);
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
LOGD("ptrace call error");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}
// 获取远程进程的寄存器值,方便获取返回值
if (ptrace_getregs(pid, regs) == -1)
{
LOGD("After call getregs error");
return -1;
}
return 0;
}
/*************************************************
Description: 在指定进程中搜索对应模块的基址
Input: pid表示远程进程的ID,若为-1表示自身进程,ModuleName表示要搜索的模块的名称
Output: 无
Return: 返回0表示获取模块基址失败,返回非0为要搜索的模块基址
Others: 无
*************************************************/
void* GetModuleBaseAddr(pid_t pid, const char* ModuleName)
{
FILE *fp = NULL;
long ModuleBaseAddr = 0;
char *ModulePath, *MapFileLineItem;
char szFileName[50] = {0};
char szMapFileLine[1024] = {0};
char szProcessInfo[1024] = {0};
// 读取"/proc/pid/maps"可以获得该进程加载的模块
if (pid < 0) {
// 枚举自身进程模块
snprintf(szFileName, sizeof(szFileName), "/proc/self/maps");
} else {
snprintf(szFileName, sizeof(szFileName), "/proc/%d/maps", pid);
}
fp = fopen(szFileName, "r");
if (fp != NULL)
{
while (fgets(szMapFileLine, sizeof(szMapFileLine), fp)) {
if (strstr(szMapFileLine, ModuleName))
{
MapFileLineItem = strtok(szMapFileLine, " \t"); // 基址信息
char *Addr = strtok(szMapFileLine, "-");
ModuleBaseAddr = strtoul(Addr, NULL, 16 );
if (ModuleBaseAddr == 0x8000)
ModuleBaseAddr = 0;
break;
}
}
fclose(fp) ;
}
return (void *)ModuleBaseAddr;
}
/*************************************************
Description: 获取远程进程与本进程都加载的模块中函数的地址
Input: pid表示远程进程的ID,ModuleName表示模块名称,LocalFuncAddr表示本地进程中该函数的地址
Output: 无
Return: 返回远程进程中对应函数的地址
Others: 无
*************************************************/
void* GetRemoteFuncAddr(pid_t pid, const char *ModuleName, void *LocalFuncAddr)
{
void *LocalModuleAddr, *RemoteModuleAddr, *RemoteFuncAddr;
LocalModuleAddr = GetModuleBaseAddr(-1, ModuleName);
RemoteModuleAddr = GetModuleBaseAddr(pid, ModuleName);
RemoteFuncAddr = (void *)((long)LocalFuncAddr - (long)LocalModuleAddr + (long)RemoteModuleAddr);
return RemoteFuncAddr;
}
/*************************************************
Description: 通过远程直接调用dlopen\dlsym的方法ptrace注入so模块到远程进程中
Input: pid表示远程进程的ID,LibPath为被远程注入的so模块路径,FunctionName为远程注入的模块后调用的函数
FuncParameter指向被远程调用函数的参数(若传递字符串,需要先将字符串写入到远程进程空间中),NumParameter为参数的个数
Output: 无
Return: 返回0表示注入成功,返回-1表示失败
Others: 无
*************************************************/
int inject_remote_process(pid_t pid, char *LibPath, char *FunctionName, long *FuncParameter, long NumParameter)
{
int iRet = -1;
struct pt_regs CurrentRegs, OriginalRegs; // CurrentRegs表示远程进程中当前的寄存器值,OriginalRegs存储注入前的寄存器值,方便恢复
void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr; // 远程进程中需要调用函数的地址
void *RemoteMapMemoryAddr, *RemoteModuleAddr, *RemoteModuleFuncAddr; // RemoteMapMemoryAddr为远程进程空间中映射的内存基址,RemoteModuleAddr为远程注入的so模块加载基址,RemoteModuleFuncAddr为注入模块中需要调用的函数地址
long parameters[6];
// Attach远程进程
if (ptrace_attach(pid) == -1)
return iRet;
// 获取远程进程的寄存器值
if (ptrace_getregs(pid, &CurrentRegs) == -1)
{
ptrace_detach(pid);
return iRet;
}
LOGD("ARM_r0:0x%lx, ARM_r1:0x%lx, ARM_r2:0x%lx, ARM_r3:0x%lx, ARM_r4:0x%lx, ARM_r5:0x%lx, ARM_r6:0x%lx, ARM_r7:0x%lx, ARM_r8:0x%lx, ARM_r9:0x%lx, ARM_r10:0x%lx, ARM_ip:0x%lx, ARM_sp:0x%lx, ARM_lr:0x%lx, ARM_pc:0x%lx", \
CurrentRegs.ARM_r0, CurrentRegs.ARM_r1, CurrentRegs.ARM_r2, CurrentRegs.ARM_r3, CurrentRegs.ARM_r4, CurrentRegs.ARM_r5, CurrentRegs.ARM_r6, CurrentRegs.ARM_r7, CurrentRegs.ARM_r8, CurrentRegs.ARM_r9, CurrentRegs.ARM_r10, CurrentRegs.ARM_ip, CurrentRegs.ARM_sp, CurrentRegs.ARM_lr, CurrentRegs.ARM_pc);
// 保存远程进程空间中当前的上下文寄存器环境
memcpy(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs));
// 获取mmap函数在远程进程中的地址
mmap_addr = GetRemoteFuncAddr(pid, libc_path, (void *)mmap);
LOGD("mmap RemoteFuncAddr:0x%lx", (long)mmap_addr);
// 设置mmap的参数
// void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
parameters[0] = 0; // 设置为NULL表示让系统自动选择分配内存的地址
parameters[1] = 0x1000; // 映射内存的大小
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // 表示映射内存区域可读可写可执行
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // 建立匿名映射
parameters[4] = 0; // 若需要映射文件到内存中,则为文件的fd
parameters[5] = 0; //文件映射偏移量
// 调用远程进程的mmap函数,建立远程进程的内存映射
if (ptrace_call(pid, (long)mmap_addr, parameters, 6, &CurrentRegs) == -1)
{
LOGD("Call Remote mmap Func Failed");
ptrace_detach(pid);
return iRet;
}
// 获取mmap函数执行后的返回值,也就是内存映射的起始地址
RemoteMapMemoryAddr = (void *)ptrace_getret(&CurrentRegs);
LOGD("Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);
// 分别获取dlopen、dlsym、dlclose等函数的地址
dlopen_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlopen);
dlsym_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlsym);
dlclose_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlclose);
dlerror_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlerror);
LOGD("dlopen RemoteFuncAddr:0x%lx", (long)dlopen_addr);
LOGD("dlsym RemoteFuncAddr:0x%lx", (long)dlsym_addr);
LOGD("dlclose RemoteFuncAddr:0x%lx", (long)dlclose_addr);
LOGD("dlerror RemoteFuncAddr:0x%lx", (long)dlerror_addr);
// 将要加载的so库路径写入到远程进程内存空间中
if (ptrace_writedata(pid, RemoteMapMemoryAddr, LibPath, strlen(LibPath) + 1) == -1)
{
LOGD("Write LibPath:%s to RemoteProcess error", LibPath);
ptrace_detach(pid);
return iRet;
}
// 设置dlopen的参数,返回值为模块加载的地址
// void *dlopen(const char *filename, int flag);
parameters[0] = (long)RemoteMapMemoryAddr;
parameters[1] = RTLD_NOW| RTLD_GLOBAL;
if (ptrace_call(pid, (long)dlopen_addr, parameters, 2, &CurrentRegs) == -1)
{
LOGD("Call Remote dlopen Func Failed");
ptrace_detach(pid);
return iRet;
}
// RemoteModuleAddr为远程进程加载注入模块的地址
RemoteModuleAddr = (void *)ptrace_getret(&CurrentRegs);
LOGD("Remote Process load module Addr:0x%lx", (long)RemoteModuleAddr);
if ((long)RemoteModuleAddr == 0x0) // dlopen 错误
{
LOGD("dlopen error");
if (ptrace_call(pid, (long)dlerror_addr, parameters, 0, &CurrentRegs) == -1)
{
LOGD("Call Remote dlerror Func Failed");
ptrace_detach(pid);
return iRet;
}
char *Error = (void *)ptrace_getret(&CurrentRegs);
char LocalErrorInfo[1024] = {0};
ptrace_readdata(pid, Error, LocalErrorInfo, 1024);
LOGD("dlopen error:%s", LocalErrorInfo);
ptrace_detach(pid);
return iRet;
}
// 将so库中需要调用的函数名称写入到远程进程内存空间中
if (ptrace_writedata(pid, RemoteMapMemoryAddr + strlen(LibPath) + 2, FunctionName, strlen(FunctionName) + 1) == -1)
{
LOGD("Write FunctionName:%s to RemoteProcess error", FunctionName);
ptrace_detach(pid);
return iRet;
}
// 设置dlsym的参数,返回值为远程进程内函数的地址
// void *dlsym(void *handle, const char *symbol);
parameters[0] = (long)RemoteModuleAddr;
parameters[1] = (long)(RemoteMapMemoryAddr + strlen(LibPath) + 2);
if (ptrace_call(pid, (long)dlsym_addr, parameters, 2, &CurrentRegs) == -1)
{
LOGD("Call Remote dlsym Func Failed");
ptrace_detach(pid);
return iRet;
}
// RemoteModuleFuncAddr为远程进程空间内获取的函数地址
RemoteModuleFuncAddr = (void *)ptrace_getret(&CurrentRegs);
LOGD("Remote Process ModuleFunc Addr:0x%lx", (long)RemoteModuleFuncAddr);
if (ptrace_call(pid, (long)RemoteModuleFuncAddr, FuncParameter, NumParameter, &CurrentRegs) == -1)
{
LOGD("Call Remote injected Func Failed");
ptrace_detach(pid);
return iRet;
}
if (ptrace_setregs(pid, &OriginalRegs) == -1)
{
LOGD("Recover reges failed");
ptrace_detach(pid);
return iRet;
}
LOGD("Recover Regs Success");
ptrace_getregs(pid, &CurrentRegs);
if (memcmp(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs)) != 0)
{
LOGD("Set Regs Error");
}
//Detach
if (ptrace_detach(pid) == -1)
{
LOGD("ptrace detach failed");
return iRet;
}
return 0;
}
/*************************************************
Description: 通过shellcode方式ptrace注入so模块到远程进程中
Input: pid表示远程进程的ID,LibPath为被远程注入的so模块路径,FunctionName为远程注入的模块后调用的函数
FuncParameter指向被远程调用函数的参数(若传递字符串,需要先将字符串写入到远程进程空间中),NumParameter为参数的个数
Output: 无
Return: 返回0表示注入成功,返回-1表示失败
Others: 无
*************************************************/
int inject_remote_process_shellcode(pid_t pid, char *LibPath, char *FunctionName, long *FuncParameter, long NumParameter)
{
int iRet = -1;
struct pt_regs CurrentRegs, OriginalRegs; // CurrentRegs表示远程进程中当前的寄存器值,OriginalRegs存储注入前的寄存器值,方便恢复
void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr; // 远程进程中需要调用函数的地址
void *RemoteMapMemoryAddr, *RemoteModuleAddr, *RemoteModuleFuncAddr; // RemoteMapMemoryAddr为远程进程空间中映射的内存基址,RemoteModuleAddr为远程注入的so模块加载基址,RemoteModuleFuncAddr为注入模块中需要调用的函数地址
long parameters[10];
uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_start_ptr, *local_code_start_ptr, *local_code_end_ptr;
extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, \
_dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, \
_saved_cpsr_s, _saved_r0_pc_s;
uint32_t code_length;
// Attach远程进程
if (ptrace_attach(pid) == -1)
return iRet;
// 获取远程进程的寄存器值
if (ptrace_getregs(pid, &CurrentRegs) == -1)
{
ptrace_detach(pid);
return iRet;
}
LOGD("ARM_r0:0x%lx, ARM_r1:0x%lx, ARM_r2:0x%lx, ARM_r3:0x%lx, ARM_r4:0x%lx, ARM_r5:0x%lx, ARM_r6:0x%lx, ARM_r7:0x%lx, ARM_r8:0x%lx, ARM_r9:0x%lx, ARM_r10:0x%lx, ARM_ip:0x%lx, ARM_sp:0x%lx, ARM_lr:0x%lx, ARM_pc:0x%lx", \
CurrentRegs.ARM_r0, CurrentRegs.ARM_r1, CurrentRegs.ARM_r2, CurrentRegs.ARM_r3, CurrentRegs.ARM_r4, CurrentRegs.ARM_r5, CurrentRegs.ARM_r6, CurrentRegs.ARM_r7, CurrentRegs.ARM_r8, CurrentRegs.ARM_r9, CurrentRegs.ARM_r10, CurrentRegs.ARM_ip, CurrentRegs.ARM_sp, CurrentRegs.ARM_lr, CurrentRegs.ARM_pc);
// 保存远程进程空间中当前的上下文寄存器环境
memcpy(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs));
// 获取mmap函数在远程进程中的地址
mmap_addr = GetRemoteFuncAddr(pid, libc_path, (void *)mmap);
LOGD("mmap RemoteFuncAddr:0x%lx", (long)mmap_addr);
// 设置mmap的参数
// void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
parameters[0] = 0; // 设置为NULL表示让系统自动选择分配内存的地址
parameters[1] = 0x4000; // 映射内存的大小
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // 表示映射内存区域可读可写可执行
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // 建立匿名映射
parameters[4] = 0; // 若需要映射文件到内存中,则为文件的fd
parameters[5] = 0; //文件映射偏移量
// 调用远程进程的mmap函数,建立远程进程的内存映射
if (ptrace_call(pid, (long)mmap_addr, parameters, 6, &CurrentRegs) == -1)
{
LOGD("Call Remote mmap Func Failed");
ptrace_detach(pid);
return iRet;
}
// 获取mmap函数执行后的返回值,也就是内存映射的起始地址
RemoteMapMemoryAddr = (void *)ptrace_getret(&CurrentRegs);
LOGD("Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);
// 分别获取dlopen、dlsym、dlclose等函数的地址
dlopen_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlopen);
dlsym_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlsym);
dlclose_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlclose);
dlerror_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlerror);
LOGD("dlopen RemoteFuncAddr:0x%lx", (long)dlopen_addr);
LOGD("dlsym RemoteFuncAddr:0x%lx", (long)dlsym_addr);
LOGD("dlclose RemoteFuncAddr:0x%lx", (long)dlclose_addr);
LOGD("dlerror RemoteFuncAddr:0x%lx", (long)dlerror_addr);
remote_code_start_ptr = RemoteMapMemoryAddr + 0x3C00; // 远程进程中存放shellcode代码的起始地址
local_code_start_ptr = (uint8_t *)&_inject_start_s; // 本地进程中shellcode的起始地址
local_code_end_ptr = (uint8_t *)&_inject_end_s; // 本地进程中shellcode的结束地址
_dlopen_addr_s = (uint32_t)dlopen_addr;
_dlsym_addr_s = (uint32_t)dlsym_addr;
_dlclose_addr_s = (uint32_t)dlclose_addr;
LOGD("Inject Code Start:0x%x, end:0x%x", (int)local_code_start_ptr, (int)local_code_end_ptr);
// 计算shellcode中一些变量的存放起始地址
code_length = (uint32_t)&_inject_end_s - (uint32_t)&_inject_start_s;
dlopen_param1_ptr = local_code_start_ptr + code_length + 0x20;
dlsym_param2_ptr = dlopen_param1_ptr + MAX_PATH;
saved_r0_pc_ptr = dlsym_param2_ptr + MAX_PATH;
inject_param_ptr = saved_r0_pc_ptr + MAX_PATH;
// 写入dlopen的参数LibPath
strcpy( dlopen_param1_ptr, LibPath );
_dlopen_param1_s = REMOTE_ADDR( dlopen_param1_ptr, local_code_start_ptr, remote_code_start_ptr );
// 写入dlsym的第二个参数,需要调用的函数名称
strcpy( dlsym_param2_ptr, FunctionName );
_dlsym_param2_s = REMOTE_ADDR( dlsym_param2_ptr, local_code_start_ptr, remote_code_start_ptr );
//保存cpsr寄存器
_saved_cpsr_s = OriginalRegs.ARM_cpsr;
//保存r0-pc寄存器
memcpy( saved_r0_pc_ptr, &(OriginalRegs.ARM_r0), 16 * 4 ); // r0 ~ r15
_saved_r0_pc_s = REMOTE_ADDR( saved_r0_pc_ptr, local_code_start_ptr, remote_code_start_ptr );
memcpy( inject_param_ptr, FuncParameter, NumParameter );
_inject_function_param_s = REMOTE_ADDR( inject_param_ptr, local_code_start_ptr, remote_code_start_ptr );
ptrace_writedata( pid, remote_code_start_ptr, local_code_start_ptr, 0x400 );
memcpy( &CurrentRegs, &OriginalRegs, sizeof(CurrentRegs) );
CurrentRegs.ARM_sp = (long)remote_code_start_ptr;
CurrentRegs.ARM_pc = (long)remote_code_start_ptr;
ptrace_setregs( pid, &CurrentRegs );
ptrace_detach( pid );
return 0;
}
完整代码已经上传到附件