小柏实战学习安卓图文教程-第十三课-Frida动态脱壳2
本节课主题:实战跳过CrackMe.apk 的登录验证;
上节课总结:
1.Hook验证逻辑的源头,跳过验证逻辑,直接返回成功;
2.各种验证逻辑类:auth,valid,verify,check等 里面的验证方法强制修改成 true , 1 success
3.网络请求拦截: 如果发现验证请求,要么直接监控这个请求,并修改请求后的参数; 要么找到他的请求url本地修改host替换一个假的在线服务;
干!
方案一:Hook验证逻辑的源头
脚本如下,自己创建文件后执行文件:
// bypass_validation_source.js Java.perform(function() { console.log("[*] 开始绕过验证逻辑源头"); // 1. 根据调用栈,找到验证逻辑的入口 // 之前的调用栈显示:iI1i丨I.run → IL1Iii → ILil // 我们需要Hook iI1i丨I.run 方法 try { var ValidationTask = Java.use("com.nx.assist.iI1i丨I"); console.log("[+] 找到验证任务类: com.nx.assist.iI1i丨I"); // Hook run 方法(验证逻辑的入口) ValidationTask.run.implementation = function() { console.log("[!!!] 验证任务被触发,开始绕过..."); // 方案A:直接返回,不执行验证 console.log("[BYPASS] 跳过验证逻辑,直接返回成功"); return; // 不执行原逻辑 // 方案B:如果需要返回值,根据实际情况返回 // 如果是布尔型验证:return true; // 如果是整型验证:return 1; (成功) }; } catch(e) { console.log("[-] 直接Hook验证任务失败: " + e.message); } // 2. 同时Hook可能存在的验证方法 try { // 搜索所有可能包含验证逻辑的类 Java.enumerateLoadedClasses({ onMatch: function(className) { if (className.indexOf("com.nx.") !== -1) { // 检查类名是否包含验证相关关键词 var lowerClassName = className.toLowerCase(); if (lowerClassName.indexOf("check") !== -1 || lowerClassName.indexOf("verify") !== -1 || lowerClassName.indexOf("valid") !== -1 || lowerClassName.indexOf("auth") !== -1) { console.log("[CLASS] 发现验证相关类: " + 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 (returnType === "boolean" || returnType === "int" || returnType === "java.lang.String") { console.log(" [METHOD] 可能验证方法: " + methodName + " → " + returnType); // Hook这些方法 try { clazz[methodName].overloads.forEach(function(overload) { overload.implementation = function() { console.log("[VERIFY] 调用验证: " + className + "." + methodName); // 根据返回类型强制返回成功 if (returnType === "boolean") { console.log("[BYPASS] 布尔验证 → 强制返回 true"); return true; } else if (returnType === "int") { console.log("[BYPASS] 整型验证 → 强制返回 1"); return 1; } else if (returnType === "java.lang.String") { console.log("[BYPASS] 字符串验证 → 强制返回 'success'"); return "success"; } return overload.apply(this, arguments); }; }); } catch(e) {} } }); } catch(e) {} } } }, onComplete: function() { console.log("[*] 验证类扫描完成"); } }); } catch(e) { console.log("[-] 验证类扫描失败: " + e.message); } });
方案二:网络请求拦截(如果验证涉及服务器)
// bypass_network_validation.js Java.perform(function() { console.log("[*] 开始拦截网络验证请求"); // 1. 监控HTTP请求 try { var URL = Java.use("java.net.URL"); URL.openConnection.implementation = function() { var url = this.toString(); console.log("[NETWORK] 请求URL: " + url); // 如果是验证相关的请求,可以修改响应 if (url.indexOf("verify") !== -1 || url.indexOf("check") !== -1 || url.indexOf("vip") !== -1) { console.log("[!!!] 发现验证请求: " + url); } return this.openConnection(); }; } catch(e) {} // 2. 监控OkHttp(如果使用) try { var OkHttpClient = Java.use("okhttp3.OkHttpClient"); var RealCall = Java.use("okhttp3.RealCall"); RealCall.execute.implementation = function() { var request = this.request(); var url = request.url().toString(); console.log("[OKHTTP] 请求: " + url); return this.execute(); }; } catch(e) {} });
方案三:综合绕过脚本(推荐尝试)
// comprehensive_bypass.js Java.perform(function() { console.log("[*] 综合绕过脚本启动"); var bypassEnabled = true; // 1. 修改Toast显示(用户体验) var ToastUtil = Java.use("com.nx.assist.ILL丨Ii"); ToastUtil.ILil.overload('java.lang.String', 'int', 'int', 'int').implementation = function(message, duration, x, y) { if (bypassEnabled && message && message.indexOf("会员") !== -1) { console.log("[UI] 修改提示: '" + message + "' → '验证通过'"); message = "验证通过,欢迎使用!"; } return this.ILil(message, duration, x, y); }; // 2. 拦截验证任务 try { var ValidationTask = Java.use("com.nx.assist.iI1i丨I"); ValidationTask.run.implementation = function() { if (bypassEnabled) { console.log("[!!!] 拦截验证任务,直接返回成功"); return; // 跳过验证 } return this.run(); }; } catch(e) { console.log("[-] 验证任务Hook失败: " + e.message); } // 3. 修改系统属性读取(如果有本地验证) try { var System = Java.use("java.lang.System"); System.getProperty.overload('java.lang.String').implementation = function(key) { if (bypassEnabled && key && (key.indexOf("vip") !== -1 || key.indexOf("member") !== -1)) { console.log("[SYSTEM] 读取属性: " + key + " → 返回 'true'"); return "true"; } return this.getProperty(key); }; } catch(e) {} // 4. 修改SharedPreferences(本地存储的验证状态) try { var SharedPreferences = Java.use("android.content.SharedPreferences"); SharedPreferences.getBoolean.overload('java.lang.String', 'boolean').implementation = function(key, defValue) { if (bypassEnabled && key && (key.indexOf("vip") !== -1 || key.indexOf("is_login") !== -1)) { console.log("[SP] 读取布尔值: " + key + " → 返回 true"); return true; } return this.getBoolean(key, defValue); }; } catch(e) {} console.log("[*] 综合绕过脚本就绪"); });
上面三个尝试都不行,都是假的通过,实际没有通过,
可能的原因:
1.验证逻辑不止一处:可能有多重验证机制,用户只绕过了一部分。
2.验证在Native层:验证逻辑可能在.so文件中,而不是Java层。
3.网络验证:应用可能依赖服务器端验证,本地绕过无效。
4.签名或完整性检查:应用可能检测到Frida或代码修改,拒绝运行。
5.混淆和反调试:验证逻辑被深度混淆,用户Hook的点不正确。
我们监听捕获到了Toast显示"会员已到期",并找到了相关的类和方法。但现在绕过Toast和验证任务后,功能仍无效,说明需要更深入的分析。
下一步建议:
1.动态分析验证流程:使用Frida跟踪整个验证过程,包括网络请求、文件读写、系统调用等。
2.检查网络请求:验证可能涉及服务器通信,需要监控网络活动。
3.Native层监控:如果验证在C/C++层,需要使用Frida的NativeAPI。
4.完整性检查:应用可能检查自身完整性,防止篡改,需要绕过这些检查。
5.多维度Hook:同时Hook多个点,确保覆盖所有验证路径。
综上所属我们接下来的具体行动方案:
步骤1:监控网络请求
-使用FridaHook网络库(如OkHttp、HttpURLConnection)来捕获所有网络流量。
-检查是否有验证请求发送到服务器,并分析响应。
步骤2:Native层分析
-使用Frida的NativeAPI来Hook .so文件中的函数。-特别是查找与验证、解密、签名相关的函数。
步骤3:全面方法跟踪
-使用Frida的Stalker功能来跟踪代码执行流,找到真正的验证点。
-或者使用Frida的API来跟踪所有方法调用,过滤出关键路径。
步骤4:反反调试-应用可能检测Frida,需要实施反反调试措施,如隐藏Frida痕迹。
步骤5:静态分析辅助
一:网络请求监控(关键!)
很多应用会向服务器验证会员状态,本地绕过无效。创建网络监控脚本:
// network_monitor.js Java.perform(function() { console.log("[*] 修正版网络监控启动"); // 1. 修复URL.openConnection监控 - 处理所有重载 try { var URL = Java.use("java.net.URL"); // Hook无参数重载 URL.openConnection.overload().implementation = function() { var url = this.toString(); if (url.indexOf("http") !== -1) { console.log("[NETWORK] 请求URL: " + url); if (isValidationRequest(url)) { console.log("[!!!] 可能验证请求: " + url); printFilteredStackTrace(); } } return this.openConnection(); }; // Hook带Proxy参数的重载 URL.openConnection.overload('java.net.Proxy').implementation = function(proxy) { var url = this.toString(); if (url.indexOf("http") !== -1) { console.log("[NETWORK] 代理请求URL: " + url); if (isValidationRequest(url)) { console.log("[!!!] 可能验证请求: " + url); printFilteredStackTrace(); } } return this.openConnection(proxy); }; console.log("[+] URL.openConnection监控就绪"); } catch(e) { console.log("[-] URL监控失败: " + e.message); } // 2. 监控HttpURLConnection的更具体方法 try { var HttpURLConnection = Java.use("java.net.HttpURLConnection"); // Hook连接建立 HttpURLConnection.connect.implementation = function() { var url = this.getURL().toString(); console.log("[HTTP] 连接: " + url); if (isValidationRequest(url)) { console.log("[!!!] HTTP验证请求: " + url); printFilteredStackTrace(); } return this.connect(); }; // Hook获取输入流(实际数据请求) HttpURLConnection.getInputStream.implementation = function() { var url = this.getURL().toString(); console.log("[HTTP] 获取输入流: " + url); if (isValidationRequest(url)) { console.log("[!!!] HTTP验证数据请求: " + url); printFilteredStackTrace(); } return this.getInputStream(); }; console.log("[+] HttpURLConnection监控就绪"); } catch(e) { console.log("[-] HttpURLConnection监控失败: " + e.message); } // 3. 监控WebView网络请求 try { var WebView = Java.use("android.webkit.WebView"); WebView.loadUrl.overload('java.lang.String').implementation = function(url) { if (url.indexOf("http") !== -1) { console.log("[WEBVIEW] 加载URL: " + url); } return this.loadUrl(url); }; console.log("[+] WebView监控就绪"); } catch(e) { console.log("[-] WebView监控失败: " + e.message); } console.log("[*] 网络监控脚本注入完成"); }); // 辅助函数:判断是否是验证请求 function isValidationRequest(url) { return url && (url.indexOf("verify") !== -1 || url.indexOf("vip") !== -1 || url.indexOf("member") !== -1 || url.indexOf("auth") !== -1 || url.indexOf("check") !== -1 || url.indexOf("validate") !== -1); } // 辅助函数:打印过滤后的调用栈 function printFilteredStackTrace() { Java.perform(function() { var stackTrace = Java.use("android.util.Log").getStackTraceString( Java.use("java.lang.Exception").$new() ); var lines = stackTrace.split("\n"); console.log("[STACK] 过滤后的调用栈:"); for (var i = 2; i < Math.min(lines.length, 10); i++) { var line = lines[i].trim(); if (line.length > 0 && line.indexOf("android.") === -1 && line.indexOf("java.") === -1 && line.indexOf("com.android.") === -1) { console.log(" => " + line); } } }); }
结果是点了登录没有反应:

