Skip to content

Commit a0057fe

Browse files
committed
feat: enhance logging capabilities and improve deobfuscation process with detailed summaries and error handling
1 parent c8b5b19 commit a0057fe

14 files changed

Lines changed: 174 additions & 52 deletions

packages/deob/src/ast-utils/generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function codePreview(node: t.Node): string {
1818
...defaultOptions,
1919
})
2020
if (code.length > 100) {
21-
return `${code.slice(0, 70)}${code.slice(-30)}`
21+
return `${code.slice(0, 70)} ${code.slice(-30)}`
2222
}
2323
return code
2424
}

packages/deob/src/ast-utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './matcher'
55
export * from './rename'
66
export * from './scope'
77
export * from './transform'
8+
export * from './logger'
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import debug from 'debug'
2+
3+
export const deobLogger = debug('Deob')
4+
5+
export function createLogger(namespace: string) {
6+
return debug(namespace)
7+
}
8+
9+
export function enableLogger(namespace = 'Deob') {
10+
debug.enable(namespace)
11+
}

packages/deob/src/ast-utils/transform.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import type {
66
import traverse, { visitors } from '@babel/traverse'
77
import debug from 'debug'
88

9-
const logger = debug('webcrack:transforms')
10-
119
export async function applyTransformAsync<TOptions>(
1210
ast: Node,
1311
transform: AsyncTransform<TOptions>,
@@ -52,8 +50,6 @@ export function applyTransforms(
5250
options: { noScope?: boolean; name?: string; log?: boolean } = {},
5351
): TransformState {
5452
options.log ??= true
55-
const name = options.name ?? transforms.map(t => t.name).join(', ')
56-
if (options.log) logger(`${name}: started`)
5753
const state: TransformState = { changes: 0 }
5854

5955
for (const transform of transforms) {
@@ -68,7 +64,6 @@ export function applyTransforms(
6864
traverse(ast, visitor, undefined, state)
6965
}
7066

71-
if (options.log) logger(`${name}: finished with ${state.changes} changes`)
7267
return state
7368
}
7469

packages/deob/src/deobfuscate/inline-decoder-wrappers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
import {
77
inlineFunctionAliases,
88
inlineVariableAliases,
9+
deobLogger as logger,
910
} from '../ast-utils'
1011

1112
/**
@@ -21,8 +22,11 @@ export default {
2122
const decoderName = decoder.node.id.name
2223
const decoderBinding = decoder.parentPath.scope.getBinding(decoderName)
2324
if (decoderBinding) {
24-
state.changes += inlineVariableAliases(decoderBinding).changes
25-
state.changes += inlineFunctionAliases(decoderBinding).changes
25+
const varState = inlineVariableAliases(decoderBinding).changes
26+
const fnState = inlineFunctionAliases(decoderBinding).changes
27+
state.changes += varState
28+
state.changes += fnState
29+
logger(`解密函数包装内联: ${decoderName} | 变量别名 ${varState} 处, 函数别名 ${fnState} 处`)
2630
}
2731
},
2832
} satisfies Transform<NodePath<t.FunctionDeclaration>>

packages/deob/src/deobfuscate/inline-object-props.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
import {
66
constKey,
77
constMemberExpression,
8+
deobLogger as logger,
89
inlineObjectProperties,
910
isReadonlyObject,
1011
} from '../ast-utils'
@@ -70,6 +71,7 @@ export default {
7071
m.or(m.stringLiteral(), m.numericLiteral()),
7172
),
7273
)
74+
logger(`对象属性内联: ${varId.current!.name} -> ${objectProperties.current!.length} 个字面量属性`)
7375
},
7476
}
7577
},

packages/deob/src/deobfuscate/self-defending.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type {
55
Transform,
66
} from '../ast-utils'
77
import {
8+
codePreview,
89
constMemberExpression,
10+
deobLogger as logger,
911
emptyIife,
1012
falseMatcher,
1113
findParent,
@@ -26,6 +28,15 @@ export default {
2628
tags: ['safe'],
2729
scope: true,
2830
visitor() {
31+
const removedSnippets: string[] = []
32+
const MAX_SNIPPETS = 5
33+
34+
const recordRemoval = (node: t.Node, reason: string) => {
35+
if (removedSnippets.length >= MAX_SNIPPETS)
36+
return
37+
removedSnippets.push(`${reason}: ${codePreview(node)}`)
38+
}
39+
2940
const callController = m.capture(m.anyString())
3041
const firstCall = m.capture(m.identifier())
3142
const rfn = m.capture(m.identifier())
@@ -126,27 +137,38 @@ export default {
126137
// callControllerFunctionName(this, function () { ... })();
127138
// ^ ref
128139
ref.parentPath.parentPath?.remove()
140+
recordRemoval(ref.parentPath.parentPath?.node!, '移除自卫入口调用')
129141
}
130142
else {
131143
// const selfDefendingFunctionName = callControllerFunctionName(this, function () {
132144
// selfDefendingFunctionName(); ^ ref
133-
removeSelfDefendingRefs(ref as NodePath<t.Identifier>)
145+
removeSelfDefendingRefs(ref as NodePath<t.Identifier>, recordRemoval)
134146
}
135147

136148
// leftover (function () {})() from debug protection function call
137-
findParent(ref, emptyIife)?.remove()
149+
const leftover = findParent(ref, emptyIife)
150+
if (leftover) {
151+
recordRemoval(leftover.node, '移除多余 IIFE')
152+
leftover.remove()
153+
}
138154

139155
this.changes++
140156
})
141157

158+
logger([
159+
'移除自卫代码片段预览:',
160+
...removedSnippets,
161+
removedSnippets.length >= MAX_SNIPPETS ? '... 更多片段已省略' : '',
162+
].filter(Boolean).join('\n'))
163+
142164
path.remove()
143165
this.changes++
144166
},
145167
}
146168
},
147169
} satisfies Transform
148170

149-
function removeSelfDefendingRefs(path: NodePath<t.Identifier>) {
171+
function removeSelfDefendingRefs(path: NodePath<t.Identifier>, recordRemoval: (node: t.Node, reason: string) => void) {
150172
const varName = m.capture(m.anyString())
151173
const varMatcher = m.variableDeclarator(
152174
m.identifier(varName),
@@ -161,9 +183,12 @@ function removeSelfDefendingRefs(path: NodePath<t.Identifier>) {
161183
const binding = varDecl.scope.getBinding(varName.current!)
162184

163185
binding?.referencePaths.forEach((ref) => {
164-
if (callMatcher.match(ref.parentPath?.parent))
186+
if (callMatcher.match(ref.parentPath?.parent)) {
187+
recordRemoval(ref.parentPath?.parentPath?.node!, '移除自卫函数调用')
165188
ref.parentPath?.parentPath?.remove()
189+
}
166190
})
191+
recordRemoval(varDecl.node, '移除自卫函数声明')
167192
varDecl.remove()
168193
}
169194
}

packages/deob/src/index.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { join, normalize } from 'node:path'
22
import * as parser from '@babel/parser'
33
import { codeFrameColumns } from '@babel/code-frame'
44
import type * as t from '@babel/types'
5-
import debug from 'debug'
6-
import { applyTransform, applyTransforms, codePrettier, generate } from './ast-utils'
5+
import { applyTransform, applyTransforms, codePrettier, codePreview, enableLogger, generate, deobLogger as logger } from './ast-utils'
76
import type { Options } from './options'
87
import { defaultOptions, mergeOptions } from './options'
98
import type { StringArray } from './deobfuscate/string-array'
@@ -59,8 +58,6 @@ function handleError(error: any, rawCode: string) {
5958
}
6059
}
6160

62-
const logger = debug('Deob')
63-
6461
function shorten(value: string, max = 120) {
6562
const clean = value.replace(/\s+/g, ' ').trim()
6663
if (clean.length <= max)
@@ -69,7 +66,7 @@ function shorten(value: string, max = 120) {
6966
}
7067

7168
function buildDecryptionSummaryLog(map: Map<string, string>) {
72-
const lines = []
69+
const lines = ['=== 解密结果预览 ===']
7370

7471
lines.push(`- 解密条目: ${map.size} 个`)
7572
if (map.size) {
@@ -101,8 +98,7 @@ export class Deob {
10198
mergeOptions(options)
10299
this.options = options
103100

104-
// debug.enable('webcrack:*')
105-
debug.enable('Deob')
101+
enableLogger('Deob')
106102

107103
if (!rawCode)
108104
throw new Error('请载入js代码')
@@ -143,11 +139,11 @@ export class Deob {
143139
}
144140

145141
prepare() {
146-
applyTransforms(this.ast, [blockStatements, sequence, splitVariableDeclarations, varFunctions, rawLiterals])
142+
return applyTransforms(this.ast, [blockStatements, sequence, splitVariableDeclarations, varFunctions, rawLiterals])
147143
}
148144

149145
unminify() {
150-
applyTransform(this.ast, unminify)
146+
return applyTransform(this.ast, unminify)
151147
}
152148

153149
run(): DeobResult {
@@ -187,7 +183,9 @@ export class Deob {
187183
decoders = designDecoder(this.ast, options.designDecoderName!)
188184
}
189185

190-
logger(`${stringArray ? `字符串数组: ${stringArray?.name}` : '没找到字符串数组'} | ${decoders.length ? `解密器函数: ${decoders.map(d => d.name)}` : '没找到解密器函数'}`)
186+
logger(`${stringArray ? `字符串数组: ${stringArray?.name} (${stringArray?.length} 个) 引用 ${stringArray?.references.length} 处` : '没找到字符串数组'} | ${decoders.length ? `解密器函数: ${decoders.map(d => d.name)}` : '没找到解密器函数'}`)
187+
if (rotators.length)
188+
logger(`字符串数组旋转器: ${rotators.length} 个`)
191189

192190
evalCode(setupCode)
193191

@@ -206,17 +204,37 @@ export class Deob {
206204
logger(buildDecryptionSummaryLog(map))
207205

208206
if (options.isRemoveDecoder && !options.isStrongRemove) {
209-
stringArray?.path.remove()
210-
rotators.forEach(r => r.remove())
211-
decoders.forEach(d => d.path?.remove())
207+
const removedSnippets: string[] = []
208+
const MAX_SNIPPETS = 3
209+
const addSnippet = (node: t.Node) => {
210+
if (removedSnippets.length < MAX_SNIPPETS)
211+
removedSnippets.push(codePreview(node))
212+
}
213+
214+
if (stringArray?.path) {
215+
addSnippet(stringArray.path.node)
216+
stringArray.path.remove()
217+
}
218+
rotators.forEach((r) => {
219+
addSnippet(r.node)
220+
r.remove()
221+
})
222+
decoders.forEach((d) => {
223+
addSnippet(d.path.node)
224+
d.path?.remove()
225+
})
226+
logger(`已移除解密相关节点${removedSnippets.length ? `,片段:\n${removedSnippets.join('\n')}` : ''}`)
212227
}
228+
return { changes: (map as any)?.size ?? decoders.length + rotators.length }
213229
},
214230
/** 对象引用替换 */
215231
() => applyTransform(this.ast, inlineObjectProps),
216232
/** 控制流平坦化 */
217233
() => {
234+
logger(`控制流平坦化执行次数: ${options.execCount}`)
235+
let changes = 0
218236
for (let i = 0; i < options.execCount; i++) {
219-
applyTransforms(
237+
const state = applyTransforms(
220238
this.ast,
221239
[
222240
mergeStrings,
@@ -225,31 +243,40 @@ export class Deob {
225243
controlFlowSwitch,
226244
],
227245
)
246+
changes += state.changes
228247
}
248+
return { changes }
229249
},
230250
/** 合并对象 */
231251
() => applyTransform(this.ast, mergeObjectAssignments),
232252
/** 去 minify */
233-
() => applyTransform(this.ast, unminify),
253+
() => this.unminify(),
234254
/** 对象命名优化 */
235255
() => {
236256
const matcher = getMangleMatcher(options)
237-
if (matcher)
238-
applyTransform(this.ast, mangle, matcher)
257+
if (matcher) {
258+
return applyTransform(this.ast, mangle, matcher)
259+
}
239260
},
240261
/** 移除自卫代码 */
241-
() => {
242-
return applyTransforms(
243-
this.ast,
244-
[
245-
[selfDefending, debugProtection],
246-
].flat(),
247-
)
248-
},
262+
() => applyTransforms(
263+
this.ast,
264+
[
265+
[selfDefending, debugProtection],
266+
].flat(),
267+
),
249268
/** 标记关键字 */
250-
options.isMarkEnable && (() => markKeyword(this.ast, options.keywords)),
269+
options.isMarkEnable && (() => {
270+
logger(`关键字列表: [${options.keywords.join(', ')}]`)
271+
markKeyword(this.ast, options.keywords)
272+
return { changes: options.keywords.length }
273+
}),
251274
/** 输出代码 */
252-
() => (outputCode = generate(this.ast)),
275+
() => {
276+
outputCode = generate(this.ast)
277+
logger(`输出代码长度: ${outputCode.length} 字符`)
278+
return { changes: outputCode.length }
279+
},
253280
].filter(Boolean) as (() => unknown)[]
254281

255282
for (let i = 0; i < stages.length; i++) {

packages/deob/src/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export interface Options {
22
/** 是否强力清除 */
33
isStrongRemove?: boolean
44

5-
/** 解密器嵌套深度 */
5+
/** 解密函数包装深度 */
66
inlineWrappersDepth?: number
77
/** 解密器定位方式 */
88
decoderLocationMethod?: 'obfuscate' | 'callCount' | 'stringArray' | 'evalCode'

packages/deob/src/transforms/decode-strings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as t from '@babel/types'
22
import type { Decoder } from '../deobfuscate/decoder'
3+
import { deobLogger as logger } from '../ast-utils'
34

45
/**
56
* 执行解密器 (使用 eval 执行)
@@ -11,6 +12,7 @@ import type { Decoder } from '../deobfuscate/decoder'
1112
*/
1213
export function decodeStrings(decoders: Decoder[]) {
1314
const map = new Map<string, string>() // 记录解密结果
15+
let failures = 0
1416

1517
for (const decoder of decoders) {
1618
decoder?.path.scope.getBinding(decoder.name)?.referencePaths.forEach((ref) => {
@@ -29,12 +31,16 @@ export function decodeStrings(decoders: Decoder[]) {
2931
callExpression.replaceWith(t.valueToNode(value))
3032
}
3133
catch (error) {
34+
failures++
3235
// 解密失败 则添加注释
3336
callExpression.addComment('leading', `decode_error: ${(error as any).message}`, true)
3437
}
3538
}
3639
})
3740
}
3841

42+
if (failures)
43+
logger(`\x1B[31m解密失败 ${failures} 处,已在代码中标记 decode_error\x1B[0m`)
44+
3945
return map
4046
}

0 commit comments

Comments
 (0)