首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
14
13
[原创]【银行逆向百例】17Android逆向之libDexHelper梆梆加固frida检测绕过
发表于: 2026-5-22 15:12
4489
[原创]【银行逆向百例】17Android逆向之libDexHelper梆梆加固frida检测绕过
差不多的张三
2026-5-22 15:12
4489
“ 你拥有的一切都过期了,你热爱的一切都旧了,所有你曾经嘲笑过的,你变成他们了。——《Forever Young》 ” # 01环境版本 环境: 电脑,Windows 11 专业版 23H2 ``` https://github.com/JiaoSuInfoSec/JiaoSuInfoSec_T00ls_Win11 ``` 软件: Florida,16.1.8 ``` https://github.com/Ylarod/Florida/releases/tag/16.1.8 ``` # 02操作步骤 1、梆梆加固企业版  2、使用florida 16.1.8  3、进入APP首页一段时间后frida崩溃  4、定位崩溃点在libDexHelper.so  ``` /** * trace-exit.js * * 通用 Android native 崩溃诊断脚本 * * 只做三类记录: * 1. 加载链记录 * 2. JNI 注册链记录 * 3. 崩溃现场记录 * * 不做: * - 返回值修改 * - 内存 patch * - 目标样本定制 * - “疑似模块/关键帧”推断 * * 用法: * frida -U -f <package> -l trace-exit.js * * RUNBOOK: * 1. 第一次跑,先用 PRESET = "startup" * 看最后加载了哪些 so,以及 JNI 初始化是否开始 * * 2. 如果怀疑崩在 JNI 注册链,切 PRESET = "jni" * 看 FindClass / RegisterNatives / GetMethodID * * 3. 如果只想看最终崩溃点,切 PRESET = "crash" * 重点看 fault 类型、native 栈、Java 栈 * * 4. 如果想长时间采集并离线分析,切 PRESET = "json" * 然后把输出重定向到文件 * * 常用配置: * - PRESET = "default" * 默认通用模式,保留完整功能 * * - PRESET = "startup" * 快速看启动链,只看 LOAD / JNI / CRASH 摘要 * * - PRESET = "jni" * 重点看 JNI 注册链和相关调用栈 * * - PRESET = "crash" * 只看崩溃相关 native / Java 栈 * * - PRESET = "json" * 用 JSON 行输出,适合落盘和后处理 * * 手动细调: * - CONFIG.mode = "summary" * 只输出 LOAD / JNI / CRASH 摘要,适合先粗看启动过程 * * - CONFIG.mode = "verbose" * 输出完整 native / Java 调用栈,适合深入定位崩溃现场 * * - CONFIG.logAllDlopen = true * 打印所有 so 加载路径;默认只重点打印 /data/ 下的 so * * - CONFIG.logAllFindClass = true * 打印所有 FindClass 的 native 栈;默认只对 com/ 类名展开 * * - CONFIG.logAllMethodLookups = true * 打印 GetMethodID / GetStaticMethodID 的 native 栈 * * - CONFIG.printJavaStack = false * 关闭 Java 栈输出,减少日志量 * * - CONFIG.printRecentContextOnEveryEvent = true * 每次事件都输出最近上下文;默认只在 CRASH 场景展开 * * - CONFIG.outputFormat = "json" * 每行输出一个 JSON 对象,方便落盘和后处理 * * - CONFIG.eventAllowlist = ["LOAD", "CRASH"] * 只输出指定类型的日志;留空表示不过滤 * * - CONFIG.eventBlocklist = ["HOOK"] * 屏蔽指定类型的日志;在 allowlist 之后生效 * * - CONFIG.showHookRegistrationLogs = false * 关闭启动时的 HOOK 注册日志,减少初始化噪音 * */ "use strict"; const DEFAULT_PRESET = "default"; // default | startup | jni | crash | json const PRESET = (typeof globalThis !== "undefined" && globalThis.TRACE_EXIT_PRESET) ? String(globalThis.TRACE_EXIT_PRESET) : DEFAULT_PRESET; const BASE_CONFIG = { mode: "verbose", // "summary" | "verbose" outputFormat: "text", // "text" | "json" eventAllowlist: [], eventBlocklist: [], showHookRegistrationLogs: true, maxFrames: 24, maxJavaFrames: 24, maxRecentEvents: 80, maxRecentLoads: 30, recentContextCount: 8, maxStringLength: 160, dedupWindowMs: 800, logAllDlopen: false, logAllFindClass: false, logAllMethodLookups: false, printJavaStack: true, printRecentContextOnEveryEvent: false, dumpNativeStackOnLoad: true, dumpNativeStackOnJniRegister: true, dumpJavaStackOnLoad: true, classPrefixesForStack: ["com/"], libraryPrefixesForStack: ["/data/"] }; const PRESET_OVERRIDES = { default: {}, startup: { mode: "summary", eventAllowlist: ["LOAD", "JNI", "CRASH"], eventBlocklist: ["HOOK"], showHookRegistrationLogs: false, printJavaStack: false, dumpJavaStackOnLoad: false }, jni: { mode: "verbose", eventAllowlist: ["JNI", "CRASH", "JAVA_STACK"], eventBlocklist: ["HOOK", "LOAD", "INFO"] }, crash: { mode: "verbose", eventAllowlist: ["CRASH", "JAVA_STACK"], eventBlocklist: ["HOOK", "LOAD", "JNI", "INFO"], showHookRegistrationLogs: false }, json: { mode: "summary", outputFormat: "json", eventBlocklist: ["HOOK"], showHookRegistrationLogs: false, printJavaStack: false, dumpJavaStackOnLoad: false } }; function buildConfig(preset) { const override = PRESET_OVERRIDES[preset] || PRESET_OVERRIDES.default; return Object.assign({}, BASE_CONFIG, override); } const CONFIG = buildConfig(PRESET); const SCRIPT_VERSION = "1.0.0"; const JSON_SCHEMA_VERSION = "1.0"; const SESSION_ID = Date.now().toString(16) + "-" + Math.random().toString(16).slice(2, 8); const SESSION_START_MS = Date.now(); let gRecentEvents = []; let gRecentLoads = []; let gJavaReady = false; let gLastEventTimes = Object.create(null); let gEventSeq = 0; let gPackageName = ""; let gEventCounts = Object.create(null); function now() { return new Date().toISOString(); } function uptimeMs() { return Date.now() - SESSION_START_MS; } function formatUptime(ms) { return "+" + String(ms) + "ms"; } function emitJsonEvent(type, payload) { const event = Object.assign({ schema: "trace-exit", schemaVersion: JSON_SCHEMA_VERSION, scriptVersion: SCRIPT_VERSION, preset: PRESET, sessionId: SESSION_ID, packageName: gPackageName, processId: Process.id, processName: Process.name, processArch: Process.arch, processPlatform: Process.platform, threadId: Process.getCurrentThreadId(), seq: ++gEventSeq, ts: now(), uptimeMs: uptimeMs(), type: String(type) }, payload || {}); console.log(JSON.stringify(event)); } function log(msg) { if (CONFIG.outputFormat === "json") { emitJsonEvent("LOG", { message: String(msg) }); return; } console.log("[" + now() + "][" + formatUptime(uptimeMs()) + "] " + msg); } function shouldEmitType(type) { const t = String(type || "LOG"); if (t === "HOOK" && !CONFIG.showHookRegistrationLogs) { return false; } if (CONFIG.eventAllowlist.length > 0 && CONFIG.eventAllowlist.indexOf(t) === -1) { return false; } if (CONFIG.eventBlocklist.indexOf(t) !== -1) { return false; } return true; } function logType(type, msg) { if (!shouldEmitType(type)) { return; } if (CONFIG.outputFormat === "json") { emitJsonEvent(type, { message: String(msg) }); return; } log("[" + type + "] " + msg); } function isVerbose() { return CONFIG.mode === "verbose"; } function summarizeConfig() { return { version: SCRIPT_VERSION, preset: PRESET, sessionId: SESSION_ID, packageName: gPackageName, processArch: Process.arch, processPlatform: Process.platform, pageSize: Process.pageSize, mode: CONFIG.mode, outputFormat: CONFIG.outputFormat, printJavaStack: CONFIG.printJavaStack, showHookRegistrationLogs: CONFIG.showHookRegistrationLogs, dumpNativeStackOnLoad: CONFIG.dumpNativeStackOnLoad, dumpNativeStackOnJniRegister: CONFIG.dumpNativeStackOnJniRegister, dumpJavaStackOnLoad: CONFIG.dumpJavaStackOnLoad, eventAllowlist: CONFIG.eventAllowlist, eventBlocklist: CONFIG.eventBlocklist, classPrefixesForStack: CONFIG.classPrefixesForStack, libraryPrefixesForStack: CONFIG.libraryPrefixesForStack }; } function truncateString(value) { if (value === null || value === undefined) { return ""; } const s = String(value); if (s.length <= CONFIG.maxStringLength) { return s; } return s.substring(0, CONFIG.maxStringLength) + "..."; } function shouldSuppressDuplicate(type, detail) { const key = type + "::" + detail; const ts = Date.now(); const last = gLastEventTimes[key]; gLastEventTimes[key] = ts; return last !== undefined && (ts - last) < CONFIG.dedupWindowMs; } function recordEvent(type, detail) { detail = truncateString(detail); if (shouldSuppressDuplicate(type, detail)) { return false; } gEventCounts[type] = (gEventCounts[type] || 0) + 1; const item = { time: now(), uptimeMs: uptimeMs(), type: type, detail: detail }; gRecentEvents.push(item); if (gRecentEvents.length > CONFIG.maxRecentEvents) { gRecentEvents.shift(); } return true; } function recordLoad(path) { path = truncateString(path); const item = { time: now(), uptimeMs: uptimeMs(), path: path }; gRecentLoads.push(item); if (gRecentLoads.length > CONFIG.maxRecentLoads) { gRecentLoads.shift(); } } function safeModuleByAddress(addr) { try { return Process.findModuleByAddress(addr); } catch (e) { return null; } } function formatAddress(addr) { if (!addr) { return "null"; } try { const p = ptr(addr); const sym = DebugSymbol.fromAddress(p); const mod = safeModuleByAddress(p); if (mod) { const off = p.sub(mod.base); return mod.name + "!" + (sym && sym.name ? sym.name : p) + " + 0x" + off.toString(16); } return sym ? sym.toString() : p.toString(); } catch (e) { try { return ptr(addr).toString(); } catch (e2) { return String(addr); } } } function collectBacktrace(context) { try { return Thread.backtrace(context, Backtracer.ACCURATE).slice(0, CONFIG.maxFrames); } catch (e) { try { return Thread.backtrace(context, Backtracer.FUZZY).slice(0, CONFIG.maxFrames); } catch (e2) { return []; } } } function dumpRecentContext() { const summary = buildSessionSummary(); log("事件计数:"); const keys = Object.keys(summary.eventCounts).sort(); if (keys.length === 0) { log(" <empty>"); } else { keys.forEach(function (key) { log(" " + key + " = " + summary.eventCounts[key]); }); } log("最近事件:"); if (gRecentEvents.length === 0) { log(" <empty>"); } else { gRecentEvents.slice(-CONFIG.recentContextCount).forEach(function (item) { log(" [" + item.time + "][" + formatUptime(item.uptimeMs) + "] " + item.type + " | " + item.detail); }); } log("最近加载的 so:"); if (gRecentLoads.length === 0) { log(" <empty>"); } else { gRecentLoads.slice(-CONFIG.recentContextCount).forEach(function (item) { log(" [" + item.time + "][" + formatUptime(item.uptimeMs) + "] " + item.path); }); } } function buildSessionSummary() { return { sessionId: SESSION_ID, packageName: gPackageName, processId: Process.id, processName: Process.name, processArch: Process.arch, processPlatform: Process.platform, uptimeMs: uptimeMs(), eventCounts: Object.assign({}, gEventCounts), recentEventCount: gRecentEvents.length, recentLoadCount: gRecentLoads.length }; } function dumpNativeStack(tag, context, extraLines, category) { if (!isVerbose() && category !== "CRASH") { return; } if (!shouldEmitType(category || "STACK")) { return; } const frames = collectBacktrace(context); if (CONFIG.outputFormat === "json") { emitJsonEvent(category || "STACK", { tag: tag, tid: Process.getCurrentThreadId(), extra: extraLines || [], frames: frames.map(formatAddress), sessionSummary: buildSessionSummary(), recentEvents: (CONFIG.printRecentContextOnEveryEvent || category === "CRASH") ? gRecentEvents.slice(-CONFIG.recentContextCount) : [], recentLoads: (CONFIG.printRecentContextOnEveryEvent || category === "CRASH") ? gRecentLoads.slice(-CONFIG.recentContextCount) : [] }); return; } log("========== " + tag + " =========="); if (extraLines && extraLines.length) { extraLines.forEach(function (line) { log(line); }); } log("tid=" + Process.getCurrentThreadId()); log("调用堆栈:"); frames.forEach(function (addr, idx) { log(" #" + idx + " " + formatAddress(addr)); }); if (CONFIG.printRecentContextOnEveryEvent || category === "CRASH") { dumpRecentContext(); } log("================================"); } function dumpJavaStack(tag, extra) { if (!gJavaReady || !CONFIG.printJavaStack || !isVerbose()) { return; } if (!shouldEmitType("JAVA_STACK")) { return; } try { Java.perform(function () { const Throwable = Java.use("java.lang.Throwable"); const Thread = Java.use("java.lang.Thread"); const t = Throwable.$new(); const stack = t.getStackTrace(); if (CONFIG.outputFormat === "json") { emitJsonEvent("JAVA_STACK", { tag: tag, extra: extra || "", thread: String(Thread.currentThread().getName()), frames: Array.prototype.slice.call(stack, 0, CONFIG.maxJavaFrames).map(function (f) { return f.toString(); }), sessionSummary: buildSessionSummary(), recentEvents: (CONFIG.printRecentContextOnEveryEvent || tag.indexOf("Exception") !== -1) ? gRecentEvents.slice(-CONFIG.recentContextCount) : [], recentLoads: (CONFIG.printRecentContextOnEveryEvent || tag.indexOf("Exception") !== -1) ? gRecentLoads.slice(-CONFIG.recentContextCount) : [] }); return; } log("========== " + tag + " =========="); if (extra) { log(extra); } const current = Thread.currentThread(); log("thread=" + current.getName()); for (let i = 0; i < stack.length && i < CONFIG.maxJavaFrames; i++) { log(" [J#" + i + "] " + stack[i].toString()); } if (CONFIG.printRecentContextOnEveryEvent || tag.indexOf("Exception") !== -1) { dumpRecentContext(); } log("================================"); }); } catch (e) { log("dumpJavaStack 失败: " + e); } } function hookExport(moduleName, exportName, handlerFactory) { let addr = null; try { addr = Module.findExportByName(moduleName, exportName); } catch (e) { addr = null; } if (!addr) { return false; } Interceptor.attach(addr, handlerFactory(addr)); logType("HOOK", exportName + " @ " + addr + (moduleName ? " [" + moduleName + "]" : "")); return true; } function shouldLogDlopen(path) { if (!path || path.indexOf(".so") === -1) { return false; } if (CONFIG.logAllDlopen) { return true; } for (let i = 0; i < CONFIG.libraryPrefixesForStack.length; i++) { if (path.indexOf(CONFIG.libraryPrefixesForStack[i]) === 0) { return true; } } return false; } function hookDlopenChain() { const seen = {}; function onEnterCommon(args, context, apiName) { let path = ""; try { path = args[0].isNull() ? "" : args[0].readCString(); } catch (e) {} if (!path) { return; } recordLoad(path); const recorded = recordEvent(apiName, "path=" + path); if (!shouldLogDlopen(path)) { return; } if (seen[apiName + ":" + path]) { return; } seen[apiName + ":" + path] = true; logType("LOAD", apiName + " -> " + path); if (CONFIG.dumpNativeStackOnLoad && recorded) { dumpNativeStack(apiName, context, ["path=" + path], "LOAD"); } } hookExport(null, "dlopen", function () { return { onEnter(args) { onEnterCommon(args, this.context, "dlopen()"); } }; }); hookExport(null, "android_dlopen_ext", function () { return { onEnter(args) { onEnterCommon(args, this.context, "android_dlopen_ext()"); } }; }); } function hookJniRegistrationChain() { function logJniCall(name, details, context, forceStack) { const detail = truncateString(details.join(", ")); const recorded = recordEvent(name, detail); logType("JNI", name + " | " + detail); if (forceStack && CONFIG.dumpNativeStackOnJniRegister && recorded) { dumpNativeStack(name, context, details, "JNI"); } } hookExport(null, "jniRegisterNativeMethods", function () { return { onEnter(args) { const details = [ "classNamePtr=" + args[0], "methods=" + args[1], "count=" + args[2].toInt32() ]; logJniCall("jniRegisterNativeMethods()", details, this.context, true); } }; }); hookExport(null, "RegisterNatives", function () { return { onEnter(args) { const details = [ "env=" + args[0], "clazz=" + args[1], "methods=" + args[2], "count=" + args[3].toInt32() ]; logJniCall("RegisterNatives()", details, this.context, true); } }; }); hookExport(null, "FindClass", function () { return { onEnter(args) { let name = ""; try { name = args[1].isNull() ? "" : args[1].readCString(); } catch (e) {} const details = [ "name=" + name ]; let needStack = CONFIG.logAllFindClass; if (!needStack && name) { for (let i = 0; i < CONFIG.classPrefixesForStack.length; i++) { if (name.indexOf(CONFIG.classPrefixesForStack[i]) === 0) { needStack = true; break; } } } logJniCall("FindClass()", details, this.context, needStack); } }; }); hookExport(null, "GetMethodID", function () { return { onEnter(args) { let name = ""; let sig = ""; try { name = args[2].isNull() ? "" : args[2].readCString(); } catch (e) {} try { sig = args[3].isNull() ? "" : args[3].readCString(); } catch (e) {} const details = [ "name=" + name, "sig=" + sig ]; logJniCall("GetMethodID()", details, this.context, !!CONFIG.logAllMethodLookups); } }; }); hookExport(null, "GetStaticMethodID", function () { return { onEnter(args) { let name = ""; let sig = ""; try { name = args[2].isNull() ? "" : args[2].readCString(); } catch (e) {} try { sig = args[3].isNull() ? "" : args[3].readCString(); } catch (e) {} const details = [ "name=" + name, "sig=" + sig ]; logJniCall("GetStaticMethodID()", details, this.context, !!CONFIG.logAllMethodLookups); } }; }); hookExport(null, "GetFieldID", function () { return { onEnter(args) { let name = ""; let sig = ""; try { name = args[2].isNull() ? "" : args[2].readCString(); } catch (e) {} try { sig = args[3].isNull() ? "" : args[3].readCString(); } catch (e) {} const details = [ "name=" + name, "sig=" + sig ]; logJniCall("GetFieldID()", details, this.context, false); } }; }); hookExport(null, "GetStaticFieldID", function () { return { onEnter(args) { let name = ""; let sig = ""; try { name = args[2].isNull() ? "" : args[2].readCString(); } catch (e) {} try { sig = args[3].isNull() ? "" : args[3].readCString(); } catch (e) {} const details = [ "name=" + name, "sig=" + sig ]; logJniCall("GetStaticFieldID()", details, this.context, false); } }; }); hookExport(null, "NewStringUTF", function () { return { onEnter(args) { let s = ""; try { s = args[1].isNull() ? "" : args[1].readCString(); } catch (e) {} if (s && s.length) { recordEvent("NewStringUTF()", "str=" + truncateString(s)); } } }; }); } function hookJavaLoadChain() { if (!Java.available) { logType("INFO", "Java 不可用,跳过 Java 层加载链诊断"); return; } Java.perform(function () { gJavaReady = true; const SystemCls = Java.use("java.lang.System"); const RuntimeCls = Java.use("java.lang.Runtime"); const Thread = Java.use("java.lang.Thread"); try { const ActivityThread = Java.use("android.app.ActivityThread"); gPackageName = String(ActivityThread.currentPackageName() || ""); } catch (e) { gPackageName = ""; } if (gPackageName) { logType("INFO", "package.name=" + gPackageName); } SystemCls.loadLibrary.overload("java.lang.String").implementation = function (libname) { const detail = "libname=" + libname; recordEvent("System.loadLibrary()", detail); logType("LOAD", "System.loadLibrary -> " + libname); if (CONFIG.dumpJavaStackOnLoad) { dumpJavaStack("System.loadLibrary()", detail); } return this.loadLibrary(libname); }; logType("HOOK", "Java System.loadLibrary"); SystemCls.load.overload("java.lang.String").implementation = function (filename) { const detail = "filename=" + filename; recordEvent("System.load()", detail); logType("LOAD", "System.load -> " + filename); if (CONFIG.dumpJavaStackOnLoad) { dumpJavaStack("System.load()", detail); } return this.load(filename); }; logType("HOOK", "Java System.load"); RuntimeCls.loadLibrary0.overload("java.lang.Class", "java.lang.String").implementation = function (fromClass, libname) { const detail = "libname=" + libname; recordEvent("Runtime.loadLibrary0()", detail); logType("LOAD", "Runtime.loadLibrary0 -> " + libname); if (CONFIG.dumpJavaStackOnLoad) { dumpJavaStack("Runtime.loadLibrary0()", detail); } return this.loadLibrary0(fromClass, libname); }; logType("HOOK", "Java Runtime.loadLibrary0"); RuntimeCls.load0.overload("java.lang.Class", "java.lang.String").implementation = function (fromClass, filename) { const detail = "filename=" + filename; recordEvent("Runtime.load0()", detail); logType("LOAD", "Runtime.load0 -> " + filename); if (CONFIG.dumpJavaStackOnLoad) { dumpJavaStack("Runtime.load0()", detail); } return this.load0(fromClass, filename); }; logType("HOOK", "Java Runtime.load0"); Thread.setDefaultUncaughtExceptionHandler.implementation = function (handler) { const detail = "handler=" + handler; recordEvent("Thread.setDefaultUncaughtExceptionHandler()", detail); logType("JNI", "Thread.setDefaultUncaughtExceptionHandler | " + detail); dumpJavaStack("Thread.setDefaultUncaughtExceptionHandler()", detail); return this.setDefaultUncaughtExceptionHandler(handler); }; logType("HOOK", "Java Thread.setDefaultUncaughtExceptionHandler"); }); } function installExceptionHandler() { Process.setExceptionHandler(function (details) { const extra = [ "type=" + details.type, "address=" + details.address, "memory.operation=" + (details.memory ? details.memory.operation : ""), "memory.address=" + (details.memory ? details.memory.address : "") ]; recordEvent("native-exception", extra.join(", ")); logType("CRASH", extra.join(", ")); dumpNativeStack("Native Exception", details.context, extra, "CRASH"); if (gJavaReady) { dumpJavaStack("Java Stack At Native Exception", ""); } return false; }); logType("HOOK", "Process.setExceptionHandler"); } function main() { logType("INFO", "trace-exit.js 通用诊断版启动"); logType("INFO", "config=" + JSON.stringify(summarizeConfig())); logType("INFO", "process.id=" + Process.id); logType("INFO", "process.arch=" + Process.arch); logType("INFO", "process.platform=" + Process.platform); logType("INFO", "process.pageSize=" + Process.pageSize); logType("INFO", "session.id=" + SESSION_ID); installExceptionHandler(); hookDlopenChain(); hookJniRegistrationChain(); hookJavaLoadChain(); logType("INFO", "所有诊断 hook 已安装完成"); } rpc.exports = { summary: function () { return buildSessionSummary(); }, config: function () { return summarizeConfig(); }, recentevents: function () { return gRecentEvents.slice(-CONFIG.recentContextCount); }, recentloads: function () { return gRecentLoads.slice(-CONFIG.recentContextCount); } }; setImmediate(main); ``` 5、查看APP完整路径,拷贝libDexHelper.so到本地 ``` adb shell dumpsys window | grep mCurrentFocus adb shell pm path com.xxx.mobilebank adb shell "su -c 'cp /data/app/~~FVWGiuhhMTzVI3uIqPYuqw==/com.xxx.mobilebank-xs76wXVDYGHBnmiVcXA5lA==/lib/arm64/libDexHelper.so /data/local/tmp/ && chmod 777 /data/local/tmp/libDexHelper.so'" adb pull /data/local/tmp/libDexHelper.so ./dump/ ```  6、IDA导入libDexHelper.so,Shift+F7查看段结构Segments,发现主段seg000处于加壳保护状态,而具有RX (可读可执行) 权限的LOAD段为壳代码执行区  7、Shift+F12搜索常见frida检测特征,默认端口27042,a转换字符串,发现websocket请求包探测frida默认端口  8、Shift+F12搜索常见frida检测特征,内存映射/proc/self/maps,Shift+X查看交叉引用  9、进入sub_1092A8,F5查看伪代码,读取/proc/self/maps内容,逐行匹配壳硬编码的白名单特征,若遍历至文件末尾仍未匹配成功,则进入sub_1091DC  10、进入sub_1091DC查看汇编,利用底层SVC指令(MOV X8, #0x5E; SVC #0)发起exit_group系统调用,绕过常规的libc层hook  11、查看sub_1092A8交叉引用进入sub_1095EC。调用sub_1095EC的上一层代码,必须在返回地址处带有一串连续的__b_a_n_g_c_l_e__check_env签名。如果使用frida强行跳转过来,签名对不上,就会触发sub_1092A8,最终导致进程被exit_group杀掉。否则会在内存中动态解密真正的APP代码 修改内存权限为RWX(可读可写可执行),XOR (异或)解密循环  12、编写frida脚本执行进入APP稳定运行 在常规 libc hook(拦截 connect 端口扫描、openat 路径检测、fgets 过滤 maps 内容、strstr 关键字检测及 ptrace 附加检测等)基础上,重点通过 Memory.patchCode 将 0x1092A8(扫描函数)和 0x1091DC(杀进程函数)的头部指令直接替换为 RET,彻底阻断底层的exit_group  ``` "use strict"; function log(msg) { console.log("[*] " + msg); } function logOk(msg) { console.log("[+] " + msg); } // ===================== 1. connect 端口扫描拦截 ===================== function hookConnect() { var addr = Module.findExportByName("libc.so", "connect"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { try { var sinFamily = args[1].readU16(); if (sinFamily === 2) { var port = (args[1].add(2).readU8() << 8) | args[1].add(3).readU8(); if (port >= 20000 && port <= 30001) { this.block = true; } } } catch (e) {} }, onLeave: function (retval) { if (this.block) { retval.replace(ptr(-1)); try { var fn = new NativeFunction(Module.findExportByName("libc.so", "__errno"), 'pointer', []); fn().writeS32(111); } catch (e) {} } } }); logOk("connect 端口扫描拦截"); } // ===================== 2. openat 拦截 ===================== function hookOpenat() { var addr = Module.findExportByName("libc.so", "openat"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { try { var path = args[1].readCString(); if (path && (path.indexOf("frida") !== -1 || path.indexOf("gadget") !== -1)) { this.block = true; } } catch (e) {} }, onLeave: function (retval) { if (this.block) retval.replace(ptr(-1)); } }); } // ===================== 3. fopen + fgets maps 过滤 ===================== var mapsFiles = {}; function hookFopen() { var addr = Module.findExportByName("libc.so", "fopen"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { try { var path = args[0].readCString(); if (path && (path.indexOf("/proc/self/maps") !== -1 || path.indexOf("/proc/" + Process.id + "/maps") !== -1)) { this.isMaps = true; } } catch (e) {} }, onLeave: function (retval) { if (this.isMaps && !retval.isNull()) { mapsFiles[retval.toString()] = true; } } }); } function hookFgets() { var addr = Module.findExportByName("libc.so", "fgets"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { this.buf = args[0]; this.fp = args[2]; }, onLeave: function (retval) { if (!retval.isNull() && mapsFiles[this.fp.toString()]) { try { var line = this.buf.readCString(); if (line) { var ll = line.toLowerCase(); if (ll.indexOf('frida') !== -1 || ll.indexOf('gum-js') !== -1 || ll.indexOf('gmain') !== -1 || ll.indexOf('linjector') !== -1 || ll.indexOf('gadget') !== -1 || ll.indexOf('re.frida') !== -1) { this.buf.writeU8(0); retval.replace(ptr(0)); } } } catch (e) {} } } }); } // ===================== 4. ptrace ===================== function hookPtrace() { var addr = Module.findExportByName("libc.so", "ptrace"); if (!addr) return; Interceptor.attach(addr, { onLeave: function (retval) { retval.replace(ptr(0)); } }); } // ===================== 5. strstr 关键词拦截 ===================== function hookStrstr() { var keywords = ["frida", "gum-js", "gmain", "linjector", "pool-frida", "frida-agent"]; var addr = Module.findExportByName("libc.so", "strstr"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { try { var needle = args[1].readCString(); if (needle) { var nl = needle.toLowerCase(); for (var i = 0; i < keywords.length; i++) { if (nl.indexOf(keywords[i]) !== -1) { this.block = true; break; } } } } catch (e) {} }, onLeave: function (retval) { if (this.block) retval.replace(ptr(0)); } }); } // ===================== 6. 线程名清理 ===================== function hookPthreadSetname() { var addr = Module.findExportByName("libc.so", "pthread_setname_np"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { try { var name = args[1].readCString(); if (name) { var nl = name.toLowerCase(); if (nl.indexOf('frida') !== -1 || nl.indexOf('gum') !== -1 || nl.indexOf('gmain') !== -1) { args[1].writeUtf8String("main"); } } } catch (e) {} } }); } // ===================== 7. kill / tgkill 拦截 ===================== function hookKill() { var addr = Module.findExportByName("libc.so", "kill"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { var pid = args[0].toInt32(); var sig = args[1].toInt32(); if (pid === Process.id || pid === 0 || pid === -Process.id) { if (sig === 9 || sig === 6 || sig === 15) { this.block = true; log("[!] kill(pid=" + pid + ", sig=" + sig + ") 被拦截"); } } }, onLeave: function (retval) { if (this.block) retval.replace(ptr(0)); } }); logOk("kill hook"); } // ===================== 8. tgkill 拦截 (线程级杀信号) ===================== function hookTgkill() { var addr = Module.findExportByName("libc.so", "tgkill"); if (!addr) return; Interceptor.attach(addr, { onEnter: function (args) { var tgid = args[0].toInt32(); var tid = args[1].toInt32(); var sig = args[2].toInt32(); if (tgid === Process.id) { if (sig === 9 || sig === 6 || sig === 15 || sig === 11) { this.block = true; log("[!] tgkill(tgid=" + tgid + ", tid=" + tid + ", sig=" + sig + ") 被拦截"); } } }, onLeave: function (retval) { if (this.block) retval.replace(ptr(0)); } }); logOk("tgkill hook"); } // ===================== 9. exit / _exit / abort 拦截 ===================== function hookExit() { // exit() — 正常退出,检测到异常后可能调这个 var exitAddr = Module.findExportByName("libc.so", "exit"); if (exitAddr) { Interceptor.replace(exitAddr, new NativeCallback(function (status) { log("[!] exit(" + status + ") 被拦截"); }, 'void', ['int'])); logOk("exit hook"); } // _exit() — 直接系统调用退出,不刷新缓冲区 var _exitAddr = Module.findExportByName("libc.so", "_exit"); if (_exitAddr) { Interceptor.replace(_exitAddr, new NativeCallback(function (status) { log("[!] _exit(" + status + ") 被拦截"); }, 'void', ['int'])); logOk("_exit hook"); } // abort() — 发送 SIGABRT var abortAddr = Module.findExportByName("libc.so", "abort"); if (abortAddr) { Interceptor.replace(abortAddr, new NativeCallback(function () { log("[!] abort() 被拦截"); }, 'void', [])); logOk("abort hook"); } // raise() — 发送信号给自身 var raiseAddr = Module.findExportByName("libc.so", "raise"); if (raiseAddr) { Interceptor.attach(raiseAddr, { onEnter: function (args) { var sig = args[0].toInt32(); if (sig === 6 || sig === 9 || sig === 11 || sig === 15) { this.block = true; log("[!] raise(sig=" + sig + ") 被拦截"); } }, onLeave: function (retval) { if (this.block) retval.replace(ptr(0)); } }); logOk("raise hook"); } } // ===================== 10. libDexHelper.so SVC层patch ===================== // 直接 patch SVC wrapper 函数,让它们直接 RET,不执行 SVC 指令 // patch 后的代码不再调用 SVC,所以对解壳后运行的代码也有效 // (解壳后代码通过函数指针调用这些 wrapper) function patchLibDexHelper() { var RET = [0xC0, 0x03, 0x5F, 0xD6]; // ARM64 RET 指令 var timer = setInterval(function () { var mod = Process.findModuleByName("libDexHelper.so"); if (!mod) return; clearInterval(timer); log("libDexHelper.so @ " + mod.base); // patch sub_1091DC (exit_group SVC wrapper) → RET // 原指令: MOV X8, #0x5E ; SVC #0 ; RET // 这是该 SO 唯一的进程退出路径 try { Memory.patchCode(mod.base.add(0x1091DC), 4, function (code) { code.writeByteArray(RET); }); logOk("patch 0x1091DC → exit_group SVC 被禁用"); } catch (e) { log("patch 0x1091DC 失败: " + e); } // patch sub_1092A8 (maps扫描总入口) → MOV X0, #0 ; RET // 阻断通过 SVC openat/read 读取 /proc/self/maps try { Memory.patchCode(mod.base.add(0x1092A8), 8, function (code) { code.writeByteArray([0xE0, 0x03, 0x1F, 0xAA, 0xC0, 0x03, 0x5F, 0xD6]); }); logOk("patch 0x1092A8 → maps扫描被禁用"); } catch (e) { log("patch 0x1092A8 失败: " + e); } // 注意: sub_1095EC 不能 patch! // 它是解壳入口 (mmap + uncompress + 清缓存 + 跳转解壳代码) // patch 它会导致 DEX 不解包,libart.so access-violation + 白屏卡死 }, 100); } // ===================== 11. 异常捕获 ===================== // 只拦截安全模块异常,系统模块异常放行不打日志 function hookExceptions() { Process.setExceptionHandler(function (details) { var mod = Process.findModuleByAddress(details.context.pc); var modName = mod ? mod.name : ""; if (modName.indexOf("DexHelper") !== -1 || modName.indexOf("sec") !== -1 || modName.indexOf("guard") !== -1 || modName.indexOf("bang") !== -1) { log("[!] 安全模块异常被拦截: " + modName); return true; } return false; }); } // ===================== 主入口 ===================== function main() { log("========================================="); log("梆梆安全反调试绕过 v10"); log("========================================="); hookExceptions(); hookConnect(); hookOpenat(); hookFopen(); hookFgets(); hookPtrace(); hookStrstr(); hookPthreadSetname(); hookKill(); hookTgkill(); hookExit(); patchLibDexHelper(); log("所有模块已就绪"); } main(); ```
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
14
点赞
・
13
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_uxrckosb
感谢你的积极参与,期待更多精彩内容!
2026-9-13 16:25
estury
你的分享对大家帮助很大,非常感谢!
2026-9-2 17:34
mb_ybjiiova
感谢你分享这么好的资源!
2026-8-27 14:26
Evan_422
这个讨论对我很有帮助,谢谢!
2026-8-23 19:17
sher_wang
感谢你分享这么好的资源!
2026-6-23 14:21
mb_reyfinlv
为你点赞!
2026-6-22 14:04
mb_qlbmqqmd
你的帖子非常有用,感谢分享!
2026-6-18 10:44
mb_vrkpxsoe
非常支持你的观点!
2026-6-8 11:21
zz1syyd
你的帖子非常有用,感谢分享!
2026-5-29 15:14
cr_lgdx
感谢你的积极参与,期待更多精彩内容!
2026-5-24 11:30
流霜瞳
感谢你分享这么好的资源!
2026-5-23 11:21
JuneKill
感谢你分享这么好的资源!
2026-5-22 22:29
下雨天sana
期待更多优质内容的分享,论坛有你更精彩!
2026-5-22 17:32
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
7
)
mb_rirvdrlh
雪 币:
200
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
29
粉丝
0
关注
私信
mb_rirvdrlh
2
楼
6666
2026-5-22 18:45
0
流霜瞳
雪 币:
882
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
流霜瞳
3
楼
学习
2026-5-23 11:32
0
Zy143L
雪 币:
242
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
Zy143L
4
楼
好奇为什么是frida16版本呢
2026-5-24 05:32
0
hyz111
雪 币:
151
活跃值:
(1113)
能力值:
( LV2,RANK:10 )
在线值:
发帖
3
回帖
47
粉丝
2
关注
私信
hyz111
5
楼
tql话说下次长一点的脚本能不能放到附件有点影响阅读了
2026-5-25 09:52
0
mb_ysegxpmv
雪 币:
2166
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
115
粉丝
0
关注
私信
mb_ysegxpmv
6
楼
6,直接给脚本,很爽快
2026-5-25 22:07
0
zhonghuayi
雪 币:
128
活跃值:
(1650)
能力值:
( LV2,RANK:10 )
在线值:
发帖
29
回帖
118
粉丝
2
关注
私信
zhonghuayi
7
楼
什么查克软件怎么这么牛,还有有apk吗可以锻炼一下
2026-5-26 00:39
0
阳光宝贝
雪 币:
11
活跃值:
(1126)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
38
粉丝
1
关注
私信
阳光宝贝
8
楼
感谢分享
2天前
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
差不多的张三
19
发帖
13
回帖
0
RANK
关注
私信
他的文章
[原创]【银行逆向百例】18Android逆向之libDexHelper.so梆梆加固壳解密 frida-dexdump脱壳
5271
[原创]【代码审计】客户端代码执行之WebView JavaScript桥接劫持token账号接管
1889
[原创]【银行逆向百例】17Android逆向之libDexHelper梆梆加固frida检测绕过
4489
[原创]【银行逆向百例】16Android逆向之算法助手 frida绕过环境检测分析加密算法
2007
[原创]【银行逆向百例】15小程序逆向之支付宝提取data文件为tar格式
2435
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部