首页
社区
课程
招聘
[原创]【银行逆向百例】17Android逆向之libDexHelper梆梆加固frida检测绕过
发表于: 1小时前 69

[原创]【银行逆向百例】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();

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回