From ed1fd7d4844287076496003d6e36ff5d7442489a Mon Sep 17 00:00:00 2001 From: ZnDong <81907400+ZnDong@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:49:53 +0800 Subject: [PATCH] feat(hook): add missing anti-debug and anti-Frida detection methods - Add /proc/net/tcp hex port scanning for IDA (0x5D8A=23946) and Frida (0x69A2=27042) - Add linjector thread name detection in checkFridaThreads() via both comm and syscall status - Add frida-agent-32/frida-agent-64 signatures to memory maps scanning - Add /proc/self/fd linjector pipe scanning via syscall(readlinkat) - Add linjector to suspicious FD keywords in native-lib.cpp - Integrate new detections into detectFrida() combined result and JNI layer - Add checkIdaPortTcp/checkFridaPortTcp/checkFridaFdLinjector native methods - Update Java-side collectFridaDetails() with TCP port and FD linjector details --- app/src/main/cpp/detector/hook_detector.cpp | 181 +++++++++++++++++- app/src/main/cpp/detector/hook_detector.h | 11 +- app/src/main/cpp/native-lib.cpp | 37 +++- .../xff/launch/detector/DebugDetector.java | 12 ++ .../com/xff/launch/detector/HookDetector.java | 44 ++++- .../xff/launch/detector/NativeDetector.java | 14 ++ 6 files changed, 286 insertions(+), 13 deletions(-) diff --git a/app/src/main/cpp/detector/hook_detector.cpp b/app/src/main/cpp/detector/hook_detector.cpp index 69777d0..0610290 100644 --- a/app/src/main/cpp/detector/hook_detector.cpp +++ b/app/src/main/cpp/detector/hook_detector.cpp @@ -6,12 +6,24 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include + +// linux_dirent64 structure definition (not directly exposed by NDK) +struct linux_dirent64 { + uint64_t d_ino; + int64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[0]; +}; #define LOG_TAG "HookDetector" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) @@ -116,7 +128,10 @@ bool HookDetector::checkFridaMemoryNative() { if (line.find("frida") != std::string::npos || line.find("gadget") != std::string::npos || line.find("gum-js-loop") != std::string::npos || - line.find("LIBFRIDA") != std::string::npos) { + line.find("LIBFRIDA") != std::string::npos || + line.find("frida-agent-32") != std::string::npos || + line.find("frida-agent-64") != std::string::npos || + line.find("frida-agent") != std::string::npos) { LOGD("Frida memory signature found (native): %s", line.c_str()); return true; } @@ -281,7 +296,10 @@ bool HookDetector::checkFridaMemorySyscall() { if (content.find("frida") != std::string::npos || content.find("gadget") != std::string::npos || content.find("gum-js-loop") != std::string::npos || - content.find("LIBFRIDA") != std::string::npos) { + content.find("LIBFRIDA") != std::string::npos || + content.find("frida-agent-32") != std::string::npos || + content.find("frida-agent-64") != std::string::npos || + content.find("frida-agent") != std::string::npos) { LOGD("Frida memory signature found (syscall)"); return true; } @@ -418,17 +436,166 @@ bool HookDetector::checkFridaThreads() { if (threadName.find("gmain") != std::string::npos || threadName.find("gdbus") != std::string::npos || threadName.find("gum-js-loop") != std::string::npos || - threadName.find("pool-frida") != std::string::npos) { + threadName.find("pool-frida") != std::string::npos || + threadName.find("linjector") != std::string::npos) { LOGD("Frida thread found: %s", threadName.c_str()); closedir(dir); return true; } } + + // Also read Name field from /proc/self/task//status via syscall + // This approach is harder to intercept by hooking than reading comm + std::string tidStatusPath = "/proc/self/task/" + std::string(entry->d_name) + "/status"; + std::string statusContent = syscall_read_file(tidStatusPath.c_str(), 1024); + if (!statusContent.empty()) { + // Find the "Name:" line + size_t namePos = statusContent.find("Name:"); + if (namePos != std::string::npos) { + size_t lineEnd = statusContent.find('\n', namePos); + std::string nameLine = statusContent.substr(namePos + 5, + lineEnd != std::string::npos ? lineEnd - namePos - 5 : std::string::npos); + // Trim leading/trailing whitespace + size_t start = nameLine.find_first_not_of(" \t"); + if (start != std::string::npos) { + nameLine = nameLine.substr(start); + } + if (nameLine.find("linjector") != std::string::npos || + nameLine.find("gmain") != std::string::npos) { + LOGD("Frida thread found via syscall status: %s (tid: %s)", + nameLine.c_str(), entry->d_name); + closedir(dir); + return true; + } + } + } } closedir(dir); return false; } +// ===================== /proc/net/tcp IDA Port Scanning ===================== + +/** + * Detect IDA Pro android_server port (23946 = 0x5D8A) by parsing /proc/net/tcp + * Uses libc file I/O + */ +bool HookDetector::checkIdaPortTcpNative() { + std::ifstream tcp("/proc/net/tcp"); + if (!tcp.is_open()) return false; + + std::string line; + while (std::getline(tcp, line)) { + // /proc/net/tcp format: sl local_address rem_address ... + // local_address format: IP:PORT (hex) + // IDA default port 23946 = 0x5D8A + if (line.find(":5D8A") != std::string::npos) { + LOGD("IDA port 23946 (0x5D8A) found in /proc/net/tcp (native): %s", line.c_str()); + return true; + } + } + return false; +} + +/** + * Detect IDA port by reading /proc/net/tcp via syscall + * Uses direct syscall to bypass libc hooks + */ +bool HookDetector::checkIdaPortTcpSyscall() { + std::string content = syscall_read_file("/proc/net/tcp", 65536); + if (content.empty()) return false; + + if (content.find(":5D8A") != std::string::npos) { + LOGD("IDA port 23946 (0x5D8A) found in /proc/net/tcp (syscall)"); + return true; + } + return false; +} + +/** + * Detect Frida default port (27042 = 0x69A2) by parsing /proc/net/tcp + * Uses libc file I/O + */ +bool HookDetector::checkFridaPortTcpNative() { + std::ifstream tcp("/proc/net/tcp"); + if (!tcp.is_open()) return false; + + std::string line; + while (std::getline(tcp, line)) { + // Frida default port 27042 = 0x69A2 + // Frida alternate port 27043 = 0x69A3 + if (line.find(":69A2") != std::string::npos || + line.find(":69A3") != std::string::npos) { + LOGD("Frida port found in /proc/net/tcp (native): %s", line.c_str()); + return true; + } + } + return false; +} + +/** + * Detect Frida port by reading /proc/net/tcp via syscall + */ +bool HookDetector::checkFridaPortTcpSyscall() { + std::string content = syscall_read_file("/proc/net/tcp", 65536); + if (content.empty()) return false; + + if (content.find(":69A2") != std::string::npos || + content.find(":69A3") != std::string::npos) { + LOGD("Frida port found in /proc/net/tcp (syscall)"); + return true; + } + return false; +} + +// ===================== FD linjector Scanning ===================== + +/** + * Scan /proc/self/fd via syscall(readlinkat) for linjector + */ +bool HookDetector::checkFridaFdLinjectorSyscall() { + // Open /proc/self/fd directory using syscall + int dirFd = syscall(__NR_openat, AT_FDCWD, "/proc/self/fd", O_RDONLY | O_DIRECTORY); + if (dirFd < 0) return false; + + char buffer[4096]; + char linkPath[64]; + char targetPath[PATH_MAX]; + bool found = false; + + while (true) { + int nread = syscall(__NR_getdents64, dirFd, buffer, sizeof(buffer)); + if (nread <= 0) break; + + int pos = 0; + while (pos < nread) { + struct linux_dirent64* d = (struct linux_dirent64*)(buffer + pos); + + if (d->d_name[0] != '.') { + snprintf(linkPath, sizeof(linkPath), "/proc/self/fd/%s", d->d_name); + + ssize_t len = syscall(__NR_readlinkat, AT_FDCWD, linkPath, + targetPath, sizeof(targetPath) - 1); + if (len > 0) { + targetPath[len] = '\0'; + // Search for linjector (Frida injector pipe/file) + if (strstr(targetPath, "linjector") != nullptr) { + LOGD("Frida linjector FD found: %s -> %s", linkPath, targetPath); + found = true; + break; + } + } + } + + pos += d->d_reclen; + } + if (found) break; + } + + syscall(__NR_close, dirFd); + return found; +} + // Get detailed LSPosed injection information std::string HookDetector::getLSPosedDetails() { std::string details; @@ -1158,7 +1325,7 @@ bool HookDetector::checkZygiskSyscall() { LOGD("[6/7] 检测 Zygote 注入 (分析父进程)..."); std::string statContent = syscall_read_file("/proc/self/stat", 1024); if (!statContent.empty()) { - LOGD(" ├─ 成功读取 /proc/self/stat: %lu 字节", statContent.size()); + LOGD(" ├─ 成功读取 /proc/self/stat: %zu 字节", statContent.size()); // Parse PPID std::istringstream iss(statContent); @@ -1240,8 +1407,10 @@ MultiLayerResult HookDetector::detectFrida() { MultiLayerResult result; result.javaResult = false; result.nativeResult = checkFridaNative() || checkFridaPortsNative() || - checkFridaMemoryNative() || checkFridaThreads(); - result.syscallResult = checkFridaSyscall() || checkFridaMemorySyscall(); + checkFridaMemoryNative() || checkFridaThreads() || + checkFridaPortTcpNative(); + result.syscallResult = checkFridaSyscall() || checkFridaMemorySyscall() || + checkFridaPortTcpSyscall() || checkFridaFdLinjectorSyscall(); return result; } diff --git a/app/src/main/cpp/detector/hook_detector.h b/app/src/main/cpp/detector/hook_detector.h index ae6b5fe..eb93290 100644 --- a/app/src/main/cpp/detector/hook_detector.h +++ b/app/src/main/cpp/detector/hook_detector.h @@ -39,9 +39,18 @@ class HookDetector { static bool checkRiruZygiskSyscall(); static bool checkMapsForHooksSyscall(); - // Thread name detection for Frida + // Thread name detection for Frida (including linjector via syscall) static bool checkFridaThreads(); + // /proc/net/tcp port scanning (IDA & Frida) + static bool checkIdaPortTcpNative(); // IDA port 23946 (0x5D8A) + static bool checkIdaPortTcpSyscall(); + static bool checkFridaPortTcpNative(); // Frida port 27042 (0x69A2) + static bool checkFridaPortTcpSyscall(); + + // FD linjector scanning (Frida injector pipe detection) + static bool checkFridaFdLinjectorSyscall(); + // Get detailed info about LSPosed injection static std::string getLSPosedDetails(); diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index 7e227b2..951bf01 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -158,12 +158,14 @@ Java_com_xff_launch_detector_NativeDetector_checkXposedSyscall(JNIEnv *env, jobj JNIEXPORT jboolean JNICALL Java_com_xff_launch_detector_NativeDetector_checkFridaNative(JNIEnv *env, jobject thiz) { return HookDetector::checkFridaNative() || HookDetector::checkFridaPortsNative() || - HookDetector::checkFridaMemoryNative() || HookDetector::checkFridaThreads(); + HookDetector::checkFridaMemoryNative() || HookDetector::checkFridaThreads() || + HookDetector::checkFridaPortTcpNative(); } JNIEXPORT jboolean JNICALL Java_com_xff_launch_detector_NativeDetector_checkFridaSyscall(JNIEnv *env, jobject thiz) { - return HookDetector::checkFridaSyscall() || HookDetector::checkFridaMemorySyscall(); + return HookDetector::checkFridaSyscall() || HookDetector::checkFridaMemorySyscall() || + HookDetector::checkFridaPortTcpSyscall() || HookDetector::checkFridaFdLinjectorSyscall(); } JNIEXPORT jboolean JNICALL @@ -240,6 +242,33 @@ Java_com_xff_launch_detector_NativeDetector_checkZygiskSyscall(JNIEnv *env, jobj return HookDetector::checkZygiskSyscall(); } +// /proc/net/tcp port scanning (IDA & Frida) +JNIEXPORT jboolean JNICALL +Java_com_xff_launch_detector_NativeDetector_checkIdaPortTcpNative(JNIEnv *env, jobject thiz) { + return HookDetector::checkIdaPortTcpNative(); +} + +JNIEXPORT jboolean JNICALL +Java_com_xff_launch_detector_NativeDetector_checkIdaPortTcpSyscall(JNIEnv *env, jobject thiz) { + return HookDetector::checkIdaPortTcpSyscall(); +} + +JNIEXPORT jboolean JNICALL +Java_com_xff_launch_detector_NativeDetector_checkFridaPortTcpNative(JNIEnv *env, jobject thiz) { + return HookDetector::checkFridaPortTcpNative(); +} + +JNIEXPORT jboolean JNICALL +Java_com_xff_launch_detector_NativeDetector_checkFridaPortTcpSyscall(JNIEnv *env, jobject thiz) { + return HookDetector::checkFridaPortTcpSyscall(); +} + +// Frida FD linjector detection +JNIEXPORT jboolean JNICALL +Java_com_xff_launch_detector_NativeDetector_checkFridaFdLinjectorSyscall(JNIEnv *env, jobject thiz) { + return HookDetector::checkFridaFdLinjectorSyscall(); +} + // ===================== Emulator Detection ===================== JNIEXPORT jboolean JNICALL @@ -319,9 +348,9 @@ static const char* SUSPICIOUS_KEYWORDS[] = { "magisk", "su", "supersu", "superuser", "busybox", "ksu", "kernelsu", "apatch", "lsposed", "edxposed", "xposed", "riru", "zygisk", "shamiko", "hide", - "frida", "substrate", "cydia" + "frida", "substrate", "cydia", "linjector" }; -static const int SUSPICIOUS_KEYWORDS_COUNT = 18; +static const int SUSPICIOUS_KEYWORDS_COUNT = 19; static bool contains_suspicious(const std::string& path) { if (path.empty()) return false; diff --git a/app/src/main/java/com/xff/launch/detector/DebugDetector.java b/app/src/main/java/com/xff/launch/detector/DebugDetector.java index 0d11dd8..5fa1d45 100644 --- a/app/src/main/java/com/xff/launch/detector/DebugDetector.java +++ b/app/src/main/java/com/xff/launch/detector/DebugDetector.java @@ -547,6 +547,18 @@ private void collectJdwpDetails(DetectionItem item) { line.trim(), DetectionLayer.SYSCALL, "🔍"); } + // IDA Pro android_server 默认端口 23946 = 0x5D8A + if (line.contains(":5D8A")) { + item.addDetectionDetail("🌐 IDA 端口", "/proc/net/tcp", + "检测到 IDA Pro 端口 23946 (0x5D8A): " + line.trim(), + DetectionLayer.JAVA, "🔌"); + } + // Frida 默认端口 27042 = 0x69A2, 27043 = 0x69A3 + if (line.contains(":69A2") || line.contains(":69A3")) { + item.addDetectionDetail("🌐 Frida 端口", "/proc/net/tcp", + "检测到 Frida 端口: " + line.trim(), + DetectionLayer.JAVA, "🔌"); + } } reader.close(); } catch (Exception ignored) { diff --git a/app/src/main/java/com/xff/launch/detector/HookDetector.java b/app/src/main/java/com/xff/launch/detector/HookDetector.java index 59a2fff..774aede 100644 --- a/app/src/main/java/com/xff/launch/detector/HookDetector.java +++ b/app/src/main/java/com/xff/launch/detector/HookDetector.java @@ -1997,7 +1997,10 @@ private void collectFridaDetails(DetectionItem item) { String lowerLine = line.toLowerCase(); if (lowerLine.contains("frida") || lowerLine.contains("gum-js-loop") || - lowerLine.contains("frida-agent")) { + lowerLine.contains("frida-agent") || + lowerLine.contains("frida-agent-32") || + lowerLine.contains("frida-agent-64") || + lowerLine.contains("linjector")) { fridaMaps++; if (fridaMaps <= 3) { String[] parts = line.split("\\s+"); @@ -2035,7 +2038,8 @@ private void collectFridaDetails(DetectionItem item) { (threadName.contains("gmain") || threadName.contains("gdbus") || threadName.contains("gum-js-loop") || - threadName.contains("pool-frida"))) { + threadName.contains("pool-frida") || + threadName.contains("linjector"))) { item.addDetectionDetail("🧵 Frida 线程", threadName, "TID: " + task.getName() + "\n线程名: " + threadName, DetectionLayer.NATIVE, "🔗"); @@ -2046,5 +2050,41 @@ private void collectFridaDetails(DetectionItem item) { } } catch (Exception ignored) { } + + // 检测 /proc/net/tcp 中的 Frida/IDA 端口 (十六进制扫描) + try { + java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.FileReader("/proc/net/tcp")); + String line; + while ((line = reader.readLine()) != null) { + // Frida 默认端口 27042 = 0x69A2 + if (line.contains(":69A2") || line.contains(":69A3")) { + item.addDetectionDetail("🌐 Frida TCP 端口", "/proc/net/tcp", + "检测到 Frida 端口 (十六进制扫描): " + line.trim(), + DetectionLayer.JAVA, "🔌"); + } + } + reader.close(); + } catch (Exception ignored) { + } + + // 检测 FD 中的 linjector (Frida 注入器管道) + try { + java.io.File fdDir = new java.io.File("/proc/self/fd"); + String[] fds = fdDir.list(); + if (fds != null) { + for (String fd : fds) { + try { + String target = nativeDetector.readlinkSyscall("/proc/self/fd/" + fd); + if (target != null && target.contains("linjector")) { + item.addDetectionDetail("📁 Frida 注入器", "FD linjector", + "FD " + fd + " -> " + target, + DetectionLayer.SYSCALL, "💉"); + } + } catch (Exception ignored) {} + } + } + } catch (Exception ignored) { + } } } diff --git a/app/src/main/java/com/xff/launch/detector/NativeDetector.java b/app/src/main/java/com/xff/launch/detector/NativeDetector.java index 922c12c..d576bff 100644 --- a/app/src/main/java/com/xff/launch/detector/NativeDetector.java +++ b/app/src/main/java/com/xff/launch/detector/NativeDetector.java @@ -66,6 +66,20 @@ public class NativeDetector { public native boolean checkZygiskNative(); public native boolean checkZygiskSyscall(); + // /proc/net/tcp port scanning (IDA & Frida) + /** Detect IDA port 23946 (0x5D8A) via /proc/net/tcp - native */ + public native boolean checkIdaPortTcpNative(); + /** Detect IDA port 23946 (0x5D8A) via /proc/net/tcp - syscall */ + public native boolean checkIdaPortTcpSyscall(); + /** Detect Frida port 27042 (0x69A2) via /proc/net/tcp - native */ + public native boolean checkFridaPortTcpNative(); + /** Detect Frida port 27042 (0x69A2) via /proc/net/tcp - syscall */ + public native boolean checkFridaPortTcpSyscall(); + + // Frida FD linjector detection + /** Scan /proc/self/fd via syscall(readlinkat) for linjector injector */ + public native boolean checkFridaFdLinjectorSyscall(); + // ===================== Emulator Detection ===================== public native boolean checkEmulatorNative();