Frida 检测与对抗实战:进程、maps、线程、符号全特征清除

版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/

常见 Frida 检测方法

进程检测

可以通过 ps 命令检查是否存在 frida 进程

ini 复制代码
vangogh:/ # su -c "ps -A -o PID,NAME,ARGS" | grep frida
 4159 grep                        grep frida
 5908 10                          10 unix:abstract=/frida-d007576e-c54b-4d95-a136-b9ae2a8b9b18
16079 10                          10 unix:abstract=/frida-d9961233-953d-43b2-9e0b-4290e7782e72

或者扫描 /proc/pid 下的 comm 或 cmdline 文件内容检查是否存在 frida 进程

  • /proc/[pid]/comm:进程的短名称

  • /proc/[pid]/cmdline:进程的完整启动参数

通过 ps -A | grep frida 命令找到 frida-server 进程 id 为 5632

less 复制代码
1|vangogh:/ # ps -A | grep fs
root           165     2       0      0 ecryptfs_threadfn   0 S [ecryptfs-kthrea]
root           304     2       0      0 rescuer_thread      0 I [ufs_pm_qos_0]
root           306     2       0      0 rescuer_thread      0 I [ufs_recovery_wq]
root           307     2       0      0 rescuer_thread      0 I [ufs_clk_gating_]
root           308     2       0      0 rescuer_thread      0 I [ufs_clkscaling_]
root           655     2       0      0 issue_checkpoint_thread 0 S [f2fs_ckpt-259:6]
root           656     2       0      0 issue_flush_thread  0 S [f2fs_flush-259:]
root           657     2       0      0 issue_discard_thread 0 S [f2fs_discard-25]
root           658     2       0      0 gc_thread_func      0 S [f2fs_gc-259:65]
root           944     2       0      0 rescuer_thread      0 I [ipa_ut_dbgfs]
vendor_rfs    1086     1 2251048   2888 do_sys_poll         0 S tftp_server
system        1550     1 2336992   5340 binder_ioctl_write_read 0 S perfservice
root          5632     1 2275756   6928 do_sys_poll         0 S frida-server
root         24446     2       0      0 worker_thread       0 I [kworker/u16:8-ufs_pm_qos_0]
root         25294     2       0      0 worker_thread       0 I [kworker/u16:14-ufs_clkscaling_0]

进入 5632 进程目录可以看到有 comm 和 cmdline 文件

bash 复制代码
vangogh:/ # cd /proc
vangogh:/proc # ls | grep 5632
5632
vangogh:/proc # cd 5632
vangogh:/proc/5632 # ls
attr        coredump_filter   fdinfo     mountinfo   oom_score_adj  sched_boost_period_ms  sf_binder_task  syscall
autogroup   cpuset            io         mounts      pagemap        sched_group_id         smaps           task
auxv        critical_rt_task  limits     mountstats  personality    sched_init_task_load   smaps_rollup    time_in_state
cgroup      cwd               loginuid   net         reclaim        sched_low_latency      stack           timerslack_ns
clear_refs  environ           map_files  ns          root           sched_wake_up_idle     stat            wchan
cmdline     exe               maps       oom_adj     sched          schedstat              statm
comm        fd                mem        oom_score   sched_boost    sessionid              status

读取 comm 和 cmdline 文件内容

bash 复制代码
vangogh:/proc/5632 # cat comm
frida-server

vangogh:/proc/5632 # cat cmdline
/data/local/tmp/frida-server-l0.0.0.0:27042

但由于 hidepid,app内 只能看到自己进程 + 少量系统进程,其他 UID 的进程 完全不可见,除非有 root 权限。

hidepid 是 Linux 在挂载 /proc 时的权限控制参数(Android 默认常见为 hidepid=2),用于限制普通进程查看其他进程的信息;

获取 root 权限并通过 ps 命令获取完整进程列表:

c 复制代码
/**
 * 通过 su 执行 ps 获取完整进程列表
 */
static std::vector<std::string> scanByRoot(const std::vector<std::string> &keywords) {
    std::vector<std::string> results;

    // 构造 grep 正则
    std::string pattern;
    for (size_t i = 0; i < keywords.size(); i++) {
        pattern += keywords[i];
        if (i != keywords.size() - 1) pattern += "|";
    }

    std::string cmd = "su -c \"ps -A -o PID,NAME,ARGS\"";

    LOGD("exec: %s", cmd.c_str());

    FILE *fp = popen(cmd.c_str(), "r");
    if (!fp) {
        LOGD("popen failed");
        return results;
    }

    char buffer[512];
    while (fgets(buffer, sizeof(buffer), fp)) {
        std::string line(buffer);

        // 打印所有进程
        LOGD("ps: %s", line.c_str());

        if (!containsKeyword(line, keywords)) continue;

        // 简单解析:PID 在第一列
        std::istringstream iss(line);
        std::string pid, name, args;
        iss >> pid >> name;

        // 剩余部分就是 args(可能为空)
        std::getline(iss, args);

        // 去掉前导空格
        if (!args.empty() && args[0] == ' ') {
            args.erase(0, args.find_first_not_of(' '));
        }

        if (!pid.empty() && !name.empty()) {
            LOGD(">>> HIT pid=%s, name=%s, args=%s", pid.c_str(), name.c_str(), args.c_str());
            results.emplace_back(pid + ":" + name + ":" + args);
        }
    }

    pclose(fp);
    return results;
}

