应用安全 --- 安卓安全 之 恋人精灵高级版hook脚本
/** * Frida综合Hook脚本 - 字符串比较绕过 + Lua解密监控 * 功能1: 强制sub_7F9EF字符串比较函数返回1 (优先执行) * 功能2: 监控sub_13860A函数的LuaState结构体和Lua字节码解密 * 目标进程: com.test.mlgm:sc * 执行顺序: 先绕过字符串验证,再监控Lua解密过程 */ console.log("[+] ========================================"); console.log("[+] 综合Hook脚本启动"); console.log("[+] 功能: 字符串绕过 + Lua解密监控"); console.log("[+] ========================================"); // ==================== 全局配置 ==================== const GLOBAL_CONFIG = { // 模块配置 TARGET_MODULE_NAMES: [ "libengine.so", "libnative.so", "libengine_exported_functions.so" ], // 函数配置 STRING_COMPARE_FUNCTION: { name: "sub_7F9EF", offset: 0x7F9EF, description: "字符串比较函数" }, LUA_DECRYPT_FUNCTION: { name: "sub_13860A", offset: 0x13860A, description: "Lua解密函数" }, // 日志配置 LOGGING: { enableDetailedStringLog: false, // 减少字符串hook的详细日志 enableDetailedLuaLog: true, // 启用Lua详细日志 enableHexDump: true, // 启用hex dump maxDumpSize: 500 // 最大dump大小 }, // 安全配置 SAFETY: { maxStringReadLength: 32, // 字符串读取最大长度 maxMemoryDumpSize: 1000, // 内存dump最大大小 enableSafeMemoryAccess: true // 启用安全内存访问 } }; // ==================== 全局状态管理 ==================== const GLOBAL_STATE = { targetModule: null, stringHookInstalled: false, luaHookInstalled: false, hookStats: { stringCompareCalls: 0, luaDecryptCalls: 0, startTime: Date.now() } }; // ==================== 通用工具函数 ==================== // 查找目标模块 function findTargetModule() { console.log("[+] 正在查找目标模块..."); for (const moduleName of GLOBAL_CONFIG.TARGET_MODULE_NAMES) { const module = Process.findModuleByName(moduleName); if (module) { console.log(`[+] 找到目标模块: ${moduleName}`); console.log(`[+] 模块基址: ${module.base}`); console.log(`[+] 模块大小: 0x${module.size.toString(16)}`); return module; } } console.log("[!] 未找到任何目标模块,尝试的模块名:"); GLOBAL_CONFIG.TARGET_MODULE_NAMES.forEach(name => console.log(`[!] - ${name}`)); // 列出相关模块 console.log("[+] 当前已加载的相关模块:"); Process.enumerateModules().forEach(mod => { if (mod.name.toLowerCase().includes('engine') || mod.name.toLowerCase().includes('native') || mod.name.toLowerCase().includes('lib')) { console.log(`[+] - ${mod.name} (${mod.base})`); } }); return null; } // 等待模块加载 function waitForModule() { return new Promise((resolve) => { const checkModule = () => { const module = findTargetModule(); if (module) { resolve(module); } else { setTimeout(checkModule, 500); } }; checkModule(); }); } // 安全内存读取函数 function safeReadMemory(address, size, description) { try { if (!address || address.isNull()) { if (GLOBAL_CONFIG.LOGGING.enableDetailedLuaLog) { console.log(`[!] ${description}: 地址为NULL`); } return null; } return Memory.readByteArray(address, size); } catch (e) { if (GLOBAL_CONFIG.LOGGING.enableDetailedLuaLog) { console.log(`[!] ${description}: 内存读取失败 - ${e.message}`); } return null; } } function safeReadPointer(address, description) { try { if (!address || address.isNull()) { return ptr(0); } return address.readPointer(); } catch (e) { if (GLOBAL_CONFIG.LOGGING.enableDetailedLuaLog) { console.log(`[!] ${description}: 指针读取失败 - ${e.message}`); } return ptr(0); } } function safeReadU32(address, description) { try { if (!address || address.isNull()) { return 0; } return address.readU32(); } catch (e) { if (GLOBAL_CONFIG.LOGGING.enableDetailedLuaLog) { console.log(`[!] ${description}: U32读取失败 - ${e.message}`); } return 0; } } function safeHexdump(address, size, description) { if (!GLOBAL_CONFIG.LOGGING.enableHexDump) return; try { if (!address || address.isNull()) { console.log(`[!] ${description}: 地址为NULL,无法进行hexdump`); return; } console.log(`[+] ${description} hexdump:`); console.log(hexdump(address, { offset: 0, length: Math.min(size, GLOBAL_CONFIG.SAFETY.maxMemoryDumpSize), header: true, ansi: true })); } catch (e) { console.log(`[!] ${description}: hexdump失败 - ${e.message}`); } } // ==================== 字符串比较Hook (优先级1) ==================== function installStringCompareHook(module) { try { const functionOffset = GLOBAL_CONFIG.STRING_COMPARE_FUNCTION.offset; const functionAddress = module.base.add(functionOffset); console.log(`[+] 安装字符串比较Hook: ${GLOBAL_CONFIG.STRING_COMPARE_FUNCTION.name}`); console.log(`[+] Hook地址: ${functionAddress} (偏移: 0x${functionOffset.toString(16)})`); Interceptor.attach(functionAddress, { onEnter: function(args) { GLOBAL_STATE.hookStats.stringCompareCalls++; // 保存参数 this.str1 = args[0]; this.str2 = args[1]; if (GLOBAL_CONFIG.LOGGING.enableDetailedStringLog) { console.log(`[STR] ${GLOBAL_CONFIG.STRING_COMPARE_FUNCTION.name} 调用 #${GLOBAL_STATE.hookStats.stringCompareCalls}`); console.log(`[STR] 参数1: ${this.str1}, 参数2: ${this.str2}`); // 获取调用者信息(简化版) try { const callerAddress = this.returnAddress; const callerModule = Process.findModuleByAddress(callerAddress); if (callerModule) { const offset = callerAddress.sub(callerModule.base); console.log(`[STR] 调用者: ${callerModule.name}+0x${offset.toString(16)}`); } } catch (e) { // 忽略调用者信息获取错误 } // 尝试读取字符串内容(简化版) const readSimpleString = (strPtr, label) => { try { if (!strPtr || strPtr.isNull()) return; const firstByte = strPtr.readU8(); const isLongString = firstByte & 1; let dataPtr = isLongString ? strPtr.add(8).readPointer() : strPtr.add(1); let content = dataPtr.readCString(16) || "无法读取"; console.log(`[STR] ${label}: "${content}" (${isLongString ? '长' : '短'}字符串)`); } catch (e) { console.log(`[STR] ${label}: 读取失败`); } }; readSimpleString(this.str1, "字符串1"); readSimpleString(this.str2, "字符串2"); } }, onLeave: function(retval) { const originalReturn = retval.toInt32(); // 强制返回1 retval.replace(ptr(1)); if (GLOBAL_CONFIG.LOGGING.enableDetailedStringLog) { console.log(`[STR] 原始返回值: ${originalReturn} -> 强制返回值: 1`); } else { // 简化日志:只在返回值不为1时输出 if (originalReturn !== 1) { console.log(`[STR] 字符串比较被绕过 (${originalReturn} -> 1) [调用#${GLOBAL_STATE.hookStats.stringCompareCalls}]`); } } } }); GLOBAL_STATE.stringHookInstalled = true; console.log(`[+] 字符串比较Hook安装成功!`); return true; } catch (error) { console.log(`[!] 字符串比较Hook安装失败: ${error.message}`); return false; } } // ==================== LuaState结构体定义 ==================== const LuaStateStruct = { PADDING_OFFSET: 0x00, BYTECODE_PTR_OFFSET: 0x1C, BYTECODE_SIZE_OFFSET: 0x20, CURRENT_EXEC_POS_OFFSET: 0x24, PADDING2_OFFSET: 0x28, INIT_FLAG_OFFSET: 0x2C, SCRIPT_START_PTR_OFFSET: 0x30, TOTAL_SCRIPT_SIZE_OFFSET: 0x34 }; // ==================== Lua解密Hook (优先级2) ==================== function parseLuaState(luaStatePtr, paramName, callNumber) { if (!GLOBAL_CONFIG.LOGGING.enableDetailedLuaLog) return; console.log(`\n[LUA] ========== 解析${paramName} LuaState结构体 (调用#${callNumber}) ==========`); console.log(`[LUA] LuaState地址: ${luaStatePtr}`); if (luaStatePtr.isNull()) { console.log(`[LUA] ${paramName} LuaState指针为NULL`); return; } try { // 读取结构体各字段 const bytecodePtr = safeReadPointer(luaStatePtr.add(LuaStateStruct.BYTECODE_PTR_OFFSET), "字节码指针"); const bytecodeSize = safeReadU32(luaStatePtr.add(LuaStateStruct.BYTECODE_SIZE_OFFSET), "字节码大小"); const currentExecPos = safeReadPointer(luaStatePtr.add(LuaStateStruct.CURRENT_EXEC_POS_OFFSET), "当前执行位置"); const initFlag = safeReadU32(luaStatePtr.add(LuaStateStruct.INIT_FLAG_OFFSET), "初始化标志"); const scriptStartPtr = safeReadPointer(luaStatePtr.add(LuaStateStruct.SCRIPT_START_PTR_OFFSET), "脚本开始指针"); const totalScriptSize = safeReadU32(luaStatePtr.add(LuaStateStruct.TOTAL_SCRIPT_SIZE_OFFSET), "脚本总大小"); console.log(`[LUA] 字节码指针 (+0x1C): ${bytecodePtr}`); console.log(`[LUA] 字节码大小 (+0x20): ${bytecodeSize} bytes`); console.log(`[LUA] 当前执行位置 (+0x24): ${currentExecPos}`); console.log(`[LUA] 初始化标志 (+0x2C): 0x${initFlag.toString(16)}`); console.log(`[LUA] 脚本开始指针 (+0x30): ${scriptStartPtr}`); console.log(`[LUA] 脚本总大小 (+0x34): ${totalScriptSize} bytes`); // 输出LuaState结构体的内存dump console.log(`\n[LUA] ${paramName} LuaState结构体内存dump:`); safeHexdump(luaStatePtr, GLOBAL_CONFIG.LOGGING.maxDumpSize, `${paramName} LuaState结构体`); // 输出字节码内容 if (!bytecodePtr.isNull() && bytecodeSize > 0 && bytecodeSize < 10000) { const dumpSize = Math.min(bytecodeSize, GLOBAL_CONFIG.LOGGING.maxDumpSize); console.log(`\n[LUA] ${paramName} Lua字节码内容 (前${dumpSize}字节):`); safeHexdump(bytecodePtr, dumpSize, `${paramName} Lua字节码`); // 尝试识别Lua字节码头部 try { const header = bytecodePtr.readByteArray(12); if (header) { const headerBytes = new Uint8Array(header); const headerHex = Array.from(headerBytes).map(b => b.toString(16).padStart(2, '0')).join(' '); console.log(`[LUA] 字节码头部: ${headerHex}`); // 检查Lua签名 if (headerBytes[0] === 0x1B && headerBytes[1] === 0x4C && headerBytes[2] === 0x75 && headerBytes[3] === 0x61) { console.log(`[LUA] ✓ 检测到标准Lua字节码签名`); } else { console.log(`[LUA] ⚠ 非标准Lua字节码或已加密`); } } } catch (e) { console.log(`[LUA] 字节码头部分析失败: ${e.message}`); } } // 输出脚本内容 if (!scriptStartPtr.isNull() && totalScriptSize > 0 && totalScriptSize < 10000) { const dumpSize = Math.min(totalScriptSize, GLOBAL_CONFIG.LOGGING.maxDumpSize); console.log(`\n[LUA] ${paramName} 脚本内容 (前${dumpSize}字节):`); safeHexdump(scriptStartPtr, dumpSize, `${paramName} 脚本内容`); } } catch (e) { console.log(`[LUA] 解析${paramName} LuaState结构体时发生错误: ${e.message}`); } } function installLuaDecryptHook(module) { try { const functionOffset = GLOBAL_CONFIG.LUA_DECRYPT_FUNCTION.offset; const functionAddress = module.base.add(functionOffset); console.log(`[+] 安装Lua解密Hook: ${GLOBAL_CONFIG.LUA_DECRYPT_FUNCTION.name}`); console.log(`[+] Hook地址: ${functionAddress} (偏移: 0x${functionOffset.toString(16)})`); Interceptor.attach(functionAddress, { onEnter: function(args) { GLOBAL_STATE.hookStats.luaDecryptCalls++; console.log(`\n[LUA] ==================== ${GLOBAL_CONFIG.LUA_DECRYPT_FUNCTION.name} 函数调用开始 ====================`); console.log(`[LUA] 调用次数: #${GLOBAL_STATE.hookStats.luaDecryptCalls}`); console.log(`[LUA] 调用时间: ${new Date().toISOString()}`); console.log(`[LUA] 线程ID: ${Process.getCurrentThreadId()}`); // __fastcall约定:ECX=第一个参数,EDX=第二个参数 const param1 = this.context.ecx; // LuaState指针 const param2 = this.context.edx; // 第二个参数 console.log(`[LUA] 参数1 (ECX - LuaState*): ${param1}`); console.log(`[LUA] 参数2 (EDX): ${param2}`); // 保存参数 this.param1 = param1; this.param2 = param2; this.startTime = Date.now(); // 解析LuaState结构体 if (!param1.isNull()) { parseLuaState(param1, "输入参数", GLOBAL_STATE.hookStats.luaDecryptCalls); } // 输出寄存器状态(简化版) if (GLOBAL_CONFIG.LOGGING.enableDetailedLuaLog) { console.log(`\n[LUA] 关键寄存器状态:`); console.log(`[LUA] EAX: ${this.context.eax}`); console.log(`[LUA] ECX: ${this.context.ecx} (LuaState*)`); console.log(`[LUA] EDX: ${this.context.edx} (参数2)`); console.log(`[LUA] ESP: ${this.context.esp}`); } }, onLeave: function(retval) { const endTime = Date.now(); const duration = endTime - this.startTime; console.log(`\n[LUA] ==================== ${GLOBAL_CONFIG.LUA_DECRYPT_FUNCTION.name} 函数调用结束 ====================`); console.log(`[LUA] 返回时间: ${new Date().toISOString()}`); console.log(`[LUA] 执行耗时: ${duration}ms`); console.log(`[LUA] 返回值 (EAX): ${retval}`); // 再次检查LuaState结构体(可能已被修改) if (!this.param1.isNull()) { parseLuaState(this.param1, "返回时参数", GLOBAL_STATE.hookStats.luaDecryptCalls); } console.log(`[LUA] ==================== Hook结束 ====================\n`); } }); GLOBAL_STATE.luaHookInstalled = true; console.log(`[+] Lua解密Hook安装成功!`); return true; } catch (error) { console.log(`[!] Lua解密Hook安装失败: ${error.message}`); return false; } } // ==================== 状态监控和统计 ==================== function printHookStatus() { const runtime = Date.now() - GLOBAL_STATE.hookStats.startTime; const runtimeMinutes = Math.floor(runtime / 60000); const runtimeSeconds = Math.floor((runtime % 60000) / 1000); console.log(`\n[+] ========== Hook状态报告 ==========`); console.log(`[+] 运行时间: ${runtimeMinutes}分${runtimeSeconds}秒`); console.log(`[+] 字符串比较Hook: ${GLOBAL_STATE.stringHookInstalled ? '✓ 已安装' : '✗ 未安装'}`); console.log(`[+] Lua解密Hook: ${GLOBAL_STATE.luaHookInstalled ? '✓ 已安装' : '✗ 未安装'}`); console.log(`[+] 字符串比较调用次数: ${GLOBAL_STATE.hookStats.stringCompareCalls}`); console.log(`[+] Lua解密调用次数: ${GLOBAL_STATE.hookStats.luaDecryptCalls}`); console.log(`[+] =====================================\n`); } // 定期输出状态报告 setInterval(printHookStatus, 30000); // 每30秒输出一次状态 // ==================== 主执行逻辑 ==================== async function main() { try { console.log("[+] 等待目标模块加载..."); const module = await waitForModule(); if (!module) { console.log("[!] 未找到目标模块,脚本退出"); return; } GLOBAL_STATE.targetModule = module; console.log(`[+] 目标模块加载完成: ${module.name}`); // 优先级1: 安装字符串比较Hook (绕过验证) console.log("\n[+] ========== 第一阶段: 安装字符串比较Hook =========="); const stringHookSuccess = installStringCompareHook(module); if (stringHookSuccess) { console.log("[+] ✓ 字符串比较Hook安装成功,验证绕过已激活"); } else { console.log("[!] ✗ 字符串比较Hook安装失败,可能影响后续功能"); } // 短暂延迟,确保字符串Hook生效 await new Promise(resolve => setTimeout(resolve, 1000)); // 优先级2: 安装Lua解密Hook (监控解密过程) console.log("\n[+] ========== 第二阶段: 安装Lua解密Hook =========="); const luaHookSuccess = installLuaDecryptHook(module); if (luaHookSuccess) { console.log("[+] ✓ Lua解密Hook安装成功,开始监控解密过程"); } else { console.log("[!] ✗ Lua解密Hook安装失败"); } // 最终状态报告 console.log("\n[+] ========================================"); console.log("[+] 综合Hook脚本安装完成!"); console.log(`[+] 字符串比较绕过: ${stringHookSuccess ? '✓ 激活' : '✗ 失败'}`); console.log(`[+] Lua解密监控: ${luaHookSuccess ? '✓ 激活' : '✗ 失败'}`); console.log("[+] 脚本正在运行,等待目标函数调用..."); console.log("[+] ========================================"); // 输出首次状态报告 setTimeout(printHookStatus, 5000); } catch (error) { console.log(`[!] 主执行流程出错: ${error.message}`); console.log(`[!] 错误堆栈: ${error.stack}`); } } // ==================== 异常处理和清理 ==================== // 进程异常处理 Process.setExceptionHandler(function(details) { console.log(`[!] 进程异常捕获: ${JSON.stringify(details, null, 2)}`); console.log(`[!] 异常发生时Hook状态:`); printHookStatus(); return true; // 继续执行 }); // 脚本退出处理 function onScriptExit() { console.log("\n[+] ========== 脚本退出清理 =========="); printHookStatus(); console.log("[+] 综合Hook脚本已退出"); console.log("[+] ==================================="); } // 注册退出处理 if (typeof Script !== 'undefined' && Script.setGlobalAccessHandler) { Script.setGlobalAccessHandler({ enumerate: function() { return []; }, get: function(property) { return undefined; }, set: function(property, value) { return false; } }); } // ==================== 启动脚本 ==================== console.log("[+] 综合Hook脚本初始化完成,开始执行..."); main().catch(error => { console.log(`[!] 脚本启动失败: ${error.message}`); }); // 脚本加载完成提示 console.log("[+] 综合Hook脚本加载完成,等待目标模块和函数调用...");