-
Notifications
You must be signed in to change notification settings - Fork 39
/
restringer.js
executable file
·149 lines (142 loc) · 5.74 KB
/
restringer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import {fileURLToPath} from 'node:url';
import {logger as flastLogger, applyIteratively} from 'flast';
import {processors} from './processors/index.js';
import {detectObfuscation} from 'obfuscation-detector';
import {config, safe as safeMod, unsafe as unsafeMod, utils} from './modules/index.js';
const {normalizeScript} = utils.default;
import {readFileSync} from 'node:fs';
const __version__ = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf-8')).version;
const safe = {};
for (const funcName in safeMod) {
safe[funcName] = safeMod[funcName].default || safeMod[funcName];
}
const unsafe = {};
for (const funcName in unsafeMod) {
unsafe[funcName] = unsafeMod[funcName].default || unsafeMod[funcName];
}
// Silence asyc errors
// process.on('uncaughtException', () => {});
export class REstringer {
static __version__ = __version__;
logger = flastLogger;
/**
* @param {string} script The target script to be deobfuscated
* @param {boolean} normalize Run optional methods which will make the script more readable
*/
constructor(script, normalize = true) {
this.script = script;
this.normalize = normalize;
this.modified = false;
this.obfuscationName = 'Generic';
this._preprocessors = [];
this._postprocessors = [];
this.logger.setLogLevelLog();
this.maxIterations = config.defaultMaxIterations;
this.detectObfuscationType = true;
// Deobfuscation methods that don't use eval
this.safeMethods = [
safe.rearrangeSequences,
safe.separateChainedDeclarators,
safe.rearrangeSwitches,
safe.normalizeEmptyStatements,
safe.removeRedundantBlockStatements,
safe.resolveRedundantLogicalExpressions,
safe.unwrapSimpleOperations,
safe.resolveProxyCalls,
safe.resolveProxyVariables,
safe.resolveProxyReferences,
safe.resolveMemberExpressionReferencesToArrayIndex,
safe.resolveMemberExpressionsWithDirectAssignment,
safe.parseTemplateLiteralsIntoStringLiterals,
safe.resolveDeterministicIfStatements,
safe.replaceCallExpressionsWithUnwrappedIdentifier,
safe.replaceEvalCallsWithLiteralContent,
safe.replaceIdentifierWithFixedAssignedValue,
safe.replaceIdentifierWithFixedValueNotAssignedAtDeclaration,
safe.replaceNewFuncCallsWithLiteralContent,
safe.replaceBooleanExpressionsWithIf,
safe.replaceSequencesWithExpressions,
safe.resolveFunctionConstructorCalls,
safe.replaceFunctionShellsWithWrappedValue,
safe.replaceFunctionShellsWithWrappedValueIIFE,
safe.simplifyCalls,
safe.unwrapFunctionShells,
safe.unwrapIIFEs,
safe.simplifyIfStatements,
];
// Deobfuscation methods that use eval
this.unsafeMethods = [
unsafe.resolveMinimalAlphabet,
unsafe.resolveDefiniteBinaryExpressions,
unsafe.resolveAugmentedFunctionWrappedArrayReplacements,
unsafe.resolveMemberExpressionsLocalReferences,
unsafe.resolveDefiniteMemberExpressions,
unsafe.resolveBuiltinCalls,
unsafe.resolveDeterministicConditionalExpressions,
unsafe.resolveInjectedPrototypeMethodCalls,
unsafe.resolveLocalCalls,
unsafe.resolveEvalCallsOnNonLiterals,
];
}
/**
* Determine the type of the obfuscation, and populate the appropriate pre- and post- processors.
*/
determineObfuscationType() {
const detectedObfuscationType = detectObfuscation(this.script, false).slice(-1)[0];
if (detectedObfuscationType) {
this.obfuscationName = detectedObfuscationType;
if (processors[detectedObfuscationType]) {
({preprocessors: this._preprocessors, postprocessors: this._postprocessors} = processors[detectedObfuscationType]);
}
}
this.logger.log(`[+] Obfuscation type is ${this.obfuscationName}`);
return this.obfuscationName;
}
/**
* Make all changes which don't involve eval first in order to avoid running eval on probelmatic values
* which can only be detected once part of the script is deobfuscated. Once all the safe changes are made,
* continue to the unsafe changes.
* Since the unsafe modification may be overreaching, run them only once and try the safe methods again.
*/
_loopSafeAndUnsafeDeobfuscationMethods() {
let modified, script;
do {
this.modified = false;
script = applyIteratively(this.script, this.safeMethods.concat(this.unsafeMethods), this.maxIterations);
if (this.script !== script) {
this.modified = true;
this.script = script;
}
if (this.modified) modified = true;
} while (this.modified); // Run this loop until the deobfuscation methods stop being effective.
this.modified = modified;
}
/**
* Entry point for this class.
* Determine obfuscation type and run the pre- and post- processors accordingly.
* Run the deobfuscation methods in a loop until nothing more is changed.
* Normalize script to make it more readable.
* @param {boolean} clean (optional) Remove dead nodes after deobfuscation. Defaults to false.
* @return {boolean} true if the script was modified during deobfuscation; false otherwise.
*/
deobfuscate(clean = false) {
if (this.detectObfuscationType) this.determineObfuscationType();
this._runProcessors(this._preprocessors);
this._loopSafeAndUnsafeDeobfuscationMethods();
this._runProcessors(this._postprocessors);
if (this.modified && this.normalize) this.script = normalizeScript(this.script);
if (clean) this.script = applyIteratively(this.script, [unsafe.removeDeadNodes], this.maxIterations);
return this.modified;
}
/**
* Run specific deobfuscation which must run before or after the main deobfuscation loop
* in order to successfully complete deobfuscation.
* @param {Array<Function|string>} processors An array of either imported deobfuscation methods or the name of internal methods.
*/
_runProcessors(processors) {
for (let i = 0; i < processors.length; i++) {
const processor = processors[i];
this.script = applyIteratively(this.script, [processor], 1);
}
}
}