由于安卓采用的是修改后的linux内核,所以linux上的很多注入技术都可以用于安卓。ptrace远程注入技术便是一种。现在我们将实现对一款游戏进行注入。该例子是腾讯游戏安全实验室提供的,再此表示感谢!如有侵权的话,希望联系我。
Ptrace注入技术主要使用的是linux系统下的ptrace函数。关于如何深入学习Ptrace函数。大家可以参看我前面写的几篇文章:
1.系统调用理论基础:系统调用与api
2.Ptrace 的使用: Linux Ptrace 详解
3.Ptrace 源码的介绍: Linux源码分析之Ptrace
这里就默认大家会基本使用Linux 下的ptrace函数了。
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
request部分请求参数:
PTRACE_ATTACH,表示附加到指定远程进程;
PTRACE_DETACH,表示从指定远程进程分离
PTRACE_GETREGS,表示读取远程进程当前寄存器环境
PTRACE_SETREGS,表示设置远程进程的寄存器环境
PTRACE_CONT,表示使远程进程继续运行
PTRACE_PEEKTEXT,从远程进程指定内存地址读取一个word大小的数据
PTRACE_POKETEXT,往远程进程指定内存地址写入一个word大小的数据
Ptrace注入的目的是将自己的模块注入到目标进程中,让后让目标进程执行被注入模块的代码,对目进程的代码和数据修改。Ptrace 注入模块到目标进程有两种方法。第一种方法是使用ptrace将shellcode注入到远程进程的内容空间中,然后通过执行shellcode加载远程进程模块(不好意思,这种方法我目前不会,会的话分享给大家)第二种是直接远程调用dlopen、dlsym等函数加载注入模块并执行指令的代码。本文主要进行第二种方法的介绍。
下面是ptrace注入远程进程的流程图,借用的腾讯游戏安全实验室的图:
下面我们通过一个例子详细讲解Ptrace的注入过程。被注入模块的程序叫做<<超级玛丽快跑>>
我们知道Linux操作系统下的 /proc/* 目录 是一种伪文件系统(虚拟文件系统),它保存了内核的一些相关信息供我们以文件形式读取。其中进程的相关信息保存在/proc//*目录下。我们通过遍历/proc/目录中的每个目录项来找到指定进程名的PID,方法实现如下:
#include
#include
#include
#include
#define MAX_PATH 1024
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);//将数字文件名转换为int ,转换失败的话返回0;
if( ProcessDirID!=0){
snprintf(filename,MAX_PATH,"/proc/%d/cmdline",ProcessDirID);// 文件/proc//cmdline 为进程的启动命令行。安卓平台的为app包名;
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 InjectProcessName[MAX_PATH] = "com.android.settings";
pid_t pid = FindPidByProcessName(InjectProcessName);
printf(" pid is %d\n",(unsigned int)pid);
}
可看到执行成功会输出:
pid is 1403
// Attach远程进程
if (ptrace_attach(pid) == -1)
return iRet;//int iRet=-1;
ptrace_attach(pid)方法实现
/*************************************************
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;
}
其中options参数为WUNTRACTED时,表示如果对应pid的远程进程进入暂停状态,则函数马上返回,可用于等待远程进程进入暂停状态。
struct pt_regs CurrentRegs
// 获取远程进程的寄存器值
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);
先调用ptrace函数读取寄存器的值,然后将寄存器的值保存在寄存器结构 pt_regs中,ptrace_getregs(pid_t pid,struct pt_regs *regs)
方法才实现。
/*************************************************
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;
}
// 保存远程进程空间中当前的上下文寄存器环境
memcpy(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs));
在远程调用这些函数前,需要知道这些函数在远程进程中的地址,mmap函数在“/system/lib/lic.so”模块中
// 获取mmap函数在远程进程中的地址
mmap_addr = GetRemoteFuncAddr(pid, libc_path, (void *)mmap);
//LOGD("mmap RemoteFuncAddr:0x%lx", (long)mmap_addr);
获取远程进程与本进程都加载的模块中函数的地址,计算方法是远程模块函数地址=本进程函数的绝对地址-本进程模块加载地址+远程进程模块的加载地址
/*************************************************
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;
获取本进程中模块的加载地址,通过读取“/proc//maps”中的信息获得
/*************************************************
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;
}
// void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
long parameters[6];
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);
/*************************************************
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;
}
RemoteMapMemoryAddr = (void *)ptrace_getret(&CurrentRegs);
//LOGD("Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);
/*************************************************
Description: 获取返回值,ARM处理器中返回值存放在ARM_r0寄存器中
Input: regs存储远程进程当前的寄存器值
Output: 无
Return: 在ARM处理器下返回r0寄存器值
Others: 无
*************************************************/
long ptrace_getret(struct pt_regs * regs)
{
return regs->ARM_r0;
}