175 行
4.4 KiB
TypeScript
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')
|
|||
|
|
}
|