Skip to main content
Glama
flutter_utils_v2.py14.6 kB
""" Flutter SSL 验证函数定位工具 V2 模拟 IDA 分析流程: 1. 搜索 ssl_client 字符串 2. 通过 xrefs_to 找到引用代码 3. 向上追溯到函数开头 4. 生成 Frida Hook 脚本 """ import os import struct from typing import Optional, Dict, List, Tuple try: import lief LIEF_AVAILABLE = True except ImportError: LIEF_AVAILABLE = False def find_ssl_verify_function_v2(so_path: str) -> dict: """ 使用 IDA 分析流程定位 Flutter SSL 验证函数 流程: 1. 搜索 "ssl_client" 字符串 2. 查找交叉引用 (ADRP+ADD 指令) 3. 从引用位置向上追溯函数开头 4. 返回函数的虚拟地址 Args: so_path: libflutter.so 文件路径 Returns: dict: 包含函数地址和 Frida 脚本 """ if not LIEF_AVAILABLE: return {"success": False, "error": "lief not available"} if not os.path.exists(so_path): return {"success": False, "error": f"File not found: {so_path}"} try: with open(so_path, 'rb') as f: data = f.read() binary = lief.parse(so_path) if binary is None: return {"success": False, "error": "Failed to parse SO file"} result = { "success": False, "analysis_steps": [], "function_address": None, "frida_script": None, "error": "" } # ==================== Step 1: 搜索 ssl_client 字符串 ==================== search_string = "ssl_client" search_bytes = search_string.encode('utf-8') string_file_offset = data.find(search_bytes) if string_file_offset == -1: result["error"] = "String 'ssl_client' not found" return result result["analysis_steps"].append({ "step": 1, "action": "搜索字符串 'ssl_client'", "result": f"找到于文件偏移 0x{string_file_offset:x}" }) # ==================== Step 2: 计算字符串虚拟地址 ==================== string_vaddr = string_file_offset # 默认 for section in binary.sections: sec_start = section.file_offset sec_end = section.file_offset + section.size if sec_start <= string_file_offset < sec_end: offset_in_section = string_file_offset - sec_start string_vaddr = section.virtual_address + offset_in_section result["analysis_steps"].append({ "step": 2, "action": "计算字符串虚拟地址", "result": f"虚拟地址 0x{string_vaddr:x} (在 {section.name} 段)" }) break # ==================== Step 3: 获取 .text 段信息 ==================== text_section = None for section in binary.sections: if section.name == ".text": text_section = section break if text_section is None: result["error"] = ".text section not found" return result text_file_start = text_section.file_offset text_file_end = text_section.file_offset + text_section.size text_vaddr_base = text_section.virtual_address vaddr_to_file_offset = text_file_start - text_vaddr_base # 负值用于转换 result["analysis_steps"].append({ "step": 3, "action": "获取 .text 段信息", "result": f"虚拟地址 0x{text_vaddr_base:x}, 文件偏移 0x{text_file_start:x}" }) # ==================== Step 4: 搜索交叉引用 (ADRP+ADD) ==================== target_page = string_vaddr & ~0xFFF target_page_offset = string_vaddr & 0xFFF xrefs = [] for file_offset in range(text_file_start, min(text_file_end, len(data) - 8), 4): insn = struct.unpack('<I', data[file_offset:file_offset+4])[0] # ADRP 指令检查 if (insn & 0x9F000000) == 0x90000000: # 提取立即数 immlo = (insn >> 29) & 0x3 immhi = (insn >> 5) & 0x7FFFF imm = (immhi << 2) | immlo # 符号扩展 if imm & 0x100000: imm = imm - 0x200000 # 计算当前指令虚拟地址 current_vaddr = text_vaddr_base + (file_offset - text_file_start) pc_page = current_vaddr & ~0xFFF adrp_target_page = (pc_page + (imm << 12)) & 0xFFFFFFFFFFFFFFFF # 检查是否指向目标页 if adrp_target_page == target_page: # 检查下一条 ADD 指令 next_insn = struct.unpack('<I', data[file_offset+4:file_offset+8])[0] # ADD immediate (32位或64位) if (next_insn & 0x7F800000) == 0x11000000 or (next_insn & 0xFF800000) == 0x91000000: add_imm = (next_insn >> 10) & 0xFFF if add_imm == target_page_offset: xrefs.append({ "file_offset": file_offset, "vaddr": current_vaddr }) if not xrefs: result["error"] = "No xrefs found for ssl_client" return result result["analysis_steps"].append({ "step": 4, "action": f"搜索交叉引用 (xrefs_to 0x{string_vaddr:x})", "result": f"找到 {len(xrefs)} 个引用" }) # ==================== Step 5: 分析所有候选函数 ==================== candidates = [] for i, xref in enumerate(xrefs): xref_file_offset = xref["file_offset"] xref_vaddr = xref["vaddr"] func_file_offset = find_function_start_arm64(data, text_file_start, xref_file_offset) if func_file_offset is None: continue # 计算函数虚拟地址 func_vaddr = text_vaddr_base + (func_file_offset - text_file_start) # 估算函数大小 func_size = estimate_function_size_arm64(data, func_file_offset, text_file_end) # 读取函数头部字节 func_bytes = data[func_file_offset:func_file_offset+32] func_bytes_hex = ' '.join(f'{b:02x}' for b in func_bytes[:16]) # 计算置信度 confidence = 0.5 # 函数大小评分 - SSL验证函数通常在300-800字节 if 300 <= func_size <= 800: confidence += 0.4 # 最佳大小 elif 200 < func_size < 1200: confidence += 0.2 # 可接受 elif func_size < 200: confidence -= 0.3 # 太小,可能是辅助函数 candidates.append({ "index": i, "xref_vaddr": f"0x{xref_vaddr:x}", "function_vaddr": f"0x{func_vaddr:X}", "function_file_offset": f"0x{func_file_offset:x}", "function_size": func_size, "function_bytes": func_bytes_hex, "confidence": round(confidence, 2) }) if not candidates: result["error"] = "Could not find any valid functions" return result # 按置信度排序 candidates.sort(key=lambda x: -x["confidence"]) result["candidates"] = candidates result["candidates_count"] = len(candidates) # 选择最佳候选 best = candidates[0] func_vaddr = int(best["function_vaddr"], 16) result["analysis_steps"].append({ "step": 5, "action": "分析所有候选函数并选择最佳", "result": f"找到 {len(candidates)} 个候选,选择 {best['function_vaddr']} (大小: {best['function_size']}字节, 置信度: {best['confidence']})" }) # ==================== Step 6: 验证并生成脚本 ==================== # 读取函数开头的字节作为验证 func_bytes = data[func_file_offset:func_file_offset+32] func_bytes_hex = ' '.join(f'{b:02x}' for b in func_bytes) result["analysis_steps"].append({ "step": 6, "action": "读取函数头部字节", "result": func_bytes_hex[:48] + "..." }) # 生成 Frida 脚本 (使用标准模板) frida_script = f"""/** * Flutter SSL Bypass - 自动生成 * 偏移: 0x{func_vaddr:X} (通过 so-analyzer-mcp 分析获得) */ function hook_ssl() {{ var m = Module.findBaseAddress("libflutter.so"); if (!m) {{ setTimeout(hook_ssl, 500); return; }} var addr = m.add(0x{func_vaddr:X}); console.log("[+] Hook @ " + addr); Interceptor.attach(addr, {{ onLeave: function(ret) {{ ret.replace(0x1); console.log("[√] SSL bypassed"); }} }}); console.log("[+] SSL Hook OK"); }} Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {{ onEnter: function(args) {{ this.n = args[0].readCString(); }}, onLeave: function(r) {{ if (this.n && this.n.indexOf("libflutter.so") >= 0) hook_ssl(); }} }}); hook_ssl(); """ # 自动保存脚本到文件 script_saved = False script_path = None try: # 在 so 文件同目录创建脚本 so_dir = os.path.dirname(so_path) script_filename = "flutter_ssl_bypass.js" script_path = os.path.join(so_dir, script_filename) with open(script_path, 'w', encoding='utf-8') as f: f.write(frida_script) script_saved = True result["script_saved_to"] = script_path except Exception as e: result["script_save_error"] = str(e) result["success"] = True result["function_address"] = f"0x{func_vaddr:X}" result["function_file_offset"] = f"0x{func_file_offset:x}" result["xref_address"] = f"0x{xref_vaddr:x}" result["string_address"] = f"0x{string_vaddr:x}" result["frida_script"] = frida_script return result except Exception as e: import traceback return {"success": False, "error": f"{str(e)}\n{traceback.format_exc()}"} def estimate_function_size_arm64(data: bytes, func_start: int, text_end: int) -> int: """估算 ARM64 函数大小""" max_search = min(func_start + 0x2000, text_end, len(data)) for offset in range(func_start + 4, max_search, 4): if offset + 4 > len(data): break insn = struct.unpack('<I', data[offset:offset+4])[0] # RET 指令 if insn == 0xD65F03C0: return offset - func_start + 4 # 下一个函数开头 if insn == 0xD503237F or (insn & 0xFFC003FF) == 0xA98003E0: if offset >= 4: prev_insn = struct.unpack('<I', data[offset-4:offset])[0] if prev_insn == 0xD65F03C0: return offset - func_start return max_search - func_start def find_function_start_arm64(data: bytes, text_start: int, ref_offset: int) -> Optional[int]: """ 从引用位置向上搜索 ARM64 函数开头 ARM64 函数开头常见模式: 1. PACIBSP (0xD503237F) - ARMv8.3 PAC 指令 2. STP X29, X30, [SP, #imm]! - 保存帧指针和返回地址 3. SUB SP, SP, #imm - 分配栈空间 Args: data: 文件数据 text_start: .text 段起始偏移 ref_offset: 引用位置的文件偏移 Returns: 函数开头的文件偏移,找不到返回 None """ # 向前搜索最多 4KB search_start = max(text_start, ref_offset - 4096) candidates = [] for offset in range(ref_offset, search_start, -4): if offset < 4: break insn = struct.unpack('<I', data[offset:offset+4])[0] # 1. PACIBSP: 0xD503237F if insn == 0xD503237F: candidates.append((offset, "PACIBSP", 10)) # 高优先级 continue # 2. SUB SP, SP, #imm (常见函数开头) # 编码: 1101_0001_00_xxxxxxxxxxxx_11111_11111 if (insn & 0xFF0003FF) == 0xD10003FF: # 检查 imm 大小是否合理 (通常 0x10-0x200) imm = (insn >> 10) & 0xFFF if 0x10 <= imm <= 0x400: candidates.append((offset, "SUB SP", 8)) continue # 3. STP X29, X30, [SP, #imm]! # 编码模式: 1010_1001_xx_xxxxxxx_11110_11101_11111 if (insn & 0xFFC003FF) == 0xA98003E0: candidates.append((offset, "STP X29,X30", 9)) continue # 4. STP 变体 (offset 模式) # STP X29, X30, [SP, #imm] if (insn & 0xFFC003FF) == 0xA90003E0: candidates.append((offset, "STP X29,X30 offset", 7)) continue if not candidates: return None # 按优先级排序,返回最高优先级且最接近 ref_offset 的 candidates.sort(key=lambda x: (-x[2], ref_offset - x[0])) return candidates[0][0] def vaddr_to_file_offset(binary, vaddr: int) -> Optional[int]: """将虚拟地址转换为文件偏移""" for section in binary.sections: sec_vaddr_start = section.virtual_address sec_vaddr_end = section.virtual_address + section.size if sec_vaddr_start <= vaddr < sec_vaddr_end: offset_in_section = vaddr - sec_vaddr_start return section.file_offset + offset_in_section return None def file_offset_to_vaddr(binary, file_offset: int) -> Optional[int]: """将文件偏移转换为虚拟地址""" for section in binary.sections: sec_file_start = section.file_offset sec_file_end = section.file_offset + section.size if sec_file_start <= file_offset < sec_file_end: offset_in_section = file_offset - sec_file_start return section.virtual_address + offset_in_section return None

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/1600822305/so-analyzer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server