Android 下通过触发 SIGTRAP 信号实现反调试
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
详细的 Linux 信号列表
Linux 信号是一种用于进程间通信(IPC)和异常处理的机制。以下是详细的 Linux 信号列表,包含信号名称、默认行为及用途
信号编号 | 信号名称 | 默认行为 | 说明 |
---|---|---|---|
1 | SIGHUP | 终止进程 | 终止控制终端或配置文件发生变化时发出。 |
2 | SIGINT | 终止进程 | 来自用户输入(通常是 Ctrl+C)。 |
3 | SIGQUIT | 终止进程并生成core文件 | 来自用户输入(通常是 Ctrl+\)。 |
4 | SIGILL | 终止进程并生成core文件 | 非法指令执行。 |
5 | SIGTRAP | 终止进程并生成core文件 | 调试陷阱。 |
6 | SIGABRT | 终止进程并生成core文件 | 调用 abort 函数时发出。 |
7 | SIGBUS | 终止进程并生成core文件 | 总线错误(内存访问不对齐)。 |
8 | SIGFPE | 终止进程并生成core文件 | 浮点运算错误。 |
9 | SIGKILL | 终止进程 | 强制终止进程,无法被捕获或忽略。 |
10 | SIGUSR1 | 终止进程 | 用户自定义信号 1。 |
11 | SIGSEGV | 终止进程并生成core文件 | 无效的内存访问。 |
12 | SIGUSR2 | 终止进程 | 用户自定义信号 2。 |
13 | SIGPIPE | 终止进程 | 向没有读取端的管道写入数据时发出。 |
14 | SIGALRM | 终止进程 | 由 alarm 函数发出的定时器信号。 |
15 | SIGTERM | 终止进程 | 请求终止进程,可以被捕获和忽略。 |
16 | SIGSTKFLT | 终止进程 | 协处理器栈错误。 |
17 | SIGCHLD | 忽略 | 子进程停止或终止时发出。 |
18 | SIGCONT | 继续执行 | 让停止的进程继续运行。 |
19 | SIGSTOP | 停止进程 | 停止进程,无法被捕获或忽略。 |
20 | SIGTSTP | 停止进程 | 来自用户输入的停止信号(通常是 Ctrl+Z)。 |
21 | SIGTTIN | 停止进程 | 后台进程尝试从终端读取输入时发出。 |
22 | SIGTTOU | 停止进程 | 后台进程尝试向终端写入输出时发出。 |
23 | SIGURG | 忽略 | 套接字有紧急数据到达时发出。 |
24 | SIGXCPU | 终止进程 | 超出 CPU 时间限制。 |
25 | SIGXFSZ | 终止进程 | 超出文件大小限制。 |
26 | SIGVTALRM | 终止进程 | 虚拟时钟信号,由 |
27 | SIGPROF | 终止进程 | 定时器到期,由 |
28 | SIGWINCH | 忽略 | 终端窗口大小改变时发出。 |
29 | SIGIO | 忽略 | I/O 事件发生时发出。 |
30 | SIGPWR | 终止进程 | 电源故障时发出。 |
31 | SIGSYS | 终止进程并生成core文件 | 非法的系统调用。 |
此外,Linux 还支持实时信号(Real-Time Signals),编号从 32 开始,通常是用于用户自定义的信号,应用程序可根据需要使用这些信号。
利用 SIGTRAP 检测调试器
SIGTRAP 是一个陷阱信号,通常由调试器在调试被调试程序时触发。
如果一个程序主动触发 SIGTRAP 信号,并且信号处理函数被成功调用,则意味着没有调试器存在,因为信号并未被拦截。
反之,如果信号处理函数没有被调用,则意味着调试器捕获并处理了该信号,从而可以推测出程序正处于被调试状态。
Android下反调试的流程
1. 我们通过 JNI 调用 C 代码来设置一个 SIGTRAP 信号处理器。
2. 当触发 SIGTRAP 时,信号处理器将会捕获信号。如果信号处理器成功处理了信号,则表明没有调试器。
3. 如果调试器存在,SIGTRAP 信号不会到达我们的信号处理器,程序将检测到这一情况并在 3 秒后退出。
Android下反调试实现
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 反调试” 按钮调用反调试功能,未检测到调试器,程序正常运行。
2. 调试状态下
通过 IDA Pro 附加到当前应用 关于如何使用 IDA Pro 动态调试 Android App 可以参考这篇文章【使用IDA Pro动态调试Android APP】
App 中点击 “SIGTRAP 反调试” 按钮调用反调试功能,SIGTRAP 信号 被 IDA Pro 调试器捕获。
触发程序反调试机制,程序在 3 秒后退出。