You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter如何检测Dart调试器是否附着 实现应用反调试防护

核心结论

可以在Dart层通过平台通道调用原生反调试能力,但不推荐仅在Dart层做检测——Dart运行在Flutter引擎沙箱内,很容易通过hook引擎消息通道绕过,可靠方案是原生层多节点埋点检测+Dart层辅助校验的组合实现,满足安全团队的预防性防护要求。

原有实现失效原因
  • 检测时机太晚:把逻辑写在MainActivity的onCreate/onResume生命周期里,等逻辑执行时调试器完全可以在Application初始化阶段就附着进程,甚至提前hook掉isDebuggerConnected()方法篡改返回值
  • 检测频率太低:仅在生命周期触发时做单次检测,根本捕获不到“应用启动后再附着调试器”的常见逆向场景
  • 检测覆盖不全:android.os.Debug.isDebuggerConnected()只能识别Java层标准调试,检测不到ptrace实现的native层调试附着
可落地方案

Android端核心防护(必须放在原生层实现)

  1. 把检测逻辑前置到Application.attachBaseContext()阶段,这是应用进程启动后最早执行的业务逻辑点,不要放在Activity生命周期内
  2. 不要依赖单一检测接口,做多维度交叉校验:
    • 轮询检测Debug.isDebuggerConnected()、Debug.waitingForDebugger()返回值
    • 读取/proc/self/status文件内的TracerPid字段,字段值非0即代表有进程附着调试
    • 补充native层自ptrace防护,比Java层接口更难绕过
  3. 检测到调试状态后直接调用Process.killProcess(Process.myPid())终止进程,不要用finish()、System.exit()这类容易被hook拦截的退出方式

参考实现代码:

class App : Application() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        // 启动独立守护线程做高频轮询,避免单点hook绕过
        Thread {
            while (true) {
                val tracerPid = runCatching {
                    File("/proc/self/status").readLines()
                        .first { it.startsWith("TracerPid:") }
                        .split("\\s+".toRegex())[1].toInt()
                }.getOrDefault(0)
                val isJavaDebug = Debug.isDebuggerConnected() || Debug.waitingForDebugger()
                if (tracerPid != 0 || isJavaDebug) {
                    Process.killProcess(Process.myPid())
                }
                Thread.sleep(100)
            }
        }.start()
    }
}

Dart层辅助检测

如果需要在Dart侧补充校验,直接通过MethodChannel调用原生层的检测逻辑即可,不要在Dart层自行实现检测规则——Dart层快照很容易被逆向工具直接篡改绕过。
Dart侧参考实现:

const antiDebugChannel = MethodChannel("com.example.antidebug");
// 应用启动后立刻启动全周期轮询,不要绑定单个页面生命周期
void initAntiDebug() {
  Timer.periodic(const Duration(milliseconds: 200), (timer) async {
    final isDebugged = await antiDebugChannel.invokeMethod<bool>("checkDebugStatus") ?? false;
    if (isDebugged) {
      exit(0);
    }
  });
}

原生侧MethodChannel handler直接返回前面多维度检测的结果即可,不要只返回isDebuggerConnected()的单接口结果。

iOS端对应实现

在application:didFinishLaunchingWithOptions最早执行阶段调用ptrace接口,传入PT_DENY_ATTACH参数阻止调试器附着,同样建议补充native层轮询检测,避免被绕过。
参考代码:

#import <ptrace.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ptrace(PT_DENY_ATTACH, 0, 0, 0);
    // 补充其他反调试校验逻辑
    return YES;
}
优化注意点
  • 所有核心检测逻辑建议下沉到native层实现,配合代码混淆、加壳处理,避免被逆向者快速定位到检测点直接patch绕过
  • 不要做单次检测,必须覆盖应用全运行周期做高频轮询
  • 不要把反调试能力完全放在Dart层,Dart层仅做辅助校验即可

内容的提问来源于stack exchange,提问作者Antonycx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 21:03:20