端口检测(27042)

Frida 默认端口:27042,通过对指定端口发起一次 TCP 连接来判断端口是否被占用。

c 复制代码
#include <jni.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>


extern "C"
JNIEXPORT jboolean JNICALL
Java_com_cyrus_example_fridadetector_core_checks_PortCheck_checkPort(
        JNIEnv *env,
        jobject thiz,
        jint port) {

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        return JNI_FALSE;
    }

    struct sockaddr_in sa{};
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    inet_aton("127.0.0.1", &sa.sin_addr);

    int result = connect(sock, (struct sockaddr *) &sa, sizeof(sa));

    if (result == 0) {
        close(sock);
        return JNI_TRUE;
    }

    close(sock);
    return JNI_FALSE;
}

调试检测

1. ptrace

通过调用 ptrace(PTRACE_TRACEME) 判断当前进程是否已被调试器附加:

如果当前进程已经被调试 (Frida / gdb):

  • 内核会拒绝 → 返回 -1

  • errno = EPERM

否则调用成功

arduino 复制代码
#include <jni.h>
#include <sys/ptrace.h>
#include <errno.h>
#include <unistd.h>
#include <android/log.h>

#define TAG "PtraceCheck"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_cyrus_example_fridadetector_core_checks_DebugCheck_isBeingTraced(
        JNIEnv *env,
        jobject thiz) {

    errno = 0;

    // 尝试声明自己被 trace
    long result = ptrace(PTRACE_TRACEME, 0, nullptr, nullptr);

    if (result == -1) {
        LOGD("ptrace failed, errno=%d", errno);

        // EPERM:已经被其他调试器附加(典型 Frida / gdb)
        if (errno == EPERM) {
            LOGD(">>> DETECTED: already being traced");
            return JNI_TRUE;
        }
    } else {
        // 成功说明当前未被 trace,需要 detach 避免影响后续
        ptrace(PTRACE_DETACH, 0, nullptr, nullptr);
    }

    return JNI_FALSE;
}

errno 是内核返回的"失败原因编码",定义如下:

arduino 复制代码
#define EPERM   1   // Operation not permitted(操作不被允许)
#define ENOENT  2   // No such file or directory(文件或目录不存在)
#define ESRCH   3   // No such process(进程不存在)
#define EINTR   4   // Interrupted system call(系统调用被信号中断)
#define EIO     5   // I/O error(输入输出错误)
#define ENXIO   6   // No such device or address(设备或地址不存在)
#define E2BIG   7   // Argument list too long(参数列表过长)
#define ENOEXEC 8   // Exec format error(可执行文件格式错误)
#define EBADF   9   // Bad file descriptor(错误的文件描述符)
#define ECHILD  10  // No child processes(没有子进程)
#define EAGAIN  11  // Try again(资源暂时不可用,需重试)
#define ENOMEM  12  // Out of memory(内存不足)
#define EACCES  13  // Permission denied(权限被拒绝)
#define EFAULT  14  // Bad address(非法内存地址)
#define ENOTBLK 15  // Block device required(需要块设备)
#define EBUSY   16  // Device or resource busy(设备或资源忙)
#define EEXIST  17  // File exists(文件已存在)
#define EXDEV   18  // Cross-device link(跨设备链接)
#define ENODEV  19  // No such device(设备不存在)
#define ENOTDIR 20  // Not a directory(不是目录)
#define EISDIR  21  // Is a directory(是目录)
#define EINVAL  22  // Invalid argument(无效参数)
#define ENFILE  23  // File table overflow(系统文件表已满)
#define EMFILE  24  // Too many open files(进程打开文件过多)
#define ENOTTY  25  // Not a typewriter(不支持的 ioctl 设备)
#define ETXTBSY 26  // Text file busy(文本文件被占用)
#define EFBIG   27  // File too large(文件过大)
#define ENOSPC  28  // No space left on device(设备空间不足)
#define ESPIPE  29  // Illegal seek(非法 seek 操作)
#define EROFS   30  // Read-only file system(只读文件系统)
#define EMLINK  31  // Too many links(链接数过多)
#define EPIPE   32  // Broken pipe(管道破裂)
#define EDOM    33  // Math argument out of domain(数学参数超出定义域)
#define ERANGE  34  // Math result not representable(数学结果超出范围)

但在 Android 上 ptrace 经常被 seccomp(secure computing mode) 限制。

seccomp 拦截了 ptrace syscall,并返回一个"权限被拒绝"的 errno

当你在 App 中执行:

scss 复制代码
ptrace(PTRACE_TRACEME, ...)

实际会触发一个 syscall: ptrace

为什么 Android 要限制 ptrace?

因为 ptrace 可以:

  • attach 任意进程

  • 修改寄存器 / 内存

  • 实现调试、注入(Frida 就是基于它)

