手游外挂基础篇之ptrace注入
字数 760 2025-08-22 18:37:22
ptrace注入技术详解
引言
ptrace注入是一种在Linux/Android系统中将外挂模块注入到目标进程的技术。本文将以手游2048破解为例,详细介绍如何通过ptrace系统调用实现so库注入,包括两种主要方法:远程调用dlopen/dlsym和shellcode注入。
技术概述
ptrace注入的核心是利用ptrace系统调用的功能:
- 通过shellcode注入模块到远程进程
- 利用ptrace远程调用dlopen/dlsym将动态链接库注入到远程进程并执行相应操作
核心代码实现
头文件定义
// ptraceInject.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* 功能1:通过ptrace远程调用dlopen/dlsym方式注入模块到远程进程 */
int inject_remote_process(pid_t pid, char *LibPath, char *FunctionName,
long *FuncParameter, long NumParameter);
/* 功能2:通过shellcode方式注入模块到远程进程*/
int inject_remote_process_shellcode(pid_t pid, char *LibPath,
char *FunctionName, long *FuncParameter,
long NumParameter);
调试日志工具
// PrintLog.h
#ifndef _ANDROID_LOG_PRINT_H_
#define _ANDROID_LOG_PRINT_H_
#define MAX_PATH 0x100
#include <android/log.h>
// 定义调试宏
#ifdef IS_DEBUG
#define LOG_TAG ("INJECT")
#define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#else
#define LOGV(LOG_TAG, ...) NULL
#define LOGD(LOG_TAG, ...) NULL
#define LOGI(LOG_TAG, ...) NULL
#define LOGW(LOG_TAG, ...) NULL
#define LOGE(LOG_TAG, ...) NULL
#endif
#endif
ptrace基础操作函数
// ptraceInject.c
// 附加到进程
int ptrace_attach(pid_t pid) {
int status = 0;
if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
LOGD("ptrace attach error, pid:%d", pid);
return -1;
}
LOGD("attach process pid:%d", pid);
waitpid(pid, &status, WUNTRACED);
return 0;
}
// 解除附加
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;
}
// 继续运行进程
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;
}
// 获取寄存器值
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;
}
// 设置寄存器值
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;
}
// 获取返回值(ARM架构)
long ptrace_getret(struct pt_regs *regs) {
return regs->ARM_r0;
}
// 获取PC值(ARM架构)
long ptrace_getpc(struct pt_regs *regs) {
return regs->ARM_pc;
}
内存读写操作
// 读取远程进程内存
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;
}
// 写入远程进程内存
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);
for(i = 0; i < nWriteCount; i++) {
memcpy((void*)(&lTmpBuf), pCurSrcBuf, sizeof(long));
if(ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0) {
LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);
return -1;
}
pCurSrcBuf += sizeof(long);
pCurDestBuf += sizeof(long);
}
if(nRemainCount > 0) {
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;
}
远程函数调用
// 远程调用函数
int ptrace_call(pid_t pid, uint32_t ExecuteAddr, long *parameters,
long num_params, struct pt_regs *regs) {
int i = 0;
// ARM处理器参数传递规则
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/Thumb)
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;
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;
}
模块和函数地址获取
// 获取模块基址
void *GetModuleBaseAddr(pid_t pid, const char *ModuleName) {
char szFileName[50] = {0};
FILE *fp = NULL;
char szMapFileLine[1024] = {0};
char *ModulePath, *MapFileLineItem;
long ModuleBaseAddr = 0;
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 *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;
}
核心注入函数
// 通过远程调用dlopen/dlsym注入模块
int inject_remote_process(pid_t pid, char *LibPath, char *FunctionName,
long *FuncParameter, long NumParameter) {
int iRet = -1;
struct pt_regs CurrentRegs, OriginalRegs;
void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;
void *RemoteMapMemoryAddr, *RemoteModuleAddr, *RemoteModuleFuncAddr;
long parameters[6];
// 1. 附加到远程进程
if(ptrace_attach(pid) == -1) {
return iRet;
}
// 2. 获取并保存寄存器值
if(ptrace_getregs(pid, &CurrentRegs) == -1) {
ptrace_detach(pid);
return iRet;
}
memcpy(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs));
// 3. 在远程进程分配内存空间
mmap_addr = GetRemoteFuncAddr(pid, libc_path, (void*)mmap);
parameters[0] = 0; // 让系统选择地址
parameters[1] = 0x1000; // 1页大小
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // 可读可写可执行
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // 匿名映射
parameters[4] = 0; // 文件描述符
parameters[5] = 0; // 偏移量
if(ptrace_call(pid, (long)mmap_addr, parameters, 6, &CurrentRegs) == -1) {
LOGD("Call Remote mmap Func Failed");
ptrace_detach(pid);
return iRet;
}
RemoteMapMemoryAddr = (void*)ptrace_getret(&CurrentRegs);
// 4. 写入so库路径并调用dlopen
if(ptrace_writedata(pid, RemoteMapMemoryAddr, LibPath, strlen(LibPath)+1) == -1) {
LOGD("Write LibPath:%s to RemoteProcess error", LibPath);
ptrace_detach(pid);
return iRet;
}
parameters[0] = (long)RemoteMapMemoryAddr;
parameters[1] = RTLD_NOW | RTLD_GLOBAL;
dlopen_addr = GetRemoteFuncAddr(pid, linker_path, (void*)dlopen);
dlerror_addr = GetRemoteFuncAddr(pid, linker_path, (void*)dlerror);
dlclose_addr = GetRemoteFuncAddr(pid, linker_path, (void*)dlclose);
if(ptrace_call(pid, (long)dlopen_addr, parameters, 2, &CurrentRegs) == -1) {
LOGD("Call Remote dlopen Func Failed");
ptrace_detach(pid);
return iRet;
}
RemoteModuleAddr = (void*)ptrace_getret(&CurrentRegs);
if((long)RemoteModuleAddr == 0x0) {
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;
}
// 5. 写入函数名并调用dlsym
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;
}
parameters[0] = (long)RemoteModuleAddr;
parameters[1] = (long)(RemoteMapMemoryAddr+strlen(LibPath)+2);
dlsym_addr = GetRemoteFuncAddr(pid, linker_path, (void*)dlsym);
if(ptrace_call(pid, (long)dlsym_addr, parameters, 2, &CurrentRegs) == -1) {
LOGD("Call Remote dlsym Func Failed");
ptrace_detach(pid);
return iRet;
}
RemoteModuleFuncAddr = (void*)ptrace_getret(&CurrentRegs);
// 6. 调用注入的函数
if(ptrace_call(pid, (long)RemoteModuleFuncAddr, FuncParameter,
NumParameter, &CurrentRegs) == -1) {
LOGD("Call Remote injected Func Failed");
ptrace_detach(pid);
return iRet;
}
// 7. 恢复原始寄存器并解除附加
if(ptrace_setregs(pid, &OriginalRegs) == -1) {
LOGD("Recover reges failed");
ptrace_detach(pid);
return iRet;
}
if(ptrace_detach(pid) == -1) {
LOGD("ptrace detach failed");
return iRet;
}
return 0;
}
注入工具入口
// InjectModule.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/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 <PrintLog.h>
// 通过进程名查找PID
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.testjni"; // 目标进程名
// 环境检查
#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 -1;
}
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);
if(iRet == 0) {
printf("Inject Success\n");
} else {
printf("Inject Failed\n");
}
printf("end inject,%d\n", pid);
return 0;
}
被注入模块示例
// 被注入的so模块代码
#include <stdio.h>
#include <stdlib.h>
#include <PrintLog.h>
int Inject_entry() {
LOGD("Inject_entry Func is called\n");
return 0;
}
编译过程
注入工具编译配置
# Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := inject
LOCAL_SRC_FILES := ptraceInject.c InjectModule.c
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_EXECUTABLE)
# Application.mk
APP_ABI := armeabi-v7a
被注入模块编译配置
# Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := InjectModule
LOCAL_SRC_FILES := InjectModule.c
LOCAL_ARM_MODE := arm
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
# Application.mk
APP_ABI := armeabi-v7a
注入过程
- 将编译好的
libInjectModule.so放入/data目录 - 给注入工具执行权限:
chmod 777 inject - 运行注入工具
- 查看日志确认注入是否成功
关键点总结
-
远程调用原理:通过让远程进程调用
dlopen打开我们的so文件实现注入,再通过dlsym获取函数地址并调用 -
参数传递:所有字符串参数(so路径、函数名等)必须先写入远程进程内存空间,远程进程不能直接访问注入工具的内存
-
寄存器控制:通过
ptrace修改寄存器实现远程调用:pc寄存器决定执行哪条指令r0-r3和栈用于传递参数
-
内存管理:需要先在远程进程分配可执行内存(
mmap),用于存储参数和可能的shellcode -
错误处理:检查每一步
ptrace操作的返回值,特别是内存读写和函数调用 -
环境兼容:注意ARM和Thumb指令集区别,正确处理CPSR寄存器的T位
参考
- 《游戏安全-手游安全技术入门》