首页
社区
课程
招聘
[原创]基础so注入的实现
发表于: 2025-7-4 18:36 4876

[原创]基础so注入的实现

2025-7-4 18:36
4876

前言:由于安卓的进程隔离机制,我们在hook或操作其他进程时,往往需要先把so注入到目标进程

一句话概括下来就是 把自己的so加载到目标进程的地址空间

据我所知,目前有两种:

先来聊聊第一点吧,我们知道安卓中所有应用进程都forkzygote进程,所以直接把so注入zygote进程,app在启动时会fork zygote,从而达到注入的目。那zygisk是如何注入进zygote进程的呢,其实也是通过ptrace哈哈哈。所以写zygisk模块可以轻松帮我们注入自己的so。同理,用xposed插件也可以,而且更简单。这里不细说,我们主要聊一聊第二种方式

既然要使用ptrace,那么我们就得先知道ptrace是什么东西

Ptrace是Linux提供的进程调试接口,可以让一个进程去控制另一个进程。通过ptrace,我们能附加到目标进程上,读写它的内存和寄存器。有了这些能力,远程调用目标进程的函数就成为可能了。
主要用到的ptrace操作包括:

PTRACE_ATTACH:附加目标进程
PTRACE_GETREGSET/SETREGSET:读写寄存器
PTRACE_PEEKDATA/POKEDATA:读写内存

知道了ptrace是干什么的,现在就能来注入so了。既然要把自己的so塞到目标进程里,那么直接远程调用对应进程的dlopen加载我们自己的so不就好了吗~。这就是我们的终极目标了

原理就是通过设置pc(程序计数器)指向目标函数的地址。比如我们要远程调用dlopen,就得先拿到dlopen的地址,然后将参数写入对应寄存器,再修改pc指针指向dlopen的地址即可

我们知道,内存中的函数地址是由基地址(函数所在的so的起始地址)+偏移地址(函数相对于so的偏移)确定的,而同一个so的函数在不同进程里和在磁盘中的偏移是一样的。所以我们有两种办法拿到函数的真实地址(准确来说是相对于so的偏移地址)

获取基地址就没啥好说的了,用户层直接读/proc/{pid}/maps就行了

值得注意的是,我们在使用dlopen的时候需要传入so的路径,这个值是一个字符串,更准确的来讲,dlopen接收到的字符串的首地址。所以我们需要远程把so的路径写入到目标进程中。我们可以借助ptrace的PTRACE_POKEDATA实现写入,在此之前,我们得获取一块稳定已知的可写内存,所以还需要远程调用一次mmap,远程调用函数的逻辑和上面一样,直接用就行

这样主要的逻辑就全都实现了,剩下的就是处理selinux相关代码,通过修改/sys/fs/selinux/enforce文件的值实现enforce和permissive模式的切换

这只是实现了最简单最基础的attach模式so注入,会留下痕迹,虽然痕迹不多,但是都比较致命,基本上有检测的app注入进去就会挂掉。spawn模式下一次再介绍吧,至于隐藏痕迹,我感觉用户层也没啥好隐藏的,Memory Remapping依然会带来新的检测点,处理solist和maps里注入so的路径完全可以通过自定义linker来加载规避掉,但这会带来较差的兼容性和比较大的工程量。最简单的就是进到内核层操作seq_file来处理maps里的信息,但是我感觉只要路径正常,so名称随机,maps和solist不处理也没啥问题,但是dlopen只能加载那几个路径下的so哈哈哈哈