系统策略:普通 app ≈ 禁用 ptrace

出于安全考虑,所以有 seccomp 这种内核级 syscall 过滤机制,

2. TracerPid

通过读取 /proc/self/status 中的 TracerPid 字段判断是否存在调试器(非 0 表示被调试);

相比 ptrace,该方法不依赖 syscall,不易被 seccomp/SELinux 限制或 hook,因此更稳定可靠。

arduino 复制代码
/**
 * TracerPid 检测
 */
extern "C"
JNIEXPORT jint JNICALL
Java_com_cyrus_example_fridadetector_core_checks_DebugCheck_getTracerPid__(
        JNIEnv *env,
        jobject thiz) {

    std::ifstream status("/proc/self/status");
    if (!status.is_open()) {
        LOGD("open /proc/self/status failed");
        return -1;
    }

    std::string line;
    while (std::getline(status, line)) {
        if (line.find("TracerPid:") == 0) {

            int tracerPid = atoi(line.substr(10).c_str());

            LOGD("TracerPid=%d", tracerPid);

            if (tracerPid != 0) {
                LOGD(">>> tracer detected (pid=%d)", tracerPid);
            }

            return tracerPid;
        }
    }

    return 0;
}

可以通过 strace 查看 frida-server 的 ptrace 调用

bash 复制代码
vangogh:/data/local/tmp # strace -f -e ptrace ./frida-server

frida 的 ptrace 调用相关源码:frida\subprojects\frida-core\src\linux\frida-helper-backend.vala

但是 Frida 不会长期依赖 ptrace, 不会长期占用 TracerPid,因此该方法不一定有效,需要增加内存检测手段。

maps 检测

/proc/[pid]/maps 是进程的内存映射表,记录了当前进程加载的所有模块(so、dex、匿名映射等)及其地址范围和权限信息。

而 Frida 在注入时通常会加载诸如 frida-agent、libfrida-gadget.so、gum-js-loop 等特征模块或字符串,因此只需遍历 maps 内容并匹配这些关键字,就可以检测 Frida 的存在。

bash 复制代码
vangogh:/ # pidof com.cyrus.example
4639
vangogh:/ # cd /proc/4639
vangogh:/proc/4639 # cat maps | grep frida
71290ee000-7129b1c000 r--p 00000000 00:01 4581721                        /memfd:frida-agent-64.so (deleted)
7129b1d000-712a85d000 r-xp 00a2e000 00:01 4581721                        /memfd:frida-agent-64.so (deleted)
712a85d000-712a92e000 r--p 0176d000 00:01 4581721                        /memfd:frida-agent-64.so (deleted)
712a92f000-712a94a000 rw-p 0183e000 00:01 4581721                        /memfd:frida-agent-64.so (deleted)

代码实现如下:

kotlin 复制代码
package com.cyrus.example.fridadetector.core.checks

import android.util.Log
import com.cyrus.example.fridadetector.model.CheckResult
import com.cyrus.example.fridadetector.model.CheckType
import java.io.File

object MapsCheck {
    fun check(): CheckResult {
        return try {
            val maps = File("/proc/self/maps").readText()

            Log.i("MapsCheck", "maps:${maps}")

            val detected = listOf(
                "frida",
                "gum-js",
                "gmain"
            ).any { maps.contains(it, true) }

            CheckResult(
                CheckType.MAPS,
                !detected,
                if (detected) "Frida string in maps" else "OK"
            )
        } catch (e: Exception) {
            CheckResult(CheckType.MAPS, true, "Error")
        }
    }
}

完整源码

Android 完整源码地址:github.com/CYRUS-STUDI...

Frida 检测对抗

以下修改基于 Frida 16.7.19,不同版本可能略有差异。

Frida源码下载和编译参考:Frida 源码编译全流程:自己动手编译 frida-server

修改前准备

1. 切换目标版本

切换到目标版本

复制代码
git checkout 16.7.19

同步 submodule(关键)

css 复制代码
git submodule update --init --recursive --force

检测子模块是否已经同步

bash 复制代码
cyrus:~/frida$ git submodule status
 ddd0f5875c08edc005e9f89f7fdb7b0928fac4dc releng (ddd0f58)
 bc9f17ee233deb6cee53b5379db8b21ca6dce602 subprojects/frida-clr (16.7.19)
 4f05cb60ce97660189962bfcf7ffca15e182848e subprojects/frida-core (4f05cb60)
 fd52f64f49fbbde2820e02e6e6dd3aafd1ac0ce6 subprojects/frida-go (v0.7.2-1-gfd52f64)
 88e78178f786bf8cea2172ed6aeff335f30dd299 subprojects/frida-gum (88e78178)
 d742ef8ab032876514fd7d0a0fcc41093e80d25c subprojects/frida-node (16.7.19)
 3def865cf83963b099e0c37d5ab159d1e415951b subprojects/frida-python (16.7.19)
 1201454c917174cec7e233041799d2c457f947a2 subprojects/frida-qml (16.7.19)
 d48df0a7c0791a1e58bf9e5598a19e59652d90f5 subprojects/frida-swift (16.7.19)
 f61148b9a0b980c6400479c34ee8d059dfb510d6 subprojects/frida-tools (13.7.1-15-gf61148b)

2. 创建开发分支

  1. frida-core
bash 复制代码
cd subprojects/frida-core
git checkout -b cyrida-16.7.19
  1. frida-gum
bash 复制代码
cd ../frida-gum
git checkout -b cyrida-16.7.19
  1. 回到主仓库
bash 复制代码
cd ~/frida
git checkout -b cyrida-16.7.19

消除 frida:rpc 特征

源文件路径:frida\subprojects\frida-core\lib\base\rpc.vala

把 frida:rpc 的核心特征字符串从"静态可见"变成"运行时生成",从而绕过基于字符串特征的检测。

vbnet 复制代码
/**
 * 动态构造 "frida:rpc"
 *
 * @param quote 是否返回带引号的字符串:
 *               true  -> "\"frida:rpc\""
 *               false -> "frida:rpc"    
 */
public string getRpcStr(bool quote){
    string result = (string) GLib.Base64.decode((string) GLib.Base64.decode("Wm5KcFpHRTZjbkJq"));
    if(quote){
       return "\"" + result + "\"";
    }else{
       return result;
    }
}

替换点:

原始 修改后
"frida:rpc" getRpcStr(false)
""frida:rpc"" getRpcStr(true)

提交修改

bash 复制代码
cd ~/frida/subprojects/frida-core

git add lib/base/rpc.vala
git commit -m "string frida rpc"

消除 frida-agent*.so 文件名

源文件路径:frida\subprojects\frida-core\src\linux\linux-host-session.vala

消除 frida-agent*.so 这一强特征文件名,用随机名替代,从而绕过基于文件名的检测。

agent 文件名随机化(UUID)

修改后的源码如下:

csharp 复制代码
#if HAVE_EMBEDDED_ASSETS
          var blob32 = Frida.Data.Agent.get_frida_agent_32_so_blob ();
          var blob64 = Frida.Data.Agent.get_frida_agent_64_so_blob ();
          var emulated_arm = Frida.Data.Agent.get_frida_agent_arm_so_blob ();
          var emulated_arm64 = Frida.Data.Agent.get_frida_agent_arm64_so_blob ();

          // 随机前缀
          var random_prefix = GLib.Uuid.string_random ();

          agent = new AgentDescriptor (PathTemplate (random_prefix + "-<arch>.so"),
             new Bytes.static (blob32.data),
             new Bytes.static (blob64.data),
             new AgentResource[] {
                    new AgentResource (random_prefix + "-arm.so", new Bytes.static (emulated_arm.data), tempdir),
                    new AgentResource (random_prefix + "-arm64.so", new Bytes.static (emulated_arm64.data), tempdir),
             },
             AgentMode.INSTANCED,
             tempdir);
#endif

get_emulated_agent_path(避免仍用 frida-agent-*)

修改后源码如下:

typescript 复制代码
protected override string? get_emulated_agent_path (uint pid) throws Error {
    unowned string arch_suffix;
    switch (cpu_type_from_pid (pid)) {
       case Gum.CpuType.IA32:
          arch_suffix = "arm";
          break;
       case Gum.CpuType.AMD64:
          arch_suffix = "arm64";
          break;
       default:
          throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on this architecture");
    }

    // 从 agent.resources 动态匹配(不再使用硬编码)
    AgentResource? resource = agent.resources.first_match (r => r.name.has_suffix ("-" + arch_suffix + ".so"));
    if (resource == null)
       throw new Error.NOT_SUPPORTED ("Unable to handle emulated processes due to build configuration");

    return resource.get_file ().path;
}

提交修改:

sql 复制代码
git add src/linux/linux-host-session.vala
git commit -m "frida agent so randomize"

消除 frida_agent_main 符号

把 frida_agent_main 这个强特征导出符号彻底移除,并伪装成普通 main,同时批量抹除所有 symbol 中的 "frida" 字符串。

Frida 注入流程本质:

scss 复制代码
dlopen(frida-agent.so)
        ↓
dlsym("frida_agent_main")
        ↓
call(...)

vala 修改 = 改 dlsym 查找的名字

ELF 修改 = 改 symbol 实际名字

两者必须一致

1. 修改 Vala

需要修改的 vala 文件

修改入口函数名,把 frida_agent_main 改为 main;

2. 修改 ELF

embed-agent.py 同级目录下新建一个 python 脚本,用于批量抹除 frida-agent.so 中 所有 symbol 中的 "frida" 字符串。

obfuscate_agent_symbols.py

scss 复制代码
import lief
import sys
import random
import os

def log_color(msg):
    print(f"\033[1;31;40m{msg}\033[0m")

if __name__ == "__main__":
    input_file = sys.argv[1]
    random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

    log_color(f"[*] Patch frida-agent: {input_file}")

    binary = lief.parse(input_file)

    if not binary:
        log_color(f"[*] Not elf, exit")
        exit()

    random_name = "".join(random.sample(random_charset, 5))
    log_color(f"[*] Patch `frida` to `{random_name}`")

    for symbol in binary.symbols:
        if symbol.name == "frida_agent_main":
            symbol.name = "main"

        if "frida" in symbol.name:
            symbol.name = symbol.name.replace("frida", random_name)

        if "FRIDA" in symbol.name:
            symbol.name = symbol.name.replace("FRIDA", random_name)

    binary.write(input_file)