点击登录按钮后网络监控脚本没有捕获到任何请求,
这确实表明验证逻辑可能不在Java层或网络层,而很可能在Native层(.so文件)中实现。
因为许多加固应用会将核心验证逻辑放在Native代码中以增加反调试难度。
以下是针对Native层验证的完整解决方案:
为什么需要监控Native层?
-
核心逻辑隐藏:商业级加固常将关键验证放在Native层,避免Java层被轻易Hook
-
反调试机制:Native代码更容易检测调试器和Frida
-
性能考虑:敏感操作(如加密验证)在Native层执行更快
Native层监控方案:
方案一:基础Native模块分析
// native_basic_monitor.js Java.perform(function() { console.log("[*] 开始Native层基础分析"); // 1. 枚举所有已加载的Native模块 console.log("[*] 已加载的Native模块:"); Process.enumerateModules({ onMatch: function(module) { // 过滤出可能包含验证逻辑的模块 if (module.name.indexOf("nz") !== -1 || module.name.indexOf("nx") !== -1 || module.name.indexOf("liushi") !== -1 || module.name.indexOf("vip") !== -1 || module.name.indexOf("auth") !== -1) { console.log("[MODULE] 发现相关模块: " + module.name + " 基址: " + module.base + " 大小: " + module.size + " bytes"); // 列出模块的导出函数 module.enumerateExports().slice(0, 10).forEach(function(exp) { console.log(" [EXPORT] " + exp.name + " @ " + exp.address); }); } }, onComplete: function() { console.log("[*] 模块枚举完成"); } }); // 2. 监控JNI调用(Java与Native的桥梁) setTimeout(function() { console.log("[*] 开始监控JNI调用..."); // 查找可能的JNI函数 var symbols = Process.findModuleByName("libart.so").enumerateSymbols(); symbols.forEach(function(symbol) { if (symbol.name.indexOf("CheckJNI") !== -1 || symbol.name.indexOf("JNI") !== -1) { console.log("[JNI] 发现JNI相关符号: " + symbol.name); } }); }, 1000); });

