Merge pull request #241 from javascript-obfuscator/object-rest

Correct obfuscation of object rest and spread properties
This commit is contained in:
Timofey Kachalov
2018-04-16 21:44:30 +03:00
committed by GitHub
21 changed files with 301 additions and 54 deletions
+4
View File
@@ -1,5 +1,9 @@
Change Log
===
v0.16.0
---
* Correct obfuscation of object rest and spread properties
v0.15.0
---
* **Internal change:** switched AST parser from `esprima` on `espree`
+1 -1
View File
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "0.15.0",
"version": "0.16.0",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
@@ -25,7 +25,7 @@
"class-validator": "0.8.5",
"commander": "2.15.1",
"escodegen-wallaby": "1.6.18",
"espree": "3.5.4",
"espree": "4.0.0-alpha.0",
"estraverse": "4.2.0",
"inversify": "4.11.1",
"js-string-escape": "1.0.1",
@@ -53,17 +53,17 @@
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.0.0",
"@types/multimatch": "2.1.2",
"@types/node": "9.6.2",
"@types/node": "9.6.5",
"@types/rimraf": "2.0.2",
"@types/sinon": "4.3.1",
"@types/string-template": "1.0.2",
"@types/webpack-env": "1.13.5",
"awesome-typescript-loader": "5.0.0-0",
"@types/webpack-env": "1.13.6",
"awesome-typescript-loader": "5.0.0",
"babel-loader": "8.0.0-beta.2",
"chai": "4.1.2",
"coveralls": "3.0.0",
"istanbul": "1.1.0-alpha.1",
"mocha": "5.0.5",
"mocha": "5.1.0",
"pre-commit": "1.2.2",
"rimraf": "2.6.2",
"sinon": "4.5.0",
+1 -1
View File
@@ -33,7 +33,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
ecmaFeatures: {
experimentalObjectRestSpread: true
},
ecmaVersion: 8,
ecmaVersion: 9,
loc: true,
range: true
};
@@ -13,6 +13,26 @@ import { NodeUtils } from '../../../node/NodeUtils';
@injectable()
export class ObjectExpressionCalleeDataExtractor extends AbstractCalleeDataExtractor {
/**
* @param {Property} propertyNode
* @param {string | number} nextItemInCallsChain
* @returns {boolean}
*/
private static isValidTargetPropertyNode (propertyNode: ESTree.Property, nextItemInCallsChain: string | number): boolean {
if (!propertyNode.key) {
return false;
}
const isTargetPropertyNodeWithIdentifierKey: boolean =
NodeGuards.isIdentifierNode(propertyNode.key) && propertyNode.key.name === nextItemInCallsChain;
const isTargetPropertyNodeWithLiteralKey: boolean =
NodeGuards.isLiteralNode(propertyNode.key) &&
Boolean(propertyNode.key.value) &&
propertyNode.key.value === nextItemInCallsChain;
return isTargetPropertyNodeWithIdentifierKey || isTargetPropertyNodeWithLiteralKey;
}
/**
* @param {NodeGuards[]} blockScopeBody
* @param {MemberExpression} callee
@@ -135,14 +155,7 @@ export class ObjectExpressionCalleeDataExtractor extends AbstractCalleeDataExtra
}
for (const propertyNode of objectExpressionProperties) {
const isTargetPropertyNodeWithIdentifierKey: boolean =
NodeGuards.isIdentifierNode(propertyNode.key) && propertyNode.key.name === nextItemInCallsChain;
const isTargetPropertyNodeWithLiteralKey: boolean =
NodeGuards.isLiteralNode(propertyNode.key) &&
Boolean(propertyNode.key.value) &&
propertyNode.key.value === nextItemInCallsChain;
if (!isTargetPropertyNodeWithIdentifierKey && !isTargetPropertyNodeWithLiteralKey) {
if (!ObjectExpressionCalleeDataExtractor.isValidTargetPropertyNode(propertyNode, nextItemInCallsChain)) {
continue;
}
@@ -68,6 +68,10 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
* @returns {string | null}
*/
private static getPropertyNodeKeyName (propertyNode: ESTree.Property): string | null {
if (!propertyNode.key) {
return null;
}
const propertyKeyNode: ESTree.Expression = propertyNode.key;
if (NodeGuards.isLiteralNode(propertyKeyNode) && typeof propertyKeyNode.value === 'string') {
@@ -189,7 +193,7 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
* Stage 2: creating new expression statement node with member expression based on removed property
*/
const shouldCreateLiteralNode: boolean = !property.computed
|| (property.computed && NodeGuards.isLiteralNode(property.key));
|| (property.computed && !!property.key && NodeGuards.isLiteralNode(property.key));
const memberExpressionProperty: ESTree.Expression = shouldCreateLiteralNode
? NodeFactory.literalNode(propertyKeyName)
: NodeFactory.identifierNode(propertyKeyName);
@@ -61,7 +61,7 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
public transformNode (objectExpressionNode: ESTree.ObjectExpression, parentNode: ESTree.Node): ESTree.Node {
objectExpressionNode.properties
.forEach((property: ESTree.Property) => {
if (property.computed) {
if (property.computed || !property.key) {
return;
}
@@ -98,12 +98,12 @@ export class FunctionTransformer extends AbstractNodeTransformer {
private storeFunctionParams (functionNode: ESTree.Function, nodeIdentifier: number): void {
functionNode.params
.forEach((paramsNode: ESTree.Node) => {
if (NodeGuards.isObjectPatternNode(paramsNode)) {
return estraverse.VisitorOption.Skip;
}
estraverse.traverse(paramsNode, {
enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
if (NodeGuards.isPropertyNode(paramsNode)) {
return estraverse.VisitorOption.Skip;
}
if (NodeGuards.isAssignmentPatternNode(node) && NodeGuards.isIdentifierNode(node.left)) {
this.identifierObfuscatingReplacer.storeLocalName(node.left.name, nodeIdentifier);
@@ -127,7 +127,7 @@ export class FunctionTransformer extends AbstractNodeTransformer {
ignoredIdentifierNamesSet: Set<string>
): void {
properties.forEach((property: ESTree.Property) => {
if (!NodeGuards.isIdentifierNode(property.key)) {
if (!property.key || !NodeGuards.isIdentifierNode(property.key)) {
return;
}
@@ -226,12 +226,12 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
): void {
variableDeclarationNode.declarations
.forEach((declarationNode: ESTree.VariableDeclarator) => {
if (NodeGuards.isObjectPatternNode(declarationNode.id)) {
return estraverse.VisitorOption.Skip;
}
estraverse.traverse(declarationNode.id, {
enter: (node: ESTree.Node) => {
if (NodeGuards.isPropertyNode(node)) {
return estraverse.VisitorOption.Skip;
}
if (NodeGuards.isIdentifierNode(node)) {
callback(node);
}
+1
View File
@@ -15,6 +15,7 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
{
...NO_ADDITIONAL_NODES_PRESET,
compact: false,
transformObjectKeys: true
}
).getObfuscatedCode();
@@ -74,4 +74,63 @@ describe('ObjectExpressionTransformer', () => {
assert.match(obfuscatedCode, regExp);
});
});
describe('object rest', () => {
const objectRegExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\{'foo': *0x1, *'bar': *0x2, *'baz': *0x3\};/;
const objectRestRegExp: RegExp = /var *\{foo, *\.\.\.*_0x[a-f0-9]{4,6}\} *= *_0x[a-f0-9]{4,6};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/object-rest.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform object name', () => {
assert.match(obfuscatedCode, objectRegExp);
});
it('Match #2: should transform object rest construction', () => {
assert.match(obfuscatedCode, objectRestRegExp);
});
});
describe('object spread', () => {
const object1RegExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\{'foo': *0x1\};/;
const object2RegExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\{'bar': *0x2\};/;
const objectSpreadRegExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\{\.\.\._0x[a-f0-9]{4,6}, *\.\.\._0x[a-f0-9]{4,6}\};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/object-spread.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform object name', () => {
assert.match(obfuscatedCode, object1RegExp);
});
it('Match #2: should transform object name', () => {
assert.match(obfuscatedCode, object2RegExp);
});
it('Match #3: should transform object spread construction', () => {
assert.match(obfuscatedCode, objectSpreadRegExp);
});
});
});
@@ -0,0 +1,8 @@
(function() {
var obj = {
foo: 1,
bar: 2,
baz: 3
};
var {foo, ...rest} = obj;
})();
@@ -0,0 +1,5 @@
(function() {
var obj1 = {foo: 1};
var obj2 = {bar: 2};
var newObj = {...obj1, ...obj2};
})();
@@ -284,4 +284,85 @@ describe('FunctionTransformer', () => {
assert.equal(arrayPatternIdentifierName2, functionBodyIdentifierName2);
});
});
describe('rest parameters', () => {
const functionRegExp: RegExp = /function *func *\(_0x[a-f0-9]{4,6}, *..._0x[a-f0-9]{4,6}\) *\{/;
const returnRegExp: RegExp = /return *_0x[a-f0-9]{4,6} *\+ *_0x[a-f0-9]{4,6};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/rest-parameter.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform function rest parameter', () => {
assert.match(obfuscatedCode, functionRegExp);
});
it('Match #2: should transform identifiers inside function body', () => {
assert.match(obfuscatedCode, returnRegExp);
});
});
describe('array rest parameter', () => {
const functionRegExp: RegExp = /function *func *\(\[_0x[a-f0-9]{4,6}, *..._0x[a-f0-9]{4,6}\]\) *\{/;
const returnRegExp: RegExp = /return *_0x[a-f0-9]{4,6} *\+ *_0x[a-f0-9]{4,6};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/array-rest-parameter.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform function rest parameter', () => {
assert.match(obfuscatedCode, functionRegExp);
});
it('Match #2: should transform identifiers inside function body', () => {
assert.match(obfuscatedCode, returnRegExp);
});
});
describe('object rest parameter', () => {
const functionRegExp: RegExp = /function *func *\(\{foo, *..._0x[a-f0-9]{4,6}\}\) *\{/;
const returnRegExp: RegExp = /return *foo *\+ *_0x[a-f0-9]{4,6};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/object-rest-parameter.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform function rest parameter', () => {
assert.match(obfuscatedCode, functionRegExp);
});
it('Match #2: should transform identifiers inside function body', () => {
assert.match(obfuscatedCode, returnRegExp);
});
});
});
@@ -0,0 +1,3 @@
function func ([foo, ...rest]) {
return foo + rest;
}
@@ -0,0 +1,3 @@
function func ({foo, ...rest}) {
return foo + rest;
}
@@ -0,0 +1,3 @@
function func (foo, ...rest) {
return foo + rest;
}
@@ -571,4 +571,58 @@ describe('VariableDeclarationTransformer', () => {
assert.match(obfuscatedCode, defaultExportRegExp);
});
});
describe('Variant #16: array rest', () => {
const objectRegExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\['foo', *'bar', *'baz'\];/;
const objectRestRegExp: RegExp = /var *\[_0x[a-f0-9]{4,6}, *\.\.\.*_0x[a-f0-9]{4,6}] *= *_0x[a-f0-9]{4,6};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/array-rest.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform object name', () => {
assert.match(obfuscatedCode, objectRegExp);
});
it('Match #2: should transform object rest construction', () => {
assert.match(obfuscatedCode, objectRestRegExp);
});
});
describe('Variant #17: object rest', () => {
const objectRegExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\{'foo': *0x1, *'bar': *0x2, *'baz': *0x3\};/;
const objectRestRegExp: RegExp = /var *\{foo, *\.\.\.*_0x[a-f0-9]{4,6}\} *= *_0x[a-f0-9]{4,6};/;
let obfuscatedCode: string;
before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/object-rest.js');
const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET
}
);
obfuscatedCode = obfuscationResult.getObfuscatedCode();
});
it('Match #1: should transform object name', () => {
assert.match(obfuscatedCode, objectRegExp);
});
it('Match #2: should transform object rest construction', () => {
assert.match(obfuscatedCode, objectRestRegExp);
});
});
});
@@ -0,0 +1,4 @@
(function() {
var array = ['foo', 'bar', 'baz'];
var [foo, ...rest] = array;
})();
@@ -0,0 +1,8 @@
(function() {
var obj = {
foo: 1,
bar: 2,
baz: 3
};
var {foo, ...rest} = obj;
})();
+24 -27
View File
@@ -599,9 +599,9 @@
version "9.6.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.0.tgz#d3480ee666df9784b1001a1872a2f6ccefb6c2d7"
"@types/node@9.6.2":
version "9.6.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.2.tgz#e49ac1adb458835e95ca6487bc20f916b37aff23"
"@types/node@9.6.5":
version "9.6.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.5.tgz#ee700810fdf49ac1c399fc5980b7559b3e5a381d"
"@types/rimraf@2.0.2":
version "2.0.2"
@@ -618,9 +618,9 @@
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/string-template/-/string-template-1.0.2.tgz#363b273c9b456705e3111e3571e9248f6474eba4"
"@types/webpack-env@1.13.5":
version "1.13.5"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.5.tgz#ca854e9fbdbcdf45d7376882875f28e2c60593f8"
"@types/webpack-env@1.13.6":
version "1.13.6"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.6.tgz#128d1685a7c34d31ed17010fc87d6a12c1de6976"
abbrev@1:
version "1.1.1"
@@ -636,17 +636,13 @@ acorn-dynamic-import@^3.0.0:
dependencies:
acorn "^5.0.0"
acorn-jsx@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
acorn-jsx@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e"
dependencies:
acorn "^3.0.4"
acorn "^5.0.3"
acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
acorn@^5.0.0, acorn@^5.5.0:
acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.1:
version "5.5.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
@@ -869,9 +865,9 @@ atob@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
awesome-typescript-loader@5.0.0-0:
version "5.0.0-0"
resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-5.0.0-0.tgz#1330750fe603d54a5f0e25429185baed0e48f9f2"
awesome-typescript-loader@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-5.0.0.tgz#130c304ae52a60933f15d93f7629003b483fa8b1"
dependencies:
chalk "^2.3.1"
enhanced-resolve "^4.0.0"
@@ -2429,12 +2425,12 @@ eslint-scope@^3.7.1:
esrecurse "^4.1.0"
estraverse "^4.1.1"
espree@3.5.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
espree@4.0.0-alpha.0:
version "4.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-4.0.0-alpha.0.tgz#c12e621c27cdd74655aaed07011664dba9b8c22d"
dependencies:
acorn "^5.5.0"
acorn-jsx "^3.0.0"
acorn "^5.5.1"
acorn-jsx "^4.1.1"
esprima@^2.7.1:
version "2.7.3"
@@ -4017,7 +4013,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
@@ -4067,9 +4063,9 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
dependencies:
minimist "0.0.8"
mocha@5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.5.tgz#e228e3386b9387a4710007a641f127b00be44b52"
mocha@5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.1.0.tgz#5ff11cc39c0bb65330ac6c41f9086634e3e3f686"
dependencies:
browser-stdout "1.3.1"
commander "2.11.0"
@@ -4079,6 +4075,7 @@ mocha@5.0.5:
glob "7.1.2"
growl "1.10.3"
he "1.1.1"
minimatch "3.0.4"
mkdirp "0.5.1"
supports-color "4.4.0"