在 frida\subprojects\frida-core\src\embed-agent.py 中把 obfuscate_agent_symbols.py 自动嵌入到构建流程中,在生成 frida-agent.so 后立即进行二进制级"去特征处理"。

python 复制代码
elif host_os in {"linux", "android"}:
    for agent, flavor in [(agent_modern, "64"),
                          (agent_legacy, "32"),
                          (agent_emulated_modern, "arm64"),
                          (agent_emulated_legacy, "arm")]:
        embedded_agent = priv_dir / f"frida-agent-{flavor}.so"
        if agent is not None:
            shutil.copy(agent, embedded_agent)
        else:
            embedded_agent.write_bytes(b"")
        
        # === 新增:调用 obfuscate_agent_symbols.py ===
        import os
        custom_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "obfuscate_agent_symbols.py")
        return_code = os.system(f"python3 {custom_script} {str(priv_dir / f'frida-agent-{flavor}.so')}")
        
        if return_code == 0:
            print("obfuscate_agent_symbols finished")
        else:
            print("obfuscate_agent_symbols error. Code:", return_code)
        # === 新增结束 ===

        embedded_assets += [embedded_agent]

提交修改:

bash 复制代码
git add \
  src/agent-container.vala \
  src/darwin/darwin-host-session.vala \
  src/freebsd/freebsd-host-session.vala \
  src/linux/linux-host-session.vala \
  src/qnx/qnx-host-session.vala \
  src/windows/windows-host-session.vala \
  tests/test-agent.vala \
  tests/test-injector.vala \
  src/obfuscate_agent_symbols.py \
  src/embed-agent.py
git commit -m "symbol frida_agent_main to main"

抹除 gum-js-loop 线程名特征

通过修改 ELF 中的 "gum-js-loop" 字符串,间接改变 Frida 运行时线程名,从而绕过基于线程名的检测。

在 obfuscate_agent_symbols.py 中增加下面的代码:

python 复制代码
# thread gum_js_loop
random_name = "".join(random.sample(random_charset, 11))
log_color(f"[*] Patch `gum-js-loop` to `{random_name}`")
os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}") # 把 gum-js-loop 替换成随机字符串

提交修改

sql 复制代码
git add src/obfuscate_agent_symbols.py
git commit -m "thread gum_js_loop"

抹除 gmain 线程名特征

把 ELF 中所有 "gmain" 字符串替换为随机字符串。在 obfuscate_agent_symbols.py 中增加下面的代码:

python 复制代码
# thread gmain
random_name = "".join(random.sample(random_charset, 5))
log_color(f"[*] Patch `gmain` to `{random_name}`")
os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")

提交修改

sql 复制代码
git add src/obfuscate_agent_symbols.py
git commit -m "thread gmain"

抹除 gdbus 线程名特征

在整个 ELF 二进制中查找字符串 "gdbus",并用随机字符串全局替换,从而隐藏线程/组件特征。

在 obfuscate_agent_symbols.py 中增加下面的代码:

python 复制代码
# thread gdbus
random_name = "".join(random.sample(random_charset, 5))
log_color(f"[*] Patch `gdbus` to `{random_name}`")
os.system(f"sed -b -i s/gdbus/{random_name}/g {input_file}")

提交修改

sql 复制代码
git add src/obfuscate_agent_symbols.py
git commit -m "thread gdbus"

.rodata 字符串

通过在 .rodata 段中定位 Frida 相关字符串,并用"等长反转字符串"覆盖,达到隐藏特征同时不破坏 ELF 结构的目的。

ini 复制代码
all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"]
for section in binary.sections:
    if section.name != ".rodata":
        continue
    for patch_str in all_patch_string:
        addr_all = section.search_all(patch_str)
        for addr in addr_all:
            patch = [ord(n) for n in list(patch_str)[::-1]]
            log_color(f"[*] Patching section name={section.name} offset={hex(section.file_offset + addr)} orig:{patch_str} new:{''.join(list(patch_str)[::-1])}")
            binary.patch_address(section.file_offset + addr, patch)

最终效果大概如下:

复制代码
FridaScriptEngine
↓
enignEtpircSadirF
  • 不改变 ELF 结构

  • 不影响偏移

  • 不破坏引用关系

  • 不需要重定位

提交修改

sql 复制代码
git add src/obfuscate_agent_symbols.py
git commit -m ".rodata frida string"

obfuscate_agent_symbols.py

obfuscate_agent_symbols.py 完整源码如下:

python 复制代码
import lief
import sys
import random
import os

def log_color(msg):
    print(f"\033[1;31;40m{msg}\033[0m")

