-
-
[原创]【银行逆向百例】17Android逆向之libDexHelper梆梆加固frida检测绕过
-
发表于: 1小时前 69
-
“ 你拥有的一切都过期了,你热爱的一切都旧了,所有你曾经嘲笑过的,你变成他们了。——《Forever Young》 ”
01环境版本
环境:
电脑,Windows 11 专业版 23H2
a5eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6v1K9h3q4G2f1%4g2u0L8X3k6G2f1$3g2U0i4K6u0r3d9X3W2S2L8#2y4#2d9h3&6X3L8#2y4W2j5#2)9#2k6W2b7H3x3r3I4K6i4K6g2X3g2$3W2F1x3e0p5`.
软件:
Florida,16.1.8
d9dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6k6L8r3q4J5L8$3c8Q4x3V1k6r3L8r3!0J5K9h3c8S2i4K6u0r3M7X3g2D9k6h3q4K6k6i4y4Q4x3V1k6@1j5h3N6Q4x3V1j5`.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();