通过定制Linux内核规避Android 10反调试机制
Android平台的反调试技术概述
在移动安全领域,反调试是保护应用逻辑不被逆向分析的重要手段之一。尽管无法彻底阻止高级攻击者,但能显著增加动态分析的成本。目前主流的Android加固方案普遍集成了多种反调试策略,例如:利用ptrace系统调用实现自我附加、检测函数执行耗时异常、读取/proc/$pid/status中的TracerPid字段判断是否被追踪、分析/proc/$pid/stat状态标识,以及检查/proc/$pid/wchan内容来识别内核级调试行为。
本文聚焦于三种基于/proc文件系统的常见检测方式,并探讨如何通过修改Linux内核源码,使这些检测机制失效,从而绕过应用层的反调试逻辑。
/proc 文件系统检测原理剖析
1. 检测 /proc/$pid/status 中的状态信息
当一个进程处于被调试状态时,内核会在/proc/[pid]/status或其对应线程目录中写入特定标记:
- TracerPid:若该值非零,表示当前进程已被另一个调试器(如gdbserver或ptrace)附加。
- State:反映进程运行状态,其中'T'(stopped)和't'(tracing stop)明确指示进程因调试而暂停。
因此,许多加固App会周期性地读取此文件并解析上述字段,一旦发现异常即触发防御机制。
2. 分析 /proc/$pid/stat 字段
该文件第三项直接记录了进程状态字符。在调试场景下,该项同样会被设为T或t,成为轻量级检测依据。
3. 解析 /proc/$pid/wchan 内容
wchan用于显示进程阻塞时所处的内核函数名。当进程被ptrace控制时,此处通常会显示类似ptrace_stop的符号名称,暴露调试痕迹。
内核层面的绕过实现
相关逻辑位于Linux内核源码树的fs/proc/目录下。我们可通过定制化编译的方式,篡改关键输出以欺骗上层检测程序。
1. 伪造进程状态(State)
原始状态映射定义于fs/proc/array.c中的task_state_array数组:
static const char * const task_state_array[] = {
"R (running)",
"S (sleeping)",
"D (disk sleep)",
"T (stopped)",
"t (tracing stop)",
"X (dead)",
"Z (zombie)"
};
为规避检测,我们将代表调试状态的条目替换为普通睡眠状态:
static const char * const task_state_array[] = {
"R (running)",
"S (sleeping)",
"D (disk sleep)",
// "T (stopped)" → 被伪装成 S
"S (sleeping)", /* 状态码4 */
// "t (tracing stop)" → 同样伪装
"S (sleeping)", /* 状态码8 */
"X (dead)",
"Z (zombie)"
};
如此修改后,即使进程实际处于暂停调试状态,对外呈现的依然是"正在休眠",有效隐藏真实行为。
2. 清除 TracerPid 泄露
该值由task_state()函数生成,在同一源文件中可找到如下代码片段:
tpid = 0;
if (task->parent)
tpid = task_pid_nr_ns(task->parent, ns);
此处tpid即为将要输出到/proc/pid/status的TracerPid值。我们强制将其置零:
tpid = 0; // 始终返回0,无论是否被附加
这一改动确保任何进程查询自身状态时,均显示未被调试。
3. 隐蔽 wchan 调试痕迹
函数proc_pid_wchan负责填充/proc/$pid/wchan内容,其实现位于fs/proc/base.c:
#ifdef CONFIG_KALLSYMS
static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
unsigned long wchan;
char symname[KSYM_NAME_LEN];
wchan = get_wchan(task);
if (lookup_symbol_name(wchan, symname) < 0) {
if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
return 0;
else
return seq_printf(m, "%lu", wchan);
} else {
return seq_printf(m, "%s", symname);
}
}
#endif
为了防止ptrace_stop等敏感符号暴露,我们在输出前加入过滤逻辑:
else {
if (strstr(symname, "trace")) {
return seq_printf(m, "%s", "sys_epoll_wait"); // 使用常见系统调用名称混淆
}
return seq_printf(m, "%s", symname);
}
这样,所有包含"trace"关键字的内核函数名都会被替换为看似正常的sys_epoll_wait,极大降低被识别的风险。
构建与部署定制内核
完成上述修改后,进入Android开源项目(AOSP)源码根目录,执行以下命令进行编译:
source build/envsetup.sh
brunch oneplus3-userdebug
待镜像生成后,使用fastboot工具刷入设备即可启用具备反检测能力的新内核。