跟随feicong大佬学习后,对某sec.so进行了反调试分析,发现其反调试手段主要集中在:
从已有分析可以确认,这个 so 在初始化阶段会拉起多条保护链,其中与 Frida 最直接相关的一条是:
典型命中目标包括:
一旦命中,样本要么直接触发退出,要么进入更激进的异常路径,最终导致进程崩溃。
本文的重点
可以先用一张总览图把本文涉及的检测面放在一起:

详细静态分析是利用agent工具 codex + tenrec(ida的mcp)进行的,不得不感叹AI正在彻底颠覆行业。
完整的反调试机制可以先用一张图概括:

目前最准确的整理方式是:
其中:
sub_1C544 的主循环里至少包含三个检测点:
其中和本文最直接相关的是 sub_1BFAC。
可以把它还原成下面这种可读伪代码:
用户态检测流程可以概括成:
这意味着,只要用户态看到的 status 内容被改写,检测就会失效。
很多人第一次分析这类检测时,会把 gum-js-loop、gmain 当成“样本自己创建的线程”。
其实不是,这些线程名来自 Frida 运行时自身。
Frida 的脚本执行、消息循环、调度逻辑依赖 Gum。
在目标进程里注入 agent 后,Gum 会创建自己的事件循环线程,典型线程名就是:
这个线程基本可以视作“Frida JavaScript runtime 正在进程内运行”的直接信号。
Frida 底层依赖 GLib/GIO 事件循环。
一旦使用消息通信、异步 IO 或内部调度,GLib 相关线程也会出现,典型名就是:
原因很简单:
换句话说,线程名检测是最典型、也最容易落地的 Frida 识别手段之一。
既然样本依赖的是 /proc 输出,那最直接的思路就是:
本文使用的模块就是:
b7fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6F1N6h3!0W2L8W2)9J5c8X3E0W2M7X3&6W2L8q4)9#2k6X3#2G2k6s2g2D9k6i4x3`.
hookmodule 主要做了三类事:
这三类 hook 对应三条不同检测面:
从实现层面看,hookmodule 的核心思路是把“用户态读取 /proc 的结果”前移到内核里处理:

模块支持的关键参数有:
示例加载方式:
这里的 hide_so 并不只是“隐藏 so 名字”,更准确地说是:
这是本文最核心的一部分。
用户态样本读取的 status 文本最终来自内核的 proc_pid_status()。
因此最稳的方式不是去改用户态 fopen/read/fgets,而是直接在内核里拦 proc_pid_status。
模块里使用了 kretprobe:
进入 proc_pid_status 时:
如果线程名命中隐藏关键字,就临时改写:
返回 proc_pid_status 后再恢复:
因为用户态样本最终看到的是:
而 status 文件中的线程名字段,本质上就是内核按 task_struct->comm 生成的文本。
我们在文本生成前把 task->comm 临时改成替身值,样本读到的就不再是:
而是:
等到文本生成完成,再恢复原值。
这样既能骗过检测,又不会永久破坏线程本身。
这条关键绕过链可以用时序图来表示:

用户态 hook 的问题在于:
而内核里拦 proc_pid_status 的优点是:
除了 status,模块还用 ftrace hook 了 proc_task_name:
关键实现:
这个 hook 并不替代 proc_pid_status,而是补位:
对这些路径,fh_proc_task_name 可以直接把线程名输出改成:
因为有些样本并不只读 status,还会:
这时只拦 proc_pid_status 不一定够,proc_task_name 相当于多了一层兜底。
它和 proc_pid_status 的分工关系如下:

