XuqmGroup-Symbolicator/src/parsers/proguard.ts

175 行
4.4 KiB
TypeScript

/**
* 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<string, ProguardMapping> {
const mappings = new Map<string, ProguardMapping>()
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<string, ProguardMapping>,
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<string, ProguardMapping>,
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')
}