Skip to content

Commit 630d3ca

Browse files
committed
fix: replaced expression simplification with approach from other deobfuscator
1 parent e22766d commit 630d3ca

File tree

1 file changed

+232
-90
lines changed

1 file changed

+232
-90
lines changed

Diff for: src/modifications/expressions/expressionSimplifier.ts

+232-90
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,36 @@ import TraversalHelper from '../../helpers/traversalHelper';
55

66
export default class ExpressionSimplifier extends Modification {
77
private readonly types = new Set(['BinaryExpression', 'UnaryExpression']);
8+
private static readonly RESOLVABLE_UNARY_OPERATORS: Set<string> = new Set([
9+
'-',
10+
'+',
11+
'!',
12+
'~',
13+
'typeof',
14+
'void'
15+
]);
16+
private static readonly RESOLVABLE_BINARY_OPERATORS: Set<string> = new Set([
17+
'==',
18+
'!=',
19+
'===',
20+
'!==',
21+
'<',
22+
'<=',
23+
'>',
24+
'>=',
25+
'<<',
26+
'>>',
27+
'>>>',
28+
'+',
29+
'-',
30+
'*',
31+
'/',
32+
'%',
33+
'**',
34+
'|',
35+
'^',
36+
'&'
37+
]);
838

939
/**
1040
* Creates a new modification.
@@ -47,140 +77,252 @@ export default class ExpressionSimplifier extends Modification {
4777
private simplifyExpression(expression: Shift.Expression): Shift.Expression {
4878
switch (expression.type) {
4979
case 'BinaryExpression':
50-
return this.simplifyBinaryExpression(expression);
80+
return this.simplifyBinaryExpression(expression) || expression;
5181

5282
case 'UnaryExpression':
53-
return this.simplifyUnaryExpression(expression);
83+
return this.simplifyUnaryExpression(expression) || expression;
5484

5585
default:
5686
return expression;
5787
}
5888
}
5989

90+
/**
91+
* Attempts to simplify a unary expression node.
92+
* @param expression The unary expression node.
93+
*/
94+
private simplifyUnaryExpression(expression: Shift.UnaryExpression): Shift.Expression | undefined {
95+
if (!ExpressionSimplifier.RESOLVABLE_UNARY_OPERATORS.has(expression.operator)) {
96+
return expression;
97+
} else if (expression.operator == '-' && expression.operand.type == 'LiteralNumericExpression') {
98+
return expression; // avoid trying to simplify negative numbers
99+
}
100+
101+
const argument = this.simplifyExpression(expression.operand);
102+
103+
if (this.isResolvableExpression(argument)) {
104+
const argumentValue = this.getResolvableExpressionValue(argument);
105+
const value = this.applyUnaryOperation(
106+
expression.operator as ResolvableUnaryOperator,
107+
argumentValue
108+
);
109+
return this.convertValueToExpression(value);
110+
} else {
111+
return expression;
112+
}
113+
}
114+
60115
/**
61116
* Attempts to simplify a binary expression node.
62117
* @param expression The binary expression node.
63118
*/
64-
private simplifyBinaryExpression(expression: Shift.BinaryExpression): Shift.Expression {
119+
private simplifyBinaryExpression(expression: Shift.BinaryExpression): Shift.Expression | undefined {
120+
if (
121+
!expression.left.type.endsWith('Expression') ||
122+
!ExpressionSimplifier.RESOLVABLE_BINARY_OPERATORS.has(expression.operator)
123+
) {
124+
return undefined;
125+
}
126+
65127
const left = this.simplifyExpression(expression.left);
66128
const right = this.simplifyExpression(expression.right);
67129

68-
const leftValue = this.getExpressionValueAsString(left);
69-
const rightValue = this.getExpressionValueAsString(right);
70-
71-
if (leftValue != null && rightValue != null) {
72-
const code = `${leftValue} ${expression.operator} ${rightValue}`;
73-
const simplified = this.evalCodeToExpression(code);
74-
return simplified != null ? simplified : expression;
75-
} else {
130+
if (this.isResolvableExpression(left) && this.isResolvableExpression(right)) {
131+
const leftValue = this.getResolvableExpressionValue(left);
132+
const rightValue = this.getResolvableExpressionValue(right);
133+
const value = this.applyBinaryOperation(
134+
expression.operator as ResolvableBinaryOperator,
135+
leftValue,
136+
rightValue
137+
);
138+
return this.convertValueToExpression(value);
139+
} else if (expression.operator == '-' && right.type == 'UnaryExpression' && right.operator == '-' && right.operand.type == 'LiteralNumericExpression') {
140+
// convert (- -a) to +a (as long as a is a number)
141+
expression.right = right.operand;
142+
expression.operator = '+';
76143
return expression;
144+
} else {
145+
return undefined;
77146
}
78147
}
79148

80149
/**
81-
* Attempts to simplify a unary expression node.
82-
* @param expression The unary expression node.
150+
* Applies a unary operation.
151+
* @param operator The operator.
152+
* @param argument The argument value.
153+
* @returns The resultant value.
83154
*/
84-
private simplifyUnaryExpression(expression: Shift.UnaryExpression): Shift.Expression {
85-
expression.operand = this.simplifyExpression(expression.operand);
86-
const code = this.getExpressionValueAsString(expression);
155+
private applyUnaryOperation(operator: ResolvableUnaryOperator, argument: any): any {
156+
switch (operator) {
157+
case '-':
158+
return -argument;
159+
case '+':
160+
return +argument;
161+
case '!':
162+
return !argument;
163+
case '~':
164+
return ~argument;
165+
case 'typeof':
166+
return typeof argument;
167+
case 'void':
168+
return void argument;
169+
}
170+
}
87171

88-
if (code != null) {
89-
const simplified = this.evalCodeToExpression(code);
90-
return simplified != null ? simplified : expression;
91-
} else {
92-
return expression;
172+
/**
173+
* Applies a binary operation.
174+
* @param operator The resolvable binary operator.
175+
* @param left The value of the left expression.
176+
* @param right The value of the right expression.
177+
* @returns The resultant value.
178+
*/
179+
private applyBinaryOperation(operator: ResolvableBinaryOperator, left: any, right: any): any {
180+
switch (operator) {
181+
case '==':
182+
return left == right;
183+
case '!=':
184+
return left != right;
185+
case '===':
186+
return left === right;
187+
case '!==':
188+
return left !== right;
189+
case '<':
190+
return left < right;
191+
case '<=':
192+
return left <= right;
193+
case '>':
194+
return left > right;
195+
case '>=':
196+
return left >= right;
197+
case '<<':
198+
return left << right;
199+
case '>>':
200+
return left >> right;
201+
case '>>>':
202+
return left >>> right;
203+
case '+':
204+
return left + right;
205+
case '-':
206+
return left - right;
207+
case '*':
208+
return left * right;
209+
case '/':
210+
return left / right;
211+
case '%':
212+
return left % right;
213+
case '**':
214+
return left ** right;
215+
case '|':
216+
return left | right;
217+
case '^':
218+
return left ^ right;
219+
case '&':
220+
return left & right;
93221
}
94222
}
95223

96224
/**
97-
* Returns the value of a node as a string, null if not possible.
98-
* @param expression The expression node.
225+
* Gets the real value from a resolvable expression.
226+
* @param expression The resolvable expression.
227+
* @returns The value.
99228
*/
100-
private getExpressionValueAsString(expression: Shift.Expression): string | null {
229+
private getResolvableExpressionValue(expression: ResolvableExpression): any {
101230
switch (expression.type) {
102-
case 'LiteralStringExpression':
103-
const value = expression.value
104-
.replace(/"/g, '\\"')
105-
.replace(/\n/g, '\\n')
106-
.replace(/\r/g, '\\r');
107-
return `"${value}"`;
108-
109231
case 'LiteralNumericExpression':
232+
case 'LiteralStringExpression':
110233
case 'LiteralBooleanExpression':
111-
return expression.value.toString();
112-
113-
case 'ArrayExpression':
114-
if (expression.elements.length == 0) {
115-
return '[]';
116-
} else if (expression.elements.every(e => !e || e.type.startsWith('Literal'))) {
117-
let content = '';
118-
for (let i = 0; i < expression.elements.length; i++) {
119-
if (expression.elements[i]) {
120-
content += `${this.getExpressionValueAsString(
121-
expression.elements[i] as Shift.Expression
122-
)},`;
123-
} else {
124-
content += ',';
125-
}
126-
}
127-
return `[${content.substring(0, content.length - 1)}]`;
128-
} else {
129-
return null;
130-
}
131-
132-
case 'ObjectExpression':
133-
if (expression.properties.length == 0) {
134-
expression.properties;
135-
return '[]';
136-
} else {
137-
return null;
138-
}
139-
234+
return expression.value;
140235
case 'UnaryExpression':
141-
const operand = this.getExpressionValueAsString(expression.operand);
142-
return operand != null ? `${expression.operator} ${operand}` : null;
143-
144-
default:
236+
return -this.getResolvableExpressionValue(
237+
expression.operand as Literal
238+
);
239+
case 'LiteralNullExpression':
145240
return null;
241+
case 'IdentifierExpression':
242+
return undefined;
243+
case 'ArrayExpression':
244+
return [];
245+
case 'ObjectExpression':
246+
return {};
146247
}
147248
}
148249

149250
/**
150-
* Evaluates a given piece of code and converts the result to an
151-
* expression node if possible.
152-
* @param code The code to be evaluated.
251+
* Attempts to convert a value of unknown type to an expression node.
252+
* @param value The value.
253+
* @returns The expression or undefined.
153254
*/
154-
private evalCodeToExpression(code: string): Shift.Expression | null {
155-
let value;
156-
try {
157-
value = eval(code);
158-
} catch (err) {
159-
return null;
160-
}
161-
255+
private convertValueToExpression(value: any): Shift.Expression | undefined {
162256
switch (typeof value) {
163257
case 'string':
164-
return new Shift.LiteralStringExpression({
165-
value: value
166-
});
167-
258+
return new Shift.LiteralStringExpression({ value });
168259
case 'number':
169-
return new Shift.LiteralNumericExpression({
170-
value: value
171-
});
172-
260+
return value >= 0
261+
? new Shift.LiteralNumericExpression({ value })
262+
: new Shift.UnaryExpression({ operator: '-', operand: new Shift.LiteralNumericExpression({ value: Math.abs(value) })});
173263
case 'boolean':
174-
return new Shift.LiteralBooleanExpression({
175-
value: value
176-
});
177-
264+
return new Shift.LiteralBooleanExpression({ value });
265+
case 'undefined':
266+
return new Shift.IdentifierExpression({ name: 'undefined' });
178267
default:
179-
return null;
268+
return undefined;
180269
}
181270
}
182271

183-
private isSimpleArray(array: Shift.ArrayExpression): boolean {
184-
return array.elements.every(e => !e || e.type.startsWith('Literal'));
272+
/**
273+
* Returns whether a node is a resolvable expression that can be
274+
* evaluated safely.
275+
* @param node The AST node.
276+
* @returns Whether.
277+
*/
278+
private isResolvableExpression(node: Shift.Node): node is ResolvableExpression {
279+
return (
280+
this.isLiteral(node) ||
281+
(node.type == 'UnaryExpression' && node.operator == '-' && node.operand.type == 'LiteralNumericExpression') ||
282+
(node.type == 'IdentifierExpression' && node.name == 'undefined') ||
283+
(node.type == 'ArrayExpression' && node.elements.length == 0) ||
284+
(node.type == 'ObjectExpression' && node.properties.length == 0)
285+
);
286+
}
287+
288+
/**
289+
* Returns whether a node is a literal.
290+
* @param node The AST node.
291+
* @returns Whether.
292+
*/
293+
private isLiteral(node: Shift.Node): node is Literal {
294+
return node.type == 'LiteralNumericExpression' || node.type == 'LiteralStringExpression' || node.type == 'LiteralBooleanExpression' || node.type == 'LiteralNullExpression';
185295
}
186296
}
297+
298+
type Literal = Shift.LiteralNumericExpression | Shift.LiteralStringExpression | Shift.LiteralBooleanExpression | Shift.LiteralNullExpression;
299+
type ResolvableExpression =
300+
| Literal
301+
| (Shift.UnaryExpression & { operator: '-'; argument: Literal })
302+
| (Shift.IdentifierExpression & { name: 'undefined' })
303+
| (Shift.ArrayExpression & { elements: [] })
304+
| (Shift.ObjectExpression & { properties: [] });
305+
306+
type ResolvableUnaryOperator = '-' | '+' | '!' | '~' | 'typeof' | 'void';
307+
308+
type ResolvableBinaryOperator =
309+
| '=='
310+
| '!='
311+
| '==='
312+
| '!=='
313+
| '<'
314+
| '<='
315+
| '>'
316+
| '>='
317+
| '<<'
318+
| '>>'
319+
| '>>>'
320+
| '+'
321+
| '-'
322+
| '*'
323+
| '/'
324+
| '%'
325+
| '**'
326+
| '|'
327+
| '^'
328+
| '&';

0 commit comments

Comments
 (0)