bool ProcessUtils::SetupRemoteCall(RemoteCallContext* ctx, uint64_t func_addr,
                                   const std::vector<uint64_t>& args) {
    // 复制原始寄存器
    ctx->regs = ctx->orig_regs;
 
    // 设置栈指针 - 确保16字节对齐
    ctx->regs.sp = (ctx->orig_regs.sp - 0x100) & ~0xF;
 
    // 设置参数(ARM64前8个参数通过x0-x7传递)
    for (size_t i = 0; i < args.size() && i < 8; i++) {
        ctx->regs.regs[i] = args[i];
    }
 
    // 如果参数超过8个,需要压栈
    if (args.size() > 8) {
        MemoryUtils memory_utils;
        uint64_t stack_addr = ctx->regs.sp;
        for (size_t i = 8; i < args.size(); i++) {
            if (!memory_utils.WriteProcessMemory(ctx->pid, stack_addr, &args[i], sizeof(uint64_t))) {
                LOGE("Failed to write stack argument %zu", i);
                return false;
            }
            stack_addr += sizeof(uint64_t);
        }
    }
 
    // 设置PC指向目标函数
    ctx->regs.pc = func_addr;
 
    // 设置返回地址为0,这样函数返回时会触发SIGSEGV
    ctx->regs.regs[30] = 0;  // x30是链接寄存器(LR)
 
    LOGD("Setting up remote call: PC=0x%lx, SP=0x%lx", ctx->regs.pc, ctx->regs.sp);
 
    return SetRegisters(ctx->pid, &ctx->regs);
}
bool ProcessUtils::SetupRemoteCall(RemoteCallContext* ctx, uint64_t func_addr,
                                   const std::vector<uint64_t>& args) {
    // 复制原始寄存器
    ctx->regs = ctx->orig_regs;
 
    // 设置栈指针 - 确保16字节对齐
    ctx->regs.sp = (ctx->orig_regs.sp - 0x100) & ~0xF;
 
    // 设置参数(ARM64前8个参数通过x0-x7传递)
    for (size_t i = 0; i < args.size() && i < 8; i++) {
        ctx->regs.regs[i] = args[i];
    }
 
    // 如果参数超过8个,需要压栈
    if (args.size() > 8) {
        MemoryUtils memory_utils;
        uint64_t stack_addr = ctx->regs.sp;
        for (size_t i = 8; i < args.size(); i++) {
            if (!memory_utils.WriteProcessMemory(ctx->pid, stack_addr, &args[i], sizeof(uint64_t))) {
                LOGE("Failed to write stack argument %zu", i);
                return false;
            }
            stack_addr += sizeof(uint64_t);
        }
    }
 
    // 设置PC指向目标函数
    ctx->regs.pc = func_addr;
 
    // 设置返回地址为0,这样函数返回时会触发SIGSEGV
    ctx->regs.regs[30] = 0;  // x30是链接寄存器(LR)
 
    LOGD("Setting up remote call: PC=0x%lx, SP=0x%lx", ctx->regs.pc, ctx->regs.sp);
 
    return SetRegisters(ctx->pid, &ctx->regs);
}
uint64_t ProcessUtils::GetModuleBase(pid_t pid, const std::string& module_name) {
        char maps_path[256];
        snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);
 
        LOGD("GetModuleBase: pid=%d, module=%s", pid, module_name.c_str());
 
        std::ifstream maps(maps_path);
        if (!maps.is_open()) {
            LOGE("Failed to open %s: %s", maps_path, strerror(errno));
            return 0;
        }
 
        std::string line;
        bool found = false;
        while (std::getline(maps, line)) {
            if (line.find(module_name) != std::string::npos &&
                line.find(" r-xp ") != std::string::npos) {  // 只查找可执行段
                // 解析基址
                uint64_t base;
                if (sscanf(line.c_str(), "%lx", &base) == 1) {
                    maps.close();
                    LOGD("  Found module %s at base 0x%lx", module_name.c_str(), base);
                    LOGD("  Map line: %s", line.c_str());
                    return base;
                }
            }
        }
 
        maps.close();
        LOGD("  Module %s not found in process %d", module_name.c_str(), pid);
        return 0;
    }
uint64_t ProcessUtils::GetModuleBase(pid_t pid, const std::string& module_name) {
        char maps_path[256];
        snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);
 
        LOGD("GetModuleBase: pid=%d, module=%s", pid, module_name.c_str());
 
        std::ifstream maps(maps_path);
        if (!maps.is_open()) {
            LOGE("Failed to open %s: %s", maps_path, strerror(errno));
            return 0;
        }
 
        std::string line;
        bool found = false;
        while (std::getline(maps, line)) {
            if (line.find(module_name) != std::string::npos &&
                line.find(" r-xp ") != std::string::npos) {  // 只查找可执行段
                // 解析基址
                uint64_t base;
                if (sscanf(line.c_str(), "%lx", &base) == 1) {
                    maps.close();
                    LOGD("  Found module %s at base 0x%lx", module_name.c_str(), base);
                    LOGD("  Map line: %s", line.c_str());
                    return base;
                }
            }
        }
 
        maps.close();
        LOGD("  Module %s not found in process %d", module_name.c_str(), pid);
        return 0;
    }