方案二:深度Native函数Hook
// native_deep_hook.js Java.perform(function() { console.log("[*] Native层深度Hook启动"); // 1. 首先查找应用相关的Native库 var targetModules = []; Process.enumerateModules({ onMatch: function(module) { // 寻找应用自定义的Native库 if (module.path.indexOf("com.liushi.nz") !== -1 || module.name.indexOf("nz") !== -1 || module.name.indexOf("nx") !== -1) { console.log("[!!!] 发现目标Native库: " + module.name + " @ " + module.path); targetModules.push(module); // 尝试Hook这个库的导出函数 hookModuleExports(module); } }, onComplete: function() { console.log("[*] 共发现 " + targetModules.length + " 个目标模块"); } }); function hookModuleExports(module) { var exports = module.enumerateExports(); console.log("[*] 模块 " + module.name + " 有 " + exports.length + " 个导出函数"); // Hook前20个导出函数(避免过多) exports.slice(0, 20).forEach(function(exp) { try { // 只Hook可能相关的函数(根据命名模式) if (isSuspiciousFunction(exp.name)) { console.log("[HOOK] 尝试Hook: " + exp.name + " @ " + exp.address); Interceptor.attach(exp.address, { onEnter: function(args) { console.log("[NATIVE] 调用: " + exp.name); console.log(" [ARGS] 参数个数: " + this.context.argc); // 打印调用栈(Native层) console.log(" [BACKTRACE]:"); this.backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE); this.backtrace.forEach(function(frame, i) { var module = Process.findModuleByAddress(frame); if (module) { console.log(" " + i + ": " + module.name + "+" + (frame - module.base)); } }); }, onLeave: function(retval) { console.log(" [RETURN]: " + retval); } }); } } catch(e) { console.log("[-] Hook失败: " + exp.name + " - " + e.message); } }); } function isSuspiciousFunction(name) { // 根据常见验证函数命名模式过滤 var patterns = ["verify", "check", "valid", "auth", "vip", "member", "license", "init"]; return patterns.some(function(pattern) { return name.toLowerCase().indexOf(pattern) !== -1; }); } });

重要线索:
[!!!] 发现目标Native库: libassist.so @ /data/app/com.liushi.nz-8v4srfVt64fAsUl9lmPT3w==/lib/x86/libassist.so
[*] 模块 libassist.so 有 7346 个导出函数
方案三:字符串内存扫描
// native_string_scan.js Java.perform(function() { console.log("[*] 开始Native层字符串扫描"); // 在内存中搜索中文字符串(如"会员已到期") var patterns = ["会员已到期", "会员", "验证失败", "登录成功", "vip", "auth"]; patterns.forEach(function(pattern) { // 将字符串转换为字节序列 var patternBytes = []; for (var i = 0; i < pattern.length; i++) { patternBytes.push(pattern.charCodeAt(i)); } // 扫描所有可读内存区域 Process.enumerateRanges('r--').forEach(function(range) { if (range.size > 1000) { // 只扫描大于1KB的区域 try { Memory.scan(range.base, range.size, patternBytes, { onMatch: function(address, size) { console.log("[!!!] 发现字符串 '" + pattern + "' 在: " + address); console.log(" 模块: " + (Process.findModuleByAddress(address) || {}).name); console.log(" 偏移: " + address.sub(range.base)); // 尝试反汇编附近的代码 try { var insn = Instruction.parse(address); console.log(" 指令: " + insn.toString()); } catch(e) {} }, onComplete: function() {} }); } catch(e) {} } }); }); });
上面的看了一圈,
这个 libassist.so库非常可疑:
-
库名:
assist(辅助)暗示它可能包含核心功能逻辑 -
函数数量:7346个导出函数说明这是一个功能丰富的核心库
-
路径:位于应用私有目录,是应用的自定义Native库
所以验证逻辑很可能分布在Java层和Native层:
-
✅ Java层:处理UI交互和Toast显示(如之前捕获的"会员已到期")
-
✅ Native层:在
libassist.so中进行实际的身份验证和会员状态检查 -
🔄 协同工作:Java层调用Native层进行验证,然后根据返回结果显示结果
深入分析libassist.so
以下是针对性的Native层深度分析脚本:
// libassist_deep_hook.js Java.perform(function() { console.log("[*] 开始深度分析 libassist.so"); // 1. 定位并Hook libassist.so var libassist = Process.findModuleByName("libassist.so"); if (libassist) { console.log("[!!!] 发现目标库: " + libassist.name); console.log(" 基址: " + libassist.base); console.log(" 大小: " + libassist.size + " bytes"); console.log(" 路径: " + libassist.path); // 2. 枚举导出函数,寻找验证相关函数 var exports = libassist.enumerateExports(); console.log("[*] 扫描导出函数..."); var suspiciousFunctions = []; var patterns = ["verify", "check", "valid", "auth", "vip", "member", "login", "init"]; exports.forEach(function(exp) { var name = exp.name; patterns.forEach(function(pattern) { if (name.toLowerCase().indexOf(pattern) !== -1) { suspiciousFunctions.push({ name: name, address: exp.address, type: exp.type }); } }); }); console.log("[*] 发现 " + suspiciousFunctions.length + " 个可疑函数"); // 3. Hook可疑函数 suspiciousFunctions.forEach(function(func) { console.log("[HOOK] 尝试Hook: " + func.name + " @ " + func.address); try { Interceptor.attach(func.address, { onEnter: function(args) { console.log("[!!!] libassist." + func.name + " 被调用"); // 记录参数 for (var i = 0; i < Math.min(args.length, 6); i++) { try { var argValue = args[i]; if (argValue instanceof NativePointer) { try { var str = argValue.readCString(); if (str && str.length < 100) { console.log(" 参数" + i + " (字符串): " + str); } else { console.log(" 参数" + i + " (指针): " + argValue); } } catch(e) { console.log(" 参数" + i + " (指针): " + argValue); } } else { console.log(" 参数" + i + ": " + argValue); } } catch(e) { console.log(" 参数" + i + ": [读取失败]"); } } // 记录调用栈(Native层) console.log(" [BACKTRACE]:"); var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE); for (var i = 0; i < Math.min(backtrace.length, 8); i++) { var module = Process.findModuleByAddress(backtrace[i]); if (module) { console.log(" " + i + ": " + module.name + "+" + (backtrace[i] - module.base)); } else { console.log(" " + i + ": " + backtrace[i]); } } }, onLeave: function(retval) { console.log(" 返回: " + retval); // 如果是验证函数,尝试修改返回值 if (func.name.indexOf("verify") !== -1 || func.name.indexOf("check") !== -1) { console.log("[!!!] 尝试绕过验证,强制返回成功 (1)"); retval.replace(ptr(0x1)); // 返回1表示成功 } } }); } catch(e) { console.log("[-] Hook失败: " + func.name + " - " + e.message); } }); // 4. 监控JNI调用(Java与Native的桥梁) console.log("[*] 监控JNI调用..."); var JNIEnv = Java.vm.getEnv(); // Hook RegisterNatives来发现动态注册的Native方法 var RegisterNatives = Module.findExportByName("libart.so", "JNI_RegisterNatives"); if (RegisterNatives) { Interceptor.attach(RegisterNatives, { onEnter: function(args) { var clazz = args[1]; var methods = args[2]; var nMethods = args[3]; console.log("[JNI] 注册 " + nMethods + " 个Native方法"); // 尝试获取类名 try { var className = Java.vm.tryGetEnv().getClassName(clazz); console.log(" 类: " + className); } catch(e) {} } }); } } else { console.log("[-] 未找到 libassist.so"); } console.log("[*] libassist.so 分析脚本就绪"); });
运行脚本后,
点击登录按钮,观察输出。
重点关注:
-
哪些
libassist.so函数被调用 -
函数的参数和返回值
-
调用栈信息