sub_1C26C 这类检测会扫 /proc/self/maps。
模块里对应的处理是 hook show_map_vma:
关键逻辑:
因为 /proc/<pid>/maps 的每一行最终都是内核把 vma 格式化成文本。
我们在输出层直接跳过匹配项,用户态样本看到的 maps 就不会包含:
这点需要说清楚。
show_map_vma 只能隐藏:
它不能隐藏:
所以它能绕过 sub_1C26C 这类 /proc/maps 检测,
但无法直接解决 sub_20BDC 那种 linker 私有结构遍历。
模块还在 proc_pid_status kretprobe 里顺手做了两件事:
这可以绕过另一类典型用户态反调试:
这正对应 so 另一条链路里的:
sub_20BDC 本质上不是 /proc 检测函数,而是:
可以把它理解成一个私有版:
根据 tenrec 分析后结果,可以整理成:
sub_20BDC 的危险点有两个:
这意味着:
这也解释了为什么:
根因不是“Frida 改坏了 linker 偏移”,而是:
这条“正常环境通常不崩、Frida 环境更容易崩”的差异,可以用下面这张图概括:

从当前分析可以确认,sub_20BDC 至少有两条从初始化阶段可达的路径。
结合动态日志,当前更直接的崩溃短链是:
可以把这条崩溃链画成:

原因很简单:
也就是说:
因此,从对抗角度看:
**sec.so 的反调试并不是单点检测,而是一组层次化的保护:
本文给出的内核模块方案,核心价值在于:
这使得:
但同样需要明确边界:
因此在实战中,更合理的策略不是“只用一种手段”,而是分层处理:
这才是对抗 **sec.so 这类综合型反调试模块的更稳妥方案。
最后用一张总图收敛本文结论:

void scan_task_status_for_frida_threads(void) {
DIR *dir = opendir("/proc/self/task");
if (!dir)
return;
while ((de = readdir(dir)) != NULL) {
if (!is_digits(de->d_name))
continue;
char path[0x100];
snprintf(path, sizeof(path), "/proc/self/task/%s/status", de->d_name);
char buf[0x400] = {0};
if (!read_text_file(path, buf, sizeof(buf)))
continue;
if (strstr(buf, "gum-js-loop") || strstr(buf, "gmain")) {
_exit(0);
}
}
closedir(dir);
}
void scan_task_status_for_frida_threads(void) {
DIR *dir = opendir("/proc/self/task");
if (!dir)
return;
while ((de = readdir(dir)) != NULL) {
if (!is_digits(de->d_name))
continue;
char path[0x100];
snprintf(path, sizeof(path), "/proc/self/task/%s/status", de->d_name);
char buf[0x400] = {0};
if (!read_text_file(path, buf, sizeof(buf)))
continue;
if (strstr(buf, "gum-js-loop") || strstr(buf, "gmain")) {
_exit(0);
}
}
closedir(dir);
}
| 检测面 |
目标路径 |
模块对应处理 |
| 线程名 |
/proc/<pid>/task/<tid>/status |
proc_pid_status kretprobe |
| 任务名显示 |
/proc/.../task/... 相关 seq 输出 |
proc_task_name ftrace |
| 映射路径 |
/proc/<pid>/maps |
show_map_vma ftrace |
static int target_pid = 0;
static int target_uid = -1;
static char *hide_so[MAX_NAMES];
static int hide_so_cnt;
static int target_pid = 0;
static int target_uid = -1;
static char *hide_so[MAX_NAMES];
static int hide_so_cnt;
insmod hookmodule.ko hide_so="frida,gum,gmain,AGENT" debug=true
insmod hookmodule.ko hide_so="frida,gum,gmain,AGENT" debug=true
static struct kretprobe_wrap my_kretprobes[] = {
KRETPROBEHOOK(
kretprobe_ret_handler_porc_pid_status,
kretprobe_entry_handler_proc_pid_status,
sizeof(struct kretprobe_data),
"proc_pid_status",
true),
};
static struct kretprobe_wrap my_kretprobes[] = {
KRETPROBEHOOK(
kretprobe_ret_handler_porc_pid_status,
kretprobe_entry_handler_proc_pid_status,
sizeof(struct kretprobe_data),
"proc_pid_status",
true),
};
task = (struct task_struct *)regs->regs[3];
get_task_struct(task);
task_lock(task);
data->task = task;
data->original_ptrace = task->ptrace;
strscpy(data->original_comm, task->comm, TASK_COMM_LEN);
data->original_state = READ_ONCE(task->__state);
task = (struct task_struct *)regs->regs[3];
get_task_struct(task);
task_lock(task);
data->task = task;
data->original_ptrace = task->ptrace;
strscpy(data->original_comm, task->comm, TASK_COMM_LEN);
data->original_state = READ_ONCE(task->__state);
for (int i = 0; i < hide_so_cnt; i++) {
if (strstr(data->original_comm, hide_so[i])) {
strscpy(task->comm, REPLAE_COMM, TASK_COMM_LEN);
}
}
task->ptrace = 0;
for (int i = 0; i < hide_so_cnt; i++) {
if (strstr(data->original_comm, hide_so[i])) {
strscpy(task->comm, REPLAE_COMM, TASK_COMM_LEN);
}
}
task->ptrace = 0;
task_lock(task);
task->ptrace = data->original_ptrace;
if (strcmp(task->comm, REPLAE_COMM) == 0) {
memcpy(task->comm, data->original_comm, TASK_COMM_LEN);
}
task_unlock(task);
put_task_struct(task);
task_lock(task);
task->ptrace = data->original_ptrace;
if (strcmp(task->comm, REPLAE_COMM) == 0) {
memcpy(task->comm, data->original_comm, TASK_COMM_LEN);
}
task_unlock(task);
put_task_struct(task);
/proc/self/task/<tid>/status
/proc/self/task/<tid>/status
static struct ftrace_hook my_hooks[] = {
FTRACEHOOK("proc_task_name", fh_proc_task_name, &real_proc_task_name, true)
};
static struct ftrace_hook my_hooks[] = {
FTRACEHOOK("proc_task_name", fh_proc_task_name, &real_proc_task_name, true)
};
static asmlinkage void fh_proc_task_name(struct seq_file *m,
struct task_struct *p,
bool escape) {
if (!escape) {
char tcomm[64];
__get_task_comm(tcomm, sizeof(tcomm), p);
for (int i = 0; i < hide_so_cnt; i++) {
if (strstr(tcomm, hide_so[i])) {
const char *hide_str = "hidding";
strscpy(tcomm, hide_str, 64);
seq_printf(m, "%.64s", tcomm);
return;
}
}
}
real_proc_task_name(m, p, escape);
}
static asmlinkage void fh_proc_task_name(struct seq_file *m,
struct task_struct *p,
bool escape) {
if (!escape) {
char tcomm[64];
__get_task_comm(tcomm, sizeof(tcomm), p);
for (int i = 0; i < hide_so_cnt; i++) {
if (strstr(tcomm, hide_so[i])) {
const char *hide_str = "hidding";
strscpy(tcomm, hide_str, 64);
seq_printf(m, "%.64s", tcomm);
return;
}
}
}
real_proc_task_name(m, p, escape);
}
static struct ftrace_hook my_hooks[] = {
FTRACEHOOK("show_map_vma", fh_show_map_vma, &real_show_map_vma, true)
};
static struct ftrace_hook my_hooks[] = {
FTRACEHOOK("show_map_vma", fh_show_map_vma, &real_show_map_vma, true)
};
pathname = d_path(&vma->vm_file->f_path, path_buf, sizeof(path_buf));
for (int i = 0; i < hide_so_cnt; i++) {
if (strstr(pathname, hide_so[i])) {
return;
}
}
real_show_map_vma(m, vma);
pathname = d_path(&vma->vm_file->f_path, path_buf, sizeof(path_buf));
for (int i = 0; i < hide_so_cnt; i++) {
if (strstr(pathname, hide_so[i])) {
return;
}
}
real_show_map_vma(m, vma);
if (data->original_state == TASK_TRACED) {
WRITE_ONCE(task->__state, TASK_RUNNING);
}
task->ptrace = 0;
if (data->original_state == TASK_TRACED) {
WRITE_ONCE(task->__state, TASK_RUNNING);
}
task->ptrace = 0;
[ 7621.344945] hookmodule: Modified TracerPid for process 2055 to 0
[ 7621.345031] hookmodule: [SEQ:358] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:357)
[ 7621.345050] hookmodule: [SEQ:358] Restored TracerPid for process 2055 to 0
[ 7621.346354] hookmodule: orig getdents64 address is 00000000191697cc
[ 7621.346528] hookmodule: orig getdents64 address is 00000000191697cc
[ 7623.020180] hookmodule: [SEQ:359] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7623.020255] hookmodule: Modified TracerPid for process 13612 to 0
[ 7623.020571] hookmodule: [SEQ:360] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:359)
[ 7623.020614] hookmodule: [SEQ:360] Restored TracerPid for process 13612 to 0
[ 7623.020859] hookmodule: orig getdents64 address is 00000000191697cc
[ 7623.030068] hookmodule: orig getdents64 address is 00000000191697cc
[ 7624.322863] hookmodule: orig getdents64 address is 00000000191697cc
[ 7624.323171] hookmodule: orig getdents64 address is 00000000191697cc
[ 7624.342313] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.342400] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.342441] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.342486] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420313] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420355] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420376] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420396] hookmodule: Hiding target library frida form PID 13646 maps
[ 7625.677320] hookmodule: orig getdents64 address is 00000000191697cc
[ 7625.694435] hookmodule: orig getdents64 address is 00000000191697cc
[ 7629.417787] hookmodule: [SEQ:361] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7629.417859] hookmodule: Modified TracerPid for process 4725 to 0
[ 7629.418097] hookmodule: [SEQ:362] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7629.418108] hookmodule: [SEQ:363] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:361)
[ 7629.418160] hookmodule: [SEQ:363] Restored TracerPid for process 4725 to 0
[ 7629.418190] hookmodule: Modified TracerPid for process 4725 to 0
[ 7629.418328] hookmodule: [SEQ:364] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:362)
[ 7629.418367] hookmodule: [SEQ:364] Restored TracerPid for process 4725 to 0
[ 7629.422463] hookmodule: [SEQ:365] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7629.422506] hookmodule: Modified TracerPid for process 4725 to 0
[ 7629.422785] hookmodule: [SEQ:366] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:365)
[ 7629.422818] hookmodule: [SEQ:366] Restored TracerPid for process 4725 to 0
[ 7621.344945] hookmodule: Modified TracerPid for process 2055 to 0
[ 7621.345031] hookmodule: [SEQ:358] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:357)
[ 7621.345050] hookmodule: [SEQ:358] Restored TracerPid for process 2055 to 0
[ 7621.346354] hookmodule: orig getdents64 address is 00000000191697cc
[ 7621.346528] hookmodule: orig getdents64 address is 00000000191697cc
[ 7623.020180] hookmodule: [SEQ:359] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7623.020255] hookmodule: Modified TracerPid for process 13612 to 0
[ 7623.020571] hookmodule: [SEQ:360] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:359)
[ 7623.020614] hookmodule: [SEQ:360] Restored TracerPid for process 13612 to 0
[ 7623.020859] hookmodule: orig getdents64 address is 00000000191697cc
[ 7623.030068] hookmodule: orig getdents64 address is 00000000191697cc
[ 7624.322863] hookmodule: orig getdents64 address is 00000000191697cc
[ 7624.323171] hookmodule: orig getdents64 address is 00000000191697cc
[ 7624.342313] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.342400] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.342441] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.342486] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420313] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420355] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420376] hookmodule: Hiding target library frida form PID 13646 maps
[ 7624.420396] hookmodule: Hiding target library frida form PID 13646 maps
[ 7625.677320] hookmodule: orig getdents64 address is 00000000191697cc
[ 7625.694435] hookmodule: orig getdents64 address is 00000000191697cc
[ 7629.417787] hookmodule: [SEQ:361] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7629.417859] hookmodule: Modified TracerPid for process 4725 to 0
[ 7629.418097] hookmodule: [SEQ:362] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7629.418108] hookmodule: [SEQ:363] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:361)
[ 7629.418160] hookmodule: [SEQ:363] Restored TracerPid for process 4725 to 0
[ 7629.418190] hookmodule: Modified TracerPid for process 4725 to 0
[ 7629.418328] hookmodule: [SEQ:364] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:362)
[ 7629.418367] hookmodule: [SEQ:364] Restored TracerPid for process 4725 to 0
[ 7629.422463] hookmodule: [SEQ:365] KREPROBE ENTRY_HANDLER:proc_pid_status entry
[ 7629.422506] hookmodule: Modified TracerPid for process 4725 to 0
[ 7629.422785] hookmodule: [SEQ:366] KRETPROBE HANDLER :proc_pid_status return (entry was SEQ:365)
[ 7629.422818] hookmodule: [SEQ:366] Restored TracerPid for process 4725 to 0
void *find_loaded_module_by_name_via_solist(const char *name);
void *find_loaded_module_by_name_via_solist(const char *name);
void *find_loaded_module_by_name_via_solist(const char *name) {
if (!guard_initialized)
solist_head = get_linker_solist_head();
cur = solist_head;
if (!cur)
return 0;
found = 0;
sdk = *off_47FB8;
if (sdk <= 22) {
do {
if (strlen((char *)cur) <= 0x7F && strstr((char *)cur, name))
found = cur;
cur = *(void **)(cur + 176);
} while (cur);
return found;
}
if (sdk <= 25) {
do {
soname = *(const char **)(cur + 416);
if (soname && strstr(soname, name))
found = cur;
cur = *(void **)(cur + 48);
} while (cur);
return found;
}
if (sdk == 31) {
while (cur) {
if ((*(uint8_t *)(cur + 408) & 1) != 0)
soname = *(const char **)(cur + 424);
else
soname = (const char *)(cur + 409);
if (soname && strstr(soname, name))
found = cur;
cur = *(void **)(cur + 40);
}
return found;
}
if (sdk > 31) {
while (cur) {
if ((*(uint8_t *)(cur + 392) & 1) != 0)
soname = *(const char **)(cur + 408);
else
soname = (const char *)(cur + 393);
if (soname && strstr(soname, name))
found = cur;
cur = *(void **)(cur + 40);
}
return found;
}
do {
soname = *(const char **)(cur + 408);
if (soname && strstr(soname, name))
found = cur;
cur = *(void **)(cur + 40);
} while (cur);
return found;
}
void *find_loaded_module_by_name_via_solist(const char *name) {
if (!guard_initialized)
solist_head = get_linker_solist_head();
cur = solist_head;
if (!cur)
return 0;
found = 0;
sdk = *off_47FB8;
if (sdk <= 22) {
do {
if (strlen((char *)cur) <= 0x7F && strstr((char *)cur, name))
found = cur;
cur = *(void **)(cur + 176);
} while (cur);
return found;
}
if (sdk <= 25) {
do {
soname = *(const char **)(cur + 416);
if (soname && strstr(soname, name))
found = cur;
cur = *(void **)(cur + 48);
} while (cur);
return found;
}
if (sdk == 31) {
while (cur) {
if ((*(uint8_t *)(cur + 408) & 1) != 0)
soname = *(const char **)(cur + 424);
else
soname = (const char *)(cur + 409);
if (soname && strstr(soname, name))
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 6天前
被nuoen编辑
,原因: