def find_string_addr(search_str): """查找字符串的地址""" for addr in idautils.Strings(): ifstr(addr) == search_str: return addr.ea returnNone
def resolve_func_addr(func_entry): """将函数名或地址转换为地址""" ifisinstance(func_entry, int): return func_entry elif isinstance(func_entry, str): func_addr = idaapi.get_name_ea_simple(func_entry) if func_addr == idaapi.BADADDR: print(f"Function not found: {func_entry}") returnNone return func_addr else: print(f"Invalid function entry: {func_entry}") returnNone
def add_comments_to_strings(): """为 .rodata 中的字符串添加解密注释和重命名""" for encrypted, decrypted in mappings.items(): str_addr = find_string_addr(encrypted) ifstr_addr: idaapi.set_cmt(str_addr, f"Decrypted: {decrypted}", 0) # 重命名字符串,替换特殊字符以符合命名规则 safe_name = f"str_{decrypted.replace('/', '_').replace(' ', '_')}" idaapi.set_name(str_addr, safe_name, idaapi.SN_FORCE) print(f"Added comment at 0x{str_addr:x}: {encrypted} -> {decrypted}") else: print(f"String not found in .rodata: {encrypted}")
def add_comments_to_function(func_addr): """为指定函数添加注释,扫描加载字符串的指令""" func = idaapi.get_func(func_addr) if not func: print(f"Function at 0x{func_addr:x} not found or invalid") return
# 添加函数注释 func_name = idaapi.get_func_name(func_addr) idaapi.set_func_cmt(func_addr, f"Decrypts strings (e.g., {', '.join(f'{k}->{v}' for k, v in mappings.items())})", 1) print(f"Processing function: {func_name} at 0x{func_addr:x}")
# 扫描函数指令,查找加载字符串的指令 for ea in idautils.FuncItems(func.start_ea): disasm = idaapi.generate_disasm_line(ea, 0) if"LDR"indisasm: # 检查加载指令 for enc, dec in mappings.items(): if enc indisasm: idaapi.set_cmt(ea, f"Loads {enc} -> {dec}", 0) print(f"Added comment at 0x{ea:x}: {enc} -> {dec}")
deffind_string_addr(search_str): """查找字符串的地址""" for addr in idautils.Strings(): ifstr(addr) == search_str: return addr.ea returnNone
defresolve_func_addr(func_entry): """将函数名或地址转换为地址""" ifisinstance(func_entry, int): return func_entry elifisinstance(func_entry, str): func_addr = idaapi.get_name_ea_simple(func_entry) if func_addr == idaapi.BADADDR: print(f"Function not found: {func_entry}") returnNone return func_addr else: print(f"Invalid function entry: {func_entry}") returnNone
defadd_comments_to_strings(): """为 .rodata 中的字符串添加解密注释和重命名""" for encrypted, decrypted in mappings.items(): str_addr = find_string_addr(encrypted) if str_addr: idaapi.set_cmt(str_addr, f"Decrypted: {decrypted}", 0) # 重命名字符串,替换特殊字符以符合命名规则 safe_name = f"str_{decrypted.replace('/', '_').replace(' ', '_').replace('(', '_').replace(')', '_').replace(';', '_').replace('[', '_').replace(']', '_')}" idaapi.set_name(str_addr, safe_name, idaapi.SN_FORCE) print(f"Added comment at 0x{str_addr:x}: {encrypted} -> {decrypted}") else: print(f"String not found in .rodata: {encrypted}")
defadd_comments_to_function(func_addr): """为指定函数添加注释,扫描加载字符串的指令""" func = idaapi.get_func(func_addr) ifnot func: print(f"Function at 0x{func_addr:x} not found or invalid") return
# 添加函数注释 func_name = idaapi.get_func_name(func_addr) idaapi.set_func_cmt(func_addr, f"Decrypts strings (e.g., {', '.join(f'{k}->{v}'for k, v inlist(mappings.items())[:5])}...)", 1) print(f"Processing function: {func_name} at 0x{func_addr:x}")
# 扫描函数指令,查找加载字符串的指令 for ea in idautils.FuncItems(func.start_ea): disasm = idaapi.generate_disasm_line(ea, 0) if"LDR"in disasm: # 检查加载指令 for enc, dec in mappings.items(): if enc in disasm: idaapi.set_cmt(ea, f"Loads {enc} -> {dec}", 0) print(f"Added comment at 0x{ea:x}: {enc} -> {dec}")
var offset = 0x88060; var module_size = 0; var module_name = "libbaiduprotect.so"; var base_addr = null; // To store the base address of libbaiduprotect.so
// Create an empty function var empty_func = newNativeCallback(function() { return0; }, 'int', ['pointer']);
functionhook_linker_call_constructors() { let linker64_base_addr = Module.getBaseAddress('linker64'); let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv let call_constructors = linker64_base_addr.add(offset_call); let listener = Interceptor.attach(call_constructors, { onEnter: function(args) { console.log('[linker] Call_Constructors onEnter'); let secmodule = Process.findModuleByName(module_name); if (secmodule != null) { module_size = secmodule.size; base_addr = secmodule.base; // Save the base address console.log("libbaiduprotect.so base: " + base_addr + ", size: " + module_size); hook_target_func(base_addr); listener.detach(); } } }); }
functionhook_target_func(baseaddr) { let target_addr = baseaddr.add(offset); let listener = Interceptor.attach(target_addr, { onEnter: function(args) { console.log("Target function at " + target_addr + " entered"); }, onLeave: function(retval) { console.log("libbaiduprotect.so base address: " + baseaddr); hook_pthread_create(); listener.detach(); } }); }
functionhook_pthread_create() { let pthread_create_addr = Module.getExportByName("libc.so", "pthread_create"); Interceptor.attach(pthread_create_addr, { onEnter: function(args) { let func_addr = args[2]; let target_func_addr = base_addr.add(0x3E9F0); // Calculate the target function address if (func_addr.equals(target_func_addr)) { console.log("Replacing func_addr " + func_addr + " with empty function"); args[2] = empty_func; // Replace with the empty function hook_libart(); // Execute hook_libart } else { letmodule = Process.findModuleByAddress(func_addr); if (module && module.name === module_name) { console.log("pthread_create called in libbaiduprotect.so, function address: " + func_addr); } } }, onLeave: function(retval) { // Optionally log return value } }); }
functionhook_libart() { var symbols = Module.enumerateSymbolsSync("libart.so"); var addrGetStringUTFChars = null; var addrNewStringUTF = null; var addrFindClass = null; var addrGetMethodID = null; var addrGetStaticMethodID = null; var addrGetFieldID = null; var addrGetStaticFieldID = null; var addrRegisterNatives = null; var so_name = "lib"; // TODO: Specify the SO to filter
for (var i = 0; i < symbols.length; i++) { var symbol = symbols[i]; if (symbol.name.indexOf("art") >= 0 && symbol.name.indexOf("JNI") >= 0 && symbol.name.indexOf("CheckJNI") < 0 && symbol.name.indexOf("_ZN3art3JNIILb0") >= 0 ) { if (symbol.name.indexOf("GetStringUTFChars") >= 0) { addrGetStringUTFChars = symbol.address; console.log("GetStringUTFChars is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("NewStringUTF") >= 0) { addrNewStringUTF = symbol.address; console.log("NewStringUTF is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("FindClass") >= 0) { addrFindClass = symbol.address; console.log("FindClass is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("GetMethodID") >= 0) { addrGetMethodID = symbol.address; console.log("GetMethodID is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("GetStaticMethodID") >= 0) { addrGetStaticMethodID = symbol.address; console.log("GetStaticMethodID is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("GetFieldID") >= 0) { addrGetFieldID = symbol.address; console.log("GetFieldID is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("GetStaticFieldID") >= 0) { addrGetStaticFieldID = symbol.address; console.log("GetStaticFieldID is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("RegisterNatives") >= 0) { addrRegisterNatives = symbol.address; console.log("RegisterNatives is at ", symbol.address, symbol.name); } elseif (symbol.name.indexOf("CallStatic") >= 0) { console.log("CallStatic is at ", symbol.address, symbol.name); Interceptor.attach(symbol.address, { onEnter: function (args) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var java_class = args[1]; var mid = args[2]; var class_name = Java.vm.tryGetEnv().getClassName(java_class); if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) { var method_name = prettyMethod(mid, 1); console.log("<>CallStatic:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name); } } }, onLeave: function (retval) { } }); } elseif (symbol.name.indexOf("CallNonvirtual") >= 0) { console.log("CallNonvirtual is at ", symbol.address, symbol.name); Interceptor.attach(symbol.address, { onEnter: function (args) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var jobject = args[1]; var jclass = args[2]; var jmethodID = args[3]; var obj_class_name = Java.vm.tryGetEnv().getObjectClassName(jobject); var class_name = Java.vm.tryGetEnv().getClassName(jclass); if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) { var method_name = prettyMethod(jmethodID, 1); console.log("<>CallNonvirtual:", DebugSymbol.fromAddress(this.returnAddress), class_name, obj_class_name, method_name); } } }, onLeave: function (retval) { } }); } elseif (symbol.name.indexOf("Call") >= 0 && symbol.name.indexOf("Method") >= 0) { console.log("Call<>Method is at ", symbol.address, symbol.name); Interceptor.attach(symbol.address, { onEnter: function (args) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var java_class = args[1]; var mid = args[2]; var class_name = Java.vm.tryGetEnv().getObjectClassName(java_class); if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) { var method_name = prettyMethod(mid, 1); console.log("<>Call<>Method:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name); } } }, onLeave: function (retval) { } }); } } }
if (addrGetStringUTFChars != null) { Interceptor.attach(addrGetStringUTFChars, { onEnter: function (args) { }, onLeave: function (retval) { if (retval != null) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var bytes = Memory.readCString(retval); console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress)); } } } }); } if (addrNewStringUTF != null) { Interceptor.attach(addrNewStringUTF, { onEnter: function (args) { if (args[1] != null) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var string = Memory.readCString(args[1]); console.log("[NewStringUTF] bytes:" + string, DebugSymbol.fromAddress(this.returnAddress)); } } }, onLeave: function (retval) { } }); }
if (addrFindClass != null) { Interceptor.attach(addrFindClass, { onEnter: function (args) { if (args[1] != null) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var name = Memory.readCString(args[1]); console.log("[FindClass] name:" + name, DebugSymbol.fromAddress(this.returnAddress)); } } }, onLeave: function (retval) { } }); } if (addrGetMethodID != null) { Interceptor.attach(addrGetMethodID, { onEnter: function (args) { if (args[2] != null) { var clazz = args[1]; var class_name = Java.vm.tryGetEnv().getClassName(clazz); varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var name = Memory.readCString(args[2]); if (args[3] != null) { var sig = Memory.readCString(args[3]); console.log("[GetMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); } else { console.log("[GetMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress)); } } } }, onLeave: function (retval) { } }); } if (addrGetStaticMethodID != null) { Interceptor.attach(addrGetStaticMethodID, { onEnter: function (args) { if (args[2] != null) { var clazz = args[1]; var class_name = Java.vm.tryGetEnv().getClassName(clazz); varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var name = Memory.readCString(args[2]); if (args[3] != null) { var sig = Memory.readCString(args[3]); console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); } else { console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress)); } } } }, onLeave: function (retval) { } }); } if (addrGetFieldID != null) { Interceptor.attach(addrGetFieldID, { onEnter: function (args) { if (args[2] != null) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var name = Memory.readCString(args[2]); if (args[3] != null) { var sig = Memory.readCString(args[3]); console.log("[GetFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); } else { console.log("[GetFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress)); } } } }, onLeave: function (retval) { } }); } if (addrGetStaticFieldID != null) { Interceptor.attach(addrGetStaticFieldID, { onEnter: function (args) { if (args[2] != null) { varmodule = Process.findModuleByAddress(this.returnAddress); if (module != null && module.name.indexOf(so_name) == 0) { var name = Memory.readCString(args[2]); if (args[3] != null) { var sig = Memory.readCString(args[3]); console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); } else { console.log("[GetStaticFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress)); } } } }, onLeave: function (retval) { } }); }
if (addrRegisterNatives != null) { Interceptor.attach(addrRegisterNatives, { onEnter: function (args) { console.log("[RegisterNatives] method_count:", args[3], DebugSymbol.fromAddress(this.returnAddress)); var env = args[0]; var java_class = args[1]; var class_name = Java.vm.tryGetEnv().getClassName(java_class);
var methods_ptr = ptr(args[2]); var method_count = parseInt(args[3]); for (var i = 0; i < method_count; i++) { var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize)); var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr); var sig = Memory.readCString(sig_ptr); var find_module = Process.findModuleByAddress(fnPtr_ptr); console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base)); } }, onLeave: function (retval) { } }); } }
var offset = 0x88060; var module_size = 0; var module_name = "libbaiduprotect.so"; var base_addr = null; // To store the base address of libbaiduprotect.so
// Create an empty function var empty_func = newNativeCallback(function() { // console.log("[Anti-Debug] Empty function executed."); // 可选日志 return0; }, 'int', ['pointer']);
functionhook_linker_call_constructors() { // 为提高健壮性,建议使用更完善的 linker 及 call_constructors 查找逻辑 // 但为保持与你原始脚本的结构,暂时保留这种直接方式 let linker_module_name = Process.arch === 'arm64' ? 'linker64' : 'linker'; let linker64_base_addr = Module.getBaseAddress(linker_module_name); if (!linker64_base_addr) { console.warn("[-] Failed to get " + linker_module_name + " base address. Trying to find module directly later."); // Fallback if linker not found setTimeout(function() { if (!base_addr) { // Check if already found let secmodule = Process.findModuleByName(module_name); if (secmodule) { base_addr = secmodule.base; module_size = secmodule.size; console.log(module_name + " base (fallback): " + base_addr + ", size: " + module_size); hook_target_func(base_addr); } else { console.error("[-] " + module_name + " not found via fallback. Cannot proceed."); } } }, 1500); // Delay slightly return; }
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv - 这个偏移非常依赖系统 let call_constructors = linker64_base_addr.add(offset_call); console.log("[i] Attempting to attach to linker's call_constructors at " + call_constructors);
try { let listener = Interceptor.attach(call_constructors, { onEnter: function(args) { // console.log('[linker] Call_Constructors onEnter'); let secmodule = Process.findModuleByName(module_name); if (secmodule != null) { if (base_addr === null) { // 确保只初始化一次 module_size = secmodule.size; base_addr = secmodule.base; // Save the base address console.log(module_name + " base: " + base_addr + ", size: " + module_size); // *** 在这里也可以考虑直接打补丁已知崩溃点, 但Stalker内处理更灵活些 *** // patchInstructionToNop(base_addr, 0x48AC0, 4); // patchInstructionToNop(base_addr, 0x4ABB8, 4);
hook_target_func(base_addr); listener.detach(); console.log("[i] Detached from linker hook."); } } } }); } catch (e) { console.error("[-] Failed to attach to linker's call_constructors: " + e.message); console.log(" Will rely on fallback to find module " + module_name); setTimeout(function() { // 与上面的 fallback 逻辑类似 if (!base_addr) { let secmodule = Process.findModuleByName(module_name); if (secmodule) { base_addr = secmodule.base; module_size = secmodule.size; console.log(module_name + " base (fallback after attach error): " + base_addr + ", size: " + module_size); hook_target_func(base_addr); } else { console.error("[-] Fallback after attach error: " + module_name + " not found."); } } }, 1500); } }
functionhook_target_func(current_base_addr) { // 使用 current_base_addr 区分全局 base_addr if (!current_base_addr || current_base_addr.isNull()){ console.error("[-] hook_target_func: current_base_addr is invalid."); return; } let target_addr = current_base_addr.add(offset); // offset = 0x88060 console.log("[+] Hooking target function at " + target_addr + " (offset 0x" + offset.toString(16) + ")"); try { let listener = Interceptor.attach(target_addr, { onEnter: function(args) { console.log("Target function " + module_name + "!0x" + offset.toString(16) + " entered. Called from: " + this.returnAddress); }, onLeave: function(retval) { console.log(module_name + "!0x" + offset.toString(16) + " onLeave. Base address: " + current_base_addr); if (base_addr && !base_addr.isNull()) { // 确保全局base_addr有效 hook_pthread_create(); StalkerTrace(base_addr); // 传递正确的模块基址给StalkerTrace } else { console.error("[-] Global base_addr not set in hook_target_func onLeave. Cannot start Stalker / pthread hook."); } listener.detach(); console.log("[i] Detached from target_func hook at " + target_addr); } }); } catch (e) { console.error("[-] Failed to attach to target_func at " + target_addr + ": " + e.message); } }
functionhook_pthread_create() { if (!base_addr || base_addr.isNull()) { console.error("[-] hook_pthread_create: Global base_addr is null."); return; } let pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); if (pthread_create_addr) { console.log("[+] Hooking pthread_create at " + pthread_create_addr); try { Interceptor.attach(pthread_create_addr, { onEnter: function(args) { let func_addr = args[2]; let target_func_addr = base_addr.add(0x3E9F0); if (func_addr.equals(target_func_addr)) { console.log("[Anti-Debug] Replacing thread start_routine " + func_addr + " with empty function."); args[2] = empty_func; } } }); } catch (e) { console.error("[-] Failed to attach to pthread_create: " + e.message); } } else { console.warn("[-] pthread_create not found in libc.so. Hook skipped."); } }
// ======================================================================================= // Modified StalkerTrace Function (FIXED local_pre_regs access) // ======================================================================================= functionStalkerTrace(current_module_baseaddr) { // Ensure the correct module base address is passed // Use the global module_size which should be set by now if (!current_module_baseaddr || current_module_baseaddr.isNull() || module_size === 0) { console.error("[-] StalkerTrace: Invalid module base address or size. Cannot proceed."); return; }
var stalker_target_func_offset = 0x4a458; // The offset of the function you want Stalker to trace var stalker_start_addr = current_module_baseaddr.add(stalker_target_func_offset); console.log("[+] StalkerTrace will target " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " at actual address: " + stalker_start_addr);
Interceptor.attach(stalker_start_addr, { onEnter: function (args) { // Stalker operates per thread. Get the current thread ID. this.tid = Process.getCurrentThreadId(); console.log("[+] Stalker: Entered " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " (TID: " + this.tid + ")");
// Initialize previous register state *for this specific thread*. // This variable is local to this onEnter call but will be captured by the closure. const local_pre_regs = {}; // Populate initial state, excluding filtered registers const initial_context_snapshot = JSON.parse(JSON.stringify(this.context)); for (const reg_name_orig in initial_context_snapshot) { if (initial_context_snapshot.hasOwnProperty(reg_name_orig)) { const reg_name_lower = reg_name_orig.toLowerCase(); if (reg_name_lower !== "pc" && reg_name_lower !== "sp" && !reg_name_lower.startsWith("q") && !reg_name_lower.startsWith("v")) { local_pre_regs[reg_name_orig] = initial_context_snapshot[reg_name_orig]; } } } // *** REMOVE THIS LINE *** this.local_pre_regs = local_pre_regs; // The variable 'local_pre_regs' is already in scope for the transform/callout closures.
// === Patch known crashing instructions on entering the traced function === // These addresses are relative to the module base address (current_module_baseaddr) const knownCrashOffsets = [0x48AC0, 0x4ABB8]; // List of known problematic instruction offsets knownCrashOffsets.forEach(offset => { try { const patchAddr = current_module_baseaddr.add(offset); // Read instruction bytes to verify it's the expected 'MOV WSP, #1' // ARM64: MOV WSP, #1 is 320003FF (little endian) const expectedBytes = newUint8Array([0x32, 0x00, 0x03, 0xFF]); const instrBytes = patchAddr.readByteArray(4); const actualBytes = newUint8Array(instrBytes);
Stalker.follow(this.tid, { // Configure events to trace. exec=true traces every executed instruction. events: { call: false, ret: false, exec: true, block: false, compile: false }, // The transform callback allows inspecting/rewriting instruction blocks transform(iterator) { let instruction = iterator.next(); do { const currentAddress = instruction.address; let patchedThisInstruction = false;
// Check if the instruction is within the target module's bounds const is_module_code = currentAddress.compare(module_start) >= 0 && currentAddress.compare(module_end) < 0;
// === Universal MOV WSP/SP, #1 detection and NOP logic === // This is a fallback/alternative to the onEnter patch // It happens per block compiled by Stalker if (is_module_code) { // Only patch instructions within the target module const mnemonic = instruction.mnemonic.toLowerCase(); // Check for MOV or MOVZ with SP/WSP as destination and immediate 1 if ((mnemonic === 'mov' || mnemonic === 'movz') && instruction.operands.length >= 2) { const destOperand = instruction.operands[0]; const srcOperand = instruction.operands[1];
console.warn("[StalkerTransform] Identified potential crash instruction '" + instruction.toString() + "' at " + currentAddress + ". Replacing with NOP."); iterator.putNop(); // Replace the instruction with NOP patchedThisInstruction = true; } } } // =========================================================
// If the instruction wasn't NOPped by the above check, add a callout for logging if (!patchedThisInstruction) { // Only add callout for instructions within the target module if (is_module_code) { // 'local_pre_regs' is accessible here via closure iterator.putCallout(function (context) { // Access local_pre_regs directly from the outer scope (captured by closure) const thread_pre_regs = local_pre_regs; const pc = context.pc;
// Get the register differences after this instruction executes // get_diff_regs modifies thread_pre_regs in place const diff_regs = get_diff_regs(context, thread_pre_regs); // Find the module info again (can be optimized by caching, but this is reliable) var current_callout_module = Process.findModuleByAddress(pc);
// Ensure we are logging code from the target module if (current_callout_module && current_callout_module.name === TARGET_MODULE_NAME) { const instrToLog = Instruction.parse(pc); let logLine = `${current_callout_module.name}!${pc.sub(current_callout_module.base)}${instrToLog.toString()}`;
// Append changed registers and potential strings const diffRegKeys = Object.keys(diff_regs); if (diffRegKeys.length > 0) { logLine += `\t\t| Changed Regs: `; let firstReg = true; for (const regName of diffRegKeys) { if (!firstReg) logLine += ", "; firstReg = false; const regValue = diff_regs[regName]; // The new value of the register logLine += `${regName}=${regValue}`;
// --- Check if the register value points to a string --- const potentialString = tryReadString(regValue); if (potentialString !== null) { // Truncate long strings for readability const displayString = potentialString.length > 60 ? potentialString.substring(0, 57) + "..." : potentialString; // Escape potential quotes in the string for cleaner output const escapedString = displayString.replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); logLine += ` ("${escapedString}")`; } // ---------------------------------------------------------- } }
console.log(logLine); } }); } // Keep the original instruction iterator.keep(); } } while ((instruction = iterator.next()) !== null); // Process next instruction in the block } }); }, onLeave: function (retval) { console.log("[+] " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " onLeave, TID:", this.tid); // Stop stalking this thread when the function exits Stalker.unfollow(this.tid); // Clean up Stalker's compiled code cache Stalker.garbageCollect(); } }); }
// ... (rest of the script remains the same) // hook_linker_call_constructors(); // hook_target_func(base_addr); // Called from linker hook or fallback // hook_pthread_create(); // Called from hook_target_func onLeave // tryReadString() // Helper function defined above // get_diff_regs() // Helper function defined above // get_involved_regs() // Helper function defined above
var offset = 0x88060; var module_size = 0; // Will be populated when module is found var module_name = "libbaiduprotect.so"; var base_addr = null; // To store the base address of libbaiduprotect.so
// Create an empty function var empty_func = newNativeCallback(function() { // console.log("[Anti-Debug] Empty function executed."); // Optional log return0; }, 'int', ['pointer']);
// --- Helper function to attempt reading a string --- functiontryReadString(address, minPrintableBytes = 3, maxReadLength = 256) { if (!address) { returnnull; // Cannot read from null address } try { // Step 1: Read the first few bytes to see if they look like printable ASCII const preliminaryBytes = address.readByteArray(minPrintableBytes); if (!preliminaryBytes) { // Can't read the initial bytes (e.g., invalid address) returnnull; } const uint8Array = newUint8Array(preliminaryBytes); let allPrintable = true; for (let i = 0; i < uint8Array.length; i++) { const byte = uint8Array[i]; // Basic check for printable ASCII (0x20 to 0x7E) if (byte < 0x20 || byte > 0x7E) { allPrintable = false; break; } }
if (allPrintable) { // Step 2: If they look printable, attempt to read a C-string // readCString reads until null terminator or max length const str = address.readCString(maxReadLength); // Filter out empty strings or strings that only contained non-null printable chars but no null terminator early on if (str && str.length > 0) { return str; } } } catch (e) { // Reading failed (e.g., invalid memory access) // console.warn("[-] Failed to read memory at " + address + " for string check: " + e.message); // Optional: Log errors returnnull; }
returnnull; // Did not pass the checks or failed to read } // ---------------------------------------------------
functionhook_linker_call_constructors() { // 为提高健壮性,建议使用更完善的 linker 及 call_constructors 查找逻辑 // 但为保持与你原始脚本的结构,暂时保留这种直接方式 let linker_module_name = Process.arch === 'arm64' ? 'linker64' : 'linker'; let linker64_base_addr = Module.getBaseAddress(linker_module_name); if (!linker64_base_addr) { console.warn("[-] Failed to get " + linker_module_name + " base address. Trying to find module directly later."); // Fallback if linker not found setTimeout(function() { if (!base_addr) { // Check if already found let secmodule = Process.findModuleByName(module_name); if (secmodule) { base_addr = secmodule.base; module_size = secmodule.size; console.log(module_name + " base (fallback): " + base_addr + ", size: " + module_size); hook_target_func(base_addr); } else { console.error("[-] " + module_name + " not found via fallback. Cannot proceed."); } } }, 1500); // Delay slightly return; }
// NOTE: This offset is highly dependent on the system/Android version and linker variant! // A more robust approach would involve scanning for the function signature. let offset_call = 0x51BA8; // Example: __dl__ZN6soinfo17call_constructorsEv on some systems let call_constructors = linker64_base_addr.add(offset_call); console.log("[i] Attempting to attach to linker's call_constructors at " + call_constructors);
try { let listener = Interceptor.attach(call_constructors, { onEnter: function(args) { // console.log('[linker] Call_Constructors onEnter'); let secmodule = Process.findModuleByName(module_name); if (secmodule != null) { if (base_addr === null) { // Ensure initialized only once module_size = secmodule.size; base_addr = secmodule.base; // Save the base address console.log(module_name + " base: " + base_addr + ", size: " + module_size); // *** Consider patching known points here or within Stalker *** // Patching here happens earlier, might be safer for some anti-debug // patchInstructionToNop(base_addr, 0x48AC0, 4); // Example offset // patchInstructionToNop(base_addr, 0x4ABB8, 4); // Example offset
hook_target_func(base_addr); listener.detach(); // Detach once the module is found console.log("[i] Detached from linker hook."); } } } // No onLeave needed for just finding the module base }); } catch (e) { console.error("[-] Failed to attach to linker's call_constructors: " + e.message); console.log(" Will rely on fallback to find module " + module_name); setTimeout(function() { // Similar fallback logic as above if (!base_addr) { let secmodule = Process.findModuleByName(module_name); if (secmodule) { base_addr = secmodule.base; module_size = secmodule.size; console.log(module_name + " base (fallback after attach error): " + base_addr + ", size: " + module_size); hook_target_func(base_addr); } else { console.error("[-] Fallback after attach error: " + module_name + " not found."); } } }, 1500); } }
functionhook_target_func(current_base_addr) { // Use current_base_addr to distinguish from global base_addr if (!current_base_addr || current_base_addr.isNull()){ console.error("[-] hook_target_func: current_base_addr is invalid."); return; } let target_addr = current_base_addr.add(offset); // offset = 0x88060 console.log("[+] Hooking target function at " + target_addr + " (offset 0x" + offset.toString(16) + ")"); try { // Use attachOnce if you only need it to trigger once Interceptor.attach(target_addr, { onEnter: function(args) { console.log("Target function " + module_name + "!0x" + offset.toString(16) + " entered. Called from: " + this.returnAddress); }, onLeave: function(retval) { console.log(module_name + "!0x" + offset.toString(16) + " onLeave. Base address: " + current_base_addr); // IMPORTANT: Use the global base_addr here as it should be set by now and is needed by StalkerTrace/pthread hook if (base_addr && !base_addr.isNull()) { hook_pthread_create(); StalkerTrace(base_addr); // Pass the correct module base address to StalkerTrace } else { console.error("[-] Global base_addr not set in hook_target_func onLeave. Cannot start Stalker / pthread hook."); } // No need to detach here if using attachOnce, but if using attach, you might detach // listener.detach(); // console.log("[i] Detached from target_func hook at " + target_addr); } }); // Note: Interceptor.attachOnce is often better if you only need the onLeave logic once // However, let's stick to attach and detach in onLeave for now based on your original code structure. } catch (e) { console.error("[-] Failed to attach to target_func at " + target_addr + ": " + e.message); } }
functionhook_pthread_create() { // Ensure base_addr is set before hooking pthread_create as it's needed for comparison if (!base_addr || base_addr.isNull()) { console.error("[-] hook_pthread_create: Global base_addr is null. Skipping hook."); return; } let pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); if (pthread_create_addr) { console.log("[+] Hooking pthread_create at " + pthread_create_addr); try { Interceptor.attach(pthread_create_addr, { onEnter: function(args) { // pthread_create signature: // int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // start_routine is the 3rd argument (index 2) let func_addr = args[2]; // Target function address within libbaiduprotect.so // This offset (0x3E9F0) might also need dynamic finding let target_func_offset_pthread = 0x3E9F0; let target_func_addr = base_addr.add(target_func_offset_pthread);
// Check if the thread being created is the one we want to intercept if (func_addr.equals(target_func_addr)) { console.log("[Anti-Debug] Replacing thread start_routine " + func_addr + " with empty function."); // Replace the actual start routine pointer with our empty function's pointer args[2] = empty_func; } } }); } catch (e) { console.error("[-] Failed to attach to pthread_create: " + e.message); } } else { console.warn("[-] pthread_create not found in libc.so. Hook skipped."); } }
constTARGET_MODULE_NAME = "libbaiduprotect.so"; // Same as module_name for clarity with Stalker
functionget_diff_regs(context, pre_regs) { var diff_regs = {}; // Create a snapshot of the current context's general-purpose registers const current_regs_snapshot = JSON.parse(JSON.stringify(context)); // Iterate through the original register names from the snapshot for (const reg_name_orig in current_regs_snapshot) { // Ensure it's a direct property, not inherited if (current_regs_snapshot.hasOwnProperty(reg_name_orig)) { const reg_name_lower = reg_name_orig.toLowerCase(); // Filter out PC, SP, and floating-point/vector registers (q* or v*) if (reg_name_lower !== "pc" && reg_name_lower !== "sp" && !reg_name_lower.startsWith("q") && !reg_name_lower.startsWith("v")) { // Compare with the previous snapshot if (pre_regs[reg_name_orig] !== current_regs_snapshot[reg_name_orig]) { // If the value has changed, update the previous snapshot and record the difference pre_regs[reg_name_orig] = current_regs_snapshot[reg_name_orig]; diff_regs[reg_name_orig] = current_regs_snapshot[reg_name_orig]; // Store the NEW value } } } } return diff_regs; }
// This function is defined but not currently used in the logging. Keeping it as it was in the original code. functionget_involved_regs(instruction) { const involved_regs = newSet(); instruction.operands.forEach(op => { if (op.type === 'reg') { involved_regs.add(op.value); } elseif (op.type === 'mem') { if (op.value.base) { involved_regs.add(op.value.base); } if (op.value.index) { involved_regs.add(op.value.index); } } }); returnArray.from(involved_regs); }
// ======================================================================================= // Modified StalkerTrace Function (FIXED local_pre_regs access) // ======================================================================================= functionStalkerTrace(current_module_baseaddr) { // Ensure the correct module base address is passed // Use the global module_size which should be set by now if (!current_module_baseaddr || current_module_baseaddr.isNull() || module_size === 0) { console.error("[-] StalkerTrace: Invalid module base address or size. Cannot proceed."); return; }
var stalker_target_func_offset = 0x4a458; // The offset of the function you want Stalker to trace var stalker_start_addr = current_module_baseaddr.add(stalker_target_func_offset); console.log("[+] StalkerTrace will target " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " at actual address: " + stalker_start_addr);
Interceptor.attach(stalker_start_addr, { onEnter: function (args) { // Stalker operates per thread. Get the current thread ID. this.tid = Process.getCurrentThreadId(); console.log("[+] Stalker: Entered " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " (TID: " + this.tid + ")");
// Initialize previous register state *for this specific thread*. // This variable is local to this onEnter call but will be captured by the closure. const local_pre_regs = {}; // Populate initial state, excluding filtered registers const initial_context_snapshot = JSON.parse(JSON.stringify(this.context)); for (const reg_name_orig in initial_context_snapshot) { if (initial_context_snapshot.hasOwnProperty(reg_name_orig)) { const reg_name_lower = reg_name_orig.toLowerCase(); if (reg_name_lower !== "pc" && reg_name_lower !== "sp" && !reg_name_lower.startsWith("q") && !reg_name_lower.startsWith("v")) { local_pre_regs[reg_name_orig] = initial_context_snapshot[reg_name_orig]; } } } // *** REMOVE THIS LINE *** this.local_pre_regs = local_pre_regs; // The variable 'local_pre_regs' is already in scope for the transform/callout closures.
// === Patch known crashing instructions on entering the traced function === // These addresses are relative to the module base address (current_module_baseaddr) const knownCrashOffsets = [0x48AC0, 0x4ABB8]; // List of known problematic instruction offsets knownCrashOffsets.forEach(offset => { try { const patchAddr = current_module_baseaddr.add(offset); // Read instruction bytes to verify it's the expected 'MOV WSP, #1' // ARM64: MOV WSP, #1 is 320003FF (little endian) const expectedBytes = newUint8Array([0x32, 0x00, 0x03, 0xFF]); const instrBytes = patchAddr.readByteArray(4); const actualBytes = newUint8Array(instrBytes);
Stalker.follow(this.tid, { // Configure events to trace. exec=true traces every executed instruction. events: { call: false, ret: false, exec: true, block: false, compile: false }, // The transform callback allows inspecting/rewriting instruction blocks transform(iterator) { let instruction = iterator.next(); do { const currentAddress = instruction.address; let patchedThisInstruction = false;
// Check if the instruction is within the target module's bounds const is_module_code = currentAddress.compare(module_start) >= 0 && currentAddress.compare(module_end) < 0;
// === Universal MOV WSP/SP, #1 detection and NOP logic === // This is a fallback/alternative to the onEnter patch // It happens per block compiled by Stalker if (is_module_code) { // Only patch instructions within the target module const mnemonic = instruction.mnemonic.toLowerCase(); // Check for MOV or MOVZ with SP/WSP as destination and immediate 1 if ((mnemonic === 'mov' || mnemonic === 'movz') && instruction.operands.length >= 2) { const destOperand = instruction.operands[0]; const srcOperand = instruction.operands[1];
console.warn("[StalkerTransform] Identified potential crash instruction '" + instruction.toString() + "' at " + currentAddress + ". Replacing with NOP."); iterator.putNop(); // Replace the instruction with NOP patchedThisInstruction = true; } } } // =========================================================
// If the instruction wasn't NOPped by the above check, add a callout for logging if (!patchedThisInstruction) { // Only add callout for instructions within the target module if (is_module_code) { // 'local_pre_regs' is accessible here via closure iterator.putCallout(function (context) { // Access local_pre_regs directly from the outer scope (captured by closure) const thread_pre_regs = local_pre_regs; const pc = context.pc;
// Get the register differences after this instruction executes // get_diff_regs modifies thread_pre_regs in place const diff_regs = get_diff_regs(context, thread_pre_regs); // Find the module info again (can be optimized by caching, but this is reliable) var current_callout_module = Process.findModuleByAddress(pc);
// Ensure we are logging code from the target module if (current_callout_module && current_callout_module.name === TARGET_MODULE_NAME) { const instrToLog = Instruction.parse(pc); let logLine = `${current_callout_module.name}!${pc.sub(current_callout_module.base)}${instrToLog.toString()}`;
// Append changed registers and potential strings const diffRegKeys = Object.keys(diff_regs); if (diffRegKeys.length > 0) { logLine += `\t\t| Changed Regs: `; let firstReg = true; for (const regName of diffRegKeys) { if (!firstReg) logLine += ", "; firstReg = false; const regValue = diff_regs[regName]; // The new value of the register logLine += `${regName}=${regValue}`;
// --- Check if the register value points to a string --- const potentialString = tryReadString(regValue); if (potentialString !== null) { // Truncate long strings for readability const displayString = potentialString.length > 60 ? potentialString.substring(0, 57) + "..." : potentialString; // Escape potential quotes in the string for cleaner output const escapedString = displayString.replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); logLine += ` ("${escapedString}")`; } // ---------------------------------------------------------- } }
console.log(logLine); } }); } // Keep the original instruction iterator.keep(); } } while ((instruction = iterator.next()) !== null); // Process next instruction in the block } }); }, onLeave: function (retval) { console.log("[+] " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " onLeave, TID:", this.tid); // Stop stalking this thread when the function exits Stalker.unfollow(this.tid); // Clean up Stalker's compiled code cache Stalker.garbageCollect(); } }); }
// Start the script execution by hooking the linker setImmediate(function() { console.log("[i] Script starting for " + module_name + "..."); hook_linker_call_constructors(); });
console.log("[i] Script loaded. Waiting for " + module_name + " to be loaded...");
var offset = 0x88060; var module_size = 0; var module_name = "libbaiduprotect.so"; var base_addr = null; // To store the base address of libbaiduprotect.so
// Create an empty function var empty_func = newNativeCallback(function() { return0; }, 'int', ['pointer']);
functionhook_linker_call_constructors() { let linker64_base_addr = Module.getBaseAddress('linker64'); let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv let call_constructors = linker64_base_addr.add(offset_call); let listener = Interceptor.attach(call_constructors, { onEnter: function(args) { console.log('[linker] Call_Constructors onEnter'); let secmodule = Process.findModuleByName(module_name); if (secmodule != null) { module_size = secmodule.size; base_addr = secmodule.base; // Save the base address console.log("libbaiduprotect.so base: " + base_addr + ", size: " + module_size); hook_target_func(base_addr); listener.detach(); } } }); }
functionhook_target_func(baseaddr) { let target_addr = baseaddr.add(offset); let listener = Interceptor.attach(target_addr, { onEnter: function(args) { console.log("Target function at " + target_addr + " entered"); }, onLeave: function(retval) { console.log("libbaiduprotect.so base address: " + baseaddr); hook_pthread_create(); hook_sub_5ae5c(baseaddr); // Hook sub_5AE5C after target function listener.detach(); } }); }
functionhook_pthread_create() { let pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); Interceptor.attach(pthread_create_addr, { onEnter: function(args) { let func_addr = args[2]; let target_func_addr = base_addr.add(0x3E9F0); // Calculate the target function address if (func_addr.equals(target_func_addr)) { console.log("Replacing func_addr " + func_addr + " with empty function"); args[2] = empty_func; // Replace with the empty function // hook_libart(); // Commented out as it was not provided } else { letmodule = Process.findModuleByAddress(func_addr); if (module && module.name === module_name) { console.log("pthread_create called in libbaiduprotect.so, function address: " + func_addr); } } }, onLeave: function(retval) { // Optionally log return value } }); }
functionhook_sub_5ae5c(baseaddr) { if (!baseaddr || baseaddr.isNull()) { console.error("[-] hook_sub_5ae5c: Invalid base address. Cannot proceed."); return; } let target_addr = baseaddr.add(0x5AE5C); console.log("[+] Hooking sub_5AE5C at " + target_addr); Interceptor.attach(target_addr, { onEnter: function(args) { console.log("[sub_5AE5C] onEnter"); // Dump args[0] (pointer to _QWORD) try { if (!args[0].isNull()) { console.log("args[0] (*_QWORD at " + args[0] + "):"); console.log(hexdump(args[0], { length: 64, ansi: true })); } else { console.warn("args[0] is null"); } } catch (e) { console.warn("Failed to hexdump args[0]: " + e.message); } // Dump args[2] (data + 8) try { if (!args[2].isNull()) { console.log("args[2] (data + 8 at " + args[2] + "):"); console.log(hexdump(args[2], { length: 64, ansi: true })); } else { console.warn("args[2] is null"); } } catch (e) { console.warn("Failed to hexdump args[2]: " + e.message); } // Store pointers for comparison in onLeave this.arg0 = args[0]; this.arg2 = args[2]; }, onLeave: function(retval) { console.log("[sub_5AE5C] onLeave, Return value: " + retval); // Dump args[0] again to check for changes try { if (!this.arg0.isNull()) { console.log("args[0] (*_QWORD at " + this.arg0 + ") after execution:"); console.log(hexdump(this.arg0, { length: 64, ansi: true })); } else { console.warn("args[0] is null in onLeave"); } } catch (e) { console.warn("Failed to hexdump args[0] in onLeave: " + e.message); } // Dump args[2] again to check for changes try { if (!this.arg2.isNull()) { console.log("args[2] (data + 8 at " + this.arg2 + ") after execution:"); console.log(hexdump(this.arg2, { length: 64, ansi: true })); } else { console.warn("args[2] is null in onLeave"); } } catch (e) { console.warn("Failed to hexdump args[2] in onLeave: " + e.message); } } }); }