一、脚本执行结果总结
从输出日志看,脚本成功识别了 libassist.so库(基址 0xd320c000,大小约 10MB),并枚举了 390 个"可疑函数"(名称包含 verify、check、init等关键词)。脚本尝试 Hook 这些函数,但出现了以下关键问题:
-
部分 Hook 失败:一些函数无法拦截(如
CMS_Attributes_Verify_it),可能由于地址无效或内存保护。 -
运行时错误:当函数被调用时(如
BIO_set_init),脚本在访问参数时抛出RangeError: invalid array index,表明在读取函数参数时索引越界。 -
进程崩溃:最终进程因空指针解引用(SIGSEGV)崩溃,调用栈显示崩溃发生在
libc.so的strlen函数中,源头是libassist.so内的 Lua 相关函数(如lua_pcallk)。这可能是由于 Hook 逻辑错误地修改了内存或参数。
二、问题根因分析
-
脚本逻辑缺陷:
-
批量 Hook 所有"可疑函数"过于激进,未验证函数签名。Native 函数参数数量和类型未知,直接通过索引访问
args[i]可能导致越界。 -
在
onLeave回调中强制修改返回值(如retval.replace(ptr(0x1)))可能破坏函数约定,导致后续代码处理无效数据而崩溃。
-
-
库复杂性:
-
libassist.so是一个大型库(7346 个导出函数),内含 OpenSSL、Lua 等第三方组件。盲目 Hook 可能干扰底层逻辑(如加密操作或脚本执行)。 -
崩溃调用栈中频繁出现
lua_前缀函数(如lua_pcallk),表明该库使用 Lua 脚本引擎。若 Hook 影响了 Lua 虚拟机状态,易导致崩溃。
-
-
反调试机制:
-
应用可能检测到 Frida 的 Hook 行为,主动触发崩溃以阻止分析。崩溃地址
0x1(无效指针)暗示可能是有意的反调试陷阱。
-
三、再改一版
// safe_libassist_monitor.js Java.perform(function() { console.log("[*] 安全监控脚本启动 - 仅日志记录,不修改行为"); var libassist = Process.findModuleByName("libassist.so"); if (!libassist) { console.log("[-] 未找到 libassist.so"); return; } // 1. 只 Hook 少数关键函数,避免批量操作 var targetedFunctions = [ "lua_pcallk", // Lua 调用相关 "BIO_set_init", // 加密相关 "EVP_DecodeInit" // Base64 解码 ]; targetedFunctions.forEach(funcName => { var funcAddr = Module.findExportByName("libassist.so", funcName); if (funcAddr) { Interceptor.attach(funcAddr, { onEnter: function(args) { // 安全记录:不访问 args 数组,仅记录调用 console.log(`[CALL] ${funcName} 被调用`); // 打印调用栈(限制深度) console.log(" [BACKTRACE]:"); var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0, 5); backtrace.forEach((addr, i) => { var mod = Process.findModuleByAddress(addr); console.log(` ${i}: ${mod ? mod.name : "unknown"} + ${addr - (mod?.base || 0)}`); }); }, onLeave: function(retval) { // 不修改返回值,仅记录 console.log(` [RET] ${funcName} 返回: ${retval}`); } }); console.log(`[+] 已安全Hook: ${funcName}`); } }); // 2. 监控 JNI 调用,定位 Java 与 Native 交互 var JNIEnv = Java.vm.getEnv(); if (JNIEnv) { console.log("[*] 监控 JNI 调用..."); // 可添加 JNI 函数 Hook,如 CallStaticBooleanMethod 等 } console.log("[*] 安全监控就绪"); });
前面异常奔溃了,
所以重新找一下app 的 pid:
frida-ps -U
再重新执行:
frida -U -p <PID> -l safe_libassist_monitor.js
输出如下:

点击登录,结果如下:

我们看到了非常重要的信息!验证逻辑确实在Native层执行,特别是通过Lua脚本引擎。
以下是详细分析
1. Lua脚本引擎活跃
[CALL] lua_pcallk 被调用
[RET] lua_pcallk 返回: 0x2
[CALL] lua_pcallk 被调用
[RET] lua_pcallk 返回: 0x0
小结:
lua_pcallk是Lua虚拟机执行函数的关键调用
返回值 0x2和 0x0表示不同的执行状态(Lua调用结果)
2. 加密/解密操作
[CALL] EVP_DecodeInit 被调用
[CALL] BIO_set_init 被调用
小结:
EVP_DecodeInit用于Base64解码初始化
BIO_set_init是OpenSSL BIO系统初始化
3. 调用链分析
从调用栈可以看到验证流程:
接下来就是 深度Lua监控 :
以下是针对性的Lua深度监控脚本:
// lua_deep_monitor_fixed.js Java.perform(function() { console.log("[*] 修复版Lua深度监控启动"); var libassist = Process.findModuleByName("libassist.so"); if (!libassist) { console.log("[-] 未找到 libassist.so"); return; } // 1. 安全地监控Lua函数 var luaFunctions = [ "lua_pcallk", // Lua函数调用 "lua_callk", // Lua调用 "luaL_loadstring", // 加载Lua字符串 "lua_getglobal", // 获取全局变量 "lua_getfield", // 获取字段 "lua_tolstring", // 转换为字符串 "lua_toboolean" // 转换为布尔值 ]; luaFunctions.forEach(funcName => { var funcAddr = Module.findExportByName("libassist.so", funcName); if (funcAddr) { try { Interceptor.attach(funcAddr, { onEnter: function(args) { console.log(`\n[LUA] === ${funcName} 被调用 ===`); // 修复:安全地访问参数 try { // 使用安全的参数访问方式 var argCount = 0; for (let i = 0; i < 10; i++) { // 最大尝试10个参数 try { if (args[i] !== undefined && !args[i].isNull()) { argCount = i + 1; } else { break; } } catch(e) { break; } } // 只访问有效的参数 for (let i = 0; i < argCount && i < 6; i++) { try { if (args[i].isNull()) { console.log(` 参数${i}: NULL`); continue; } // 尝试读取指针内容 try { let str = args[i].readCString(); if (str && str.length > 0) { if (str.length < 100) { console.log(` 参数${i} (字符串): "${str}"`); } else { console.log(` 参数${i} (长字符串): 长度=${str.length}`); // 检查关键词 if (str.indexOf("vip") !== -1 || str.indexOf("member") !== -1 || str.indexOf("会员") !== -1) { console.log(` [!!!] 包含关键词: ${str.substring(0, 50)}...`); } } } else { console.log(` 参数${i}: 指针 0x${args[i]}`); } } catch(e) { console.log(` 参数${i}: 指针 0x${args[i]} [非字符串]`); } } catch(e) { console.log(` 参数${i}: [读取失败: ${e.message}]`); } } } catch(e) { console.log(" [参数访问错误]: " + e.message); } // 安全地获取调用栈 try { console.log(" [调用栈]:"); let backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE); for (let i = 0; i < Math.min(backtrace.length, 5); i++) { let addr = backtrace[i]; let mod = Process.findModuleByAddress(addr); if (mod && mod.name === "libassist.so") { let offset = addr - mod.base; console.log(` ${i}: ${mod.name} + 0x${offset.toString(16)}`); } else if (i < 2) { let mod = Process.findModuleByAddress(addr); if (mod) { console.log(` ${i}: ${mod.name} + 0x${(addr - mod.base).toString(16)}`); } else { console.log(` ${i}: 0x${addr}`); } } } } catch(e) { console.log(" [调用栈获取失败]"); } }, onLeave: function(retval) { console.log(` [返回]: ${retval}`); // 对于lua_tolstring,安全地读取返回字符串 if (funcName === "lua_tolstring" && retval && !retval.isNull()) { try { let str = retval.readCString(); if (str && str.length > 0) { let displayStr = str.length > 200 ? str.substring(0, 200) + "..." : str; console.log(` [LUA-STRING] 内容: "${displayStr}"`); // 检查关键内容 if (str.indexOf("会员") !== -1 || str.indexOf("vip") !== -1 || str.indexOf("成功") !== -1 || str.indexOf("失败") !== -1 || str.indexOf("到期") !== -1) { console.log(` [!!!] 发现关键字符串: ${str.substring(0, 100)}`); } } } catch(e) { // 忽略读取错误 } } } }); console.log(`[+] 已安全Hook: ${funcName}`); } catch(e) { console.log(`[-] Hook失败 ${funcName}: ${e.message}`); } } else { console.log(`[-] 未找到导出函数: ${funcName}`); } }); console.log("[*] Lua深度监控就绪(已修复)"); });
点击登录后的结果如下:

关键发现(不容易呀!)
1.Lua脚本控制:应用使用Lua脚本处理登录验证逻辑。
2.网络请求构建:通过Lua函数构建了完整的HTTP请求参数。
3.服务器响应:服务器返回了JSON数据:{"code":201,"msg":"\u4f1a\u5458\u5df2\u5230\u671f","time":1768816973},其中msg字段解码后为“会员已到期”。
4.关键字符串:在最后一步,我们捕获到了“会员已到期”这个字符串。
小结:
我们看到了完整的登录验证流程:
1. 验证机制完全解密
-
✅ 使用Lua脚本:应用通过Lua脚本处理验证逻辑
-
✅ 网络验证:向服务器发送HTTP POST请求进行验证
-
✅ 参数构造:构建完整的登录参数,包括签名验证
-
✅ 服务器响应:服务器返回JSON格式的验证结果
2. 具体验证流程
1. 构建请求参数: - account=shaun89 - password=Qq123456 - markcode=3325ded7708e34e8cd4a35ec564b5765 - t=1768816975 (时间戳) - sign=e085be074e277fe063015ac161d2b9b4 (签名) 2. 发送请求: POST http://47.96.230.2:5147/api.php?act=user_logon&app=10000&account=shaun89... 3. 服务器返回: {"code":201,"msg":"\u4f1a\u5458\u5df2\u5230\u671f","time":1768816973} (解码后:"会员已到期")
所以,我们的绕过方案:
1.修改服务器响应
// bypass_server_response.js Java.perform(function() { console.log("[*] 服务器响应修改脚本启动"); var libassist = Process.findModuleByName("libassist.so"); if (!libassist) { console.log("[-] 未找到 libassist.so"); return; } // Hook lua_tolstring 函数,在返回服务器响应时修改内容 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("[!!!] 发现服务器响应:会员已到期"); console.log(" 原始响应: " + str); // 修改为成功响应 var newResponse = '{"code":200,"msg":"\\u767b\\u5f55\\u6210\\u529f","time":' + Math.floor(Date.now() / 1000) + '}'; console.log("[!!!] 修改为: " + newResponse); // 分配新内存并写入修改后的字符串 var newPtr = Memory.allocUtf8String(newResponse); retval.replace(newPtr); } } catch(e) { // 忽略读取错误 } } } }); console.log("[+] 已Hook lua_tolstring,准备修改服务器响应"); } });

