Skip to content

Commit 0b8575a

Browse files
committed
Improved DuplicateLiteralsRemoval
1 parent 51ffe59 commit 0b8575a

File tree

1 file changed

+130
-129
lines changed

1 file changed

+130
-129
lines changed

src/transforms/extraction/duplicateLiteralsRemoval.ts

Lines changed: 130 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,17 @@ import {
1313
BinaryExpression,
1414
FunctionDeclaration,
1515
ThisExpression,
16-
FunctionExpression,
16+
ConditionalExpression,
1717
} from "../../util/gen";
18-
import {
19-
append,
20-
clone,
21-
getLexContext,
22-
isLexContext,
23-
prepend,
24-
} from "../../util/insert";
18+
import { append, clone, prepend } from "../../util/insert";
2519
import { isDirective, isPrimitive } from "../../util/compare";
2620

2721
import { ObfuscateOrder } from "../../order";
2822
import { isModuleSource } from "../string/stringConcealing";
2923
import { ComputeProbabilityMap } from "../../probability";
3024
import { ok } from "assert";
3125
import { chance, choice, getRandomInteger } from "../../util/random";
26+
import { getBlock } from "../../traverse";
3227

3328
/**
3429
* [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a variable name.
@@ -49,35 +44,40 @@ import { chance, choice, getRandomInteger } from "../../util/random";
4944
* ```
5045
*/
5146
export default class DuplicateLiteralsRemoval extends Transform {
47+
// The array holding all the duplicate literals
5248
arrayName: string;
49+
// The array expression node to be inserted into the program
5350
arrayExpression: Node;
51+
52+
/**
53+
* Literals in the array
54+
*/
5455
map: Map<string, number>;
55-
first: Map<string, Location | null>;
5656

5757
/**
58-
* getter fn name -> accumulative shift
58+
* Literals are saved here the first time they are seen.
5959
*/
60-
fnShifts: Map<string, number>;
60+
first: Map<string, Location>;
6161

6262
/**
63-
* lex context -> getter fn name
63+
* Block -> { functionName, indexShift }
6464
*/
65-
fnGetters: Map<Node, string>;
65+
functions: Map<Node, { functionName: string; indexShift: number }>;
6666

6767
constructor(o) {
6868
super(o, ObfuscateOrder.DuplicateLiteralsRemoval);
6969

7070
this.map = new Map();
7171
this.first = new Map();
7272

73-
this.fnShifts = new Map();
74-
this.fnGetters = new Map();
73+
this.functions = new Map();
7574
}
7675

7776
apply(tree) {
7877
super.apply(tree);
7978

80-
if (this.arrayName && this.arrayExpression.elements.length) {
79+
if (this.arrayName && this.arrayExpression.elements.length > 0) {
80+
// This function simply returns the array
8181
var getArrayFn = this.getPlaceholder();
8282
append(
8383
tree,
@@ -88,22 +88,79 @@ export default class DuplicateLiteralsRemoval extends Transform {
8888
)
8989
);
9090

91+
// This variable holds the array
9192
prepend(
9293
tree,
9394
VariableDeclaration(
9495
VariableDeclarator(
9596
this.arrayName,
9697
CallExpression(
97-
MemberExpression(
98-
Identifier(getArrayFn),
99-
Identifier("call"),
100-
false
101-
),
98+
MemberExpression(Identifier(getArrayFn), Literal("call"), true),
10299
[ThisExpression()]
103100
)
104101
)
105102
)
106103
);
104+
105+
// Create all the functions needed
106+
for (var blockNode of this.functions.keys()) {
107+
var { functionName, indexShift } = this.functions.get(blockNode);
108+
109+
var propertyNode: Node = BinaryExpression(
110+
"-",
111+
Identifier("index_param"),
112+
Literal(indexShift)
113+
);
114+
115+
var indexRangeInclusive = [
116+
0 + indexShift - 1,
117+
this.map.size + indexShift,
118+
];
119+
120+
// The function uses mangling to hide the index being accessed
121+
var mangleCount = getRandomInteger(1, 10);
122+
for (var i = 0; i < mangleCount; i++) {
123+
var operator = choice([">", "<"]);
124+
var compareValue = choice(indexRangeInclusive);
125+
126+
var test = BinaryExpression(
127+
operator,
128+
Identifier("index_param"),
129+
Literal(compareValue)
130+
);
131+
132+
var alternate = BinaryExpression(
133+
"-",
134+
Identifier("index_param"),
135+
Literal(getRandomInteger(-100, 100))
136+
);
137+
138+
var testValue =
139+
(operator === ">" && compareValue === indexRangeInclusive[0]) ||
140+
(operator === "<" && compareValue === indexRangeInclusive[1]);
141+
142+
propertyNode = ConditionalExpression(
143+
test,
144+
testValue ? propertyNode : alternate,
145+
!testValue ? propertyNode : alternate
146+
);
147+
}
148+
149+
var returnArgument = MemberExpression(
150+
Identifier(this.arrayName),
151+
propertyNode,
152+
true
153+
);
154+
155+
prepend(
156+
blockNode,
157+
FunctionDeclaration(
158+
functionName,
159+
[Identifier("index_param")],
160+
[ReturnStatement(returnArgument)]
161+
)
162+
);
163+
}
107164
}
108165
}
109166

@@ -122,104 +179,44 @@ export default class DuplicateLiteralsRemoval extends Transform {
122179
* @param parents
123180
* @param index
124181
*/
125-
toCaller(object: Node, parents: Node[], index: number) {
126-
// get all the getters defined here or higher
127-
var getterNames = [object, ...parents]
128-
.map((x) => this.fnGetters.get(x))
129-
.filter((x) => x);
130-
131-
// use random getter function
132-
var getterName = choice(getterNames);
133-
134-
// get this literals context
135-
var lexContext = getLexContext(object, parents);
136-
137-
var hasGetterHere = this.fnGetters.has(lexContext);
138-
139-
// create one if none are available (or by random chance if none are here locally)
140-
var shouldCreateNew =
141-
!getterName || (!hasGetterHere && Math.random() > 0.9);
142-
143-
if (shouldCreateNew) {
144-
ok(!this.fnGetters.has(lexContext));
145-
146-
var lexContextIndex = parents.findIndex(
147-
(x) => x !== lexContext && isLexContext(x)
148-
);
149-
var basedOn =
150-
lexContextIndex !== -1
151-
? choice(
152-
parents
153-
.slice(lexContextIndex + 1)
154-
.map((x) => this.fnGetters.get(x))
155-
.filter((x) => x)
156-
)
157-
: null;
158-
159-
var body = [];
160-
var thisShift = getRandomInteger(-250, 250);
161-
// the name of the getter
162-
getterName = this.getPlaceholder() + "_dLR_" + this.fnGetters.size;
163-
164-
if (basedOn) {
165-
var shift = this.fnShifts.get(basedOn);
166-
ok(typeof shift === "number");
167-
168-
body = [
169-
ReturnStatement(
170-
CallExpression(Identifier(basedOn), [
171-
BinaryExpression("+", Identifier("index"), Literal(thisShift)),
172-
])
173-
),
174-
];
175-
176-
this.fnShifts.set(getterName, shift + thisShift);
177-
} else {
178-
// from scratch
179-
180-
body = [
181-
ReturnStatement(
182-
MemberExpression(
183-
Identifier(this.arrayName),
184-
BinaryExpression("+", Identifier("index"), Literal(thisShift)),
185-
true
186-
)
187-
),
188-
];
182+
transformLiteral(object: Node, parents: Node[], index: number) {
183+
var blockNode = choice(parents.filter((x) => this.functions.has(x)));
184+
185+
// Create initial function if none exist
186+
if (this.functions.size === 0) {
187+
var root = parents[parents.length - 1];
188+
var rootFunctionName = this.getPlaceholder() + "_dLR_0";
189+
this.functions.set(root, {
190+
functionName: rootFunctionName,
191+
indexShift: getRandomInteger(-100, 100),
192+
});
193+
194+
blockNode = root;
195+
}
189196

190-
this.fnShifts.set(getterName, thisShift);
191-
}
197+
// If no function here exist, possibly create new chained function
198+
var block = getBlock(object, parents);
199+
if (!this.functions.has(block) && chance(50 - this.functions.size)) {
200+
var newFunctionName =
201+
this.getPlaceholder() + "_dLR_" + this.functions.size;
192202

193-
this.fnGetters.set(lexContext, getterName);
203+
this.functions.set(block, {
204+
functionName: newFunctionName,
205+
indexShift: getRandomInteger(-100, 100),
206+
});
194207

195-
prepend(
196-
lexContext,
197-
VariableDeclaration(
198-
VariableDeclarator(
199-
getterName,
200-
CallExpression(
201-
FunctionExpression(
202-
[],
203-
[
204-
ReturnStatement(
205-
FunctionExpression([Identifier("index")], body)
206-
),
207-
]
208-
),
209-
[]
210-
)
211-
)
212-
)
213-
);
208+
blockNode = block;
214209
}
215210

216-
var theShift = this.fnShifts.get(getterName);
211+
// Derive the function to call from the selected blockNode
212+
var { functionName, indexShift } = this.functions.get(blockNode);
217213

218-
this.replaceIdentifierOrLiteral(
219-
object,
220-
CallExpression(Identifier(getterName), [Literal(index - theShift)]),
221-
parents
222-
);
214+
// Call the function given it's indexShift
215+
var callExpression = CallExpression(Identifier(functionName), [
216+
Literal(index + indexShift),
217+
]);
218+
219+
this.replaceIdentifierOrLiteral(object, callExpression, parents);
223220
}
224221

225222
transform(object: Node, parents: Node[]) {
@@ -234,7 +231,7 @@ export default class DuplicateLiteralsRemoval extends Transform {
234231
}
235232

236233
// HARD CODED LIMIT of 10,000 (after 1,000 elements)
237-
if (this.map.size > 1000 && !chance(this.map.size / 100)) return;
234+
if (this.map.size > 1000 && chance(this.map.size / 100)) return;
238235

239236
if (
240237
this.arrayName &&
@@ -244,54 +241,58 @@ export default class DuplicateLiteralsRemoval extends Transform {
244241
return;
245242
}
246243

247-
var value;
244+
var stringValue;
248245
if (object.type == "Literal") {
249-
value = typeof object.value + ":" + object.value;
246+
stringValue = typeof object.value + ":" + object.value;
250247
if (object.value === null) {
251-
value = "null:null";
248+
stringValue = "null:null";
252249
} else {
253250
// Skip empty strings
254251
if (typeof object.value === "string" && !object.value) {
255252
return;
256253
}
257254
}
258255
} else if (object.type == "Identifier") {
259-
value = "identifier:" + object.name;
256+
stringValue = "identifier:" + object.name;
260257
} else {
261258
throw new Error("Unsupported primitive type: " + object.type);
262259
}
263260

264-
ok(value);
261+
ok(stringValue);
265262

266-
if (!this.first.has(value) && !this.map.has(value)) {
267-
this.first.set(value, [object, parents]);
268-
} else {
263+
if (this.map.has(stringValue) || this.first.has(stringValue)) {
264+
// Create the array if not already made
269265
if (!this.arrayName) {
270266
this.arrayName = this.getPlaceholder();
271267
this.arrayExpression = ArrayExpression([]);
272268
}
273269

274-
var firstLocation = this.first.get(value);
270+
// Delete with first location
271+
var firstLocation = this.first.get(stringValue);
275272
if (firstLocation) {
276-
this.first.set(value, null);
277273
var index = this.map.size;
278274

279-
ok(!this.map.has(value));
280-
this.map.set(value, index);
275+
ok(!this.map.has(stringValue));
276+
this.map.set(stringValue, index);
277+
this.first.delete(stringValue);
281278

282279
var pushing = clone(object);
283280
this.arrayExpression.elements.push(pushing);
284281

285282
ok(this.arrayExpression.elements[index] === pushing);
286283

287-
this.toCaller(firstLocation[0], firstLocation[1], index);
284+
this.transformLiteral(firstLocation[0], firstLocation[1], index);
288285
}
289286

290-
var index = this.map.get(value);
287+
var index = this.map.get(stringValue);
291288
ok(typeof index === "number");
292289

293-
this.toCaller(object, parents, index);
290+
this.transformLiteral(object, parents, index);
291+
return;
294292
}
293+
294+
// Save this, maybe a duplicate will be found.
295+
this.first.set(stringValue, [object, parents]);
295296
};
296297
}
297298
}

0 commit comments

Comments
 (0)