fix(proguard): 支持 R8 内联行映射格式,修复 r8-map-id 堆栈帧无法反混淆
1. 解析器新增 R8 内联格式支持:start🔚returnType OrigClass.method(args):origStart:origEnd -> obf
保存原始类名和方法名,而非只保存行号。
2. deobfuscateStack 正则从 [\w.]+ 改为 [^:)]+ 以匹配 r8-map-id-xxx 格式的 source 文件字段。
3. deobfuscateFrame 优先按行号范围查找 R8 内联条目,从中还原真实的类名、方法名和行号。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
5f7eb3ae13
当前提交
86ce76c7ed
@ -1,11 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* Android ProGuard mapping.txt 解析器
|
* Android ProGuard / R8 mapping.txt 解析器
|
||||||
*
|
*
|
||||||
* mapping.txt 格式示例:
|
* 标准 ProGuard 格式:
|
||||||
* com.example.MyClass -> a.b.c:
|
* com.example.MyClass -> a.b.c:
|
||||||
* int myField -> a
|
|
||||||
* void myMethod(int) -> a
|
* void myMethod(int) -> a
|
||||||
* 42:42:void otherMethod():100:100 -> b
|
*
|
||||||
|
* R8 内联格式(行号范围 + 原始类名):
|
||||||
|
* 13:22:java.lang.Object com.example.Foo.bar():141:141 -> invokeSuspend
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ProguardMapping {
|
export interface ProguardMapping {
|
||||||
@ -23,13 +24,17 @@ export interface MethodMapping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LineMapping {
|
export interface LineMapping {
|
||||||
|
obfuscatedLineStart: number
|
||||||
|
obfuscatedLineEnd: number
|
||||||
originalLine: number
|
originalLine: number
|
||||||
obfuscatedLine: number
|
|
||||||
originalFile: string
|
originalFile: string
|
||||||
|
// R8 内联:原始类名和方法名(与 obfuscatedClass 可能不同)
|
||||||
|
originalClass?: string
|
||||||
|
originalMethod?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 ProGuard mapping.txt 文件
|
* 解析 ProGuard / R8 mapping.txt 文件
|
||||||
*/
|
*/
|
||||||
export function parseProguardMapping(content: string): Map<string, ProguardMapping> {
|
export function parseProguardMapping(content: string): Map<string, ProguardMapping> {
|
||||||
const mappings = new Map<string, ProguardMapping>()
|
const mappings = new Map<string, ProguardMapping>()
|
||||||
@ -37,14 +42,11 @@ export function parseProguardMapping(content: string): Map<string, ProguardMappi
|
|||||||
let currentClass: ProguardMapping | null = null
|
let currentClass: ProguardMapping | null = null
|
||||||
let currentOriginalClass = ''
|
let currentOriginalClass = ''
|
||||||
let currentObfuscatedClass = ''
|
let currentObfuscatedClass = ''
|
||||||
let lineNumber = 0
|
|
||||||
|
|
||||||
const lines = content.split('\n')
|
const lines = content.split('\n')
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
lineNumber++
|
// 跳过空行和注释(含 R8 的 # {...} 行)
|
||||||
|
|
||||||
// 跳过空行和注释
|
|
||||||
if (!line.trim() || line.startsWith('#')) continue
|
if (!line.trim() || line.startsWith('#')) continue
|
||||||
|
|
||||||
// 类映射行:com.example.MyClass -> a.b.c:
|
// 类映射行:com.example.MyClass -> a.b.c:
|
||||||
@ -65,20 +67,43 @@ export function parseProguardMapping(content: string): Map<string, ProguardMappi
|
|||||||
|
|
||||||
if (!currentClass) continue
|
if (!currentClass) continue
|
||||||
|
|
||||||
// 行号映射行:42:42:void otherMethod():100:100 -> b
|
// R8 内联行:startLine:endLine:returnType OrigClass.method(args):origStart:origEnd -> obfuscatedMethod
|
||||||
|
// 示例: 13:22:java.lang.Object com.szyx.app.ywx.Foo$Bar.invokeSuspend(java.lang.Object):141:141 -> invokeSuspend
|
||||||
|
const r8InlineMatch = line.match(
|
||||||
|
/^\s+(\d+):(\d+):(\S+)\s+([\w$.]+)\.([\w$]+)\(([^)]*)\):(\d+):(\d+)\s+->\s+(\S+)$/
|
||||||
|
)
|
||||||
|
if (r8InlineMatch) {
|
||||||
|
const obfStart = parseInt(r8InlineMatch[1], 10)
|
||||||
|
const obfEnd = parseInt(r8InlineMatch[2], 10)
|
||||||
|
const originalClass = r8InlineMatch[4]
|
||||||
|
const originalMethod = r8InlineMatch[5]
|
||||||
|
const origLine = parseInt(r8InlineMatch[7], 10)
|
||||||
|
currentClass.lines.push({
|
||||||
|
obfuscatedLineStart: obfStart,
|
||||||
|
obfuscatedLineEnd: obfEnd,
|
||||||
|
originalLine: origLine,
|
||||||
|
originalFile: currentOriginalClass,
|
||||||
|
originalClass,
|
||||||
|
originalMethod,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProGuard 行号映射(无原始类信息):startLine:endLine:desc:origStart:origEnd -> obfuscatedMethod
|
||||||
const lineMatch = line.match(
|
const lineMatch = line.match(
|
||||||
/^\s+(\d+):(\d+):([^:]+):(\d+):(\d+)\s+->\s+(\S+)$/
|
/^\s+(\d+):(\d+):([^:]+):(\d+):(\d+)\s+->\s+(\S+)$/
|
||||||
)
|
)
|
||||||
if (lineMatch) {
|
if (lineMatch) {
|
||||||
currentClass.lines.push({
|
currentClass.lines.push({
|
||||||
|
obfuscatedLineStart: parseInt(lineMatch[1], 10),
|
||||||
|
obfuscatedLineEnd: parseInt(lineMatch[2], 10),
|
||||||
originalLine: parseInt(lineMatch[4], 10),
|
originalLine: parseInt(lineMatch[4], 10),
|
||||||
obfuscatedLine: parseInt(lineMatch[1], 10),
|
|
||||||
originalFile: currentOriginalClass,
|
originalFile: currentOriginalClass,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法映射行:void myMethod(int) -> a
|
// 方法映射行:[startLine:endLine:]returnType methodName(args) -> obfuscatedName
|
||||||
const methodMatch = line.match(
|
const methodMatch = line.match(
|
||||||
/^\s+(?:(\d+):(\d+):)?(\S+)\s+(\S+)\(([^)]*)\)\s+->\s+(\S+)$/
|
/^\s+(?:(\d+):(\d+):)?(\S+)\s+(\S+)\(([^)]*)\)\s+->\s+(\S+)$/
|
||||||
)
|
)
|
||||||
@ -89,7 +114,6 @@ export function parseProguardMapping(content: string): Map<string, ProguardMappi
|
|||||||
obfuscatedLine: methodMatch[1] ? parseInt(methodMatch[1], 10) : 0,
|
obfuscatedLine: methodMatch[1] ? parseInt(methodMatch[1], 10) : 0,
|
||||||
originalLine: methodMatch[2] ? parseInt(methodMatch[2], 10) : 0,
|
originalLine: methodMatch[2] ? parseInt(methodMatch[2], 10) : 0,
|
||||||
})
|
})
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,28 +129,50 @@ export function deobfuscateFrame(
|
|||||||
obfuscatedMethod: string,
|
obfuscatedMethod: string,
|
||||||
obfuscatedLine: number
|
obfuscatedLine: number
|
||||||
): { class: string; method: string; line: number; file: string } | null {
|
): { class: string; method: string; line: number; file: string } | null {
|
||||||
// 查找类映射
|
|
||||||
const mapping = mappings.get(obfuscatedClass)
|
const mapping = mappings.get(obfuscatedClass)
|
||||||
if (!mapping) return null
|
if (!mapping) return null
|
||||||
|
|
||||||
// 查找方法映射
|
// 优先:在行号范围内查找 R8 内联条目
|
||||||
const methodMapping = mapping.methods.find(m => m.obfuscatedMethod === obfuscatedMethod)
|
const r8Entry = mapping.lines.find(
|
||||||
|
l =>
|
||||||
// 查找行号映射
|
l.originalClass !== undefined &&
|
||||||
let originalLine = obfuscatedLine
|
l.originalMethod === obfuscatedMethod &&
|
||||||
const lineMapping = mapping.lines.find(l => l.obfuscatedLine === obfuscatedLine)
|
obfuscatedLine >= l.obfuscatedLineStart &&
|
||||||
if (lineMapping) {
|
obfuscatedLine <= l.obfuscatedLineEnd
|
||||||
originalLine = lineMapping.originalLine
|
)
|
||||||
} else if (methodMapping) {
|
if (r8Entry && r8Entry.originalClass) {
|
||||||
// 使用方法的行号范围估算
|
const cls = r8Entry.originalClass
|
||||||
originalLine = methodMapping.originalLine
|
return {
|
||||||
|
class: cls,
|
||||||
|
method: r8Entry.originalMethod!,
|
||||||
|
line: r8Entry.originalLine,
|
||||||
|
file: `${cls.split('.').pop()}.kt`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 次选:行号范围条目(无内联类信息)
|
||||||
|
const lineEntry = mapping.lines.find(
|
||||||
|
l => obfuscatedLine >= l.obfuscatedLineStart && obfuscatedLine <= l.obfuscatedLineEnd
|
||||||
|
)
|
||||||
|
if (lineEntry) {
|
||||||
|
const methodEntry = mapping.methods.find(m => m.obfuscatedMethod === obfuscatedMethod)
|
||||||
|
const cls = mapping.originalClass
|
||||||
|
return {
|
||||||
|
class: cls,
|
||||||
|
method: methodEntry?.originalMethod || obfuscatedMethod,
|
||||||
|
line: lineEntry.originalLine,
|
||||||
|
file: `${cls.split('.').pop()}.kt`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底:仅匹配方法名
|
||||||
|
const methodMapping = mapping.methods.find(m => m.obfuscatedMethod === obfuscatedMethod)
|
||||||
|
const cls = mapping.originalClass
|
||||||
return {
|
return {
|
||||||
class: mapping.originalClass,
|
class: cls,
|
||||||
method: methodMapping?.originalMethod || obfuscatedMethod,
|
method: methodMapping?.originalMethod || obfuscatedMethod,
|
||||||
line: originalLine,
|
line: methodMapping?.originalLine || obfuscatedLine,
|
||||||
file: `${mapping.originalClass.split('.').pop()}.java`,
|
file: `${cls.split('.').pop()}.kt`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,9 +187,10 @@ export function deobfuscateStack(
|
|||||||
const result: string[] = []
|
const result: string[] = []
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// 匹配 "at com.example.a.b(MyFile.java:42)" 格式
|
// 匹配 "at ClassName.method(File:line)" 格式。
|
||||||
|
// File 部分允许连字符,以兼容 R8 的 r8-map-id-xxx 格式。
|
||||||
const match = line.match(
|
const match = line.match(
|
||||||
/at\s+([\w.$]+)\.([\w$]+)\(([\w.]+):(\d+)\)/
|
/at\s+([\w.$]+)\.([\w$]+)\(([^:)]+):(\d+)\)/
|
||||||
)
|
)
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
@ -160,7 +207,7 @@ export function deobfuscateStack(
|
|||||||
|
|
||||||
if (deobfuscated) {
|
if (deobfuscated) {
|
||||||
result.push(
|
result.push(
|
||||||
`at ${deobfuscated.class}.${deobfuscated.method}(${deobfuscated.file}:${deobfuscated.line})`
|
`\tat ${deobfuscated.class}.${deobfuscated.method}(${deobfuscated.file}:${deobfuscated.line})`
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户