结论:还是没效果;
2.修改验证结果判断
// bypass_validation_logic.js Java.perform(function() { console.log("[*] 验证逻辑绕过脚本启动"); // 监控JSON解析,修改code字段 var targetFunctions = ["lua_tolstring", "lua_tonumber", "lua_toboolean"]; targetFunctions.forEach(funcName => { var funcAddr = Module.findExportByName("libassist.so", funcName); if (funcAddr) { Interceptor.attach(funcAddr, { onLeave: function(retval) { if (funcName === "lua_tolstring" && retval && !retval.isNull()) { try { var str = retval.readCString(); if (str && (str === "201" || str === "会员已到期")) { console.log("[!!!] 发现验证失败代码/消息,进行修改"); if (str === "201") { // 修改错误代码为成功代码 var newStr = "200"; var newPtr = Memory.allocUtf8String(newStr); retval.replace(newPtr); } else if (str === "会员已到期") { // 修改错误消息为成功消息 var newStr = "登录成功"; var newPtr = Memory.allocUtf8String(newStr); retval.replace(newPtr); } } } catch(e) {} } } }); } }); });

还是不行;
深呼吸,分析一下:
这说明应用可能存在多重验证机制或反调试检测。
根据我们的分析流程,现在需要深入排查其他可能的验证环节和防御机制。
这节课到这里,下节课继续
注意:所有技术仅用于学习和安全研究,请遵守相关法律法规
浙公网安备 33010602011771号