uint64_t Injector::GetRemoteAddress(pid_t pid, const std::string& module_name,
                                        const std::string& func_name) {
        LOGD("GetRemoteAddress: module=%s, function=%s", module_name.c_str(), func_name.c_str());
 
        // 获取目标进程中的模块基址
        uint64_t remote_base = process_utils_.GetModuleBase(pid, module_name);
        if (remote_base == 0) {
            LOGD("  Module %s not found in process %d", module_name.c_str(), pid);
            return 0;
        }
 
        // 获取本地进程中的模块基址
        uint64_t local_base = process_utils_.GetModuleBase(getpid(), module_name);
        if (local_base == 0) {
            LOGD("  Module %s not found in local process", module_name.c_str());
 
            // 对于loader模块,尝试动态加载
            if (module_name.find("yuuki_transit") != std::string::npos ||
                module_name == LOADER_PATH) {
                LOGD("  Trying to load loader module locally to get function offset");
 
                void* handle = dlopen(LOADER_PATH, RTLD_NOW | RTLD_LOCAL);
                if (handle) {
                    void* func = dlsym(handle, func_name.c_str());
                    if (func) {
                        // 再次获取本地基址
                        local_base = process_utils_.GetModuleBase(getpid(), "yuuki_transit.so");
                        if (local_base == 0) {
                            local_base = process_utils_.GetModuleBase(getpid(), LOADER_PATH);
                        }
 
                        if (local_base != 0) {
                            uint64_t offset = (uint64_t)func - local_base;
                            uint64_t remote_addr = remote_base + offset;
                            dlclose(handle);
                            LOGD("  Found %s at offset 0x%lx, remote addr: 0x%lx",
                                 func_name.c_str(), offset, remote_addr);
                            return remote_addr;
                        }
                    }
                    dlclose(handle);
                }
            }
 
            return 0;
        }
uint64_t Injector::GetRemoteAddress(pid_t pid, const std::string& module_name,
                                        const std::string& func_name) {
        LOGD("GetRemoteAddress: module=%s, function=%s", module_name.c_str(), func_name.c_str());
 
        // 获取目标进程中的模块基址
        uint64_t remote_base = process_utils_.GetModuleBase(pid, module_name);
        if (remote_base == 0) {
            LOGD("  Module %s not found in process %d", module_name.c_str(), pid);
            return 0;
        }
 
        // 获取本地进程中的模块基址
        uint64_t local_base = process_utils_.GetModuleBase(getpid(), module_name);
        if (local_base == 0) {
            LOGD("  Module %s not found in local process", module_name.c_str());
 
            // 对于loader模块,尝试动态加载
            if (module_name.find("yuuki_transit") != std::string::npos ||
                module_name == LOADER_PATH) {
                LOGD("  Trying to load loader module locally to get function offset");
 
                void* handle = dlopen(LOADER_PATH, RTLD_NOW | RTLD_LOCAL);
                if (handle) {
                    void* func = dlsym(handle, func_name.c_str());
                    if (func) {
                        // 再次获取本地基址
                        local_base = process_utils_.GetModuleBase(getpid(), "yuuki_transit.so");
                        if (local_base == 0) {
                            local_base = process_utils_.GetModuleBase(getpid(), LOADER_PATH);
                        }
 
                        if (local_base != 0) {
                            uint64_t offset = (uint64_t)func - local_base;
                            uint64_t remote_addr = remote_base + offset;
                            dlclose(handle);
                            LOGD("  Found %s at offset 0x%lx, remote addr: 0x%lx",
                                 func_name.c_str(), offset, remote_addr);
                            return remote_addr;

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 8
支持
分享
最新回复 (6)
雪    币: 156
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
大佬可以出一期面具模块编写注入so吗
2025-7-5 05:50
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2025-7-5 08:49
0
雪    币: 562
活跃值: (1370)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
mb_cfjwplfo 大佬可以出一期面具模块编写注入so吗
github有很多仓库都有
2025-7-5 11:10
0
雪    币: 1907
活跃值: (1514)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
mb_cfjwplfo 大佬可以出一期面具模块编写注入so吗
可以的呀
2025-7-5 19:13
0
雪    币: 209
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
mb_cfjwplfo 大佬可以出一期面具模块编写注入so吗
看追的文章
2025-7-5 19:46
0
雪    币: 293
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
666666
2025-7-6 15:25
0
游客
登录 | 注册 方可回帖
返回