if __name__ == "__main__":
    input_file = sys.argv[1]
    random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

    log_color(f"[*] Patch frida-agent: {input_file}")

    binary = lief.parse(input_file)

    if not binary:
        log_color(f"[*] Not elf, exit")
        exit()

    random_name = "".join(random.sample(random_charset, 5))
    log_color(f"[*] Patch `frida` to `{random_name}`")

    for symbol in binary.symbols:
        if symbol.name == "frida_agent_main":
            symbol.name = "main"

        if "frida" in symbol.name:
            symbol.name = symbol.name.replace("frida", random_name)

        if "FRIDA" in symbol.name:
            symbol.name = symbol.name.replace("FRIDA", random_name)

    all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"]
    for section in binary.sections:
        if section.name != ".rodata":
            continue
        for patch_str in all_patch_string:
            addr_all = section.search_all(patch_str)
            for addr in addr_all:
                patch = [ord(n) for n in list(patch_str)[::-1]]
                log_color(f"[*] Patching section name={section.name} offset={hex(section.file_offset + addr)} orig:{patch_str} new:{''.join(list(patch_str)[::-1])}")
                binary.patch_address(section.file_offset + addr, patch)

    binary.write(input_file)

    # thread gum_js_loop
    random_name = "".join(random.sample(random_charset, 11))
    log_color(f"[*] Patch `gum-js-loop` to `{random_name}`")
    os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}") # 把 gum-js-loop 替换成随机字符串

    # thread gmain
    random_name = "".join(random.sample(random_charset, 5))
    log_color(f"[*] Patch `gmain` to `{random_name}`")
    os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")

    # thread gdbus
    random_name = "".join(random.sample(random_charset, 5))
    log_color(f"[*] Patch `gdbus` to `{random_name}`")
    os.system(f"sed -b -i s/gdbus/{random_name}/g {input_file}")

    log_color(f"[*] Patch Finish")

放宽协议校验,避免异常中断

源文件路径:frida\subprojects\frida-core\src\droidy\droidy-client.vala

在 Droidy(Frida 的 Android 设备通信层)中放宽协议校验,避免异常中断 。修改如下:

一些反 Frida / 对抗环境会,主动干扰通信:

  • 插入异常 packet

  • 发送非预期 command

  • 打乱通信顺序

将协议层的"异常即中断"改为"异常忽略",提高 Frida 在异常或对抗环境下的通信稳定性。

提交修改:

sql 复制代码
git add src/droidy/droidy-client.vala
git commit -m "Relax protocol validation to avoid unexpected interruptions."

进程级特征伪装

源文件路径:

  • frida\subprojects\frida-gum\gum\gum.c

  • frida\subprojects\frida-core\src\frida-glue.c

通过 g_set_prgname 修改进程名,将 frida-agent 伪装成普通程序(如 ggbond),绕过基于进程名的检测。

g_set_prgname 是 GLib 提供的 API:

arduino 复制代码
void g_set_prgname (const gchar *prgname);

用于设置当前进程的名字。

gum.c

frida-glue.c

提交修改:

bash 复制代码
git add src/frida-glue.c
git commit -m "process_name"

cd ~/frida/subprojects/frida-gum
git add gum/gum.c
git commit -m "process_name"

隐藏 maps(memfd)特征

memfd 是 Linux 提供的一种"仅存在于内存中的文件",可用于存储和执行代码,Frida 利用它实现无文件落地的 so 注入,但其 name 会暴露在 /proc/maps 中成为检测点。

将 memfd_create 的 name 从包含 frida 特征的字符串,改为 "jit-cache"(把 Frida 行为伪装成"系统正常行为"),从而隐藏 Frida 在 /proc/self/maps 中的关键特征。

源文件路径:frida\subprojects\frida-core\src\linux\frida-helper-backend.vala

修改前:

csharp 复制代码
private int memfd_create (string name, uint flags) {
    return Linux.syscall (SysCall.memfd_create, name, flags);
}

修改后:

csharp 复制代码
private int memfd_create (string name, uint flags) {
    return Linux.syscall (SysCall.memfd_create, "jit-cache", flags);
}

提交修改:

sql 复制代码
git add src/linux/frida-helper-backend.vala
git commit -m "memfd jit cache"

抹除 "frida-" socket 前缀

Frida 在建立通信时会创建一个 Unix abstract socket

ini 复制代码
string socket_path = "/frida-" + Uuid.string_random ();

Linux 表现为:

ini 复制代码
unix:abstract=/frida-xxxx

当启动 frida-server 后,通过 ps 命令可以看到:

ini 复制代码
vangogh:/proc/27743 # ps -A -o PID,NAME,ARGS | grep frida
22357 10                          10 unix:abstract=/frida-84ebe7df-f80f-4cb0-b852-2241dbd346b4
22613 10                          10 unix:abstract=/frida-3c1d0033-6980-42cc-b8ac-02809cd8382d

这个 frida 特征来自 Unix abstract socket(frida-xxxx),这是运行时生成的,可以通过修改源码中的 socket 命名逻辑彻底消除。

源文件路径:

  • frida\subprojects\frida-core\src\linux\frida-helper-backend.vala

  • frida\subprojects\frida-core\src\linux\frida-helper-process.vala

直接把 "frida-" 前缀 删除掉,修改如下:

frida-helper-backend.vala

