Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions app/src/main/cpp/detector/root_detector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const std::vector<std::string>& RootDetector::getSuPaths() {
const std::vector<std::string>& RootDetector::getMagiskPaths() {
static std::vector<std::string> paths = {
"/sbin/.magisk",
"/sbin/.magisk/mirror",
"/sbin/.magisk/block",
"/sbin/.core", // Legacy Magisk (v15-v18) core directory
"/sbin/.core/mirror", // Legacy Magisk mirror mount
"/sbin/.core/img", // Legacy Magisk module image
"/data/adb/magisk",
"/data/adb/magisk.img",
"/data/adb/magisk.db",
Expand Down Expand Up @@ -190,6 +195,8 @@ bool RootDetector::checkSuspiciousMountsNative() {
// Note: overlay and tmpfs are normal system mounts, don't check them
const std::vector<std::string> suspiciousPatterns = {
"magisk",
"/sbin/.magisk/", // Magisk working directory
"/sbin/.core/", // Legacy Magisk (v15-v18) core directory
"zygisk",
"zygisksu",
"kernelsu",
Expand Down Expand Up @@ -321,6 +328,8 @@ bool RootDetector::checkSuspiciousMountsSyscall() {
// Note: overlay and tmpfs are normal system mounts, don't check them
const std::vector<std::string> suspiciousPatterns = {
"magisk",
"/sbin/.magisk/", // Magisk working directory
"/sbin/.core/", // Legacy Magisk (v15-v18) core directory
"zygisk",
"zygisksu",
"zygisk_su",
Expand Down Expand Up @@ -388,6 +397,81 @@ bool RootDetector::checkBuildTags() {
return content.find("ro.build.tags=test-keys") != std::string::npos;
}

// ===================== /proc/mounts Magisk Detection =====================
// Read /proc/<pid>/mounts via direct syscall, search for Magisk signatures

bool RootDetector::checkMountsForMagiskNative() {
std::ifstream file("/proc/self/mounts");
if (!file.is_open()) return false;

std::string line;
while (std::getline(file, line)) {
if (line.find("/sbin/.magisk/") != std::string::npos ||
line.find("magisk") != std::string::npos ||
line.find("/sbin/.core/") != std::string::npos) {
LOGD("Magisk mount signature found (native): %s", line.c_str());
return true;
}
}
return false;
}

bool RootDetector::checkMountsForMagiskSyscall() {
std::string content = syscall_read_file("/proc/self/mounts", 32768);
if (content.empty()) return false;

if (content.find("/sbin/.magisk/") != std::string::npos ||
content.find("magisk") != std::string::npos ||
content.find("/sbin/.core/") != std::string::npos) {
LOGD("Magisk mount signature found (syscall)");
return true;
}
return false;
}

// ===================== Zygote Context Detection =====================
// Read /proc/<pid>/attr/prev, search for "zygote" in SELinux context
// Normal Android app processes are forked from zygote, so attr/prev
// should contain the zygote's SELinux context (e.g. "u:r:zygote:s0")

bool RootDetector::checkZygoteContextNative() {
std::ifstream file("/proc/self/attr/prev");
if (!file.is_open()) return false;

std::string content;
std::getline(file, content);

// Normal: should contain "zygote" (e.g. "u:r:zygote:s0")
if (content.find("zygote") != std::string::npos) {
return false; // Normal - process was forked from zygote
}

// Abnormal: attr/prev does not contain zygote context
// This may indicate the process was spawned abnormally
LOGD("Abnormal zygote context (native): %s", content.c_str());
return true;
}

bool RootDetector::checkZygoteContextSyscall() {
std::string content = syscall_read_file("/proc/self/attr/prev", 256);

// If we can't read attr/prev, try attr/current as fallback reference
if (content.empty()) {
content = syscall_read_file("/proc/self/attr/current", 256);
}

if (content.empty()) return false;

// Normal: should contain "zygote" (e.g. "u:r:zygote:s0")
if (content.find("zygote") != std::string::npos) {
return false; // Normal - process was forked from zygote
}

// Abnormal: context does not contain zygote
LOGD("Abnormal zygote context (syscall): %s", content.c_str());
return true;
}

bool RootDetector::checkSelinuxStatus() {
std::string content = syscall_read_file("/sys/fs/selinux/enforce");
return !content.empty() && content[0] == '0';
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/cpp/detector/root_detector.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ class RootDetector {
static bool checkRootHidingSyscall();
static bool checkSuspiciousMountsSyscall();

// /proc/mounts Magisk signature detection
static bool checkMountsForMagiskNative();
static bool checkMountsForMagiskSyscall();

// Zygote SELinux context detection via /proc/self/attr/prev
static bool checkZygoteContextNative();
static bool checkZygoteContextSyscall();

// System property checks
static bool checkBuildTags();
static bool checkSelinuxStatus();
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/cpp/native-lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,28 @@ Java_com_xff_launch_detector_NativeDetector_checkMountInfoSyscall(JNIEnv *env, j
return RootDetector::checkMountInfoSyscall();
}

// /proc/mounts Magisk signature detection
JNIEXPORT jboolean JNICALL
Java_com_xff_launch_detector_NativeDetector_checkMountsForMagiskNative(JNIEnv *env, jobject thiz) {
return RootDetector::checkMountsForMagiskNative();
}

JNIEXPORT jboolean JNICALL
Java_com_xff_launch_detector_NativeDetector_checkMountsForMagiskSyscall(JNIEnv *env, jobject thiz) {
return RootDetector::checkMountsForMagiskSyscall();
}

// Zygote SELinux context detection via /proc/self/attr/prev
JNIEXPORT jboolean JNICALL
Java_com_xff_launch_detector_NativeDetector_checkZygoteContextNative(JNIEnv *env, jobject thiz) {
return RootDetector::checkZygoteContextNative();
}

JNIEXPORT jboolean JNICALL
Java_com_xff_launch_detector_NativeDetector_checkZygoteContextSyscall(JNIEnv *env, jobject thiz) {
return RootDetector::checkZygoteContextSyscall();
}

// ===================== Hook Detection =====================

JNIEXPORT jboolean JNICALL
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/xff/launch/detector/NativeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ public class NativeDetector {
public native boolean checkMountInfoNative();
public native boolean checkMountInfoSyscall();

/** Check /proc/self/mounts for Magisk mount signatures via direct syscall */
public native boolean checkMountsForMagiskNative();
public native boolean checkMountsForMagiskSyscall();

/** Check /proc/self/attr/prev for zygote SELinux context anomaly */
public native boolean checkZygoteContextNative();
public native boolean checkZygoteContextSyscall();

// ===================== Hook Detection =====================

public native boolean checkXposedNative();
Expand Down
79 changes: 79 additions & 0 deletions app/src/main/java/com/xff/launch/detector/RootDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,83 @@ public DetectionItem detectSuspiciousMounts() {
return item;
}

/**
* Detect Magisk mount signatures in /proc/self/mounts
* Uses direct syscall to bypass libc hooks
*/
public DetectionItem detectMountsForMagisk() {
DetectionItem item = new DetectionItem("Mounts Magisk 签名",
"通过 /proc/mounts 搜索 Magisk 挂载特征");

// Native layer
boolean nativeResult = nativeDetector.checkMountsForMagiskNative();
item.setLayerResult(DetectionLayer.NATIVE, nativeResult);

// Syscall layer
boolean syscallResult = nativeDetector.checkMountsForMagiskSyscall();
item.setLayerResult(DetectionLayer.SYSCALL, syscallResult);

if (item.getMostTrustworthyResult()) {
item.setStatus(DetectionStatus.RISK);
item.setDetail("检测到 Magisk 挂载签名");
item.addDetectionDetail("💾 /proc/mounts", "Magisk 挂载特征",
"搜索词: /sbin/.magisk/, magisk, /sbin/.core/",
DetectionLayer.SYSCALL, "🔍");
} else {
item.setStatus(DetectionStatus.SAFE);
item.setDetail("未检测到");
}

if (item.hasInconsistentResults()) {
item.setDetail(item.getDetail() + " (检测层不一致)");
}

return item;
}

/**
* Detect zygote SELinux context anomaly via /proc/self/attr/prev
* Uses direct syscall to bypass libc hooks
* Normal app processes should have "zygote" in their prev SELinux context
*/
public DetectionItem detectZygoteContext() {
DetectionItem item = new DetectionItem("Zygote 上下文检测",
"通过 /proc/self/attr/prev 检测 Zygote SELinux 上下文");

// Native layer
boolean nativeResult = nativeDetector.checkZygoteContextNative();
item.setLayerResult(DetectionLayer.NATIVE, nativeResult);

// Syscall layer
boolean syscallResult = nativeDetector.checkZygoteContextSyscall();
item.setLayerResult(DetectionLayer.SYSCALL, syscallResult);

if (item.getMostTrustworthyResult()) {
item.setStatus(DetectionStatus.WARNING);
item.setDetail("Zygote 上下文异常");
item.addDetectionDetail("🔑 SELinux 上下文", "/proc/self/attr/prev",
"正常应包含 \"zygote\" (如 u:r:zygote:s0)\n" +
"上下文中未找到 zygote 标识",
DetectionLayer.SYSCALL, "⚠️");
} else {
item.setStatus(DetectionStatus.SAFE);
item.setDetail("Zygote 上下文正常");

// 显示当前 SELinux context 作为参考
String context = nativeDetector.getSELinuxContextNative();
if (context != null && !context.isEmpty()) {
item.addDetectionDetail("🔑 SELinux 上下文", "attr/prev",
"当前上下文: " + context, DetectionLayer.NATIVE, "✅");
}
}

if (item.hasInconsistentResults()) {
item.setDetail(item.getDetail() + " (检测层不一致)");
}

return item;
}

/**
* Get all root detection items
*/
Expand All @@ -347,6 +424,8 @@ public List<DetectionItem> getAllDetections() {
items.add(detectRootManagers());
items.add(detectRootHiding());
items.add(detectSuspiciousMounts());
items.add(detectMountsForMagisk());
items.add(detectZygoteContext());
return items;
}

Expand Down