Merge pull request #196 from javascript-obfuscator/dead-code-injection-improvements

Dead code injection improvements
This commit is contained in:
Timofey Kachalov
2018-02-17 15:46:13 +03:00
committed by GitHub
9 changed files with 234 additions and 25 deletions
+1
View File
@@ -4,3 +4,4 @@
coverage
images
test/fixtures/compile-performance.js
test-tmp
+4
View File
@@ -1,5 +1,9 @@
Change Log
===
v0.14.3
---
* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/195
v0.14.2
---
* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/181
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "0.14.2",
"version": "0.14.3",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
@@ -6,6 +6,7 @@ import * as ESTree from 'estree';
import { TDeadNodeInjectionCustomNodeFactory } from '../../types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory';
import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
import { TNodeWithScope } from '../../types/node/TNodeWithScope';
import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
import { IOptions } from '../../interfaces/options/IOptions';
@@ -97,15 +98,62 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
}
/**
* @param {Node} blockStatementNode
* @param {Node} targetNode
* @returns {boolean}
*/
private static isValidBlockStatementNode (blockStatementNode: ESTree.Node): boolean {
const isProhibitedNode: (node: ESTree.Node) => boolean =
(node: ESTree.Node): boolean => NodeGuards.isBreakStatementNode(node) ||
NodeGuards.isContinueStatementNode(node) ||
NodeGuards.isAwaitExpressionNode(node) ||
NodeGuards.isSuperNode(node);
private static isProhibitedNodeInsideCollectedBlockStatement (targetNode: ESTree.Node): boolean {
return NodeGuards.isBreakStatementNode(targetNode)
|| NodeGuards.isContinueStatementNode(targetNode)
|| NodeGuards.isAwaitExpressionNode(targetNode)
|| NodeGuards.isSuperNode(targetNode);
}
/**
* @param {Node} targetNode
* @returns {boolean}
*/
private static isScopeHoistingFunctionDeclaration (targetNode: ESTree.Node): boolean {
if (!NodeGuards.isFunctionDeclarationNode(targetNode)) {
return false;
}
const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(targetNode);
const scopeBody: ESTree.Statement[] = !NodeGuards.isSwitchCaseNode(scopeNode)
? <ESTree.Statement[]>scopeNode.body
: scopeNode.consequent;
const indexInScope: number = scopeBody.indexOf(targetNode);
if (indexInScope === 0) {
return false;
}
const slicedBody: ESTree.Statement[] = scopeBody.slice(0, indexInScope);
const hostBlockStatementNode: ESTree.BlockStatement = Nodes.getBlockStatementNode(slicedBody);
const functionDeclarationName: string = targetNode.id.name;
let isScopeHoistedFunctionDeclaration: boolean = false;
estraverse.traverse(hostBlockStatementNode, {
enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
if (NodeGuards.isIdentifierNode(node) && node.name === functionDeclarationName) {
isScopeHoistedFunctionDeclaration = true;
return estraverse.VisitorOption.Break;
}
}
});
return isScopeHoistedFunctionDeclaration;
}
/**
* @param {BlockStatement} blockStatementNode
* @returns {boolean}
*/
private static isValidCollectedBlockStatementNode (blockStatementNode: ESTree.BlockStatement): boolean {
if (!blockStatementNode.body.length) {
return false;
}
let nestedBlockStatementsCount: number = 0;
let isValidBlockStatementNode: boolean = true;
@@ -117,8 +165,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
}
if (
nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
isProhibitedNode(node)
nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount
|| DeadCodeInjectionTransformer.isProhibitedNodeInsideCollectedBlockStatement(node)
|| DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)
) {
isValidBlockStatementNode = false;
@@ -183,7 +232,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
let clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(node);
if (!DeadCodeInjectionTransformer.isValidBlockStatementNode(clonedBlockStatementNode)) {
if (!DeadCodeInjectionTransformer.isValidCollectedBlockStatementNode(clonedBlockStatementNode)) {
return;
}
@@ -209,16 +258,21 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
* @param {NodeGuards} parentNode
* @returns {NodeGuards | VisitorOption}
*/
public transformNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node | estraverse.VisitorOption {
if (this.collectedBlockStatementsTotalLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount) {
public transformNode (
blockStatementNode: ESTree.BlockStatement,
parentNode: ESTree.Node
): ESTree.Node | estraverse.VisitorOption {
const canBreakTraverse: boolean = !this.collectedBlockStatements.length
|| this.collectedBlockStatementsTotalLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount;
if (canBreakTraverse) {
return estraverse.VisitorOption.Break;
}
if (!this.collectedBlockStatements.length) {
return estraverse.VisitorOption.Break;
}
const isInvalidBlockStatementNode: boolean = !blockStatementNode.body.length
|| this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold;
if (this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold) {
if (isInvalidBlockStatementNode) {
return blockStatementNode;
}
@@ -233,8 +287,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
const maxInteger: number = this.collectedBlockStatements.length - 1;
const randomIndex: number = this.randomGenerator.getRandomInteger(minInteger, maxInteger);
const randomBlockStatementNode: ESTree.BlockStatement = this.collectedBlockStatements.splice(randomIndex, 1)[0];
const isDuplicateBlockStatementNodes: boolean = randomBlockStatementNode === blockStatementNode;
if (randomBlockStatementNode === blockStatementNode) {
if (isDuplicateBlockStatementNodes) {
return blockStatementNode;
}
+24 -6
View File
@@ -6,18 +6,36 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
`
var bar = 1;
var baz = 2;
(function(){
var bark = bar + baz;
function foo () {
function inner1 () {}
inner1();
}
function bar () {
function inner2 () {}
inner2();
}
function baz () {
function inner3 () {}
inner3();
}
function bark () {
function inner4 () {}
inner4();
}
function hawk () {
function inner5 () {}
inner5();
}
})();
`,
{
...NO_ADDITIONAL_NODES_PRESET,
compact: false,
identifiersPrefix: 'foo',
identifierNamesGenerator: 'mangled',
renameGlobals: true
stringArray: true,
stringArrayThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1
}
).getObfuscatedCode();
@@ -481,5 +481,77 @@ describe('DeadCodeInjectionTransformer', () => {
assert.notEqual(returnIdentifierName, variableDeclarationIdentifierName);
});
});
describe('variant #11 - block statements with empty body', () => {
const regExp: RegExp = new RegExp(
`function *${variableMatch} *\\(\\) *{ *} *` +
`${variableMatch} *\\(\\); *`,
'g'
);
const expectedMatchesLength: number = 5;
let matchesLength: number = 0;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/block-statement-empty-body.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
stringArray: true,
stringArrayThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1
}
);
const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
if (functionMatches) {
matchesLength = functionMatches.length;
}
});
it('shouldn\'t add dead code conditions to the block empty block statements', () => {
assert.isAtLeast(matchesLength, expectedMatchesLength);
});
});
describe('variant #12 - block statement with scope-hoisting', () => {
const regExp: RegExp = new RegExp(
`${variableMatch} *\\(\\); *` +
`var *${variableMatch} *= *0x2; *` +
`function *${variableMatch} *\\(\\) *{ *} *`,
'g'
);
const expectedMatchesLength: number = 5;
let matchesLength: number = 0;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
stringArray: true,
stringArrayThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1
}
);
const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
if (functionMatches) {
matchesLength = functionMatches.length;
} });
it('shouldn\'t collect block statements with scope-hoisting', () => {
assert.equal(matchesLength, expectedMatchesLength);
});
});
});
});
@@ -0,0 +1,22 @@
(function(){
function foo () {
function inner1 () {}
inner1();
}
function bar () {
function inner2 () {}
inner2();
}
function baz () {
function inner3 () {}
inner3();
}
function bark () {
function inner4 () {}
inner4();
}
function hawk () {
function inner5 () {}
inner5();
}
})();
@@ -0,0 +1,37 @@
(function(){
function foo () {
var a = 1;
inner1();
var b = 2;
function inner1 () {}
var c = 3;
}
function bar () {
var a = 1;
inner2();
var b = 2;
function inner2 () {}
var c = 3;
}
function baz () {
var a = 1;
inner3();
var b = 2;
function inner3 () {}
var c = 3;
}
function bark () {
var a = 1;
inner4();
var b = 2;
function inner4 () {}
var c = 3;
}
function hawk () {
var a = 1;
inner5();
var b = 2;
function inner5 () {}
var c = 3;
}
})();