首页
社区
课程
招聘
[原创] spawn模式注入so的实现
发表于: 2025-7-11 17:01 6895

[原创] spawn模式注入so的实现

2025-7-11 17:01
6895

前言:在上一篇文章中大概解释了attach模式注入so的实现思路,但是实战中spawn模式注入也是很常用的

我的理解是 在目标进程启动时对其进行注入。这可以帮助我们绕过部分反调试,同时可以监听一些初始化函数

spawn翻译过来是产卵的意思,刚接触frida的时候我不理解spawn这个单词和注入这个操作之间的联系,后来才知道这个操作是需要注入zygote的,而zygote翻译过来刚好是受精卵的意思,现在感觉还挺有意思的,spawn这个名字确实很恰当

我们想要在进程启动时就注入,那么时机很重要,这个时机当然越早越好...吗?我们知道app进程都是由zygote进程fork而来的,fork之后才会有app进程,这里差不多就是app进程刚出生的时候了,我们在这时,把自己的so注入就可以实现上述的效果了

由于我们要hook zygote进程里的一些函数,所以首先要把hook模块注入进zygote进程,关于如何通过ptrace注入目标进程,上一篇文章已经讲的比较清晰了,这里就不过多赘述了,这里先来讲讲hook模块应该实现哪些功能吧。这里的hook框架依旧是使用dobby

首先要明确hook点

上面说了,我们是hook fork函数,所以目标so是/system/lib64/libc.so,查找函数地址可以先dlopen libc.so,然后再dlsym fork函数,这里dlopen是为了拿到对应so的handle,正常进程里libc.so 99%是已经被加载过的,你再打开一次也无所谓,因为linker会直接返回solist里的handle给你,不会浪费很多时间。当然也可以使用elf_utils解析磁盘上的libc.so拿到偏移,然后查找基地址两个相加得到真实的地址

其次编写hook函数

我刚开始是这么写的,我觉得没啥毛病,但是一运行就有问题了,各位可以先思考一下为什么这样不行哈哈哈哈

运行时输出了如下日志:

子进程的进程名依然是zygote64,为什么会这样呢???其实是因为获取进程名的时机太早了,这时子进程还没初始化完全,所以获取到的名字还是父进程的

为了解决这个问题,我们就得先研究一下android中app进程的进程名是如何被设置的,查阅了一番资料后得知android.os.Process.setArgv0函数负责设置进程名,它是一个native函数,长这样

它的第三个参数就是我们需要的进程名,所以我们可以在子进程中hook这个函数,当函数执行时取出参数,与我们的目标进程名作比较,如果一致,则加载我们自己的so。加载so使用dlopen就行了,但是dlopen只能从那几个路径加载,虽然本文不涉及注入痕迹的隐藏,但简单的能直接避免还是就避免一下吧。如果从app私有目录加载,那么app自己是有权限检查该路径下的文件的,不是很安全。如果从/system/lib下加载,那每次注入不同的so都需要重启手机重新挂载文件,这太麻烦了。所以还是从/data/local/tmp下加载吧,当然这个路径也不安全,但相对好一点。但如果选择这个路径,就会有新的问题。普通app进程是没办法访问这个目录的,所以dlopen肯定会失败。解决方案也比较简单,把selinux设置为宽容模式即可。那么新的问题又来了,hook代码是运行在app进程的,app进程没有权限设置selinux,这里只有Injector进程有这个权限,所以需要设计跨进程通信模块,用于在dlopen前后通知Injector模块修改selinux模式

设计通信模块

为了方便,本来想直接用信号的,因为这个通信场景本身也不复杂,但是后面写完才发现权限不够,于是又重写,这里直接用UDS。为了后续开发方便,就把模块单拎出来写了,另外两个模块共享通信模块

这样就解决权限的问题了:) 是不是就可以加载so了呢,直接dlopen(SO_PATH, FLAG),那么SO_PATH从哪来?当然完善上面的跨进程通信模块,也能把path传过来,但是有点麻烦了,这里我的解决方案是在hook模块里导出一个函数用于被Injector模块远程调用,参数里传递一些必要的信息,比如SO_PATH

