手游外挂基础篇之ptrace注入
字数 760 2025-08-22 18:37:22

ptrace注入技术详解

引言

ptrace注入是一种在Linux/Android系统中将外挂模块注入到目标进程的技术。本文将以手游2048破解为例,详细介绍如何通过ptrace系统调用实现so库注入,包括两种主要方法:远程调用dlopen/dlsym和shellcode注入。

技术概述

ptrace注入的核心是利用ptrace系统调用的功能:

  1. 通过shellcode注入模块到远程进程
  2. 利用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*)&parameters[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

注入过程

  1. 将编译好的libInjectModule.so放入/data目录
  2. 给注入工具执行权限:chmod 777 inject
  3. 运行注入工具
  4. 查看日志确认注入是否成功

关键点总结

  1. 远程调用原理:通过让远程进程调用dlopen打开我们的so文件实现注入,再通过dlsym获取函数地址并调用

  2. 参数传递:所有字符串参数(so路径、函数名等)必须先写入远程进程内存空间,远程进程不能直接访问注入工具的内存

  3. 寄存器控制:通过ptrace修改寄存器实现远程调用:

    • pc寄存器决定执行哪条指令
    • r0-r3和栈用于传递参数
  4. 内存管理:需要先在远程进程分配可执行内存(mmap),用于存储参数和可能的shellcode

  5. 错误处理:检查每一步ptrace操作的返回值,特别是内存读写和函数调用

  6. 环境兼容:注意ARM和Thumb指令集区别,正确处理CPSR寄存器的T位

参考

  • 《游戏安全-手游安全技术入门》
ptrace注入技术详解 引言 ptrace注入是一种在Linux/Android系统中将外挂模块注入到目标进程的技术。本文将以手游2048破解为例,详细介绍如何通过ptrace系统调用实现so库注入,包括两种主要方法:远程调用dlopen/dlsym和shellcode注入。 技术概述 ptrace注入的核心是利用ptrace系统调用的功能: 通过shellcode注入模块到远程进程 利用ptrace远程调用dlopen/dlsym将动态链接库注入到远程进程并执行相应操作 核心代码实现 头文件定义 调试日志工具 ptrace基础操作函数 内存读写操作 远程函数调用 模块和函数地址获取 核心注入函数 注入工具入口 被注入模块示例 编译过程 注入工具编译配置 被注入模块编译配置 注入过程 将编译好的 libInjectModule.so 放入 /data 目录 给注入工具执行权限: chmod 777 inject 运行注入工具 查看日志确认注入是否成功 关键点总结 远程调用原理 :通过让远程进程调用 dlopen 打开我们的so文件实现注入,再通过 dlsym 获取函数地址并调用 参数传递 :所有字符串参数(so路径、函数名等)必须先写入远程进程内存空间,远程进程不能直接访问注入工具的内存 寄存器控制 :通过 ptrace 修改寄存器实现远程调用: pc 寄存器决定执行哪条指令 r0-r3 和栈用于传递参数 内存管理 :需要先在远程进程分配可执行内存( mmap ),用于存储参数和可能的shellcode 错误处理 :检查每一步 ptrace 操作的返回值,特别是内存读写和函数调用 环境兼容 :注意ARM和Thumb指令集区别,正确处理CPSR寄存器的T位 参考 《游戏安全-手游安全技术入门》