frida-helper-process.vala

提交修改:

bash 复制代码
git add \
  src/linux/frida-helper-backend.vala \
  src/linux/frida-helper-process.vala
git commit -m "remove soket name prefix"

cd ~/frida
git add \
  subprojects/frida-core \
  subprojects/frida-gum
git commit -m "anti frida detection"

编译 frida-server

安装 lief

复制代码
python3 -m pip install lief

验证:

scss 复制代码
python3 -c "import lief; print(lief.__version__)"

重新编译 frida

bash 复制代码
# 1. 清理旧构建
rm -rf build

# 2. 配置(Android)
./configure --host=android-arm64

# 3. 编译
make

编译完成

ini 复制代码
cyrus:~/frida$ make
INFO: autodetecting backend as ninja
INFO: calculating backend command to run: /home/cyrus/frida/deps/toolchain-linux-x86_64/bin/ninja
[258/298] Generating subprojects/frida-core/src/frida-data-agent with a custom command
[*] Patch frida-agent: subprojects/frida-core/src/frida-data-agent-blob.S.p/frida-agent-64.so
[*] Patch `frida` to `JebEa`
[*] Patching section name=.rodata offset=0x1c3612 orig:FridaScriptEngine new:enignEtpircSadirF
[*] Patching section name=.rodata offset=0x1d1430 orig:FridaScriptEngine new:enignEtpircSadirF
[*] Patching section name=.rodata offset=0x1d8365 orig:GLib-GIO new:OIG-biLG
[*] Patching section name=.rodata offset=0x1946fa orig:GDBusProxy new:yxorPsuBDG
[*] Patching section name=.rodata offset=0x1c3718 orig:GDBusProxy new:yxorPsuBDG
[*] Patching section name=.rodata offset=0x1aed35 orig:GumScript new:tpircSmuG
[*] Patching section name=.rodata offset=0x20e82c orig:GumScript new:tpircSmuG
[*] Patching section name=.rodata offset=0x235eec orig:GumScript new:tpircSmuG
[*] Patching section name=.rodata offset=0x2439d9 orig:GumScript new:tpircSmuG
[*] Patch `gum-js-loop` to `vBuEgneMWtj`
[*] Patch `gmain` to `uBmwx`
[*] Patch `gdbus` to `ctdDE`
[*] Patch Finish
[*] Patch frida-agent: subprojects/frida-core/src/frida-data-agent-blob.S.p/frida-agent-32.so
[*] Patch `frida` to `NRXct`
[*] Patching section name=.rodata offset=0xcc9d1 orig:FridaScriptEngine new:enignEtpircSadirF
[*] Patching section name=.rodata offset=0xda256 orig:FridaScriptEngine new:enignEtpircSadirF
[*] Patching section name=.rodata offset=0xe1161 orig:GLib-GIO new:OIG-biLG
[*] Patching section name=.rodata offset=0x9e93a orig:GDBusProxy new:yxorPsuBDG
[*] Patching section name=.rodata offset=0xccad7 orig:GDBusProxy new:yxorPsuBDG
[*] Patching section name=.rodata offset=0xb887c orig:GumScript new:tpircSmuG
[*] Patching section name=.rodata offset=0x1158a2 orig:GumScript new:tpircSmuG
[*] Patching section name=.rodata offset=0x13cb44 orig:GumScript new:tpircSmuG
[*] Patching section name=.rodata offset=0x14a2a5 orig:GumScript new:tpircSmuG
[*] Patch `gum-js-loop` to `JydDgHiCeGf`
[*] Patch `gmain` to `tYMPy`
[*] Patch `gdbus` to `dJTEL`
[*] Patch Finish
[*] Patch frida-agent: subprojects/frida-core/src/frida-data-agent-blob.S.p/frida-agent-arm64.so
[*] Not elf, exit
[*] Patch frida-agent: subprojects/frida-core/src/frida-data-agent-blob.S.p/frida-agent-arm.so
[*] Not elf, exit
obfuscate_agent_symbols finished
obfuscate_agent_symbols finished
obfuscate_agent_symbols finished
obfuscate_agent_symbols finished
[298/298] Generating subprojects/frida-core/src/api/frida-core-api with a custom command

在 build 目录下找到 frida-server

测试对抗效果

把定制的 frida-server 推送到设备并启动,通过 frida hook 当前 app 成功,并且 rpc 通信正常执行。

less 复制代码
(frida17) PS D:\Python\anti-app\frida17\native> frida -H 127.0.0.1:1234 -F
     ____
    / _  |   Frida 16.7.19 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to 127.0.0.1:1234 (id=socket@127.0.0.1:1234)
[Remote::AndroidExample ]-> Frida.version
"16.7.19"

app 中 frida 特征检测全通过。

某充电APP检测到 frida 后会自动退出APP

换定制的 frida-server 后能正常 Hook

打一个 patchs

查看 commit 日志

css 复制代码
git log --oneline --decorate --graph

frida-core 从 b93d3254 开始导出 patch

