/** * Android ProGuard mapping.txt 解析器 * * mapping.txt 格式示例: * com.example.MyClass -> a.b.c: * int myField -> a * void myMethod(int) -> a * 42:42:void otherMethod():100:100 -> b */ export interface ProguardMapping { obfuscatedClass: string originalClass: string methods: MethodMapping[] lines: LineMapping[] } export interface MethodMapping { obfuscatedMethod: string originalMethod: string obfuscatedLine: number originalLine: number } export interface LineMapping { originalLine: number obfuscatedLine: number originalFile: string } /** * 解析 ProGuard mapping.txt 文件 */ export function parseProguardMapping(content: string): Map { const mappings = new Map() let currentClass: ProguardMapping | null = null let currentOriginalClass = '' let currentObfuscatedClass = '' let lineNumber = 0 const lines = content.split('\n') for (const line of lines) { lineNumber++ // 跳过空行和注释 if (!line.trim() || line.startsWith('#')) continue // 类映射行:com.example.MyClass -> a.b.c: const classMatch = line.match(/^(\S+)\s+->\s+(\S+):$/) if (classMatch) { currentOriginalClass = classMatch[1] currentObfuscatedClass = classMatch[2].replace(/:$/, '') currentClass = { obfuscatedClass: currentObfuscatedClass, originalClass: currentOriginalClass, methods: [], lines: [], } mappings.set(currentObfuscatedClass, currentClass) continue } if (!currentClass) continue // 行号映射行:42:42:void otherMethod():100:100 -> b const lineMatch = line.match( /^\s+(\d+):(\d+):([^:]+):(\d+):(\d+)\s+->\s+(\S+)$/ ) if (lineMatch) { currentClass.lines.push({ originalLine: parseInt(lineMatch[4], 10), obfuscatedLine: parseInt(lineMatch[1], 10), originalFile: currentOriginalClass, }) continue } // 方法映射行:void myMethod(int) -> a const methodMatch = line.match( /^\s+(?:(\d+):(\d+):)?(\S+)\s+(\S+)\(([^)]*)\)\s+->\s+(\S+)$/ ) if (methodMatch) { currentClass.methods.push({ obfuscatedMethod: methodMatch[6], originalMethod: methodMatch[4], obfuscatedLine: methodMatch[1] ? parseInt(methodMatch[1], 10) : 0, originalLine: methodMatch[2] ? parseInt(methodMatch[2], 10) : 0, }) continue } } return mappings } /** * 使用 mapping 反混淆堆栈帧 */ export function deobfuscateFrame( mappings: Map, obfuscatedClass: string, obfuscatedMethod: string, obfuscatedLine: number ): { class: string; method: string; line: number; file: string } | null { // 查找类映射 const mapping = mappings.get(obfuscatedClass) if (!mapping) return null // 查找方法映射 const methodMapping = mapping.methods.find(m => m.obfuscatedMethod === obfuscatedMethod) // 查找行号映射 let originalLine = obfuscatedLine const lineMapping = mapping.lines.find(l => l.obfuscatedLine === obfuscatedLine) if (lineMapping) { originalLine = lineMapping.originalLine } else if (methodMapping) { // 使用方法的行号范围估算 originalLine = methodMapping.originalLine } return { class: mapping.originalClass, method: methodMapping?.originalMethod || obfuscatedMethod, line: originalLine, file: `${mapping.originalClass.split('.').pop()}.java`, } } /** * 反混淆整个堆栈字符串 */ export function deobfuscateStack( mappings: Map, stacktrace: string ): string { const lines = stacktrace.split('\n') const result: string[] = [] for (const line of lines) { // 匹配 "at com.example.a.b(MyFile.java:42)" 格式 const match = line.match( /at\s+([\w.$]+)\.([\w$]+)\(([\w.]+):(\d+)\)/ ) if (match) { const obfuscatedClass = match[1] const obfuscatedMethod = match[2] const obfuscatedLine = parseInt(match[4], 10) const deobfuscated = deobfuscateFrame( mappings, obfuscatedClass, obfuscatedMethod, obfuscatedLine ) if (deobfuscated) { result.push( `at ${deobfuscated.class}.${deobfuscated.method}(${deobfuscated.file}:${deobfuscated.line})` ) continue } } // 无法反混淆,保留原始行 result.push(line) } return result.join('\n') }