前言:在上一篇文章中大概解释了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());
}
}
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());
}
}
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 );
}
}
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 );
}
}
#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);
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;
}
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);
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;
}
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编辑
,原因: