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

利用 SIGTRAP 检测调试器

在 Linux 的进程管理中,信号(Signal) 是调试器与被调试进程沟通的核心机制。断点、单步执行、进程暂停与恢复等操作,背后都依赖于特定信号的传递。

其中,SIGTRAP (陷阱信号)尤为特殊。通常它由调试器在调试过程中触发,例如执行断点指令或单步运行时都会引发 SIGTRAP。

如果一个程序主动触发 SIGTRAP,并设置了对应的信号处理函数,就能实现一种“自检”:

  • 若信号处理函数被成功调用,说明 SIGTRAP 没有被调试器截获,此时可以推断程序处于非调试状态

  • 若信号处理函数未被调用,反而被调试器捕获并消费,则意味着程序正被调试。

由此,利用 SIGTRAP 信号进行反调试检测 ,就成为 Android 应用中一种更隐蔽、更底层的安全防护手段。

常见 Linux 信号列表(基于 signal.h)

编号名称说明
1SIGHUP挂起信号,通常在控制终端关闭时发送给进程
2SIGINT中断信号,通常由 Ctrl+C 触发
3SIGQUIT退出信号,通常由 Ctrl+\ 触发,并生成 core dump
4SIGILL非法指令(非法 CPU 指令执行)
5SIGTRAP陷阱信号,主要用于调试(断点、单步)
6SIGABRT异常终止,通常由 abort() 触发
7SIGBUS总线错误(非法内存访问,例如未对齐访问)
8SIGFPE浮点异常(除零、溢出等算术错误)
9SIGKILL强制终止信号,无法被捕获或忽略
11SIGSEGV无效内存访问(段错误)
13SIGPIPE向无读端的管道写数据时触发
14SIGALRM定时器超时,由 alarm() 触发
15SIGTERM终止信号,可被捕获和处理
17SIGCHLD子进程状态发生变化时通知父进程
18SIGCONT继续执行(与 SIGSTOP 配合使用)
19SIGSTOP停止进程,无法被捕获或忽略
20SIGTSTP终端暂停信号,通常由 Ctrl+Z 触发
21SIGTTIN后台进程尝试从终端读输入时触发
22SIGTTOU后台进程尝试往终端写输出时触发
23SIGURGsocket 上的紧急数据到达
24SIGXCPU超过 CPU 时间限制
25SIGXFSZ文件大小超过限制
26SIGVTALRM虚拟时钟超时
27SIGPROFprofiling 定时器超时
28SIGWINCH窗口大小发生变化(终端调整大小)
29SIGIO / SIGPOLL异步 I/O 事件
30SIGPWR电源故障
31SIGSYS非法系统调用

此外,Linux 还支持 实时信号(Real-Time Signals) ,编号从 32 开始 ,具体上限依赖于系统实现(通常是 SIGRTMIN 到 SIGRTMAX),通常是用于用户自定义的信号,应用程序可根据需要使用这些信号。

Android 下利用 SIGTRAP 反调试流程

在 Android 环境中,利用 SIGTRAP 进行反调试的核心思路是:让程序主动触发 SIGTRAP,并观察信号是否能够进入我们自己定义的处理器。如果信号能被捕获,说明程序运行正常;如果信号被调试器拦截,则说明进程正处于被调试状态。

Android 下利用 SIGTRAP + 自定义 Handler 的反调试流程:

                ┌─────────────────────────────┐
                │   App 进程启动 (目标进程)   │
                └──────────────┬──────────────┘
                               │
                               ▼
                   ┌──────────────────────────┐
                   │ 注册 SIGTRAP Handler     │
                   │ sigaction(SIGTRAP, ...)  │
                   └───────────┬─────────────┘
                               │
                               ▼
                   ┌────────────────────┐
                   │ 调用 raise(SIGTRAP)│
                   └───────────┬────────┘
                               │
               ┌───────────────┼─────────────────┐
               │                                 │
   ┌───────────▼────────────┐        ┌───────────▼────────────┐
   │ 无调试器存在            │        │ 有调试器附加           │
   │ ──────────────          │        │ ──────────────         │
   │ 信号交由自定义 handler  │        │ 调试器捕获 SIGTRAP     │
   │ handler 正常执行        │        │ handler 未被调用       │
   └───────────┬────────────┘        └───────────┬────────────┘
               │                                 │
               ▼                                 ▼
       [App 判定未被调试]                [App 判定被调试]
               │                                 │
               ▼                                 ▼
 ┌───────────────────────────┐     ┌───────────────────────────┐
 │   正常运行 (继续业务逻辑) │     │ 采取对抗动作:             │
 │                           │     │  - exit() / 延时退出       │
 │                           │     │  - 触发异常崩溃           │
 │                           │     │  - 执行混淆/假逻辑        │
 └───────────────────────────┘     └───────────────────────────┘

Android 下利用 SIGTRAP 反调试实现

1. 定义信号处理器并检测调试器

首先,我们在 C 代码中编写反调试逻辑,核心是通过 raise(SIGTRAP) 触发 SIGTRAP 信号,并判断信号是否被捕获。

#include <jni.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/log.h>

#define LOG_TAG "AntiDebug"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// 标志变量,判断 SIGTRAP 是否被捕获
volatile int sigtrap_caught = 0;

// SIGTRAP 信号处理函数
void sigtrap_handler(int sig) {
    LOGI("Caught SIGTRAP. No debugger present.");
    sigtrap_caught = 1; // 标记 SIGTRAP 被捕获
}

// JNI 方法,触发 SIGTRAP 信号并检测调试器
JNIEXPORT jboolean JNICALL
Java_com_cyrus_example_antidebug_AntiDebug_detectDebugger(JNIEnv *env, jobject instance) {
    // 注册 SIGTRAP 处理器
    signal(SIGTRAP, sigtrap_handler);

    // 触发 SIGTRAP 信号
    raise(SIGTRAP);

    // 检查信号是否被捕获
    if (sigtrap_caught) {
        LOGI("No debugger detected.");
        return JNI_FALSE; // 没有检测到调试器
    } else {
        // 如果信号未被捕获,说明有调试器
        LOGI("Debugger detected! The program will exit in 3 seconds...");
        sleep(3); // 等待 3 秒
        exit(EXIT_FAILURE); // 退出程序
        return JNI_TRUE; // 返回 true,表示检测到调试器
    }
}

配置 CMakeLists.txt 文件

cmake_minimum_required(VERSION 3.4.1)



find_library( # 查找 log 库
              log-lib

              # 库名
              log )

add_library( # 库名称
             antidebug

             # 库类型
             SHARED

             # 源文件
             anti_debug.c )

target_link_libraries( # 绑定库到 log 库
                       antidebug
                       ${log-lib} )

2. Kotlin 层调用 JNI 方法

在 Kotlin 层,我们通过 JNI 调用 detectDebugger 函数,以检测是否存在调试器。根据结果,程序可以作出不同的响应。

package com.cyrus.example.antidebug

import android.util.Log

object AntiDebug {

    init {
        // 加载 native 库
        System.loadLibrary("antidebug")
    }

    external fun detectDebugger(): Boolean

    fun isDebuggerDetected(): Boolean {
        val detected = detectDebugger()
        if (detected) {
            Log.i("AntiDebug", "Debugger detected!")
        } else {
            Log.i("AntiDebug", "No debugger detected.")
        }
        return detected
    }
}

3. 调用反调试功能

val debuggerDetected = AntiDebug.isDebuggerDetected()
if (debuggerDetected) {
    Toast.makeText(this, "Debugger Detected", Toast.LENGTH_SHORT).show()
} else {
    Toast.makeText(this, "No Debugger Detected", Toast.LENGTH_SHORT).show()
}

测试反调试

1. 无调试状态

无调式状态 App 中点击 “SIGTRAP 反调试” 按钮调用反调试功能,未检测到调试器,程序正常运行。

word/media/image1.png

2. 调试状态下

通过 IDA Pro 附加到当前应用

word/media/image2.png

相关文章:静态分析根本不够!IDA Pro 动态调试 Android 应用的完整实战

App 中点击 “SIGTRAP 反调试” 按钮调用反调试功能,SIGTRAP 信号 被 IDA Pro 调试器捕获。

word/media/image3.png

触发程序反调试机制,程序在 3 秒后退出。

word/media/image4.png

完整源码

开源地址:https://github.com/CYRUS-STUDIO/AndroidExample