安卓端总结监测总结

两只羊 Lv3

Root检测

BL锁检测

  1. 系统属性(Properties)检测

这是最简单但也最容易被伪装的层级。

  • ro.boot.flash.locked: 值为 1 表示锁定,0 表示解锁。
  • ro.boot.verifiedbootstate:
    • green: 锁定且签名验证通过。
    • orange: 已解锁(最常见的检测点)。
    • yellow: 加载了自定义证书。
    • red: 验证失败。
  • ro.secureboot.devicelock: 部分厂商(如华为、小米)自定义的锁定状态属性。
  1. 内核启动参数 (/proc/cmdline)

系统启动时,Bootloader 会将硬件状态传递给内核。

  • /proc/cmdline 中,通常包含 androidboot.verifiedbootstate=orangeandroidboot.flash.locked=0
  • 关键点:由于 /proc/cmdline 是内核导出的只读信息,传统的 setprop 无法修改它。
  1. 安全环境校验 (TEE / Keymaster)

这是目前最主流的硬件级检测

  • App 会生成一个密钥对,并要求 TEE(可信执行环境)对该密钥进行 Attestation(证书颁发)
  • TEE 会在证书中包含硬件状态信息。如果 BL 已解锁,TEE 签发的证书里会明确标注 SecurityLevelSoftware 或标注 Bootloader 状态。
  • 不可伪装性:因为证书是由 TEE 内置的私钥签名的,外部无法篡改。
  1. Play Integrity API (原 SafetyNet)

Google 的云端校验方案:

  • MEETS_DEVICE_INTEGRITY: 检查基本完整性(是否 Root/解锁)。
  • MEETS_STRONG_INTEGRITY: 终极杀招。它要求硬件级的 TEE 验证,只要 BL 解锁,绝无可能通过此项。

magisk

/proc/self/mounts查看挂载信息里是否有magisk的痕迹

kernelsu检测

分别收集__NR_faccessat和__NR_fchownat的执行时间,kernelsu会对这些函数进行hook

f59e77b3-3419-4385-a347-2cb19ca9751a

hook/调试检测

Frida端口扫描

Frida 在默认模式下运行(例如通过 frida-server 模式)时,会在设备上启动一个服务端,并默认监听 27042 和 27043 端口,用于与 PC 端的 Frida 客户端进行通信。

Maps记录检测

读取/proc/self/maps,检测程序是否加载了frida相关的库,最常见的就是frida-agent.so。

为了对应魔改,还会检测是否存在未命名且具有 r-xp(可读可执行)权限的大段内存

fd检测

遍历 /proc/self/fd/ 目录,并通过 readlink 获取每个 fd 指向的真实目标。

检测是否存在指向 Frida 特有文件的描述符,例如包含 linjector 关键字的路径,或者异常的 Socket 链接(用于与 frida-server 交互)。

Trace状态检查

读取 /proc/self/status 文件,检查其中的 TracerPid 字段。

在正常运行状态下,TracerPid 的值应为 0。如果该值大于 0,说明当前 App 正在被另一个进程(很可能就是 Frida、调试器或注入工具)附加和调试。

父子进程检测

fork出一个子进程,然后attach,像frida的attach模式,调试器附加,都没办法再生效了。

线程检测

App 遍历 /proc/self/task/ 目录(或者 /proc/self/status 文件),读取各个线程的名称(通过读取 comm 或 status 文件)。

寻找特定的特征线程名,例如:gmain、gdbus、gum-js-loop、pool-frida 等。

1
2
3
2026-03-04 13:03:09.451 11077-11077 FridaDetect             twogoat.checker2                     E  Thread Detection: gmain 
2026-03-04 13:03:09.451 11077-11077 FridaDetect twogoat.checker2 E Thread Detection: gdbus
2026-03-04 13:03:09.451 11077-11077 FridaDetect twogoat.checker2 E Thread Detection: pool-frida

内存校验

通常采用crc校验等对一段代码段校验完整性,或者检查函数头是否有inline hook的痕迹

为了防止被stackplz等工具检测到,可以使用process_vm_readv系统调用来把内存复制到缓冲区

ArtMethod检测

通过解析java方法的access_flags和entry_point_from_quick_compiled_code是否遭到篡改检测

多线程检测

由于上面很多检测都需要实时检测,不可避免的要用到多线程,因此用pthread_create是最常见的做法,但也是容易直接被替换为一个空函数直接逃过所有检测

因此现在很常见的做法是使用更底层的clone函数创建线程

或者在线程里用系统调用实时更新一个全局变量的心跳,在另一边,不容易被发现的地方,比如在godot游戏引擎中,创建一个实时刷新的函数,检测自己获取的时间与心跳的差值,同时可以防止反调试

这里还有思路就是多线程一直解锁,业务层一直上锁,但是这里不能用互斥锁,因为规定一个mutex不能在当前线程被lock,在另一个线程unlock,不允许跨线程操作

这里使用了信号量,敢替换sem_wait直接阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <jni.h>
#include <string>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <android/log.h>
#include <stdlib.h>

sem_t g_security_gate;


bool perform_heavy_check() {

return true;
}

void* detection_thread_entry(void* arg) {
LOGD("Detection thread started.");
while (true) {
if (perform_heavy_check()) {
int val;
sem_getvalue(&g_security_gate, &val);
if (val < 1) {
sem_post(&g_security_gate);
}
} else {
LOGE("Frida Detected! Application Terminating...");
}

usleep(500000);
}
}

void createCheckThread() {
if (sem_init(&g_security_gate, 0, 0) != 0) {
LOGE("Semaphore init failed!");
return;
}

pthread_t t;
if (pthread_create(&t, nullptr, detection_thread_entry, nullptr) != 0) {
LOGE("Failed to create detection thread!");
}
pthread_detach(t);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_twogoat_opsu3_fridachecker_MainActivity_getText(JNIEnv *env, jobject thiz, jstring text) {

LOGD("Business: getText called. Checking security gate...");

sem_wait(&g_security_gate);

return text;
}

不过要注意sem_wait会阻塞当前线程,所以最好不要在主线程阻塞,不然整个程序就真的卡死了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void registerButton()
{
Button button = findViewById(R.id.button);
TextView editText = findViewById(R.id.editTextText);
TextView sampleText = findViewById(R.id.sample_text);
button.setOnClickListener(v -> {
String inputText = editText.getText().toString();


new Thread(() -> {
try {

String result = getText(inputText);
runOnUiThread(() -> sampleText.setText(result));

} catch (Exception e) {
e.printStackTrace();
}
}).start();
});
}

流量检测

frida server与agent之间通过dbus协议传输,server与rpc之间通过websocket传输,经过抓包发现都是明文且特征明显,所以可以hook相关的网络协议函数,进行侦测

Trampoline特征查找

Frida生成的匿名函数,前面都是固定的机器码,是很多对寄存器的操作,可以直接在一些可疑的匿名函数段尝试暴力匹配

  • 标题: 安卓端总结监测总结
  • 作者: 两只羊
  • 创建于 : 2026-05-06 11:27:39
  • 更新于 : 2026-05-12 10:44:56
  • 链接: https://twogoat.github.io/2026/05/06/安卓端总结监测总结/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论