lua 复制代码
cyrus:~/frida/subprojects/frida-core$ git log --oneline --decorate --graph
* 8fe3dbbe (HEAD -> cyrida-16.7.19) remove soket name prefix
* 7d3259f6 string frida rpc
* e2d7b964 memfd jit cache
* 1303afd4 process_name
* bf00791b Relax protocol validation to avoid unexpected interruptions.
* 6520e8c7 thread gdbus
* c5cb2c37 thread gmain
* 6230fce6 thread gum_js_loop
* ed561f84 .rodata frida string
* 7d5a49b6 symbol frida_agent_main to main
* b93d3254 frida agent so randomize
...

导出到 cyrida/patches/frida-core

bash 复制代码
cyrus:~/frida/subprojects/frida-core$ git format-patch b93d3254^ -o /home/cyrus/cyrida/patches/frida-core
/home/cyrus/cyrida/patches/frida-core/0001-frida-agent-so-randomize.patch
/home/cyrus/cyrida/patches/frida-core/0002-symbol-frida_agent_main-to-main.patch
/home/cyrus/cyrida/patches/frida-core/0003-.rodata-frida-string.patch
/home/cyrus/cyrida/patches/frida-core/0004-thread-gum_js_loop.patch
/home/cyrus/cyrida/patches/frida-core/0005-thread-gmain.patch
/home/cyrus/cyrida/patches/frida-core/0006-thread-gdbus.patch
/home/cyrus/cyrida/patches/frida-core/0007-Relax-protocol-validation-to-avoid-unexpected-interr.patch
/home/cyrus/cyrida/patches/frida-core/0008-process_name.patch
/home/cyrus/cyrida/patches/frida-core/0009-memfd-jit-cache.patch
/home/cyrus/cyrida/patches/frida-core/0010-string-frida-rpc.patch
/home/cyrus/cyrida/patches/frida-core/0011-remove-soket-name-prefix.patch

frida-gum 从 62d75bfd 开始导出 patch

less 复制代码
cyrus:~/frida/subprojects/frida-gum$ git log --oneline --decorate --graph
* 62d75bfd (HEAD -> cyrida-16.7.19) process_name

导出到 cyrida/patches/frida-gum

ruby 复制代码
cyrus:~/frida/subprojects/frida-gum$ git format-patch 62d75bfd^ -o /home/cyrus/cyrida/patches/frida-gum
/home/cyrus/cyrida/patches/frida-gum/0001-process_name.patch

Push Patches

csharp 复制代码
cd ~cyrida

git init
git add .
git commit -m "cyrida patches based on frida 16.7.19"
git tag 16.7.19
git branch -M main
git remote add origin https://github.com/CYRUS-STUDIO/Cyrida.git
git push -u origin main

项目开源地址:github.com/CYRUS-STUDI...

apply patchs

apply 之前先切换到对应的版本

css 复制代码
git checkout 16.7.19
git submodule update --init --recursive --force

应用 frida-core patches

vbnet 复制代码
cyrus:~/frida$ cd ~/frida/subprojects/frida-core
cyrus:~/frida/subprojects/frida-core$ git am ~/cyrida/patches/frida-core/*.patch
Applying: frida agent so randomize
Applying: symbol frida_agent_main to main
Applying: .rodata frida string
Applying: thread gum_js_loop
Applying: thread gmain
Applying: thread gdbus
/home/cyrus/frida/.git/modules/frida-core/rebase-apply/patch:27: trailing whitespace.

warning: 1 line adds whitespace errors.
Applying: Relax protocol validation to avoid unexpected interruptions.
Applying: process_name
/home/cyrus/frida/.git/modules/frida-core/rebase-apply/patch:14: trailing whitespace.

warning: 1 line adds whitespace errors.
Applying: memfd jit cache
Applying: string frida rpc
Applying: remove soket name prefix

应用 frida-gum patches

javascript 复制代码
cyrus:~/frida/subprojects/frida-core$ cd ~/frida/subprojects/frida-gum
cyrus:~/frida/subprojects/frida-gum$ git am ~/cyrida/patches/frida-gum/*.patch
Applying: process_name

注意:patch 属于哪个 repo,就在哪个 repo 里 apply,如果 patch 失败必须先 abort 然后重新来一遍:

c 复制代码
git am --abort

相关链接:

相关推荐
csj503 小时前
安卓基础之《(28)—Service组件》
android
lhbian5 小时前
PHP、C++和C语言对比:哪个更适合你?
android·数据库·spring boot·mysql·kafka
catoop6 小时前
Android 最佳实践、分层架构与全流程解析(2025)
android
ZHANG13HAO6 小时前
Android 13 特权应用(Android Studio 开发)调用 AOSP 隐藏 API 完整教程
android·ide·android studio
田梓燊7 小时前
leetcode 142
android·java·leetcode
angerdream7 小时前
Android手把手编写儿童手机远程监控App之JAVA基础
android
菠萝地亚狂想曲8 小时前
Zephyr_01, environment
android·java·javascript
sTone873758 小时前
跨端框架通信机制全解析:从 URL Schema 到 JSI 到 Platform Channel
android·前端
sTone873758 小时前
Java 注解完全指南:从 "这是什么" 到 "自己写一个"
android·前端