js-confuser 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ <!-- # `1.5.0`
2
+ Hexadecimal Numbers
3
+
4
+ - The hexadecimal representation can now be used. [#29](https://github.com/MichaelXF/js-confuser/issues/29)
5
+ - New option `hexadecimalNumbers` to control this behavior.
6
+
7
+ ### `hexadecimalNumbers`
8
+
9
+ Uses the hexadecimal representation (`50` -> `0x32`) for numbers. (`true/false`) -->
10
+
11
+ # `1.4.3`
12
+ Minify Fix
13
+
14
+ - Fixed [#34](https://github.com/MichaelXF/js-confuser/issues/34)
15
+ - - Minify was removing `return` statements too aggressively, fixed in this version.
16
+
17
+ - Slight improvement to Control Flow Flattening
18
+
1
19
  # `1.4.2`
2
20
  Eval Fix
3
21
 
package/README.md CHANGED
@@ -115,6 +115,10 @@ JsConfuser.obfuscate(`<source code>`, {
115
115
 
116
116
  Remove's whitespace from the final output. Enabled by default. (`true/false`)
117
117
 
118
+ <!-- ### `hexadecimalNumbers`
119
+
120
+ Uses the hexadecimal representation (`50` -> `0x32`) for numbers. (`true/false`) -->
121
+
118
122
  ### `minify`
119
123
 
120
124
  Minifies redundant code. (`true/false`)
package/dist/compiler.js CHANGED
@@ -14,7 +14,8 @@ async function compileJs(tree, options) {
14
14
 
15
15
  function compileJsSync(tree, options) {
16
16
  var api = {
17
- format: escodegen.FORMAT_MINIFY
17
+ format: { ...escodegen.FORMAT_MINIFY
18
+ }
18
19
  };
19
20
 
20
21
  if (!options.compact) {
@@ -67,6 +67,8 @@ var _antiTooling = _interopRequireDefault(require("./transforms/antiTooling"));
67
67
 
68
68
  var _hideInitializingCode = _interopRequireDefault(require("./transforms/hideInitializingCode"));
69
69
 
70
+ var _hexadecimalNumbers = _interopRequireDefault(require("./transforms/hexadecimalNumbers"));
71
+
70
72
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
71
73
 
72
74
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -128,6 +130,7 @@ class Obfuscator extends _events.EventEmitter {
128
130
  test(options.stack, _stack.default);
129
131
  test(true, _antiTooling.default);
130
132
  test(options.hideInitializingCode, _hideInitializingCode.default);
133
+ test(options.hexadecimalNumbers, _hexadecimalNumbers.default);
131
134
 
132
135
  if (options.lock && Object.keys(options.lock).filter(x => x == "domainLock" ? options.lock.domainLock && options.lock.domainLock.length : options.lock[x]).length) {
133
136
  test(true, _lock.default);
package/dist/options.js CHANGED
@@ -12,7 +12,7 @@ var _presets = _interopRequireDefault(require("./presets"));
12
12
 
13
13
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
14
 
15
- const validProperties = new Set(["preset", "target", "indent", "compact", "minify", "es5", "renameVariables", "renameGlobals", "identifierGenerator", "nameRecycling", "controlFlowFlattening", "hideInitializingCode", "globalConcealing", "stringCompression", "stringConcealing", "stringEncoding", "stringSplitting", "duplicateLiteralsRemoval", "dispatcher", "eval", "rgf", "objectExtraction", "flatten", "deadCode", "calculator", "lock", "movedDeclarations", "opaquePredicates", "shuffle", "stack", "verbose", "globalVariables", "debugComments"]);
15
+ const validProperties = new Set(["preset", "target", "indent", "compact", "hexadecimalNumbers", "minify", "es5", "renameVariables", "renameGlobals", "identifierGenerator", "nameRecycling", "controlFlowFlattening", "hideInitializingCode", "globalConcealing", "stringCompression", "stringConcealing", "stringEncoding", "stringSplitting", "duplicateLiteralsRemoval", "dispatcher", "eval", "rgf", "objectExtraction", "flatten", "deadCode", "calculator", "lock", "movedDeclarations", "opaquePredicates", "shuffle", "stack", "verbose", "globalVariables", "debugComments"]);
16
16
  const validOses = new Set(["windows", "linux", "osx", "ios", "android"]);
17
17
  const validBrowsers = new Set(["firefox", "chrome", "iexplorer", "edge", "safari", "opera"]);
18
18
 
package/dist/order.js CHANGED
@@ -39,4 +39,5 @@ exports.ObfuscateOrder = ObfuscateOrder;
39
39
  ObfuscateOrder[ObfuscateOrder["ES5"] = 31] = "ES5";
40
40
  ObfuscateOrder[ObfuscateOrder["StringEncoding"] = 32] = "StringEncoding";
41
41
  ObfuscateOrder[ObfuscateOrder["AntiTooling"] = 34] = "AntiTooling";
42
+ ObfuscateOrder[ObfuscateOrder["HexadecimalNumbers"] = 35] = "HexadecimalNumbers";
42
43
  })(ObfuscateOrder || (exports.ObfuscateOrder = ObfuscateOrder = {}));
package/dist/presets.js CHANGED
@@ -33,6 +33,7 @@ const highPreset = {
33
33
  preset: "high",
34
34
  calculator: true,
35
35
  compact: true,
36
+ hexadecimalNumbers: false,
36
37
  controlFlowFlattening: 0.75,
37
38
  deadCode: 0.2,
38
39
  dispatcher: true,
@@ -69,6 +70,7 @@ const mediumPreset = {
69
70
  preset: "medium",
70
71
  calculator: true,
71
72
  compact: true,
73
+ hexadecimalNumbers: false,
72
74
  controlFlowFlattening: 0.5,
73
75
  deadCode: 0.025,
74
76
  dispatcher: 0.75,
@@ -96,6 +98,7 @@ const lowPreset = {
96
98
  preset: "low",
97
99
  calculator: true,
98
100
  compact: true,
101
+ hexadecimalNumbers: false,
99
102
  controlFlowFlattening: 0.25,
100
103
  deadCode: 0.01,
101
104
  dispatcher: 0.5,
@@ -193,6 +193,8 @@ class ControlFlowFlattening extends _transform.default {
193
193
 
194
194
  var resultVar = this.getPlaceholder();
195
195
  var argVar = this.getPlaceholder();
196
+ var testVar = this.getPlaceholder();
197
+ var needsTestVar = false;
196
198
  var needsResultAndArgVar = false;
197
199
  var fnToLabel = Object.create(null);
198
200
  fnNames.forEach(fnName => {
@@ -364,7 +366,10 @@ class ControlFlowFlattening extends _transform.default {
364
366
 
365
367
 
366
368
  finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath);
367
- currentBody.push((0, _gen.IfStatement)(control.test || (0, _gen.Literal)(true), [{
369
+ currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(testVar), control.test || (0, _gen.Literal)(true))));
370
+ needsTestVar = true;
371
+ finishCurrentChunk();
372
+ currentBody.push((0, _gen.IfStatement)((0, _gen.Identifier)(testVar), [{
368
373
  type: "GotoStatement",
369
374
  label: bodyPath
370
375
  }])); // create new label called `bodyPath` and have test body point to afterPath (goto afterPath)
@@ -388,13 +393,16 @@ class ControlFlowFlattening extends _transform.default {
388
393
  }
389
394
 
390
395
  if (stmt.type == "IfStatement" && stmt.consequent.type == "BlockStatement" && (!stmt.alternate || stmt.alternate.type == "BlockStatement")) {
396
+ finishCurrentChunk();
397
+ currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(testVar), stmt.test)));
398
+ needsTestVar = true;
391
399
  finishCurrentChunk();
392
400
  var hasAlternate = !!stmt.alternate;
393
401
  (0, _assert.ok)(!(hasAlternate && stmt.alternate.type !== "BlockStatement"));
394
402
  var yesPath = this.getPlaceholder();
395
403
  var noPath = this.getPlaceholder();
396
404
  var afterPath = this.getPlaceholder();
397
- currentBody.push((0, _gen.IfStatement)(stmt.test, [{
405
+ currentBody.push((0, _gen.IfStatement)((0, _gen.Identifier)(testVar), [{
398
406
  type: "GotoStatement",
399
407
  label: yesPath
400
408
  }]));
@@ -658,6 +666,10 @@ class ControlFlowFlattening extends _transform.default {
658
666
  }));
659
667
  var declarations = [];
660
668
 
669
+ if (needsTestVar) {
670
+ declarations.push((0, _gen.VariableDeclarator)(testVar));
671
+ }
672
+
661
673
  if (needsResultAndArgVar) {
662
674
  declarations.push((0, _gen.VariableDeclarator)(resultVar));
663
675
  declarations.push((0, _gen.VariableDeclarator)(argVar));
@@ -25,7 +25,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
25
25
 
26
26
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
27
27
 
28
- const templates = [(0, _template.default)("\n function curCSS( elem, name, computed ) {\n var ret;\n \n computed = computed || getStyles( elem );\n \n if ( computed ) {\n ret = computed.getPropertyValue( name ) || computed[ name ];\n \n if ( ret === \"\" && !isAttached( elem ) ) {\n ret = redacted.style( elem, name );\n }\n }\n \n return ret !== undefined ?\n \n // Support: IE <=9 - 11+\n // IE returns zIndex value as an integer.\n ret + \"\" :\n ret;\n }"), (0, _template.default)("\n function Example() {\n var state = redacted.useState(false);\n return x(\n ErrorBoundary,\n null,\n x(\n DisplayName,\n null,\n )\n );\n }"), (0, _template.default)("\n const path = require('path');\nconst { version } = require('../../package');\nconst { version: dashboardPluginVersion } = require('@redacted/enterprise-plugin/package');\nconst { version: componentsVersion } = require('@redacted/components/package');\nconst { sdkVersion } = require('@redacted/enterprise-plugin');\nconst isStandaloneExecutable = require('../utils/isStandaloneExecutable');\nconst resolveLocalRedactedPath = require('./resolve-local-redacted-path');\n\nconst redactedPath = path.resolve(__dirname, '../redacted.js');"), (0, _template.default)("\nmodule.exports = async (resolveLocalRedactedPath = ()=>{throw new Error(\"No redacted path provided\")}) => {\n const cliParams = new Set(process.argv.slice(2));\n if (!cliParams.has('--version')) {\n if (cliParams.size !== 1) return false;\n if (!cliParams.has('-v')) return false;\n }\n\n const installationModePostfix = await (async (isStandaloneExecutable, redactedPath) => {\n if (isStandaloneExecutable) return ' (standalone)';\n if (redactedPath === (await resolveLocalRedactedPath())) return ' (local)';\n return '';\n })();\n\n return true;\n};"), (0, _template.default)("\nfunction setCookie(cname, cvalue, exdays) {\n var d = new Date();\n d.setTime(d.getTime() + (exdays*24*60*60*1000));\n var expires = \"expires=\"+ d.toUTCString();\n document.cookie = cname + \"=\" + cvalue + \";\" + expires + \";path=/\";\n}"), (0, _template.default)("function getCookie(cname) {\n var name = cname + \"=\";\n var decodedCookie = decodeURIComponent(document.cookie);\n var ca = decodedCookie.split(';');\n for(var i = 0; i <ca.length; i++) {\n var c = ca[i];\n while (c.charAt(0) == ' ') {\n c = c.substring(1);\n }\n if (c.indexOf(name) == 0) {\n return c.substring(name.length, c.length);\n }\n }\n return \"\";\n}"), (0, _template.default)("function getLocalStorageValue(key, cb){\n if ( typeof key !== \"string\" ) {\n throw new Error(\"Invalid data key provided (not type string)\")\n }\n if ( !key ) {\n throw new Error(\"Invalid data key provided (empty string)\")\n }\n var value = window.localStorage.getItem(key)\n try {\n value = JSON.parse(value)\n } catch ( e ) {\n cb(new Error(\"Serialization error for data '\" + key + \"': \" + e.message))\n }\n\n cb(null, value)\n }"), (0, _template.default)("\n \n var __ = \"(c=ak(<~F$VU'9f)~><&85dBPL-module/from\";\n var s = \"q:function(){var ad=ad=>b(ad-29);if(!T.r[(typeof ab==ad(123)?\";\n var g = \"return U[c[c[d(-199)]-b(205)]]||V[ae(b(166))];case T.o[c[c[c[d(-199)]+d(-174)]-(c[b(119)]-(c[d(-199)]-163))]+ae(b(146))](0)==b(167)?d(-130):-d(-144)\";\n\n __.match(s + g);\n ")];
28
+ const templates = [(0, _template.default)("\n function curCSS( elem, name, computed ) {\n var ret;\n \n computed = computed || getStyles( elem );\n \n if ( computed ) {\n ret = computed.getPropertyValue( name ) || computed[ name ];\n \n if ( ret === \"\" && !isAttached( elem ) ) {\n ret = redacted.style( elem, name );\n }\n }\n \n return ret !== undefined ?\n \n // Support: IE <=9 - 11+\n // IE returns zIndex value as an integer.\n ret + \"\" :\n ret;\n }"), (0, _template.default)("\n function Example() {\n var state = redacted.useState(false);\n return x(\n ErrorBoundary,\n null,\n x(\n DisplayName,\n null,\n )\n );\n }"), (0, _template.default)("\n const path = require('path');\nconst { version } = require('../../package');\nconst { version: dashboardPluginVersion } = require('@redacted/enterprise-plugin/package');\nconst { version: componentsVersion } = require('@redacted/components/package');\nconst { sdkVersion } = require('@redacted/enterprise-plugin');\nconst isStandaloneExecutable = require('../utils/isStandaloneExecutable');\nconst resolveLocalRedactedPath = require('./resolve-local-redacted-path');\n\nconst redactedPath = path.resolve(__dirname, '../redacted.js');"), (0, _template.default)("\nmodule.exports = async (resolveLocalRedactedPath = ()=>{throw new Error(\"No redacted path provided\")}) => {\n const cliParams = new Set(process.argv.slice(2));\n if (!cliParams.has('--version')) {\n if (cliParams.size !== 1) return false;\n if (!cliParams.has('-v')) return false;\n }\n\n const installationModePostfix = await (async (isStandaloneExecutable, redactedPath) => {\n if (isStandaloneExecutable) return ' (standalone)';\n if (redactedPath === (await resolveLocalRedactedPath())) return ' (local)';\n return '';\n })();\n\n return true;\n};"), (0, _template.default)("\nfunction setCookie(cname, cvalue, exdays) {\n var d = new Date();\n d.setTime(d.getTime() + (exdays*24*60*60*1000));\n var expires = \"expires=\"+ d.toUTCString();\n document.cookie = cname + \"=\" + cvalue + \";\" + expires + \";path=/\";\n}"), (0, _template.default)("function getCookie(cname) {\n var name = cname + \"=\";\n var decodedCookie = decodeURIComponent(document.cookie);\n var ca = decodedCookie.split(';');\n for(var i = 0; i <ca.length; i++) {\n var c = ca[i];\n while (c.charAt(0) == ' ') {\n c = c.substring(1);\n }\n if (c.indexOf(name) == 0) {\n return c.substring(name.length, c.length);\n }\n }\n return \"\";\n}"), (0, _template.default)("function getLocalStorageValue(key, cb){\n if ( typeof key !== \"string\" ) {\n throw new Error(\"Invalid data key provided (not type string)\")\n }\n if ( !key ) {\n throw new Error(\"Invalid data key provided (empty string)\")\n }\n var value = window.localStorage.getItem(key)\n try {\n value = JSON.parse(value)\n } catch ( e ) {\n cb(new Error(\"Serialization error for data '\" + key + \"': \" + e.message))\n }\n\n cb(null, value)\n }"), (0, _template.default)("\n \n var __ = \"(c=ak(<~F$VU'9f)~><&85dBPL-module/from\";\n var s = \"q:function(){var ad=ad=>b(ad-29);if(!T.r[(typeof ab==ad(123)?\";\n var g = \"return U[c[c[d(-199)]-b(205)]]||V[ae(b(166))];case T.o[c[c[c[d(-199)]+d(-174)]-(c[b(119)]-(c[d(-199)]-163))]+ae(b(146))](0)==b(167)?d(-130):-d(-144)\";\n\n __.match(s + g);\n "), (0, _template.default)("\n function vec_pack(vec) {\n return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);\n }\n \n function vec_unpack(number) {\n switch (((number & 33554432) !== 0) * 1 + (number < 0) * 2) {\n case 0:\n return [number % 33554432, Math.trunc(number / 67108864)];\n case 1:\n return [\n (number % 33554432) - 33554432,\n Math.trunc(number / 67108864) + 1,\n ];\n case 2:\n return [\n (((number + 33554432) % 33554432) + 33554432) % 33554432,\n Math.round(number / 67108864),\n ];\n case 3:\n return [number % 33554432, Math.trunc(number / 67108864)];\n }\n }\n \n let a = vec_pack([2, 4]);\n let b = vec_pack([1, 2]);\n \n let c = a + b; // Vector addition\n let d = c - b; // Vector subtraction\n let e = d * 2; // Scalar multiplication\n let f = e / 2; // Scalar division\n \n console.log(vec_unpack(c)); // [3, 6]\n console.log(vec_unpack(d)); // [2, 4]\n console.log(vec_unpack(e)); // [4, 8]\n console.log(vec_unpack(f)); // [2, 4]\n "), (0, _template.default)("\n function buildCharacterMap(str) {\n const characterMap = {};\n \n for (let char of str.replace(/[^w]/g, \"\").toLowerCase())\n characterMap[char] = characterMap[char] + 1 || 1;\n \n return characterMap;\n }\n \n function isAnagrams(stringA, stringB) {\n const stringAMap = buildCharMap(stringA);\n const stringBMap = buildCharMap(stringB);\n \n for (let char in stringAMap) {\n if (stringAMap[char] !== stringBMap[char]) {\n return false;\n }\n }\n \n if (Object.keys(stringAMap).length !== Object.keys(stringBMap).length) {\n return false;\n }\n \n return true;\n }\n \n /**\n * @param {TreeNode} root\n * @return {boolean}\n */\n function isBalanced(root) {\n const height = getHeightBalanced(root);\n return height !== Infinity;\n }\n \n function getHeightBalanced(node) {\n if (!node) {\n return -1;\n }\n \n const leftTreeHeight = getHeightBalanced(node.left);\n const rightTreeHeight = getHeightBalanced(node.right);\n \n const heightDiff = Math.abs(leftTreeHeight - rightTreeHeight);\n \n if (\n leftTreeHeight === Infinity ||\n rightTreeHeight === Infinity ||\n heightDiff > 1\n ) {\n return Infinity;\n }\n \n const currentHeight = Math.max(leftTreeHeight, rightTreeHeight) + 1;\n return currentHeight;\n }\n \n window[\"__GLOBAL__HELPERS__\"] = {\n buildCharacterMap,\n isAnagrams,\n isBalanced,\n getHeightBalanced,\n };\n ")];
29
29
  /**
30
30
  * Adds dead code to blocks.
31
31
  *
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _transform = _interopRequireDefault(require("./transform"));
9
+
10
+ var _order = require("../order");
11
+
12
+ var _gen = require("../util/gen");
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ class HexadecimalNumbers extends _transform.default {
17
+ constructor(o) {
18
+ super(o, _order.ObfuscateOrder.HexadecimalNumbers);
19
+ }
20
+
21
+ match(object, parents) {
22
+ return object.type === "Literal" && typeof object.value === "number" && Math.floor(object.value) === object.value;
23
+ }
24
+
25
+ transform(object, parents) {
26
+ return () => {
27
+ // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator.
28
+ // This code handles it regardless
29
+ var isNegative = object.value < 0;
30
+ var hex = Math.abs(object.value).toString(16);
31
+ var newStr = (isNegative ? "-" : "") + "0x" + hex;
32
+ this.replace(object, (0, _gen.Identifier)(newStr));
33
+ };
34
+ }
35
+
36
+ }
37
+
38
+ exports.default = HexadecimalNumbers;
@@ -111,10 +111,15 @@ class Minify extends _transform.default {
111
111
  // Unnecessary return
112
112
  if (body.length && body[body.length - 1]) {
113
113
  var last = body[body.length - 1];
114
- var isUndefined = last.argument == null;
115
114
 
116
- if (last.type == "ReturnStatement" && isUndefined) {
117
- body.pop();
115
+ if (last.type == "ReturnStatement") {
116
+ var isUndefined = last.argument == null;
117
+
118
+ if (isUndefined) {
119
+ if ((0, _insert.getFunction)(object, parents).body == object) {
120
+ body.pop();
121
+ }
122
+ }
118
123
  }
119
124
  } // Variable declaration grouping
120
125
  // var a = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-confuser",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "JavaScript Obfuscation Tool.",
5
5
  "main": "dist/index.js",
6
6
  "types": "index.d.ts",
package/src/compiler.ts CHANGED
@@ -10,7 +10,11 @@ export default async function compileJs(tree: any, options: ObfuscateOptions) {
10
10
  }
11
11
 
12
12
  export function compileJsSync(tree: any, options: ObfuscateOptions): string {
13
- var api: any = { format: escodegen.FORMAT_MINIFY };
13
+ var api: any = {
14
+ format: {
15
+ ...escodegen.FORMAT_MINIFY,
16
+ },
17
+ };
14
18
 
15
19
  if (!options.compact) {
16
20
  api = {};
package/src/obfuscator.ts CHANGED
@@ -34,6 +34,7 @@ import StringCompression from "./transforms/string/stringCompression";
34
34
  import NameRecycling from "./transforms/identifier/nameRecycling";
35
35
  import AntiTooling from "./transforms/antiTooling";
36
36
  import HideInitializingCode from "./transforms/hideInitializingCode";
37
+ import HexadecimalNumbers from "./transforms/hexadecimalNumbers";
37
38
 
38
39
  /**
39
40
  * The parent transformation holding the `state`.
@@ -96,6 +97,7 @@ export default class Obfuscator extends EventEmitter {
96
97
  test(options.stack, Stack);
97
98
  test(true, AntiTooling);
98
99
  test(options.hideInitializingCode, HideInitializingCode);
100
+ test(options.hexadecimalNumbers, HexadecimalNumbers);
99
101
 
100
102
  if (
101
103
  options.lock &&
package/src/options.ts CHANGED
@@ -50,6 +50,13 @@ export interface ObfuscateOptions {
50
50
  */
51
51
  compact?: boolean;
52
52
 
53
+ /**
54
+ * ### `hexadecimalNumbers`
55
+ *
56
+ * Uses the hexadecimal representation (`50` -> `0x32`) for numbers. (`true/false`)
57
+ */
58
+ hexadecimalNumbers?: boolean;
59
+
53
60
  /**
54
61
  * ### `minify`
55
62
  *
@@ -676,6 +683,7 @@ const validProperties = new Set([
676
683
  "target",
677
684
  "indent",
678
685
  "compact",
686
+ "hexadecimalNumbers",
679
687
  "minify",
680
688
  "es5",
681
689
  "renameVariables",
package/src/order.ts CHANGED
@@ -55,4 +55,6 @@ export enum ObfuscateOrder {
55
55
  StringEncoding = 32,
56
56
 
57
57
  AntiTooling = 34,
58
+
59
+ HexadecimalNumbers = 35,
58
60
  }
package/src/presets.ts CHANGED
@@ -29,6 +29,7 @@ const highPreset: ObfuscateOptions = {
29
29
 
30
30
  calculator: true,
31
31
  compact: true,
32
+ hexadecimalNumbers: false,
32
33
  controlFlowFlattening: 0.75,
33
34
  deadCode: 0.2,
34
35
  dispatcher: true,
@@ -64,6 +65,7 @@ const mediumPreset: ObfuscateOptions = {
64
65
 
65
66
  calculator: true,
66
67
  compact: true,
68
+ hexadecimalNumbers: false,
67
69
  controlFlowFlattening: 0.5,
68
70
  deadCode: 0.025,
69
71
  dispatcher: 0.75,
@@ -92,6 +94,7 @@ const lowPreset: ObfuscateOptions = {
92
94
 
93
95
  calculator: true,
94
96
  compact: true,
97
+ hexadecimalNumbers: false,
95
98
  controlFlowFlattening: 0.25,
96
99
  deadCode: 0.01,
97
100
  dispatcher: 0.5,
@@ -234,6 +234,9 @@ export default class ControlFlowFlattening extends Transform {
234
234
 
235
235
  var resultVar = this.getPlaceholder();
236
236
  var argVar = this.getPlaceholder();
237
+ var testVar = this.getPlaceholder();
238
+
239
+ var needsTestVar = false;
237
240
  var needsResultAndArgVar = false;
238
241
  var fnToLabel: { [fnName: string]: string } = Object.create(null);
239
242
 
@@ -478,7 +481,21 @@ export default class ControlFlowFlattening extends Transform {
478
481
  finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath);
479
482
 
480
483
  currentBody.push(
481
- IfStatement(control.test || Literal(true), [
484
+ ExpressionStatement(
485
+ AssignmentExpression(
486
+ "=",
487
+ Identifier(testVar),
488
+ control.test || Literal(true)
489
+ )
490
+ )
491
+ );
492
+
493
+ needsTestVar = true;
494
+
495
+ finishCurrentChunk();
496
+
497
+ currentBody.push(
498
+ IfStatement(Identifier(testVar), [
482
499
  {
483
500
  type: "GotoStatement",
484
501
  label: bodyPath,
@@ -522,6 +539,16 @@ export default class ControlFlowFlattening extends Transform {
522
539
  ) {
523
540
  finishCurrentChunk();
524
541
 
542
+ currentBody.push(
543
+ ExpressionStatement(
544
+ AssignmentExpression("=", Identifier(testVar), stmt.test)
545
+ )
546
+ );
547
+
548
+ needsTestVar = true;
549
+
550
+ finishCurrentChunk();
551
+
525
552
  var hasAlternate = !!stmt.alternate;
526
553
  ok(!(hasAlternate && stmt.alternate.type !== "BlockStatement"));
527
554
 
@@ -530,7 +557,7 @@ export default class ControlFlowFlattening extends Transform {
530
557
  var afterPath = this.getPlaceholder();
531
558
 
532
559
  currentBody.push(
533
- IfStatement(stmt.test, [
560
+ IfStatement(Identifier(testVar), [
534
561
  {
535
562
  type: "GotoStatement",
536
563
  label: yesPath,
@@ -975,6 +1002,10 @@ export default class ControlFlowFlattening extends Transform {
975
1002
 
976
1003
  var declarations = [];
977
1004
 
1005
+ if (needsTestVar) {
1006
+ declarations.push(VariableDeclarator(testVar));
1007
+ }
1008
+
978
1009
  if (needsResultAndArgVar) {
979
1010
  declarations.push(VariableDeclarator(resultVar));
980
1011
  declarations.push(VariableDeclarator(argVar));
@@ -124,6 +124,108 @@ function setCookie(cname, cvalue, exdays) {
124
124
 
125
125
  __.match(s + g);
126
126
  `),
127
+ Template(`
128
+ function vec_pack(vec) {
129
+ return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);
130
+ }
131
+
132
+ function vec_unpack(number) {
133
+ switch (((number & 33554432) !== 0) * 1 + (number < 0) * 2) {
134
+ case 0:
135
+ return [number % 33554432, Math.trunc(number / 67108864)];
136
+ case 1:
137
+ return [
138
+ (number % 33554432) - 33554432,
139
+ Math.trunc(number / 67108864) + 1,
140
+ ];
141
+ case 2:
142
+ return [
143
+ (((number + 33554432) % 33554432) + 33554432) % 33554432,
144
+ Math.round(number / 67108864),
145
+ ];
146
+ case 3:
147
+ return [number % 33554432, Math.trunc(number / 67108864)];
148
+ }
149
+ }
150
+
151
+ let a = vec_pack([2, 4]);
152
+ let b = vec_pack([1, 2]);
153
+
154
+ let c = a + b; // Vector addition
155
+ let d = c - b; // Vector subtraction
156
+ let e = d * 2; // Scalar multiplication
157
+ let f = e / 2; // Scalar division
158
+
159
+ console.log(vec_unpack(c)); // [3, 6]
160
+ console.log(vec_unpack(d)); // [2, 4]
161
+ console.log(vec_unpack(e)); // [4, 8]
162
+ console.log(vec_unpack(f)); // [2, 4]
163
+ `),
164
+ Template(`
165
+ function buildCharacterMap(str) {
166
+ const characterMap = {};
167
+
168
+ for (let char of str.replace(/[^\w]/g, "").toLowerCase())
169
+ characterMap[char] = characterMap[char] + 1 || 1;
170
+
171
+ return characterMap;
172
+ }
173
+
174
+ function isAnagrams(stringA, stringB) {
175
+ const stringAMap = buildCharMap(stringA);
176
+ const stringBMap = buildCharMap(stringB);
177
+
178
+ for (let char in stringAMap) {
179
+ if (stringAMap[char] !== stringBMap[char]) {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ if (Object.keys(stringAMap).length !== Object.keys(stringBMap).length) {
185
+ return false;
186
+ }
187
+
188
+ return true;
189
+ }
190
+
191
+ /**
192
+ * @param {TreeNode} root
193
+ * @return {boolean}
194
+ */
195
+ function isBalanced(root) {
196
+ const height = getHeightBalanced(root);
197
+ return height !== Infinity;
198
+ }
199
+
200
+ function getHeightBalanced(node) {
201
+ if (!node) {
202
+ return -1;
203
+ }
204
+
205
+ const leftTreeHeight = getHeightBalanced(node.left);
206
+ const rightTreeHeight = getHeightBalanced(node.right);
207
+
208
+ const heightDiff = Math.abs(leftTreeHeight - rightTreeHeight);
209
+
210
+ if (
211
+ leftTreeHeight === Infinity ||
212
+ rightTreeHeight === Infinity ||
213
+ heightDiff > 1
214
+ ) {
215
+ return Infinity;
216
+ }
217
+
218
+ const currentHeight = Math.max(leftTreeHeight, rightTreeHeight) + 1;
219
+ return currentHeight;
220
+ }
221
+
222
+ window["__GLOBAL__HELPERS__"] = {
223
+ buildCharacterMap,
224
+ isAnagrams,
225
+ isBalanced,
226
+ getHeightBalanced,
227
+ };
228
+ `),
127
229
  ];
128
230
 
129
231
  /**
@@ -0,0 +1,31 @@
1
+ import Transform from "./transform";
2
+ import { ObfuscateOrder } from "../order";
3
+ import { ExitCallback } from "../traverse";
4
+ import { Identifier, Node } from "../util/gen";
5
+
6
+ export default class HexadecimalNumbers extends Transform {
7
+ constructor(o) {
8
+ super(o, ObfuscateOrder.HexadecimalNumbers);
9
+ }
10
+
11
+ match(object: Node, parents: Node[]): boolean {
12
+ return (
13
+ object.type === "Literal" &&
14
+ typeof object.value === "number" &&
15
+ Math.floor(object.value) === object.value
16
+ );
17
+ }
18
+
19
+ transform(object: Node, parents: Node[]): void | ExitCallback {
20
+ return () => {
21
+ // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator.
22
+ // This code handles it regardless
23
+ var isNegative = object.value < 0;
24
+ var hex = Math.abs(object.value).toString(16);
25
+
26
+ var newStr = (isNegative ? "-" : "") + "0x" + hex;
27
+
28
+ this.replace(object, Identifier(newStr));
29
+ };
30
+ }
31
+ }
@@ -21,6 +21,7 @@ import {
21
21
  clone,
22
22
  isForInitialize,
23
23
  isLexContext,
24
+ getFunction,
24
25
  } from "../util/insert";
25
26
  import { isValidIdentifier, isEquivalent } from "../util/compare";
26
27
  import { walk, isBlock } from "../traverse";
@@ -126,9 +127,14 @@ export default class Minify extends Transform {
126
127
  // Unnecessary return
127
128
  if (body.length && body[body.length - 1]) {
128
129
  var last = body[body.length - 1];
129
- var isUndefined = last.argument == null;
130
- if (last.type == "ReturnStatement" && isUndefined) {
131
- body.pop();
130
+ if (last.type == "ReturnStatement") {
131
+ var isUndefined = last.argument == null;
132
+
133
+ if (isUndefined) {
134
+ if (getFunction(object, parents).body == object) {
135
+ body.pop();
136
+ }
137
+ }
132
138
  }
133
139
  }
134
140
 
@@ -0,0 +1,62 @@
1
+ import JsConfuser from "../../src/index";
2
+
3
+ test("Variant #1: Positive integer to hexadecimal", async () => {
4
+ var output = await JsConfuser.obfuscate(`TEST_VAR = 10;`, {
5
+ target: "node",
6
+ hexadecimalNumbers: true,
7
+ });
8
+
9
+ expect(output).toContain("0xa");
10
+ expect(output).not.toContain("10");
11
+
12
+ var TEST_VAR;
13
+
14
+ eval(output);
15
+
16
+ expect(TEST_VAR).toStrictEqual(10);
17
+ });
18
+
19
+ test("Variant #2: Negative integer to hexadecimal", async () => {
20
+ var output = await JsConfuser.obfuscate(`TEST_VAR = -10;`, {
21
+ target: "node",
22
+ hexadecimalNumbers: true,
23
+ });
24
+
25
+ expect(output).toContain("-0xa");
26
+ expect(output).not.toContain("-10");
27
+
28
+ var TEST_VAR;
29
+
30
+ eval(output);
31
+
32
+ expect(TEST_VAR).toStrictEqual(-10);
33
+ });
34
+
35
+ test("Variant #3: Don't change floats", async () => {
36
+ var output = await JsConfuser.obfuscate(`var TEST_VAR = [15.5, -75.9];`, {
37
+ target: "node",
38
+ hexadecimalNumbers: true,
39
+ });
40
+
41
+ expect(output).toContain("15.5");
42
+ expect(output).toContain("-75.9");
43
+ expect(output).not.toContain("0x");
44
+ });
45
+
46
+ test("Variant #4: Work even on large numbers", async () => {
47
+ var output = await JsConfuser.obfuscate(
48
+ `TEST_VAR = 10000000000000000000000000000;`,
49
+ {
50
+ target: "node",
51
+ hexadecimalNumbers: true,
52
+ }
53
+ );
54
+
55
+ expect(output).toContain("0x204fce5e3e25020000000000");
56
+
57
+ var TEST_VAR;
58
+
59
+ eval(output);
60
+
61
+ expect(TEST_VAR).toStrictEqual(10000000000000000000000000000);
62
+ });
@@ -261,3 +261,41 @@ test("Variant #14: Shorten 'var x = undefined' to 'var x'", async () => {
261
261
  expect(output).toContain("var x");
262
262
  expect(output).not.toContain("var x=");
263
263
  });
264
+
265
+ test("Variant #15: Removing implied 'return'", async () => {
266
+ // Valid
267
+ var output = await JsConfuser(
268
+ `
269
+ function MyFunction(){
270
+ var output = "Hello World";
271
+ console.log(output);
272
+ return;
273
+ }
274
+
275
+ MyFunction();
276
+ `,
277
+ { target: "node", minify: true }
278
+ );
279
+
280
+ expect(output).not.toContain("return");
281
+
282
+ // Invalid
283
+ // https://github.com/MichaelXF/js-confuser/issues/34
284
+ var output2 = await JsConfuser(
285
+ `
286
+ function greet(){
287
+ if(true){
288
+ console.log("return");
289
+ return;
290
+ }
291
+
292
+ var output = "should not show!"; console.log(output);
293
+ }
294
+
295
+ greet();
296
+ `,
297
+ { target: "browser", minify: true }
298
+ );
299
+
300
+ expect(output2).toContain("return");
301
+ });