总的代码就是这样子的

自己写时记得及时unhook,不然有的软件会进不去

在学校时,用完frida之后去食堂吃饭,付钱的软件老是打不开,当时不明白为什么我明明调试的是别的app,这个app凭什么打不开,其实是用完之后zygote进程里的一些函数没有被unhook,app启动会fork zygote进程,所以就被检测到了:(

再来聊聊检测和过检测吧,就说我写的这个工具吧,如果不开源,可能就是maps里有可疑路径的so,soinfo list里也会有,/proc/self/fd/ 中的文件描述符,还有就是通信时的痕迹,别的地方感觉也没啥了(注入留下的痕迹,hook的不归我管)。前面两个,目前用户层有的解决方案就是:

但这样还是解决不了问题的,因为正常app基本没用可执行的匿名内存段,但是一些加固之后的似乎有,我之前看36O是有的

solist里的痕迹就是通过找到solist_remove_soinfo函数的地址,然后调用它,移除目标so的soinfo,当然这里还涉及获取目标so的soinfo,遍历solist等操作,不赘述了

使用自定义linker的方案可以在so的路径上多一些选择,而且不会在solist留下痕迹,但也并非一点痕迹没有的:( 所以还是配合内核模块一起用吧

static std::string getCurrentProcessName() {
    char process_name[64] = {0};
    int fd = open("/proc/self/cmdline", O_RDONLY);
    if (fd != -1) {
        ssize_t len = read(fd, process_name, sizeof(process_name) - 1);
        if (len > 0) {
            process_name[len] = '\0';
        }
        close(fd);
    }
    return std::string(process_name);
}
 
static pid_t hooked_fork() {
    if (in_hook_fork) {
        return orig_fork();
    }
 
    LOGD("enter fork func!");
 
    pid_t result = orig_fork();
 
    if (result == 0) {
        LOGD("enter child process");
        std::string current_process = getCurrentProcessName();
        if (!current_process.empty()) {
            LOGD("Child process started: %s", current_process.c_str());
            //todo 然后判断进程名是否为目标进程,后续再dlopen自己的so
        }
    }
 
    return result;
}
static std::string getCurrentProcessName() {
    char process_name[64] = {0};
    int fd = open("/proc/self/cmdline", O_RDONLY);
    if (fd != -1) {
        ssize_t len = read(fd, process_name, sizeof(process_name) - 1);
        if (len > 0) {
            process_name[len] = '\0';
        }
        close(fd);
    }
    return std::string(process_name);
}
 
static pid_t hooked_fork() {
    if (in_hook_fork) {
        return orig_fork();
    }
 
    LOGD("enter fork func!");
 
    pid_t result = orig_fork();
 
    if (result == 0) {
        LOGD("enter child process");
        std::string current_process = getCurrentProcessName();
        if (!current_process.empty()) {
            LOGD("Child process started: %s", current_process.c_str());
            //todo 然后判断进程名是否为目标进程,后续再dlopen自己的so
        }
    }
 
    return result;
}
07-09 10:52:08.046 19197 19197 I [Zygote_Hook]: enter child process
07-09 10:52:08.047 19197 19197 I [Zygote_Hook]: Child process started: zygote64
07-09 10:52:08.046 19197 19197 I [Zygote_Hook]: enter child process
07-09 10:52:08.047 19197 19197 I [Zygote_Hook]: Child process started: zygote64
void android_os_Process_setArgV0(JNIEnv* env, jobject clazz, jstring name)
{
    if (name == NULL) {
        jniThrowNullPointerException(env, NULL);
        return;
    }
 
    const jchar* str = env->GetStringCritical(name, 0);
    String8 name8;
    if (str) {
        name8 = String8(reinterpret_cast<const char16_t*>(str),
                        env->GetStringLength(name));
        env->ReleaseStringCritical(name, str);
    }
 
    if (!name8.empty()) {
        AndroidRuntime::getRuntime()->setArgv0(name8.c_str(), true /* setProcName */);
    }
}
void android_os_Process_setArgV0(JNIEnv* env, jobject clazz, jstring name)
{
    if (name == NULL) {
        jniThrowNullPointerException(env, NULL);
        return;
    }
 
    const jchar* str = env->GetStringCritical(name, 0);
    String8 name8;
    if (str) {
        name8 = String8(reinterpret_cast<const char16_t*>(str),
                        env->GetStringLength(name));
        env->ReleaseStringCritical(name, str);
    }
 
    if (!name8.empty()) {
        AndroidRuntime::getRuntime()->setArgv0(name8.c_str(), true /* setProcName */);
    }
}
#include "socket_comm.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <thread>
#include <android/log.h>
#include <sys/stat.h>
#include <errno.h>
 
#define LOG_TAG "[Socket_Comm]"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
 
namespace comm {
 
    SocketServer::SocketServer() : server_fd_(-1), running_(false), background_thread_(nullptr) {
    }
 
    SocketServer::~SocketServer() {
        Stop();
    }
 
    bool SocketServer::Start() {
        if (server_fd_ != -1) {
            LOGE("Server already started");
            return false;
        }
 
        unlink(COMM_SOCKET_PATH);
 
        // 创建Unix Domain Socket
        server_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
        if (server_fd_ < 0) {
            LOGE("Failed to create socket: %s", strerror(errno));
            return false;
        }
 
        struct sockaddr_un server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sun_family = AF_UNIX;
        strncpy(server_addr.sun_path, COMM_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
 
        if (bind(server_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            LOGE("Failed to bind socket: %s", strerror(errno));
            close(server_fd_);
            server_fd_ = -1;
            return false;
        }
 
        // 设置socket文件权限,允许所有用户访问
        chmod(COMM_SOCKET_PATH, 0666);
 
        if (listen(server_fd_, 1) < 0) {
            LOGE("Failed to listen on socket: %s", strerror(errno));
            close(server_fd_);
            server_fd_ = -1;
            unlink(COMM_SOCKET_PATH);
            return false;
        }
 
        running_ = true;
        LOGI("Unix Domain Socket server started at %s", COMM_SOCKET_PATH);
        return true;
    }
 
    void SocketServer::Stop() {
        running_ = false;
 
        if (server_fd_ != -1) {
            shutdown(server_fd_, SHUT_RDWR);
            close(server_fd_);
            server_fd_ = -1;
        }
 
        unlink(COMM_SOCKET_PATH);
 
        if (background_thread_) {
            if (background_thread_->joinable()) {
                background_thread_->join();
            }
            delete background_thread_;
            background_thread_ = nullptr;
        }
 
        LOGI("Socket server stopped");
    }
 
    std::string SocketServer::WaitForMessage(int timeout_ms) {
        if (server_fd_ == -1) {
            LOGE("Server not started");
            return "";
        }
 
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(server_fd_, &readfds);
 
        struct timeval* tv_ptr = nullptr;
        struct timeval tv;
        if (timeout_ms >= 0) {
            tv.tv_sec = timeout_ms / 1000;
            tv.tv_usec = (timeout_ms % 1000) * 1000;
            tv_ptr = &tv;
        }
 
        int ret = select(server_fd_ + 1, &readfds, nullptr, nullptr, tv_ptr);
        if (ret <= 0) {
            if (ret < 0 && errno != EINTR) {
                LOGE("select error: %s", strerror(errno));
            }
            return "";
        }
 
        struct sockaddr_un client_addr;
        socklen_t client_len = sizeof(client_addr);
        int client_fd = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            if (errno != EINTR && errno != EAGAIN) {
                LOGE("Failed to accept connection: %s", strerror(errno));
            }
            return "";
        }
 
        char buffer[256] = {0};
        ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
        close(client_fd);
 
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            LOGI("Received message: %s", buffer);
            return std::string(buffer);
        }
 
        return "";
    }
 
    void SocketServer::RunInBackground(std::function<void(const std::string&)> callback) {
        if (background_thread_) {
            LOGE("Background thread already running");
            return;
        }
 
        background_thread_ = new std::thread(&SocketServer::AcceptLoop, this, callback);
    }
 
    void SocketServer::AcceptLoop(std::function<void(const std::string&)> callback) {
        LOGI("Background accept loop started");
 
        while (running_) {
            std::string msg = WaitForMessage(1000);
            if (!msg.empty() && callback) {
                callback(msg);
            }
        }
 
        LOGI("Background accept loop ended");
    }
 
    bool SocketClient::SendMessage(const std::string& message) {
        int client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (client_fd < 0) {
            LOGE("Failed to create client socket: %s", strerror(errno));
            return false;
        }
 
        struct sockaddr_un server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sun_family = AF_UNIX;
        strncpy(server_addr.sun_path, COMM_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
 
        if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            LOGE("Failed to connect to server: %s (path: %s)", strerror(errno), COMM_SOCKET_PATH);
            close(client_fd);
            return false;
        }
 
        ssize_t bytes_sent = send(client_fd, message.c_str(), message.length(), 0);
        close(client_fd);
 
        if (bytes_sent < 0) {
            LOGE("Failed to send message: %s", strerror(errno));
            return false;
        }
 
        LOGI("Message sent: %s", message.c_str());
        return true;
    }
 
}
#include "socket_comm.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <thread>
#include <android/log.h>
#include <sys/stat.h>
#include <errno.h>
 
#define LOG_TAG "[Socket_Comm]"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
 
namespace comm {
 
    SocketServer::SocketServer() : server_fd_(-1), running_(false), background_thread_(nullptr) {
    }
 
    SocketServer::~SocketServer() {
        Stop();
    }
 
    bool SocketServer::Start() {
        if (server_fd_ != -1) {
            LOGE("Server already started");
            return false;
        }
 
        unlink(COMM_SOCKET_PATH);
 
        // 创建Unix Domain Socket
        server_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
        if (server_fd_ < 0) {
            LOGE("Failed to create socket: %s", strerror(errno));
            return false;
        }
 
        struct sockaddr_un server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sun_family = AF_UNIX;
        strncpy(server_addr.sun_path, COMM_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
 
        if (bind(server_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            LOGE("Failed to bind socket: %s", strerror(errno));
            close(server_fd_);
            server_fd_ = -1;
            return false;
        }
 
        // 设置socket文件权限,允许所有用户访问
        chmod(COMM_SOCKET_PATH, 0666);
 
        if (listen(server_fd_, 1) < 0) {
            LOGE("Failed to listen on socket: %s", strerror(errno));
            close(server_fd_);
            server_fd_ = -1;
            unlink(COMM_SOCKET_PATH);
            return false;
        }
 
        running_ = true;
        LOGI("Unix Domain Socket server started at %s", COMM_SOCKET_PATH);
        return true;
    }
 
    void SocketServer::Stop() {
        running_ = false;
 
        if (server_fd_ != -1) {
            shutdown(server_fd_, SHUT_RDWR);
            close(server_fd_);
            server_fd_ = -1;
        }
 
        unlink(COMM_SOCKET_PATH);
 
        if (background_thread_) {
            if (background_thread_->joinable()) {
                background_thread_->join();
            }
            delete background_thread_;
            background_thread_ = nullptr;
        }
 
        LOGI("Socket server stopped");
    }
 
    std::string SocketServer::WaitForMessage(int timeout_ms) {
        if (server_fd_ == -1) {
            LOGE("Server not started");
            return "";
        }
 
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(server_fd_, &readfds);
 
        struct timeval* tv_ptr = nullptr;
        struct timeval tv;
        if (timeout_ms >= 0) {
            tv.tv_sec = timeout_ms / 1000;
            tv.tv_usec = (timeout_ms % 1000) * 1000;
            tv_ptr = &tv;
        }
 
        int ret = select(server_fd_ + 1, &readfds, nullptr, nullptr, tv_ptr);
        if (ret <= 0) {
            if (ret < 0 && errno != EINTR) {
                LOGE("select error: %s", strerror(errno));
            }
            return "";
        }
 
        struct sockaddr_un client_addr;
        socklen_t client_len = sizeof(client_addr);
        int client_fd = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            if (errno != EINTR && errno != EAGAIN) {
                LOGE("Failed to accept connection: %s", strerror(errno));
            }
            return "";
        }
 
        char buffer[256] = {0};
        ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
        close(client_fd);
 
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            LOGI("Received message: %s", buffer);
            return std::string(buffer);
        }
 
        return "";
    }
 
    void SocketServer::RunInBackground(std::function<void(const std::string&)> callback) {
        if (background_thread_) {
            LOGE("Background thread already running");
            return;
        }
 
        background_thread_ = new std::thread(&SocketServer::AcceptLoop, this, callback);
    }
 
    void SocketServer::AcceptLoop(std::function<void(const std::string&)> callback) {
        LOGI("Background accept loop started");
 
        while (running_) {
            std::string msg = WaitForMessage(1000);
            if (!msg.empty() && callback) {
                callback(msg);
            }
        }
 
        LOGI("Background accept loop ended");
    }
 
    bool SocketClient::SendMessage(const std::string& message) {
        int client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (client_fd < 0) {
            LOGE("Failed to create client socket: %s", strerror(errno));
            return false;
        }
 
        struct sockaddr_un server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sun_family = AF_UNIX;
        strncpy(server_addr.sun_path, COMM_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
 
        if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            LOGE("Failed to connect to server: %s (path: %s)", strerror(errno), COMM_SOCKET_PATH);
            close(client_fd);
            return false;

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

最后于 2025-7-11 19:02 被Yangser编辑 ,原因:
收藏
免费 108
支持
分享
最新回复 (69)
雪    币: 156
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
先创建通信模块注入so然后在这个so文件里面新注入核心代码so是吗
2025-7-11 17:29
0
雪    币: 1907
活跃值: (1489)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
mb_cfjwplfo 先创建通信模块注入so然后在这个so文件里面新注入核心代码so是吗[em_004]
是这样的,因为要监控进程创建
2025-7-11 19:01
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2025-7-11 19:18
0
雪    币: 213
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
6666
2025-7-13 17:06
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
新东西
2025-7-13 20:56
0
雪    币: 128
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
mark
2025-7-14 01:15
0
雪    币: 242
活跃值: (3305)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
666
2025-7-14 09:05
0
雪    币: 2805
活跃值: (12062)
能力值: (RANK:385 )
在线值:
发帖
回帖
粉丝
9
一件收藏
2025-7-14 09:51
1
雪    币: 605
活跃值: (4349)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
666
2025-7-14 13:48
0
雪    币: 1907
活跃值: (1489)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11

嘻嘻

最后于 2025-7-14 14:51 被Yangser编辑 ,原因:
2025-7-14 14:50
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
6666
2025-7-14 15:35
0
雪    币: 290
活跃值: (660)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
6666666
2025-7-15 02:38
0
雪    币: 23
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
z
2025-7-15 07:40
0
雪    币: 190
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
学习
2025-7-15 10:48
0
雪    币: 0
活跃值: (154)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
mark
2025-7-15 15:23
0
雪    币: 86
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
学习
2025-7-15 17:08
0
雪    币: 3242
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
看看
2025-7-15 17:19
0
雪    币: 245
活跃值: (2692)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
感谢分享
2025-7-17 11:29
0
雪    币: 34
活跃值: (1847)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
66666
2025-7-18 11:31
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
666
2025-7-19 09:17
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
能私聊吗
2025-7-20 15:50
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
Yangser 是这样的,因为要监控进程创建
能私聊吗
2025-7-20 15:51
0
雪    币: 1907
活跃值: (1489)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
24
mb_yqsvfqmf 能私聊吗
可以的,ShuangXve@gmail
2025-7-21 09:19
0
雪    币: 396
活跃值: (2958)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
25
学习一下
2025-7-21 10:55
0
游客
登录 | 注册 方可回帖
返回