小柏实战学习安卓图文教程-第十三课-Frida动态脱壳3

本节课主题:实战排查反Frida检测;

 

上节课总结:

应用可能存在多重验证机制反调试检测

根据我们的分析流程,现在需要深入排查其他可能的验证环节和防御机制。

 

🧪 第一步:排查反Frida检测

很多应用会检测Frida的存在,导致Hook失效。请先运行以下脚本,确保Frida本身没有被发现。

// anti_anti_frida.js
Java.perform(function() {
    console.log("[*] 修复版反反Frida检测启动");

    // 1. 修复connect方法重载问题
    try {
        var NetUnix = Java.use("android.net.LocalSocket");
        
        // 明确指定重载版本
        NetUnix.connect.overload('android.net.LocalSocketAddress').implementation = function(address) {
            var addrStr = address.getName();
            if (addrStr && addrStr.indexOf("27042") !== -1) {
                console.log("[!] 拦截对Frida默认端口的检测连接");
                return; // 静默拦截
            }
            return this.connect(address);
        };
        
        // 另一个重载版本也进行拦截
        NetUnix.connect.overload('android.net.LocalSocketAddress', 'int').implementation = function(address, timeout) {
            var addrStr = address.getName();
            if (addrStr && addrStr.indexOf("27042") !== -1) {
                console.log("[!] 拦截带超时的Frida端口检测");
                return;
            }
            return this.connect(address, timeout);
        };
        
        console.log("[+] 端口检测绕过就绪");
    } catch(e) {
        console.log("[-] 端口检测绕过失败: " + e.message);
    }

    // 2. 增强型反检测措施
    try {
        var File = Java.use("java.io.File");
        File.exists.implementation = function() {
            var path = this.getPath();
            if (path && (path.indexOf("frida") !== -1 || path.indexOf("xposed") !== -1)) {
                console.log("[!] 拦截Frida相关文件检测: " + path);
                return false;
            }
            return this.exists();
        };
        console.log("[+] 文件检测绕过就绪");
    } catch(e) {
        console.log("[-] 文件检测绕过失败: " + e.message);
    }

    // 3. 进程列表隐藏
    try {
        var ProcessBuilder = Java.use("java.lang.ProcessBuilder");
        ProcessBuilder.start.implementation = function() {
            var commandList = this.command();
            var commandStr = commandList.toString();
            
            if (commandStr.indexOf("ps") !== -1 || commandStr.indexOf("frida") !== -1) {
                console.log("[!] 拦截进程扫描命令: " + commandStr);
                // 返回空进程对象
                var ArrayList = Java.use("java.util.ArrayList");
                var fakeProcess = {
                    getInputStream: function() {
                        var ByteArrayInputStream = Java.use("java.io.ByteArrayInputStream");
                        return ByteArrayInputStream.$new("".getBytes());
                    },
                    waitFor: function() { return 0; },
                    exitValue: function() { return 0; }
                };
                return fakeProcess;
            }
            return this.start();
        };
        console.log("[+] 进程检测绕过就绪");
    } catch(e) {
        console.log("[-] 进程检测绕过失败: " + e.message);
    }

    // 4. 调试检测绕过
    try {
        var Debug = Java.use("android.os.Debug");
        Debug.isDebuggerConnected.implementation = function() {
            console.log("[!] 调试状态检测被调用,返回false");
            return false;
        };
        console.log("[+] 调试检测绕过就绪");
    } catch(e) {
        console.log("[-] 调试检测绕过失败: " + e.message);
    }

    console.log("[*] 修复版反反Frida检测就绪");
});

 

执行命令:

frida -U -p 17866 -l anti_anti_frida.js -l bypass_server_response.js

image

结果:发现还是没有效果;

再来,诊断脚本如下:

// comprehensive_diagnosis.js
Java.perform(function () {
    console.log("\n========== [!] 全面诊断脚本启动 ==========\n");

    // 1. 增强版 Lua 字符串监控:捕获所有可能包含状态码的字符串
    var lua_tolstring = Module.findExportByName("libassist.so", "lua_tolstring");
    if (lua_tolstring) {
        Interceptor.attach(lua_tolstring, {
            onLeave: function (retval) {
                if (retval && !retval.isNull()) {
                    try {
                        var str = retval.readCString();
                        // 扩大关键词范围,捕获任何可能与状态、权限相关的字符串
                        if (str && (
                            str.indexOf("code") !== -1 ||
                            str.indexOf("status") !== -1 ||
                            str.indexOf("vip") !== -1 ||
                            str.indexOf("auth") !== -1 ||
                            str.indexOf("success") !== -1 ||
                            str.indexOf("fail") !== -1 ||
                            str.indexOf("true") !== -1 ||
                            str.indexOf("false") !== -1 ||
                            str.indexOf("200") !== -1 ||
                            str.indexOf("201") !== -1
                        )) {
                            console.log(`[LUA-状态数据] lua_tolstring 返回: ${str}`);
                        }
                    } catch (e) { }
                }
            }
        });
    }

    // 2. 关键突破:监控应用从网络响应到设置本地状态的逻辑
    // 查找并监控可能处理登录结果的Java类(通常包含 "User", "Login", "Auth", "Manager" 等关键词)
    Java.enumerateLoadedClasses({
        onMatch: function (className) {
            if (className.toLowerCase().indexOf("user") !== -1 ||
                className.toLowerCase().indexOf("login") !== -1 ||
                className.toLowerCase().indexOf("auth") !== -1 ||
                className.toLowerCase().indexOf("vip") !== -1 ||
                className.toLowerCase().indexOf("member") !== -1) {
                console.log(`[*] 发现可能的相关类: ${className}`);

                try {
                    var clazz = Java.use(className);
                    var methods = clazz.class.getDeclaredMethods();

                    methods.forEach(function (method) {
                        var methodName = method.getName();
                        var returnType = method.getReturnType().getName();

                        // 重点关注设置状态、保存数据的方法
                        if (methodName.toLowerCase().indexOf("set") !== -1 ||
                            methodName.toLowerCase().indexOf("save") !== -1 ||
                            methodName.toLowerCase().indexOf("parse") !== -1 ||
                            (returnType === "boolean" && methodName.length <= 4) // 混淆的setter方法可能很短
                        ) {
                            // 安全地尝试Hook
                            try {
                                if (clazz[methodName] && clazz[methodName].overloads) {
                                    clazz[methodName].overloads.forEach(function (overload) {
                                        overload.implementation = function () {
                                            // 打印调用信息,但不干扰执行
                                            console.log(`[Java] 调用: ${className}.${methodName}`);
                                            for (var i = 0; i < arguments.length; i++) {
                                                try {
                                                    if (arguments[i] === null) continue;
                                                    var argStr = arguments[i].toString();
                                                    if (argStr.indexOf("vip") !== -1 || argStr.indexOf("200") !== -1 || argStr.indexOf("201") !== -1) {
                                                        console.log(`   参数 ${i}: ${argStr}`);
                                                    }
                                                } catch (e) { }
                                            }
                                            var result = overload.apply(this, arguments);
                                            if (result !== undefined && result !== null) {
                                                console.log(`   返回: ${result}`);
                                            }
                                            return result;
                                        };
                                    });
                                }
                            } catch (e) { /* 忽略Hook错误 */ }
                        }
                    });
                } catch (e) { /* 忽略类操作错误 */ }
            }
        },
        onComplete: function () { console.log("[*] 关键类扫描完成。"); }
    });

    // 3. 监控文件或SharedPreferences的写入,看是否保存了正确的会员状态
    var SharedPreferences = Java.use("android.content.SharedPreferences$Editor");
    if (SharedPreferences.putBoolean) {
        SharedPreferences.putBoolean.overload('java.lang.String', 'boolean').implementation = function (key, value) {
            if (key && (key.indexOf("is_vip") !== -1 || key.indexOf("logged_in") !== -1 || key.indexOf("active") !== -1)) {
                console.log(`[!] 正在保存关键配置: ${key} = ${value}`);
                // 强制设置为true,确保会员状态被正确记录
                if (value === false) {
                    console.log(`[!!!] 尝试将 ${key} 强制改为 true`);
                    value = true;
                }
            }
            return this.putBoolean(key, value);
        };
    }

    console.log("\n========== [!] 诊断系统就绪,请点击登录按钮 ==========\n");
});

运行诊断脚本

frida -U -p 17866 -l comprehensive_diagnosis.js

和之前的结论一样:

  • API地址: http://:5147/api.php?act=user_logon

  • 应用标识: app=10000

  • markcode: 3325ded7708e34e8cd4a35ec564b5765
  • 返回信息:{"code":201,"msg":"会员已到期","time":1768873785}

 

分析一下:

以下的中高分险的扫描加一下:

1./proc文件扫描​ (maps, fd等)

2.D-Bus协议检测

3.内存特征扫描

 

更新脚本如下:

// enhanced_anti_detection.js
Java.perform(function() {
    console.log("[*] 增强版Frida反检测启动");

    // 1. 增强/proc文件系统防护
    var libc = Module.findBaseAddress("libc.so");
    
    // Hook文件打开操作,隐藏/proc相关检测
    var openat = Module.findExportByName("libc.so", "openat");
    if (openat) {
        Interceptor.attach(openat, {
            onEnter: function(args) {
                var pathname = ptr(args[1]);
                if (!pathname.isNull()) {
                    try {
                        var path = pathname.readCString();
                        if (path && (path.indexOf("/proc/self/maps") !== -1 || 
                                    path.indexOf("/proc/self/fd") !== -1 ||
                                    path.indexOf("/proc/self/task") !== -1)) {
                            console.log("[!] 拦截/proc文件扫描: " + path);
                            
                            // 重定向到无害文件
                            var fakePath = "/dev/null";
                            var newPath = Memory.allocUtf8String(fakePath);
                            args[1] = newPath;
                        }
                    } catch(e) {}
                }
            }
        });
    }

    // 2. 字符串操作Hook(防D-Bus检测)
    var strstr = Module.findExportByName("libc.so", "strstr");
    if (strstr) {
        Interceptor.attach(strstr, {
            onEnter: function(args) {
                this.haystack = ptr(args[0]);
                this.needle = ptr(args[1]);
                
                if (!this.haystack.isNull() && !this.needle.isNull()) {
                    try {
                        var needleStr = this.needle.readCString();
                        var haystackStr = this.haystack.readCString();
                        
                        // 拦截Frida特征字符串检测
                        if (needleStr && (needleStr.indexOf("frida") !== -1 || 
                                         needleStr.indexOf("gmain") !== -1 ||
                                         needleStr.indexOf("gum-js") !== -1)) {
                            console.log("[!] 拦截特征字符串检测: " + needleStr);
                            this.shouldBypass = true;
                        }
                        
                        // 拦截D-Bus协议检测
                        if (haystackStr && haystackStr.indexOf("REJECT") !== -1) {
                            console.log("[!] 拦截D-Bus协议检测");
                            this.shouldBypass = true;
                        }
                    } catch(e) {}
                }
            },
            onLeave: function(retval) {
                if (this.shouldBypass) {
                    // 返回null表示未找到匹配
                    retval.replace(ptr(0));
                }
            }
        });
    }

    // 3. 内存maps文件内容过滤
    var read = Module.findExportByName("libc.so", "read");
    if (read) {
        Interceptor.attach(read, {
            onEnter: function(args) {
                this.fd = args[0].toInt32();
                this.buffer = args[1];
                this.size = args[2].toInt32();
            },
            onLeave: function(retval) {
                var bytesRead = retval.toInt32();
                if (bytesRead > 0) {
                    try {
                        var data = this.buffer.readCString();
                        if (data && data.indexOf("frida") !== -1) {
                            console.log("[!] 过滤maps中的Frida特征");
                            // 清除Frida相关行
                            var cleanData = data.split('\n').filter(function(line) {
                                return line.indexOf("frida") === -1 && 
                                       line.indexOf("gum") === -1 &&
                                       line.indexOf("frida-agent") === -1;
                            }).join('\n');
                            
                            Memory.writeUtf8String(this.buffer, cleanData);
                            retval.replace(ptr(cleanData.length));
                        }
                    } catch(e) {}
                }
            }
        });
    }

    console.log("[+] 增强版反检测就绪");
});

结果: 直接app停止运行了;

应用在登录两次后直接停止运行,这强烈表明存在强力的反调试机制

🛠️ 崩溃防护与修复方案:

// crash_protection.js
Java.perform(function() {
    console.log("[*] 应用崩溃防护脚本启动");
    
    // 1. 防止应用主动退出
    var System = Java.use("java.lang.System");
    System.exit.overload('int').implementation = function(code) {
        console.log("[!] 拦截应用退出请求,退出码: " + code);
        // 不执行退出,记录堆栈
        printStackTrace("应用尝试退出");
        return; // 静默拦截
    };
    
    // 2. 防止进程终止
    var Process = Java.use("android.os.Process");
    Process.killProcess.overload('int').implementation = function(pid) {
        console.log("[!] 拦截进程终止请求,PID: " + pid);
        printStackTrace("进程终止被调用");
        return; // 拦截
    };
    
    // 3. 监控异常抛出,防止未处理异常导致崩溃
    var Thread = Java.use("java.lang.Thread");
    var defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
    
    Thread.setDefaultUncaughtExceptionHandler.implementation = function(handler) {
        console.log("[!] 监控到异常处理器设置");
        // 设置我们自己的异常处理器来捕获崩溃
        var wrappedHandler = Java.registerClass({
            name: 'com.safety.ExceptionHandler',
            implements: [Java.use('java.lang.Thread$UncaughtExceptionHandler')],
            methods: {
                uncaughtException: function(thread, exception) {
                    console.log("[!!!] 捕获未处理异常:");
                    console.log("  线程: " + thread.getName());
                    console.log("  异常: " + exception.toString());
                    printStackTrace("崩溃堆栈");
                    
                    // 可以选择继续使用原处理器,或者静默处理
                    // defaultUncaughtExceptionHandler.uncaughtException(thread, exception);
                    console.log("[!] 异常已被拦截,应用继续运行");
                }
            }
        });
        return this.setDefaultUncaughtExceptionHandler(wrappedHandler.$new());
    };

    console.log("[+] 崩溃防护就绪");
});

function printStackTrace(reason) {
    Java.perform(function() {
        var stackTrace = Java.use("android.util.Log").getStackTraceString(
            Java.use("java.lang.Exception").$new()
        );
        console.log("[STACK] " + reason + " 调用栈:");
        var lines = stackTrace.split("\n");
        for (var i = 0; i < Math.min(lines.length, 8); i++) {
            if (lines[i].indexOf("android.") === -1 && lines[i].indexOf("java.") === -1) {
                console.log("  " + lines[i]);
            }
        }
    });
}

立即运行崩溃防护:

frida -U -p 17866 -l crash_protection.js -l enhanced_anti_detection.js -l bypass_server_response.js

image

 结果还是不行,简单分析一下:

可能原因包括:

  1. 本地状态未更新:应用可能缓存了之前的会员状态

  2. 多重验证点:除了服务器响应,还有本地验证逻辑

  3. 签名验证:修改响应后数字签名不匹配

  4. 时间戳验证:响应时间与服务器时间不一致

 

增强版绕过方案:

// comprehensive_vip_bypass_enhanced.js
Java.perform(function() {
    console.log("[*] 增强版会员验证综合绕过启动");

    // 1. 增强服务器响应修改 - 添加完整成功数据
    var lua_tolstring = Module.findExportByName("libassist.so", "lua_tolstring");
    if (lua_tolstring) {
        Interceptor.attach(lua_tolstring, {
            onLeave: function(retval) {
                if (retval && !retval.isNull()) {
                    try {
                        var str = retval.readCString();
                        if (str && str.indexOf('"code":201') !== -1) {
                            console.log("[!!!] 拦截会员到期响应");
                            
                            // 构建完整的成功响应,包含所有可能需要的字段
                            var currentTime = Math.floor(Date.now() / 1000);
                            var newResponse = {
                                code: 200,
                                msg: "登录成功",
                                vip_status: 1,
                                is_vip: true,
                                vip_level: 9,
                                expire_time: "2099-12-31 23:59:59",
                                active: 1,
                                timestamp: currentTime,
                                data: {
                                    user_id: "100001",
                                    vip_expire: "2099-12-31",
                                    balance: "999.00",
                                    permission: "all"
                                }
                            };
                            
                            var newResponseStr = JSON.stringify(newResponse);
                            console.log("原始响应: " + str);
                            console.log("增强响应: " + newResponseStr);
                            
                            var newPtr = Memory.allocUtf8String(newResponseStr);
                            retval.replace(newPtr);
                        }
                    } catch(e) {
                        console.log("[-] 响应修改错误: " + e.message);
                    }
                }
            }
        });
    }

    // 2. 强制本地会员状态(关键步骤)
    var SharedPreferences = Java.use("android.content.SharedPreferences");
    
    // Hook读取操作
    SharedPreferences.getBoolean.overload('java.lang.String', 'boolean').implementation = function(key, defValue) {
        var result = this.getBoolean(key, defValue);
        if (key && (key.toLowerCase().indexOf("vip") !== -1 || 
                   key.toLowerCase().indexOf("login") !== -1 ||
                   key.toLowerCase().indexOf("active") !== -1 ||
                   key.toLowerCase().indexOf("is_") !== -1)) {
            console.log("[VIP状态] 读取 " + key + " = " + result + " → 强制为 true");
            return true;
        }
        return result;
    };
    
    // Hook写入操作
    var SharedPreferencesEditor = Java.use("android.content.SharedPreferences$Editor");
    SharedPreferencesEditor.putBoolean.overload('java.lang.String', 'boolean').implementation = function(key, value) {
        if (key && key.toLowerCase().indexOf("vip") !== -1) {
            console.log("[VIP状态] 写入 " + key + " = " + value + " → 强制为 true");
            return this.putBoolean(key, true);
        }
        return this.putBoolean(key, value);
    };

    // 3. 关键突破:查找并Hook会员验证类
    setTimeout(function() {
        Java.enumerateLoadedClasses({
            onMatch: function(className) {
                // 扩大类名匹配模式
                if (className.indexOf("vip") !== -1 || 
                    className.indexOf("Vip") !== -1 ||
                    className.indexOf("member") !== -1 ||
                    className.indexOf("Member") !== -1 ||
                    className.indexOf("auth") !== -1 ||
                    className.indexOf("Auth") !== -1 ||
                    className.indexOf("user") !== -1 ||
                    className.indexOf("User") !== -1 ||
                    className.indexOf("login") !== -1 ||
                    className.indexOf("Login") !== -1) {
                    
                    console.log("[!] 发现验证相关类: " + className);
                    
                    try {
                        var clazz = Java.use(className);
                        var methods = clazz.class.getDeclaredMethods();
                        
                        methods.forEach(function(method) {
                            var methodName = method.getName();
                            var returnType = method.getReturnType().getName();
                            
                            // Hook所有可能返回验证结果的方法
                            if (returnType === "boolean" || returnType === "int" || returnType === "java.lang.String") {
                                try {
                                    if (clazz[methodName] && clazz[methodName].overloads) {
                                        clazz[methodName].overloads.forEach(function(overload) {
                                            overload.implementation = function() {
                                                console.log("[验证方法] " + className + "." + methodName + " 被调用");
                                                
                                                // 调用原方法获取结果
                                                var originalResult = overload.apply(this, arguments);
                                                console.log("  原始返回: " + originalResult);
                                                
                                                // 根据返回类型强制成功
                                                if (returnType === "boolean" && originalResult === false) {
                                                    console.log("  → 强制返回 true");
                                                    return true;
                                                } else if (returnType === "int" && originalResult !== 1 && originalResult !== 200) {
                                                    console.log("  → 强制返回 1");
                                                    return 1;
                                                } else if (returnType === "java.lang.String" && 
                                                          originalResult && (originalResult.indexOf("false") !== -1 || originalResult.indexOf("fail") !== -1)) {
                                                    console.log("  → 强制返回 success");
                                                    return "success";
                                                }
                                                
                                                return originalResult;
                                            };
                                        });
                                    }
                                } catch(e) {}
                            }
                        });
                    } catch(e) {}
                }
            },
            onComplete: function() {
                console.log("[*] 验证类扫描完成");
            }
        });
    }, 2000);

    // 4. 监控文件操作(可能读取本地会员配置)
    var FileInputStream = Java.use("java.io.FileInputStream");
    FileInputStream.$init.overload('java.io.File').implementation = function(file) {
        var path = file.getPath();
        if (path && (path.indexOf("vip") !== -1 || path.indexOf("config") !== -1 || path.indexOf("license") !== -1)) {
            console.log("[!] 读取验证文件: " + path);
        }
        return this.$init(file);
    };

    console.log("[+] 增强版会员验证绕过就绪");
});

 运行增强版脚本,并登录:

frida -U -p 17866 -l crash_protection.js -l enhanced_anti_detection.js -l comprehensive_vip_bypass_enhanced.js

image

 结论:还是不行,(看来是不是得买个天卡看看正确的返回到底是什么情况?)

算了,先简单分析一下:

能看到很多验证相关的类,所以接下来,这些类都得过检验:

精确Hook关键验证类:

// minimal_precise_bypass.js
Java.perform(function() {
    console.log("[*] 最小化精准绕过脚本启动");
    
    // 1. 关键发现:只Hook最核心的3个方法,避免过度侵入
    var bypassCount = 0;
    
    // 核心验证方法 - LuaNative.checkField
    var LuaNative = Java.use("com.nx.assist.lua.LuaNative");
    if (LuaNative.checkField) {
        LuaNative.checkField.overloads.forEach(function(overload) {
            overload.implementation = function(arg0, arg1, arg2) {
                var result = overload.apply(this, arguments);
                console.log("[核心验证] LuaNative.checkField 被调用");
                console.log("  参数2: " + arg2); // 应该是 httpPost
                console.log("  原始返回: " + result);
                
                if (result === 0) {
                    console.log("[!!!] 强制验证通过: 0 → 1");
                    bypassCount++;
                    return 1; // 强制成功
                }
                return result;
            };
        });
    }
    
    // 2. 增强:同时Hook可能的其他验证方法
    var criticalMethods = ["checkClass", "checkMethod", "validate", "verify"];
    criticalMethods.forEach(function(methodName) {
        if (LuaNative[methodName]) {
            LuaNative[methodName].overloads.forEach(function(overload) {
                overload.implementation = function() {
                    var result = overload.apply(this, arguments);
                    if (result === 0 || result === false) {
                        console.log("[!!!] 强制" + methodName + "通过");
                        bypassCount++;
                        return (typeof result === "boolean") ? true : 1;
                    }
                    return result;
                };
            });
        }
    });
    
    // 3. 监控验证结果应用
    var AssistNative = Java.use("com.nx.assist.AssistNative");
    if (AssistNative.toast) {
        AssistNative.toast.overloads.forEach(function(overload) {
            overload.implementation = function() {
                console.log("[Toast] 显示消息被调用");
                for (var i = 0; i < arguments.length; i++) {
                    if (arguments[i] !== null && arguments[i] !== undefined) {
                        var str = arguments[i].toString();
                        if (str.length > 0 && str.length < 100) {
                            console.log("  参数" + i + ": " + str);
                        }
                    }
                }
                return overload.apply(this, arguments);
            };
        });
    }
    
    // 4. 轻量级崩溃防护
    var System = Java.use("java.lang.System");
    System.exit.overload('int').implementation = function(code) {
        console.log("[!] 拦截退出请求: " + code);
        // 静默拦截,不执行退出
        return;
    };
    
    console.log("[+] 最小化绕过脚本就绪,已保护 " + (bypassCount + 1) + " 个验证点");
});

稳定性增强脚本:

// stability_enhancer.js
Java.perform(function() {
    console.log("[*] 稳定性增强脚本启动");
    
    // 1. 内存优化:限制日志输出
    var logCount = 0;
    var maxLogs = 50;
    
    // 2. 性能监控:检测内存使用
    var runtime = Java.use("java.lang.Runtime");
    var totalMemory = runtime.getRuntime().totalMemory();
    var maxMemory = runtime.getRuntime().maxMemory();
    console.log("[内存] 总内存: " + (totalMemory/1024/1024).toFixed(1) + "MB, 最大: " + (maxMemory/1024/1024).toFixed(1) + "MB");
    
    // 3. 防止内存泄漏的轻量级监控
    var monitoredMethods = {};
    
    function safeHook(className, methodName, hookFunction) {
        try {
            var clazz = Java.use(className);
            if (clazz[methodName]) {
                clazz[methodName].overloads.forEach(function(overload, index) {
                    overload.implementation = function() {
                        if (logCount < maxLogs) {
                            logCount++;
                            return hookFunction.apply(this, arguments);
                        } else {
                            // 超过日志限制,直接调用原方法
                            return overload.apply(this, arguments);
                        }
                    };
                });
                return true;
            }
        } catch(e) {
            console.log("[-] Hook失败: " + className + "." + methodName);
        }
        return false;
    }
    
    // 4. 应用稳定性监控
    var Thread = Java.use("java.lang.Thread");
    Thread.setDefaultUncaughtExceptionHandler.implementation = function(handler) {
        console.log("[!] 异常处理器被设置,注入我们的保护处理器");
        
        // 创建保护性异常处理器
        var ExceptionHandler = Java.registerClass({
            name: 'com.protection.ExceptionHandler',
            implements: [Java.use('java.lang.Thread$UncaughtExceptionHandler')],
            methods: {
                uncaughtException: function(thread, exception) {
                    console.log("[!!!] 捕获未处理异常: " + exception.toString());
                    console.log("[保护] 阻止应用崩溃");
                    // 不调用原处理器,静默处理
                }
            }
        });
        
        return this.setDefaultUncaughtExceptionHandler(ExceptionHandler.$new());
    };
    
    console.log("[+] 稳定性增强就绪");
});

 

 

如果进程重启了,那就重新查询一下:

frida-ps -U

 PID 是 27280

运行上面的精准稳定脚本:

frida -U -p 27280 -l minimal_precise_bypass.js -l stability_enhancer.js

image

 结论: 可能存在多重验证,checkField可能只是验证链条中的一环,还有其他验证点未被Hook

 

首先运行这个脚本,动态发现所有验证点:

// dynamic_validator_discovery.js
Java.perform(function() {
    console.log("[*] 动态验证点发现脚本启动");
    
    // 监控所有方法调用,动态发现验证点
    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            if (className.indexOf("com.nx.") !== -1 || className.indexOf("com.liushi.") !== -1) {
                try {
                    var clazz = Java.use(className);
                    var methods = clazz.class.getDeclaredMethods();
                    
                    methods.forEach(function(method) {
                        var methodName = method.getName();
                        var returnType = method.getReturnType().getName();
                        
                        // 关注返回布尔值或整型的方法(可能是验证方法)
                        if (returnType === "boolean" || returnType === "int") {
                            if (methodName.length <= 10 || 
                                methodName.indexOf("check") !== -1 ||
                                methodName.indexOf("verify") !== -1 ||
                                methodName.indexOf("valid") !== -1 ||
                                methodName.indexOf("is") === 0) {
                                
                                console.log(`[发现验证点] ${className}.${methodName} -> ${returnType}`);
                            }
                        }
                    });
                } catch(e) {}
            }
        },
        onComplete: function() {
            console.log("[*] 验证点扫描完成");
        }
    });
});

image

登录: (又发现app奔溃了)

image

 结论:崩溃发生在com.nx.assist.lua.LuaNative.compareTypes方法中,这可能是由于我们的Hook脚本过于侵入性导致的。

(自己慢慢的边调试边修改脚本,我自己尝试的过程,就不详细展示了)

 

如果还是不行,没什么能分析的了,感觉卡主了,那么可以再次尝试一下反调试的方案:使用修改版Frida

# 1. 使用HLuda(魔改版Frida)
# 下载:https://github.com/Lauraa2333/HLuda

# 2. 重命名并更改端口
mv frida-server hluda-server
./hluda-server -l 0.0.0.0:8888

# 3. 连接时指定端口
adb forward tcp:8888 tcp:8888
frida -H 127.0.0.1:8888 -l your_script.js

 

转化一下思路:

好的,我们暂时跳出代码脚本的“游击战”,从更高维度的“战略图”来审视当前的局面。

我们面临的挑战非常典型:一个经过加固、具有反调试能力、验证逻辑复杂的Android应用。

下面我们结合静态分析与动态分析的现状,重新制定一份清晰的行动纲领。

当前技术现状分析

首先,我们明确一下静态分析和动态分析当前的定位和能力边界,这有助于我们制定更合理的策略。

分析维度

现状与特点

在我们当前案例中的体现

静态分析

优势
全局视野:能在不运行代码的情况下,系统性分析所有代码路径、资源文件、配置信息,提供完整的应用蓝图。
安全性:不会触发应用的反调试或完整性检查机制。
深度可溯:结合反编译与源码分析,可以理解深层逻辑和依赖关系。
劣势
代码混淆:面对高强度的名称混淆、控制流扁平化等手段,分析难度极大。
动态加载:无法有效分析运行时动态加载的代码(如DexClassLoader加载的dex)。
逻辑丢失:反编译生成的代码(如smali、Jadx生成的Java代码)可读性差,原始逻辑可能丢失。

我们已经成功脱壳并获取了DEX文件,这是关键第一步。但目前的分析可能停留在方法名的Hook上,对验证逻辑的完整调用链核心算法缺乏深入理解。

动态分析

优势
实时洞察:可以观察程序运行时的真实行为,如函数参数、返回值、网络请求、文件操作。
绕过混淆:直接Hook方法无论其名称如何混淆,只关心其行为。
动态修改:可以实时修改内存、返回值、参数,实现“热修复”。
劣势
对抗性强:应用会检测Frida等调试环境,导致崩溃或行为异常。
覆盖性难题:像“盲人摸象”,需要准确知道要Hook哪里,否则容易迷失在海量的函数调用中。
时机问题:Hook的时机可能晚于某些验证逻辑的执行。

我们已成功Hook了多个关键方法(如 isEnableOpenAcc),并实现了返回值的强制修改,这证明动态分析是可行的。但应用在Frida退出后闪退,且登录功能未真正解锁,说明验证是多层次的,我们可能只突破了第一道防线,或触发了更深层的反制措施。

 

接下来的行动纲领

综合以上分析,我们的核心问题在于:动态分析因缺乏静态分析提供的“地图”而盲目,静态分析因混淆和复杂度而难以提供精准地图。

因此,接下来的行动纲领是:静动结合,以静制动

  1. 目标:不再盲目Hook,而是通过深入的静态分析,绘制出应用的完整验证流程图和关键函数调用栈

  2. 方法:将静态分析提升到最优先位置,进行一次彻底的地毯式侦察。然后利用获取到的精确情报,指导动态分析进行精准打击。

  3. 预期:找到最根本的验证标志位或函数,实现稳定、彻底的绕过。

详细任务清单 (Todolist)

以下是详细任务清单,我们按顺序执行。

阶段一:深度静态分析 - 绘制“作战地图”

这个阶段的目标是彻底理解应用,不再猜测。

任务序号

任务内容

具体操作与目标

所需工具

T1

全面审计应用结构

1. 确认脱壳结果:确认脱壳得到的DEX文件是否完整,是否还有多个DEX或So库需要关注。
2. 分析So库:使用反编译器(如IDA Pro)或readelfobjdump等工具,查看libassist.so等Native库导出的函数名。搜索是否有Java_com_nx_assist_开头的JNI函数,这能直接告诉我们Java层和Native层交互的精确接口。
3. 审查资源与配置:查看AndroidManifest.xml、资源文件(res/values/strings.xml等),寻找硬编码的URL、密钥、或配置开关。

Jadx-GUI, APKTool, IDA Pro

T2

定位登录按钮的完整事件链

1. 查找布局文件:在反编译的资源中,找到登录Activity的布局XML文件,定位登录按钮的ID(如btn_login)。
2. 追踪点击事件:在Jadx中全局搜索这个ID,找到其setOnClickListener方法。这个方法对应的onClick函数就是登录的起点

Jadx-GUI

T3

逆向核心验证逻辑

1. 绘制调用栈:从onClick方法开始,一步步向下分析。关注:网络请求(okhttp3, HttpURLConnection)、数据解析(JSON)、条件判断(if-else)、以及跳转到新Activity的Intent
2. 关键字符串搜索:在代码中搜索如"code""success""vip""expire"等字符串,定位到服务器返回值的判断逻辑处。
3. 分析判断分支:找到最关键的那个if语句,看看是哪个值决定了登录成功与否。记录下这个关键变量或方法。

Jadx-GUI

阶段二:精准动态验证 - 实施“定点清除”

在获得静态分析情报后,再进行动态验证。

任务序号

任务内容

具体操作与目标

T4

验证静态分析结果

1. Hook事件起点:编写一个极简的Frida脚本,只Hook在T2中找到的登录按钮的onClick方法,确保点击时能打印日志,确认我们找对了起点。
2. 监控网络层:根据T3的分析,Hook发起网络请求的类和方法,直接打印出原始的服务器响应(responseBody.string())。这是最关键的一步,它能告诉我们服务器到底返回了什么。

T5

实施精准打击

1. 修改服务器响应:如果发现服务器返回{"code": 201, "msg":"会员已到期"},那么在网络层Hook中,直接将响应体字符串修改为{"code": 200, ...},再让应用去解析。
2. Hook最终判断点:如果网络响应修改不成功,或者在T3中找到了那个最核心的判断函数(比如一个叫a()的方法返回true才能登录),那么就直接Hook这个函数,让它永远返回true这通常比HookisEnableOpenAcc这类外围函数更有效。

T6

对抗反调试与持久化

1. 处理闪退:应用闪退很可能是检测到Frida退出。可以尝试在Frida脚本中HookSystem.exit()方法,使其不执行。或者使用frida -U --no-pause -f com.liushi.nz在应用启动最早期注入。
2. 持久化状态:如果登录成功一次后,应用会将状态(如VIP标志)写入SharedPreferences或数据库。可以尝试在静态分析中找到这个存储位置和键名,在应用启动时就通过Frida脚本将其修改为成功状态。

流程图:破解工作流

graph TD
    A[开始: 深度静态分析] --> B[T1: 全面审计应用结构];
    B --> C[T2: 定位登录事件链];
    C --> D[T3: 逆向核心验证逻辑];
    D --> E[产出: 精确的关键点情报];
    E --> F[阶段二: 精准动态验证];
    F --> G[T4: 验证静态分析结果];
    G --> H[T5: 实施精准打击<br>(如修改网络响应)];
    H --> I[T6: 对抗反调试];
    I --> J[成功破解];

一些重要的提醒

  • 优先级:请务必先投入时间完成阶段一(T1-T3)。磨刀不误砍柴工,一份准确的“地图”能节省我们大量盲目调试的时间。

  • 迭代与验证:这是一个循环过程。动态验证(T4)的结果可能会修正或补充静态分析(T3)的结论,从而需要再次进行静态分析。

  • 改变思路:我们的目标从“用Frida脚本狂轰滥炸看运气”转变为“通过静态分析获得情报,进行外科手术式的精确打击”。

当我们完成阶段一的深度分析后,我们会对整个应用的运行机制有更清晰的认识,届时我们再针对找到的具体关键点来编写小而精的Frida脚本,成功率会远高于现在.

 

 ========================================================================================================================================

 好了,既然2026年了,并且环境都有的,那么直接上AI ,先让AI来一轮,(把我们的成果记得分享给AI),让他分析一下

image

 

AI的结论如下:

 

基于“静态-动态组合”策略,我已完成第一阶段:深度静态分析,并部分执行了第二阶段的初始侦察。

以下是通过分析生成的“作战地图”,它揭示了应用程序在加固(加壳)背后的内部结构。

🗺️ 第一阶段成果:“作战地图”

在成功从内存中提取核心代码(绕过加壳程序)后,确认的情报如下:

  1. 🎯 核心目标识别(T1 和 T2)

    • 真实代码库位置:该应用程序使用包装器/插件架构。

    • 外壳包名com.liushi.nz(在清单文件中可见)

    • 真实逻辑包名com.nx.main(隐藏在加密的 DEX 文件中,现已提取)

    • 关键活动com.nx.main.activity.MainActivity

      这是包含登录逻辑的实际“战场”,在启动屏(com.nx.main.activity.SplashActivity)之后动态加载。

    • 混淆状态:高。方法名被严重混淆(例如 I1I, IL1Iii, ILil)。

  2. 🔍 验证逻辑情报(T3)

    • 触发点:登录按钮点击由 com.nx.main.activity.MainActivity.onClick处理。

    • 可疑迹象

      • 发现字符串:md5_check_success, md5_check_failure, password, check

      • 这强烈表明验证过程涉及对密码(或签名)进行 MD5 哈希计算,并与硬编码值进行比较。

    • 候选方法

      • MainActivity包含几个被混淆的方法:I1I, IL1Iii, ILil

      • 其中之一很可能是 check(String password)函数。

  3. 🛠️ 加壳状态

    • 绕过成功:我们已成功使用 frida-dexdump 从内存中导出了 5MB 的 classes.dex

    • 哈希值比较

      • 磁盘上的原始文件:f8c89694fba636e92e02feebba2dcc49

      • 从内存导出的文件:9760d71f2fbb8092f907ffa6b152bee5(这是脱壳后的代码)。

🚀 第二阶段计划:精准动态验证

现在我们已经有了地图,停止猜测,开始进行“精准打击”(T4 和 T5)。

目标:在被混淆的方法中精确定位验证函数,并绕过它。

后续步骤(准备执行)

  1. 钩住 MainActivity.onClick:确认点击“登录”时该方法被调用。

  2. 跟踪被混淆的方法:钩住 I1I, IL1Iii等方法,观察哪一个接收了输入的密码字符串。

  3. “将军”步骤

    • 选项 A(绕过):强制验证方法返回 true

    • 选项 B(注册机):拦截比较逻辑,以查看正确的密码(正确密码或其 MD5 哈希值通常在比较前会以明文形式出现)。

我们是否继续进行第二阶段,并开始对 MainActivity进行 Frida 钩子操作?


已完成任务

  • ✅ 验证本地 CrackMe.apk 文件结构

  • ✅ 第一阶段:深度静态分析 - T1: 应用程序结构审计(成功:发现 com.nx.main

  • ✅ 第一阶段:深度静态分析 - T2: 登录事件链(成功:识别出 MainActivity.onClick

  • ✅ 第一阶段:深度静态分析 - T3: 逆向验证逻辑(成功:定位逻辑,发现关键词)

  • ✅ 设置 Frida 环境(验证可用)

  • ✅ 尝试基于 Frida 的脱壳(成功:获得 dump_result/classes.dex

待完成任务

  • ☐ 第二阶段:精准动态验证 - T4: 跟踪被混淆的方法

  • ☐ 第二阶段:精准动态验证 - T5: 绕过/破解逻辑

 

 

 

哦约,不错哟,它自己先dump 了一个 dex文件,看起来比我们dump的要好一点,我们顺便自己反编译一下看看

image

GDA打开一下这个dex

image

可读行太差的代码,硬着头皮看一看,(丸辣,代码加密了)

image

 

 

 不急,自己搞不定了,先接着告诉AI,我们的账号是shaun89 , 密码是XXX  , 登录后提示会员已到期,让他想办法跳过验证,保证我们能登陆使用功能;

(好了,它自己再跑了)

image

 trae 燃尽了,几轮尝试后,仍然无法过登录校验,

换kiro,再战

image

uild fingerprint: 'Android/graceltexx/gracelte:9/PQ3A.190705.12301359/G9700FXXU1APFO:user/release-keys'
Revision: '0'
ABI: 'x86'
pid: 14650, tid: 14650, name: com.liushi.nz >>> com.liushi.nz <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xffff0214
Abort message: 'FORTIFY: fclose: null FILE*'
eax f42d6310 ebx f4398754 ecx fff99c68 edx da0abe80
edi ffff0208 esi 00000000
ebp fff99dac esp fff99d80 eip f4335133

backtrace:
#00 pc 00092133 /system/lib/libc.so (offset 0x92000) (fclose+35)
#01 pc 007f5dd3 [stack:ff7a4000]
***
[Android Emulator 5554::com.liushi.nz ]->

Thank you for using Frida!
PS D:\soft\moniqi\脱壳\crackme软件>

 

很好!我们看到了反调试检测的活动:

✅ 成功阻止了 /proc/self/cmdline 和 /proc/14650/maps 的读取
✅ 阻止了 pool-frida 进程名设置
✅ 阻止了 abort() 调用
❌ 但最终还是崩溃了,可能是因为我们的hook太激进

image

AI知道多进程了,有些进展了;

com.liushi.nz 这是悬浮窗应用

com.nx.main.activity.SplashActivity 这个是启动了悬浮窗;

com.nx.sdk.ProxyActivity 这个是脚本登录

 它终于意识到了,这个外挂是有root权限的

image

 又找到了一个外挂作者,反调试的点:

image

 AI 的发现,佐证了上节课的小结: 这个外挂,有反调试,有内存保护机制,人家写外挂的就是大佬呀,不像我们这些菜鸡
(AI 还是给力呀,一天能干完我一周的进度)

image

 好了现在,只能再次尝试一下,修改版的frida了,
下节课重新搭建修改版Frida的环境;

 

 

注意:所有技术仅用于学习和安全研究,请遵守相关法律法规

 

posted on 2026-01-23 15:43  shaun88  阅读(5)  评论(0)    收藏  举报

导航