From dd408e82640d56acc853075403240bd569b0c20e Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 24 Aug 2024 16:06:47 -0400 Subject: [PATCH 001/103] Initial 2.0 Rewrite --- Migration.md | 41 + README.md | 919 +------- babel.config.js | 10 +- babel.register.js | 6 + dev.js | 8 - package.json | 147 +- samples/example.js | 15 - samples/high.js | 1 - samples/input.js | 3 - samples/javascriptobfuscator.com.js | 8 - samples/jscrambler_advanced.js | 1894 ----------------- samples/jscrambler_light.js | 1134 ---------- samples/low.js | 1 - samples/medium.js | 1 - samples/obfuscator.io.js | 1686 --------------- samples/preemptive.com.js | 16 - src/index.ts | 213 +- src/obfuscationResult.ts | 11 + src/obfuscator.ts | 254 +-- src/options.ts | 291 +-- src/presets.ts | 4 +- src/probability.ts | 4 +- src/templates/deadCodeTemplates.ts | 549 +++++ src/templates/template.ts | 224 +- src/transforms/deadCode.ts | 715 +------ src/transforms/dispatcher.ts | 901 +++----- .../extraction/duplicateLiteralsRemoval.ts | 403 +--- src/transforms/extraction/objectExtraction.ts | 464 ++-- src/transforms/finalizer.ts | 104 +- src/transforms/flatten.ts | 711 ++----- src/transforms/identifier/globalConcealing.ts | 360 +--- src/transforms/identifier/renameVariables.ts | 324 +-- src/transforms/plugin.ts | 49 + src/transforms/preparation.ts | 333 +-- src/transforms/shuffle.ts | 321 +-- src/transforms/string/stringCompression.ts | 369 +--- src/transforms/string/stringConcealing.ts | 423 +--- src/transforms/string/stringEncoding.ts | 77 +- src/transforms/string/stringSplitting.ts | 134 +- src/transforms/variableMasking.ts | 135 ++ src/utils/IntGen.ts | 33 + src/utils/NameGen.ts | 29 + src/utils/ast-utils.ts | 172 ++ src/utils/object-utils.ts | 16 + src/utils/random-utils.ts | 80 + src/validateOptions.ts | 286 +++ {src => src_old}/compiler.ts | 0 {src => src_old}/constants.ts | 0 src_old/index.ts | 198 ++ src_old/obfuscator.ts | 165 ++ src_old/options.ts | 898 ++++++++ {src => src_old}/order.ts | 0 {src => src_old}/parser.ts | 0 {src => src_old}/precedence.ts | 0 src_old/presets.ts | 120 ++ src_old/probability.ts | 125 ++ {src => src_old}/templates/bufferToString.ts | 0 {src => src_old}/templates/core.ts | 0 {src => src_old}/templates/crash.ts | 0 {src => src_old}/templates/es5.ts | 0 {src => src_old}/templates/functionLength.ts | 0 {src => src_old}/templates/globals.ts | 0 src_old/templates/template.ts | 230 ++ {src => src_old}/transforms/antiTooling.ts | 0 {src => src_old}/transforms/calculator.ts | 0 .../controlFlowFlattening.ts | 0 .../expressionObfuscation.ts | 0 src_old/transforms/deadCode.ts | 676 ++++++ src_old/transforms/dispatcher.ts | 640 ++++++ {src => src_old}/transforms/es5/antiClass.ts | 0 .../transforms/es5/antiDestructuring.ts | 0 .../transforms/es5/antiES6Object.ts | 0 .../transforms/es5/antiSpreadOperator.ts | 0 .../transforms/es5/antiTemplate.ts | 0 {src => src_old}/transforms/es5/es5.ts | 0 .../transforms/extraction/classExtraction.ts | 0 .../extraction/duplicateLiteralsRemoval.ts | 297 +++ .../transforms/extraction/objectExtraction.ts | 360 ++++ src_old/transforms/finalizer.ts | 75 + src_old/transforms/flatten.ts | 557 +++++ .../transforms/identifier/globalAnalysis.ts | 0 .../transforms/identifier/globalConcealing.ts | 297 +++ .../identifier/movedDeclarations.ts | 0 .../transforms/identifier/renameVariables.ts | 300 +++ .../transforms/identifier/variableAnalysis.ts | 0 {src => src_old}/transforms/lock/antiDebug.ts | 0 {src => src_old}/transforms/lock/integrity.ts | 0 {src => src_old}/transforms/lock/lock.ts | 0 {src => src_old}/transforms/minify.ts | 0 .../transforms/opaquePredicates.ts | 0 src_old/transforms/preparation.ts | 254 +++ {src => src_old}/transforms/renameLabels.ts | 0 {src => src_old}/transforms/rgf.ts | 0 src_old/transforms/shuffle.ts | 254 +++ {src => src_old}/transforms/stack.ts | 0 .../transforms/string/encoding.ts | 0 .../transforms/string/stringCompression.ts | 309 +++ src_old/transforms/string/stringConcealing.ts | 380 ++++ src_old/transforms/string/stringEncoding.ts | 95 + src_old/transforms/string/stringSplitting.ts | 86 + {src => src_old}/transforms/transform.ts | 0 {src => src_old}/traverse.ts | 0 {src => src_old}/types.ts | 0 {src => src_old}/util/compare.ts | 0 {src => src_old}/util/gen.ts | 0 {src => src_old}/util/guard.ts | 0 {src => src_old}/util/identifiers.ts | 0 {src => src_old}/util/insert.ts | 0 {src => src_old}/util/math.ts | 0 {src => src_old}/util/object.ts | 0 {src => src_old}/util/random.ts | 0 {src => src_old}/util/scope.ts | 0 test/compare.test.ts | 104 - test/index.test.ts | 33 +- test/templates/template.test.ts | 3 - test/transforms/transform.test.ts | 66 - test/traverse.test.ts | 139 -- test/util/compare.test.ts | 34 - test/util/gen.test.ts | 121 -- test/util/identifiers.test.ts | 253 --- test/util/math.test.ts | 5 - test/util/random.test.ts | 4 +- tsconfig.json | 21 +- 123 files changed, 9273 insertions(+), 11705 deletions(-) create mode 100644 Migration.md create mode 100644 babel.register.js delete mode 100644 dev.js delete mode 100644 samples/example.js delete mode 100644 samples/high.js delete mode 100644 samples/input.js delete mode 100644 samples/javascriptobfuscator.com.js delete mode 100644 samples/jscrambler_advanced.js delete mode 100644 samples/jscrambler_light.js delete mode 100644 samples/low.js delete mode 100644 samples/medium.js delete mode 100644 samples/obfuscator.io.js delete mode 100644 samples/preemptive.com.js create mode 100644 src/obfuscationResult.ts create mode 100644 src/templates/deadCodeTemplates.ts create mode 100644 src/transforms/plugin.ts create mode 100644 src/transforms/variableMasking.ts create mode 100644 src/utils/IntGen.ts create mode 100644 src/utils/NameGen.ts create mode 100644 src/utils/ast-utils.ts create mode 100644 src/utils/object-utils.ts create mode 100644 src/utils/random-utils.ts create mode 100644 src/validateOptions.ts rename {src => src_old}/compiler.ts (100%) rename {src => src_old}/constants.ts (100%) create mode 100644 src_old/index.ts create mode 100644 src_old/obfuscator.ts create mode 100644 src_old/options.ts rename {src => src_old}/order.ts (100%) rename {src => src_old}/parser.ts (100%) rename {src => src_old}/precedence.ts (100%) create mode 100644 src_old/presets.ts create mode 100644 src_old/probability.ts rename {src => src_old}/templates/bufferToString.ts (100%) rename {src => src_old}/templates/core.ts (100%) rename {src => src_old}/templates/crash.ts (100%) rename {src => src_old}/templates/es5.ts (100%) rename {src => src_old}/templates/functionLength.ts (100%) rename {src => src_old}/templates/globals.ts (100%) create mode 100644 src_old/templates/template.ts rename {src => src_old}/transforms/antiTooling.ts (100%) rename {src => src_old}/transforms/calculator.ts (100%) rename {src => src_old}/transforms/controlFlowFlattening/controlFlowFlattening.ts (100%) rename {src => src_old}/transforms/controlFlowFlattening/expressionObfuscation.ts (100%) create mode 100644 src_old/transforms/deadCode.ts create mode 100644 src_old/transforms/dispatcher.ts rename {src => src_old}/transforms/es5/antiClass.ts (100%) rename {src => src_old}/transforms/es5/antiDestructuring.ts (100%) rename {src => src_old}/transforms/es5/antiES6Object.ts (100%) rename {src => src_old}/transforms/es5/antiSpreadOperator.ts (100%) rename {src => src_old}/transforms/es5/antiTemplate.ts (100%) rename {src => src_old}/transforms/es5/es5.ts (100%) rename {src => src_old}/transforms/extraction/classExtraction.ts (100%) create mode 100644 src_old/transforms/extraction/duplicateLiteralsRemoval.ts create mode 100644 src_old/transforms/extraction/objectExtraction.ts create mode 100644 src_old/transforms/finalizer.ts create mode 100644 src_old/transforms/flatten.ts rename {src => src_old}/transforms/identifier/globalAnalysis.ts (100%) create mode 100644 src_old/transforms/identifier/globalConcealing.ts rename {src => src_old}/transforms/identifier/movedDeclarations.ts (100%) create mode 100644 src_old/transforms/identifier/renameVariables.ts rename {src => src_old}/transforms/identifier/variableAnalysis.ts (100%) rename {src => src_old}/transforms/lock/antiDebug.ts (100%) rename {src => src_old}/transforms/lock/integrity.ts (100%) rename {src => src_old}/transforms/lock/lock.ts (100%) rename {src => src_old}/transforms/minify.ts (100%) rename {src => src_old}/transforms/opaquePredicates.ts (100%) create mode 100644 src_old/transforms/preparation.ts rename {src => src_old}/transforms/renameLabels.ts (100%) rename {src => src_old}/transforms/rgf.ts (100%) create mode 100644 src_old/transforms/shuffle.ts rename {src => src_old}/transforms/stack.ts (100%) rename {src => src_old}/transforms/string/encoding.ts (100%) create mode 100644 src_old/transforms/string/stringCompression.ts create mode 100644 src_old/transforms/string/stringConcealing.ts create mode 100644 src_old/transforms/string/stringEncoding.ts create mode 100644 src_old/transforms/string/stringSplitting.ts rename {src => src_old}/transforms/transform.ts (100%) rename {src => src_old}/traverse.ts (100%) rename {src => src_old}/types.ts (100%) rename {src => src_old}/util/compare.ts (100%) rename {src => src_old}/util/gen.ts (100%) rename {src => src_old}/util/guard.ts (100%) rename {src => src_old}/util/identifiers.ts (100%) rename {src => src_old}/util/insert.ts (100%) rename {src => src_old}/util/math.ts (100%) rename {src => src_old}/util/object.ts (100%) rename {src => src_old}/util/random.ts (100%) rename {src => src_old}/util/scope.ts (100%) delete mode 100644 test/compare.test.ts delete mode 100644 test/transforms/transform.test.ts delete mode 100644 test/traverse.test.ts delete mode 100644 test/util/compare.test.ts delete mode 100644 test/util/gen.test.ts delete mode 100644 test/util/identifiers.test.ts delete mode 100644 test/util/math.test.ts diff --git a/Migration.md b/Migration.md new file mode 100644 index 0000000..4f296cf --- /dev/null +++ b/Migration.md @@ -0,0 +1,41 @@ +## Migration guide to JS-Confuser 2.0 + +JS-Confuser 2.0 is complete rewrite of the original JS-Confuser created in 2020! + +### JSConfuser.obfuscate() returns an object now + +The method `JSConfuser.obfuscate()` resolves to a object now instead of a string. This result object contains the obfuscated code on the `code` property. + +```js +JSConfuser.obfuscate(sourceCode, options).then(result=>{ + console.log(result.code); +}); +``` + +### Removed Anti Debug Lock / Browser Lock / OS Lock + +These features have been removed but you can still add these locks using the `lock.customLocks` option. + +```js +{ + lock: { + customLocks: [ + { + template: `if(window.navigator.userAgent.includes('Chrome')){ + {countermeasures} +}`, + percentage: 10, + max: 100 + } + ] + } +} +``` + +### Stack renamed to Variable Masking + +The option `stack` has been renamed to `variableMasking` + +### Flatten renamed to Function Hoisting + +The option `flatten` has been renamed to `functionHoisting` \ No newline at end of file diff --git a/README.md b/README.md index 80704c9..304fe18 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,6 @@ JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ - Locks (domainLock, date) - [Detect changes to source code](https://github.com/MichaelXF/js-confuser/blob/master/docs/Integrity.md) -## Presets - -JS-Confuser comes with three presets built into the obfuscator. - -| Preset | Transforms | Performance Reduction | Sample | -| --- | --- | --- | --- | -| High | 22/25 | 98% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/high.js) | -| Medium | 19/25 | 52% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/medium.js) | -| Low | 15/25 | 30% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/low.js) | - You can extend each preset or all go without them entirely. ## Installation @@ -69,876 +59,13 @@ var AF59rI,ZgbbeaU,WDgj3I,gpR2qG,Ox61sk,pTNPNpX;AF59rI=[60,17,25,416,22,23,83,26 ``` -## `obfuscate(sourceCode, options)` - -Obfuscates the `sourceCode`. Returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves to a string of the obfuscated code. - -| Parameter | Type | Description | -| --- | --- | --- | -| `sourceCode` | `string` | The JavaScript code to be obfuscated. | -| `options` | `object` | The obfuscator settings. | - -## `obfuscateAST(AST, options)` - -Obfuscates an [ESTree](https://github.com/estree/estree) compliant AST. Returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). - -**Note:** Mutates the object. - -| Parameter | Type | Description | -| --- | --- | --- | -| `AST` | `object` | The [ESTree](https://github.com/estree/estree) compliant AST. This object will be mutated. | -| `options` | `object` | The obfuscator settings. | - -## Options - -### `target` - -The execution context for your output. _Required_. - -1. `"node"` -2. `"browser"` - -### `preset` - -[JS-Confuser comes with three presets built into the obfuscator](https://github.com/MichaelXF/js-confuser#presets). _Optional_. (`"high"/"medium"/"low"`) - -```js -var JsConfuser = require('js-confuser'); - -JsConfuser.obfuscate(``, { - target: "node", - preset: "high" // | "medium" | "low" -}).then(obfuscated=>{ - console.log(obfuscated); // obfuscated is a string -}) -``` - -### `compact` - -Remove's whitespace from the final output. Enabled by default. (`true/false`) - -### `hexadecimalNumbers` - -Uses the hexadecimal representation for numbers. (`true/false`) - -### `minify` - -Minifies redundant code. (`true/false`) - -### `es5` - -Converts output to ES5-compatible code. (`true/false`) - -Does not cover all cases such as Promises or Generator functions. Use [Babel](https://babel.dev/). - -[Learn more here.](https://github.com/MichaelXF/js-confuser/blob/master/docs/ES5.md) - -### `renameVariables` - -Determines if variables should be renamed. (`true/false`) - -```js -// Input -var twoSum = function (nums, target) { - var hash = {}; - var len = nums.length; - for (var i = 0; i < len; i++) { - if (nums[i] in hash) return [hash[nums[i]], i]; - hash[target - nums[i]] = i; - } - return [-1, -1]; -}; - -var test = function () { - var inputNums = [2, 7, 11, 15]; - var inputTarget = 9; - var expectedResult = [0, 1]; - - var actualResult = twoSum(inputNums, inputTarget); - ok(actualResult[0] === expectedResult[0]); - ok(actualResult[1] === expectedResult[1]); -}; - -test(); - -// Output -var _O2mOcF = function (kB4uXM, w_07HXS) { - var ZLTJcx = {}; - var sXQOaUx = kB4uXM["length"]; - for (var JYYxEk = 0; JYYxEk < sXQOaUx; JYYxEk++) { - if (kB4uXM[JYYxEk] in ZLTJcx) { - return [ZLTJcx[kB4uXM[JYYxEk]], JYYxEk]; - } - ZLTJcx[w_07HXS - kB4uXM[JYYxEk]] = JYYxEk; - } - return [-1, -1]; -}; -var qFaI6S = function () { - var fZpeOw = [2, 7, 11, 15]; - var UJ62R2c = 9; - var dG6R0cV = [0, 1]; - var WgYXwn = _O2mOcF(fZpeOw, UJ62R2c); - void (ok(WgYXwn[0] === dG6R0cV[0]), ok(WgYXwn[1] === dG6R0cV[1])); -}; -qFaI6S(); -``` - -[Learn mode here.](https://github.com/MichaelXF/js-confuser/blob/master/docs/RenameVariables.md) - -### `renameGlobals` - -Renames top-level variables, turn this off for web-related scripts. Enabled by default. (`true/false`) - -### `identifierGenerator` - -Determines how variables are renamed. - -| Mode | Description | Example | -| --- | --- | --- | -| `"hexadecimal"` | Random hex strings | \_0xa8db5 | -| `"randomized"` | Random characters | w$Tsu4G | -| `"zeroWidth"` | Invisible characters | U+200D | -| `"mangled"` | Alphabet sequence | a, b, c | -| `"number"` | Numbered sequence | var_1, var_2 | -| `` | Write a custom name generator | See Below | - -```js -// Custom implementation -JsConfuser.obfuscate(code, { - target: "node", - renameVariables: true, - identifierGenerator: function () { - return "$" + Math.random().toString(36).substring(7); - }, -}); - -// Numbered variables -var counter = 0; -JsConfuser.obfuscate(code, { - target: "node", - renameVariables: true, - identifierGenerator: function () { - return "var_" + (counter++); - }, -}); -``` - -JSConfuser tries to reuse names when possible, creating very potent code. - -### `controlFlowFlattening` - -**⚠️ Significantly impacts performance, use sparingly!** - -Control-flow Flattening hinders program comprehension by creating convoluted switch statements. (`true/false/0-1`) - -Use a number to control the percentage from 0 to 1. - -[Learn more here.](https://github.com/MichaelXF/js-confuser/blob/master/docs/ControlFlowFlattening.md) - -```js -// Input -function countTo(num){ - for ( var i = 1; i <= num; i++ ) { - console.log(i); - } -} - -var number = 10; -countTo(number); // 1,2,3,4,5,6,7,8,9,10 - -// Output -var n2DUka, - O7yZ0oU, - mJMdMhJ = -337, - A1Nyvv = -94, - xDwpOk6 = 495, - uKcJl2 = { - TGCpW6t: "log", - qUrjFe: function () { - return xDwpOk6 == (126 > mJMdMhJ ? -16 : 34); - }, - YN20IBx: function () { - return (A1Nyvv -= 53); - }, - CTW4vwx: -73, - PLzWYDx: function () { - return (O7yZ0oU = [[385, -94, -282], [10]]); - }, - bW2FK2: function () { - return (mJMdMhJ *= 2), (mJMdMhJ += 366); - }, - AfOoRT: function () { - return xDwpOk6 == xDwpOk6 + 867; - }, - KTNMdj: function () { - if (uKcJl2.AfOoRT()) { - typeof ((mJMdMhJ += 0), uKcJl2.Q0I6e4f(), (xDwpOk6 += 0)); - return "cobTe8G"; - } - typeof (uKcJl2.htRXYx(), - (mJMdMhJ += 59), - (A1Nyvv -= 537), - (xDwpOk6 += uKcJl2.mLuSzZ < mJMdMhJ ? 449 : -33)); - return "cobTe8G"; - }, - }; -while (mJMdMhJ + A1Nyvv + xDwpOk6 != 83) { - var yQNDJh = (mJMdMhJ + A1Nyvv + xDwpOk6) * 58 + 54; - switch (yQNDJh) { - case 750: - if (A1Nyvv == 24) { - uKcJl2.FxREGd6(); - break; - } - case 1214: - if (uKcJl2.qUrjFe()) { - typeof ((mJMdMhJ *= -8 > xDwpOk6 ? -109 : 2), - (mJMdMhJ += 1168), - (xDwpOk6 += xDwpOk6 - 1290)); - break; - } - function _VSsIw() { - var [yQNDJh, _VSsIw] = O7yZ0oU, - [L9B14E] = _VSsIw, - uTyFFb = 322; - while (uTyFFb != 23) { - var cBx3ysg = uTyFFb * 48 - 77; - switch (cBx3ysg) { - case 15379: - var IOoqIZ = 1; - uTyFFb -= 306; - break; - case 691: - uTyFFb += IOoqIZ <= L9B14E ? 976 : 7; - break; - case 47539: - typeof (console[uKcJl2.TGCpW6t](IOoqIZ), (uTyFFb -= 795)); - break; - case 9379: - !(IOoqIZ++, (uTyFFb -= 181)); - } - } - return ([mJMdMhJ, A1Nyvv, xDwpOk6] = yQNDJh), (n2DUka = void 0); - } - (xDwpOk6 == -73 ? parseInt : _VSsIw)(); - break; - case 576: - typeof (mJMdMhJ == -4 ? clearImmediate : void 0, - uKcJl2.bky8kL(), - (xDwpOk6 -= 463)); - break; - case 4172: - var L9B14E = 10; - void ((O7yZ0oU = [[385, -94, -282], [10]]), - (mJMdMhJ -= 187), - uKcJl2.YN20IBx(), - (xDwpOk6 += 189)); - break; - case 3766: - !((uKcJl2.Fpp8x5 = -167), - (uKcJl2.mLuSzZ = 144), - (uKcJl2.FxREGd6 = function () { - return (mJMdMhJ += uKcJl2.Fpp8x5), (xDwpOk6 += 164); - }), - (uKcJl2.bky8kL = function () { - return (A1Nyvv += 537); - }), - (uKcJl2.Q0I6e4f = function () { - return (A1Nyvv += 0); - }), - (uKcJl2.htRXYx = function () { - return (xDwpOk6 = -82); - })); - var L9B14E = 10; - void (uKcJl2.PLzWYDx(), uKcJl2.bW2FK2(), (xDwpOk6 += uKcJl2.CTW4vwx)); - break; - default: - if (uKcJl2.KTNMdj() == "cobTe8G") { - break; - } - } -} -``` - -### `globalConcealing` - -Global Concealing hides global variables being accessed. (`true/false`) - -```js -// Input -console.log("Hello World"); - -// Output -yAt1T_y(-93)["log"]("Hello World"); -``` - -### `stringCompression` - -String Compression uses LZW's compression algorithm to compress strings. (`true/false/0-1`) - -Use a number to control the percentage of strings. - -`"console"` -> `inflate('replaĕ!ğğuģģ<~@')` - -### `stringConcealing` - -String Concealing involves encoding strings to conceal plain-text values. (`true/false/0-1`) - -Use a number to control the percentage of strings. - -`"console"` -> `decrypt('<~@rH7+Dert~>')` - -### `stringEncoding` - -String Encoding transforms a string into an encoded representation. (`true/false/0-1`) - -**⚠️ Warning: Significantly increases file size! It is not recommended for most use cases.** - -Use a number to control the percentage of strings. - -`"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` - -### `stringSplitting` - -String Splitting splits your strings into multiple expressions. (`true/false/0-1`) - -Use a number to control the percentage of strings. - -`"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` - -### `duplicateLiteralsRemoval` - -Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`) - -### `dispatcher` - -Creates a middleman function to process function calls. (`true/false/0-1`) - -```js -// Input -function print(x){ - console.log(x); -} - -print("Hello World"); // "Hello World" - -// Output -var RfN5Yz = Object.create(null), - GEMxMoq = []; -typeof ((GEMxMoq = ["Hello World"]), yT9GzM("jlg2V0")); -function yT9GzM(yT9GzM, ChVrLK, b8q2HVZ) { - var RuH38a = { - jlg2V0: function (_x5bmV, fslYszl, YbdYYlj) { - if (!_x5bmV) { - return fslYszl(this, YbdYYlj); - } - var [yT9GzM] = GEMxMoq; - console.log(yT9GzM); - }, - }, - JwN3oMY; - if (ChVrLK == "smHux1f") { - GEMxMoq = []; - } - JwN3oMY = - ChVrLK == "DiwMvrE" - ? RfN5Yz[yT9GzM] || - (RfN5Yz[yT9GzM] = function (...fslYszl) { - GEMxMoq = fslYszl; - return RuH38a[yT9GzM].call(this, "vZWlke7"); - }) - : RuH38a[yT9GzM]("EuVJE6"); - return b8q2HVZ == "ePsy9W" ? { occYQrC: JwN3oMY } : JwN3oMY; -} -``` - -### `rgf` - -RGF (Runtime-Generated-Functions) uses the [`new Function(code...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) syntax to construct executable code from strings. (`true/false/0-1`) - -- **This can break your code.** -- **Due to the security concerns of arbitrary code execution, you must enable this yourself.** -- The arbitrary code is also obfuscated. - -Note: RGF will only apply to functions that do not rely on any outside-scoped variables. Enable `flatten` along with `rgf` to apply to these functions. - -Note: Does not apply to arrow, async, or generator functions. - -Use a number to control the percentage of functions changed. - -[Learn more here.](https://github.com/MichaelXF/js-confuser/blob/master/docs/RGF.md) - -```js -// Input -function printToConsole(message){ - console.log(message); -} - -printToConsole("Hello World"); // "Hello World" - -// Output -var Ricvq8s = [new Function('function HIGRHaD(ANVivo_){console[\'log\'](ANVivo_)}return HIGRHaD[\'apply\'](this,arguments)')]; -function uhj6obs() { - return Ricvq8s[0]['apply'](this, arguments); -} -uhj6obs('Hello World'); // "Hello World" -``` - - -### `flatten` - -Brings independent declarations to the highest scope. (`true/false/0-1`) - -This transformation makes functions eligible for the RGF transformation. - -Use a number to control the percentage of functions changed. - -```js -// Input -(function(){ - var stringToPrint = "Hello World"; - var timesPrinted = 0; - - function printString(){ - timesPrinted++; - console.log(stringToPrint); - } - - printString(); // "Hello World" -})(); - -// Output -var XKlik0N = lP2p9dc(([], pgswImq) => { - void (pgswImq.rGFfJKd++, console.log(pgswImq.I6NTID)); -}); -function M5IeIO([], mu63vsS) { - var p_hOdnM = "Hello World", - X_bU9rL = 0; - function Iwe3cJW(...nuTwoiz) { - var aNxnp94 = { - set rGFfJKd(C9XSMeD) { - X_bU9rL = C9XSMeD; - }, - get I6NTID() { - return p_hOdnM; - }, - get rGFfJKd() { - return X_bU9rL; - }, - }; - return mu63vsS.PbELcOw(nuTwoiz, aNxnp94); - } - Iwe3cJW(); -} -lP2p9dc((...AvydL3) => { - var B6ymQf = { - get PbELcOw() { - return XKlik0N; - }, - }; - return M5IeIO(AvydL3, B6ymQf); -})(); -function lP2p9dc(fJxfZW) { - return function () { - return fJxfZW(...arguments); - }; -} -``` - -### `objectExtraction` - -Extracts object properties into separate variables. (`true/false`) - -```js -// Input -var utils = { - isString: x=>typeof x === "string", - isBoolean: x=>typeof x === "boolean" -} -if ( utils.isString("Hello") ) { - // ... -} - -// Output -var utils_isString = x=>typeof x === "string"; -var utils_isBoolean = x=>typeof x === "boolean"; -if ( utils_isString("Hello") ) { - // ... -} -``` - -### `deadCode` - -Randomly injects dead code. (`true/false/0-1`) - -Use a number to control the percentage from 0 to 1. - -### `calculator` - -Creates a calculator function to handle arithmetic and logical expressions. (`true/false/0-1`) - -### `lock.antiDebug` - -Adds `debugger` statements throughout the code. Additionally adds a background function for DevTools detection. (`true/false/0-1`) - -### `lock.tamperProtection` - -Tamper Protection safeguards the runtime behavior from being altered by JavaScript pitfalls. (`true/false`) - -**⚠️ Tamper Protection requires eval and ran in a non-strict mode environment!** - -- **This can break your code.** -- **Due to the security concerns of arbitrary code execution, you must enable this yourself.** - -[Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md). - -### `lock.startDate` - -When the program is first able to be used. (`number` or `Date`) - -Number should be in milliseconds. - -### `lock.endDate` - -When the program is no longer able to be used. (`number` or `Date`) - -Number should be in milliseconds. - -### `lock.domainLock` - -Array of regex strings that the `window.location.href` must follow. (`Regex[]` or `string[]`) - -### `lock.osLock` - -Array of operating-systems where the script is allowed to run. (`string[]`) - -Allowed values: `"linux"`, `"windows"`, `"osx"`, `"android"`, `"ios"` - -Example: `["linux", "windows"]` - -### `lock.browserLock` - -Array of browsers where the script is allowed to run. (`string[]`) - -Allowed values: `"firefox"`, `"chrome"`, `"iexplorer"`, `"edge"`, `"safari"`, `"opera"` - -Example: `["firefox", "chrome"]` - -### `lock.selfDefending` - -Prevents the use of code beautifiers or formatters against your code. - -[Identical to Obfuscator.io's Self Defending](https://github.com/javascript-obfuscator/javascript-obfuscator#selfdefending) - -### `lock.integrity` - -Integrity ensures the source code is unchanged. (`true/false/0-1`) - -[Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/docs/Integrity.md). - -### `lock.countermeasures` - -A custom callback function to invoke when a lock is triggered. (`string/false`) - -[Learn more here.](https://github.com/MichaelXF/js-confuser/blob/master/docs/Countermeasures.md) - -Otherwise, the obfuscator falls back to crashing the process. - -### `lock.context` - -Properties that must be present on the `window` object (or `global` for NodeJS). (`string[]`) - -### `movedDeclarations` - -Moves variable declarations to the top of the context. (`true/false`) - -```js -// Input -function getAreaOfCircle(radius) { - var pi = Math.PI; - var radiusSquared = Math.pow(radius, 2); - var area = pi * radiusSquared; - - return area; -} - -// Output -function getAreaOfCircle(yLu5YB1, eUf7Wle, XVYH4D, F8QuPL) { - F8QuPL = Math["PI"]; - typeof ((eUf7Wle = Math["pow"](yLu5YB1, 2)), (XVYH4D = F8QuPL * eUf7Wle)); - return XVYH4D; -} -``` - -### `opaquePredicates` - -An Opaque Predicate that is evaluated at runtime, this can confuse reverse engineers from understanding your code. (`true/false/0-1`) - -### `shuffle` - -Shuffles the initial order of arrays. The order is brought back to the original during runtime. (`"hash"/true/false/0-1`) - -| Mode | Description | -| --- | --- | -| `"hash"`| Array is shifted based on hash of the elements | -| `true`| Arrays are shifted *n* elements, unshifted at runtime | -| `false` | Feature disabled | - -### `stack` - -Local variables are consolidated into a rotating array. (`true/false/0-1`) - -[Similar to Jscrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) - -```js -// Input -function add3(x, y, z){ - return x + y + z; -} - -// Output -function iVQoGQD(...iVQoGQD){ - ~(iVQoGQD.length = 3, iVQoGQD[215] = iVQoGQD[2], iVQoGQD[75] = 227, iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - 75)] = iVQoGQD[75] - (iVQoGQD[75] - 239), iVQoGQD[iVQoGQD[iVQoGQD[75] - 164] - 127] = iVQoGQD[iVQoGQD[75] - 238], iVQoGQD[iVQoGQD[75] - 104] = iVQoGQD[75] - 482, iVQoGQD[iVQoGQD[135] + 378] = iVQoGQD[iVQoGQD[135] + 318] - 335, iVQoGQD[21] = iVQoGQD[iVQoGQD[135] + 96], iVQoGQD[iVQoGQD[iVQoGQD[75] - 104] - (iVQoGQD[75] - 502)] = iVQoGQD[iVQoGQD[75] - 164] - 440); - return iVQoGQD[75] > iVQoGQD[75] + 90 ? iVQoGQD[iVQoGQD[135] - (iVQoGQD[135] + 54)] : iVQoGQD[iVQoGQD[135] + 117] + iVQoGQD[iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - (iVQoGQD[75] - 104))] - (iVQoGQD[135] - 112)] + iVQoGQD[215]; -}; -``` - -## High preset -```js -{ - target: "node", - preset: "high", - - calculator: true, - compact: true, - hexadecimalNumbers: true, - controlFlowFlattening: 0.75, - deadCode: 0.2, - dispatcher: true, - duplicateLiteralsRemoval: 0.75, - flatten: true, - globalConcealing: true, - identifierGenerator: "randomized", - minify: true, - movedDeclarations: true, - objectExtraction: true, - opaquePredicates: 0.75, - renameVariables: true, - renameGlobals: true, - shuffle: { hash: 0.5, true: 0.5 }, - stack: true, - stringConcealing: true, - stringCompression: true, - stringEncoding: true, - stringSplitting: 0.75, - - // Use at own risk - rgf: false -} -``` - -## Medium preset -```js -{ - target: "node", - preset: "medium", - - calculator: true, - compact: true, - hexadecimalNumbers: true, - controlFlowFlattening: 0.5, - deadCode: 0.025, - dispatcher: 0.75, - duplicateLiteralsRemoval: 0.5, - globalConcealing: true, - identifierGenerator: "randomized", - minify: true, - movedDeclarations: true, - objectExtraction: true, - opaquePredicates: 0.5, - renameVariables: true, - renameGlobals: true, - shuffle: true, - stack: 0.5, - stringConcealing: true, - stringSplitting: 0.25 -} -``` - -## Low Preset - -```js -{ - target: "node", - preset: "low", - - calculator: true, - compact: true, - hexadecimalNumbers: true, - controlFlowFlattening: 0.25, - deadCode: 0.01, - dispatcher: 0.5, - duplicateLiteralsRemoval: true, - identifierGenerator: "randomized", - minify: true, - movedDeclarations: true, - objectExtraction: true, - opaquePredicates: 0.1, - renameVariables: true, - renameGlobals: true, - stringConcealing: true -} -``` - -## Locks - -You must enable locks yourself, and configure them to your needs. - -```js -{ - target: "node", - lock: { - integrity: true, - selfDefending: true, - tamperProtection: true, - domainLock: ["mywebsite.com"], - osLock: ["windows", "linux"], - browserLock: ["firefox"], - startDate: new Date("Feb 1 2021"), - endDate: new Date("Mar 1 2021"), - antiDebug: true, - - // crashes browser - countermeasures: true, - - // or custom callback (pre-obfuscated name) - countermeasures: "onLockDetected" - } -} -``` - -## Optional features - -These features are experimental or a security concern. +## Documentation -```js -{ - target: "node", - rgf: true, // (security concern) - - // set to false for web-related scripts - renameGlobals: false, - - // custom implementation - identifierGenerator: function(){ - return "myvar_" + (counter++); - }, - - // Modified functions will retain the correct `function.length` property. - // Enabled by default. - preserveFunctionLength: false -} -``` +Read the [documentation](https://js-confuser.com/docs) for everything to know about JS-Confuser, including: -## Percentages - -Most settings allow percentages to control the frequency of the transformation. Fine-tune the percentages to keep file size down, and performance high. - -```js -{ - target: "node", - controlFlowFlattening: true, // equal to 1, which is 100% (slow) - - controlFlowFlattening: 0.5, // 50% - controlFlowFlattening: 0.01 // 1% -} -``` - -## Probabilities - -Mix modes using an object with key-value pairs to represent each mode's percentage. - -```js -{ - target: "node", - identifierGenerator: { - "hexadecimal": 0.25, // 25% each - "randomized": 0.25, - "mangled": 0.25, - "number": 0.25 - }, - - shuffle: {hash: 0.5, true: 0.5} // 50% hash, 50% normal -} -``` - -## Custom Implementations - -```js -{ - target: "node", - - // avoid renaming a certain variable - renameVariables: name=>name!="jQuery", - - // custom variable names - identifierGenerator: ()=>{ - return "_0x" + Math.random().toString(16).slice(2, 8); - }, - - // force encoding on a string - stringConcealing: (str)=>{ - if (str=="https://mywebsite.com/my-secret-api"){ - return true; - } - - // 60% - return Math.random() < 0.6; - }, - - // set limits - deadCode: ()=>{ - dead++; - - return dead < 50; - } -} -``` - -## Potential Issues - -1. String Encoding can corrupt files. Disable `stringEncoding` if this happens. -2. Dead Code can bloat file size. Reduce or disable `deadCode`. -3. Rename Globals can break web-scripts. - i. Disable `renameGlobals` or - ii. Refactor your code - ```js - // Avoid doing this - var myGlobalFunction = ()=>console.log("Called"); - - // Do this instead - window.myGlobalFunction = ()=>console.log("Called"); - ``` - -## File size and Performance - -Obfuscation can bloat file size and negatively impact performance. Avoid using the following: - -| Option | Description | -| --- | --- | -| `deadCode` | Bloats file size. Use low percentages. | -| `stringSplitting`, `stringEncoding` | Bloats file size. Avoid using these altogether. | -| `controlFlowFlattening` | Significant performance impact. Use very low percentage when source code is large. | -| `dispatcher` | Slow performance. Use low percentage. | - -## "The obfuscator broke my code!" - -Try disabling features in the following order: -1. `flatten` -2. `stack` -3. `dispatcher` - -If the error continues then [open an issue](https://github.com/MichaelXF/js-confuser/issues). +- All the obfuscator options +- API methods +- Presets ## Bug report @@ -947,39 +74,3 @@ Please [open an issue](https://github.com/MichaelXF/js-confuser/issues) with the ## Feature request Please [open an issue](https://github.com/MichaelXF/js-confuser/issues) and be descriptive. Don't submit any PRs until approved. - -## JsConfuser vs. Javascript-obfuscator - -Javascript-obfuscator ([https://obfuscator.io](https://obfuscator.io)) is the popular choice for JS obfuscation. This means more attackers are aware of their strategies. JSConfuser provides unique features and is lesser-known. - -Automated deobfuscators are aware of [https://obfuscator.io](https://obfuscator.io)'s techniques: - -https://www.youtube.com/watch?v=_UIqhaYyCMI - -However, the dev is [quick to fix these](https://github.com/LostMyCode/javascript-deobfuscator/issues/12). The one above no longer works. - -Alternatively, you could go the paid-route with [Jscrambler.com (enterprise only)](https://jscrambler.com/) or [PreEmptive.com](https://www.preemptive.com/products/jsdefender/online-javascript-obfuscator-demo) - -I've included several alternative obfuscators in the [`samples/`](https://github.com/MichaelXF/js-confuser/tree/master/samples) folder. They are all derived from the `input.js` file. - -## Debugging - -Enable logs to view the obfuscator's state. - -```js -{ - target: "node", - verbose: true -} -``` - -## About the internals - -This obfuscator depends on two libraries to work: `acorn` and `escodegen` - -- `acorn` is responsible for parsing source code into an AST. -- `escodegen` is responsible for generating source from modified AST. - -The tree is modified by transformations, which each traverse the entire tree. -Properties starting with `$` are for saving information (typically circular data), -these properties are deleted before output. diff --git a/babel.config.js b/babel.config.js index cfbe591..e70ae90 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,12 +1,6 @@ -// babel.config.js -// Jest uses this & for building module.exports = { presets: [ - ["@babel/preset-env", { targets: "defaults, not ie 11, not ie_mob 11" }], - "@babel/preset-typescript", - ], - plugins: [ - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-optional-chaining", + "@babel/preset-env", // For handling ES6+ syntax + "@babel/preset-typescript", // For handling TypeScript syntax ], }; diff --git a/babel.register.js b/babel.register.js new file mode 100644 index 0000000..708a174 --- /dev/null +++ b/babel.register.js @@ -0,0 +1,6 @@ +require("@babel/register")({ + extensions: [".ts", ".tsx", ".js", ".jsx"], +}); + +// Now run the main TypeScript file +require("./dev.ts"); diff --git a/dev.js b/dev.js deleted file mode 100644 index 6213356..0000000 --- a/dev.js +++ /dev/null @@ -1,8 +0,0 @@ -require("@babel/register")({ - presets: ["@babel/preset-typescript"], - extensions: [".js", ".ts"], - cache: true, - retainLines: true, -}); - -module.exports = require("./dev.ts"); diff --git a/package.json b/package.json index 050d8fa..127ab21 100644 --- a/package.json +++ b/package.json @@ -1,54 +1,117 @@ { "name": "js-confuser", - "version": "1.7.3", - "description": "JavaScript Obfuscation Tool.", - "main": "dist/index.js", - "types": "index.d.ts", + "version": "2.0.0-alpha", + "main": "index.js", "scripts": { - "dev": "node ./dev.js", - "build": "./node_modules/.bin/babel src --out-dir dist --copy-files --extensions \".ts\"", + "build": "tsc && babel src --out-dir dist --extensions \".ts,.tsx\"", + "dev": "node babel.register.js", "test": "jest --forceExit", "test:coverage": "jest --coverage", - "prepublishOnly": "npm run build & npm run test --forceExit" + "prepublishOnly": "npm run build" }, - "keywords": [ - "obfuscator", - "obfuscation", - "uglify", - "code protection", - "javascript obfuscator", - "js obfuscator" - ], + "keywords": [], "author": "MichaelXF", "license": "MIT", - "dependencies": { - "acorn": "^8.12.1", - "escodegen": "^2.1.0" - }, "devDependencies": { - "@babel/cli": "^7.17.6", - "@babel/core": "^7.17.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.16.7", - "@babel/register": "^7.17.7", - "@types/jest": "^26.0.24", - "@types/node": "^15.14.9", - "babel-jest": "^26.6.3", - "jest": "^29.5.0" + "@babel/cli": "^7.24.8", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-decorators": "^7.24.7", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/preset-env": "^7.25.3", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "@eslint/js": "^9.9.0", + "@types/babel__core": "^7.20.5", + "@types/jest": "^29.5.12", + "@types/node": "^22.2.0", + "globals": "^15.9.0", + "jest": "^29.7.0", + "typescript": "^5.5.4", + "typescript-eslint": "^8.1.0" }, - "repository": { - "type": "git", - "url": "https://github.com/MichaelXF/js-confuser" - }, - "bugs": { - "url": "https://github.com/MichaelXF/js-confuser/issues" + "dependencies": { + "ansi-styles": "^3.2.1", + "anymatch": "^3.1.3", + "babel-plugin-polyfill-corejs2": "^0.4.11", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.2", + "balanced-match": "^1.0.2", + "binary-extensions": "^2.3.0", + "brace-expansion": "^1.1.11", + "braces": "^3.0.3", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001651", + "chalk": "^2.4.2", + "chokidar": "^3.6.0", + "color-convert": "^1.9.3", + "color-name": "^1.1.3", + "commander": "^6.2.1", + "concat-map": "^0.0.1", + "convert-source-map": "^2.0.0", + "core-js-compat": "^3.38.0", + "debug": "^4.3.6", + "electron-to-chromium": "^1.5.6", + "escalade": "^3.1.2", + "escape-string-regexp": "^1.0.5", + "esutils": "^2.0.3", + "fill-range": "^7.1.1", + "fs-readdir-recursive": "^1.1.0", + "fs.realpath": "^1.0.0", + "fsevents": "^2.3.3", + "function-bind": "^1.1.2", + "gensync": "^1.0.0-beta.2", + "glob": "^7.2.3", + "glob-parent": "^5.1.2", + "has-flag": "^3.0.0", + "hasown": "^2.0.2", + "inflight": "^1.0.6", + "inherits": "^2.0.4", + "is-binary-path": "^2.1.0", + "is-core-module": "^2.15.0", + "is-extglob": "^2.1.1", + "is-glob": "^4.0.3", + "is-number": "^7.0.0", + "js-tokens": "^4.0.0", + "jsesc": "^2.5.2", + "json5": "^2.2.3", + "lodash.debounce": "^4.0.8", + "lru-cache": "^5.1.1", + "make-dir": "^2.1.0", + "minimatch": "^3.1.2", + "ms": "^2.1.2", + "node-releases": "^2.0.18", + "normalize-path": "^3.0.0", + "once": "^1.4.0", + "path-is-absolute": "^1.0.1", + "path-parse": "^1.0.7", + "picocolors": "^1.0.1", + "picomatch": "^2.3.1", + "pify": "^4.0.1", + "readdirp": "^3.6.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.1", + "regenerator-runtime": "^0.14.1", + "regenerator-transform": "^0.15.2", + "regexpu-core": "^5.3.2", + "regjsparser": "^0.9.1", + "resolve": "^1.22.8", + "semver": "^6.3.1", + "slash": "^2.0.0", + "supports-color": "^5.5.0", + "supports-preserve-symlinks-flag": "^1.0.0", + "to-fast-properties": "^2.0.0", + "to-regex-range": "^5.0.1", + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0", + "unicode-property-aliases-ecmascript": "^2.1.0", + "update-browserslist-db": "^1.1.0", + "wrappy": "^1.0.2", + "yallist": "^3.1.1" }, - "homepage": "https://js-confuser.com", - "jest": { - "coverageReporters": [ - "html" - ] - } + "description": "" } diff --git a/samples/example.js b/samples/example.js deleted file mode 100644 index 2f6eec3..0000000 --- a/samples/example.js +++ /dev/null @@ -1,15 +0,0 @@ -// Import `js-confuser` -const JsConfuser = require("js-confuser"); -const { readFileSync, writeFileSync } = require("fs"); - -// Read the file's code -const file = readFileSync("./input.js", "utf-8"); - -// Obfuscate the code -JsConfuser.obfuscate(file, { - target: "node", - preset: "high", -}).then((obfuscated) => { - // Write output to file - writeFileSync("./high.js", obfuscated, { encoding: "utf-8" }); -}); diff --git a/samples/high.js b/samples/high.js deleted file mode 100644 index 4f9dfd9..0000000 --- a/samples/high.js +++ /dev/null @@ -1 +0,0 @@ -var lm1RhP7,bpUy3x=B48k5jJ.call(this),DZjCQp=function(){return lm1RhP7=>{return bpUy3x[lm1RhP7-0xef]}}(),G20O6C=function(...lm1RhP7){var DZjCQp=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7+0x6c]}})();!(lm1RhP7.length=DZjCQp(-0x52),lm1RhP7[DZjCQp(-0x6c)]=lm1RhP7[DZjCQp(-0x6b)]);return lm1RhP7[0x1](lm1RhP7[DZjCQp(-0x6c)]())}(XGxBk7,z6DjMr),TtonEk=[],S0leuD6=DZjCQp(0xf0),uQhOlq=function(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7-0xe2]}})();!(lm1RhP7.length=0x0,lm1RhP7[DZjCQp(0xf1)]=0x3f,lm1RhP7[lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[DZjCQp(0xf1)]-DZjCQp(0xf2)))]=['\u007b\u0030\u0078\u0037\u0045\u0035\u0038\u0033\u0037\u0033\u0032\u002c\u0030\u0078\u0036\u0038\u0038\u002c\u0030\u0078\u0037\u0045\u0037\u0033\u0033\u0039\u0033\u0037\u002c\u0030\u0078\u0036\u0031\u0031\u002c\u0030\u0078\u0037\u0045\u0036\u0041\u0034\u0043\u0033\u0034\u002c\u0030\u0078\u0036\u0034\u0032\u002c\u0030\u0078\u0037\u0045\u0036\u0039\u0034\u0036\u002c\u0030\u0078\u0038\u0031\u007d','\x7b\x30\x78\x37\x45\x36\x46\x35\x37\x33\x38\x2c\x30\x78\x36\x38\x31\x2c\x30\x78\x37\x45\x36\x42\x36\x39\x34\x46\x2c\x30\x78\x36\x35\x30\x7d',ZEYuX4(lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[lm1RhP7[0x4f]+DZjCQp(0xf3)]-DZjCQp(0xf2)))),ZEYuX4(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+DZjCQp(0xf3)]-DZjCQp(0xf1))]-DZjCQp(0xf1))]-0x3e),'\x2e\x2a\x36\x37\x3f\x3d\x3c\x27\x27\x32\x24\x31','\x7b\x30\x78\x37\x45\x35\x38\x34\x31\x2c\x30\x78\x34\x30\x39\x2c\x30\x78\x37\x45\x37\x31\x35\x35\x33\x36\x2c\x30\x78\x36\x30\x41\x7d','\x2b\x31\x39\x37\x2b\x39\x2c\x3a\x2e\x3d','\u007b\u0030\u0078\u0037\u0045\u0036\u0043\u0036\u0033\u0036\u0031\u002c\u0030\u0078\u0036\u0038\u0031\u002c\u0030\u0078\u0037\u0045\u0036\u0043\u002c\u0030\u0078\u0038\u007d',ZEYuX4(lm1RhP7[lm1RhP7[G20O6C(0xe4)]-(lm1RhP7[G20O6C(0xe4)]-(lm1RhP7[0x4f]+G20O6C(0xe6)))]-(lm1RhP7[0x4f]-(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+DZjCQp(0xf3)]-(lm1RhP7[0x4f]-0x2)))),ZEYuX4(lm1RhP7[DZjCQp(0xf1)]-0x3c),'\u007b\u0030\u0078\u0037\u0045\u0036\u0046\u0036\u0043\u0036\u0037\u002c\u0030\u0078\u0036\u0031\u0031\u007d','\u007b\u0030\u0078\u0037\u0045\u0033\u0031\u0032\u0042\u0032\u0030\u002c\u0030\u0078\u0036\u0034\u0032\u002c\u0030\u0078\u0037\u0045\u0033\u0031\u0032\u0030\u002c\u0030\u0078\u0034\u0030\u0038\u007d',ZEYuX4(lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+0x10]-(lm1RhP7[DZjCQp(0xf1)]-DZjCQp(0x15d)))),ZEYuX4(lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[DZjCQp(0xf1)]-G20O6C(0x14a))),ZEYuX4(G20O6C(0xf3)),ZEYuX4(G20O6C(0x109)),ZEYuX4(lm1RhP7[G20O6C(0xe4)]-0x37),ZEYuX4(lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+0x10]+0x10]+(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+DZjCQp(0xf3)]-(lm1RhP7[G20O6C(0xe4)]-G20O6C(0xe6)))]-DZjCQp(0x11f)),ZEYuX4(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+G20O6C(0xe6)]-(lm1RhP7[lm1RhP7[0x4f]+0x10]-0xa)),'\x7b\x30\x78\x37\x45\x36\x46\x36\x43\x36\x37\x2c\x30\x78\x36\x38\x38\x2c\x30\x78\x37\x45\x36\x43\x36\x32\x36\x31\x2c\x30\x78\x36\x38\x31\x7d',ZEYuX4(G20O6C(0x103)),ZEYuX4(0xc),ZEYuX4(0xd),'\u007b\u0030\u0078\u0037\u0045\u0037\u0032\u0037\u0030\u0036\u0046\u002c\u0030\u0078\u0036\u0031\u0031\u002c\u0030\u0078\u0037\u0045\u0037\u0033\u0036\u0035\u0036\u0033\u002c\u0030\u0078\u0036\u0038\u0038\u007d',ZEYuX4(G20O6C(0xff)),ZEYuX4(DZjCQp(0xff)),'\x7b\x30\x78\x37\x45\x36\x36\x35\x46\x2c\x30\x78\x34\x34\x30\x2c\x30\x78\x37\x45\x36\x43\x36\x39\x36\x35\x2c\x30\x78\x36\x31\x31\x7d','\x2e\x3a\x31\x37\x3b\x3a\x29',ZEYuX4(DZjCQp(0xf3)),'\x2f\x22\x31\x38\x25\x3d\x3c\x26\x2a\x25',ZEYuX4(0x11),'\x7b\x30\x78\x37\x45\x36\x46\x36\x43\x36\x31\x2c\x30\x78\x36\x31\x31\x2c\x30\x78\x37\x45\x37\x34\x2c\x30\x78\x38\x7d',ZEYuX4(DZjCQp(0xfe)),ZEYuX4(lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+G20O6C(0xe6)]-(lm1RhP7[G20O6C(0xe4)]-0x13)),ZEYuX4(0x14),'\x7b\x30\x78\x37\x45\x36\x46\x34\x32\x2c\x30\x78\x34\x34\x38\x2c\x30\x78\x37\x45\x36\x43\x36\x35\x36\x31\x2c\x30\x78\x36\x30\x41\x7d',ZEYuX4(lm1RhP7[0x4f]-0x2a),'\u007b\u0030\u0078\u0037\u0045\u0036\u0041\u0036\u0032\u0034\u0046\u002c\u0030\u0078\u0036\u0038\u0038\u002c\u0030\u0078\u0037\u0045\u0037\u0034\u0036\u0035\u0036\u0033\u002c\u0030\u0078\u0036\u0038\u0031\u007d',ZEYuX4(lm1RhP7[lm1RhP7[G20O6C(0xe4)]+DZjCQp(0xf3)]-G20O6C(0xe7)),ZEYuX4(G20O6C(0xef)),ZEYuX4(lm1RhP7[lm1RhP7[0x4f]+G20O6C(0xe6)]-0x27),ZEYuX4(lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[G20O6C(0xe4)]-G20O6C(0x121))),ZEYuX4(G20O6C(0x11f)),'\x7b\x30\x78\x37\x45\x36\x43\x36\x35\x36\x33\x2c\x30\x78\x36\x35\x30\x2c\x30\x78\x37\x45\x37\x32\x36\x31\x35\x34\x2c\x30\x78\x36\x31\x31\x7d','\u002e\u0026\u0037\u0037\u002b\u003c\u003c\u0036\u002f\u0031','\u003c\u007e\u0046\u0028\u004b\u0047\u0062\u0044\u004b\u004b\u0048\u0031\u0047\u0025\u0023\u0031\u007e\u003e','\u007b\u0030\u0078\u0037\u0045\u0037\u0034\u0037\u0033\u0036\u0035\u002c\u0030\u0078\u0036\u0038\u0031\u002c\u0030\u0078\u0037\u0045\u0037\u0034\u0036\u0045\u0034\u0039\u002c\u0030\u0078\u0036\u0038\u0038\u007d',ZEYuX4(G20O6C(0xee)),ZEYuX4(0x1c),'\u003c\u007e\u0044\u004b\u004b\u0048\u0031\u0047\u0024\u0074\u007e\u003e',ZEYuX4(lm1RhP7[DZjCQp(0xf1)]-DZjCQp(0x10e)),'\x7b\x30\x78\x37\x45\x36\x39\x36\x35\x36\x34\x2c\x30\x78\x36\x38\x31\x2c\x30\x78\x37\x45\x37\x34\x36\x35\x36\x31\x2c\x30\x78\x36\x35\x30\x7d','\x7b\x30\x78\x37\x45\x36\x44\x36\x35\x2c\x30\x78\x34\x30\x39\x2c\x30\x78\x37\x45\x36\x39\x36\x34\x36\x31\x2c\x30\x78\x36\x31\x31\x7d','\x7b\x30\x78\x37\x45\x37\x35\x37\x31\x36\x35\x2c\x30\x78\x36\x31\x31\x2c\x30\x78\x37\x45\x37\x35\x36\x35\x34\x44\x2c\x30\x78\x36\x30\x41\x7d','\u003c\u007e\u0042\u006b\u0029\u0031\u0025\u0046\u0043\u003e\u007e\u003e',ZEYuX4(DZjCQp(0x124)),ZEYuX4(0x1f),ZEYuX4(DZjCQp(0x13a)),ZEYuX4(DZjCQp(0x129)),ZEYuX4(lm1RhP7[0x4f]-0x1d),ZEYuX4(DZjCQp(0x112))],lm1RhP7[lm1RhP7[DZjCQp(0xf1)]-(lm1RhP7[DZjCQp(0xf1)]-0x68)]=lm1RhP7[lm1RhP7[lm1RhP7[0x4f]+DZjCQp(0xf3)]-DZjCQp(0xf2)]);return lm1RhP7[DZjCQp(0xf1)]>lm1RhP7[lm1RhP7[0x4f]+0x10]-(lm1RhP7[G20O6C(0xe4)]-DZjCQp(0x13c))?lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0xe4)]-(lm1RhP7[lm1RhP7[G20O6C(0xe4)]+G20O6C(0xe6)]-(lm1RhP7[G20O6C(0xe4)]+DZjCQp(0xf3)))]-0x90]:(S0leuD6?lm1RhP7[lm1RhP7[DZjCQp(0xf1)]+DZjCQp(0xf4)].pop():S0leuD6++,lm1RhP7[lm1RhP7[lm1RhP7[0x4f]+(lm1RhP7[G20O6C(0xe4)]-DZjCQp(0x117))]-(lm1RhP7[0x4f]-G20O6C(0x126))])}(),ZR5X1e=XqU_Fqx(DZjCQp(0x102)),DN_p_i=iEpc2w(0x39),dxqKfQ6=XqU_Fqx(DZjCQp(0xf8)),LZhCc0j=XqU_Fqx.apply(this,[0x38]),zk1d4V=XqU_Fqx(0x36),z6_Uude=bHT8644(DZjCQp(0x120)),ggz61J=XqU_Fqx.apply(this,[DZjCQp(0xf5)]),BX5JJvf=bHT8644.apply(this,[0x34]),qgs9ww=XqU_Fqx(DZjCQp(0xf5)),zyLi4ua=bHT8644.call(this,DZjCQp(0xf6)),TUzu2G=XqU_Fqx.apply(this,[DZjCQp(0xf7)]),F9LAG7=bHT8644.apply(this,[DZjCQp(0xf6)]),J_CyoIf=XqU_Fqx.apply(this,[DZjCQp(0xf7)]),M2NlKG=XqU_Fqx.call(this,DZjCQp(0xf8)),ka59Sk=XqU_Fqx.call(this,DZjCQp(0x121)),JHTO0V8=iEpc2w.apply(this,[DZjCQp(0xf9)]),JvXCxIp=bHT8644.apply(this,[DZjCQp(0xfa)]),s60sQ_=iEpc2w.call(this,DZjCQp(0xf9)),Rfoubp=bHT8644.apply(this,[DZjCQp(0xfa)]),aT0942k=XqU_Fqx(0x2a),KV16GX=XqU_Fqx.apply(this,[0x28]),EKys3x=XqU_Fqx.call(this,0x28),tpwgSgZ=XqU_Fqx.apply(this,[0x27]),pkpgtg=XqU_Fqx.apply(this,[0x27]),UiCVZ0W=XqU_Fqx.apply(this,[DZjCQp(0xf8)]),hWLc2a=XqU_Fqx(DZjCQp(0x115)),lOPmZV=bHT8644.apply(this,[DZjCQp(0x114)]),Xwvw78=iEpc2w.apply(this,[DZjCQp(0x101)]),wNs62u=bHT8644.call(this,0x23),y6mcAH=XqU_Fqx.apply(this,[DZjCQp(0xf8)]),Y5TyGif=iEpc2w.apply(this,[0x1d]),KDkpl0g=iEpc2w.call(this,0x1c),ynKGk8n=iEpc2w.call(this,0x1c),_cm54bf=bHT8644.call(this,DZjCQp(0xfd)),Q9nRWFJ=iEpc2w.apply(this,[DZjCQp(0xfb)]),S_IavC=bHT8644.call(this,0x1a),bMcuvu=iEpc2w(DZjCQp(0xfb)),syDJYsE=bHT8644.apply(this,[DZjCQp(0xfc)]),Clb9SQE=XqU_Fqx.apply(this,[DZjCQp(0x143)]),JuvoFHk=XqU_Fqx.apply(this,[DZjCQp(0xf8)]),EemGgL=XqU_Fqx.apply(this,[DZjCQp(0x111)]),ChzLf81=XqU_Fqx.call(this,0x14),bw0uqT6=bHT8644(DZjCQp(0xfd)),Dduo6Tp=XqU_Fqx(DZjCQp(0xfe)),ZvCZ3z=XqU_Fqx.apply(this,[0x11]),r2JrSH=bHT8644.apply(this,[DZjCQp(0xff)]),WOzacQX=bHT8644(DZjCQp(0x123)),zLmxDY=iEpc2w.apply(this,[0xc]),pffyew=bHT8644.call(this,DZjCQp(0x10f));lm1RhP7=XqU_Fqx(DZjCQp(0xf8));var BjS0ix3=iEpc2w.apply(this,[DZjCQp(0x100)]),D_hcEq=function(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7+0x44]}})();!(lm1RhP7.length=DZjCQp(0xf0),lm1RhP7[DZjCQp(0xef)]=0x18,lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0xef)]+0x3c]-DZjCQp(0x127)]=iEpc2w(lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0xef)]+0x3c]+(lm1RhP7[lm1RhP7[G20O6C(-0x44)]+0x3c]+G20O6C(-0x32))]-(lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0xef)]+G20O6C(-0x31)]+G20O6C(-0x31)]-(lm1RhP7[0x54]-0x14))),lm1RhP7[lm1RhP7[G20O6C(-0x44)]+0x3c]=lm1RhP7[lm1RhP7[G20O6C(-0x44)]+0x3c]-(lm1RhP7[G20O6C(-0x44)]-(lm1RhP7[DZjCQp(0xef)]-DZjCQp(0x15a))),lm1RhP7[lm1RhP7[0x54]-(lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x44)]+DZjCQp(0x103)]+DZjCQp(0x103)]-G20O6C(-0x29))]={bUoplwt:lm1RhP7[lm1RhP7[G20O6C(-0x44)]+DZjCQp(0x103)]-(lm1RhP7[lm1RhP7[DZjCQp(0xef)]-(lm1RhP7[DZjCQp(0xef)]-DZjCQp(0xef))]-0x41),DSwyn9:lm1RhP7[lm1RhP7[DZjCQp(0xef)]+0x6e],b4gdoS:bHT8644.call(this,lm1RhP7[G20O6C(-0x44)]+DZjCQp(0x137)),kPZ_Mvp:lm1RhP7[G20O6C(-0x44)]+0xc3,jqhZopp:BjS0ix3});return lm1RhP7[lm1RhP7[DZjCQp(0xef)]+G20O6C(-0x30)]>lm1RhP7[DZjCQp(0xef)]-(lm1RhP7[lm1RhP7[0x54]+0xc2]+DZjCQp(0x10d))?lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x44)]-(lm1RhP7[G20O6C(-0x44)]-G20O6C(-0x44))]-(lm1RhP7[0x54]+0xcd)]:lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0xef)]+0xc2]+(lm1RhP7[lm1RhP7[DZjCQp(0xef)]+0xc2]+0xdd)]}(),uTxoRnF,g97zGF,X3KdEQ1=function(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7-0x3a]}})();typeof(lm1RhP7.length=DZjCQp(0xf0),lm1RhP7[DZjCQp(0x104)]=lm1RhP7[0x0],lm1RhP7[DZjCQp(0x104)]=function(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return DZjCQp(lm1RhP7-0xbd)}})();+(lm1RhP7.length=DZjCQp(0xf0),lm1RhP7[DZjCQp(0x105)]=-G20O6C(0x1c4),lm1RhP7[lm1RhP7[0x9f]-(lm1RhP7[DZjCQp(0x105)]-DZjCQp(0xf0))]=bHT8644.apply(this,[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x1c2)]+0x189]+DZjCQp(0x106)]+(lm1RhP7[0x9f]+0x1db)]),lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x105)]+0x189]+DZjCQp(0x106)]+0x10f]=lm1RhP7[lm1RhP7[DZjCQp(0x105)]+DZjCQp(0x107)]);try{return global}catch(bpUy3x){var S0leuD6=(()=>{return lm1RhP7=>{return DZjCQp(lm1RhP7+0x6a)}})();return TtonEk[lm1RhP7[lm1RhP7[lm1RhP7[S0leuD6(0x9b)]+S0leuD6(0x9c)]+(lm1RhP7[0x9f]-(lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[0x9f]-(lm1RhP7[DZjCQp(0x105)]-G20O6C(0x1c2))]+G20O6C(0x1c3)]-(lm1RhP7[0x9f]-(lm1RhP7[DZjCQp(0x105)]+S0leuD6(0x9c)))]-(lm1RhP7[0x9f]+0x1f9)))]](this)}},lm1RhP7[G20O6C(0x53)]=lm1RhP7[0x56]);var TtonEk=function(){try{return this}catch(lm1RhP7){return null}};return g97zGF=TtonEk[ZEYuX4(DZjCQp(0x101))](this,KJnLbu),uTxoRnF=lm1RhP7[DZjCQp(0x108)][ZEYuX4(DZjCQp(0x101))](this)}[ZEYuX4(DZjCQp(0x101))]();function PVWm2YQ(...lm1RhP7){!(lm1RhP7.length=DZjCQp(0x109),lm1RhP7[DZjCQp(0x10b)]=lm1RhP7[DZjCQp(0x10a)]);switch(N39zwSm){case!(D_hcEq.jqhZopp[XqU_Fqx.apply(this,[0x8])](0x1)=='\u0031')?null:-0x229:return lm1RhP7[DZjCQp(0xf0)]+lm1RhP7[DZjCQp(0x10b)]}}var Me_hRt,iLEFfw,QufFzA={},zJdAV5=DZjCQp(0x156),dZeXrZD=0x63,BzVgOkX=-0xb2;while(zJdAV5+dZeXrZD+BzVgOkX!=DZjCQp(0x125)&&D_hcEq.DSwyn9[lm1RhP7](DZjCQp(0x109))==DZjCQp(0x138)){var JKMqCi,iomgxy=function(){return lm1RhP7=>{return bpUy3x[lm1RhP7+0x60]}}(),uPlghs=iEpc2w.call(this,DZjCQp(0xf3)),oF2Y49=XqU_Fqx.apply(this,[DZjCQp(0x10c)]),heLpnI=bHT8644.apply(this,[DZjCQp(0x10d)]),J_YJjCb=bHT8644.apply(this,[iomgxy(-0x42)]),BhpXhN=XqU_Fqx.apply(this,[0x8]);JKMqCi=(zJdAV5+dZeXrZD+BzVgOkX)*-DZjCQp(0x10e)+iomgxy(-0x27);switch(JKMqCi){case!(D_hcEq.bUoplwt>-DZjCQp(0xf5))?null:-0xaa9:case!(D_hcEq.jqhZopp[BhpXhN](0x1)==iomgxy(-0x1d))?0xdf:iomgxy(-0x37):var N39zwSm,lc7spp;+(QufFzA[pffyew]=J_YJjCb,N39zwSm=void 0x0,lc7spp=PVWm2YQ(-iomgxy(0xd)>zJdAV5?-0x2d:DZjCQp(0x10a),dZeXrZD-0x62,N39zwSm=-(dZeXrZD+0x1c6)),zJdAV5+=0xe4>BzVgOkX?DZjCQp(0x13d)>dZeXrZD?-0xa3:BzVgOkX+0x193:-0xd8,dZeXrZD*=dZeXrZD-DZjCQp(0x163),dZeXrZD-=-0x58zJdAV5?DZjCQp(0x109):-DZjCQp(0x145),BzVgOkX-=zJdAV5+(zJdAV5-0x102));break;case!(D_hcEq.bUoplwt>-iomgxy(-0x5a))?null:-0x2d3:void(KJnLbu(iomgxy(-0x3c))[QufFzA[bHT8644.apply(this,[DZjCQp(0x10f)])]](PVWm2YQ(bHT8644.call(this,DZjCQp(0x110))+'\u0069\u0073\u0020',lc7spp,N39zwSm=-(dZeXrZD+0x1ff))),zJdAV5+=BzVgOkX-iomgxy(-0x3e),dZeXrZD+=dZeXrZD-iomgxy(0x0),BzVgOkX+=-0x5e>zJdAV5?DZjCQp(0x112):dZeXrZD+0x7b);break;case-0x2b:void(KJnLbu(iomgxy(-0x3c))[heLpnI](zLmxDY+WOzacQX+oF2Y49+ZEYuX4(iomgxy(-0x3b))+r2JrSH+ZEYuX4(DZjCQp(0x115))+uPlghs),zJdAV5+=dZeXrZD-DZjCQp(0x116),dZeXrZD+=BzVgOkX+(-iomgxy(-0x38)DZjCQp(0x10f))?null:-0xacb:function XB_mmwQ(){var [lm1RhP7,bpUy3x]=iLEFfw,[G20O6C]=bpUy3x;return[zJdAV5,dZeXrZD,BzVgOkX]=lm1RhP7,Me_hRt=(G20O6C=N39zwSm+(N39zwSm=G20O6C,DZjCQp(0xf0)),G20O6C)}XB_mmwQ()}}function KJnLbu(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7+0x50]}})();+(lm1RhP7.length=DZjCQp(0x10a),lm1RhP7[0x72]=-G20O6C(0x22),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x11a)]+0x89]=XqU_Fqx.apply(this,[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[0x72]-0x72)]+(lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[0x72]-0xfa))]+(lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x11a)]+DZjCQp(0x11a)]-(lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[DZjCQp(0x119)]+G20O6C(0x1))))]+(lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x134))]),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x11a)]+0x125]=lm1RhP7[DZjCQp(0x116)],lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0x8a]=iEpc2w.apply(this,[0x3a]),lm1RhP7[lm1RhP7[lm1RhP7[0x72]+DZjCQp(0x11a)]+G20O6C(-0x24)]=lm1RhP7[DZjCQp(0x119)]+DZjCQp(0xff),lm1RhP7[lm1RhP7[lm1RhP7[0x72]+G20O6C(-0x24)]-(lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[G20O6C(-0x23)]+0x7c))]=iEpc2w.apply(this,[lm1RhP7[DZjCQp(0x11c)]+DZjCQp(0x12f)]),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]+DZjCQp(0x11d)]-(lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xfa]+DZjCQp(0x110))]=XqU_Fqx.apply(this,[lm1RhP7[G20O6C(-0x23)]+(lm1RhP7[0x2e]-(lm1RhP7[G20O6C(-0x23)]-G20O6C(0x16)))]),lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[lm1RhP7[0x2e]+G20O6C(-0x22)]+G20O6C(-0x21)))]=XqU_Fqx.call(this,lm1RhP7[0x2e]+0xb0),lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[DZjCQp(0x11c)]-0x15)]=XqU_Fqx.call(this,lm1RhP7[lm1RhP7[DZjCQp(0x11c)]+DZjCQp(0x11d)]-(lm1RhP7[lm1RhP7[DZjCQp(0x11c)]+G20O6C(-0x22)]-DZjCQp(0x11f))),lm1RhP7[lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11a)]-0x8e)]=bHT8644.call(this,G20O6C(-0x1f)),lm1RhP7[0x8]=bHT8644.apply(this,[lm1RhP7[G20O6C(-0x26)]+0xbc]),lm1RhP7[lm1RhP7[G20O6C(-0x23)]+0x82]=XqU_Fqx(G20O6C(-0x1e)),lm1RhP7[lm1RhP7[lm1RhP7[0x72]+DZjCQp(0x11b)]+0x83]=XqU_Fqx.call(this,lm1RhP7[0x2e]+G20O6C(-0x14)),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11a)]+0x167]=lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]+DZjCQp(0x122)]-(lm1RhP7[DZjCQp(0x119)]-0x28)],lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]+G20O6C(-0x1d)]-(lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x24)))]-(lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x139))]=XqU_Fqx.apply(this,[G20O6C(-0x4a)]),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]+0xeb]-(lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0xfb))]=XqU_Fqx.call(this,lm1RhP7[0x72]-(lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x3a)))),lm1RhP7[lm1RhP7[0x72]-(lm1RhP7[lm1RhP7[0x72]+G20O6C(-0x25)]-G20O6C(-0x1c))]=iEpc2w.apply(this,[G20O6C(-0x28)]),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]+G20O6C(-0x22)]+0x87]=bHT8644.call(this,lm1RhP7[G20O6C(-0x23)]+G20O6C(-0x22)),lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x124))]=XqU_Fqx.call(this,0x2d),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xfa]+G20O6C(0x9)]=XqU_Fqx.apply(this,[0x8]),lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[DZjCQp(0x119)]-0x11)]=XqU_Fqx.apply(this,[DZjCQp(0x125)]),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]+0xa7]+G20O6C(0xc)]=XqU_Fqx.call(this,DZjCQp(0xf4)),lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0x9b]=XqU_Fqx.apply(this,[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x24)]+0xa2]),lm1RhP7[lm1RhP7[0x72]+G20O6C(-0x19)]=XqU_Fqx(DZjCQp(0x115)),lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[DZjCQp(0x119)]-G20O6C(-0x2e))]=bHT8644.apply(this,[DZjCQp(0x114)]),lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0x9e]=iEpc2w.apply(this,[DZjCQp(0x101)]),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[G20O6C(-0x23)]+DZjCQp(0x122)))]+DZjCQp(0x105)]=bHT8644(G20O6C(-0x2d)),lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[0x2e]+DZjCQp(0x11d)]+G20O6C(-0x22)]-(lm1RhP7[G20O6C(-0x23)]-G20O6C(-0x18))]=iEpc2w.apply(this,[DZjCQp(0x10e)]),lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]+G20O6C(-0x1d)]+DZjCQp(0x11a)]+G20O6C(-0x17)]=iEpc2w.apply(this,[G20O6C(-0x31)]),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11a)]-(lm1RhP7[lm1RhP7[DZjCQp(0x119)]+G20O6C(-0x24)]-G20O6C(0xb))]=lm1RhP7[lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[DZjCQp(0x119)]-0x1f)]-(lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x131)),lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-0x67]-(lm1RhP7[G20O6C(-0x26)]+0x103)]=XqU_Fqx.apply(this,[G20O6C(-0x16)]),lm1RhP7[lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x13f)]=XqU_Fqx.apply(this,[0x20]),lm1RhP7[lm1RhP7[DZjCQp(0x119)]+G20O6C(-0x15)]=XqU_Fqx.apply(this,[lm1RhP7[0x2e]-G20O6C(0xf)]),lm1RhP7[lm1RhP7[0x2e]-0x78]=XqU_Fqx.apply(this,[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11b)]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[G20O6C(-0x23)]+DZjCQp(0x11b)))]-DZjCQp(0x124))]),lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xa6]=bHT8644.apply(this,[0x1f]),lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+G20O6C(-0x24)]-G20O6C(-0x2d)]-(lm1RhP7[0x2e]-0x13c)]=XqU_Fqx(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x24)]-(lm1RhP7[DZjCQp(0x119)]+G20O6C(0x1f))),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x2d)]+G20O6C(0x14)]=iEpc2w.call(this,lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[lm1RhP7[0x2e]-DZjCQp(0x12d)]-DZjCQp(0x13e))),lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x112)]+DZjCQp(0x11a)]+G20O6C(-0x14)]=bHT8644(DZjCQp(0xfd)),lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xaa]=XqU_Fqx.call(this,G20O6C(-0x47)),lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[0x2e]-G20O6C(-0x2d))]=bHT8644.call(this,G20O6C(-0x13)),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11b)]-DZjCQp(0x136)]=XqU_Fqx.call(this,lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[G20O6C(-0x26)]+G20O6C(0x3))),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-G20O6C(-0x12)]-(lm1RhP7[DZjCQp(0x119)]+0xf8)]=bHT8644(lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-G20O6C(-0x12)]-(lm1RhP7[0x72]+0xef)]-(lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x127))),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x112)]+0xae]=XqU_Fqx.apply(this,[G20O6C(-0x11)]),lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-0x67]-(lm1RhP7[DZjCQp(0x119)]+0xf6)]=bHT8644.call(this,lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[G20O6C(-0x23)]-0x135)),lm1RhP7[lm1RhP7[0x2e]+0x4a]=XqU_Fqx.apply(this,[0x15]),lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xb1]=XqU_Fqx.call(this,DZjCQp(0xf8)),lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x112)]-(lm1RhP7[lm1RhP7[0x2e]-G20O6C(-0x12)]-0x147)]=XqU_Fqx.call(this,0x14),lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x2d)]+DZjCQp(0x12f)]=XqU_Fqx.apply(this,[DZjCQp(0xf8)]),lm1RhP7[lm1RhP7[0x2e]-(lm1RhP7[DZjCQp(0x11c)]-0x2c)]=bHT8644.apply(this,[G20O6C(-0x42)]),lm1RhP7[lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[0x72]+0xf0)]=G20O6C(-0xf));switch(lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x131)]){case!(D_hcEq.kPZ_Mvp>0x9)?0xbe:lm1RhP7[G20O6C(-0x26)]+0x232:return uTxoRnF[ZvCZ3z+G20O6C(-0xa)]||g97zGF[Dduo6Tp];case!(D_hcEq.bUoplwt>-G20O6C(-0x4a))?G20O6C(-0xf):-0x1a5:lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-0x23]+G20O6C(-0x24)]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11b)]-DZjCQp(0x15f))]=lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xb4]||g97zGF[bw0uqT6];break;case!(D_hcEq.jqhZopp[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[0x72]+G20O6C(0x15))]](G20O6C(-0x35))==G20O6C(-0xd))?0x47:0xf71:lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x2d)]+DZjCQp(0x11b)]-DZjCQp(0x12d)]-G20O6C(-0xc)]=ChzLf81||g97zGF[lm1RhP7[G20O6C(-0x1a)]];break;case!(D_hcEq.jqhZopp[lm1RhP7[lm1RhP7[0x2e]-0x6c]](0x1)==G20O6C(-0xd))?null:-0x375:return uTxoRnF[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[lm1RhP7[0x72]-(lm1RhP7[G20O6C(-0x23)]-G20O6C(-0xb))]-(lm1RhP7[G20O6C(-0x26)]+0xb6))]-(lm1RhP7[G20O6C(-0x26)]+0x3e)]+DZjCQp(0x135)]||g97zGF[EemGgL+DZjCQp(0x135)];case D_hcEq.jqhZopp[JuvoFHk](G20O6C(-0x35))=='\x31'?0x486:G20O6C(-0x35):return uTxoRnF[Clb9SQE]||g97zGF[syDJYsE+DZjCQp(0x151)];case!(D_hcEq.kPZ_Mvp>DZjCQp(0x10f))?-0xbb:0xa8b:lm1RhP7[lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x141)]=lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x25)]-0x2e)]-0x67]-0x6e]+lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-0x6f]||g97zGF[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xad]+lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-G20O6C(-0x12)]-G20O6C(-0x9)]];break;case!(D_hcEq.kPZ_Mvp>DZjCQp(0x10f))?0x7b:0x574:lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+DZjCQp(0x11b)]-(lm1RhP7[0x72]+0xf0)]=lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x26)]+bMcuvu||g97zGF[S_IavC+Q9nRWFJ];break;case D_hcEq.DSwyn9[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[G20O6C(-0x23)]-0x14b)]-DZjCQp(0x137)]](DZjCQp(0x109))==G20O6C(-0x7)?0x5e3:-DZjCQp(0x139):lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[0x2e]-0x14a)]=_cm54bf+ynKGk8n||g97zGF[lm1RhP7[lm1RhP7[0x2e]-(lm1RhP7[lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x112)]-(lm1RhP7[lm1RhP7[0x72]+DZjCQp(0x11a)]-0x74))]+KDkpl0g];break;case D_hcEq.kPZ_Mvp>G20O6C(-0x30)?0xcf1:0xad:return uTxoRnF[lm1RhP7[DZjCQp(0x13a)]+G20O6C(-0x4)]||g97zGF[Y5TyGif+DZjCQp(0x13b)];case 0xa34:return uTxoRnF[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+(lm1RhP7[G20O6C(-0x26)]+0x13e)]-G20O6C(-0x12)]-DZjCQp(0x13c)]+lm1RhP7[lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x13d)]]||g97zGF[lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-DZjCQp(0x112)]-(lm1RhP7[G20O6C(-0x23)]-G20O6C(-0xb))]-G20O6C(-0x1))]+ZEYuX4(0x27)];case 0xbaa:return uTxoRnF[ZEYuX4(0x28)]||g97zGF[ZEYuX4(0x28)];case!(D_hcEq.DSwyn9[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-0x79]](DZjCQp(0x109))=='\u006f')?DZjCQp(0x11d):0x81e:return uTxoRnF[lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-G20O6C(-0x2d)]+DZjCQp(0x13f)]+DZjCQp(0x135)]||g97zGF[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x12)]-(lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[G20O6C(-0x26)]-DZjCQp(0x11c))]-0x198))]];case!(D_hcEq.kPZ_Mvp>0x9)?G20O6C(-0x21):0xd7b:lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+(lm1RhP7[0x72]+DZjCQp(0x140))]+DZjCQp(0x141)]=lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[0x72]+DZjCQp(0x11b)]-0x23]+G20O6C(-0x24)]-G20O6C(-0x12)]-(lm1RhP7[DZjCQp(0x119)]+G20O6C(0x3))]||g97zGF[lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-0x23]-(lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xb6]-0x135)]];break;case!(D_hcEq.jqhZopp[y6mcAH](G20O6C(-0x35))==DZjCQp(0x132))?-0xe5:0xf56:return uTxoRnF[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x21)]+'\u006e']||g97zGF[wNs62u+'\u006e'];case!(D_hcEq.kPZ_Mvp>DZjCQp(0x10f))?-(lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[DZjCQp(0x11c)]-G20O6C(0x7))]+0x16e):0x67f:lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[lm1RhP7[G20O6C(-0x23)]-0x67]-DZjCQp(0x147))]=Xwvw78+G20O6C(0x5)||g97zGF[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[lm1RhP7[0x72]+0xb6]-DZjCQp(0x143))]+G20O6C(0x5)];break;case 0x1fa:lm1RhP7[lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x25)]+G20O6C(0x2)))]=lOPmZV||g97zGF[lm1RhP7[lm1RhP7[0x72]-(lm1RhP7[G20O6C(-0x23)]-0x132)]];break;case D_hcEq.bUoplwt>-DZjCQp(0xf5)?0x1ed:-(lm1RhP7[G20O6C(-0x26)]-(lm1RhP7[G20O6C(-0x26)]-G20O6C(0x6))):lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[0x72]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+G20O6C(-0x25)]-0x2e)]-(lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[DZjCQp(0x11c)]-0x184))]-0x68]=lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x146))]-(lm1RhP7[G20O6C(-0x23)]-0x131)]||g97zGF[hWLc2a];break;case!(D_hcEq.jqhZopp[UiCVZ0W](lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[0x2e]-G20O6C(0x19)))=='\x31')?-0xbb:0x92d:lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xfa]-(lm1RhP7[0x2e]-G20O6C(0x8))]=pkpgtg||g97zGF[tpwgSgZ];break;case!(D_hcEq.bUoplwt>-DZjCQp(0xf5))?-0x50:0x8a6:lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x133)]=EKys3x||g97zGF[KV16GX];break;case 0x41f:lm1RhP7[lm1RhP7[lm1RhP7[0x72]+0xfa]+DZjCQp(0x141)]=lm1RhP7[lm1RhP7[G20O6C(-0x23)]-G20O6C(0xa)]+aT0942k||g97zGF[lm1RhP7[lm1RhP7[DZjCQp(0x119)]+G20O6C(0x1a)]+lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-G20O6C(-0x12)]-0x84]];break;case!(D_hcEq.kPZ_Mvp>DZjCQp(0x10f))?-0xf:DZjCQp(0x139):lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[G20O6C(-0x23)]-G20O6C(-0xb))]-G20O6C(-0x12)]-(lm1RhP7[lm1RhP7[DZjCQp(0x119)]+0xb6]-0x2d)]=Rfoubp+s60sQ_||g97zGF[JvXCxIp+JHTO0V8];break;case!(D_hcEq.jqhZopp[lm1RhP7[lm1RhP7[0x72]+DZjCQp(0x148)]](G20O6C(-0x35))==DZjCQp(0x132))?-G20O6C(0xa):0xd2a:return uTxoRnF[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xfa]-(lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x12)]-DZjCQp(0x112)]-G20O6C(-0x26))]+G20O6C(-0x24)]-(lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x40))]]||g97zGF[lm1RhP7[DZjCQp(0x10c)]+lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x12d)]-(lm1RhP7[lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-G20O6C(-0x12)]-G20O6C(-0x12)]-G20O6C(0x7))]+0x110)]];case D_hcEq.b4gdoS[lm1RhP7[lm1RhP7[0x72]-(lm1RhP7[DZjCQp(0x119)]-(lm1RhP7[DZjCQp(0x11c)]-0x89))]](DZjCQp(0x139))==DZjCQp(0x14d)?0xb3d:G20O6C(0xb):return uTxoRnF[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-(lm1RhP7[DZjCQp(0x119)]+0x112)]+ka59Sk+G20O6C(0xd)]||g97zGF[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xb6]-DZjCQp(0x14b)]+lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11a)]+(lm1RhP7[0x72]-(lm1RhP7[G20O6C(-0x26)]-G20O6C(0x13)))]+DZjCQp(0x14c)];case D_hcEq.b4gdoS[M2NlKG](0x4)==DZjCQp(0x14d)?0x62a:-0x5e:return uTxoRnF[J_CyoIf+F9LAG7]||g97zGF[TUzu2G+zyLi4ua];case!(D_hcEq.bUoplwt>-G20O6C(-0x4a))?0xf6:lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-DZjCQp(0x12d)]-G20O6C(-0x2d)]+DZjCQp(0x11a)]+0xeed:return uTxoRnF[qgs9ww+BX5JJvf+'\u0074\u0065']||g97zGF[ggz61J+lm1RhP7[lm1RhP7[G20O6C(-0x23)]-G20O6C(0xf)]+'\x74\x65'];case D_hcEq.kPZ_Mvp>0x9?0xde:-0x9a:return uTxoRnF[z6_Uude+zk1d4V+DZjCQp(0x150)]||g97zGF[lm1RhP7[lm1RhP7[lm1RhP7[0x2e]-DZjCQp(0x112)]-(lm1RhP7[G20O6C(-0x26)]-DZjCQp(0x14f))]+lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0x8e]+DZjCQp(0x150)];case D_hcEq.bUoplwt>-0x30?0x3c4:-0x58:lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xfa]+DZjCQp(0x141)]=lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x23)]-DZjCQp(0x112)]+G20O6C(0xf)]||g97zGF[LZhCc0j+G20O6C(0x12)];break;case!(D_hcEq.jqhZopp[dxqKfQ6](DZjCQp(0x10a))=='\u0031')?-0x5f:lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[G20O6C(-0x23)]-0x719):lm1RhP7[0x2d]=DN_p_i||g97zGF[ZEYuX4(0x29)];break;case D_hcEq.b4gdoS[lm1RhP7[lm1RhP7[0x2e]-G20O6C(0x13)]](DZjCQp(0x139))==G20O6C(0xe)?0x3c6:DZjCQp(0x153):lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-G20O6C(-0x12)]-DZjCQp(0x133)]=lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(-0x26)]+DZjCQp(0x11b)]-(lm1RhP7[0x2e]-DZjCQp(0x11c))]-(lm1RhP7[lm1RhP7[G20O6C(-0x26)]+0xb6]-0x3)]||g97zGF[lm1RhP7[lm1RhP7[0x2e]-0x93]];break;case D_hcEq.bUoplwt>-DZjCQp(0xf5)?-DZjCQp(0x154):null:return uTxoRnF[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-(lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x10a))]]||g97zGF[ZR5X1e+'\x74\x65']}return lm1RhP7[lm1RhP7[0x2e]-(lm1RhP7[G20O6C(-0x26)]+0xab)]>lm1RhP7[G20O6C(-0x26)]+G20O6C(0x16)?lm1RhP7[lm1RhP7[0x72]-0xb]:uTxoRnF[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x11c)]-DZjCQp(0x12d)]-G20O6C(-0xc)]]||g97zGF[lm1RhP7[lm1RhP7[0x72]-(lm1RhP7[lm1RhP7[0x2e]-0x23]-0x2d)]]}function vdLM5n(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7-0xc0]}})();void(lm1RhP7.length=G20O6C(0xdb),lm1RhP7[DZjCQp(0xf5)]=lm1RhP7[DZjCQp(0x139)],lm1RhP7[G20O6C(0xdb)]='',lm1RhP7[G20O6C(0x127)]=0x26,lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]+G20O6C(0xcb)]+(lm1RhP7[G20O6C(0x127)]+DZjCQp(0x157))]-DZjCQp(0x115)]=lm1RhP7[lm1RhP7[G20O6C(0x127)]-(lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]+G20O6C(0xcb)]+DZjCQp(0xfa)]-(lm1RhP7[G20O6C(0x127)]-G20O6C(0x127))]-(lm1RhP7[lm1RhP7[DZjCQp(0x156)]+G20O6C(0xcb)]-(lm1RhP7[G20O6C(0x127)]-0x0)))].substring(lm1RhP7[lm1RhP7[lm1RhP7[0x51]+0x2b]+0x2b]-0x25,lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]+G20O6C(0xcb)]-(lm1RhP7[0x51]-(lm1RhP7[lm1RhP7[DZjCQp(0x156)]-(lm1RhP7[DZjCQp(0x156)]-G20O6C(0x127))]-G20O6C(0xe6)))].length-(lm1RhP7[lm1RhP7[DZjCQp(0x156)]+DZjCQp(0xfa)]-(lm1RhP7[DZjCQp(0x156)]-0x1))),lm1RhP7[lm1RhP7[DZjCQp(0x156)]+DZjCQp(0x15b)]=lm1RhP7[lm1RhP7[lm1RhP7[0x51]+G20O6C(0xcb)]-(lm1RhP7[G20O6C(0x127)]-DZjCQp(0x156))]-(lm1RhP7[lm1RhP7[DZjCQp(0x156)]-(lm1RhP7[G20O6C(0x127)]-DZjCQp(0x156))]-(lm1RhP7[lm1RhP7[G20O6C(0x127)]+0x2b]-0xbe)),lm1RhP7[lm1RhP7[lm1RhP7[0x86]+G20O6C(0x129)]+G20O6C(0x12a)]=lm1RhP7[lm1RhP7[lm1RhP7[0x51]+DZjCQp(0xfa)]-0x26].split('\u002c'));for(var TtonEk=G20O6C(0xc1);TtonEk{return lm1RhP7=>{return DZjCQp(lm1RhP7+0xc3)}})();lm1RhP7[lm1RhP7[DZjCQp(0x15a)]+0xc8]=[lm1RhP7[lm1RhP7[DZjCQp(0x156)]-(lm1RhP7[DZjCQp(0x15a)]-(lm1RhP7[DZjCQp(0x156)]-(lm1RhP7[G20O6C(0x12b)]-(lm1RhP7[DZjCQp(0x15a)]-0xe2))))][TtonEk],lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]+(lm1RhP7[DZjCQp(0x15a)]-(lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]-(lm1RhP7[DZjCQp(0x156)]-0x86)]+0x11e]-(lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]+G20O6C(0xcb)]-(lm1RhP7[0x51]-S0leuD6(0x93))]+0x5)))]+DZjCQp(0xfa)]+G20O6C(0x12c)]+DZjCQp(0x159)][TtonEk+DZjCQp(0x10a)]];var [uQhOlq,ZR5X1e]=lm1RhP7[lm1RhP7[0x51]+(lm1RhP7[lm1RhP7[S0leuD6(0x97)]+0x11e]+0xa2)].map(Number);while(ZR5X1e){var DN_p_i=(()=>{return lm1RhP7=>{return DZjCQp(lm1RhP7-0x4f)}})();typeof(lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x12b)]+G20O6C(0x12d)]+(lm1RhP7[lm1RhP7[S0leuD6(0x97)]+0x11e]-(lm1RhP7[S0leuD6(0x93)]-DZjCQp(0x158)))]-(lm1RhP7[DZjCQp(0x156)]-0xbf)]+=String.fromCharCode(uQhOlq>>DN_p_i(0x147)*(ZR5X1e&S0leuD6(0x53))&lm1RhP7[0x51]+0xd9),ZR5X1e>>=DZjCQp(0x160))}}return lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x15a)]-(lm1RhP7[G20O6C(0x127)]-0x10f)]-(lm1RhP7[0x51]-(lm1RhP7[0x86]+DZjCQp(0x158)))]>lm1RhP7[0x51]-G20O6C(0x12e)?lm1RhP7[lm1RhP7[lm1RhP7[G20O6C(0x127)]+G20O6C(0xcb)]+G20O6C(0xf7)]:lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[lm1RhP7[DZjCQp(0x156)]+DZjCQp(0xfa)]+G20O6C(0xcb)]+0x2b]-(lm1RhP7[DZjCQp(0x15a)]+0xbd)].replace(/~/g,'')}function bHT8644(lm1RhP7,bpUy3x,DZjCQp,G20O6C=vdLM5n,S0leuD6=TtonEk){if(DZjCQp){return bpUy3x[TtonEk[DZjCQp]]=bHT8644(lm1RhP7,bpUy3x)}else{if(bpUy3x){[S0leuD6,bpUy3x]=[G20O6C(S0leuD6),lm1RhP7||DZjCQp]}}return bpUy3x?lm1RhP7[S0leuD6[bpUy3x]]:TtonEk[lm1RhP7]||(DZjCQp=(S0leuD6[lm1RhP7],G20O6C),TtonEk[lm1RhP7]=DZjCQp(uQhOlq[lm1RhP7]))}function NldV9X(lm1RhP7,bpUy3x=[ZEYuX4(DZjCQp(0x125)),ZEYuX4(DZjCQp(0xfa))]){var G20O6C,TtonEk,S0leuD6,uQhOlq,ZR5X1e,DN_p_i=String,dxqKfQ6=ZEYuX4(DZjCQp(0xf9)),LZhCc0j=DZjCQp(0x15e),zk1d4V=ZEYuX4(DZjCQp(0x15f)),z6_Uude=ZEYuX4(0x2e),ggz61J=ZEYuX4(DZjCQp(0x117));for('\u003c\u007e'===lm1RhP7[z6_Uude](DZjCQp(0xf0),DZjCQp(0x109))&&'\u007e\u003e'===lm1RhP7[z6_Uude](-DZjCQp(0x109)),lm1RhP7=lm1RhP7[z6_Uude](DZjCQp(0x109),-DZjCQp(0x109))[ggz61J](/s/g,'')[ggz61J]('\x7a',ZEYuX4(DZjCQp(0xf5))),G20O6C=ZEYuX4(DZjCQp(0x121))[z6_Uude](lm1RhP7[dxqKfQ6]%DZjCQp(0x157)||DZjCQp(0x157)),lm1RhP7+=G20O6C,S0leuD6=[],uQhOlq=0x0,ZR5X1e=lm1RhP7[dxqKfQ6];ZR5X1e>uQhOlq;uQhOlq+=0x5)TtonEk=0x31c84b1*(lm1RhP7[zk1d4V](uQhOlq)-DZjCQp(0x129))+0x95eed*(lm1RhP7[zk1d4V](uQhOlq+0x1)-DZjCQp(0x129))+0x1c39*(lm1RhP7[zk1d4V](uQhOlq+DZjCQp(0x109))-DZjCQp(0x129))+0x55*(lm1RhP7[zk1d4V](uQhOlq+DZjCQp(0x160))-DZjCQp(0x129))+(lm1RhP7[zk1d4V](uQhOlq+0x4)-DZjCQp(0x129)),S0leuD6.push(LZhCc0j&TtonEk>>DZjCQp(0x127),LZhCc0j&TtonEk>>DZjCQp(0xf3),LZhCc0j&TtonEk>>DZjCQp(0xf8),LZhCc0j&TtonEk);return function(lm1RhP7,TtonEk){var G20O6C;for(G20O6C=TtonEk;G20O6C>DZjCQp(0xf0);G20O6C--)lm1RhP7.pop()}(S0leuD6,G20O6C[dxqKfQ6]),DN_p_i[bpUy3x[DZjCQp(0xf0)]][bpUy3x[DZjCQp(0x10a)]](DN_p_i,S0leuD6)}function XqU_Fqx(lm1RhP7,bpUy3x,DZjCQp,G20O6C=NldV9X,S0leuD6=TtonEk){if(DZjCQp){return bpUy3x[TtonEk[DZjCQp]]=XqU_Fqx(lm1RhP7,bpUy3x)}else{if(bpUy3x){[S0leuD6,bpUy3x]=[G20O6C(S0leuD6),lm1RhP7||DZjCQp]}}return bpUy3x?lm1RhP7[S0leuD6[bpUy3x]]:TtonEk[lm1RhP7]||(DZjCQp=(S0leuD6[lm1RhP7],G20O6C),TtonEk[lm1RhP7]=DZjCQp(uQhOlq[lm1RhP7]))}function xSA2Qr(...lm1RhP7){var G20O6C=(()=>{return lm1RhP7=>{return bpUy3x[lm1RhP7+0x8c]}})();+(lm1RhP7.length=G20O6C(-0x71),lm1RhP7[DZjCQp(0x161)]=lm1RhP7[G20O6C(-0x8b)]);var TtonEk,S0leuD6,uQhOlq=G20O6C(-0x8b),ZR5X1e='',DN_p_i=lm1RhP7[G20O6C(-0x1a)].length,dxqKfQ6=String,LZhCc0j=ZEYuX4(0x2d),zk1d4V=ZEYuX4(DZjCQp(0x125)),z6_Uude;lm1RhP7[G20O6C(-0x19)]=lm1RhP7[DZjCQp(0x161)];for(z6_Uude=DZjCQp(0xf0);z6_Uude{return lm1RhP7=>{return DZjCQp(lm1RhP7+0x5e)}})();void(TtonEk=lm1RhP7[ggz61J(0x104)][LZhCc0j](z6_Uude)-DZjCQp(0x129),TtonEk>=G20O6C(-0x8b)&&TtonEk=DZjCQp(0xf8)?uQhOlq-=(ZR5X1e+=dxqKfQ6[zk1d4V](S0leuD6>>uQhOlq-0x8&0xff),DZjCQp(0xf8)):ggz61J(0x92)):0x0)}lm1RhP7[G20O6C(-0x88)]=0x11;return lm1RhP7[lm1RhP7[lm1RhP7[0x10]-DZjCQp(0x10a)]-G20O6C(-0x71)]>lm1RhP7[0x10]-(lm1RhP7[DZjCQp(0xf3)]-(lm1RhP7[G20O6C(-0x88)]+0x6d))?lm1RhP7[-DZjCQp(0x149)]:ZR5X1e}function iEpc2w(lm1RhP7,bpUy3x,DZjCQp,G20O6C=xSA2Qr,S0leuD6=TtonEk){if(DZjCQp){return bpUy3x[TtonEk[DZjCQp]]=iEpc2w(lm1RhP7,bpUy3x)}else{if(bpUy3x){[S0leuD6,bpUy3x]=[G20O6C(S0leuD6),lm1RhP7||DZjCQp]}}return bpUy3x?lm1RhP7[S0leuD6[bpUy3x]]:TtonEk[lm1RhP7]||(DZjCQp=(S0leuD6[lm1RhP7],G20O6C),TtonEk[lm1RhP7]=DZjCQp(uQhOlq[lm1RhP7]))}function XGxBk7(){return'\x3c\x7e\x43\x2f\x53\x2f\x33\x3d\x23\x58\x29\x58\x3c\x5e\x42\x44\x4d\x3d\x44\x39\x55\x38\x33\x46\x6c\x59\x7e\x3e\x31\x2a\x3d\x3b\x36\x27\x34\x2a\x34\x29\x32\x22\x27\x37\x3b\x3b\x2c\x31Ā\x40\x71\x5d\x3a\x6b\x36\x24\x2aĚ\x31\x7b\x30\x78\x37\x45\x37\x37\x36\x45\x35\x38\x2cĺ\x36\x35\x30ńĻŁ\x37\x34\x45\x33\x32ʼnņ\x30\x7d\x31\x2b\x32\x35\x27\x2b\x29\x24\x34\x2e\x3dĸĺļ\x37\x35\x37\x32\x36\x33ő\x30\x41ʼnļņŧ\x32ňŅ\x34\x32ŔĀ\x44\x65\x2a\x45\x25\x42\x6c\x61ķĹŊŌť\x36ŃŲŐš\x45ņŰʼn\x34\x30\x39Ŕ\x2e\x32\x38\x37\x3d\x3a\x3a\x22ĭ\x7e\x40\x72\x48\x37\x2bŷ\x6fķĮƜƞŷ\x72\x74Ƣ\x7e\x36\x3f\x51\x6d\x50\x41\x54\x40Ʃ\x45\x62\x30ņż\x58Ƴ\x2c\x6f\x6c\x2cư\x4d\x6eſƈ\x36\x34\x35\x46ƌƍŬĽŦƉ\x39őŇŵƚ\x3b\x54\x51ķŖŘĆ\x39ƙ\x45\x2b\x2a\x64\x2e\x41\x4f\x5aƩ\x3a\x69\x5e\x4a\x6bżžěĀǥǧǩ\x6c\x64\x68ǖ\x2e\x3b\x28\x25ī\x2f\x2dş\x29\x3a\x3bƔ\x39\x3c\x35\x2e\x25ƙ\x36\x23\x71\x21\x53\x47\x6cǤǦ\x4b\x21ȌƩ\x37\x3c\x33\x45\x65\x45\x57Ʃ\x46\x28\x4b\x47\x6dżĶǬ\x7eư\x29\x5e\x35ķ\x2d\x36\x3a\x28\x2dǎ\x2dƙƛ\x2c\x5e\x62\x45\x5f\x2dțȝ\x47\x62\x44\x2f\x42Ʃ\x41\x55\x26\x30ō\x63\x63ƲȣɁɃɅ\x5fķ\x2eƄ\x37\x29\x3e\x2c\x2d\x2d\x35\x31\x2e\x26\x3a\x35\x3d\x39\x2b\x2fƙż\x63\x61\x58\x44\x4a\x3d\x33\x28Ʃɢɤɦ\x39ķɣ\x6c\x6c\x31\x20\x6f\x6e\x6c\x79\x20\x31\x6c\x69\x6e\x65\x73ɺ\x6c\x6f\x61\x74\x31\x4dʄ\x68\x31\x6d\x6f\x64\x75\x6c\x65\x31\x66\x72\x6f\x6d\x43\x68\x61\x72\x43ʌʐ\x61\x70\x70ɸɻ\x65\x6e\x67\x74ʉ\x63ʗʙʛ\x41ʅ\x73ɼ\x63ʐ\x72\x65ʟ\x61ʯ\x31\x21ʷʷ\x31\x75ʻʻ'}function ZEYuX4(...lm1RhP7){+(lm1RhP7.length=DZjCQp(0x10a),lm1RhP7[DZjCQp(0x163)]=lm1RhP7[DZjCQp(0xf0)]);return G20O6C[lm1RhP7[DZjCQp(0x163)]]}function z6DjMr(lm1RhP7){var bpUy3x,G20O6C,TtonEk,S0leuD6={},uQhOlq=lm1RhP7.split(''),ZR5X1e=G20O6C=uQhOlq[DZjCQp(0xf0)],DN_p_i=[ZR5X1e],dxqKfQ6=bpUy3x=0x100;for(lm1RhP7=DZjCQp(0x10a);lm1RhP7TtonEk?uQhOlq[lm1RhP7]:S0leuD6[TtonEk]?S0leuD6[TtonEk]:G20O6C+ZR5X1e,DN_p_i.push(TtonEk),ZR5X1e=TtonEk.charAt(0x0),S0leuD6[bpUy3x]=G20O6C+ZR5X1e,bpUy3x++,G20O6C=TtonEk;return DN_p_i.join('').split('\u0031')}function B48k5jJ(){return[0x54,0x0,0x4f,0x3f,0x10,0x29,0x30,0x33,0x32,0x8,0x2c,0x2b,0x1b,0x17,0x13,0x12,0xf,0x6,0x24,0x3c,0xc2,0x56,0x9f,0x189,0xea,0x65,0x2,0x1,0xb0,0xe,0xa,0x22,0x9,0xb,0x15,0x23,0x1aa,0x25,0x26,0x7,0x2f,0x5d,0x72,0xfa,0xb6,0x2e,0xa7,0x7e,0x36,0x35,0x31,0xeb,0xd,0x1e,0x2a,0x9c,0x18,0xa1,0x21,0xa4,0xa9,0x1a,0x67,0x19,0xb3,void 0x0,0x95,'\x31',0x68,0x14b,'\x65',0x71,0x73,'\x6f',0x4,0x20,'\x6e\x74',0x76,0x77,0x13a,0xa3,0x182,0xb5,0x104,0x16,'\x6f\x6e',0x5a,0x18f,0x14a,0x98,0x82,0x3d,0x8b,'\u006c','\u0055',0x8d,0x9d,'\x73\x6b','\u0073',0x91,0xa8,0xf2,0x81,0x51,0x5,0x11e,0x9a,0x86,0x60,0xe9,0x3b,0xff,0x2d,0x3,0x88,0xb9,0x61]} \ No newline at end of file diff --git a/samples/input.js b/samples/input.js deleted file mode 100644 index 01faa90..0000000 --- a/samples/input.js +++ /dev/null @@ -1,3 +0,0 @@ -var result = 1 + 1; -console.log("1 + 1 is " + result); -console.log("The source code is only three lines long!"); diff --git a/samples/javascriptobfuscator.com.js b/samples/javascriptobfuscator.com.js deleted file mode 100644 index 7778f5e..0000000 --- a/samples/javascriptobfuscator.com.js +++ /dev/null @@ -1,8 +0,0 @@ -var _0x36dc = [ - "\x31\x20\x2B\x20\x31\x20\x69\x73\x20", - "\x6C\x6F\x67", - "\x54\x68\x65\x20\x73\x6F\x75\x72\x63\x65\x20\x63\x6F\x64\x65\x20\x69\x73\x20\x6F\x6E\x6C\x79\x20\x74\x68\x72\x65\x65\x20\x6C\x69\x6E\x65\x73\x20\x6C\x6F\x6E\x67\x21", -]; -var result = 1 + 1; -console[_0x36dc[1]](_0x36dc[0] + result); -console[_0x36dc[1]](_0x36dc[2]); diff --git a/samples/jscrambler_advanced.js b/samples/jscrambler_advanced.js deleted file mode 100644 index 60e1dcd..0000000 --- a/samples/jscrambler_advanced.js +++ /dev/null @@ -1,1894 +0,0 @@ -a277.a = (function () { - var Y = 2; - for (; Y !== 9; ) { - switch (Y) { - case 2: - Y = typeof globalThis === "\u006f\x62\u006a\u0065\x63\x74" ? 1 : 5; - break; - case 1: - return globalThis; - break; - case 5: - var b; - try { - var T = 2; - for (; T !== 6; ) { - switch (T) { - case 9: - delete b["\x6b\x58\x66\x31\x4a"]; - var w = - Object["\u0070\x72\u006f\x74\u006f\u0074\u0079\u0070\u0065"]; - delete w["\x42\u0048\u0067\u0041\x44"]; - T = 6; - break; - case 4: - T = - typeof kXf1J === - "\u0075\x6e\x64\u0065\u0066\u0069\x6e\u0065\u0064" - ? 3 - : 9; - break; - case 2: - Object[ - "\u0064\x65\u0066\x69\x6e\u0065\x50\x72\u006f\x70\x65\x72\x74\x79" - ]( - Object[ - "\u0070\u0072\u006f\u0074\x6f\u0074\u0079\u0070\u0065" - ], - "\x42\x48\x67\u0041\x44", - { - "\x67\x65\x74": function () { - var Z = 2; - for (; Z !== 1; ) { - switch (Z) { - case 2: - return this; - break; - } - } - }, - "\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65": true, - } - ); - b = BHgAD; - b["\x6b\u0058\x66\u0031\u004a"] = b; - T = 4; - break; - case 3: - throw ""; - T = 9; - break; - } - } - } catch (z) { - b = window; - } - return b; - break; - } - } -})(); -a277.M = L666(a277.a); -a277.c7jj = c7jj; -a277.P9 = y0ZZ(a277.a); -a277.D1 = X9UU(a277.a); -a277.b0 = (function () { - var z3 = 2; - for (; z3 !== 1; ) { - switch (z3) { - case 2: - return { - x9: (function (D9) { - var d3 = 2; - for (; d3 !== 10; ) { - switch (d3) { - case 3: - d3 = C9 === D9.length ? 9 : 8; - break; - case 6: - F9 = F9.W0ZZ("{"); - var V9 = 0; - var K9 = function (d9) { - var L3 = 2; - for (; L3 !== 20; ) { - switch (L3) { - case 9: - L3 = V9 === 2 && d9 === 0 ? 8 : 7; - break; - case 3: - F9.x0ZZ.F0ZZ(F9, F9.V0ZZ(-7, 7).V0ZZ(0, 5)); - L3 = 5; - break; - case 13: - F9.x0ZZ.F0ZZ(F9, F9.V0ZZ(-5, 5).V0ZZ(0, 3)); - L3 = 5; - break; - case 7: - L3 = V9 === 3 && d9 === 9 ? 6 : 14; - break; - case 6: - F9.x0ZZ.F0ZZ(F9, F9.V0ZZ(-10, 10).V0ZZ(0, 8)); - L3 = 5; - break; - case 4: - L3 = V9 === 1 && d9 === 7 ? 3 : 9; - break; - case 12: - L3 = V9 === 5 && d9 === 7 ? 11 : 10; - break; - case 8: - F9.x0ZZ.F0ZZ(F9, F9.V0ZZ(-9, 9).V0ZZ(0, 7)); - L3 = 5; - break; - case 11: - F9.x0ZZ.F0ZZ(F9, F9.V0ZZ(-4, 4).V0ZZ(0, 2)); - L3 = 5; - break; - case 1: - F9.x0ZZ.F0ZZ(F9, F9.V0ZZ(-4, 4).V0ZZ(0, 2)); - L3 = 5; - break; - case 2: - L3 = V9 === 0 && d9 === 7 ? 1 : 4; - break; - case 5: - return V9++, F9[d9]; - break; - case 14: - L3 = V9 === 4 && d9 === 5 ? 13 : 12; - break; - case 10: - K9 = T9; - L3 = 5; - break; - } - } - }; - var T9 = function (l9) { - var B3 = 2; - for (; B3 !== 1; ) { - switch (B3) { - case 2: - return F9[l9]; - break; - } - } - }; - return K9; - break; - case 2: - var j9 = function (r9) { - var k3 = 2; - for (; k3 !== 13; ) { - switch (k3) { - case 6: - k3 = !y9 ? 8 : 14; - break; - case 1: - var p9 = 0; - k3 = 5; - break; - case 4: - Q9.G0ZZ(e0ZZ.A0ZZ(r9[p9] + 95)); - k3 = 3; - break; - case 14: - return y9; - break; - case 2: - var Q9 = []; - k3 = 1; - break; - case 8: - w9 = Q9.s0ZZ(function () { - var q3 = 2; - for (; q3 !== 1; ) { - switch (q3) { - case 2: - return 0.5 - X0ZZ.S0ZZ(); - break; - } - } - }).t0ZZ(""); - y9 = a277[w9]; - k3 = 6; - break; - case 5: - k3 = p9 < r9.length ? 4 : 9; - break; - case 3: - p9++; - k3 = 5; - break; - case 9: - var w9, y9; - k3 = 8; - break; - } - } - }; - var F9 = "", - v9 = Y0ZZ(j9([-40, 11, 4, 11])()); - d3 = 5; - break; - case 5: - var B9 = 0, - C9 = 0; - d3 = 4; - break; - case 8: - F9 += e0ZZ.A0ZZ(v9.m0ZZ(B9) ^ D9.m0ZZ(C9)); - d3 = 7; - break; - case 4: - d3 = B9 < v9.length ? 3 : 6; - break; - case 9: - C9 = 0; - d3 = 8; - break; - case 7: - B9++, C9++; - d3 = 4; - break; - } - } - })("YVRT7I"), - }; - break; - } - } -})(); -a277.W0 = function () { - return typeof a277.b0.x9 === "function" - ? a277.b0.x9.apply(a277.b0, arguments) - : a277.b0.x9; -}; -a277.V0 = function () { - return typeof a277.b0.x9 === "function" - ? a277.b0.x9.apply(a277.b0, arguments) - : a277.b0.x9; -}; -a277.l8 = function () { - return typeof a277.Z8.a8 === "function" - ? a277.Z8.a8.apply(a277.Z8, arguments) - : a277.Z8.a8; -}; -a277.i9 = (function (V) { - var b9 = 2; - for (; b9 !== 10; ) { - switch (b9) { - case 5: - C = a277.a; - b9 = 4; - break; - case 1: - b9 = !o-- ? 5 : 4; - break; - case 9: - q = typeof m; - b9 = 8; - break; - case 6: - b9 = !o-- ? 14 : 13; - break; - case 2: - var C, q, j, o; - b9 = 1; - break; - case 7: - j = q.b666(new C[L]("^['-|]"), "S"); - b9 = 6; - break; - case 8: - b9 = !o-- ? 7 : 6; - break; - case 14: - V = V.o666(function (u) { - var U9 = 2; - for (; U9 !== 13; ) { - switch (U9) { - case 8: - K++; - U9 = 3; - break; - case 4: - var K = 0; - U9 = 3; - break; - case 7: - U9 = !N ? 6 : 14; - break; - case 5: - N = ""; - U9 = 4; - break; - case 3: - U9 = K < u.length ? 9 : 7; - break; - case 1: - U9 = !o-- ? 5 : 4; - break; - case 9: - N += C[j][m](u[K] + 116); - U9 = 8; - break; - case 6: - return; - break; - case 14: - return N; - break; - case 2: - var N; - U9 = 1; - break; - } - } - }); - b9 = 13; - break; - case 12: - var B, - S = 0; - b9 = 11; - break; - case 13: - b9 = !o-- ? 12 : 11; - break; - case 3: - b9 = !o-- ? 9 : 8; - break; - case 4: - var m = "fromCharCode", - L = "RegExp"; - b9 = 3; - break; - case 11: - return { - b: function (D) { - var n9 = 2; - for (; n9 !== 6; ) { - switch (n9) { - case 9: - S = Z + 60000; - n9 = 8; - break; - case 4: - B = E(Z); - n9 = 3; - break; - case 5: - n9 = !o-- ? 4 : 3; - break; - case 3: - n9 = !o-- ? 9 : 8; - break; - case 8: - var U = (function (l, W) { - var O9 = 2; - for (; O9 !== 10; ) { - switch (O9) { - case 8: - var G = C[W[4]](l[W[2]](x), 16)[W[3]](2); - var J = G[W[2]](G[W[5]] - 1); - O9 = 6; - break; - case 3: - var h, - x = 0; - O9 = 9; - break; - case 5: - O9 = - typeof W === "undefined" && typeof V !== "undefined" - ? 4 - : 3; - break; - case 2: - O9 = - typeof l === "undefined" && typeof D !== "undefined" - ? 1 - : 5; - break; - case 11: - return h; - break; - case 12: - h = h ^ J; - O9 = 13; - break; - case 13: - x++; - O9 = 9; - break; - case 9: - O9 = x < l[W[5]] ? 8 : 11; - break; - case 1: - l = D; - O9 = 5; - break; - case 6: - O9 = x === 0 ? 14 : 12; - break; - case 14: - h = J; - O9 = 13; - break; - case 4: - W = V; - O9 = 3; - break; - } - } - })(undefined, undefined); - return U ? B : !B; - break; - case 1: - n9 = Z > S ? 5 : 8; - break; - case 2: - var Z = new C[V[0]]()[V[1]](); - n9 = 1; - break; - } - } - }, - }; - break; - } - } - function E(A) { - var u9 = 2; - for (; u9 !== 15; ) { - switch (u9) { - case 7: - u9 = !o-- ? 6 : 14; - break; - case 16: - w = r - A > p; - u9 = 19; - break; - case 8: - X = V[6]; - u9 = 7; - break; - case 19: - return w; - break; - case 9: - u9 = !o-- ? 8 : 7; - break; - case 5: - g = C[V[4]]; - u9 = 4; - break; - case 2: - var w, p, X, r, y, k, g; - u9 = 1; - break; - case 13: - y = V[7]; - u9 = 12; - break; - case 4: - u9 = !o-- ? 3 : 9; - break; - case 11: - k = (y || y === 0) && g(y, p); - u9 = 10; - break; - case 10: - u9 = k >= 0 && r >= 0 ? 20 : 18; - break; - case 12: - u9 = !o-- ? 11 : 10; - break; - case 14: - u9 = !o-- ? 13 : 12; - break; - case 1: - u9 = !o-- ? 5 : 4; - break; - case 6: - r = X && g(X, p); - u9 = 14; - break; - case 3: - p = 27; - u9 = 9; - break; - case 17: - w = A - k > p; - u9 = 19; - break; - case 20: - w = A - k > p && r - A > p; - u9 = 19; - break; - case 18: - u9 = k >= 0 ? 17 : 16; - break; - } - } - } -})([ - [-48, -19, 0, -15], - [-13, -15, 0, -32, -11, -7, -15], - [-17, -12, -19, -2, -51, 0], - [0, -5, -33, 0, -2, -11, -6, -13], - [-4, -19, -2, -1, -15, -43, -6, 0], - [-8, -15, -6, -13, 0, -12], - [-63, -9, -66, -66, -65, -62, -65, -63, -68], - [], -]); -a277.c8 = function () { - return typeof a277.Z8.a8 === "function" - ? a277.Z8.a8.apply(a277.Z8, arguments) - : a277.Z8.a8; -}; -a277.W1G = function () { - return typeof a277.X1G.H2 === "function" - ? a277.X1G.H2.apply(a277.X1G, arguments) - : a277.X1G.H2; -}; -a277.Z2 = function () { - return typeof a277.p2.Q2 === "function" - ? a277.p2.Q2.apply(a277.p2, arguments) - : a277.p2.Q2; -}; -a277.I9 = function () { - return typeof a277.i9.b === "function" - ? a277.i9.b.apply(a277.i9, arguments) - : a277.i9.b; -}; -function L666(j7) { - var V7 = 2; - for (; V7 !== 18; ) { - switch (V7) { - case 3: - F7[2] = 1; - F7[3] = "6"; - F7[9] = "66"; - F7[8] = F7[4]; - V7 = 6; - break; - case 10: - var Y = function (K7, m7, J7, D7) { - var z7 = 2; - for (; z7 !== 5; ) { - switch (z7) { - case 2: - var R7 = [arguments]; - a7(F7[0][0], R7[0][0], R7[0][1], R7[0][2], R7[0][3]); - z7 = 5; - break; - } - } - }; - V7 = 20; - break; - case 2: - var F7 = [arguments]; - F7[7] = ""; - F7[7] = "b6"; - F7[4] = "o"; - V7 = 3; - break; - case 6: - F7[8] += F7[9]; - F7[8] += F7[3]; - F7[1] = F7[7]; - F7[1] += F7[3]; - V7 = 11; - break; - case 11: - F7[1] += F7[3]; - V7 = 10; - break; - case 20: - Y(z, "replace", F7[2], F7[1]); - V7 = 19; - break; - case 19: - Y(Q, "map", F7[2], F7[8]); - V7 = 18; - break; - } - } - function a7(r7, T7, Q7, q7, p7) { - var l7 = 2; - for (; l7 !== 14; ) { - switch (l7) { - case 6: - try { - var x7 = 2; - for (; x7 !== 8; ) { - switch (x7) { - case 2: - y7[1] = {}; - y7[7] = (1, y7[0][1])(y7[0][0]); - y7[4] = [y7[6], y7[7].prototype][y7[0][3]]; - y7[1].value = y7[4][y7[0][2]]; - x7 = 3; - break; - case 3: - try { - var a9 = 2; - for (; a9 !== 3; ) { - switch (a9) { - case 2: - y7[3] = y7[8]; - y7[3] += y7[5]; - y7[3] += y7[2]; - a9 = 4; - break; - case 4: - y7[0][0].Object[y7[3]](y7[4], y7[0][4], y7[1]); - a9 = 3; - break; - } - } - } catch (o7) {} - y7[4][y7[0][4]] = y7[1].value; - x7 = 8; - break; - } - } - } catch (M7) {} - l7 = 14; - break; - case 2: - var y7 = [arguments]; - y7[5] = ""; - y7[2] = "rty"; - y7[5] = ""; - l7 = 3; - break; - case 3: - y7[5] = "nePrope"; - y7[8] = ""; - y7[8] = "defi"; - y7[6] = 2; - l7 = 6; - break; - } - } - } - function z(w7) { - var S7 = 2; - for (; S7 !== 5; ) { - switch (S7) { - case 2: - var v7 = [arguments]; - return v7[0][0].String; - break; - } - } - } - function Q(Y7) { - var C7 = 2; - for (; C7 !== 5; ) { - switch (C7) { - case 2: - var P7 = [arguments]; - return P7[0][0].Array; - break; - } - } - } -} -a277.X1G = (function () { - var G1G = 2; - for (; G1G !== 9; ) { - switch (G1G) { - case 2: - var I1G = [arguments]; - I1G[5] = undefined; - I1G[7] = {}; - G1G = 4; - break; - case 4: - I1G[7].H2 = function () { - var Y1G = 2; - for (; Y1G !== 90; ) { - switch (Y1G) { - case 2: - var w1G = [arguments]; - Y1G = 1; - break; - case 7: - w1G[7] = w1G[1]; - w1G[4] = {}; - w1G[4].W8 = ["Q0"]; - w1G[4].O8 = function () { - var C1 = false; - var z1 = []; - try { - for (var R1 in console) { - z1.D4UU(R1); - } - C1 = z1.length === 0; - } catch (x1) {} - var A1 = C1; - return A1; - }; - w1G[8] = w1G[4]; - Y1G = 11; - break; - case 68: - Y1G = 49 ? 68 : 67; - break; - case 71: - w1G[92]++; - Y1G = 76; - break; - case 43: - w1G[10] = {}; - w1G[10].W8 = ["Q0"]; - w1G[10].O8 = function () { - var k1 = typeof d4UU === "function"; - return k1; - }; - Y1G = 40; - break; - case 16: - w1G[5].O8 = function () { - var I1 = function () { - return "aa".endsWith("a"); - }; - var K1 = /\x74\u0072\u0075\x65/.p4UU(I1 + []); - return K1; - }; - w1G[6] = w1G[5]; - w1G[40] = {}; - w1G[40].W8 = ["V8"]; - Y1G = 25; - break; - case 29: - w1G[97].W8 = ["V8"]; - w1G[97].O8 = function () { - var i1 = function () { - return escape("="); - }; - var H1 = /\u0033\x44/.p4UU(i1 + []); - return H1; - }; - w1G[14] = w1G[97]; - Y1G = 43; - break; - case 49: - w1G[3].D4UU(w1G[14]); - w1G[3].D4UU(w1G[6]); - w1G[3].D4UU(w1G[9]); - w1G[3].D4UU(w1G[7]); - Y1G = 45; - break; - case 25: - w1G[40].O8 = function () { - var W1 = function () { - return String.fromCharCode(0x61); - }; - var Q1 = !/\x30\u0078\x36\x31/.p4UU(W1 + []); - return Q1; - }; - w1G[77] = w1G[40]; - w1G[83] = {}; - w1G[83].W8 = ["Q0"]; - Y1G = 21; - break; - case 40: - w1G[80] = w1G[10]; - w1G[33] = {}; - w1G[33].W8 = ["Q0"]; - Y1G = 37; - break; - case 72: - w1G[95].D4UU(w1G[89]); - Y1G = 71; - break; - case 57: - Y1G = w1G[27] < w1G[3].length ? 56 : 69; - break; - case 69: - Y1G = (function (q1G) { - var F1G = 2; - for (; F1G !== 22; ) { - switch (F1G) { - case 6: - A1G[5] = A1G[0][0][A1G[1]]; - F1G = 14; - break; - case 4: - A1G[2] = {}; - A1G[4] = []; - A1G[1] = 0; - F1G = 8; - break; - case 13: - A1G[2][A1G[5][w1G[44]]] = function () { - var u1G = 2; - for (; u1G !== 9; ) { - switch (u1G) { - case 2: - var O1G = [arguments]; - O1G[9] = {}; - O1G[9].h = 0; - O1G[9].t = 0; - return O1G[9]; - break; - } - } - }.G4UU(this, arguments); - F1G = 12; - break; - case 14: - F1G = - typeof A1G[2][A1G[5][w1G[44]]] === "undefined" - ? 13 - : 11; - break; - case 17: - A1G[1] = 0; - F1G = 16; - break; - case 24: - A1G[1]++; - F1G = 16; - break; - case 25: - A1G[8] = true; - F1G = 24; - break; - case 26: - F1G = A1G[7] >= 0.5 ? 25 : 24; - break; - case 5: - return; - break; - case 19: - A1G[1]++; - F1G = 7; - break; - case 23: - return A1G[8]; - break; - case 1: - F1G = A1G[0][0].length === 0 ? 5 : 4; - break; - case 7: - F1G = A1G[1] < A1G[0][0].length ? 6 : 18; - break; - case 18: - A1G[8] = false; - F1G = 17; - break; - case 8: - A1G[1] = 0; - F1G = 7; - break; - case 11: - A1G[2][A1G[5][w1G[44]]].t += true; - F1G = 10; - break; - case 15: - A1G[3] = A1G[4][A1G[1]]; - A1G[7] = A1G[2][A1G[3]].h / A1G[2][A1G[3]].t; - F1G = 26; - break; - case 2: - var A1G = [arguments]; - F1G = 1; - break; - case 10: - F1G = A1G[5][w1G[65]] === w1G[84] ? 20 : 19; - break; - case 16: - F1G = A1G[1] < A1G[4].length ? 15 : 23; - break; - case 12: - A1G[4].D4UU(A1G[5][w1G[44]]); - F1G = 11; - break; - case 20: - A1G[2][A1G[5][w1G[44]]].h += true; - F1G = 19; - break; - } - } - })(w1G[95]) - ? 68 - : 67; - break; - case 1: - Y1G = I1G[5] ? 5 : 4; - break; - case 21: - w1G[83].O8 = function () { - var l1 = typeof N4UU === "function"; - return l1; - }; - w1G[75] = w1G[83]; - w1G[39] = {}; - w1G[39].W8 = ["V8"]; - w1G[39].O8 = function () { - var Y1 = function () { - return "x".toLocaleUpperCase(); - }; - var F1 = /\u0058/.p4UU(Y1 + []); - return F1; - }; - w1G[50] = w1G[39]; - w1G[97] = {}; - Y1G = 29; - break; - case 10: - w1G[2].W8 = ["V8"]; - w1G[2].O8 = function () { - var y1 = function () { - return "X".toLocaleLowerCase(); - }; - var X1 = /\u0078/.p4UU(y1 + []); - return X1; - }; - w1G[9] = w1G[2]; - w1G[5] = {}; - w1G[5].W8 = ["V8"]; - Y1G = 16; - break; - case 56: - w1G[48] = w1G[3][w1G[27]]; - try { - w1G[78] = w1G[48][w1G[59]]() ? w1G[84] : w1G[72]; - } catch (n1) { - w1G[78] = w1G[72]; - } - Y1G = 77; - break; - case 77: - w1G[92] = 0; - Y1G = 76; - break; - case 5: - return 40; - break; - case 45: - w1G[3].D4UU(w1G[50]); - w1G[95] = []; - w1G[84] = "I8"; - w1G[72] = "p8"; - Y1G = 62; - break; - case 11: - w1G[2] = {}; - Y1G = 10; - break; - case 70: - w1G[27]++; - Y1G = 57; - break; - case 67: - I1G[5] = 72; - return 71; - break; - case 58: - w1G[27] = 0; - Y1G = 57; - break; - case 75: - w1G[89] = {}; - w1G[89][w1G[44]] = w1G[48][w1G[13]][w1G[92]]; - w1G[89][w1G[65]] = w1G[78]; - Y1G = 72; - break; - case 4: - w1G[3] = []; - w1G[1] = {}; - w1G[1].W8 = ["V8"]; - w1G[1].O8 = function () { - var a1 = function () { - return atob("PQ=="); - }; - var b1 = !/\u0061\u0074\u006f\u0062/.p4UU(a1 + []); - return b1; - }; - Y1G = 7; - break; - case 62: - w1G[13] = "W8"; - w1G[65] = "w8"; - w1G[59] = "O8"; - w1G[44] = "i8"; - Y1G = 58; - break; - case 37: - w1G[33].O8 = function () { - var w1 = typeof U4UU === "function"; - return w1; - }; - w1G[93] = w1G[33]; - w1G[3].D4UU(w1G[80]); - w1G[3].D4UU(w1G[8]); - w1G[3].D4UU(w1G[77]); - w1G[3].D4UU(w1G[75]); - w1G[3].D4UU(w1G[93]); - Y1G = 49; - break; - case 76: - Y1G = w1G[92] < w1G[48][w1G[13]].length ? 75 : 70; - break; - } - } - }; - return I1G[7]; - break; - } - } -})(); -function y0ZZ(x3) { - function w6(t3) { - var A3 = 2; - for (; A3 !== 5; ) { - switch (A3) { - case 2: - var N3 = [arguments]; - return N3[0][0].Math; - break; - } - } - } - var m3 = 2; - for (; m3 !== 104; ) { - switch (m3) { - case 85: - L1(w6, "random", v3[91], v3[16]); - m3 = 84; - break; - case 90: - L1(B1, "push", v3[22], v3[79]); - m3 = 89; - break; - case 84: - L1(B1, "join", v3[22], v3[39]); - m3 = 83; - break; - case 82: - L1(b6, "charCodeAt", v3[22], v3[61]); - m3 = 81; - break; - case 6: - v3[3] = ""; - v3[3] = ""; - v3[3] = "S"; - v3[8] = ""; - m3 = 11; - break; - case 72: - v3[78] = v3[7]; - v3[78] += v3[6]; - v3[78] += v3[4]; - v3[79] = v3[5]; - m3 = 68; - break; - case 80: - L1(B1, "unshift", v3[22], v3[28]); - m3 = 79; - break; - case 3: - v3[7] = "e"; - v3[2] = "A"; - v3[9] = "s"; - v3[1] = "X0"; - m3 = 6; - break; - case 76: - v3[45] += v3[63]; - v3[56] = v3[2]; - v3[56] += v3[62]; - v3[56] += v3[63]; - m3 = 72; - break; - case 26: - v3[84] = ""; - v3[84] = "m"; - v3[62] = ""; - v3[62] = "0Z"; - v3[36] = ""; - v3[36] = "W"; - m3 = 35; - break; - case 81: - L1(b6, "split", v3[22], v3[76]); - m3 = 80; - break; - case 51: - v3[76] += v3[62]; - v3[76] += v3[63]; - v3[61] = v3[84]; - v3[61] += v3[62]; - v3[61] += v3[63]; - m3 = 46; - break; - case 89: - L1(a6, "String", v3[91], v3[78]); - m3 = 88; - break; - case 59: - v3[16] += v3[4]; - v3[50] = v3[1]; - v3[50] += v3[63]; - v3[50] += v3[63]; - v3[45] = v3[9]; - v3[45] += v3[62]; - m3 = 76; - break; - case 78: - L1(B1, "splice", v3[22], v3[20]); - m3 = 104; - break; - case 31: - v3[49] = "V0"; - v3[22] = 0; - v3[22] = 1; - v3[91] = 2; - m3 = 44; - break; - case 88: - L1(E6, "fromCharCode", v3[91], v3[56]); - m3 = 87; - break; - case 2: - var v3 = [arguments]; - v3[5] = "G"; - v3[7] = ""; - v3[7] = ""; - m3 = 3; - break; - case 11: - v3[8] = "t0"; - v3[4] = ""; - v3[4] = "ZZ"; - v3[6] = ""; - m3 = 18; - break; - case 66: - var L1 = function (C3, O3, e3, g3) { - var c3 = 2; - for (; c3 !== 5; ) { - switch (c3) { - case 2: - var J3 = [arguments]; - Z6(v3[0][0], J3[0][0], J3[0][1], J3[0][2], J3[0][3]); - c3 = 5; - break; - } - } - }; - m3 = 90; - break; - case 36: - v3[28] = v3[31]; - v3[28] += v3[63]; - v3[28] += v3[63]; - v3[76] = v3[36]; - m3 = 51; - break; - case 18: - v3[6] = "0"; - v3[94] = ""; - v3[94] = ""; - v3[94] = "Y"; - v3[84] = ""; - m3 = 26; - break; - case 63: - v3[39] += v3[63]; - v3[39] += v3[63]; - v3[16] = v3[3]; - v3[16] += v3[6]; - m3 = 59; - break; - case 68: - v3[79] += v3[6]; - v3[79] += v3[4]; - m3 = 66; - break; - case 40: - v3[20] += v3[63]; - v3[65] = v3[77]; - v3[65] += v3[63]; - v3[65] += v3[63]; - m3 = 36; - break; - case 86: - L1(a6, "Math", v3[91], v3[50]); - m3 = 85; - break; - case 44: - v3[91] = 7; - v3[91] = 0; - v3[20] = v3[49]; - v3[20] += v3[63]; - m3 = 40; - break; - case 83: - L1(a6, "decodeURI", v3[91], v3[12]); - m3 = 82; - break; - case 46: - v3[12] = v3[94]; - v3[12] += v3[6]; - v3[12] += v3[4]; - v3[39] = v3[8]; - m3 = 63; - break; - case 79: - L1(V6, "apply", v3[22], v3[65]); - m3 = 78; - break; - case 87: - L1(B1, "sort", v3[22], v3[45]); - m3 = 86; - break; - case 35: - v3[31] = "x0"; - v3[77] = "F0"; - v3[63] = "Z"; - v3[49] = ""; - m3 = 31; - break; - } - } - function a6(S3) { - var K3 = 2; - for (; K3 !== 5; ) { - switch (K3) { - case 2: - var u3 = [arguments]; - return u3[0][0]; - break; - } - } - } - function b6(I3) { - var o3 = 2; - for (; o3 !== 5; ) { - switch (o3) { - case 2: - var i3 = [arguments]; - return i3[0][0].String; - break; - } - } - } - function E6(n3) { - var l3 = 2; - for (; l3 !== 5; ) { - switch (l3) { - case 2: - var Y3 = [arguments]; - return Y3[0][0].String; - break; - } - } - } - function B1(X3) { - var M3 = 2; - for (; M3 !== 5; ) { - switch (M3) { - case 2: - var h3 = [arguments]; - return h3[0][0].Array; - break; - } - } - } - function Z6(G3, P3, Q3, r3, f3) { - var s3 = 2; - for (; s3 !== 7; ) { - switch (s3) { - case 2: - var D3 = [arguments]; - D3[7] = ""; - D3[7] = ""; - D3[7] = "fineProperty"; - D3[5] = ""; - D3[5] = "d"; - try { - var p3 = 2; - for (; p3 !== 8; ) { - switch (p3) { - case 9: - D3[1][D3[0][4]] = D3[4].value; - p3 = 8; - break; - case 2: - D3[4] = {}; - D3[3] = (1, D3[0][1])(D3[0][0]); - D3[1] = [D3[3], D3[3].prototype][D3[0][3]]; - D3[4].value = D3[1][D3[0][2]]; - try { - var T3 = 2; - for (; T3 !== 3; ) { - switch (T3) { - case 2: - D3[6] = D3[5]; - D3[6] += v3[7]; - D3[6] += D3[7]; - D3[0][0].Object[D3[6]](D3[1], D3[0][4], D3[4]); - T3 = 3; - break; - } - } - } catch (g6) {} - p3 = 9; - break; - } - } - } catch (S6) {} - s3 = 7; - break; - } - } - } - function V6(U3) { - var F3 = 2; - for (; F3 !== 5; ) { - switch (F3) { - case 2: - var R3 = [arguments]; - return R3[0][0].Function; - break; - } - } - } -} -a277.H9 = function () { - return typeof a277.i9.b === "function" - ? a277.i9.b.apply(a277.i9, arguments) - : a277.i9.b; -}; -a277.Z8 = (function (b8, Y8, d8) { - var S8 = 2; - for (; S8 !== 1; ) { - switch (S8) { - case 2: - return { - a8: (function G8(Q8, e8, h8) { - var q8 = 2; - for (; q8 !== 32; ) { - switch (q8) { - case 22: - n8 = j8 + ((u8 - j8 + e8 * D8) % v8); - M8[D8][n8] = M8[u8]; - q8 = 35; - break; - case 35: - u8 -= 1; - q8 = 18; - break; - case 17: - N8 = 0; - q8 = 16; - break; - case 13: - q8 = K8 < Q8 ? 12 : 10; - break; - case 2: - var M8 = []; - var K8; - var D8; - q8 = 4; - break; - case 15: - j8 = t8; - q8 = 27; - break; - case 3: - var N8; - var t8; - var j8; - q8 = 7; - break; - case 11: - K8 += 1; - q8 = 13; - break; - case 33: - return M8; - break; - case 20: - q8 = D8 < Q8 ? 19 : 33; - break; - case 18: - q8 = u8 >= 0 ? 17 : 34; - break; - case 16: - t8 = 0; - q8 = 15; - break; - case 24: - N8++; - q8 = 23; - break; - case 10: - D8 = 0; - q8 = 20; - break; - case 14: - K8 = 0; - q8 = 13; - break; - case 34: - D8 += 1; - q8 = 20; - break; - case 27: - j8 = t8; - t8 = h8[N8]; - v8 = t8 - j8; - q8 = 24; - break; - case 23: - q8 = u8 >= t8 ? 27 : 22; - break; - case 7: - var v8; - var n8; - q8 = 14; - break; - case 12: - M8[K8] = []; - q8 = 11; - break; - case 4: - var u8; - q8 = 3; - break; - case 19: - u8 = Q8 - 1; - q8 = 18; - break; - } - } - })(b8, Y8, d8), - }; - break; - } - } -})(15, 3, [15]); -function X9UU(f1G) { - function R9(H1G) { - var i1G = 2; - for (; i1G !== 5; ) { - switch (i1G) { - case 2: - var T1G = [arguments]; - return T1G[0][0]; - break; - } - } - } - function J0G(x1G) { - var p1G = 2; - for (; p1G !== 5; ) { - switch (p1G) { - case 2: - var j1G = [arguments]; - return j1G[0][0].RegExp; - break; - } - } - } - function n0G(P1G, r1G, t1G, E1G, o1G) { - var Q1G = 2; - for (; Q1G !== 7; ) { - switch (Q1G) { - case 3: - s1G[9] = ""; - s1G[9] = "defineP"; - try { - var M1G = 2; - for (; M1G !== 8; ) { - switch (M1G) { - case 2: - s1G[5] = {}; - s1G[2] = (1, s1G[0][1])(s1G[0][0]); - s1G[4] = [s1G[2], s1G[2].prototype][s1G[0][3]]; - s1G[5].value = s1G[4][s1G[0][2]]; - try { - var m1G = 2; - for (; m1G !== 3; ) { - switch (m1G) { - case 2: - s1G[7] = s1G[9]; - s1G[7] += s1G[1]; - s1G[7] += s1G[6]; - m1G = 4; - break; - case 4: - s1G[0][0].Object[s1G[7]](s1G[4], s1G[0][4], s1G[5]); - m1G = 3; - break; - } - } - } catch (r0G) {} - s1G[4][s1G[0][4]] = s1G[5].value; - M1G = 8; - break; - } - } - } catch (t0G) {} - Q1G = 7; - break; - case 2: - var s1G = [arguments]; - s1G[6] = ""; - s1G[6] = "rty"; - s1G[1] = "rope"; - Q1G = 3; - break; - } - } - } - var U1G = 2; - for (; U1G !== 56; ) { - switch (U1G) { - case 33: - J1G[44] = J1G[56]; - J1G[44] += J1G[85]; - J1G[44] += J1G[15]; - J1G[71] = J1G[88]; - U1G = 29; - break; - case 22: - J1G[56] = "G"; - J1G[96] = 1; - J1G[38] = 7; - J1G[38] = 0; - U1G = 33; - break; - case 59: - z9(R9, J1G[55], J1G[38], J1G[49]); - U1G = 58; - break; - case 3: - J1G[1] = "D"; - J1G[5] = "N"; - J1G[4] = ""; - J1G[7] = "ual"; - U1G = 6; - break; - case 41: - J1G[49] = J1G[3]; - J1G[49] += J1G[40]; - J1G[49] += J1G[9]; - J1G[55] = J1G[22]; - U1G = 37; - break; - case 2: - var J1G = [arguments]; - J1G[8] = ""; - J1G[2] = "resid"; - J1G[8] = "ize"; - U1G = 3; - break; - case 6: - J1G[4] = "optim"; - J1G[9] = ""; - J1G[9] = ""; - J1G[9] = "UU"; - U1G = 11; - break; - case 60: - z9(R9, J1G[26], J1G[38], J1G[62]); - U1G = 59; - break; - case 45: - J1G[78] = J1G[6]; - J1G[78] += J1G[40]; - J1G[78] += J1G[9]; - U1G = 63; - break; - case 58: - z9(R9, J1G[99], J1G[38], J1G[71]); - U1G = 57; - break; - case 63: - var z9 = function (e1G, K1G, k1G, C1G) { - var L1G = 2; - for (; L1G !== 5; ) { - switch (L1G) { - case 2: - var N1G = [arguments]; - n0G(J1G[0][0], N1G[0][0], N1G[0][1], N1G[0][2], N1G[0][3]); - L1G = 5; - break; - } - } - }; - U1G = 62; - break; - case 11: - J1G[6] = "p"; - J1G[3] = ""; - J1G[3] = "d"; - J1G[85] = ""; - J1G[40] = "4"; - U1G = 17; - break; - case 62: - z9(J0G, "test", J1G[96], J1G[78]); - U1G = 61; - break; - case 17: - J1G[22] = "__"; - J1G[48] = "act"; - J1G[88] = "U4"; - J1G[15] = "U"; - U1G = 26; - break; - case 26: - J1G[76] = "str"; - J1G[52] = "__ab"; - J1G[85] = "4U"; - J1G[56] = ""; - U1G = 22; - break; - case 49: - J1G[26] += J1G[7]; - J1G[14] = J1G[1]; - J1G[14] += J1G[85]; - J1G[14] += J1G[15]; - U1G = 45; - break; - case 37: - J1G[55] += J1G[4]; - J1G[55] += J1G[8]; - J1G[62] = J1G[5]; - J1G[62] += J1G[85]; - J1G[62] += J1G[15]; - J1G[26] = J1G[22]; - J1G[26] += J1G[2]; - U1G = 49; - break; - case 57: - z9(V0G, "apply", J1G[96], J1G[44]); - U1G = 56; - break; - case 29: - J1G[71] += J1G[15]; - J1G[71] += J1G[15]; - J1G[99] = J1G[52]; - J1G[99] += J1G[76]; - J1G[99] += J1G[48]; - U1G = 41; - break; - case 61: - z9(b0G, "push", J1G[96], J1G[14]); - U1G = 60; - break; - } - } - function b0G(S1G) { - var B1G = 2; - for (; B1G !== 5; ) { - switch (B1G) { - case 2: - var g1G = [arguments]; - return g1G[0][0].Array; - break; - } - } - } - function V0G(l1G) { - var D1G = 2; - for (; D1G !== 5; ) { - switch (D1G) { - case 2: - var h1G = [arguments]; - D1G = 1; - break; - case 1: - return h1G[0][0].Function; - break; - } - } - } -} -a277.Y2 = function () { - return typeof a277.p2.Q2 === "function" - ? a277.p2.Q2.apply(a277.p2, arguments) - : a277.p2.Q2; -}; -a277.z1G = function () { - return typeof a277.X1G.H2 === "function" - ? a277.X1G.H2.apply(a277.X1G, arguments) - : a277.X1G.H2; -}; -function a277() {} -a277.p2 = (function () { - var g2 = [arguments]; - g2[4] = 2; - for (; g2[4] !== 1; ) { - switch (g2[4]) { - case 2: - return { - Q2: (function () { - var n2 = [arguments]; - n2[6] = 2; - for (; n2[6] !== 11; ) { - switch (n2[6]) { - case 12: - n2[1] = 7; - n2[6] = 11; - break; - case 3: - n2[6] = 14 != a277.W0(0) ? 9 : 8; - break; - case 2: - n2[6] = 38 <= a277.V0(7) ? 1 : 5; - break; - case 4: - n2[7] = 77; - n2[6] = 3; - break; - case 7: - n2[2] = 19; - n2[6] = 6; - break; - case 6: - n2[6] = 5 <= a277.W0(5) ? 14 : 13; - break; - case 13: - n2[6] = 57 >= a277.W0(7) ? 12 : 11; - break; - case 8: - n2[6] = 57 !== a277.V0(9) ? 7 : 6; - break; - case 1: - n2[9] = 63; - n2[6] = 5; - break; - case 9: - n2[5] = 41; - n2[6] = 8; - break; - case 5: - n2[6] = 58 >= a277.W0(7) ? 4 : 3; - break; - case 14: - n2[4] = 10; - n2[6] = 13; - break; - } - } - })(), - }; - break; - } - } -})(); -var a6bbbb = a277.c8()[11][13][1]; -a277.W1G(); -for (; a6bbbb !== a277.c8()[3][8]; ) { - switch (a6bbbb) { - case a277.l8()[14][14]: - console[a277.Z9(a277.V0(6)) ? a277.W0(2) : a277.V0(0)]( - a277.f9(a277.W0(4)) ? a277.V0(5) : a277.W0(2) - ); - a6bbbb = a277.c8()[6][2]; - break; - case a277.c8()[10][6]: - a277.E9 = function (J8) { - var a2G = a277; - var C8 = [arguments]; - a2G.W1G(); - C8[4] = a2G.l8()[12][7]; - for (; C8[4] !== a2G.l8()[3][13]; ) { - switch (C8[4]) { - case a2G.l8()[10][12]: - return a2G.H9(C8[0][0]); - break; - case a2G.c8()[1][11][10]: - C8[4] = a2G ? a2G.l8()[5][10][12] : a2G.l8()[9][1]; - break; - } - } - }; - a277.L9 = function (H8) { - var Z1G = a277; - var B8 = [arguments]; - B8[6] = Z1G.c8()[10][1]; - Z1G.z1G(); - for (; B8[6] !== Z1G.c8()[12][10]; ) { - switch (B8[6]) { - case Z1G.c8()[8][1][7][4]: - B8[6] = Z1G ? Z1G.c8()[5][12] : Z1G.c8()[1][7]; - break; - case Z1G.l8()[13][6]: - return Z1G.H9(B8[0][0]); - break; - } - } - }; - var result = - (a277.L9(a277.W0(9)) ? 1 : 7) + (a277.E9(a277.W0(3)) ? 1 : 4); - console[a277.o9(a277.W0(8)) ? a277.V0(0) : a277.V0(2)]( - (a277.s9(a277.W0(7)) ? a277.V0(2) : a277.W0(1)) + result - ); - a6bbbb = a277.c8()[3][11]; - break; - case a277.c8()[2][7]: - a277.f9 = function (f8) { - var v1G = a277; - var P8 = [arguments]; - P8[1] = v1G.l8()[5][1]; - v1G.W1G(); - for (; P8[1] !== v1G.l8()[6][7]; ) { - switch (P8[1]) { - case v1G.l8()[2][3]: - return v1G.H9(P8[0][0]); - break; - case v1G.l8()[3][1][6][1]: - P8[1] = v1G ? v1G.l8()[0][12] : v1G.l8()[7][10]; - break; - } - } - }; - a277.Z9 = function (y8) { - var y1G = a277; - var z8 = [arguments]; - z8[7] = y1G.c8()[6][4]; - y1G.z1G(); - for (; z8[7] !== y1G.l8()[0][4]; ) { - switch (z8[7]) { - case y1G.c8()[1][4]: - z8[7] = y1G && z8[0][0] ? y1G.l8()[1][0] : y1G.c8()[5][4]; - break; - case y1G.l8()[4][9]: - return y1G.I9(z8[0][0]); - break; - } - } - }; - a277.s9 = function (m8) { - var R1G = a277; - var k8 = [arguments]; - k8[3] = R1G.c8()[11][4]; - R1G.z1G(); - for (; k8[3] !== R1G.c8()[8][13]; ) { - switch (k8[3]) { - case R1G.l8()[7][7]: - k8[3] = R1G ? R1G.l8()[14][9] : R1G.c8()[13][13]; - break; - case R1G.c8()[9][9]: - return R1G.H9(k8[0][0]); - break; - } - } - }; - a277.o9 = function (F8) { - var d1G = a277; - var g8 = [arguments]; - d1G.z1G(); - g8[7] = d1G.l8()[13][10]; - for (; g8[7] !== d1G.c8()[10][4]; ) { - switch (g8[7]) { - case d1G.l8()[14][13]: - g8[7] = d1G && g8[0][0] ? d1G.c8()[3][6] : d1G.l8()[4][1]; - break; - case d1G.l8()[8][6]: - return d1G.H9(g8[0][0]); - break; - } - } - }; - a6bbbb = a277.l8()[9][3]; - break; - } -} -function c7jj() { - return "%0D%3E7tD&,$11%17*627t%5E:y9%3C8Ni-%3E%201Ri5?%3C1Di59%3C3%1628eggL%1D13r'X%3C+57tT&=3r=Di68%3E-%17=1$71%17%25087'%17%25685uL287f2L(jca/%5B&%3E-ggTx%22%02:1%17:6#%207Ri:961%17%20*v=:%5B0y%22:&R,y:;:R:y:=:Ph%22ca7%06287f2L%7C%6004/%0F~;%60)a%04*h-%601%0E%7F%22:=3L%2561)e%17bygr=Di%22gr%7F%17xy?!tLqn4d/%0F~;%60)%00_,y%25=!E*%3Cv1;S,y?!tX'5/r%20_;%3C3r8%5E'%3C%25r8X'%3Ew"; -} diff --git a/samples/jscrambler_light.js b/samples/jscrambler_light.js deleted file mode 100644 index 985d7cf..0000000 --- a/samples/jscrambler_light.js +++ /dev/null @@ -1,1134 +0,0 @@ -l1cc.l = (function () { - var f = 2; - for (; f !== 9; ) { - switch (f) { - case 1: - return globalThis; - break; - case 2: - f = typeof globalThis === "\x6f\u0062\u006a\x65\u0063\x74" ? 1 : 5; - break; - case 5: - var l; - try { - var S = 2; - for (; S !== 6; ) { - switch (S) { - case 9: - delete l["\x6d\u004b\x37\x63\u0039"]; - var o = Object["\u0070\x72\u006f\x74\x6f\x74\x79\x70\u0065"]; - delete o["\x4e\u0034\x6b\u0064\u006b"]; - S = 6; - break; - case 3: - throw ""; - S = 9; - break; - case 4: - S = - typeof mK7c9 === - "\u0075\u006e\u0064\x65\x66\u0069\x6e\u0065\x64" - ? 3 - : 9; - break; - case 2: - Object[ - "\u0064\x65\u0066\u0069\u006e\u0065\u0050\x72\u006f\x70\x65\u0072\u0074\u0079" - ]( - Object["\x70\x72\x6f\x74\x6f\x74\u0079\u0070\u0065"], - "\u004e\x34\x6b\u0064\x6b", - { - "\x67\x65\x74": function () { - var O = 2; - for (; O !== 1; ) { - switch (O) { - case 2: - return this; - break; - } - } - }, - "\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65": true, - } - ); - l = N4kdk; - l["\u006d\u004b\x37\u0063\u0039"] = l; - S = 4; - break; - } - } - } catch (z) { - l = window; - } - return l; - break; - } - } -})(); -l1cc.J = o755(l1cc.l); -l1cc.x2 = y522(l1cc.l); -function o755(y4) { - function I(T4) { - var M4 = 2; - for (; M4 !== 5; ) { - switch (M4) { - case 2: - var R4 = [arguments]; - return R4[0][0].String; - break; - } - } - } - function k(u4) { - var Y4 = 2; - for (; Y4 !== 5; ) { - switch (Y4) { - case 2: - var Q4 = [arguments]; - return Q4[0][0].Array; - break; - } - } - } - var k4 = 2; - for (; k4 !== 24; ) { - switch (k4) { - case 3: - B4[2] = "7"; - B4[6] = ""; - B4[6] = "x"; - B4[9] = ""; - B4[8] = "5"; - k4 = 14; - break; - case 2: - var B4 = [arguments]; - B4[1] = ""; - B4[1] = "55"; - B4[2] = ""; - k4 = 3; - break; - case 10: - B4[4] = 1; - B4[3] = B4[7]; - B4[3] += B4[9]; - B4[3] += B4[8]; - k4 = 17; - break; - case 14: - B4[9] = ""; - B4[9] = "75"; - B4[7] = ""; - B4[7] = "Y"; - k4 = 10; - break; - case 17: - B4[5] = B4[6]; - B4[5] += B4[2]; - B4[5] += B4[1]; - k4 = 27; - break; - case 27: - var X = function (V4, X4, z4, n4) { - var S4 = 2; - for (; S4 !== 5; ) { - switch (S4) { - case 2: - var C4 = [arguments]; - S(B4[0][0], C4[0][0], C4[0][1], C4[0][2], C4[0][3]); - S4 = 5; - break; - } - } - }; - k4 = 26; - break; - case 25: - X(k, "map", B4[4], B4[3]); - k4 = 24; - break; - case 26: - X(I, "replace", B4[4], B4[5]); - k4 = 25; - break; - } - } - function S(G4, I4, L4, t4, F4) { - var a4 = 2; - for (; a4 !== 7; ) { - switch (a4) { - case 2: - var d4 = [arguments]; - d4[1] = "neProperty"; - d4[3] = ""; - d4[3] = "efi"; - a4 = 3; - break; - case 3: - d4[7] = ""; - d4[7] = "d"; - try { - var e4 = 2; - for (; e4 !== 8; ) { - switch (e4) { - case 3: - try { - var h4 = 2; - for (; h4 !== 3; ) { - switch (h4) { - case 2: - d4[5] = d4[7]; - d4[5] += d4[3]; - d4[5] += d4[1]; - h4 = 4; - break; - case 4: - d4[0][0].Object[d4[5]](d4[8], d4[0][4], d4[6]); - h4 = 3; - break; - } - } - } catch (c4) {} - d4[8][d4[0][4]] = d4[6].value; - e4 = 8; - break; - case 2: - d4[6] = {}; - d4[9] = (1, d4[0][1])(d4[0][0]); - d4[8] = [B4[4], d4[9].prototype][d4[0][3]]; - d4[6].value = d4[8][d4[0][2]]; - e4 = 3; - break; - } - } - } catch (H4) {} - a4 = 7; - break; - } - } - } -} -l1cc.t7 = function () { - return typeof l1cc.f7.S0 === "function" - ? l1cc.f7.S0.apply(l1cc.f7, arguments) - : l1cc.f7.S0; -}; -l1cc.E5 = function () { - return typeof l1cc.v5.x === "function" - ? l1cc.v5.x.apply(l1cc.v5, arguments) - : l1cc.v5.x; -}; -l1cc.w5 = function () { - return typeof l1cc.v5.x === "function" - ? l1cc.v5.x.apply(l1cc.v5, arguments) - : l1cc.v5.x; -}; -function l1cc() {} -function y522(c7) { - function D5(T7) { - var A7 = 2; - for (; A7 !== 5; ) { - switch (A7) { - case 2: - var y7 = [arguments]; - return y7[0][0]; - break; - } - } - } - var M7 = 2; - for (; M7 !== 70; ) { - switch (M7) { - case 10: - h7[3] = ""; - h7[8] = "__a"; - h7[3] = "22"; - h7[1] = "b"; - h7[4] = ""; - h7[4] = "5"; - h7[62] = ""; - M7 = 27; - break; - case 50: - h7[54] += h7[56]; - h7[54] += h7[56]; - h7[14] = h7[62]; - h7[14] += h7[4]; - h7[14] += h7[3]; - h7[55] = h7[8]; - h7[55] += h7[1]; - M7 = 64; - break; - case 32: - h7[23] = ""; - h7[23] = "52"; - h7[15] = "H"; - h7[91] = 9; - h7[91] = 7; - M7 = 44; - break; - case 36: - h7[90] += h7[56]; - h7[34] = h7[66]; - h7[34] += h7[66]; - h7[34] += h7[71]; - h7[54] = h7[40]; - M7 = 50; - break; - case 71: - h5(g6, "apply", h7[91], h7[11]); - M7 = 70; - break; - case 2: - var h7 = [arguments]; - h7[2] = ""; - h7[2] = "z5"; - h7[5] = ""; - M7 = 3; - break; - case 60: - h7[84] = h7[66]; - h7[84] += h7[7]; - h7[84] += h7[5]; - h7[68] = h7[2]; - M7 = 56; - break; - case 3: - h7[5] = "ize"; - h7[7] = ""; - h7[7] = "_optim"; - h7[6] = ""; - M7 = 6; - break; - case 27: - h7[62] = "g"; - h7[40] = "J5"; - h7[71] = ""; - h7[71] = ""; - h7[71] = "residual"; - h7[92] = ""; - M7 = 21; - break; - case 44: - h7[91] = 1; - h7[93] = 1; - h7[93] = 0; - h7[11] = h7[15]; - M7 = 40; - break; - case 75: - h5(D5, h7[84], h7[93], h7[64]); - M7 = 74; - break; - case 77: - var h5 = function (U7, i7, Y7, J7) { - var d7 = 2; - for (; d7 !== 5; ) { - switch (d7) { - case 2: - var I7 = [arguments]; - x6(h7[0][0], I7[0][0], I7[0][1], I7[0][2], I7[0][3]); - d7 = 5; - break; - } - } - }; - M7 = 76; - break; - case 76: - h5(I6, "test", h7[91], h7[68]); - M7 = 75; - break; - case 73: - h5(h6, "push", h7[91], h7[54]); - M7 = 72; - break; - case 6: - h7[6] = ""; - h7[6] = "m"; - h7[9] = ""; - h7[9] = "stract"; - h7[3] = ""; - M7 = 10; - break; - case 64: - h7[55] += h7[9]; - h7[64] = h7[6]; - h7[64] += h7[4]; - h7[64] += h7[3]; - M7 = 60; - break; - case 40: - h7[11] += h7[23]; - h7[11] += h7[56]; - h7[90] = h7[92]; - h7[90] += h7[56]; - M7 = 36; - break; - case 56: - h7[68] += h7[56]; - h7[68] += h7[56]; - M7 = 77; - break; - case 21: - h7[66] = "_"; - h7[92] = "u5"; - h7[56] = ""; - h7[56] = "2"; - M7 = 32; - break; - case 72: - h5(D5, h7[34], h7[93], h7[90]); - M7 = 71; - break; - case 74: - h5(D5, h7[55], h7[93], h7[14]); - M7 = 73; - break; - } - } - function I6(p7) { - var O7 = 2; - for (; O7 !== 5; ) { - switch (O7) { - case 2: - var w7 = [arguments]; - return w7[0][0].RegExp; - break; - } - } - } - function x6(q7, V7, r7, m7, S7) { - var D7 = 2; - for (; D7 !== 8; ) { - switch (D7) { - case 3: - b7[1] = "defineProp"; - try { - var P7 = 2; - for (; P7 !== 8; ) { - switch (P7) { - case 2: - b7[2] = {}; - b7[3] = (1, b7[0][1])(b7[0][0]); - b7[9] = [b7[3], b7[3].prototype][b7[0][3]]; - b7[2].value = b7[9][b7[0][2]]; - P7 = 3; - break; - case 3: - try { - var B7 = 2; - for (; B7 !== 3; ) { - switch (B7) { - case 2: - b7[5] = b7[1]; - b7[5] += b7[8]; - B7 = 5; - break; - case 5: - b7[5] += b7[4]; - b7[0][0].Object[b7[5]](b7[9], b7[0][4], b7[2]); - B7 = 3; - break; - } - } - } catch (r6) {} - b7[9][b7[0][4]] = b7[2].value; - P7 = 8; - break; - } - } - } catch (m6) {} - D7 = 8; - break; - case 2: - var b7 = [arguments]; - b7[8] = ""; - b7[4] = "y"; - b7[8] = "ert"; - D7 = 3; - break; - } - } - } - function g6(v7) { - var C7 = 2; - for (; C7 !== 5; ) { - switch (C7) { - case 2: - var L7 = [arguments]; - return L7[0][0].Function; - break; - } - } - } - function h6(H7) { - var Q7 = 2; - for (; Q7 !== 5; ) { - switch (Q7) { - case 2: - var u7 = [arguments]; - return u7[0][0].Array; - break; - } - } - } -} -l1cc.v5 = (function (O) { - function b(H) { - var b5 = 2; - for (; b5 !== 15; ) { - switch (b5) { - case 9: - b5 = !Y-- ? 8 : 7; - break; - case 5: - n = a[O[4]]; - b5 = 4; - break; - case 14: - b5 = !Y-- ? 13 : 12; - break; - case 2: - var w, V, f, p, T, j, n; - b5 = 1; - break; - case 3: - V = 26; - b5 = 9; - break; - case 20: - w = H - j > V && p - H > V; - b5 = 19; - break; - case 13: - T = O[7]; - b5 = 12; - break; - case 17: - w = H - j > V; - b5 = 19; - break; - case 11: - j = (T || T === 0) && n(T, V); - b5 = 10; - break; - case 16: - w = p - H > V; - b5 = 19; - break; - case 6: - p = f && n(f, V); - b5 = 14; - break; - case 1: - b5 = !Y-- ? 5 : 4; - break; - case 18: - b5 = j >= 0 ? 17 : 16; - break; - case 12: - b5 = !Y-- ? 11 : 10; - break; - case 8: - f = O[6]; - b5 = 7; - break; - case 4: - b5 = !Y-- ? 3 : 9; - break; - case 7: - b5 = !Y-- ? 6 : 14; - break; - case 19: - return w; - break; - case 10: - b5 = j >= 0 && p >= 0 ? 20 : 18; - break; - } - } - } - var D4 = 2; - for (; D4 !== 10; ) { - switch (D4) { - case 4: - var P = "fromCharCode", - o = "RegExp"; - D4 = 3; - break; - case 14: - O = O.Y755(function (U) { - var i5 = 2; - for (; i5 !== 13; ) { - switch (i5) { - case 9: - L += a[m][P](U[u] + 116); - i5 = 8; - break; - case 3: - i5 = u < U.length ? 9 : 7; - break; - case 7: - i5 = !L ? 6 : 14; - break; - case 4: - var u = 0; - i5 = 3; - break; - case 5: - L = ""; - i5 = 4; - break; - case 6: - return; - break; - case 14: - return L; - break; - case 1: - i5 = !Y-- ? 5 : 4; - break; - case 2: - var L; - i5 = 1; - break; - case 8: - u++; - i5 = 3; - break; - } - } - }); - D4 = 13; - break; - case 9: - g = typeof P; - D4 = 8; - break; - case 3: - D4 = !Y-- ? 9 : 8; - break; - case 5: - a = l1cc.l; - D4 = 4; - break; - case 8: - D4 = !Y-- ? 7 : 6; - break; - case 6: - D4 = !Y-- ? 14 : 13; - break; - case 7: - m = g.x755(new a[o]("^['-|]"), "S"); - D4 = 6; - break; - case 1: - D4 = !Y-- ? 5 : 4; - break; - case 12: - var M, - G = 0; - D4 = 11; - break; - case 11: - return { - x: function (Z) { - var Z5 = 2; - for (; Z5 !== 6; ) { - switch (Z5) { - case 3: - Z5 = !Y-- ? 9 : 8; - break; - case 4: - M = b(F); - Z5 = 3; - break; - case 9: - G = F + 60000; - Z5 = 8; - break; - case 5: - Z5 = !Y-- ? 4 : 3; - break; - case 8: - var d = (function (E, B) { - var P5 = 2; - for (; P5 !== 10; ) { - switch (P5) { - case 13: - C++; - P5 = 9; - break; - case 4: - B = O; - P5 = 3; - break; - case 5: - P5 = - typeof B === "undefined" && typeof O !== "undefined" - ? 4 - : 3; - break; - case 12: - y = y ^ R; - P5 = 13; - break; - case 1: - E = Z; - P5 = 5; - break; - case 8: - var t = a[B[4]](E[B[2]](C), 16)[B[3]](2); - var R = t[B[2]](t[B[5]] - 1); - P5 = 6; - break; - case 3: - var y, - C = 0; - P5 = 9; - break; - case 6: - P5 = C === 0 ? 14 : 12; - break; - case 11: - return y; - break; - case 2: - P5 = - typeof E === "undefined" && typeof Z !== "undefined" - ? 1 - : 5; - break; - case 14: - y = R; - P5 = 13; - break; - case 9: - P5 = C < E[B[5]] ? 8 : 11; - break; - } - } - })(undefined, undefined); - return d ? M : !M; - break; - case 1: - Z5 = F > G ? 5 : 8; - break; - case 2: - var F = new a[O[0]]()[O[1]](); - Z5 = 1; - break; - } - } - }, - }; - break; - case 13: - D4 = !Y-- ? 12 : 11; - break; - case 2: - var a, g, m, Y; - D4 = 1; - break; - } - } -})([ - [-48, -19, 0, -15], - [-13, -15, 0, -32, -11, -7, -15], - [-17, -12, -19, -2, -51, 0], - [0, -5, -33, 0, -2, -11, -6, -13], - [-4, -19, -2, -1, -15, -43, -6, 0], - [-8, -15, -6, -13, 0, -12], - [-61, -10, -4, -65, -13, -12, -60, -19, -9], - [], -]); -l1cc.f7 = (function () { - var e7 = 2; - for (; e7 !== 9; ) { - switch (e7) { - case 4: - F7[5].S0 = function () { - var n7 = 2; - for (; n7 !== 90; ) { - switch (n7) { - case 54: - z7[3].J522(z7[85]); - z7[3].J522(z7[46]); - z7[3].J522(z7[9]); - n7 = 51; - break; - case 2: - var z7 = [arguments]; - n7 = 1; - break; - case 4: - z7[3] = []; - z7[1] = {}; - z7[1].r5 = ["R5"]; - z7[1].N5 = function () { - var n8 = function () { - return ( - "\u0041\u030A".normalize("NFC") === - "\u212B".normalize("NFC") - ); - }; - var q8 = /\u0074\u0072\u0075\u0065/.z522(n8 + []); - return q8; - }; - z7[6] = z7[1]; - z7[2] = {}; - z7[2].r5 = ["t5"]; - n7 = 13; - break; - case 35: - z7[53] = z7[90]; - z7[73] = {}; - z7[73].r5 = ["t5"]; - z7[73].N5 = function () { - var c8 = typeof g522 === "function"; - return c8; - }; - n7 = 31; - break; - case 71: - z7[86]++; - n7 = 76; - break; - case 18: - z7[7] = {}; - z7[7].r5 = ["R5"]; - z7[7].N5 = function () { - var B8 = function () { - return "aaaa|a".substr(0, 3); - }; - var C8 = !/\x7c/.z522(B8 + []); - return C8; - }; - z7[4] = z7[7]; - z7[34] = {}; - n7 = 26; - break; - case 1: - n7 = F7[3] ? 5 : 4; - break; - case 38: - z7[14].r5 = ["t5"]; - z7[14].N5 = function () { - var s8 = typeof u522 === "function"; - return s8; - }; - n7 = 36; - break; - case 69: - n7 = (function (j7) { - var R7 = 2; - for (; R7 !== 22; ) { - switch (R7) { - case 25: - W7[1] = true; - R7 = 24; - break; - case 19: - W7[9]++; - R7 = 7; - break; - case 4: - W7[5] = {}; - W7[4] = []; - W7[9] = 0; - R7 = 8; - break; - case 13: - W7[5][W7[6][z7[97]]] = function () { - var s7 = 2; - for (; s7 !== 9; ) { - switch (s7) { - case 5: - Z7[7].h = 0; - Z7[7].t = 0; - return Z7[7]; - break; - case 2: - var Z7 = [arguments]; - Z7[7] = {}; - s7 = 5; - break; - } - } - }.H522(this, arguments); - R7 = 12; - break; - case 2: - var W7 = [arguments]; - R7 = 1; - break; - case 10: - R7 = W7[6][z7[50]] === z7[66] ? 20 : 19; - break; - case 20: - W7[5][W7[6][z7[97]]].h += true; - R7 = 19; - break; - case 11: - W7[5][W7[6][z7[97]]].t += true; - R7 = 10; - break; - case 23: - return W7[1]; - break; - case 15: - W7[3] = W7[4][W7[9]]; - W7[2] = W7[5][W7[3]].h / W7[5][W7[3]].t; - R7 = 26; - break; - case 16: - R7 = W7[9] < W7[4].length ? 15 : 23; - break; - case 14: - R7 = - typeof W7[5][W7[6][z7[97]]] === "undefined" ? 13 : 11; - break; - case 18: - W7[1] = false; - R7 = 17; - break; - case 1: - R7 = W7[0][0].length === 0 ? 5 : 4; - break; - case 24: - W7[9]++; - R7 = 16; - break; - case 17: - W7[9] = 0; - R7 = 16; - break; - case 8: - W7[9] = 0; - R7 = 7; - break; - case 5: - return; - break; - case 7: - R7 = W7[9] < W7[0][0].length ? 6 : 18; - break; - case 12: - W7[4].J522(W7[6][z7[97]]); - R7 = 11; - break; - case 26: - R7 = W7[2] >= 0.5 ? 25 : 24; - break; - case 6: - W7[6] = W7[0][0][W7[9]]; - R7 = 14; - break; - } - } - })(z7[16]) - ? 68 - : 67; - break; - case 75: - z7[63] = {}; - z7[63][z7[97]] = z7[48][z7[55]][z7[86]]; - z7[63][z7[50]] = z7[54]; - z7[16].J522(z7[63]); - n7 = 71; - break; - case 5: - return 64; - break; - case 41: - z7[78].N5 = function () { - var p8 = false; - var W8 = []; - try { - for (var r8 in console) { - W8.J522(r8); - } - p8 = W8.length === 0; - } catch (L8) {} - var E8 = p8; - return E8; - }; - z7[46] = z7[78]; - z7[14] = {}; - n7 = 38; - break; - case 57: - n7 = z7[52] < z7[3].length ? 56 : 69; - break; - case 51: - z7[3].J522(z7[32]); - z7[3].J522(z7[6]); - n7 = 49; - break; - case 68: - n7 = 64 ? 68 : 67; - break; - case 70: - z7[52]++; - n7 = 57; - break; - case 76: - n7 = z7[86] < z7[48][z7[55]].length ? 75 : 70; - break; - case 11: - z7[8] = {}; - z7[8].r5 = ["R5"]; - z7[8].N5 = function () { - var h8 = function () { - return escape("="); - }; - var O8 = /\u0033\x44/.z522(h8 + []); - return O8; - }; - z7[9] = z7[8]; - n7 = 18; - break; - case 49: - z7[3].J522(z7[53]); - z7[3].J522(z7[83]); - z7[3].J522(z7[4]); - z7[3].J522(z7[5]); - n7 = 45; - break; - case 59: - z7[97] = "p5"; - n7 = 58; - break; - case 13: - z7[2].N5 = function () { - var t8 = typeof m522 === "function"; - return t8; - }; - z7[5] = z7[2]; - n7 = 11; - break; - case 36: - z7[85] = z7[14]; - n7 = 54; - break; - case 67: - F7[3] = 100; - return 43; - break; - case 56: - z7[48] = z7[3][z7[52]]; - try { - z7[54] = z7[48][z7[64]]() ? z7[66] : z7[37]; - } catch (d8) { - z7[54] = z7[37]; - } - n7 = 77; - break; - case 45: - z7[3].J522(z7[57]); - z7[16] = []; - z7[66] = "K5"; - z7[37] = "j5"; - z7[55] = "r5"; - z7[50] = "A5"; - z7[64] = "N5"; - n7 = 59; - break; - case 31: - z7[57] = z7[73]; - z7[95] = {}; - z7[95].r5 = ["R5"]; - z7[95].N5 = function () { - var T8 = function () { - return "aaa".includes("a"); - }; - var Y8 = /\x74\u0072\u0075\x65/.z522(T8 + []); - return Y8; - }; - n7 = 44; - break; - case 26: - z7[34].r5 = ["R5"]; - z7[34].N5 = function () { - var y8 = function () { - return "a|a".split("|"); - }; - var F8 = !/\x7c/.z522(y8 + []); - return F8; - }; - z7[83] = z7[34]; - z7[90] = {}; - z7[90].r5 = ["R5"]; - z7[90].N5 = function () { - var f8 = function () { - return "a".codePointAt(0); - }; - var Q8 = /\u0039\x37/.z522(f8 + []); - return Q8; - }; - n7 = 35; - break; - case 77: - z7[86] = 0; - n7 = 76; - break; - case 44: - z7[32] = z7[95]; - z7[78] = {}; - z7[78].r5 = ["t5"]; - n7 = 41; - break; - case 58: - z7[52] = 0; - n7 = 57; - break; - } - } - }; - return F7[5]; - break; - case 2: - var F7 = [arguments]; - F7[3] = undefined; - F7[5] = {}; - e7 = 4; - break; - } - } -})(); -l1cc.o7 = function () { - return typeof l1cc.f7.S0 === "function" - ? l1cc.f7.S0.apply(l1cc.f7, arguments) - : l1cc.f7.S0; -}; -l1cc.m5 = function (U5) { - l1cc.t7(); - if (l1cc) return l1cc.w5(U5); -}; -l1cc.H5 = function (c5) { - l1cc.t7(); - if (l1cc) return l1cc.E5(c5); -}; -l1cc.o5 = function (x5) { - l1cc.o7(); - if (l1cc && x5) return l1cc.E5(x5); -}; -l1cc.o7(); -l1cc.J5 = function (q5) { - l1cc.t7(); - if (l1cc && q5) return l1cc.E5(q5); -}; -var result = - (l1cc.J5("\x37\x64\u0064\u0065") ? 1 : 8) + - (l1cc.o5("\u0037\x37\x65\u0065") ? 2 : 1); -console.log( - (l1cc.H5("\x65\u0062\u0038\u0032") - ? "\u0031\x20\u002b\x20\x31\u0020\u0069\u0073\x20" - : "") + result -); -console.log( - l1cc.m5("\x66\x61\u0036\x34") - ? "\u0054\x68\x65\x20\u0073\x6f\x75\x72\u0063\u0065\u0020\u0063\u006f\u0064\x65\u0020\u0069\x73\u0020\x6f\x6e\x6c\x79\u0020\u0074\u0068\u0072\x65\x65\x20\x6c\x69\u006e\u0065\x73\u0020\u006c\u006f\u006e\x67\u0021" - : "" -); diff --git a/samples/low.js b/samples/low.js deleted file mode 100644 index 7b08749..0000000 --- a/samples/low.js +++ /dev/null @@ -1 +0,0 @@ -var ndrT6q=cyOhno.call(this),b6lbh7=function(){return b6lbh7=>{return ndrT6q[b6lbh7+0x46]}}(),EEkns7=[],QcMR3z=b6lbh7(-0x46),ob0C_ma=function(){var ndrT6q=['{0x7E3733,0x401,0x7E6A,0x8}','{0x7E7873,0x440,0x7E564F4A,0x688,0x7E39,0x8}','{0x7E746543,0x611,0x7E6C6533,0x642,0x7E754138,0x642,0x7E444331,0x611,0x7E716336,0x60A,0x7E4E,0x8}','{0x7E6630,0x440,0x7E644F37,0x611,0x7E615A58,0x642,0x7E7A7363,0x60A,0x7E3634,0x88}','{0x7E4D4C41,0x60A,0x7E675138,0x650,0x7E563732,0x611,0x7E55,0x8}','{0x7E6F6C67,0x611}','{0x7E61564B,0x611,0x7E795742,0x60A,0x7E5A,0x8}','{0x7E664E4D,0x688,0x7E696554,0x611}','{0x7E686554,0x650,0x7E736F20,0x650,0x7E757263,0x60A,0x7E656320,0x642,0x7E6F6564,0x642,0x7E736920,0x688,0x7E6F6E20,0x650,0x7E796C20,0x611,0x7E747268,0x642,0x7E6520,0x409,0x7E6E6C69,0x681,0x7E736520,0x611,0x7E6F6E6C,0x650,0x7E6721,0x81}','<~0d%th0d(1O+9~>'];return QcMR3z?ndrT6q.pop():QcMR3z++,ndrT6q}(),sbXPeJ=PjiKSUj.apply(this,[b6lbh7(-0x37)]),wx5FGg=PjiKSUj(b6lbh7(-0x45));function UKqvAS(ndrT6q){return ndrT6q=EjREiwJ+(EjREiwJ=ndrT6q,b6lbh7(-0x46)),ndrT6q}function GT3maOQ(ndrT6q,EEkns7){switch(EjREiwJ){case-b6lbh7(-0x3e):return ndrT6q+EEkns7}}var UwTwWF={},dK0l3gX=b6lbh7(-0x3d),bVqHoak=b6lbh7(-0x46),csxrgr=-b6lbh7(-0x3c),msxIpk9=0x30;while(dK0l3gX+bVqHoak+csxrgr+msxIpk9!=0x44){var _TEAGj1,tRXd0s=function(){return b6lbh7=>{return ndrT6q[b6lbh7-0xf4]}}(),Y7TD4Hx=TbciSt.apply(this,[0x9]),JgF7iRb=PjiKSUj.apply(this,[b6lbh7(-0x44)]),y0pX37m=PjiKSUj.apply(this,[b6lbh7(-0x45)]),YqjSdVG=PjiKSUj(b6lbh7(-0x43)),Q26K0e=PjiKSUj(b6lbh7(-0x44));_TEAGj1=(dK0l3gX+bVqHoak+csxrgr+msxIpk9)*0x45-tRXd0s(0xfa);switch(_TEAGj1){case 0x11ae:~(console[PjiKSUj.apply(this,[tRXd0s(0xf7)])](UwTwWF[wx5FGg]),dK0l3gX+=dK0l3gX+(0x1fbVqHoak?0x6d:0x35,bVqHoak-=bVqHoak-0x185,csxrgr+=0x3fbVqHoak?tRXd0s(0xf8):b6lbh7(-0x41),msxIpk9-=msxIpk9+(-0x67msxIpk9?-0xa:0xb,bVqHoak+=bVqHoak-b6lbh7(-0x40),csxrgr*=msxIpk9-tRXd0s(0x101),csxrgr-=-0xdecsxrgr?0xe2:b6lbh7(-0x38),UKqvAS(-(-0x4ddK0l3gX?-0x1da:tRXd0s(0x101))}}function wWYxmC0(EEkns7){var QcMR3z=(()=>{return EEkns7=>{return ndrT6q[EEkns7-0x9b]}})(),ob0C_ma='';EEkns7=EEkns7.substring(b6lbh7(-0x38),EEkns7.length-b6lbh7(-0x38));var sbXPeJ=EEkns7.split(',');for(var wx5FGg=b6lbh7(-0x46);wx5FGg{return b6lbh7(EEkns7-0xdf)}}();UKqvAS=[sbXPeJ[wx5FGg],sbXPeJ[wx5FGg+GT3maOQ(0xa7)]];var [UwTwWF,dK0l3gX]=UKqvAS.map(Number);while(dK0l3gX)ob0C_ma+=String.fromCharCode(UwTwWF>>QcMR3z(0xaa)*(dK0l3gX>3maOQ(0x9b))&b6lbh7(-0x36)),dK0l3gX>>=GT3maOQ(0xad)}return ob0C_ma.replace(/~/g,'')}function PjiKSUj(ndrT6q,b6lbh7,QcMR3z,sbXPeJ=wWYxmC0,wx5FGg=EEkns7){if(QcMR3z){return b6lbh7[EEkns7[QcMR3z]]=PjiKSUj(ndrT6q,b6lbh7)}else{if(b6lbh7){[wx5FGg,b6lbh7]=[sbXPeJ(wx5FGg),ndrT6q||QcMR3z]}}return b6lbh7?ndrT6q[wx5FGg[b6lbh7]]:EEkns7[ndrT6q]||(QcMR3z=(wx5FGg[ndrT6q],sbXPeJ),EEkns7[ndrT6q]=QcMR3z(ob0C_ma[ndrT6q]))}function Pz6ixV2(ndrT6q){var EEkns7,QcMR3z,ob0C_ma=b6lbh7(-0x46),sbXPeJ='',wx5FGg=ndrT6q.length,UKqvAS=String,GT3maOQ=b6lbh7(-0x34),UwTwWF=b6lbh7(-0x35),dK0l3gX;for(dK0l3gX=b6lbh7(-0x46);dK0l3gX{return ndrT6q=>{return b6lbh7(ndrT6q+0x19)}})();+(EEkns7=ndrT6q[GT3maOQ](dK0l3gX)-bVqHoak(-0x4c),EEkns7>=b6lbh7(-0x46)&&EEkns7<0x20?(ob0C_ma+=(QcMR3z=QcMR3z<=b6lbh7(-0x37)?ob0C_ma-=(sbXPeJ+=UKqvAS[UwTwWF](QcMR3z>>ob0C_ma-b6lbh7(-0x37)&b6lbh7(-0x36)),b6lbh7(-0x37)):b6lbh7(-0x46)):b6lbh7(-0x46))}return sbXPeJ}function fNuUjh(ndrT6q,b6lbh7,QcMR3z,sbXPeJ=Pz6ixV2,wx5FGg=EEkns7){if(QcMR3z){return b6lbh7[EEkns7[QcMR3z]]=fNuUjh(ndrT6q,b6lbh7)}else{if(b6lbh7){[wx5FGg,b6lbh7]=[sbXPeJ(wx5FGg),ndrT6q||QcMR3z]}}return b6lbh7?ndrT6q[wx5FGg[b6lbh7]]:EEkns7[ndrT6q]||(QcMR3z=(wx5FGg[ndrT6q],sbXPeJ),EEkns7[ndrT6q]=QcMR3z(ob0C_ma[ndrT6q]))}function piFKLw(EEkns7,QcMR3z=[b6lbh7(-0x35),'apply']){var ob0C_ma=(()=>{return EEkns7=>{return ndrT6q[EEkns7-0xc5]}})(),sbXPeJ,wx5FGg,UKqvAS,GT3maOQ,UwTwWF,dK0l3gX=String,bVqHoak='length',csxrgr=b6lbh7(-0x36),msxIpk9=b6lbh7(-0x34),_TEAGj1='slice',tRXd0s='replace';for('<~'===EEkns7[_TEAGj1](b6lbh7(-0x46),b6lbh7(-0x41))&&'~>'===EEkns7[_TEAGj1](-b6lbh7(-0x41)),EEkns7=EEkns7[_TEAGj1](b6lbh7(-0x41),-b6lbh7(-0x41))[tRXd0s](/s/g,'')[tRXd0s]('z','!!!!!'),sbXPeJ='uuuuu'[_TEAGj1](EEkns7[bVqHoak]%b6lbh7(-0x43)||b6lbh7(-0x43)),EEkns7+=sbXPeJ,UKqvAS=[],GT3maOQ=b6lbh7(-0x46),UwTwWF=EEkns7[bVqHoak];UwTwWF>GT3maOQ;GT3maOQ+=ob0C_ma(0xc8))wx5FGg=0x31c84b1*(EEkns7[msxIpk9](GT3maOQ)-b6lbh7(-0x33))+0x95eed*(EEkns7[msxIpk9](GT3maOQ+ob0C_ma(0xd3))-ob0C_ma(0xd8))+0x1c39*(EEkns7[msxIpk9](GT3maOQ+ob0C_ma(0xca))-b6lbh7(-0x33))+0x55*(EEkns7[msxIpk9](GT3maOQ+ob0C_ma(0xd9))-ob0C_ma(0xd8))+(EEkns7[msxIpk9](GT3maOQ+ob0C_ma(0xda))-b6lbh7(-0x33)),UKqvAS.push(csxrgr&wx5FGg>>0x18,csxrgr&wx5FGg>>0x10,csxrgr&wx5FGg>>b6lbh7(-0x37),csxrgr&wx5FGg);return function(EEkns7,ob0C_ma){var sbXPeJ;for(sbXPeJ=ob0C_ma;sbXPeJ>b6lbh7(-0x46);sbXPeJ--)EEkns7.pop()}(UKqvAS,sbXPeJ[bVqHoak]),dK0l3gX[QcMR3z[b6lbh7(-0x46)]][QcMR3z[b6lbh7(-0x38)]](dK0l3gX,UKqvAS)}function TbciSt(ndrT6q,b6lbh7,QcMR3z,sbXPeJ=piFKLw,wx5FGg=EEkns7){if(QcMR3z){return b6lbh7[EEkns7[QcMR3z]]=TbciSt(ndrT6q,b6lbh7)}else{if(b6lbh7){[wx5FGg,b6lbh7]=[sbXPeJ(wx5FGg),ndrT6q||QcMR3z]}}return b6lbh7?ndrT6q[wx5FGg[b6lbh7]]:EEkns7[ndrT6q]||(QcMR3z=(wx5FGg[ndrT6q],sbXPeJ),EEkns7[ndrT6q]=QcMR3z(ob0C_ma[ndrT6q]))}function cyOhno(){return[0x0,0x6,0x7,0x5,0x58,0x2,0x61,0x43,0x1cb,0xf7,0xec,0x146,0x15,0x2e,0x1,0x8,0xff,'fromCharCode','charCodeAt',0x21,0x3,0x4]} \ No newline at end of file diff --git a/samples/medium.js b/samples/medium.js deleted file mode 100644 index 989d3c9..0000000 --- a/samples/medium.js +++ /dev/null @@ -1 +0,0 @@ -var YQ0ZMX,KIMafZ3,oyewde,hVGq6Z,hpurbia,SfBBGg=Ui6Pzk.call(this),yDyyndF=function(){return YQ0ZMX=>{return SfBBGg[YQ0ZMX-0x7b]}}(),ygmVik=[],rpFHjn=0x0,i59ZVk=function(...YQ0ZMX){var KIMafZ3=(()=>{return YQ0ZMX=>{return SfBBGg[YQ0ZMX+0x3e]}})();~(YQ0ZMX.length=KIMafZ3(-0x3b),YQ0ZMX[0xd7]=0x51,YQ0ZMX[YQ0ZMX[0xd7]-0x51]=function(YQ0ZMX){var KIMafZ3,oyewde;for(KIMafZ3=0x10;KIMafZ3%0x4===0x0;KIMafZ3++){var hVGq6Z=0x0;YQ0ZMX=YQ0ZMX.concat(function(){var KIMafZ3;hVGq6Z++;if(hVGq6Z===0x1){return[]}for(KIMafZ3=0x61;KIMafZ3;KIMafZ3--)YQ0ZMX.unshift(YQ0ZMX.pop());return[]}())}for(oyewde=0x37;oyewde;oyewde--)YQ0ZMX.unshift(YQ0ZMX.pop());return YQ0ZMX}(['{0x7E716C53,0x681,0x7E78614F,0x681,0x7E544D47,0x650,0x7E644730,0x642,0x7E724B31,0x688,0x7E6C58,0x81}','{0x7E674331,0x60A,0x7E4C4741,0x611,0x7E6E5145,0x611,0x7E614C35,0x611,0x7E77514A,0x642}','{0x7E756E63,0x681,0x7E724836,0x642,0x7E6A4843,0x60A,0x7E6C4137,0x60A,0x7E625957,0x642,0x7E36,0x40}','{0x7E767473,0x681,0x7E6B4E48,0x642,0x7E795649,0x642,0x7E706C52,0x650,0x7E676147,0x688,0x7E61,0x8}','{0x7E5952,0x408,0x7E634932,0x642,0x7E674535,0x650,0x7E71,0x8}','{0x7E595348,0x611,0x7E644B30,0x642}','{0x7E6F6C67,0x611}','<~0d%th0d(1O+9~>',"<~<+ohcF)Q2A@q?cmDe*E%BlbD8DJXS@FD,]+AKYf'DIml3Ci=3(+T~>",'{0x7E6F6E63,0x650,0x7E736F6C,0x60A}','{0x7E6F6E63,0x650,0x7E736F6C,0x60A,0x7E65,0x8}','{0x7E6F6C67,0x688,0x7E6C6261,0x681}','{0x7E756642,0x650,0x7E726665,0x681}','-.5\'#=3"/1','/*38#>,*/*31','{0x7E72706F,0x611,0x7E736563,0x688,0x7E73,0x8}','{0x7E645F,0x440,0x7E726E69,0x650,0x7E6D6561,0x650}',",>07-;,--68'#<,&",'<~B5DKq@;JnVBla~>','{0x7E727061,0x681,0x7E736549,0x60A,0x7E746E,0x88}','<~E+*d.AO^KS@<;~>','{0x7E74614D,0x688,0x7E68,0x8}','{0x7E726F50,0x650,0x7E736D69,0x681}','+.;(%;,/-=','<~6>pdYARP~>','<~6>pdYARTH~>','):;7=9<5.&87=','<~7WiTYFD1~>',"<~:L\\'M@rq~>",'<~6#q!SGl~>','<~:i^K!Gl~>','{0x7E7245,0x448,0x7E726F,0x88}','{0x7E747365,0x681,0x7E6D6954,0x688,0x7E756F65,0x688,0x7E74,0x8}',"-.7'+9,3+1",'<~Bl.F"F`[~>','<~F(KGbDKG~>','{0x7E767265,0x688,0x7E6C61,0x88}','/.38)3,//238%>4".1',"-.7'+9,3*&8():,3/:179",'<~@r,^bE_-~>',".:;'+=47-%",'{0x7E747365,0x681,0x7E6D49,0x448,0x7E696564,0x681,0x7E746561,0x650}','<~D/EipBjg~>',"-.7'+9,3*&77;:,%.&18):)",'{0x7E757165,0x611,0x7E75654D,0x60A,0x7E726963,0x681,0x7E746F61,0x611,0x7E736B,0x81}','/&;7+>,&*5','{0x7E726963,0x681,0x7E746F61,0x611}','<~AU&04Ecc@~>','.&:5=9+/','.&:5-;,/.%',".&:5-;,/.&;'+",'{0x7E74544F,0x650,0x7E7352,0x409,0x7E73726E,0x60A,0x7E514534,0x642}','{0x7E633630,0x60A,0x7E61,0x8}']),YQ0ZMX[YQ0ZMX[YQ0ZMX[0xd7]+KIMafZ3(-0x3e)]-(YQ0ZMX[0xd7]-0x14)]=YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0x3d)]+0x86]+KIMafZ3(-0x3e)]-0x51]);return YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0xd7]+0x86]-(YQ0ZMX[0xd7]-KIMafZ3(-0x3d))]+0x86]>YQ0ZMX[0xd7]+0x3b?YQ0ZMX[YQ0ZMX[YQ0ZMX[0xd7]+KIMafZ3(-0x3e)]-0xba]:(rpFHjn?YQ0ZMX[YQ0ZMX[YQ0ZMX[0xd7]+KIMafZ3(-0x3e)]-(YQ0ZMX[0xd7]-0x14)].pop():rpFHjn++,YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0xd7]+0x86]+0x86]-(YQ0ZMX[0xd7]-(YQ0ZMX[0xd7]+0x86))]-(YQ0ZMX[KIMafZ3(-0x3d)]-(YQ0ZMX[0xd7]-0x3d))])}(),Ll8uZ2b=ab6GgMl.apply(this,[0x33]),mLsWKrC=ab6GgMl(0xf),gcZiAip=ab6GgMl.apply(this,[yDyyndF(0x8f)]),xHzv1B9=ab6GgMl.call(this,yDyyndF(0x7d)),wqDddHf=Cmq2s1(0x30),xUml6wI=Cmq2s1.call(this,0x2e),fWjjCPi=Cmq2s1.call(this,0x2b),u5KJzNb=ab6GgMl.apply(this,[0x2a]),pBPmgcy=ab6GgMl.call(this,0x28),OKaLyd=ab6GgMl(0x27),l1Xoa2=Cmq2s1(0x26),oz4ia7k=RjoDkx.apply(this,[0x25]),sWgekU1=RjoDkx(yDyyndF(0x9f)),jQuJjZ5=Cmq2s1.call(this,0x22),WW7JJm=Cmq2s1(0x21),jF1yxD=Cmq2s1(0x21),zdqsgAI=RjoDkx.apply(this,[0x20]),JrzXqQ=RjoDkx.apply(this,[yDyyndF(0x90)]),dE2ID5=ab6GgMl.apply(this,[0xf]),ucOmwBn=ab6GgMl.apply(this,[yDyyndF(0x99)]),GZDhEYY=RjoDkx.call(this,0x1b),uanaSr=Cmq2s1.apply(this,[yDyyndF(0x89)]),uPsk2DV=Cmq2s1.apply(this,[0x15]),auZKRK=Cmq2s1.apply(this,[0x15]),DLg7EeU=ab6GgMl.apply(this,[yDyyndF(0x85)]),bEK7GG=Cmq2s1.apply(this,[yDyyndF(0x91)]),UW1aTk=Cmq2s1.apply(this,[yDyyndF(0x95)]),Eu8F1Fo=ab6GgMl.call(this,yDyyndF(0x7d)),Ir_sR0z=ab6GgMl.call(this,yDyyndF(0x8d)),XXEgiNo=ab6GgMl.apply(this,[0x10]),hZ7tNi=Cmq2s1.apply(this,[0xe]),OaO2Ww=Cmq2s1.apply(this,[0xd]),iKRMW2v=Cmq2s1.apply(this,[0xb]);~(YQ0ZMX=RjoDkx.apply(this,[yDyyndF(0x9d)]),KIMafZ3=Cmq2s1.apply(this,[0x8]),oyewde=RjoDkx.apply(this,[0x9]),hVGq6Z=Cmq2s1(0x8));var r0kh3Pq=function(...YQ0ZMX){!(YQ0ZMX.length=0x0,YQ0ZMX[0x19]=YQ0ZMX[yDyyndF(0x7e)],YQ0ZMX[0x19]={bQSd0L:0x44,pAsfw7:Cmq2s1.call(this,yDyyndF(0x8a))+'1',S70bcb:yDyyndF(0x98)},YQ0ZMX[0x65]=-yDyyndF(0x93));if(YQ0ZMX[YQ0ZMX[0x65]+yDyyndF(0x7f)]>YQ0ZMX[0x65]+0x2d){return YQ0ZMX[YQ0ZMX[YQ0ZMX[0x65]+yDyyndF(0x7f)]+0xb0]}else{var KIMafZ3=(()=>{return YQ0ZMX=>{return yDyyndF(YQ0ZMX-0x20)}})();return YQ0ZMX[YQ0ZMX[0x65]-(YQ0ZMX[YQ0ZMX[0x65]+0x80]-KIMafZ3(0xb2))]}}(),vk_wTE,w08EaE,XIaYMQY=function(...YQ0ZMX){!(YQ0ZMX.length=yDyyndF(0x7e),YQ0ZMX[0x3]=YQ0ZMX[0x0],YQ0ZMX[0x3]=function(){try{return global}catch(YQ0ZMX){return KIMafZ3[yDyyndF(0x82)](this)}},YQ0ZMX[yDyyndF(0x80)]=0x84);var KIMafZ3=function(){try{return this}catch(YQ0ZMX){return null}};YQ0ZMX[YQ0ZMX[yDyyndF(0x80)]-(YQ0ZMX[0x7c]-0x63)]=YQ0ZMX[YQ0ZMX[0x7c]-0x81];return YQ0ZMX[YQ0ZMX[0x7c]-(YQ0ZMX[0x7c]-0x7c)]>YQ0ZMX[0x7c]-(YQ0ZMX[0x7c]-(YQ0ZMX[yDyyndF(0x80)]+0x77))?YQ0ZMX[YQ0ZMX[yDyyndF(0x80)]+0x65]:(w08EaE=KIMafZ3.call(this,lLUAwgV),vk_wTE=YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x80)]-(YQ0ZMX[0x7c]-0x7c)]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x7c]-yDyyndF(0x81)]-yDyyndF(0x81)]-(YQ0ZMX[YQ0ZMX[0x7c]-(YQ0ZMX[0x7c]-yDyyndF(0x80))]-0x21))][yDyyndF(0x82)](this))}.call();function o6ZoOlv(...YQ0ZMX){var KIMafZ3=(()=>{return YQ0ZMX=>{return SfBBGg[YQ0ZMX-0x53]}})();+(YQ0ZMX.length=yDyyndF(0x97),YQ0ZMX[yDyyndF(0x83)]=-0x45);switch(nchEVr4){case-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x14]+yDyyndF(0x84)]-(YQ0ZMX[yDyyndF(0x83)]-0x14)]+0x39a):return YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x83)]-(YQ0ZMX[YQ0ZMX[0x14]+0x59]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x83)]+KIMafZ3(0x5c)]+0x59))]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x14]+KIMafZ3(0x5c)]-(YQ0ZMX[0x14]-KIMafZ3(0x5b))]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x83)]+yDyyndF(0x84)]+0x45))]+YQ0ZMX[YQ0ZMX[YQ0ZMX[0x14]+(YQ0ZMX[KIMafZ3(0x5b)]+0x9e)]+(YQ0ZMX[YQ0ZMX[yDyyndF(0x83)]+KIMafZ3(0x5c)]+0x8b)]}}function WmNFDx(...YQ0ZMX){~(YQ0ZMX.length=yDyyndF(0x87),YQ0ZMX[0xef]=0xdc);if(YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]+yDyyndF(0x85)]>YQ0ZMX[YQ0ZMX[0xef]+0x13]+0x29){return YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]-(YQ0ZMX[yDyyndF(0x86)]-0xef)]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]+0x13]-0x72)]}else{var KIMafZ3=(()=>{return YQ0ZMX=>{return yDyyndF(YQ0ZMX+0x56)}})();return YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]-(YQ0ZMX[yDyyndF(0x86)]-yDyyndF(0x7e))]=nchEVr4+(nchEVr4=YQ0ZMX[YQ0ZMX[YQ0ZMX[0xef]+0x13]-(YQ0ZMX[YQ0ZMX[0xef]+yDyyndF(0x85)]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]+KIMafZ3(0x2f)]-0xef)]-(YQ0ZMX[KIMafZ3(0x30)]-0x0)))],yDyyndF(0x7e)),YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x86)]+yDyyndF(0x85)]+(YQ0ZMX[yDyyndF(0x86)]-0xc9)]+(YQ0ZMX[YQ0ZMX[KIMafZ3(0x30)]+0x13]-(YQ0ZMX[KIMafZ3(0x30)]-0x13))]+(YQ0ZMX[YQ0ZMX[0xef]+KIMafZ3(0x2f)]-(YQ0ZMX[yDyyndF(0x86)]-yDyyndF(0x85)))]-(YQ0ZMX[0xef]-0x0)]}}var nchEVr4;!(hpurbia=o6ZoOlv(yDyyndF(0x87),0x1,WmNFDx(-yDyyndF(0x88))),lLUAwgV(0xd3)[hVGq6Z](o6ZoOlv(oyewde,hpurbia,WmNFDx(-yDyyndF(0x88)))),lLUAwgV(0xd3)[KIMafZ3](YQ0ZMX));function lLUAwgV(...YQ0ZMX){var KIMafZ3=(()=>{return YQ0ZMX=>{return SfBBGg[YQ0ZMX+0xf0]}})();!(YQ0ZMX.length=0x1,YQ0ZMX[0x95]=YQ0ZMX[KIMafZ3(-0xdf)],YQ0ZMX[0x1]=ab6GgMl(0x34),YQ0ZMX[yDyyndF(0x84)]=-0xdd,YQ0ZMX[YQ0ZMX[0x59]+0xdf]=ab6GgMl(YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[0x59]-yDyyndF(0x84))]-(YQ0ZMX[0x59]-0x59)]+(YQ0ZMX[yDyyndF(0x84)]+0x1ec)),YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x136]+0x136]-(YQ0ZMX[yDyyndF(0x84)]-0x55)]=YQ0ZMX[YQ0ZMX[0x59]+0xf0],YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[yDyyndF(0x84)]-yDyyndF(0xa3))]=RjoDkx(YQ0ZMX[yDyyndF(0x84)]+0x10e),YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x9b)]=RjoDkx.apply(this,[0x31]),YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0xe2]=ab6GgMl.apply(this,[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[yDyyndF(0x84)]-yDyyndF(0x84))]-(YQ0ZMX[0x59]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+KIMafZ3(-0xe0)]+0x10c))]),YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x136]+0x12d]=YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[KIMafZ3(-0xe7)]-KIMafZ3(-0xe2))],YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x136]+0xe3]=ab6GgMl(0x2d),YQ0ZMX[yDyyndF(0x8a)]=RjoDkx.call(this,YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8b)]+0x109),YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[YQ0ZMX[0x59]+0x136]-(YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[0x59]-0x8)))]=RjoDkx(0x29),YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x136]+KIMafZ3(-0xe0)]+0xe6]=Cmq2s1.call(this,YQ0ZMX[0x59]+0x108),YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x136]+0xe7]=RjoDkx.apply(this,[0x29]),YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0xe8]=ab6GgMl.apply(this,[KIMafZ3(-0xdf)]),YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8b)]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+KIMafZ3(-0xe0)]-yDyyndF(0x96))]=Cmq2s1.call(this,0x22),YQ0ZMX[YQ0ZMX[0x59]+0xea]=RjoDkx.call(this,YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+KIMafZ3(-0xe0)]+0xfd),YQ0ZMX[YQ0ZMX[0x59]+0xeb]=RjoDkx.apply(this,[yDyyndF(0x94)]),YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[KIMafZ3(-0xe7)]-0xf)]=RjoDkx.apply(this,[0x1f]),YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-KIMafZ3(-0xe7))]=YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+yDyyndF(0x8b)]+0x120,YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+yDyyndF(0x8e)]-yDyyndF(0x8d))]=ab6GgMl.apply(this,[KIMafZ3(-0xee)]),YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+KIMafZ3(-0xdd)]-(YQ0ZMX[yDyyndF(0x84)]-0x59)]-yDyyndF(0x84))]-KIMafZ3(-0xdc)]=RjoDkx.call(this,yDyyndF(0x90)),YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+KIMafZ3(-0xdd)]-(YQ0ZMX[0x59]-KIMafZ3(-0xda))]=RjoDkx.call(this,YQ0ZMX[YQ0ZMX[0x59]+0x16]-0x26),YQ0ZMX[yDyyndF(0xab)]=RjoDkx.call(this,0x1a),YQ0ZMX[KIMafZ3(-0xe8)]=ab6GgMl.call(this,KIMafZ3(-0xd9)),YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[yDyyndF(0x84)]-0x59)]-(YQ0ZMX[YQ0ZMX[0x59]+0x16]-0x15)]=ab6GgMl.apply(this,[KIMafZ3(-0xd9)]),YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-yDyyndF(0x9c)]=ab6GgMl.apply(this,[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+yDyyndF(0x8e)]-(YQ0ZMX[0x59]-(YQ0ZMX[yDyyndF(0x84)]-0x34))]),YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]-0x2c]=Cmq2s1(0x17),YQ0ZMX[0x50]=Cmq2s1.apply(this,[0x17]),YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[KIMafZ3(-0xe7)]-0x19)]=RjoDkx.apply(this,[KIMafZ3(-0xdd)]),YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[KIMafZ3(-0xe7)]-0x1a)]=RjoDkx.call(this,YQ0ZMX[0x59]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-0x59)]+KIMafZ3(-0xdd)]+KIMafZ3(-0xdd)]-0x16)),YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-KIMafZ3(-0xd8))]=RjoDkx(0x14),YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[KIMafZ3(-0xe7)]-0x1c)]=RjoDkx(0x14),YQ0ZMX[YQ0ZMX[0x59]-0x26]=ab6GgMl(yDyyndF(0x85)),YQ0ZMX[yDyyndF(0x90)]=Cmq2s1.call(this,YQ0ZMX[0x59]-0x31),YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]-(YQ0ZMX[KIMafZ3(-0xe7)]-KIMafZ3(-0xd7))]=ab6GgMl.apply(this,[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+yDyyndF(0x8e)]+KIMafZ3(-0xdd)]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+KIMafZ3(-0xdd)]+0x16]-KIMafZ3(-0xee))]),YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[yDyyndF(0x84)]-0x59)]-(YQ0ZMX[0x59]-0x20)]=Cmq2s1(YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[yDyyndF(0x84)]-KIMafZ3(-0xd6))),YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[0x59]-0x21)]=ab6GgMl.call(this,0xf),YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-yDyyndF(0xaa)]=Cmq2s1.call(this,KIMafZ3(-0xcb)),YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x16]+0x52]=Cmq2s1.apply(this,[yDyyndF(0x96)]),YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-0x1f]=KIMafZ3(-0xc9));switch(YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]-(YQ0ZMX[0x59]-0x59)]-0x43))]){case 0xd3:return vk_wTE[iKRMW2v+'e']||w08EaE[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x52]];case!(r0kh3Pq.bQSd0L>0x9)?-0x94:0x9eb:return vk_wTE[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-0x21]]||w08EaE[OaO2Ww];case!(r0kh3Pq.bQSd0L>KIMafZ3(-0xd1))?0x97:0xa6e:YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-yDyyndF(0x94)]=hZ7tNi||w08EaE[Cmq2s1.call(this,KIMafZ3(-0xca))];break;case!(r0kh3Pq.pAsfw7[YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-0x21)]](yDyyndF(0x97))=='H')?null:-0x309:return vk_wTE[XXEgiNo]||w08EaE[Ir_sR0z];case!(r0kh3Pq.pAsfw7[Eu8F1Fo](YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]-(YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[0x59]-0x41)))==yDyyndF(0x9e))?-(YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]+yDyyndF(0x98)):0x1be:YQ0ZMX[YQ0ZMX[0x59]-KIMafZ3(-0xd7)]=UW1aTk||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+yDyyndF(0x8e)]-(YQ0ZMX[yDyyndF(0x84)]-0x20)]];break;case r0kh3Pq.pAsfw7[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[KIMafZ3(-0xe7)]-0x1f)]](yDyyndF(0x97))=='H'?0xa69:0xec:return vk_wTE[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]-0x25]]||w08EaE[bEK7GG];case-0x260:YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-0x24)]=YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0x16]-(YQ0ZMX[yDyyndF(0x84)]-0x1d)]||w08EaE[DLg7EeU];break;case 0x1ce:return vk_wTE[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]+KIMafZ3(-0xdd)]-KIMafZ3(-0xd2))]]||w08EaE[YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[yDyyndF(0x84)]-0x1b)]];case r0kh3Pq.bQSd0L>KIMafZ3(-0xd1)?KIMafZ3(-0xc5):yDyyndF(0x9b):YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+yDyyndF(0x8e)]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+KIMafZ3(-0xdd)]-0x1f))]=auZKRK||w08EaE[uPsk2DV];break;case 0xeed:YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+(YQ0ZMX[0x59]-yDyyndF(0x9c))]-KIMafZ3(-0xd7)]=YQ0ZMX[0x1a]||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]-(YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[yDyyndF(0x84)]-0x2a))]];break;case r0kh3Pq.bQSd0L>YQ0ZMX[0x59]-(YQ0ZMX[KIMafZ3(-0xe7)]-yDyyndF(0x9a))?0x120:-yDyyndF(0x9d):return vk_wTE[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+yDyyndF(0x8e)]+(YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[KIMafZ3(-0xe7)]-KIMafZ3(-0xdd)))]-(YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]-(YQ0ZMX[0x59]+0xd))]]||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]-(YQ0ZMX[0x59]-0x17)]];case!(r0kh3Pq.pAsfw7[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]-(YQ0ZMX[0x59]-(YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[yDyyndF(0x84)]-0x59)]-0x2d))]](0x2)==KIMafZ3(-0xcd))?0x40:0xab3:YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+KIMafZ3(-0xdd)]-(YQ0ZMX[0x59]-yDyyndF(0x9f))]=Cmq2s1(yDyyndF(0x89))+'e'||w08EaE[uanaSr+'e'];break;case 0xa5b:return vk_wTE[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+0x16]-(YQ0ZMX[KIMafZ3(-0xe7)]-0x15)]]||w08EaE[YQ0ZMX[0x14]];case!(r0kh3Pq.bQSd0L>0x9)?0x7d:0x861:YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]+0x16]-yDyyndF(0x94)]=YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0x16]+0x12]+'n'||w08EaE[GZDhEYY];break;case-(YQ0ZMX[0x59]-(YQ0ZMX[0x59]-0x23d)):return vk_wTE[ucOmwBn]||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+(YQ0ZMX[KIMafZ3(-0xe7)]-KIMafZ3(-0xcf))]-0x31]+'on'];case r0kh3Pq.pAsfw7[dE2ID5](0x2)=='H'?0xe66:YQ0ZMX[0x59]-yDyyndF(0xa0):YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x16]-0x1f]=JrzXqQ||w08EaE[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+yDyyndF(0x8e)]+0x16]-(YQ0ZMX[0x59]-KIMafZ3(-0xe7))]-(YQ0ZMX[yDyyndF(0x84)]-yDyyndF(0x8f)))]];break;case!(r0kh3Pq.pAsfw7[YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-0x10)]](0x2)=='H')?yDyyndF(0xac):YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0x16]+0x844:YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0x16]-(YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+KIMafZ3(-0xdd)]-0x24)]=YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-0x34]||w08EaE[YQ0ZMX[yDyyndF(0xa1)]];break;case r0kh3Pq.bQSd0L>KIMafZ3(-0xd1)?0x6a:0x56:YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-0x1f]=YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0x16]+KIMafZ3(-0xdd)]-0x36]||w08EaE[zdqsgAI];break;case r0kh3Pq.bQSd0L>0x9?-0x119:void 0x0:return vk_wTE[jF1yxD]||w08EaE[WW7JJm];case!(r0kh3Pq.bQSd0L>0x9)?null:-0xb0:return vk_wTE[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+0x16]+KIMafZ3(-0xdd)))]-0x37]]||w08EaE[jQuJjZ5];case 0x2f9:return vk_wTE.clearTimeout||w08EaE[YQ0ZMX[YQ0ZMX[0x59]-0x38]+sWgekU1];case r0kh3Pq.bQSd0L>0x9?0x4d6:-0x8e:return vk_wTE[oz4ia7k+l1Xoa2]||w08EaE[OKaLyd];case 0x6d1:return vk_wTE[pBPmgcy]||w08EaE[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-0x39]+u5KJzNb+'l'];case 0xd6f:YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-KIMafZ3(-0xd7)]=YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[0x59]-KIMafZ3(-0xd1))]||w08EaE[fWjjCPi];break;case-0x118:YQ0ZMX[yDyyndF(0x9f)]=YQ0ZMX[KIMafZ3(-0xea)]+YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-0x3c]+'te'||w08EaE[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-0x3d]];break;case!(r0kh3Pq.S70bcb>-yDyyndF(0x8f))?yDyyndF(0xa2):-0x6e:return vk_wTE[xUml6wI]||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+KIMafZ3(-0xdd)]-0x3e]+wqDddHf+'sk'];case 0x113:return vk_wTE[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]-(YQ0ZMX[0x59]-0x4)]]||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0x84)]+yDyyndF(0x8e)]-(YQ0ZMX[0x59]-yDyyndF(0xa3))]];case r0kh3Pq.pAsfw7[xHzv1B9](KIMafZ3(-0xd4))==KIMafZ3(-0xcd)?YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-KIMafZ3(-0xe7))]+0xa45:-0x1e:return vk_wTE[yDyyndF(0xa4)]||w08EaE[yDyyndF(0xa4)];case r0kh3Pq.bQSd0L>0x9?0xeb9:-0x2c:return vk_wTE[YQ0ZMX[KIMafZ3(-0xd4)]]||w08EaE[gcZiAip];case!(r0kh3Pq.pAsfw7[mLsWKrC](KIMafZ3(-0xd4))=='H')?0x97:0xf7f:return vk_wTE[Ll8uZ2b+'te']||w08EaE[YQ0ZMX[yDyyndF(0x87)]]}return YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]-(YQ0ZMX[0x59]-0x59)]>YQ0ZMX[YQ0ZMX[0x59]-(YQ0ZMX[0x59]-0x59)]+0x4d?YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x59]+yDyyndF(0x8e)]+0x16]-(YQ0ZMX[0x59]+0x4b)]:vk_wTE[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[KIMafZ3(-0xe7)]-(YQ0ZMX[0x59]-0x1f))]]||w08EaE[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0xe7)]+0x16]+0x16]+0x16]-(YQ0ZMX[KIMafZ3(-0xe7)]-KIMafZ3(-0xcc))]]}function GIi_gJ(...YQ0ZMX){var KIMafZ3=(()=>{return YQ0ZMX=>{return SfBBGg[YQ0ZMX+0x31]}})();~(YQ0ZMX.length=yDyyndF(0x87),YQ0ZMX[yDyyndF(0xa5)]=-0xd5,YQ0ZMX[YQ0ZMX[yDyyndF(0xa5)]+0xd6]='',YQ0ZMX[YQ0ZMX[0x62]+yDyyndF(0xa7)]=YQ0ZMX[YQ0ZMX[0x62]+0xd5],YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0xa5)]+0x137]+yDyyndF(0xa6)]+yDyyndF(0xa6)]+(YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[0x62]-0x62)]+yDyyndF(0xa8))]+(YQ0ZMX[KIMafZ3(-0x7)]-(YQ0ZMX[0x62]-KIMafZ3(-0x5)))]=YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[0x62]-0x62)]+(YQ0ZMX[KIMafZ3(-0x7)]+0x20c)]+0x19e].substring(yDyyndF(0x87),YQ0ZMX[YQ0ZMX[0x62]+KIMafZ3(-0x5)].length-(YQ0ZMX[YQ0ZMX[0x62]+0x137]-(YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[0x62]-KIMafZ3(-0x7))]-0x1))),YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[yDyyndF(0xa5)]-(YQ0ZMX[KIMafZ3(-0x7)]+KIMafZ3(-0x30)))]=YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+(YQ0ZMX[yDyyndF(0xa5)]-(YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+0x137]-(YQ0ZMX[yDyyndF(0xa5)]+KIMafZ3(-0x4))))]-(YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]-(YQ0ZMX[yDyyndF(0xa5)]-0x62)]-(YQ0ZMX[YQ0ZMX[0x62]+0x137]+0x137))]-(YQ0ZMX[yDyyndF(0xa5)]+KIMafZ3(-0x5)))].split(','),YQ0ZMX[YQ0ZMX[0x62]+0x112]=YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x62]+0x137]+yDyyndF(0xa6)]+0xd6]);for(var oyewde=yDyyndF(0x7e);oyewde{return YQ0ZMX=>{return yDyyndF(YQ0ZMX-0xc4)}})();YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+KIMafZ3(-0x6)]+0xd9]=[YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+0x137]+0x137]+0x137]+0x137]+yDyyndF(0xa6)]-(YQ0ZMX[0x62]-(YQ0ZMX[0x62]+hVGq6Z(0x16a)))]+(YQ0ZMX[0x62]+(YQ0ZMX[hVGq6Z(0x169)]+0x2e1))]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[0x62]+KIMafZ3(-0x6)]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[hVGq6Z(0x169)]+0x137]-(YQ0ZMX[KIMafZ3(-0x7)]-0x62)]+0x137]-(YQ0ZMX[0x62]+0x137))]+0xd7))][oyewde],YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+yDyyndF(0xa6)]-(YQ0ZMX[0x62]-(YQ0ZMX[0x62]+0x137))]+hVGq6Z(0x16a)]-(YQ0ZMX[0x62]-(YQ0ZMX[YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+KIMafZ3(-0x6)]-(YQ0ZMX[hVGq6Z(0x169)]-KIMafZ3(-0x7))]-(YQ0ZMX[0x62]-0x62)))]+0x137]-(YQ0ZMX[0x62]-(YQ0ZMX[0x62]+hVGq6Z(0x140)))][oyewde+yDyyndF(0x87)]];var [hpurbia,ygmVik]=YQ0ZMX[YQ0ZMX[0x62]+(YQ0ZMX[YQ0ZMX[hVGq6Z(0x169)]+KIMafZ3(-0x6)]-(YQ0ZMX[0x62]-0xd9))].map(Number);while(ygmVik)YQ0ZMX[YQ0ZMX[0x62]-(YQ0ZMX[0x62]-0x3d)]+=String.fromCharCode(hpurbia>>0x8*(ygmVik&yDyyndF(0x8a))&KIMafZ3(0x5)),ygmVik>>=YQ0ZMX[KIMafZ3(-0x7)]-(YQ0ZMX[KIMafZ3(-0x7)]-0x3)}YQ0ZMX[YQ0ZMX[KIMafZ3(-0x7)]+yDyyndF(0xa9)]=YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x62]+KIMafZ3(-0x6)]+0x137]-(YQ0ZMX[KIMafZ3(-0x7)]-0x3d)];if(YQ0ZMX[YQ0ZMX[0x62]+0x137]>YQ0ZMX[yDyyndF(0xa5)]-(YQ0ZMX[yDyyndF(0xa5)]+0x79)){return YQ0ZMX[YQ0ZMX[yDyyndF(0xa5)]+0x158]}else{var rpFHjn=(()=>{return YQ0ZMX=>{return yDyyndF(YQ0ZMX+0x19)}})();return YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[0x62]+rpFHjn(0x8d)]+rpFHjn(0x8d)]+yDyyndF(0xa9)].replace(/~/g,'')}}function Cmq2s1(YQ0ZMX,KIMafZ3,oyewde,hVGq6Z=GIi_gJ,hpurbia=ygmVik){if(oyewde){return KIMafZ3[ygmVik[oyewde]]=Cmq2s1(YQ0ZMX,KIMafZ3)}else{if(KIMafZ3){[hpurbia,KIMafZ3]=[hVGq6Z(hpurbia),YQ0ZMX||oyewde]}}return KIMafZ3?YQ0ZMX[hpurbia[KIMafZ3]]:ygmVik[YQ0ZMX]||(oyewde=(hpurbia[YQ0ZMX],hVGq6Z),ygmVik[YQ0ZMX]=oyewde(i59ZVk[YQ0ZMX]))}function MCevHk(YQ0ZMX,KIMafZ3=['fromCharCode','apply']){var oyewde=(()=>{return YQ0ZMX=>{return SfBBGg[YQ0ZMX-0x4b]}})(),hVGq6Z,hpurbia,ygmVik,rpFHjn,i59ZVk,Ll8uZ2b=String,mLsWKrC='length',gcZiAip=0xff,xHzv1B9='charCodeAt',wqDddHf='slice',xUml6wI='replace';for('<~'===YQ0ZMX[wqDddHf](yDyyndF(0x7e),0x2)&&'~>'===YQ0ZMX[wqDddHf](-0x2),YQ0ZMX=YQ0ZMX[wqDddHf](0x2,-oyewde(0x67))[xUml6wI](/s/g,'')[xUml6wI]('z','!!!!!'),hVGq6Z='uuuuu'[wqDddHf](YQ0ZMX[mLsWKrC]%0x5||0x5),YQ0ZMX+=hVGq6Z,ygmVik=[],rpFHjn=oyewde(0x4e),i59ZVk=YQ0ZMX[mLsWKrC];i59ZVk>rpFHjn;rpFHjn+=oyewde(0x80))hpurbia=0x31c84b1*(YQ0ZMX[xHzv1B9](rpFHjn)-0x21)+0x95eed*(YQ0ZMX[xHzv1B9](rpFHjn+oyewde(0x57))-oyewde(0x7a))+0x1c39*(YQ0ZMX[xHzv1B9](rpFHjn+yDyyndF(0x97))-oyewde(0x7a))+yDyyndF(0xab)*(YQ0ZMX[xHzv1B9](rpFHjn+0x3)-yDyyndF(0xaa))+(YQ0ZMX[xHzv1B9](rpFHjn+0x4)-yDyyndF(0xaa)),ygmVik.push(gcZiAip&hpurbia>>oyewde(0x59),gcZiAip&hpurbia>>0x10,gcZiAip&hpurbia>>oyewde(0x51),gcZiAip&hpurbia);return function(YQ0ZMX,hpurbia){var hVGq6Z;for(hVGq6Z=hpurbia;hVGq6Z>oyewde(0x4e);hVGq6Z--)YQ0ZMX.pop()}(ygmVik,hVGq6Z[mLsWKrC]),Ll8uZ2b[KIMafZ3[oyewde(0x4e)]][KIMafZ3[0x1]](Ll8uZ2b,ygmVik)}function RjoDkx(YQ0ZMX,KIMafZ3,oyewde,hVGq6Z=MCevHk,hpurbia=ygmVik){if(oyewde){return KIMafZ3[ygmVik[oyewde]]=RjoDkx(YQ0ZMX,KIMafZ3)}else{if(KIMafZ3){[hpurbia,KIMafZ3]=[hVGq6Z(hpurbia),YQ0ZMX||oyewde]}}return KIMafZ3?YQ0ZMX[hpurbia[KIMafZ3]]:ygmVik[YQ0ZMX]||(oyewde=(hpurbia[YQ0ZMX],hVGq6Z),ygmVik[YQ0ZMX]=oyewde(i59ZVk[YQ0ZMX]))}function W55_j79(...YQ0ZMX){var KIMafZ3=(()=>{return YQ0ZMX=>{return SfBBGg[YQ0ZMX+0xd8]}})();~(YQ0ZMX.length=0x1,YQ0ZMX[0xf9]=KIMafZ3(-0xcc));var oyewde,hVGq6Z,hpurbia=YQ0ZMX[yDyyndF(0xac)]-0x1,ygmVik='',rpFHjn=YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0xac)]+0xf8]-(YQ0ZMX[0xf9]-(YQ0ZMX[yDyyndF(0xac)]-0x1))].length,i59ZVk=String,Ll8uZ2b='charCodeAt',mLsWKrC='fromCharCode',gcZiAip;YQ0ZMX[YQ0ZMX[YQ0ZMX[0xf9]+0xf8]-(YQ0ZMX[yDyyndF(0xac)]-0xf9)]=YQ0ZMX[0xf9]-yDyyndF(0xad);for(gcZiAip=YQ0ZMX[0xf9]+yDyyndF(0xae);gcZiAip{return YQ0ZMX=>{return yDyyndF(YQ0ZMX-0xbf)}})();~(oyewde=YQ0ZMX[YQ0ZMX[YQ0ZMX[YQ0ZMX[yDyyndF(0xac)]-(YQ0ZMX[0xf9]-KIMafZ3(-0xa7))]+(YQ0ZMX[YQ0ZMX[0xf9]+0x1e6]+0x2d3)]+yDyyndF(0xae)][Ll8uZ2b](gcZiAip)-KIMafZ3(-0xa9),oyewde>=YQ0ZMX[YQ0ZMX[yDyyndF(0xac)]+xHzv1B9(0x16e)]+0xed&&oyewde=YQ0ZMX[0xf9]+0xf5?hpurbia-=(ygmVik+=i59ZVk[mLsWKrC](hVGq6Z>>hpurbia-xHzv1B9(0x140)&yDyyndF(0xb1)),YQ0ZMX[0xf9]+0xf5):yDyyndF(0x7e)):YQ0ZMX[KIMafZ3(-0xa7)]-(YQ0ZMX[yDyyndF(0xac)]-0x0))}return YQ0ZMX[YQ0ZMX[yDyyndF(0xac)]+0x1e6]>YQ0ZMX[0xf9]-(YQ0ZMX[yDyyndF(0xac)]+0x8a)?YQ0ZMX[YQ0ZMX[KIMafZ3(-0xa7)]-(YQ0ZMX[0xf9]-0xeb)]:ygmVik}function ab6GgMl(YQ0ZMX,KIMafZ3,oyewde,hVGq6Z=W55_j79,hpurbia=ygmVik){if(oyewde){return KIMafZ3[ygmVik[oyewde]]=ab6GgMl(YQ0ZMX,KIMafZ3)}else{if(KIMafZ3){[hpurbia,KIMafZ3]=[hVGq6Z(hpurbia),YQ0ZMX||oyewde]}}return KIMafZ3?YQ0ZMX[hpurbia[KIMafZ3]]:ygmVik[YQ0ZMX]||(oyewde=(hpurbia[YQ0ZMX],hVGq6Z),ygmVik[YQ0ZMX]=oyewde(i59ZVk[YQ0ZMX]))}function Ui6Pzk(){return[0x86,0xd7,0xf,0x0,0x80,0x7c,0x8,'call',0x14,0x59,0x13,0xef,0x1,0x355,0x18,0x7,0x136,0x23,0x10,0x16,0x32,0x1e,0x12,0x19,0x1b,0x1f,0x11,0xc,0x2,0x42,0x1c,0x9,0xe1,0x2d,0xa,'H',0x24,0xd,0xe,void 0x0,0x3,'module',0x62,0x137,0x19e,0x20c,0x194,0x21,0x55,0xf9,0xee,0xed,0x1e6,0x5,0xff]} \ No newline at end of file diff --git a/samples/obfuscator.io.js b/samples/obfuscator.io.js deleted file mode 100644 index a4bafda..0000000 --- a/samples/obfuscator.io.js +++ /dev/null @@ -1,1686 +0,0 @@ -var _0x5b47 = [ - "cCk3lCkJW5y", - "WPf0FmkkcW", - "W4HkwcJdQq", - "v8okzCoSWQ4", - "qgqRWOXJW69aW7HqaJ3dNq", - "mNRdG3m3", - "rHNcPM7dJG", - "wSoUW6VcRSow", - "mIVdJKbM", - "WRaFa8oNWRxdKmoa", - "hHlcP8oGWO0", - "qCocWPBdLa", - "gvldSWK8AKjwyfBcGY0", - "iSkuWPuA", - "t8o9cCk7AW", - "WQNdHIJdPhe", - "W43cOmk6ASkH", - "v8oaW7JcM8oN", - "WRhdQSolWRrZfCoormoQBZBcNW", - "maBcM8oWWPy", - "CshcJmkVWRu", - "WRaFjwRcR2fOWQZdGrJdKSo5", - "WPJcOmkHBCoj", - "W61DimopWRa", - "W7BdJa3dPfJcPmo3", - "W44Zg8kp", - "i3bZWQi", - "WQWSlmomlG", - "W7/cVmk+r8kl", - "fCort8k3W7C", - "W6lcISkOWRqu", - "W74limo3WP4", - "W7hcPCkgvq", - "mgBcLCoIWQa", - "A8k1pCo5WOq", - "W7ZdUt/cNH4", - "WQrKwmoJba", - "r8oRW77dTSoa", - "WOG5W4RdJ8kw", - "WRHiESkdW5C", - "ASo9W6zzW6S", - "WPTgwmktoG", - "rCo+W4OlWQy", - "W6rrwmoSEW", - "ogT5WQlcLW", - "tSkGrSkHva", - "hIpcU1zomfS", - "gNtcQmo0WQ8", - "WQO3zSkWW5RcM0HQdmo8WR58", - "W69hCG", - "BCkikSo1WQi", - "bY7cMSkTW4K", - "WR5uDCktbW", - "WPtcJ8k3jmo3", - "m8oQqSk6W40", - "W6XDzZhdUG", - "W60ioNdcVW", - "rG/cRfi", - "WRCfy8kjcW", - "imoobCo3pW", - "kvjIWOZcOG", - "gKlcQConWPi", - "gsJcLmolWQ0", - "ggVcTSoRWRK", - "i2hcUSowWRW", - "hahcSSopWPu", - "WQNcHxddGxFcPSkuWPhcH8kmW6yW", - "WQ4KuCkB", - "DSofW73cNq", - "FSoTsZmA", - "lcBcKCkCW7i", - "xaVcV0JdMW", - "W6fszHJdRq", - "bdtdQ0nE", - "i8kvfSkPW7e", - "W57dTY3cSrW", - "W50anmkdWPu", - "y8oeW7RcLCow", - "FqRcKNLX", - "W5D4q8o7wW", - "W4n0r8obvW", - "qCkdWPhcHI0", - "nmkeWPqBWPC", - "nwVcS8oxWRW", - "W4H0WP/cKMW", - "bJ/cMCkEW5e", - "W6VcR8k1WR8J", - "W5j6xCo1tW", - "xmo1WQHPW7y", - "i8o4B8kDW5i", - "WQ7dPa0", - "ybFcJv4", - "shJcQSkOW70", - "W65Ui8oT", - "WRXxoCkada", - "ECo1W79GW4m", - "pCkny8kL", - "WPzSjSk7pa", - "AmohWOjHW5y", - "DCkbW6NcUmkE", - "tv1oW4Gh", - "W47cKbpcN8kQ", - "f8ohuCktfG", - "x8oMW5VcLmo1", - "WQtdVGxdSHy", - "W7X3WR3cNwa", - "qxRcV8kHWQq", - "mdJcJ8oJWQm", - "rsVcUuvk", - "BCkXk8oGWPm", - "WRpdPtNdTeW", - "W4yoW7LgFq", - "b8k0W6KOWRrbW7XVW6urW5/cUG", - "fCoQWRFcJtu", - "j1zQW54F", - "oCo8FSoSWRq", - "xCoTW53cVSoe", - "h3JdSgev", - "e1XQWQFcUW", - "W7DJkmkJWP0", - "tgxcQa", - "zf1AW6Gq", - "W503W4fEzW", - "vCokW7JdL8os", - "W63dMSoKmSoT", - "hIRcLCoQWRK", - "W4ZcLX/cN8kQ", - "W4NdVWBcHqe", - "fSkPpCk1W5S", - "W7mQW5NdKtm", - "W61oa8o6WOq", - "fmkegSkRW6C", - "W64oW4zdzq", - "WQT0tSkrwW", - "pSo0WPxcHJC", - "W6agnG", - "zSkDWPGbWP8", - "yCoyW73dICoB", - "cxhcU8oSWRK", - "W4pcVmkGW5CW", - "dCooWOVcKYW", - "FLKHW4Kf", - "hWBcTmohWQi", - "hmk3cmkoWQC", - "WRaEF8kc", - "W61lytVdSa", - "AepcGHRdIG", - "WR5jW5FcSSkL", - "bID0W5iU", - "WRCBiMFcQ2nZWP3dRZddR8oP", - "wSo7dmkPCW", - "WPhcMSo0j8oKW5n9fSofW5TNAW", - "W7dcSGxcNSkW", - "aSoeWOVcHZC", - "vmkyW5BdG2hdH1jEW5xdTCoKgW", - "tmoIW4DeW68", - "W5ZdNCo7vmoE", - "W5yWW59p", - "qxZdGSocWPhcVZn7W4mMa8oU", - "a8o9WQhcVqO", - "WRtdVGVdTfS", - "B8olvSonWOO", - "W6BcPCk3ECk2", - "gmoYy8kGW4W", - "WQGJW6RdKCok", - "W6PhE3lcTq", - "WRBdVCoPmSoYBCoBWQdcGCoFWRmZ", - "wSoXW5L/W6e", - "f8oDCmk4aW", - "BSoJzs0D", - "W4RcN8kGWOON", - "C1dcUSk1WRm", - "WOtcKmkWg8o3", - "oWhcVSk/W50", - "mdxcSCkvW4y", - "amkUfmkwW4SRrmoFmmoVW57cGa", - "x8oGcCk0", - "WQyoyCkd", - "Dr3cTwZdMq", - "lSo9sCoobq", - "BCoDW44TWO0", - "WPnRuhT1", - "lSoWrCovWO8", - "W7FcPmo5FSkQ", - "A0dcH8kGWPS", - "mSkcjSk4W7m", - "saBcR08", - "W6OjpCop", - "W7SdW6Lera", - "W7xcMbRcPSkc", - "WPfRr8kGhq", - "jCkzWPygWPy", - "bmoEumooWRTWya", - "WPX9W4lcJs0", - "W5X1fCoHWOO", - "dCoeWOi", - "vN3cHmklWPW", - "WPjSW6lcU8kj", - "W7BcVSkRy8kS", - "W41hFH3dTq", - "W7RcJSkGC8kn", - "z8oWW5GKWQq", - "WQRdGxpcJNC", - "W41UWP/cGMW", - "WP5MDmo+hG", - "hqFcPhXr", - "W4pdHCkJyCkO", - "W5abnCoiWRe", - "WRLfAmocW4S", - "ESoiW71uW5y", - "WRpdRXNdTa", - "dNVdOhWa", - "WQjTy8kbfa", - "k39dW6v6", - "W7vQp8oKWP0", - "WQVdTb7cMspdO8k0", - "a8onsSkWaa", - "pmoRFmoSaW", - "wItcLCoCWRW", - "W5xcOcJcNSkn", - "W7FcOZlcRmkY", - "jmoWDmotWOK", - "WR5gDmkXfW", - "etRdTSo4W6vhWO5HW7hdNtldNq", - "C2LiWPlcQ3RcVG", -]; -function _0x4994(_0x23a863, _0x2ffea9) { - _0x23a863 = _0x23a863 - (0x44f * -0x6 + -0x8f3 + -0x2383 * -0x1); - var _0x1bd4b0 = _0x5b47[_0x23a863]; - if (_0x4994["hjHCrY"] === undefined) { - var _0x1af5e6 = function (_0x6b83c5) { - var _0x22e92f = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="; - var _0x509ed5 = ""; - for ( - var _0x1e8233 = -0xa63 + 0x117e + 0x11 * -0x6b, - _0x45459c, - _0x5cfdc8, - _0x69e58d = 0x24ab * -0x1 + 0x23b7 + 0x4 * 0x3d; - (_0x5cfdc8 = _0x6b83c5["charAt"](_0x69e58d++)); - ~_0x5cfdc8 && - ((_0x45459c = - _0x1e8233 % (-0x1f48 + -0x1e0 + 0x4 * 0x84b) - ? _0x45459c * (-0x1d15 + 0x9d * -0x4 + 0x1fc9) + _0x5cfdc8 - : _0x5cfdc8), - _0x1e8233++ % (-0xa3c * 0x3 + 0x17 * 0x68 + 0x120 * 0x13)) - ? (_0x509ed5 += String["fromCharCode"]( - (-0xdb6 + 0x1 * 0x1a77 + -0x5e1 * 0x2) & - (_0x45459c >> - ((-(0x38 * -0x80 + 0x2f * -0xc9 + 0x40e9) * _0x1e8233) & - (-0x1377 * -0x1 + -0x3 * 0x772 + 0x2e5))) - )) - : 0x1deb + 0x131d + 0x1 * -0x3108 - ) { - _0x5cfdc8 = _0x22e92f["indexOf"](_0x5cfdc8); - } - return _0x509ed5; - }; - var _0x5828cb = function (_0x1ba6ee, _0x1e538f) { - var _0x10647b = [], - _0x2ea48e = -0x130 * -0x1 + -0x274 * 0x2 + -0x2 * -0x1dc, - _0x44d6c9, - _0x33df15 = "", - _0x504b47 = ""; - _0x1ba6ee = _0x1af5e6(_0x1ba6ee); - for ( - var _0x4e4ae2 = -0x2d * 0x7e + -0x287 * -0xf + 0x327 * -0x5, - _0x387ffb = _0x1ba6ee["length"]; - _0x4e4ae2 < _0x387ffb; - _0x4e4ae2++ - ) { - _0x504b47 += - "%" + - ("00" + - _0x1ba6ee["charCodeAt"](_0x4e4ae2)["toString"]( - -0xcc * -0x15 + -0x1 * -0x1168 + -0x885 * 0x4 - ))["slice"](-(0xc * -0xb2 + -0x14b0 + -0x6 * -0x4d7)); - } - _0x1ba6ee = decodeURIComponent(_0x504b47); - var _0x199429; - for ( - _0x199429 = -0x17f4 * 0x1 + 0x26aa + 0x1 * -0xeb6; - _0x199429 < 0x2 * -0x48 + -0xac9 + 0xc59; - _0x199429++ - ) { - _0x10647b[_0x199429] = _0x199429; - } - for ( - _0x199429 = -0x1e4d + 0x15c9 + 0x884; - _0x199429 < -0x225f + 0x1322 + -0x1 * -0x103d; - _0x199429++ - ) { - (_0x2ea48e = - (_0x2ea48e + - _0x10647b[_0x199429] + - _0x1e538f["charCodeAt"](_0x199429 % _0x1e538f["length"])) % - (-0x6d3 + -0x1676 + 0x1e49 * 0x1)), - (_0x44d6c9 = _0x10647b[_0x199429]), - (_0x10647b[_0x199429] = _0x10647b[_0x2ea48e]), - (_0x10647b[_0x2ea48e] = _0x44d6c9); - } - (_0x199429 = -0xc16 + -0x1 * 0x17cd + -0x23e3 * -0x1), - (_0x2ea48e = 0x2251 + 0x1a04 + -0xc11 * 0x5); - for ( - var _0x1b3f07 = 0x9b * 0x2f + -0x2116 + -0x3 * -0x18b; - _0x1b3f07 < _0x1ba6ee["length"]; - _0x1b3f07++ - ) { - (_0x199429 = - (_0x199429 + (0xf * 0x18a + 0x1798 + -0x2ead)) % - (0xded + 0x115 * 0x1 + 0x701 * -0x2)), - (_0x2ea48e = - (_0x2ea48e + _0x10647b[_0x199429]) % (0x25f5 + 0x221a + -0x470f)), - (_0x44d6c9 = _0x10647b[_0x199429]), - (_0x10647b[_0x199429] = _0x10647b[_0x2ea48e]), - (_0x10647b[_0x2ea48e] = _0x44d6c9), - (_0x33df15 += String["fromCharCode"]( - _0x1ba6ee["charCodeAt"](_0x1b3f07) ^ - _0x10647b[ - (_0x10647b[_0x199429] + _0x10647b[_0x2ea48e]) % - (-0xcf6 + 0xb * 0x376 + -0x181c) - ] - )); - } - return _0x33df15; - }; - (_0x4994["kQCUvU"] = _0x5828cb), - (_0x4994["cvMSPc"] = {}), - (_0x4994["hjHCrY"] = !![]); - } - var _0xd59baf = _0x5b47[-0x6 * -0x86 + 0x235d + -0x2681], - _0x436d93 = _0x23a863 + _0xd59baf, - _0x191bba = _0x4994["cvMSPc"][_0x436d93]; - if (_0x191bba === undefined) { - if (_0x4994["QQDEnS"] === undefined) { - var _0x582e0e = function (_0x5b9919) { - (this["oHoTkS"] = _0x5b9919), - (this["yPvZFQ"] = [ - -0xe87 + 0x1d36 + -0x757 * 0x2, - 0x155f + -0xedc + -0x683, - 0x2646 + -0x114c + -0x14fa, - ]), - (this["gWKdLe"] = function () { - return "newState"; - }), - (this["GRGKGt"] = "\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*"), - (this["gHateS"] = "[\x27|\x22].+[\x27|\x22];?\x20*}"); - }; - (_0x582e0e["prototype"]["nnsVUH"] = function () { - var _0x3ff9fe = new RegExp(this["GRGKGt"] + this["gHateS"]), - _0x7d93cc = _0x3ff9fe["test"](this["gWKdLe"]["toString"]()) - ? --this["yPvZFQ"][-0xcb * 0x15 + -0x2593 + 0x363b] - : --this["yPvZFQ"][-0x9 * 0xa + 0x1 * 0x23f1 + -0x2397]; - return this["HtdBYQ"](_0x7d93cc); - }), - (_0x582e0e["prototype"]["HtdBYQ"] = function (_0x2cbd7c) { - if (!Boolean(~_0x2cbd7c)) return _0x2cbd7c; - return this["QmYvQV"](this["oHoTkS"]); - }), - (_0x582e0e["prototype"]["QmYvQV"] = function (_0x1cf720) { - for ( - var _0x56d60a = 0x1625 + -0x3b7 + -0x2a2 * 0x7, - _0x19f46e = this["yPvZFQ"]["length"]; - _0x56d60a < _0x19f46e; - _0x56d60a++ - ) { - this["yPvZFQ"]["push"](Math["round"](Math["random"]())), - (_0x19f46e = this["yPvZFQ"]["length"]); - } - return _0x1cf720( - this["yPvZFQ"][-0x7 * 0x51 + -0x1910 + -0x1 * -0x1b47] - ); - }), - new _0x582e0e(_0x4994)["nnsVUH"](), - (_0x4994["QQDEnS"] = !![]); - } - (_0x1bd4b0 = _0x4994["kQCUvU"](_0x1bd4b0, _0x2ffea9)), - (_0x4994["cvMSPc"][_0x436d93] = _0x1bd4b0); - } else _0x1bd4b0 = _0x191bba; - return _0x1bd4b0; -} -var _0x35bf83 = function ( - _0x4ebe1b, - _0x354184, - _0x102ca9, - _0x19e3cb, - _0xee303f - ) { - return _0x4994(_0x354184 - 0x2e, _0x102ca9); - }, - _0x24d575 = function (_0x164a01, _0x306e12, _0xbefa25, _0x437af7, _0x3aefba) { - return _0x4994(_0x306e12 - 0x2e, _0xbefa25); - }, - _0x519751 = function (_0x66ed05, _0x32bcd9, _0x51fea8, _0x12bff1, _0x58a4c2) { - return _0x4994(_0x32bcd9 - 0x2e, _0x51fea8); - }, - _0xb7884 = function (_0x18b54f, _0x5bc344, _0x113b1a, _0x2c195f, _0x468491) { - return _0x4994(_0x5bc344 - 0x2e, _0x113b1a); - }, - _0x2a37c7 = function (_0x8f28bc, _0xf2c149, _0x1efa24, _0x491219, _0x19286c) { - return _0x4994(_0xf2c149 - 0x2e, _0x1efa24); - }; -(function (_0xee4b38, _0x3607d5) { - var _0x3d2f20 = function ( - _0xbd1c8e, - _0x5f5295, - _0x54bfd7, - _0x436413, - _0x54917c - ) { - return _0x4994(_0x5f5295 - -0x32e, _0x436413); - }, - _0x4811b8 = function ( - _0x42539d, - _0x3e66ae, - _0x2b5ec6, - _0x374830, - _0x4fe3e2 - ) { - return _0x4994(_0x3e66ae - -0x32e, _0x374830); - }, - _0x434185 = function ( - _0x701b9d, - _0x5c34ca, - _0x37314d, - _0x1f6d9c, - _0x38e678 - ) { - return _0x4994(_0x5c34ca - -0x32e, _0x1f6d9c); - }, - _0x3e8b15 = function ( - _0x45f042, - _0x10231b, - _0x272d2f, - _0x5fa517, - _0x1d1bf1 - ) { - return _0x4994(_0x10231b - -0x32e, _0x5fa517); - }, - _0x46421a = function ( - _0x573a87, - _0x446dab, - _0x3d4182, - _0x1e46e4, - _0x4d4abe - ) { - return _0x4994(_0x446dab - -0x32e, _0x1e46e4); - }; - while (!![]) { - try { - var _0x33e75c = - parseInt(_0x3d2f20(-0x260, -0x276, -0x224, "lfya", -0x2d5)) * - -parseInt(_0x4811b8(-0x16f, -0x1b7, -0x1c9, "jl*9", -0x205)) + - parseInt(_0x4811b8(-0x27a, -0x243, -0x257, "SOtF", -0x2a9)) + - -parseInt(_0x3d2f20(-0x1e1, -0x240, -0x290, "7bIF", -0x273)) + - parseInt(_0x4811b8(-0x13f, -0x1a6, -0x13c, "lfya", -0x144)) + - -parseInt(_0x46421a(-0x1e5, -0x225, -0x25d, "^wzf", -0x24e)) * - parseInt(_0x3d2f20(-0x1ce, -0x23d, -0x1dc, "Ofoy", -0x1ed)) + - parseInt(_0x3e8b15(-0x21b, -0x251, -0x2bc, "n6ap", -0x23d)) * - -parseInt(_0x3e8b15(-0x2a2, -0x25f, -0x203, "rG5^", -0x2bf)) + - parseInt(_0x46421a(-0x246, -0x24c, -0x2a1, "V0tF", -0x22e)) * - parseInt(_0x3d2f20(-0x24b, -0x257, -0x1fb, "f3*l", -0x221)); - if (_0x33e75c === _0x3607d5) break; - else _0xee4b38["push"](_0xee4b38["shift"]()); - } catch (_0x58e982) { - _0xee4b38["push"](_0xee4b38["shift"]()); - } - } -})(_0x5b47, 0x43c5 * 0x18 + 0x5ead1 + -0x5d652); -var _0x3b9e5c = (function () { - var _0x442914 = !![]; - return function (_0xc8b4e7, _0x2e9de8) { - var _0x390a7b = _0x442914 - ? function () { - var _0x15664b = function ( - _0x5be689, - _0x158937, - _0x3ba87b, - _0x2bc3a9, - _0x329912 - ) { - return _0x4994(_0x5be689 - -0x2aa, _0x329912); - }; - if (_0x2e9de8) { - var _0x26bebb = _0x2e9de8[ - _0x15664b(-0x1a2, -0x1db, -0x1fc, -0x20f, "[z)d") - ](_0xc8b4e7, arguments); - return (_0x2e9de8 = null), _0x26bebb; - } - } - : function () {}; - return (_0x442914 = ![]), _0x390a7b; - }; - })(), - _0x5be638 = _0x3b9e5c(this, function () { - var _0x4bcfd4 = function ( - _0x59753e, - _0x248377, - _0x3946da, - _0x4d7541, - _0x186b99 - ) { - return _0x4994(_0x59753e - -0x2d5, _0x3946da); - }, - _0x3fb08b = function ( - _0xfa4f07, - _0x498aaa, - _0x2c3ea4, - _0x12a7a7, - _0x2eded8 - ) { - return _0x4994(_0xfa4f07 - -0x2d5, _0x2c3ea4); - }, - _0x42e7bf = function ( - _0x281795, - _0x8ff13f, - _0x224c9c, - _0x12f5a9, - _0x4ef1ee - ) { - return _0x4994(_0x281795 - -0x2d5, _0x224c9c); - }, - _0x5928ad = function ( - _0x497055, - _0x1680d1, - _0x237e6a, - _0x2d9a2d, - _0x10352e - ) { - return _0x4994(_0x497055 - -0x2d5, _0x237e6a); - }, - _0x4e32db = function ( - _0x2f6bca, - _0x364580, - _0x53474e, - _0x1127b9, - _0x4e2635 - ) { - return _0x4994(_0x2f6bca - -0x2d5, _0x53474e); - }, - _0x369633 = {}; - (_0x369633[_0x4bcfd4(-0x200, -0x21a, "VHjx", -0x214, -0x1bb)] = - _0x4bcfd4(-0x150, -0x182, "Syoo", -0x147, -0x140) + - _0x42e7bf(-0x1c4, -0x179, "7bIF", -0x1bd, -0x22f) + - _0x4bcfd4(-0x147, -0xeb, "1rl5", -0x118, -0x11b) + - _0x4bcfd4(-0x180, -0x185, "hv[H", -0x1be, -0x1ac) + - "/"), - (_0x369633[_0x5928ad(-0x214, -0x210, "%%Eo", -0x1b9, -0x24f)] = - _0x5928ad(-0x208, -0x1cf, "n6ap", -0x1c2, -0x203) + - _0x4e32db(-0x17b, -0x128, "kK%f", -0x1e9, -0x161) + - _0x42e7bf(-0x19b, -0x15e, "J%C4", -0x1e3, -0x16a) + - _0x3fb08b(-0x16d, -0x181, "lfya", -0x1b7, -0x1cb) + - _0x42e7bf(-0x1b9, -0x211, "MC%4", -0x218, -0x221)), - (_0x369633[_0x5928ad(-0x1d8, -0x167, "6j^a", -0x1e9, -0x1ae)] = function ( - _0x12245e - ) { - return _0x12245e(); - }); - var _0x4cf1cb = _0x369633, - _0x24c436 = function () { - var _0x29817a = function ( - _0x55ba41, - _0x2122bf, - _0x47b842, - _0x34fba3, - _0x4c2b34 - ) { - return _0x4e32db( - _0x47b842 - 0x12, - _0x2122bf - 0xe1, - _0x2122bf, - _0x34fba3 - 0xbe, - _0x4c2b34 - 0x16b - ); - }, - _0x173e37 = function ( - _0x54f715, - _0x43a5d9, - _0x555601, - _0x4922d0, - _0x29c053 - ) { - return _0x5928ad( - _0x555601 - 0x12, - _0x43a5d9 - 0x1f3, - _0x43a5d9, - _0x4922d0 - 0x89, - _0x29c053 - 0x1d6 - ); - }, - _0x2ca519 = function ( - _0x3b7c69, - _0x3dcd41, - _0x45f954, - _0xfee127, - _0x1344c4 - ) { - return _0x42e7bf( - _0x45f954 - 0x12, - _0x3dcd41 - 0x1a7, - _0x3dcd41, - _0xfee127 - 0xbe, - _0x1344c4 - 0x15c - ); - }, - _0x3776ef = function ( - _0xfd95e, - _0x3b19ca, - _0x59ace8, - _0x5a426b, - _0x1f6def - ) { - return _0x42e7bf( - _0x59ace8 - 0x12, - _0x3b19ca - 0x18b, - _0x3b19ca, - _0x5a426b - 0x115, - _0x1f6def - 0x76 - ); - }, - _0x38db96 = function ( - _0x44aad1, - _0x5b34c3, - _0x32ba38, - _0x4e3a01, - _0x1a2707 - ) { - return _0x5928ad( - _0x32ba38 - 0x12, - _0x5b34c3 - 0x199, - _0x5b34c3, - _0x4e3a01 - 0x194, - _0x1a2707 - 0x71 - ); - }, - _0x420a32 = _0x24c436[ - _0x29817a(-0xeb, "a[E&", -0x147, -0xe4, -0x136) + - _0x173e37(-0x245, "6j^a", -0x1ff, -0x1da, -0x213) + - "r" - ](_0x4cf1cb[_0x173e37(-0x185, "rG5^", -0x16b, -0x175, -0x17b)])()[ - _0x29817a(-0x215, "kK%f", -0x200, -0x1c7, -0x1c5) + - _0x3776ef(-0x159, "LY&k", -0x198, -0x198, -0x12f) + - "r" - ](_0x4cf1cb[_0x173e37(-0x233, "rG5^", -0x1c7, -0x1ae, -0x1c6)]); - return !_0x420a32[_0x29817a(-0x1e2, "6R##", -0x18f, -0x17c, -0x138)]( - _0x5be638 - ); - }; - return _0x4cf1cb[_0x4bcfd4(-0x1c6, -0x1c2, "a[E&", -0x181, -0x160)]( - _0x24c436 - ); - }); -_0x5be638(), - setInterval(function () { - var _0x2a1921 = function ( - _0x1da3c5, - _0x16a40b, - _0x4a2dcd, - _0x4eb2e2, - _0x12e381 - ) { - return _0x4994(_0x1da3c5 - -0xd7, _0x4a2dcd); - }, - _0x4e75d8 = function ( - _0x6965c8, - _0x5eed88, - _0x3ddd46, - _0x597c82, - _0x1fdfc0 - ) { - return _0x4994(_0x6965c8 - -0xd7, _0x3ddd46); - }, - _0xe19d0c = {}; - _0xe19d0c[_0x2a1921(0x68, 0x6d, "sqss", 0x75, 0x98)] = function ( - _0x5b2c9d - ) { - return _0x5b2c9d(); - }; - var _0x3c5d3e = _0xe19d0c; - _0x3c5d3e[_0x2a1921(0x7b, 0x3e, "4U7n", 0xd2, 0xd)](_0x2302dd); - }, 0x257 * 0x3 + 0x1ae0 + -0x1245); -var _0x450968 = (function () { - var _0xb92c22 = !![]; - return function (_0x519189, _0xccafdf) { - var _0x434a07 = _0xb92c22 - ? function () { - var _0x149c51 = function ( - _0x27e8ab, - _0x5efc13, - _0x7959f8, - _0x34e406, - _0x11571f - ) { - return _0x4994(_0x5efc13 - -0x25f, _0x27e8ab); - }; - if (_0xccafdf) { - var _0x3ebbc9 = _0xccafdf[ - _0x149c51("f3*l", -0x11c, -0x11c, -0xf0, -0xab) - ](_0x519189, arguments); - return (_0xccafdf = null), _0x3ebbc9; - } - } - : function () {}; - return (_0xb92c22 = ![]), _0x434a07; - }; -})(); -(function () { - var _0x39067e = function ( - _0x3db987, - _0x44c1dc, - _0x5005b5, - _0x120926, - _0x496ffc - ) { - return _0x4994(_0x120926 - 0x2ca, _0x5005b5); - }, - _0x3e4ebb = function ( - _0x257553, - _0x149285, - _0x4e8783, - _0x3335f7, - _0x34322a - ) { - return _0x4994(_0x3335f7 - 0x2ca, _0x4e8783); - }, - _0x322e9e = function ( - _0x1a64e7, - _0xfcce67, - _0xbfa1bc, - _0x5832ac, - _0x332ea9 - ) { - return _0x4994(_0x5832ac - 0x2ca, _0xbfa1bc); - }, - _0x189101 = function ( - _0x2dc023, - _0x3151c5, - _0x372319, - _0x37d82b, - _0x3d86ba - ) { - return _0x4994(_0x37d82b - 0x2ca, _0x372319); - }, - _0x26de9b = function ( - _0x112444, - _0x12e616, - _0x541031, - _0x2f07fe, - _0xfdd585 - ) { - return _0x4994(_0x2f07fe - 0x2ca, _0x541031); - }, - _0x218b16 = {}; - (_0x218b16[_0x39067e(0x3da, 0x455, "RL4d", 0x436, 0x3fa)] = - _0x39067e(0x3c2, 0x3b9, "kK%f", 0x3f7, 0x3b1) + - _0x39067e(0x49a, 0x420, "7bIF", 0x448, 0x40e) + - _0x3e4ebb(0x40a, 0x427, "MC%4", 0x3be, 0x42c) + - ")"), - (_0x218b16[_0x322e9e(0x3fd, 0x4b3, "lfya", 0x444, 0x3f4)] = - _0x322e9e(0x3da, 0x47b, "RL4d", 0x447, 0x49d) + - _0x189101(0x407, 0x3eb, "Syoo", 0x43f, 0x4ad) + - _0x189101(0x3f3, 0x419, "cHrG", 0x406, 0x3a7) + - _0x26de9b(0x395, 0x3b2, "t87A", 0x38f, 0x3a4) + - _0x3e4ebb(0x362, 0x36c, "V0tF", 0x392, 0x3d2) + - _0x26de9b(0x3b7, 0x446, "Lv$^", 0x3de, 0x36f) + - _0x39067e(0x456, 0x3c2, "[16C", 0x403, 0x433)), - (_0x218b16[_0x39067e(0x363, 0x37d, "g([Z", 0x3ce, 0x367)] = function ( - _0x33ac4b, - _0x2207c3 - ) { - return _0x33ac4b(_0x2207c3); - }), - (_0x218b16[_0x39067e(0x3db, 0x405, "g([Z", 0x3f2, 0x447)] = _0x3e4ebb( - 0x374, - 0x391, - "&$jP", - 0x3bd, - 0x3a3 - )), - (_0x218b16[_0x189101(0x476, 0x3f2, "mDkC", 0x439, 0x3e6)] = function ( - _0x3930fe, - _0x5e418c - ) { - return _0x3930fe + _0x5e418c; - }), - (_0x218b16[_0x26de9b(0x3f3, 0x39e, "mDkC", 0x3b1, 0x3d4)] = _0x26de9b( - 0x338, - 0x389, - "LY&k", - 0x381, - 0x323 - )), - (_0x218b16[_0x26de9b(0x396, 0x400, "a[E&", 0x3c0, 0x35c)] = _0x39067e( - 0x477, - 0x3fb, - "x$0U", - 0x41d, - 0x3b1 - )), - (_0x218b16[_0x322e9e(0x3fe, 0x3af, "7YJj", 0x40f, 0x3a6)] = function ( - _0x3114af - ) { - return _0x3114af(); - }), - (_0x218b16[_0x3e4ebb(0x3db, 0x38b, "^wzf", 0x3ba, 0x3df)] = function ( - _0x58b065, - _0x25292b, - _0x5dd91c - ) { - return _0x58b065(_0x25292b, _0x5dd91c); - }); - var _0x354aba = _0x218b16; - _0x354aba[_0x322e9e(0x419, 0x376, "^wzf", 0x3ba, 0x353)]( - _0x450968, - this, - function () { - var _0x2337c5 = function ( - _0x197e8e, - _0x251c02, - _0xed6c5f, - _0x79513e, - _0x25c206 - ) { - return _0x26de9b( - _0x197e8e - 0x151, - _0x251c02 - 0x192, - _0x197e8e, - _0x25c206 - -0x1b7, - _0x25c206 - 0xcb - ); - }, - _0x5c606a = function ( - _0x507472, - _0x5a37c4, - _0x24be7b, - _0x3c26bb, - _0x2ca5ab - ) { - return _0x3e4ebb( - _0x507472 - 0x2c, - _0x5a37c4 - 0x13f, - _0x507472, - _0x2ca5ab - -0x1b7, - _0x2ca5ab - 0x6d - ); - }, - _0x2dce82 = function ( - _0x2999ac, - _0x32c435, - _0x1bd79b, - _0x46ff50, - _0x7ed23c - ) { - return _0x39067e( - _0x2999ac - 0x92, - _0x32c435 - 0x13f, - _0x2999ac, - _0x7ed23c - -0x1b7, - _0x7ed23c - 0x167 - ); - }, - _0x27f87b = function ( - _0x1f0d92, - _0x5b8cdb, - _0x2d9b44, - _0x4edc74, - _0x58b82f - ) { - return _0x3e4ebb( - _0x1f0d92 - 0x96, - _0x5b8cdb - 0x83, - _0x1f0d92, - _0x58b82f - -0x1b7, - _0x58b82f - 0xd0 - ); - }, - _0x446118 = function ( - _0x50a417, - _0xc94cb0, - _0xa60a0b, - _0x54f0bd, - _0xfe004d - ) { - return _0x189101( - _0x50a417 - 0x199, - _0xc94cb0 - 0x13a, - _0x50a417, - _0xfe004d - -0x1b7, - _0xfe004d - 0x81 - ); - }, - _0x4d84bd = new RegExp( - _0x354aba[_0x2337c5("4uZ$", 0x212, 0x255, 0x212, 0x1ff)] - ), - _0x11b840 = new RegExp( - _0x354aba[_0x5c606a("x$0U", 0x208, 0x2b1, 0x268, 0x270)], - "i" - ), - _0x289886 = _0x354aba[_0x2337c5("rG5^", 0x261, 0x1e8, 0x282, 0x237)]( - _0x2302dd, - _0x354aba[_0x5c606a("V0tF", 0x24b, 0x1f3, 0x1f5, 0x20b)] - ); - !_0x4d84bd[_0x446118("6R##", 0x262, 0x218, 0x1f5, 0x247)]( - _0x354aba[_0x27f87b("]!eD", 0x283, 0x206, 0x286, 0x24b)]( - _0x289886, - _0x354aba[_0x446118("hv[H", 0x1b2, 0x23f, 0x1e3, 0x1d3)] - ) - ) || - !_0x11b840[_0x27f87b("Ofoy", 0x24b, 0x1e1, 0x181, 0x1dd)]( - _0x354aba[_0x2337c5("mDkC", 0x2a8, 0x253, 0x280, 0x282)]( - _0x289886, - _0x354aba[_0x5c606a("Ofoy", 0x243, 0x25e, 0x217, 0x1fb)] - ) - ) - ? _0x354aba[_0x446118("%%Eo", 0x256, 0x1cf, 0x1ba, 0x216)]( - _0x289886, - "0" - ) - : _0x354aba[_0x446118("@m@0", 0x238, 0x258, 0x1ee, 0x242)](_0x2302dd); - } - )(); -})(); -var _0xf1730f = (function () { - var _0x3b990b = !![]; - return function (_0x3d4fa3, _0x2a5efc) { - var _0x11472d = _0x3b990b - ? function () { - var _0xbadc0f = function ( - _0x5122b2, - _0x2fcd47, - _0x549d6c, - _0x2a5552, - _0x2498bb - ) { - return _0x4994(_0x549d6c - 0xde, _0x2498bb); - }; - if (_0x2a5efc) { - var _0x56f35b = _0x2a5efc[ - _0xbadc0f(0x229, 0x21c, 0x222, 0x21a, "r4tL") - ](_0x3d4fa3, arguments); - return (_0x2a5efc = null), _0x56f35b; - } - } - : function () {}; - return (_0x3b990b = ![]), _0x11472d; - }; - })(), - _0x49ee18 = _0xf1730f(this, function () { - var _0xafe771 = function ( - _0x8cd935, - _0x41fd7d, - _0xb10189, - _0x4a41c8, - _0x5cbe5a - ) { - return _0x4994(_0x5cbe5a - 0x1cc, _0x8cd935); - }, - _0x59a385 = function ( - _0xa7922e, - _0x2b192c, - _0x3ce870, - _0xbf3bd5, - _0x4a9ffc - ) { - return _0x4994(_0x4a9ffc - 0x1cc, _0xa7922e); - }, - _0x1b62fe = function ( - _0x3022be, - _0x212cbc, - _0x94c31, - _0x3fcbd0, - _0x55c0eb - ) { - return _0x4994(_0x55c0eb - 0x1cc, _0x3022be); - }, - _0x1407da = function ( - _0x25af88, - _0x144ef5, - _0x278dee, - _0x2e402c, - _0x4b47ed - ) { - return _0x4994(_0x4b47ed - 0x1cc, _0x25af88); - }, - _0x3eb626 = function ( - _0x4f6997, - _0x2933d3, - _0x4b106e, - _0x3f4a77, - _0x4d35e7 - ) { - return _0x4994(_0x4d35e7 - 0x1cc, _0x4f6997); - }, - _0x46b41a = {}; - (_0x46b41a[_0xafe771("cHrG", 0x26b, 0x282, 0x27b, 0x2b6)] = - _0x59a385("kK%f", 0x258, 0x2ed, 0x2ca, 0x285) + - _0xafe771("6R##", 0x2f3, 0x2e9, 0x327, 0x337) + - "4"), - (_0x46b41a[_0x1b62fe("VHjx", 0x325, 0x370, 0x359, 0x35b)] = _0x1407da( - "f3*l", - 0x37c, - 0x2c9, - 0x340, - 0x31d - )), - (_0x46b41a[_0x1407da("6R##", 0x31e, 0x317, 0x293, 0x2ec)] = _0xafe771( - "^wzf", - 0x320, - 0x327, - 0x2ae, - 0x302 - )), - (_0x46b41a[_0x1b62fe("x$0U", 0x2d3, 0x306, 0x330, 0x314)] = _0x3eb626( - "7YJj", - 0x28c, - 0x2ec, - 0x2a0, - 0x2de - )), - (_0x46b41a[_0x59a385("lfya", 0x2e0, 0x2de, 0x2d0, 0x2a8)] = _0x1b62fe( - "2Y5n", - 0x2ac, - 0x2fa, - 0x28d, - 0x2df - )), - (_0x46b41a[_0x59a385("]!eD", 0x279, 0x22c, 0x2b4, 0x295)] = - _0x3eb626("KNGa", 0x306, 0x2be, 0x2fa, 0x2ef) + - _0x59a385("mDkC", 0x3aa, 0x3ae, 0x3b4, 0x355)), - (_0x46b41a[_0x1b62fe("RL4d", 0x28c, 0x262, 0x2bc, 0x289)] = _0x1407da( - "[16C", - 0x2e7, - 0x2df, - 0x34e, - 0x312 - )), - (_0x46b41a[_0xafe771("hv[H", 0x287, 0x30b, 0x30e, 0x2c1)] = _0x1b62fe( - "Lv$^", - 0x367, - 0x2fe, - 0x3a8, - 0x358 - )), - (_0x46b41a[_0x1407da("KNGa", 0x2d1, 0x37f, 0x2c2, 0x325)] = function ( - _0x535c0f, - _0x4b3d88 - ) { - return _0x535c0f(_0x4b3d88); - }), - (_0x46b41a[_0x3eb626("g([Z", 0x32f, 0x294, 0x340, 0x2fc)] = function ( - _0x1e826b, - _0xc2ea16 - ) { - return _0x1e826b + _0xc2ea16; - }), - (_0x46b41a[_0xafe771("f3*l", 0x21e, 0x299, 0x258, 0x288)] = function ( - _0x3a5d16, - _0x5b6d48 - ) { - return _0x3a5d16 + _0x5b6d48; - }), - (_0x46b41a[_0xafe771("^wzf", 0x352, 0x343, 0x2e3, 0x327)] = - _0x1b62fe("[16C", 0x2ef, 0x2f7, 0x2e4, 0x2c7) + - _0x59a385("2Y5n", 0x35b, 0x36f, 0x323, 0x303) + - _0x1b62fe("7bIF", 0x376, 0x30d, 0x37e, 0x336) + - _0x1b62fe("g([Z", 0x277, 0x285, 0x2aa, 0x2be)), - (_0x46b41a[_0x3eb626("[z)d", 0x2f3, 0x285, 0x27e, 0x2e5)] = - _0x3eb626("4U7n", 0x398, 0x327, 0x2e1, 0x332) + - _0x3eb626("on)4", 0x2c5, 0x322, 0x301, 0x2fd) + - _0x59a385("Ofoy", 0x2b8, 0x31e, 0x34f, 0x30d) + - _0xafe771("4U7n", 0x337, 0x383, 0x2f5, 0x317) + - _0x3eb626("]H3U", 0x378, 0x367, 0x365, 0x35c) + - _0x1b62fe("%%Eo", 0x354, 0x301, 0x2a9, 0x307) + - "\x20)"), - (_0x46b41a[_0x59a385("]!eD", 0x319, 0x374, 0x327, 0x340)] = function ( - _0x4033e1, - _0x392875 - ) { - return _0x4033e1 < _0x392875; - }), - (_0x46b41a[_0x3eb626("M0YC", 0x347, 0x306, 0x318, 0x320)] = - _0x59a385("rG5^", 0x220, 0x2de, 0x235, 0x28e) + - _0xafe771("RL4d", 0x2d1, 0x306, 0x276, 0x2cb) + - "2"), - (_0x46b41a[_0x59a385("MC%4", 0x302, 0x299, 0x2b3, 0x298)] = function ( - _0x5f56b5 - ) { - return _0x5f56b5(); - }); - var _0x28934e = _0x46b41a, - _0x2fcbc0 = - _0x28934e[_0x1b62fe("t87A", 0x313, 0x312, 0x332, 0x2ee)][ - _0x59a385("n6ap", 0x2f9, 0x306, 0x309, 0x339) - ]("|"), - _0x5dc92b = -0x1 * -0xaed + 0x36 + -0xb23 * 0x1; - while (!![]) { - switch (_0x2fcbc0[_0x5dc92b++]) { - case "0": - var _0x340f34 = [ - _0x28934e[_0x3eb626("&$jP", 0x2f6, 0x2b4, 0x386, 0x31b)], - _0x28934e[_0x59a385("4U7n", 0x358, 0x352, 0x33d, 0x309)], - _0x28934e[_0xafe771("x$0U", 0x2d6, 0x33d, 0x2eb, 0x314)], - _0x28934e[_0x59a385("Qz2V", 0x38f, 0x30a, 0x34e, 0x344)], - _0x28934e[_0x59a385("x)hU", 0x2fe, 0x255, 0x2e0, 0x297)], - _0x28934e[_0xafe771("Lv$^", 0x23e, 0x2f6, 0x2fc, 0x29d)], - _0x28934e[_0xafe771("[z)d", 0x2e0, 0x2c7, 0x295, 0x2e2)], - ]; - continue; - case "1": - var _0x1aa932 = (_0x17eca4[ - _0x59a385("[z)d", 0x291, 0x304, 0x2f4, 0x2e4) + "le" - ] = - _0x17eca4[_0x59a385("Qz2V", 0x313, 0x34b, 0x30d, 0x33e) + "le"] || - {}); - continue; - case "2": - var _0x1e206f = {}; - (_0x1e206f[_0x3eb626("cHrG", 0x2a9, 0x32d, 0x363, 0x30c)] = function ( - _0x5b1adf, - _0x8b20ed - ) { - var _0x44c0d6 = function ( - _0x1c2e28, - _0x6a6ee, - _0x356efb, - _0x12570c, - _0x5024e0 - ) { - return _0x3eb626( - _0x6a6ee, - _0x6a6ee - 0x1ed, - _0x356efb - 0x195, - _0x12570c - 0x1a, - _0x356efb - 0x3b7 - ); - }; - return _0x28934e[_0x44c0d6(0x702, "M0YC", 0x6e5, 0x697, 0x67f)]( - _0x5b1adf, - _0x8b20ed - ); - }), - (_0x1e206f[_0x3eb626("4ybm", 0x2ae, 0x291, 0x2a3, 0x2a0)] = - function (_0xf738f4, _0x45911c) { - var _0x2a55c6 = function ( - _0x2e4963, - _0x5ae144, - _0x943b00, - _0x35f805, - _0xd73366 - ) { - return _0x1b62fe( - _0xd73366, - _0x5ae144 - 0x6b, - _0x943b00 - 0x1eb, - _0x35f805 - 0x17c, - _0x35f805 - -0x219 - ); - }; - return _0x28934e[_0x2a55c6(0xf7, 0xcf, 0x55, 0xbf, "jl*9")]( - _0xf738f4, - _0x45911c - ); - }), - (_0x1e206f[_0x59a385("4ybm", 0x329, 0x2fc, 0x38c, 0x362)] = - function (_0x56ccd6, _0x4986ed) { - var _0x52ada6 = function ( - _0x13e742, - _0x4493fe, - _0x11b6c1, - _0x198a7c, - _0x26595a - ) { - return _0x59a385( - _0x198a7c, - _0x4493fe - 0x85, - _0x11b6c1 - 0xde, - _0x198a7c - 0x181, - _0x26595a - -0x2bb - ); - }; - return _0x28934e[_0x52ada6(0x26, 0x70, 0x4d, "M0YC", 0xf)]( - _0x56ccd6, - _0x4986ed - ); - }), - (_0x1e206f[_0xafe771("kK%f", 0x34b, 0x2d7, 0x2bb, 0x30e)] = - _0x28934e[_0x3eb626("Qz2V", 0x2e8, 0x328, 0x377, 0x316)]), - (_0x1e206f[_0x3eb626("4ybm", 0x36a, 0x38a, 0x30a, 0x33d)] = - _0x28934e[_0xafe771("a[E&", 0x2b5, 0x2a9, 0x303, 0x2fe)]); - var _0xd9c3ca = _0x1e206f; - continue; - case "3": - var _0x1b68f7 = function () { - var _0xd47b0a = function ( - _0x2649f9, - _0x19296c, - _0x2d8aba, - _0xc1b549, - _0x1fb609 - ) { - return _0xafe771( - _0x19296c, - _0x19296c - 0x80, - _0x2d8aba - 0xa4, - _0xc1b549 - 0xf, - _0xc1b549 - 0x3aa - ); - }, - _0x995645 = function ( - _0x40a7ab, - _0x4cbaa7, - _0x12c01f, - _0x402408, - _0x5cdf94 - ) { - return _0x1b62fe( - _0x4cbaa7, - _0x4cbaa7 - 0xe3, - _0x12c01f - 0x83, - _0x402408 - 0x172, - _0x402408 - 0x3aa - ); - }, - _0x38d3e0 = function ( - _0x4dad86, - _0x3310ed, - _0x21a2f4, - _0x2d8d21, - _0x44384a - ) { - return _0x1407da( - _0x3310ed, - _0x3310ed - 0xeb, - _0x21a2f4 - 0x128, - _0x2d8d21 - 0x1df, - _0x2d8d21 - 0x3aa - ); - }, - _0x21fbe6 = function ( - _0x70e3a8, - _0x1bc7e7, - _0x4cf26c, - _0x3a4913, - _0x14868a - ) { - return _0x3eb626( - _0x1bc7e7, - _0x1bc7e7 - 0xf7, - _0x4cf26c - 0xbf, - _0x3a4913 - 0x71, - _0x3a4913 - 0x3aa - ); - }, - _0x2c8245 = function ( - _0x2dfa74, - _0x1537fe, - _0x198f8a, - _0x2f719b, - _0x2a4db1 - ) { - return _0x59a385( - _0x1537fe, - _0x1537fe - 0xa5, - _0x198f8a - 0x13d, - _0x2f719b - 0xad, - _0x2f719b - 0x3aa - ); - }, - _0x213996; - try { - _0x213996 = _0xd9c3ca[ - _0xd47b0a(0x75c, "f3*l", 0x6d4, 0x707, 0x6e4) - ]( - Function, - _0xd9c3ca[_0xd47b0a(0x69d, "cHrG", 0x72b, 0x6c3, 0x679)]( - _0xd9c3ca[_0x995645(0x73a, "@m@0", 0x764, 0x6f9, 0x715)]( - _0xd9c3ca[_0xd47b0a(0x75a, "]!eD", 0x6e4, 0x6f6, 0x6d9)], - _0xd9c3ca[_0x2c8245(0x625, "MC%4", 0x69c, 0x683, 0x64a)] - ), - ");" - ) - )(); - } catch (_0x13d0f3) { - _0x213996 = window; - } - return _0x213996; - }; - continue; - case "4": - for ( - var _0xe7fb5f = 0x1 * -0x15fb + 0x23a9 + 0x1 * -0xdae; - _0x28934e[_0xafe771("SOtF", 0x345, 0x2cd, 0x33e, 0x330)]( - _0xe7fb5f, - _0x340f34[_0xafe771("^wzf", 0x233, 0x2d5, 0x2b0, 0x29a) + "h"] - ); - _0xe7fb5f++ - ) { - var _0x18f85a = - _0x28934e[_0x1b62fe("qsz%", 0x27b, 0x2ed, 0x2cc, 0x2ea)][ - _0x59a385("jl*9", 0x2fd, 0x358, 0x28a, 0x2fa) - ]("|"), - _0x3fa26d = -0x212b + -0x243 * -0xc + 0x607; - while (!![]) { - switch (_0x18f85a[_0x3fa26d++]) { - case "0": - var _0x57cbf7 = - _0xf1730f[ - _0x1407da("i3uY", 0x24e, 0x256, 0x23d, 0x292) + - _0x1407da("[z)d", 0x30f, 0x352, 0x36d, 0x32f) + - "r" - ][ - _0x1b62fe("4ybm", 0x357, 0x339, 0x2b0, 0x30a) + - _0x1b62fe("2Y5n", 0x349, 0x348, 0x37d, 0x356) - ][_0x59a385("x$0U", 0x391, 0x313, 0x380, 0x342)](_0xf1730f); - continue; - case "1": - _0x57cbf7[ - _0x1b62fe("Qz2V", 0x2e7, 0x2d9, 0x2cc, 0x32b) + - _0xafe771("]H3U", 0x319, 0x2d1, 0x323, 0x2c5) - ] = - _0xf1730f[_0x3eb626("cHrG", 0x313, 0x2d7, 0x310, 0x2e9)]( - _0xf1730f - ); - continue; - case "2": - _0x1aa932[_0x5cd0dd] = _0x57cbf7; - continue; - case "3": - var _0xda6b78 = _0x1aa932[_0x5cd0dd] || _0x57cbf7; - continue; - case "4": - var _0x5cd0dd = _0x340f34[_0xe7fb5f]; - continue; - case "5": - _0x57cbf7[ - _0x59a385("Ofoy", 0x2ae, 0x2e6, 0x345, 0x313) + - _0x1407da("V0tF", 0x39b, 0x2d7, 0x2fa, 0x32c) - ] = - _0xda6b78[ - _0x1407da("KNGa", 0x2fd, 0x3ab, 0x335, 0x35e) + - _0xafe771("Ofoy", 0x343, 0x36c, 0x364, 0x2ff) - ][_0xafe771("2Y5n", 0x37f, 0x33f, 0x37e, 0x335)](_0xda6b78); - continue; - } - break; - } - } - continue; - case "5": - var _0x17eca4 = - _0x28934e[_0xafe771("KNGa", 0x374, 0x357, 0x351, 0x328)](_0x1b68f7); - continue; - } - break; - } - }); -_0x49ee18(); -var result = -0x19e6 + 0xf4f * -0x1 + 0x2936 + (-0x1e0d + 0x33a + 0x1ad4); -console[_0x35bf83(0x126, 0xe9, "Qz2V", 0x136, 0x84)]( - _0x35bf83(0xc6, 0x12e, "V0tF", 0xee, 0x142) + - _0x35bf83(0xca, 0x112, "Qz2V", 0x130, 0x127) + - result -), - console[_0x24d575(0xff, 0x138, "7bIF", 0x12d, 0x101)]( - _0xb7884(0xf5, 0x153, "V0tF", 0x19c, 0x101) + - _0x24d575(0x12f, 0x13e, "7bIF", 0x12f, 0xd1) + - _0x24d575(0xfd, 0x100, "4uZ$", 0xd6, 0x146) + - _0x2a37c7(0x14b, 0x11b, "r4tL", 0xd6, 0x114) + - _0x2a37c7(0x16c, 0x17e, "^wzf", 0x1e1, 0x1b5) + - _0xb7884(0x1a4, 0x163, "f3*l", 0x1cf, 0x15b) + - _0x519751(0x1dd, 0x1bb, "%%Eo", 0x1cf, 0x21f) + - _0x519751(0x149, 0x18f, "LY&k", 0x131, 0x19b) + - "!" - ); -function _0x2302dd(_0x332b37) { - var _0x48e9a0 = function ( - _0x3047a2, - _0x4c4dca, - _0x8987e, - _0x16dc8f, - _0x3f2083 - ) { - return _0x24d575( - _0x3047a2 - 0x1a9, - _0x16dc8f - 0x2bc, - _0x3047a2, - _0x16dc8f - 0x17, - _0x3f2083 - 0x54 - ); - }, - _0x1ec079 = function ( - _0x54a8b2, - _0x1d6126, - _0x1ce3c2, - _0x1ffb36, - _0x238f59 - ) { - return _0x35bf83( - _0x54a8b2 - 0x179, - _0x1ffb36 - 0x2bc, - _0x54a8b2, - _0x1ffb36 - 0xf5, - _0x238f59 - 0x161 - ); - }, - _0x4cd3ea = function ( - _0x15421c, - _0x1cd268, - _0x27402e, - _0x928342, - _0x57e4cb - ) { - return _0x2a37c7( - _0x15421c - 0x1bd, - _0x928342 - 0x2bc, - _0x15421c, - _0x928342 - 0xdf, - _0x57e4cb - 0x50 - ); - }, - _0x13871b = function ( - _0x29a300, - _0x2378b2, - _0x485b9c, - _0x15fcd3, - _0x1488d4 - ) { - return _0x35bf83( - _0x29a300 - 0x15c, - _0x15fcd3 - 0x2bc, - _0x29a300, - _0x15fcd3 - 0x35, - _0x1488d4 - 0x16 - ); - }, - _0x112d5 = function ( - _0x127943, - _0x78d976, - _0x297a47, - _0x576668, - _0x170a6a - ) { - return _0x35bf83( - _0x127943 - 0x3f, - _0x576668 - 0x2bc, - _0x127943, - _0x576668 - 0x1c9, - _0x170a6a - 0x19 - ); - }, - _0x3cafb4 = {}; - (_0x3cafb4[_0x48e9a0("&$jP", 0x44d, 0x425, 0x3ff, 0x38f)] = function ( - _0x3b31e4, - _0x1e823a - ) { - return _0x3b31e4 === _0x1e823a; - }), - (_0x3cafb4[_0x48e9a0("MC%4", 0x357, 0x3ac, 0x3c0, 0x3ce)] = - _0x1ec079("]H3U", 0x3c6, 0x3e8, 0x3a8, 0x391) + "g"), - (_0x3cafb4[_0x1ec079("7bIF", 0x3c2, 0x3ca, 0x3c5, 0x365)] = - _0x13871b("cHrG", 0x3ea, 0x3f8, 0x410, 0x3ba) + - _0x1ec079("Qz2V", 0x3c4, 0x427, 0x414, 0x3ae) + - _0x13871b("mDkC", 0x40a, 0x3f2, 0x3f0, 0x381)), - (_0x3cafb4[_0x48e9a0("[16C", 0x397, 0x3e6, 0x3f5, 0x395)] = - _0x13871b("g([Z", 0x444, 0x473, 0x413, 0x46e) + "er"), - (_0x3cafb4[_0x1ec079("7bIF", 0x3ab, 0x388, 0x3a9, 0x3b9)] = function ( - _0x1a5840, - _0x4057fd - ) { - return _0x1a5840 !== _0x4057fd; - }), - (_0x3cafb4[_0x4cd3ea("r4tL", 0x420, 0x45f, 0x451, 0x47c)] = function ( - _0xf454da, - _0x197c5c - ) { - return _0xf454da + _0x197c5c; - }), - (_0x3cafb4[_0x13871b("t87A", 0x382, 0x362, 0x3cb, 0x433)] = function ( - _0x2567dd, - _0xe69ecc - ) { - return _0x2567dd / _0xe69ecc; - }), - (_0x3cafb4[_0x13871b("J%C4", 0x425, 0x47b, 0x448, 0x49e)] = - _0x1ec079("Qz2V", 0x491, 0x414, 0x44f, 0x416) + "h"), - (_0x3cafb4[_0x4cd3ea("4ybm", 0x35c, 0x403, 0x3bd, 0x351)] = function ( - _0x42350f, - _0x3d411d - ) { - return _0x42350f === _0x3d411d; - }), - (_0x3cafb4[_0x4cd3ea("jl*9", 0x410, 0x3cd, 0x409, 0x44a)] = function ( - _0x4070f6, - _0xb7fad6 - ) { - return _0x4070f6 % _0xb7fad6; - }), - (_0x3cafb4[_0x4cd3ea("x$0U", 0x4b1, 0x483, 0x47f, 0x460)] = _0x48e9a0( - "LY&k", - 0x412, - 0x424, - 0x3d0, - 0x40b - )), - (_0x3cafb4[_0x112d5("7bIF", 0x46b, 0x45d, 0x40b, 0x3b9)] = _0x112d5( - "7YJj", - 0x440, - 0x43d, - 0x47d, - 0x46c - )), - (_0x3cafb4[_0x1ec079("MC%4", 0x36f, 0x381, 0x3a0, 0x372)] = - _0x1ec079("sqss", 0x42f, 0x483, 0x46b, 0x433) + "n"), - (_0x3cafb4[_0x13871b("f3*l", 0x46e, 0x481, 0x46e, 0x483)] = - _0x48e9a0("Ofoy", 0x4a9, 0x4cd, 0x463, 0x492) + - _0x1ec079("r4tL", 0x4a4, 0x413, 0x440, 0x470) + - "t"), - (_0x3cafb4[_0x48e9a0("^wzf", 0x3e4, 0x376, 0x3a4, 0x3ac)] = function ( - _0x59acbc, - _0x2c4416 - ) { - return _0x59acbc(_0x2c4416); - }), - (_0x3cafb4[_0x4cd3ea("7YJj", 0x439, 0x420, 0x411, 0x473)] = function ( - _0x39ce44, - _0x5a0797 - ) { - return _0x39ce44(_0x5a0797); - }); - var _0x2d64a5 = _0x3cafb4; - function _0x2a3f13(_0xafdf65) { - var _0x4d6293 = function ( - _0x4a72c0, - _0x3d65f9, - _0x41b94d, - _0x4efc04, - _0x3c85a4 - ) { - return _0x112d5( - _0x3d65f9, - _0x3d65f9 - 0x98, - _0x41b94d - 0xd0, - _0x41b94d - 0x2e, - _0x3c85a4 - 0x153 - ); - }, - _0x53ca39 = function ( - _0x51f2df, - _0x4bf079, - _0x317d6a, - _0x5d26bc, - _0x186d7b - ) { - return _0x48e9a0( - _0x4bf079, - _0x4bf079 - 0x3f, - _0x317d6a - 0x1c1, - _0x317d6a - 0x2e, - _0x186d7b - 0xec - ); - }, - _0x1f10fd = function ( - _0x57a8ee, - _0x31fb70, - _0x2aed0a, - _0x2a2b68, - _0x26b8ca - ) { - return _0x4cd3ea( - _0x31fb70, - _0x31fb70 - 0x1ba, - _0x2aed0a - 0x37, - _0x2aed0a - 0x2e, - _0x26b8ca - 0xcb - ); - }, - _0x311adb = function ( - _0x4bbbbf, - _0x30be08, - _0x140c72, - _0x1d5eb7, - _0x2ecd2b - ) { - return _0x48e9a0( - _0x30be08, - _0x30be08 - 0x16d, - _0x140c72 - 0x104, - _0x140c72 - 0x2e, - _0x2ecd2b - 0x6 - ); - }, - _0x5b1fae = function ( - _0x15c83e, - _0x56bb07, - _0x3d053c, - _0x5e37a6, - _0x3941c1 - ) { - return _0x13871b( - _0x56bb07, - _0x56bb07 - 0xeb, - _0x3d053c - 0x25, - _0x3d053c - 0x2e, - _0x3941c1 - 0x1b4 - ); - }; - if ( - _0x2d64a5[_0x4d6293(0x4d2, "x)hU", 0x466, 0x4a1, 0x4ac)]( - typeof _0xafdf65, - _0x2d64a5[_0x53ca39(0x466, "cHrG", 0x3f8, 0x401, 0x434)] - ) - ) - return function (_0x182d37) {} - [ - _0x1f10fd(0x4ac, "]H3U", 0x493, 0x43a, 0x486) + - _0x53ca39(0x432, "&$jP", 0x41d, 0x43b, 0x45d) + - "r" - ](_0x2d64a5[_0x4d6293(0x3d2, "[z)d", 0x412, 0x3b0, 0x435)]) - [_0x4d6293(0x41a, "MC%4", 0x3f2, 0x41a, 0x396)]( - _0x2d64a5[_0x53ca39(0x4d9, "qsz%", 0x49a, 0x501, 0x431)] - ); - else - _0x2d64a5[_0x53ca39(0x49d, "[z)d", 0x444, 0x3e8, 0x46e)]( - _0x2d64a5[_0x5b1fae(0x4ad, "VHjx", 0x464, 0x4ae, 0x481)]( - "", - _0x2d64a5[_0x53ca39(0x3f6, "sqss", 0x3e8, 0x390, 0x410)]( - _0xafdf65, - _0xafdf65 - ) - )[_0x2d64a5[_0x4d6293(0x495, "4uZ$", 0x432, 0x448, 0x42d)]], - 0x65 * 0x55 + -0x2201 + 0x79 - ) || - _0x2d64a5[_0x5b1fae(0x47b, "MC%4", 0x41a, 0x430, 0x3e3)]( - _0x2d64a5[_0x53ca39(0x37c, "V0tF", 0x3df, 0x3f4, 0x41f)]( - _0xafdf65, - -0x23 * 0xe1 + 0x6bb + 0x181c - ), - 0x751 * 0x1 + -0x179e + 0xd * 0x141 - ) - ? function () { - return !![]; - } - [ - _0x53ca39(0x3fa, "@m@0", 0x40f, 0x44f, 0x478) + - _0x53ca39(0x4a3, "4ybm", 0x46f, 0x477, 0x428) + - "r" - ]( - _0x2d64a5[_0x4d6293(0x3bc, "x)hU", 0x3f6, 0x462, 0x454)]( - _0x2d64a5[_0x311adb(0x3d2, "r4tL", 0x3fb, 0x3f3, 0x416)], - _0x2d64a5[_0x311adb(0x40c, "]!eD", 0x419, 0x459, 0x3fb)] - ) - ) - [_0x5b1fae(0x4db, "V0tF", 0x4ac, 0x515, 0x447)]( - _0x2d64a5[_0x5b1fae(0x4d1, "6R##", 0x4a3, 0x4f8, 0x4ca)] - ) - : function () { - return ![]; - } - [ - _0x53ca39(0x452, "4uZ$", 0x42f, 0x3d3, 0x3c7) + - _0x1f10fd(0x3d9, "I*nC", 0x407, 0x3e2, 0x3f7) + - "r" - ]( - _0x2d64a5[_0x5b1fae(0x4f6, "jl*9", 0x49e, 0x448, 0x42e)]( - _0x2d64a5[_0x53ca39(0x3fc, "KNGa", 0x3f1, 0x403, 0x39a)], - _0x2d64a5[_0x311adb(0x40c, "Syoo", 0x426, 0x489, 0x427)] - ) - ) - [_0x4d6293(0x3e7, "MC%4", 0x3f2, 0x426, 0x3e8)]( - _0x2d64a5[_0x5b1fae(0x45f, "hv[H", 0x401, 0x3fd, 0x449)] - ); - _0x2d64a5[_0x53ca39(0x436, "jl*9", 0x49f, 0x45e, 0x498)]( - _0x2a3f13, - ++_0xafdf65 - ); - } - try { - if (_0x332b37) return _0x2a3f13; - else - _0x2d64a5[_0x1ec079("6R##", 0x415, 0x3df, 0x3c9, 0x390)]( - _0x2a3f13, - -0x9 * 0x209 + -0xfa8 + 0x21f9 - ); - } catch (_0x32bb5c) {} -} diff --git a/samples/preemptive.com.js b/samples/preemptive.com.js deleted file mode 100644 index c616a61..0000000 --- a/samples/preemptive.com.js +++ /dev/null @@ -1,16 +0,0 @@ -// Job ID: kvfcj22weki4 -let nYuib; -!(function () { - const s2jN = Array.prototype.slice.call(arguments); - return eval( - "(function QCni(zSu){const bqx=jCeb(zSu,zU1(QCni.toString()));try{let bs4=eval(bqx);return bs4.apply(null,s2jN);}catch(DZ6){var XmZ=(0x21786%3);while(XmZ<(80176-0o234266))switch(XmZ){case (0o1000131%0x10013):XmZ=(0o204610-67854);{console.log('Error: the code has been tampered!');return}break;case (0x75bcd15-0O726746425):XmZ=DZ6 instanceof SyntaxError?(0o400065%65556):(0x40236%0o200157);break;}throw DZ6;}function zU1(ThU){let vPW=1842049150;var PcP=(0x40137%0o200101);{let rKR;while(PcP<(0o227720-77640)){switch(PcP){case (0o1000467%65601):PcP=(0o400342%0x10048);rKR=(0x21786%3);break;case (0o600502%65616):PcP=rKR>>(0O73567354%6)))^1195825704;}break;case (72559687&0O312111266):PcP=(0o213154-0x1161A);rKR++;break;}}}let rMob=\"\";var Tjrb=(0o600475%65611);{let nHjb;while(Tjrb<(0x20171%0o200152)){switch(Tjrb){case (0O3153050563-0x19AC516B):Tjrb=(0o203150-0x10616);nHjb++;break;case (0x10BF4-0o205701):Tjrb=(0O3153050563-0x19AC516B);{const Pemb=vPW%(0o400131%0x1001B);vPW=Math.floor(vPW/(131173%0o200041));rMob+=Pemb>=(0o203030-0x105FE)?String.fromCharCode((0o212120-70671)+(Pemb-(0x3006E%0o200034))):String.fromCharCode((131261%0o200056)+Pemb);}break;case (0o214450-0x118CC):Tjrb=(0o400336%0x10046);nHjb=(0x21786%3);break;case (72916-0o216202):Tjrb=nHjb<(0O3153050563-0x19AC516B)?(0o206762-0x10DBF):(0o203042-66949);break;}}}return rMob;}function jCeb(L9gb,fx9){L9gb=decodeURI(L9gb);let H4bb=(0x75bcd15-0O726746425);let Xk2ub=\"\";var zS4ub=(0o210100-0x11020);{let TfXub;while(zS4ub<(0x13ED0-0o237112)){switch(zS4ub){case (0o201356-0x102A3):zS4ub=(0o203650-67398);{Xk2ub+=String.fromCharCode(L9gb.charCodeAt(TfXub)^fx9.charCodeAt(H4bb));H4bb++;var vNZub=(0o210376-0x110A7);while(vNZub<(0x302A4%0o200253))switch(vNZub){case (0o400120%65563):vNZub=(76946-0o225757);{H4bb=(0x21786%3);}break;case (262523%0o200111):vNZub=H4bb>=fx9.length?(68396-0o205422):(0o1001517%65707);break;}}break;case (69876-0o210266):zS4ub=TfXub { + var obfuscator = new Obfuscator(options); - remove$Properties(AST); + return obfuscator.obfuscate(sourceCode); } -/** - * **JsConfuser**: Obfuscates JavaScript. - * @param code - The code to be obfuscated. - * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. - */ -var JsConfuser: IJsConfuser = async function ( - code: string, +export async function obfuscateAST( + ast: babelTypes.File, options: ObfuscateOptions -): Promise { - if (typeof code !== "string") { - throw new TypeError("code must be type string"); - } - validateOptions(options); - - options = await correctOptions(options); - - options.verbose && console.log("* Parsing source code"); - - var tree = await parseJS(code); - - options.verbose && console.log("* Obfuscating..."); - - var obfuscator = new Obfuscator(options as any); - - await obfuscator.apply(tree); - - options.verbose && console.log("* Removing $ properties"); - - remove$Properties(tree); - - options.verbose && console.log("* Generating code"); - - var result = await compileJs(tree, options); - - return result; -} as any; - -export const debugTransformations: IJsConfuserDebugTransformations = - async function ( - code: string, - options: ObfuscateOptions - ): Promise<{ name: string; code: string; ms: number }[]> { - validateOptions(options); - options = await correctOptions(options); - - var frames = []; - - var tree = parseSync(code); - var obfuscator = new Obfuscator(options as any); - - var time = Date.now(); - - obfuscator.on("debug", (name: string, tree: Node) => { - frames.push({ - name: name, - code: compileJsSync(tree, options), - ms: Date.now() - time, - }); - - time = Date.now(); - }); - - await obfuscator.apply(tree, true); - - return frames; - }; - -/** - * This method is used by the obfuscator website to display a progress bar and additional information - * about the obfuscation. - * - * @param code - Source code to obfuscate - * @param options - Options - * @param callback - Progress callback, called after each transformation - * @returns - */ -export const debugObfuscation: IJsConfuserDebugObfuscation = async function ( - code: string, - options: ObfuscateOptions, - callback: (name: string, complete: number, totalTransforms: number) => void, - performance: Performance ) { - const startTime = performance.now(); - - validateOptions(options); - options = await correctOptions(options); - - const beforeParseTime = performance.now(); - - var tree = parseSync(code); + var obfuscator = new Obfuscator(options); - const parseTime = performance.now() - beforeParseTime; - - var obfuscator = new Obfuscator(options as any); - var totalTransforms = obfuscator.array.length; - - var transformationTimes = Object.create(null); - var currentTransformTime = performance.now(); - - obfuscator.on("debug", (name: string, tree: Node, i: number) => { - var nowTime = performance.now(); - transformationTimes[name] = nowTime - currentTransformTime; - currentTransformTime = nowTime; - - callback(name, i, totalTransforms); - }); - - await obfuscator.apply(tree, true); + return obfuscator.obfuscateAST(ast); +} - const beforeCompileTime = performance.now(); +export async function obfuscateWithProfiler( + sourceCode: string, + options: ObfuscateOptions, + profiler: { + callback: ProfilerCallback; + performance: { now(): number }; + } +): Promise { + var obfuscator = new Obfuscator(options); - var output = await compileJs(tree, options); + var ast = obfuscator.parseCode(sourceCode); - const compileTime = performance.now() - beforeCompileTime; + ast = obfuscator.obfuscateAST(ast, profiler.callback); - const endTime = performance.now(); + var code = obfuscator.generateCode(ast); return { - obfuscated: output, - transformationTimes: transformationTimes, - obfuscationTime: endTime - startTime, - parseTime: parseTime, - compileTime: compileTime, - totalTransforms: totalTransforms, - totalPossibleTransforms: obfuscator.totalPossibleTransforms, + code: code, }; -}; - -JsConfuser.obfuscate = obfuscate; -JsConfuser.obfuscateAST = obfuscateAST; -JsConfuser.presets = presets; -JsConfuser.debugTransformations = debugTransformations; -JsConfuser.debugObfuscation = debugObfuscation; -JsConfuser.Obfuscator = Obfuscator; -JsConfuser.Transform = Transform; -JsConfuser.Template = Template; - -if (typeof window !== "undefined") { - window["JsConfuser"] = JsConfuser; -} -if (typeof global !== "undefined") { - global["JsConfuser"] = JsConfuser; } -export default JsConfuser; +const JSConfuser = Object.assign(obfuscate, { + obfuscate, + obfuscateAST, + obfuscateWithProfiler, +}); -export { presets, Obfuscator, Transform, Template }; +export default JSConfuser; diff --git a/src/obfuscationResult.ts b/src/obfuscationResult.ts new file mode 100644 index 0000000..1b3ceac --- /dev/null +++ b/src/obfuscationResult.ts @@ -0,0 +1,11 @@ +export interface ObfuscationResult { + code: string; +} + +export type ProfilerCallback = (log: ProfilerLog) => void; +export interface ProfilerLog { + currentTransform: string; + currentTransformNumber: number; + nextTransform: string; + totalTransforms: number; +} diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 995d19b..5f98423 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -1,165 +1,133 @@ -import { ok } from "assert"; -import { EventEmitter } from "events"; -import { Node } from "./util/gen"; -import traverse from "./traverse"; import { ObfuscateOptions } from "./options"; -import { ProbabilityMap, isProbabilityMapProbable } from "./probability"; - -import Transform from "./transforms/transform"; - -import Preparation from "./transforms/preparation"; -import ObjectExtraction from "./transforms/extraction/objectExtraction"; -import Lock from "./transforms/lock/lock"; -import Dispatcher from "./transforms/dispatcher"; -import DeadCode from "./transforms/deadCode"; -import OpaquePredicates from "./transforms/opaquePredicates"; -import Calculator from "./transforms/calculator"; -import ControlFlowFlattening from "./transforms/controlFlowFlattening/controlFlowFlattening"; -import GlobalConcealing from "./transforms/identifier/globalConcealing"; -import StringSplitting from "./transforms/string/stringSplitting"; -import StringConcealing from "./transforms/string/stringConcealing"; -import StringCompression from "./transforms/string/stringCompression"; -import DuplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval"; -import Shuffle from "./transforms/shuffle"; -import MovedDeclarations from "./transforms/identifier/movedDeclarations"; -import RenameVariables from "./transforms/identifier/renameVariables"; -import RenameLabels from "./transforms/renameLabels"; -import Minify from "./transforms/minify"; -import ES5 from "./transforms/es5/es5"; -import RGF from "./transforms/rgf"; -import Flatten from "./transforms/flatten"; -import Stack from "./transforms/stack"; -import AntiTooling from "./transforms/antiTooling"; -import Finalizer from "./transforms/finalizer"; - -/** - * The parent transformation holding the `state`. - */ -export default class Obfuscator extends EventEmitter { - varCount: number; - transforms: { [name: string]: Transform }; - array: Transform[]; - - state: "transform" | "eval" = "transform"; - generated: Set; - - totalPossibleTransforms: number; - - constructor(public options: ObfuscateOptions) { - super(); - - this.varCount = 0; - this.transforms = Object.create(null); - this.generated = new Set(); - this.totalPossibleTransforms = 0; - - const test = (map: ProbabilityMap, ...transformers: any[]) => { - this.totalPossibleTransforms += transformers.length; - - if (isProbabilityMapProbable(map)) { - // options.verbose && console.log("+ Added " + transformer.name); - - transformers.forEach((Transformer) => this.push(new Transformer(this))); - } else { - // options.verbose && console.log("- Skipped adding " + transformer.name); - } +import * as babel from "@babel/core"; +import generate from "@babel/generator"; +import { PluginInstance } from "./transforms/plugin"; +import { ok } from "assert"; +import { applyDefaultsToOptions, validateOptions } from "./validateOptions"; + +import preparation from "./transforms/preparation"; +import renameVariables from "./transforms/identifier/renameVariables"; +import variableMasking from "./transforms/variableMasking"; +import dispatcher from "./transforms/dispatcher"; +import duplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval"; +import objectExtraction from "./transforms/extraction/objectExtraction"; +import globalConcealing from "./transforms/identifier/globalConcealing"; +import stringCompression from "./transforms/string/stringCompression"; +import deadCode from "./transforms/deadCode"; +import stringSplitting from "./transforms/string/stringSplitting"; +import shuffle from "./transforms/shuffle"; +import finalizer from "./transforms/finalizer"; +import { + ObfuscationResult, + ProfilerCallback, + ProfilerLog, +} from "./obfuscationResult"; +import { isProbabilityMapProbable } from "./probability"; + +export default class Obfuscator { + plugins: babel.PluginObj[] = []; + options: ObfuscateOptions; + + totalPossibleTransforms: number = 0; + + public constructor(userOptions: ObfuscateOptions) { + validateOptions(userOptions); + this.options = applyDefaultsToOptions({ ...userOptions }); + + const allPlugins = []; + + const push = (probabilityMap, ...pluginFns) => { + this.totalPossibleTransforms += pluginFns.length; + if (!isProbabilityMapProbable(probabilityMap)) return; + + allPlugins.push(...pluginFns); }; - // Optimization: Only add needed transformers. If a probability always return false, no need in running that extra code. - test(true, Preparation); - test(true, RenameLabels); - - test(options.objectExtraction, ObjectExtraction); - test(options.flatten, Flatten); - test(options.rgf, RGF); - test(options.dispatcher, Dispatcher); - test(options.deadCode, DeadCode); - test(options.calculator, Calculator); - test(options.controlFlowFlattening, ControlFlowFlattening); - test(options.globalConcealing, GlobalConcealing); - test(options.opaquePredicates, OpaquePredicates); - test(options.stringSplitting, StringSplitting); - test(options.stringConcealing, StringConcealing); - test(options.stringCompression, StringCompression); - test(options.stack, Stack); - test(options.duplicateLiteralsRemoval, DuplicateLiteralsRemoval); - test(options.shuffle, Shuffle); - test(options.movedDeclarations, MovedDeclarations); - test(options.minify, Minify); - test(options.renameVariables, RenameVariables); - test(options.es5, ES5); - - test(true, AntiTooling); - test(true, Finalizer); // String Encoding, Hexadecimal Numbers, BigInt support is included - - if ( - options.lock && - Object.keys(options.lock).filter((x) => - x == "domainLock" - ? options.lock.domainLock && options.lock.domainLock.length - : options.lock[x] - ).length - ) { - test(true, Lock); - } + push(true, preparation); + push(this.options.deadCode, deadCode); + + push(this.options.dispatcher, dispatcher); + push(this.options.duplicateLiteralsRemoval, duplicateLiteralsRemoval); + push(this.options.objectExtraction, objectExtraction); + push(this.options.globalConcealing, globalConcealing); + push(this.options.variableMasking, variableMasking); + push(this.options.renameVariables, renameVariables); + push(this.options.stringCompression, stringCompression); + push(this.options.stringSplitting, stringSplitting); + push(this.options.shuffle, shuffle); - // Make array - this.array = Object.values(this.transforms); + push(true, finalizer); - // Sort transformations based on their priority - this.array.sort((a, b) => a.priority - b.priority); + allPlugins.map((pluginFunction) => { + var pluginInstance: PluginInstance; + var plugin = pluginFunction({ + Plugin: (name) => { + return (pluginInstance = new PluginInstance({ name }, this)); + }, + }); + + ok(pluginInstance, "Plugin instance not created."); + + this.plugins.push(plugin); + }); } - push(transform: Transform) { - if (transform.className) { - ok( - !this.transforms[transform.className], - "Already have " + transform.className - ); + obfuscateAST( + ast: babel.types.File, + profiler?: ProfilerCallback + ): babel.types.File { + for (var i = 0; i < this.plugins.length; i++) { + const plugin = this.plugins[i]; + babel.traverse(ast, plugin.visitor as babel.Visitor); + + if (profiler) { + profiler({ + currentTransform: plugin.name, + currentTransformNumber: i, + nextTransform: this.plugins[i + 1]?.name, + totalTransforms: this.plugins.length, + }); + } } - this.transforms[transform.className] = transform; - } - resetState() { - this.varCount = 0; - this.generated = new Set(); - this.state = "transform"; + return ast; } - async apply(tree: Node, debugMode = false) { - ok(tree.type == "Program", "The root node must be type 'Program'"); - ok(Array.isArray(tree.body), "The root's body property must be an array"); - ok(Array.isArray(this.array)); + async obfuscate(sourceCode: string): Promise { + // Parse the source code into an AST + let ast = this.parseCode(sourceCode); - this.resetState(); + this.obfuscateAST(ast); - var completed = 0; - for (var transform of this.array) { - await transform.apply(tree); - completed++; + // Generate the transformed code from the modified AST with comments removed and compacted output + const code = this.generateCode(ast); - if (debugMode) { - this.emit("debug", transform.className, tree, completed); - } + if (code) { + return { + code: code, + }; + } else { + throw new Error("Failed to obfuscate the code."); } + } - if (this.options.verbose) { - console.log("-> Check for Eval Callbacks"); - } + generateCode(ast: babel.types.File): string { + const { code } = generate(ast, { + comments: false, // Remove comments + compact: false, // Compact the output + }); - this.state = "eval"; + return code; + } - // Find eval callbacks - traverse(tree, (o, p) => { - if (o.$eval) { - return () => { - o.$eval(o, p); - }; - } + parseCode(sourceCode: string): babel.types.File { + // Parse the source code into an AST + let ast = babel.parseSync(sourceCode, { + sourceType: "unambiguous", + babelrc: false, + configFile: false, }); - if (this.options.verbose) { - console.log("<- Done"); - } + return ast; } } diff --git a/src/options.ts b/src/options.ts index a184a35..f81bede 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,5 +1,3 @@ -import { ok } from "assert"; -import presets from "./presets"; import { ProbabilityMap } from "./probability"; export interface ObfuscateOptions { @@ -279,7 +277,7 @@ export interface ObfuscateOptions { rgf?: ProbabilityMap; /** - * ### `stack` + * ### `variableMasking` * * Local variables are consolidated into a rotating array. * @@ -302,7 +300,7 @@ export interface ObfuscateOptions { * }; * ``` */ - stack?: ProbabilityMap; + variableMasking?: ProbabilityMap; /** * ### `objectExtraction` @@ -611,288 +609,3 @@ export interface ObfuscateOptions { */ preserveFunctionLength?: boolean; } - -const validProperties = new Set([ - "preset", - "target", - "indent", - "compact", - "hexadecimalNumbers", - "minify", - "es5", - "renameVariables", - "renameGlobals", - "identifierGenerator", - "controlFlowFlattening", - "globalConcealing", - "stringCompression", - "stringConcealing", - "stringEncoding", - "stringSplitting", - "duplicateLiteralsRemoval", - "dispatcher", - "rgf", - "objectExtraction", - "flatten", - "deadCode", - "calculator", - "lock", - "movedDeclarations", - "opaquePredicates", - "shuffle", - "stack", - "verbose", - "globalVariables", - "debugComments", - "preserveFunctionLength", -]); - -const validLockProperties = new Set([ - "selfDefending", - "antiDebug", - "context", - "tamperProtection", - "startDate", - "endDate", - "domainLock", - "osLock", - "browserLock", - "integrity", - "countermeasures", -]); - -const validOses = new Set(["windows", "linux", "osx", "ios", "android"]); -const validBrowsers = new Set([ - "firefox", - "chrome", - "iexplorer", - "edge", - "safari", - "opera", -]); - -export function validateOptions(options: ObfuscateOptions) { - if (!options || Object.keys(options).length <= 1) { - /** - * Give a welcoming introduction to those who skipped the documentation. - */ - var line = `You provided zero obfuscation options. By default everything is disabled.\nYou can use a preset with:\n\n> {target: '${ - options.target || "node" - }', preset: 'high' | 'medium' | 'low'}.\n\n\nView all settings here:\nhttps://github.com/MichaelXF/js-confuser#options`; - throw new Error( - `\n\n` + - line - .split("\n") - .map((x) => `\t${x}`) - .join("\n") + - `\n\n` - ); - } - - ok(options, "options cannot be null"); - ok( - options.target, - "Missing options.target option (required, must one the following: 'browser' or 'node')" - ); - - ok( - ["browser", "node"].includes(options.target), - `'${options.target}' is not a valid target mode` - ); - - Object.keys(options).forEach((key) => { - if (!validProperties.has(key)) { - throw new TypeError("Invalid option: '" + key + "'"); - } - }); - - if ( - options.target === "node" && - options.lock && - options.lock.browserLock && - options.lock.browserLock.length - ) { - throw new TypeError('browserLock can only be used when target="browser"'); - } - - if (options.lock) { - ok(typeof options.lock === "object", "options.lock must be an object"); - Object.keys(options.lock).forEach((key) => { - if (!validLockProperties.has(key)) { - throw new TypeError("Invalid lock option: '" + key + "'"); - } - }); - - // Validate browser-lock option - if ( - options.lock.browserLock && - typeof options.lock.browserLock !== "undefined" - ) { - ok( - Array.isArray(options.lock.browserLock), - "browserLock must be an array" - ); - ok( - !options.lock.browserLock.find( - (browserName) => !validBrowsers.has(browserName) - ), - 'Invalid browser name. Allowed: "firefox", "chrome", "iexplorer", "edge", "safari", "opera"' - ); - } - // Validate os-lock option - if (options.lock.osLock && typeof options.lock.osLock !== "undefined") { - ok(Array.isArray(options.lock.osLock), "osLock must be an array"); - ok( - !options.lock.osLock.find((osName) => !validOses.has(osName)), - 'Invalid OS name. Allowed: "windows", "linux", "osx", "ios", "android"' - ); - } - // Validate domain-lock option - if ( - options.lock.domainLock && - typeof options.lock.domainLock !== "undefined" - ) { - ok(Array.isArray(options.lock.domainLock), "domainLock must be an array"); - } - - // Validate context option - if (options.lock.context && typeof options.lock.context !== "undefined") { - ok(Array.isArray(options.lock.context), "context must be an array"); - } - - // Validate start-date option - if ( - typeof options.lock.startDate !== "undefined" && - options.lock.startDate - ) { - ok( - typeof options.lock.startDate === "number" || - options.lock.startDate instanceof Date, - "startDate must be Date object or number" - ); - } - - // Validate end-date option - if (typeof options.lock.endDate !== "undefined" && options.lock.endDate) { - ok( - typeof options.lock.endDate === "number" || - options.lock.endDate instanceof Date, - "endDate must be Date object or number" - ); - } - } - - if (options.preset) { - if (!presets[options.preset]) { - throw new TypeError("Unknown preset of '" + options.preset + "'"); - } - } -} - -/** - * Corrects the user's options. Sets the default values and validates the configuration. - * @param options - * @returns - */ -export async function correctOptions( - options: ObfuscateOptions -): Promise { - if (options.preset) { - // Clone and allow overriding - options = Object.assign({}, presets[options.preset], options); - } - - if (!options.hasOwnProperty("debugComments")) { - options.debugComments = false; // debugComments is off by default - } - - if (!options.hasOwnProperty("compact")) { - options.compact = true; // Compact is on by default - } - if (!options.hasOwnProperty("renameGlobals")) { - options.renameGlobals = true; // RenameGlobals is on by default - } - if (!options.hasOwnProperty("preserveFunctionLength")) { - options.preserveFunctionLength = true; // preserveFunctionLength is on by default - } - - if (options.globalVariables && !(options.globalVariables instanceof Set)) { - options.globalVariables = new Set(Object.keys(options.globalVariables)); - } - - if (options.lock) { - if (options.lock.selfDefending) { - options.compact = true; // self defending forcibly enables this - } - } - - // options.globalVariables outlines generic globals that should be present in the execution context - if (!options.hasOwnProperty("globalVariables")) { - options.globalVariables = new Set([]); - - if (options.target == "browser") { - // browser - [ - "window", - "document", - "postMessage", - "alert", - "confirm", - "location", - "btoa", - "atob", - "unescape", - "encodeURIComponent", - ].forEach((x) => options.globalVariables.add(x)); - } else { - // node - [ - "global", - "Buffer", - "require", - "process", - "exports", - "module", - "__dirname", - "__filename", - ].forEach((x) => options.globalVariables.add(x)); - } - - [ - "globalThis", - "console", - "parseInt", - "parseFloat", - "Math", - "JSON", - "Promise", - "String", - "Boolean", - "Function", - "Object", - "Array", - "Proxy", - "Error", - "TypeError", - "ReferenceError", - "RangeError", - "EvalError", - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "queueMicrotask", - "isNaN", - "isFinite", - "Set", - "Map", - "WeakSet", - "WeakMap", - "Symbol", - ].forEach((x) => options.globalVariables.add(x)); - } - - return options; -} diff --git a/src/presets.ts b/src/presets.ts index 2fb3081..d512c13 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -44,7 +44,7 @@ const highPreset: ObfuscateOptions = { renameVariables: true, renameGlobals: true, shuffle: { hash: 0.5, true: 0.5 }, - stack: true, + variableMasking: true, stringConcealing: true, stringCompression: true, stringEncoding: true, @@ -78,7 +78,7 @@ const mediumPreset: ObfuscateOptions = { renameVariables: true, renameGlobals: true, shuffle: true, - stack: 0.5, + variableMasking: 0.5, stringConcealing: true, stringSplitting: 0.25, }; diff --git a/src/probability.ts b/src/probability.ts index 6463a27..d3a8cbf 100644 --- a/src/probability.ts +++ b/src/probability.ts @@ -1,5 +1,5 @@ import { ok } from "assert"; -import { createObject } from "./util/object"; +import { createObject } from "./utils/object-utils"; type Stringed = (V extends string ? V : never) | "true" | "false"; @@ -29,7 +29,7 @@ export type ProbabilityMap = * @param runner Custom function to determine return value * @param customFnArgs Args given to user-implemented function, such as a variable name. */ -export function ComputeProbabilityMap( +export function computeProbabilityMap( map: ProbabilityMap, runner: (mode?: T) => any = (x?: T) => x, ...customFnArgs: any[] diff --git a/src/templates/deadCodeTemplates.ts b/src/templates/deadCodeTemplates.ts new file mode 100644 index 0000000..0c3115e --- /dev/null +++ b/src/templates/deadCodeTemplates.ts @@ -0,0 +1,549 @@ +import Template from "./template"; + +export const deadCodeTemplates = [ + new Template(` + function curCSS( elem, name, computed ) { + var ret; + + computed = computed || getStyles( elem ); + + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = redacted.style( elem, name ); + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11+ + // IE returns zIndex value as an integer. + ret + "" : + ret; + }`), + new Template(` + function Example() { + var state = redacted.useState(false); + return x( + ErrorBoundary, + null, + x( + DisplayName, + null, + ) + ); + }`), + + new Template(` + const path = require('path'); +const { version } = require('../../package'); +const { version: dashboardPluginVersion } = require('@redacted/enterprise-plugin/package'); +const { version: componentsVersion } = require('@redacted/components/package'); +const { sdkVersion } = require('@redacted/enterprise-plugin'); +const isStandaloneExecutable = require('../utils/isStandaloneExecutable'); +const resolveLocalRedactedPath = require('./resolve-local-redacted-path'); + +const redactedPath = path.resolve(__dirname, '../redacted.js');`), + + new Template(` +module.exports = async (resolveLocalRedactedPath = ()=>{throw new Error("No redacted path provided")}) => { + const cliParams = new Set(process.argv.slice(2)); + if (!cliParams.has('--version')) { + if (cliParams.size !== 1) return false; + if (!cliParams.has('-v')) return false; + } + + const installationModePostfix = await (async (isStandaloneExecutable, redactedPath) => { + if (isStandaloneExecutable) return ' (standalone)'; + if (redactedPath === (await resolveLocalRedactedPath())) return ' (local)'; + return ''; + })(); + + return true; +};`), + new Template(` +function setCookie(cname, cvalue, exdays) { + var d = new Date(); + d.setTime(d.getTime() + (exdays*24*60*60*1000)); + var expires = "expires="+ d.toUTCString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; +}`), + + new Template(`function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for(var i = 0; i 1 + ) { + return Infinity; + } + + const currentHeight = Math.max(leftTreeHeight, rightTreeHeight) + 1; + return currentHeight; + } + + window["__GLOBAL__HELPERS__"] = { + buildCharacterMap, + isAnagrams, + isBalanced, + getHeightBalanced, + }; + `), + new Template(` + function ListNode(){} + var addTwoNumbers = function(l1, l2) { + var carry = 0; + var sum = 0; + var head = new ListNode(0); + var now = head; + var a = l1; + var b = l2; + while (a !== null || b !== null) { + sum = (a ? a.val : 0) + (b ? b.val : 0) + carry; + carry = Math.floor(sum / 10); + now.next = new ListNode(sum % 10); + now = now.next; + a = a ? a.next : null; + b = b ? b.next : null; + } + if (carry) now.next = new ListNode(carry); + return head.next; + }; + + console.log(addTwoNumbers) + `), + new Template(` + var threeSum = function(nums) { + var len = nums.length; + var res = []; + var l = 0; + var r = 0; + nums.sort((a, b) => (a - b)); + for (var i = 0; i < len; i++) { + if (i > 0 && nums[i] === nums[i - 1]) continue; + l = i + 1; + r = len - 1; + while (l < r) { + if (nums[i] + nums[l] + nums[r] < 0) { + l++; + } else if (nums[i] + nums[l] + nums[r] > 0) { + r--; + } else { + res.push([nums[i], nums[l], nums[r]]); + while (l < r && nums[l] === nums[l + 1]) l++; + while (l < r && nums[r] === nums[r - 1]) r--; + l++; + r--; + } + } + } + return res; + }; + console.log(threeSum) + `), + new Template(` + var combinationSum2 = function(candidates, target) { + var res = []; + var len = candidates.length; + candidates.sort((a, b) => (a - b)); + dfs(res, [], 0, len, candidates, target); + return res; + }; + + var dfs = function (res, stack, index, len, candidates, target) { + var tmp = null; + if (target < 0) return; + if (target === 0) return res.push(stack); + for (var i = index; i < len; i++) { + if (candidates[i] > target) break; + if (i > index && candidates[i] === candidates[i - 1]) continue; + tmp = Array.from(stack); + tmp.push(candidates[i]); + dfs(res, tmp, i + 1, len, candidates, target - candidates[i]); + } + }; + + console.log(combinationSum2); + `), + new Template(` + var isScramble = function(s1, s2) { + return helper({}, s1, s2); + }; + + var helper = function (dp, s1, s2) { + var map = {}; + + if (dp[s1 + s2] !== undefined) return dp[s1 + s2]; + if (s1 === s2) return true; + + for (var j = 0; j < s1.length; j++) { + if (map[s1[j]] === undefined) map[s1[j]] = 0; + if (map[s2[j]] === undefined) map[s2[j]] = 0; + map[s1[j]]++; + map[s2[j]]--; + } + + for (var key in map) { + if (map[key] !== 0) { + dp[s1 + s2] = false; + return false; + } + } + + for (var i = 1; i < s1.length; i++) { + if ((helper(dp, s1.substr(0, i), s2.substr(0, i)) + && helper(dp, s1.substr(i), s2.substr(i))) || + (helper(dp, s1.substr(0, i), s2.substr(s2.length - i)) + && helper(dp, s1.substr(i), s2.substr(0, s2.length - i)))) { + dp[s1 + s2] = true; + return true; + } + } + + dp[s1 + s2] = false; + return false; + }; + + console.log(isScramble); + `), + new Template(` + var candy = function(ratings) { + var len = ratings.length; + var res = []; + var sum = 0; + for (var i = 0; i < len; i++) { + res.push((i !== 0 && ratings[i] > ratings[i - 1]) ? (res[i - 1] + 1) : 1); + } + for (var j = len - 1; j >= 0; j--) { + if (j !== len - 1 && ratings[j] > ratings[j + 1]) res[j] = Math.max(res[j], res[j + 1] + 1); + sum += res[j]; + } + return sum; + }; + + console.log(candy) + `), + new Template(` + var maxPoints = function(points) { + var max = 0; + var map = {}; + var localMax = 0; + var samePoint = 0; + var k = 0; + var len = points.length; + for (var i = 0; i < len; i++) { + map = {}; + localMax = 0; + samePoint = 1; + for (var j = i + 1; j < len; j++) { + if (points[i].x === points[j].x && points[i].y === points[j].y) { + samePoint++; + continue; + } + if (points[i].y === points[j].y) k = Number.MAX_SAFE_INTEGER; + else k = (points[i].x - points[j].x) / (points[i].y - points[j].y); + if (!map[k]) map[k] = 0; + map[k]++; + localMax = Math.max(localMax, map[k]); + } + localMax += samePoint; + max = Math.max(max, localMax); + } + return max; + }; + + console.log(maxPoints) + `), + new Template(` + var maximumGap = function(nums) { + var len = nums.length; + if (len < 2) return 0; + + var max = Math.max(...nums); + var min = Math.min(...nums); + if (max === min) return 0; + + var minBuckets = Array(len - 1).fill(Number.MAX_SAFE_INTEGER); + var maxBuckets = Array(len - 1).fill(Number.MIN_SAFE_INTEGER); + var gap = Math.ceil((max - min) / (len - 1)); + var index = 0; + for (var i = 0; i < len; i++) { + if (nums[i] === min || nums[i] === max) continue; + index = Math.floor((nums[i] - min) / gap); + minBuckets[index] = Math.min(minBuckets[index], nums[i]); + maxBuckets[index] = Math.max(maxBuckets[index], nums[i]); + } + + var maxGap = Number.MIN_SAFE_INTEGER; + var preVal = min; + for (var j = 0; j < len - 1; j++) { + if (minBuckets[j] === Number.MAX_SAFE_INTEGER && maxBuckets[j] === Number.MIN_SAFE_INTEGER) continue; + maxGap = Math.max(maxGap, minBuckets[j] - preVal); + preVal = maxBuckets[j]; + } + maxGap = Math.max(maxGap, max - preVal); + + return maxGap; + }; + + console.log(maximumGap); + `), + new Template(` + var LRUCache = function(capacity) { + this.capacity = capacity; + this.length = 0; + this.map = {}; + this.head = null; + this.tail = null; + }; + + LRUCache.prototype.get = function(key) { + var node = this.map[key]; + if (node) { + this.remove(node); + this.insert(node.key, node.val); + return node.val; + } else { + return -1; + } + }; + + LRUCache.prototype.put = function(key, value) { + if (this.map[key]) { + this.remove(this.map[key]); + this.insert(key, value); + } else { + if (this.length === this.capacity) { + this.remove(this.head); + this.insert(key, value); + } else { + this.insert(key, value); + this.length++; + } + } + }; + + /** + * Your LRUCache object will be instantiated and called as such: + * var obj = Object.create(LRUCache).createNew(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ + + LRUCache.prototype.remove = function (node) { + var prev = node.prev; + var next = node.next; + if (next) next.prev = prev; + if (prev) prev.next = next; + if (this.head === node) this.head = next; + if (this.tail === node) this.tail = prev; + delete this.map[node.key]; + }; + + LRUCache.prototype.insert = function (key, val) { + var node = new List(key, val); + if (!this.tail) { + this.tail = node; + this.head = node; + } else { + this.tail.next = node; + node.prev = this.tail; + this.tail = node; + } + this.map[key] = node; + }; + + console.log(LRUCache); + `), + new Template(` + var isInterleave = function(s1, s2, s3) { + var dp = {}; + if (s3.length !== s1.length + s2.length) return false; + return helper(s1, s2, s3, 0, 0, 0, dp); + }; + + var helper = function (s1, s2, s3, i, j, k, dp) { + var res = false; + + if (k >= s3.length) return true; + if (dp['' + i + j + k] !== undefined) return dp['' + i + j + k]; + + if (s3[k] === s1[i] && s3[k] === s2[j]) { + res = helper(s1, s2, s3, i + 1, j, k + 1, dp) || helper(s1, s2, s3, i, j + 1, k + 1, dp); + } else if (s3[k] === s1[i]) { + res = helper(s1, s2, s3, i + 1, j, k + 1, dp); + } else if (s3[k] === s2[j]) { + res = helper(s1, s2, s3, i, j + 1, k + 1, dp); + } + + dp['' + i + j + k] = res; + + return res; + }; + + console.log(isInterleave); + `), + new Template(` + var solveNQueens = function(n) { + var res = []; + if (n === 1 || n >= 4) dfs(res, [], n, 0); + return res; + }; + + var dfs = function (res, points, n, index) { + for (var i = index; i < n; i++) { + if (points.length !== i) return; + for (var j = 0; j < n; j++) { + if (isValid(points, [i, j])) { + points.push([i, j]); + dfs(res, points, n, i + 1); + if (points.length === n) res.push(buildRes(points)); + points.pop(); + } + } + } + }; + + var buildRes = function (points) { + var res = []; + var n = points.length; + for (var i = 0; i < n; i++) { + res[i] = ''; + for (var j = 0; j < n; j++) { + res[i] += (points[i][1] === j ? 'Q' : '.'); + } + } + return res; + }; + + var isValid = function (oldPoints, newPoint) { + var len = oldPoints.length; + for (var i = 0; i < len; i++) { + if (oldPoints[i][0] === newPoint[0] || oldPoints[i][1] === newPoint[1]) return false; + if (Math.abs((oldPoints[i][0] - newPoint[0]) / (oldPoints[i][1] - newPoint[1])) === 1) return false; + } + return true; + }; + + console.log(solveNQueens); + `), +]; diff --git a/src/templates/template.ts b/src/templates/template.ts index 6b8fd01..039451d 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -1,82 +1,23 @@ -import { Node } from "../util/gen"; -import { parseSnippet, parseSync } from "../parser"; +import * as babelTypes from "@babel/types"; +import { parse } from "@babel/parser"; +import traverse from "@babel/traverse"; +import { NodePath } from "@babel/traverse"; import { ok } from "assert"; -import { choice } from "../util/random"; -import { placeholderVariablePrefix } from "../constants"; -import traverse from "../traverse"; export interface TemplateVariables { [varName: string]: | string - | (() => Node | Node[] | Template) - | Node - | Node[] + | number + | (() => babelTypes.Node | babelTypes.Node[] | Template) + | babelTypes.Node + | babelTypes.Node[] | Template; } -/** - * Templates provides an easy way to parse code snippets into AST subtrees. - * - * These AST subtrees can added to the obfuscated code, tailored with variable names. - * - * 1. Basic string interpolation - * - * ```js - * var Base64Template = new Template(` - * function {name}(str){ - * return btoa(str) - * } - * `); - * - * var functionDeclaration = Base64Template.single({ name: "atob" }); - * ``` - * - * 2. AST subtree insertion - * - * ```js - * var Base64Template = new Template(` - * function {name}(str){ - * {getWindow} - * - * return {getWindowName}btoa(str) - * }`) - * - * var functionDeclaration = Base64Template.single({ - * name: "atob", - * getWindowName: "newWindow", - * getWindow: () => { - * return acorn.parse("var newWindow = {}").body[0]; - * } - * }); - * ``` - * - * Here, the `getWindow` variable is a function that returns an AST subtree. This must be a `Node[]` array or Template. - * Optionally, the function can be replaced with just the `Node[]` array or Template if it's already computed. - * - * 3. Template subtree insertion - * - * ```js - * var NewWindowTemplate = new Template(` - * var newWindow = {}; - * `); - * - * var Base64Template = new Template(` - * function {name}(str){ - * {NewWindowTemplate} - * - * return newWindow.btoa(str) - * }`) - * - * var functionDeclaration = Base64Template.single({ - * name: "atob", - * NewWindowTemplate: NewWindowTemplate - * }); - * ``` - */ export default class Template { - templates: string[]; - defaultVariables: TemplateVariables; - requiredVariables: Set; + private templates: string[]; + private defaultVariables: TemplateVariables; + private requiredVariables: Set; constructor(...templates: string[]) { this.templates = templates; @@ -92,14 +33,16 @@ export default class Template { } private findRequiredVariables() { - var matches = this.templates[0].match(/{[$A-z0-9_]+}/g); + const matches = this.templates[0].match(/{[$A-Za-z0-9_]+}/g); if (matches !== null) { matches.forEach((variable) => { - var name = variable.slice(1, -1); + const name = variable.slice(1, -1); // $ variables are for default variables if (name.startsWith("$")) { - throw new Error("Default variables are no longer supported."); + this.defaultVariables[name] = `td_${ + Object.keys(this.defaultVariables).length + }`; } else { this.requiredVariables.add(name); } @@ -107,124 +50,119 @@ export default class Template { } } - /** - * Interpolates the template with the given variables. - * - * Prepares the template string for AST parsing. - * - * @param variables - */ private interpolateTemplate(variables: TemplateVariables = {}) { - var allVariables = { ...this.defaultVariables, ...variables }; + const allVariables = { ...this.defaultVariables, ...variables }; - // Validate all variables were passed in - for (var requiredVariable of this.requiredVariables) { + for (const requiredVariable of this.requiredVariables) { if (typeof allVariables[requiredVariable] === "undefined") { throw new Error( - this.templates[0] + - " missing variable: " + - requiredVariable + - " from " + - JSON.stringify(allVariables) + `${ + this.templates[0] + } missing variable: ${requiredVariable} from ${JSON.stringify( + allVariables + )}` ); } } - var template = choice(this.templates); - var output = template; + const template = + this.templates[Math.floor(Math.random() * this.templates.length)]; + let output = template; Object.keys(allVariables).forEach((name) => { - var bracketName = "{" + name.replace("$", "\\$") + "}"; + const bracketName = `{${name.replace("$", "\\$")}}`; + let value = allVariables[name] as string; - var value = allVariables[name] + ""; - if (typeof allVariables[name] !== "string") { + if (this.isASTVariable(value)) { value = name; } - var reg = new RegExp(bracketName, "g"); - + const reg = new RegExp(bracketName, "g"); output = output.replace(reg, value); }); return { output, template }; } - /** - * Finds the variables in the AST and replaces them with the given values. - * - * Note: Mutates the AST. - * @param ast - * @param variables - */ - private interpolateAST(ast: Node, variables: TemplateVariables) { - var allVariables = { ...this.defaultVariables, ...variables }; - - var astNames = new Set( + private isASTVariable(variable: any): boolean { + return typeof variable !== "string" && typeof variable !== "number"; + } + + private interpolateAST(ast: babelTypes.Node, variables: TemplateVariables) { + const allVariables = { ...this.defaultVariables, ...variables }; + + const astNames = new Set( Object.keys(allVariables).filter((name) => { - return typeof allVariables[name] !== "string"; + return this.isASTVariable(allVariables[name]); }) ); if (astNames.size === 0) return; - traverse(ast, (o, p) => { - if (o.type === "Identifier" && allVariables[o.name]) { - return () => { - var value = allVariables[o.name]; - ok(typeof value !== "string"); - - var insertNodes = typeof value === "function" ? value() : value; - if (insertNodes instanceof Template) { - insertNodes = insertNodes.compile(allVariables); + traverse(ast, { + Identifier(path: NodePath) { + const name = path.node.name; + if (allVariables[name]) { + let value = allVariables[name]; + if (typeof value === "function") { + value = value(); } - if (!Array.isArray(insertNodes)) { - // Replace with expression + if (value instanceof Template) { + value = value.compile(allVariables); + } - Object.assign(o, insertNodes); + if (!Array.isArray(value)) { + path.replaceWith(value as babelTypes.Node); } else { - // Insert multiple statements/declarations - var expressionStatement: Node = p[0]; - var body: Node[] = p[1] as any; - - ok(expressionStatement.type === "ExpressionStatement"); - ok(Array.isArray(body)); - - var index = body.indexOf(expressionStatement); - - body.splice(index, 1, ...insertNodes); + path.replaceWithMultiple(value as babelTypes.Node[]); } - }; - } + } + }, }); } - compile(variables: TemplateVariables = {}): Node[] { - var { output, template } = this.interpolateTemplate(variables); + compile(variables: TemplateVariables = {}): babelTypes.Statement[] { + const { output } = this.interpolateTemplate(variables); - var program: Node; + let file: babelTypes.File; try { - program = parseSnippet(output); + file = parse(output, { sourceType: "module" }); } catch (e) { - throw new Error(output + "\n" + "Template failed to parse: " + e.message); + throw new Error( + output + "\n" + "Template failed to parse: " + (e as Error).message + ); } - this.interpolateAST(program, variables); + this.interpolateAST(file, variables); - return program.body; + return file.program.body; } - single(variables: TemplateVariables = {}): Node { - var nodes = this.compile(variables); + single(variables: TemplateVariables = {}): babelTypes.Statement { + const nodes = this.compile(variables); if (nodes.length !== 1) { - nodes = nodes.filter((node) => node.type !== "EmptyStatement"); + const filteredNodes = nodes.filter( + (node) => node.type !== "EmptyStatement" + ); ok( - nodes.length === 1, - `Expected single node, got ${nodes.map((node) => node.type).join(", ")}` + filteredNodes.length === 1, + `Expected single node, got ${filteredNodes + .map((node) => node.type) + .join(", ")}` ); + return filteredNodes[0]; } return nodes[0]; } + + expression(variables: TemplateVariables = {}): babelTypes.Expression { + const statement = this.single(variables); + + babelTypes.assertExpressionStatement(statement); + + return statement.expression; + } } diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index ad456e3..db6ed56 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -1,676 +1,39 @@ -import { ObfuscateOrder } from "../order"; -import { ComputeProbabilityMap } from "../probability"; -import Template from "../templates/template"; -import { isBlock } from "../traverse"; -import { - AssignmentExpression, - BinaryExpression, - ExpressionStatement, - Identifier, - IfStatement, - Literal, - MemberExpression, - Node, - ObjectExpression, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { getBlockBody, isFunction, prepend } from "../util/insert"; -import { chance, choice, getRandomInteger } from "../util/random"; -import Transform from "./transform"; - -const templates = [ - new Template(` - function curCSS( elem, name, computed ) { - var ret; - - computed = computed || getStyles( elem ); - - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = redacted.style( elem, name ); - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11+ - // IE returns zIndex value as an integer. - ret + "" : - ret; - }`), - new Template(` - function Example() { - var state = redacted.useState(false); - return x( - ErrorBoundary, - null, - x( - DisplayName, - null, - ) - ); - }`), - - new Template(` - const path = require('path'); -const { version } = require('../../package'); -const { version: dashboardPluginVersion } = require('@redacted/enterprise-plugin/package'); -const { version: componentsVersion } = require('@redacted/components/package'); -const { sdkVersion } = require('@redacted/enterprise-plugin'); -const isStandaloneExecutable = require('../utils/isStandaloneExecutable'); -const resolveLocalRedactedPath = require('./resolve-local-redacted-path'); - -const redactedPath = path.resolve(__dirname, '../redacted.js');`), - - new Template(` -module.exports = async (resolveLocalRedactedPath = ()=>{throw new Error("No redacted path provided")}) => { - const cliParams = new Set(process.argv.slice(2)); - if (!cliParams.has('--version')) { - if (cliParams.size !== 1) return false; - if (!cliParams.has('-v')) return false; - } - - const installationModePostfix = await (async (isStandaloneExecutable, redactedPath) => { - if (isStandaloneExecutable) return ' (standalone)'; - if (redactedPath === (await resolveLocalRedactedPath())) return ' (local)'; - return ''; - })(); - - return true; -};`), - new Template(` -function setCookie(cname, cvalue, exdays) { - var d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - var expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; -}`), - - new Template(`function getCookie(cname) { - var name = cname + "="; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for(var i = 0; i 1 - ) { - return Infinity; - } - - const currentHeight = Math.max(leftTreeHeight, rightTreeHeight) + 1; - return currentHeight; - } - - window["__GLOBAL__HELPERS__"] = { - buildCharacterMap, - isAnagrams, - isBalanced, - getHeightBalanced, - }; - `), - new Template(` - function ListNode(){} - var addTwoNumbers = function(l1, l2) { - var carry = 0; - var sum = 0; - var head = new ListNode(0); - var now = head; - var a = l1; - var b = l2; - while (a !== null || b !== null) { - sum = (a ? a.val : 0) + (b ? b.val : 0) + carry; - carry = Math.floor(sum / 10); - now.next = new ListNode(sum % 10); - now = now.next; - a = a ? a.next : null; - b = b ? b.next : null; - } - if (carry) now.next = new ListNode(carry); - return head.next; - }; - - console.log(addTwoNumbers) - `), - new Template(` - var threeSum = function(nums) { - var len = nums.length; - var res = []; - var l = 0; - var r = 0; - nums.sort((a, b) => (a - b)); - for (var i = 0; i < len; i++) { - if (i > 0 && nums[i] === nums[i - 1]) continue; - l = i + 1; - r = len - 1; - while (l < r) { - if (nums[i] + nums[l] + nums[r] < 0) { - l++; - } else if (nums[i] + nums[l] + nums[r] > 0) { - r--; - } else { - res.push([nums[i], nums[l], nums[r]]); - while (l < r && nums[l] === nums[l + 1]) l++; - while (l < r && nums[r] === nums[r - 1]) r--; - l++; - r--; - } - } - } - return res; - }; - console.log(threeSum) - `), - new Template(` - var combinationSum2 = function(candidates, target) { - var res = []; - var len = candidates.length; - candidates.sort((a, b) => (a - b)); - dfs(res, [], 0, len, candidates, target); - return res; - }; - - var dfs = function (res, stack, index, len, candidates, target) { - var tmp = null; - if (target < 0) return; - if (target === 0) return res.push(stack); - for (var i = index; i < len; i++) { - if (candidates[i] > target) break; - if (i > index && candidates[i] === candidates[i - 1]) continue; - tmp = Array.from(stack); - tmp.push(candidates[i]); - dfs(res, tmp, i + 1, len, candidates, target - candidates[i]); - } - }; - - console.log(combinationSum2); - `), - new Template(` - var isScramble = function(s1, s2) { - return helper({}, s1, s2); - }; - - var helper = function (dp, s1, s2) { - var map = {}; - - if (dp[s1 + s2] !== undefined) return dp[s1 + s2]; - if (s1 === s2) return true; - - for (var j = 0; j < s1.length; j++) { - if (map[s1[j]] === undefined) map[s1[j]] = 0; - if (map[s2[j]] === undefined) map[s2[j]] = 0; - map[s1[j]]++; - map[s2[j]]--; - } - - for (var key in map) { - if (map[key] !== 0) { - dp[s1 + s2] = false; - return false; - } - } - - for (var i = 1; i < s1.length; i++) { - if ((helper(dp, s1.substr(0, i), s2.substr(0, i)) - && helper(dp, s1.substr(i), s2.substr(i))) || - (helper(dp, s1.substr(0, i), s2.substr(s2.length - i)) - && helper(dp, s1.substr(i), s2.substr(0, s2.length - i)))) { - dp[s1 + s2] = true; - return true; - } - } - - dp[s1 + s2] = false; - return false; - }; - - console.log(isScramble); - `), - new Template(` - var candy = function(ratings) { - var len = ratings.length; - var res = []; - var sum = 0; - for (var i = 0; i < len; i++) { - res.push((i !== 0 && ratings[i] > ratings[i - 1]) ? (res[i - 1] + 1) : 1); - } - for (var j = len - 1; j >= 0; j--) { - if (j !== len - 1 && ratings[j] > ratings[j + 1]) res[j] = Math.max(res[j], res[j + 1] + 1); - sum += res[j]; - } - return sum; - }; - - console.log(candy) - `), - new Template(` - var maxPoints = function(points) { - var max = 0; - var map = {}; - var localMax = 0; - var samePoint = 0; - var k = 0; - var len = points.length; - for (var i = 0; i < len; i++) { - map = {}; - localMax = 0; - samePoint = 1; - for (var j = i + 1; j < len; j++) { - if (points[i].x === points[j].x && points[i].y === points[j].y) { - samePoint++; - continue; - } - if (points[i].y === points[j].y) k = Number.MAX_SAFE_INTEGER; - else k = (points[i].x - points[j].x) / (points[i].y - points[j].y); - if (!map[k]) map[k] = 0; - map[k]++; - localMax = Math.max(localMax, map[k]); - } - localMax += samePoint; - max = Math.max(max, localMax); - } - return max; - }; - - console.log(maxPoints) - `), - new Template(` - var maximumGap = function(nums) { - var len = nums.length; - if (len < 2) return 0; - - var max = Math.max(...nums); - var min = Math.min(...nums); - if (max === min) return 0; - - var minBuckets = Array(len - 1).fill(Number.MAX_SAFE_INTEGER); - var maxBuckets = Array(len - 1).fill(Number.MIN_SAFE_INTEGER); - var gap = Math.ceil((max - min) / (len - 1)); - var index = 0; - for (var i = 0; i < len; i++) { - if (nums[i] === min || nums[i] === max) continue; - index = Math.floor((nums[i] - min) / gap); - minBuckets[index] = Math.min(minBuckets[index], nums[i]); - maxBuckets[index] = Math.max(maxBuckets[index], nums[i]); - } - - var maxGap = Number.MIN_SAFE_INTEGER; - var preVal = min; - for (var j = 0; j < len - 1; j++) { - if (minBuckets[j] === Number.MAX_SAFE_INTEGER && maxBuckets[j] === Number.MIN_SAFE_INTEGER) continue; - maxGap = Math.max(maxGap, minBuckets[j] - preVal); - preVal = maxBuckets[j]; - } - maxGap = Math.max(maxGap, max - preVal); - - return maxGap; - }; - - console.log(maximumGap); - `), - new Template(` - var LRUCache = function(capacity) { - this.capacity = capacity; - this.length = 0; - this.map = {}; - this.head = null; - this.tail = null; - }; - - LRUCache.prototype.get = function(key) { - var node = this.map[key]; - if (node) { - this.remove(node); - this.insert(node.key, node.val); - return node.val; - } else { - return -1; - } - }; - - LRUCache.prototype.put = function(key, value) { - if (this.map[key]) { - this.remove(this.map[key]); - this.insert(key, value); - } else { - if (this.length === this.capacity) { - this.remove(this.head); - this.insert(key, value); - } else { - this.insert(key, value); - this.length++; - } - } - }; - - /** - * Your LRUCache object will be instantiated and called as such: - * var obj = Object.create(LRUCache).createNew(capacity) - * var param_1 = obj.get(key) - * obj.put(key,value) - */ - - LRUCache.prototype.remove = function (node) { - var prev = node.prev; - var next = node.next; - if (next) next.prev = prev; - if (prev) prev.next = next; - if (this.head === node) this.head = next; - if (this.tail === node) this.tail = prev; - delete this.map[node.key]; - }; - - LRUCache.prototype.insert = function (key, val) { - var node = new List(key, val); - if (!this.tail) { - this.tail = node; - this.head = node; - } else { - this.tail.next = node; - node.prev = this.tail; - this.tail = node; - } - this.map[key] = node; - }; - - console.log(LRUCache); - `), - new Template(` - var isInterleave = function(s1, s2, s3) { - var dp = {}; - if (s3.length !== s1.length + s2.length) return false; - return helper(s1, s2, s3, 0, 0, 0, dp); - }; - - var helper = function (s1, s2, s3, i, j, k, dp) { - var res = false; - - if (k >= s3.length) return true; - if (dp['' + i + j + k] !== undefined) return dp['' + i + j + k]; - - if (s3[k] === s1[i] && s3[k] === s2[j]) { - res = helper(s1, s2, s3, i + 1, j, k + 1, dp) || helper(s1, s2, s3, i, j + 1, k + 1, dp); - } else if (s3[k] === s1[i]) { - res = helper(s1, s2, s3, i + 1, j, k + 1, dp); - } else if (s3[k] === s2[j]) { - res = helper(s1, s2, s3, i, j + 1, k + 1, dp); - } - - dp['' + i + j + k] = res; - - return res; - }; - - console.log(isInterleave); - `), - new Template(` - var solveNQueens = function(n) { - var res = []; - if (n === 1 || n >= 4) dfs(res, [], n, 0); - return res; - }; - - var dfs = function (res, points, n, index) { - for (var i = index; i < n; i++) { - if (points.length !== i) return; - for (var j = 0; j < n; j++) { - if (isValid(points, [i, j])) { - points.push([i, j]); - dfs(res, points, n, i + 1); - if (points.length === n) res.push(buildRes(points)); - points.pop(); - } - } - } - }; - - var buildRes = function (points) { - var res = []; - var n = points.length; - for (var i = 0; i < n; i++) { - res[i] = ''; - for (var j = 0; j < n; j++) { - res[i] += (points[i][1] === j ? 'Q' : '.'); - } - } - return res; - }; - - var isValid = function (oldPoints, newPoint) { - var len = oldPoints.length; - for (var i = 0; i < len; i++) { - if (oldPoints[i][0] === newPoint[0] || oldPoints[i][1] === newPoint[1]) return false; - if (Math.abs((oldPoints[i][0] - newPoint[0]) / (oldPoints[i][1] - newPoint[1])) === 1) return false; - } - return true; - }; - - console.log(solveNQueens); - `), -]; - -/** - * Adds dead code to blocks. - * - * - Adds fake predicates. - * - Adds fake code from various samples. - */ -export default class DeadCode extends Transform { - made: number; - - compareObjectName: string; - gen = this.getGenerator("randomized"); - - constructor(o) { - super(o, ObfuscateOrder.DeadCode); - - this.made = 0; - } - - match(object: Node, parents: Node[]) { - return ( - isFunction(object) && - isBlock(object.body) && - !object.$multiTransformSkip && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object: Node, parents: Node[]) { - if (!ComputeProbabilityMap(this.options.deadCode)) { - return; - } - - // Hard-coded limit of 100 Dead Code insertions - this.made++; - if (this.made > 100) { - return; - } - - return () => { - var body = getBlockBody(object); - - // Do not place code before Import statements or 'use strict' directives - var safeOffset = 0; - for (var node of body) { - if (node.type === "ImportDeclaration" || node.directive) safeOffset++; - else break; - } - - var index = getRandomInteger(safeOffset, body.length); - - if (!this.compareObjectName) { - this.compareObjectName = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || object, - VariableDeclaration( - VariableDeclarator( - this.compareObjectName, - new Template(`Object["create"](null)`).single().expression - ) - ) - ); - } - - var name = this.getPlaceholder(); - var variableDeclaration = VariableDeclaration( - VariableDeclarator( - name, - BinaryExpression( - "in", - Literal(this.gen.generate()), - Identifier(this.compareObjectName) - ) - ) - ); - - var template: Template; - do { - template = choice(templates); - } while (this.options.es5 && template.templates[0].includes("async")); - - var nodes = template.compile(); - - if (chance(50)) { - nodes.unshift( - ExpressionStatement( - AssignmentExpression( - "=", - MemberExpression( - Identifier(this.compareObjectName), - Literal(this.gen.generate()), - true - ), - Literal(this.gen.generate()) - ) - ) - ); - } - - var ifStatement = IfStatement(Identifier(name), nodes, null); - - body.splice(index, 0, ifStatement); - - prepend(object, variableDeclaration); - }; - } -} +import { PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { chance, choice } from "../utils/random-utils"; +import { blockStatement, booleanLiteral, ifStatement } from "@babel/types"; +import { deadCodeTemplates } from "../templates/deadCodeTemplates"; +import { computeProbabilityMap } from "../probability"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("deadCode"); + let created = 0; + + return { + visitor: { + Block: { + exit(path) { + if (path.node.body.length === 0) { + return; + } + + if (!computeProbabilityMap(me.options.deadCode)) { + return; + } + + if (created > 100 && chance(created - 100)) return; + created++; + + var template = choice(deadCodeTemplates); + var nodes = template.compile(); + + path.unshiftContainer( + "body", + ifStatement(booleanLiteral(false), blockStatement([...nodes])) + ); + path.stop(); + }, + }, + }, + }; +}; diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index b237777..3388b5c 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -1,640 +1,261 @@ -import { walk } from "../traverse"; -import { - ArrayExpression, - AssignmentExpression, - BinaryExpression, - CallExpression, - ExpressionStatement, - FunctionDeclaration, - FunctionExpression, - Identifier, - IfStatement, - Literal, - Node, - Location, - MemberExpression, - ObjectExpression, - Property, - ReturnStatement, - VariableDeclaration, - SequenceExpression, - NewExpression, - UnaryExpression, - BlockStatement, - LogicalExpression, - ThisExpression, - VariableDeclarator, - RestElement, -} from "../util/gen"; -import { getIdentifierInfo } from "../util/identifiers"; -import { - deleteDirect, - getBlockBody, - getVarContext, - isVarContext, - prepend, - append, - computeFunctionLength, - isFunction, -} from "../util/insert"; -import Transform from "./transform"; -import { isInsideType } from "../util/compare"; -import { choice, shuffle } from "../util/random"; -import { ComputeProbabilityMap } from "../probability"; -import { predictableFunctionTag, reservedIdentifiers } from "../constants"; -import { ObfuscateOrder } from "../order"; -import Template from "../templates/template"; -import { FunctionLengthTemplate } from "../templates/functionLength"; -import { ObjectDefineProperty } from "../templates/globals"; -import { getLexicalScope } from "../util/scope"; -import { isJSConfuserVar } from "../util/guard"; - -/** - * A Dispatcher processes function calls. All the function declarations are brought into a dictionary. - * - * ```js - * var param1; - * function dispatcher(key){ - * var fns = { - * 'fn1': function(){ - * var [arg1] = [param1]; - * console.log(arg1); - * } - * } - * return fns[key](); - * }; - * param1 = "Hello World"; - * dispatcher('fn1'); // > "Hello World" - * ``` - * - * Can break code with: - * - * 1. testing function equality, - * 2. using `arguments.callee`, - * 3. using `this` - */ -export default class Dispatcher extends Transform { - // Debug mode preserves function names - isDebug = false; - count: number; - - functionLengthName: string; - - constructor(o) { - super(o, ObfuscateOrder.Dispatcher); - - this.count = 0; - } - - apply(tree: Node): void { - super.apply(tree); - - if (this.options.preserveFunctionLength && this.functionLengthName) { - prepend( - tree, - FunctionLengthTemplate.single({ - name: this.functionLengthName, - ObjectDefineProperty: this.createInitVariable(ObjectDefineProperty, [ - tree, - ]), - }) - ); - } - } - - match(object: Node, parents: Node[]) { - if (isInsideType("AwaitExpression", object, parents)) { - return false; - } - - return ( - isVarContext(object) && - object.type !== "ArrowFunctionExpression" && - !object.$multiTransformSkip && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (ComputeProbabilityMap(this.options.dispatcher, (mode) => mode)) { - if (object.type != "Program" && object.body.type != "BlockStatement") { - return; - } - - // Map of FunctionDeclarations - var functionDeclarations: { [name: string]: Location } = - Object.create(null); - - // Array of Identifier nodes - var identifiers: Location[] = []; - var illegalFnNames: Set = new Set(); - - // New Names for Functions - var newFnNames: { [name: string]: string } = Object.create(null); // [old name]: randomized name - - var context = isVarContext(object) - ? object - : getVarContext(object, parents); - - var lexicalScope = isFunction(context) ? context.body : context; - - walk(object, parents, (o: Node, p: Node[]) => { - if (object == o) { - // Fix 1 - return; - } - - var c = getVarContext(o, p); - if (o.type == "FunctionDeclaration") { - c = getVarContext(p[0], p.slice(1)); - } - - if (context === c) { - if (o.type == "FunctionDeclaration" && o.id.name) { - var name = o.id.name; - - if ( - o.$requiresEval || - o.async || - o.generator || - p.find( - (x) => x.$multiTransformSkip || x.type == "MethodDefinition" - ) || - o.body.type != "BlockStatement" - ) { - illegalFnNames.add(name); - return; - } - - // Must defined in the same block as the current function being scanned - // Solves 'let' and 'class' declaration issue - var ls = getLexicalScope(o, p); - if (ls !== lexicalScope) { - illegalFnNames.add(name); - return; - } - - // If dupe, no routing - if (functionDeclarations[name]) { - illegalFnNames.add(name); - return; - } - - walk(o, p, (oo, pp) => { - if ( - (oo.type == "Identifier" && oo.name == "arguments") || - oo.type == "ThisExpression" || - oo.type == "Super" - ) { - if (getVarContext(oo, pp) === o) { - illegalFnNames.add(name); - return "EXIT"; - } - } - - // Avoid functions with function expressions as they have a different scope - if ( - (oo.type === "FunctionExpression" || - oo.type === "ArrowFunctionExpression") && - pp.find((x) => x == o.params) - ) { - illegalFnNames.add(name); - return "EXIT"; - } - }); - - functionDeclarations[name] = [o, p]; - } - } - - if (o.type == "Identifier") { - if (reservedIdentifiers.has(o.name)) { - return; - } - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced) { - return; - } - if (isJSConfuserVar(p)) { - illegalFnNames.add(o.name); - } - if (info.spec.isDefined) { - if (info.isFunctionDeclaration) { - if ( - p[0].id && - (!functionDeclarations[p[0].id.name] || - functionDeclarations[p[0].id.name][0] !== p[0]) - ) { - illegalFnNames.add(o.name); - } - } else { - illegalFnNames.add(o.name); - } - } else if (info.spec.isModified) { - illegalFnNames.add(o.name); - } else { - identifiers.push([o, p]); - } - } - }); - - illegalFnNames.forEach((name) => { - delete functionDeclarations[name]; - }); - - // map original name->new game - var gen = this.getGenerator(); - Object.keys(functionDeclarations).forEach((name) => { - newFnNames[name] = this.isDebug - ? "_dispatcher_" + this.count + "_" + name - : gen.generate(); - }); - // set containing new name - var set = new Set(Object.keys(newFnNames)); - - // Only make a dispatcher function if it caught any functions - if (set.size > 0) { - if (!this.functionLengthName) { - this.functionLengthName = this.getPlaceholder(); - } - - var payloadArg = - this.getPlaceholder() + "_dispatcher_" + this.count + "_payload"; - - var dispatcherFnName = - this.getPlaceholder() + - "_dispatcher_" + - this.count + - predictableFunctionTag; - - this.log(dispatcherFnName, set); - this.count++; - - var expectedGet = gen.generate(); - var expectedClearArgs = gen.generate(); - var expectedNew = gen.generate(); - - var returnProp = gen.generate(); - var newReturnMemberName = gen.generate(); - - var shuffledKeys = shuffle(Object.keys(functionDeclarations)); - var mapName = this.getPlaceholder(); - - var cacheName = this.getPlaceholder(); - - // creating the dispatcher function - // 1. create function map - var map = VariableDeclaration( - VariableDeclarator( - mapName, - ObjectExpression( - shuffledKeys.map((name) => { - var [def, defParents] = functionDeclarations[name]; - var body = getBlockBody(def.body); - - var functionExpression: Node = { - ...def, - expression: false, - type: "FunctionExpression", - id: null, - params: [], - [predictableFunctionTag]: true, - }; - this.addComment(functionExpression, name); - - if (def.params.length > 0) { - const fixParam = (param: Node) => { - return param; - }; - - var variableDeclaration = VariableDeclaration( - VariableDeclarator( - { - type: "ArrayPattern", - elements: def.params.map(fixParam), - }, - Identifier(payloadArg) - ) - ); - - prepend(def.body, variableDeclaration); - } - - // For logging purposes - var signature = - name + - "(" + - def.params.map((x) => x.name || "<>").join(",") + - ")"; - this.log("Added", signature); - - // delete ref in block - if (defParents.length) { - deleteDirect(def, defParents[0]); - } - - this.addComment(functionExpression, signature); - return Property( - Literal(newFnNames[name]), - functionExpression, - false - ); - }) - ) - ) - ); - - var getterArgName = this.getPlaceholder(); - - var x = this.getPlaceholder(); - var y = this.getPlaceholder(); - var z = this.getPlaceholder(); - - function getAccessor() { - return MemberExpression(Identifier(mapName), Identifier(x), true); - } - - // 2. define it - var fn = FunctionDeclaration( - dispatcherFnName, - [Identifier(x), Identifier(y), Identifier(z)], - [ - // Define map of callable functions - map, - - // Set returning variable to undefined - VariableDeclaration(VariableDeclarator(returnProp)), - - // Arg to clear the payload - IfStatement( - BinaryExpression( - "==", - Identifier(y), - Literal(expectedClearArgs) - ), - [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(payloadArg), - ArrayExpression([]) - ) - ), - ], - null - ), - - VariableDeclaration( - VariableDeclarator( - Identifier("lengths"), - ObjectExpression( - !this.options.preserveFunctionLength - ? [] - : shuffledKeys - .map((name) => { - var [def, defParents] = functionDeclarations[name]; - - return { - key: newFnNames[name], - value: computeFunctionLength(def.params), - }; - }) - .filter((item) => item.value !== 0) - .map((item) => - Property(Literal(item.key), Literal(item.value)) - ) - ) - ) - ), - - new Template(` - function makeFn${predictableFunctionTag}(){ - var fn = function(...args){ - ${payloadArg} = args; - return ${mapName}[${x}].call(this) - }, a = lengths[${x}] - - ${ - this.options.preserveFunctionLength - ? `if(a){ - return ${this.functionLengthName}(fn, a) - }` - : "" - } - - return fn - } - `).single(), - - // Arg to get a function reference - IfStatement( - BinaryExpression("==", Identifier(y), Literal(expectedGet)), - [ - // Getter flag: return the function object - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(returnProp), - LogicalExpression( - "||", - MemberExpression( - Identifier(cacheName), - Identifier(x), - true - ), - AssignmentExpression( - "=", - MemberExpression( - Identifier(cacheName), - Identifier(x), - true - ), - CallExpression( - Identifier(`makeFn${predictableFunctionTag}`), - [] - ) - ) - ) - ) - ), - ], - [ - // Call the function, return result - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(returnProp), - CallExpression(getAccessor(), []) - ) - ), - ] - ), - - // Check how the function was invoked (new () vs ()) - IfStatement( - BinaryExpression("==", Identifier(z), Literal(expectedNew)), - [ - // Wrap in object - ReturnStatement( - ObjectExpression([ - Property( - Identifier(newReturnMemberName), - Identifier(returnProp), - false - ), - ]) - ), - ], - [ - // Return raw result - ReturnStatement(Identifier(returnProp)), - ] - ), - ] - ); - - append(object, fn); - - if (payloadArg) { - prepend( - object, - VariableDeclaration( - VariableDeclarator(payloadArg, ArrayExpression([])) - ) - ); - } - - identifiers.forEach(([o, p]) => { - if (o.type != "Identifier") { - return; - } - - var newName = newFnNames[o.name]; - if (!newName || typeof newName !== "string") { - return; - } - - if (!functionDeclarations[o.name]) { - this.error(new Error("newName, missing function declaration")); - } - - var info = getIdentifierInfo(o, p); - if ( - info.isFunctionCall && - p[0].type == "CallExpression" && - p[0].callee === o - ) { - // Invoking call expression: `a();` - - if (o.name == dispatcherFnName) { - return; - } - - this.log( - `${o.name}(${p[0].arguments - .map((_) => "<>") - .join(",")}) -> ${dispatcherFnName}('${newName}')` - ); - - var assignmentExpressions: Node[] = []; - var dispatcherArgs: Node[] = [Literal(newName)]; - - if (p[0].arguments.length) { - assignmentExpressions = [ - AssignmentExpression( - "=", - Identifier(payloadArg), - ArrayExpression(p[0].arguments) - ), - ]; - } else { - dispatcherArgs.push(Literal(expectedClearArgs)); - } - - var type = choice(["CallExpression", "NewExpression"]); - var callExpression = null; - - switch (type) { - case "CallExpression": - callExpression = CallExpression( - Identifier(dispatcherFnName), - dispatcherArgs - ); - break; - - case "NewExpression": - if (dispatcherArgs.length == 1) { - dispatcherArgs.push(Identifier("undefined")); - } - callExpression = MemberExpression( - NewExpression(Identifier(dispatcherFnName), [ - ...dispatcherArgs, - Literal(expectedNew), - ]), - Identifier(newReturnMemberName), - false - ); - break; - } - - this.addComment( - callExpression, - "Calling " + - o.name + - "(" + - p[0].arguments.map((x) => x.name).join(", ") + - ")" - ); - - var expr: Node = assignmentExpressions.length - ? SequenceExpression([...assignmentExpressions, callExpression]) - : callExpression; - - // Replace the parent call expression - this.replace(p[0], expr); - } else { - // Non-invoking reference: `a` - - if (info.spec.isDefined) { - if (info.isFunctionDeclaration) { - this.log( - "Skipped getter " + o.name + " (function declaration)" - ); - } else { - this.log("Skipped getter " + o.name + " (defined)"); - } - return; - } - if (info.spec.isModified) { - this.log("Skipped getter " + o.name + " (modified)"); - return; - } - - this.log( - `(getter) ${o.name} -> ${dispatcherFnName}('${newName}')` - ); - this.replace( - o, - CallExpression(Identifier(dispatcherFnName), [ - Literal(newName), - Literal(expectedGet), - ]) - ); - } - }); - - prepend( - object, - VariableDeclaration( - VariableDeclarator( - Identifier(cacheName), - new Template(`Object.create(null)`).single().expression - ) - ) - ); - } - } - }; - } -} +import { PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { ok } from "assert"; +import { chance, getRandomString } from "../utils/random-utils"; +import { computeProbabilityMap } from "../probability"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("dispatcher"); + + return { + visitor: { + Block: { + exit(blockPath: NodePath) { + // For testing + if (!blockPath.isProgram()) return; + + // Track functions and illegal ones + // A function is illegal if: + // - the function is async or generator + // - the function is redefined + // - the function uses 'this', 'eval', or 'arguments' + var functionPaths = new Map< + string, + NodePath + >(); + var illegalNames = new Set(); + + // Scan for function declarations + blockPath.traverse({ + FunctionDeclaration: { + exit(path: NodePath) { + const name = path.node.id.name; + // If the function is not named, we can't dispatch it + if (!name) { + return; + } + + // Do not apply to async or generator functions + if (path.node.async || path.node.generator) { + return; + } + + if (functionPaths.has(name)) { + illegalNames.add(name); + return; + } + + functionPaths.set(name, path); + }, + }, + }); + + for (let name of illegalNames) { + functionPaths.delete(name); + } + + for (var name of functionPaths.keys()) { + if (!computeProbabilityMap(me.options.dispatcher, (x) => x, name)) { + functionPaths.delete(name); + } + } + + // No functions here to change + if (functionPaths.size === 0) { + return; + } + + const dispatcherName = me.getPlaceholder() + "_dispatcher"; + const payloadName = me.getPlaceholder() + "_payload"; + const cacheName = me.getPlaceholder() + "_cache"; + const newNameMapping = new Map(); + + const keys = { + placeholderNoMeaning: getRandomString(10), + clearPayload: getRandomString(10), + nonCall: getRandomString(10), + returnAsObject: getRandomString(10), + returnAsObjectProperty: getRandomString(10), + }; + + for (var name of functionPaths.keys()) { + newNameMapping.set(name, "_" + name); + } + + // Find identifiers calling/referencing the functions + blockPath.traverse({ + ReferencedIdentifier: { + exit(path: NodePath) { + if (path.isJSX()) return; + const name = path.node.name; + + var fnPath = functionPaths.get(name); + if (!fnPath) return; + + var newName = newNameMapping.get(name); + + const createDispatcherCall = (name, flagArg?) => { + var dispatcherArgs = [t.stringLiteral(name)]; + if (flagArg) { + dispatcherArgs.push(t.stringLiteral(flagArg)); + } + + var asObject = chance(50); + + if (asObject) { + if (dispatcherArgs.length < 2) { + dispatcherArgs.push( + t.stringLiteral(keys.placeholderNoMeaning) + ); + } + dispatcherArgs.push(t.stringLiteral(keys.returnAsObject)); + } + + var callExpression = t.callExpression( + t.identifier(dispatcherName), + dispatcherArgs + ); + + if (!asObject) { + return callExpression; + } + + return t.memberExpression( + callExpression, + t.stringLiteral(keys.returnAsObjectProperty), + true + ); + }; + + // Replace the identifier with a call to the function + if (path.parent.type === "CallExpression") { + var expressions: t.Expression[] = []; + var callArguments = path.parent.arguments; + + if (callArguments.length === 0) { + expressions.push( + // Call the function + createDispatcherCall(newName, keys.clearPayload) + ); + } else { + expressions.push( + // Prepare the payload arguments + t.assignmentExpression( + "=", + t.identifier(payloadName), + t.arrayExpression(callArguments as t.Expression[]) + ), + + // Call the function + createDispatcherCall(newName) + ); + } + + const output = + expressions.length === 1 + ? expressions[0] + : t.sequenceExpression(expressions); + + path.parentPath.replaceWith(output); + } else { + // Replace non-invocation references with a 'cached' version of the function + path.replaceWith(createDispatcherCall(newName, keys.nonCall)); + } + }, + }, + }); + + // Create the dispatcher function + const objectExpression = t.objectExpression( + Array.from(newNameMapping).map(([name, newName]) => { + const originalFn = functionPaths.get(name)!.node; + + const newBody = [...originalFn.body.body]; + ok(Array.isArray(newBody)); + + newBody.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern([...originalFn.params]), + t.identifier(payloadName) + ), + ]) + ); + + const functionExpression = t.functionExpression( + null, + [], + t.blockStatement(newBody) + ); + + return t.objectProperty( + t.stringLiteral(newName), + + functionExpression + ); + }) + ); + + const dispatcher = new Template(` + function ${dispatcherName}(name, flagArg, returnTypeArg) { + var output, fns = {objectExpression}; + + if(flagArg === "${keys.clearPayload}") { + ${payloadName} = []; + } + if(flagArg === "${keys.nonCall}") { + output = ${cacheName}[name] || (${cacheName}[name] = function(...args){ + ${payloadName} = args; + return fns[name].apply(this); + }); + } else { + output = fns[name](); + } + + if(returnTypeArg === "${keys.returnAsObject}") { + return { "${keys.returnAsObjectProperty}": output }; + } else { + return output; + } + } + `).single({ + objectExpression, + }); + + // Insert the dispatcher function + var p = blockPath.unshiftContainer("body", dispatcher); + blockPath.scope.registerDeclaration(p[0]); + + // Insert the payload variable + p = blockPath.unshiftContainer( + "body", + t.variableDeclaration("var", [ + t.variableDeclarator(t.identifier(payloadName)), + ]) + ); + blockPath.scope.registerDeclaration(p[0]); + + // Insert the cache variable + p = blockPath.unshiftContainer( + "body", + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(cacheName), + new Template(`Object["create"](null)`).expression() + ), + ]) + ); + blockPath.scope.registerDeclaration(p[0]); + + // Remove original functions + for (let path of functionPaths.values()) { + path.remove(); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 1a6fbd6..973e552 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -1,297 +1,122 @@ -import Transform from "../transform"; -import { - Identifier, - Literal, - VariableDeclaration, - Node, - ArrayExpression, - MemberExpression, - VariableDeclarator, - Location, - ReturnStatement, - CallExpression, - BinaryExpression, - FunctionDeclaration, - ConditionalExpression, -} from "../../util/gen"; -import { append, clone, prepend } from "../../util/insert"; -import { isDirective, isModuleSource, isPrimitive } from "../../util/compare"; -import { ObfuscateOrder } from "../../order"; -import { ComputeProbabilityMap } from "../../probability"; +import * as babel from "@babel/core"; +import * as babelTypes from "@babel/types"; import { ok } from "assert"; -import { chance, choice, getRandomInteger } from "../../util/random"; -import { getBlock } from "../../traverse"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { predictableFunctionTag } from "../../constants"; +import { PluginArg } from "../plugin"; -/** - * [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a variable name. - * - * - Potency Medium - * - Resilience Medium - * - Cost Medium - * - * ```js - * // Input - * var foo = "http://www.example.xyz"; - * bar("http://www.example.xyz"); - * - * // Output - * var a = "http://www.example.xyz"; - * var foo = a; - * bar(a); - * ``` - */ -export default class DuplicateLiteralsRemoval extends Transform { - // The array holding all the duplicate literals - arrayName: string; - // The array expression node to be inserted into the program - arrayExpression: Node; +type LiteralValue = string | number | boolean; +const createLiteral = (value: LiteralValue) => { + switch (typeof value) { + case "string": + return babelTypes.stringLiteral(value); - /** - * Literals in the array - */ - map: Map; + case "number": + return babelTypes.numericLiteral(value); - /** - * Literals are saved here the first time they are seen. - */ - first: Map; - - /** - * Block -> { functionName, indexShift } - */ - functions: Map; - - constructor(o) { - super(o, ObfuscateOrder.DuplicateLiteralsRemoval); - - this.map = new Map(); - this.first = new Map(); - - this.functions = new Map(); + case "boolean": + return babelTypes.booleanLiteral(value); } - apply(tree) { - super.apply(tree); - - if (this.arrayName && this.arrayExpression.elements.length > 0) { - // This function simply returns the array - var getArrayFn = this.getPlaceholder(); - append( - tree, - FunctionDeclaration( - getArrayFn, - [], - [ReturnStatement(this.arrayExpression)] - ) - ); - - // This variable holds the array - prepend( - tree, - VariableDeclaration( - VariableDeclarator( - this.arrayName, - CallExpression(Identifier(getArrayFn), []) - ) - ) - ); - - // Create all the functions needed - for (var blockNode of this.functions.keys()) { - var { functionName, indexShift } = this.functions.get(blockNode); - - var propertyNode: Node = BinaryExpression( - "-", - Identifier("index_param"), - Literal(indexShift) - ); - - var indexRangeInclusive = [ - 0 + indexShift - 1, - this.map.size + indexShift, - ]; - - // The function uses mangling to hide the index being accessed - var mangleCount = getRandomInteger(1, 5); - for (var i = 0; i < mangleCount; i++) { - var operator = choice([">", "<"]); - var compareValue = choice(indexRangeInclusive); - - var test = BinaryExpression( - operator, - Identifier("index_param"), - Literal(compareValue) + ok(false); +}; + +export default ({ Plugin }: PluginArg): babel.PluginObj => { + const me = Plugin("duplicateLiteralsRemoval"); + + return { + visitor: { + Program: { + enter(programPath) { + const arrayName = me.generateRandomIdentifier(); + + // Collect all literals + const literalsMap = new Map(); + const firstTimeMap = new Map< + LiteralValue, + babel.NodePath + >(); + + const arrayExpression = babelTypes.arrayExpression([]); + + const createMemberExpression = (index) => { + return babelTypes.memberExpression( + babelTypes.identifier(arrayName), + babelTypes.numericLiteral(index), + true + ); + }; + + // Traverse through all nodes to find literals + programPath.traverse({ + Literal(literalPath) { + let node = literalPath.node; + if ( + babelTypes.isNullLiteral(node) || + babelTypes.isRegExpLiteral(node) || + babelTypes.isTemplateLiteral(node) + ) + return; + const value = node.value; + + if ( + typeof value !== "string" && + typeof value !== "number" && + typeof value !== "boolean" + ) { + return; + } + + var index = -1; + + if (literalsMap.has(value)) { + index = literalsMap.get(value); + } else if (firstTimeMap.has(value)) { + // Create new index + + index = literalsMap.size; + literalsMap.set(value, index); + + firstTimeMap + .get(value) + .replaceWith(createMemberExpression(index)); + + arrayExpression.elements.push(createLiteral(value)); + } else { + firstTimeMap.set(value, literalPath); + + return; + } + + ok(index !== -1); + + // Replace literals in the code with a placeholder + literalPath.replaceWith(createMemberExpression(index)); + literalPath.skip(); + }, + }); + + if (arrayExpression.elements.length === 0) return; + + // Create the literals array declaration + const itemsArrayDeclaration = babelTypes.variableDeclaration( + "const", + [ + babelTypes.variableDeclarator( + babelTypes.identifier(arrayName), + arrayExpression + ), + ] ); - var alternate = BinaryExpression( - "-", - Identifier("index_param"), - Literal(getRandomInteger(-100, 100)) - ); - - var testValue = - (operator === ">" && compareValue === indexRangeInclusive[0]) || - (operator === "<" && compareValue === indexRangeInclusive[1]); - - propertyNode = ConditionalExpression( - test, - testValue ? propertyNode : alternate, - !testValue ? propertyNode : alternate - ); - } - - var returnArgument = MemberExpression( - Identifier(this.arrayName), - propertyNode, - true - ); - - prepend( - blockNode, - FunctionDeclaration( - functionName, - [Identifier("index_param")], - [ReturnStatement(returnArgument)] - ) - ); - } - } - } - - match(object: Node, parents: Node[]) { - return ( - isPrimitive(object) && - !isDirective(object, parents) && - !isModuleSource(object, parents) && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - /** - * Converts ordinary literal to go through a getter function. - * @param object - * @param parents - * @param index - */ - transformLiteral(object: Node, parents: Node[], index: number) { - var blockNode = choice(parents.filter((x) => this.functions.has(x))); - - // Create initial function if none exist - if (this.functions.size === 0) { - var root = parents[parents.length - 1]; - var rootFunctionName = this.getPlaceholder() + "_dLR_0"; - this.functions.set(root, { - functionName: rootFunctionName + predictableFunctionTag, - indexShift: getRandomInteger(-100, 100), - }); - - blockNode = root; - } - - // If no function here exist, possibly create new chained function - var block = getBlock(object, parents); - if (!this.functions.has(block) && chance(50 - this.functions.size)) { - var newFunctionName = - this.getPlaceholder() + - "_dLR_" + - this.functions.size + - predictableFunctionTag; - - this.functions.set(block, { - functionName: newFunctionName, - indexShift: getRandomInteger(-100, 100), - }); - - blockNode = block; - } - - // Derive the function to call from the selected blockNode - var { functionName, indexShift } = this.functions.get(blockNode); - - // Call the function given it's indexShift - var callExpression = CallExpression(Identifier(functionName), [ - Literal(index + indexShift), - ]); - - this.replaceIdentifierOrLiteral(object, callExpression, parents); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (object.type === "Identifier") { - var info = getIdentifierInfo(object, parents); - if (info.isLabel || info.spec.isDefined || info.spec.isModified) return; - } - if (object.regex) { - return; - } - - if (!ComputeProbabilityMap(this.options.duplicateLiteralsRemoval)) { - return; - } - - if ( - this.arrayName && - parents[0].object && - parents[0].object.name == this.arrayName - ) { - return; - } - - var stringValue; - if (object.type == "Literal") { - stringValue = typeof object.value + ":" + object.value; - if (object.value === null) { - stringValue = "null:null"; - } else { - // Skip empty strings - if (typeof object.value === "string" && !object.value) { - return; - } - } - } else if (object.type == "Identifier") { - stringValue = "identifier:" + object.name; - } else { - throw new Error("Unsupported primitive type: " + object.type); - } - - ok(stringValue); - - if (this.map.has(stringValue) || this.first.has(stringValue)) { - // Create the array if not already made - if (!this.arrayName) { - this.arrayName = this.getPlaceholder(); - this.arrayExpression = ArrayExpression([]); - } - - // Delete with first location - var firstLocation = this.first.get(stringValue); - if (firstLocation) { - var index = this.map.size; - - ok(!this.map.has(stringValue)); - this.map.set(stringValue, index); - this.first.delete(stringValue); - - var pushing = clone(object); - this.arrayExpression.elements.push(pushing); - - ok(this.arrayExpression.elements[index] === pushing); - - this.transformLiteral(firstLocation[0], firstLocation[1], index); - } - - var index = this.map.get(stringValue); - ok(typeof index === "number"); - - this.transformLiteral(object, parents, index); - return; - } - - // Save this, maybe a duplicate will be found. - this.first.set(stringValue, [object, parents]); - }; - } -} + // Insert the array at the top of the program body + var path = programPath.unshiftContainer( + "body", + itemsArrayDeclaration + )[0]; + + programPath.scope.registerDeclaration(path); + console.log(programPath.scope.bindings[arrayName].identifier); + }, + }, + }, + }; +}; diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index 5d3bfba..f1f7cbc 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -1,360 +1,142 @@ -import Transform from "../transform"; -import { walk } from "../../traverse"; -import { Node, Location, Identifier, VariableDeclarator } from "../../util/gen"; -import { getVarContext, isVarContext } from "../../util/insert"; -import { ObfuscateOrder } from "../../order"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { isValidIdentifier } from "../../util/compare"; -import { ComputeProbabilityMap } from "../../probability"; -import { ok } from "assert"; -import { isStringLiteral } from "../../util/guard"; - -/** - * Extracts keys out of an object if possible. - * ```js - * // Input - * var utils = { - * isString: x=>typeof x === "string", - * isBoolean: x=>typeof x === "boolean" - * } - * if ( utils.isString("Hello") ) { - * ... - * } - * - * // Output - * var utils_isString = x=>typeof x === "string"; - * var utils_isBoolean = x=>typeof x === "boolean" - * - * if ( utils_isString("Hello") ) { - * ... - * } - * ``` - */ -export default class ObjectExtraction extends Transform { - constructor(o) { - super(o, ObfuscateOrder.ObjectExtraction); - } - - match(object: Node, parents: Node[]) { - return isVarContext(object); +import * as babelTypes from "@babel/types"; +import { NodePath, PluginObj } from "@babel/core"; +import { + getMemberExpressionPropertyAsString, + getObjectPropertyAsString, + isComputedMemberExpression, +} from "../../utils/ast-utils"; +import { PluginArg } from "../plugin"; + +function isObjectSafeForExtraction( + path: NodePath +): boolean { + const id = path.node.id; + babelTypes.assertIdentifier(id); + const identifierName = id.name; + + const init = path.node.init; + + // Check if the object is not re-assigned + const binding = path.scope.getBinding(identifierName); + if (!binding || binding.constantViolations.length > 0) { + return false; } - transform(context: Node, contextParents: Node[]) { - // ObjectExpression Extractor - - return () => { - // First pass through to find the maps - var objectDefs: { [name: string]: Location } = Object.create(null); - var objectDefiningIdentifiers: { [name: string]: Location } = - Object.create(null); - - var illegal = new Set(); + var propertyNames = new Set(); + + // Check all properties of the object + if (babelTypes.isObjectExpression(init)) { + for (const prop of init.properties) { + if ( + babelTypes.isObjectMethod(prop) || + babelTypes.isSpreadElement(prop) || + (babelTypes.isObjectProperty(prop) && + !babelTypes.isIdentifier(prop.key) && + !babelTypes.isStringLiteral(prop.key)) || + (babelTypes.isObjectProperty(prop) && + babelTypes.isFunctionExpression(prop.value) && + prop.value.body.body.some((node) => + babelTypes.isThisExpression(node) + )) + ) { + return false; + } - walk(context, contextParents, (object: Node, parents: Node[]) => { - if (object.type == "ObjectExpression") { - // this.log(object, parents); - if ( - parents[0].type == "VariableDeclarator" && - parents[0].init == object && - parents[0].id.type == "Identifier" - ) { - var name = parents[0].id.name; - if (name) { - if (getVarContext(object, parents) != context) { - illegal.add(name); - return; - } - if (!object.properties.length) { - illegal.add(name); - return; - } + var propertyKey = getObjectPropertyAsString(prop); + if (typeof propertyKey !== "string") { + return false; + } + propertyNames.add(propertyKey); + } + } - // duplicate name - if (objectDefiningIdentifiers[name]) { - illegal.add(name); - return; - } + // Check all references to ensure they are safe + return binding.referencePaths.every((refPath) => { + const parent = refPath.parent; - // check for computed properties - // Change String literals to non-computed - object.properties.forEach((prop) => { - if (prop.computed && isStringLiteral(prop.key)) { - prop.computed = false; - } - }); + // Referencing the object name by itself is not allowed + if (!babelTypes.isMemberExpression(parent)) { + return false; + } - var nonInitOrComputed = object.properties.find( - (x) => x.kind !== "init" || x.computed - ); + if (babelTypes.isCallExpression(parent.property)) { + return false; + } - if (nonInitOrComputed) { - if (nonInitOrComputed.key) { - this.log( - name + - " has non-init/computed property: " + - nonInitOrComputed.key.name || nonInitOrComputed.key.value - ); - } else { - this.log( - name + " has spread-element or other type of property" - ); - } + var propertyName = getMemberExpressionPropertyAsString(parent); - illegal.add(name); - return; - } else { - var illegalName = object.properties - .map((x) => - x.computed ? x.key.value : x.key.name || x.key.value - ) - .find((x) => !x || !isValidIdentifier(x)); + if (typeof propertyName !== "string") { + return false; + } + return propertyNames.has(propertyName); + }); +} - if (illegalName) { - this.log( - name + " has an illegal property '" + illegalName + "'" - ); - illegal.add(name); - return; - } else { - var isIllegal = false; - walk(object, parents, (o, p) => { - if (o.type == "ThisExpression" || o.type == "Super") { - isIllegal = true; - return "EXIT"; - } - }); - if (isIllegal) { - illegal.add(name); - return; - } +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("objectExtraction"); + + return { + visitor: { + VariableDeclarator(path: NodePath) { + // Ensure the variable is an object literal and the object is not re-assigned + if ( + babelTypes.isObjectExpression(path.node.init) && + path.node.id.type === "Identifier" && + isObjectSafeForExtraction(path) + ) { + const objectName = path.node.id.name; + const properties = path.node.init.properties; + + // Extract each property and create a new variable for it + const extractedVariables = properties + .map((prop) => { + if ( + babelTypes.isObjectProperty(prop) && + (babelTypes.isIdentifier(prop.key) || + babelTypes.isStringLiteral(prop.key)) + ) { + const propName = getObjectPropertyAsString(prop); + + const newVarName = `${objectName}_${propName}`; + const newVarDeclaration = babelTypes.variableDeclarator( + babelTypes.identifier(newVarName), + prop.value as babelTypes.Expression + ); - objectDefs[name] = [object, parents]; - objectDefiningIdentifiers[name] = [ - parents[0].id, - [...parents], - ]; - } + return newVarDeclaration; } - } - } - } - }); - - illegal.forEach((name) => { - delete objectDefs[name]; - delete objectDefiningIdentifiers[name]; - }); - // this.log("object defs", objectDefs); - // huge map of changes - var objectDefChanges: { - [name: string]: { key: string; object: Node; parents: Node[] }[]; - } = {}; + return null; + }) + .filter(Boolean); - if (Object.keys(objectDefs).length) { - // A second pass through is only required when extracting object keys + // Replace the original object with extracted variables + if (extractedVariables.length > 0) { + path.replaceWithMultiple(extractedVariables); - // Second pass through the exclude the dynamic map (counting keys, re-assigning) - walk(context, contextParents, (object: any, parents: Node[]) => { - if (object.type == "Identifier") { - var info = getIdentifierInfo(object, parents); - if (!info.spec.isReferenced) { - return; + var variableDeclaration = + path.parent as babelTypes.VariableDeclaration; + if (variableDeclaration.kind === "const") { + variableDeclaration.kind = "let"; } - var def = objectDefs[object.name]; - if (def) { - var isIllegal = false; - - if (info.spec.isDefined) { - if (objectDefiningIdentifiers[object.name][0] !== object) { - this.log(object.name, "you can't redefine the object"); - isIllegal = true; - } - } else { - var isMemberExpression = - parents[0].type == "MemberExpression" && - parents[0].object == object; - - if ( - (parents.find((x) => x.type == "AssignmentExpression") && - !isMemberExpression) || - parents.find( - (x) => x.type == "UnaryExpression" && x.operator == "delete" - ) - ) { - this.log(object.name, "you can't re-assign the object"); - - isIllegal = true; - } else if (isMemberExpression) { - var key = - parents[0].property.value || parents[0].property.name; - - if ( - parents[0].computed && - parents[0].property.type !== "Literal" - ) { - this.log( - object.name, - "object[expr] detected, only object['key'] is allowed" - ); - - isIllegal = true; - } else if ( - !parents[0].computed && - parents[0].property.type !== "Identifier" - ) { - this.log( - object.name, - "object. detected, only object.key is allowed" - ); - - isIllegal = true; - } else if ( - !key || - !def[0].properties.some( - (x) => (x.key.value || x.key.name) == key - ) - ) { - // check if initialized property - // not in initialized object. - this.log( - object.name, - "not in initialized object.", - def[0].properties, - key - ); - isIllegal = true; - } - - if (!isIllegal && key) { - // allowed. - // start the array if first time - if (!objectDefChanges[object.name]) { - objectDefChanges[object.name] = []; - } - // add to array - objectDefChanges[object.name].push({ - key: key, - object: object, - parents: parents, - }); - } - } else { - this.log( - object.name, - "you must access a property on the when referring to the identifier (accessors must be hard-coded literals), parent is " + - parents[0].type - ); - - isIllegal = true; - } - } - if (isIllegal) { - // this is illegal, delete it from being moved and delete accessor changes from happening - this.log(object.name + " is illegal"); - delete objectDefs[object.name]; - delete objectDefChanges[object.name]; - } - } - } - }); - - Object.keys(objectDefs).forEach((name) => { - if ( - !ComputeProbabilityMap( - this.options.objectExtraction, - (x) => x, - name - ) - ) { - //continue; - return; - } - - var [object, parents] = objectDefs[name]; - var declarator = parents[0]; - var declaration = parents[2]; - - ok(declarator.type === "VariableDeclarator"); - ok(declaration.type === "VariableDeclaration"); - - var properties = object.properties; - // change the prop names while extracting - var newPropNames: { [key: string]: string } = {}; - - var variableDeclarators = []; - - properties.forEach((property: Node) => { - var keyName = property.key.name || property.key.value; - - var nn = name + "_" + keyName; - newPropNames[keyName] = nn; - - var v = property.value; - - variableDeclarators.push( - VariableDeclarator(nn, this.addComment(v, `${name}.${keyName}`)) - ); - }); - - declaration.declarations.splice( - declaration.declarations.indexOf(declarator), - 1, - ...variableDeclarators - ); + // Replace references to the object with the new variables + path.scope.traverse(path.scope.block, { + MemberExpression( + memberPath: NodePath + ) { + const propName = getMemberExpressionPropertyAsString( + memberPath.node + ); - // const can only be safely changed to let - if (declaration.kind === "const") { - declaration.kind = "let"; - } + const newVarName = `${objectName}_${propName}`; - // update all identifiers that pointed to the old object - objectDefChanges[name] && - objectDefChanges[name].forEach((change) => { - if (!change.key) { - this.error(new Error("key is undefined")); - } - if (newPropNames[change.key]) { - var memberExpression = change.parents[0]; - if (memberExpression.type == "MemberExpression") { - this.replace( - memberExpression, - this.addComment( - Identifier(newPropNames[change.key]), - `Original Accessor: ${name}.${change.key}` - ) - ); - } else { - // Provide error with more information: - console.log(memberExpression); - this.error( - new Error( - `should be MemberExpression, found type=${memberExpression.type}` - ) - ); - } - } else { - console.log(objectDefChanges[name], newPropNames); - this.error( - new Error( - `"${change.key}" not found in [${Object.keys( - newPropNames - ).join(", ")}] while flattening ${name}.` - ) - ); - } + memberPath.replaceWith(babelTypes.identifier(newVarName)); + }, }); - - this.log( - `Extracted ${ - Object.keys(newPropNames).length - } properties from ${name}, affecting ${ - Object.keys(objectDefChanges[name] || {}).length - } line(s) of code.` - ); - }); - } - }; - } -} + } + } + }, + }, + }; +}; diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index c07c446..6498012 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -1,75 +1,29 @@ -import { ObfuscateOrder } from "../order"; -import { ExitCallback } from "../traverse"; -import { Identifier, Node } from "../util/gen"; -import StringEncoding from "./string/stringEncoding"; -import Transform from "./transform"; - -/** - * The Finalizer is the last transformation before the code is ready to be generated. - * - * Hexadecimal numbers: - * - Convert integer literals into `Identifier` nodes with the name being a hexadecimal number - * - * BigInt support: - * - Convert BigInt literals into `Identifier` nodes with the name being the raw BigInt string value + "n" - * - * String Encoding: - * - Convert String literals into `Identifier` nodes with the name being a unicode escaped string - */ -export default class Finalizer extends Transform { - stringEncoding: StringEncoding; - - constructor(o) { - super(o, ObfuscateOrder.Finalizer); - - this.stringEncoding = new StringEncoding(o); - } - - isNumberLiteral(object: Node) { - return ( - object.type === "Literal" && - typeof object.value === "number" && - Math.floor(object.value) === object.value - ); - } - - isBigIntLiteral(object: Node) { - return object.type === "Literal" && typeof object.value === "bigint"; - } - - match(object, parents) { - return object.type === "Literal"; - } - - transform(object: Node, parents: Node[]): void | ExitCallback { - // Hexadecimal Numbers - if (this.options.hexadecimalNumbers && this.isNumberLiteral(object)) { - return () => { - // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator. - // This code handles it regardless - var isNegative = object.value < 0; - var hex = Math.abs(object.value).toString(16); - - var newStr = (isNegative ? "-" : "") + "0x" + hex; - - this.replace(object, Identifier(newStr)); - }; - } - - // BigInt support - if (this.isBigIntLiteral(object)) { - // https://github.com/MichaelXF/js-confuser/issues/79 - return () => { - // Use an Identifier with the raw string - this.replace(object, Identifier(object.raw)); - }; - } - - if ( - this.options.stringEncoding && - this.stringEncoding.match(object, parents) - ) { - return this.stringEncoding.transform(object, parents); - } - } -} +import { PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import * as t from "@babel/types"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("finalizer"); + + return { + visitor: { + NumberLiteral: { + exit(path) { + if (me.options.hexadecimalNumbers) { + const { value } = path.node; + + // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator. + // This code handles it regardless + var isNegative = value < 0; + var hex = Math.abs(value).toString(16); + + var newStr = (isNegative ? "-" : "") + "0x" + hex; + + path.replaceWith(t.identifier(newStr)); + path.skip(); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 99aa47f..97afe86 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -1,557 +1,154 @@ -import { ok } from "assert"; -import { - noRenameVariablePrefix, - predictableFunctionTag, - reservedIdentifiers, -} from "../constants"; -import { ObfuscateOrder } from "../order"; -import { walk } from "../traverse"; -import { - Identifier, - ReturnStatement, - VariableDeclaration, - VariableDeclarator, - CallExpression, - MemberExpression, - ExpressionStatement, - AssignmentExpression, - Node, - BlockStatement, - ArrayPattern, - FunctionExpression, - ObjectExpression, - Property, - Literal, - AwaitExpression, - FunctionDeclaration, - SpreadElement, - UnaryExpression, - RestElement, -} from "../util/gen"; -import { getIdentifierInfo } from "../util/identifiers"; -import { - getBlockBody, - prepend, - clone, - getDefiningContext, - computeFunctionLength, -} from "../util/insert"; -import { shuffle } from "../util/random"; -import Transform from "./transform"; -import { FunctionLengthTemplate } from "../templates/functionLength"; -import { ObjectDefineProperty } from "../templates/globals"; - -/** - * Flatten takes functions and isolates them from their original scope, and brings it to the top level of the program. - * - * An additional `flatObject` parameter is passed in, giving access to the original scoped variables. - * - * The `flatObject` uses `get` and `set` properties to allow easy an AST transformation: - * - * ```js - * // Input - * function myFunction(myParam){ - * modified = true; - * if(reference) { - * - * } - * ... - * console.log(myParam); - * } - * - * // Output - * function myFunction_flat([myParam], flatObject){ - * flatObject["set_modified"] = true; - * if(flatObject["get_reference"]) { - * - * } - * ... - * console.log(myParam) - * } - * - * function myFunction(){ - * var flatObject = { - * set set_modified(v) { modified = v } - * get get_reference() { return reference } - * } - * return myFunction_flat([...arguments], flatObject) - * } - * ``` - * - * Flatten is used to make functions eligible for the RGF transformation. - * - * - `myFunction_flat` is now eligible because it does not rely on outside scoped variables - */ -export default class Flatten extends Transform { - isDebug = false; - - definedNames: Map>; - - // Array of FunctionDeclaration nodes - flattenedFns: Node[]; - gen: ReturnType; - - functionLengthName: string; - - constructor(o) { - super(o, ObfuscateOrder.Flatten); - - this.definedNames = new Map(); - this.flattenedFns = []; - this.gen = this.getGenerator("mangled"); - - if (this.isDebug) { - console.warn("Flatten debug mode"); - } - } - - apply(tree) { - super.apply(tree); - - if (this.flattenedFns.length) { - prepend(tree, ...this.flattenedFns); - } - } - - match(object: Node, parents: Node[]) { - return ( - (object.type == "FunctionDeclaration" || - object.type === "FunctionExpression") && - object.body.type == "BlockStatement" && - !object.$requiresEval && - !object.generator && - !object.params.find((x) => x.type !== "Identifier") - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (parents[0]) { - // Don't change class methods - if ( - parents[0].type === "MethodDefinition" && - parents[0].value === object - ) { - return; - } - - // Don't change getter/setter methods - if ( - parents[0].type === "Property" && - parents[0].value === object && - (parents[0].kind !== "init" || parents[0].method) - ) { - return; - } - } - - ok( - object.type === "FunctionDeclaration" || - object.type === "FunctionExpression" - ); - - // The name is purely for debugging purposes - var currentFnName = - object.type === "FunctionDeclaration" - ? object.id?.name - : parents[0]?.type === "VariableDeclarator" && - parents[0].id?.type === "Identifier" && - parents[0].id?.name; - - if (parents[0]?.type === "Property" && parents[0]?.key) { - currentFnName = currentFnName || String(parents[0]?.key?.name); - } - - if (!currentFnName) currentFnName = "unnamed"; - - var definedMap = new Map>(); - - var illegal = new Set(); - var isIllegal = false; - - var identifierNodes: [ - Node, - Node[], - ReturnType - ][] = []; - - walk(object, parents, (o, p) => { - if ( - (o.type === "Identifier" && o.name === "arguments") || - (o.type === "UnaryExpression" && o.operator === "delete") || - o.type == "ThisExpression" || - o.type == "Super" || - o.type == "MetaProperty" - ) { - isIllegal = true; - return "EXIT"; - } - - if ( - o.type == "Identifier" && - o !== object.id && - !this.options.globalVariables.has(o.name) && - !reservedIdentifiers.has(o.name) - ) { - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced) { - return; - } - - if ( - info.spec.isExported || - o.name.startsWith(noRenameVariablePrefix) - ) { - illegal.add(o.name); - - return; - } - - if (info.spec.isDefined) { - var definingContext = getDefiningContext(o, p); - - if (!definedMap.has(definingContext)) { - definedMap.set(definingContext, new Set([o.name])); - } else { - definedMap.get(definingContext).add(o.name); - } - return; - } - - var isDefined = p.find( - (x) => definedMap.has(x) && definedMap.get(x).has(o.name) - ); - - if (!isDefined) { - identifierNodes.push([o, p, info]); - } - } - - if (o.type == "TryStatement") { - isIllegal = true; - return "EXIT"; - } - }); - - if (isIllegal) { - return; - } - if (illegal.size) { - return; - } - - var newFnName = - this.getPlaceholder() + - "_flat_" + - currentFnName + - predictableFunctionTag; - var flatObjectName = this.getPlaceholder() + "_flat_object"; - - const getFlatObjectMember = (propertyName: string) => { - return MemberExpression( - Identifier(flatObjectName), - Literal(propertyName), - true - ); - }; - - var getterPropNames: { [identifierName: string]: string } = - Object.create(null); - var setterPropNames: { [identifierName: string]: string } = - Object.create(null); - var typeofPropNames: { [identifierName: string]: string } = - Object.create(null); - var callPropNames: { [identifierName: string]: string } = - Object.create(null); - - for (var [o, p, info] of identifierNodes) { - var identifierName: string = o.name; - if ( - p.find( - (x) => definedMap.has(x) && definedMap.get(x).has(identifierName) - ) - ) - continue; - - ok(!info.spec.isDefined); - - var type = info.spec.isModified ? "setter" : "getter"; - - switch (type) { - case "setter": - var setterPropName = setterPropNames[identifierName]; - if (typeof setterPropName === "undefined") { - // No getter function made yet, make it (Try to re-use getter name if available) - setterPropName = - getterPropNames[identifierName] || - (this.isDebug ? "set_" + identifierName : this.gen.generate()); - setterPropNames[identifierName] = setterPropName; - } - - // If an update expression, ensure a getter function is also available. Ex: a++ - if (p[0].type === "UpdateExpression") { - getterPropNames[identifierName] = setterPropName; - } else { - // If assignment on member expression, ensure a getter function is also available: Ex. myObject.property = ... - var assignmentIndex = p.findIndex( - (x) => x.type === "AssignmentExpression" - ); - if ( - assignmentIndex !== -1 && - p[assignmentIndex].left.type !== "Identifier" - ) { - getterPropNames[identifierName] = setterPropName; - } - } - - // calls flatObject.set_identifier_value(newValue) - this.replace(o, getFlatObjectMember(setterPropName)); - break; - - case "getter": - var getterPropName = getterPropNames[identifierName]; - if (typeof getterPropName === "undefined") { - // No getter function made yet, make it (Try to re-use setter name if available) - getterPropName = - setterPropNames[identifierName] || - (this.isDebug ? "get_" + identifierName : this.gen.generate()); - getterPropNames[identifierName] = getterPropName; - } - - // Typeof expression check - if ( - p[0].type === "UnaryExpression" && - p[0].operator === "typeof" && - p[0].argument === o - ) { - var typeofPropName = typeofPropNames[identifierName]; - if (typeof typeofPropName === "undefined") { - // No typeof getter function made yet, make it (Don't re-use getter/setter names) - typeofPropName = this.isDebug - ? "get_typeof_" + identifierName - : this.gen.generate(); - typeofPropNames[identifierName] = typeofPropName; - } - - // Replace the entire unary expression not just the identifier node - // calls flatObject.get_typeof_identifier() - this.replace(p[0], getFlatObjectMember(typeofPropName)); - break; - } - - // Bound call-expression check - if (p[0].type === "CallExpression" && p[0].callee === o) { - var callPropName = callPropNames[identifierName]; - if (typeof callPropName === "undefined") { - callPropName = this.isDebug - ? "call_" + identifierName - : this.gen.generate(); - callPropNames[identifierName] = callPropName; - } - - // Replace the entire call expression not just the identifier node - // calls flatObject.call_identifier(...arguments) - this.replace( - p[0], - CallExpression( - getFlatObjectMember(callPropName), - p[0].arguments - ) - ); - break; - } - - // calls flatObject.get_identifier_value() - this.replace(o, getFlatObjectMember(getterPropName)); - break; - } - } - - // Create the getter and setter functions - var flatObjectProperties: Node[] = []; - - // Getter functions - for (var identifierName in getterPropNames) { - var getterPropName = getterPropNames[identifierName]; - - flatObjectProperties.push( - Property( - Literal(getterPropName), - FunctionExpression( - [], - [ReturnStatement(Identifier(identifierName))] - ), - true, - "get" - ) - ); - } - - // Get typeof functions - for (var identifierName in typeofPropNames) { - var typeofPropName = typeofPropNames[identifierName]; - - flatObjectProperties.push( - Property( - Literal(typeofPropName), - FunctionExpression( - [], - [ - ReturnStatement( - UnaryExpression("typeof", Identifier(identifierName)) - ), - ] - ), - true, - "get" - ) - ); - } - - // Call functions - for (var identifierName in callPropNames) { - var callPropName = callPropNames[identifierName]; - var argumentsName = this.getPlaceholder(); - flatObjectProperties.push( - Property( - Literal(callPropName), - FunctionExpression( - [RestElement(Identifier(argumentsName))], - [ - ReturnStatement( - CallExpression(Identifier(identifierName), [ - SpreadElement(Identifier(argumentsName)), - ]) - ), - ] - ), - true - ) - ); - } - - // Setter functions - for (var identifierName in setterPropNames) { - var setterPropName = setterPropNames[identifierName]; - var newValueParameterName = this.getPlaceholder(); - - flatObjectProperties.push( - Property( - Literal(setterPropName), - FunctionExpression( - [Identifier(newValueParameterName)], - [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(identifierName), - Identifier(newValueParameterName) - ) - ), - ] - ), - true, - "set" - ) - ); - } - - if (!this.isDebug) { - shuffle(flatObjectProperties); - } - - var newBody = getBlockBody(object.body); - - // Remove 'use strict' directive - if (newBody.length > 0 && newBody[0].directive) { - newBody.shift(); - } - - var newFunctionDeclaration = FunctionDeclaration( - newFnName, - [ArrayPattern(clone(object.params)), Identifier(flatObjectName)], - newBody - ); - - newFunctionDeclaration.async = !!object.async; - newFunctionDeclaration.generator = false; - - this.flattenedFns.push(newFunctionDeclaration); - - var argumentsName = this.getPlaceholder(); - - // newFn.call([...arguments], flatObject) - var callExpression = CallExpression(Identifier(newFnName), [ - Identifier(argumentsName), - Identifier(flatObjectName), - ]); - - var newObjectBody: Node[] = [ - // var flatObject = { get(), set() }; - VariableDeclaration([ - VariableDeclarator( - flatObjectName, - ObjectExpression(flatObjectProperties) - ), - ]), - - ReturnStatement( - newFunctionDeclaration.async - ? AwaitExpression(callExpression) - : callExpression - ), - ]; - - object.body = BlockStatement(newObjectBody); - - // Preserve function.length property - var originalFunctionLength = computeFunctionLength(object.params); - - object.params = [RestElement(Identifier(argumentsName))]; - - if (this.options.preserveFunctionLength && originalFunctionLength !== 0) { - if (!this.functionLengthName) { - this.functionLengthName = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || object, - FunctionLengthTemplate.single({ - name: this.functionLengthName, - ObjectDefineProperty: this.createInitVariable( - ObjectDefineProperty, - parents - ), - }) - ); - } - - if (object.type === "FunctionDeclaration") { - var body = parents[0]; - if (Array.isArray(body)) { - var index = body.indexOf(object); - - body.splice( - index + 1, - 0, - ExpressionStatement( - CallExpression(Identifier(this.functionLengthName), [ - Identifier(object.id.name), - Literal(originalFunctionLength), - ]) - ) - ); - } - } else { - ok(object.type === "FunctionExpression"); - this.replace( - object, - CallExpression(Identifier(this.functionLengthName), [ - { ...object }, - Literal(originalFunctionLength), - ]) - ); - } - } - }; - } -} +import * as t from "@babel/types"; +import { NodePath, PluginObj } from "@babel/core"; +import { + insertIntoNearestBlockScope, + isReservedIdentifier, +} from "../utils/ast-utils"; +import { PluginArg } from "./plugin"; +import { computeProbabilityMap } from "../probability"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("flatten"); + + function flattenFunction( + path: NodePath + ) { + const functionName = path.node.id ? path.node.id.name : "anonymous"; + if (!computeProbabilityMap(me.options.flatten, (x) => x, functionName)) { + return; + } + + const flatObjectName = `__${functionName}_flat_object`; + const newFnName = `__${functionName}_flat_fn`; + + const getterProps: t.ObjectMember[] = []; + const setterProps: t.ObjectMember[] = []; + const flatObjectProperties: t.ObjectMember[] = []; + + // Traverse function to identify variables to be replaced with flat object properties + path.traverse({ + ReferencedIdentifier: { + exit(identifierPath) { + const identifierName = identifierPath.node.name; + + if (identifierName === "value") { + return false; + } + + if ( + identifierPath.isBindingIdentifier() || + !path.scope.hasBinding(identifierName) || + path.scope.hasOwnBinding(identifierName) || + path.scope.hasGlobal(identifierName) || + isReservedIdentifier(identifierPath.node) + ) { + // Skip identifiers that are local to this function + return; + } + + // Create getter and setter properties in the flat object + const getterPropName = `_prop_${identifierName}`; + const setterPropName = `_prop_${identifierName}`; + + console.log(identifierName, "Extracting into", getterPropName); + + getterProps.push( + t.objectMethod( + "get", + t.stringLiteral(getterPropName), + [], + t.blockStatement([ + t.returnStatement(t.identifier(identifierName)), + ]), + false, + false, + false + ) + ); + + setterProps.push( + t.objectMethod( + "set", + t.stringLiteral(setterPropName), + [t.identifier("value")], + t.blockStatement([ + t.expressionStatement( + t.assignmentExpression( + "=", + t.identifier(identifierName), + t.identifier("value") + ) + ), + ]), + false, + false, + false + ) + ); + + // Replace identifier with a reference to the flat object property + identifierPath.replaceWith( + t.memberExpression( + t.identifier(flatObjectName), + t.stringLiteral(getterPropName), + true + ) + ); + identifierPath.skip(); + }, + }, + }); + + flatObjectProperties.push(...getterProps, ...setterProps); + + // Create the flat object variable declaration + const flatObjectDeclaration = t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(flatObjectName), + t.objectExpression(flatObjectProperties) + ), + ]); + + // Create the new flattened function + const flattenedFunctionDeclaration = t.functionDeclaration( + t.identifier(newFnName), + [t.arrayPattern(path.node.params), t.identifier(flatObjectName)], + path.node.body + ); + + // Replace original function body with a call to the flattened function + path.node.body = t.blockStatement([ + flatObjectDeclaration, + t.returnStatement( + t.callExpression(t.identifier(newFnName), [ + t.identifier("arguments"), + t.identifier(flatObjectName), + ]) + ), + ]); + + // Add the new flattened function at the top level + const newPaths = insertIntoNearestBlockScope( + path, + flattenedFunctionDeclaration + ); + + newPaths.forEach((newPath) => newPath.stop()); + path.stop(); + } + + return { + visitor: { + FunctionDeclaration: { + exit(path: NodePath) { + flattenFunction(path); + }, + }, + FunctionExpression: { + exit(path: NodePath) { + flattenFunction(path); + }, + }, + }, + }; +}; diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index f04b4fd..b3c5482 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -1,297 +1,93 @@ +import * as t from "@babel/types"; +import { NodePath, PluginObj } from "@babel/core"; +import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; -import Transform from "../transform"; -import { ObfuscateOrder } from "../../order"; -import { - Node, - Location, - CallExpression, - Identifier, - Literal, - FunctionDeclaration, - ReturnStatement, - MemberExpression, - SwitchStatement, - SwitchCase, - LogicalExpression, - VariableDeclarator, - FunctionExpression, - ExpressionStatement, - AssignmentExpression, - VariableDeclaration, - BreakStatement, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; -import { chance, getRandomInteger } from "../../util/random"; -import { - predictableFunctionTag, - reservedIdentifiers, - variableFunctionName, -} from "../../constants"; -import { ComputeProbabilityMap } from "../../probability"; -import GlobalAnalysis from "./globalAnalysis"; -import { createGetGlobalTemplate } from "../../templates/bufferToString"; -import { isJSConfuserVar } from "../../util/guard"; +import { PluginArg } from "../plugin"; -/** - * Global Concealing hides global variables being accessed. - * - * - Any variable that is not defined is considered "global" - */ -export default class GlobalConcealing extends Transform { - globalAnalysis: GlobalAnalysis; - ignoreGlobals = new Set([ - "require", - "__dirname", - "eval", - variableFunctionName, - ]); +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("globalConcealing"); - constructor(o) { - super(o, ObfuscateOrder.GlobalConcealing); + var globalMapping = new Map(), + globalFnName = me.getPlaceholder() + "_getGlobal", + globalVarName = me.getPlaceholder() + "_globalVar", + gen = new NameGen(); - this.globalAnalysis = new GlobalAnalysis(o); - this.before.push(this.globalAnalysis); - } - - match(object: Node, parents: Node[]) { - return object.type == "Program"; - } - - transform(object: Node, parents: Node[]) { - return () => { - var globals: { [name: string]: Location[] } = this.globalAnalysis.globals; - this.globalAnalysis.notGlobals.forEach((del) => { - delete globals[del]; - }); + // Create the getGlobal function using a template + function createGetGlobalFunction(): t.FunctionDeclaration { + const createSwitchStatement = () => { + const cases = Array.from(globalMapping.keys()).map((originalName) => { + var mappedKey = globalMapping.get(originalName); - for (var varName of this.ignoreGlobals) { - delete globals[varName]; - } - - reservedIdentifiers.forEach((x) => { - delete globals[x]; - }); - - Object.keys(globals).forEach((x) => { - if (this.globalAnalysis.globals[x].length < 1) { - delete globals[x]; - } else if ( - !ComputeProbabilityMap(this.options.globalConcealing, (x) => x, x) - ) { - delete globals[x]; - } + return t.switchCase(t.stringLiteral(mappedKey), [ + t.returnStatement(t.identifier(originalName)), + ]); }); - if (Object.keys(globals).length > 0) { - var usedStates = new Set(); - - // Make getter function - - // holds "window" or "global" - var globalVar = this.getPlaceholder(); - - var getGlobalVariableFnName = - this.getPlaceholder() + predictableFunctionTag; - - // Returns global variable or fall backs to `this` - var getGlobalVariableFn = createGetGlobalTemplate( - this, - object, - parents - ).compile({ - getGlobalFnName: getGlobalVariableFnName, - }); - - // 2. Replace old accessors - var globalFn = this.getPlaceholder() + predictableFunctionTag; - - var newNames: { [globalVarName: string]: number } = Object.create(null); - - Object.keys(globals).forEach((name) => { - var locations: Location[] = globals[name]; - var state: number; - do { - state = getRandomInteger(-1000, 1000 + usedStates.size); - } while (usedStates.has(state)); - usedStates.add(state); - - newNames[name] = state; - - locations.forEach(([node, p]) => { - if (p.find((x) => x.$multiTransformSkip)) { - return; - } - - var newExpression = CallExpression(Identifier(globalFn), [ - Literal(state), - ]); - - this.replace(node, newExpression); - - if ( - this.options.lock?.tamperProtection && - this.lockTransform.nativeFunctionName - ) { - var isMemberExpression = false; - var nameAndPropertyPath = [name]; - var callExpression: Node; - - var index = 0; - do { - if (p[index].type === "CallExpression") { - callExpression = p[index]; - break; - } - - var memberExpression = p[index]; - if (memberExpression.type !== "MemberExpression") return; - var property = memberExpression.property; - var stringValue = - property.type === "Literal" - ? property.value - : memberExpression.computed - ? null - : property.type === "Identifier" - ? property.name - : null; - - if (!stringValue) return; - - isMemberExpression = true; - nameAndPropertyPath.push(stringValue); - index++; - } while (index < p.length); + return t.switchStatement(t.identifier("mapping"), cases); + }; - if ( - !this.lockTransform.shouldTransformNativeFunction( - nameAndPropertyPath - ) - ) - return; + return t.functionDeclaration( + t.identifier(globalFnName), + [t.identifier("mapping")], + t.blockStatement([createSwitchStatement()]) + ); + } - if (callExpression && callExpression.type === "CallExpression") { - if (isMemberExpression) { - callExpression.callee = CallExpression( - Identifier(this.lockTransform.nativeFunctionName), - [ - callExpression.callee.object, - callExpression.callee.computed - ? callExpression.callee.property - : Literal( - callExpression.callee.property.name || - callExpression.callee.property.value - ), - ] - ); - } else { - callExpression.callee = CallExpression( - Identifier(this.lockTransform.nativeFunctionName), - [{ ...callExpression.callee }] - ); - } + return { + visitor: { + Program: { + exit(programPath: NodePath) { + // Insert the getGlobal function at the top of the program body + const getGlobalFunction = createGetGlobalFunction(); + + var p = programPath.unshiftContainer("body", getGlobalFunction); + var p2 = programPath.unshiftContainer( + "body", + new Template(` + var {globalVarName} = (function (){ + try { + return window; + } catch ( e ) { + } + try { + return global; + } catch ( e ) { } - } - }); - }); - - // Adds all global variables to the switch statement - this.options.globalVariables.forEach((name) => { - if (!newNames[name]) { - var state; - do { - state = getRandomInteger( - 0, - 1000 + usedStates.size + this.options.globalVariables.size * 100 - ); - } while (usedStates.has(state)); - usedStates.add(state); - - newNames[name] = state; - } - }); - - var indexParamName = this.getPlaceholder(); - var returnName = this.getPlaceholder(); - - var functionDeclaration = FunctionDeclaration( - globalFn, - [Identifier(indexParamName)], - [ - VariableDeclaration(VariableDeclarator(returnName)), - SwitchStatement( - Identifier(indexParamName), - Object.keys(newNames).map((name) => { - var code = newNames[name]; - var body: Node[] = [ - ReturnStatement( - MemberExpression(Identifier(globalVar), Literal(name), true) - ), - ]; - if (chance(50)) { - body = [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(returnName), - LogicalExpression( - "||", - Literal(name), - MemberExpression( - Identifier(globalVar), - Literal(name), - true - ) - ) - ) - ), - BreakStatement(), - ]; - } - return SwitchCase(Literal(code), body); - }) - ), - ReturnStatement( - MemberExpression( - Identifier(globalVar), - Identifier(returnName), - true - ) - ), - ] - ); + return this; + })(); - var tempVar = this.getPlaceholder(); + `).compile({ globalVarName: globalVarName }) + ); - var variableDeclaration = new Template(` - var ${globalVar}; - `).single(); + // Skip transformation for the inserted getGlobal function + programPath.get("body")[0].stop(); + programPath.get("body")[1].stop(); + }, + }, + ReferencedIdentifier(path: NodePath) { + var identifierName = path.node.name; - variableDeclaration.declarations.push( - VariableDeclarator( - tempVar, - CallExpression( - MemberExpression( - FunctionExpression( - [], - [ - ...getGlobalVariableFn, - new Template( - `return ${globalVar} = ${getGlobalVariableFnName}["call"](this)` - ).single(), - ] - ), - Literal("call"), - true - ), - [] - ) - ) - ); + if ( + !path.scope.hasGlobal(identifierName) || + path.scope.hasOwnBinding(identifierName) + ) { + return; + } + var mapping = globalMapping.get(identifierName); + if (!mapping) { + mapping = gen.generate(); + globalMapping.set(identifierName, mapping); + } - prepend(object, variableDeclaration); - append(object, functionDeclaration); - } - }; - } -} + // Replace global reference with getGlobal("name") + const callExpression = t.callExpression(t.identifier(globalFnName), [ + t.stringLiteral(mapping), + ]); + + path.replaceWith(callExpression); + path.skip(); + }, + }, + }; +}; diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 7d7a5b3..6caff70 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -1,300 +1,58 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { walk } from "../../traverse"; -import { Literal, Node } from "../../util/gen"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { - isVarContext, - isContext, - isLexContext, - clone, - isFunction, -} from "../../util/insert"; -import Transform from "../transform"; -import { - noRenameVariablePrefix, - placeholderVariablePrefix, - reservedIdentifiers, - variableFunctionName, -} from "../../constants"; -import { ComputeProbabilityMap } from "../../probability"; -import VariableAnalysis from "./variableAnalysis"; +import { NodePath, PluginObj } from "@babel/core"; +import { Binding, Scope } from "@babel/traverse"; +import { PluginArg } from "../plugin"; +import * as t from "@babel/types"; -/** - * Rename variables to randomly generated names. - * - * - 1. First collect data on identifiers in all scope using 'VariableAnalysis' - * - 2. After 'VariableAnalysis' is finished start applying to each scope (top-down) - * - 3. Each scope, find the all names used here and exclude those names from being re-named - * - 4. Now loop through all the defined names in this scope and set it to a random name (or re-use previously generated name) - * - 5. Update all the Identifiers node's 'name' property to reflect this change - */ -export default class RenameVariables extends Transform { - // Names already used - generated: string[]; +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("renameVariables"); - // Map of Context->Object of changes - changed: Map; + // Keep track of available names to reuse + const availableNames: string[] = []; - // Ref to VariableAnalysis data - variableAnalysis: VariableAnalysis; + const generateNewName = (scope: Scope): string => { + let newName; - // Option to re-use previously generated names - reusePreviousNames = true; + // Always generate a new name + newName = availableNames.pop() || me.generateRandomIdentifier(); - constructor(o) { - super(o, ObfuscateOrder.RenameVariables); - - this.changed = new Map(); - - // 1. - this.variableAnalysis = new VariableAnalysis(o); - this.before.push(this.variableAnalysis); - this.generated = []; - } - - match(object: Node, parents: Node[]) { - return isContext(object) || object.type === "Identifier"; - } - - transformContext(object: Node, parents: Node[]) { - // 2. Notice this is on 'onEnter' (top-down) - var isGlobal = object.type == "Program"; - var type = isGlobal - ? "root" - : isVarContext(object) - ? "var" - : isLexContext(object) - ? "lex" - : undefined; - - ok(type); + // Ensure the new name isn't already used in the scope + while (scope.hasBinding(newName) || scope.hasGlobal(newName)) { + newName = me.generateRandomIdentifier(); + } - var newNames = Object.create(null); + return newName; + }; - var defined = this.variableAnalysis.defined.get(object) || new Set(); - var references = this.variableAnalysis.references.get(object) || new Set(); + var renamedSet = new WeakSet(); - // No changes needed here - if (!defined && !this.changed.has(object)) { - this.changed.set(object, Object.create(null)); - return; - } + return { + visitor: { + Scopable: { + exit(path: NodePath) { + var createdNames = []; - // Names possible to be re-used here - var possible = new Set(); + Object.keys(path.scope.bindings).forEach((name) => { + me.log("Checking", name); - // 3. Try to re-use names when possible - if (this.reusePreviousNames && this.generated.length && !isGlobal) { - var allReferences = new Set(); - var nope = new Set(defined); - walk(object, [], (o, p) => { - var ref = this.variableAnalysis.references.get(o); - if (ref) { - ref.forEach((x) => allReferences.add(x)); - } + const binding = path.scope.bindings[name]; + if (renamedSet.has(binding)) return; - var def = this.variableAnalysis.defined.get(o); - if (def) { - def.forEach((x) => allReferences.add(x)); - } - }); + const newName = generateNewName(path.scope); - var passed = new Set(); - parents.forEach((p) => { - var changes = this.changed.get(p); - if (changes) { - Object.keys(changes).forEach((x) => { - var name = changes[x]; + me.log("Renaming", name, "to", newName); - if (!allReferences.has(x) && !references.has(x)) { - passed.add(name); - } else { - nope.add(name); + path.scope.rename(name, newName); + renamedSet.add(binding); + if (name !== newName) { + createdNames.push(newName); } }); - } - }); - - nope.forEach((x) => passed.delete(x)); - - possible = passed; - } - // 4. Defined names to new names - for (var name of defined) { - if ( - !name.startsWith(noRenameVariablePrefix) && // Variables prefixed with '__NO_JS_CONFUSER_RENAME__' are never renamed - (isGlobal && !name.startsWith(placeholderVariablePrefix) // Variables prefixed with '__p_' are created by the obfuscator, always renamed - ? ComputeProbabilityMap(this.options.renameGlobals, (x) => x, name) - : true) && - ComputeProbabilityMap( - // Check the user's option for renaming variables - this.options.renameVariables, - (x) => x, - name, - isGlobal - ) - ) { - // Create a new name from (1) or (2) methods - var newName: string; - do { - if (possible.size) { - // (1) Re-use previously generated name - var first = possible.values().next().value; - possible.delete(first); - newName = first; - } else { - // (2) Create a new name with `generateIdentifier` function - var generatedName = this.generateIdentifier(); + me.log("Created names", createdNames); - newName = generatedName; - this.generated.push(generatedName); - } - } while (this.variableAnalysis.globals.has(newName)); // Ensure global names aren't overridden - - newNames[name] = newName; - } else { - // This variable name was deemed not to be renamed. - newNames[name] = name; - } - } - - // console.log(object.type, newNames); - this.changed.set(object, newNames); - } - - transformIdentifier(object: Node, parents: Node[]) { - const identifierName = object.name; - if ( - reservedIdentifiers.has(identifierName) || - this.options.globalVariables.has(identifierName) - ) { - return; - } - - if (object.$renamed) { - return; - } - - var info = getIdentifierInfo(object, parents); - - if (info.spec.isExported) { - return; - } - - if (!info.spec.isReferenced) { - return; - } - - var contexts = [object, ...parents].filter((x) => isContext(x)); - var newName = null; - - // Function default parameter check! - var functionIndices = []; - for (var i in parents) { - if (isFunction(parents[i])) { - functionIndices.push(i); - } - } - - for (var functionIndex of functionIndices) { - if (parents[functionIndex].id === object) { - // This context is not referenced, so remove it - contexts = contexts.filter( - (context) => context != parents[functionIndex] - ); - continue; - } - if (parents[functionIndex].params === parents[functionIndex - 1]) { - var isReferencedHere = true; - - var slicedParents = parents.slice(0, functionIndex); - var forIndex = 0; - for (var parent of slicedParents) { - var childNode = slicedParents[forIndex - 1] || object; - - if ( - parent.type === "AssignmentPattern" && - parent.right === childNode - ) { - isReferencedHere = false; - break; - } - - forIndex++; - } - - if (!isReferencedHere) { - // This context is not referenced, so remove it - contexts = contexts.filter( - (context) => context != parents[functionIndex] - ); - } - } - } - - for (var check of contexts) { - if ( - this.variableAnalysis.defined.has(check) && - this.variableAnalysis.defined.get(check).has(identifierName) - ) { - if ( - this.changed.has(check) && - this.changed.get(check)[identifierName] - ) { - newName = this.changed.get(check)[identifierName]; - break; - } - } - } - - if (newName && typeof newName === "string") { - // Strange behavior where the `local` and `imported` objects are the same - if (info.isImportSpecifier) { - var importSpecifierIndex = parents.findIndex( - (x) => x.type === "ImportSpecifier" - ); - if ( - importSpecifierIndex != -1 && - parents[importSpecifierIndex].imported === - (parents[importSpecifierIndex - 1] || object) && - parents[importSpecifierIndex].imported && - parents[importSpecifierIndex].imported.type === "Identifier" - ) { - parents[importSpecifierIndex].imported = clone( - parents[importSpecifierIndex - 1] || object - ); - } - } - - if ( - parents[1] && - parents[1].type === "CallExpression" && - parents[1].arguments === parents[0] - ) { - if ( - parents[1].callee.type === "Identifier" && - parents[1].callee.name === variableFunctionName - ) { - this.replace(parents[1], Literal(newName)); - return; - } - } - - // console.log(o.name, "->", newName); - // 5. Update Identifier node's 'name' property - object.name = newName; - object.$renamed = true; - } - } - - transform(object: Node, parents: Node[]) { - var matchType = object.type === "Identifier" ? "Identifier" : "Context"; - if (matchType === "Identifier") { - this.transformIdentifier(object, parents); - } else { - this.transformContext(object, parents); - } - } -} + availableNames.push(...createdNames); + }, + }, + }, + }; +}; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts new file mode 100644 index 0000000..0c63a8a --- /dev/null +++ b/src/transforms/plugin.ts @@ -0,0 +1,49 @@ +import { PluginObj } from "@babel/core"; +import * as babelTypes from "@babel/types"; +import Obfuscator from "../obfuscator"; +import { getRandomString } from "../utils/random-utils"; + +export type PluginFunction = (pluginArg: PluginArg) => PluginObj["visitor"]; + +export type PluginArg = { + Plugin: (name: string) => PluginInstance; +}; + +export class PluginInstance { + constructor( + public pluginOptions: { name?: string; order?: number }, + public obfuscator: Obfuscator + ) {} + + get name() { + return this.pluginOptions.name || "unnamed"; + } + + get options() { + return this.obfuscator.options; + } + + getPlaceholder(suffix = "") { + return "__p_" + getRandomString(6) + (suffix ? "_" + suffix : ""); + } + + generateRandomIdentifier() { + return "_" + getRandomString(6); + } + + log(...messages: any[]) { + if (this.options.verbose) { + console.log(`[${this.name}]`, ...messages); + } + } + + warn(...messages: any[]) { + if (this.options.verbose) { + console.warn(`[${this.name}]`, ...messages); + } + } + + error(...messages: any[]) { + throw new Error(`[${this.name}] ${messages.join(", ")}`); + } +} diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 31f7f20..d9e9c35 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -1,254 +1,93 @@ -import Transform from "./transform"; - -import { - BlockStatement, - Identifier, - LabeledStatement, - Literal, - Node, - ReturnStatement, -} from "../util/gen"; -import { ObfuscateOrder } from "../order"; -import { clone, getFunction } from "../util/insert"; -import { getIdentifierInfo } from "../util/identifiers"; -import { isLoop } from "../util/compare"; -import { ExitCallback, walk } from "../traverse"; -import { variableFunctionName } from "../constants"; - -/** - * Preparation arranges the user's code into an AST the obfuscator can easily transform. - * - * ExplicitIdentifiers - * - `object.IDENTIFIER` -> `object['IDENTIFIER']` // Now String Concealing can apply on it - * - `{ IDENTIFIER: ... }` -> `{ "IDENTIFIER": ... }` - * - * ExplicitDeclarations - * - `var a,b,c` -> `var a; var b; var c;` // Now Stack can apply on it - * - * Block - * - `x => x * 2` -> `x => { return x * 2 }` // Change into Block Statements - * - `if(true) return` -> `if (true) { return }` - * - `while(a) a--;` -> `while(a) { a-- }` - * - * Label - * - `for(...) { break; }` -> `_1: for(...) { break _1; }` - * - `switch(v) { case 1...break }` -> `_2: switch(v) { case 1...break _2; }` - * - // Control Flow Flattening can safely apply now - */ -export default class Preparation extends Transform { - constructor(o) { - super(o, ObfuscateOrder.Preparation); - } - - match(object: Node, parents: Node[]) { - return !!object.type; - } - - transform(object: Node, parents: Node[]): void | ExitCallback { - // ExplicitIdentifiers - if (object.type === "Identifier") { - return this.transformExplicitIdentifiers(object, parents); - } - - // __JS_CONFUSER_VAR__ - Remove when Rename Variables is disabled - if ( - object.type === "CallExpression" && - object.callee.type === "Identifier" && - object.callee.name === variableFunctionName - ) { - if (object.arguments[0].type === "Identifier") { - if (!this.obfuscator.transforms["RenameVariables"]) { - return () => { - this.replace(object, Literal(object.arguments[0].name)); - }; - } - } - } - - // ExplicitDeclarations - if (object.type === "VariableDeclaration") { - return this.transformExplicitDeclarations(object, parents); - } - - // Block - switch (object.type) { - /** - * People use shortcuts and its harder to parse. - * - * - `if (a) b()` -> `if (a) { b() }` - * - Ensures all bodies are `BlockStatement`, not individual expression statements - */ - case "IfStatement": - if (object.consequent.type != "BlockStatement") { - object.consequent = BlockStatement([clone(object.consequent)]); - } - if (object.alternate && object.alternate.type != "BlockStatement") { - object.alternate = BlockStatement([clone(object.alternate)]); - } - break; - - case "WhileStatement": - case "WithStatement": - case "ForStatement": - case "ForOfStatement": - case "ForInStatement": - if (object.body.type != "BlockStatement") { - object.body = BlockStatement([clone(object.body)]); - } - break; - - case "ArrowFunctionExpression": - if (object.body.type !== "BlockStatement" && object.expression) { - object.body = BlockStatement([ReturnStatement(clone(object.body))]); - object.expression = false; - } - break; - } - - // Label - if ( - isLoop(object) || - (object.type == "BlockStatement" && - parents[0] && - parents[0].type == "LabeledStatement" && - parents[0].body === object) - ) { - return this.transformLabel(object, parents); - } - } +import { PluginObj } from "@babel/core"; +import { NodePath } from "@babel/traverse"; +import { PluginArg } from "./plugin"; +import * as t from "@babel/types"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("preparation"); + + return { + visitor: { + // console.log() -> console["log"](); + MemberExpression: { + exit(path) { + if (!path.node.computed && path.node.property.type === "Identifier") { + path.node.property = t.stringLiteral(path.node.property.name); + path.node.computed = true; + } + }, + }, - /** - * Ensures every break; statement has a label to point to. - * - * This is because Control Flow Flattening adds For Loops which label-less break statements point to the nearest, - * when they actually need to point to the original statement. - */ - transformLabel(object: Node, parents: Node[]) { - return () => { - var currentLabel = - parents[0].type == "LabeledStatement" && parents[0].label.name; + // { key: true } -> { "key": true } + "Property|Method": { + exit(_path) { + let path = _path as NodePath; - var label = currentLabel || this.getPlaceholder(); + if (t.isClassPrivateProperty(path.node)) return; - walk(object, parents, (o, p) => { - if (o.type == "BreakStatement" || o.type == "ContinueStatement") { - function isContinuableStatement(x) { - return isLoop(x) && x.type !== "SwitchStatement"; + if (!path.node.computed && path.node.key.type === "Identifier") { + path.node.key = t.stringLiteral(path.node.key.name); + path.node.computed = true; } - function isBreakableStatement(x) { - return isLoop(x) || (o.label && x.type == "BlockStatement"); + }, + }, + + // var a,b,c -> var a; var b; var c; + VariableDeclaration: { + exit(path) { + if (path.node.declarations.length > 1) { + var extraDeclarations = path.node.declarations.slice(1); + path.node.declarations.length = 1; + path.insertAfter(extraDeclarations); } - - var fn = - o.type == "ContinueStatement" - ? isContinuableStatement - : isBreakableStatement; - - var loop = p.find(fn); - if (object == loop) { - if (!o.label) { - o.label = Identifier(label); - } + }, + }, + + // () => a() -> () => { return a(); } + ArrowFunctionExpression: { + exit(path: NodePath) { + if (path.node.body.type !== "BlockStatement") { + path.node.expression = false; + path.node.body = t.blockStatement([ + t.returnStatement(path.node.body), + ]); + } + }, + }, + + // if (a) b() -> if (a) { b(); } + // if (a) {b()} else c() -> if (a) { b(); } else { c(); } + IfStatement: { + exit(path) { + if (path.node.consequent.type !== "BlockStatement") { + path.node.consequent = t.blockStatement([path.node.consequent]); } - } - }); - - // Append label statement as this loop has none - if (!currentLabel) { - this.replace(object, LabeledStatement(label, { ...object })); - } - }; - } - - /** - * Transforms Identifiers (a.IDENTIFIER, {IDENTIFIER:...}) into string properties - */ - transformExplicitIdentifiers(object: Node, parents: Node[]) { - // Mark functions containing 'eval' - // Some transformations avoid functions that have 'eval' to not break them - if (object.name === "eval") { - var fn = getFunction(object, parents); - if (fn) { - fn.$requiresEval = true; - } - } - - var info = getIdentifierInfo(object, parents); - if (info.isPropertyKey || info.isAccessor) { - var propIndex = parents.findIndex( - (x) => x.type == "MethodDefinition" || x.type == "Property" - ); - - // Don't change constructor! - if (propIndex !== -1) { - if ( - parents[propIndex].type == "MethodDefinition" && - parents[propIndex].kind == "constructor" - ) { - return; - } - } - - this.replace(object, Literal(object.name)); - parents[0].computed = true; - parents[0].shorthand = false; - } - } - - /** - * Transforms VariableDeclaration into single declarations. - */ - transformExplicitDeclarations(object: Node, parents: Node[]) { - // for ( var x in ... ) {...} - var forIndex = parents.findIndex( - (x) => x.type == "ForInStatement" || x.type == "ForOfStatement" - ); - if ( - forIndex != -1 && - parents[forIndex].left == (parents[forIndex - 1] || object) - ) { - object.declarations.forEach((x) => { - x.init = null; - }); - return; - } - - var body = parents[0]; - if (isLoop(body) || body.type == "LabeledStatement") { - return; - } - - if (body.type == "ExportNamedDeclaration") { - return; - } - - if (!Array.isArray(body)) { - this.error(new Error("body is " + body.type)); - } - - if (object.declarations.length > 1) { - // Make singular - - var index = body.indexOf(object); - if (index == -1) { - this.error(new Error("index is -1")); - } - - var after = object.declarations.slice(1); - - body.splice( - index + 1, - 0, - ...after.map((x) => { - return { - type: "VariableDeclaration", - declarations: [clone(x)], - kind: object.kind, - }; - }) - ); - object.declarations.length = 1; - } - } -} + if ( + path.node.alternate && + path.node.alternate.type !== "BlockStatement" + ) { + path.node.alternate = t.blockStatement([path.node.alternate]); + } + }, + }, + + // for() d() -> for() { d(); } + // while(a) b() -> while(a) { b(); } + "ForStatement|ForInStatement|ForOfStatement|WhileStatement": { + exit(_path) { + var path = _path as NodePath< + | t.ForStatement + | t.ForInStatement + | t.ForOfStatement + | t.WhileStatement + >; + + if (path.node.body.type !== "BlockStatement") { + path.node.body = t.blockStatement([path.node.body]); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index 86e0a07..d71c8ea 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -1,254 +1,67 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../order"; -import { ComputeProbabilityMap } from "../probability"; -import Template from "../templates/template"; -import { - BinaryExpression, - CallExpression, - ExpressionStatement, - ForStatement, - FunctionExpression, - Identifier, - Literal, - MemberExpression, - ReturnStatement, - UpdateExpression, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { clone, prepend } from "../util/insert"; -import { getRandomInteger } from "../util/random"; -import Transform from "./transform"; - -var Hash = function (s) { - var a = 1, - c = 0, - h, - o; - if (s) { - a = 0; - for (h = s.length - 1; h >= 0; h--) { - o = s.charCodeAt(h); - a = ((a << 6) & 268435455) + o + (o << 14); - c = a & 266338304; - a = c !== 0 ? a ^ (c >> 21) : a; - } - } - return ~~String(a).slice(0, 3); -}; - -var HashTemplate = new Template( - ` - var {name} = function(arr) { - var s = arr.map(x=>x+"").join(''), a = 1, c = 0, h, o; - if (s) { - a = 0; - for (h = s.length - 1; h >= 0; h--) { - o = s.charCodeAt(h); - a = (a<<6&268435455) + o + (o<<14); - c = a & 266338304; - a = c!==0?a^c>>21:a; - } - } - return ~~String(a).slice(0, 3); -};` -); - -/** - * Shuffles arrays initial order of elements. - * - * "Un-shuffles" the array at runtime. - */ -export default class Shuffle extends Transform { - hashName: string; - constructor(o) { - super(o, ObfuscateOrder.Shuffle); - } - - match(object, parents) { - return ( - object.type == "ArrayExpression" && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object, parents) { - return () => { - if (object.elements.length < 3) { - // Min: 4 elements - return; - } - - function isAllowed(e) { - return ( - e.type == "Literal" && - { number: 1, boolean: 1, string: 1 }[typeof e.value] - ); - } - - // Only arrays with only literals - var illegal = object.elements.find((x) => !isAllowed(x)); - - if (illegal) { - return; - } - - var mapped = object.elements.map((x) => x.value); - - var mode = ComputeProbabilityMap(this.options.shuffle, (x) => x, mapped); - if (mode) { - var shift = getRandomInteger( - 1, - Math.min(60, object.elements.length * 6) - ); - - var expr = Literal(shift); - var name = this.getPlaceholder(); - - if (mode == "hash") { - var str = mapped.join(""); - shift = Hash(str); - - if (!this.hashName) { - prepend( - parents[parents.length - 1], - HashTemplate.single({ - name: (this.hashName = this.getPlaceholder()), - }) - ); - } - - for (var i = 0; i < shift; i++) { - object.elements.push(object.elements.shift()); - } - - var shiftedHash = Hash( - object.elements.map((x) => x.value + "").join("") - ); - - expr = BinaryExpression( - "-", - CallExpression(Identifier(this.hashName), [Identifier(name)]), - Literal(shiftedHash - shift) - ); - } else { - for (var i = 0; i < shift; i++) { - object.elements.push(object.elements.shift()); - } - } - - var code = []; - - var iName = this.getPlaceholder(); - - var inPlace = false; - var inPlaceName; - var inPlaceBody; - var inPlaceIndex; - - var varDeclarator = parents[0]; - if (varDeclarator.type == "VariableDeclarator") { - var varDec = parents[2]; - if (varDec.type == "VariableDeclaration" && varDec.kind !== "const") { - var body = parents[3]; - if ( - varDec.declarations.length == 1 && - Array.isArray(body) && - varDeclarator.id.type === "Identifier" && - varDeclarator.init === object - ) { - inPlaceIndex = body.indexOf(varDec); - inPlaceBody = body; - inPlace = inPlaceIndex !== -1; - inPlaceName = varDeclarator.id.name; - } - } - } - - if (mode !== "hash") { - var varPrefix = this.getPlaceholder(); - code.push( - new Template(` - for ( var ${varPrefix}x = 16; ${varPrefix}x%4 === 0; ${varPrefix}x++) { - var ${varPrefix}z = 0; - ${ - inPlace ? `${inPlaceName} = ${name}` : name - } = ${name}.concat((function(){ - ${varPrefix}z++; - if(${varPrefix}z === 1){ - return []; - } - - for( var ${varPrefix}i = ${getRandomInteger( - 5, - 105 - )}; ${varPrefix}i; ${varPrefix}i-- ){ - ${name}.unshift(${name}.pop()); - } - return []; - })()); - } - `).single() - ); - } - - code.push( - ForStatement( - VariableDeclaration(VariableDeclarator(iName, expr)), - Identifier(iName), - UpdateExpression("--", Identifier(iName), false), - [ - // ${name}.unshift(${name}.pop()); - ExpressionStatement( - CallExpression( - MemberExpression( - Identifier(name), - Identifier("unshift"), - false - ), - [ - CallExpression( - MemberExpression( - Identifier(name), - Identifier("pop"), - false - ), - [] - ), - ] - ) - ), - ] - ) - ); - - if (inPlace) { - var varDeclarator = parents[0]; - ok(i != -1); - - inPlaceBody.splice( - inPlaceIndex + 1, - 0, - VariableDeclaration( - VariableDeclarator(name, Identifier(varDeclarator.id.name)) - ), - ...code - ); - } - - if (!inPlace) { - this.replace( - object, - CallExpression( - FunctionExpression( - [Identifier(name)], - [...code, ReturnStatement(Identifier(name))] - ), - [clone(object)] - ) - ); - } - } - }; - } -} +import { NodePath, PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import * as t from "@babel/types"; +import { computeProbabilityMap } from "../probability"; +import { getRandomInteger } from "../utils/random-utils"; +import Template from "../templates/template"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("shuffle"); + + return { + visitor: { + ArrayExpression: { + exit(path) { + if (path.node.elements.length <= 3) { + return; + } + var illegalElement = path.node.elements.find((element) => { + !t.isStringLiteral(element); + }); + + if (illegalElement) return; + + if (!computeProbabilityMap(me.options.shuffle)) { + return; + } + + var shift = getRandomInteger( + 1, + Math.min(40, path.node.elements.length * 6) + ); + + var shiftedElements = [...path.node.elements]; + for (var i = 0; i < shift; i++) { + shiftedElements.push(shiftedElements.shift()); + } + + var runtimeFn = me.getPlaceholder(); + + (path.scope.path as NodePath).unshiftContainer( + "body", + new Template( + ` + function ${runtimeFn}(arr, shift) { + for (var i = 0; i < shift; i++) { + arr.push(arr.shift()); + } + + return arr; + } + ` + ).single() + ); + + path.replaceWith( + t.callExpression(t.identifier(runtimeFn), [ + t.arrayExpression(shiftedElements), + t.numericLiteral(shift), + ]) + ); + + path.skip(); + }, + }, + }, + }; +}; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 11b440e..9607037 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -1,309 +1,60 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { ComputeProbabilityMap } from "../../probability"; -import Template from "../../templates/template"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { - AssignmentExpression, - BinaryExpression, - CallExpression, - ExpressionStatement, - FunctionDeclaration, - FunctionExpression, - Identifier, - IfStatement, - Literal, - MemberExpression, - ObjectExpression, - Property, - ReturnStatement, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; -import Transform from "../transform"; -import { predictableFunctionTag } from "../../constants"; -import { - chance, - choice, - getRandomFalseExpression, - getRandomInteger, - getRandomString, - splitIntoChunks, -} from "../../util/random"; - -function LZ_encode(c) { - ok(c); - var x = "charCodeAt", - b, - e = {}, - f = c.split(""), - d = [], - a = f[0], - g = 256; - for (b = 1; b < f.length; b++) - (c = f[b]), - null != e[a + c] - ? (a += c) - : (d.push(1 < a.length ? e[a] : a[x](0)), (e[a + c] = g), g++, (a = c)); - d.push(1 < a.length ? e[a] : a[x](0)); - for (b = 0; b < d.length; b++) d[b] = String.fromCharCode(d[b]); - return d.join(""); -} - -function LZ_decode(b) { - ok(b); - var o, - f, - a, - e = {}, - d = b.split(""), - c = (f = d[0]), - g = [c], - h = (o = 256); - for (var i = 1; i < d.length; i++) - (a = d[i].charCodeAt(0)), - (a = h > a ? d[i] : e[a] ? e[a] : f + c), - g.push(a), - (c = a.charAt(0)), - (e[o] = f + c), - o++, - (f = a); - return g.join(""); -} - -const DecodeTemplate = new Template( - `function {name}(b){ - var o, - f, - a, - e = {}, - d = b.split(""), - c = (f = d[0]), - g = [c], - h = (o = 256); - for (b = 1; b < d.length; b++) - (a = d[b].charCodeAt(0)), - (a = h > a ? d[b] : e[a] ? e[a] : f + c), - g.push(a), - (c = a.charAt(0)), - (e[o] = f + c), - o++, - (f = a); - return g.join("").split("{delimiter}"); - }` -); - -export default class StringCompression extends Transform { - map: Map; - ignore: Set; - string: string; - delimiter = "|"; - - fnName: string; - - constructor(o) { - super(o, ObfuscateOrder.StringCompression); - - this.map = new Map(); - this.ignore = new Set(); - this.string = ""; - this.fnName = this.getPlaceholder() + predictableFunctionTag; - } - - apply(tree) { - super.apply(tree); - - this.string = this.string.slice(0, this.string.length - 1); - if (!this.string.length) { - return; - } - - var split = this.getPlaceholder(); - var decoder = this.getPlaceholder(); - var getStringName = this.getPlaceholder() + predictableFunctionTag; // Returns the string payload - - var encoded = LZ_encode(this.string); - if (LZ_decode(encoded) !== this.string) { - this.error( - new Error( - "String failed to be decoded. Try disabling the 'stringCompression' option." - ) - ); - } - - var getStringParamName = this.getPlaceholder(); - var decoderParamName = this.getPlaceholder(); - - var callExpression = CallExpression(Identifier(decoderParamName), [ - CallExpression(Identifier(getStringParamName), []), - ]); - - prepend( - tree, - VariableDeclaration( - VariableDeclarator( - split, - CallExpression( - FunctionExpression( - [Identifier(getStringParamName), Identifier(decoderParamName)], - [ReturnStatement(callExpression)] - ), - [Identifier(getStringName), Identifier(decoder)] - ) - ) - ) - ); - - var keys = new Set(); - var keysToMake = getRandomInteger(4, 14); - for (var i = 0; i < keysToMake; i++) { - keys.add(getRandomString(getRandomInteger(4, 14))); - } - - var objectExpression = ObjectExpression( - Array.from(keys).map((key) => { - return Property(Literal(key), getRandomFalseExpression(), true); - }) - ); - - // Get string function - var getStringBody = []; - var splits = splitIntoChunks( - encoded, - Math.floor(encoded.length / getRandomInteger(3, 6)) - ); - - getStringBody.push( - VariableDeclaration(VariableDeclarator("str", Literal(splits.shift()))) - ); - - getStringBody.push( - VariableDeclaration(VariableDeclarator("objectToTest", objectExpression)) - ); - - const addIfStatement = (testingFor, literalValueToBeAppended) => { - getStringBody.push( - IfStatement( - BinaryExpression( - "in", - Literal(testingFor), - Identifier("objectToTest") - ), - [ - ExpressionStatement( - AssignmentExpression( - "+=", - Identifier("str"), - Literal(literalValueToBeAppended) - ) - ), - ] - ) - ); - }; - - for (const split of splits) { - if (chance(50)) { - var fakeKey; - do { - fakeKey = getRandomString(getRandomInteger(4, 14)); - } while (keys.has(fakeKey) || fakeKey in {}); - - addIfStatement(fakeKey, getRandomString(split.length)); - } - - addIfStatement(choice(Array.from(keys)), split); - } - - // Return computed string - getStringBody.push(ReturnStatement(Identifier("str"))); - - append(tree, FunctionDeclaration(getStringName, [], getStringBody)); - - append( - tree, - FunctionDeclaration( - this.fnName, - [Identifier("index")], - [ - ReturnStatement( - MemberExpression(Identifier(split), Identifier("index"), true) - ), - ] - ) - ); - - append( - tree, - DecodeTemplate.single({ name: decoder, delimiter: this.delimiter }) - ); - } - - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value && - object.value.length > 3 && - !isDirective(object, parents) && - !isModuleSource(object, parents) && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object, parents) { - if (!object.value) { - return; - } - if ( - this.ignore.has(object.value) || - object.value.includes(this.delimiter) - ) { - return; - } - - if ( - !parents[0] || - (parents[0].type == "CallExpression" && - parents[0].callee.type == "Identifier" && - parents[0].callee.name == this.fnName) - ) { - return; - } - - if ( - !ComputeProbabilityMap( - this.options.stringCompression, - (x) => x, - object.value - ) - ) { - return; - } - - var index = this.map.get(object.value); - - // New string, add it! - if (typeof index !== "number") { - // Ensure the string gets properly decoded - if (LZ_decode(LZ_encode(object.value)) !== object.value) { - this.ignore.add(object.value); - return; - } - - index = this.map.size; - this.map.set(object.value, index); - this.string += object.value + this.delimiter; - } - ok(typeof index === "number"); - - return () => { - this.replaceIdentifierOrLiteral( - object, - CallExpression(Identifier(this.fnName), [Literal(index)]), - parents - ); - }; - } -} +import { PluginObj } from "@babel/core"; +import { PluginArg } from "../plugin"; +import * as t from "@babel/types"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("stringCompression"); + + return { + visitor: { + Program: { + exit(programPath) { + const stringFn = me.getPlaceholder() + "_SC"; + const stringMap = new Map(); + + // Find all the strings + + programPath.traverse({ + StringLiteral: { + exit: (path) => { + const originalValue = path.node.value; + let index = stringMap.get(originalValue); + if (typeof index === "undefined") { + index = stringMap.size; + stringMap.set(originalValue, index); + } + + path.replaceWith( + t.callExpression(t.identifier(stringFn), [ + t.numericLiteral(index), + ]) + ); + }, + }, + }); + + // No strings changed + if (stringMap.size === 0) return; + + // Create the string function + var arrayExpression = t.arrayExpression( + Array.from(stringMap.keys()).map((value) => t.stringLiteral(value)) + ); + + var stringFunction = t.functionDeclaration( + t.identifier(stringFn), + [t.identifier("index")], + t.blockStatement([ + t.returnStatement( + t.memberExpression(arrayExpression, t.identifier("index"), true) + ), + ]) + ); + + var p = programPath.unshiftContainer("body", stringFunction); + programPath.scope.registerDeclaration(p[0]); + }, + }, + }, + }; +}; diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index db07308..bfd8384 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -1,380 +1,51 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; +import * as t from "@babel/types"; +import { NodePath, PluginObj } from "@babel/core"; import Template from "../../templates/template"; -import { getBlock } from "../../traverse"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { - ArrayExpression, - CallExpression, - Identifier, - Literal, - MemberExpression, - Node, - ObjectExpression, - Property, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; -import { - chance, - choice, - getRandomInteger, - getRandomString, - shuffle, -} from "../../util/random"; -import Transform from "../transform"; -import { - EncodingImplementation, - EncodingImplementations, - createEncodingImplementation, - hasAllEncodings, -} from "./encoding"; -import { ComputeProbabilityMap } from "../../probability"; -import { - BufferToStringTemplate, - createGetGlobalTemplate, -} from "../../templates/bufferToString"; -import { criticalFunctionTag, predictableFunctionTag } from "../../constants"; - -interface FunctionObject { - block: Node; - fnName: string; - encodingImplementation: EncodingImplementation; -} - -export default class StringConcealing extends Transform { - arrayExpression: Node; - set: Set; - index: { [str: string]: [number, string, Node] }; // index, fnName, block - - arrayName = this.getPlaceholder(); - ignore = new Set(); - variablesMade = 1; - gen: ReturnType; - - functionObjects: FunctionObject[] = []; - - constructor(o) { - super(o, ObfuscateOrder.StringConcealing); - - this.set = new Set(); - this.index = Object.create(null); - this.arrayExpression = ArrayExpression([]); - this.gen = this.getGenerator(); - } - - apply(tree) { - super.apply(tree); - - // Pad array with useless strings - var dead = getRandomInteger(50, 200); - for (var i = 0; i < dead; i++) { - var str = getRandomString(getRandomInteger(5, 40)); - var fn = this.transform(Literal(str), [tree]); - if (fn) { - fn(); - } - } - - var cacheName = this.getPlaceholder(); - var bufferToStringName = this.getPlaceholder() + predictableFunctionTag; - - // This helper functions convert UInt8 Array to UTf-string - prepend( - tree, - ...BufferToStringTemplate.compile({ - name: bufferToStringName, - getGlobalFnName: this.getPlaceholder() + predictableFunctionTag, - GetGlobalTemplate: createGetGlobalTemplate(this, tree, []), - }) - ); - - for (var functionObject of this.functionObjects) { - var { - block, - fnName: getterFnName, - encodingImplementation, - } = functionObject; - - var decodeFn = - this.getPlaceholder() + predictableFunctionTag + criticalFunctionTag; - - append( - block, - encodingImplementation.template.single({ - __fnName__: decodeFn, - __bufferToString__: bufferToStringName, - }) - ); - // All these are fake and never ran - var ifStatements = new Template(`if ( z == x ) { - return y[${cacheName}[z]] = ${getterFnName}(x, y); - } - if ( y ) { - [b, y] = [a(b), x || z] - return ${getterFnName}(x, b, z) - } - if ( z && a !== ${decodeFn} ) { - ${getterFnName} = ${decodeFn} - return ${getterFnName}(x, -1, z, a, b) - } - if ( a === ${getterFnName} ) { - ${decodeFn} = y - return ${decodeFn}(z) - } - if( a === undefined ) { - ${getterFnName} = b - } - if( z == a ) { - return y ? x[b[y]] : ${cacheName}[x] || (z=(b[x] || a), ${cacheName}[x] = z(${this.arrayName}[x])) - } - `).compile(); - - // Not all fake if-statements are needed - ifStatements = ifStatements.filter(() => chance(50)); - - // This one is always used - ifStatements.push( - new Template(` - if ( x !== y ) { - return b[x] || (b[x] = a(${this.arrayName}[x])) - } - `).single() - ); - - shuffle(ifStatements); - - var varDeclaration = new Template(` - var ${getterFnName} = (x, y, z, a, b)=>{ - if(typeof a === "undefined") { - a = ${decodeFn} - } - if(typeof b === "undefined") { - b = ${cacheName} - } - } - `).single(); - - varDeclaration.declarations[0].init.body.body.push(...ifStatements); - - prepend(block, varDeclaration); - } - - prepend( - tree, - VariableDeclaration([ - VariableDeclarator(cacheName, ArrayExpression([])), - VariableDeclarator(this.arrayName, this.arrayExpression), - ]) - ); - } - - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length >= 3 && - !isModuleSource(object, parents) && - !isDirective(object, parents) //&& - /*!parents.find((x) => x.$multiTransformSkip)*/ - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - // Empty strings are discarded - if ( - !object.value || - this.ignore.has(object.value) || - object.value.length == 0 - ) { - return; - } - - // Allow user to choose which strings get changed - if ( - !ComputeProbabilityMap( - this.options.stringConcealing, - (x) => x, - object.value - ) - ) { - return; - } - - var currentBlock = getBlock(object, parents); - - // Find created functions - var functionObjects: FunctionObject[] = parents - .filter((node) => node.$stringConcealingFunctionObject) - .map((item) => item.$stringConcealingFunctionObject); - - // Choose random functionObject to use - var functionObject = choice(functionObjects); - - if ( - !functionObject || - (!hasAllEncodings() && - chance(25 / this.functionObjects.length) && - !currentBlock.$stringConcealingFunctionObject) - ) { - // No functions, create one - - var newFunctionObject: FunctionObject = { - block: currentBlock, - encodingImplementation: createEncodingImplementation(), - fnName: this.getPlaceholder() + predictableFunctionTag, - }; - - this.functionObjects.push(newFunctionObject); - currentBlock.$stringConcealingFunctionObject = newFunctionObject; - functionObject = newFunctionObject; - } - - var { fnName, encodingImplementation } = functionObject; - - var index = -1; - - // String already decoded? - if (this.set.has(object.value)) { - var row = this.index[object.value]; - if (parents.includes(row[2])) { - [index, fnName] = row; - ok(typeof index === "number"); - } - } - - if (index == -1) { - // The decode function must return correct result - var encoded = encodingImplementation.encode(object.value); - if (encodingImplementation.decode(encoded) !== object.value) { - this.ignore.add(object.value); - this.warn( - encodingImplementation.identity, - object.value.slice(0, 100) +import { PluginArg } from "../plugin"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("stringConcealing"); + var decoderName = "decoder"; + + return { + visitor: { + Program: { + exit(programPath: NodePath) { + // Create the decoder function + const decoderFunction = new Template(` + function {decoderName}(encoded) { + return Buffer.from(encoded, "base64").toString("utf-8"); + } + `).single({ + decoderName, + }); + + // Insert the decoder function at the top of the program body + var path = programPath.unshiftContainer("body", decoderFunction)[0]; + + // Skip transformation for the inserted decoder function + path.skip(); + }, + }, + + StringLiteral: { + exit(path: NodePath) { + const originalValue = path.node.value; + const encodedValue = Buffer.from(originalValue, "utf-8").toString( + "base64" ); - delete EncodingImplementations[encodingImplementation.identity]; - - return; - } - - this.arrayExpression.elements.push(Literal(encoded)); - index = this.arrayExpression.elements.length - 1; - this.index[object.value] = [index, fnName, currentBlock]; - - this.set.add(object.value); - } - - ok(index != -1, "index == -1"); - - var callExpr = CallExpression(Identifier(fnName), [Literal(index)]); - - // use `.apply` to fool automated de-obfuscators - if (chance(10)) { - callExpr = CallExpression( - MemberExpression(Identifier(fnName), Literal("apply"), true), - [Identifier("undefined"), ArrayExpression([Literal(index)])] - ); - } - - // use `.call` - else if (chance(10)) { - callExpr = CallExpression( - MemberExpression(Identifier(fnName), Literal("call"), true), - [Identifier("undefined"), Literal(index)] - ); - } - - var referenceType = "call"; - if (parents.length && chance(50 - this.variablesMade)) { - referenceType = "constantReference"; - } - - var newExpr: Node = callExpr; - - if (referenceType === "constantReference") { - // Define the string earlier, reference the name here - this.variablesMade++; - var constantReferenceType = choice(["variable", "array", "object"]); - - var place = currentBlock; - if (!place) { - this.error(new Error("No lexical block to insert code")); - } - - switch (constantReferenceType) { - case "variable": - var name = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration(VariableDeclarator(name, callExpr)) - ); - - newExpr = Identifier(name); - break; - case "array": - if (!place.$stringConcealingArray) { - place.$stringConcealingArray = ArrayExpression([]); - place.$stringConcealingArrayName = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration( - VariableDeclarator( - place.$stringConcealingArrayName, - place.$stringConcealingArray - ) - ) - ); - } - - var arrayIndex = place.$stringConcealingArray.elements.length; - - place.$stringConcealingArray.elements.push(callExpr); - - var memberExpression = MemberExpression( - Identifier(place.$stringConcealingArrayName), - Literal(arrayIndex), - true - ); - - newExpr = memberExpression; - break; - case "object": - if (!place.$stringConcealingObject) { - place.$stringConcealingObject = ObjectExpression([]); - place.$stringConcealingObjectName = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration( - VariableDeclarator( - place.$stringConcealingObjectName, - place.$stringConcealingObject - ) - ) - ); - } - - var propName = this.gen.generate(); - var property = Property(Literal(propName), callExpr, true); - place.$stringConcealingObject.properties.push(property); - - var memberExpression = MemberExpression( - Identifier(place.$stringConcealingObjectName), - Literal(propName), - true - ); - - newExpr = memberExpression; - break; - } - } + // Replace the string literal with a call to the decoder function + path.replaceWith( + t.callExpression(t.identifier(decoderName), [ + t.stringLiteral(encodedValue), + ]) + ); - this.replaceIdentifierOrLiteral(object, newExpr, parents); - }; - } -} + // Skip the transformation for the newly created node + path.skip(); + }, + }, + }, + }; +}; diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 04418e9..dc19cbd 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -1,8 +1,8 @@ -import Transform from "../transform"; -import { choice } from "../../util/random"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { ComputeProbabilityMap } from "../../probability"; -import { Identifier } from "../../util/gen"; +import { PluginObj } from "@babel/core"; +import { PluginArg } from "../plugin"; +import * as t from "@babel/types"; +import { choice } from "../../utils/random-utils"; +import { computeProbabilityMap } from "../../probability"; function pad(x: string, len: number): string { while (x.length < len) { @@ -46,50 +46,33 @@ function toUnicodeRepresentation(str: string) { return escapedString; } -/** - * [String Encoding](https://docs.jscrambler.com/code-integrity/documentation/transformations/string-encoding) transforms a string into an encoded representation. - * - * - Potency Low - * - Resilience Low - * - Cost Low - */ -export default class StringEncoding extends Transform { - constructor(o) { - super(o); - } - - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length > 0 && - !isModuleSource(object, parents) && - !isDirective(object, parents) - ); - } +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("stringEncoding"); - transform(object, parents) { - // Allow percentages - if ( - !ComputeProbabilityMap( - this.options.stringEncoding, - (x) => x, - object.value - ) - ) - return; + return { + visitor: { + StringLiteral: { + exit(path) { + const { value } = path.node; - var type = choice(["hexadecimal", "unicode"]); + // Allow percentages + if ( + !computeProbabilityMap(me.options.stringEncoding, (x) => x, value) + ) + return; - var escapedString = ( - type == "hexadecimal" ? toHexRepresentation : toUnicodeRepresentation - )(object.value); + var type = choice(["hexadecimal", "unicode"]); - return () => { - if (object.type !== "Literal") return; + var escapedString = ( + type == "hexadecimal" + ? toHexRepresentation + : toUnicodeRepresentation + )(value); - // ESCodeGen tries to escape backslashes, here is a work-around - this.replace(object, Identifier(`'${escapedString}'`)); - }; - } -} + path.replaceWith(t.identifier(`'${escapedString}'`)); + path.skip(); + }, + }, + }, + }; +}; diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 765ceb1..d22854c 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -1,86 +1,66 @@ -import Transform from "../transform"; -import { Node, Literal, BinaryExpression } from "../../util/gen"; -import { clone } from "../../util/insert"; -import { getRandomInteger, shuffle, splitIntoChunks } from "../../util/random"; -import { ObfuscateOrder } from "../../order"; -import { isDirective, isModuleSource } from "../../util/compare"; +import { PluginObj } from "@babel/core"; +import { PluginArg } from "../plugin"; +import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; +import { computeProbabilityMap } from "../../probability"; +import { binaryExpression, stringLiteral } from "@babel/types"; import { ok } from "assert"; -import { ComputeProbabilityMap } from "../../probability"; -export default class StringSplitting extends Transform { - joinPrototype: string; - strings: { [value: string]: string }; +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("stringSplitting"); - adders: Node[][]; - vars: Node[]; + return { + visitor: { + StringLiteral: { + exit(path) { + var object = path.node; - constructor(o) { - super(o, ObfuscateOrder.StringSplitting); - - this.joinPrototype = null; - this.strings = Object.create(null); - - this.adders = []; - this.vars = []; - } - - match(object: Node, parents: Node[]) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length >= 8 && - !isModuleSource(object, parents) && - !isDirective(object, parents) - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - var size = Math.round( - Math.max(6, object.value.length / getRandomInteger(3, 8)) - ); - if (object.value.length <= size) { - return; - } + var size = Math.round( + Math.max(6, object.value.length / getRandomInteger(3, 8)) + ); + if (object.value.length <= size) { + return; + } - var chunks = splitIntoChunks(object.value, size); - if (!chunks || chunks.length <= 1) { - return; - } + var chunks = splitIntoChunks(object.value, size); + if (!chunks || chunks.length <= 1) { + return; + } - if ( - !ComputeProbabilityMap( - this.options.stringSplitting, - (x) => x, - object.value - ) - ) { - return; - } + if ( + !computeProbabilityMap( + me.options.stringSplitting, + (x) => x, + object.value + ) + ) { + return; + } - var binaryExpression; - var parent; - var last = chunks.pop(); - chunks.forEach((chunk, i) => { - if (i == 0) { - parent = binaryExpression = BinaryExpression( - "+", - Literal(chunk), - Literal("") - ); - } else { - binaryExpression.left = BinaryExpression( - "+", - clone(binaryExpression.left), - Literal(chunk) - ); - ok(binaryExpression); - } - }); + var binExpr; + var parent; + var last = chunks.pop(); + chunks.forEach((chunk, i) => { + if (i == 0) { + parent = binExpr = binaryExpression( + "+", + stringLiteral(chunk), + stringLiteral("") + ); + } else { + binExpr.left = binaryExpression( + "+", + { ...binExpr.left }, + stringLiteral(chunk) + ); + ok(binExpr); + } + }); - parent.right = Literal(last); + parent.right = stringLiteral(last); - this.replaceIdentifierOrLiteral(object, parent, parents); - }; - } -} + path.replaceWith(parent); + }, + }, + }, + }; +}; diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts new file mode 100644 index 0000000..8de5b30 --- /dev/null +++ b/src/transforms/variableMasking.ts @@ -0,0 +1,135 @@ +import { PluginObj } from "@babel/core"; +import { NodePath } from "@babel/traverse"; +import { PluginArg } from "./plugin"; +import * as babelTypes from "@babel/types"; +import Template from "../templates/template"; +import { computeProbabilityMap } from "../probability"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin("variableMasking"); + + const transformFunction = (path: NodePath) => { + // Do not apply to getter/setter methods + if (path.isObjectMethod() && path.node.kind !== "method") { + return; + } + + // Do not apply to class getters/setters + if (path.isClassMethod() && path.node.kind !== "method") { + return; + } + + // Do not apply to async or generator functions + if (path.node.generator || path.node.async) { + return; + } + + // Do not apply to functions with rest parameters or destructuring + if (path.node.params.some((param) => !babelTypes.isIdentifier(param))) { + return; + } + + const functionName = + ((path.isFunctionDeclaration() || path.isFunctionExpression()) && + path.node.id?.name) ?? + "anonymous"; + + if ( + !computeProbabilityMap(me.options.variableMasking, (x) => x, functionName) + ) { + return; + } + + const stackName = me.generateRandomIdentifier() + "_stack"; + const stackMap = new Map(); + + for (const param of path.node.params) { + stackMap.set((param as babelTypes.Identifier).name, 0); + } + + path.traverse({ + BindingIdentifier(identifierPath) { + const binding = identifierPath.scope.getBinding( + identifierPath.node.name + ); + + if (!binding) { + return; + } + + if (binding.constantViolations.length > 0) { + return; + } + + if (binding.scope !== path.scope) { + return; + } + + let stackIndex = stackMap.get(identifierPath.node.name); + + if (typeof stackIndex === "undefined") { + if ( + !binding.path.isVariableDeclarator() || + !binding.path.parentPath.isVariableDeclaration() || + binding.path.parentPath.node.declarations.length !== 1 + ) { + return; + } + + stackIndex = stackMap.size; + stackMap.set(identifierPath.node.name, stackIndex); + + binding.path.parentPath.replaceWith( + new Template(`{stackName}[{stackIndex}] = {value}`).single({ + stackName: stackName, + stackIndex: stackIndex, + value: + (binding.path.node as any).init ?? + babelTypes.identifier("undefined"), + }) + ); + } + + binding.referencePaths.forEach((refPath) => { + if (!refPath.isReferencedIdentifier()) return; + + if ( + refPath.getFunctionParent() !== binding.path.getFunctionParent() + ) { + return; + } + + const refBiding = refPath.scope.getBinding(refPath.node.name); + if (refBiding !== binding) { + return; + } + + refPath.replaceWith( + new Template(`{stackName}[{stackIndex}]`).single({ + stackName: stackName, + stackIndex: stackIndex, + }) + ); + }); + + identifierPath.scope.removeBinding(identifierPath.node.name); + }, + }); + + path.node.params = [ + babelTypes.restElement(babelTypes.identifier(stackName)), + ]; + + path.scope.registerBinding("param", path.get("params.0") as NodePath, path); + }; + + return { + visitor: { + Function: { + exit(path: NodePath) { + transformFunction(path); + }, + }, + }, + }; +}; diff --git a/src/utils/IntGen.ts b/src/utils/IntGen.ts new file mode 100644 index 0000000..65b3464 --- /dev/null +++ b/src/utils/IntGen.ts @@ -0,0 +1,33 @@ +export class IntGen { + private min: number; + private max: number; + private generatedInts: Set; + + constructor(min: number = -250, max: number = 250) { + this.min = min; + this.max = max; + this.generatedInts = new Set(); + } + + private getRandomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + generate(): number { + let randomInt: number; + + // Keep generating until we find a unique integer + do { + randomInt = this.getRandomInt(this.min, this.max); + + // Expand the range if most integers in the current range are exhausted + if (this.generatedInts.size >= 0.8 * (this.max - this.min)) { + this.min -= 100; + this.max += 100; + } + } while (this.generatedInts.has(randomInt)); + + this.generatedInts.add(randomInt); + return randomInt; + } +} diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts new file mode 100644 index 0000000..a7a8398 --- /dev/null +++ b/src/utils/NameGen.ts @@ -0,0 +1,29 @@ +import { alphabeticalGenerator, getRandomString } from "./random-utils"; + +export class NameGen { + private mode: "mangled" | "randomized"; + private generatedNames: Set; + private mangledIndex: number; + + constructor(mode: "mangled" | "randomized" = "randomized") { + this.mode = mode; + this.generatedNames = new Set(); + this.mangledIndex = 0; + } + + generate(): string { + let name: string; + + do { + if (this.mode === "mangled") { + this.mangledIndex++; + name = alphabeticalGenerator(this.mangledIndex); + } else { + name = getRandomString(6); // Adjust length as needed + } + } while (this.generatedNames.has(name)); + + this.generatedNames.add(name); + return name; + } +} diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts new file mode 100644 index 0000000..d32de75 --- /dev/null +++ b/src/utils/ast-utils.ts @@ -0,0 +1,172 @@ +import * as babelTypes from "@babel/types"; +import { NodePath } from "@babel/core"; + +export function insertIntoNearestBlockScope( + path: NodePath, + ...nodesToInsert: babelTypes.Statement[] +): NodePath[] { + // Traverse up the AST until we find a BlockStatement or Program + let targetPath: NodePath = path; + + while (targetPath && !babelTypes.isProgram(targetPath.node)) { + targetPath = targetPath.parentPath; + } + + // Ensure that we found a valid insertion point + if (babelTypes.isBlockStatement(targetPath.node)) { + // Insert before the current statement within the found block + return targetPath.insertBefore(nodesToInsert) as NodePath[]; + } else if (targetPath.isProgram()) { + // Insert at the top of the program body + return targetPath.unshiftContainer("body", nodesToInsert) as NodePath[]; + } else { + throw new Error( + "Could not find a suitable block scope to insert the nodes." + ); + } +} + +export function isReservedIdentifier( + node: babelTypes.Identifier | babelTypes.JSXIdentifier +): boolean { + return ( + node.name === "arguments" || // Check for 'arguments' + node.name === "undefined" || // Check for 'undefined' + node.name === "NaN" || // Check for 'NaN' + node.name === "Infinity" || // Check for 'Infinity' + node.name === "eval" || // Check for 'eval' + babelTypes.isThisExpression(node) || // Check for 'this' + babelTypes.isSuper(node) || // Check for 'super' + babelTypes.isMetaProperty(node) // Check for meta properties like 'new.target' + ); +} + +export function hasNestedBinding(path: NodePath, name: string): boolean { + let found = false; + + // Traverse through the child paths (nested scopes) + path.traverse({ + Scope(nestedPath) { + if (nestedPath.scope.hasOwnBinding(name)) { + found = true; + nestedPath.stop(); // Stop further traversal if found + } + }, + }); + + return found; +} + +export function isModifiedIdentifier( + path: NodePath +): boolean { + const parent = path.parent; + + // Check if the identifier is on the left-hand side of an assignment + if (babelTypes.isAssignmentExpression(parent) && parent.left === path.node) { + return true; + } + + // Check if the identifier is in an update expression (like i++) + if (babelTypes.isUpdateExpression(parent) && parent.argument === path.node) { + return true; + } + + // Check if the identifier is being deleted + if ( + babelTypes.isUnaryExpression(parent) && + parent.operator === "delete" && + parent.argument === path.node + ) { + return true; + } + + // Check if the identifier is part of a destructuring pattern being assigned + if ( + (babelTypes.isObjectPattern(path.parent) || + babelTypes.isArrayPattern(path.parent)) && + path.key === "elements" + ) { + return true; + } + + return false; +} + +/** + * Determines if the MemberExpression is computed. + * + * @param memberPath - The path of the MemberExpression node. + * @returns True if the MemberExpression is computed; false otherwise. + */ +export function isComputedMemberExpression( + memberExpression: babelTypes.MemberExpression +): boolean { + const property = memberExpression.property; + + if (!memberExpression.computed) { + // If the property is a non-computed identifier, it is not computed + if (babelTypes.isIdentifier(property)) { + return false; + } + } + + // If the property is a computed literal (string or number), it is not computed + if ( + babelTypes.isStringLiteral(property) || + babelTypes.isNumericLiteral(property) + ) { + return false; + } + + // In all other cases, the property is computed + return true; +} + +export function getObjectPropertyAsString( + property: babelTypes.ObjectMember +): string { + babelTypes.assertObjectMember(property); + + if (babelTypes.isIdentifier(property.key)) { + return property.key.name; + } + + if (babelTypes.isStringLiteral(property.key)) { + return property.key.value; + } + + if (babelTypes.isNumericLiteral(property.key)) { + return property.key.value.toString(); + } + + return null; +} + +/** + * Gets the property of a MemberExpression as a string. + * + * @param memberPath - The path of the MemberExpression node. + * @returns The property as a string or null if it cannot be determined. + */ +export function getMemberExpressionPropertyAsString( + member: babelTypes.MemberExpression +): string | null { + babelTypes.assertMemberExpression(member); + + const property = member.property; + + if (!member.computed && babelTypes.isIdentifier(property)) { + return property.name; + } + + if (babelTypes.isStringLiteral(property)) { + return property.value; + } + + if (babelTypes.isNumericLiteral(property)) { + return property.value.toString(); + } + + return null; // If the property cannot be determined +} diff --git a/src/utils/object-utils.ts b/src/utils/object-utils.ts new file mode 100644 index 0000000..170006a --- /dev/null +++ b/src/utils/object-utils.ts @@ -0,0 +1,16 @@ +export function createObject( + keys: string[], + values: any[] +): { [key: string]: any } { + if (keys.length != values.length) { + throw new Error("length mismatch"); + } + + var newObject = {}; + + keys.forEach((x, i) => { + newObject[x] = values[i]; + }); + + return newObject; +} diff --git a/src/utils/random-utils.ts b/src/utils/random-utils.ts new file mode 100644 index 0000000..8aeca58 --- /dev/null +++ b/src/utils/random-utils.ts @@ -0,0 +1,80 @@ +import { ok } from "assert"; + +/** + * Returns a random element from the given array + * @param choices Array of items + * @returns One of the items in the array at random + */ +export function choice(choices: T[]): T { + var index = Math.floor(Math.random() * choices.length); + return choices[index]; +} + +/** + * Returns a true/false based on the percent chance (0%-100%) + * @param percentChance AS A PERCENTAGE 0 - 100% + */ +export function chance(percentChance: number): boolean { + return Math.random() < percentChance / 100; +} + +/** + * **Mutates the given array** + * @param array + */ +export function shuffle(array: any[]): any[] { + array.sort(() => Math.random() - 0.5); + return array; +} + +/** + * Returns a random string. + */ +export function getRandomString(length = 10) { + var result = ""; + var characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +export function getRandom(min, max) { + return Math.random() * (max - min) + min; +} + +export function getRandomInteger(min, max) { + return Math.floor(getRandom(min, max)); +} + +export function splitIntoChunks(str: string, size: number) { + ok(typeof str === "string", "str must be typeof string"); + ok(typeof size === "number", "size must be typeof number"); + ok(Math.floor(size) === size, "size must be integer"); + + const numChunks = Math.ceil(str.length / size); + const chunks = new Array(numChunks); + + for (let i = 0, o = 0; i < numChunks; ++i, o += size) { + chunks[i] = str.substr(o, size); + } + + return chunks; +} + +export function alphabeticalGenerator(index: number) { + let name = ""; + while (index > 0) { + var t = (index - 1) % 52; + var thisChar = + t >= 26 ? String.fromCharCode(65 + t - 26) : String.fromCharCode(97 + t); + name = thisChar + name; + index = ((index - t) / 52) | 0; + } + if (!name) { + name = "_"; + } + return name; +} diff --git a/src/validateOptions.ts b/src/validateOptions.ts new file mode 100644 index 0000000..9fdff20 --- /dev/null +++ b/src/validateOptions.ts @@ -0,0 +1,286 @@ +import { ok } from "assert"; +import { ObfuscateOptions } from "./options"; +import presets from "./presets"; + +const validProperties = new Set([ + "preset", + "target", + "indent", + "compact", + "hexadecimalNumbers", + "minify", + "es5", + "renameVariables", + "renameGlobals", + "identifierGenerator", + "controlFlowFlattening", + "globalConcealing", + "stringCompression", + "stringConcealing", + "stringEncoding", + "stringSplitting", + "duplicateLiteralsRemoval", + "dispatcher", + "rgf", + "objectExtraction", + "flatten", + "deadCode", + "calculator", + "lock", + "movedDeclarations", + "opaquePredicates", + "shuffle", + "stack", + "verbose", + "globalVariables", + "debugComments", + "preserveFunctionLength", +]); + +const validLockProperties = new Set([ + "selfDefending", + "antiDebug", + "context", + "tamperProtection", + "startDate", + "endDate", + "domainLock", + "osLock", + "browserLock", + "integrity", + "countermeasures", +]); + +const validOses = new Set(["windows", "linux", "osx", "ios", "android"]); +const validBrowsers = new Set([ + "firefox", + "chrome", + "iexplorer", + "edge", + "safari", + "opera", +]); + +export function validateOptions(options: ObfuscateOptions) { + if (!options || Object.keys(options).length <= 1) { + /** + * Give a welcoming introduction to those who skipped the documentation. + */ + var line = `You provided zero obfuscation options. By default everything is disabled.\nYou can use a preset with:\n\n> {target: '${ + options.target || "node" + }', preset: 'high' | 'medium' | 'low'}.\n\n\nView all settings here:\nhttps://github.com/MichaelXF/js-confuser#options`; + throw new Error( + `\n\n` + + line + .split("\n") + .map((x) => `\t${x}`) + .join("\n") + + `\n\n` + ); + } + + ok(options, "options cannot be null"); + ok( + options.target, + "Missing options.target option (required, must one the following: 'browser' or 'node')" + ); + + ok( + ["browser", "node"].includes(options.target), + `'${options.target}' is not a valid target mode` + ); + + Object.keys(options).forEach((key) => { + if (!validProperties.has(key)) { + throw new TypeError("Invalid option: '" + key + "'"); + } + }); + + if ( + options.target === "node" && + options.lock && + options.lock.browserLock && + options.lock.browserLock.length + ) { + throw new TypeError('browserLock can only be used when target="browser"'); + } + + if (options.lock) { + ok(typeof options.lock === "object", "options.lock must be an object"); + Object.keys(options.lock).forEach((key) => { + if (!validLockProperties.has(key)) { + throw new TypeError("Invalid lock option: '" + key + "'"); + } + }); + + // Validate browser-lock option + if ( + options.lock.browserLock && + typeof options.lock.browserLock !== "undefined" + ) { + ok( + Array.isArray(options.lock.browserLock), + "browserLock must be an array" + ); + ok( + !options.lock.browserLock.find( + (browserName) => !validBrowsers.has(browserName) + ), + 'Invalid browser name. Allowed: "firefox", "chrome", "iexplorer", "edge", "safari", "opera"' + ); + } + // Validate os-lock option + if (options.lock.osLock && typeof options.lock.osLock !== "undefined") { + ok(Array.isArray(options.lock.osLock), "osLock must be an array"); + ok( + !options.lock.osLock.find((osName) => !validOses.has(osName)), + 'Invalid OS name. Allowed: "windows", "linux", "osx", "ios", "android"' + ); + } + // Validate domain-lock option + if ( + options.lock.domainLock && + typeof options.lock.domainLock !== "undefined" + ) { + ok(Array.isArray(options.lock.domainLock), "domainLock must be an array"); + } + + // Validate context option + if (options.lock.context && typeof options.lock.context !== "undefined") { + ok(Array.isArray(options.lock.context), "context must be an array"); + } + + // Validate start-date option + if ( + typeof options.lock.startDate !== "undefined" && + options.lock.startDate + ) { + ok( + typeof options.lock.startDate === "number" || + options.lock.startDate instanceof Date, + "startDate must be Date object or number" + ); + } + + // Validate end-date option + if (typeof options.lock.endDate !== "undefined" && options.lock.endDate) { + ok( + typeof options.lock.endDate === "number" || + options.lock.endDate instanceof Date, + "endDate must be Date object or number" + ); + } + } + + if (options.preset) { + if (!presets[options.preset]) { + throw new TypeError("Unknown preset of '" + options.preset + "'"); + } + } +} + +/** + * Sets the default values and validates the configuration. + */ +export function applyDefaultsToOptions( + options: ObfuscateOptions +): ObfuscateOptions { + if (options.preset) { + // Clone and allow overriding + options = Object.assign({}, presets[options.preset], options); + } + + if (!options.hasOwnProperty("debugComments")) { + options.debugComments = false; // debugComments is off by default + } + + if (!options.hasOwnProperty("compact")) { + options.compact = true; // Compact is on by default + } + if (!options.hasOwnProperty("renameGlobals")) { + options.renameGlobals = true; // RenameGlobals is on by default + } + if (!options.hasOwnProperty("preserveFunctionLength")) { + options.preserveFunctionLength = true; // preserveFunctionLength is on by default + } + + if (options.globalVariables && !(options.globalVariables instanceof Set)) { + options.globalVariables = new Set(Object.keys(options.globalVariables)); + } + + if (options.lock) { + if (options.lock.selfDefending) { + options.compact = true; // self defending forcibly enables this + } + } + + // options.globalVariables outlines generic globals that should be present in the execution context + if (!options.hasOwnProperty("globalVariables")) { + options.globalVariables = new Set([]); + + if (options.target == "browser") { + // browser + [ + "window", + "document", + "postMessage", + "alert", + "confirm", + "location", + "btoa", + "atob", + "unescape", + "encodeURIComponent", + ].forEach((x) => options.globalVariables.add(x)); + } else { + // node + [ + "global", + "Buffer", + "require", + "process", + "exports", + "module", + "__dirname", + "__filename", + ].forEach((x) => options.globalVariables.add(x)); + } + + [ + "globalThis", + "console", + "parseInt", + "parseFloat", + "Math", + "JSON", + "Promise", + "String", + "Boolean", + "Function", + "Object", + "Array", + "Proxy", + "Error", + "TypeError", + "ReferenceError", + "RangeError", + "EvalError", + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "queueMicrotask", + "isNaN", + "isFinite", + "Set", + "Map", + "WeakSet", + "WeakMap", + "Symbol", + ].forEach((x) => options.globalVariables.add(x)); + } + + return options; +} diff --git a/src/compiler.ts b/src_old/compiler.ts similarity index 100% rename from src/compiler.ts rename to src_old/compiler.ts diff --git a/src/constants.ts b/src_old/constants.ts similarity index 100% rename from src/constants.ts rename to src_old/constants.ts diff --git a/src_old/index.ts b/src_old/index.ts new file mode 100644 index 0000000..9347ecf --- /dev/null +++ b/src_old/index.ts @@ -0,0 +1,198 @@ +import compileJs, { compileJsSync } from "./compiler"; +import parseJS, { parseSync } from "./parser"; +import Obfuscator from "./obfuscator"; +import Transform from "./transforms/transform"; +import Template from "./templates/template"; +import { remove$Properties } from "./util/object"; +import presets from "./presets"; + +import * as assert from "assert"; +import { correctOptions, ObfuscateOptions, validateOptions } from "./options"; +import { + IJsConfuser, + IJsConfuserDebugObfuscation, + IJsConfuserDebugTransformations, +} from "./types"; + +/** + * **JsConfuser**: Obfuscates JavaScript. + * @param code - The code to be obfuscated. + * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. + */ +export async function obfuscate(code: string, options: ObfuscateOptions) { + return await JsConfuser(code, options); +} + +/** + * Obfuscates an [ESTree](https://github.com/estree/estree) compliant AST. + * + * **Note:** Mutates the object. + * + * @param AST - The [ESTree](https://github.com/estree/estree) compliant AST. This object will be mutated. + * @param options - The obfuscation options. + * + * [See all settings here](https://github.com/MichaelXF/js-confuser#options) + */ +export async function obfuscateAST(AST, options: ObfuscateOptions) { + assert.ok(typeof AST === "object", "AST must be type object"); + assert.ok(AST.type == "Program", "AST.type must be equal to 'Program'"); + validateOptions(options); + + options = await correctOptions(options); + + var obfuscator = new Obfuscator(options as any); + + await obfuscator.apply(AST); + + options.verbose && console.log("* Removing $ properties"); + + remove$Properties(AST); +} + +/** + * **JsConfuser**: Obfuscates JavaScript. + * @param code - The code to be obfuscated. + * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. + */ +var JsConfuser: IJsConfuser = async function ( + code: string, + options: ObfuscateOptions +): Promise { + if (typeof code !== "string") { + throw new TypeError("code must be type string"); + } + validateOptions(options); + + options = await correctOptions(options); + + options.verbose && console.log("* Parsing source code"); + + var tree = await parseJS(code); + + options.verbose && console.log("* Obfuscating..."); + + var obfuscator = new Obfuscator(options as any); + + await obfuscator.apply(tree); + + options.verbose && console.log("* Removing $ properties"); + + remove$Properties(tree); + + options.verbose && console.log("* Generating code"); + + var result = await compileJs(tree, options); + + return result; +} as any; + +export const debugTransformations: IJsConfuserDebugTransformations = + async function ( + code: string, + options: ObfuscateOptions + ): Promise<{ name: string; code: string; ms: number }[]> { + validateOptions(options); + options = await correctOptions(options); + + var frames = []; + + var tree = parseSync(code); + var obfuscator = new Obfuscator(options as any); + + var time = Date.now(); + + obfuscator.on("debug", (name: string, tree: Node) => { + frames.push({ + name: name, + code: compileJsSync(tree, options), + ms: Date.now() - time, + }); + + time = Date.now(); + }); + + await obfuscator.apply(tree, true); + + return frames; + }; + +/** + * This method is used by the obfuscator website to display a progress bar and additional information + * about the obfuscation. + * + * @param code - Source code to obfuscate + * @param options - Options + * @param callback - Progress callback, called after each transformation + * @returns + */ +export const debugObfuscation: IJsConfuserDebugObfuscation = async function ( + code: string, + options: ObfuscateOptions, + callback: (name: string, complete: number, totalTransforms: number) => void, + performance: Performance +) { + const startTime = performance.now(); + + validateOptions(options); + options = await correctOptions(options); + + const beforeParseTime = performance.now(); + + var tree = parseSync(code); + + const parseTime = performance.now() - beforeParseTime; + + var obfuscator = new Obfuscator(options as any); + var totalTransforms = obfuscator.array.length; + + var transformationTimes = Object.create(null); + var currentTransformTime = performance.now(); + + obfuscator.on("debug", (name: string, tree: Node, i: number) => { + var nowTime = performance.now(); + transformationTimes[name] = nowTime - currentTransformTime; + currentTransformTime = nowTime; + + callback(name, i, totalTransforms); + }); + + await obfuscator.apply(tree, true); + + const beforeCompileTime = performance.now(); + + var output = await compileJs(tree, options); + + const compileTime = performance.now() - beforeCompileTime; + + const endTime = performance.now(); + + return { + obfuscated: output, + transformationTimes: transformationTimes, + obfuscationTime: endTime - startTime, + parseTime: parseTime, + compileTime: compileTime, + totalTransforms: totalTransforms, + totalPossibleTransforms: obfuscator.totalPossibleTransforms, + }; +}; + +JsConfuser.obfuscate = obfuscate; +JsConfuser.obfuscateAST = obfuscateAST; +JsConfuser.presets = presets; +JsConfuser.debugTransformations = debugTransformations; +JsConfuser.debugObfuscation = debugObfuscation; +JsConfuser.Obfuscator = Obfuscator; +JsConfuser.Transform = Transform; +JsConfuser.Template = Template; + +if (typeof window !== "undefined") { + window["JsConfuser"] = JsConfuser; +} +if (typeof global !== "undefined") { + global["JsConfuser"] = JsConfuser; +} + +export default JsConfuser; + +export { presets, Obfuscator, Transform, Template }; diff --git a/src_old/obfuscator.ts b/src_old/obfuscator.ts new file mode 100644 index 0000000..995d19b --- /dev/null +++ b/src_old/obfuscator.ts @@ -0,0 +1,165 @@ +import { ok } from "assert"; +import { EventEmitter } from "events"; +import { Node } from "./util/gen"; +import traverse from "./traverse"; +import { ObfuscateOptions } from "./options"; +import { ProbabilityMap, isProbabilityMapProbable } from "./probability"; + +import Transform from "./transforms/transform"; + +import Preparation from "./transforms/preparation"; +import ObjectExtraction from "./transforms/extraction/objectExtraction"; +import Lock from "./transforms/lock/lock"; +import Dispatcher from "./transforms/dispatcher"; +import DeadCode from "./transforms/deadCode"; +import OpaquePredicates from "./transforms/opaquePredicates"; +import Calculator from "./transforms/calculator"; +import ControlFlowFlattening from "./transforms/controlFlowFlattening/controlFlowFlattening"; +import GlobalConcealing from "./transforms/identifier/globalConcealing"; +import StringSplitting from "./transforms/string/stringSplitting"; +import StringConcealing from "./transforms/string/stringConcealing"; +import StringCompression from "./transforms/string/stringCompression"; +import DuplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval"; +import Shuffle from "./transforms/shuffle"; +import MovedDeclarations from "./transforms/identifier/movedDeclarations"; +import RenameVariables from "./transforms/identifier/renameVariables"; +import RenameLabels from "./transforms/renameLabels"; +import Minify from "./transforms/minify"; +import ES5 from "./transforms/es5/es5"; +import RGF from "./transforms/rgf"; +import Flatten from "./transforms/flatten"; +import Stack from "./transforms/stack"; +import AntiTooling from "./transforms/antiTooling"; +import Finalizer from "./transforms/finalizer"; + +/** + * The parent transformation holding the `state`. + */ +export default class Obfuscator extends EventEmitter { + varCount: number; + transforms: { [name: string]: Transform }; + array: Transform[]; + + state: "transform" | "eval" = "transform"; + generated: Set; + + totalPossibleTransforms: number; + + constructor(public options: ObfuscateOptions) { + super(); + + this.varCount = 0; + this.transforms = Object.create(null); + this.generated = new Set(); + this.totalPossibleTransforms = 0; + + const test = (map: ProbabilityMap, ...transformers: any[]) => { + this.totalPossibleTransforms += transformers.length; + + if (isProbabilityMapProbable(map)) { + // options.verbose && console.log("+ Added " + transformer.name); + + transformers.forEach((Transformer) => this.push(new Transformer(this))); + } else { + // options.verbose && console.log("- Skipped adding " + transformer.name); + } + }; + + // Optimization: Only add needed transformers. If a probability always return false, no need in running that extra code. + test(true, Preparation); + test(true, RenameLabels); + + test(options.objectExtraction, ObjectExtraction); + test(options.flatten, Flatten); + test(options.rgf, RGF); + test(options.dispatcher, Dispatcher); + test(options.deadCode, DeadCode); + test(options.calculator, Calculator); + test(options.controlFlowFlattening, ControlFlowFlattening); + test(options.globalConcealing, GlobalConcealing); + test(options.opaquePredicates, OpaquePredicates); + test(options.stringSplitting, StringSplitting); + test(options.stringConcealing, StringConcealing); + test(options.stringCompression, StringCompression); + test(options.stack, Stack); + test(options.duplicateLiteralsRemoval, DuplicateLiteralsRemoval); + test(options.shuffle, Shuffle); + test(options.movedDeclarations, MovedDeclarations); + test(options.minify, Minify); + test(options.renameVariables, RenameVariables); + test(options.es5, ES5); + + test(true, AntiTooling); + test(true, Finalizer); // String Encoding, Hexadecimal Numbers, BigInt support is included + + if ( + options.lock && + Object.keys(options.lock).filter((x) => + x == "domainLock" + ? options.lock.domainLock && options.lock.domainLock.length + : options.lock[x] + ).length + ) { + test(true, Lock); + } + + // Make array + this.array = Object.values(this.transforms); + + // Sort transformations based on their priority + this.array.sort((a, b) => a.priority - b.priority); + } + + push(transform: Transform) { + if (transform.className) { + ok( + !this.transforms[transform.className], + "Already have " + transform.className + ); + } + this.transforms[transform.className] = transform; + } + + resetState() { + this.varCount = 0; + this.generated = new Set(); + this.state = "transform"; + } + + async apply(tree: Node, debugMode = false) { + ok(tree.type == "Program", "The root node must be type 'Program'"); + ok(Array.isArray(tree.body), "The root's body property must be an array"); + ok(Array.isArray(this.array)); + + this.resetState(); + + var completed = 0; + for (var transform of this.array) { + await transform.apply(tree); + completed++; + + if (debugMode) { + this.emit("debug", transform.className, tree, completed); + } + } + + if (this.options.verbose) { + console.log("-> Check for Eval Callbacks"); + } + + this.state = "eval"; + + // Find eval callbacks + traverse(tree, (o, p) => { + if (o.$eval) { + return () => { + o.$eval(o, p); + }; + } + }); + + if (this.options.verbose) { + console.log("<- Done"); + } + } +} diff --git a/src_old/options.ts b/src_old/options.ts new file mode 100644 index 0000000..a184a35 --- /dev/null +++ b/src_old/options.ts @@ -0,0 +1,898 @@ +import { ok } from "assert"; +import presets from "./presets"; +import { ProbabilityMap } from "./probability"; + +export interface ObfuscateOptions { + /** + * ### `preset` + * + * JS-Confuser comes with three presets built into the obfuscator. + * + * | Preset | Transforms | Performance Reduction | Sample | + * | --- | --- | --- | --- | + * | High | 22/25 | 98% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/high.js) | + * | Medium | 19/25 | 52% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/medium.js) | + * | Low | 15/25 | 30% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/low.js) | + * + * You can extend each preset or all go without them entirely. (`"high"/"medium"/"low"`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + preset?: "high" | "medium" | "low" | false; + + /** + * ### `target` + * + * The execution context for your output. _Required_. + * + * 1. `"node"` + * 2. `"browser"` + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + target: "node" | "browser"; + + /** + * ### `indent` + * + * Controls the indentation of the output. + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + indent?: 2 | 4 | "tabs"; + + /** + * ### `compact` + * + * Remove's whitespace from the final output. (`true/false`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + compact?: boolean; + + /** + * ### `hexadecimalNumbers` + * + * Uses the hexadecimal representation for numbers. (`true/false`) + */ + hexadecimalNumbers?: boolean; + + /** + * ### `minify` + * + * Minifies redundant code. (`true/false`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + minify?: boolean; + + /** + * ### `es5` + * + * Converts output to ES5-compatible code. (`true/false`) + * + * Does not cover all cases such as Promises or Generator functions. Use [Babel](https://babel.dev/). + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + es5?: boolean; + + /** + * ### `renameVariables` + * + * Determines if variables should be renamed. (`true/false`) + * - Potency High + * - Resilience High + * - Cost Medium + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + renameVariables?: ProbabilityMap; + + /** + * ### `renameGlobals` + * + * Renames top-level variables, turn this off for web-related scripts. Enabled by default. (`true/false`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + renameGlobals?: ProbabilityMap; + + /** + * ### `identifierGenerator` + * + * Determines how variables are renamed. + * + * | Mode | Description | Example | + * | --- | --- | --- | + * | `"hexadecimal"` | Random hex strings | \_0xa8db5 | + * | `"randomized"` | Random characters | w$Tsu4G | + * | `"zeroWidth"` | Invisible characters | U+200D | + * | `"mangled"` | Alphabet sequence | a, b, c | + * | `"number"` | Numbered sequence | var_1, var_2 | + * | `` | Write a custom name generator | See Below | + * + * ```js + * // Custom implementation + * JsConfuser.obfuscate(code, { + * target: "node", + * renameVariables: true, + * identifierGenerator: function () { + * return "$" + Math.random().toString(36).substring(7); + * }, + * }); + * + * // Numbered variables + * var counter = 0; + * JsConfuser.obfuscate(code, { + * target: "node", + * renameVariables: true, + * identifierGenerator: function () { + * return "var_" + (counter++); + * }, + * }); + * ``` + * + * JSConfuser tries to reuse names when possible, creating very potent code. + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + identifierGenerator?: ProbabilityMap< + "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number" + >; + + /** + * ### `controlFlowFlattening` + * + * ⚠️ Significantly impacts performance, use sparingly! + * + * Control-flow Flattening hinders program comprehension by creating convoluted switch statements. (`true/false/0-1`) + * + * Use a number to control the percentage from 0 to 1. + * + * - Potency High + * - Resilience High + * - Cost High + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + controlFlowFlattening?: ProbabilityMap; + + /** + * ### `globalConcealing` + * + * Global Concealing hides global variables being accessed. (`true/false`) + * + * - Potency Medium + * - Resilience High + * - Cost Low + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + globalConcealing?: ProbabilityMap; + + /** + * ### `stringCompression` + * + * String Compression uses LZW's compression algorithm to compress strings. (`true/false/0-1`) + * + * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` + * + * - Potency High + * - Resilience Medium + * - Cost Medium + */ + stringCompression?: ProbabilityMap; + + /** + * ### `stringConcealing` + * + * String Concealing involves encoding strings to conceal plain-text values. (`true/false/0-1`) + * + * `"console"` -> `decrypt('<~@rH7+Dert~>')` + * + * - Potency High + * - Resilience Medium + * - Cost Medium + */ + stringConcealing?: ProbabilityMap; + + /** + * ### `stringEncoding` + * + * String Encoding transforms a string into an encoded representation. (`true/false/0-1`) + * + * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` + * + * - Potency Low + * - Resilience Low + * - Cost Low + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + stringEncoding?: ProbabilityMap; + + /** + * ### `stringSplitting` + * + * String Splitting splits your strings into multiple expressions. (`true/false/0-1`) + * + * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` + * + * - Potency Medium + * - Resilience Medium + * - Cost Medium + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + stringSplitting?: ProbabilityMap; + + /** + * ### `duplicateLiteralsRemoval` + * + * Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`) + * + * - Potency Medium + * - Resilience Low + * - Cost High + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + duplicateLiteralsRemoval?: ProbabilityMap; + + /** + * ### `dispatcher` + * + * Creates a middleman function to process function calls. (`true/false/0-1`) + * + * - Potency Medium + * - Resilience Medium + * - Cost High + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + dispatcher?: ProbabilityMap; + + /** + * ### `rgf` + * + * RGF (Runtime-Generated-Functions) uses the [`new Function(code...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) syntax to construct executable code from strings. (`"all"/true/false`) + * + * - **This can break your code. + * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.** + * - The arbitrary code is also obfuscated. + * + * ```js + * // Input + * function log(x){ + * console.log(x) + * } + * + * log("Hello World") + * + * // Output + * var C6z0jyO=[new Function('a2Fjjl',"function OqNW8x(OqNW8x){console['log'](OqNW8x)}return OqNW8x(...Array.prototype.slice.call(arguments,1))")];(function(){return C6z0jyO[0](C6z0jyO,...arguments)}('Hello World')) + * ``` + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + rgf?: ProbabilityMap; + + /** + * ### `stack` + * + * Local variables are consolidated into a rotating array. + * + * [Similar to Jscrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) + * + * - Potency Medium + * - Resilience Medium + * - Cost Low + * + * ```js + * // Input + * function add3(x, y, z){ + * return x + y + z; + * } + * + * // Output + * function iVQoGQD(...iVQoGQD){ + * ~(iVQoGQD.length = 3, iVQoGQD[215] = iVQoGQD[2], iVQoGQD[75] = 227, iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - 75)] = iVQoGQD[75] - (iVQoGQD[75] - 239), iVQoGQD[iVQoGQD[iVQoGQD[75] - 164] - 127] = iVQoGQD[iVQoGQD[75] - 238], iVQoGQD[iVQoGQD[75] - 104] = iVQoGQD[75] - 482, iVQoGQD[iVQoGQD[135] + 378] = iVQoGQD[iVQoGQD[135] + 318] - 335, iVQoGQD[21] = iVQoGQD[iVQoGQD[135] + 96], iVQoGQD[iVQoGQD[iVQoGQD[75] - 104] - (iVQoGQD[75] - 502)] = iVQoGQD[iVQoGQD[75] - 164] - 440); + * return iVQoGQD[75] > iVQoGQD[75] + 90 ? iVQoGQD[iVQoGQD[135] - (iVQoGQD[135] + 54)] : iVQoGQD[iVQoGQD[135] + 117] + iVQoGQD[iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - (iVQoGQD[75] - 104))] - (iVQoGQD[135] - 112)] + iVQoGQD[215]; + * }; + * ``` + */ + stack?: ProbabilityMap; + + /** + * ### `objectExtraction` + * + * Extracts object properties into separate variables. (`true/false`) + * + * - Potency Medium + * - Resilience Medium + * - Cost Low + * + * ```js + * // Input + * var utils = { + * isString: x=>typeof x === "string", + * isBoolean: x=>typeof x === "boolean" + * } + * if ( utils.isString("Hello") ) { + * // ... + * } + * + * // Output + * var utils_isString = x=>typeof x === "string"; + * var utils_isBoolean = x=>typeof x === "boolean" + * if ( utils_isString("Hello") ) { + * // ... + * } + * ``` + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + objectExtraction?: ProbabilityMap; + + /** + * ### `flatten` + * + * Brings independent declarations to the highest scope. (`true/false`) + * + * - Potency Medium + * - Resilience Medium + * - Cost High + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + flatten?: ProbabilityMap; + + /** + * ### `deadCode` + * + * Randomly injects dead code. (`true/false/0-1`) + * + * Use a number to control the percentage from 0 to 1. + * + * - Potency Medium + * - Resilience Medium + * - Cost Low + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + deadCode?: ProbabilityMap; + + /** + * ### `calculator` + * + * Creates a calculator function to handle arithmetic and logical expressions. (`true/false/0-1`) + * + * - Potency Medium + * - Resilience Medium + * - Cost Low + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + calculator?: ProbabilityMap; + + lock?: { + /** + * ### `lock.selfDefending` + * + * Prevents the use of code beautifiers or formatters against your code. + * + * [Identical to Obfuscator.io's Self Defending](https://github.com/javascript-obfuscator/javascript-obfuscator#selfdefending) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + selfDefending?: boolean; + + /** + * ### `lock.antiDebug` + * + * Adds `debugger` statements throughout the code. Additionally adds a background function for DevTools detection. (`true/false/0-1`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + antiDebug?: ProbabilityMap; + + /** + * ### `lock.context` + * + * Properties that must be present on the `window` object (or `global` for NodeJS). (`string[]`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + context?: string[]; + + /** + * ### `lock.tamperProtection` + * + * Tamper Protection safeguards the runtime behavior from being altered by JavaScript pitfalls. (`true/false`) + * + * **⚠️ Tamper Protection requires eval and ran in a non-strict mode environment!** + * + * - **This can break your code.** + * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.** + * + * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md). + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + tamperProtection?: boolean | ((varName: string) => boolean); + + /** + * ### `lock.startDate` + * + * When the program is first able to be used. (`number` or `Date`) + * + * Number should be in milliseconds. + * + * - Potency Low + * - Resilience Medium + * - Cost Medium + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + startDate?: number | Date | false; + + /** + * ### `lock.endDate` + * + * When the program is no longer able to be used. (`number` or `Date`) + * + * Number should be in milliseconds. + * + * - Potency Low + * - Resilience Medium + * - Cost Medium + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + endDate?: number | Date | false; + + /** + * ### `lock.domainLock` + * Array of regex strings that the `window.location.href` must follow. (`Regex[]` or `string[]`) + * + * - Potency Low + * - Resilience Medium + * - Cost Medium + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + domainLock?: RegExp[] | string[] | false; + + /** + * ### `lock.osLock` + * Array of operating-systems where the script is allowed to run. (`string[]`) + * + * - Potency Low + * - Resilience Medium + * - Cost Medium + * + * Allowed values: `"linux"`, `"windows"`, `"osx"`, `"android"`, `"ios"` + * + * Example: `["linux", "windows"]` + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + osLock?: ("linux" | "windows" | "osx" | "android" | "ios")[] | false; + + /** + * ### `lock.browserLock` + * Array of browsers where the script is allowed to run. (`string[]`) + * + * - Potency Low + * - Resilience Medium + * - Cost Medium + * + * Allowed values: `"firefox"`, `"chrome"`, `"iexplorer"`, `"edge"`, `"safari"`, `"opera"` + * + * Example: `["firefox", "chrome"]` + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + browserLock?: + | ("firefox" | "chrome" | "iexplorer" | "edge" | "safari" | "opera")[] + | false; + + /** + * ### `lock.integrity` + * + * Integrity ensures the source code is unchanged. (`true/false/0-1`) + * + * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md). + * + * - Potency Medium + * - Resilience High + * - Cost High + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + integrity?: ProbabilityMap; + + /** + * ### `lock.countermeasures` + * + * A custom callback function to invoke when a lock is triggered. (`string/false`) + * + * This could be due to an invalid domain, incorrect time, or code's integrity changed. + * + * [Learn more about the rules of your countermeasures function](https://github.com/MichaelXF/js-confuser/blob/master/Countermeasures.md). + * + * Otherwise, the obfuscator falls back to crashing the process. + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + countermeasures?: string | boolean; + }; + + /** + * ### `movedDeclarations` + * + * Moves variable declarations to the top of the context. (`true/false`) + * + * - Potency Medium + * - Resilience Medium + * - Cost Low + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + movedDeclarations?: ProbabilityMap; + + /** + * ### `opaquePredicates` + * + * An [Opaque Predicate](https://en.wikipedia.org/wiki/Opaque_predicate) is a predicate(true/false) that is evaluated at runtime, this can confuse reverse engineers + * understanding your code. (`true/false`) + * + * - Potency Medium + * - Resilience Medium + * - Cost Low + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + opaquePredicates?: ProbabilityMap; + + /** + * ### `shuffle` + * + * Shuffles the initial order of arrays. The order is brought back to the original during runtime. (`"hash"/true/false/0-1`) + * + * - Potency Medium + * - Resilience Low + * - Cost Low + * + * | Mode | Description | + * | --- | --- | + * | `"hash"`| Array is shifted based on hash of the elements | + * | `true`| Arrays are shifted *n* elements, unshifted at runtime | + * | `false` | Feature disabled | + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + shuffle?: ProbabilityMap; + + /** + * ### `verbose` + * + * Enable logs to view the obfuscator's state. (`true/false`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + verbose?: boolean; + + /** + * ### `globalVariables` + * + * Set of global variables. *Optional*. (`Set`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + globalVariables?: Set; + + /** + * ### `debugComments` + * + * Enable debug comments. (`true/false`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + debugComments?: boolean; + + /** + * ### `preserveFunctionLength` + * + * Modified functions will retain the correct `function.length` property. Enabled by default. (`true/false`) + * + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + */ + preserveFunctionLength?: boolean; +} + +const validProperties = new Set([ + "preset", + "target", + "indent", + "compact", + "hexadecimalNumbers", + "minify", + "es5", + "renameVariables", + "renameGlobals", + "identifierGenerator", + "controlFlowFlattening", + "globalConcealing", + "stringCompression", + "stringConcealing", + "stringEncoding", + "stringSplitting", + "duplicateLiteralsRemoval", + "dispatcher", + "rgf", + "objectExtraction", + "flatten", + "deadCode", + "calculator", + "lock", + "movedDeclarations", + "opaquePredicates", + "shuffle", + "stack", + "verbose", + "globalVariables", + "debugComments", + "preserveFunctionLength", +]); + +const validLockProperties = new Set([ + "selfDefending", + "antiDebug", + "context", + "tamperProtection", + "startDate", + "endDate", + "domainLock", + "osLock", + "browserLock", + "integrity", + "countermeasures", +]); + +const validOses = new Set(["windows", "linux", "osx", "ios", "android"]); +const validBrowsers = new Set([ + "firefox", + "chrome", + "iexplorer", + "edge", + "safari", + "opera", +]); + +export function validateOptions(options: ObfuscateOptions) { + if (!options || Object.keys(options).length <= 1) { + /** + * Give a welcoming introduction to those who skipped the documentation. + */ + var line = `You provided zero obfuscation options. By default everything is disabled.\nYou can use a preset with:\n\n> {target: '${ + options.target || "node" + }', preset: 'high' | 'medium' | 'low'}.\n\n\nView all settings here:\nhttps://github.com/MichaelXF/js-confuser#options`; + throw new Error( + `\n\n` + + line + .split("\n") + .map((x) => `\t${x}`) + .join("\n") + + `\n\n` + ); + } + + ok(options, "options cannot be null"); + ok( + options.target, + "Missing options.target option (required, must one the following: 'browser' or 'node')" + ); + + ok( + ["browser", "node"].includes(options.target), + `'${options.target}' is not a valid target mode` + ); + + Object.keys(options).forEach((key) => { + if (!validProperties.has(key)) { + throw new TypeError("Invalid option: '" + key + "'"); + } + }); + + if ( + options.target === "node" && + options.lock && + options.lock.browserLock && + options.lock.browserLock.length + ) { + throw new TypeError('browserLock can only be used when target="browser"'); + } + + if (options.lock) { + ok(typeof options.lock === "object", "options.lock must be an object"); + Object.keys(options.lock).forEach((key) => { + if (!validLockProperties.has(key)) { + throw new TypeError("Invalid lock option: '" + key + "'"); + } + }); + + // Validate browser-lock option + if ( + options.lock.browserLock && + typeof options.lock.browserLock !== "undefined" + ) { + ok( + Array.isArray(options.lock.browserLock), + "browserLock must be an array" + ); + ok( + !options.lock.browserLock.find( + (browserName) => !validBrowsers.has(browserName) + ), + 'Invalid browser name. Allowed: "firefox", "chrome", "iexplorer", "edge", "safari", "opera"' + ); + } + // Validate os-lock option + if (options.lock.osLock && typeof options.lock.osLock !== "undefined") { + ok(Array.isArray(options.lock.osLock), "osLock must be an array"); + ok( + !options.lock.osLock.find((osName) => !validOses.has(osName)), + 'Invalid OS name. Allowed: "windows", "linux", "osx", "ios", "android"' + ); + } + // Validate domain-lock option + if ( + options.lock.domainLock && + typeof options.lock.domainLock !== "undefined" + ) { + ok(Array.isArray(options.lock.domainLock), "domainLock must be an array"); + } + + // Validate context option + if (options.lock.context && typeof options.lock.context !== "undefined") { + ok(Array.isArray(options.lock.context), "context must be an array"); + } + + // Validate start-date option + if ( + typeof options.lock.startDate !== "undefined" && + options.lock.startDate + ) { + ok( + typeof options.lock.startDate === "number" || + options.lock.startDate instanceof Date, + "startDate must be Date object or number" + ); + } + + // Validate end-date option + if (typeof options.lock.endDate !== "undefined" && options.lock.endDate) { + ok( + typeof options.lock.endDate === "number" || + options.lock.endDate instanceof Date, + "endDate must be Date object or number" + ); + } + } + + if (options.preset) { + if (!presets[options.preset]) { + throw new TypeError("Unknown preset of '" + options.preset + "'"); + } + } +} + +/** + * Corrects the user's options. Sets the default values and validates the configuration. + * @param options + * @returns + */ +export async function correctOptions( + options: ObfuscateOptions +): Promise { + if (options.preset) { + // Clone and allow overriding + options = Object.assign({}, presets[options.preset], options); + } + + if (!options.hasOwnProperty("debugComments")) { + options.debugComments = false; // debugComments is off by default + } + + if (!options.hasOwnProperty("compact")) { + options.compact = true; // Compact is on by default + } + if (!options.hasOwnProperty("renameGlobals")) { + options.renameGlobals = true; // RenameGlobals is on by default + } + if (!options.hasOwnProperty("preserveFunctionLength")) { + options.preserveFunctionLength = true; // preserveFunctionLength is on by default + } + + if (options.globalVariables && !(options.globalVariables instanceof Set)) { + options.globalVariables = new Set(Object.keys(options.globalVariables)); + } + + if (options.lock) { + if (options.lock.selfDefending) { + options.compact = true; // self defending forcibly enables this + } + } + + // options.globalVariables outlines generic globals that should be present in the execution context + if (!options.hasOwnProperty("globalVariables")) { + options.globalVariables = new Set([]); + + if (options.target == "browser") { + // browser + [ + "window", + "document", + "postMessage", + "alert", + "confirm", + "location", + "btoa", + "atob", + "unescape", + "encodeURIComponent", + ].forEach((x) => options.globalVariables.add(x)); + } else { + // node + [ + "global", + "Buffer", + "require", + "process", + "exports", + "module", + "__dirname", + "__filename", + ].forEach((x) => options.globalVariables.add(x)); + } + + [ + "globalThis", + "console", + "parseInt", + "parseFloat", + "Math", + "JSON", + "Promise", + "String", + "Boolean", + "Function", + "Object", + "Array", + "Proxy", + "Error", + "TypeError", + "ReferenceError", + "RangeError", + "EvalError", + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "queueMicrotask", + "isNaN", + "isFinite", + "Set", + "Map", + "WeakSet", + "WeakMap", + "Symbol", + ].forEach((x) => options.globalVariables.add(x)); + } + + return options; +} diff --git a/src/order.ts b/src_old/order.ts similarity index 100% rename from src/order.ts rename to src_old/order.ts diff --git a/src/parser.ts b/src_old/parser.ts similarity index 100% rename from src/parser.ts rename to src_old/parser.ts diff --git a/src/precedence.ts b/src_old/precedence.ts similarity index 100% rename from src/precedence.ts rename to src_old/precedence.ts diff --git a/src_old/presets.ts b/src_old/presets.ts new file mode 100644 index 0000000..2fb3081 --- /dev/null +++ b/src_old/presets.ts @@ -0,0 +1,120 @@ +import { ObfuscateOptions } from "./options"; + +/** + * - High Obfuscation preset. + * - **Average 90% performance reduction.** + * + * ## **`Enabled features`** + * 1. Variable renaming + * 2. Control flow obfuscation + * 3. String concealing + * 4. Opaque predicates + * 5. Dead code + * 6. Dispatcher + * 7. Moved declarations + * 8. Object extraction + * 9. Global concealing + * 10. Minified output + * + * ## **`Disabled features`** + * - `rgf` Use at your own risk! + * + * ### Potential Issues + * 1. *String Encoding* can corrupt files. Disable `stringEncoding` manually if this happens. + * 2. *Dead Code* can bloat file size. Reduce or disable `deadCode`. + */ +const highPreset: ObfuscateOptions = { + target: "node", + preset: "high", + + calculator: true, + compact: true, + hexadecimalNumbers: true, + controlFlowFlattening: 0.75, + deadCode: 0.2, + dispatcher: true, + duplicateLiteralsRemoval: 0.75, + flatten: true, + globalConcealing: true, + identifierGenerator: "randomized", + minify: true, + movedDeclarations: true, + objectExtraction: true, + opaquePredicates: 0.75, + renameVariables: true, + renameGlobals: true, + shuffle: { hash: 0.5, true: 0.5 }, + stack: true, + stringConcealing: true, + stringCompression: true, + stringEncoding: true, + stringSplitting: 0.75, + + // Use at own risk + rgf: false, +}; + +/** + * - Medium Obfuscation preset. + * - Average 50% performance reduction. + */ +const mediumPreset: ObfuscateOptions = { + target: "node", + preset: "medium", + + calculator: true, + compact: true, + hexadecimalNumbers: true, + controlFlowFlattening: 0.25, + deadCode: 0.025, + dispatcher: 0.5, + duplicateLiteralsRemoval: 0.5, + globalConcealing: true, + identifierGenerator: "randomized", + minify: true, + movedDeclarations: true, + objectExtraction: true, + opaquePredicates: 0.5, + renameVariables: true, + renameGlobals: true, + shuffle: true, + stack: 0.5, + stringConcealing: true, + stringSplitting: 0.25, +}; + +/** + * - Low Obfuscation preset. + * - Average 30% performance reduction. + */ +const lowPreset: ObfuscateOptions = { + target: "node", + preset: "low", + + calculator: true, + compact: true, + hexadecimalNumbers: true, + controlFlowFlattening: 0.1, + deadCode: 0.01, + dispatcher: 0.25, + duplicateLiteralsRemoval: 0.5, + identifierGenerator: "randomized", + minify: true, + movedDeclarations: true, + objectExtraction: true, + opaquePredicates: 0.1, + renameVariables: true, + renameGlobals: true, + stringConcealing: true, +}; + +/** + * Built-in obfuscator presets. + */ +const presets = { + high: highPreset, + medium: mediumPreset, + low: lowPreset, +}; + +export default presets; diff --git a/src_old/probability.ts b/src_old/probability.ts new file mode 100644 index 0000000..6463a27 --- /dev/null +++ b/src_old/probability.ts @@ -0,0 +1,125 @@ +import { ok } from "assert"; +import { createObject } from "./util/object"; + +type Stringed = (V extends string ? V : never) | "true" | "false"; + +/** + * Configurable probabilities for obfuscator options. + * - **`false`** = this feature is disabled + * - **`true`** = this feature is enabled, use default mode + * - **`0.5`** = 50% chance + * - **`"mode"`** = enabled, use specified mode + * - **`["mode1", "mode2"]`** - enabled, choose random mode each occurrence + * - **`{"mode1": 0.5, "mode2": 0.5}`** - enabled, choose based on specified probabilities + * - **`{"mode1": 50, "mode2": 50}`** - enabled, each is divided based on total + * - **`function(x){ return "custom_implementation" }`** - enabled, use specified function + */ +export type ProbabilityMap = + | false + | true + | number + | T + | T[] + | { [key in Stringed]?: number } + | ((...params: any[]) => any); + +/** + * Evaluates a ProbabilityMap. + * @param map The setting object. + * @param runner Custom function to determine return value + * @param customFnArgs Args given to user-implemented function, such as a variable name. + */ +export function ComputeProbabilityMap( + map: ProbabilityMap, + runner: (mode?: T) => any = (x?: T) => x, + ...customFnArgs: any[] +): any { + if (!map) { + return runner(); + } + if (map === true || map === 1) { + return runner(true as any); + } + if (typeof map === "number") { + return runner((Math.random() < map) as any); + } + + if (typeof map === "function") { + return (map as any)(...customFnArgs); + } + if (typeof map === "string") { + return runner(map); + } + var asObject: { [mode: string]: number } = {}; + if (Array.isArray(map)) { + map.forEach((x: any) => { + asObject[x.toString()] = 1; + }); + } else { + asObject = map as any; + } + + var total = Object.values(asObject).reduce((a, b) => a + b); + var percentages = createObject( + Object.keys(asObject), + Object.values(asObject).map((x) => x / total) + ); + + var ticket = Math.random(); + + var count = 0; + var winner = null; + Object.keys(percentages).forEach((key) => { + var x = parseFloat(percentages[key]); + + if (ticket >= count && ticket < count + x) { + winner = key; + } + count += x; + }); + + return runner(winner); +} + +/** + * Determines if a probability map can return a positive result (true, or some string mode). + * - Negative probability maps are used to remove transformations from running entirely. + * @param map + */ +export function isProbabilityMapProbable(map: ProbabilityMap): boolean { + ok(!Number.isNaN(map), "Numbers cannot be NaN"); + + if (!map || typeof map === "undefined") { + return false; + } + if (typeof map === "function") { + return true; + } + if (typeof map === "number") { + if (map > 1 || map < 0) { + throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`); + } + } + if (Array.isArray(map)) { + ok( + map.length != 0, + "Empty arrays are not allowed for options. Use false instead." + ); + + if (map.length == 1) { + return !!map[0]; + } + } + if (typeof map === "object") { + var keys = Object.keys(map); + ok( + keys.length != 0, + "Empty objects are not allowed for options. Use false instead." + ); + + if (keys.length == 1) { + return !!keys[0]; + } + } + return true; +} diff --git a/src/templates/bufferToString.ts b/src_old/templates/bufferToString.ts similarity index 100% rename from src/templates/bufferToString.ts rename to src_old/templates/bufferToString.ts diff --git a/src/templates/core.ts b/src_old/templates/core.ts similarity index 100% rename from src/templates/core.ts rename to src_old/templates/core.ts diff --git a/src/templates/crash.ts b/src_old/templates/crash.ts similarity index 100% rename from src/templates/crash.ts rename to src_old/templates/crash.ts diff --git a/src/templates/es5.ts b/src_old/templates/es5.ts similarity index 100% rename from src/templates/es5.ts rename to src_old/templates/es5.ts diff --git a/src/templates/functionLength.ts b/src_old/templates/functionLength.ts similarity index 100% rename from src/templates/functionLength.ts rename to src_old/templates/functionLength.ts diff --git a/src/templates/globals.ts b/src_old/templates/globals.ts similarity index 100% rename from src/templates/globals.ts rename to src_old/templates/globals.ts diff --git a/src_old/templates/template.ts b/src_old/templates/template.ts new file mode 100644 index 0000000..6b8fd01 --- /dev/null +++ b/src_old/templates/template.ts @@ -0,0 +1,230 @@ +import { Node } from "../util/gen"; +import { parseSnippet, parseSync } from "../parser"; +import { ok } from "assert"; +import { choice } from "../util/random"; +import { placeholderVariablePrefix } from "../constants"; +import traverse from "../traverse"; + +export interface TemplateVariables { + [varName: string]: + | string + | (() => Node | Node[] | Template) + | Node + | Node[] + | Template; +} + +/** + * Templates provides an easy way to parse code snippets into AST subtrees. + * + * These AST subtrees can added to the obfuscated code, tailored with variable names. + * + * 1. Basic string interpolation + * + * ```js + * var Base64Template = new Template(` + * function {name}(str){ + * return btoa(str) + * } + * `); + * + * var functionDeclaration = Base64Template.single({ name: "atob" }); + * ``` + * + * 2. AST subtree insertion + * + * ```js + * var Base64Template = new Template(` + * function {name}(str){ + * {getWindow} + * + * return {getWindowName}btoa(str) + * }`) + * + * var functionDeclaration = Base64Template.single({ + * name: "atob", + * getWindowName: "newWindow", + * getWindow: () => { + * return acorn.parse("var newWindow = {}").body[0]; + * } + * }); + * ``` + * + * Here, the `getWindow` variable is a function that returns an AST subtree. This must be a `Node[]` array or Template. + * Optionally, the function can be replaced with just the `Node[]` array or Template if it's already computed. + * + * 3. Template subtree insertion + * + * ```js + * var NewWindowTemplate = new Template(` + * var newWindow = {}; + * `); + * + * var Base64Template = new Template(` + * function {name}(str){ + * {NewWindowTemplate} + * + * return newWindow.btoa(str) + * }`) + * + * var functionDeclaration = Base64Template.single({ + * name: "atob", + * NewWindowTemplate: NewWindowTemplate + * }); + * ``` + */ +export default class Template { + templates: string[]; + defaultVariables: TemplateVariables; + requiredVariables: Set; + + constructor(...templates: string[]) { + this.templates = templates; + this.defaultVariables = Object.create(null); + this.requiredVariables = new Set(); + + this.findRequiredVariables(); + } + + setDefaultVariables(defaultVariables: TemplateVariables): this { + this.defaultVariables = defaultVariables; + return this; + } + + private findRequiredVariables() { + var matches = this.templates[0].match(/{[$A-z0-9_]+}/g); + if (matches !== null) { + matches.forEach((variable) => { + var name = variable.slice(1, -1); + + // $ variables are for default variables + if (name.startsWith("$")) { + throw new Error("Default variables are no longer supported."); + } else { + this.requiredVariables.add(name); + } + }); + } + } + + /** + * Interpolates the template with the given variables. + * + * Prepares the template string for AST parsing. + * + * @param variables + */ + private interpolateTemplate(variables: TemplateVariables = {}) { + var allVariables = { ...this.defaultVariables, ...variables }; + + // Validate all variables were passed in + for (var requiredVariable of this.requiredVariables) { + if (typeof allVariables[requiredVariable] === "undefined") { + throw new Error( + this.templates[0] + + " missing variable: " + + requiredVariable + + " from " + + JSON.stringify(allVariables) + ); + } + } + + var template = choice(this.templates); + var output = template; + + Object.keys(allVariables).forEach((name) => { + var bracketName = "{" + name.replace("$", "\\$") + "}"; + + var value = allVariables[name] + ""; + if (typeof allVariables[name] !== "string") { + value = name; + } + + var reg = new RegExp(bracketName, "g"); + + output = output.replace(reg, value); + }); + + return { output, template }; + } + + /** + * Finds the variables in the AST and replaces them with the given values. + * + * Note: Mutates the AST. + * @param ast + * @param variables + */ + private interpolateAST(ast: Node, variables: TemplateVariables) { + var allVariables = { ...this.defaultVariables, ...variables }; + + var astNames = new Set( + Object.keys(allVariables).filter((name) => { + return typeof allVariables[name] !== "string"; + }) + ); + + if (astNames.size === 0) return; + + traverse(ast, (o, p) => { + if (o.type === "Identifier" && allVariables[o.name]) { + return () => { + var value = allVariables[o.name]; + ok(typeof value !== "string"); + + var insertNodes = typeof value === "function" ? value() : value; + if (insertNodes instanceof Template) { + insertNodes = insertNodes.compile(allVariables); + } + + if (!Array.isArray(insertNodes)) { + // Replace with expression + + Object.assign(o, insertNodes); + } else { + // Insert multiple statements/declarations + var expressionStatement: Node = p[0]; + var body: Node[] = p[1] as any; + + ok(expressionStatement.type === "ExpressionStatement"); + ok(Array.isArray(body)); + + var index = body.indexOf(expressionStatement); + + body.splice(index, 1, ...insertNodes); + } + }; + } + }); + } + + compile(variables: TemplateVariables = {}): Node[] { + var { output, template } = this.interpolateTemplate(variables); + + var program: Node; + try { + program = parseSnippet(output); + } catch (e) { + throw new Error(output + "\n" + "Template failed to parse: " + e.message); + } + + this.interpolateAST(program, variables); + + return program.body; + } + + single(variables: TemplateVariables = {}): Node { + var nodes = this.compile(variables); + + if (nodes.length !== 1) { + nodes = nodes.filter((node) => node.type !== "EmptyStatement"); + ok( + nodes.length === 1, + `Expected single node, got ${nodes.map((node) => node.type).join(", ")}` + ); + } + + return nodes[0]; + } +} diff --git a/src/transforms/antiTooling.ts b/src_old/transforms/antiTooling.ts similarity index 100% rename from src/transforms/antiTooling.ts rename to src_old/transforms/antiTooling.ts diff --git a/src/transforms/calculator.ts b/src_old/transforms/calculator.ts similarity index 100% rename from src/transforms/calculator.ts rename to src_old/transforms/calculator.ts diff --git a/src/transforms/controlFlowFlattening/controlFlowFlattening.ts b/src_old/transforms/controlFlowFlattening/controlFlowFlattening.ts similarity index 100% rename from src/transforms/controlFlowFlattening/controlFlowFlattening.ts rename to src_old/transforms/controlFlowFlattening/controlFlowFlattening.ts diff --git a/src/transforms/controlFlowFlattening/expressionObfuscation.ts b/src_old/transforms/controlFlowFlattening/expressionObfuscation.ts similarity index 100% rename from src/transforms/controlFlowFlattening/expressionObfuscation.ts rename to src_old/transforms/controlFlowFlattening/expressionObfuscation.ts diff --git a/src_old/transforms/deadCode.ts b/src_old/transforms/deadCode.ts new file mode 100644 index 0000000..ad456e3 --- /dev/null +++ b/src_old/transforms/deadCode.ts @@ -0,0 +1,676 @@ +import { ObfuscateOrder } from "../order"; +import { ComputeProbabilityMap } from "../probability"; +import Template from "../templates/template"; +import { isBlock } from "../traverse"; +import { + AssignmentExpression, + BinaryExpression, + ExpressionStatement, + Identifier, + IfStatement, + Literal, + MemberExpression, + Node, + ObjectExpression, + VariableDeclaration, + VariableDeclarator, +} from "../util/gen"; +import { getBlockBody, isFunction, prepend } from "../util/insert"; +import { chance, choice, getRandomInteger } from "../util/random"; +import Transform from "./transform"; + +const templates = [ + new Template(` + function curCSS( elem, name, computed ) { + var ret; + + computed = computed || getStyles( elem ); + + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = redacted.style( elem, name ); + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11+ + // IE returns zIndex value as an integer. + ret + "" : + ret; + }`), + new Template(` + function Example() { + var state = redacted.useState(false); + return x( + ErrorBoundary, + null, + x( + DisplayName, + null, + ) + ); + }`), + + new Template(` + const path = require('path'); +const { version } = require('../../package'); +const { version: dashboardPluginVersion } = require('@redacted/enterprise-plugin/package'); +const { version: componentsVersion } = require('@redacted/components/package'); +const { sdkVersion } = require('@redacted/enterprise-plugin'); +const isStandaloneExecutable = require('../utils/isStandaloneExecutable'); +const resolveLocalRedactedPath = require('./resolve-local-redacted-path'); + +const redactedPath = path.resolve(__dirname, '../redacted.js');`), + + new Template(` +module.exports = async (resolveLocalRedactedPath = ()=>{throw new Error("No redacted path provided")}) => { + const cliParams = new Set(process.argv.slice(2)); + if (!cliParams.has('--version')) { + if (cliParams.size !== 1) return false; + if (!cliParams.has('-v')) return false; + } + + const installationModePostfix = await (async (isStandaloneExecutable, redactedPath) => { + if (isStandaloneExecutable) return ' (standalone)'; + if (redactedPath === (await resolveLocalRedactedPath())) return ' (local)'; + return ''; + })(); + + return true; +};`), + new Template(` +function setCookie(cname, cvalue, exdays) { + var d = new Date(); + d.setTime(d.getTime() + (exdays*24*60*60*1000)); + var expires = "expires="+ d.toUTCString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; +}`), + + new Template(`function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for(var i = 0; i 1 + ) { + return Infinity; + } + + const currentHeight = Math.max(leftTreeHeight, rightTreeHeight) + 1; + return currentHeight; + } + + window["__GLOBAL__HELPERS__"] = { + buildCharacterMap, + isAnagrams, + isBalanced, + getHeightBalanced, + }; + `), + new Template(` + function ListNode(){} + var addTwoNumbers = function(l1, l2) { + var carry = 0; + var sum = 0; + var head = new ListNode(0); + var now = head; + var a = l1; + var b = l2; + while (a !== null || b !== null) { + sum = (a ? a.val : 0) + (b ? b.val : 0) + carry; + carry = Math.floor(sum / 10); + now.next = new ListNode(sum % 10); + now = now.next; + a = a ? a.next : null; + b = b ? b.next : null; + } + if (carry) now.next = new ListNode(carry); + return head.next; + }; + + console.log(addTwoNumbers) + `), + new Template(` + var threeSum = function(nums) { + var len = nums.length; + var res = []; + var l = 0; + var r = 0; + nums.sort((a, b) => (a - b)); + for (var i = 0; i < len; i++) { + if (i > 0 && nums[i] === nums[i - 1]) continue; + l = i + 1; + r = len - 1; + while (l < r) { + if (nums[i] + nums[l] + nums[r] < 0) { + l++; + } else if (nums[i] + nums[l] + nums[r] > 0) { + r--; + } else { + res.push([nums[i], nums[l], nums[r]]); + while (l < r && nums[l] === nums[l + 1]) l++; + while (l < r && nums[r] === nums[r - 1]) r--; + l++; + r--; + } + } + } + return res; + }; + console.log(threeSum) + `), + new Template(` + var combinationSum2 = function(candidates, target) { + var res = []; + var len = candidates.length; + candidates.sort((a, b) => (a - b)); + dfs(res, [], 0, len, candidates, target); + return res; + }; + + var dfs = function (res, stack, index, len, candidates, target) { + var tmp = null; + if (target < 0) return; + if (target === 0) return res.push(stack); + for (var i = index; i < len; i++) { + if (candidates[i] > target) break; + if (i > index && candidates[i] === candidates[i - 1]) continue; + tmp = Array.from(stack); + tmp.push(candidates[i]); + dfs(res, tmp, i + 1, len, candidates, target - candidates[i]); + } + }; + + console.log(combinationSum2); + `), + new Template(` + var isScramble = function(s1, s2) { + return helper({}, s1, s2); + }; + + var helper = function (dp, s1, s2) { + var map = {}; + + if (dp[s1 + s2] !== undefined) return dp[s1 + s2]; + if (s1 === s2) return true; + + for (var j = 0; j < s1.length; j++) { + if (map[s1[j]] === undefined) map[s1[j]] = 0; + if (map[s2[j]] === undefined) map[s2[j]] = 0; + map[s1[j]]++; + map[s2[j]]--; + } + + for (var key in map) { + if (map[key] !== 0) { + dp[s1 + s2] = false; + return false; + } + } + + for (var i = 1; i < s1.length; i++) { + if ((helper(dp, s1.substr(0, i), s2.substr(0, i)) + && helper(dp, s1.substr(i), s2.substr(i))) || + (helper(dp, s1.substr(0, i), s2.substr(s2.length - i)) + && helper(dp, s1.substr(i), s2.substr(0, s2.length - i)))) { + dp[s1 + s2] = true; + return true; + } + } + + dp[s1 + s2] = false; + return false; + }; + + console.log(isScramble); + `), + new Template(` + var candy = function(ratings) { + var len = ratings.length; + var res = []; + var sum = 0; + for (var i = 0; i < len; i++) { + res.push((i !== 0 && ratings[i] > ratings[i - 1]) ? (res[i - 1] + 1) : 1); + } + for (var j = len - 1; j >= 0; j--) { + if (j !== len - 1 && ratings[j] > ratings[j + 1]) res[j] = Math.max(res[j], res[j + 1] + 1); + sum += res[j]; + } + return sum; + }; + + console.log(candy) + `), + new Template(` + var maxPoints = function(points) { + var max = 0; + var map = {}; + var localMax = 0; + var samePoint = 0; + var k = 0; + var len = points.length; + for (var i = 0; i < len; i++) { + map = {}; + localMax = 0; + samePoint = 1; + for (var j = i + 1; j < len; j++) { + if (points[i].x === points[j].x && points[i].y === points[j].y) { + samePoint++; + continue; + } + if (points[i].y === points[j].y) k = Number.MAX_SAFE_INTEGER; + else k = (points[i].x - points[j].x) / (points[i].y - points[j].y); + if (!map[k]) map[k] = 0; + map[k]++; + localMax = Math.max(localMax, map[k]); + } + localMax += samePoint; + max = Math.max(max, localMax); + } + return max; + }; + + console.log(maxPoints) + `), + new Template(` + var maximumGap = function(nums) { + var len = nums.length; + if (len < 2) return 0; + + var max = Math.max(...nums); + var min = Math.min(...nums); + if (max === min) return 0; + + var minBuckets = Array(len - 1).fill(Number.MAX_SAFE_INTEGER); + var maxBuckets = Array(len - 1).fill(Number.MIN_SAFE_INTEGER); + var gap = Math.ceil((max - min) / (len - 1)); + var index = 0; + for (var i = 0; i < len; i++) { + if (nums[i] === min || nums[i] === max) continue; + index = Math.floor((nums[i] - min) / gap); + minBuckets[index] = Math.min(minBuckets[index], nums[i]); + maxBuckets[index] = Math.max(maxBuckets[index], nums[i]); + } + + var maxGap = Number.MIN_SAFE_INTEGER; + var preVal = min; + for (var j = 0; j < len - 1; j++) { + if (minBuckets[j] === Number.MAX_SAFE_INTEGER && maxBuckets[j] === Number.MIN_SAFE_INTEGER) continue; + maxGap = Math.max(maxGap, minBuckets[j] - preVal); + preVal = maxBuckets[j]; + } + maxGap = Math.max(maxGap, max - preVal); + + return maxGap; + }; + + console.log(maximumGap); + `), + new Template(` + var LRUCache = function(capacity) { + this.capacity = capacity; + this.length = 0; + this.map = {}; + this.head = null; + this.tail = null; + }; + + LRUCache.prototype.get = function(key) { + var node = this.map[key]; + if (node) { + this.remove(node); + this.insert(node.key, node.val); + return node.val; + } else { + return -1; + } + }; + + LRUCache.prototype.put = function(key, value) { + if (this.map[key]) { + this.remove(this.map[key]); + this.insert(key, value); + } else { + if (this.length === this.capacity) { + this.remove(this.head); + this.insert(key, value); + } else { + this.insert(key, value); + this.length++; + } + } + }; + + /** + * Your LRUCache object will be instantiated and called as such: + * var obj = Object.create(LRUCache).createNew(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ + + LRUCache.prototype.remove = function (node) { + var prev = node.prev; + var next = node.next; + if (next) next.prev = prev; + if (prev) prev.next = next; + if (this.head === node) this.head = next; + if (this.tail === node) this.tail = prev; + delete this.map[node.key]; + }; + + LRUCache.prototype.insert = function (key, val) { + var node = new List(key, val); + if (!this.tail) { + this.tail = node; + this.head = node; + } else { + this.tail.next = node; + node.prev = this.tail; + this.tail = node; + } + this.map[key] = node; + }; + + console.log(LRUCache); + `), + new Template(` + var isInterleave = function(s1, s2, s3) { + var dp = {}; + if (s3.length !== s1.length + s2.length) return false; + return helper(s1, s2, s3, 0, 0, 0, dp); + }; + + var helper = function (s1, s2, s3, i, j, k, dp) { + var res = false; + + if (k >= s3.length) return true; + if (dp['' + i + j + k] !== undefined) return dp['' + i + j + k]; + + if (s3[k] === s1[i] && s3[k] === s2[j]) { + res = helper(s1, s2, s3, i + 1, j, k + 1, dp) || helper(s1, s2, s3, i, j + 1, k + 1, dp); + } else if (s3[k] === s1[i]) { + res = helper(s1, s2, s3, i + 1, j, k + 1, dp); + } else if (s3[k] === s2[j]) { + res = helper(s1, s2, s3, i, j + 1, k + 1, dp); + } + + dp['' + i + j + k] = res; + + return res; + }; + + console.log(isInterleave); + `), + new Template(` + var solveNQueens = function(n) { + var res = []; + if (n === 1 || n >= 4) dfs(res, [], n, 0); + return res; + }; + + var dfs = function (res, points, n, index) { + for (var i = index; i < n; i++) { + if (points.length !== i) return; + for (var j = 0; j < n; j++) { + if (isValid(points, [i, j])) { + points.push([i, j]); + dfs(res, points, n, i + 1); + if (points.length === n) res.push(buildRes(points)); + points.pop(); + } + } + } + }; + + var buildRes = function (points) { + var res = []; + var n = points.length; + for (var i = 0; i < n; i++) { + res[i] = ''; + for (var j = 0; j < n; j++) { + res[i] += (points[i][1] === j ? 'Q' : '.'); + } + } + return res; + }; + + var isValid = function (oldPoints, newPoint) { + var len = oldPoints.length; + for (var i = 0; i < len; i++) { + if (oldPoints[i][0] === newPoint[0] || oldPoints[i][1] === newPoint[1]) return false; + if (Math.abs((oldPoints[i][0] - newPoint[0]) / (oldPoints[i][1] - newPoint[1])) === 1) return false; + } + return true; + }; + + console.log(solveNQueens); + `), +]; + +/** + * Adds dead code to blocks. + * + * - Adds fake predicates. + * - Adds fake code from various samples. + */ +export default class DeadCode extends Transform { + made: number; + + compareObjectName: string; + gen = this.getGenerator("randomized"); + + constructor(o) { + super(o, ObfuscateOrder.DeadCode); + + this.made = 0; + } + + match(object: Node, parents: Node[]) { + return ( + isFunction(object) && + isBlock(object.body) && + !object.$multiTransformSkip && + !parents.find((x) => x.$multiTransformSkip) + ); + } + + transform(object: Node, parents: Node[]) { + if (!ComputeProbabilityMap(this.options.deadCode)) { + return; + } + + // Hard-coded limit of 100 Dead Code insertions + this.made++; + if (this.made > 100) { + return; + } + + return () => { + var body = getBlockBody(object); + + // Do not place code before Import statements or 'use strict' directives + var safeOffset = 0; + for (var node of body) { + if (node.type === "ImportDeclaration" || node.directive) safeOffset++; + else break; + } + + var index = getRandomInteger(safeOffset, body.length); + + if (!this.compareObjectName) { + this.compareObjectName = this.getPlaceholder(); + + prepend( + parents[parents.length - 1] || object, + VariableDeclaration( + VariableDeclarator( + this.compareObjectName, + new Template(`Object["create"](null)`).single().expression + ) + ) + ); + } + + var name = this.getPlaceholder(); + var variableDeclaration = VariableDeclaration( + VariableDeclarator( + name, + BinaryExpression( + "in", + Literal(this.gen.generate()), + Identifier(this.compareObjectName) + ) + ) + ); + + var template: Template; + do { + template = choice(templates); + } while (this.options.es5 && template.templates[0].includes("async")); + + var nodes = template.compile(); + + if (chance(50)) { + nodes.unshift( + ExpressionStatement( + AssignmentExpression( + "=", + MemberExpression( + Identifier(this.compareObjectName), + Literal(this.gen.generate()), + true + ), + Literal(this.gen.generate()) + ) + ) + ); + } + + var ifStatement = IfStatement(Identifier(name), nodes, null); + + body.splice(index, 0, ifStatement); + + prepend(object, variableDeclaration); + }; + } +} diff --git a/src_old/transforms/dispatcher.ts b/src_old/transforms/dispatcher.ts new file mode 100644 index 0000000..b237777 --- /dev/null +++ b/src_old/transforms/dispatcher.ts @@ -0,0 +1,640 @@ +import { walk } from "../traverse"; +import { + ArrayExpression, + AssignmentExpression, + BinaryExpression, + CallExpression, + ExpressionStatement, + FunctionDeclaration, + FunctionExpression, + Identifier, + IfStatement, + Literal, + Node, + Location, + MemberExpression, + ObjectExpression, + Property, + ReturnStatement, + VariableDeclaration, + SequenceExpression, + NewExpression, + UnaryExpression, + BlockStatement, + LogicalExpression, + ThisExpression, + VariableDeclarator, + RestElement, +} from "../util/gen"; +import { getIdentifierInfo } from "../util/identifiers"; +import { + deleteDirect, + getBlockBody, + getVarContext, + isVarContext, + prepend, + append, + computeFunctionLength, + isFunction, +} from "../util/insert"; +import Transform from "./transform"; +import { isInsideType } from "../util/compare"; +import { choice, shuffle } from "../util/random"; +import { ComputeProbabilityMap } from "../probability"; +import { predictableFunctionTag, reservedIdentifiers } from "../constants"; +import { ObfuscateOrder } from "../order"; +import Template from "../templates/template"; +import { FunctionLengthTemplate } from "../templates/functionLength"; +import { ObjectDefineProperty } from "../templates/globals"; +import { getLexicalScope } from "../util/scope"; +import { isJSConfuserVar } from "../util/guard"; + +/** + * A Dispatcher processes function calls. All the function declarations are brought into a dictionary. + * + * ```js + * var param1; + * function dispatcher(key){ + * var fns = { + * 'fn1': function(){ + * var [arg1] = [param1]; + * console.log(arg1); + * } + * } + * return fns[key](); + * }; + * param1 = "Hello World"; + * dispatcher('fn1'); // > "Hello World" + * ``` + * + * Can break code with: + * + * 1. testing function equality, + * 2. using `arguments.callee`, + * 3. using `this` + */ +export default class Dispatcher extends Transform { + // Debug mode preserves function names + isDebug = false; + count: number; + + functionLengthName: string; + + constructor(o) { + super(o, ObfuscateOrder.Dispatcher); + + this.count = 0; + } + + apply(tree: Node): void { + super.apply(tree); + + if (this.options.preserveFunctionLength && this.functionLengthName) { + prepend( + tree, + FunctionLengthTemplate.single({ + name: this.functionLengthName, + ObjectDefineProperty: this.createInitVariable(ObjectDefineProperty, [ + tree, + ]), + }) + ); + } + } + + match(object: Node, parents: Node[]) { + if (isInsideType("AwaitExpression", object, parents)) { + return false; + } + + return ( + isVarContext(object) && + object.type !== "ArrowFunctionExpression" && + !object.$multiTransformSkip && + !parents.find((x) => x.$multiTransformSkip) + ); + } + + transform(object: Node, parents: Node[]) { + return () => { + if (ComputeProbabilityMap(this.options.dispatcher, (mode) => mode)) { + if (object.type != "Program" && object.body.type != "BlockStatement") { + return; + } + + // Map of FunctionDeclarations + var functionDeclarations: { [name: string]: Location } = + Object.create(null); + + // Array of Identifier nodes + var identifiers: Location[] = []; + var illegalFnNames: Set = new Set(); + + // New Names for Functions + var newFnNames: { [name: string]: string } = Object.create(null); // [old name]: randomized name + + var context = isVarContext(object) + ? object + : getVarContext(object, parents); + + var lexicalScope = isFunction(context) ? context.body : context; + + walk(object, parents, (o: Node, p: Node[]) => { + if (object == o) { + // Fix 1 + return; + } + + var c = getVarContext(o, p); + if (o.type == "FunctionDeclaration") { + c = getVarContext(p[0], p.slice(1)); + } + + if (context === c) { + if (o.type == "FunctionDeclaration" && o.id.name) { + var name = o.id.name; + + if ( + o.$requiresEval || + o.async || + o.generator || + p.find( + (x) => x.$multiTransformSkip || x.type == "MethodDefinition" + ) || + o.body.type != "BlockStatement" + ) { + illegalFnNames.add(name); + return; + } + + // Must defined in the same block as the current function being scanned + // Solves 'let' and 'class' declaration issue + var ls = getLexicalScope(o, p); + if (ls !== lexicalScope) { + illegalFnNames.add(name); + return; + } + + // If dupe, no routing + if (functionDeclarations[name]) { + illegalFnNames.add(name); + return; + } + + walk(o, p, (oo, pp) => { + if ( + (oo.type == "Identifier" && oo.name == "arguments") || + oo.type == "ThisExpression" || + oo.type == "Super" + ) { + if (getVarContext(oo, pp) === o) { + illegalFnNames.add(name); + return "EXIT"; + } + } + + // Avoid functions with function expressions as they have a different scope + if ( + (oo.type === "FunctionExpression" || + oo.type === "ArrowFunctionExpression") && + pp.find((x) => x == o.params) + ) { + illegalFnNames.add(name); + return "EXIT"; + } + }); + + functionDeclarations[name] = [o, p]; + } + } + + if (o.type == "Identifier") { + if (reservedIdentifiers.has(o.name)) { + return; + } + var info = getIdentifierInfo(o, p); + if (!info.spec.isReferenced) { + return; + } + if (isJSConfuserVar(p)) { + illegalFnNames.add(o.name); + } + if (info.spec.isDefined) { + if (info.isFunctionDeclaration) { + if ( + p[0].id && + (!functionDeclarations[p[0].id.name] || + functionDeclarations[p[0].id.name][0] !== p[0]) + ) { + illegalFnNames.add(o.name); + } + } else { + illegalFnNames.add(o.name); + } + } else if (info.spec.isModified) { + illegalFnNames.add(o.name); + } else { + identifiers.push([o, p]); + } + } + }); + + illegalFnNames.forEach((name) => { + delete functionDeclarations[name]; + }); + + // map original name->new game + var gen = this.getGenerator(); + Object.keys(functionDeclarations).forEach((name) => { + newFnNames[name] = this.isDebug + ? "_dispatcher_" + this.count + "_" + name + : gen.generate(); + }); + // set containing new name + var set = new Set(Object.keys(newFnNames)); + + // Only make a dispatcher function if it caught any functions + if (set.size > 0) { + if (!this.functionLengthName) { + this.functionLengthName = this.getPlaceholder(); + } + + var payloadArg = + this.getPlaceholder() + "_dispatcher_" + this.count + "_payload"; + + var dispatcherFnName = + this.getPlaceholder() + + "_dispatcher_" + + this.count + + predictableFunctionTag; + + this.log(dispatcherFnName, set); + this.count++; + + var expectedGet = gen.generate(); + var expectedClearArgs = gen.generate(); + var expectedNew = gen.generate(); + + var returnProp = gen.generate(); + var newReturnMemberName = gen.generate(); + + var shuffledKeys = shuffle(Object.keys(functionDeclarations)); + var mapName = this.getPlaceholder(); + + var cacheName = this.getPlaceholder(); + + // creating the dispatcher function + // 1. create function map + var map = VariableDeclaration( + VariableDeclarator( + mapName, + ObjectExpression( + shuffledKeys.map((name) => { + var [def, defParents] = functionDeclarations[name]; + var body = getBlockBody(def.body); + + var functionExpression: Node = { + ...def, + expression: false, + type: "FunctionExpression", + id: null, + params: [], + [predictableFunctionTag]: true, + }; + this.addComment(functionExpression, name); + + if (def.params.length > 0) { + const fixParam = (param: Node) => { + return param; + }; + + var variableDeclaration = VariableDeclaration( + VariableDeclarator( + { + type: "ArrayPattern", + elements: def.params.map(fixParam), + }, + Identifier(payloadArg) + ) + ); + + prepend(def.body, variableDeclaration); + } + + // For logging purposes + var signature = + name + + "(" + + def.params.map((x) => x.name || "<>").join(",") + + ")"; + this.log("Added", signature); + + // delete ref in block + if (defParents.length) { + deleteDirect(def, defParents[0]); + } + + this.addComment(functionExpression, signature); + return Property( + Literal(newFnNames[name]), + functionExpression, + false + ); + }) + ) + ) + ); + + var getterArgName = this.getPlaceholder(); + + var x = this.getPlaceholder(); + var y = this.getPlaceholder(); + var z = this.getPlaceholder(); + + function getAccessor() { + return MemberExpression(Identifier(mapName), Identifier(x), true); + } + + // 2. define it + var fn = FunctionDeclaration( + dispatcherFnName, + [Identifier(x), Identifier(y), Identifier(z)], + [ + // Define map of callable functions + map, + + // Set returning variable to undefined + VariableDeclaration(VariableDeclarator(returnProp)), + + // Arg to clear the payload + IfStatement( + BinaryExpression( + "==", + Identifier(y), + Literal(expectedClearArgs) + ), + [ + ExpressionStatement( + AssignmentExpression( + "=", + Identifier(payloadArg), + ArrayExpression([]) + ) + ), + ], + null + ), + + VariableDeclaration( + VariableDeclarator( + Identifier("lengths"), + ObjectExpression( + !this.options.preserveFunctionLength + ? [] + : shuffledKeys + .map((name) => { + var [def, defParents] = functionDeclarations[name]; + + return { + key: newFnNames[name], + value: computeFunctionLength(def.params), + }; + }) + .filter((item) => item.value !== 0) + .map((item) => + Property(Literal(item.key), Literal(item.value)) + ) + ) + ) + ), + + new Template(` + function makeFn${predictableFunctionTag}(){ + var fn = function(...args){ + ${payloadArg} = args; + return ${mapName}[${x}].call(this) + }, a = lengths[${x}] + + ${ + this.options.preserveFunctionLength + ? `if(a){ + return ${this.functionLengthName}(fn, a) + }` + : "" + } + + return fn + } + `).single(), + + // Arg to get a function reference + IfStatement( + BinaryExpression("==", Identifier(y), Literal(expectedGet)), + [ + // Getter flag: return the function object + ExpressionStatement( + AssignmentExpression( + "=", + Identifier(returnProp), + LogicalExpression( + "||", + MemberExpression( + Identifier(cacheName), + Identifier(x), + true + ), + AssignmentExpression( + "=", + MemberExpression( + Identifier(cacheName), + Identifier(x), + true + ), + CallExpression( + Identifier(`makeFn${predictableFunctionTag}`), + [] + ) + ) + ) + ) + ), + ], + [ + // Call the function, return result + ExpressionStatement( + AssignmentExpression( + "=", + Identifier(returnProp), + CallExpression(getAccessor(), []) + ) + ), + ] + ), + + // Check how the function was invoked (new () vs ()) + IfStatement( + BinaryExpression("==", Identifier(z), Literal(expectedNew)), + [ + // Wrap in object + ReturnStatement( + ObjectExpression([ + Property( + Identifier(newReturnMemberName), + Identifier(returnProp), + false + ), + ]) + ), + ], + [ + // Return raw result + ReturnStatement(Identifier(returnProp)), + ] + ), + ] + ); + + append(object, fn); + + if (payloadArg) { + prepend( + object, + VariableDeclaration( + VariableDeclarator(payloadArg, ArrayExpression([])) + ) + ); + } + + identifiers.forEach(([o, p]) => { + if (o.type != "Identifier") { + return; + } + + var newName = newFnNames[o.name]; + if (!newName || typeof newName !== "string") { + return; + } + + if (!functionDeclarations[o.name]) { + this.error(new Error("newName, missing function declaration")); + } + + var info = getIdentifierInfo(o, p); + if ( + info.isFunctionCall && + p[0].type == "CallExpression" && + p[0].callee === o + ) { + // Invoking call expression: `a();` + + if (o.name == dispatcherFnName) { + return; + } + + this.log( + `${o.name}(${p[0].arguments + .map((_) => "<>") + .join(",")}) -> ${dispatcherFnName}('${newName}')` + ); + + var assignmentExpressions: Node[] = []; + var dispatcherArgs: Node[] = [Literal(newName)]; + + if (p[0].arguments.length) { + assignmentExpressions = [ + AssignmentExpression( + "=", + Identifier(payloadArg), + ArrayExpression(p[0].arguments) + ), + ]; + } else { + dispatcherArgs.push(Literal(expectedClearArgs)); + } + + var type = choice(["CallExpression", "NewExpression"]); + var callExpression = null; + + switch (type) { + case "CallExpression": + callExpression = CallExpression( + Identifier(dispatcherFnName), + dispatcherArgs + ); + break; + + case "NewExpression": + if (dispatcherArgs.length == 1) { + dispatcherArgs.push(Identifier("undefined")); + } + callExpression = MemberExpression( + NewExpression(Identifier(dispatcherFnName), [ + ...dispatcherArgs, + Literal(expectedNew), + ]), + Identifier(newReturnMemberName), + false + ); + break; + } + + this.addComment( + callExpression, + "Calling " + + o.name + + "(" + + p[0].arguments.map((x) => x.name).join(", ") + + ")" + ); + + var expr: Node = assignmentExpressions.length + ? SequenceExpression([...assignmentExpressions, callExpression]) + : callExpression; + + // Replace the parent call expression + this.replace(p[0], expr); + } else { + // Non-invoking reference: `a` + + if (info.spec.isDefined) { + if (info.isFunctionDeclaration) { + this.log( + "Skipped getter " + o.name + " (function declaration)" + ); + } else { + this.log("Skipped getter " + o.name + " (defined)"); + } + return; + } + if (info.spec.isModified) { + this.log("Skipped getter " + o.name + " (modified)"); + return; + } + + this.log( + `(getter) ${o.name} -> ${dispatcherFnName}('${newName}')` + ); + this.replace( + o, + CallExpression(Identifier(dispatcherFnName), [ + Literal(newName), + Literal(expectedGet), + ]) + ); + } + }); + + prepend( + object, + VariableDeclaration( + VariableDeclarator( + Identifier(cacheName), + new Template(`Object.create(null)`).single().expression + ) + ) + ); + } + } + }; + } +} diff --git a/src/transforms/es5/antiClass.ts b/src_old/transforms/es5/antiClass.ts similarity index 100% rename from src/transforms/es5/antiClass.ts rename to src_old/transforms/es5/antiClass.ts diff --git a/src/transforms/es5/antiDestructuring.ts b/src_old/transforms/es5/antiDestructuring.ts similarity index 100% rename from src/transforms/es5/antiDestructuring.ts rename to src_old/transforms/es5/antiDestructuring.ts diff --git a/src/transforms/es5/antiES6Object.ts b/src_old/transforms/es5/antiES6Object.ts similarity index 100% rename from src/transforms/es5/antiES6Object.ts rename to src_old/transforms/es5/antiES6Object.ts diff --git a/src/transforms/es5/antiSpreadOperator.ts b/src_old/transforms/es5/antiSpreadOperator.ts similarity index 100% rename from src/transforms/es5/antiSpreadOperator.ts rename to src_old/transforms/es5/antiSpreadOperator.ts diff --git a/src/transforms/es5/antiTemplate.ts b/src_old/transforms/es5/antiTemplate.ts similarity index 100% rename from src/transforms/es5/antiTemplate.ts rename to src_old/transforms/es5/antiTemplate.ts diff --git a/src/transforms/es5/es5.ts b/src_old/transforms/es5/es5.ts similarity index 100% rename from src/transforms/es5/es5.ts rename to src_old/transforms/es5/es5.ts diff --git a/src/transforms/extraction/classExtraction.ts b/src_old/transforms/extraction/classExtraction.ts similarity index 100% rename from src/transforms/extraction/classExtraction.ts rename to src_old/transforms/extraction/classExtraction.ts diff --git a/src_old/transforms/extraction/duplicateLiteralsRemoval.ts b/src_old/transforms/extraction/duplicateLiteralsRemoval.ts new file mode 100644 index 0000000..1a6fbd6 --- /dev/null +++ b/src_old/transforms/extraction/duplicateLiteralsRemoval.ts @@ -0,0 +1,297 @@ +import Transform from "../transform"; +import { + Identifier, + Literal, + VariableDeclaration, + Node, + ArrayExpression, + MemberExpression, + VariableDeclarator, + Location, + ReturnStatement, + CallExpression, + BinaryExpression, + FunctionDeclaration, + ConditionalExpression, +} from "../../util/gen"; +import { append, clone, prepend } from "../../util/insert"; +import { isDirective, isModuleSource, isPrimitive } from "../../util/compare"; +import { ObfuscateOrder } from "../../order"; +import { ComputeProbabilityMap } from "../../probability"; +import { ok } from "assert"; +import { chance, choice, getRandomInteger } from "../../util/random"; +import { getBlock } from "../../traverse"; +import { getIdentifierInfo } from "../../util/identifiers"; +import { predictableFunctionTag } from "../../constants"; + +/** + * [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a variable name. + * + * - Potency Medium + * - Resilience Medium + * - Cost Medium + * + * ```js + * // Input + * var foo = "http://www.example.xyz"; + * bar("http://www.example.xyz"); + * + * // Output + * var a = "http://www.example.xyz"; + * var foo = a; + * bar(a); + * ``` + */ +export default class DuplicateLiteralsRemoval extends Transform { + // The array holding all the duplicate literals + arrayName: string; + // The array expression node to be inserted into the program + arrayExpression: Node; + + /** + * Literals in the array + */ + map: Map; + + /** + * Literals are saved here the first time they are seen. + */ + first: Map; + + /** + * Block -> { functionName, indexShift } + */ + functions: Map; + + constructor(o) { + super(o, ObfuscateOrder.DuplicateLiteralsRemoval); + + this.map = new Map(); + this.first = new Map(); + + this.functions = new Map(); + } + + apply(tree) { + super.apply(tree); + + if (this.arrayName && this.arrayExpression.elements.length > 0) { + // This function simply returns the array + var getArrayFn = this.getPlaceholder(); + append( + tree, + FunctionDeclaration( + getArrayFn, + [], + [ReturnStatement(this.arrayExpression)] + ) + ); + + // This variable holds the array + prepend( + tree, + VariableDeclaration( + VariableDeclarator( + this.arrayName, + CallExpression(Identifier(getArrayFn), []) + ) + ) + ); + + // Create all the functions needed + for (var blockNode of this.functions.keys()) { + var { functionName, indexShift } = this.functions.get(blockNode); + + var propertyNode: Node = BinaryExpression( + "-", + Identifier("index_param"), + Literal(indexShift) + ); + + var indexRangeInclusive = [ + 0 + indexShift - 1, + this.map.size + indexShift, + ]; + + // The function uses mangling to hide the index being accessed + var mangleCount = getRandomInteger(1, 5); + for (var i = 0; i < mangleCount; i++) { + var operator = choice([">", "<"]); + var compareValue = choice(indexRangeInclusive); + + var test = BinaryExpression( + operator, + Identifier("index_param"), + Literal(compareValue) + ); + + var alternate = BinaryExpression( + "-", + Identifier("index_param"), + Literal(getRandomInteger(-100, 100)) + ); + + var testValue = + (operator === ">" && compareValue === indexRangeInclusive[0]) || + (operator === "<" && compareValue === indexRangeInclusive[1]); + + propertyNode = ConditionalExpression( + test, + testValue ? propertyNode : alternate, + !testValue ? propertyNode : alternate + ); + } + + var returnArgument = MemberExpression( + Identifier(this.arrayName), + propertyNode, + true + ); + + prepend( + blockNode, + FunctionDeclaration( + functionName, + [Identifier("index_param")], + [ReturnStatement(returnArgument)] + ) + ); + } + } + } + + match(object: Node, parents: Node[]) { + return ( + isPrimitive(object) && + !isDirective(object, parents) && + !isModuleSource(object, parents) && + !parents.find((x) => x.$multiTransformSkip) + ); + } + + /** + * Converts ordinary literal to go through a getter function. + * @param object + * @param parents + * @param index + */ + transformLiteral(object: Node, parents: Node[], index: number) { + var blockNode = choice(parents.filter((x) => this.functions.has(x))); + + // Create initial function if none exist + if (this.functions.size === 0) { + var root = parents[parents.length - 1]; + var rootFunctionName = this.getPlaceholder() + "_dLR_0"; + this.functions.set(root, { + functionName: rootFunctionName + predictableFunctionTag, + indexShift: getRandomInteger(-100, 100), + }); + + blockNode = root; + } + + // If no function here exist, possibly create new chained function + var block = getBlock(object, parents); + if (!this.functions.has(block) && chance(50 - this.functions.size)) { + var newFunctionName = + this.getPlaceholder() + + "_dLR_" + + this.functions.size + + predictableFunctionTag; + + this.functions.set(block, { + functionName: newFunctionName, + indexShift: getRandomInteger(-100, 100), + }); + + blockNode = block; + } + + // Derive the function to call from the selected blockNode + var { functionName, indexShift } = this.functions.get(blockNode); + + // Call the function given it's indexShift + var callExpression = CallExpression(Identifier(functionName), [ + Literal(index + indexShift), + ]); + + this.replaceIdentifierOrLiteral(object, callExpression, parents); + } + + transform(object: Node, parents: Node[]) { + return () => { + if (object.type === "Identifier") { + var info = getIdentifierInfo(object, parents); + if (info.isLabel || info.spec.isDefined || info.spec.isModified) return; + } + if (object.regex) { + return; + } + + if (!ComputeProbabilityMap(this.options.duplicateLiteralsRemoval)) { + return; + } + + if ( + this.arrayName && + parents[0].object && + parents[0].object.name == this.arrayName + ) { + return; + } + + var stringValue; + if (object.type == "Literal") { + stringValue = typeof object.value + ":" + object.value; + if (object.value === null) { + stringValue = "null:null"; + } else { + // Skip empty strings + if (typeof object.value === "string" && !object.value) { + return; + } + } + } else if (object.type == "Identifier") { + stringValue = "identifier:" + object.name; + } else { + throw new Error("Unsupported primitive type: " + object.type); + } + + ok(stringValue); + + if (this.map.has(stringValue) || this.first.has(stringValue)) { + // Create the array if not already made + if (!this.arrayName) { + this.arrayName = this.getPlaceholder(); + this.arrayExpression = ArrayExpression([]); + } + + // Delete with first location + var firstLocation = this.first.get(stringValue); + if (firstLocation) { + var index = this.map.size; + + ok(!this.map.has(stringValue)); + this.map.set(stringValue, index); + this.first.delete(stringValue); + + var pushing = clone(object); + this.arrayExpression.elements.push(pushing); + + ok(this.arrayExpression.elements[index] === pushing); + + this.transformLiteral(firstLocation[0], firstLocation[1], index); + } + + var index = this.map.get(stringValue); + ok(typeof index === "number"); + + this.transformLiteral(object, parents, index); + return; + } + + // Save this, maybe a duplicate will be found. + this.first.set(stringValue, [object, parents]); + }; + } +} diff --git a/src_old/transforms/extraction/objectExtraction.ts b/src_old/transforms/extraction/objectExtraction.ts new file mode 100644 index 0000000..5d3bfba --- /dev/null +++ b/src_old/transforms/extraction/objectExtraction.ts @@ -0,0 +1,360 @@ +import Transform from "../transform"; +import { walk } from "../../traverse"; +import { Node, Location, Identifier, VariableDeclarator } from "../../util/gen"; +import { getVarContext, isVarContext } from "../../util/insert"; +import { ObfuscateOrder } from "../../order"; +import { getIdentifierInfo } from "../../util/identifiers"; +import { isValidIdentifier } from "../../util/compare"; +import { ComputeProbabilityMap } from "../../probability"; +import { ok } from "assert"; +import { isStringLiteral } from "../../util/guard"; + +/** + * Extracts keys out of an object if possible. + * ```js + * // Input + * var utils = { + * isString: x=>typeof x === "string", + * isBoolean: x=>typeof x === "boolean" + * } + * if ( utils.isString("Hello") ) { + * ... + * } + * + * // Output + * var utils_isString = x=>typeof x === "string"; + * var utils_isBoolean = x=>typeof x === "boolean" + * + * if ( utils_isString("Hello") ) { + * ... + * } + * ``` + */ +export default class ObjectExtraction extends Transform { + constructor(o) { + super(o, ObfuscateOrder.ObjectExtraction); + } + + match(object: Node, parents: Node[]) { + return isVarContext(object); + } + + transform(context: Node, contextParents: Node[]) { + // ObjectExpression Extractor + + return () => { + // First pass through to find the maps + var objectDefs: { [name: string]: Location } = Object.create(null); + var objectDefiningIdentifiers: { [name: string]: Location } = + Object.create(null); + + var illegal = new Set(); + + walk(context, contextParents, (object: Node, parents: Node[]) => { + if (object.type == "ObjectExpression") { + // this.log(object, parents); + if ( + parents[0].type == "VariableDeclarator" && + parents[0].init == object && + parents[0].id.type == "Identifier" + ) { + var name = parents[0].id.name; + if (name) { + if (getVarContext(object, parents) != context) { + illegal.add(name); + return; + } + if (!object.properties.length) { + illegal.add(name); + return; + } + + // duplicate name + if (objectDefiningIdentifiers[name]) { + illegal.add(name); + return; + } + + // check for computed properties + // Change String literals to non-computed + object.properties.forEach((prop) => { + if (prop.computed && isStringLiteral(prop.key)) { + prop.computed = false; + } + }); + + var nonInitOrComputed = object.properties.find( + (x) => x.kind !== "init" || x.computed + ); + + if (nonInitOrComputed) { + if (nonInitOrComputed.key) { + this.log( + name + + " has non-init/computed property: " + + nonInitOrComputed.key.name || nonInitOrComputed.key.value + ); + } else { + this.log( + name + " has spread-element or other type of property" + ); + } + + illegal.add(name); + return; + } else { + var illegalName = object.properties + .map((x) => + x.computed ? x.key.value : x.key.name || x.key.value + ) + .find((x) => !x || !isValidIdentifier(x)); + + if (illegalName) { + this.log( + name + " has an illegal property '" + illegalName + "'" + ); + illegal.add(name); + return; + } else { + var isIllegal = false; + walk(object, parents, (o, p) => { + if (o.type == "ThisExpression" || o.type == "Super") { + isIllegal = true; + return "EXIT"; + } + }); + if (isIllegal) { + illegal.add(name); + return; + } + + objectDefs[name] = [object, parents]; + objectDefiningIdentifiers[name] = [ + parents[0].id, + [...parents], + ]; + } + } + } + } + } + }); + + illegal.forEach((name) => { + delete objectDefs[name]; + delete objectDefiningIdentifiers[name]; + }); + + // this.log("object defs", objectDefs); + // huge map of changes + var objectDefChanges: { + [name: string]: { key: string; object: Node; parents: Node[] }[]; + } = {}; + + if (Object.keys(objectDefs).length) { + // A second pass through is only required when extracting object keys + + // Second pass through the exclude the dynamic map (counting keys, re-assigning) + walk(context, contextParents, (object: any, parents: Node[]) => { + if (object.type == "Identifier") { + var info = getIdentifierInfo(object, parents); + if (!info.spec.isReferenced) { + return; + } + var def = objectDefs[object.name]; + if (def) { + var isIllegal = false; + + if (info.spec.isDefined) { + if (objectDefiningIdentifiers[object.name][0] !== object) { + this.log(object.name, "you can't redefine the object"); + isIllegal = true; + } + } else { + var isMemberExpression = + parents[0].type == "MemberExpression" && + parents[0].object == object; + + if ( + (parents.find((x) => x.type == "AssignmentExpression") && + !isMemberExpression) || + parents.find( + (x) => x.type == "UnaryExpression" && x.operator == "delete" + ) + ) { + this.log(object.name, "you can't re-assign the object"); + + isIllegal = true; + } else if (isMemberExpression) { + var key = + parents[0].property.value || parents[0].property.name; + + if ( + parents[0].computed && + parents[0].property.type !== "Literal" + ) { + this.log( + object.name, + "object[expr] detected, only object['key'] is allowed" + ); + + isIllegal = true; + } else if ( + !parents[0].computed && + parents[0].property.type !== "Identifier" + ) { + this.log( + object.name, + "object. detected, only object.key is allowed" + ); + + isIllegal = true; + } else if ( + !key || + !def[0].properties.some( + (x) => (x.key.value || x.key.name) == key + ) + ) { + // check if initialized property + // not in initialized object. + this.log( + object.name, + "not in initialized object.", + def[0].properties, + key + ); + isIllegal = true; + } + + if (!isIllegal && key) { + // allowed. + // start the array if first time + if (!objectDefChanges[object.name]) { + objectDefChanges[object.name] = []; + } + // add to array + objectDefChanges[object.name].push({ + key: key, + object: object, + parents: parents, + }); + } + } else { + this.log( + object.name, + "you must access a property on the when referring to the identifier (accessors must be hard-coded literals), parent is " + + parents[0].type + ); + + isIllegal = true; + } + } + + if (isIllegal) { + // this is illegal, delete it from being moved and delete accessor changes from happening + this.log(object.name + " is illegal"); + delete objectDefs[object.name]; + delete objectDefChanges[object.name]; + } + } + } + }); + + Object.keys(objectDefs).forEach((name) => { + if ( + !ComputeProbabilityMap( + this.options.objectExtraction, + (x) => x, + name + ) + ) { + //continue; + return; + } + + var [object, parents] = objectDefs[name]; + var declarator = parents[0]; + var declaration = parents[2]; + + ok(declarator.type === "VariableDeclarator"); + ok(declaration.type === "VariableDeclaration"); + + var properties = object.properties; + // change the prop names while extracting + var newPropNames: { [key: string]: string } = {}; + + var variableDeclarators = []; + + properties.forEach((property: Node) => { + var keyName = property.key.name || property.key.value; + + var nn = name + "_" + keyName; + newPropNames[keyName] = nn; + + var v = property.value; + + variableDeclarators.push( + VariableDeclarator(nn, this.addComment(v, `${name}.${keyName}`)) + ); + }); + + declaration.declarations.splice( + declaration.declarations.indexOf(declarator), + 1, + ...variableDeclarators + ); + + // const can only be safely changed to let + if (declaration.kind === "const") { + declaration.kind = "let"; + } + + // update all identifiers that pointed to the old object + objectDefChanges[name] && + objectDefChanges[name].forEach((change) => { + if (!change.key) { + this.error(new Error("key is undefined")); + } + if (newPropNames[change.key]) { + var memberExpression = change.parents[0]; + if (memberExpression.type == "MemberExpression") { + this.replace( + memberExpression, + this.addComment( + Identifier(newPropNames[change.key]), + `Original Accessor: ${name}.${change.key}` + ) + ); + } else { + // Provide error with more information: + console.log(memberExpression); + this.error( + new Error( + `should be MemberExpression, found type=${memberExpression.type}` + ) + ); + } + } else { + console.log(objectDefChanges[name], newPropNames); + this.error( + new Error( + `"${change.key}" not found in [${Object.keys( + newPropNames + ).join(", ")}] while flattening ${name}.` + ) + ); + } + }); + + this.log( + `Extracted ${ + Object.keys(newPropNames).length + } properties from ${name}, affecting ${ + Object.keys(objectDefChanges[name] || {}).length + } line(s) of code.` + ); + }); + } + }; + } +} diff --git a/src_old/transforms/finalizer.ts b/src_old/transforms/finalizer.ts new file mode 100644 index 0000000..c07c446 --- /dev/null +++ b/src_old/transforms/finalizer.ts @@ -0,0 +1,75 @@ +import { ObfuscateOrder } from "../order"; +import { ExitCallback } from "../traverse"; +import { Identifier, Node } from "../util/gen"; +import StringEncoding from "./string/stringEncoding"; +import Transform from "./transform"; + +/** + * The Finalizer is the last transformation before the code is ready to be generated. + * + * Hexadecimal numbers: + * - Convert integer literals into `Identifier` nodes with the name being a hexadecimal number + * + * BigInt support: + * - Convert BigInt literals into `Identifier` nodes with the name being the raw BigInt string value + "n" + * + * String Encoding: + * - Convert String literals into `Identifier` nodes with the name being a unicode escaped string + */ +export default class Finalizer extends Transform { + stringEncoding: StringEncoding; + + constructor(o) { + super(o, ObfuscateOrder.Finalizer); + + this.stringEncoding = new StringEncoding(o); + } + + isNumberLiteral(object: Node) { + return ( + object.type === "Literal" && + typeof object.value === "number" && + Math.floor(object.value) === object.value + ); + } + + isBigIntLiteral(object: Node) { + return object.type === "Literal" && typeof object.value === "bigint"; + } + + match(object, parents) { + return object.type === "Literal"; + } + + transform(object: Node, parents: Node[]): void | ExitCallback { + // Hexadecimal Numbers + if (this.options.hexadecimalNumbers && this.isNumberLiteral(object)) { + return () => { + // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator. + // This code handles it regardless + var isNegative = object.value < 0; + var hex = Math.abs(object.value).toString(16); + + var newStr = (isNegative ? "-" : "") + "0x" + hex; + + this.replace(object, Identifier(newStr)); + }; + } + + // BigInt support + if (this.isBigIntLiteral(object)) { + // https://github.com/MichaelXF/js-confuser/issues/79 + return () => { + // Use an Identifier with the raw string + this.replace(object, Identifier(object.raw)); + }; + } + + if ( + this.options.stringEncoding && + this.stringEncoding.match(object, parents) + ) { + return this.stringEncoding.transform(object, parents); + } + } +} diff --git a/src_old/transforms/flatten.ts b/src_old/transforms/flatten.ts new file mode 100644 index 0000000..99aa47f --- /dev/null +++ b/src_old/transforms/flatten.ts @@ -0,0 +1,557 @@ +import { ok } from "assert"; +import { + noRenameVariablePrefix, + predictableFunctionTag, + reservedIdentifiers, +} from "../constants"; +import { ObfuscateOrder } from "../order"; +import { walk } from "../traverse"; +import { + Identifier, + ReturnStatement, + VariableDeclaration, + VariableDeclarator, + CallExpression, + MemberExpression, + ExpressionStatement, + AssignmentExpression, + Node, + BlockStatement, + ArrayPattern, + FunctionExpression, + ObjectExpression, + Property, + Literal, + AwaitExpression, + FunctionDeclaration, + SpreadElement, + UnaryExpression, + RestElement, +} from "../util/gen"; +import { getIdentifierInfo } from "../util/identifiers"; +import { + getBlockBody, + prepend, + clone, + getDefiningContext, + computeFunctionLength, +} from "../util/insert"; +import { shuffle } from "../util/random"; +import Transform from "./transform"; +import { FunctionLengthTemplate } from "../templates/functionLength"; +import { ObjectDefineProperty } from "../templates/globals"; + +/** + * Flatten takes functions and isolates them from their original scope, and brings it to the top level of the program. + * + * An additional `flatObject` parameter is passed in, giving access to the original scoped variables. + * + * The `flatObject` uses `get` and `set` properties to allow easy an AST transformation: + * + * ```js + * // Input + * function myFunction(myParam){ + * modified = true; + * if(reference) { + * + * } + * ... + * console.log(myParam); + * } + * + * // Output + * function myFunction_flat([myParam], flatObject){ + * flatObject["set_modified"] = true; + * if(flatObject["get_reference"]) { + * + * } + * ... + * console.log(myParam) + * } + * + * function myFunction(){ + * var flatObject = { + * set set_modified(v) { modified = v } + * get get_reference() { return reference } + * } + * return myFunction_flat([...arguments], flatObject) + * } + * ``` + * + * Flatten is used to make functions eligible for the RGF transformation. + * + * - `myFunction_flat` is now eligible because it does not rely on outside scoped variables + */ +export default class Flatten extends Transform { + isDebug = false; + + definedNames: Map>; + + // Array of FunctionDeclaration nodes + flattenedFns: Node[]; + gen: ReturnType; + + functionLengthName: string; + + constructor(o) { + super(o, ObfuscateOrder.Flatten); + + this.definedNames = new Map(); + this.flattenedFns = []; + this.gen = this.getGenerator("mangled"); + + if (this.isDebug) { + console.warn("Flatten debug mode"); + } + } + + apply(tree) { + super.apply(tree); + + if (this.flattenedFns.length) { + prepend(tree, ...this.flattenedFns); + } + } + + match(object: Node, parents: Node[]) { + return ( + (object.type == "FunctionDeclaration" || + object.type === "FunctionExpression") && + object.body.type == "BlockStatement" && + !object.$requiresEval && + !object.generator && + !object.params.find((x) => x.type !== "Identifier") + ); + } + + transform(object: Node, parents: Node[]) { + return () => { + if (parents[0]) { + // Don't change class methods + if ( + parents[0].type === "MethodDefinition" && + parents[0].value === object + ) { + return; + } + + // Don't change getter/setter methods + if ( + parents[0].type === "Property" && + parents[0].value === object && + (parents[0].kind !== "init" || parents[0].method) + ) { + return; + } + } + + ok( + object.type === "FunctionDeclaration" || + object.type === "FunctionExpression" + ); + + // The name is purely for debugging purposes + var currentFnName = + object.type === "FunctionDeclaration" + ? object.id?.name + : parents[0]?.type === "VariableDeclarator" && + parents[0].id?.type === "Identifier" && + parents[0].id?.name; + + if (parents[0]?.type === "Property" && parents[0]?.key) { + currentFnName = currentFnName || String(parents[0]?.key?.name); + } + + if (!currentFnName) currentFnName = "unnamed"; + + var definedMap = new Map>(); + + var illegal = new Set(); + var isIllegal = false; + + var identifierNodes: [ + Node, + Node[], + ReturnType + ][] = []; + + walk(object, parents, (o, p) => { + if ( + (o.type === "Identifier" && o.name === "arguments") || + (o.type === "UnaryExpression" && o.operator === "delete") || + o.type == "ThisExpression" || + o.type == "Super" || + o.type == "MetaProperty" + ) { + isIllegal = true; + return "EXIT"; + } + + if ( + o.type == "Identifier" && + o !== object.id && + !this.options.globalVariables.has(o.name) && + !reservedIdentifiers.has(o.name) + ) { + var info = getIdentifierInfo(o, p); + if (!info.spec.isReferenced) { + return; + } + + if ( + info.spec.isExported || + o.name.startsWith(noRenameVariablePrefix) + ) { + illegal.add(o.name); + + return; + } + + if (info.spec.isDefined) { + var definingContext = getDefiningContext(o, p); + + if (!definedMap.has(definingContext)) { + definedMap.set(definingContext, new Set([o.name])); + } else { + definedMap.get(definingContext).add(o.name); + } + return; + } + + var isDefined = p.find( + (x) => definedMap.has(x) && definedMap.get(x).has(o.name) + ); + + if (!isDefined) { + identifierNodes.push([o, p, info]); + } + } + + if (o.type == "TryStatement") { + isIllegal = true; + return "EXIT"; + } + }); + + if (isIllegal) { + return; + } + if (illegal.size) { + return; + } + + var newFnName = + this.getPlaceholder() + + "_flat_" + + currentFnName + + predictableFunctionTag; + var flatObjectName = this.getPlaceholder() + "_flat_object"; + + const getFlatObjectMember = (propertyName: string) => { + return MemberExpression( + Identifier(flatObjectName), + Literal(propertyName), + true + ); + }; + + var getterPropNames: { [identifierName: string]: string } = + Object.create(null); + var setterPropNames: { [identifierName: string]: string } = + Object.create(null); + var typeofPropNames: { [identifierName: string]: string } = + Object.create(null); + var callPropNames: { [identifierName: string]: string } = + Object.create(null); + + for (var [o, p, info] of identifierNodes) { + var identifierName: string = o.name; + if ( + p.find( + (x) => definedMap.has(x) && definedMap.get(x).has(identifierName) + ) + ) + continue; + + ok(!info.spec.isDefined); + + var type = info.spec.isModified ? "setter" : "getter"; + + switch (type) { + case "setter": + var setterPropName = setterPropNames[identifierName]; + if (typeof setterPropName === "undefined") { + // No getter function made yet, make it (Try to re-use getter name if available) + setterPropName = + getterPropNames[identifierName] || + (this.isDebug ? "set_" + identifierName : this.gen.generate()); + setterPropNames[identifierName] = setterPropName; + } + + // If an update expression, ensure a getter function is also available. Ex: a++ + if (p[0].type === "UpdateExpression") { + getterPropNames[identifierName] = setterPropName; + } else { + // If assignment on member expression, ensure a getter function is also available: Ex. myObject.property = ... + var assignmentIndex = p.findIndex( + (x) => x.type === "AssignmentExpression" + ); + if ( + assignmentIndex !== -1 && + p[assignmentIndex].left.type !== "Identifier" + ) { + getterPropNames[identifierName] = setterPropName; + } + } + + // calls flatObject.set_identifier_value(newValue) + this.replace(o, getFlatObjectMember(setterPropName)); + break; + + case "getter": + var getterPropName = getterPropNames[identifierName]; + if (typeof getterPropName === "undefined") { + // No getter function made yet, make it (Try to re-use setter name if available) + getterPropName = + setterPropNames[identifierName] || + (this.isDebug ? "get_" + identifierName : this.gen.generate()); + getterPropNames[identifierName] = getterPropName; + } + + // Typeof expression check + if ( + p[0].type === "UnaryExpression" && + p[0].operator === "typeof" && + p[0].argument === o + ) { + var typeofPropName = typeofPropNames[identifierName]; + if (typeof typeofPropName === "undefined") { + // No typeof getter function made yet, make it (Don't re-use getter/setter names) + typeofPropName = this.isDebug + ? "get_typeof_" + identifierName + : this.gen.generate(); + typeofPropNames[identifierName] = typeofPropName; + } + + // Replace the entire unary expression not just the identifier node + // calls flatObject.get_typeof_identifier() + this.replace(p[0], getFlatObjectMember(typeofPropName)); + break; + } + + // Bound call-expression check + if (p[0].type === "CallExpression" && p[0].callee === o) { + var callPropName = callPropNames[identifierName]; + if (typeof callPropName === "undefined") { + callPropName = this.isDebug + ? "call_" + identifierName + : this.gen.generate(); + callPropNames[identifierName] = callPropName; + } + + // Replace the entire call expression not just the identifier node + // calls flatObject.call_identifier(...arguments) + this.replace( + p[0], + CallExpression( + getFlatObjectMember(callPropName), + p[0].arguments + ) + ); + break; + } + + // calls flatObject.get_identifier_value() + this.replace(o, getFlatObjectMember(getterPropName)); + break; + } + } + + // Create the getter and setter functions + var flatObjectProperties: Node[] = []; + + // Getter functions + for (var identifierName in getterPropNames) { + var getterPropName = getterPropNames[identifierName]; + + flatObjectProperties.push( + Property( + Literal(getterPropName), + FunctionExpression( + [], + [ReturnStatement(Identifier(identifierName))] + ), + true, + "get" + ) + ); + } + + // Get typeof functions + for (var identifierName in typeofPropNames) { + var typeofPropName = typeofPropNames[identifierName]; + + flatObjectProperties.push( + Property( + Literal(typeofPropName), + FunctionExpression( + [], + [ + ReturnStatement( + UnaryExpression("typeof", Identifier(identifierName)) + ), + ] + ), + true, + "get" + ) + ); + } + + // Call functions + for (var identifierName in callPropNames) { + var callPropName = callPropNames[identifierName]; + var argumentsName = this.getPlaceholder(); + flatObjectProperties.push( + Property( + Literal(callPropName), + FunctionExpression( + [RestElement(Identifier(argumentsName))], + [ + ReturnStatement( + CallExpression(Identifier(identifierName), [ + SpreadElement(Identifier(argumentsName)), + ]) + ), + ] + ), + true + ) + ); + } + + // Setter functions + for (var identifierName in setterPropNames) { + var setterPropName = setterPropNames[identifierName]; + var newValueParameterName = this.getPlaceholder(); + + flatObjectProperties.push( + Property( + Literal(setterPropName), + FunctionExpression( + [Identifier(newValueParameterName)], + [ + ExpressionStatement( + AssignmentExpression( + "=", + Identifier(identifierName), + Identifier(newValueParameterName) + ) + ), + ] + ), + true, + "set" + ) + ); + } + + if (!this.isDebug) { + shuffle(flatObjectProperties); + } + + var newBody = getBlockBody(object.body); + + // Remove 'use strict' directive + if (newBody.length > 0 && newBody[0].directive) { + newBody.shift(); + } + + var newFunctionDeclaration = FunctionDeclaration( + newFnName, + [ArrayPattern(clone(object.params)), Identifier(flatObjectName)], + newBody + ); + + newFunctionDeclaration.async = !!object.async; + newFunctionDeclaration.generator = false; + + this.flattenedFns.push(newFunctionDeclaration); + + var argumentsName = this.getPlaceholder(); + + // newFn.call([...arguments], flatObject) + var callExpression = CallExpression(Identifier(newFnName), [ + Identifier(argumentsName), + Identifier(flatObjectName), + ]); + + var newObjectBody: Node[] = [ + // var flatObject = { get(), set() }; + VariableDeclaration([ + VariableDeclarator( + flatObjectName, + ObjectExpression(flatObjectProperties) + ), + ]), + + ReturnStatement( + newFunctionDeclaration.async + ? AwaitExpression(callExpression) + : callExpression + ), + ]; + + object.body = BlockStatement(newObjectBody); + + // Preserve function.length property + var originalFunctionLength = computeFunctionLength(object.params); + + object.params = [RestElement(Identifier(argumentsName))]; + + if (this.options.preserveFunctionLength && originalFunctionLength !== 0) { + if (!this.functionLengthName) { + this.functionLengthName = this.getPlaceholder(); + + prepend( + parents[parents.length - 1] || object, + FunctionLengthTemplate.single({ + name: this.functionLengthName, + ObjectDefineProperty: this.createInitVariable( + ObjectDefineProperty, + parents + ), + }) + ); + } + + if (object.type === "FunctionDeclaration") { + var body = parents[0]; + if (Array.isArray(body)) { + var index = body.indexOf(object); + + body.splice( + index + 1, + 0, + ExpressionStatement( + CallExpression(Identifier(this.functionLengthName), [ + Identifier(object.id.name), + Literal(originalFunctionLength), + ]) + ) + ); + } + } else { + ok(object.type === "FunctionExpression"); + this.replace( + object, + CallExpression(Identifier(this.functionLengthName), [ + { ...object }, + Literal(originalFunctionLength), + ]) + ); + } + } + }; + } +} diff --git a/src/transforms/identifier/globalAnalysis.ts b/src_old/transforms/identifier/globalAnalysis.ts similarity index 100% rename from src/transforms/identifier/globalAnalysis.ts rename to src_old/transforms/identifier/globalAnalysis.ts diff --git a/src_old/transforms/identifier/globalConcealing.ts b/src_old/transforms/identifier/globalConcealing.ts new file mode 100644 index 0000000..f04b4fd --- /dev/null +++ b/src_old/transforms/identifier/globalConcealing.ts @@ -0,0 +1,297 @@ +import Template from "../../templates/template"; +import Transform from "../transform"; +import { ObfuscateOrder } from "../../order"; +import { + Node, + Location, + CallExpression, + Identifier, + Literal, + FunctionDeclaration, + ReturnStatement, + MemberExpression, + SwitchStatement, + SwitchCase, + LogicalExpression, + VariableDeclarator, + FunctionExpression, + ExpressionStatement, + AssignmentExpression, + VariableDeclaration, + BreakStatement, +} from "../../util/gen"; +import { append, prepend } from "../../util/insert"; +import { chance, getRandomInteger } from "../../util/random"; +import { + predictableFunctionTag, + reservedIdentifiers, + variableFunctionName, +} from "../../constants"; +import { ComputeProbabilityMap } from "../../probability"; +import GlobalAnalysis from "./globalAnalysis"; +import { createGetGlobalTemplate } from "../../templates/bufferToString"; +import { isJSConfuserVar } from "../../util/guard"; + +/** + * Global Concealing hides global variables being accessed. + * + * - Any variable that is not defined is considered "global" + */ +export default class GlobalConcealing extends Transform { + globalAnalysis: GlobalAnalysis; + ignoreGlobals = new Set([ + "require", + "__dirname", + "eval", + variableFunctionName, + ]); + + constructor(o) { + super(o, ObfuscateOrder.GlobalConcealing); + + this.globalAnalysis = new GlobalAnalysis(o); + this.before.push(this.globalAnalysis); + } + + match(object: Node, parents: Node[]) { + return object.type == "Program"; + } + + transform(object: Node, parents: Node[]) { + return () => { + var globals: { [name: string]: Location[] } = this.globalAnalysis.globals; + this.globalAnalysis.notGlobals.forEach((del) => { + delete globals[del]; + }); + + for (var varName of this.ignoreGlobals) { + delete globals[varName]; + } + + reservedIdentifiers.forEach((x) => { + delete globals[x]; + }); + + Object.keys(globals).forEach((x) => { + if (this.globalAnalysis.globals[x].length < 1) { + delete globals[x]; + } else if ( + !ComputeProbabilityMap(this.options.globalConcealing, (x) => x, x) + ) { + delete globals[x]; + } + }); + + if (Object.keys(globals).length > 0) { + var usedStates = new Set(); + + // Make getter function + + // holds "window" or "global" + var globalVar = this.getPlaceholder(); + + var getGlobalVariableFnName = + this.getPlaceholder() + predictableFunctionTag; + + // Returns global variable or fall backs to `this` + var getGlobalVariableFn = createGetGlobalTemplate( + this, + object, + parents + ).compile({ + getGlobalFnName: getGlobalVariableFnName, + }); + + // 2. Replace old accessors + var globalFn = this.getPlaceholder() + predictableFunctionTag; + + var newNames: { [globalVarName: string]: number } = Object.create(null); + + Object.keys(globals).forEach((name) => { + var locations: Location[] = globals[name]; + var state: number; + do { + state = getRandomInteger(-1000, 1000 + usedStates.size); + } while (usedStates.has(state)); + usedStates.add(state); + + newNames[name] = state; + + locations.forEach(([node, p]) => { + if (p.find((x) => x.$multiTransformSkip)) { + return; + } + + var newExpression = CallExpression(Identifier(globalFn), [ + Literal(state), + ]); + + this.replace(node, newExpression); + + if ( + this.options.lock?.tamperProtection && + this.lockTransform.nativeFunctionName + ) { + var isMemberExpression = false; + var nameAndPropertyPath = [name]; + var callExpression: Node; + + var index = 0; + do { + if (p[index].type === "CallExpression") { + callExpression = p[index]; + break; + } + + var memberExpression = p[index]; + if (memberExpression.type !== "MemberExpression") return; + var property = memberExpression.property; + var stringValue = + property.type === "Literal" + ? property.value + : memberExpression.computed + ? null + : property.type === "Identifier" + ? property.name + : null; + + if (!stringValue) return; + + isMemberExpression = true; + nameAndPropertyPath.push(stringValue); + index++; + } while (index < p.length); + + if ( + !this.lockTransform.shouldTransformNativeFunction( + nameAndPropertyPath + ) + ) + return; + + if (callExpression && callExpression.type === "CallExpression") { + if (isMemberExpression) { + callExpression.callee = CallExpression( + Identifier(this.lockTransform.nativeFunctionName), + [ + callExpression.callee.object, + callExpression.callee.computed + ? callExpression.callee.property + : Literal( + callExpression.callee.property.name || + callExpression.callee.property.value + ), + ] + ); + } else { + callExpression.callee = CallExpression( + Identifier(this.lockTransform.nativeFunctionName), + [{ ...callExpression.callee }] + ); + } + } + } + }); + }); + + // Adds all global variables to the switch statement + this.options.globalVariables.forEach((name) => { + if (!newNames[name]) { + var state; + do { + state = getRandomInteger( + 0, + 1000 + usedStates.size + this.options.globalVariables.size * 100 + ); + } while (usedStates.has(state)); + usedStates.add(state); + + newNames[name] = state; + } + }); + + var indexParamName = this.getPlaceholder(); + var returnName = this.getPlaceholder(); + + var functionDeclaration = FunctionDeclaration( + globalFn, + [Identifier(indexParamName)], + [ + VariableDeclaration(VariableDeclarator(returnName)), + SwitchStatement( + Identifier(indexParamName), + Object.keys(newNames).map((name) => { + var code = newNames[name]; + var body: Node[] = [ + ReturnStatement( + MemberExpression(Identifier(globalVar), Literal(name), true) + ), + ]; + if (chance(50)) { + body = [ + ExpressionStatement( + AssignmentExpression( + "=", + Identifier(returnName), + LogicalExpression( + "||", + Literal(name), + MemberExpression( + Identifier(globalVar), + Literal(name), + true + ) + ) + ) + ), + BreakStatement(), + ]; + } + + return SwitchCase(Literal(code), body); + }) + ), + ReturnStatement( + MemberExpression( + Identifier(globalVar), + Identifier(returnName), + true + ) + ), + ] + ); + + var tempVar = this.getPlaceholder(); + + var variableDeclaration = new Template(` + var ${globalVar}; + `).single(); + + variableDeclaration.declarations.push( + VariableDeclarator( + tempVar, + CallExpression( + MemberExpression( + FunctionExpression( + [], + [ + ...getGlobalVariableFn, + new Template( + `return ${globalVar} = ${getGlobalVariableFnName}["call"](this)` + ).single(), + ] + ), + Literal("call"), + true + ), + [] + ) + ) + ); + + prepend(object, variableDeclaration); + append(object, functionDeclaration); + } + }; + } +} diff --git a/src/transforms/identifier/movedDeclarations.ts b/src_old/transforms/identifier/movedDeclarations.ts similarity index 100% rename from src/transforms/identifier/movedDeclarations.ts rename to src_old/transforms/identifier/movedDeclarations.ts diff --git a/src_old/transforms/identifier/renameVariables.ts b/src_old/transforms/identifier/renameVariables.ts new file mode 100644 index 0000000..7d7a5b3 --- /dev/null +++ b/src_old/transforms/identifier/renameVariables.ts @@ -0,0 +1,300 @@ +import { ok } from "assert"; +import { ObfuscateOrder } from "../../order"; +import { walk } from "../../traverse"; +import { Literal, Node } from "../../util/gen"; +import { getIdentifierInfo } from "../../util/identifiers"; +import { + isVarContext, + isContext, + isLexContext, + clone, + isFunction, +} from "../../util/insert"; +import Transform from "../transform"; +import { + noRenameVariablePrefix, + placeholderVariablePrefix, + reservedIdentifiers, + variableFunctionName, +} from "../../constants"; +import { ComputeProbabilityMap } from "../../probability"; +import VariableAnalysis from "./variableAnalysis"; + +/** + * Rename variables to randomly generated names. + * + * - 1. First collect data on identifiers in all scope using 'VariableAnalysis' + * - 2. After 'VariableAnalysis' is finished start applying to each scope (top-down) + * - 3. Each scope, find the all names used here and exclude those names from being re-named + * - 4. Now loop through all the defined names in this scope and set it to a random name (or re-use previously generated name) + * - 5. Update all the Identifiers node's 'name' property to reflect this change + */ +export default class RenameVariables extends Transform { + // Names already used + generated: string[]; + + // Map of Context->Object of changes + changed: Map; + + // Ref to VariableAnalysis data + variableAnalysis: VariableAnalysis; + + // Option to re-use previously generated names + reusePreviousNames = true; + + constructor(o) { + super(o, ObfuscateOrder.RenameVariables); + + this.changed = new Map(); + + // 1. + this.variableAnalysis = new VariableAnalysis(o); + this.before.push(this.variableAnalysis); + this.generated = []; + } + + match(object: Node, parents: Node[]) { + return isContext(object) || object.type === "Identifier"; + } + + transformContext(object: Node, parents: Node[]) { + // 2. Notice this is on 'onEnter' (top-down) + var isGlobal = object.type == "Program"; + var type = isGlobal + ? "root" + : isVarContext(object) + ? "var" + : isLexContext(object) + ? "lex" + : undefined; + + ok(type); + + var newNames = Object.create(null); + + var defined = this.variableAnalysis.defined.get(object) || new Set(); + var references = this.variableAnalysis.references.get(object) || new Set(); + + // No changes needed here + if (!defined && !this.changed.has(object)) { + this.changed.set(object, Object.create(null)); + return; + } + + // Names possible to be re-used here + var possible = new Set(); + + // 3. Try to re-use names when possible + if (this.reusePreviousNames && this.generated.length && !isGlobal) { + var allReferences = new Set(); + var nope = new Set(defined); + walk(object, [], (o, p) => { + var ref = this.variableAnalysis.references.get(o); + if (ref) { + ref.forEach((x) => allReferences.add(x)); + } + + var def = this.variableAnalysis.defined.get(o); + if (def) { + def.forEach((x) => allReferences.add(x)); + } + }); + + var passed = new Set(); + parents.forEach((p) => { + var changes = this.changed.get(p); + if (changes) { + Object.keys(changes).forEach((x) => { + var name = changes[x]; + + if (!allReferences.has(x) && !references.has(x)) { + passed.add(name); + } else { + nope.add(name); + } + }); + } + }); + + nope.forEach((x) => passed.delete(x)); + + possible = passed; + } + + // 4. Defined names to new names + for (var name of defined) { + if ( + !name.startsWith(noRenameVariablePrefix) && // Variables prefixed with '__NO_JS_CONFUSER_RENAME__' are never renamed + (isGlobal && !name.startsWith(placeholderVariablePrefix) // Variables prefixed with '__p_' are created by the obfuscator, always renamed + ? ComputeProbabilityMap(this.options.renameGlobals, (x) => x, name) + : true) && + ComputeProbabilityMap( + // Check the user's option for renaming variables + this.options.renameVariables, + (x) => x, + name, + isGlobal + ) + ) { + // Create a new name from (1) or (2) methods + var newName: string; + do { + if (possible.size) { + // (1) Re-use previously generated name + var first = possible.values().next().value; + possible.delete(first); + newName = first; + } else { + // (2) Create a new name with `generateIdentifier` function + var generatedName = this.generateIdentifier(); + + newName = generatedName; + this.generated.push(generatedName); + } + } while (this.variableAnalysis.globals.has(newName)); // Ensure global names aren't overridden + + newNames[name] = newName; + } else { + // This variable name was deemed not to be renamed. + newNames[name] = name; + } + } + + // console.log(object.type, newNames); + this.changed.set(object, newNames); + } + + transformIdentifier(object: Node, parents: Node[]) { + const identifierName = object.name; + if ( + reservedIdentifiers.has(identifierName) || + this.options.globalVariables.has(identifierName) + ) { + return; + } + + if (object.$renamed) { + return; + } + + var info = getIdentifierInfo(object, parents); + + if (info.spec.isExported) { + return; + } + + if (!info.spec.isReferenced) { + return; + } + + var contexts = [object, ...parents].filter((x) => isContext(x)); + var newName = null; + + // Function default parameter check! + var functionIndices = []; + for (var i in parents) { + if (isFunction(parents[i])) { + functionIndices.push(i); + } + } + + for (var functionIndex of functionIndices) { + if (parents[functionIndex].id === object) { + // This context is not referenced, so remove it + contexts = contexts.filter( + (context) => context != parents[functionIndex] + ); + continue; + } + if (parents[functionIndex].params === parents[functionIndex - 1]) { + var isReferencedHere = true; + + var slicedParents = parents.slice(0, functionIndex); + var forIndex = 0; + for (var parent of slicedParents) { + var childNode = slicedParents[forIndex - 1] || object; + + if ( + parent.type === "AssignmentPattern" && + parent.right === childNode + ) { + isReferencedHere = false; + break; + } + + forIndex++; + } + + if (!isReferencedHere) { + // This context is not referenced, so remove it + contexts = contexts.filter( + (context) => context != parents[functionIndex] + ); + } + } + } + + for (var check of contexts) { + if ( + this.variableAnalysis.defined.has(check) && + this.variableAnalysis.defined.get(check).has(identifierName) + ) { + if ( + this.changed.has(check) && + this.changed.get(check)[identifierName] + ) { + newName = this.changed.get(check)[identifierName]; + break; + } + } + } + + if (newName && typeof newName === "string") { + // Strange behavior where the `local` and `imported` objects are the same + if (info.isImportSpecifier) { + var importSpecifierIndex = parents.findIndex( + (x) => x.type === "ImportSpecifier" + ); + if ( + importSpecifierIndex != -1 && + parents[importSpecifierIndex].imported === + (parents[importSpecifierIndex - 1] || object) && + parents[importSpecifierIndex].imported && + parents[importSpecifierIndex].imported.type === "Identifier" + ) { + parents[importSpecifierIndex].imported = clone( + parents[importSpecifierIndex - 1] || object + ); + } + } + + if ( + parents[1] && + parents[1].type === "CallExpression" && + parents[1].arguments === parents[0] + ) { + if ( + parents[1].callee.type === "Identifier" && + parents[1].callee.name === variableFunctionName + ) { + this.replace(parents[1], Literal(newName)); + return; + } + } + + // console.log(o.name, "->", newName); + // 5. Update Identifier node's 'name' property + object.name = newName; + object.$renamed = true; + } + } + + transform(object: Node, parents: Node[]) { + var matchType = object.type === "Identifier" ? "Identifier" : "Context"; + if (matchType === "Identifier") { + this.transformIdentifier(object, parents); + } else { + this.transformContext(object, parents); + } + } +} diff --git a/src/transforms/identifier/variableAnalysis.ts b/src_old/transforms/identifier/variableAnalysis.ts similarity index 100% rename from src/transforms/identifier/variableAnalysis.ts rename to src_old/transforms/identifier/variableAnalysis.ts diff --git a/src/transforms/lock/antiDebug.ts b/src_old/transforms/lock/antiDebug.ts similarity index 100% rename from src/transforms/lock/antiDebug.ts rename to src_old/transforms/lock/antiDebug.ts diff --git a/src/transforms/lock/integrity.ts b/src_old/transforms/lock/integrity.ts similarity index 100% rename from src/transforms/lock/integrity.ts rename to src_old/transforms/lock/integrity.ts diff --git a/src/transforms/lock/lock.ts b/src_old/transforms/lock/lock.ts similarity index 100% rename from src/transforms/lock/lock.ts rename to src_old/transforms/lock/lock.ts diff --git a/src/transforms/minify.ts b/src_old/transforms/minify.ts similarity index 100% rename from src/transforms/minify.ts rename to src_old/transforms/minify.ts diff --git a/src/transforms/opaquePredicates.ts b/src_old/transforms/opaquePredicates.ts similarity index 100% rename from src/transforms/opaquePredicates.ts rename to src_old/transforms/opaquePredicates.ts diff --git a/src_old/transforms/preparation.ts b/src_old/transforms/preparation.ts new file mode 100644 index 0000000..31f7f20 --- /dev/null +++ b/src_old/transforms/preparation.ts @@ -0,0 +1,254 @@ +import Transform from "./transform"; + +import { + BlockStatement, + Identifier, + LabeledStatement, + Literal, + Node, + ReturnStatement, +} from "../util/gen"; +import { ObfuscateOrder } from "../order"; +import { clone, getFunction } from "../util/insert"; +import { getIdentifierInfo } from "../util/identifiers"; +import { isLoop } from "../util/compare"; +import { ExitCallback, walk } from "../traverse"; +import { variableFunctionName } from "../constants"; + +/** + * Preparation arranges the user's code into an AST the obfuscator can easily transform. + * + * ExplicitIdentifiers + * - `object.IDENTIFIER` -> `object['IDENTIFIER']` // Now String Concealing can apply on it + * - `{ IDENTIFIER: ... }` -> `{ "IDENTIFIER": ... }` + * + * ExplicitDeclarations + * - `var a,b,c` -> `var a; var b; var c;` // Now Stack can apply on it + * + * Block + * - `x => x * 2` -> `x => { return x * 2 }` // Change into Block Statements + * - `if(true) return` -> `if (true) { return }` + * - `while(a) a--;` -> `while(a) { a-- }` + * + * Label + * - `for(...) { break; }` -> `_1: for(...) { break _1; }` + * - `switch(v) { case 1...break }` -> `_2: switch(v) { case 1...break _2; }` + * - // Control Flow Flattening can safely apply now + */ +export default class Preparation extends Transform { + constructor(o) { + super(o, ObfuscateOrder.Preparation); + } + + match(object: Node, parents: Node[]) { + return !!object.type; + } + + transform(object: Node, parents: Node[]): void | ExitCallback { + // ExplicitIdentifiers + if (object.type === "Identifier") { + return this.transformExplicitIdentifiers(object, parents); + } + + // __JS_CONFUSER_VAR__ - Remove when Rename Variables is disabled + if ( + object.type === "CallExpression" && + object.callee.type === "Identifier" && + object.callee.name === variableFunctionName + ) { + if (object.arguments[0].type === "Identifier") { + if (!this.obfuscator.transforms["RenameVariables"]) { + return () => { + this.replace(object, Literal(object.arguments[0].name)); + }; + } + } + } + + // ExplicitDeclarations + if (object.type === "VariableDeclaration") { + return this.transformExplicitDeclarations(object, parents); + } + + // Block + switch (object.type) { + /** + * People use shortcuts and its harder to parse. + * + * - `if (a) b()` -> `if (a) { b() }` + * - Ensures all bodies are `BlockStatement`, not individual expression statements + */ + case "IfStatement": + if (object.consequent.type != "BlockStatement") { + object.consequent = BlockStatement([clone(object.consequent)]); + } + if (object.alternate && object.alternate.type != "BlockStatement") { + object.alternate = BlockStatement([clone(object.alternate)]); + } + break; + + case "WhileStatement": + case "WithStatement": + case "ForStatement": + case "ForOfStatement": + case "ForInStatement": + if (object.body.type != "BlockStatement") { + object.body = BlockStatement([clone(object.body)]); + } + break; + + case "ArrowFunctionExpression": + if (object.body.type !== "BlockStatement" && object.expression) { + object.body = BlockStatement([ReturnStatement(clone(object.body))]); + object.expression = false; + } + break; + } + + // Label + if ( + isLoop(object) || + (object.type == "BlockStatement" && + parents[0] && + parents[0].type == "LabeledStatement" && + parents[0].body === object) + ) { + return this.transformLabel(object, parents); + } + } + + /** + * Ensures every break; statement has a label to point to. + * + * This is because Control Flow Flattening adds For Loops which label-less break statements point to the nearest, + * when they actually need to point to the original statement. + */ + transformLabel(object: Node, parents: Node[]) { + return () => { + var currentLabel = + parents[0].type == "LabeledStatement" && parents[0].label.name; + + var label = currentLabel || this.getPlaceholder(); + + walk(object, parents, (o, p) => { + if (o.type == "BreakStatement" || o.type == "ContinueStatement") { + function isContinuableStatement(x) { + return isLoop(x) && x.type !== "SwitchStatement"; + } + function isBreakableStatement(x) { + return isLoop(x) || (o.label && x.type == "BlockStatement"); + } + + var fn = + o.type == "ContinueStatement" + ? isContinuableStatement + : isBreakableStatement; + + var loop = p.find(fn); + if (object == loop) { + if (!o.label) { + o.label = Identifier(label); + } + } + } + }); + + // Append label statement as this loop has none + if (!currentLabel) { + this.replace(object, LabeledStatement(label, { ...object })); + } + }; + } + + /** + * Transforms Identifiers (a.IDENTIFIER, {IDENTIFIER:...}) into string properties + */ + transformExplicitIdentifiers(object: Node, parents: Node[]) { + // Mark functions containing 'eval' + // Some transformations avoid functions that have 'eval' to not break them + if (object.name === "eval") { + var fn = getFunction(object, parents); + if (fn) { + fn.$requiresEval = true; + } + } + + var info = getIdentifierInfo(object, parents); + if (info.isPropertyKey || info.isAccessor) { + var propIndex = parents.findIndex( + (x) => x.type == "MethodDefinition" || x.type == "Property" + ); + + // Don't change constructor! + if (propIndex !== -1) { + if ( + parents[propIndex].type == "MethodDefinition" && + parents[propIndex].kind == "constructor" + ) { + return; + } + } + + this.replace(object, Literal(object.name)); + parents[0].computed = true; + parents[0].shorthand = false; + } + } + + /** + * Transforms VariableDeclaration into single declarations. + */ + transformExplicitDeclarations(object: Node, parents: Node[]) { + // for ( var x in ... ) {...} + var forIndex = parents.findIndex( + (x) => x.type == "ForInStatement" || x.type == "ForOfStatement" + ); + if ( + forIndex != -1 && + parents[forIndex].left == (parents[forIndex - 1] || object) + ) { + object.declarations.forEach((x) => { + x.init = null; + }); + return; + } + + var body = parents[0]; + if (isLoop(body) || body.type == "LabeledStatement") { + return; + } + + if (body.type == "ExportNamedDeclaration") { + return; + } + + if (!Array.isArray(body)) { + this.error(new Error("body is " + body.type)); + } + + if (object.declarations.length > 1) { + // Make singular + + var index = body.indexOf(object); + if (index == -1) { + this.error(new Error("index is -1")); + } + + var after = object.declarations.slice(1); + + body.splice( + index + 1, + 0, + ...after.map((x) => { + return { + type: "VariableDeclaration", + declarations: [clone(x)], + kind: object.kind, + }; + }) + ); + + object.declarations.length = 1; + } + } +} diff --git a/src/transforms/renameLabels.ts b/src_old/transforms/renameLabels.ts similarity index 100% rename from src/transforms/renameLabels.ts rename to src_old/transforms/renameLabels.ts diff --git a/src/transforms/rgf.ts b/src_old/transforms/rgf.ts similarity index 100% rename from src/transforms/rgf.ts rename to src_old/transforms/rgf.ts diff --git a/src_old/transforms/shuffle.ts b/src_old/transforms/shuffle.ts new file mode 100644 index 0000000..86e0a07 --- /dev/null +++ b/src_old/transforms/shuffle.ts @@ -0,0 +1,254 @@ +import { ok } from "assert"; +import { ObfuscateOrder } from "../order"; +import { ComputeProbabilityMap } from "../probability"; +import Template from "../templates/template"; +import { + BinaryExpression, + CallExpression, + ExpressionStatement, + ForStatement, + FunctionExpression, + Identifier, + Literal, + MemberExpression, + ReturnStatement, + UpdateExpression, + VariableDeclaration, + VariableDeclarator, +} from "../util/gen"; +import { clone, prepend } from "../util/insert"; +import { getRandomInteger } from "../util/random"; +import Transform from "./transform"; + +var Hash = function (s) { + var a = 1, + c = 0, + h, + o; + if (s) { + a = 0; + for (h = s.length - 1; h >= 0; h--) { + o = s.charCodeAt(h); + a = ((a << 6) & 268435455) + o + (o << 14); + c = a & 266338304; + a = c !== 0 ? a ^ (c >> 21) : a; + } + } + return ~~String(a).slice(0, 3); +}; + +var HashTemplate = new Template( + ` + var {name} = function(arr) { + var s = arr.map(x=>x+"").join(''), a = 1, c = 0, h, o; + if (s) { + a = 0; + for (h = s.length - 1; h >= 0; h--) { + o = s.charCodeAt(h); + a = (a<<6&268435455) + o + (o<<14); + c = a & 266338304; + a = c!==0?a^c>>21:a; + } + } + return ~~String(a).slice(0, 3); +};` +); + +/** + * Shuffles arrays initial order of elements. + * + * "Un-shuffles" the array at runtime. + */ +export default class Shuffle extends Transform { + hashName: string; + constructor(o) { + super(o, ObfuscateOrder.Shuffle); + } + + match(object, parents) { + return ( + object.type == "ArrayExpression" && + !parents.find((x) => x.$multiTransformSkip) + ); + } + + transform(object, parents) { + return () => { + if (object.elements.length < 3) { + // Min: 4 elements + return; + } + + function isAllowed(e) { + return ( + e.type == "Literal" && + { number: 1, boolean: 1, string: 1 }[typeof e.value] + ); + } + + // Only arrays with only literals + var illegal = object.elements.find((x) => !isAllowed(x)); + + if (illegal) { + return; + } + + var mapped = object.elements.map((x) => x.value); + + var mode = ComputeProbabilityMap(this.options.shuffle, (x) => x, mapped); + if (mode) { + var shift = getRandomInteger( + 1, + Math.min(60, object.elements.length * 6) + ); + + var expr = Literal(shift); + var name = this.getPlaceholder(); + + if (mode == "hash") { + var str = mapped.join(""); + shift = Hash(str); + + if (!this.hashName) { + prepend( + parents[parents.length - 1], + HashTemplate.single({ + name: (this.hashName = this.getPlaceholder()), + }) + ); + } + + for (var i = 0; i < shift; i++) { + object.elements.push(object.elements.shift()); + } + + var shiftedHash = Hash( + object.elements.map((x) => x.value + "").join("") + ); + + expr = BinaryExpression( + "-", + CallExpression(Identifier(this.hashName), [Identifier(name)]), + Literal(shiftedHash - shift) + ); + } else { + for (var i = 0; i < shift; i++) { + object.elements.push(object.elements.shift()); + } + } + + var code = []; + + var iName = this.getPlaceholder(); + + var inPlace = false; + var inPlaceName; + var inPlaceBody; + var inPlaceIndex; + + var varDeclarator = parents[0]; + if (varDeclarator.type == "VariableDeclarator") { + var varDec = parents[2]; + if (varDec.type == "VariableDeclaration" && varDec.kind !== "const") { + var body = parents[3]; + if ( + varDec.declarations.length == 1 && + Array.isArray(body) && + varDeclarator.id.type === "Identifier" && + varDeclarator.init === object + ) { + inPlaceIndex = body.indexOf(varDec); + inPlaceBody = body; + inPlace = inPlaceIndex !== -1; + inPlaceName = varDeclarator.id.name; + } + } + } + + if (mode !== "hash") { + var varPrefix = this.getPlaceholder(); + code.push( + new Template(` + for ( var ${varPrefix}x = 16; ${varPrefix}x%4 === 0; ${varPrefix}x++) { + var ${varPrefix}z = 0; + ${ + inPlace ? `${inPlaceName} = ${name}` : name + } = ${name}.concat((function(){ + ${varPrefix}z++; + if(${varPrefix}z === 1){ + return []; + } + + for( var ${varPrefix}i = ${getRandomInteger( + 5, + 105 + )}; ${varPrefix}i; ${varPrefix}i-- ){ + ${name}.unshift(${name}.pop()); + } + return []; + })()); + } + `).single() + ); + } + + code.push( + ForStatement( + VariableDeclaration(VariableDeclarator(iName, expr)), + Identifier(iName), + UpdateExpression("--", Identifier(iName), false), + [ + // ${name}.unshift(${name}.pop()); + ExpressionStatement( + CallExpression( + MemberExpression( + Identifier(name), + Identifier("unshift"), + false + ), + [ + CallExpression( + MemberExpression( + Identifier(name), + Identifier("pop"), + false + ), + [] + ), + ] + ) + ), + ] + ) + ); + + if (inPlace) { + var varDeclarator = parents[0]; + ok(i != -1); + + inPlaceBody.splice( + inPlaceIndex + 1, + 0, + VariableDeclaration( + VariableDeclarator(name, Identifier(varDeclarator.id.name)) + ), + ...code + ); + } + + if (!inPlace) { + this.replace( + object, + CallExpression( + FunctionExpression( + [Identifier(name)], + [...code, ReturnStatement(Identifier(name))] + ), + [clone(object)] + ) + ); + } + } + }; + } +} diff --git a/src/transforms/stack.ts b/src_old/transforms/stack.ts similarity index 100% rename from src/transforms/stack.ts rename to src_old/transforms/stack.ts diff --git a/src/transforms/string/encoding.ts b/src_old/transforms/string/encoding.ts similarity index 100% rename from src/transforms/string/encoding.ts rename to src_old/transforms/string/encoding.ts diff --git a/src_old/transforms/string/stringCompression.ts b/src_old/transforms/string/stringCompression.ts new file mode 100644 index 0000000..11b440e --- /dev/null +++ b/src_old/transforms/string/stringCompression.ts @@ -0,0 +1,309 @@ +import { ok } from "assert"; +import { ObfuscateOrder } from "../../order"; +import { ComputeProbabilityMap } from "../../probability"; +import Template from "../../templates/template"; +import { isDirective, isModuleSource } from "../../util/compare"; +import { + AssignmentExpression, + BinaryExpression, + CallExpression, + ExpressionStatement, + FunctionDeclaration, + FunctionExpression, + Identifier, + IfStatement, + Literal, + MemberExpression, + ObjectExpression, + Property, + ReturnStatement, + VariableDeclaration, + VariableDeclarator, +} from "../../util/gen"; +import { append, prepend } from "../../util/insert"; +import Transform from "../transform"; +import { predictableFunctionTag } from "../../constants"; +import { + chance, + choice, + getRandomFalseExpression, + getRandomInteger, + getRandomString, + splitIntoChunks, +} from "../../util/random"; + +function LZ_encode(c) { + ok(c); + var x = "charCodeAt", + b, + e = {}, + f = c.split(""), + d = [], + a = f[0], + g = 256; + for (b = 1; b < f.length; b++) + (c = f[b]), + null != e[a + c] + ? (a += c) + : (d.push(1 < a.length ? e[a] : a[x](0)), (e[a + c] = g), g++, (a = c)); + d.push(1 < a.length ? e[a] : a[x](0)); + for (b = 0; b < d.length; b++) d[b] = String.fromCharCode(d[b]); + return d.join(""); +} + +function LZ_decode(b) { + ok(b); + var o, + f, + a, + e = {}, + d = b.split(""), + c = (f = d[0]), + g = [c], + h = (o = 256); + for (var i = 1; i < d.length; i++) + (a = d[i].charCodeAt(0)), + (a = h > a ? d[i] : e[a] ? e[a] : f + c), + g.push(a), + (c = a.charAt(0)), + (e[o] = f + c), + o++, + (f = a); + return g.join(""); +} + +const DecodeTemplate = new Template( + `function {name}(b){ + var o, + f, + a, + e = {}, + d = b.split(""), + c = (f = d[0]), + g = [c], + h = (o = 256); + for (b = 1; b < d.length; b++) + (a = d[b].charCodeAt(0)), + (a = h > a ? d[b] : e[a] ? e[a] : f + c), + g.push(a), + (c = a.charAt(0)), + (e[o] = f + c), + o++, + (f = a); + return g.join("").split("{delimiter}"); + }` +); + +export default class StringCompression extends Transform { + map: Map; + ignore: Set; + string: string; + delimiter = "|"; + + fnName: string; + + constructor(o) { + super(o, ObfuscateOrder.StringCompression); + + this.map = new Map(); + this.ignore = new Set(); + this.string = ""; + this.fnName = this.getPlaceholder() + predictableFunctionTag; + } + + apply(tree) { + super.apply(tree); + + this.string = this.string.slice(0, this.string.length - 1); + if (!this.string.length) { + return; + } + + var split = this.getPlaceholder(); + var decoder = this.getPlaceholder(); + var getStringName = this.getPlaceholder() + predictableFunctionTag; // Returns the string payload + + var encoded = LZ_encode(this.string); + if (LZ_decode(encoded) !== this.string) { + this.error( + new Error( + "String failed to be decoded. Try disabling the 'stringCompression' option." + ) + ); + } + + var getStringParamName = this.getPlaceholder(); + var decoderParamName = this.getPlaceholder(); + + var callExpression = CallExpression(Identifier(decoderParamName), [ + CallExpression(Identifier(getStringParamName), []), + ]); + + prepend( + tree, + VariableDeclaration( + VariableDeclarator( + split, + CallExpression( + FunctionExpression( + [Identifier(getStringParamName), Identifier(decoderParamName)], + [ReturnStatement(callExpression)] + ), + [Identifier(getStringName), Identifier(decoder)] + ) + ) + ) + ); + + var keys = new Set(); + var keysToMake = getRandomInteger(4, 14); + for (var i = 0; i < keysToMake; i++) { + keys.add(getRandomString(getRandomInteger(4, 14))); + } + + var objectExpression = ObjectExpression( + Array.from(keys).map((key) => { + return Property(Literal(key), getRandomFalseExpression(), true); + }) + ); + + // Get string function + var getStringBody = []; + var splits = splitIntoChunks( + encoded, + Math.floor(encoded.length / getRandomInteger(3, 6)) + ); + + getStringBody.push( + VariableDeclaration(VariableDeclarator("str", Literal(splits.shift()))) + ); + + getStringBody.push( + VariableDeclaration(VariableDeclarator("objectToTest", objectExpression)) + ); + + const addIfStatement = (testingFor, literalValueToBeAppended) => { + getStringBody.push( + IfStatement( + BinaryExpression( + "in", + Literal(testingFor), + Identifier("objectToTest") + ), + [ + ExpressionStatement( + AssignmentExpression( + "+=", + Identifier("str"), + Literal(literalValueToBeAppended) + ) + ), + ] + ) + ); + }; + + for (const split of splits) { + if (chance(50)) { + var fakeKey; + do { + fakeKey = getRandomString(getRandomInteger(4, 14)); + } while (keys.has(fakeKey) || fakeKey in {}); + + addIfStatement(fakeKey, getRandomString(split.length)); + } + + addIfStatement(choice(Array.from(keys)), split); + } + + // Return computed string + getStringBody.push(ReturnStatement(Identifier("str"))); + + append(tree, FunctionDeclaration(getStringName, [], getStringBody)); + + append( + tree, + FunctionDeclaration( + this.fnName, + [Identifier("index")], + [ + ReturnStatement( + MemberExpression(Identifier(split), Identifier("index"), true) + ), + ] + ) + ); + + append( + tree, + DecodeTemplate.single({ name: decoder, delimiter: this.delimiter }) + ); + } + + match(object, parents) { + return ( + object.type == "Literal" && + typeof object.value === "string" && + object.value && + object.value.length > 3 && + !isDirective(object, parents) && + !isModuleSource(object, parents) && + !parents.find((x) => x.$multiTransformSkip) + ); + } + + transform(object, parents) { + if (!object.value) { + return; + } + if ( + this.ignore.has(object.value) || + object.value.includes(this.delimiter) + ) { + return; + } + + if ( + !parents[0] || + (parents[0].type == "CallExpression" && + parents[0].callee.type == "Identifier" && + parents[0].callee.name == this.fnName) + ) { + return; + } + + if ( + !ComputeProbabilityMap( + this.options.stringCompression, + (x) => x, + object.value + ) + ) { + return; + } + + var index = this.map.get(object.value); + + // New string, add it! + if (typeof index !== "number") { + // Ensure the string gets properly decoded + if (LZ_decode(LZ_encode(object.value)) !== object.value) { + this.ignore.add(object.value); + return; + } + + index = this.map.size; + this.map.set(object.value, index); + this.string += object.value + this.delimiter; + } + ok(typeof index === "number"); + + return () => { + this.replaceIdentifierOrLiteral( + object, + CallExpression(Identifier(this.fnName), [Literal(index)]), + parents + ); + }; + } +} diff --git a/src_old/transforms/string/stringConcealing.ts b/src_old/transforms/string/stringConcealing.ts new file mode 100644 index 0000000..db07308 --- /dev/null +++ b/src_old/transforms/string/stringConcealing.ts @@ -0,0 +1,380 @@ +import { ok } from "assert"; +import { ObfuscateOrder } from "../../order"; +import Template from "../../templates/template"; +import { getBlock } from "../../traverse"; +import { isDirective, isModuleSource } from "../../util/compare"; +import { + ArrayExpression, + CallExpression, + Identifier, + Literal, + MemberExpression, + Node, + ObjectExpression, + Property, + VariableDeclaration, + VariableDeclarator, +} from "../../util/gen"; +import { append, prepend } from "../../util/insert"; +import { + chance, + choice, + getRandomInteger, + getRandomString, + shuffle, +} from "../../util/random"; +import Transform from "../transform"; +import { + EncodingImplementation, + EncodingImplementations, + createEncodingImplementation, + hasAllEncodings, +} from "./encoding"; +import { ComputeProbabilityMap } from "../../probability"; +import { + BufferToStringTemplate, + createGetGlobalTemplate, +} from "../../templates/bufferToString"; +import { criticalFunctionTag, predictableFunctionTag } from "../../constants"; + +interface FunctionObject { + block: Node; + fnName: string; + encodingImplementation: EncodingImplementation; +} + +export default class StringConcealing extends Transform { + arrayExpression: Node; + set: Set; + index: { [str: string]: [number, string, Node] }; // index, fnName, block + + arrayName = this.getPlaceholder(); + ignore = new Set(); + variablesMade = 1; + gen: ReturnType; + + functionObjects: FunctionObject[] = []; + + constructor(o) { + super(o, ObfuscateOrder.StringConcealing); + + this.set = new Set(); + this.index = Object.create(null); + this.arrayExpression = ArrayExpression([]); + this.gen = this.getGenerator(); + } + + apply(tree) { + super.apply(tree); + + // Pad array with useless strings + var dead = getRandomInteger(50, 200); + for (var i = 0; i < dead; i++) { + var str = getRandomString(getRandomInteger(5, 40)); + var fn = this.transform(Literal(str), [tree]); + if (fn) { + fn(); + } + } + + var cacheName = this.getPlaceholder(); + var bufferToStringName = this.getPlaceholder() + predictableFunctionTag; + + // This helper functions convert UInt8 Array to UTf-string + prepend( + tree, + ...BufferToStringTemplate.compile({ + name: bufferToStringName, + getGlobalFnName: this.getPlaceholder() + predictableFunctionTag, + GetGlobalTemplate: createGetGlobalTemplate(this, tree, []), + }) + ); + + for (var functionObject of this.functionObjects) { + var { + block, + fnName: getterFnName, + encodingImplementation, + } = functionObject; + + var decodeFn = + this.getPlaceholder() + predictableFunctionTag + criticalFunctionTag; + + append( + block, + encodingImplementation.template.single({ + __fnName__: decodeFn, + __bufferToString__: bufferToStringName, + }) + ); + // All these are fake and never ran + var ifStatements = new Template(`if ( z == x ) { + return y[${cacheName}[z]] = ${getterFnName}(x, y); + } + if ( y ) { + [b, y] = [a(b), x || z] + return ${getterFnName}(x, b, z) + } + if ( z && a !== ${decodeFn} ) { + ${getterFnName} = ${decodeFn} + return ${getterFnName}(x, -1, z, a, b) + } + if ( a === ${getterFnName} ) { + ${decodeFn} = y + return ${decodeFn}(z) + } + if( a === undefined ) { + ${getterFnName} = b + } + if( z == a ) { + return y ? x[b[y]] : ${cacheName}[x] || (z=(b[x] || a), ${cacheName}[x] = z(${this.arrayName}[x])) + } + `).compile(); + + // Not all fake if-statements are needed + ifStatements = ifStatements.filter(() => chance(50)); + + // This one is always used + ifStatements.push( + new Template(` + if ( x !== y ) { + return b[x] || (b[x] = a(${this.arrayName}[x])) + } + `).single() + ); + + shuffle(ifStatements); + + var varDeclaration = new Template(` + var ${getterFnName} = (x, y, z, a, b)=>{ + if(typeof a === "undefined") { + a = ${decodeFn} + } + if(typeof b === "undefined") { + b = ${cacheName} + } + } + `).single(); + + varDeclaration.declarations[0].init.body.body.push(...ifStatements); + + prepend(block, varDeclaration); + } + + prepend( + tree, + VariableDeclaration([ + VariableDeclarator(cacheName, ArrayExpression([])), + VariableDeclarator(this.arrayName, this.arrayExpression), + ]) + ); + } + + match(object, parents) { + return ( + object.type == "Literal" && + typeof object.value === "string" && + object.value.length >= 3 && + !isModuleSource(object, parents) && + !isDirective(object, parents) //&& + /*!parents.find((x) => x.$multiTransformSkip)*/ + ); + } + + transform(object: Node, parents: Node[]) { + return () => { + // Empty strings are discarded + if ( + !object.value || + this.ignore.has(object.value) || + object.value.length == 0 + ) { + return; + } + + // Allow user to choose which strings get changed + if ( + !ComputeProbabilityMap( + this.options.stringConcealing, + (x) => x, + object.value + ) + ) { + return; + } + + var currentBlock = getBlock(object, parents); + + // Find created functions + var functionObjects: FunctionObject[] = parents + .filter((node) => node.$stringConcealingFunctionObject) + .map((item) => item.$stringConcealingFunctionObject); + + // Choose random functionObject to use + var functionObject = choice(functionObjects); + + if ( + !functionObject || + (!hasAllEncodings() && + chance(25 / this.functionObjects.length) && + !currentBlock.$stringConcealingFunctionObject) + ) { + // No functions, create one + + var newFunctionObject: FunctionObject = { + block: currentBlock, + encodingImplementation: createEncodingImplementation(), + fnName: this.getPlaceholder() + predictableFunctionTag, + }; + + this.functionObjects.push(newFunctionObject); + currentBlock.$stringConcealingFunctionObject = newFunctionObject; + functionObject = newFunctionObject; + } + + var { fnName, encodingImplementation } = functionObject; + + var index = -1; + + // String already decoded? + if (this.set.has(object.value)) { + var row = this.index[object.value]; + if (parents.includes(row[2])) { + [index, fnName] = row; + ok(typeof index === "number"); + } + } + + if (index == -1) { + // The decode function must return correct result + var encoded = encodingImplementation.encode(object.value); + if (encodingImplementation.decode(encoded) !== object.value) { + this.ignore.add(object.value); + this.warn( + encodingImplementation.identity, + object.value.slice(0, 100) + ); + delete EncodingImplementations[encodingImplementation.identity]; + + return; + } + + this.arrayExpression.elements.push(Literal(encoded)); + index = this.arrayExpression.elements.length - 1; + this.index[object.value] = [index, fnName, currentBlock]; + + this.set.add(object.value); + } + + ok(index != -1, "index == -1"); + + var callExpr = CallExpression(Identifier(fnName), [Literal(index)]); + + // use `.apply` to fool automated de-obfuscators + if (chance(10)) { + callExpr = CallExpression( + MemberExpression(Identifier(fnName), Literal("apply"), true), + [Identifier("undefined"), ArrayExpression([Literal(index)])] + ); + } + + // use `.call` + else if (chance(10)) { + callExpr = CallExpression( + MemberExpression(Identifier(fnName), Literal("call"), true), + [Identifier("undefined"), Literal(index)] + ); + } + + var referenceType = "call"; + if (parents.length && chance(50 - this.variablesMade)) { + referenceType = "constantReference"; + } + + var newExpr: Node = callExpr; + + if (referenceType === "constantReference") { + // Define the string earlier, reference the name here + this.variablesMade++; + + var constantReferenceType = choice(["variable", "array", "object"]); + + var place = currentBlock; + if (!place) { + this.error(new Error("No lexical block to insert code")); + } + + switch (constantReferenceType) { + case "variable": + var name = this.getPlaceholder(); + + prepend( + place, + VariableDeclaration(VariableDeclarator(name, callExpr)) + ); + + newExpr = Identifier(name); + break; + case "array": + if (!place.$stringConcealingArray) { + place.$stringConcealingArray = ArrayExpression([]); + place.$stringConcealingArrayName = this.getPlaceholder(); + + prepend( + place, + VariableDeclaration( + VariableDeclarator( + place.$stringConcealingArrayName, + place.$stringConcealingArray + ) + ) + ); + } + + var arrayIndex = place.$stringConcealingArray.elements.length; + + place.$stringConcealingArray.elements.push(callExpr); + + var memberExpression = MemberExpression( + Identifier(place.$stringConcealingArrayName), + Literal(arrayIndex), + true + ); + + newExpr = memberExpression; + break; + case "object": + if (!place.$stringConcealingObject) { + place.$stringConcealingObject = ObjectExpression([]); + place.$stringConcealingObjectName = this.getPlaceholder(); + + prepend( + place, + VariableDeclaration( + VariableDeclarator( + place.$stringConcealingObjectName, + place.$stringConcealingObject + ) + ) + ); + } + + var propName = this.gen.generate(); + var property = Property(Literal(propName), callExpr, true); + place.$stringConcealingObject.properties.push(property); + + var memberExpression = MemberExpression( + Identifier(place.$stringConcealingObjectName), + Literal(propName), + true + ); + + newExpr = memberExpression; + break; + } + } + + this.replaceIdentifierOrLiteral(object, newExpr, parents); + }; + } +} diff --git a/src_old/transforms/string/stringEncoding.ts b/src_old/transforms/string/stringEncoding.ts new file mode 100644 index 0000000..04418e9 --- /dev/null +++ b/src_old/transforms/string/stringEncoding.ts @@ -0,0 +1,95 @@ +import Transform from "../transform"; +import { choice } from "../../util/random"; +import { isDirective, isModuleSource } from "../../util/compare"; +import { ComputeProbabilityMap } from "../../probability"; +import { Identifier } from "../../util/gen"; + +function pad(x: string, len: number): string { + while (x.length < len) { + x = "0" + x; + } + return x; +} + +function even(x: string) { + if (x.length % 2 != 0) { + return "0" + x; + } + return x; +} + +function toHexRepresentation(str: string) { + var escapedString = ""; + str.split("").forEach((char) => { + var code = char.charCodeAt(0); + if (code < 128) { + escapedString += "\\x" + even(pad(code.toString(16), 2)); + } else { + escapedString += char; + } + }); + + return escapedString; +} + +function toUnicodeRepresentation(str: string) { + var escapedString = ""; + str.split("").forEach((char) => { + var code = char.charCodeAt(0); + if (code < 128) { + escapedString += "\\u" + even(pad(code.toString(16), 4)); + } else { + escapedString += char; + } + }); + + return escapedString; +} + +/** + * [String Encoding](https://docs.jscrambler.com/code-integrity/documentation/transformations/string-encoding) transforms a string into an encoded representation. + * + * - Potency Low + * - Resilience Low + * - Cost Low + */ +export default class StringEncoding extends Transform { + constructor(o) { + super(o); + } + + match(object, parents) { + return ( + object.type == "Literal" && + typeof object.value === "string" && + object.value.length > 0 && + !isModuleSource(object, parents) && + !isDirective(object, parents) + ); + } + + transform(object, parents) { + // Allow percentages + if ( + !ComputeProbabilityMap( + this.options.stringEncoding, + (x) => x, + object.value + ) + ) + return; + + var type = choice(["hexadecimal", "unicode"]); + + var escapedString = ( + type == "hexadecimal" ? toHexRepresentation : toUnicodeRepresentation + )(object.value); + + return () => { + if (object.type !== "Literal") return; + + // ESCodeGen tries to escape backslashes, here is a work-around + this.replace(object, Identifier(`'${escapedString}'`)); + }; + } +} diff --git a/src_old/transforms/string/stringSplitting.ts b/src_old/transforms/string/stringSplitting.ts new file mode 100644 index 0000000..765ceb1 --- /dev/null +++ b/src_old/transforms/string/stringSplitting.ts @@ -0,0 +1,86 @@ +import Transform from "../transform"; +import { Node, Literal, BinaryExpression } from "../../util/gen"; +import { clone } from "../../util/insert"; +import { getRandomInteger, shuffle, splitIntoChunks } from "../../util/random"; +import { ObfuscateOrder } from "../../order"; +import { isDirective, isModuleSource } from "../../util/compare"; +import { ok } from "assert"; +import { ComputeProbabilityMap } from "../../probability"; + +export default class StringSplitting extends Transform { + joinPrototype: string; + strings: { [value: string]: string }; + + adders: Node[][]; + vars: Node[]; + + constructor(o) { + super(o, ObfuscateOrder.StringSplitting); + + this.joinPrototype = null; + this.strings = Object.create(null); + + this.adders = []; + this.vars = []; + } + + match(object: Node, parents: Node[]) { + return ( + object.type == "Literal" && + typeof object.value === "string" && + object.value.length >= 8 && + !isModuleSource(object, parents) && + !isDirective(object, parents) + ); + } + + transform(object: Node, parents: Node[]) { + return () => { + var size = Math.round( + Math.max(6, object.value.length / getRandomInteger(3, 8)) + ); + if (object.value.length <= size) { + return; + } + + var chunks = splitIntoChunks(object.value, size); + if (!chunks || chunks.length <= 1) { + return; + } + + if ( + !ComputeProbabilityMap( + this.options.stringSplitting, + (x) => x, + object.value + ) + ) { + return; + } + + var binaryExpression; + var parent; + var last = chunks.pop(); + chunks.forEach((chunk, i) => { + if (i == 0) { + parent = binaryExpression = BinaryExpression( + "+", + Literal(chunk), + Literal("") + ); + } else { + binaryExpression.left = BinaryExpression( + "+", + clone(binaryExpression.left), + Literal(chunk) + ); + ok(binaryExpression); + } + }); + + parent.right = Literal(last); + + this.replaceIdentifierOrLiteral(object, parent, parents); + }; + } +} diff --git a/src/transforms/transform.ts b/src_old/transforms/transform.ts similarity index 100% rename from src/transforms/transform.ts rename to src_old/transforms/transform.ts diff --git a/src/traverse.ts b/src_old/traverse.ts similarity index 100% rename from src/traverse.ts rename to src_old/traverse.ts diff --git a/src/types.ts b/src_old/types.ts similarity index 100% rename from src/types.ts rename to src_old/types.ts diff --git a/src/util/compare.ts b/src_old/util/compare.ts similarity index 100% rename from src/util/compare.ts rename to src_old/util/compare.ts diff --git a/src/util/gen.ts b/src_old/util/gen.ts similarity index 100% rename from src/util/gen.ts rename to src_old/util/gen.ts diff --git a/src/util/guard.ts b/src_old/util/guard.ts similarity index 100% rename from src/util/guard.ts rename to src_old/util/guard.ts diff --git a/src/util/identifiers.ts b/src_old/util/identifiers.ts similarity index 100% rename from src/util/identifiers.ts rename to src_old/util/identifiers.ts diff --git a/src/util/insert.ts b/src_old/util/insert.ts similarity index 100% rename from src/util/insert.ts rename to src_old/util/insert.ts diff --git a/src/util/math.ts b/src_old/util/math.ts similarity index 100% rename from src/util/math.ts rename to src_old/util/math.ts diff --git a/src/util/object.ts b/src_old/util/object.ts similarity index 100% rename from src/util/object.ts rename to src_old/util/object.ts diff --git a/src/util/random.ts b/src_old/util/random.ts similarity index 100% rename from src/util/random.ts rename to src_old/util/random.ts diff --git a/src/util/scope.ts b/src_old/util/scope.ts similarity index 100% rename from src/util/scope.ts rename to src_old/util/scope.ts diff --git a/test/compare.test.ts b/test/compare.test.ts deleted file mode 100644 index 5144ada..0000000 --- a/test/compare.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import Template from "../src/templates/template"; -import { isIndependent } from "../src/util/compare"; -import { - ArrayExpression, - FunctionExpression, - Identifier, - Literal, -} from "../src/util/gen"; - -describe("isIndependent", () => { - it("should return true for literals", () => { - expect(isIndependent(Literal("String"), [])).toStrictEqual(true); - }); - - it("should return false for identifiers", () => { - expect( - isIndependent(Identifier("variable"), [{ type: "Program" }]) - ).toStrictEqual(false); - }); - - it("should return true for reserved identifiers (undefined, NaN, etc)", () => { - expect( - isIndependent(Identifier("undefined"), [{ type: "Program" }]) - ).toStrictEqual(true); - }); - - it("should return true for arrays of literals", () => { - expect( - isIndependent(ArrayExpression([Literal("String")]), []) - ).toStrictEqual(true); - }); - - it("should return false for arrays with identifiers", () => { - expect( - isIndependent( - ArrayExpression([Literal("String"), Identifier("variable")]), - [] - ) - ).toStrictEqual(false); - }); - - it("should return false for everything else", () => { - expect(isIndependent(FunctionExpression([], []), [])).toStrictEqual(false); - }); - - it("various cases", () => { - expect( - isIndependent( - new Template(`({ - x: 1, - y: 2, - z: 3, - })`).single().expression, - [] - ) - ).toStrictEqual(true); - - expect( - isIndependent( - new Template(`({ - x: 1, - y: 2, - z: [3,4,5,6,7,"My String",undefined,null,NaN], - })`).single().expression, - [] - ) - ).toStrictEqual(true); - - expect( - isIndependent( - new Template(`({ - x: 1, - y: 2, - z: 3, - _: function(){return value} - })`).single().expression, - [] - ) - ).toStrictEqual(false); - - expect( - isIndependent( - new Template(`({ - x: 1, - y: 2, - z: 3, - _: [value] - })`).single().expression, - [] - ) - ).toStrictEqual(false); - - expect( - isIndependent( - new Template(`([ - { - x: value - } - ])`).single().expression, - [] - ) - ).toStrictEqual(false); - }); -}); diff --git a/test/index.test.ts b/test/index.test.ts index ce01768..3c6f84d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,7 +1,4 @@ -import JsConfuser, { - debugObfuscation, - debugTransformations, -} from "../src/index"; +import JsConfuser, { obfuscateWithProfiler } from "../src/index"; it("should be a function", async () => { expect(typeof JsConfuser).toBe("function"); @@ -195,40 +192,24 @@ describe("obfuscateAST", () => { }); }); -describe("debugTransformations", () => { - test("Variant #1: Return array of objects containing `name`, `code`, and `ms` properties", async () => { - var frames = await debugTransformations(`console.log(1)`, { - target: "node", - preset: "low", - }); - - expect(Array.isArray(frames)).toStrictEqual(true); - expect(frames.length).toBeTruthy(); - - frames.forEach((frame) => { - expect(typeof frame.name).toStrictEqual("string"); - expect(typeof frame.code).toStrictEqual("string"); - expect(typeof frame.ms).toStrictEqual("number"); - }); - }); -}); - describe("debugObfuscation", () => { test("Variant #1: Return array of objects containing code, ms, and name properties", async () => { var called = false; - var callback = (name, complete, totalTransforms) => { + var callback = ({ name, complete, totalTransforms }) => { expect(typeof name).toStrictEqual("string"); expect(typeof complete).toStrictEqual("number"); expect(typeof totalTransforms).toStrictEqual("number"); called = true; }; - var output = await debugObfuscation( + var output = await JsConfuser.obfuscateWithProfiler( `console.log(1)`, { target: "node", preset: "low" }, - callback, - require("perf_hooks").performance + { + callback, + performance: require("perf_hooks").performance, + } ); expect(typeof output).toStrictEqual("object"); diff --git a/test/templates/template.test.ts b/test/templates/template.test.ts index 6cb463b..357cb53 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -1,7 +1,4 @@ -import { compileJsSync } from "../../src/compiler"; -import { parseSnippet } from "../../src/parser"; import Template from "../../src/templates/template"; -import { Literal } from "../../src/util/gen"; describe("Template", () => { test("Variant #1: Error when invalid code passed in", () => { diff --git a/test/transforms/transform.test.ts b/test/transforms/transform.test.ts deleted file mode 100644 index c4f43ed..0000000 --- a/test/transforms/transform.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import Obfuscator from "../../src/obfuscator"; -import Transform from "../../src/transforms/transform"; - -describe("Transform class", () => { - class MyTransformClass extends Transform { - constructor(o) { - super(o); - } - } - - const myObfuscator = new Obfuscator({ - target: "node", - identifierGenerator: "mangled", - }); - const myTransform = new MyTransformClass(myObfuscator); - - const tree = { - type: "Program", - body: [], - }; - - test("Variant #1: Not implemented match()", () => { - expect(() => myTransform.match(tree, [])).toThrow(); - }); - - test("Variant #2: Not implemented transform()", () => { - expect(() => myTransform.transform(tree, [])).toThrow(); - }); - - test("Variant #3: getGenerator()", () => { - const generator = myTransform.getGenerator(); - - const generated = new Set(); - - const count = 50; - - for (var i = 0; i < count; i++) { - const newName = generator.generate(); - generated.add(newName); - } - - // This ensures all generated names are unique! - expect(generated.size).toStrictEqual(count); - }); - - test("Variant #4: getGenerator() with overrideMode parameter", () => { - // number generator - const generator = myTransform.getGenerator("number"); - - expect(generator.generate()).toStrictEqual("var_1"); - expect(generator.generate()).toStrictEqual("var_2"); - expect(generator.generate()).toStrictEqual("var_3"); - - // hexadecimal generator - const anotherGenerator = myTransform.getGenerator("hexadecimal"); - - expect(anotherGenerator.generate()).toContain("_0x"); - - // mangled generator - const yetAnotherGenerator = myTransform.getGenerator("mangled"); - - expect(yetAnotherGenerator.generate()).toStrictEqual("a"); - expect(yetAnotherGenerator.generate()).toStrictEqual("b"); - expect(yetAnotherGenerator.generate()).toStrictEqual("c"); - }); -}); diff --git a/test/traverse.test.ts b/test/traverse.test.ts deleted file mode 100644 index 7a4dd2c..0000000 --- a/test/traverse.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import traverse, { assertNoCircular } from "../src/traverse"; -import { Node } from "../src/util/gen"; - -describe("traverse", function () { - test("Variant #1: Traverse tree", function () { - var executionOrder = []; - - var tree: Node = { - type: "Program", - start: 0, - end: 27, - body: [ - { - type: "ExpressionStatement", - start: 0, - end: 27, - expression: { - type: "CallExpression", - start: 0, - end: 26, - callee: { - type: "MemberExpression", - start: 0, - end: 11, - object: { - type: "Identifier", - start: 0, - end: 7, - name: "console", - }, - property: { - type: "Identifier", - start: 8, - end: 11, - name: "log", - }, - computed: false, - optional: false, - }, - arguments: [ - { - type: "Literal", - start: 12, - end: 25, - value: "Hello World", - raw: '"Hello World"', - }, - ], - optional: false, - }, - }, - ], - sourceType: "module", - }; - - var literalParents; - - traverse(tree, (object, parents) => { - if (object.type) { - if (object.type === "Literal") { - literalParents = parents; - } - - executionOrder.push("ENTER:" + object.type); - - return () => { - executionOrder.push("EXIT:" + object.type); - }; - } - }); - - var displayString = executionOrder.join(","); - - expect(displayString).toStrictEqual( - "ENTER:Program,ENTER:ExpressionStatement,ENTER:CallExpression,ENTER:MemberExpression,ENTER:Identifier,EXIT:Identifier,ENTER:Identifier,EXIT:Identifier,EXIT:MemberExpression,ENTER:Literal,EXIT:Literal,EXIT:CallExpression,EXIT:ExpressionStatement,EXIT:Program" - ); - - var displayLiteralParents = literalParents - .map((x) => (Array.isArray(x) ? "(array)" : x.type)) - .join(","); - expect(displayLiteralParents).toStrictEqual( - "(array),CallExpression,ExpressionStatement,(array),Program" - ); - }); -}); - -describe("assertNoCircular", function () { - test("Variant #1: Valid tree", function () { - var tree = { - a: 1, - b: 2, - c: 3, - d: { - a: 1, - b: 2, - c: 3, - }, - e: [ - { - a: 1, - b: 2, - c: 3, - f: { - a: 1, - }, - }, - ], - }; - - expect(() => assertNoCircular(tree)).not.toThrow(); - }); - - test("Variant #2: Invalid tree", function () { - var circularRef = {}; - - var tree = { - a: 1, - b: 2, - c: circularRef, - d: { - a: 1, - b: 2, - c: 3, - }, - e: [ - { - a: 1, - b: 2, - c: 3, - f: { - a: circularRef, - }, - }, - ], - }; - - expect(() => assertNoCircular(tree)).toThrow(); - }); -}); diff --git a/test/util/compare.test.ts b/test/util/compare.test.ts deleted file mode 100644 index 3488348..0000000 --- a/test/util/compare.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isEquivalent, isValidIdentifier } from "../../src/util/compare"; -import { Identifier } from "../../src/util/gen"; - -it("should compare nodes correctly", () => { - expect(isEquivalent(Identifier("name"), Identifier("name"))).toStrictEqual( - true - ); - - expect( - isEquivalent(Identifier("name"), Identifier("different_name")) - ).toStrictEqual(false); -}); - -describe("isValidIdentifier", () => { - test("Variant #1: Basic examples", () => { - // true examples - expect(isValidIdentifier("myClass")).toStrictEqual(true); - expect(isValidIdentifier("MyClass")).toStrictEqual(true); - expect(isValidIdentifier("$myObject")).toStrictEqual(true); - expect(isValidIdentifier("_myObject")).toStrictEqual(true); - expect(isValidIdentifier("myObject2")).toStrictEqual(true); - expect(isValidIdentifier("_0")).toStrictEqual(true); - - // false examples - expect(isValidIdentifier("0")).toStrictEqual(false); - expect(isValidIdentifier("0myInvalidVar")).toStrictEqual(false); - expect(isValidIdentifier("^")).toStrictEqual(false); - expect(isValidIdentifier("%")).toStrictEqual(false); - expect(isValidIdentifier("invalid*Var")).toStrictEqual(false); - expect(isValidIdentifier("invalid!")).toStrictEqual(false); - expect(isValidIdentifier("my invalid var")).toStrictEqual(false); - expect(isValidIdentifier("my-invalid-var")).toStrictEqual(false); - }); -}); diff --git a/test/util/gen.test.ts b/test/util/gen.test.ts deleted file mode 100644 index f4da62b..0000000 --- a/test/util/gen.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - AssignmentExpression, - AssignmentPattern, - AwaitExpression, - BinaryExpression, - BlockStatement, - BreakStatement, - ClassDeclaration, - DebuggerStatement, - FunctionDeclaration, - FunctionExpression, - Identifier, - IfStatement, - Literal, - MemberExpression, - MethodDefinition, - ObjectExpression, - Property, - RegexLiteral, - RestElement, - SequenceExpression, - SwitchDefaultCase, - ThrowStatement, - WithStatement, -} from "../../src/util/gen"; - -it("should return correct types", async () => { - expect(BreakStatement("label")).toHaveProperty("type", "BreakStatement"); - expect(AwaitExpression(Identifier("test"))).toHaveProperty( - "type", - "AwaitExpression" - ); - expect(AwaitExpression(Identifier("test"))).toHaveProperty( - "type", - "AwaitExpression" - ); - expect(RegexLiteral("match", "")).toHaveProperty("type", "Literal"); - expect(SwitchDefaultCase([])).toHaveProperty("type", "SwitchCase"); - expect( - MethodDefinition( - Identifier("name"), - FunctionExpression([], []), - "method", - false, - false - ) - ).toHaveProperty("type", "MethodDefinition"); - expect(ThrowStatement(Identifier("error"))).toHaveProperty( - "type", - "ThrowStatement" - ); - expect(ThrowStatement(Identifier("error"))).toHaveProperty( - "type", - "ThrowStatement" - ); - - expect(ClassDeclaration(Identifier("className"), null, [])).toHaveProperty( - "type", - "ClassDeclaration" - ); - - expect(WithStatement(Identifier("variable"), [])).toHaveProperty( - "type", - "WithStatement" - ); - - expect(DebuggerStatement()).toHaveProperty("type", "DebuggerStatement"); - expect(RestElement(Identifier("array"))).toHaveProperty( - "type", - "RestElement" - ); -}); - -it("should throw when parameters are missing", async () => { - expect(Identifier).toThrow(); - expect(() => Identifier("this")).toThrow(); - expect(() => Identifier("super")).toThrow(); - expect(() => Literal(undefined)).toThrow(); - - expect(BlockStatement).toThrow(); - expect(() => - BinaryExpression("||", Identifier("left"), Identifier("right")) - ).toThrow(); - - expect(() => - BinaryExpression("||", Identifier("left"), Identifier("right")) - ).toThrow(); - - expect(Property).toThrow(); - expect(() => Property(Identifier("key"), null)).toThrow(); - - expect(ObjectExpression).toThrow(); - expect(IfStatement).toThrow(); - expect(() => IfStatement(Identifier("test"), null)).toThrow(); - expect(() => - IfStatement(Identifier("test"), Identifier("notArray") as any) - ).toThrow(); - expect(() => - IfStatement(Identifier("test"), [], Identifier("notArray") as any) - ).toThrow(); - - expect(() => FunctionDeclaration("test", [], null)).toThrow(); - expect(() => FunctionDeclaration("test", [], [[]] as any)).toThrow(); - expect(() => SequenceExpression(null)).toThrow(); - expect(() => SequenceExpression([])).toThrow(); - - expect(() => MemberExpression(null, Identifier("target"), false)).toThrow(); - expect(() => MemberExpression(Identifier("object"), null, false)).toThrow(); - expect(() => - MemberExpression(Identifier("object"), Literal("stringKey"), false) - ).toThrow(); - - expect(() => AssignmentPattern(Identifier("object"), null)).toThrow(); - expect(() => AssignmentPattern(null, Identifier("defaultValue"))).toThrow(); - expect(() => WithStatement(null, [])).toThrow(); - expect(() => WithStatement([] as any, [])).toThrow(); - - expect(() => - MemberExpression(Identifier("new"), Identifier("target"), false) - ).toThrow(); -}); diff --git a/test/util/identifiers.test.ts b/test/util/identifiers.test.ts deleted file mode 100644 index f45b330..0000000 --- a/test/util/identifiers.test.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { ok } from "assert"; -import parseJS, { parseSync } from "../../src/parser"; -import traverse from "../../src/traverse"; -import { Location, Node } from "../../src/util/gen"; -import { - getFunctionParameters, - getIdentifierInfo, - validateChain, -} from "../../src/util/identifiers"; - -describe("getIdentifierInfo", () => { - test("Variant #1: Determine function declarations", async () => { - var tree = await parseJS(`function abc(){}`); - - var object = tree.body[0].id; - - expect(object.type).toStrictEqual("Identifier"); - - var parents = [tree.body[0], tree.body, tree]; - - var info = getIdentifierInfo(object, parents as any); - - expect(info.isFunctionDeclaration).toStrictEqual(true); - expect(info.spec.isDefined).toStrictEqual(true); - }); - - test("Variant #2: Determine labels", async () => { - var tree = await parseJS(`label: for (var i = 0; i < 0; i++ ) {}`); - - var object = tree.body[0].label; - - expect(object.type).toStrictEqual("Identifier"); - - var parents = [tree.body[0], tree.body, tree]; - - var info = getIdentifierInfo(object, parents as any); - - expect(info.isLabel).toStrictEqual(true); - expect(info.spec.isReferenced).toStrictEqual(false); - }); - - test("Variant #3: Error when a non-identifier node is given", async () => { - expect(() => { - getIdentifierInfo({ type: "Literal", value: true }, []); - }).toThrow(); - }); - - function findIdentifier(tree: Node, identifierName: string) { - var searchLocation: Location; - - traverse(tree, (o, p) => { - if (o.type === "Identifier" && o.name === identifierName) { - ok(!searchLocation); - searchLocation = [o, p]; - } - }); - - ok(searchLocation); - return searchLocation; - } - - test("Variant #4: Variable declaration assignment pattern", async () => { - var tree = parseSync(` - var [ definedIdentifier = nonDefinedIdentifier ] = []; - `); - - var definedIdentifier = findIdentifier(tree, "definedIdentifier"); - var definedInfo = getIdentifierInfo( - definedIdentifier[0], - definedIdentifier[1] - ); - expect(definedInfo.spec.isDefined).toStrictEqual(true); - expect(definedInfo.spec.isReferenced).toStrictEqual(true); - - var nonDefinedIdentifier = findIdentifier(tree, "nonDefinedIdentifier"); - var nonDefinedInfo = getIdentifierInfo( - nonDefinedIdentifier[0], - nonDefinedIdentifier[1] - ); - expect(nonDefinedInfo.spec.isDefined).toStrictEqual(false); - expect(nonDefinedInfo.spec.isReferenced).toStrictEqual(true); - }); - - test("Variant #5: Function parameter assignment pattern", async () => { - var tree = parseSync(` - function myFunction(definedIdentifier = nonDefinedIdentifier) { - - } - `); - - var myFunction = findIdentifier(tree, "myFunction"); - var myFunctionInfo = getIdentifierInfo(myFunction[0], myFunction[1]); - - expect(myFunctionInfo.isFunctionDeclaration).toStrictEqual(true); - expect(myFunctionInfo.spec.isDefined).toStrictEqual(true); - - var definedIdentifier = findIdentifier(tree, "definedIdentifier"); - var definedInfo = getIdentifierInfo( - definedIdentifier[0], - definedIdentifier[1] - ); - expect(definedInfo.spec.isDefined).toStrictEqual(true); - expect(definedInfo.spec.isReferenced).toStrictEqual(true); - - var nonDefinedIdentifier = findIdentifier(tree, "nonDefinedIdentifier"); - var nonDefinedInfo = getIdentifierInfo( - nonDefinedIdentifier[0], - nonDefinedIdentifier[1] - ); - expect(nonDefinedInfo.spec.isDefined).toStrictEqual(false); - expect(nonDefinedInfo.spec.isReferenced).toStrictEqual(true); - }); - - test("Variant #6: Object pattern", async () => { - var tree = parseSync(` - var { nonDefinedIdentifier: definedIdentifier } = {}; - - ( { nonModifiedIdentifier: modifiedIdentifier } = {} ); - `); - - var definedIdentifier = findIdentifier(tree, "definedIdentifier"); - var definedInfo = getIdentifierInfo( - definedIdentifier[0], - definedIdentifier[1] - ); - expect(definedInfo.spec.isDefined).toStrictEqual(true); - expect(definedInfo.spec.isReferenced).toStrictEqual(true); - - var nonDefinedIdentifier = findIdentifier(tree, "nonDefinedIdentifier"); - var nonDefinedInfo = getIdentifierInfo( - nonDefinedIdentifier[0], - nonDefinedIdentifier[1] - ); - expect(nonDefinedInfo.spec.isDefined).toStrictEqual(false); - expect(nonDefinedInfo.spec.isReferenced).toStrictEqual(false); - - var modifiedIdentifier = findIdentifier(tree, "modifiedIdentifier"); - var modifiedInfo = getIdentifierInfo( - modifiedIdentifier[0], - modifiedIdentifier[1] - ); - expect(modifiedInfo.spec.isDefined).toStrictEqual(false); - expect(modifiedInfo.spec.isModified).toStrictEqual(true); - expect(modifiedInfo.spec.isReferenced).toStrictEqual(true); - - var nonModifiedIdentifier = findIdentifier(tree, "nonModifiedIdentifier"); - var nonModifiedInfo = getIdentifierInfo( - nonModifiedIdentifier[0], - nonModifiedIdentifier[1] - ); - - expect(nonModifiedInfo.spec.isDefined).toStrictEqual(false); - expect(nonModifiedInfo.spec.isModified).toStrictEqual(false); - expect(nonModifiedInfo.spec.isReferenced).toStrictEqual(false); - }); - - test("Variant #7: Default parameter, function expression", async () => { - var tree = parseSync(` - function myFunction( myParameter = function() { - var myNestedDeclaration = true; - } ){ - - } - `); - - var myNestedDeclaration = findIdentifier(tree, "myNestedDeclaration"); - var myNestedDeclarationInfo = getIdentifierInfo( - myNestedDeclaration[0], - myNestedDeclaration[1] - ); - - expect(myNestedDeclarationInfo.isVariableDeclaration).toStrictEqual(true); - expect(myNestedDeclarationInfo.spec.isDefined).toStrictEqual(true); - expect(myNestedDeclarationInfo.spec.isReferenced).toStrictEqual(true); - expect(myNestedDeclarationInfo.spec.isModified).toStrictEqual(false); - }); -}); - -describe("validateChain", () => { - test("Variant #1: Error when parents is not an array", () => { - expect(() => { - validateChain({ type: "Identifier", name: "name" }, {} as any); - }).toThrow(); - }); - - test("Variant #2: Error when object is undefined", () => { - expect(() => { - validateChain(undefined, []); - }).toThrow(); - }); - - test("Variant #3: Error when object is not connected to direct parent", () => { - expect(() => { - validateChain({ type: "Identifier", name: "name" }, [ - { type: "Program", body: [] }, - ]); - }).toThrow(); - }); -}); - -describe("getFunctionParameters", () => { - test("Variant #1: Work with default values and destructuring", async () => { - var code = `function a(A=_b,{B,[_c]:C},[D]){}`; - var tree = await parseJS(code); - - var object = tree.body[0]; - var parents: any = [tree.body, tree]; - - var locations = getFunctionParameters(object, parents); - var names = locations.map((x) => x[0].name); - - expect(names).toStrictEqual(["A", "B", "C", "D"]); - }); - - test("Variant #2: Work with spread element", async () => { - var code = `function a(...A){}`; - var tree = await parseJS(code); - - var object = tree.body[0]; - var parents: any = [tree.body, tree]; - - var locations = getFunctionParameters(object, parents); - var names = locations.map((x) => x[0].name); - - expect(names).toStrictEqual(["A"]); - }); - - test("Variant #3: Normal parameters", async () => { - var code = `function a(A,B,C,D){}`; - var tree = await parseJS(code); - - var object = tree.body[0]; - var parents: any = [tree.body, tree]; - - var locations = getFunctionParameters(object, parents); - var names = locations.map((x) => x[0].name); - - expect(names).toStrictEqual(["A", "B", "C", "D"]); - }); - - test("Variant #4: Default values as functions", async () => { - var code = `function a(A = function(_a){ return _a; },B = function(_a, _b = function(){return this;}){return _a + _b();},C,D){}`; - var tree = await parseJS(code); - - var object = tree.body[0]; - var parents: any = [tree.body, tree]; - - var locations = getFunctionParameters(object, parents); - var names = locations.map((x) => x[0].name); - - expect(names).toStrictEqual(["A", "B", "C", "D"]); - }); -}); diff --git a/test/util/math.test.ts b/test/util/math.test.ts deleted file mode 100644 index 2b5fe88..0000000 --- a/test/util/math.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { getFactors } from "../../src/util/math"; - -it("getFactors() should return correct factors", () => { - expect(getFactors(6)).toEqual([1, 6, 2, 3]); -}); diff --git a/test/util/random.test.ts b/test/util/random.test.ts index 5594ec6..b709241 100644 --- a/test/util/random.test.ts +++ b/test/util/random.test.ts @@ -1,10 +1,8 @@ import { alphabeticalGenerator, choice, - getRandomFalseExpression, getRandomString, - getRandomTrueExpression, -} from "../../src/util/random"; +} from "../../src/utils/random-utils"; const escodegen = require("escodegen"); diff --git a/tsconfig.json b/tsconfig.json index fff6255..2af96d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,14 @@ { - "compilerOptions": { - "target": "ESNext", - "module": "ES6", - "noUnusedParameters": false, - "strict": false, - "noImplicitAny": false, - } -} \ No newline at end of file + "compilerOptions": { + "module": "esnext", + "target": "es6", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "outDir": "./dist", + "skipLibCheck": true, + "noImplicitAny": false, + "strictNullChecks": false + }, + "include": ["src/**/*"] +} From 3508099b0f50f0d7cc56352a00678ea5636eaa67 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 24 Aug 2024 18:33:37 -0400 Subject: [PATCH 002/103] AST Scrambler, Order, Test refactoring --- src/constants.ts | 1 + src/index.ts | 6 +- src/obfuscator.ts | 25 ++- src/options.ts | 2 + src/order.ts | 56 +++++++ src/presets.ts | 3 + src/transforms/astScrambler.ts | 84 +++++++++++ src/transforms/calculator.ts | 76 ++++++++++ src/transforms/deadCode.ts | 3 +- src/transforms/dispatcher.ts | 3 +- .../extraction/duplicateLiteralsRemoval.ts | 3 +- src/transforms/extraction/objectExtraction.ts | 3 +- src/transforms/finalizer.ts | 3 +- src/transforms/flatten.ts | 3 +- src/transforms/identifier/globalConcealing.ts | 3 +- src/transforms/identifier/renameVariables.ts | 3 +- src/transforms/plugin.ts | 7 +- src/transforms/preparation.ts | 3 +- src/transforms/shuffle.ts | 3 +- src/transforms/string/stringCompression.ts | 3 +- src/transforms/string/stringConcealing.ts | 3 +- src/transforms/string/stringEncoding.ts | 3 +- src/transforms/string/stringSplitting.ts | 3 +- src/transforms/variableMasking.ts | 3 +- src/validateOptions.ts | 1 + ...tiTooling.test.ts => astScrambler.test.ts} | 8 +- test/transforms/flatten.test.ts | 24 +-- test/transforms/hexadecimalNumbers.test.ts | 17 ++- .../identifier/movedDeclarations.test.ts | 14 +- test/transforms/lock/antiDebug.test.ts | 6 +- test/transforms/lock/browserLock.test.ts | 8 +- test/transforms/lock/lock.test.ts | 10 +- test/transforms/lock/osLock.test.ts | 20 +-- test/transforms/lock/selfDefending.test.ts | 6 +- test/transforms/preparation.test.ts | 14 +- test/transforms/rgf.test.ts | 20 +-- test/util/insert.test.ts | 142 ------------------ .../{random.test.ts => random-utils.test.ts} | 0 38 files changed, 361 insertions(+), 234 deletions(-) create mode 100644 src/constants.ts create mode 100644 src/order.ts create mode 100644 src/transforms/astScrambler.ts create mode 100644 src/transforms/calculator.ts rename test/transforms/{antiTooling.test.ts => astScrambler.test.ts} (77%) delete mode 100644 test/util/insert.test.ts rename test/util/{random.test.ts => random-utils.test.ts} (100%) diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..b27bcc2 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const predictableFunctionTag = "__JS_PREDICT__"; diff --git a/src/index.ts b/src/index.ts index cbebebc..0d4eddd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,11 @@ export async function obfuscateWithProfiler( }; } -const JSConfuser = Object.assign(obfuscate, { +var oldJSConfuser = async (sourceCode: string, options: ObfuscateOptions) => { + return (await obfuscate(sourceCode, options)).code; +}; + +const JSConfuser = Object.assign(oldJSConfuser, { obfuscate, obfuscateAST, obfuscateWithProfiler, diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 5f98423..753335e 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -23,6 +23,9 @@ import { ProfilerLog, } from "./obfuscationResult"; import { isProbabilityMapProbable } from "./probability"; +import astScrambler from "./transforms/astScrambler"; +import calculator from "./transforms/calculator"; +import { Order } from "./order"; export default class Obfuscator { plugins: babel.PluginObj[] = []; @@ -55,21 +58,37 @@ export default class Obfuscator { push(this.options.stringCompression, stringCompression); push(this.options.stringSplitting, stringSplitting); push(this.options.shuffle, shuffle); + push(this.options.astScrambler, astScrambler); + push(this.options.calculator, calculator); push(true, finalizer); allPlugins.map((pluginFunction) => { var pluginInstance: PluginInstance; var plugin = pluginFunction({ - Plugin: (name) => { - return (pluginInstance = new PluginInstance({ name }, this)); + Plugin: (nameOrOrder) => { + var pluginOptions; + if (typeof nameOrOrder === "string") { + pluginOptions = { name: nameOrOrder }; + } else if (typeof nameOrOrder === "number") { + pluginOptions = { name: Order[nameOrOrder], order: nameOrOrder }; + } else if (typeof nameOrOrder === "object" && nameOrOrder) { + pluginOptions = nameOrOrder; + } else { + ok(false); + } + + return (pluginInstance = new PluginInstance(pluginOptions, this)); }, }); ok(pluginInstance, "Plugin instance not created."); + plugin.order = pluginInstance.order; this.plugins.push(plugin); }); + + this.plugins = this.plugins.sort((a, b) => a.order - b.order); } obfuscateAST( @@ -114,7 +133,7 @@ export default class Obfuscator { generateCode(ast: babel.types.File): string { const { code } = generate(ast, { comments: false, // Remove comments - compact: false, // Compact the output + compact: this.options.compact, // Compact the output }); return code; diff --git a/src/options.ts b/src/options.ts index f81bede..e751b2e 100644 --- a/src/options.ts +++ b/src/options.ts @@ -608,4 +608,6 @@ export interface ObfuscateOptions { * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ preserveFunctionLength?: boolean; + + astScrambler?: boolean; } diff --git a/src/order.ts b/src/order.ts new file mode 100644 index 0000000..224158e --- /dev/null +++ b/src/order.ts @@ -0,0 +1,56 @@ +/** + * Describes the order of transformations. + */ +export enum Order { + Preparation = 0, + + ObjectExtraction = 1, + + Flatten = 2, + + RGF = 3, + + Lock = 4, // Includes Integrity & Anti Debug + + Dispatcher = 6, + + DeadCode = 8, + + Calculator = 9, + + ControlFlowFlattening = 10, + + Eval = 11, + + GlobalConcealing = 12, + + OpaquePredicates = 13, + + StringSplitting = 16, + + StringConcealing = 17, + + StringCompression = 18, + + VariableMasking = 20, + + DuplicateLiteralsRemoval = 22, + + Shuffle = 24, + + NameRecycling = 25, + + MovedDeclarations = 26, + + RenameLabels = 27, + + Minify = 28, + + AstScrambler = 29, + + RenameVariables = 30, + + ES5 = 31, + + Finalizer = 35, +} diff --git a/src/presets.ts b/src/presets.ts index d512c13..49c1f1f 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -49,6 +49,7 @@ const highPreset: ObfuscateOptions = { stringCompression: true, stringEncoding: true, stringSplitting: 0.75, + astScrambler: true, // Use at own risk rgf: false, @@ -81,6 +82,7 @@ const mediumPreset: ObfuscateOptions = { variableMasking: 0.5, stringConcealing: true, stringSplitting: 0.25, + astScrambler: true, }; /** @@ -106,6 +108,7 @@ const lowPreset: ObfuscateOptions = { renameVariables: true, renameGlobals: true, stringConcealing: true, + astScrambler: true, }; /** diff --git a/src/transforms/astScrambler.ts b/src/transforms/astScrambler.ts new file mode 100644 index 0000000..185be05 --- /dev/null +++ b/src/transforms/astScrambler.ts @@ -0,0 +1,84 @@ +import { PluginObj, NodePath } from "@babel/core"; +import { PluginArg } from "./plugin"; +import * as t from "@babel/types"; +import { ok } from "assert"; +import { Order } from "../order"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.AstScrambler); + var callExprName: string; + + return { + visitor: { + "Block|SwitchCase": { + exit(_path) { + const path = _path as NodePath; + + let containerKey: string; + if (path.isSwitchCase()) { + containerKey = "consequent"; + } else if (path.isBlock()) { + containerKey = "body"; + } + var container: t.Statement[] = path.node[containerKey]; + var newContainer: t.Statement[] = []; + + ok(Array.isArray(container)); + + var expressions: t.Expression[] = []; + + const flushExpressions = () => { + if (!expressions.length) return; + + // Not enough expressions to require a call expression + if (expressions.length === 1) { + newContainer.push(t.expressionStatement(expressions[0])); + expressions = []; + return; + } + + if (!callExprName) { + callExprName = me.getPlaceholder() + "_ast"; + } + + newContainer.push( + t.expressionStatement( + t.callExpression(t.identifier(callExprName), expressions) + ) + ); + expressions = []; + }; + + for (var statement of container) { + if (t.isExpressionStatement(statement)) { + if (t.isSequenceExpression(statement.expression)) { + expressions.push(...statement.expression.expressions); + } else { + expressions.push(statement.expression); + } + } else { + flushExpressions(); + newContainer.push(statement); + } + } + + flushExpressions(); + + path.node[containerKey] = newContainer; + + if (path.isProgram()) { + if (callExprName) { + var functionDeclaration = t.functionDeclaration( + t.identifier(callExprName), + [], + t.blockStatement([]) + ); + var p = path.unshiftContainer("body", functionDeclaration); + path.scope.registerDeclaration(p[0]); + } + } + }, + }, + }, + }; +}; diff --git a/src/transforms/calculator.ts b/src/transforms/calculator.ts new file mode 100644 index 0000000..a2fdcb8 --- /dev/null +++ b/src/transforms/calculator.ts @@ -0,0 +1,76 @@ +import { PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import * as t from "@babel/types"; +import { Order } from "../order"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.Calculator); + + return { + visitor: { + Program: { + exit(path) { + var allowedOperators = new Set(["+", "-", "*", "/"]); + var operatorsMap = new Map(); + var calculatorFnName; + + path.traverse({ + BinaryExpression: { + exit(path) { + const { operator } = path.node; + + if (t.isPrivate(path.node.left)) return; + + if (!allowedOperators.has(operator)) return; + + if (!calculatorFnName) { + calculatorFnName = me.getPlaceholder() + "_calc"; + } + + const operatorKey = operatorsMap.get(operator); + if (typeof operatorKey === "undefined") { + operatorsMap.set(operator, me.getPlaceholder()); + } + + path.replaceWith( + t.callExpression(t.identifier(calculatorFnName), [ + t.identifier(operatorsMap.get(operator) as string), + path.node.left, + path.node.right, + ]) + ); + }, + }, + }); + + if (calculatorFnName) { + // Create the calculator function and insert into program path + + var p = path.unshiftContainer( + "body", + t.functionDeclaration( + t.identifier(calculatorFnName), + [ + t.identifier("operator"), + t.identifier("a"), + t.identifier("b"), + ], + t.blockStatement([ + t.returnStatement( + t.binaryExpression( + "+", + t.identifier("a"), + t.identifier("b") + ) + ), + ]) + ) + ); + + path.scope.registerDeclaration(p[0]); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index db6ed56..9374bad 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -4,9 +4,10 @@ import { chance, choice } from "../utils/random-utils"; import { blockStatement, booleanLiteral, ifStatement } from "@babel/types"; import { deadCodeTemplates } from "../templates/deadCodeTemplates"; import { computeProbabilityMap } from "../probability"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("deadCode"); + const me = Plugin(Order.DeadCode); let created = 0; return { diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index 3388b5c..ab4e9df 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -6,9 +6,10 @@ import Template from "../templates/template"; import { ok } from "assert"; import { chance, getRandomString } from "../utils/random-utils"; import { computeProbabilityMap } from "../probability"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("dispatcher"); + const me = Plugin(Order.Dispatcher); return { visitor: { diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 973e552..0eec17e 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -2,6 +2,7 @@ import * as babel from "@babel/core"; import * as babelTypes from "@babel/types"; import { ok } from "assert"; import { PluginArg } from "../plugin"; +import { Order } from "../../order"; type LiteralValue = string | number | boolean; const createLiteral = (value: LiteralValue) => { @@ -20,7 +21,7 @@ const createLiteral = (value: LiteralValue) => { }; export default ({ Plugin }: PluginArg): babel.PluginObj => { - const me = Plugin("duplicateLiteralsRemoval"); + const me = Plugin(Order.DuplicateLiteralsRemoval); return { visitor: { diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index f1f7cbc..291c8bf 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -6,6 +6,7 @@ import { isComputedMemberExpression, } from "../../utils/ast-utils"; import { PluginArg } from "../plugin"; +import { Order } from "../../order"; function isObjectSafeForExtraction( path: NodePath @@ -73,7 +74,7 @@ function isObjectSafeForExtraction( } export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("objectExtraction"); + const me = Plugin(Order.ObjectExtraction); return { visitor: { diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index 6498012..39850f5 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -1,9 +1,10 @@ import { PluginObj } from "@babel/core"; import { PluginArg } from "./plugin"; import * as t from "@babel/types"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("finalizer"); + const me = Plugin(Order.Flatten); return { visitor: { diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 97afe86..35ff01f 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -6,9 +6,10 @@ import { } from "../utils/ast-utils"; import { PluginArg } from "./plugin"; import { computeProbabilityMap } from "../probability"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("flatten"); + const me = Plugin(Order.Flatten); function flattenFunction( path: NodePath diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index b3c5482..d051a66 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -3,9 +3,10 @@ import { NodePath, PluginObj } from "@babel/core"; import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; import { PluginArg } from "../plugin"; +import { Order } from "../../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("globalConcealing"); + const me = Plugin(Order.GlobalConcealing); var globalMapping = new Map(), globalFnName = me.getPlaceholder() + "_getGlobal", diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 6caff70..c6a6f5d 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -2,9 +2,10 @@ import { NodePath, PluginObj } from "@babel/core"; import { Binding, Scope } from "@babel/traverse"; import { PluginArg } from "../plugin"; import * as t from "@babel/types"; +import { Order } from "../../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("renameVariables"); + const me = Plugin(Order.RenameVariables); // Keep track of available names to reuse const availableNames: string[] = []; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 0c63a8a..07a6eb4 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -2,11 +2,12 @@ import { PluginObj } from "@babel/core"; import * as babelTypes from "@babel/types"; import Obfuscator from "../obfuscator"; import { getRandomString } from "../utils/random-utils"; +import { Order } from "../order"; export type PluginFunction = (pluginArg: PluginArg) => PluginObj["visitor"]; export type PluginArg = { - Plugin: (name: string) => PluginInstance; + Plugin: (order: Order) => PluginInstance; }; export class PluginInstance { @@ -19,6 +20,10 @@ export class PluginInstance { return this.pluginOptions.name || "unnamed"; } + get order() { + return this.pluginOptions.order; + } + get options() { return this.obfuscator.options; } diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index d9e9c35..71b5d97 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -2,9 +2,10 @@ import { PluginObj } from "@babel/core"; import { NodePath } from "@babel/traverse"; import { PluginArg } from "./plugin"; import * as t from "@babel/types"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("preparation"); + const me = Plugin(Order.Preparation); return { visitor: { diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index d71c8ea..ddf1198 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -4,9 +4,10 @@ import * as t from "@babel/types"; import { computeProbabilityMap } from "../probability"; import { getRandomInteger } from "../utils/random-utils"; import Template from "../templates/template"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("shuffle"); + const me = Plugin(Order.Shuffle); return { visitor: { diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 9607037..7b6dca0 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -1,9 +1,10 @@ import { PluginObj } from "@babel/core"; import { PluginArg } from "../plugin"; import * as t from "@babel/types"; +import { Order } from "../../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("stringCompression"); + const me = Plugin(Order.StringCompression); return { visitor: { diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index bfd8384..6e41b4f 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -2,9 +2,10 @@ import * as t from "@babel/types"; import { NodePath, PluginObj } from "@babel/core"; import Template from "../../templates/template"; import { PluginArg } from "../plugin"; +import { Order } from "../../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("stringConcealing"); + const me = Plugin(Order.StringConcealing); var decoderName = "decoder"; return { diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index dc19cbd..93096de 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -3,6 +3,7 @@ import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { choice } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; +import { Order } from "../../order"; function pad(x: string, len: number): string { while (x.length < len) { @@ -47,7 +48,7 @@ function toUnicodeRepresentation(str: string) { } export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("stringEncoding"); + const me = Plugin(Order.Finalizer); return { visitor: { diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index d22854c..da961ae 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -4,9 +4,10 @@ import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; import { binaryExpression, stringLiteral } from "@babel/types"; import { ok } from "assert"; +import { Order } from "../../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("stringSplitting"); + const me = Plugin(Order.StringSplitting); return { visitor: { diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 8de5b30..250f8e4 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -4,9 +4,10 @@ import { PluginArg } from "./plugin"; import * as babelTypes from "@babel/types"; import Template from "../templates/template"; import { computeProbabilityMap } from "../probability"; +import { Order } from "../order"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin("variableMasking"); + const me = Plugin(Order.VariableMasking); const transformFunction = (path: NodePath) => { // Do not apply to getter/setter methods diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 9fdff20..1484c36 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -35,6 +35,7 @@ const validProperties = new Set([ "globalVariables", "debugComments", "preserveFunctionLength", + "astScrambler", ]); const validLockProperties = new Set([ diff --git a/test/transforms/antiTooling.test.ts b/test/transforms/astScrambler.test.ts similarity index 77% rename from test/transforms/antiTooling.test.ts rename to test/transforms/astScrambler.test.ts index dde7ead..e5804b3 100644 --- a/test/transforms/antiTooling.test.ts +++ b/test/transforms/astScrambler.test.ts @@ -4,7 +4,7 @@ import JsConfuser from "../../src/index"; test("Variant #1: Don't break Symbols", async () => { if (typeof Symbol !== "undefined") { for (var i = 0; i < 6; i++) { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var sym1 = Symbol(); @@ -18,7 +18,7 @@ test("Variant #1: Don't break Symbols", async () => { TEST_OUTPUT = sym1; `, - { target: "node", renameVariables: true } + { target: "node", astScrambler: true, renameVariables: true } ); var TEST_OUTPUT; @@ -30,7 +30,7 @@ test("Variant #1: Don't break Symbols", async () => { }); test("Variant #2: Join expressions into sequence expressions", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OUTPUT = 0; TEST_OUTPUT++; @@ -40,7 +40,7 @@ test("Variant #2: Join expressions into sequence expressions", async () => { TEST_OUTPUT *= 2; } `, - { target: "node", renameVariables: true } + { target: "node", astScrambler: true, renameVariables: true } ); expect(output).toContain("(TEST_OUTPUT=0,TEST_OUTPUT++"); diff --git a/test/transforms/flatten.test.ts b/test/transforms/flatten.test.ts index 110e809..b2a53d2 100644 --- a/test/transforms/flatten.test.ts +++ b/test/transforms/flatten.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../src/index"; test("Variant #1: Function Declaration", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ return 10; @@ -24,7 +24,7 @@ test("Variant #1: Function Declaration", async () => { }); test("Variant #2: Function Expression", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var outsideVar = "Correct Value"; @@ -49,7 +49,7 @@ test("Variant #2: Function Expression", async () => { }); test("Variant #3: Simple parameters", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(x, y){ TEST_OUTPUT = x + y; @@ -72,7 +72,7 @@ test("Variant #3: Simple parameters", async () => { }); test("Variant #4: Simple parameters nested", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(x){ function TEST_NESTED_FUNCTION(y){ @@ -98,7 +98,7 @@ test("Variant #4: Simple parameters nested", async () => { }); test("Variant #5: Correct return values when nested", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ function TEST_NESTED_FUNCTION(){ @@ -124,7 +124,7 @@ test("Variant #5: Correct return values when nested", async () => { }); test("Variant #6: Correct values when deeply nested", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(x, y){ function TEST_NESTED_FUNCTION(){ @@ -155,7 +155,7 @@ test("Variant #6: Correct values when deeply nested", async () => { }); test("Variant #7: Correct values when modifying local variables", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(x, y){ var A = 0; @@ -189,7 +189,7 @@ test("Variant #7: Correct values when modifying local variables", async () => { }); test("Variant #8: Work with dispatcher", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function container(x){ function nested(x){ @@ -219,7 +219,7 @@ test("Variant #8: Work with dispatcher", async () => { // https://github.com/MichaelXF/js-confuser/issues/25 test("Variant #9: Work with pattern-based assignment expressions", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var i = 0; @@ -247,7 +247,7 @@ test("Variant #9: Work with pattern-based assignment expressions", async () => { }); test("Variant #10: Async function", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` async function timeout(ms){ return await new Promise((resolve, reject)=>{ @@ -291,7 +291,7 @@ test("Variant #10: Async function", async () => { }); test("Variant #11: Work with properties", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var outsideVar = "Incorrect Value"; @@ -332,7 +332,7 @@ test("Variant #11: Work with properties", async () => { }); test("Variant #12: Work with RGF enabled", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var outsideVar = "Correct Value"; diff --git a/test/transforms/hexadecimalNumbers.test.ts b/test/transforms/hexadecimalNumbers.test.ts index 5c8c257..08e2872 100644 --- a/test/transforms/hexadecimalNumbers.test.ts +++ b/test/transforms/hexadecimalNumbers.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../src/index"; test("Variant #1: Positive integer to hexadecimal", async () => { - var output = await JsConfuser.obfuscate(`TEST_VAR = 10;`, { + var { code: output } = await JsConfuser.obfuscate(`TEST_VAR = 10;`, { target: "node", hexadecimalNumbers: true, }); @@ -17,7 +17,7 @@ test("Variant #1: Positive integer to hexadecimal", async () => { }); test("Variant #2: Negative integer to hexadecimal", async () => { - var output = await JsConfuser.obfuscate(`TEST_VAR = -10;`, { + var { code: output } = await JsConfuser.obfuscate(`TEST_VAR = -10;`, { target: "node", hexadecimalNumbers: true, }); @@ -33,10 +33,13 @@ test("Variant #2: Negative integer to hexadecimal", async () => { }); test("Variant #3: Don't change floats", async () => { - var output = await JsConfuser.obfuscate(`var TEST_VAR = [15.5, -75.9];`, { - target: "node", - hexadecimalNumbers: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var TEST_VAR = [15.5, -75.9];`, + { + target: "node", + hexadecimalNumbers: true, + } + ); expect(output).toContain("15.5"); expect(output).toContain("-75.9"); @@ -44,7 +47,7 @@ test("Variant #3: Don't change floats", async () => { }); test("Variant #4: Work even on large numbers", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( `TEST_VAR = 10000000000000000000000000000;`, { target: "node", diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 8ab9bfd..86d798b 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -8,7 +8,7 @@ test("Variant #1: Move variable 'y' to top", async () => { TEST_VARIABLE = x + y; `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -30,7 +30,7 @@ test("Variant #2: Move variable 'y' and 'z' to top", async () => { TEST_VARIABLE = x + y + z; `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -52,7 +52,7 @@ test("Variant #3: Don't move 'y' (destructuring)", async () => { TEST_VARIABLE = x + y; `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -77,7 +77,7 @@ test("Variant #4: Move 'y' (nested lexical scope)", async () => { TEST_VARIABLE = x + y; `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -99,7 +99,7 @@ test("Variant #5: Move 'y' (for statement initializer)", async () => { TEST_VARIABLE = x + y; `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -121,7 +121,7 @@ test("Variant #6: Move 'y' (for-in left-hand initializer)", async () => { TEST_VARIABLE = x + parseInt(y); `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -145,7 +145,7 @@ test("Variant #7: Don't move const or let variables", async () => { TEST_VARIABLE = x + y; `; - var output = await JsConfuser.obfuscate(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); diff --git a/test/transforms/lock/antiDebug.test.ts b/test/transforms/lock/antiDebug.test.ts index a6c6975..4b2311c 100644 --- a/test/transforms/lock/antiDebug.test.ts +++ b/test/transforms/lock/antiDebug.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../../src/index"; it("add debugger statements", async () => { - var output = await JsConfuser.obfuscate("input(true)", { + var { code: output } = await JsConfuser.obfuscate("input(true)", { target: "node", lock: { antiDebug: true, @@ -12,7 +12,7 @@ it("add debugger statements", async () => { }); it("add a background interval", async () => { - var output = await JsConfuser.obfuscate("input(true)", { + var { code: output } = await JsConfuser.obfuscate("input(true)", { target: "node", lock: { antiDebug: true, @@ -24,7 +24,7 @@ it("add a background interval", async () => { it("should place syntax-correct code", async () => { for (var i = 0; i < 25; i++) { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` /** * GitHub: https://github.com/MichaelXF/js-confuser diff --git a/test/transforms/lock/browserLock.test.ts b/test/transforms/lock/browserLock.test.ts index 1dbb5e3..6f0d088 100644 --- a/test/transforms/lock/browserLock.test.ts +++ b/test/transforms/lock/browserLock.test.ts @@ -9,7 +9,7 @@ test("Variant #1: Chrome", async () => { }, }; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught" @@ -41,7 +41,7 @@ test( }, }; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught" @@ -73,7 +73,7 @@ test("Variant #2: Firefox", async () => { }, }; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught" @@ -105,7 +105,7 @@ test( }, }; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught" diff --git a/test/transforms/lock/lock.test.ts b/test/transforms/lock/lock.test.ts index 5a26150..8f5f462 100644 --- a/test/transforms/lock/lock.test.ts +++ b/test/transforms/lock/lock.test.ts @@ -84,7 +84,7 @@ it("should work with endDate and call countermeasures function", async () => { // }); it("countermeasures function should still work even with renameVariables enabled", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function countermeasures(){ input(true) } `, { target: "node", @@ -110,7 +110,7 @@ it("countermeasures function should still work even with renameVariables enabled }); it("should not call countermeasures when domainLock is correct", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function countermeasures(){ input(true) } `, { target: "browser", @@ -135,7 +135,7 @@ it("should not call countermeasures when domainLock is correct", async () => { }); it("should call countermeasures when domain is different", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function countermeasures(){ input(true) } `, { target: "browser", @@ -160,7 +160,7 @@ it("should call countermeasures when domain is different", async () => { }); it("should not call countermeasures when context is correct", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function countermeasures(){ input(true) } `, { target: "node", @@ -183,7 +183,7 @@ it("should not call countermeasures when context is correct", async () => { }); it("should call countermeasures when context is different", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function countermeasures(){ input(true) } `, { target: "node", diff --git a/test/transforms/lock/osLock.test.ts b/test/transforms/lock/osLock.test.ts index 0ae9333..2d72fc3 100644 --- a/test/transforms/lock/osLock.test.ts +++ b/test/transforms/lock/osLock.test.ts @@ -16,7 +16,7 @@ describe("OSLock on target 'node'", () => { }; require = newRequire; - var output = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { + var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { target: "node", lock: { osLock: ["linux"], @@ -45,7 +45,7 @@ describe("OSLock on target 'node'", () => { }; require = newRequire; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught" @@ -81,7 +81,7 @@ describe("OSLock on target 'node'", () => { }; require = newRequire; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught" @@ -117,7 +117,7 @@ describe("OSLock on target 'node'", () => { }; require = newRequire; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught"; @@ -153,7 +153,7 @@ describe("OSLock on target 'node'", () => { }; require = newRequire; - var output = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { + var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { target: "node", lock: { osLock: ["osx"], @@ -182,7 +182,7 @@ describe("OSLock on target 'node'", () => { }; require = newRequire; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught"; @@ -214,7 +214,7 @@ describe("OSLock on target 'browser'", () => { }, }; - var output = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { + var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { target: "browser", lock: { osLock: ["linux"], @@ -237,7 +237,7 @@ describe("OSLock on target 'browser'", () => { }, }; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught"; @@ -266,7 +266,7 @@ describe("OSLock on target 'browser'", () => { }, }; - var output = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { + var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { target: "browser", lock: { osLock: ["windows"], @@ -289,7 +289,7 @@ describe("OSLock on target 'browser'", () => { }, }; - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_VARIABLE = "caught"; diff --git a/test/transforms/lock/selfDefending.test.ts b/test/transforms/lock/selfDefending.test.ts index bda0286..3b20fc4 100644 --- a/test/transforms/lock/selfDefending.test.ts +++ b/test/transforms/lock/selfDefending.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../../src/index"; test("Variant #1: SelfDefending should forcibly enable `compact`", async () => { - var output = await JsConfuser.obfuscate(`console.log(1)`, { + var { code: output } = await JsConfuser.obfuscate(`console.log(1)`, { target: "node", compact: false, lock: { @@ -13,7 +13,7 @@ test("Variant #1: SelfDefending should forcibly enable `compact`", async () => { }); test("Variant #2: SelfDefending should not crash when unchanged", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_CAUGHT = true; @@ -38,7 +38,7 @@ test("Variant #2: SelfDefending should not crash when unchanged", async () => { }); test("Variant #2: SelfDefending should crash when changed", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_CAUGHT = true; diff --git a/test/transforms/preparation.test.ts b/test/transforms/preparation.test.ts index 8144cca..72028a1 100644 --- a/test/transforms/preparation.test.ts +++ b/test/transforms/preparation.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../src/index"; test("Variant #1: Force Block Statements on If statements", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` if ( a ) b(); @@ -19,7 +19,7 @@ test("Variant #1: Force Block Statements on If statements", async () => { }); test("Variant #2: Force Block Statements on Arrow functions", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OUTPUT = ()=>true; `, @@ -43,7 +43,7 @@ test("Variant #2: Force Block Statements on Arrow functions", async () => { }); test("Variant #3: Force Block Statements on For loops", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` for(;;) forStatement(); for(a in b) forInStatement(); @@ -62,7 +62,7 @@ test("Variant #3: Force Block Statements on For loops", async () => { }); test("Variant #4: Force Block Statements on While loops/With statement", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` while(1) whileStatement(); with(a) withStatement(); @@ -79,7 +79,7 @@ test("Variant #4: Force Block Statements on While loops/With statement", async ( }); test("Variant #5: Force object accessors to use strings instead", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` console.log("Hello World") `, @@ -94,7 +94,7 @@ test("Variant #5: Force object accessors to use strings instead", async () => { }); test("Variant #6: Force object property keys to use strings instead", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var myObject = { myKey: 1 @@ -111,7 +111,7 @@ test("Variant #6: Force object property keys to use strings instead", async () = }); test("Variant #7: Force Variable declarations to be expanded", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var myVar1, myVar2, myVar3; diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index 5757f0c..618d032 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -2,7 +2,7 @@ import { writeFileSync } from "fs"; import JsConfuser from "../../src/index"; test("Variant #1: Convert Function Declaration into 'new Function' code", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function addTwoNumbers(a, b){ return a + b; @@ -25,7 +25,7 @@ test("Variant #1: Convert Function Declaration into 'new Function' code", async }); test("Variant #2: Convert Function Expression into 'new Function' code", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var addTwoNumbers = function(a, b){ return a + b; @@ -48,7 +48,7 @@ test("Variant #2: Convert Function Expression into 'new Function' code", async ( }); test("Variant #3: Convert functions that use global variables", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function floorNumber(num){ return Math.floor(num); @@ -71,7 +71,7 @@ test("Variant #3: Convert functions that use global variables", async () => { }); test("Variant #4: Don't convert functions that rely on outside-scoped variables", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var _Math = Math; @@ -96,7 +96,7 @@ test("Variant #4: Don't convert functions that rely on outside-scoped variables" }); test("Variant #5: Don't convert functions that rely on outside-scoped variables (trap)", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var _Math = Math; @@ -124,7 +124,7 @@ test("Variant #5: Don't convert functions that rely on outside-scoped variables }); test("Variant #6: Work on High Preset", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function addTwoNumbers(a, b){ return a + b; @@ -146,7 +146,7 @@ test("Variant #6: Work on High Preset", async () => { }); test("Variant #7: Don't convert arrow, async, or generator functions", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var arrowFunction = ()=>{}; async function asyncFunction(){ @@ -173,7 +173,7 @@ test("Variant #7: Don't convert arrow, async, or generator functions", async () }); test("Variant #8: Modified Function", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function addTwoNumbers(x,y){ return x + y; @@ -204,7 +204,7 @@ test("Variant #8: Modified Function", async () => { }); test("Variant #8: Modified Function (non function value)", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` function addTwoNumbers(x,y){ return x+y; @@ -229,7 +229,7 @@ test("Variant #8: Modified Function (non function value)", async () => { }); test("Variant #9: Work with Flatten on any function", async () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var outsideCounter = 0; var outsideFlag = false; diff --git a/test/util/insert.test.ts b/test/util/insert.test.ts deleted file mode 100644 index 567c9c2..0000000 --- a/test/util/insert.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { ok } from "assert"; -import { compileJsSync } from "../../src/compiler"; -import parseJS, { parseSync } from "../../src/parser"; -import traverse, { isBlock } from "../../src/traverse"; -import { Identifier, Location } from "../../src/util/gen"; -import { - deleteDeclaration, - isVarContext, - isFunction, - deleteDirect, - getContexts, - getLexContext, - getVarContext, - computeFunctionLength, -} from "../../src/util/insert"; - -it("isBlock() should be true for block statements and program", async () => { - expect(isBlock({ type: "Program", body: [] })).toStrictEqual(true); - expect(isBlock({ type: "BlockStatement", body: [] })).toStrictEqual(true); -}); - -it("isVarContext() should return true for Function Nodes", () => { - expect(isVarContext({ type: "FunctionDeclaration" })).toStrictEqual(true); - expect(isVarContext({ type: "FunctionExpression" })).toStrictEqual(true); - expect(isVarContext({ type: "ArrowFunctionExpression" })).toStrictEqual(true); -}); - -it("isFunction() should return true for Function Nodes", () => { - expect(isFunction({ type: "FunctionDeclaration" })).toStrictEqual(true); - expect(isFunction({ type: "FunctionExpression" })).toStrictEqual(true); - expect(isFunction({ type: "ArrowFunctionExpression" })).toStrictEqual(true); -}); - -it("isVarContext() should return true for the Program Node (root node)", () => { - expect(isVarContext({ type: "Program" })).toStrictEqual(true); -}); - -it("should delete variable declarations correctly", async () => { - var tree = await parseJS("var a = 1;"); - - deleteDeclaration(tree.body[0].declarations[0], [ - tree.body[0].declarations, - tree.body[0], - tree.body, - tree, - ]); - - expect(tree.body.length).toStrictEqual(0); -}); - -it("should delete function declarations correctly", async () => { - var tree = await parseJS("function a(){}"); - - deleteDeclaration(tree.body[0], [tree.body as any, tree]); - - expect(tree.body.length).toStrictEqual(0); -}); - -it("should delete variable declarations with multiple declarations without leave side-effects", async () => { - var tree = await parseJS("var a = 1, b = 1, c = 1"); - - // delete "b" - deleteDeclaration(tree.body[0].declarations[1], [ - tree.body[0].declarations, - tree.body[0], - tree.body, - tree, - ]); - - expect(tree.body.length).toStrictEqual(1); - expect(tree.body[0].declarations.length).toStrictEqual(2); - expect(tree.body[0].declarations.find((x) => x.id.name == "b")).toBeFalsy(); - expect(tree.body[0].declarations.map((x) => x.id.name)).toEqual(["a", "c"]); -}); - -it("getContexts should return correct results", () => { - expect(getContexts({ type: "Program", body: [] }, [])).toEqual([ - { type: "Program", body: [] }, - ]); -}); - -it("should throw when missing parameters", () => { - expect(deleteDirect).toThrow(); - expect(() => deleteDirect(Identifier("node"), null)).toThrow(); - - expect(getLexContext).toThrow(); - expect(getVarContext).toThrow(); - expect(() => getLexContext(Identifier("test"), [])).toThrow(); - expect(() => getVarContext(Identifier("test"), [])).toThrow(); -}); - -test("computeFunctionLength", () => { - var tree = parseSync(` - function zeroParameters(){}; // 0 - function oneParameter(a){}; // 1 - function twoParameter(a,b){}; // 2 - function restParameter1(...a){}; // 0 - function restParameter2(a,b,...c){}; // 2 - function defaultValue(a,b,c=1,d){}; // 2 - function arrayPattern([a],[b = 2],[[c]]){}; // 3 - function objectPattern({a},{b = 2},{c, d}){}; // 3 - function mixed(a,{b},[c = 3],d,e=5,f,...g){}; // 4 - `); - - function getFunction(searchName: string): Location { - var searchLocation: Location; - traverse(tree, (o, p) => { - if (o.type === "FunctionDeclaration" && o.id.name === searchName) { - ok(!searchLocation); - searchLocation = [o, p]; - } - }); - - ok(searchLocation); - return searchLocation; - } - - expect( - computeFunctionLength(getFunction("zeroParameters")[0].params) - ).toStrictEqual(0); - expect( - computeFunctionLength(getFunction("oneParameter")[0].params) - ).toStrictEqual(1); - expect( - computeFunctionLength(getFunction("twoParameter")[0].params) - ).toStrictEqual(2); - expect( - computeFunctionLength(getFunction("restParameter1")[0].params) - ).toStrictEqual(0); - expect( - computeFunctionLength(getFunction("restParameter2")[0].params) - ).toStrictEqual(2); - expect( - computeFunctionLength(getFunction("arrayPattern")[0].params) - ).toStrictEqual(3); - expect( - computeFunctionLength(getFunction("objectPattern")[0].params) - ).toStrictEqual(3); - expect(computeFunctionLength(getFunction("mixed")[0].params)).toStrictEqual( - 4 - ); -}); diff --git a/test/util/random.test.ts b/test/util/random-utils.test.ts similarity index 100% rename from test/util/random.test.ts rename to test/util/random-utils.test.ts From 31ed9563dd88edd023c2f4fe8db78a64c4c479e1 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 24 Aug 2024 18:59:25 -0400 Subject: [PATCH 003/103] Finish calculator --- src/obfuscator.ts | 21 +++-- src/transforms/calculator.ts | 124 +++++++++++++++++++++-------- test/transforms/calculator.test.ts | 35 +++++--- 3 files changed, 128 insertions(+), 52 deletions(-) diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 753335e..5e63ddd 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -28,7 +28,10 @@ import calculator from "./transforms/calculator"; import { Order } from "./order"; export default class Obfuscator { - plugins: babel.PluginObj[] = []; + plugins: { + plugin: babel.PluginObj; + pluginInstance: PluginInstance; + }[] = []; options: ObfuscateOptions; totalPossibleTransforms: number = 0; @@ -84,11 +87,15 @@ export default class Obfuscator { ok(pluginInstance, "Plugin instance not created."); - plugin.order = pluginInstance.order; - this.plugins.push(plugin); + this.plugins.push({ + plugin, + pluginInstance, + }); }); - this.plugins = this.plugins.sort((a, b) => a.order - b.order); + this.plugins = this.plugins.sort( + (a, b) => a.pluginInstance.order - b.pluginInstance.order + ); } obfuscateAST( @@ -96,14 +103,14 @@ export default class Obfuscator { profiler?: ProfilerCallback ): babel.types.File { for (var i = 0; i < this.plugins.length; i++) { - const plugin = this.plugins[i]; + const { plugin, pluginInstance } = this.plugins[i]; babel.traverse(ast, plugin.visitor as babel.Visitor); if (profiler) { profiler({ - currentTransform: plugin.name, + currentTransform: pluginInstance.name, currentTransformNumber: i, - nextTransform: this.plugins[i + 1]?.name, + nextTransform: this.plugins[i + 1]?.pluginInstance?.name, totalTransforms: this.plugins.length, }); } diff --git a/src/transforms/calculator.ts b/src/transforms/calculator.ts index a2fdcb8..153f3cb 100644 --- a/src/transforms/calculator.ts +++ b/src/transforms/calculator.ts @@ -2,6 +2,7 @@ import { PluginObj } from "@babel/core"; import { PluginArg } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; +import { ok } from "assert"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Calculator); @@ -10,31 +11,66 @@ export default ({ Plugin }: PluginArg): PluginObj => { visitor: { Program: { exit(path) { - var allowedOperators = new Set(["+", "-", "*", "/"]); + const allowedBinaryOperators = new Set(["+", "-", "*", "/"]); + const allowedUnaryOperators = new Set([ + "!", + "void", + "typeof", + "-", + "~", + "+", + ]); + var operatorsMap = new Map(); - var calculatorFnName; + var calculatorFnName = me.getPlaceholder() + "_calc"; path.traverse({ - BinaryExpression: { + UnaryExpression: { exit(path) { const { operator } = path.node; - if (t.isPrivate(path.node.left)) return; + if (!allowedUnaryOperators.has(operator)) return; - if (!allowedOperators.has(operator)) return; + // Special `typeof identifier` check + if ( + operator === "typeof" && + path.get("argument").isIdentifier() + ) + return; - if (!calculatorFnName) { - calculatorFnName = me.getPlaceholder() + "_calc"; + const mapKey = "unaryExpression_" + operator; + let operatorKey = operatorsMap.get(mapKey); + if (typeof operatorKey === "undefined") { + operatorKey = me.generateRandomIdentifier(); + operatorsMap.set(mapKey, operatorKey); } - const operatorKey = operatorsMap.get(operator); + path.replaceWith( + t.callExpression(t.identifier(calculatorFnName), [ + t.stringLiteral(operatorKey), + path.node.argument, + ]) + ); + }, + }, + BinaryExpression: { + exit(path) { + const { operator } = path.node; + + if (t.isPrivate(path.node.left)) return; + + if (!allowedBinaryOperators.has(operator)) return; + + const mapKey = "binaryExpression_" + operator; + let operatorKey = operatorsMap.get(mapKey); if (typeof operatorKey === "undefined") { - operatorsMap.set(operator, me.getPlaceholder()); + operatorKey = me.generateRandomIdentifier(); + operatorsMap.set(mapKey, operatorKey); } path.replaceWith( t.callExpression(t.identifier(calculatorFnName), [ - t.identifier(operatorsMap.get(operator) as string), + t.stringLiteral(operatorKey), path.node.left, path.node.right, ]) @@ -43,32 +79,50 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }); - if (calculatorFnName) { - // Create the calculator function and insert into program path - - var p = path.unshiftContainer( - "body", - t.functionDeclaration( - t.identifier(calculatorFnName), - [ - t.identifier("operator"), - t.identifier("a"), - t.identifier("b"), - ], - t.blockStatement([ - t.returnStatement( - t.binaryExpression( - "+", - t.identifier("a"), - t.identifier("b") - ) - ), - ]) - ) - ); - - path.scope.registerDeclaration(p[0]); + // No operators created + if (operatorsMap.size < 1) { + return; } + + // Create the calculator function and insert into program path + var switchCases: t.SwitchCase[] = Array.from( + operatorsMap.entries() + ).map(([mapKey, key]) => { + const [type, operator] = mapKey.split("_"); + + let expression: t.Expression; + if (type === "binaryExpression") { + expression = t.binaryExpression( + operator as any, + t.identifier("a"), + t.identifier("b") + ); + } else if (type === "unaryExpression") { + expression = t.unaryExpression( + operator as any, + t.identifier("a") + ); + } else { + ok(false); + } + + return t.switchCase(t.stringLiteral(key), [ + t.returnStatement(expression), + ]); + }); + + var p = path.unshiftContainer( + "body", + t.functionDeclaration( + t.identifier(calculatorFnName), + [t.identifier("operator"), t.identifier("a"), t.identifier("b")], + t.blockStatement([ + t.switchStatement(t.identifier("operator"), switchCases), + ]) + ) + ); + + path.scope.registerDeclaration(p[0]); }, }, }, diff --git a/test/transforms/calculator.test.ts b/test/transforms/calculator.test.ts index 9acf738..29e5492 100644 --- a/test/transforms/calculator.test.ts +++ b/test/transforms/calculator.test.ts @@ -1,19 +1,25 @@ import JsConfuser from "../../src/index"; -it("should hide binary expressions", async () => { +test("Variant #1: Hide binary expressions", async () => { var code = `5 + 5`; - var output = await JsConfuser(code, { target: "browser", calculator: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + calculator: true, + }); expect(output).not.toContain("5+5"); expect(output).not.toContain("5 + 5"); expect(output).toContain("switch"); }); -it("should result with correct values", async () => { +test("Variant #2: Result with correct values", async () => { var code = `input(5 + 5)`; - var output = await JsConfuser(code, { target: "browser", calculator: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + calculator: true, + }); function input(x) { expect(x).toStrictEqual(10); @@ -22,10 +28,13 @@ it("should result with correct values", async () => { eval(output); }); -it("should execute property with complex operations", async () => { +test("Variant #3: Execute property with complex operations", async () => { var code = `input((40 * 35 + 4) * 4 + 2)`; - var output = await JsConfuser(code, { target: "browser", calculator: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + calculator: true, + }); var value; function input(x) { @@ -37,7 +46,7 @@ it("should execute property with complex operations", async () => { expect(value).toStrictEqual(5618); }); -it("should apply to unary operators", async () => { +test("Variant #4: Apply to unary operators", async () => { var code = ` var one = +1; var negativeOne = -one; @@ -48,7 +57,10 @@ it("should apply to unary operators", async () => { TEST_OUTPUT = typeof (1, falseValue) === "boolean" && negativeOne === ~~-1 && void 0 === undefined; `; - var output = await JsConfuser(code, { target: "node", calculator: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + calculator: true, + }); expect(output).toContain("_calc"); expect(output).not.toContain("+1"); @@ -62,12 +74,15 @@ it("should apply to unary operators", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); -it("should not break typeof expressions", async () => { +test("Variant #5: Don't break typeof expressions", async () => { var code = ` TEST_OUTPUT = typeof nonExistentVariable === "undefined"; `; - var output = await JsConfuser(code, { target: "node", calculator: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + calculator: true, + }); expect(output).not.toContain("_calc"); From 0171f2bb4ddbb8c522050aaf52518d4f5710a13d Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 24 Aug 2024 22:38:01 -0400 Subject: [PATCH 004/103] Finish Dispatcher --- src/constants.ts | 6 ++ src/transforms/dispatcher.ts | 62 +++++++++++++---- src/transforms/preparation.ts | 27 ++++++++ src/utils/ast-utils.ts | 74 +++++++++++--------- src/validateOptions.ts | 2 +- test/transforms/dispatcher.test.ts | 106 +++++++++++++++++++---------- 6 files changed, 194 insertions(+), 83 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index b27bcc2..67eb298 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,7 @@ export const predictableFunctionTag = "__JS_PREDICT__"; + +export const UNSAFE = Symbol("unsafe"); + +export interface NodeSymbol { + [UNSAFE]?: boolean; +} diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index ab4e9df..bce54a0 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -7,16 +7,23 @@ import { ok } from "assert"; import { chance, getRandomString } from "../utils/random-utils"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; +import { NodeSymbol, UNSAFE } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Dispatcher); + var dispatcherCounter = 0; return { visitor: { - Block: { - exit(blockPath: NodePath) { + "Program|Function": { + exit(_path) { + const blockPath = _path as NodePath; // For testing - if (!blockPath.isProgram()) return; + // if (!blockPath.isProgram()) return; + + var blockStatement: NodePath = blockPath.isProgram() + ? blockPath + : (blockPath.get("body") as NodePath); // Track functions and illegal ones // A function is illegal if: @@ -44,11 +51,27 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } + // Do not apply to functions in nested scopes + if (path.parentPath !== blockStatement) { + illegalNames.add(name); + return; + } + if (functionPaths.has(name)) { illegalNames.add(name); return; } + if ( + path.node.params.find( + (x) => x.type === "AssignmentPattern" + ) || + (path.node as NodeSymbol)[UNSAFE] + ) { + illegalNames.add(name); + return; + } + functionPaths.set(name, path); }, }, @@ -69,7 +92,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - const dispatcherName = me.getPlaceholder() + "_dispatcher"; + const dispatcherName = + me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++; const payloadName = me.getPlaceholder() + "_payload"; const cacheName = me.getPlaceholder() + "_cache"; const newNameMapping = new Map(); @@ -83,7 +107,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { }; for (var name of functionPaths.keys()) { - newNameMapping.set(name, "_" + name); + newNameMapping.set(name, getRandomString(6) /** "_" + name */); } // Find identifiers calling/referencing the functions @@ -98,6 +122,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { var newName = newNameMapping.get(name); + // Do not replace if not referencing the actual function + if (path.scope.getBinding(name).path !== fnPath) { + return; + } + const createDispatcherCall = (name, flagArg?) => { var dispatcherArgs = [t.stringLiteral(name)]; if (flagArg) { @@ -226,22 +255,30 @@ export default ({ Plugin }: PluginArg): PluginObj => { objectExpression, }); + /** + * Prepends the node into the block. (And registers the declaration) + * @param node + */ + function prepend(node: t.Statement) { + var p = blockStatement.unshiftContainer( + "body", + node + ); + blockStatement.scope.registerDeclaration(p[0]); + } + // Insert the dispatcher function - var p = blockPath.unshiftContainer("body", dispatcher); - blockPath.scope.registerDeclaration(p[0]); + prepend(dispatcher); // Insert the payload variable - p = blockPath.unshiftContainer( - "body", + prepend( t.variableDeclaration("var", [ t.variableDeclarator(t.identifier(payloadName)), ]) ); - blockPath.scope.registerDeclaration(p[0]); // Insert the cache variable - p = blockPath.unshiftContainer( - "body", + prepend( t.variableDeclaration("var", [ t.variableDeclarator( t.identifier(cacheName), @@ -249,7 +286,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]) ); - blockPath.scope.registerDeclaration(p[0]); // Remove original functions for (let path of functionPaths.values()) { diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 71b5d97..d0743e9 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -3,12 +3,39 @@ import { NodePath } from "@babel/traverse"; import { PluginArg } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; +import path from "path"; +import { NodeSymbol, UNSAFE } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Preparation); + const markFunctionUnsafe = (path: NodePath) => { + const functionScope = path.scope.getFunctionParent(); + if (!functionScope) return; + + const functionNode = functionScope.path.node; + if (!t.isFunction(functionNode)) return; + + (functionNode as NodeSymbol)[UNSAFE] = true; + }; + return { visitor: { + ThisExpression: { + exit(path) { + markFunctionUnsafe(path); + }, + }, + + ReferencedIdentifier: { + exit(path) { + const { name } = path.node; + if (["arguments", "eval"].includes(name)) { + markFunctionUnsafe(path); + } + }, + }, + // console.log() -> console["log"](); MemberExpression: { exit(path) { diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index d32de75..a86a31c 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -1,19 +1,33 @@ -import * as babelTypes from "@babel/types"; +import * as t from "@babel/types"; import { NodePath } from "@babel/core"; +import { ok } from "assert"; + +export function getParentFunctionOrProgram( + path: NodePath +): NodePath { + // Find the nearest function-like parent + const functionOrProgramPath = path.findParent( + (parentPath) => parentPath.isFunction() || parentPath.isProgram() + ); + + return functionOrProgramPath as NodePath; + + ok(false); +} export function insertIntoNearestBlockScope( path: NodePath, - ...nodesToInsert: babelTypes.Statement[] + ...nodesToInsert: t.Statement[] ): NodePath[] { // Traverse up the AST until we find a BlockStatement or Program let targetPath: NodePath = path; - while (targetPath && !babelTypes.isProgram(targetPath.node)) { + while (targetPath && !t.isProgram(targetPath.node)) { targetPath = targetPath.parentPath; } // Ensure that we found a valid insertion point - if (babelTypes.isBlockStatement(targetPath.node)) { + if (t.isBlockStatement(targetPath.node)) { // Insert before the current statement within the found block return targetPath.insertBefore(nodesToInsert) as NodePath[]; } else if (targetPath.isProgram()) { @@ -27,7 +41,7 @@ export function insertIntoNearestBlockScope( } export function isReservedIdentifier( - node: babelTypes.Identifier | babelTypes.JSXIdentifier + node: t.Identifier | t.JSXIdentifier ): boolean { return ( node.name === "arguments" || // Check for 'arguments' @@ -35,9 +49,9 @@ export function isReservedIdentifier( node.name === "NaN" || // Check for 'NaN' node.name === "Infinity" || // Check for 'Infinity' node.name === "eval" || // Check for 'eval' - babelTypes.isThisExpression(node) || // Check for 'this' - babelTypes.isSuper(node) || // Check for 'super' - babelTypes.isMetaProperty(node) // Check for meta properties like 'new.target' + t.isThisExpression(node) || // Check for 'this' + t.isSuper(node) || // Check for 'super' + t.isMetaProperty(node) // Check for meta properties like 'new.target' ); } @@ -57,24 +71,22 @@ export function hasNestedBinding(path: NodePath, name: string): boolean { return found; } -export function isModifiedIdentifier( - path: NodePath -): boolean { +export function isModifiedIdentifier(path: NodePath): boolean { const parent = path.parent; // Check if the identifier is on the left-hand side of an assignment - if (babelTypes.isAssignmentExpression(parent) && parent.left === path.node) { + if (t.isAssignmentExpression(parent) && parent.left === path.node) { return true; } // Check if the identifier is in an update expression (like i++) - if (babelTypes.isUpdateExpression(parent) && parent.argument === path.node) { + if (t.isUpdateExpression(parent) && parent.argument === path.node) { return true; } // Check if the identifier is being deleted if ( - babelTypes.isUnaryExpression(parent) && + t.isUnaryExpression(parent) && parent.operator === "delete" && parent.argument === path.node ) { @@ -83,8 +95,7 @@ export function isModifiedIdentifier( // Check if the identifier is part of a destructuring pattern being assigned if ( - (babelTypes.isObjectPattern(path.parent) || - babelTypes.isArrayPattern(path.parent)) && + (t.isObjectPattern(path.parent) || t.isArrayPattern(path.parent)) && path.key === "elements" ) { return true; @@ -100,22 +111,19 @@ export function isModifiedIdentifier( * @returns True if the MemberExpression is computed; false otherwise. */ export function isComputedMemberExpression( - memberExpression: babelTypes.MemberExpression + memberExpression: t.MemberExpression ): boolean { const property = memberExpression.property; if (!memberExpression.computed) { // If the property is a non-computed identifier, it is not computed - if (babelTypes.isIdentifier(property)) { + if (t.isIdentifier(property)) { return false; } } // If the property is a computed literal (string or number), it is not computed - if ( - babelTypes.isStringLiteral(property) || - babelTypes.isNumericLiteral(property) - ) { + if (t.isStringLiteral(property) || t.isNumericLiteral(property)) { return false; } @@ -123,20 +131,18 @@ export function isComputedMemberExpression( return true; } -export function getObjectPropertyAsString( - property: babelTypes.ObjectMember -): string { - babelTypes.assertObjectMember(property); +export function getObjectPropertyAsString(property: t.ObjectMember): string { + t.assertObjectMember(property); - if (babelTypes.isIdentifier(property.key)) { + if (t.isIdentifier(property.key)) { return property.key.name; } - if (babelTypes.isStringLiteral(property.key)) { + if (t.isStringLiteral(property.key)) { return property.key.value; } - if (babelTypes.isNumericLiteral(property.key)) { + if (t.isNumericLiteral(property.key)) { return property.key.value.toString(); } @@ -150,21 +156,21 @@ export function getObjectPropertyAsString( * @returns The property as a string or null if it cannot be determined. */ export function getMemberExpressionPropertyAsString( - member: babelTypes.MemberExpression + member: t.MemberExpression ): string | null { - babelTypes.assertMemberExpression(member); + t.assertMemberExpression(member); const property = member.property; - if (!member.computed && babelTypes.isIdentifier(property)) { + if (!member.computed && t.isIdentifier(property)) { return property.name; } - if (babelTypes.isStringLiteral(property)) { + if (t.isStringLiteral(property)) { return property.value; } - if (babelTypes.isNumericLiteral(property)) { + if (t.isNumericLiteral(property)) { return property.value.toString(); } diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 1484c36..a3f3b09 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -30,7 +30,7 @@ const validProperties = new Set([ "movedDeclarations", "opaquePredicates", "shuffle", - "stack", + "variableMasking", "verbose", "globalVariables", "debugComments", diff --git a/test/transforms/dispatcher.test.ts b/test/transforms/dispatcher.test.ts index 2cd6370..da58674 100644 --- a/test/transforms/dispatcher.test.ts +++ b/test/transforms/dispatcher.test.ts @@ -1,6 +1,6 @@ import JsConfuser from "../../src/index"; -it("should middleman function calls", async () => { +test("Variant #1: Middleman function calls", async () => { var code = ` function TEST_FUNCTION(arg){ @@ -10,7 +10,10 @@ it("should middleman function calls", async () => { TEST_FUNCTION(10); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(10); @@ -20,7 +23,7 @@ it("should middleman function calls", async () => { eval(output); }); -it("should not middleman functions relying on arguments identifier", async () => { +test("Variant #2: Don't middleman functions relying on arguments identifier", async () => { var code = ` function TEST_FUNCTION(){ @@ -30,12 +33,15 @@ it("should not middleman functions relying on arguments identifier", async () => TEST_FUNCTION(10); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); expect(output).toContain("function TEST_FUNCTION("); }); -it("should not middleman functions relying on this identifier", async () => { +test("Variant #3: Don't middleman functions relying on this identifier", async () => { var code = ` function TEST_FUNCTION(){ @@ -45,12 +51,15 @@ it("should not middleman functions relying on this identifier", async () => { TEST_FUNCTION(10); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); expect(output).toContain("function TEST_FUNCTION("); }); -it("should work with nested functions", async () => { +test("Variant #4: Work with nested functions", async () => { var code = ` function TEST_FUNCTION(){ @@ -64,7 +73,10 @@ it("should work with nested functions", async () => { TEST_FUNCTION(); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(10); } @@ -73,7 +85,7 @@ it("should work with nested functions", async () => { eval(output); }); -it("should work with nested functions and parameters", async () => { +test("Variant #5: Work with nested functions and parameters", async () => { var code = ` function TEST_FUNCTION(x){ @@ -87,7 +99,10 @@ it("should work with nested functions and parameters", async () => { TEST_FUNCTION(10); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(10); } @@ -96,7 +111,7 @@ it("should work with nested functions and parameters", async () => { eval(output); }); -it("should work with nested functions and return values", async () => { +test("Variant #6: Work with nested functions and return values", async () => { var code = ` function TEST_FUNCTION(){ @@ -110,7 +125,10 @@ it("should work with nested functions and return values", async () => { input(TEST_FUNCTION()); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(10); } @@ -119,7 +137,7 @@ it("should work with nested functions and return values", async () => { eval(output); }); -it("should work with nested and sibling functions and return values", async () => { +test("Variant #7: Work with nested and sibling functions and return values", async () => { var code = ` function TEST_FUNCTION_2(){ @@ -137,7 +155,10 @@ it("should work with nested and sibling functions and return values", async () = input(TEST_FUNCTION()); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(10); } @@ -146,7 +167,7 @@ it("should work with nested and sibling functions and return values", async () = eval(output); }); -it("should work with referencing the function itself", async () => { +test("Variant #8: Work with referencing the function itself", async () => { var code = ` @@ -160,7 +181,10 @@ it("should work with referencing the function itself", async () => { input(fn(10)); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(10); } @@ -169,7 +193,7 @@ it("should work with referencing the function itself", async () => { eval(output); }); -it("should work with the spread operator on arguments", async () => { +test("Variant #9: Work with the spread operator on arguments", async () => { var code = ` function TEST_FUNCTION(x, y, z){ @@ -179,7 +203,10 @@ it("should work with the spread operator on arguments", async () => { input(TEST_FUNCTION(...[2, 10, 8])); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(20); } @@ -188,7 +215,7 @@ it("should work with the spread operator on arguments", async () => { eval(output); }); -it("should work with the spread operator on parameters", async () => { +test("Variant #10: Work with the spread operator on parameters", async () => { var code = ` @@ -200,7 +227,10 @@ it("should work with the spread operator on parameters", async () => { input(TEST_FUNCTION(2, 10, 8)); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(20); } @@ -209,7 +239,7 @@ it("should work with the spread operator on parameters", async () => { eval(output); }); -it("should work with the spread operator on both arguments and parameters", async () => { +test("Variant #11: Work with the spread operator on both arguments and parameters", async () => { var code = ` @@ -220,7 +250,10 @@ it("should work with the spread operator on both arguments and parameters", asyn input(TEST_FUNCTION(...[2, 10, 8])); `; - var output = await JsConfuser(code, { target: "browser", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + dispatcher: true, + }); function input(x) { expect(x).toStrictEqual(20); } @@ -229,7 +262,7 @@ it("should work with the spread operator on both arguments and parameters", asyn eval(output); }); -it("should work with Stack", async () => { +test("Variant #12: Work with Variable Masking", async () => { var code = ` function TEST_FUNCTION_2(){ @@ -248,10 +281,10 @@ it("should work with Stack", async () => { input(TEST_FUNCTION()); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", dispatcher: true, - stack: true, + variableMasking: true, }); var value = "never_called"; @@ -265,7 +298,7 @@ it("should work with Stack", async () => { expect(value).toStrictEqual(10); }); -it("should work with Control Flow Flattening", async () => { +test("Variant #13: Work with Control Flow Flattening", async () => { var code = ` function TEST_FUNCTION_2(){ @@ -284,7 +317,7 @@ it("should work with Control Flow Flattening", async () => { input(TEST_FUNCTION()); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", dispatcher: true, controlFlowFlattening: true, @@ -302,7 +335,7 @@ it("should work with Control Flow Flattening", async () => { }); // https://github.com/MichaelXF/js-confuser/issues/26 -it("should apply to every level of the code", async () => { +test("Variant #14: Apply to every level of the code", async () => { var code = ` function OUTER(){ function INNER(){ @@ -315,7 +348,10 @@ it("should apply to every level of the code", async () => { input(OUTER()); `; - var output = await JsConfuser(code, { target: "node", dispatcher: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + dispatcher: true, + }); expect(output).not.toContain("OUTER"); expect(output).not.toContain("INNER"); @@ -331,8 +367,8 @@ it("should apply to every level of the code", async () => { }); // https://github.com/MichaelXF/js-confuser/issues/77 -it("should work with code that uses toString() function", async () => { - var output = await JsConfuser( +test("Variant #15: Work with code that uses toString() function", async () => { + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ @@ -355,7 +391,7 @@ it("should work with code that uses toString() function", async () => { }); test("Variant #16: Don't change functions that use 'eval'", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myEvalFunction(){ return eval("1+1"); @@ -376,7 +412,7 @@ test("Variant #16: Don't change functions that use 'eval'", async () => { // https://github.com/MichaelXF/js-confuser/issues/103 test("Variant #17: Don't break default parameter, function expression", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var X = "Correct Value"; @@ -402,7 +438,7 @@ printX(); }); test("Variant #18: Preserve function.length property", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction1(){ // Function.length = 0 @@ -430,7 +466,7 @@ test("Variant #18: Preserve function.length property", async () => { }); test("Variant #19: Lexically bound variables", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` switch (true) { case true: From 2b646b9677e057ce06c4c9e934730971bb81e9ac Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 00:14:03 -0400 Subject: [PATCH 005/103] Finish `Moved Declarations` --- src/constants.ts | 2 + src/obfuscator.ts | 2 + .../identifier/movedDeclarations.ts | 108 ++++++++++++++++++ src/transforms/preparation.ts | 40 ++++++- src/utils/static-utils.ts | 55 +++++++++ .../identifier/movedDeclarations.test.ts | 5 +- 6 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 src/transforms/identifier/movedDeclarations.ts create mode 100644 src/utils/static-utils.ts diff --git a/src/constants.ts b/src/constants.ts index 67eb298..d196bf2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,9 @@ export const predictableFunctionTag = "__JS_PREDICT__"; export const UNSAFE = Symbol("unsafe"); +export const PREDICTABLE = Symbol("predictable"); export interface NodeSymbol { [UNSAFE]?: boolean; + [PREDICTABLE]?: boolean; } diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 5e63ddd..7bd7f21 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -26,6 +26,7 @@ import { isProbabilityMapProbable } from "./probability"; import astScrambler from "./transforms/astScrambler"; import calculator from "./transforms/calculator"; import { Order } from "./order"; +import movedDeclarations from "./transforms/identifier/movedDeclarations"; export default class Obfuscator { plugins: { @@ -63,6 +64,7 @@ export default class Obfuscator { push(this.options.shuffle, shuffle); push(this.options.astScrambler, astScrambler); push(this.options.calculator, calculator); + push(this.options.movedDeclarations, movedDeclarations); push(true, finalizer); diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts new file mode 100644 index 0000000..ab9240c --- /dev/null +++ b/src/transforms/identifier/movedDeclarations.ts @@ -0,0 +1,108 @@ +import { NodePath, PluginObj } from "@babel/core"; +import { Order } from "../../order"; +import { PluginArg } from "../plugin"; +import { NodeSymbol, PREDICTABLE } from "../../constants"; +import * as t from "@babel/types"; +import { isStaticValue } from "../../utils/static-utils"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.MovedDeclarations); + + return { + visitor: { + VariableDeclaration: { + exit(path) { + if (path.node.kind !== "var") return; + + var insertionMethod = "variableDeclaration"; + var functionPath = path.findParent((path) => + path.isFunction() + ) as NodePath; + + if (functionPath && (functionPath.node as NodeSymbol)[PREDICTABLE]) { + var strictModeDirective = functionPath.node.body.directives.find( + (directive) => directive.value.value === "use strict" + ); + if (!strictModeDirective) { + insertionMethod = "functionParameter"; + } + } + + const declaration = path.node.declarations[0]; + if (!t.isIdentifier(declaration.id)) return; + + const { name } = declaration.id; + const value = declaration.init || t.identifier("undefined"); + + const isStatic = isStaticValue(value); + let isDefinedAtTop = false; + const parentPath = path.parentPath; + if (parentPath.isBlock()) { + isDefinedAtTop = parentPath.get("body").indexOf(path) === 0; + } + + if (insertionMethod === "variableDeclaration" && isDefinedAtTop) { + return; + } + + let defaultParamValue: t.Expression; + + if ( + insertionMethod === "functionParameter" && + isStatic && + isDefinedAtTop + ) { + defaultParamValue = value; + path.remove(); + } else { + if ( + parentPath.isForInStatement() || + parentPath.isForOfStatement() + ) { + path.replaceWith(t.identifier(name)); + } else { + path.replaceWith( + t.assignmentExpression( + "=", + t.identifier(name), + declaration.init || t.identifier("undefined") + ) + ); + } + } + + switch (insertionMethod) { + case "functionParameter": + var param: t.Pattern | t.Identifier = t.identifier(name); + if (defaultParamValue) { + param = t.assignmentPattern(param, defaultParamValue); + } + + functionPath.node.params.push(param); + break; + case "variableDeclaration": + var block = path.findParent((path) => + path.isBlock() + ) as NodePath; + + var topNode = block.node.body[0]; + const variableDeclarator = t.variableDeclarator( + t.identifier(name) + ); + + if (t.isVariableDeclaration(topNode) && topNode.kind === "var") { + topNode.declarations.push(variableDeclarator); + break; + } else { + block.node.body.unshift( + t.variableDeclaration("var", [variableDeclarator]) + ); + } + + break; + } + }, + }, + }, + }; +}; diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index d0743e9..dfd6a26 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -4,16 +4,16 @@ import { PluginArg } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; import path from "path"; -import { NodeSymbol, UNSAFE } from "../constants"; +import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Preparation); const markFunctionUnsafe = (path: NodePath) => { - const functionScope = path.scope.getFunctionParent(); - if (!functionScope) return; + const functionPath = path.findParent((path) => path.isFunction()); + if (!functionPath) return; - const functionNode = functionScope.path.node; + const functionNode = functionPath.node; if (!t.isFunction(functionNode)) return; (functionNode as NodeSymbol)[UNSAFE] = true; @@ -36,6 +36,38 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }, + FunctionDeclaration: { + exit(path) { + // A function is 'predictable' if the parameter lengths are guaranteed to be known + // a(true) -> predictable + // (a || b)(true) -> unpredictable (Must be directly in a Call Expression) + // a(...args) -> unpredictable (Cannot use SpreadElement) + + const { name } = path.node.id; + + var binding = path.scope.getBinding(name); + var predictable = true; + + for (var referencePath of binding.referencePaths) { + if (!referencePath.parentPath.isCallExpression()) { + predictable = false; + break; + } + + for (var arg of referencePath.parentPath.get("arguments")) { + if (arg.isSpreadElement()) { + predictable = false; + break; + } + } + } + + if (predictable) { + (path.node as NodeSymbol)[PREDICTABLE] = true; + } + }, + }, + // console.log() -> console["log"](); MemberExpression: { exit(path) { diff --git a/src/utils/static-utils.ts b/src/utils/static-utils.ts new file mode 100644 index 0000000..79c1a70 --- /dev/null +++ b/src/utils/static-utils.ts @@ -0,0 +1,55 @@ +import * as t from "@babel/types"; +import { NodePath } from "@babel/traverse"; + +// Function to check if a node is a static value +export function isStaticValue(node: t.Node): boolean { + // Check for literals which are considered static + if (t.isLiteral(node)) { + return true; + } + + // Handle unary expressions like -42 + if (t.isUnaryExpression(node)) { + return isStaticValue(node.argument); + } + + // Handle binary expressions with static values only + if (t.isBinaryExpression(node)) { + return isStaticValue(node.left) && isStaticValue(node.right); + } + + // Handle logical expressions (&&, ||) with static values only + if (t.isLogicalExpression(node)) { + return isStaticValue(node.left) && isStaticValue(node.right); + } + + // Handle conditional (ternary) expressions with static values + if (t.isConditionalExpression(node)) { + return ( + isStaticValue(node.test) && + isStaticValue(node.consequent) && + isStaticValue(node.alternate) + ); + } + + // Handle array expressions where all elements are static + if (t.isArrayExpression(node)) { + return node.elements.every( + (element) => element !== null && isStaticValue(element) + ); + } + + // Handle object expressions where all properties are static + if (t.isObjectExpression(node)) { + return node.properties.every((prop) => { + if (t.isObjectProperty(prop)) { + return isStaticValue(prop.key) && isStaticValue(prop.value); + } + return false; + }); + } + + // Add more cases as needed, depending on what you consider "static" + + return false; +} diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 86d798b..3ace2cb 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -57,7 +57,7 @@ test("Variant #3: Don't move 'y' (destructuring)", async () => { movedDeclarations: true, }); - expect(output).toContain("var [y]=[15];"); + expect(output).toContain("var[y]=[15];"); var TEST_VARIABLE; eval(output); @@ -178,7 +178,8 @@ test("Variant #8: Work with 'use strict'", async () => { }); // Ensure movedDeclarations applied and 'use strict' is still first - expect(output).toContain("function myFunction(){'use strict';var x;"); + // x cannot be moved as a parameter as 'use strict' disallows non-simple parameters + expect(output).toContain("function myFunction(){'use strict';var x=1;"); var TEST_OUTPUT; eval(output); From cfc8d22c4320e0437e2bdeee42ad15495ef4acf5 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 13:56:31 -0400 Subject: [PATCH 006/103] Rename Labels, RGF improvements, Refactor --- CHANGELOG.md | 7 + src/obfuscator.ts | 55 ++++-- src/options.ts | 2 + src/transforms/calculator.ts | 4 + src/transforms/finalizer.ts | 6 + .../identifier/movedDeclarations.ts | 21 ++- src/transforms/plugin.ts | 3 +- src/transforms/preparation.ts | 6 +- src/transforms/renameLabels.ts | 84 +++++++++ src/transforms/rgf.ts | 170 ++++++++++++++++++ src/transforms/string/stringCompression.ts | 1 - src/transforms/string/stringConcealing.ts | 2 +- src/transforms/string/stringEncoding.ts | 6 +- src/transforms/string/stringSplitting.ts | 2 +- src/utils/ast-utils.ts | 52 +++++- src/utils/static-utils.ts | 1 - src/validateOptions.ts | 3 + test/templates/template.test.ts | 24 +-- .../identifier/movedDeclarations.test.ts | 2 +- test/transforms/rgf.test.ts | 26 +-- .../transforms/string/stringSplitting.test.ts | 26 +-- test/util/ast-utils.test.ts | 104 +++++++++++ test/util/random-utils.test.ts | 2 - 23 files changed, 540 insertions(+), 69 deletions(-) create mode 100644 src/transforms/renameLabels.ts create mode 100644 src/transforms/rgf.ts create mode 100644 test/util/ast-utils.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index dc81546..4222edb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# `2.0.0` +2.0 Rewrite + +- New option `renameLabels` to control if/which labels get renamed. Previously enabled but was not configurable. + +- RGF no longers uses `new Function` + # `1.7.3` Tamper Protection diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 7bd7f21..a8836e1 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -1,7 +1,7 @@ import { ObfuscateOptions } from "./options"; import * as babel from "@babel/core"; import generate from "@babel/generator"; -import { PluginInstance } from "./transforms/plugin"; +import { PluginFunction, PluginInstance } from "./transforms/plugin"; import { ok } from "assert"; import { applyDefaultsToOptions, validateOptions } from "./validateOptions"; @@ -27,6 +27,8 @@ import astScrambler from "./transforms/astScrambler"; import calculator from "./transforms/calculator"; import { Order } from "./order"; import movedDeclarations from "./transforms/identifier/movedDeclarations"; +import renameLabels from "./transforms/renameLabels"; +import rgf from "./transforms/rgf"; export default class Obfuscator { plugins: { @@ -41,7 +43,7 @@ export default class Obfuscator { validateOptions(userOptions); this.options = applyDefaultsToOptions({ ...userOptions }); - const allPlugins = []; + const allPlugins: PluginFunction[] = []; const push = (probabilityMap, ...pluginFns) => { this.totalPossibleTransforms += pluginFns.length; @@ -65,6 +67,8 @@ export default class Obfuscator { push(this.options.astScrambler, astScrambler); push(this.options.calculator, calculator); push(this.options.movedDeclarations, movedDeclarations); + push(this.options.renameLabels, renameLabels); + push(this.options.rgf, rgf); push(true, finalizer); @@ -87,7 +91,10 @@ export default class Obfuscator { }, }); - ok(pluginInstance, "Plugin instance not created."); + ok( + pluginInstance, + "Plugin instance not created: " + pluginFunction.toString() + ); this.plugins.push({ plugin, @@ -100,16 +107,22 @@ export default class Obfuscator { ); } + index: number = 0; + obfuscateAST( ast: babel.types.File, - profiler?: ProfilerCallback + options?: { + profiler?: ProfilerCallback; + startIndex?: number; + } ): babel.types.File { - for (var i = 0; i < this.plugins.length; i++) { + for (var i = options?.startIndex || 0; i < this.plugins.length; i++) { + this.index = i; const { plugin, pluginInstance } = this.plugins[i]; babel.traverse(ast, plugin.visitor as babel.Visitor); - if (profiler) { - profiler({ + if (options?.profiler) { + options?.profiler({ currentTransform: pluginInstance.name, currentTransformNumber: i, nextTransform: this.plugins[i + 1]?.pluginInstance?.name, @@ -123,12 +136,12 @@ export default class Obfuscator { async obfuscate(sourceCode: string): Promise { // Parse the source code into an AST - let ast = this.parseCode(sourceCode); + let ast = Obfuscator.parseCode(sourceCode); this.obfuscateAST(ast); // Generate the transformed code from the modified AST with comments removed and compacted output - const code = this.generateCode(ast); + const code = Obfuscator.generateCode(ast, this.options); if (code) { return { @@ -139,16 +152,34 @@ export default class Obfuscator { } } - generateCode(ast: babel.types.File): string { + static createDefaultInstance() { + return new Obfuscator({ + target: "node", + renameLabels: true, + }); + } + + /** + * Generates code from an AST using `@babel/generator` + */ + static generateCode( + ast: T, + options?: ObfuscateOptions + ): string { + const compact = options ? options.compact : true; + const { code } = generate(ast, { comments: false, // Remove comments - compact: this.options.compact, // Compact the output + compact: compact, // Compact the output }); return code; } - parseCode(sourceCode: string): babel.types.File { + /** + * Parses the source code into an AST using `babel.parseSync` + */ + static parseCode(sourceCode: string): babel.types.File { // Parse the source code into an AST let ast = babel.parseSync(sourceCode, { sourceType: "unambiguous", diff --git a/src/options.ts b/src/options.ts index e751b2e..03f5595 100644 --- a/src/options.ts +++ b/src/options.ts @@ -75,6 +75,8 @@ export interface ObfuscateOptions { */ es5?: boolean; + renameLabels?: ProbabilityMap; + /** * ### `renameVariables` * diff --git a/src/transforms/calculator.ts b/src/transforms/calculator.ts index 153f3cb..4d14be3 100644 --- a/src/transforms/calculator.ts +++ b/src/transforms/calculator.ts @@ -40,6 +40,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { const mapKey = "unaryExpression_" + operator; let operatorKey = operatorsMap.get(mapKey); + + // Add unary operator to the map if it doesn't exist if (typeof operatorKey === "undefined") { operatorKey = me.generateRandomIdentifier(); operatorsMap.set(mapKey, operatorKey); @@ -63,6 +65,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { const mapKey = "binaryExpression_" + operator; let operatorKey = operatorsMap.get(mapKey); + + // Add binary operator to the map if it doesn't exist if (typeof operatorKey === "undefined") { operatorKey = me.generateRandomIdentifier(); operatorsMap.set(mapKey, operatorKey); diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index 39850f5..04c3afe 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -2,12 +2,18 @@ import { PluginObj } from "@babel/core"; import { PluginArg } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; +import stringEncoding from "./string/stringEncoding"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Flatten); + const stringEncodingPlugin = stringEncoding(me); return { visitor: { + // String encoding + ...stringEncodingPlugin.visitor, + + // Hexadecimal numbers NumberLiteral: { exit(path) { if (me.options.hexadecimalNumbers) { diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index ab9240c..a8eeb82 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -5,6 +5,12 @@ import { NodeSymbol, PREDICTABLE } from "../../constants"; import * as t from "@babel/types"; import { isStaticValue } from "../../utils/static-utils"; +/** + * Moved Declarations moves variables in two ways: + * + * 1) Move variables to top of the current block + * 2) Move variables as unused function parameters + */ export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.MovedDeclarations); @@ -13,6 +19,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { VariableDeclaration: { exit(path) { if (path.node.kind !== "var") return; + if (path.node.declarations.length !== 1) return; var insertionMethod = "variableDeclaration"; var functionPath = path.findParent((path) => @@ -20,9 +27,15 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) as NodePath; if (functionPath && (functionPath.node as NodeSymbol)[PREDICTABLE]) { - var strictModeDirective = functionPath.node.body.directives.find( - (directive) => directive.value.value === "use strict" - ); + // Check for "use strict" directive + // Strict mode disallows non-simple parameters + // So we can't move the declaration to the function parameters + var strictModeDirective: t.Directive; + if (t.isBlockStatement(functionPath.node.body)) { + strictModeDirective = functionPath.node.body.directives.find( + (directive) => directive.value.value === "use strict" + ); + } if (!strictModeDirective) { insertionMethod = "functionParameter"; } @@ -41,6 +54,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { isDefinedAtTop = parentPath.get("body").indexOf(path) === 0; } + // Already at the top - nothing will change if (insertionMethod === "variableDeclaration" && isDefinedAtTop) { return; } @@ -55,6 +69,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { defaultParamValue = value; path.remove(); } else { + // For-in / For-of can only reference the variable name if ( parentPath.isForInStatement() || parentPath.isForOfStatement() diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 07a6eb4..28706ff 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -1,10 +1,9 @@ import { PluginObj } from "@babel/core"; -import * as babelTypes from "@babel/types"; import Obfuscator from "../obfuscator"; import { getRandomString } from "../utils/random-utils"; import { Order } from "../order"; -export type PluginFunction = (pluginArg: PluginArg) => PluginObj["visitor"]; +export type PluginFunction = (pluginArg: PluginArg) => PluginObj; export type PluginArg = { Plugin: (order: Order) => PluginInstance; diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index dfd6a26..1a2d200 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -21,7 +21,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { - ThisExpression: { + "ThisExpression|Super": { exit(path) { markFunctionUnsafe(path); }, @@ -86,6 +86,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (t.isClassPrivateProperty(path.node)) return; if (!path.node.computed && path.node.key.type === "Identifier") { + // Don't change constructor key + if (t.isClassMethod(path.node) && path.node.kind === "constructor") + return; + path.node.key = t.stringLiteral(path.node.key.name); path.node.computed = true; } diff --git a/src/transforms/renameLabels.ts b/src/transforms/renameLabels.ts new file mode 100644 index 0000000..129d5d0 --- /dev/null +++ b/src/transforms/renameLabels.ts @@ -0,0 +1,84 @@ +import * as t from "@babel/types"; +import { NodePath, PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { Order } from "../order"; + +export default function ({ Plugin }: PluginArg): PluginObj { + const me = Plugin(Order.RenameLabels); + + return { + visitor: { + Program(path) { + const labelUsageMap = new Map(); + + // First pass: Collect all label usages + path.traverse({ + LabeledStatement(labelPath) { + const labelName = labelPath.node.label.name; + labelUsageMap.set(labelName, 0); + }, + BreakStatement(breakPath) { + if (breakPath.node.label) { + const labelName = breakPath.node.label.name; + labelUsageMap.set( + labelName, + (labelUsageMap.get(labelName) || 0) + 1 + ); + } + }, + ContinueStatement(continuePath) { + if (continuePath.node.label) { + const labelName = continuePath.node.label.name; + labelUsageMap.set( + labelName, + (labelUsageMap.get(labelName) || 0) + 1 + ); + } + }, + }); + + // Generate short names for used labels + const labelNameMap = new Map(); + let labelCounter = 0; + labelUsageMap.forEach((usageCount, labelName) => { + if (usageCount > 0) { + labelNameMap.set(labelName, `L${labelCounter++}`); + } + }); + + // Second pass: Rename labels and remove unused ones + path.traverse({ + LabeledStatement(labelPath) { + const labelName = labelPath.node.label.name; + if (labelUsageMap.get(labelName) === 0) { + labelPath.remove(); + } else { + const newLabelName = labelNameMap.get(labelName); + if (newLabelName) { + labelPath.node.label.name = newLabelName; + } + } + }, + BreakStatement(breakPath) { + if (breakPath.node.label) { + const labelName = breakPath.node.label.name; + const newLabelName = labelNameMap.get(labelName); + if (newLabelName) { + breakPath.node.label.name = newLabelName; + } + } + }, + ContinueStatement(continuePath) { + if (continuePath.node.label) { + const labelName = continuePath.node.label.name; + const newLabelName = labelNameMap.get(labelName); + if (newLabelName) { + continuePath.node.label.name = newLabelName; + } + } + }, + }); + }, + }, + }; +} diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts new file mode 100644 index 0000000..0d1e222 --- /dev/null +++ b/src/transforms/rgf.ts @@ -0,0 +1,170 @@ +import { PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { Order } from "../order"; +import * as t from "@babel/types"; +import Obfuscator from "../obfuscator"; +import { computeProbabilityMap } from "../probability"; +import { getFunctionName } from "../utils/ast-utils"; + +/** + * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. + * + * Limitations: + * + * 1. Does not apply to async or generator functions + * 2. Does not apply to functions that reference outside variables + */ +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.RGF); + + const rgfArrayName = me.getPlaceholder() + "_rgf"; + const rgfEvalName = me.getPlaceholder() + "_rgf_eval"; + const rgfArrayExpression = t.arrayExpression([]); + + return { + visitor: { + Program: { + exit(path) { + if (rgfArrayExpression.elements.length === 0) return; + + // Insert the RGF array at the top of the program + path.node.body.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(rgfArrayName), + rgfArrayExpression + ), + ]) + ); + + path.node.body.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(rgfEvalName), + t.identifier("eval") + ), + ]) + ); + }, + }, + Function: { + exit(path) { + // Skip async and generator functions + if (path.node.async || path.node.generator) return; + + // Don't apply to arrow functions + if (t.isArrowFunctionExpression(path.node)) return; + + const name = getFunctionName(path); + + if (!computeProbabilityMap(me.options.rgf, (x) => x, name)) return; + + // Skip functions with references to outside variables + // Check the scope to see if this function relies on any variables defined outside the function + var identifierPreventingTransform: string; + + path.traverse({ + ReferencedIdentifier(refPath) { + const { name } = refPath.node; + // RGF array name is allowed, it is not considered an outside reference + if (name === rgfArrayName) return; + + const binding = refPath.scope.getBinding(name); + if (!binding) return; + + // If the binding is not in the current scope, it is an outside reference + if (binding.scope !== path.scope) { + identifierPreventingTransform = name; + } + }, + }); + + if (identifierPreventingTransform) { + me.log( + "Skipping function " + + name + + " due to reference to outside variable", + path.node.loc + ); + return; + } + + const embeddedName = me.getPlaceholder() + "_embedded"; + const replacementName = me.getPlaceholder() + "_replacement"; + const thisName = me.getPlaceholder() + "_this"; + + // Transform the function + const evalTree: t.Program = t.program([ + t.functionDeclaration( + t.identifier(embeddedName), + [], + t.blockStatement([ + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern([ + t.identifier(thisName), + t.identifier(rgfArrayName), + ]), + t.thisExpression() + ), + ]), + t.functionDeclaration( + t.identifier(replacementName), + path.node.params as (t.Identifier | t.Pattern)[], + path.node.body + ), + t.returnStatement( + t.callExpression( + t.memberExpression( + t.identifier(replacementName), + t.identifier("apply") + ), + [t.identifier(thisName), t.identifier("arguments")] + ) + ), + ]) + ), + t.expressionStatement(t.identifier(embeddedName)), + ]); + const evalFile = t.file(evalTree); + + me.obfuscator.obfuscateAST(evalFile, { + startIndex: me.obfuscator.index + 1, + }); + + const generated = Obfuscator.generateCode(evalFile); + + var functionExpression = t.callExpression(t.identifier(rgfEvalName), [ + t.stringLiteral(generated), + ]); + + var index = rgfArrayExpression.elements.length; + rgfArrayExpression.elements.push(functionExpression); + + path.node.body = t.blockStatement([ + t.returnStatement( + t.callExpression( + t.memberExpression( + t.memberExpression( + t.identifier(rgfArrayName), + t.numericLiteral(index), + true + ), + t.stringLiteral("apply"), + true + ), + [ + t.arrayExpression([ + t.identifier("this"), + t.identifier(rgfArrayName), + ]), + t.identifier("arguments"), + ] + ) + ), + ]); + }, + }, + }, + }; +}; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 7b6dca0..7dc4c16 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -14,7 +14,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { const stringMap = new Map(); // Find all the strings - programPath.traverse({ StringLiteral: { exit: (path) => { diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 6e41b4f..4fb1c16 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -6,7 +6,7 @@ import { Order } from "../../order"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringConcealing); - var decoderName = "decoder"; + var decoderName = me.getPlaceholder() + "_decoder"; return { visitor: { diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 93096de..79214c5 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -1,5 +1,5 @@ import { PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginInstance } from "../plugin"; import * as t from "@babel/types"; import { choice } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; @@ -47,9 +47,7 @@ function toUnicodeRepresentation(str: string) { return escapedString; } -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Finalizer); - +export default (me: PluginInstance): PluginObj => { return { visitor: { StringLiteral: { diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index da961ae..2005ef3 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -1,5 +1,5 @@ import { PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginInstance } from "../plugin"; import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; import { binaryExpression, stringLiteral } from "@babel/types"; diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index a86a31c..7185e65 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -2,6 +2,46 @@ import * as t from "@babel/types"; import { NodePath } from "@babel/core"; import { ok } from "assert"; +/** + * Retrieves a function name from debugging purposes. + * - Function Declaration / Expression + * - Variable Declaration + * - Object property / method + * - Class property / method + * @param path + * @returns + */ +export function getFunctionName(path: NodePath): string { + // Check function declaration/expression ID + if ( + (t.isFunctionDeclaration(path.node) || t.isFunctionExpression(path.node)) && + path.node.id + ) { + return path.node.id.name; + } + + // Check for containing variable declaration + if ( + path.parentPath.isVariableDeclarator() && + t.isIdentifier(path.parentPath.node.id) + ) { + return path.parentPath.node.id.name; + } + + if (path.isObjectMethod() || path.isClassMethod()) { + var property = getObjectPropertyAsString(path.node); + if (property) return property; + } + + // Check for containing property in an object + if (path.parentPath.isObjectProperty() || path.parentPath.isClassProperty()) { + var property = getObjectPropertyAsString(path.parentPath.node); + if (property) return property; + } + + return "anonymous"; +} + export function getParentFunctionOrProgram( path: NodePath ): NodePath { @@ -131,10 +171,16 @@ export function isComputedMemberExpression( return true; } -export function getObjectPropertyAsString(property: t.ObjectMember): string { - t.assertObjectMember(property); +export function getObjectPropertyAsString( + property: t.ObjectMember | t.ClassProperty | t.ClassMethod +): string { + ok( + t.isObjectMember(property) || + t.isClassProperty(property) || + t.isClassMethod(property) + ); - if (t.isIdentifier(property.key)) { + if (!property.computed && t.isIdentifier(property.key)) { return property.key.name; } diff --git a/src/utils/static-utils.ts b/src/utils/static-utils.ts index 79c1a70..053439d 100644 --- a/src/utils/static-utils.ts +++ b/src/utils/static-utils.ts @@ -1,5 +1,4 @@ import * as t from "@babel/types"; -import { NodePath } from "@babel/traverse"; // Function to check if a node is a static value export function isStaticValue(node: t.Node): boolean { diff --git a/src/validateOptions.ts b/src/validateOptions.ts index a3f3b09..6488bff 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -204,6 +204,9 @@ export function applyDefaultsToOptions( if (!options.hasOwnProperty("preserveFunctionLength")) { options.preserveFunctionLength = true; // preserveFunctionLength is on by default } + if (!options.hasOwnProperty("renameLabels")) { + options.renameLabels = true; // RenameLabels is on by default + } if (options.globalVariables && !(options.globalVariables instanceof Set)) { options.globalVariables = new Set(Object.keys(options.globalVariables)); diff --git a/test/templates/template.test.ts b/test/templates/template.test.ts index 357cb53..6c172ff 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -1,4 +1,6 @@ +import Obfuscator from "../../src/obfuscator"; import Template from "../../src/templates/template"; +import { stringLiteral } from "@babel/types"; describe("Template", () => { test("Variant #1: Error when invalid code passed in", () => { @@ -42,7 +44,7 @@ describe("Template", () => { expect(functionDeclaration.id.name).toStrictEqual("decodeBase64"); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); @@ -62,7 +64,7 @@ describe("Template", () => { var functionDeclaration = Base64Template.single({ name: "decodeBase64", getWindowName: "newWindow", - getWindow: parseSnippet("var newWindow = {}").body, + getWindow: Obfuscator.parseCode("var newWindow = {}").program.body, }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); @@ -71,7 +73,7 @@ describe("Template", () => { ); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); @@ -92,7 +94,7 @@ describe("Template", () => { name: "decodeBase64", getWindowName: "newWindow", getWindow: () => { - return parseSnippet("var newWindow = {}").body; + return Obfuscator.parseCode("var newWindow = {}").program.body; }, }); @@ -102,7 +104,7 @@ describe("Template", () => { ); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); @@ -134,7 +136,7 @@ describe("Template", () => { ); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); @@ -166,7 +168,7 @@ describe("Template", () => { ); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); @@ -183,13 +185,13 @@ describe("Template", () => { var functionDeclaration = Base64Template.single({ name: "decodeBase64", - property: Literal("atob"), + property: stringLiteral("atob"), }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); @@ -205,13 +207,13 @@ describe("Template", () => { var functionDeclaration = Base64Template.single({ name: "decodeBase64", - property: () => Literal("atob"), + property: () => stringLiteral("atob"), }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); // Generated code and check - var output = compileJsSync(functionDeclaration, { + var output = Obfuscator.generateCode(functionDeclaration, { target: "node", compact: true, }); diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 3ace2cb..4fd0fd0 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -187,7 +187,7 @@ test("Variant #8: Work with 'use strict'", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); -test("Variant #9: Defined variable without an initializer", async () => { +test("Variant #9: Defined variable without an initializer + CFF + Duplicate Literals Removal", async () => { var code = ` var x; x = 1; diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index 618d032..e2d07c8 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -1,7 +1,7 @@ import { writeFileSync } from "fs"; import JsConfuser from "../../src/index"; -test("Variant #1: Convert Function Declaration into 'new Function' code", async () => { +test("Variant #1: Convert Function Declaration into 'eval' code", async () => { var { code: output } = await JsConfuser.obfuscate( ` function addTwoNumbers(a, b){ @@ -16,7 +16,7 @@ test("Variant #1: Convert Function Declaration into 'new Function' code", async } ); - expect(output).toContain("new Function"); + expect(output).toContain("_rgf_eval"); var TEST_OUTPUT; eval(output); @@ -24,7 +24,7 @@ test("Variant #1: Convert Function Declaration into 'new Function' code", async expect(TEST_OUTPUT).toStrictEqual(15); }); -test("Variant #2: Convert Function Expression into 'new Function' code", async () => { +test("Variant #2: Convert Function Expression into 'eval' code", async () => { var { code: output } = await JsConfuser.obfuscate( ` var addTwoNumbers = function(a, b){ @@ -39,7 +39,7 @@ test("Variant #2: Convert Function Expression into 'new Function' code", async ( } ); - expect(output).toContain("new Function"); + expect(output).toContain("_rgf_eval("); var TEST_OUTPUT; eval(output); @@ -62,7 +62,7 @@ test("Variant #3: Convert functions that use global variables", async () => { } ); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -87,7 +87,7 @@ test("Variant #4: Don't convert functions that rely on outside-scoped variables" } ); - expect(output).not.toContain("new Function"); + expect(output).not.toContain("eval"); var TEST_OUTPUT; eval(output); @@ -115,7 +115,7 @@ test("Variant #5: Don't convert functions that rely on outside-scoped variables } ); - expect(output).not.toContain("new Function"); + expect(output).not.toContain("eval"); var TEST_OUTPUT; eval(output); @@ -164,7 +164,7 @@ test("Variant #7: Don't convert arrow, async, or generator functions", async () } ); - expect(output).not.toContain("new Function"); + expect(output).not.toContain("eval"); var TEST_OUTPUT; eval(output); @@ -195,7 +195,7 @@ test("Variant #8: Modified Function", async () => { } ); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -220,7 +220,7 @@ test("Variant #8: Modified Function (non function value)", async () => { } ); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -259,7 +259,7 @@ test("Variant #9: Work with Flatten on any function", async () => { } ); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -317,7 +317,7 @@ test("Variant #10: Configurable by custom function option", async () => { "rgfThisFunction", "doNotRgfThisFunction", ]); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT_1; var TEST_OUTPUT_2; @@ -347,7 +347,7 @@ test("Variant #11: Function containing function should both be changed", async f ); // 2 means one Function changed, 3 means two Functions changed - expect(output.split("new Function").length).toStrictEqual(3); + expect(output.split("_rgf_eval(").length).toStrictEqual(3); var TEST_OUTPUT; eval(output); diff --git a/test/transforms/string/stringSplitting.test.ts b/test/transforms/string/stringSplitting.test.ts index 48c50e5..17d5a43 100644 --- a/test/transforms/string/stringSplitting.test.ts +++ b/test/transforms/string/stringSplitting.test.ts @@ -1,9 +1,9 @@ import JsConfuser from "../../../src/index"; -it("should split strings", async () => { +test("Variant #1: Split strings", async () => { var code = `var TEST_STRING = "the brown dog jumped over the lazy fox."`; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringSplitting: true, }); @@ -11,10 +11,10 @@ it("should split strings", async () => { expect(output).not.toContain("the brown dog jumped over the lazy fox."); }); -it("should split strings and concatenate correctly", async () => { +test("Variant #2: Split strings and concatenate correctly", async () => { var code = `input("the brown dog jumped over the lazy fox.")`; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringSplitting: true, }); @@ -31,7 +31,7 @@ it("should split strings and concatenate correctly", async () => { expect(value).toStrictEqual("the brown dog jumped over the lazy fox."); }); -it("should work on property keys", async () => { +test("Variant #3: Work on property keys", async () => { var code = ` var myObject = { myVeryLongStringThatShouldGetSplit: 100 @@ -40,7 +40,7 @@ it("should work on property keys", async () => { TEST_VAR = myObject.myVeryLongStringThatShouldGetSplit; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringSplitting: true, }); @@ -53,7 +53,7 @@ it("should work on property keys", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should work on class keys", async () => { +test("Variant #4: Work on class keys", async () => { var code = ` class MyClass { myVeryLongMethodName(){ @@ -66,7 +66,7 @@ it("should work on class keys", async () => { TEST_VAR = myObject.myVeryLongMethodName(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringSplitting: true, }); @@ -79,7 +79,7 @@ it("should work on class keys", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should not encode constructor key", async () => { +test("Variant #5: Don't encode constructor key", async () => { var code = ` class MyClass { constructor(){ @@ -90,7 +90,7 @@ it("should not encode constructor key", async () => { new MyClass(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringSplitting: true, }); @@ -101,15 +101,15 @@ it("should not encode constructor key", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should allow custom callback to exclude strings", async () => { +test("Variant #6: Allow custom callback to exclude strings", async () => { var code = ` var str1 = "-- Hello World --"; var str2 = "-- This String Will Not Be Split --"; var str3 = "-- This string Will Be Split --"; `; - var strings = []; - var output = await JsConfuser(code, { + var strings: string[] = []; + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringSplitting: (str) => { strings.push(str); diff --git a/test/util/ast-utils.test.ts b/test/util/ast-utils.test.ts new file mode 100644 index 0000000..4ea31a1 --- /dev/null +++ b/test/util/ast-utils.test.ts @@ -0,0 +1,104 @@ +import Obfuscator from "../../src/obfuscator"; +import { getFunctionName } from "../../src/utils/ast-utils"; +import traverse from "@babel/traverse"; + +describe("getFunctionName", () => { + test("Variant #1: Function Declaration / Expression", () => { + var file = Obfuscator.parseCode(` + function myFunctionDeclaration(){} + (function myFunctionExpression(){}) + `); + + var count = 0; + + traverse(file, { + FunctionDeclaration(path) { + expect(getFunctionName(path)).toBe("myFunctionDeclaration"); + count++; + }, + FunctionExpression(path) { + expect(getFunctionName(path)).toBe("myFunctionExpression"); + count++; + }, + }); + + expect(count).toStrictEqual(2); + }); + + test("Variant #2: Variable Declaration", () => { + var file = Obfuscator.parseCode(` + var myFunctionVariable = function(){} + var {myFunctionVariable2} = [ + function(){} + ] + `); + + var count = 0; + + traverse(file, { + FunctionExpression(path) { + expect(getFunctionName(path)).toBe( + ["myFunctionVariable", "anonymous"][count] + ); + count++; + }, + }); + + expect(count).toStrictEqual(2); + }); + + test("Variant #3: Object property / method", () => { + var file = Obfuscator.parseCode(` + var object = { + myFunctionProperty: function(){}, + myFunctionMethod(){}, + ["myEasyToComputeFunction"]: function(){}, + ["my" + "HardToComputeFunction"]: function(){}, + } + `); + + var count = 0; + + traverse(file, { + Function(path) { + expect(getFunctionName(path)).toBe( + [ + "myFunctionProperty", + "myFunctionMethod", + "myEasyToComputeFunction", + "anonymous", + ][count] + ); + count++; + }, + }); + + expect(count).toStrictEqual(4); + }); + + test("Variant #4: Class methods", () => { + var file = Obfuscator.parseCode(` + class MyClass { + myMethod(){} + myProperty = function(){}; + ["myEasyToComputeFunction"](){} + ["my" + "HardToComputeFunction"](){} + } + `); + + var count = 0; + + traverse(file, { + Function(path) { + expect(getFunctionName(path)).toBe( + ["myMethod", "myProperty", "myEasyToComputeFunction", "anonymous"][ + count + ] + ); + count++; + }, + }); + + expect(count).toStrictEqual(4); + }); +}); diff --git a/test/util/random-utils.test.ts b/test/util/random-utils.test.ts index b709241..a5b7714 100644 --- a/test/util/random-utils.test.ts +++ b/test/util/random-utils.test.ts @@ -4,8 +4,6 @@ import { getRandomString, } from "../../src/utils/random-utils"; -const escodegen = require("escodegen"); - it("choice() should return a random element from an array", async () => { var sample = [10, 20, 30, 40, 50]; From bc84dd5a0a3ef66b9642454f58efbfc8d0db13c0 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 16:33:37 -0400 Subject: [PATCH 007/103] Finish `obfuscateWithProfiler`, Generic templates, __JS_CONFUSER_VAR__ --- CHANGELOG.md | 2 + src/constants.ts | 8 +++ src/index.ts | 49 +++++++++++++-- src/obfuscationResult.ts | 11 ++++ src/obfuscator.ts | 6 +- src/templates/template.ts | 8 ++- src/transforms/extraction/objectExtraction.ts | 12 ++++ src/transforms/finalizer.ts | 8 +++ src/transforms/identifier/globalConcealing.ts | 29 ++++++++- src/transforms/preparation.ts | 60 ++++++++++++++----- src/transforms/string/stringCompression.ts | 12 ++++ test/index.test.ts | 43 ++++++------- test/templates/template.test.ts | 24 ++++---- test/transforms/deadCode.test.ts | 2 +- .../extraction/objectExtraction.test.ts | 2 +- .../identifier/globalConcealing.test.ts | 4 +- test/transforms/preparation.test.ts | 6 +- 17 files changed, 223 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4222edb..76b2fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - RGF no longers uses `new Function` +- Improved Moved Declaration's ability to move variables as unused function parameters + # `1.7.3` Tamper Protection diff --git a/src/constants.ts b/src/constants.ts index d196bf2..f4bae73 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,3 +7,11 @@ export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; } + +/** + * Allows the user to grab the variable name of a renamed variable. + */ +export const variableFunctionName = "__JS_CONFUSER_VAR__"; + +export const noRenameVariablePrefix = "__NO_JS_CONFUSER_RENAME__"; +export const placeholderVariablePrefix = "__p_"; diff --git a/src/index.ts b/src/index.ts index 0d4eddd..3ffbdad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,12 @@ import * as babelTypes from "@babel/types"; import Obfuscator from "./obfuscator"; import { ObfuscateOptions } from "./options"; -import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; +import { + ObfuscationResult, + ProfileData, + ProfilerCallback, + ProfilerLog, +} from "./obfuscationResult"; export async function obfuscate( sourceCode: string, @@ -28,17 +33,51 @@ export async function obfuscateWithProfiler( callback: ProfilerCallback; performance: { now(): number }; } -): Promise { +): Promise { + const startTime = performance.now(); + var obfuscator = new Obfuscator(options); + var totalTransforms = obfuscator.plugins.length; + + var transformTimeMap: { [transformName: string]: number } = + Object.create(null); + var currentTransformTime = performance.now(); + + const beforeParseTime = performance.now(); + + var ast = Obfuscator.parseCode(sourceCode); + + const parseTime = performance.now() - beforeParseTime; + + ast = obfuscator.obfuscateAST(ast, { + profiler: (log: ProfilerLog) => { + var nowTime = performance.now(); + transformTimeMap[log.currentTransform] = nowTime - currentTransformTime; + currentTransformTime = nowTime; + profiler.callback(log); + }, + }); + + const beforeCompileTime = performance.now(); + + var code = Obfuscator.generateCode(ast, obfuscator.options); - var ast = obfuscator.parseCode(sourceCode); + const compileTime = performance.now() - beforeCompileTime; - ast = obfuscator.obfuscateAST(ast, profiler.callback); + const endTime = performance.now(); - var code = obfuscator.generateCode(ast); + const obfuscationTime = endTime - startTime; return { code: code, + profileData: { + transformTimeMap: transformTimeMap, + obfuscationTime: obfuscationTime, + parseTime: parseTime, + compileTime: compileTime, + totalTransforms: totalTransforms, + totalPossibleTransforms: obfuscator.totalPossibleTransforms, + }, }; } diff --git a/src/obfuscationResult.ts b/src/obfuscationResult.ts index 1b3ceac..935e75b 100644 --- a/src/obfuscationResult.ts +++ b/src/obfuscationResult.ts @@ -2,6 +2,17 @@ export interface ObfuscationResult { code: string; } +export interface ProfileData { + obfuscationTime: number; + compileTime: number; + parseTime: number; + totalPossibleTransforms: number; + totalTransforms: number; + transformTimeMap: { + [key: string]: number; + }; +} + export type ProfilerCallback = (log: ProfilerLog) => void; export interface ProfilerLog { currentTransform: string; diff --git a/src/obfuscator.ts b/src/obfuscator.ts index a8836e1..1657ec3 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -152,6 +152,10 @@ export default class Obfuscator { } } + getPlugin(order: Order) { + return this.plugins.find((x) => x.pluginInstance.order === order); + } + static createDefaultInstance() { return new Obfuscator({ target: "node", @@ -170,7 +174,7 @@ export default class Obfuscator { const { code } = generate(ast, { comments: false, // Remove comments - compact: compact, // Compact the output + minified: compact, }); return code; diff --git a/src/templates/template.ts b/src/templates/template.ts index 039451d..5c163bb 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -139,7 +139,9 @@ export default class Template { return file.program.body; } - single(variables: TemplateVariables = {}): babelTypes.Statement { + single( + variables: TemplateVariables = {} + ): T { const nodes = this.compile(variables); if (nodes.length !== 1) { @@ -152,10 +154,10 @@ export default class Template { .map((node) => node.type) .join(", ")}` ); - return filteredNodes[0]; + return filteredNodes[0] as T; } - return nodes[0]; + return nodes[0] as T; } expression(variables: TemplateVariables = {}): babelTypes.Expression { diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index 291c8bf..f7e3a15 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -7,6 +7,7 @@ import { } from "../../utils/ast-utils"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; +import { computeProbabilityMap } from "../../probability"; function isObjectSafeForExtraction( path: NodePath @@ -113,6 +114,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Replace the original object with extracted variables if (extractedVariables.length > 0) { + // Allow user to disable certain objects + if ( + !computeProbabilityMap( + me.options.objectExtraction, + (x) => x, + objectName + ) + ) { + return; + } + path.replaceWithMultiple(extractedVariables); var variableDeclaration = diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index 04c3afe..9836e22 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -19,6 +19,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (me.options.hexadecimalNumbers) { const { value } = path.node; + if ( + Number.isNaN(value) || + !Number.isFinite(value) || + Math.floor(value) !== value + ) { + return; + } + // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator. // This code handles it regardless var isNegative = value < 0; diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index d051a66..134cf06 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -4,6 +4,15 @@ import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; +import { computeProbabilityMap } from "../../probability"; +import { variableFunctionName } from "../../constants"; + +const ignoreGlobals = new Set([ + "require", + "__dirname", + "eval", + variableFunctionName, +]); export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.GlobalConcealing); @@ -20,7 +29,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { var mappedKey = globalMapping.get(originalName); return t.switchCase(t.stringLiteral(mappedKey), [ - t.returnStatement(t.identifier(originalName)), + t.returnStatement( + t.memberExpression( + t.identifier(globalVarName), + t.stringLiteral(originalName), + true + ) + ), ]); }); @@ -69,6 +84,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ReferencedIdentifier(path: NodePath) { var identifierName = path.node.name; + if (ignoreGlobals.has(identifierName)) return; + if ( !path.scope.hasGlobal(identifierName) || path.scope.hasOwnBinding(identifierName) @@ -77,6 +94,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { } var mapping = globalMapping.get(identifierName); if (!mapping) { + // Allow user to disable custom global variables + if ( + !computeProbabilityMap( + me.options.globalConcealing, + (x) => x, + identifierName + ) + ) + return; + mapping = gen.generate(); globalMapping.set(identifierName, mapping); } diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 1a2d200..465e297 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -4,7 +4,13 @@ import { PluginArg } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; import path from "path"; -import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; +import { + NodeSymbol, + PREDICTABLE, + UNSAFE, + variableFunctionName, +} from "../constants"; +import { ok } from "assert"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Preparation); @@ -33,6 +39,23 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (["arguments", "eval"].includes(name)) { markFunctionUnsafe(path); } + + // When Rename Variables is disabled, __JS_CONFUSER_VAR__ must still be removed + if ( + name === variableFunctionName && + !me.obfuscator.getPlugin(Order.RenameVariables) + ) { + ok( + path.parentPath.isCallExpression(), + variableFunctionName + " must be directly called" + ); + + var argument = path.parentPath.node.arguments[0]; + t.assertIdentifier(argument); + + // Remove the variableFunctionName call + path.parentPath.replaceWith(t.stringLiteral(argument.name)); + } }, }, @@ -102,7 +125,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (path.node.declarations.length > 1) { var extraDeclarations = path.node.declarations.slice(1); path.node.declarations.length = 1; - path.insertAfter(extraDeclarations); + path.insertAfter( + extraDeclarations.map((declaration) => + t.variableDeclaration(path.node.kind, [declaration]) + ) + ); } }, }, @@ -138,20 +165,23 @@ export default ({ Plugin }: PluginArg): PluginObj => { // for() d() -> for() { d(); } // while(a) b() -> while(a) { b(); } - "ForStatement|ForInStatement|ForOfStatement|WhileStatement": { - exit(_path) { - var path = _path as NodePath< - | t.ForStatement - | t.ForInStatement - | t.ForOfStatement - | t.WhileStatement - >; - - if (path.node.body.type !== "BlockStatement") { - path.node.body = t.blockStatement([path.node.body]); - } + // with(a) b() -> with(a) { b(); } + "ForStatement|ForInStatement|ForOfStatement|WhileStatement|WithStatement": + { + exit(_path) { + var path = _path as NodePath< + | t.ForStatement + | t.ForInStatement + | t.ForOfStatement + | t.WhileStatement + | t.WithStatement + >; + + if (path.node.body.type !== "BlockStatement") { + path.node.body = t.blockStatement([path.node.body]); + } + }, }, - }, }, }; }; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 7dc4c16..210484c 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -2,6 +2,7 @@ import { PluginObj } from "@babel/core"; import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; +import { computeProbabilityMap } from "../../probability"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringCompression); @@ -20,6 +21,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { const originalValue = path.node.value; let index = stringMap.get(originalValue); if (typeof index === "undefined") { + // Allow user option to skip compression for certain strings + if ( + !computeProbabilityMap( + me.options.stringCompression, + (x) => x, + originalValue + ) + ) { + return; + } + index = stringMap.size; stringMap.set(originalValue, index); } diff --git a/test/index.test.ts b/test/index.test.ts index 3c6f84d..cd2b845 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,4 +1,5 @@ import JsConfuser, { obfuscateWithProfiler } from "../src/index"; +import { ProfilerLog } from "../src/obfuscationResult"; it("should be a function", async () => { expect(typeof JsConfuser).toBe("function"); @@ -158,7 +159,7 @@ describe("obfuscateAST", () => { }; var before = JSON.stringify(AST); - JsConfuser.obfuscateAST(AST, { target: "node", es5: true }); + JsConfuser.obfuscateAST(AST as any, { target: "node", es5: true }); var after = JSON.stringify(AST); @@ -171,7 +172,7 @@ describe("obfuscateAST", () => { test("Variant #2: Error on invalid parameters", async () => { await expect(async () => { - return await JsConfuser.obfuscateAST("string", { + return await JsConfuser.obfuscateAST("string" as any, { target: "node", preset: "low", }); @@ -184,7 +185,7 @@ describe("obfuscateAST", () => { type: "NotProgram", }; - return await JsConfuser.obfuscateAST(invalidAST, { + return await JsConfuser.obfuscateAST(invalidAST as any, { target: "node", preset: "low", }); @@ -192,18 +193,21 @@ describe("obfuscateAST", () => { }); }); -describe("debugObfuscation", () => { - test("Variant #1: Return array of objects containing code, ms, and name properties", async () => { +describe("obfuscateWithProfiler", () => { + test("Variant #1: Return Profile Data and notify the Profile Log callback", async () => { var called = false; - var callback = ({ name, complete, totalTransforms }) => { - expect(typeof name).toStrictEqual("string"); - expect(typeof complete).toStrictEqual("number"); - expect(typeof totalTransforms).toStrictEqual("number"); + var callback = (log: ProfilerLog) => { + expect(typeof log.currentTransform).toStrictEqual("string"); + expect(typeof log.currentTransformNumber).toStrictEqual("number"); + expect(typeof log.totalTransforms).toStrictEqual("number"); + if (typeof log.nextTransform !== "undefined") { + expect(typeof log.nextTransform).toStrictEqual("string"); + } called = true; }; - var output = await JsConfuser.obfuscateWithProfiler( + var { code, profileData } = await JsConfuser.obfuscateWithProfiler( `console.log(1)`, { target: "node", preset: "low" }, { @@ -212,19 +216,18 @@ describe("debugObfuscation", () => { } ); - expect(typeof output).toStrictEqual("object"); - expect(typeof output.obfuscated).toStrictEqual("string"); - expect(typeof output.obfuscationTime).toStrictEqual("number"); - expect(typeof output.compileTime).toStrictEqual("number"); - expect(typeof output.parseTime).toStrictEqual("number"); - expect(typeof output.totalPossibleTransforms).toStrictEqual("number"); - expect(typeof output.totalTransforms).toStrictEqual("number"); - expect(typeof output.transformationTimes).toStrictEqual("object"); - expect(typeof output.transformationTimes.RenameVariables).toStrictEqual( + expect(typeof code).toStrictEqual("string"); + expect(typeof profileData.obfuscationTime).toStrictEqual("number"); + expect(typeof profileData.compileTime).toStrictEqual("number"); + expect(typeof profileData.parseTime).toStrictEqual("number"); + expect(typeof profileData.totalPossibleTransforms).toStrictEqual("number"); + expect(typeof profileData.totalTransforms).toStrictEqual("number"); + expect(typeof profileData.transformTimeMap).toStrictEqual("object"); + expect(typeof profileData.transformTimeMap.RenameVariables).toStrictEqual( "number" ); - eval(output.obfuscated); + eval(code); expect(called).toStrictEqual(true); }); }); diff --git a/test/templates/template.test.ts b/test/templates/template.test.ts index 6c172ff..ff9a455 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -1,6 +1,6 @@ import Obfuscator from "../../src/obfuscator"; import Template from "../../src/templates/template"; -import { stringLiteral } from "@babel/types"; +import * as t from "@babel/types"; describe("Template", () => { test("Variant #1: Error when invalid code passed in", () => { @@ -36,12 +36,12 @@ describe("Template", () => { return window.btoa(str) }`); - var functionDeclaration = Base64Template.single({ + var functionDeclaration = Base64Template.single({ name: "decodeBase64", }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(functionDeclaration.id.name).toStrictEqual("decodeBase64"); + expect(functionDeclaration.id!.name).toStrictEqual("decodeBase64"); // Generated code and check var output = Obfuscator.generateCode(functionDeclaration, { @@ -61,7 +61,7 @@ describe("Template", () => { return {getWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ + var functionDeclaration = Base64Template.single({ name: "decodeBase64", getWindowName: "newWindow", getWindow: Obfuscator.parseCode("var newWindow = {}").program.body, @@ -90,7 +90,7 @@ describe("Template", () => { return {getWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ + var functionDeclaration = Base64Template.single({ name: "decodeBase64", getWindowName: "newWindow", getWindow: () => { @@ -99,6 +99,7 @@ describe("Template", () => { }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(t.isBlockStatement(functionDeclaration.body)).toStrictEqual(true); expect(functionDeclaration.body.body[0].type).toStrictEqual( "VariableDeclaration" ); @@ -124,13 +125,14 @@ describe("Template", () => { return {NewWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ + var functionDeclaration = Base64Template.single({ name: "atob", NewWindowTemplate: NewWindowTemplate, NewWindowName: "newWindow", }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(t.isBlockStatement(functionDeclaration.body)).toStrictEqual(true); expect(functionDeclaration.body.body[0].type).toStrictEqual( "VariableDeclaration" ); @@ -156,7 +158,7 @@ describe("Template", () => { return {NewWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ + var functionDeclaration = Base64Template.single({ name: "atob", NewWindowTemplate: () => NewWindowTemplate, NewWindowName: "newWindow", @@ -185,7 +187,7 @@ describe("Template", () => { var functionDeclaration = Base64Template.single({ name: "decodeBase64", - property: stringLiteral("atob"), + property: t.stringLiteral("atob"), }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); @@ -196,7 +198,7 @@ describe("Template", () => { compact: true, }); - expect(output).toContain("return window['atob'](str)"); + expect(output).toContain('return window["atob"](str)'); }); test("Variant #9: AST string replacement with Literal node (callback)", async () => { @@ -207,7 +209,7 @@ describe("Template", () => { var functionDeclaration = Base64Template.single({ name: "decodeBase64", - property: () => stringLiteral("atob"), + property: () => t.stringLiteral("atob"), }); expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); @@ -218,6 +220,6 @@ describe("Template", () => { compact: true, }); - expect(output).toContain("return window['atob'](str)"); + expect(output).toContain('return window["atob"](str)'); }); }); diff --git a/test/transforms/deadCode.test.ts b/test/transforms/deadCode.test.ts index f4b35a4..5f74450 100644 --- a/test/transforms/deadCode.test.ts +++ b/test/transforms/deadCode.test.ts @@ -69,7 +69,7 @@ test("Variant #2: Preserve 'use strict' directive", async () => { }); // Ensure 'use strict' was preversed - expect(output.startsWith("'use strict'")).toStrictEqual(true); + expect(output.startsWith('"use strict"')).toStrictEqual(true); // Ensure Dead code was added expect(output).toContain("if"); diff --git a/test/transforms/extraction/objectExtraction.test.ts b/test/transforms/extraction/objectExtraction.test.ts index 76711b9..5db9f5b 100644 --- a/test/transforms/extraction/objectExtraction.test.ts +++ b/test/transforms/extraction/objectExtraction.test.ts @@ -444,7 +444,7 @@ test("Variant #14: Not apply to objects with non-init properties (method, set, g }); expect(output).toContain("TEST_OBJECT"); - expect(output).toContain("set "); + expect(output).toContain("set["); }); // https://github.com/MichaelXF/js-confuser/issues/78 diff --git a/test/transforms/identifier/globalConcealing.test.ts b/test/transforms/identifier/globalConcealing.test.ts index af3c74f..f563e7a 100644 --- a/test/transforms/identifier/globalConcealing.test.ts +++ b/test/transforms/identifier/globalConcealing.test.ts @@ -12,7 +12,7 @@ test("Variant #1: Hide global names (such as Math)", async () => { expect(output).not.toContain("Math.floor"); expect(output).not.toContain("=Math"); - expect(output).toContain("['Math']"); + expect(output).toContain('["Math"]'); expect(output).toContain("window"); }); @@ -28,7 +28,7 @@ test("Variant #2: Do not hide modified identifiers", async () => { globalConcealing: true, }); - expect(output).toContain("log'](Math)"); + expect(output).toContain('log"](Math)'); }); test("Variant #3: Properly hide in default parameter, function expression", async () => { diff --git a/test/transforms/preparation.test.ts b/test/transforms/preparation.test.ts index 72028a1..4388f94 100644 --- a/test/transforms/preparation.test.ts +++ b/test/transforms/preparation.test.ts @@ -75,7 +75,7 @@ test("Variant #4: Force Block Statements on While loops/With statement", async ( // Ensure parenthesis were added expect(output).toContain("{whileStatement()}"); - expect(output).toContain("{withStatement()}"); + expect(output).toContain("withStatement()"); }); test("Variant #5: Force object accessors to use strings instead", async () => { @@ -90,7 +90,7 @@ test("Variant #5: Force object accessors to use strings instead", async () => { ); // Ensure the member expression got changed to a string - expect(output).toContain("console['log']"); + expect(output).toContain('console["log"]'); }); test("Variant #6: Force object property keys to use strings instead", async () => { @@ -107,7 +107,7 @@ test("Variant #6: Force object property keys to use strings instead", async () = ); // Ensure key got changed to a string - expect(output).toContain("'myKey'"); + expect(output).toContain('"myKey"'); }); test("Variant #7: Force Variable declarations to be expanded", async () => { From 4e26a82c33ee860a04f8edbc574e2ca22dc584da Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 16:41:03 -0400 Subject: [PATCH 008/103] `Stack` renamed to -> `Variable Masking`, Single quote to double quotes --- src/transforms/variableMasking.ts | 31 ++++++++++------ test/code/ES6.test.ts | 2 +- .../identifier/movedDeclarations.test.ts | 2 +- ...{stack.test.ts => variableMasking.test.ts} | 36 +++++++++---------- 4 files changed, 41 insertions(+), 30 deletions(-) rename test/transforms/{stack.test.ts => variableMasking.test.ts} (94%) diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 250f8e4..9819176 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -1,15 +1,16 @@ import { PluginObj } from "@babel/core"; import { NodePath } from "@babel/traverse"; import { PluginArg } from "./plugin"; -import * as babelTypes from "@babel/types"; +import * as t from "@babel/types"; import Template from "../templates/template"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; +import { NodeSymbol, UNSAFE } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.VariableMasking); - const transformFunction = (path: NodePath) => { + const transformFunction = (path: NodePath) => { // Do not apply to getter/setter methods if (path.isObjectMethod() && path.node.kind !== "method") { return; @@ -26,10 +27,23 @@ export default ({ Plugin }: PluginArg): PluginObj => { } // Do not apply to functions with rest parameters or destructuring - if (path.node.params.some((param) => !babelTypes.isIdentifier(param))) { + if (path.node.params.some((param) => !t.isIdentifier(param))) { return; } + // Do not apply to 'use strict' functions + if ( + t.isBlockStatement(path.node.body) && + path.node.body.directives.some( + (directive) => directive.value.value === "use strict" + ) + ) { + return; + } + + // Do not apply to functions marked unsafe + if ((path.node as NodeSymbol)[UNSAFE]) return; + const functionName = ((path.isFunctionDeclaration() || path.isFunctionExpression()) && path.node.id?.name) ?? @@ -45,7 +59,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const stackMap = new Map(); for (const param of path.node.params) { - stackMap.set((param as babelTypes.Identifier).name, 0); + stackMap.set((param as t.Identifier).name, 0); } path.traverse({ @@ -85,8 +99,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { stackName: stackName, stackIndex: stackIndex, value: - (binding.path.node as any).init ?? - babelTypes.identifier("undefined"), + (binding.path.node as any).init ?? t.identifier("undefined"), }) ); } @@ -117,9 +130,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }); - path.node.params = [ - babelTypes.restElement(babelTypes.identifier(stackName)), - ]; + path.node.params = [t.restElement(t.identifier(stackName))]; path.scope.registerBinding("param", path.get("params.0") as NodePath, path); }; @@ -127,7 +138,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { Function: { - exit(path: NodePath) { + exit(path: NodePath) { transformFunction(path); }, }, diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index e003e97..3b92f6e 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -11,7 +11,7 @@ test.concurrent("Variant #1: ES6 code on High Preset", async () => { }); // Ensure 'use strict' directive is preserved - expect(output.startsWith("'use strict'")).toStrictEqual(true); + expect(output.startsWith('"use strict"')).toStrictEqual(true); var ranAllTest = false; eval(output); diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 4fd0fd0..625a3e5 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -179,7 +179,7 @@ test("Variant #8: Work with 'use strict'", async () => { // Ensure movedDeclarations applied and 'use strict' is still first // x cannot be moved as a parameter as 'use strict' disallows non-simple parameters - expect(output).toContain("function myFunction(){'use strict';var x=1;"); + expect(output).toContain('function myFunction(){"use strict";var x=1;'); var TEST_OUTPUT; eval(output); diff --git a/test/transforms/stack.test.ts b/test/transforms/variableMasking.test.ts similarity index 94% rename from test/transforms/stack.test.ts rename to test/transforms/variableMasking.test.ts index 9bc1f96..d21395d 100644 --- a/test/transforms/stack.test.ts +++ b/test/transforms/variableMasking.test.ts @@ -13,7 +13,7 @@ test("Variant #1: Replace all variables with array indexes (single variable)", a `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -40,7 +40,7 @@ test("Variant #2: Replace all variables with array indexes (multiple variables)" `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -66,7 +66,7 @@ test("Variant #3: Replace all variables with array indexes (uninitialized variab `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -91,7 +91,7 @@ test("Variant #4: Replace all variables with array indexes (parameters)", async `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -121,7 +121,7 @@ test("Variant #5: Replace all variables with array indexes (nested function)", a `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -158,7 +158,7 @@ test("Variant #6: Replace all variables with array indexes (nested class)", asyn `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -184,7 +184,7 @@ test("Variant #7: Replace variables defined within the function, and not run if `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -203,7 +203,7 @@ test("Variant #8: Work even when differing amount of arguments passed in", async `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -235,7 +235,7 @@ test("Variant #9: Replace all variables with array indexes (middle indexes use a `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -264,7 +264,7 @@ test("Variant #10: Guess execution order correctly (CallExpression, arguments ru `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -292,7 +292,7 @@ test("Variant #11: Guess execution order correctly (AssignmentExpression, right `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -318,7 +318,7 @@ test("Variant #12: Should not entangle floats or NaN", async () => { `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -369,7 +369,7 @@ test("Variant #13: Correctly entangle property keys", async () => { `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -437,7 +437,7 @@ test("Variant #14: Correctly entangle method definition keys", async () => { `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -467,7 +467,7 @@ test("Variant #15: Function with 'use strict' directive", async () => { useStrictFunction(); `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); // Stack will not apply to functions with 'use strict' directive @@ -505,7 +505,7 @@ test("Variant #16: Function with 'this'", async () => { TEST_OUTPUT = stackFunction() === undefined; `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); // Ensure stack applied @@ -533,7 +533,7 @@ test("Variant #17: Syncing arguments parameter", async () => { syncingArguments("Incorrect Value"); `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); function evalNoStrictMode(evalCode) { @@ -563,7 +563,7 @@ test("Variant #18: Preserve function.length property", async () => { TEST_OUTPUT = oneParameter.length + twoParameters.length + myObject.threeParameters.length; `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); var TEST_OUTPUT; From b0adc3ab499ea720b569b6d8f285fb0d41a389a2 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 21:32:43 -0400 Subject: [PATCH 009/103] Improve Flatten --- src/obfuscator.ts | 2 + src/transforms/extraction/objectExtraction.ts | 1 - src/transforms/flatten.ts | 355 +++++++++++++----- src/utils/ast-utils.ts | 66 +--- src/utils/function-utils.ts | 15 + test/transforms/flatten.test.ts | 4 +- 6 files changed, 292 insertions(+), 151 deletions(-) create mode 100644 src/utils/function-utils.ts diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 1657ec3..146efc8 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -29,6 +29,7 @@ import { Order } from "./order"; import movedDeclarations from "./transforms/identifier/movedDeclarations"; import renameLabels from "./transforms/renameLabels"; import rgf from "./transforms/rgf"; +import flatten from "./transforms/flatten"; export default class Obfuscator { plugins: { @@ -69,6 +70,7 @@ export default class Obfuscator { push(this.options.movedDeclarations, movedDeclarations); push(this.options.renameLabels, renameLabels); push(this.options.rgf, rgf); + push(this.options.flatten, flatten); push(true, finalizer); diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index f7e3a15..af99987 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -3,7 +3,6 @@ import { NodePath, PluginObj } from "@babel/core"; import { getMemberExpressionPropertyAsString, getObjectPropertyAsString, - isComputedMemberExpression, } from "../../utils/ast-utils"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 35ff01f..e9add00 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -1,106 +1,293 @@ import * as t from "@babel/types"; import { NodePath, PluginObj } from "@babel/core"; import { + getFunctionName, insertIntoNearestBlockScope, isReservedIdentifier, } from "../utils/ast-utils"; import { PluginArg } from "./plugin"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; +import { getRandomInteger, getRandomString } from "../utils/random-utils"; +import { NodeSymbol, UNSAFE } from "../constants"; +import { isFunctionStrictMode } from "../utils/function-utils"; +import { ok } from "assert"; + +const SKIP = Symbol("skip"); +interface NodeSkip { + [SKIP]?: boolean; +} + +function skipNode(node: T): T { + (node as NodeSkip)[SKIP] = true; + return node; +} export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Flatten); - function flattenFunction( - path: NodePath - ) { - const functionName = path.node.id ? path.node.id.name : "anonymous"; + function flattenFunction(fnPath: NodePath) { + // Skip if already processed + if ((fnPath.node as NodeSkip)[SKIP]) { + return; + } + + // Don't apply to generator functions + if (fnPath.node.generator) return; + + // Skip getter/setter methods + if (fnPath.isObjectMethod()) { + if (fnPath.node.kind !== "method") return; + } + + // Skip if marked as unsafe + if ((fnPath.node as NodeSymbol)[UNSAFE]) return; + + let functionName = getFunctionName(fnPath); + if (!t.isValidIdentifier(functionName, true)) { + functionName = "anonymous"; + } + if (!computeProbabilityMap(me.options.flatten, (x) => x, functionName)) { return; } - const flatObjectName = `__${functionName}_flat_object`; - const newFnName = `__${functionName}_flat_fn`; + me.log("Transforming", functionName); - const getterProps: t.ObjectMember[] = []; - const setterProps: t.ObjectMember[] = []; - const flatObjectProperties: t.ObjectMember[] = []; + const flatObjectName = `${me.getPlaceholder()}_flat_object`; + const newFnName = `${me.getPlaceholder()}_flat_${functionName}`; + + function generateProp(originalName) { + var newProp; + do { + newProp = originalName + getRandomInteger(0, 10); + // newProp = getRandomString(6); + } while ( + standardProps.has(newProp) || + typeofProps.has(newProp) || + functionCallProps.has(newProp) + ); + + return newProp; + } + + const standardProps = new Map(); + const typeofProps = new Map(); + const functionCallProps = new Map(); + + const identifierPaths: NodePath[] = []; // Traverse function to identify variables to be replaced with flat object properties - path.traverse({ - ReferencedIdentifier: { + fnPath.traverse({ + Identifier: { exit(identifierPath) { + if (identifierPath.isJSXIdentifier()) return; + if ((identifierPath.node as NodeSymbol)[UNSAFE]) return; const identifierName = identifierPath.node.name; - if (identifierName === "value") { - return false; + if ( + t.isFunctionDeclaration(identifierPath.parent) && + identifierPath.parent.id === identifierPath.node + ) + return; + if (identifierName === "arguments" || identifierName === "console") + return; + + var binding = identifierPath.scope.getBinding(identifierName); + if (!binding) { + return; } - if ( - identifierPath.isBindingIdentifier() || - !path.scope.hasBinding(identifierName) || - path.scope.hasOwnBinding(identifierName) || - path.scope.hasGlobal(identifierName) || - isReservedIdentifier(identifierPath.node) - ) { - // Skip identifiers that are local to this function + var cursor = identifierPath; + var isDefinedWithin = false; + do { + if (cursor.scope === binding.scope) { + isDefinedWithin = true; + } + cursor = cursor.parentPath; + } while (cursor && cursor !== fnPath); + + if (isDefinedWithin) { return; } - // Create getter and setter properties in the flat object - const getterPropName = `_prop_${identifierName}`; - const setterPropName = `_prop_${identifierName}`; - - console.log(identifierName, "Extracting into", getterPropName); - - getterProps.push( - t.objectMethod( - "get", - t.stringLiteral(getterPropName), - [], - t.blockStatement([ - t.returnStatement(t.identifier(identifierName)), - ]), - false, - false, - false + identifierPaths.push(identifierPath); + }, + }, + }); + + me.log( + `Function ${functionName}`, + "requires", + Array.from(new Set(identifierPaths.map((x) => x.node.name))) + ); + + for (var identifierPath of identifierPaths) { + const identifierName = identifierPath.node.name; + const isTypeof = identifierPath.parentPath.isUnaryExpression({ + operator: "typeof", + }); + const isFunctionCall = + identifierPath.parentPath.isCallExpression() && + identifierPath.parentPath.node.callee === identifierPath.node; + + if (isTypeof) { + var typeofProp = typeofProps.get(identifierName); + if (!typeofProp) { + typeofProp = generateProp(identifierName); + typeofProps.set(identifierName, typeofProp); + } + + identifierPath.parentPath + .replaceWith( + t.memberExpression( + t.identifier(flatObjectName), + t.stringLiteral(typeofProp), + true ) - ); - - setterProps.push( - t.objectMethod( - "set", - t.stringLiteral(setterPropName), - [t.identifier("value")], - t.blockStatement([ - t.expressionStatement( - t.assignmentExpression( - "=", - t.identifier(identifierName), - t.identifier("value") - ) - ), - ]), - false, - false, - false + )[0] + .skip(); + return; + } else if (isFunctionCall) { + let functionCallProp = functionCallProps.get(identifierName); + if (!functionCallProp) { + functionCallProp = generateProp(identifierName); + functionCallProps.set(identifierName, functionCallProp); + } + + // Replace identifier with a reference to the flat object property + identifierPath + .replaceWith( + t.memberExpression( + t.identifier(flatObjectName), + t.stringLiteral(functionCallProp), + true ) - ); + )[0] + .skip(); + } else { + let standardProp = standardProps.get(identifierName); + if (!standardProp) { + standardProp = generateProp(identifierName); + standardProps.set(identifierName, standardProp); + } - // Replace identifier with a reference to the flat object property - identifierPath.replaceWith( + // Replace identifier with a reference to the flat object property + identifierPath + .replaceWith( t.memberExpression( t.identifier(flatObjectName), - t.stringLiteral(getterPropName), + t.stringLiteral(standardProp), true ) - ); - identifierPath.skip(); - }, - }, - }); + )[0] + .skip(); + } + } + + // for (const prop of [...typeofProps.keys(), ...functionCallProps.keys()]) { + // if (!standardProps.has(prop)) { + // standardProps.set(prop, generateProp()); + // } + // } + + const flatObjectProperties: t.ObjectMember[] = []; + + for (var entry of standardProps) { + const [identifierName, objectProp] = entry; + + flatObjectProperties.push( + skipNode( + t.objectMethod( + "get", + t.stringLiteral(objectProp), + [], + t.blockStatement([t.returnStatement(t.identifier(identifierName))]), + false, + false, + false + ) + ) + ); + + var valueArgName = me.getPlaceholder() + "_value"; + flatObjectProperties.push( + skipNode( + t.objectMethod( + "set", + t.stringLiteral(objectProp), + [t.identifier(valueArgName)], + t.blockStatement([ + t.expressionStatement( + t.assignmentExpression( + "=", + t.identifier(identifierName), + t.identifier(valueArgName) + ) + ), + ]), + false, + false, + false + ) + ) + ); + } + + for (const entry of typeofProps) { + const [identifierName, objectProp] = entry; + + flatObjectProperties.push( + skipNode( + t.objectMethod( + "get", + t.stringLiteral(objectProp), + [], + t.blockStatement([ + t.returnStatement( + t.unaryExpression("typeof", t.identifier(identifierName)) + ), + ]), + false, + false, + false + ) + ) + ); + } - flatObjectProperties.push(...getterProps, ...setterProps); + for (const entry of functionCallProps) { + const [identifierName, objectProp] = entry; + + flatObjectProperties.push( + skipNode( + t.objectMethod( + "method", + t.stringLiteral(objectProp), + [t.restElement(t.identifier("args"))], + t.blockStatement([ + t.returnStatement( + t.callExpression(t.identifier(identifierName), [ + t.spreadElement(t.identifier("args")), + ]) + ), + ]), + false, + false, + false + ) + ) + ); + } + + // Create the new flattened function + const flattenedFunctionDeclaration = t.functionDeclaration( + t.identifier(newFnName), + [t.arrayPattern([...fnPath.node.params]), t.identifier(flatObjectName)], + t.blockStatement([...[...fnPath.node.body.body]]), + false, + fnPath.node.async + ); // Create the flat object variable declaration const flatObjectDeclaration = t.variableDeclaration("var", [ @@ -110,46 +297,40 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]); - // Create the new flattened function - const flattenedFunctionDeclaration = t.functionDeclaration( - t.identifier(newFnName), - [t.arrayPattern(path.node.params), t.identifier(flatObjectName)], - path.node.body - ); + var argName = me.getPlaceholder() + "_args"; // Replace original function body with a call to the flattened function - path.node.body = t.blockStatement([ + fnPath.node.body = t.blockStatement([ flatObjectDeclaration, t.returnStatement( t.callExpression(t.identifier(newFnName), [ - t.identifier("arguments"), + t.identifier(argName), t.identifier(flatObjectName), ]) ), ]); + fnPath.node.params = [t.restElement(t.identifier(argName))]; + fnPath.skip(); + + (flattenedFunctionDeclaration as NodeSkip)[SKIP] = true; + // Add the new flattened function at the top level - const newPaths = insertIntoNearestBlockScope( - path, - flattenedFunctionDeclaration - ); + var program = fnPath.findParent((p) => p.isProgram()); + var p = program.unshiftContainer("body", flattenedFunctionDeclaration); + program.scope.registerDeclaration(p[0]); - newPaths.forEach((newPath) => newPath.stop()); - path.stop(); + // p[0].scope.registerDeclaration(p[0].get("body.body.0")); + p[0].skip(); } return { visitor: { - FunctionDeclaration: { + Function: { exit(path: NodePath) { flattenFunction(path); }, }, - FunctionExpression: { - exit(path: NodePath) { - flattenFunction(path); - }, - }, }, }; }; diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 7185e65..2ea156c 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -62,7 +62,11 @@ export function insertIntoNearestBlockScope( // Traverse up the AST until we find a BlockStatement or Program let targetPath: NodePath = path; - while (targetPath && !t.isProgram(targetPath.node)) { + while ( + targetPath && + !t.isBlockStatement(targetPath.node) && + !t.isProgram(targetPath.node) + ) { targetPath = targetPath.parentPath; } @@ -111,66 +115,6 @@ export function hasNestedBinding(path: NodePath, name: string): boolean { return found; } -export function isModifiedIdentifier(path: NodePath): boolean { - const parent = path.parent; - - // Check if the identifier is on the left-hand side of an assignment - if (t.isAssignmentExpression(parent) && parent.left === path.node) { - return true; - } - - // Check if the identifier is in an update expression (like i++) - if (t.isUpdateExpression(parent) && parent.argument === path.node) { - return true; - } - - // Check if the identifier is being deleted - if ( - t.isUnaryExpression(parent) && - parent.operator === "delete" && - parent.argument === path.node - ) { - return true; - } - - // Check if the identifier is part of a destructuring pattern being assigned - if ( - (t.isObjectPattern(path.parent) || t.isArrayPattern(path.parent)) && - path.key === "elements" - ) { - return true; - } - - return false; -} - -/** - * Determines if the MemberExpression is computed. - * - * @param memberPath - The path of the MemberExpression node. - * @returns True if the MemberExpression is computed; false otherwise. - */ -export function isComputedMemberExpression( - memberExpression: t.MemberExpression -): boolean { - const property = memberExpression.property; - - if (!memberExpression.computed) { - // If the property is a non-computed identifier, it is not computed - if (t.isIdentifier(property)) { - return false; - } - } - - // If the property is a computed literal (string or number), it is not computed - if (t.isStringLiteral(property) || t.isNumericLiteral(property)) { - return false; - } - - // In all other cases, the property is computed - return true; -} - export function getObjectPropertyAsString( property: t.ObjectMember | t.ClassProperty | t.ClassMethod ): string { diff --git a/src/utils/function-utils.ts b/src/utils/function-utils.ts new file mode 100644 index 0000000..4ae9d6e --- /dev/null +++ b/src/utils/function-utils.ts @@ -0,0 +1,15 @@ +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; + +export function isFunctionStrictMode(path: NodePath) { + if ( + t.isBlockStatement(path.node.body) && + path.node.body.directives.some( + (directive) => directive.value.value === "use strict" + ) + ) { + return true; + } + + return false; +} diff --git a/test/transforms/flatten.test.ts b/test/transforms/flatten.test.ts index b2a53d2..ee3d178 100644 --- a/test/transforms/flatten.test.ts +++ b/test/transforms/flatten.test.ts @@ -353,7 +353,7 @@ test("Variant #12: Work with RGF enabled", async () => { expect(output).toContain("_flat_myFunction"); // Ensure RGF applied - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -518,7 +518,7 @@ test("Variant #17: Don't apply to generator functions", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); -test("Variant #18: Redefined variable in nested scope", async () => { +test("Variant #18: Redefined variable in nested scope + Rename Variables", async () => { var output = await JsConfuser( ` (function (){ From c1b98fed5248db7e7327b5e9e3963307a13c644c Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 21:49:31 -0400 Subject: [PATCH 010/103] Single Quote Refactoring --- .../extraction/duplicateLiteralsRemoval.ts | 3 +++ src/transforms/flatten.ts | 15 ++++++++++++--- src/transforms/string/stringSplitting.ts | 1 + .../extraction/duplicateLiteralsRemoval.test.ts | 2 +- test/transforms/string/stringCompression.test.ts | 8 ++++---- test/transforms/string/stringEncoding.test.ts | 2 +- 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 0eec17e..361da3c 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -66,6 +66,9 @@ export default ({ Plugin }: PluginArg): babel.PluginObj => { return; } + // Skip empty strings + if (typeof value === "string" && value.length === 0) return; + var index = -1; if (literalsMap.has(value)) { diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index e9add00..61d3389 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -40,6 +40,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (fnPath.node.kind !== "method") return; } + // Do not apply to arrow functions + if (t.isArrowFunctionExpression(fnPath.node)) return; + if (!t.isBlockStatement(fnPath.node.body)) return; + + // Do not apply to non-simple parameter functions + if (fnPath.node.params.find((x) => !t.isIdentifier(x))) return; + // Skip if marked as unsafe if ((fnPath.node as NodeSymbol)[UNSAFE]) return; @@ -98,7 +105,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - var cursor = identifierPath; + var cursor: NodePath = identifierPath; var isDefinedWithin = false; do { if (cursor.scope === binding.scope) { @@ -316,7 +323,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { (flattenedFunctionDeclaration as NodeSkip)[SKIP] = true; // Add the new flattened function at the top level - var program = fnPath.findParent((p) => p.isProgram()); + var program = fnPath.findParent((p) => + p.isProgram() + ) as NodePath; var p = program.unshiftContainer("body", flattenedFunctionDeclaration); program.scope.registerDeclaration(p[0]); @@ -327,7 +336,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { Function: { - exit(path: NodePath) { + exit(path: NodePath) { flattenFunction(path); }, }, diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 2005ef3..2f66bed 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -60,6 +60,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { parent.right = stringLiteral(last); path.replaceWith(parent); + path.skip(); }, }, }, diff --git a/test/transforms/extraction/duplicateLiteralsRemoval.test.ts b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts index 5760fa2..0107928 100644 --- a/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +++ b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts @@ -71,7 +71,7 @@ test("Variant #4: Do not remove empty strings", async () => { duplicateLiteralsRemoval: true, }); - expect(output).toContain("'','','',''"); + expect(output).toContain('"","","",""'); var TEST_ARRAY; diff --git a/test/transforms/string/stringCompression.test.ts b/test/transforms/string/stringCompression.test.ts index afa7730..23c1d4c 100644 --- a/test/transforms/string/stringCompression.test.ts +++ b/test/transforms/string/stringCompression.test.ts @@ -87,7 +87,7 @@ it("should be configurable by custom function option", async () => { TEST_OUTPUT_3 = "My String 3"; `; - var stringsFound = []; + var stringsFound: string[] = []; var output = await JsConfuser(code, { target: "node", @@ -105,9 +105,9 @@ it("should be configurable by custom function option", async () => { expect(stringsFound).toContain("My String 3"); // Ensure the strings got changed (except for "My String 2") - expect(output).not.toContain("TEST_OUTPUT_1='My String 1'"); - expect(output).toContain("TEST_OUTPUT_2='My String 2'"); - expect(output).not.toContain("TEST_OUTPUT_3='My String 3'"); + expect(output).not.toContain('TEST_OUTPUT_1="My String 1"'); + expect(output).toContain('TEST_OUTPUT_2="My String 2"'); + expect(output).not.toContain('TEST_OUTPUT_3="My String 3"'); // Make sure the code still works! var TEST_OUTPUT_1, TEST_OUTPUT_2, TEST_OUTPUT_3; diff --git a/test/transforms/string/stringEncoding.test.ts b/test/transforms/string/stringEncoding.test.ts index cdf3183..0385da3 100644 --- a/test/transforms/string/stringEncoding.test.ts +++ b/test/transforms/string/stringEncoding.test.ts @@ -91,5 +91,5 @@ test("Variant #5: Preserve 'use strict' directive", async () => { preset: "high", }); - expect(output.startsWith("'use strict'")).toStrictEqual(true); + expect(output.startsWith('"use strict"')).toStrictEqual(true); }); From 5108312e6ce7db5dcfd94281abfda8420a6c8d01 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 25 Aug 2024 21:59:39 -0400 Subject: [PATCH 011/103] Add `undefined` and `null` to Duplicate Literals Removal --- .../extraction/duplicateLiteralsRemoval.ts | 86 ++++++++++++------- .../duplicateLiteralsRemoval.test.ts | 2 +- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 361da3c..b51ea3b 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -1,26 +1,33 @@ -import * as babel from "@babel/core"; -import * as babelTypes from "@babel/types"; +import { PluginObj } from "@babel/core"; +import * as t from "@babel/types"; import { ok } from "assert"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; -type LiteralValue = string | number | boolean; +function fail(): never { + throw new Error("Assertion failed"); +} + +type LiteralValue = string | number | boolean | undefined | null; const createLiteral = (value: LiteralValue) => { + if (value === null) return t.nullLiteral(); + if (value === undefined) return t.identifier("undefined"); + switch (typeof value) { case "string": - return babelTypes.stringLiteral(value); + return t.stringLiteral(value); case "number": - return babelTypes.numericLiteral(value); + return t.numericLiteral(value); case "boolean": - return babelTypes.booleanLiteral(value); + return t.booleanLiteral(value); } ok(false); }; -export default ({ Plugin }: PluginArg): babel.PluginObj => { +export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.DuplicateLiteralsRemoval); return { @@ -33,35 +40,59 @@ export default ({ Plugin }: PluginArg): babel.PluginObj => { const literalsMap = new Map(); const firstTimeMap = new Map< LiteralValue, - babel.NodePath + babel.NodePath >(); - const arrayExpression = babelTypes.arrayExpression([]); + const arrayExpression = t.arrayExpression([]); const createMemberExpression = (index) => { - return babelTypes.memberExpression( - babelTypes.identifier(arrayName), - babelTypes.numericLiteral(index), + return t.memberExpression( + t.identifier(arrayName), + t.numericLiteral(index), true ); }; // Traverse through all nodes to find literals programPath.traverse({ - Literal(literalPath) { + "Literal|Identifier"(_path) { + const literalPath = _path as babel.NodePath< + t.Literal | t.Identifier + >; + let node = literalPath.node; - if ( - babelTypes.isNullLiteral(node) || - babelTypes.isRegExpLiteral(node) || - babelTypes.isTemplateLiteral(node) - ) - return; - const value = node.value; + var isUndefined = false; + if (literalPath.isIdentifier()) { + // Allow 'undefined' to be redefined + if ( + literalPath.scope.hasBinding(literalPath.node.name, { + noGlobals: true, + }) + ) + return; + + if (literalPath.node.name === "undefined") { + isUndefined = true; + } else { + return; + } + } + if (t.isRegExpLiteral(node) || t.isTemplateLiteral(node)) return; + + const value: LiteralValue = isUndefined + ? undefined + : t.isNullLiteral(node) + ? null + : t.isLiteral(node) + ? node.value + : fail(); if ( typeof value !== "string" && typeof value !== "number" && - typeof value !== "boolean" + typeof value !== "boolean" && + value !== null && + value !== undefined ) { return; } @@ -101,15 +132,9 @@ export default ({ Plugin }: PluginArg): babel.PluginObj => { if (arrayExpression.elements.length === 0) return; // Create the literals array declaration - const itemsArrayDeclaration = babelTypes.variableDeclaration( - "const", - [ - babelTypes.variableDeclarator( - babelTypes.identifier(arrayName), - arrayExpression - ), - ] - ); + const itemsArrayDeclaration = t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier(arrayName), arrayExpression), + ]); // Insert the array at the top of the program body var path = programPath.unshiftContainer( @@ -118,7 +143,6 @@ export default ({ Plugin }: PluginArg): babel.PluginObj => { )[0]; programPath.scope.registerDeclaration(path); - console.log(programPath.scope.bindings[arrayName].identifier); }, }, }, diff --git a/test/transforms/extraction/duplicateLiteralsRemoval.test.ts b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts index 0107928..403d061 100644 --- a/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +++ b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts @@ -194,7 +194,7 @@ test("Variant #9: Undefined as variable name", async () => { { target: "node", duplicateLiteralsRemoval: true } ); - expect(output).toContain("(undefined="); + expect(output).toContain("undefined="); eval(output); }); From 76419f14d92f83401b031d578d573a249b4cd134 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 26 Aug 2024 01:22:06 -0400 Subject: [PATCH 012/103] Improve String Concealing, Variable Masking, Remove ES5/Unused Locks --- CHANGELOG.md | 4 + src/obfuscator.ts | 4 +- src/options.ts | 165 ++----- src/order.ts | 2 - src/templates/bufferToStringTemplate.ts | 56 +++ src/templates/getGlobalTemplate.ts | 70 +++ src/templates/template.ts | 6 +- src/transforms/string/encoding.ts | 250 ++++++++++ src/transforms/string/stringCompression.ts | 3 + src/transforms/string/stringConcealing.ts | 219 ++++++++- src/transforms/string/stringSplitting.ts | 3 + src/transforms/variableMasking.ts | 112 +++-- src/utils/ast-utils.ts | 47 ++ test/index.test.ts | 5 +- .../expressionObfuscation.test.ts | 192 -------- test/transforms/es5/antiClass.test.ts | 427 ------------------ test/transforms/es5/antiDestructuring.test.ts | 157 ------- test/transforms/es5/antiES6Object.test.ts | 245 ---------- test/transforms/es5/antiTemplate.test.ts | 116 ----- test/transforms/es5/es5.test.ts | 110 ----- .../extraction/classExtraction.test.ts | 86 ---- .../string/stringConcealing.test.ts | 110 +++-- test/transforms/variableMasking.test.ts | 4 +- 23 files changed, 840 insertions(+), 1553 deletions(-) create mode 100644 src/templates/bufferToStringTemplate.ts create mode 100644 src/templates/getGlobalTemplate.ts create mode 100644 src/transforms/string/encoding.ts delete mode 100644 test/transforms/controlFlowFlattening/expressionObfuscation.test.ts delete mode 100644 test/transforms/es5/antiClass.test.ts delete mode 100644 test/transforms/es5/antiDestructuring.test.ts delete mode 100644 test/transforms/es5/antiES6Object.test.ts delete mode 100644 test/transforms/es5/antiTemplate.test.ts delete mode 100644 test/transforms/es5/es5.test.ts delete mode 100644 test/transforms/extraction/classExtraction.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b2fd5..7eb0f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Improved Moved Declaration's ability to move variables as unused function parameters +- Removed ES5 option - Use Babel Instead + +- Removed Browser Lock and OS Lock - Use custom Locks instead + # `1.7.3` Tamper Protection diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 146efc8..ad2b5a0 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -30,6 +30,7 @@ import movedDeclarations from "./transforms/identifier/movedDeclarations"; import renameLabels from "./transforms/renameLabels"; import rgf from "./transforms/rgf"; import flatten from "./transforms/flatten"; +import stringConcealing from "./transforms/string/stringConcealing"; export default class Obfuscator { plugins: { @@ -62,6 +63,7 @@ export default class Obfuscator { push(this.options.globalConcealing, globalConcealing); push(this.options.variableMasking, variableMasking); push(this.options.renameVariables, renameVariables); + push(this.options.stringConcealing, stringConcealing); push(this.options.stringCompression, stringCompression); push(this.options.stringSplitting, stringSplitting); push(this.options.shuffle, shuffle); @@ -150,7 +152,7 @@ export default class Obfuscator { code: code, }; } else { - throw new Error("Failed to obfuscate the code."); + throw new Error("Failed to generate code"); } } diff --git a/src/options.ts b/src/options.ts index 03f5595..c28601c 100644 --- a/src/options.ts +++ b/src/options.ts @@ -64,26 +64,12 @@ export interface ObfuscateOptions { */ minify?: boolean; - /** - * ### `es5` - * - * Converts output to ES5-compatible code. (`true/false`) - * - * Does not cover all cases such as Promises or Generator functions. Use [Babel](https://babel.dev/). - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - es5?: boolean; - renameLabels?: ProbabilityMap; /** * ### `renameVariables` * * Determines if variables should be renamed. (`true/false`) - * - Potency High - * - Resilience High - * - Cost Medium * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ @@ -150,10 +136,6 @@ export interface ObfuscateOptions { * * Use a number to control the percentage from 0 to 1. * - * - Potency High - * - Resilience High - * - Cost High - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ controlFlowFlattening?: ProbabilityMap; @@ -163,10 +145,6 @@ export interface ObfuscateOptions { * * Global Concealing hides global variables being accessed. (`true/false`) * - * - Potency Medium - * - Resilience High - * - Cost Low - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ globalConcealing?: ProbabilityMap; @@ -178,9 +156,7 @@ export interface ObfuscateOptions { * * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` * - * - Potency High - * - Resilience Medium - * - Cost Medium + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringCompression?: ProbabilityMap; @@ -191,9 +167,7 @@ export interface ObfuscateOptions { * * `"console"` -> `decrypt('<~@rH7+Dert~>')` * - * - Potency High - * - Resilience Medium - * - Cost Medium + * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringConcealing?: ProbabilityMap; @@ -204,10 +178,6 @@ export interface ObfuscateOptions { * * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` * - * - Potency Low - * - Resilience Low - * - Cost Low - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringEncoding?: ProbabilityMap; @@ -219,10 +189,6 @@ export interface ObfuscateOptions { * * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` * - * - Potency Medium - * - Resilience Medium - * - Cost Medium - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringSplitting?: ProbabilityMap; @@ -232,10 +198,6 @@ export interface ObfuscateOptions { * * Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`) * - * - Potency Medium - * - Resilience Low - * - Cost High - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ duplicateLiteralsRemoval?: ProbabilityMap; @@ -245,10 +207,6 @@ export interface ObfuscateOptions { * * Creates a middleman function to process function calls. (`true/false/0-1`) * - * - Potency Medium - * - Resilience Medium - * - Cost High - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ dispatcher?: ProbabilityMap; @@ -285,10 +243,6 @@ export interface ObfuscateOptions { * * [Similar to Jscrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * * ```js * // Input * function add3(x, y, z){ @@ -309,10 +263,6 @@ export interface ObfuscateOptions { * * Extracts object properties into separate variables. (`true/false`) * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * * ```js * // Input * var utils = { @@ -340,10 +290,6 @@ export interface ObfuscateOptions { * * Brings independent declarations to the highest scope. (`true/false`) * - * - Potency Medium - * - Resilience Medium - * - Cost High - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ flatten?: ProbabilityMap; @@ -355,10 +301,6 @@ export interface ObfuscateOptions { * * Use a number to control the percentage from 0 to 1. * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ deadCode?: ProbabilityMap; @@ -368,10 +310,6 @@ export interface ObfuscateOptions { * * Creates a calculator function to handle arithmetic and logical expressions. (`true/false/0-1`) * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ calculator?: ProbabilityMap; @@ -429,10 +367,6 @@ export interface ObfuscateOptions { * * Number should be in milliseconds. * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ startDate?: number | Date | false; @@ -444,10 +378,6 @@ export interface ObfuscateOptions { * * Number should be in milliseconds. * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ endDate?: number | Date | false; @@ -456,48 +386,10 @@ export interface ObfuscateOptions { * ### `lock.domainLock` * Array of regex strings that the `window.location.href` must follow. (`Regex[]` or `string[]`) * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ domainLock?: RegExp[] | string[] | false; - /** - * ### `lock.osLock` - * Array of operating-systems where the script is allowed to run. (`string[]`) - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * Allowed values: `"linux"`, `"windows"`, `"osx"`, `"android"`, `"ios"` - * - * Example: `["linux", "windows"]` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - osLock?: ("linux" | "windows" | "osx" | "android" | "ios")[] | false; - - /** - * ### `lock.browserLock` - * Array of browsers where the script is allowed to run. (`string[]`) - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * Allowed values: `"firefox"`, `"chrome"`, `"iexplorer"`, `"edge"`, `"safari"`, `"opera"` - * - * Example: `["firefox", "chrome"]` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - browserLock?: - | ("firefox" | "chrome" | "iexplorer" | "edge" | "safari" | "opera")[] - | false; - /** * ### `lock.integrity` * @@ -505,10 +397,6 @@ export interface ObfuscateOptions { * * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md). * - * - Potency Medium - * - Resilience High - * - Cost High - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ integrity?: ProbabilityMap; @@ -527,17 +415,50 @@ export interface ObfuscateOptions { * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ countermeasures?: string | boolean; + + customLocks?: { + /** + * Template lock code that must contain: + * + * - `{countermeasures}` + * + * The countermeasures function will be invoked when the lock is triggered. + * + * ```js + * if(window.navigator.userAgent.includes('Chrome')){ + * {countermeasures} + * } + * ``` + */ + code: string; + percentagePerBlock: number; + maxCount: number; + }[]; }; + customStringEncodings?: { + /** + * Template string decoder that must contain: + * + * - `{fnName}` + * + * This function will be invoked by the obfuscated code to DECODE the string. + * + * ```js + * function {fnName}(str){ + * return Buffer.from(str, 'base64').toString('utf-8') + * } + * ``` + */ + code: string; + encode: (str: string) => string; + }[]; + /** * ### `movedDeclarations` * * Moves variable declarations to the top of the context. (`true/false`) * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ movedDeclarations?: ProbabilityMap; @@ -548,10 +469,6 @@ export interface ObfuscateOptions { * An [Opaque Predicate](https://en.wikipedia.org/wiki/Opaque_predicate) is a predicate(true/false) that is evaluated at runtime, this can confuse reverse engineers * understanding your code. (`true/false`) * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ opaquePredicates?: ProbabilityMap; @@ -561,10 +478,6 @@ export interface ObfuscateOptions { * * Shuffles the initial order of arrays. The order is brought back to the original during runtime. (`"hash"/true/false/0-1`) * - * - Potency Medium - * - Resilience Low - * - Cost Low - * * | Mode | Description | * | --- | --- | * | `"hash"`| Array is shifted based on hash of the elements | diff --git a/src/order.ts b/src/order.ts index 224158e..2e1ee7c 100644 --- a/src/order.ts +++ b/src/order.ts @@ -50,7 +50,5 @@ export enum Order { RenameVariables = 30, - ES5 = 31, - Finalizer = 35, } diff --git a/src/templates/bufferToStringTemplate.ts b/src/templates/bufferToStringTemplate.ts new file mode 100644 index 0000000..864ee29 --- /dev/null +++ b/src/templates/bufferToStringTemplate.ts @@ -0,0 +1,56 @@ +import Template from "./template"; + +export const BufferToStringTemplate = new Template(` + {GetGlobalTemplate} + + var __globalObject = {getGlobalFnName}() || {}; + var __TextDecoder = __globalObject["TextDecoder"]; + var __Uint8Array = __globalObject["Uint8Array"]; + var __Buffer = __globalObject["Buffer"]; + var __String = __globalObject["String"] || String; + var __Array = __globalObject["Array"] || Array; + + var utf8ArrayToStr = (function () { + var charCache = new __Array(128); // Preallocate the cache for the common single byte chars + var charFromCodePt = __String["fromCodePoint"] || __String["fromCharCode"]; + var result = []; + + return function (array) { + var codePt, byte1; + var buffLen = array["length"]; + + result["length"] = 0; + + for (var i = 0; i < buffLen;) { + byte1 = array[i++]; + + if (byte1 <= 0x7F) { + codePt = byte1; + } else if (byte1 <= 0xDF) { + codePt = ((byte1 & 0x1F) << 6) | (array[i++] & 0x3F); + } else if (byte1 <= 0xEF) { + codePt = ((byte1 & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F); + } else if (__String["fromCodePoint"]) { + codePt = ((byte1 & 0x07) << 18) | ((array[i++] & 0x3F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F); + } else { + codePt = 63; // Cannot convert four byte code points, so use "?" instead + i += 3; + } + + result["push"](charCache[codePt] || (charCache[codePt] = charFromCodePt(codePt))); + } + + return result["join"](''); + }; + })(); + + function {name}(buffer){ + if(typeof __TextDecoder !== "undefined" && __TextDecoder) { + return new __TextDecoder()["decode"](new __Uint8Array(buffer)); + } else if(typeof __Buffer !== "undefined" && __Buffer) { + return __Buffer["from"](buffer)["toString"]("utf-8"); + } else { + return utf8ArrayToStr(buffer); + } + } +`); diff --git a/src/templates/getGlobalTemplate.ts b/src/templates/getGlobalTemplate.ts new file mode 100644 index 0000000..1dc935b --- /dev/null +++ b/src/templates/getGlobalTemplate.ts @@ -0,0 +1,70 @@ +import { NodePath } from "@babel/traverse"; +import { PluginInstance } from "../transforms/plugin"; +import Template from "./template"; + +export const createGetGlobalTemplate = ( + pluginInstance: PluginInstance, + path: NodePath +) => { + var options = pluginInstance.options; + // if (options.lock?.tamperProtection) { + // return new Template(` + // function {getGlobalFnName}(){ + // var localVar = false; + // eval(${transform.jsConfuserVar("localVar")} + " = true") + // if (!localVar) { + // {countermeasures} + // } + + // const root = eval("this"); + // return root; + // } + // `).setDefaultVariables({ + // countermeasures: transform.lockTransform.getCounterMeasuresCode( + // object, + // parents + // ), + // }); + // } + + return GetGlobalTemplate; +}; + +const GetGlobalTemplate = new Template(` + function {getGlobalFnName}(){ + var array = [ + function (){ + return globalThis + }, + function (){ + return global + }, + function (){ + return window + }, + function (){ + return new Function("return this")() + } + ]; + + var bestMatch + var itemsToSearch = [] + try { + bestMatch = Object + itemsToSearch["push"](("")["__proto__"]["constructor"]["name"]) + } catch(e) { + + } + A: for(var i = 0; i < array["length"]; i++) { + try { + bestMatch = array[i]() + for(var j = 0; j < itemsToSearch["length"]; j++) { + if(typeof bestMatch[itemsToSearch[j]] === "undefined") continue A; + } + return bestMatch + } catch(e) {} + } + + return bestMatch || this; + } +`); diff --git a/src/templates/template.ts b/src/templates/template.ts index 5c163bb..8f5780d 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -160,11 +160,13 @@ export default class Template { return nodes[0] as T; } - expression(variables: TemplateVariables = {}): babelTypes.Expression { + expression( + variables: TemplateVariables = {} + ): T { const statement = this.single(variables); babelTypes.assertExpressionStatement(statement); - return statement.expression; + return statement.expression as T; } } diff --git a/src/transforms/string/encoding.ts b/src/transforms/string/encoding.ts new file mode 100644 index 0000000..5f5205e --- /dev/null +++ b/src/transforms/string/encoding.ts @@ -0,0 +1,250 @@ +import Template from "../../templates/template"; +import { choice, shuffle } from "../../utils/random-utils"; +import * as t from "@babel/types"; + +/** + * Defines an encoding implementation the Obfuscator + */ +export interface EncodingImplementation { + identity: string; + + encode(s: string): string; + decode(s: string): string; + template: Template; +} + +let _hasAllEncodings = false; +export function hasAllEncodings() { + return _hasAllEncodings; +} + +export function createEncodingImplementation(): EncodingImplementation { + if (_hasAllEncodings) { + return EncodingImplementations[ + choice(Object.keys(EncodingImplementations)) + ]; + } + + // create base91 encoding + let strTable = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"'; + + // shuffle table + strTable = shuffle(strTable.split("")).join(""); + + let identity = "base91_" + strTable; + + if (EncodingImplementations.hasOwnProperty(identity)) { + _hasAllEncodings = true; + return EncodingImplementations[identity]; + } + + var encodingImplementation = { + identity, + encode(str) { + const table = strTable; + + const raw = Buffer.from(str, "utf-8"); + const len = raw.length; + let ret = ""; + + let n = 0; + let b = 0; + + for (let i = 0; i < len; i++) { + b |= raw[i] << n; + n += 8; + + if (n > 13) { + let v = b & 8191; + if (v > 88) { + b >>= 13; + n -= 13; + } else { + v = b & 16383; + b >>= 14; + n -= 14; + } + ret += table[v % 91] + table[(v / 91) | 0]; + } + } + + if (n) { + ret += table[b % 91]; + if (n > 7 || b > 90) ret += table[(b / 91) | 0]; + } + + return ret; + }, + decode(str) { + const table = strTable; + + const raw = "" + (str || ""); + const len = raw.length; + const ret = []; + + let b = 0; + let n = 0; + let v = -1; + + for (let i = 0; i < len; i++) { + const p = table.indexOf(raw[i]); + if (p === -1) continue; + if (v < 0) { + v = p; + } else { + v += p * 91; + b |= v << n; + n += (v & 8191) > 88 ? 13 : 14; + do { + ret.push(b & 0xff); + b >>= 8; + n -= 8; + } while (n > 7); + v = -1; + } + } + + if (v > -1) { + ret.push((b | (v << n)) & 0xff); + } + + return Buffer.from(ret).toString("utf-8"); + }, + template: new Template(` + function {__fnName__}(str){ + var table = {__strTable__}; + + var raw = "" + (str || ""); + var len = raw.length; + var ret = []; + + var b = 0; + var n = 0; + var v = -1; + + for (var i = 0; i < len; i++) { + var p = table.indexOf(raw[i]); + if (p === -1) continue; + if (v < 0) { + v = p; + } else { + v += p * 91; + b |= v << n; + n += (v & 8191) > 88 ? 13 : 14; + do { + ret.push(b & 0xff); + b >>= 8; + n -= 8; + } while (n > 7); + v = -1; + } + } + + if (v > -1) { + ret.push((b | (v << n)) & 0xff); + } + + return {__bufferToStringFunction__}(ret); + } + `).setDefaultVariables({ + __strTable__: t.stringLiteral(strTable), + }), + }; + + EncodingImplementations[identity] = encodingImplementation; + return encodingImplementation; +} + +export const EncodingImplementations: { + [encodingIdentity: string]: EncodingImplementation; +} = { + /* ascii85: { This implementation is flaky and causes decoding errors + encode(a) { + var b, c, d, e, f, g, h, i, j, k; + // @ts-ignore + for ( + // @ts-ignore + !/[^\x00-\xFF]/.test(a), + b = "\x00\x00\x00\x00".slice(a.length % 4 || 4), + a += b, + c = [], + d = 0, + e = a.length; + e > d; + d += 4 + ) + (f = + (a.charCodeAt(d) << 24) + + (a.charCodeAt(d + 1) << 16) + + (a.charCodeAt(d + 2) << 8) + + a.charCodeAt(d + 3)), + 0 !== f + ? ((k = f % 85), + (f = (f - k) / 85), + (j = f % 85), + (f = (f - j) / 85), + (i = f % 85), + (f = (f - i) / 85), + (h = f % 85), + (f = (f - h) / 85), + (g = f % 85), + c.push(g + 33, h + 33, i + 33, j + 33, k + 33)) + : c.push(122); + return ( + (function (a, b) { + for (var c = b; c > 0; c--) a.pop(); + })(c, b.length), + "<~" + String.fromCharCode.apply(String, c) + "~>" + ); + }, + decode(a) { + var c, + d, + e, + f, + g, + h = String, + l = "length", + w = 255, + x = "charCodeAt", + y = "slice", + z = "replace"; + for ( + "<~" === a[y](0, 2) && "~>" === a[y](-2), + a = a[y](2, -2)[z](/s/g, "")[z]("z", "!!!!!"), + c = "uuuuu"[y](a[l] % 5 || 5), + a += c, + e = [], + f = 0, + g = a[l]; + g > f; + f += 5 + ) + (d = + 52200625 * (a[x](f) - 33) + + 614125 * (a[x](f + 1) - 33) + + 7225 * (a[x](f + 2) - 33) + + 85 * (a[x](f + 3) - 33) + + (a[x](f + 4) - 33)), + e.push(w & (d >> 24), w & (d >> 16), w & (d >> 8), w & d); + return ( + (function (a, b) { + for (var c = b; c > 0; c--) a.pop(); + })(e, c[l]), + h.fromCharCode.apply(h, e) + ); + }, + template: Template(` + function {name}(a, LL = ["fromCharCode", "apply"]) { + var c, d, e, f, g, h = String, l = "length", w = 255, x = "charCodeAt", y = "slice", z = "replace"; + for ("<~" === a[y](0, 2) && "~>" === a[y](-2), a = a[y](2, -2)[z](/\s/g, "")[z]("z", "!!!!!"), + c = "uuuuu"[y](a[l] % 5 || 5), a += c, e = [], f = 0, g = a[l]; g > f; f += 5) d = 52200625 * (a[x](f) - 33) + 614125 * (a[x](f + 1) - 33) + 7225 * (a[x](f + 2) - 33) + 85 * (a[x](f + 3) - 33) + (a[x](f + 4) - 33), + e.push(w & d >> 24, w & d >> 16, w & d >> 8, w & d); + return function(a, b) { + for (var c = b; c > 0; c--) a.pop(); + }(e, c[l]), h[LL[0]][LL[1]](h, e); + } + `), + }, */ +}; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 210484c..1f1adb7 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -3,6 +3,7 @@ import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; +import { ensureComputedExpression } from "../../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringCompression); @@ -36,6 +37,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { stringMap.set(originalValue, index); } + ensureComputedExpression(path); + path.replaceWith( t.callExpression(t.identifier(stringFn), [ t.numericLiteral(index), diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 4fb1c16..8a21582 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -3,48 +3,217 @@ import { NodePath, PluginObj } from "@babel/core"; import Template from "../../templates/template"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; +import { computeProbabilityMap } from "../../probability"; +import { + createEncodingImplementation, + EncodingImplementation, + hasAllEncodings, +} from "./encoding"; +import { ok } from "assert"; +import { BufferToStringTemplate } from "../../templates/bufferToStringTemplate"; +import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; +import { + ensureComputedExpression, + isModuleImport, +} from "../../utils/ast-utils"; +import { + chance, + getRandomInteger, + getRandomString, +} from "../../utils/random-utils"; + +interface StringConcealingInterface { + encodingImplementation: EncodingImplementation; + fnName: string; +} + +const STRING_CONCEALING = Symbol("StringConcealing"); + +interface NodeStringConcealing { + [STRING_CONCEALING]?: StringConcealingInterface; +} export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringConcealing); - var decoderName = me.getPlaceholder() + "_decoder"; + + const blocks: NodePath[] = []; + const stringMap = new Map(); + const stringArrayName = me.getPlaceholder() + "_array"; return { visitor: { Program: { exit(programPath: NodePath) { - // Create the decoder function - const decoderFunction = new Template(` - function {decoderName}(encoded) { - return Buffer.from(encoded, "base64").toString("utf-8"); + let mainEncodingImplementation: EncodingImplementation; + + // Create a main encoder function for the Program + (programPath.node as NodeStringConcealing)[STRING_CONCEALING] = { + encodingImplementation: (mainEncodingImplementation = + createEncodingImplementation()), + fnName: me.getPlaceholder() + "_MAIN_STR", + }; + blocks.push(programPath); + + // Use that encoder function for these fake strings + const fakeStringCount = getRandomInteger(75, 125); + for (var i = 0; i < fakeStringCount; i++) { + const fakeString = getRandomString(getRandomInteger(5, 50)); + stringMap.set( + mainEncodingImplementation.encode(fakeString), + stringMap.size + ); } - `).single({ - decoderName, + + programPath.traverse({ + StringLiteral: { + exit(path: NodePath) { + const originalValue = path.node.value; + + // Ignore require() calls / Import statements + if (isModuleImport(path)) { + return; + } + + // Minimum length of 3 characters + if (originalValue.length < 3) { + return; + } + + // Check user setting + if ( + !computeProbabilityMap( + me.options.stringConcealing, + (x) => x, + originalValue + ) + ) { + return; + } + + let block = path.findParent( + (p) => + p.isBlock() && + !!(p.node as NodeStringConcealing)?.[STRING_CONCEALING] + ) as NodePath; + + let stringConcealingInterface = ( + block?.node as NodeStringConcealing + )?.[STRING_CONCEALING] as StringConcealingInterface; + + if ( + !block || + (!hasAllEncodings() && chance(75 - blocks.length)) + ) { + // Create a new encoder function + // Select random block parent (or Program) + block = path.findParent((p) => + p.isBlock() + ) as NodePath; + + const stringConcealingNode = + block.node as NodeStringConcealing; + + // Ensure not to overwrite the previous encoders + if (!stringConcealingNode[STRING_CONCEALING]) { + // Create a new encoding function for this block + const encodingImplementation = + createEncodingImplementation(); + const fnName = + me.getPlaceholder() + "_STR_" + blocks.length; + + stringConcealingInterface = { + encodingImplementation, + fnName: fnName, + }; + + // Save this info in the AST for future strings + stringConcealingNode[STRING_CONCEALING] = + stringConcealingInterface; + + blocks.push(block); + } + } + + ok(stringConcealingInterface); + + const encodedValue = + stringConcealingInterface.encodingImplementation.encode( + originalValue + ); + let index = stringMap.get(encodedValue); + + if (typeof index === "undefined") { + index = stringMap.size; + stringMap.set(encodedValue, index); + } + + // Ensure the string is computed so we can replace it with complex call expression + ensureComputedExpression(path); + + // Replace the string literal with a call to the decoder function + path.replaceWith( + t.callExpression( + t.identifier(stringConcealingInterface.fnName), + [t.numericLiteral(index)] + ) + ); + + // Skip the transformation for the newly created node + path.skip(); + }, + }, }); - // Insert the decoder function at the top of the program body - var path = programPath.unshiftContainer("body", decoderFunction)[0]; + const bufferToStringName = me.getPlaceholder() + "_bufferToString"; + const getGlobalFnName = me.getPlaceholder() + "_getGlobal"; - // Skip transformation for the inserted decoder function - path.skip(); - }, - }, + const bufferToString = BufferToStringTemplate.compile({ + GetGlobalTemplate: createGetGlobalTemplate(me, programPath), + getGlobalFnName: getGlobalFnName, + name: bufferToStringName, + }); - StringLiteral: { - exit(path: NodePath) { - const originalValue = path.node.value; - const encodedValue = Buffer.from(originalValue, "utf-8").toString( - "base64" - ); + programPath.unshiftContainer("body", bufferToString); - // Replace the string literal with a call to the decoder function - path.replaceWith( - t.callExpression(t.identifier(decoderName), [ - t.stringLiteral(encodedValue), + // Create the string array + programPath.unshiftContainer( + "body", + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(stringArrayName), + t.arrayExpression( + Array.from(stringMap.keys()).map((x) => t.stringLiteral(x)) + ) + ), ]) ); - // Skip the transformation for the newly created node - path.skip(); + for (var block of blocks) { + const { encodingImplementation, fnName } = ( + block.node as NodeStringConcealing + )[STRING_CONCEALING] as StringConcealingInterface; + + const decodeFnName = fnName + "_d"; + + // The decoder function + const decoder = encodingImplementation.template.compile({ + __fnName__: decodeFnName, + __bufferToStringFunction__: bufferToStringName, + }); + + // The main function to get the string value + const retrieveFunctionDeclaration = new Template(` + function ${fnName}(index) { + return ${decodeFnName}(${stringArrayName}[index]); + } + `).single(); + + block.unshiftContainer("body", [ + ...decoder, + retrieveFunctionDeclaration, + ]); + block.skip(); + } }, }, }, diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 2f66bed..3d1f66c 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -5,6 +5,7 @@ import { computeProbabilityMap } from "../../probability"; import { binaryExpression, stringLiteral } from "@babel/types"; import { ok } from "assert"; import { Order } from "../../order"; +import { ensureComputedExpression } from "../../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringSplitting); @@ -59,6 +60,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { parent.right = stringLiteral(last); + ensureComputedExpression(path); + path.replaceWith(parent); path.skip(); }, diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 9819176..9ea38f9 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -6,48 +6,40 @@ import Template from "../templates/template"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; +import { getFunctionName } from "../utils/ast-utils"; +import { isFunctionStrictMode } from "../utils/function-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.VariableMasking); - const transformFunction = (path: NodePath) => { + const transformFunction = (fnPath: NodePath) => { // Do not apply to getter/setter methods - if (path.isObjectMethod() && path.node.kind !== "method") { + if (fnPath.isObjectMethod() && fnPath.node.kind !== "method") { return; } // Do not apply to class getters/setters - if (path.isClassMethod() && path.node.kind !== "method") { + if (fnPath.isClassMethod() && fnPath.node.kind !== "method") { return; } // Do not apply to async or generator functions - if (path.node.generator || path.node.async) { + if (fnPath.node.generator || fnPath.node.async) { return; } // Do not apply to functions with rest parameters or destructuring - if (path.node.params.some((param) => !t.isIdentifier(param))) { + if (fnPath.node.params.some((param) => !t.isIdentifier(param))) { return; } // Do not apply to 'use strict' functions - if ( - t.isBlockStatement(path.node.body) && - path.node.body.directives.some( - (directive) => directive.value.value === "use strict" - ) - ) { - return; - } + if (isFunctionStrictMode(fnPath)) return; // Do not apply to functions marked unsafe - if ((path.node as NodeSymbol)[UNSAFE]) return; + if ((fnPath.node as NodeSymbol)[UNSAFE]) return; - const functionName = - ((path.isFunctionDeclaration() || path.isFunctionExpression()) && - path.node.id?.name) ?? - "anonymous"; + const functionName = getFunctionName(fnPath); if ( !computeProbabilityMap(me.options.variableMasking, (x) => x, functionName) @@ -55,14 +47,15 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - const stackName = me.generateRandomIdentifier() + "_stack"; + const stackName = me.generateRandomIdentifier() + "_varMask"; const stackMap = new Map(); + let needsStack = false; - for (const param of path.node.params) { - stackMap.set((param as t.Identifier).name, 0); + for (const param of fnPath.node.params) { + stackMap.set((param as t.Identifier).name, stackMap.size); } - path.traverse({ + fnPath.traverse({ BindingIdentifier(identifierPath) { const binding = identifierPath.scope.getBinding( identifierPath.node.name @@ -72,11 +65,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - if (binding.constantViolations.length > 0) { + if (binding.kind === "const" && binding.constantViolations.length > 0) { return; } - if (binding.scope !== path.scope) { + if (binding.scope !== fnPath.scope) { return; } @@ -94,16 +87,46 @@ export default ({ Plugin }: PluginArg): PluginObj => { stackIndex = stackMap.size; stackMap.set(identifierPath.node.name, stackIndex); + let value: t.Expression = + (binding.path.node as any).init ?? t.identifier("undefined"); + + needsStack = true; binding.path.parentPath.replaceWith( new Template(`{stackName}[{stackIndex}] = {value}`).single({ stackName: stackName, stackIndex: stackIndex, - value: - (binding.path.node as any).init ?? t.identifier("undefined"), + value: value, }) ); } + binding.constantViolations.forEach((constantViolation) => { + switch (constantViolation.type) { + case "AssignmentExpression": + case "UpdateExpression": + // Find the Identifier that is being assigned to + // and replace it with the stack variable + constantViolation.traverse({ + Identifier(path) { + if (path.node.name === identifierPath.node.name) { + path.replaceWith( + new Template(`{stackName}[{stackIndex}]`).expression({ + stackName: stackName, + stackIndex: stackIndex, + }) + ); + } + }, + }); + + break; + default: + throw new Error( + `Unsupported constant violation type: ${constantViolation.type}` + ); + } + }); + binding.referencePaths.forEach((refPath) => { if (!refPath.isReferencedIdentifier()) return; @@ -118,21 +141,42 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - refPath.replaceWith( - new Template(`{stackName}[{stackIndex}]`).single({ - stackName: stackName, - stackIndex: stackIndex, - }) - ); + const memberExpression = new Template( + `{stackName}[{stackIndex}]` + ).expression({ + stackName: stackName, + stackIndex: stackIndex, + }); + + needsStack = true; + if ( + t.isCallExpression(refPath.parent) && + refPath.parent.callee === refPath.node + ) { + refPath.parent.callee = t.memberExpression( + memberExpression, + t.stringLiteral("call"), + true + ); + refPath.parent.arguments.unshift(t.thisExpression()); + } else { + refPath.replaceWith(memberExpression); + } }); identifierPath.scope.removeBinding(identifierPath.node.name); }, }); - path.node.params = [t.restElement(t.identifier(stackName))]; + if (!needsStack) return; + + fnPath.node.params = [t.restElement(t.identifier(stackName))]; - path.scope.registerBinding("param", path.get("params.0") as NodePath, path); + fnPath.scope.registerBinding( + "param", + fnPath.get("params.0") as NodePath, + fnPath + ); }; return { diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 2ea156c..2cb8be2 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -2,6 +2,34 @@ import * as t from "@babel/types"; import { NodePath } from "@babel/core"; import { ok } from "assert"; +/** + * Ensures a `String Literal` is 'computed' before replacing it with a more complex expression. + * + * ```js + * // Input + * { + * "myToBeEncodedString": "value" + * } + * + * // Output + * { + * ["myToBeEncodedString"]: "value" + * } + * ``` + * @param path + */ +export function ensureComputedExpression(path: NodePath) { + if ( + (t.isObjectMember(path.parent) || + t.isClassMethod(path.parent) || + t.isClassProperty(path.parent)) && + path.parent.key === path.node && + !path.parent.computed + ) { + path.parent.computed = true; + } +} + /** * Retrieves a function name from debugging purposes. * - Function Declaration / Expression @@ -42,6 +70,25 @@ export function getFunctionName(path: NodePath): string { return "anonymous"; } +export function isModuleImport(path: NodePath) { + // Import Declaration + if (path.parentPath.isImportDeclaration()) { + return true; + } + + // Dynamic Import / require() call + if ( + t.isCallExpression(path.parent) && + (t.isIdentifier(path.parent.callee, { name: "require" }) || + t.isImport(path.parent.callee)) && + path.node === path.parent.arguments[0] + ) { + return true; + } + + return false; +} + export function getParentFunctionOrProgram( path: NodePath ): NodePath { diff --git a/test/index.test.ts b/test/index.test.ts index cd2b845..7a9e45a 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -159,7 +159,10 @@ describe("obfuscateAST", () => { }; var before = JSON.stringify(AST); - JsConfuser.obfuscateAST(AST as any, { target: "node", es5: true }); + JsConfuser.obfuscateAST(AST as any, { + target: "node", + renameVariables: true, + }); var after = JSON.stringify(AST); diff --git a/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts b/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts deleted file mode 100644 index 2a5cc26..0000000 --- a/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { criticalFunctionTag } from "../../../src/constants"; -import JsConfuser from "../../../src/index"; - -// Enable Control Flow Flattening's 'Expression Obfuscation' but skips all CFF switch transformations -function fakeEnabled() { - return false; -} - -test("Variant #1: Join expressions in a sequence expression", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT=0; - TEST_OUTPUT++; - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT=0,TEST_OUTPUT++"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(1); -}); - -test("Variant #2: If Statement", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT=0; - if(true){ - TEST_OUTPUT++; - } - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT=0,true"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(1); -}); - -test("Variant #3: ForStatement (Variable Declaration initializer)", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT=0; - for(var i =0; i < 10; i++){ - TEST_OUTPUT++; - } - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT=0,0"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(10); -}); - -test("Variant #4: ForStatement (Assignment expression initializer)", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT=0; - for(i = 0; i < 10; i++){ - TEST_OUTPUT++; - } - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT=0,0"); - - var TEST_OUTPUT, i; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(10); -}); - -test("Variant #5: Return statement", async () => { - var output = await JsConfuser( - ` - function fn(){ - TEST_OUTPUT = 10; - return "Value"; - } - fn(); - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT=10,'Value'"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(10); -}); - -test("Variant #6: Return statement (no argument)", async () => { - var output = await JsConfuser( - ` - function fn(){ - TEST_OUTPUT = 10; - return; - } - fn(); - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT=10,undefined"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(10); -}); - -test("Variant #7: Throw statement", async () => { - var output = await JsConfuser( - ` - function fn(){ - TEST_OUTPUT = "Correct Value"; - throw new Error("My Error") - } - try { - fn(); - } catch(e){ - - } - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("TEST_OUTPUT='Correct Value',new Error"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual("Correct Value"); -}); - -test("Variant #8: Variable declaration", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT = "Correct Value"; - var x = 1, y = 2; - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("(TEST_OUTPUT='Correct Value'"); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual("Correct Value"); -}); - -test("Variant #9: Variable declaration (no initializer)", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT = "Correct Value"; - var x,y; - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain(`(TEST_OUTPUT='Correct Value',undefined)`); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual("Correct Value"); -}); - -test("Variant #10: Use function call", async () => { - var output = await JsConfuser( - ` - TEST_OUTPUT = "Correct Value"; - var x,y; - `, - { target: "node", controlFlowFlattening: fakeEnabled } - ); - - expect(output).toContain("function"); - expect(output).toContain(criticalFunctionTag); -}); diff --git a/test/transforms/es5/antiClass.test.ts b/test/transforms/es5/antiClass.test.ts deleted file mode 100644 index 1ff1ab2..0000000 --- a/test/transforms/es5/antiClass.test.ts +++ /dev/null @@ -1,427 +0,0 @@ -import JsConfuser from "../../../src/index"; - -it("should convert class declarations to variable declarations", async () => { - var output = await JsConfuser( - ` - class Person { - constructor(name){this.name = name} - getName(){return this.name} - }; - - var person = new Person("John"); - - TEST_VARIABLE = person.name`, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("John"); -}); - -it("should convert class expressions to function expressions", async () => { - var output = await JsConfuser( - ` - - var PersonClass = (class Person { constructor(name){this.name = name} getName(){return this.name} }); - var person = new PersonClass("John"); - TEST_VARIABLE = person.getName()`, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("John"); -}); - -it("should support static methods", async () => { - var output = await JsConfuser( - ` - class Person { - static createPerson(name){ - return new Person(name) - } - constructor(name){ - this.name = name - } - getName(){ - return this.name - } - }; - - var person = Person.createPerson("John"); - - TEST_VARIABLE = person.name; - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("John"); -}); - -it("should support computed property names", async () => { - var output = await JsConfuser( - ` - - var constructorKey = "constructor"; - var getNameKey = "getName"; - - class Person { - static createPerson(name){ - return new Person(name) - } - [constructorKey](name){ - this.name = name - } - [getNameKey](){ - return this.name - } - }; - - var person = new Person("John"); - - TEST_VARIABLE = person.name; - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("John"); -}); - -it("should support extending from a super class", async () => { - var output = await JsConfuser( - ` - class Shape { - isShape(){ - return true - } - - getShapeID(){ - return 0; - } - } - - class Rectangle extends Shape { - - constructor(width, height){ - super() - this.width = width; - this.height = height; - } - - getShapeID(){ - return super.getShapeID() + 10; - } - - getArea(){ - return this.width * this.height; - } - } - - - var rect = new Rectangle(10, 20) - - TEST_AREA = rect.getArea() - TEST_SHAPE_ID = rect.getShapeID() - TEST_IS_SHAPE = rect.isShape() - `, - { target: "node", es5: true } - ); - - expect(output).not.toContain("class"); - - var TEST_AREA, TEST_SHAPE_ID, TEST_IS_SHAPE; - - eval(output); - - expect(TEST_AREA).toStrictEqual(200); - expect(TEST_SHAPE_ID).toStrictEqual(10); - expect(TEST_IS_SHAPE).toStrictEqual(true); -}); - -it("should support getters and setters on the class", async () => { - var output = await JsConfuser( - ` - class Rectangle { - constructor(width, height){ - this.width = width; - this.height = height; - } - - get area(){ - return this.width * this.height; - } - - set setterProperty(newArea){ - this.width = newArea/2; - this.height = 2; - } - } - - var rect = new Rectangle(10, 15) - TEST_AREA = rect.area - - rect.setterProperty = 500 - - TEST_NEW_AREA = rect.area - - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_AREA, TEST_NEW_AREA; - eval(output); - - expect(TEST_AREA).toStrictEqual(150); - expect(TEST_NEW_AREA).toStrictEqual(500); -}); - -it("should support getters and setters on static methods", async () => { - var output = await JsConfuser( - ` - - - var timesSet = 0; - var theRectangle; - - class Rectangle { - constructor(width, height){ - this.width = width; - this.height = height; - } - - static get theRectangle(){ - if( !theRectangle ) { - this.theRectangle = new Rectangle(4, 5) - } - } - - static set theRectangle(value){ - theRectangle = value; - timesSet++; - } - } - - TEST_FIRST_RECT = theRectangle - var rect = Rectangle.theRectangle; - - TEST_SECOND_RECT = theRectangle.width - - Rectangle.theRectangle = new Rectangle(40, 35) - - TEST_THIRD_RECT = theRectangle.width - - TEST_TIMES_SET = timesSet; - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_FIRST_RECT, TEST_SECOND_RECT, TEST_THIRD_RECT, TEST_TIMES_SET; - eval(output); - - expect(TEST_FIRST_RECT).toStrictEqual(undefined); - expect(TEST_SECOND_RECT).toStrictEqual(4); - expect(TEST_THIRD_RECT).toStrictEqual(40); - expect(TEST_TIMES_SET).toStrictEqual(2); -}); - -it("should support classes with no constructor", async () => { - var output = await JsConfuser( - ` - class Rectangle { - getArea(){ - return this.width * this.height; - } - } - - var rect = new Rectangle(); - rect.width = 5; - rect.height = 10; - - TEST_AREA = rect.getArea() - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_AREA; - eval(output); - - expect(TEST_AREA).toStrictEqual(50); -}); - -it("should support preserving the class name", async () => { - var output = await JsConfuser( - ` - class Rectangle { - getArea(){ - return this.width * this.height; - } - } - - var rect = new Rectangle(); - TEST_VALUE = rect.constructor.name; - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_VALUE; - eval(output); - - expect(TEST_VALUE).toStrictEqual("Rectangle"); -}); - -it("should properly pass arguments to super class", async () => { - var output = await JsConfuser( - ` - class Logger { - constructor(name){ - TEST_INIT_ARG = name; - this.name = name; - } - - log(message){ - TEST_NAME = this.name; - } - } - - class Player extends Logger { - constructor(){ - super("Player") - } - - jump(){ - this.log("I jumped") - } - } - - var player = new Player(); - player.jump(); - `, - { - target: "node", - es5: true, - } - ); - - expect(output).not.toContain("class"); - - var TEST_INIT_ARG, TEST_NAME; - eval(output); - - expect(TEST_INIT_ARG).toStrictEqual("Player"); - expect(TEST_NAME).toStrictEqual("Player"); -}); - -it("should work with stringConcealing and hide method names", async () => { - var output = await JsConfuser( - ` - class MyClass { - - constructor(value){ - this.value = value - } - - getValue(){ - return this.value - } - } - - var instance = new MyClass(100) - TEST_VALUE = instance.getValue() - `, - { - target: "node", - es5: true, - stringConcealing: true, - } - ); - - expect(output).not.toContain("class"); - expect(output).not.toContain("getValue"); - - var TEST_VALUE; - eval(output); - - expect(TEST_VALUE).toStrictEqual(100); -}); - -// https://github.com/MichaelXF/js-confuser/issues/72 -it("should support class fields", async () => { - var output = await JsConfuser( - ` - class MyClass { - myField = 1; - ["myComputedField"] = 2; - - static myStaticField = 3; - static ["myComputedStaticField"] = 4; - } - - var myObject = new MyClass(); - TEST_OUTPUT_1 = myObject.myField; - TEST_OUTPUT_2 = myObject.myComputedField; - TEST_OUTPUT_3 = MyClass.myStaticField; - TEST_OUTPUT_4 = MyClass.myComputedStaticField; - `, - { - target: "node", - es5: true, - } - ); - - var TEST_OUTPUT_1, TEST_OUTPUT_2, TEST_OUTPUT_3, TEST_OUTPUT_4; - - eval(output); - expect(TEST_OUTPUT_1).toStrictEqual(1); - expect(TEST_OUTPUT_2).toStrictEqual(2); - expect(TEST_OUTPUT_3).toStrictEqual(3); - expect(TEST_OUTPUT_4).toStrictEqual(4); -}); diff --git a/test/transforms/es5/antiDestructuring.test.ts b/test/transforms/es5/antiDestructuring.test.ts deleted file mode 100644 index 14000f8..0000000 --- a/test/transforms/es5/antiDestructuring.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import JsConfuser from "../../../src/index"; - -it("should fix destructuring in assignment expressions", async () => { - var code = `[TEST_VARIABLE] = [100];`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(100); -}); - -it("should fix destructuring in a sequence of assignment expressions", async () => { - var code = `([TEST_VARIABLE] = [100], [TEST_VARIABLE_2] = [50]);`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - var TEST_VARIABLE_2; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(100); - expect(TEST_VARIABLE_2).toStrictEqual(50); -}); - -it("should fix destructuring with empty elements", async () => { - var code = `[, TEST_VARIABLE] = [100, 10];`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(10); -}); - -it("should fix destructuring in parameters", async () => { - var code = ` - - TEST_FUNCTION = function({key}){ - TEST_VARIABLE = key; - } - - `; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - var TEST_FUNCTION; - - eval(output); - - TEST_FUNCTION({ key: 64 }); - - expect(TEST_VARIABLE).toStrictEqual(64); -}); - -it("should fix destructuring in shorthand arrow function parameters", async () => { - var code = ` - TEST_FUNCTION = ({key})=>TEST_VARIABLE=key; - `; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - var TEST_FUNCTION; - - eval(output); - - TEST_FUNCTION({ key: 64 }); - - expect(TEST_VARIABLE).toStrictEqual(64); -}); - -it("should fix destructuring in variable declarations", async () => { - var code = ` - - var {TEST_KEY} = {TEST_KEY: 50}; - - TEST_VARIABLE = TEST_KEY; - - `; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(50); -}); - -it("should fix destructuring with rest elements", async () => { - var code = `[...TEST_VARIABLE] = [1, 2, 3, 4, 5];`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual([1, 2, 3, 4, 5]); -}); - -it("should fix destructuring with default values", async () => { - var code = `var {key: TEST_KEY = 50} = {key: undefined}; TEST_VARIABLE = TEST_KEY; `; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(50); -}); - -it("should fix destructuring inside the try...catch clause", async () => { - var code = ` - try { - - throw {message: 100}; - - // Why can you even do this? - } catch ({message}) { - - TEST_VARIABLE = message; - } - `; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(100); -}); - -it("should fix destructuring member expressions", async () => { - var code = ` - var myObject = { nested: {} }; - [myObject.key1, myObject["key2"], myObject.nested.key3] = [1, 2, 3]; - - TEST_VARIABLE = [myObject.key1, myObject.key2, myObject.nested.key3]; - `; - - var output = await JsConfuser(code, { target: "node", es5: true }); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual([1, 2, 3]); -}); diff --git a/test/transforms/es5/antiES6Object.test.ts b/test/transforms/es5/antiES6Object.test.ts deleted file mode 100644 index 9d08a06..0000000 --- a/test/transforms/es5/antiES6Object.test.ts +++ /dev/null @@ -1,245 +0,0 @@ -import JsConfuser from "../../../src/index"; - -it("should fix objects with computed properties", async () => { - var code = ` - - TEST_VARIABLE = {["k" + "e" + "y"] : 100}; - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toEqual({ key: 100 }); -}); - -it("should fix objects with getters", async () => { - var code = ` - - TEST_VARIABLE = { - get x (){ - return 100; - } - }; - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE.x).toEqual(100); -}); - -it("should fix objects with getters and setters", async () => { - var code = ` - - var TEST_STATE; - TEST_VARIABLE = { - get a () { - return TEST_STATE / 2; - }, - get x (){ - return TEST_STATE; - }, - set x (y){ - TEST_STATE = y - } - }; - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - var TEST_VARIABLE; - - eval(output); - - TEST_VARIABLE.x = 50; - - expect(TEST_VARIABLE.a).toEqual(25); - expect(TEST_VARIABLE.x).toEqual(50); -}); - -it("should fix objects with methods", async () => { - var code = ` - - var TEST_STATE; - TEST_VARIABLE = { - method(){ - return 10; - } - }; - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - var TEST_VARIABLE; - - eval(output); - - expect(TEST_VARIABLE.method()).toEqual(10); -}); - -it("should fix spread operator in array expressions", async () => { - var code = ` - - var subarray = [1,2,3]; - TEST_VARIABLE = [0,...subarray,4,5,...[6,7,8],9]; - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - expect(output).not.toContain("..."); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); -}); - -it("should fix spread operator in array expressions where spread operator is last", async () => { - var code = ` - - var subarray = [1,2,3]; - var subarray2 = [6,7,8,9]; - TEST_VARIABLE = [0,...subarray,4,5,...subarray2]; - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - expect(output).not.toContain("..."); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); -}); - -it("should fix spread operator in object expressions", async () => { - var code = ` - - var subObject = {key3: 30, key4: 40}; - TEST_VARIABLE = {key1: 10, key2: 20, ...subObject, key5: 50} - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - expect(output).not.toContain("..."); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toEqual({ - key1: 10, - key2: 20, - key3: 30, - key4: 40, - key5: 50, - }); - - expect(Object.keys(TEST_VARIABLE)).toEqual([ - "key1", - "key2", - "key3", - "key4", - "key5", - ]); -}); - -it("should fix spread operator in object expressions where spread operator is last", async () => { - var code = ` - - var subObject = {key3: 30, key4: 40}; - var subObject2 = {key6: 60, key7: 70, key8: 80} - TEST_VARIABLE = {key1: 10, key2: 20, ...subObject, key5: 50, ...subObject2} - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - expect(output).not.toContain("..."); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toEqual({ - key1: 10, - key2: 20, - key3: 30, - key4: 40, - key5: 50, - key6: 60, - key7: 70, - key8: 80, - }); - - expect(Object.values(TEST_VARIABLE)).toEqual([ - 10, 20, 30, 40, 50, 60, 70, 80, - ]); -}); - -it("should fix spread operator in object expressions in complex situations", async () => { - var code = ` - - var subObject = {key3: 30, key4: 40}; - var subObject2 = {key6: 60, key7: 70, key8: 80} - TEST_VARIABLE = {key1: 10, key2: 20, ...subObject, key5: 50, ...subObject2, ...{key9: 90}, key10: 100} - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - expect(output).not.toContain("..."); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toEqual({ - key1: 10, - key2: 20, - key3: 30, - key4: 40, - key5: 50, - key6: 60, - key7: 70, - key8: 80, - key9: 90, - key10: 100, - }); - - expect(Object.values(TEST_VARIABLE)).toEqual([ - 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, - ]); -}); diff --git a/test/transforms/es5/antiTemplate.test.ts b/test/transforms/es5/antiTemplate.test.ts deleted file mode 100644 index 4141a9d..0000000 --- a/test/transforms/es5/antiTemplate.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import JsConfuser from "../../../src/index"; - -describe("ES5 > AntiTemplate", () => { - it("should convert single template literals to strings", async () => { - var output = await JsConfuser("TEST_VARIABLE = `Hello World`", { - target: "node", - es5: true, - }); - - expect(output).not.toContain("`"); - expect(output).not.toContain("'+'"); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toStrictEqual("Hello World"); - }); - - it("should convert complex template literals to concatenated strings", async () => { - var TEST_VARIABLE_2 = 10; - - var output = await JsConfuser( - "TEST_VARIABLE = `Hello World: ${TEST_VARIABLE_2}`", - { target: "node", es5: true } - ); - - expect(output).not.toContain("`"); - expect(output).toContain("'+"); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toStrictEqual("Hello World: 10"); - }); - - it("should convert tagged template literals to call expressions", async () => { - var TEST_VARIABLE = 10; - - var output = await JsConfuser( - "TEST_FUNCTION`Hello World: ${TEST_VARIABLE}`", - { target: "node", es5: true } - ); - - expect(output).not.toContain("`"); - expect(output).toContain("','"); - - var input; - function TEST_FUNCTION(p, ...fillers) { - input = p[0] + fillers[0]; - } - - eval(output); - expect(input).toStrictEqual("Hello World: 10"); - }); - - it("should work on tagged template literals with multiple expressions", async () => { - var TEST_VARIABLE_1 = 10; - var TEST_VARIABLE_2 = 30; - - var output = await JsConfuser( - "TEST_FUNCTION`${TEST_VARIABLE_1},20,${TEST_VARIABLE_2}`", - { target: "node", es5: true } - ); - - expect(output).not.toContain("`"); - expect(output).toContain("','"); - - var input; - function TEST_FUNCTION(p, ...fillers) { - input = p[0] + fillers[0] + p[1] + fillers[1]; - } - - eval(output); - expect(input).toStrictEqual("10,20,30"); - }); - - it("should add the .raw property to the first parameter on tagged template literals", async () => { - var TEST_VARIABLE = 10; - - var output = await JsConfuser( - "TEST_FUNCTION`Hello World: ${TEST_VARIABLE}`", - { target: "node", es5: true } - ); - - expect(output).not.toContain("`"); - expect(output).toContain("','"); - - var input; - function TEST_FUNCTION(p, ...fillers) { - input = p.raw[0]; - } - - eval(output); - expect(input).toStrictEqual("Hello World: "); - }); - - it("should have the correct .raw property for strings with backslashes", async () => { - var TEST_VARIABLE = 10; - - var output = await JsConfuser( - "TEST_FUNCTION`Hello World:\\t${TEST_VARIABLE}`", - { target: "node", es5: true } - ); - - expect(output).not.toContain("`"); - expect(output).toContain("','"); - - var input; - function TEST_FUNCTION(p, ...fillers) { - input = p.raw[0]; - } - - eval(output); - expect(input).toStrictEqual("Hello World:\\t"); - }); -}); diff --git a/test/transforms/es5/es5.test.ts b/test/transforms/es5/es5.test.ts deleted file mode 100644 index c18acba..0000000 --- a/test/transforms/es5/es5.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import JsConfuser from "../../../src/index"; - -it("should convert arrow functions to function expressions", async () => { - var code = `var arrow = ()=>"Hello World"`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - expect(output).not.toContain("=>"); -}); - -it("should convert arrow functions and work", async () => { - var code = `arrow = ()=>this;`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - // Ensure arrow function is gone - expect(output).not.toContain("=>"); - - expect(output).toContain("function"); - - var arrow; - - eval(output); - - expect(typeof arrow).toStrictEqual("function"); - expect(arrow()).toBeTruthy(); -}); - -it("should fix let/const", async () => { - var code = `let TEST_VARIABLE = 100;`; - - var output = await JsConfuser(code, { target: "browser", es5: true }); - - expect(output).not.toContain("let"); -}); - -it("should fix let with RenameVariables", async () => { - var code = ` - - if ( true ) { - let TEST_VARIABLE = 100; - } - - var check; - try { - TEST_VARIABLE - } catch ( e ) { - check = true; - } - - input(check) - - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - renameVariables: true, - }); - - expect(output).not.toContain("let"); - - var value = "never_called"; - function input(x) { - value = x; - } - - eval(output); - expect(value).toStrictEqual(true); -}); - -it("should add forEach polyfill", async () => { - var code = ` - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - }); - - expect(output).toContain("forEach"); -}); - -it("should fix reserved keywords when used in properties", async () => { - var code = ` - TEST_VARIABLE = {for: 1}; - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - minify: true, - }); - - expect(output).toContain("'for'"); -}); - -it("should fix reserved keywords when used in member expressions", async () => { - var code = ` - TEST_VARIABLE.for = 1; - `; - - var output = await JsConfuser(code, { - target: "browser", - es5: true, - minify: true, - }); - - expect(output).toContain("['for']"); -}); diff --git a/test/transforms/extraction/classExtraction.test.ts b/test/transforms/extraction/classExtraction.test.ts deleted file mode 100644 index f8f7fb2..0000000 --- a/test/transforms/extraction/classExtraction.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import JsConfuser from "../../../src/index"; - -// TODO -test.skip("Variant #1: Extract class methods", async () => { - var code = ` - function nested() { - if (true) { - switch (true) { - case true: - let dimension2D = "2D"; - - class Square { - constructor(size) { - this.size = size; - } - - static fromJSON(json) { - return new Square(json.size); - } - - getArea() { - return this.size * this.size; - } - - get dimensions() { - return dimension2D; - } - - set dimensions(value) { - if (value !== "2D") { - throw new Error("Only supports 2D"); - } - } - } - - class Rectangle extends Square { - constructor(width, length) { - super(null); - this.width = width; - this.length = length; - } - - static fromJSON(json) { - return new Rectangle(json.width, json.height); - } - - getArea() { - return this.width * this.length; - } - - myMethod(dim = super.dimensions) { - console.log(dim); - } - } - - var rectangle = Rectangle.fromJSON({ width: 10, height: 5 }); - - console.log(rectangle.getArea()); - rectangle.myMethod(); - - rectangle.dimensions = "2D"; // Allowed - - try { - rectangle.dimensions = "3D"; // Not allowed - } catch (e) { - if (e.message.includes("Only supports 2D")) { - // console.log("Failed to set dimensions"); - TEST_OUTPUT = true; - } - } - - } - } - } - - nested(); - - `; - - var output = await JsConfuser(code, { target: "node" }); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(true); -}); diff --git a/test/transforms/string/stringConcealing.test.ts b/test/transforms/string/stringConcealing.test.ts index eb43055..9a7712f 100644 --- a/test/transforms/string/stringConcealing.test.ts +++ b/test/transforms/string/stringConcealing.test.ts @@ -1,24 +1,30 @@ import JsConfuser from "../../../src/index"; -it("should conceal strings", async () => { - var code = `var TEST_STRING = "Hello World"`; +test("Variant #1: Conceal strings", async () => { + var code = `TEST_STRING = "Hello World"`; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); expect(output).not.toContain("Hello World"); + + var TEST_STRING; + + eval(code); + + expect(TEST_STRING).toStrictEqual("Hello World"); }); -it("should decode strings properly", async () => { +test("Variant #2: Decode strings properly", async () => { var code = ` var TEST_STRING = "Hello World" input(TEST_STRING); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); @@ -35,14 +41,14 @@ it("should decode strings properly", async () => { expect(value).toStrictEqual("Hello World"); }); -it("should decode multiple strings properly", async () => { +test("Variant #3: Decode multiple strings properly", async () => { var code = ` TEST_STRING_1 = "Hello World" TEST_STRING_2 = "Hello World" TEST_STRING_3 = "Another String" `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); @@ -59,14 +65,14 @@ it("should decode multiple strings properly", async () => { expect(TEST_STRING_3).toStrictEqual("Another String"); }); -it("should not encode import expressions", async () => { +test("Variant #4: Don't encode import expressions", async () => { var code = ` import("my-module").then(module=>{ // ... }) `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); @@ -74,12 +80,12 @@ it("should not encode import expressions", async () => { expect(output).toContain("my-module"); }); -it("should not encode import statements", async () => { +test("Variant #5: Don't encode import statements", async () => { var code = ` import x from "my-module" `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); @@ -87,12 +93,12 @@ it("should not encode import statements", async () => { expect(output).toContain("my-module"); }); -it("should not encode require imports", async () => { +test("Variant #6: Don't encode require imports", async () => { var code = ` require("my-module") `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); @@ -100,12 +106,12 @@ it("should not encode require imports", async () => { expect(output).toContain("my-module"); }); -it("should not encode directives ('use strict')", async () => { +test("Variant #7: Don't encode directives ('use strict')", async () => { var code = ` 'use strict' `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringConcealing: true, }); @@ -113,7 +119,7 @@ it("should not encode directives ('use strict')", async () => { expect(output).toContain("use strict"); }); -it("should work on property keys", async () => { +test("Variant #8: Work on property keys", async () => { var code = ` var myObject = { myKey: 100 @@ -122,7 +128,7 @@ it("should work on property keys", async () => { TEST_VAR = myObject.myKey; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringConcealing: true, }); @@ -135,7 +141,7 @@ it("should work on property keys", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should work on class keys", async () => { +test("Variant #9: Work on class keys", async () => { var code = ` class MyClass { myMethod(){ @@ -148,7 +154,7 @@ it("should work on class keys", async () => { TEST_VAR = myObject.myMethod(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringConcealing: true, }); @@ -161,7 +167,7 @@ it("should work on class keys", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should not encode constructor key", async () => { +test("Variant #10: Don't encode constructor key", async () => { var code = ` class MyClass { constructor(){ @@ -172,7 +178,7 @@ it("should not encode constructor key", async () => { new MyClass(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringConcealing: true, }); @@ -184,7 +190,7 @@ it("should not encode constructor key", async () => { }); // https://github.com/MichaelXF/js-confuser/issues/82 -it("should work inside the Class Constructor function", async () => { +test("Variant #11: Work inside the Class Constructor function", async () => { var code = ` class MyClass1 {} class MyClass2 extends MyClass1 { @@ -201,7 +207,7 @@ it("should work inside the Class Constructor function", async () => { TEST_OUTPUT = instance.myString1 === true; // true `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringConcealing: true, }); @@ -216,7 +222,7 @@ it("should work inside the Class Constructor function", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); -it("should be configurable by custom function option", async () => { +test("Variant #12: Configurable by custom function option", async () => { var code = ` var myVar1 = "My First String"; var myVar2 = "My Second String"; @@ -225,9 +231,9 @@ it("should be configurable by custom function option", async () => { TEST_RESULT = [myVar1, myVar2, myVar3]; `; - var strings = []; + var strings: string[] = []; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringConcealing: (str) => { strings.push(str); @@ -260,7 +266,7 @@ it("should be configurable by custom function option", async () => { }); test("Variant #13: Work without TextEncoder or Buffer being defined", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OUTPUT = []; TEST_OUTPUT.push("My First String"); @@ -297,3 +303,53 @@ test("Variant #13: Work without TextEncoder or Buffer being defined", async () = "My Fifth String", ]); }); + +test("Variant #14: Nested, duplicate strings", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var myVar = "Hello World"; + var myVar2 = "Another String"; + + function main(){ + var object = { + "Hello World": "Another String", + "Another String"() {} + } + class MyClass { + "Hello World"(){} + "Another String" = () => {} + } + + var myMainVar = "Hello World"; + var myMainVar2 = "Another String"; + + function nested(){ + var myNestedVar = "Hello World"; + var myNestedVar2 = "Another String"; + + TEST_OUTPUT = myNestedVar === "Hello World" && + myNestedVar2 === "Another String" && + (myNestedVar + myNestedVar2) == (myMainVar + myMainVar2) && + (myNestedVar + myNestedVar2) == (myVar + myVar2); + } + + nested("Hello World") + } + + main("Hello World") + `, + { + target: "node", + stringConcealing: true, + } + ); + + expect(code).not.toContain("Hello World"); + expect(code).not.toContain("Another String"); + + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(true); +}); diff --git a/test/transforms/variableMasking.test.ts b/test/transforms/variableMasking.test.ts index d21395d..c8d3e2d 100644 --- a/test/transforms/variableMasking.test.ts +++ b/test/transforms/variableMasking.test.ts @@ -240,7 +240,7 @@ test("Variant #9: Replace all variables with array indexes (middle indexes use a ); expect(output).not.toContain("TEST_VARIABLE"); - expect(output).toContain("]='Updated'"); + expect(output).toContain(']="Updated"'); var value = "never_called", input = (x) => (value = x); @@ -509,7 +509,7 @@ test("Variant #16: Function with 'this'", async () => { ); // Ensure stack applied - expect(output).toContain("_stack"); + expect(output).toContain("_varMask"); // Ensure 'thisFunction' was not changed by stack due to using 'this' keyword expect(output).toContain("function thisFunction"); From 13ec3ee2159e42044198b55d8c9b2ff00b674233 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 26 Aug 2024 01:35:14 -0400 Subject: [PATCH 013/103] Improve `Shuffle` --- CHANGELOG.md | 2 + src/presets.ts | 2 +- src/transforms/shuffle.ts | 7 ++-- test/transforms/shuffle.test.ts | 68 +++++---------------------------- 4 files changed, 16 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb0f58..54a5a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - Removed Browser Lock and OS Lock - Use custom Locks instead +- Removed Shuffle Hash option + # `1.7.3` Tamper Protection diff --git a/src/presets.ts b/src/presets.ts index 49c1f1f..264f124 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -43,7 +43,7 @@ const highPreset: ObfuscateOptions = { opaquePredicates: 0.75, renameVariables: true, renameGlobals: true, - shuffle: { hash: 0.5, true: 0.5 }, + shuffle: true, variableMasking: true, stringConcealing: true, stringCompression: true, diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index ddf1198..91cec89 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -5,6 +5,7 @@ import { computeProbabilityMap } from "../probability"; import { getRandomInteger } from "../utils/random-utils"; import Template from "../templates/template"; import { Order } from "../order"; +import { isStaticValue } from "../utils/static-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Shuffle); @@ -17,7 +18,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } var illegalElement = path.node.elements.find((element) => { - !t.isStringLiteral(element); + !isStaticValue(element); }); if (illegalElement) return; @@ -28,12 +29,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { var shift = getRandomInteger( 1, - Math.min(40, path.node.elements.length * 6) + Math.min(30, path.node.elements.length * 6) ); var shiftedElements = [...path.node.elements]; for (var i = 0; i < shift; i++) { - shiftedElements.push(shiftedElements.shift()); + shiftedElements.unshift(shiftedElements.pop()); } var runtimeFn = me.getPlaceholder(); diff --git a/test/transforms/shuffle.test.ts b/test/transforms/shuffle.test.ts index 161f87f..03c9314 100644 --- a/test/transforms/shuffle.test.ts +++ b/test/transforms/shuffle.test.ts @@ -1,13 +1,13 @@ import JsConfuser from "../../src/index"; -it("should result in the same order", async () => { +test("Variant #1: Result in the same order", async () => { var code = ` var TEST_ARRAY = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; input(TEST_ARRAY); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", shuffle: true, }); @@ -21,12 +21,13 @@ it("should result in the same order", async () => { expect(value).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -it("should properly shuffle arrays within expressions", async () => { + +test("Variant #2: Properly shuffle arrays within expressions", async () => { var code = ` input([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", shuffle: true, }); @@ -41,70 +42,19 @@ it("should properly shuffle arrays within expressions", async () => { expect(value).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -it("should shuffle arrays based on hash and unshuffle correctly", async () => { - var code = `input([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);`; - - for (var i = 0; i < 20; i++) { - var output = await JsConfuser(code, { - target: "node", - shuffle: "hash", - }); - - var value; - function input(valueIn) { - value = valueIn; - } - - eval(output); - - expect(value).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - } -}); - -it("should shuffle arrays based on hash and unshuffle incorrect if changed", async () => { - var code = `input([1, 2, 3, 4, 5]);`; - - var different = false; - for (var i = 0; i < 20; i++) { - var output = await JsConfuser(code, { - target: "node", - shuffle: "hash", - }); - - output = - output.split("[")[0] + "[" + output.split("[")[1].replace("5", "6"); - - var value; - function input(valueIn) { - value = valueIn; - } - - eval(output); - expect(value).toHaveLength(5); - expect(typeof value[0] === "number").toStrictEqual(true); - if (value[0] !== 1) { - different = true; - } - } - - expect(different).toStrictEqual(true); -}); - // https://github.com/MichaelXF/js-confuser/issues/48 -it("Should properly apply to const variables", async () => { +test("Variant #3: Properly apply to const variables", async () => { var code = ` const TEST_ARRAY = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; input(TEST_ARRAY); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", shuffle: true, }); - expect(output).toContain("TEST_ARRAY=function"); - var value; function input(valueIn) { value = valueIn; @@ -116,7 +66,7 @@ it("Should properly apply to const variables", async () => { }); // https://github.com/MichaelXF/js-confuser/issues/53 -it("Should not use common variable names like x", async () => { +test("Variant #4: Don't use common variable names like x", async () => { var code = ` let x = -999; let a = [1, 2, 3, 4, 5, 6]; @@ -124,7 +74,7 @@ it("Should not use common variable names like x", async () => { VALUE = a; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", shuffle: true, }); From b24fdf318dbcd78fba6454930c882faa4d6b889e Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 27 Aug 2024 10:39:12 -0400 Subject: [PATCH 014/103] `Object Extraction` Complete tests (16/16) --- src/transforms/extraction/objectExtraction.ts | 268 ++++++++++-------- .../extraction/objectExtraction.test.ts | 1 - 2 files changed, 142 insertions(+), 127 deletions(-) diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index af99987..3656b72 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -1,152 +1,168 @@ -import * as babelTypes from "@babel/types"; import { NodePath, PluginObj } from "@babel/core"; +import { PluginArg } from "../plugin"; +import { Order } from "../../order"; +import * as t from "@babel/types"; import { getMemberExpressionPropertyAsString, getObjectPropertyAsString, + getParentFunctionOrProgram, } from "../../utils/ast-utils"; -import { PluginArg } from "../plugin"; -import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; -function isObjectSafeForExtraction( - path: NodePath -): boolean { - const id = path.node.id; - babelTypes.assertIdentifier(id); - const identifierName = id.name; - - const init = path.node.init; - - // Check if the object is not re-assigned - const binding = path.scope.getBinding(identifierName); - if (!binding || binding.constantViolations.length > 0) { - return false; - } - - var propertyNames = new Set(); - - // Check all properties of the object - if (babelTypes.isObjectExpression(init)) { - for (const prop of init.properties) { - if ( - babelTypes.isObjectMethod(prop) || - babelTypes.isSpreadElement(prop) || - (babelTypes.isObjectProperty(prop) && - !babelTypes.isIdentifier(prop.key) && - !babelTypes.isStringLiteral(prop.key)) || - (babelTypes.isObjectProperty(prop) && - babelTypes.isFunctionExpression(prop.value) && - prop.value.body.body.some((node) => - babelTypes.isThisExpression(node) - )) - ) { - return false; - } - - var propertyKey = getObjectPropertyAsString(prop); - if (typeof propertyKey !== "string") { - return false; - } - propertyNames.add(propertyKey); - } - } - - // Check all references to ensure they are safe - return binding.referencePaths.every((refPath) => { - const parent = refPath.parent; - - // Referencing the object name by itself is not allowed - if (!babelTypes.isMemberExpression(parent)) { - return false; - } - - if (babelTypes.isCallExpression(parent.property)) { - return false; - } - - var propertyName = getMemberExpressionPropertyAsString(parent); - - if (typeof propertyName !== "string") { - return false; - } - return propertyNames.has(propertyName); - }); -} - export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.ObjectExtraction); return { visitor: { - VariableDeclarator(path: NodePath) { - // Ensure the variable is an object literal and the object is not re-assigned - if ( - babelTypes.isObjectExpression(path.node.init) && - path.node.id.type === "Identifier" && - isObjectSafeForExtraction(path) - ) { - const objectName = path.node.id.name; - const properties = path.node.init.properties; - - // Extract each property and create a new variable for it - const extractedVariables = properties - .map((prop) => { - if ( - babelTypes.isObjectProperty(prop) && - (babelTypes.isIdentifier(prop.key) || - babelTypes.isStringLiteral(prop.key)) - ) { - const propName = getObjectPropertyAsString(prop); + VariableDeclaration(varDecPath) { + if (varDecPath.node.declarations.length !== 1) return; + const declaration = varDecPath.get( + "declarations.0" + ) as NodePath; + + // Must be simple variable declaration (No destructuring) + const identifier = declaration.get("id"); + if (!identifier.isIdentifier()) return; + + // Must be an object expression + const objectExpression = declaration.get("init"); + if (!objectExpression.isObjectExpression()) return; + + // Not allowed to reassign the object + const binding = varDecPath.scope.getBinding(identifier.node.name); + if (!binding || binding.constantViolations.length > 0) return; + + var pendingReplacements: { + path: NodePath; + replaceWith: t.Expression; + }[] = []; + + const newObjectName = me.getPlaceholder() + "_" + identifier.node.name; + const newPropertyMappings = new Map(); + + // Create new property names from the original object properties + var newDeclarations: t.VariableDeclarator[] = []; + for (var property of objectExpression.get("properties")) { + if (!property.isObjectProperty()) return; + const propertyKey = getObjectPropertyAsString(property.node); + if (!propertyKey) { + // Property key is not a static string, not allowed + return; + } + + let newPropertyName = newPropertyMappings.get(propertyKey); + if (newPropertyName) { + // Duplicate property, not allowed + return; + } else { + newPropertyName = + newObjectName + + "_" + + (t.isValidIdentifier(propertyKey) + ? propertyKey + : me.getPlaceholder()); + newPropertyMappings.set(propertyKey, newPropertyName); + } - const newVarName = `${objectName}_${propName}`; - const newVarDeclaration = babelTypes.variableDeclarator( - babelTypes.identifier(newVarName), - prop.value as babelTypes.Expression - ); + // Check function for referencing 'this' + const value = property.get("value"); + if (value.isFunction()) { + var referencesThis = false; - return newVarDeclaration; - } + value.traverse({ + ThisExpression(thisPath) { + referencesThis = true; + }, + }); - return null; - }) - .filter(Boolean); - - // Replace the original object with extracted variables - if (extractedVariables.length > 0) { - // Allow user to disable certain objects - if ( - !computeProbabilityMap( - me.options.objectExtraction, - (x) => x, - objectName - ) - ) { + if (referencesThis) { + // Function references 'this', not allowed + // When extracted, this will not refer to the original object return; } + } - path.replaceWithMultiple(extractedVariables); + newDeclarations.push( + t.variableDeclarator( + t.identifier(newPropertyName), + value.node as t.Expression + ) + ); + } - var variableDeclaration = - path.parent as babelTypes.VariableDeclaration; - if (variableDeclaration.kind === "const") { - variableDeclaration.kind = "let"; - } + var isObjectSafe = true; + + getParentFunctionOrProgram(varDecPath).traverse({ + Identifier: { + exit(idPath) { + if (idPath.node.name !== identifier.node.name) return; + if (idPath === identifier) return; // Skip the original declaration + + const memberExpression = idPath.parentPath; + if (!memberExpression || !memberExpression.isMemberExpression()) { + isObjectSafe = false; + return; + } + const property = getMemberExpressionPropertyAsString( + memberExpression.node + ); + if (!property) { + isObjectSafe = false; + return; + } - // Replace references to the object with the new variables - path.scope.traverse(path.scope.block, { - MemberExpression( - memberPath: NodePath + // Delete expression check + if ( + memberExpression.parentPath.isUnaryExpression({ + operator: "delete", + }) ) { - const propName = getMemberExpressionPropertyAsString( - memberPath.node - ); + // Deleting object properties is not allowed + isObjectSafe = false; + return; + } - const newVarName = `${objectName}_${propName}`; + let newPropertyName = newPropertyMappings.get(property); + if (!newPropertyName) { + // Property added later on, not allowed + isObjectSafe = false; + return; + } - memberPath.replaceWith(babelTypes.identifier(newVarName)); - }, - }); - } + const extractedIdentifier = t.identifier(newPropertyName); + + pendingReplacements.push({ + path: memberExpression, + replaceWith: extractedIdentifier, + }); + }, + }, + }); + + // Object references are too complex to safely extract + if (!isObjectSafe) return; + + if ( + !computeProbabilityMap( + me.options.objectExtraction, + (x) => x, + identifier.node.name + ) + ) + return; + + const newDeclarationKind = + varDecPath.node.kind === "const" ? "let" : varDecPath.node.kind; + + varDecPath.replaceWithMultiple( + newDeclarations.map((declaration) => + t.variableDeclaration(newDeclarationKind, [declaration]) + ) + ); + + // Replace all references to new singular identifiers + for (const { path, replaceWith } of pendingReplacements) { + path.replaceWith(replaceWith); } }, }, diff --git a/test/transforms/extraction/objectExtraction.test.ts b/test/transforms/extraction/objectExtraction.test.ts index 5db9f5b..7fb1c0c 100644 --- a/test/transforms/extraction/objectExtraction.test.ts +++ b/test/transforms/extraction/objectExtraction.test.ts @@ -294,7 +294,6 @@ test("Variant #9: Not extract properties on objects when the variable gets reass test("Variant #10: Not extract properties on objects with methods referencing 'this'", async () => { var code = ` - var TEST_OBJECT = { key: "value", getKey: function(){ From a43309ee408f70772d48aeb7937d21e1fba96baa Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 27 Aug 2024 10:41:23 -0400 Subject: [PATCH 015/103] `Variable Masking` Tests (18/19) --- src/transforms/preparation.ts | 40 +++++- src/transforms/variableMasking.ts | 181 +++++++++++++----------- test/transforms/variableMasking.test.ts | 69 ++++++++- 3 files changed, 197 insertions(+), 93 deletions(-) diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 465e297..d731db4 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -123,13 +123,39 @@ export default ({ Plugin }: PluginArg): PluginObj => { VariableDeclaration: { exit(path) { if (path.node.declarations.length > 1) { - var extraDeclarations = path.node.declarations.slice(1); - path.node.declarations.length = 1; - path.insertAfter( - extraDeclarations.map((declaration) => - t.variableDeclaration(path.node.kind, [declaration]) - ) - ); + // E.g. for (var i = 0, j = 1;;) + if (path.key === "init" && path.parentPath.isForStatement()) { + if ( + !path.parentPath.node.test && + !path.parentPath.node.update && + path.node.kind === "var" + ) { + path.parentPath.insertBefore( + path.node.declarations.map((declaration) => + t.variableDeclaration(path.node.kind, [declaration]) + ) + ); + path.remove(); + } + } else { + if (path.parentPath.isExportNamedDeclaration()) { + path.parentPath.replaceWithMultiple( + path.node.declarations.map((declaration) => + t.exportNamedDeclaration( + t.variableDeclaration(path.node.kind, [declaration]) + ) + ) + ); + } else { + path.replaceWithMultiple( + path.node.declarations.map((declaration) => + t.variableDeclaration(path.node.kind, [declaration]) + ) + ); + + path.scope.crawl(); + } + } } }, }, diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 9ea38f9..6567562 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -8,6 +8,7 @@ import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; import { getFunctionName } from "../utils/ast-utils"; import { isFunctionStrictMode } from "../utils/function-utils"; +import { ok } from "assert"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.VariableMasking); @@ -60,111 +61,123 @@ export default ({ Plugin }: PluginArg): PluginObj => { const binding = identifierPath.scope.getBinding( identifierPath.node.name ); + if (!binding || binding.kind === "const") return; - if (!binding) { + if (binding.path.isIdentifier()) { + // Parameter check + if ( + !fnPath.node.params.some( + (param) => + t.isIdentifier(param) && + param.name === (binding.path.node as t.Identifier).name + ) + ) { + return; + } + } else if (binding.path.isVariableDeclarator()) { + if (binding.path.parentPath.node?.type !== "VariableDeclaration") + return; + if (binding.path.parentPath.node.declarations.length > 1) return; + } else { return; } - if (binding.kind === "const" && binding.constantViolations.length > 0) { - return; - } - - if (binding.scope !== fnPath.scope) { - return; - } + needsStack = true; let stackIndex = stackMap.get(identifierPath.node.name); - if (typeof stackIndex === "undefined") { - if ( - !binding.path.isVariableDeclarator() || - !binding.path.parentPath.isVariableDeclaration() || - binding.path.parentPath.node.declarations.length !== 1 - ) { - return; - } - stackIndex = stackMap.size; stackMap.set(identifierPath.node.name, stackIndex); - - let value: t.Expression = - (binding.path.node as any).init ?? t.identifier("undefined"); - - needsStack = true; - binding.path.parentPath.replaceWith( - new Template(`{stackName}[{stackIndex}] = {value}`).single({ - stackName: stackName, - stackIndex: stackIndex, - value: value, - }) - ); } - binding.constantViolations.forEach((constantViolation) => { - switch (constantViolation.type) { - case "AssignmentExpression": - case "UpdateExpression": - // Find the Identifier that is being assigned to - // and replace it with the stack variable - constantViolation.traverse({ - Identifier(path) { - if (path.node.name === identifierPath.node.name) { - path.replaceWith( - new Template(`{stackName}[{stackIndex}]`).expression({ - stackName: stackName, - stackIndex: stackIndex, - }) - ); - } - }, - }); - - break; - default: - throw new Error( - `Unsupported constant violation type: ${constantViolation.type}` - ); - } - }); + const memberExpression = new Template(` + ${stackName}[${stackIndex}] + `).expression(); - binding.referencePaths.forEach((refPath) => { - if (!refPath.isReferencedIdentifier()) return; + binding.referencePaths.forEach((referencePath) => { + var callExpressionChild = referencePath; if ( - refPath.getFunctionParent() !== binding.path.getFunctionParent() + callExpressionChild && + callExpressionChild.parentPath?.isCallExpression() && + callExpressionChild.parentPath.node.callee === + callExpressionChild.node ) { - return; - } + callExpressionChild.parentPath.replaceWith( + t.callExpression( + t.memberExpression( + t.cloneNode(memberExpression), + t.identifier("call") + ), + [ + t.thisExpression(), + ...callExpressionChild.parentPath.node.arguments, + ] + ) + ); - const refBiding = refPath.scope.getBinding(refPath.node.name); - if (refBiding !== binding) { return; } - const memberExpression = new Template( - `{stackName}[{stackIndex}]` - ).expression({ - stackName: stackName, - stackIndex: stackIndex, - }); - - needsStack = true; - if ( - t.isCallExpression(refPath.parent) && - refPath.parent.callee === refPath.node - ) { - refPath.parent.callee = t.memberExpression( - memberExpression, - t.stringLiteral("call"), - true - ); - refPath.parent.arguments.unshift(t.thisExpression()); - } else { - refPath.replaceWith(memberExpression); - } + referencePath.replaceWith(t.cloneNode(memberExpression)); }); - identifierPath.scope.removeBinding(identifierPath.node.name); + [binding.path, ...binding.constantViolations].forEach( + (constantViolation) => { + constantViolation.traverse({ + Identifier(idPath) { + const cBinding = idPath.scope.getBinding(idPath.node.name); + if (cBinding !== binding) return; + + var replacePath: NodePath = idPath; + var valueNode: t.Expression | null = null; + + var forInOfChild = idPath.find( + (p) => + p.parentPath?.isForInStatement() || + p.parentPath?.isForOfStatement() + ); + + var variableDeclarationChild = idPath.find((p) => + p.parentPath?.isVariableDeclarator() + ); + + if ( + variableDeclarationChild && + t.isVariableDeclarator(variableDeclarationChild.parent) && + variableDeclarationChild.parent.id === + variableDeclarationChild.node + ) { + replacePath = variableDeclarationChild.parentPath.parentPath; + valueNode = + variableDeclarationChild.parent.init || + t.identifier("undefined"); + } + + if ( + forInOfChild && + (t.isForInStatement(forInOfChild.parent) || + t.isForOfStatement(forInOfChild.parent)) && + forInOfChild.parent.left === forInOfChild.node + ) { + replacePath = forInOfChild; + valueNode = null; + } + + let replaceExpr: t.Node = t.cloneNode(memberExpression); + if (valueNode) { + replaceExpr = t.assignmentExpression( + "=", + replaceExpr, + valueNode + ); + } + + replacePath.replaceWith(replaceExpr); + }, + }); + } + ); }, }); diff --git a/test/transforms/variableMasking.test.ts b/test/transforms/variableMasking.test.ts index c8d3e2d..1cc6412 100644 --- a/test/transforms/variableMasking.test.ts +++ b/test/transforms/variableMasking.test.ts @@ -496,11 +496,14 @@ test("Variant #16: Function with 'this'", async () => { var thisFunctionExpression2; - thisFunctionExpression2 = function(){ + thisFunctionExpression2 = function(...args){ + if(args.length !== 3) { + throw new Error("Invalid args passed in") + } return this; } - return thisFunctionDeclaration() || thisFunctionExpression1() || thisFunctionExpression2(); + return thisFunctionDeclaration() || thisFunctionExpression1() || thisFunctionExpression2(1,...[2,3]); } TEST_OUTPUT = stackFunction() === undefined; @@ -571,3 +574,65 @@ test("Variant #18: Preserve function.length property", async () => { expect(TEST_OUTPUT).toStrictEqual(6); }); + +test("Variant #19: For, For In/Of Statement", async () => { + var fnNames: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function declarationInitializer(){ + var items = [1,2,3,4,5], output = 0; + for(var i1 = 0; i1 < items.length; i1++) { + output += items[i1] + } + for(var i2 in items) { + output += items[i2] * 2 + } + for(var item of items) { + output += item * 2 + } + return output; + } + + function nonDeclarationInitializer(){ + var items = [5,6,7,8,9,10], output = 0, i1, item; + for(i1 = 0; i1 < items.length; i1++) { + output += items[i1] + } + for(i1 in items) { + output += items[i1] * 2 + } + for(item of items) { + output += item * 2 + } + return output; + } + + TEST_OUTPUT = declarationInitializer() + nonDeclarationInitializer(); + `, + { + target: "node", + variableMasking: (fnName) => { + fnNames.push(fnName); + return true; + }, + } + ); + + expect(fnNames).toStrictEqual([ + "declarationInitializer", + "nonDeclarationInitializer", + ]); + expect(code).toContain("_varMask"); + + // Ensure local variables got replaced + expect(code).not.toContain("i1"); + expect(code).not.toContain("i2"); + expect(code).not.toContain("item"); + expect(code).not.toContain("items"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(300); +}); From 59d54e52545b323e92e14010120d05b834c3d32f Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 27 Aug 2024 10:41:55 -0400 Subject: [PATCH 016/103] Improve Obfuscator Internals --- src/index.ts | 6 +- src/lock/lock.ts | 225 +++++++++++++++++++++++++++++++++++++++ src/obfuscator.ts | 10 +- src/options.ts | 129 ++++++++-------------- src/order.ts | 4 +- src/transforms/plugin.ts | 4 +- src/utils/ast-utils.ts | 2 +- src/validateOptions.ts | 51 +++------ 8 files changed, 298 insertions(+), 133 deletions(-) create mode 100644 src/lock/lock.ts diff --git a/src/index.ts b/src/index.ts index 3ffbdad..c070a20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -81,14 +81,14 @@ export async function obfuscateWithProfiler( }; } -var oldJSConfuser = async (sourceCode: string, options: ObfuscateOptions) => { +var oldJsConfuser = async (sourceCode: string, options: ObfuscateOptions) => { return (await obfuscate(sourceCode, options)).code; }; -const JSConfuser = Object.assign(oldJSConfuser, { +const JsConfuser = Object.assign(oldJsConfuser, { obfuscate, obfuscateAST, obfuscateWithProfiler, }); -export default JSConfuser; +export default JsConfuser; diff --git a/src/lock/lock.ts b/src/lock/lock.ts new file mode 100644 index 0000000..fe4f6ae --- /dev/null +++ b/src/lock/lock.ts @@ -0,0 +1,225 @@ +import { NodePath, PluginObj } from "@babel/core"; +import { PluginArg } from "../transforms/plugin"; +import { Order } from "../order"; +import { chance, choice } from "../utils/random-utils"; +import Template from "../templates/template"; +import * as t from "@babel/types"; +import { CustomLock } from "../options"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.Lock); + + if (me.options.lock.startDate instanceof Date) { + me.options.lock.customLocks.push({ + code: [ + ` + if(Date.now()<${me.options.lock.startDate.getTime()}) { + {countermeasures} + } + `, + ` + if((new Date()).getTime()<${me.options.lock.startDate.getTime()}) { + {countermeasures} + } + `, + ], + percentagePerBlock: 0.5, + }); + } + + if (me.options.lock.endDate instanceof Date) { + me.options.lock.customLocks.push({ + code: [ + ` + if(Date.now()>${me.options.lock.endDate.getTime()}) { + {countermeasures} + } + `, + ` + if((new Date()).getTime()>${me.options.lock.endDate.getTime()}) { + {countermeasures} + } + `, + ], + percentagePerBlock: 0.5, + }); + } + + if (me.options.lock.domainLock) { + var domainArray = Array.isArray(me.options.lock.domainLock) + ? me.options.lock.domainLock + : [me.options.lock.domainLock]; + + for (const regexString of domainArray) { + me.options.lock.customLocks.push({ + code: new Template(` + if(!new RegExp({regexString}).test(window.location.href)) { + {countermeasures} + } + `).setDefaultVariables({ + regexString: t.stringLiteral(regexString.toString()), + }), + percentagePerBlock: 0.5, + }); + } + } + + if (me.options.lock.selfDefending) { + me.options.lock.customLocks.push({ + code: ` + ( + function(){ + // Breaks any code formatter + var namedFunction = function(){ + const test = function(){ + const regExp=new RegExp('\\n'); + return regExp['test'](namedFunction) + }; + + if(test()) { + {countermeasures} + } + } + + return namedFunction(); + } + )(); + `, + percentagePerBlock: 0.5, + }); + } + + if (me.options.lock.antiDebug) { + me.options.lock.customLocks.push({ + code: ` + debugger; + `, + percentagePerBlock: 0.5, + }); + } + + const timesMap = new WeakMap(); + + let countermeasuresNode: NodePath; + let invokeCountermeasuresFnName; + + if (me.options.lock.countermeasures) { + invokeCountermeasuresFnName = me.getPlaceholder("invokeCountermeasures"); + } + + function createCountermeasuresCode(): t.Statement[] { + if (invokeCountermeasuresFnName) { + return new Template(`${invokeCountermeasuresFnName}()`).compile(); + } + + return new Template(`while(true){}`).compile(); + } + + function applyLockToBlock(path: NodePath, customLock: CustomLock) { + let times = timesMap.get(customLock); + + if (typeof times === "undefined") { + times = 0; + } + + let maxCount = customLock.maxCount || 100; // 100 is default max count + let minCount = customLock.minCount || 1; // 1 is default min count + + if (maxCount >= 0 && times > maxCount) { + // Limit creation, allowing -1 to disable the limit entirely + return; + } + + // The Program always gets a lock + // Else based on the percentage + // Try to reach the minimum count + if ( + !path.isProgram() && + !chance(customLock.percentagePerBlock * 100) && + times >= minCount + ) { + return; + } + + // Increment the times + timesMap.set(customLock, times + 1); + + const lockCode = Array.isArray(customLock.code) + ? choice(customLock.code) + : customLock.code; + + const template = + typeof lockCode === "string" ? new Template(lockCode) : lockCode; + const lockNodes = template.compile({ + countermeasures: () => createCountermeasuresCode(), + }); + var p = path.unshiftContainer("body", lockNodes); + p.forEach((p) => p.skip()); + } + + return { + visitor: { + Identifier: { + enter(path) { + console.log(path.node.name); + if (path.node.name !== me.options.lock.countermeasures) { + return; + } + + if (countermeasuresNode) { + // Disallow multiple countermeasures functions + me.error( + "Countermeasures function was already defined, it must have a unique name from the rest of your code" + ); + } + + if ( + path.scope.getBinding(path.node.name).scope !== + path.scope.getProgramParent() + ) { + me.error( + "Countermeasures function must be defined at the global level" + ); + } + + countermeasuresNode = path; + }, + }, + Program: { + exit(path) { + // Insert invokeCountermeasures function + + if (invokeCountermeasuresFnName) { + if (!countermeasuresNode) { + me.warn( + "Countermeasures function named '" + + me.options.lock.countermeasures + + "' was not found." + ); + } + + var hasInvoked = me.getPlaceholder(); + var statements = new Template(` + var ${hasInvoked} = false; + function ${invokeCountermeasuresFnName}(){ + if(${hasInvoked}) return; + ${hasInvoked} = true; + ${me.options.lock.countermeasures}(); + } + `).compile(); + + path.unshiftContainer("body", statements).forEach((p) => p.skip()); + } + }, + }, + Block: { + exit(path) { + var customLock = choice(me.options.lock.customLocks); + if (customLock) { + applyLockToBlock(path, customLock); + } + }, + }, + }, + }; +}; diff --git a/src/obfuscator.ts b/src/obfuscator.ts index ad2b5a0..1496f80 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -31,6 +31,7 @@ import renameLabels from "./transforms/renameLabels"; import rgf from "./transforms/rgf"; import flatten from "./transforms/flatten"; import stringConcealing from "./transforms/string/stringConcealing"; +import lock from "./lock/lock"; export default class Obfuscator { plugins: { @@ -73,6 +74,7 @@ export default class Obfuscator { push(this.options.renameLabels, renameLabels); push(this.options.rgf, rgf); push(this.options.flatten, flatten); + push(this.options.lock, lock); push(true, finalizer); @@ -120,9 +122,15 @@ export default class Obfuscator { startIndex?: number; } ): babel.types.File { - for (var i = options?.startIndex || 0; i < this.plugins.length; i++) { + for (let i = options?.startIndex || 0; i < this.plugins.length; i++) { this.index = i; const { plugin, pluginInstance } = this.plugins[i]; + if (this.options.verbose) { + console.log( + `Applying ${pluginInstance.name} (${i + 1}/${this.plugins.length})` + ); + } + babel.traverse(ast, plugin.visitor as babel.Visitor); if (options?.profiler) { diff --git a/src/options.ts b/src/options.ts index c28601c..6f88fc1 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,4 +1,45 @@ import { ProbabilityMap } from "./probability"; +import Template from "./templates/template"; + +export interface CustomLock { + /** + * Template lock code that must contain: + * + * - `{countermeasures}` + * + * The countermeasures function will be invoked when the lock is triggered. + * + * ```js + * if(window.navigator.userAgent.includes('Chrome')){ + * {countermeasures} + * } + * ``` + * + * Multiple templates can be passed a string array, a random one will be chosen each time. + */ + code: string | string[] | Template; + percentagePerBlock: number; + maxCount?: number; + minCount?: number; +} + +export interface CustomStringEncoding { + /** + * Template string decoder that must contain: + * + * - `{fnName}` + * + * This function will be invoked by the obfuscated code to DECODE the string. + * + * ```js + * function {fnName}(str){ + * return Buffer.from(str, 'base64').toString('utf-8') + * } + * ``` + */ + code: string; + encode: (str: string) => string; +} export interface ObfuscateOptions { /** @@ -13,8 +54,6 @@ export interface ObfuscateOptions { * | Low | 15/25 | 30% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/low.js) | * * You can extend each preset or all go without them entirely. (`"high"/"medium"/"low"`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ preset?: "high" | "medium" | "low" | false; @@ -25,8 +64,6 @@ export interface ObfuscateOptions { * * 1. `"node"` * 2. `"browser"` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ target: "node" | "browser"; @@ -34,8 +71,6 @@ export interface ObfuscateOptions { * ### `indent` * * Controls the indentation of the output. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ indent?: 2 | 4 | "tabs"; @@ -43,8 +78,6 @@ export interface ObfuscateOptions { * ### `compact` * * Remove's whitespace from the final output. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ compact?: boolean; @@ -59,8 +92,6 @@ export interface ObfuscateOptions { * ### `minify` * * Minifies redundant code. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ minify?: boolean; @@ -70,8 +101,6 @@ export interface ObfuscateOptions { * ### `renameVariables` * * Determines if variables should be renamed. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ renameVariables?: ProbabilityMap; @@ -80,7 +109,6 @@ export interface ObfuscateOptions { * * Renames top-level variables, turn this off for web-related scripts. Enabled by default. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ renameGlobals?: ProbabilityMap; @@ -121,7 +149,6 @@ export interface ObfuscateOptions { * * JSConfuser tries to reuse names when possible, creating very potent code. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ identifierGenerator?: ProbabilityMap< "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number" @@ -136,7 +163,6 @@ export interface ObfuscateOptions { * * Use a number to control the percentage from 0 to 1. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ controlFlowFlattening?: ProbabilityMap; @@ -145,7 +171,6 @@ export interface ObfuscateOptions { * * Global Concealing hides global variables being accessed. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ globalConcealing?: ProbabilityMap; @@ -156,7 +181,6 @@ export interface ObfuscateOptions { * * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringCompression?: ProbabilityMap; @@ -167,7 +191,6 @@ export interface ObfuscateOptions { * * `"console"` -> `decrypt('<~@rH7+Dert~>')` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringConcealing?: ProbabilityMap; @@ -178,7 +201,6 @@ export interface ObfuscateOptions { * * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringEncoding?: ProbabilityMap; @@ -189,7 +211,6 @@ export interface ObfuscateOptions { * * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ stringSplitting?: ProbabilityMap; @@ -198,7 +219,6 @@ export interface ObfuscateOptions { * * Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ duplicateLiteralsRemoval?: ProbabilityMap; @@ -207,7 +227,6 @@ export interface ObfuscateOptions { * * Creates a middleman function to process function calls. (`true/false/0-1`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ dispatcher?: ProbabilityMap; @@ -232,7 +251,6 @@ export interface ObfuscateOptions { * var C6z0jyO=[new Function('a2Fjjl',"function OqNW8x(OqNW8x){console['log'](OqNW8x)}return OqNW8x(...Array.prototype.slice.call(arguments,1))")];(function(){return C6z0jyO[0](C6z0jyO,...arguments)}('Hello World')) * ``` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ rgf?: ProbabilityMap; @@ -281,7 +299,6 @@ export interface ObfuscateOptions { * } * ``` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ objectExtraction?: ProbabilityMap; @@ -290,7 +307,6 @@ export interface ObfuscateOptions { * * Brings independent declarations to the highest scope. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ flatten?: ProbabilityMap; @@ -301,7 +317,6 @@ export interface ObfuscateOptions { * * Use a number to control the percentage from 0 to 1. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ deadCode?: ProbabilityMap; @@ -310,7 +325,6 @@ export interface ObfuscateOptions { * * Creates a calculator function to handle arithmetic and logical expressions. (`true/false/0-1`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ calculator?: ProbabilityMap; @@ -322,7 +336,6 @@ export interface ObfuscateOptions { * * [Identical to Obfuscator.io's Self Defending](https://github.com/javascript-obfuscator/javascript-obfuscator#selfdefending) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ selfDefending?: boolean; @@ -331,19 +344,9 @@ export interface ObfuscateOptions { * * Adds `debugger` statements throughout the code. Additionally adds a background function for DevTools detection. (`true/false/0-1`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ antiDebug?: ProbabilityMap; - /** - * ### `lock.context` - * - * Properties that must be present on the `window` object (or `global` for NodeJS). (`string[]`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - context?: string[]; - /** * ### `lock.tamperProtection` * @@ -356,7 +359,6 @@ export interface ObfuscateOptions { * * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md). * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ tamperProtection?: boolean | ((varName: string) => boolean); @@ -367,7 +369,6 @@ export interface ObfuscateOptions { * * Number should be in milliseconds. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ startDate?: number | Date | false; @@ -378,7 +379,6 @@ export interface ObfuscateOptions { * * Number should be in milliseconds. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ endDate?: number | Date | false; @@ -386,7 +386,6 @@ export interface ObfuscateOptions { * ### `lock.domainLock` * Array of regex strings that the `window.location.href` must follow. (`Regex[]` or `string[]`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ domainLock?: RegExp[] | string[] | false; @@ -397,7 +396,6 @@ export interface ObfuscateOptions { * * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md). * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ integrity?: ProbabilityMap; @@ -412,54 +410,19 @@ export interface ObfuscateOptions { * * Otherwise, the obfuscator falls back to crashing the process. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ countermeasures?: string | boolean; - customLocks?: { - /** - * Template lock code that must contain: - * - * - `{countermeasures}` - * - * The countermeasures function will be invoked when the lock is triggered. - * - * ```js - * if(window.navigator.userAgent.includes('Chrome')){ - * {countermeasures} - * } - * ``` - */ - code: string; - percentagePerBlock: number; - maxCount: number; - }[]; + customLocks?: CustomLock[]; }; - customStringEncodings?: { - /** - * Template string decoder that must contain: - * - * - `{fnName}` - * - * This function will be invoked by the obfuscated code to DECODE the string. - * - * ```js - * function {fnName}(str){ - * return Buffer.from(str, 'base64').toString('utf-8') - * } - * ``` - */ - code: string; - encode: (str: string) => string; - }[]; + customStringEncodings?: CustomStringEncoding[]; /** * ### `movedDeclarations` * * Moves variable declarations to the top of the context. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ movedDeclarations?: ProbabilityMap; @@ -469,7 +432,6 @@ export interface ObfuscateOptions { * An [Opaque Predicate](https://en.wikipedia.org/wiki/Opaque_predicate) is a predicate(true/false) that is evaluated at runtime, this can confuse reverse engineers * understanding your code. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ opaquePredicates?: ProbabilityMap; @@ -484,7 +446,6 @@ export interface ObfuscateOptions { * | `true`| Arrays are shifted *n* elements, unshifted at runtime | * | `false` | Feature disabled | * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ shuffle?: ProbabilityMap; @@ -493,7 +454,6 @@ export interface ObfuscateOptions { * * Enable logs to view the obfuscator's state. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ verbose?: boolean; @@ -502,7 +462,6 @@ export interface ObfuscateOptions { * * Set of global variables. *Optional*. (`Set`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ globalVariables?: Set; @@ -511,7 +470,6 @@ export interface ObfuscateOptions { * * Enable debug comments. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ debugComments?: boolean; @@ -520,7 +478,6 @@ export interface ObfuscateOptions { * * Modified functions will retain the correct `function.length` property. Enabled by default. (`true/false`) * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ preserveFunctionLength?: boolean; diff --git a/src/order.ts b/src/order.ts index 2e1ee7c..db23264 100644 --- a/src/order.ts +++ b/src/order.ts @@ -8,9 +8,9 @@ export enum Order { Flatten = 2, - RGF = 3, + Lock = 3, // Includes Integrity & Anti Debug - Lock = 4, // Includes Integrity & Anti Debug + RGF = 4, Dispatcher = 6, diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 28706ff..00e54c0 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -43,11 +43,11 @@ export class PluginInstance { warn(...messages: any[]) { if (this.options.verbose) { - console.warn(`[${this.name}]`, ...messages); + console.log(`WARN [${this.name}]`, ...messages); } } - error(...messages: any[]) { + error(...messages: any[]): never { throw new Error(`[${this.name}] ${messages.join(", ")}`); } } diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 2cb8be2..8d5ed50 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -18,7 +18,7 @@ import { ok } from "assert"; * ``` * @param path */ -export function ensureComputedExpression(path: NodePath) { +export function ensureComputedExpression(path: NodePath) { if ( (t.isObjectMember(path.parent) || t.isClassMethod(path.parent) || diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 6488bff..fcb0782 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -97,15 +97,6 @@ export function validateOptions(options: ObfuscateOptions) { } }); - if ( - options.target === "node" && - options.lock && - options.lock.browserLock && - options.lock.browserLock.length - ) { - throw new TypeError('browserLock can only be used when target="browser"'); - } - if (options.lock) { ok(typeof options.lock === "object", "options.lock must be an object"); Object.keys(options.lock).forEach((key) => { @@ -114,30 +105,6 @@ export function validateOptions(options: ObfuscateOptions) { } }); - // Validate browser-lock option - if ( - options.lock.browserLock && - typeof options.lock.browserLock !== "undefined" - ) { - ok( - Array.isArray(options.lock.browserLock), - "browserLock must be an array" - ); - ok( - !options.lock.browserLock.find( - (browserName) => !validBrowsers.has(browserName) - ), - 'Invalid browser name. Allowed: "firefox", "chrome", "iexplorer", "edge", "safari", "opera"' - ); - } - // Validate os-lock option - if (options.lock.osLock && typeof options.lock.osLock !== "undefined") { - ok(Array.isArray(options.lock.osLock), "osLock must be an array"); - ok( - !options.lock.osLock.find((osName) => !validOses.has(osName)), - 'Invalid OS name. Allowed: "windows", "linux", "osx", "ios", "android"' - ); - } // Validate domain-lock option if ( options.lock.domainLock && @@ -146,11 +113,6 @@ export function validateOptions(options: ObfuscateOptions) { ok(Array.isArray(options.lock.domainLock), "domainLock must be an array"); } - // Validate context option - if (options.lock.context && typeof options.lock.context !== "undefined") { - ok(Array.isArray(options.lock.context), "context must be an array"); - } - // Validate start-date option if ( typeof options.lock.startDate !== "undefined" && @@ -216,6 +178,19 @@ export function applyDefaultsToOptions( if (options.lock.selfDefending) { options.compact = true; // self defending forcibly enables this } + + if (!options.lock.customLocks) { + options.lock.customLocks = []; + } + + // Convert 'startDate' and 'endDate' to Dates + if (typeof options.lock.startDate === "number") { + options.lock.startDate = new Date(options.lock.startDate); + } + + if (typeof options.lock.endDate === "number") { + options.lock.endDate = new Date(options.lock.endDate); + } } // options.globalVariables outlines generic globals that should be present in the execution context From 379e2e3acba778c469077628368b5623ad9396ad Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 30 Aug 2024 02:27:11 -0400 Subject: [PATCH 017/103] Control Flow Flattening, Lock, Variable Concealing --- CHANGELOG.md | 4 +- babel.register.js | 1 + src/constants.ts | 22 + src/lock/integrity.ts | 89 +++ src/lock/lock.ts | 105 ++- src/obfuscator.ts | 53 +- src/options.ts | 6 +- src/order.ts | 8 +- src/templates/integrityTemplate.ts | 60 ++ src/templates/template.ts | 7 +- src/transforms/controlFlowFlattening.ts | 699 ++++++++++++++++++ src/transforms/finalizer.ts | 2 +- .../identifier/variableConcealing.ts | 68 ++ src/transforms/plugin.ts | 6 +- src/transforms/preparation.ts | 25 +- src/transforms/rgf.ts | 55 +- src/transforms/shuffle.ts | 3 +- src/transforms/variableMasking.ts | 6 +- src/utils/ast-utils.ts | 47 ++ src/validateOptions.ts | 11 +- .../controlFlowFlattening.test.ts | 50 +- test/transforms/lock/antiDebug.test.ts | 73 +- test/transforms/lock/browserLock.test.ts | 129 ---- test/transforms/lock/countermeasures.test.ts | 9 +- test/transforms/lock/dateLock.test.ts | 91 +++ test/transforms/lock/domainLock.test.ts | 55 ++ test/transforms/lock/integrity.test.ts | 40 +- test/transforms/lock/lock.test.ts | 204 ----- test/transforms/lock/osLock.test.ts | 312 -------- test/transforms/lock/selfDefending.test.ts | 4 +- test/transforms/lock/tamperProtection.test.ts | 22 +- 31 files changed, 1424 insertions(+), 842 deletions(-) create mode 100644 src/lock/integrity.ts create mode 100644 src/templates/integrityTemplate.ts create mode 100644 src/transforms/controlFlowFlattening.ts create mode 100644 src/transforms/identifier/variableConcealing.ts delete mode 100644 test/transforms/lock/browserLock.test.ts create mode 100644 test/transforms/lock/dateLock.test.ts create mode 100644 test/transforms/lock/domainLock.test.ts delete mode 100644 test/transforms/lock/osLock.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a5a7e..0b3187d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ - New option `renameLabels` to control if/which labels get renamed. Previously enabled but was not configurable. -- RGF no longers uses `new Function` +- RGF no longers uses `new Function` instead uses `eval` - Improved Moved Declaration's ability to move variables as unused function parameters @@ -13,6 +13,8 @@ - Removed Shuffle Hash option +- Control Flow Flattening no longer flattens `For Statement`/`While Statement/`Do Statement` for performance reasons. It now applies to `FunctionDeclaration` and flattens functions. + # `1.7.3` Tamper Protection diff --git a/babel.register.js b/babel.register.js index 708a174..13ec1a6 100644 --- a/babel.register.js +++ b/babel.register.js @@ -1,5 +1,6 @@ require("@babel/register")({ extensions: [".ts", ".tsx", ".js", ".jsx"], + // Automatically merges babel.config.js for us }); // Now run the main TypeScript file diff --git a/src/constants.ts b/src/constants.ts index f4bae73..6538024 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,11 +1,33 @@ export const predictableFunctionTag = "__JS_PREDICT__"; +/** + * A function is 'unsafe' if it requires 'eval', 'arguments' or 'this' + * + * Transformations will generally not touch unsafe functions. + */ export const UNSAFE = Symbol("unsafe"); + +/** + * A function is 'predictable' if the invoking parameter lengths are guaranteed to be known. + * + * ```js + * a(1,2,3) // predictable + * a(...[1,2,3]) // unpredictable + * ``` + */ export const PREDICTABLE = Symbol("predictable"); +/** + * A node is marked as 'skip' if it should not be transformed. + * + * Preserved throughout transformations, so be careful with this. + */ +export const SKIP = Symbol("skip"); + export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; + [SKIP]?: boolean; } /** diff --git a/src/lock/integrity.ts b/src/lock/integrity.ts new file mode 100644 index 0000000..c8606bb --- /dev/null +++ b/src/lock/integrity.ts @@ -0,0 +1,89 @@ +import { PluginObj } from "@babel/core"; +import { PluginArg } from "../transforms/plugin"; +import { Order } from "../order"; +import { getRandomInteger } from "../utils/random-utils"; +import { HashFunction } from "../templates/integrityTemplate"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { NodePath } from "@babel/traverse"; + +export interface IntegrityInterface { + fnPath: NodePath; + fnName: string; +} + +export const INTEGRITY = Symbol("Integrity"); + +export interface NodeIntegrity { + [INTEGRITY]?: IntegrityInterface; +} + +/** + * Integrity has two passes: + * + * - First in the 'lock' plugin to select functions and prepare them for Integrity + * - Secondly here to apply the integrity check + * + * This transformation must run last as any changes to the code will break the hash + */ +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.Integrity); + + return { + visitor: { + FunctionDeclaration: { + exit(funcDecPath) { + const integrityInterface = (funcDecPath.node as NodeIntegrity)[ + INTEGRITY + ]; + if (!integrityInterface) return; + + const newFnPath = integrityInterface.fnPath; + if (newFnPath.removed) return; + + const newFunctionDeclaration = newFnPath.node; + if ( + !newFunctionDeclaration || + !t.isFunctionDeclaration(newFunctionDeclaration) + ) + return; + + const { hashFnName } = me.globalState.lock.integrity; + + const newFnName = newFunctionDeclaration.id.name; + const binding = newFnPath.scope.getBinding(newFnName); + + // Function is redefined, do not apply integrity + if (!binding || binding.constantViolations.length > 0) return; + + var code = me.obfuscator.generateCode(newFunctionDeclaration); + var codeTrimmed = code.replace( + me.globalState.lock.integrity.sensitivityRegex, + "" + ); + + var seed = getRandomInteger(0, 10000000); + + var hashCode = HashFunction(codeTrimmed, seed); + + me.log(codeTrimmed, hashCode); + + funcDecPath.node.body = t.blockStatement( + new Template(` + var hash = ${hashFnName}(${newFunctionDeclaration.id.name}, ${seed}); + if(hash === ${hashCode}) { + {originalBody} + } else { + {countermeasures} + } + `).compile({ + originalBody: funcDecPath.node.body.body, + countermeasures: () => + me.globalState.lock.createCountermeasuresCode(), + }) + ); + }, + }, + }, + }; +}; diff --git a/src/lock/lock.ts b/src/lock/lock.ts index fe4f6ae..bae03d4 100644 --- a/src/lock/lock.ts +++ b/src/lock/lock.ts @@ -5,6 +5,10 @@ import { chance, choice } from "../utils/random-utils"; import Template from "../templates/template"; import * as t from "@babel/types"; import { CustomLock } from "../options"; +import { getParentFunctionOrProgram } from "../utils/ast-utils"; +import { INTEGRITY, NodeIntegrity } from "./integrity"; +import { HashTemplate } from "../templates/integrityTemplate"; +import { NodeSymbol, SKIP } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Lock); @@ -107,13 +111,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { invokeCountermeasuresFnName = me.getPlaceholder("invokeCountermeasures"); } - function createCountermeasuresCode(): t.Statement[] { + me.globalState.lock.createCountermeasuresCode = () => { if (invokeCountermeasuresFnName) { return new Template(`${invokeCountermeasuresFnName}()`).compile(); } + if (me.options.lock.countermeasures === false) { + return []; + } + return new Template(`while(true){}`).compile(); - } + }; function applyLockToBlock(path: NodePath, customLock: CustomLock) { let times = timesMap.get(customLock); @@ -151,7 +159,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const template = typeof lockCode === "string" ? new Template(lockCode) : lockCode; const lockNodes = template.compile({ - countermeasures: () => createCountermeasuresCode(), + countermeasures: () => me.globalState.lock.createCountermeasuresCode(), }); var p = path.unshiftContainer("body", lockNodes); p.forEach((p) => p.skip()); @@ -161,7 +169,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { visitor: { Identifier: { enter(path) { - console.log(path.node.name); if (path.node.name !== me.options.lock.countermeasures) { return; } @@ -185,13 +192,23 @@ export default ({ Plugin }: PluginArg): PluginObj => { countermeasuresNode = path; }, }, + + Block: { + exit(path) { + var customLock = choice(me.options.lock.customLocks); + if (customLock) { + applyLockToBlock(path, customLock); + } + }, + }, + Program: { exit(path) { // Insert invokeCountermeasures function if (invokeCountermeasuresFnName) { if (!countermeasuresNode) { - me.warn( + me.error( "Countermeasures function named '" + me.options.lock.countermeasures + "' was not found." @@ -210,14 +227,80 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.unshiftContainer("body", statements).forEach((p) => p.skip()); } + + if (me.options.lock.integrity) { + const hashFnName = me.getPlaceholder() + "_hash"; + const imulFnName = me.getPlaceholder() + "_imul"; + + const { sensitivityRegex } = me.globalState.lock.integrity; + me.globalState.lock.integrity.hashFnName = hashFnName; + + path + .unshiftContainer( + "body", + HashTemplate.compile({ + imul: imulFnName, + name: hashFnName, + hashingUtilFnName: me.getPlaceholder(), + sensitivityRegex: () => + t.newExpression(t.identifier("RegExp"), [ + t.stringLiteral(sensitivityRegex.source), + t.stringLiteral(sensitivityRegex.flags), + ]), + }) + ) + .forEach((path) => { + (path.node as NodeSymbol)[SKIP] = true; + path.scope.registerDeclaration(path); + }); + } }, }, - Block: { - exit(path) { - var customLock = choice(me.options.lock.customLocks); - if (customLock) { - applyLockToBlock(path, customLock); - } + + // Integrity first pass + // Functions are prepared for Integrity by simply extracting the function body + // The extracted function is hashed in the 'integrity' plugin + FunctionDeclaration: { + exit(funcDecPath) { + // Mark functions for integrity + // Don't apply to async or generator functions + if (funcDecPath.node.async || funcDecPath.node.generator) return; + + if (funcDecPath.find((p) => (p.node as NodeSymbol)[SKIP])) return; + + var program = getParentFunctionOrProgram(funcDecPath); + // Only top-level functions + if (!program.isProgram()) return; + + var newFnName = me.getPlaceholder(); + var newFunctionDeclaration = t.functionDeclaration( + t.identifier(newFnName), + funcDecPath.node.params, + funcDecPath.node.body + ); + (newFunctionDeclaration as NodeSymbol)[SKIP] = true; + + var [newFnPath] = program.unshiftContainer( + "body", + newFunctionDeclaration + ); + + // Function simply calls the new function + // In the case Integrity cannot transform the function, the original behavior is preserved + funcDecPath.node.body = t.blockStatement( + new Template(` + return ${newFnName}(...arguments) + `).compile() + ); + + // Parameters no longer needed, using 'arguments' instead + funcDecPath.node.params = []; + + // Mark the function for integrity + (funcDecPath.node as NodeIntegrity)[INTEGRITY] = { + fnPath: newFnPath, + fnName: newFnName, + }; }, }, }, diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 1496f80..9b20fb3 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -32,6 +32,10 @@ import rgf from "./transforms/rgf"; import flatten from "./transforms/flatten"; import stringConcealing from "./transforms/string/stringConcealing"; import lock from "./lock/lock"; +import integrity from "./lock/integrity"; +import { Statement } from "@babel/types"; +import controlFlowFlattening from "./transforms/controlFlowFlattening"; +import variableConcealing from "./transforms/identifier/variableConcealing"; export default class Obfuscator { plugins: { @@ -42,6 +46,19 @@ export default class Obfuscator { totalPossibleTransforms: number = 0; + globalState = { + lock: { + integrity: { + hashFnName: "", + sensitivityRegex: / |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g, + }, + + createCountermeasuresCode: (): Statement[] => { + throw new Error("Not implemented"); + }, + }, + }; + public constructor(userOptions: ObfuscateOptions) { validateOptions(userOptions); this.options = applyDefaultsToOptions({ ...userOptions }); @@ -56,27 +73,32 @@ export default class Obfuscator { }; push(true, preparation); - push(this.options.deadCode, deadCode); - - push(this.options.dispatcher, dispatcher); - push(this.options.duplicateLiteralsRemoval, duplicateLiteralsRemoval); push(this.options.objectExtraction, objectExtraction); + push(this.options.flatten, flatten); + push(this.options.lock, lock); + push(this.options.rgf, rgf); + push(this.options.dispatcher, dispatcher); + push(this.options.deadCode, deadCode); + push(this.options.controlFlowFlattening, controlFlowFlattening); + push(this.options.calculator, calculator); push(this.options.globalConcealing, globalConcealing); - push(this.options.variableMasking, variableMasking); - push(this.options.renameVariables, renameVariables); + // Opaque Predicates + push(this.options.stringSplitting, stringSplitting); push(this.options.stringConcealing, stringConcealing); push(this.options.stringCompression, stringCompression); - push(this.options.stringSplitting, stringSplitting); + push(this.options.variableMasking, variableMasking); + push(this.options.duplicateLiteralsRemoval, duplicateLiteralsRemoval); push(this.options.shuffle, shuffle); - push(this.options.astScrambler, astScrambler); - push(this.options.calculator, calculator); push(this.options.movedDeclarations, movedDeclarations); push(this.options.renameLabels, renameLabels); - push(this.options.rgf, rgf); - push(this.options.flatten, flatten); - push(this.options.lock, lock); + // Minify + push(this.options.astScrambler, astScrambler); + push(this.options.renameVariables, renameVariables); push(true, finalizer); + push(this.options.lock?.integrity, integrity); + + push(this.options.variableConcealing, variableConcealing); allPlugins.map((pluginFunction) => { var pluginInstance: PluginInstance; @@ -175,6 +197,13 @@ export default class Obfuscator { }); } + /** + * Calls `Obfuscator.generateCode` with the current instance options + */ + generateCode(ast: T): string { + return Obfuscator.generateCode(ast, this.options); + } + /** * Generates code from an AST using `@babel/generator` */ diff --git a/src/options.ts b/src/options.ts index 6f88fc1..24a8581 100644 --- a/src/options.ts +++ b/src/options.ts @@ -408,7 +408,9 @@ export interface ObfuscateOptions { * * [Learn more about the rules of your countermeasures function](https://github.com/MichaelXF/js-confuser/blob/master/Countermeasures.md). * - * Otherwise, the obfuscator falls back to crashing the process. + * If no countermeasures function is provided (`undefined` or `true`), the obfuscator falls back to crashing the process. + * + * If `countermeasures` is `false`, no crash will occur. * */ countermeasures?: string | boolean; @@ -482,4 +484,6 @@ export interface ObfuscateOptions { preserveFunctionLength?: boolean; astScrambler?: boolean; + + variableConcealing?: ProbabilityMap; } diff --git a/src/order.ts b/src/order.ts index db23264..83f5ff4 100644 --- a/src/order.ts +++ b/src/order.ts @@ -8,7 +8,7 @@ export enum Order { Flatten = 2, - Lock = 3, // Includes Integrity & Anti Debug + Lock = 3, // Includes Anti Debug RGF = 4, @@ -20,8 +20,6 @@ export enum Order { ControlFlowFlattening = 10, - Eval = 11, - GlobalConcealing = 12, OpaquePredicates = 13, @@ -38,8 +36,6 @@ export enum Order { Shuffle = 24, - NameRecycling = 25, - MovedDeclarations = 26, RenameLabels = 27, @@ -51,4 +47,6 @@ export enum Order { RenameVariables = 30, Finalizer = 35, + + Integrity = 36, // Must run last } diff --git a/src/templates/integrityTemplate.ts b/src/templates/integrityTemplate.ts new file mode 100644 index 0000000..6588b04 --- /dev/null +++ b/src/templates/integrityTemplate.ts @@ -0,0 +1,60 @@ +import Template from "./template"; + +/** + * Hashing Algorithm for Integrity: `cyrb53` + * @param str + * @param seed + */ +export function HashFunction(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, + h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = + Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ + Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = + Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ + Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1 >>> 0); +} + +// In template form to be inserted into code +export const HashTemplate = new Template(` +// Math.imul polyfill for ES5 +var {imul} = Math.imul || function(opA, opB){ + opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. + // floating points give us 53 bits of precision to work with plus 1 sign bit + // automatically handled for our convienence: + // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 + // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + var result = (opA & 0x003fffff) * opB; + // 2. We can remove an integer coersion from the statement above because: + // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 + // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + if (opA & 0xffc00000 /*!== 0*/) result += (opA & 0xffc00000) * opB |0; + return result |0; +}; + +function {hashingUtilFnName}(str, seed) { + var h1 = 0xdeadbeef ^ seed; + var h2 = 0x41c6ce57 ^ seed; + for (var i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = {imul}(h1 ^ ch, 2654435761); + h2 = {imul}(h2 ^ ch, 1597334677); + } + h1 = {imul}(h1 ^ (h1>>>16), 2246822507) ^ {imul}(h2 ^ (h2>>>13), 3266489909); + h2 = {imul}(h2 ^ (h2>>>16), 2246822507) ^ {imul}(h1 ^ (h1>>>13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1>>>0); +}; + +// Simple function that returns .toString() value with spaces replaced out +function {name}(fnObject, seed, regex={sensitivityRegex}){ + var fnStringed = fnObject["toString"]()["replace"](regex, ""); + return {hashingUtilFnName}(fnStringed, seed); +} +`); diff --git a/src/templates/template.ts b/src/templates/template.ts index 8f5780d..418ca01 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -102,7 +102,7 @@ export default class Template { traverse(ast, { Identifier(path: NodePath) { const name = path.node.name; - if (allVariables[name]) { + if (astNames.has(name)) { let value = allVariables[name]; if (typeof value === "function") { value = value(); @@ -127,7 +127,10 @@ export default class Template { let file: babelTypes.File; try { - file = parse(output, { sourceType: "module" }); + file = parse(output, { + sourceType: "module", + allowReturnOutsideFunction: true, + }); } catch (e) { throw new Error( output + "\n" + "Template failed to parse: " + (e as Error).message diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts new file mode 100644 index 0000000..2921166 --- /dev/null +++ b/src/transforms/controlFlowFlattening.ts @@ -0,0 +1,699 @@ +import { NodePath, PluginObj, traverse, Visitor } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { Order } from "../order"; +import { computeProbabilityMap } from "../probability"; +import { + ensureComputedExpression, + getParentFunctionOrProgram, + getPatternIdentifierNames, +} from "../utils/ast-utils"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { + chance, + choice, + getRandomInteger, + shuffle, +} from "../utils/random-utils"; +import { IntGen } from "../utils/IntGen"; +import { ok } from "assert"; + +/** + * Control-Flow-Flattening breaks your code into Basic Blocks. + * + * Basic Blocks are simple statements without any jumps or branches. + */ +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.ControlFlowFlattening); + + const isDebug = true; + + return { + visitor: { + Block: { + exit(blockPath) { + if (!blockPath.isProgram()) return; + + const body = blockPath.node.body; + const blockFnParent = getParentFunctionOrProgram(blockPath); + + let hasContinueOrBreak = false; + blockPath.traverse({ + "ContinueStatement|BreakStatement"(path) { + if (getParentFunctionOrProgram(path) === blockFnParent) { + hasContinueOrBreak = true; + path.stop(); + } + }, + }); + + if (hasContinueOrBreak) { + return; + } + + // Limit how many numbers get entangled + let mangledNumericLiteralsCreated = 0; + + // Must be at least 3 statements or more + if (body.length < 3) { + return; + } + + // Check user's threshold setting + if (!computeProbabilityMap(me.options.controlFlowFlattening)) { + return; + } + + const prefix = me.getPlaceholder(); + + const mainFnName = prefix + "_main"; + + const stateVars = new Array(isDebug ? 1 : getRandomInteger(2, 5)) + .fill("") + .map((_, i) => `${prefix}_state_${i}`); + + const argVar = prefix + "_arg"; + + const basicBlocks = new Map(); + + // Map labels to states + const statIntGen = new IntGen(); + + interface BasicBlockOptions { + topLevel: boolean; + fnLabel: string; + } + + /** + * A Basic Block is a sequence of instructions with no diversion except at the entry and exit points. + */ + class BasicBlock { + totalState: number; + stateValues: number[]; + + constructor( + public label: string, + public options: BasicBlockOptions, + public body: t.Statement[] = [] + ) { + if (isDebug) { + // States in debug mode are just 1, 2, 3, ... + this.totalState = basicBlocks.size + 1; + } else { + this.totalState = statIntGen.generate(); + } + + // Correct state values + // Start with random numbers + this.stateValues = stateVars.map(() => + getRandomInteger(-250, 250) + ); + + // Try to re-use old state values to make diffs smaller + if (basicBlocks.size > 1) { + const lastBlock = [...basicBlocks.values()].at(-1); + this.stateValues = lastBlock.stateValues.map((oldValue, i) => { + return choice([oldValue, this.stateValues[i]]); + }); + } + + // Correct one of the values so that the accumulated sum is equal to the state + const correctIndex = getRandomInteger(0, this.stateValues.length); + + const getCurrentState = () => + this.stateValues.reduce((a, b) => a + b, 0); + + // Correct the value + this.stateValues[correctIndex] = + this.totalState - + (getCurrentState() - this.stateValues[correctIndex]); + + ok(getCurrentState() === this.totalState); + + // Store basic block + basicBlocks.set(label, this); + } + } + + const switchLabel = me.getPlaceholder(); + const breakStatement = () => { + return t.breakStatement(t.identifier(switchLabel)); + }; + + const startLabel = me.getPlaceholder(); + const endLabel = me.getPlaceholder(); + + let currentBasicBlock = new BasicBlock(startLabel, { + topLevel: true, + fnLabel: null, + }); + + interface Metadata { + label?: string; + type?: string; + } + + interface NodeMetadata { + metadata?: Metadata; + } + + function ControlStatement(metadata: Metadata): t.ExpressionStatement { + var exprStmt = new Template( + `ControlStatement()` + ).single(); + + (exprStmt.expression as NodeMetadata).metadata = metadata; + + return exprStmt; + } + + function GotoControlStatement(label: string) { + return ControlStatement({ + type: "goto", + label, + }); + } + + // Ends the current block and starts a new one + function endCurrentBasicBlock( + { + jumpToNext = true, + nextLabel = me.getPlaceholder(), + prevJumpTo = null, + } = {}, + options: BasicBlockOptions + ) { + if (prevJumpTo) { + currentBasicBlock.body.push(GotoControlStatement(prevJumpTo)); + } else if (jumpToNext) { + currentBasicBlock.body.push(GotoControlStatement(nextLabel)); + } + + currentBasicBlock = new BasicBlock(nextLabel, options); + } + + const callableMap = new Map(); + const callableOriginalFnMap = new Map< + string, + t.FunctionDeclaration + >(); + + const prependNodes = []; + + function flattenIntoBasicBlocks( + block: t.Block, + options: BasicBlockOptions + ) { + for (const statement of block.body) { + // Keep Imports before everything else + if (t.isImportDeclaration(statement)) { + prependNodes.push(statement); + continue; + } + + if (t.isClassDeclaration(statement)) { + prependNodes.push(statement); + continue; + } + + // Convert Function Declaration into Basic Blocks + if (t.isFunctionDeclaration(statement)) { + const fnName = statement.id.name; + + // Function cannot be redefined + if (statement.async || statement.generator) { + if (options.topLevel) { + prependNodes.push(statement); + } else { + currentBasicBlock.body.push(statement); + continue; + } + continue; + } + + const isRedefined = callableOriginalFnMap.has(fnName); + + const afterPath = me.getPlaceholder(); + const fnLabel = me.getPlaceholder(); + + if (!isRedefined) { + callableOriginalFnMap.set(fnName, statement); + callableMap.set(fnName, fnLabel); + } else { + } + + currentBasicBlock.body.unshift( + t.expressionStatement( + t.assignmentExpression( + "=", + t.identifier(fnName), + ControlStatement({ + type: "function", + label: fnLabel, + }).expression + ) + ) + ); + + var newBasicBlockOptions = { topLevel: false, fnLabel }; + + endCurrentBasicBlock( + { + prevJumpTo: afterPath, + nextLabel: fnLabel, + }, + newBasicBlockOptions + ); + + let embeddedName = fnLabel + "_" + statement.id.name; + statement.id.name = embeddedName; + + // Start function body + currentBasicBlock.body.push(statement); + currentBasicBlock.body.push( + t.returnStatement( + t.callExpression(t.identifier(embeddedName), [ + t.spreadElement(t.identifier(argVar)), + ]) + ) + ); + + endCurrentBasicBlock( + { + jumpToNext: false, + nextLabel: afterPath, + }, + options + ); + + if (!isRedefined) { + prependNodes.push( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(fnName), + createBasicBlockFunctionExpression(fnLabel) + ), + ]) + ); + } + + continue; + } + + // Convert IF statements into Basic Blocks + if (t.isIfStatement(statement)) { + function ensureBlockStatement( + node: t.Statement + ): t.BlockStatement { + if (t.isBlockStatement(node)) { + return node; + } + return t.blockStatement([node]); + } + + const test = statement.test; + const consequent = ensureBlockStatement(statement.consequent); + const alternate = statement.alternate + ? ensureBlockStatement(statement.alternate) + : null; + + const consequentLabel = me.getPlaceholder(); + const alternateLabel = alternate ? me.getPlaceholder() : null; + const afterPath = me.getPlaceholder(); + + currentBasicBlock.body.push( + t.ifStatement( + test, + GotoControlStatement(consequentLabel), + alternateLabel + ? GotoControlStatement(alternateLabel) + : GotoControlStatement(afterPath) + ) + ); + + endCurrentBasicBlock( + { + jumpToNext: false, + nextLabel: consequentLabel, + }, + options + ); + + flattenIntoBasicBlocks(consequent, options); + + if (alternate) { + endCurrentBasicBlock( + { + prevJumpTo: afterPath, + nextLabel: alternateLabel, + }, + options + ); + + flattenIntoBasicBlocks(alternate, options); + } + + endCurrentBasicBlock( + { + prevJumpTo: afterPath, + nextLabel: afterPath, + }, + options + ); + + continue; + } + + // 3 or more statements should be split more + if ( + currentBasicBlock.body.length > 1 && + chance(50 + currentBasicBlock.body.length) + ) { + endCurrentBasicBlock({}, options); + } + + currentBasicBlock.body.push(statement); + } + } + + // Convert our code into Basic Blocks + flattenIntoBasicBlocks(blockPath.node, { + topLevel: true, + fnLabel: null, + }); + + // Ensure always jumped to the Program end + endCurrentBasicBlock( + { + jumpToNext: true, + nextLabel: endLabel, + }, + { topLevel: true, fnLabel: null } + ); + + const topLevelNames = new Set(); + + // Remap 'GotoStatement' to actual state assignments and Break statements + for (const basicBlock of basicBlocks.values()) { + const { stateValues: currentStateValues } = basicBlock; + // Wrap the statement in a Babel path to allow traversal + + const visitor: Visitor = { + FunctionDeclaration: { + exit(fnPath) { + if (!callableMap.has(fnPath.node.id.name)) { + return; + } + + var block = fnPath.find((p) => + p.isBlock() + ) as NodePath; + + var oldName = fnPath.node.id.name; + var newName = me.getPlaceholder(); + + fnPath.node.id.name = newName; + + block.node.body.unshift( + t.expressionStatement( + t.assignmentExpression( + "=", + t.identifier(oldName), + t.identifier(newName) + ) + ) + ); + }, + }, + // Mangle numbers with the state values + NumericLiteral: { + exit(numPath) { + // Don't mangle numbers in debug mode + if (isDebug) return; + + const num = numPath.node.value; + if ( + Math.floor(num) !== num || + Math.abs(num) > 100_000 || + !Number.isFinite(num) || + Number.isNaN(num) + ) + return; + + const numFnParent = getParentFunctionOrProgram(numPath); + if (!numFnParent.isProgram()) return; + + if (chance(50 + mangledNumericLiteralsCreated)) return; + + mangledNumericLiteralsCreated++; + + const index = getRandomInteger(0, stateVars.length - 1); + const stateVar = stateVars[index]; + + // num = 50 + // stateVar = 30 + // stateVar + 30 + + const diff = t.binaryExpression( + "+", + t.identifier(stateVar), + t.numericLiteral(num - currentStateValues[index]) + ); + + ensureComputedExpression(numPath); + + numPath.replaceWith(diff); + numPath.skip(); + }, + }, + + BindingIdentifier: { + exit(path) { + if (path.findParent((p) => p.isFunction())) return; + + if (basicBlock.options.topLevel) { + if (!callableMap.has(path.node.name)) { + topLevelNames.add(path.node.name); + } + } + + // Variable declaration -> Assignment expression + var variableDeclaration = path.findParent((p) => + p.isVariableDeclaration() + ) as NodePath; + if (!variableDeclaration) return; + + var wrapInExpressionStatement = true; + + if (variableDeclaration.parentPath.isForStatement()) { + if (variableDeclaration.node.kind === "var") { + wrapInExpressionStatement = false; + } else { + // 'let'/'const' don't get extracted + return; + } + } + + ok(variableDeclaration.node.declarations.length === 1); + + const assignment = t.assignmentExpression( + "=", + variableDeclaration.node.declarations[0].id, + variableDeclaration.node.declarations[0].init || + t.identifier("undefined") + ); + + // Replace variable declaration with assignment expression + variableDeclaration.replaceWith( + wrapInExpressionStatement + ? t.expressionStatement(assignment) + : assignment + ); + }, + }, + + CallExpression: { + exit(path) { + if ( + t.isIdentifier(path.node.callee) && + path.node.callee.name === "ControlStatement" + ) { + const metadata = (path.node as any).metadata as Metadata; + ok(metadata); + + const { label, type } = metadata; + ok(["goto", "function"].includes(type)); + + switch (type) { + case "function": + var node = createBasicBlockFunctionExpression(label); + path.replaceWith(node); + break; + case "goto": + const { stateValues: newStateValues } = + basicBlocks.get(label); + + const assignments = []; + + for (let i = 0; i < stateVars.length; i++) { + const oldValue = currentStateValues[i]; + const newValue = newStateValues[i]; + if (oldValue === newValue) continue; // No diff needed if the value doesn't change + + let assignment = t.assignmentExpression( + "=", + t.identifier(stateVars[i]), + t.numericLiteral(newValue) + ); + + if (!isDebug) { + // Use diffs to create confusing code + assignment = t.assignmentExpression( + "+=", + t.identifier(stateVars[i]), + t.numericLiteral(newValue - oldValue) + ); + } + + assignments.push(assignment); + } + + path.parentPath + .replaceWith( + t.expressionStatement( + t.sequenceExpression(assignments) + ) + )[0] + .skip(); + + // Debugging information + // console.log("Path:", path); + // console.log("ParentPath:", path.parentPath); + + path.insertAfter(breakStatement()); + + break; + } + } + }, + }, + }; + + traverse(t.file(t.program(basicBlock.body)), visitor); + } + + let switchCases: t.SwitchCase[] = []; + let blocks = Array.from(basicBlocks.values()); + if (!isDebug) { + shuffle(blocks); + } + for (const block of blocks) { + if (block.label === endLabel) { + ok(block.body.length === 0); + continue; + } + + const tests = [t.numericLiteral(block.totalState)]; + + if (!isDebug) { + // Add some random numbers to confuse the switch statement + for (let i = 0; i < getRandomInteger(1, 3); i++) { + tests.push(t.numericLiteral(statIntGen.generate())); + } + + shuffle(tests); + } + + const lastTest = tests.pop(); + + for (var test of tests) { + switchCases.push(t.switchCase(test, [])); + } + + switchCases.push(t.switchCase(lastTest, block.body)); + } + + if (!isDebug) { + // A random test can be 'default' + choice(switchCases).test = null; + } + + const discriminant = new Template(` + ${stateVars.join(" + ")} + `).expression(); + + // Create a new SwitchStatement + const switchStatement = t.labeledStatement( + t.identifier(switchLabel), + t.switchStatement(discriminant, switchCases) + ); + + const startStateValues = basicBlocks.get(startLabel).stateValues; + const endTotalState = basicBlocks.get(endLabel).totalState; + + const whileStatement = t.whileStatement( + t.binaryExpression( + "!==", + t.cloneNode(discriminant), + t.numericLiteral(endTotalState) + ), + t.blockStatement([switchStatement]) + ); + + function variableDeclaration(name: string, value?: t.Expression) { + return t.variableDeclaration("var", [ + t.variableDeclarator(t.identifier(name), value), + ]); + } + + const variableDeclarations: t.Statement[] = [ + ...Array.from(topLevelNames).map((name) => + variableDeclaration(name) + ), + ]; + + function createBasicBlockFunctionExpression(label: string) { + return t.functionExpression( + null, + [t.restElement(t.identifier(argVar))], + t.blockStatement([ + t.returnStatement( + t.callExpression(t.identifier(mainFnName), [ + ...basicBlocks + .get(label) + .stateValues.map((stateValue) => + t.numericLiteral(stateValue) + ), + t.identifier(argVar), + ]) + ), + ]) + ); + } + + const mainFnDeclaration = t.functionDeclaration( + t.identifier(mainFnName), + [ + ...stateVars.map((stateVar) => t.identifier(stateVar)), + t.identifier(argVar), + ], + t.blockStatement([whileStatement]) + ); + + blockPath.node.body = [ + ...prependNodes, + ...variableDeclarations, + mainFnDeclaration, + t.expressionStatement( + t.callExpression(t.identifier(mainFnName), [ + ...startStateValues.map((stateValue) => + t.numericLiteral(stateValue) + ), + ]) + ), + ]; + }, + }, + }, + }; +}; diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index 9836e22..27357a5 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -5,7 +5,7 @@ import { Order } from "../order"; import stringEncoding from "./string/stringEncoding"; export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Flatten); + const me = Plugin(Order.Finalizer); const stringEncodingPlugin = stringEncoding(me); return { diff --git a/src/transforms/identifier/variableConcealing.ts b/src/transforms/identifier/variableConcealing.ts new file mode 100644 index 0000000..90365df --- /dev/null +++ b/src/transforms/identifier/variableConcealing.ts @@ -0,0 +1,68 @@ +import { PluginObj } from "@babel/core"; +import { PluginArg } from "../plugin"; +import { Order } from "../../order"; +import * as t from "@babel/types"; +import { ok } from "assert"; + +/** + * Variable Concealing uses the `with` statement to obfuscate variable names. + */ +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.RenameVariables); + + return { + visitor: { + FunctionDeclaration: { + exit(path) { + var scopeName = me.getPlaceholder(); + + for (var identifierName in path.scope.bindings) { + const binding = path.scope.bindings[identifierName]; + if (binding.kind === "param") { + continue; + } + + // Replace 'var' kind to simply define a property on the scope object + if (binding.kind === "var") { + var declaration = binding.path.parentPath; + ok( + declaration.isVariableDeclaration(), + "Expected variable declaration" + ); + ok( + declaration.node.declarations.length === 1, + "Expected single declaration" + ); + + declaration.replaceWith( + t.expressionStatement( + t.assignmentExpression( + "=", + t.memberExpression( + t.identifier(scopeName), + t.identifier(identifierName) + ), + declaration.node.declarations[0].init + ) + ) + ); + } + } + + path.node.body = t.blockStatement([ + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(scopeName), + t.objectExpression([]) + ), + ]), + t.withStatement( + t.identifier(scopeName), + t.blockStatement(path.node.body.body) + ), + ]); + }, + }, + }, + }; +}; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 00e54c0..7a381f9 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -27,8 +27,12 @@ export class PluginInstance { return this.obfuscator.options; } + get globalState() { + return this.obfuscator.globalState; + } + getPlaceholder(suffix = "") { - return "__p_" + getRandomString(6) + (suffix ? "_" + suffix : ""); + return "__p_" + getRandomString(4) + (suffix ? "_" + suffix : ""); } generateRandomIdentifier() { diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index d731db4..020a2c5 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -11,6 +11,7 @@ import { variableFunctionName, } from "../constants"; import { ok } from "assert"; +import { getPatternIdentifierNames } from "../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Preparation); @@ -147,13 +148,25 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) ); } else { - path.replaceWithMultiple( - path.node.declarations.map((declaration) => - t.variableDeclaration(path.node.kind, [declaration]) + path + .replaceWithMultiple( + path.node.declarations.map((declaration, i) => { + var names = getPatternIdentifierNames( + path.get("declarations")[i] + ); + names.forEach((name) => { + path.scope.removeBinding(name); + }); + + var newNode = t.variableDeclaration(path.node.kind, [ + declaration, + ]); + return newNode; + }) ) - ); - - path.scope.crawl(); + .forEach((path) => { + path.scope.registerDeclaration(path); + }); } } } diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 0d1e222..f4791fb 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -56,6 +56,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (t.isArrowFunctionExpression(path.node)) return; const name = getFunctionName(path); + if (name === me.options.lock?.countermeasures) return; + me.log(name); if (!computeProbabilityMap(me.options.rgf, (x) => x, name)) return; @@ -83,8 +85,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { me.log( "Skipping function " + name + - " due to reference to outside variable", - path.node.loc + " due to reference to outside variable:" + + identifierPreventingTransform ); return; } @@ -141,28 +143,35 @@ export default ({ Plugin }: PluginArg): PluginObj => { var index = rgfArrayExpression.elements.length; rgfArrayExpression.elements.push(functionExpression); - path.node.body = t.blockStatement([ - t.returnStatement( - t.callExpression( - t.memberExpression( - t.memberExpression( - t.identifier(rgfArrayName), - t.numericLiteral(index), - true - ), - t.stringLiteral("apply"), - true + // Params no longer needed, using 'arguments' instead + path.node.params = []; + + path + .get("body") + .replaceWith( + t.blockStatement([ + t.returnStatement( + t.callExpression( + t.memberExpression( + t.memberExpression( + t.identifier(rgfArrayName), + t.numericLiteral(index), + true + ), + t.stringLiteral("apply"), + true + ), + [ + t.arrayExpression([ + t.identifier("this"), + t.identifier(rgfArrayName), + ]), + t.identifier("arguments"), + ] + ) ), - [ - t.arrayExpression([ - t.identifier("this"), - t.identifier(rgfArrayName), - ]), - t.identifier("arguments"), - ] - ) - ), - ]); + ]) + ); }, }, }, diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index 91cec89..36a192f 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -39,7 +39,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { var runtimeFn = me.getPlaceholder(); - (path.scope.path as NodePath).unshiftContainer( + var program = path.find((p) => p.isProgram()) as NodePath; + program.unshiftContainer( "body", new Template( ` diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 6567562..772cc8d 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -78,6 +78,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (binding.path.parentPath.node?.type !== "VariableDeclaration") return; if (binding.path.parentPath.node.declarations.length > 1) return; + if (!t.isIdentifier(binding.path.parentPath.node.declarations[0].id)) + return; } else { return; } @@ -125,7 +127,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { [binding.path, ...binding.constantViolations].forEach( (constantViolation) => { constantViolation.traverse({ - Identifier(idPath) { + "ReferencedIdentifier|BindingIdentifier"(idPath) { + if (!idPath.isIdentifier()) return; + const cBinding = idPath.scope.getBinding(idPath.node.name); if (cBinding !== binding) return; diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 8d5ed50..409d0db 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -2,6 +2,53 @@ import * as t from "@babel/types"; import { NodePath } from "@babel/core"; import { ok } from "assert"; +export function containsLexicallyBoundVariables(path: NodePath): boolean { + var foundLexicalDeclaration = false; + + path.traverse({ + VariableDeclaration(declarationPath) { + if ( + declarationPath.node.kind === "let" || + declarationPath.node.kind === "const" + ) { + foundLexicalDeclaration = true; + declarationPath.stop(); + } + }, + + ClassDeclaration(declarationPath) { + foundLexicalDeclaration = true; + declarationPath.stop(); + }, + }); + + return foundLexicalDeclaration; +} + +export function getPatternIdentifierNames(path: NodePath): string[] { + var names = new Set(); + + var functionParent = path.find((parent) => parent.isFunction()); + + path.traverse({ + BindingIdentifier: (bindingPath) => { + var bindingFunctionParent = bindingPath.find((parent) => + parent.isFunction() + ); + if (functionParent === bindingFunctionParent) { + names.add(bindingPath.node.name); + } + }, + }); + + // Check if the path itself is a binding identifier + if (path.isBindingIdentifier()) { + names.add(path.node.name); + } + + return Array.from(names); +} + /** * Ensures a `String Literal` is 'computed' before replacing it with a more complex expression. * diff --git a/src/validateOptions.ts b/src/validateOptions.ts index fcb0782..0d69bbe 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -36,6 +36,7 @@ const validProperties = new Set([ "debugComments", "preserveFunctionLength", "astScrambler", + "variableConcealing", ]); const validLockProperties = new Set([ @@ -52,16 +53,6 @@ const validLockProperties = new Set([ "countermeasures", ]); -const validOses = new Set(["windows", "linux", "osx", "ios", "android"]); -const validBrowsers = new Set([ - "firefox", - "chrome", - "iexplorer", - "edge", - "safari", - "opera", -]); - export function validateOptions(options: ObfuscateOptions) { if (!options || Object.keys(options).length <= 1) { /** diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index d31dd83..74e6f18 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -33,7 +33,7 @@ test("Variant #1: Obfuscate code and still execute in correct order", async () = expect(TEST_OUTPUT).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -test("Variant #2: Obfuscate for loops", async () => { +test("Variant #2: Properly handle for-loop", async () => { var code = ` var array = []; @@ -52,9 +52,6 @@ test("Variant #2: Obfuscate for loops", async () => { // Ensure Control Flow Flattening applied expect(output).toContain("while"); - // Ensure the for statement got flattened - expect(output).not.toContain("for"); - // Ensure the output is the exact same var TEST_OUTPUT; @@ -62,7 +59,7 @@ test("Variant #2: Obfuscate for loops", async () => { expect(TEST_OUTPUT).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -test("Variant #3: Obfuscate while loops", async () => { +test("Variant #3: Properly handle while-loop", async () => { var code = ` var array = []; var i = 1; @@ -81,7 +78,7 @@ test("Variant #3: Obfuscate while loops", async () => { }); // Ensure Control Flow Flattening applied - expect(output).toContain("while"); + expect(output).toContain("switch"); // Ensure the output is the exact same var TEST_OUTPUT; @@ -90,7 +87,7 @@ test("Variant #3: Obfuscate while loops", async () => { expect(TEST_OUTPUT).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -test("Variant #4: Work with break statements", async () => { +test("Variant #4: Properly handle break statements", async () => { var code = ` var TEST_ARRAY = []; @@ -121,7 +118,7 @@ test("Variant #4: Work with break statements", async () => { expect(TEST_OUTPUT).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -test("Variant #5: Don't obfuscate code with `let` (Lexically bound variables)", async () => { +test("Variant #5: Properly handle 'let' variables", async () => { var code = ` let array = []; @@ -144,8 +141,8 @@ test("Variant #5: Don't obfuscate code with `let` (Lexically bound variables)", controlFlowFlattening: true, }); - // Ensure Control Flow Flattening did NOT apply here - expect(output).not.toContain("while"); + // Ensure Control Flow Flattening applied + expect(output).toContain("while"); // Ensure the output is the exact same var TEST_OUTPUT; @@ -154,7 +151,7 @@ test("Variant #5: Don't obfuscate code with `let` (Lexically bound variables)", expect(TEST_OUTPUT).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); -test("Variant #6: Don't obfuscate code with `let` (Lexically bound variables)", async () => { +test("Variant #6: Properly handle 'let' in for-loops", async () => { var code = ` var array=[]; for ( let i =1; i <= 10; i++ ) { @@ -169,8 +166,8 @@ test("Variant #6: Don't obfuscate code with `let` (Lexically bound variables)", controlFlowFlattening: true, }); - // Ensure Control Flow Flattening did NOT apply here - expect(output).not.toContain("while"); + // Ensure Control Flow Flattening did applied + expect(output).toContain("while"); // Ensure the output is the exact same var TEST_OUTPUT; @@ -381,7 +378,7 @@ test("Variant #11: Flatten nested if statements", async () => { expect(TEST_ARRAY).toStrictEqual([1, 2, 3, 4, 5]); }); -test("Variant #12: Flatten nested for loops", async () => { +test("Variant #12: Properly handle nested for loops", async () => { var output = await JsConfuser( ` TEST_ARRAY = []; @@ -407,8 +404,7 @@ test("Variant #12: Flatten nested for loops", async () => { } ); - expect(output).not.toContain("for(var i)"); - expect(output).not.toContain("for(var j)"); + expect(output).toContain("switch"); var TEST_ARRAY; @@ -416,7 +412,7 @@ test("Variant #12: Flatten nested for loops", async () => { expect(TEST_ARRAY).toStrictEqual([1, 2, 3, 4, 5]); }); -test("Variant #13: Flatten nested while loops", async () => { +test("Variant #13: Properly handle nested while-loops", async () => { var output = await JsConfuser( ` TEST_ARRAY = []; @@ -450,14 +446,13 @@ test("Variant #13: Flatten nested while loops", async () => { var TEST_ARRAY; - expect(output).not.toContain("while(i<0)"); - expect(output).not.toContain("while(j<4)"); + expect(output).toContain("switch"); eval(output); expect(TEST_ARRAY).toStrictEqual([1, 2, 3, 4, 5]); }); -test("Variant #14: Flatten nested switch statements", async () => { +test("Variant #14: Properly handle nested switch statements", async () => { var output = await JsConfuser( ` TEST_ARRAY = []; @@ -497,8 +492,7 @@ test("Variant #14: Flatten nested switch statements", async () => { } ); - expect(output).not.toContain("switch(i"); - expect(output).not.toContain("switch(j"); + expect(output).toContain("while"); var TEST_ARRAY; @@ -575,7 +569,7 @@ test("Variant #17: Flatten with infinite for loop and break", async () => { } ); - expect(output).not.toContain("for(;"); + expect(output).toContain("while"); var TEST_ARRAY; @@ -652,7 +646,7 @@ test("Variant #21: Don't move Import Declarations", async () => { // Convert to runnable code output = output.replace( - `import{createHash}from'crypto';`, + `import{createHash}from"crypto";`, "const {createHash}=require('crypto');" ); @@ -776,7 +770,7 @@ test("Variant #24: Nested function-calls with labeled breaks/continues", async ( controlFlowFlattening: true, renameVariables: true, identifierGenerator: "mangled", - stack: true, + variableMasking: true, }); var TEST_OUTPUT; @@ -969,7 +963,7 @@ test("Variant #29: Nested labeled break and continue statements with RGF enabled expect(TEST_OUTPUT).toStrictEqual(15); }); -test("Variant #30: Obfuscate switch statements", async () => { +test("Variant #30: Properly handle switch statements", async () => { var output = await JsConfuser( ` switch("DON'T CHANGE ME"){} // Empty switch for testing @@ -1061,10 +1055,6 @@ test("Variant #30: Obfuscate switch statements", async () => { // Ensure Control Flow Flattening applied expect(output).toContain("while"); - // Ensure switch-statements got changed - expect(output).not.toContain("switch(true)"); - expect(output).not.toContain("switch(TEST_OUTPUT)"); - var TEST_OUTPUT; eval(output); diff --git a/test/transforms/lock/antiDebug.test.ts b/test/transforms/lock/antiDebug.test.ts index 4b2311c..96e52ab 100644 --- a/test/transforms/lock/antiDebug.test.ts +++ b/test/transforms/lock/antiDebug.test.ts @@ -1,66 +1,21 @@ import JsConfuser from "../../../src/index"; -it("add debugger statements", async () => { - var { code: output } = await JsConfuser.obfuscate("input(true)", { - target: "node", - lock: { - antiDebug: true, - }, - }); +test("Variant #1: Add debugger statements", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = true;`, + { + target: "node", + lock: { + antiDebug: true, + }, + } + ); expect(output).toContain("debugger"); -}); -it("add a background interval", async () => { - var { code: output } = await JsConfuser.obfuscate("input(true)", { - target: "node", - lock: { - antiDebug: true, - }, - }); - - expect(output).toContain("setInterval"); -}); + var TEST_OUTPUT = false; + eval(output); -it("should place syntax-correct code", async () => { - for (var i = 0; i < 25; i++) { - var { code: output } = await JsConfuser.obfuscate( - ` - /** - * GitHub: https://github.com/MichaelXF/js-confuser - * NPM: https://www.npmjs.com/package/js-confuser - * - * Welcome to Js Confuser - * - * You can obfuscate the code with the top right button 'Obfuscate'. - * - * You can customize the obfuscator with the button 'Options'. - * (Set the target to 'node' for NodeJS apps) - * - * Happy Hacking! - */ - - function greet(name){ - var output = "Hello " + name + "!"; - } - - greet("Internet User"); - - `, - { - compact: true, - controlFlowFlattening: true, - identifierGenerator: "randomized", - lock: { antiDebug: true }, - minify: true, - target: "node", - } - ); - - try { - eval(output); - } catch (e) { - expect(e).toStrictEqual(undefined); - } - } + expect(TEST_OUTPUT).toStrictEqual(true); }); diff --git a/test/transforms/lock/browserLock.test.ts b/test/transforms/lock/browserLock.test.ts deleted file mode 100644 index 6f0d088..0000000 --- a/test/transforms/lock/browserLock.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import JsConfuser from "../../../src/index"; - -test("Variant #1: Chrome", async () => { - // Chrome user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36", - }, - }; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught" - } - `, - { - target: "browser", - lock: { - browserLock: ["chrome"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toStrictEqual(undefined); -}); - -test( - "Variant #2: Chrome on Firefox browser", - async () => { - // Firefox user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1", - }, - }; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught" - } - `, - { - target: "browser", - lock: { - browserLock: ["chrome"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toStrictEqual("caught"); - }, - 30 * 1000 -); - -test("Variant #2: Firefox", async () => { - // Firefox user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1", - }, - }; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught" - } - `, - { - target: "browser", - lock: { - browserLock: ["firefox"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toStrictEqual(undefined); -}); - -test( - "Variant #4: Firefox on Chrome browser", - async () => { - // Chrome user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36", - }, - }; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught" - } - `, - { - target: "browser", - lock: { - browserLock: ["firefox"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - - eval(output); - expect(TEST_VARIABLE).toStrictEqual("caught"); - }, - 30 * 1000 -); diff --git a/test/transforms/lock/countermeasures.test.ts b/test/transforms/lock/countermeasures.test.ts index a8eff20..6dc717c 100644 --- a/test/transforms/lock/countermeasures.test.ts +++ b/test/transforms/lock/countermeasures.test.ts @@ -83,11 +83,13 @@ test("Variant #4: Should work when countermeasures is variable declaration", asy // https://github.com/MichaelXF/js-confuser/issues/66 test("Variant #5: Should work with RGF enabled", async () => { - await JsConfuser.obfuscate( + var { code } = await JsConfuser.obfuscate( ` function myCountermeasuresFunction(){ } + + TEST_OUTPUT = true; `, { target: "node", @@ -97,4 +99,9 @@ test("Variant #5: Should work with RGF enabled", async () => { rgf: true, } ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(true); }); diff --git a/test/transforms/lock/dateLock.test.ts b/test/transforms/lock/dateLock.test.ts new file mode 100644 index 0000000..d6b21df --- /dev/null +++ b/test/transforms/lock/dateLock.test.ts @@ -0,0 +1,91 @@ +import JsConfuser from "../../../src/index"; + +test("Variant #1: Work with startDate and call countermeasures function", async () => { + var { code } = await JsConfuser.obfuscate( + ` function countermeasures(){ input(true) } `, + { + target: "node", + lock: { + startDate: Date.now() + 1000 * 60 * 60 * 24, // one day in the future + countermeasures: "countermeasures", + }, + } + ); + + var value = "never_called"; + function input(valueIn) { + value = valueIn; + } + + eval(code); + expect(value).toStrictEqual(true); +}); + +test("Variant #2: Don't call countermeasures if the time is correct", async () => { + var { code } = await JsConfuser.obfuscate( + ` function countermeasures(){ input(true) } `, + { + target: "node", + lock: { + startDate: Date.now() - 1000 * 60 * 60 * 24, // one day in the past + endDate: Date.now() + 1000 * 60 * 60 * 24, // one day in the future (2-day window to run this code) + countermeasures: "countermeasures", + }, + } + ); + + var value = "never_called"; + function input(valueIn) { + value = valueIn; + } + + eval(code); + expect(value).toStrictEqual("never_called"); +}); + +test("Variant #3: Work with endDate and call countermeasures function", async () => { + var { code } = await JsConfuser.obfuscate( + ` function countermeasures(){ input(true) } `, + { + target: "node", + lock: { + endDate: Date.now() - 1000 * 60 * 60 * 24, // one day in the past + countermeasures: "countermeasures", + }, + } + ); + + var value = "never_called"; + function input(valueIn) { + value = valueIn; + } + + eval(code); + expect(value).toStrictEqual(true); +}); + +test("Variant #4: Countermeasures function should still work even with renameVariables enabled", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` function countermeasures(){ input(true) } `, + { + target: "node", + renameVariables: true, + renameGlobals: true, // <- `countermeasures` is top level name + lock: { + endDate: Date.now() - 1000 * 60 * 60 * 24, // always in the past, therefore countermeasures will always be called + countermeasures: "countermeasures", + }, + } + ); + + // ensure function was renamed + expect(output).not.toContain("countermeasures"); + + var value = "never_called"; + function input(valueIn) { + value = valueIn; + } + + eval(output); + expect(value).toStrictEqual(true); +}); diff --git a/test/transforms/lock/domainLock.test.ts b/test/transforms/lock/domainLock.test.ts new file mode 100644 index 0000000..d546560 --- /dev/null +++ b/test/transforms/lock/domainLock.test.ts @@ -0,0 +1,55 @@ +import JsConfuser from "../../../src"; + +test("Variant #1: Don't call countermeasures when domainLock is correct", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` function countermeasures(){ input(true) } `, + { + target: "browser", + lock: { + domainLock: ["mywebsite.com"], + countermeasures: "countermeasures", + }, + } + ); + + var location = { + href: "mywebsite.com", + }; + + var window = { location }; + + var value = "never_called"; + function input(valueIn) { + value = valueIn; + } + + eval(output); + expect(value).toStrictEqual("never_called"); +}); + +test("Variant #2: Call countermeasures when domain is different", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` function countermeasures(){ input(true) } `, + { + target: "browser", + lock: { + domainLock: ["mywebsite.com"], + countermeasures: "countermeasures", + }, + } + ); + + var location = { + href: "anotherwebsite.com", + }; + + var window = { location }; + + var value = "never_called"; + function input(valueIn) { + value = valueIn; + } + + eval(output); + expect(value).toStrictEqual(true); +}); diff --git a/test/transforms/lock/integrity.test.ts b/test/transforms/lock/integrity.test.ts index bffd424..d00c4ef 100644 --- a/test/transforms/lock/integrity.test.ts +++ b/test/transforms/lock/integrity.test.ts @@ -1,6 +1,6 @@ import JsConfuser from "../../../src/index"; -it("should run correctly", async () => { +test("Variant #1: Run correctly", async () => { var code = ` function TEST_FUNCTION(){ input_test1(true) @@ -9,7 +9,7 @@ it("should run correctly", async () => { TEST_FUNCTION(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", lock: { integrity: true, countermeasures: false }, }); @@ -26,17 +26,16 @@ it("should run correctly", async () => { expect(value).toStrictEqual(true); }); -it("should not run when source code is modified", async () => { +test("Variant #2: Don't run when source code is modified", async () => { var code = ` function TEST_FUNCTION(){ input("Hello World") } TEST_FUNCTION(); - `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", lock: { integrity: true, countermeasures: false }, }); @@ -58,7 +57,7 @@ it("should not run when source code is modified", async () => { expect(value).toStrictEqual("never_called"); }); -it("should run countermeasures function when changed", async () => { +test("Variant #3: Run countermeasures function when changed", async () => { var code = ` function TEST_FUNCTION(){ input("The code was never changed") @@ -72,7 +71,7 @@ it("should run countermeasures function when changed", async () => { `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", lock: { integrity: true, countermeasures: "TEST_COUNTERMEASURES" }, }); @@ -95,7 +94,7 @@ it("should run countermeasures function when changed", async () => { expect(value).toStrictEqual("countermeasures"); }); -it("should error when countermeasures function doesn't exist", async () => { +test("Variant #4: Error when countermeasures function doesn't exist", async () => { var code = ` function TEST_FUNCTION(){ input("The code was never changed") @@ -106,7 +105,7 @@ it("should error when countermeasures function doesn't exist", async () => { var errorCaught; try { - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", lock: { integrity: true, countermeasures: "TEST_COUNTERMEASURES" }, }); @@ -121,14 +120,17 @@ it("should error when countermeasures function doesn't exist", async () => { expect(errorCaught.toString()).toContain("TEST_COUNTERMEASURES"); }); -it("should work on High Preset", async () => { - var output = await JsConfuser(`TEST_OUTPUT = ("Hello World")`, { - target: "node", - preset: "high", - lock: { - integrity: true, - }, - }); +test("Variant #5: Work on High Preset", async () => { + var { code: output } = await JsConfuser.obfuscate( + `TEST_OUTPUT = ("Hello World")`, + { + target: "node", + preset: "high", + lock: { + integrity: true, + }, + } + ); var TEST_OUTPUT; eval(output); @@ -136,8 +138,8 @@ it("should work on High Preset", async () => { expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); -it("should work with RGF enabled", async () => { - var output = await JsConfuser( +test("Variant #6: Work with RGF enabled", async () => { + var { code: output } = await JsConfuser.obfuscate( ` function getTestOutput(){ return "Hello World"; diff --git a/test/transforms/lock/lock.test.ts b/test/transforms/lock/lock.test.ts index 8f5f462..e69de29 100644 --- a/test/transforms/lock/lock.test.ts +++ b/test/transforms/lock/lock.test.ts @@ -1,204 +0,0 @@ -import JsConfuser from "../../../src/index"; - -it("should work with startDate and call countermeasures function", async () => { - var startDate = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - lock: { - startDate: Date.now() + 1000 * 60 * 60 * 24, // one day in the future - countermeasures: "countermeasures", - }, - } - ); - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(startDate); - expect(value).toStrictEqual(true); -}); - -it("should not call countermeasures if the time is correct", async () => { - var startDate = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - lock: { - startDate: Date.now() - 1000 * 60 * 60 * 24, // one day in the past - endDate: Date.now() + 1000 * 60 * 60 * 24, // one day in the future (2-day window to run this code) - countermeasures: "countermeasures", - }, - } - ); - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(startDate); - expect(value).toStrictEqual("never_called"); -}); - -it("should work with endDate and call countermeasures function", async () => { - var endDate = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - lock: { - endDate: Date.now() - 1000 * 60 * 60 * 24, // one day in the past - countermeasures: "countermeasures", - }, - } - ); - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(endDate); - expect(value).toStrictEqual(true); -}); - -// REMOVED FEATURE: -// it("strings should be encoded when startDate and endDate are given", async () => { -// var startDate = await JsConfuser.obfuscate(` input("ENCODED_STRING") `, { -// target: "node", -// lock: { -// startDate: Date.now() - 1000 * 60 * 60 * 24, // one day in the past -// endDate: Date.now() + 1000 * 60 * 60 * 24, // one day in the future (2-day window to run this code) -// }, -// }); - -// var value = "never_called"; -// function input(valueIn) { -// value = valueIn; -// } - -// eval(startDate); -// expect(value).toStrictEqual("ENCODED_STRING"); -// }); - -it("countermeasures function should still work even with renameVariables enabled", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - renameVariables: true, - renameGlobals: true, // <- `countermeasures` is top level name - lock: { - endDate: Date.now() - 1000 * 60 * 60 * 24, // always in the past, therefore countermeasures will always be called - countermeasures: "countermeasures", - }, - } - ); - - // ensure function was renamed - expect(output).not.toContain("countermeasures"); - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(output); - expect(value).toStrictEqual(true); -}); - -it("should not call countermeasures when domainLock is correct", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "browser", - lock: { - domainLock: ["mywebsite.com"], - countermeasures: "countermeasures", - }, - } - ); - - var location = { - href: "mywebsite.com", - }; - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(output); - expect(value).toStrictEqual("never_called"); -}); - -it("should call countermeasures when domain is different", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "browser", - lock: { - domainLock: ["mywebsite.com"], - countermeasures: "countermeasures", - }, - } - ); - - var location = { - href: "anotherwebsite.com", - }; - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(output); - expect(value).toStrictEqual(true); -}); - -it("should not call countermeasures when context is correct", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - lock: { - context: ["authenticated"], - countermeasures: "countermeasures", - }, - } - ); - - (global as any).authenticated = true; - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(output); - expect(value).toStrictEqual("never_called"); -}); - -it("should call countermeasures when context is different", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - lock: { - context: ["missingProperty"], - countermeasures: "countermeasures", - }, - } - ); - - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } - - eval(output); - expect(value).toStrictEqual(true); -}); diff --git a/test/transforms/lock/osLock.test.ts b/test/transforms/lock/osLock.test.ts deleted file mode 100644 index 2d72fc3..0000000 --- a/test/transforms/lock/osLock.test.ts +++ /dev/null @@ -1,312 +0,0 @@ -import JsConfuser from "../../../src/index"; - -describe("OSLock on target 'node'", () => { - test("Variant #1: Linux", async () => { - var _require = require; - var newRequire: any = (name) => { - if (name == "os") { - return { - platform() { - return "linux"; - }, - }; - } else { - return _require(name); - } - }; - require = newRequire; - - var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { - target: "node", - lock: { - osLock: ["linux"], - countermeasures: false, - }, - }); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(1); - }); - - test("Variant #2: Linux on windows machine", async () => { - var _require = require; - var newRequire: any = (name) => { - if (name == "os") { - return { - platform() { - return "win32"; - }, - }; - } else { - return _require(name); - } - }; - require = newRequire; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught" - } - `, - { - target: "node", - lock: { - osLock: ["linux"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("caught"); - }); - - test("Variant #3: Windows", async () => { - var _require = require; - var newRequire: any = (name) => { - if (name == "os") { - return { - platform() { - return "win32"; - }, - }; - } else { - return _require(name); - } - }; - require = newRequire; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught" - } - `, - { - target: "node", - lock: { - osLock: ["windows"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(undefined); - }); - - test("Variant #4: Windows on linux machine", async () => { - var _require = require; - var newRequire: any = (name) => { - if (name == "os") { - return { - platform() { - return "linux"; - }, - }; - } else { - return _require(name); - } - }; - require = newRequire; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught"; - } - `, - { - target: "node", - lock: { - osLock: ["windows"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("caught"); - }); - - test("Variant #5: MacOs", async () => { - var _require = require; - var newRequire: any = (name) => { - if (name == "os") { - return { - platform() { - return "darwin"; - }, - }; - } else { - return _require(name); - } - }; - require = newRequire; - - var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { - target: "node", - lock: { - osLock: ["osx"], - countermeasures: false, - }, - }); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(1); - }); - - test("Variant #6: MacOs on linux machine", async () => { - var _require = require; - var newRequire: any = (name) => { - if (name == "os") { - return { - platform() { - return "linux"; - }, - }; - } else { - return _require(name); - } - }; - require = newRequire; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught"; - } - `, - { - target: "node", - lock: { - osLock: ["osx"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("caught"); - }); -}); - -describe("OSLock on target 'browser'", () => { - test("Variant #1: Linux", async () => { - // Linux user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", - }, - }; - - var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { - target: "browser", - lock: { - osLock: ["linux"], - countermeasures: false, - }, - }); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(1); - }); - - test("Variant #2: Linux on windows machine", async () => { - // Windows user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", - }, - }; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught"; - }`, - { - target: "browser", - lock: { - osLock: ["linux"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("caught"); - }); - - test("Variant #3: Windows", async () => { - // Windows user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", - }, - }; - - var { code: output } = await JsConfuser.obfuscate(`TEST_VARIABLE = 1`, { - target: "browser", - lock: { - osLock: ["windows"], - countermeasures: false, - }, - }); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual(1); - }); - - test("Variant #4: Windows on linux machine", async () => { - // Linux user-agent - var window = { - navigator: { - userAgent: - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", - }, - }; - - var { code: output } = await JsConfuser.obfuscate( - ` - function caught(){ - TEST_VARIABLE = "caught"; - } - `, - { - target: "browser", - lock: { - osLock: ["windows"], - countermeasures: "caught", - }, - } - ); - - var TEST_VARIABLE; - eval(output); - - expect(TEST_VARIABLE).toStrictEqual("caught"); - }); -}); diff --git a/test/transforms/lock/selfDefending.test.ts b/test/transforms/lock/selfDefending.test.ts index 3b20fc4..d40f96c 100644 --- a/test/transforms/lock/selfDefending.test.ts +++ b/test/transforms/lock/selfDefending.test.ts @@ -37,7 +37,7 @@ test("Variant #2: SelfDefending should not crash when unchanged", async () => { expect(TEST_CAUGHT).toStrictEqual(undefined); }); -test("Variant #2: SelfDefending should crash when changed", async () => { +test("Variant #3: SelfDefending should crash when changed", async () => { var { code: output } = await JsConfuser.obfuscate( ` function caught(){ @@ -55,7 +55,7 @@ test("Variant #2: SelfDefending should crash when changed", async () => { ); // Re-run through obfuscator without compact = new lines = crash should occur - var output2 = await JsConfuser.obfuscate(output, { + var { code: output2 } = await JsConfuser.obfuscate(output, { target: "node", compact: false, }); diff --git a/test/transforms/lock/tamperProtection.test.ts b/test/transforms/lock/tamperProtection.test.ts index 68e355a..dedaf14 100644 --- a/test/transforms/lock/tamperProtection.test.ts +++ b/test/transforms/lock/tamperProtection.test.ts @@ -29,7 +29,7 @@ describe("Global Concealing", () => { global.TEST_GLOBAL_OUTPUT = global.TEST_GLOBAL; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", globalConcealing: true, lock: { @@ -56,7 +56,7 @@ describe("Global Concealing", () => { global.TEST_GLOBAL_VARIANT_7_OUTPUT = global.TEST_GLOBAL_VARIANT_7; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", globalConcealing: (varName) => varName != "TEST_OUTPUT_SET", lock: { @@ -87,7 +87,7 @@ describe("Global Concealing", () => { mockConsoleLog.toString = () => "{ [native code] }"; (global as any).mockConsoleLog = mockConsoleLog; - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function onTamperDetected(){ TEST_OUTPUT_SET(true); @@ -122,7 +122,7 @@ describe("Global Concealing", () => { mockConsoleLog.toString = () => "[native code]"; (global as any).mockConsoleLog = mockConsoleLog; - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function onTamperDetected(){ TEST_OUTPUT_SET(true); @@ -153,7 +153,7 @@ describe("Global Concealing", () => { }); test("Variant #5: Native check on non-existent functions", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` a.b.c.d() `, @@ -168,8 +168,8 @@ describe("Global Concealing", () => { }); test("Variant #6: Custom implementation for lock.tamperProtection", async () => { - var foundNames = []; - var output = await JsConfuser( + var foundNames: string[] = []; + var { code: output } = await JsConfuser.obfuscate( ` fetch() console.log() @@ -209,7 +209,7 @@ describe("Global Concealing", () => { }); test("Variant #7: Protect native function Math.floor", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OUTPUT_SET(Math.floor(10.1)); `, @@ -233,7 +233,7 @@ describe("Global Concealing", () => { describe("RGF", () => { test("Variant #1: Use Eval instead of new Function", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction1(){ TEST_OUTPUT_SET(true); @@ -268,7 +268,7 @@ describe("RGF", () => { }); test("Variant #2: Detect Eval tamper", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function onTamperDetected(){ TEST_OUTPUT_SET("Correct Value"); @@ -312,7 +312,7 @@ describe("RGF", () => { describe("Strict Mode", () => { test("Variant #1: Disallow Strict Mode", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` "use strict"; // Note: Jest testing environment is already in Strict Mode function onTamperDetected(){ From 8cf640b03077b0ceb3f31374d0c48f992916d7f3 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 30 Aug 2024 18:39:24 -0400 Subject: [PATCH 018/103] Improved Rename Variables, Scope Integrity checking --- src/obfuscator.ts | 9 ++ src/options.ts | 30 ++-- src/probability.ts | 38 ++--- src/transforms/controlFlowFlattening.ts | 5 + src/transforms/deadCode.ts | 36 ++++- src/transforms/dispatcher.ts | 12 +- src/transforms/extraction/objectExtraction.ts | 1 - src/transforms/flatten.ts | 21 ++- src/transforms/identifier/globalConcealing.ts | 27 ++-- src/transforms/identifier/renameVariables.ts | 123 +++++++++++---- src/transforms/preparation.ts | 53 ++++++- src/transforms/string/stringConcealing.ts | 17 ++- src/transforms/variableMasking.ts | 13 +- src/utils/NameGen.ts | 71 +++++++-- src/utils/function-utils.ts | 15 ++ src/utils/gen-utils.ts | 96 ++++++++++++ src/utils/random-utils.ts | 29 ++-- src/utils/scope-utils.ts | 97 ++++++++++++ .../identifier/renameVariables.test.ts | 142 +++++++++++------- 19 files changed, 642 insertions(+), 193 deletions(-) create mode 100644 src/utils/gen-utils.ts create mode 100644 src/utils/scope-utils.ts diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 9b20fb3..10c320b 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -36,6 +36,8 @@ import integrity from "./lock/integrity"; import { Statement } from "@babel/types"; import controlFlowFlattening from "./transforms/controlFlowFlattening"; import variableConcealing from "./transforms/identifier/variableConcealing"; +import { NameGen } from "./utils/NameGen"; +import { assertScopeIntegrity } from "./utils/scope-utils"; export default class Obfuscator { plugins: { @@ -59,9 +61,15 @@ export default class Obfuscator { }, }; + /** + * The main Name Generator for `Rename Variables` + */ + nameGen: NameGen; + public constructor(userOptions: ObfuscateOptions) { validateOptions(userOptions); this.options = applyDefaultsToOptions({ ...userOptions }); + this.nameGen = new NameGen(this.options.identifierGenerator); const allPlugins: PluginFunction[] = []; @@ -154,6 +162,7 @@ export default class Obfuscator { } babel.traverse(ast, plugin.visitor as babel.Visitor); + assertScopeIntegrity(pluginInstance.name, ast); if (options?.profiler) { options?.profiler({ diff --git a/src/options.ts b/src/options.ts index 24a8581..de375db 100644 --- a/src/options.ts +++ b/src/options.ts @@ -102,7 +102,10 @@ export interface ObfuscateOptions { * * Determines if variables should be renamed. (`true/false`) */ - renameVariables?: ProbabilityMap; + renameVariables?: ProbabilityMap< + boolean, + (variableName: string, topLevel: boolean) => boolean + >; /** * ### `renameGlobals` @@ -151,7 +154,8 @@ export interface ObfuscateOptions { * */ identifierGenerator?: ProbabilityMap< - "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number" + "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number", + () => string >; /** @@ -172,7 +176,7 @@ export interface ObfuscateOptions { * Global Concealing hides global variables being accessed. (`true/false`) * */ - globalConcealing?: ProbabilityMap; + globalConcealing?: ProbabilityMap boolean>; /** * ### `stringCompression` @@ -182,7 +186,7 @@ export interface ObfuscateOptions { * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` * */ - stringCompression?: ProbabilityMap; + stringCompression?: ProbabilityMap boolean>; /** * ### `stringConcealing` @@ -192,7 +196,7 @@ export interface ObfuscateOptions { * `"console"` -> `decrypt('<~@rH7+Dert~>')` * */ - stringConcealing?: ProbabilityMap; + stringConcealing?: ProbabilityMap boolean>; /** * ### `stringEncoding` @@ -202,7 +206,7 @@ export interface ObfuscateOptions { * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` * */ - stringEncoding?: ProbabilityMap; + stringEncoding?: ProbabilityMap boolean>; /** * ### `stringSplitting` @@ -212,7 +216,7 @@ export interface ObfuscateOptions { * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` * */ - stringSplitting?: ProbabilityMap; + stringSplitting?: ProbabilityMap boolean>; /** * ### `duplicateLiteralsRemoval` @@ -228,7 +232,7 @@ export interface ObfuscateOptions { * Creates a middleman function to process function calls. (`true/false/0-1`) * */ - dispatcher?: ProbabilityMap; + dispatcher?: ProbabilityMap boolean>; /** * ### `rgf` @@ -252,7 +256,7 @@ export interface ObfuscateOptions { * ``` * */ - rgf?: ProbabilityMap; + rgf?: ProbabilityMap boolean>; /** * ### `variableMasking` @@ -274,7 +278,7 @@ export interface ObfuscateOptions { * }; * ``` */ - variableMasking?: ProbabilityMap; + variableMasking?: ProbabilityMap boolean>; /** * ### `objectExtraction` @@ -300,7 +304,7 @@ export interface ObfuscateOptions { * ``` * */ - objectExtraction?: ProbabilityMap; + objectExtraction?: ProbabilityMap boolean>; /** * ### `flatten` @@ -308,7 +312,7 @@ export interface ObfuscateOptions { * Brings independent declarations to the highest scope. (`true/false`) * */ - flatten?: ProbabilityMap; + flatten?: ProbabilityMap boolean>; /** * ### `deadCode` @@ -397,7 +401,7 @@ export interface ObfuscateOptions { * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md). * */ - integrity?: ProbabilityMap; + integrity?: ProbabilityMap boolean>; /** * ### `lock.countermeasures` diff --git a/src/probability.ts b/src/probability.ts index d3a8cbf..4e5a5a9 100644 --- a/src/probability.ts +++ b/src/probability.ts @@ -14,14 +14,10 @@ type Stringed = (V extends string ? V : never) | "true" | "false"; * - **`{"mode1": 50, "mode2": 50}`** - enabled, each is divided based on total * - **`function(x){ return "custom_implementation" }`** - enabled, use specified function */ -export type ProbabilityMap = - | false - | true - | number - | T - | T[] - | { [key in Stringed]?: number } - | ((...params: any[]) => any); +export type ProbabilityMap< + T, + F extends (...args: any[]) => any = (...args: any[]) => any // Default to a generic function +> = false | true | number | T | T[] | { [key in Stringed]?: number } | F; /** * Evaluates a ProbabilityMap. @@ -29,27 +25,31 @@ export type ProbabilityMap = * @param runner Custom function to determine return value * @param customFnArgs Args given to user-implemented function, such as a variable name. */ -export function computeProbabilityMap( - map: ProbabilityMap, - runner: (mode?: T) => any = (x?: T) => x, - ...customFnArgs: any[] -): any { +export function computeProbabilityMap< + T, + F extends (...args: any[]) => any = (...args: any[]) => any +>( + map: ProbabilityMap, + ...customImplementationArgs: F extends (...args: infer P) => any ? P : never +): boolean | string { if (!map) { - return runner(); + return false; } if (map === true || map === 1) { - return runner(true as any); + return true; } if (typeof map === "number") { - return runner((Math.random() < map) as any); + return Math.random() < map; } if (typeof map === "function") { - return (map as any)(...customFnArgs); + return (map as Function)(...customImplementationArgs); } + if (typeof map === "string") { - return runner(map); + return map; } + var asObject: { [mode: string]: number } = {}; if (Array.isArray(map)) { map.forEach((x: any) => { @@ -78,7 +78,7 @@ export function computeProbabilityMap( count += x; }); - return runner(winner); + return winner; } /** diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 2921166..4e526f6 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -692,6 +692,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]) ), ]; + + // Register new declarations + for (var node of blockPath.get("body")) { + blockPath.scope.registerDeclaration(node); + } }, }, }, diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index 9374bad..e114645 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -1,10 +1,12 @@ import { PluginObj } from "@babel/core"; import { PluginArg } from "./plugin"; import { chance, choice } from "../utils/random-utils"; -import { blockStatement, booleanLiteral, ifStatement } from "@babel/types"; import { deadCodeTemplates } from "../templates/deadCodeTemplates"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { NameGen } from "../utils/NameGen"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.DeadCode); @@ -28,10 +30,38 @@ export default ({ Plugin }: PluginArg): PluginObj => { var template = choice(deadCodeTemplates); var nodes = template.compile(); - path.unshiftContainer( + var containingFnName = me.getPlaceholder("dead_" + created); + + var newPath = path.unshiftContainer( + "body", + t.functionDeclaration( + t.identifier(containingFnName), + [], + t.blockStatement([...nodes]) + ) + ); + + // Overcomplicated way to get a random property name that doesn't exist on the Function + var randomProperty: string; + var nameGen = new NameGen("randomized"); + function PrototypeCollision() {} + + do { + randomProperty = nameGen.generate(); + } while ( + !randomProperty || + PrototypeCollision[randomProperty] !== undefined + ); + + path.pushContainer( "body", - ifStatement(booleanLiteral(false), blockStatement([...nodes])) + new Template(` + if("${randomProperty}" in ${containingFnName}) { + ${containingFnName}() + } + `).single() ); + path.stop(); }, }, diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index bce54a0..01c2d14 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -8,6 +8,7 @@ import { chance, getRandomString } from "../utils/random-utils"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; +import { isVariableFunctionIdentifier } from "../utils/function-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Dispatcher); @@ -18,6 +19,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { "Program|Function": { exit(_path) { const blockPath = _path as NodePath; + + if ((blockPath.node as NodeSymbol)[UNSAFE]) return; + // For testing // if (!blockPath.isProgram()) return; @@ -57,7 +61,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - if (functionPaths.has(name)) { + if ( + functionPaths.has(name) || + (path.node as NodeSymbol)[UNSAFE] + ) { illegalNames.add(name); return; } @@ -82,7 +89,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } for (var name of functionPaths.keys()) { - if (!computeProbabilityMap(me.options.dispatcher, (x) => x, name)) { + if (!computeProbabilityMap(me.options.dispatcher, name)) { functionPaths.delete(name); } } @@ -115,6 +122,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ReferencedIdentifier: { exit(path: NodePath) { if (path.isJSX()) return; + if (isVariableFunctionIdentifier(path)) return; const name = path.node.name; var fnPath = functionPaths.get(name); diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index 3656b72..c68ad74 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -145,7 +145,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { if ( !computeProbabilityMap( me.options.objectExtraction, - (x) => x, identifier.node.name ) ) diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 61d3389..40f260a 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -55,7 +55,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { functionName = "anonymous"; } - if (!computeProbabilityMap(me.options.flatten, (x) => x, functionName)) { + if (!computeProbabilityMap(me.options.flatten, functionName)) { return; } @@ -318,6 +318,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]); fnPath.node.params = [t.restElement(t.identifier(argName))]; + + // Ensure updated parameter gets registered in the function scope + fnPath.scope.crawl(); fnPath.skip(); (flattenedFunctionDeclaration as NodeSkip)[SKIP] = true; @@ -326,11 +329,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { var program = fnPath.findParent((p) => p.isProgram() ) as NodePath; - var p = program.unshiftContainer("body", flattenedFunctionDeclaration); - program.scope.registerDeclaration(p[0]); - // p[0].scope.registerDeclaration(p[0].get("body.body.0")); - p[0].skip(); + var newPath = program.unshiftContainer( + "body", + flattenedFunctionDeclaration + )[0]; + + // Register the new function declaration at the root scope + program.scope.registerDeclaration(newPath); + + // Ensure parameters are registered in the new function scope + newPath.scope.crawl(); + + newPath.skip(); } return { diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 134cf06..a32ffb2 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -49,15 +49,24 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); } + var inserting = true; + return { visitor: { Program: { exit(programPath: NodePath) { + inserting = false; + // Insert the getGlobal function at the top of the program body const getGlobalFunction = createGetGlobalFunction(); - var p = programPath.unshiftContainer("body", getGlobalFunction); - var p2 = programPath.unshiftContainer( + var newPath = programPath.unshiftContainer( + "body", + getGlobalFunction + )[0]; + programPath.scope.registerDeclaration(newPath); + + var varPath = programPath.unshiftContainer( "body", new Template(` var {globalVarName} = (function (){ @@ -74,14 +83,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { })(); `).compile({ globalVarName: globalVarName }) - ); + )[0]; - // Skip transformation for the inserted getGlobal function - programPath.get("body")[0].stop(); - programPath.get("body")[1].stop(); + programPath.scope.registerDeclaration(varPath); }, }, ReferencedIdentifier(path: NodePath) { + if (!inserting) return; + var identifierName = path.node.name; if (ignoreGlobals.has(identifierName)) return; @@ -96,11 +105,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (!mapping) { // Allow user to disable custom global variables if ( - !computeProbabilityMap( - me.options.globalConcealing, - (x) => x, - identifierName - ) + !computeProbabilityMap(me.options.globalConcealing, identifierName) ) return; diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index c6a6f5d..1703d90 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -1,57 +1,116 @@ import { NodePath, PluginObj } from "@babel/core"; -import { Binding, Scope } from "@babel/traverse"; +import { Scope } from "@babel/traverse"; import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; +import { + noRenameVariablePrefix, + placeholderVariablePrefix, + variableFunctionName, +} from "../../constants"; +import { computeProbabilityMap } from "../../probability"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.RenameVariables); + let availableNames: string[] = []; - // Keep track of available names to reuse - const availableNames: string[] = []; + let renamedScopes = new Set(); - const generateNewName = (scope: Scope): string => { - let newName; + return { + visitor: { + CallExpression: { + exit(path: NodePath) { + if ( + path.get("callee").isIdentifier({ + name: variableFunctionName, + }) + ) { + const [arg] = path.get("arguments"); + if (arg.isIdentifier()) { + path.replaceWith(t.stringLiteral(arg.node.name)); + } + } + }, + }, - // Always generate a new name - newName = availableNames.pop() || me.generateRandomIdentifier(); + Scopable: { + enter(path: NodePath) { + const { scope } = path; + if (renamedScopes.has(scope)) { + return; + } + renamedScopes.add(scope); - // Ensure the new name isn't already used in the scope - while (scope.hasBinding(newName) || scope.hasGlobal(newName)) { - newName = me.generateRandomIdentifier(); - } + var names = []; - return newName; - }; + // Collect all referenced identifiers in the current scope + const referencedIdentifiers = new Set(); - var renamedSet = new WeakSet(); + path.traverse({ + Identifier(innerPath) { + // Use Babel's built-in method to check if the identifier is a referenced variable + if (innerPath.isReferencedIdentifier()) { + const binding = innerPath.scope.getBinding(innerPath.node.name); - return { - visitor: { - Scopable: { - exit(path: NodePath) { - var createdNames = []; + // If the binding exists and is not defined in the current scope, it is a reference + if (binding && binding.scope !== path.scope) { + referencedIdentifiers.add(innerPath.node.name); + } + } + }, + }); + + var actuallyAvailableNames = availableNames.filter( + (x) => !referencedIdentifiers.has(x) && !scope.bindings[x] + ); - Object.keys(path.scope.bindings).forEach((name) => { - me.log("Checking", name); + const isGlobal = scope.path.isProgram(); - const binding = path.scope.bindings[name]; - if (renamedSet.has(binding)) return; + for (var identifierName in scope.bindings) { + // __NO_JS_CONFUSER_RENAME__ prefix should not be renamed + if (identifierName.startsWith(noRenameVariablePrefix)) continue; - const newName = generateNewName(path.scope); + const isPlaceholder = identifierName.startsWith( + placeholderVariablePrefix + ); - me.log("Renaming", name, "to", newName); + if (!isPlaceholder) { + // Global variables should be checked against user's options + if (isGlobal) { + if ( + !computeProbabilityMap( + me.options.renameGlobals, + (x) => x, + identifierName + ) + ) + continue; + } - path.scope.rename(name, newName); - renamedSet.add(binding); - if (name !== newName) { - createdNames.push(newName); + // Allow user to disable renaming certain variables + if ( + !computeProbabilityMap( + me.options.renameVariables, + identifierName, + isGlobal + ) + ) + continue; + } + + let newName = actuallyAvailableNames.pop(); + + if (!newName) { + while (!newName || scope.hasGlobal(newName)) { + newName = me.obfuscator.nameGen.generate(); + } + names.push(newName); } - }); - me.log("Created names", createdNames); + scope.rename(identifierName, newName); + } - availableNames.push(...createdNames); + availableNames.push(...names); }, }, }, diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 020a2c5..09e835e 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -12,16 +12,18 @@ import { } from "../constants"; import { ok } from "assert"; import { getPatternIdentifierNames } from "../utils/ast-utils"; +import { isVariableFunctionIdentifier } from "../utils/function-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Preparation); const markFunctionUnsafe = (path: NodePath) => { - const functionPath = path.findParent((path) => path.isFunction()); + const functionPath = path.findParent( + (path) => path.isFunction() || path.isProgram() + ); if (!functionPath) return; const functionNode = functionPath.node; - if (!t.isFunction(functionNode)) return; (functionNode as NodeSymbol)[UNSAFE] = true; }; @@ -43,8 +45,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { // When Rename Variables is disabled, __JS_CONFUSER_VAR__ must still be removed if ( - name === variableFunctionName && - !me.obfuscator.getPlugin(Order.RenameVariables) + !me.obfuscator.getPlugin(Order.RenameVariables) && + isVariableFunctionIdentifier(path) ) { ok( path.parentPath.isCallExpression(), @@ -221,6 +223,49 @@ export default ({ Plugin }: PluginArg): PluginObj => { } }, }, + + // function a(param = ()=>b) + // _getB = ()=> ()=>b + // function a(param = _getB()) + // Basically Babel scope.rename misses this edge case, so we need to manually handle it + // Here were essentially making the variables easier to understand + Function: { + exit(path) { + for (var param of path.get("params")) { + param.traverse({ + "FunctionExpression|ArrowFunctionExpression"(_innerPath) { + let innerPath = _innerPath as NodePath< + t.FunctionExpression | t.ArrowFunctionExpression + >; + const child = innerPath.find((path) => + path.parentPath?.isAssignmentPattern() + ); + + if (!child) return; + + if ( + t.isAssignmentPattern(child.parent) && + child.parent.right === child.node + ) { + var creatorName = me.getPlaceholder(); + path.insertBefore( + t.variableDeclaration("const", [ + t.variableDeclarator( + t.identifier(creatorName), + t.arrowFunctionExpression([], innerPath.node, false) + ), + ]) + ); + + innerPath.replaceWith( + t.callExpression(t.identifier(creatorName), []) + ); + } + }, + }); + } + }, + }, }, }; }; diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 8a21582..238d357 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -83,7 +83,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { if ( !computeProbabilityMap( me.options.stringConcealing, - (x) => x, originalValue ) ) { @@ -173,10 +172,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { name: bufferToStringName, }); - programPath.unshiftContainer("body", bufferToString); + programPath + .unshiftContainer("body", bufferToString) + .forEach((path) => { + programPath.scope.registerDeclaration(path); + }); // Create the string array - programPath.unshiftContainer( + var stringArrayPath = programPath.unshiftContainer( "body", t.variableDeclaration("var", [ t.variableDeclarator( @@ -186,7 +189,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) ), ]) - ); + )[0]; + programPath.scope.registerDeclaration(stringArrayPath); for (var block of blocks) { const { encodingImplementation, fnName } = ( @@ -208,10 +212,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { } `).single(); - block.unshiftContainer("body", [ + var newPath = block.unshiftContainer("body", [ ...decoder, retrieveFunctionDeclaration, - ]); + ])[0]; + block.scope.registerDeclaration(newPath); block.skip(); } }, diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 772cc8d..c88f872 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -8,7 +8,6 @@ import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; import { getFunctionName } from "../utils/ast-utils"; import { isFunctionStrictMode } from "../utils/function-utils"; -import { ok } from "assert"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.VariableMasking); @@ -42,9 +41,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const functionName = getFunctionName(fnPath); - if ( - !computeProbabilityMap(me.options.variableMasking, (x) => x, functionName) - ) { + if (!computeProbabilityMap(me.options.variableMasking, functionName)) { return; } @@ -182,6 +179,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { }); } ); + + identifierPath.scope.removeBinding(identifierPath.node.name); }, }); @@ -189,11 +188,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnPath.node.params = [t.restElement(t.identifier(stackName))]; - fnPath.scope.registerBinding( - "param", - fnPath.get("params.0") as NodePath, - fnPath - ); + fnPath.scope.registerBinding("param", fnPath.get("params")[0], fnPath); }; return { diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts index a7a8398..3cabbff 100644 --- a/src/utils/NameGen.ts +++ b/src/utils/NameGen.ts @@ -1,26 +1,67 @@ -import { alphabeticalGenerator, getRandomString } from "./random-utils"; +import { ok } from "assert"; +import { ObfuscateOptions } from "../options"; +import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils"; +import { choice, getRandomHexString, getRandomInteger } from "./random-utils"; export class NameGen { - private mode: "mangled" | "randomized"; - private generatedNames: Set; - private mangledIndex: number; - - constructor(mode: "mangled" | "randomized" = "randomized") { - this.mode = mode; - this.generatedNames = new Set(); - this.mangledIndex = 0; + private generatedNames = new Set(); + private counter = 1; + private zeroWidthGenerator = createZeroWidthGenerator(); + + constructor( + private identifierGenerator: ObfuscateOptions["identifierGenerator"] = "randomized" + ) {} + + private attemptGenerate() { + if (typeof this.identifierGenerator === "function") { + var value = this.identifierGenerator(); + ok( + typeof value === "string", + "Custom identifier generator must return a string" + ); + return value; + } + + const randomizedLength = getRandomInteger(6, 8); + + switch (this.identifierGenerator) { + case "randomized": + var characters = + "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""); + var numbers = "0123456789".split(""); + + var combined = [...characters, ...numbers]; + + var result = ""; + for (var i = 0; i < randomizedLength; i++) { + result += choice(i == 0 ? characters : combined); + } + return result; + + case "hexadecimal": + return "_0x" + getRandomHexString(randomizedLength); + + case "mangled": + return alphabeticalGenerator(this.counter++); + + case "number": + return "var_" + this.counter++; + + case "zeroWidth": + return this.zeroWidthGenerator.generate(); + + default: + throw new Error( + "Invalid identifier generator mode: " + this.identifierGenerator + ); + } } generate(): string { let name: string; do { - if (this.mode === "mangled") { - this.mangledIndex++; - name = alphabeticalGenerator(this.mangledIndex); - } else { - name = getRandomString(6); // Adjust length as needed - } + name = this.attemptGenerate(); } while (this.generatedNames.has(name)); this.generatedNames.add(name); diff --git a/src/utils/function-utils.ts b/src/utils/function-utils.ts index 4ae9d6e..399498c 100644 --- a/src/utils/function-utils.ts +++ b/src/utils/function-utils.ts @@ -1,5 +1,6 @@ import { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; +import { variableFunctionName } from "../constants"; export function isFunctionStrictMode(path: NodePath) { if ( @@ -13,3 +14,17 @@ export function isFunctionStrictMode(path: NodePath) { return false; } + +/** + * @example __JS_CONFUSER_VAR__(identifier) // true + * @param path + * @returns + */ +export function isVariableFunctionIdentifier(path: NodePath) { + if (path.isIdentifier() && path.parentPath?.isCallExpression()) { + const callee = path.parentPath.get("callee"); + return callee.isIdentifier({ name: variableFunctionName }); + } + + return false; +} diff --git a/src/utils/gen-utils.ts b/src/utils/gen-utils.ts new file mode 100644 index 0000000..db5a169 --- /dev/null +++ b/src/utils/gen-utils.ts @@ -0,0 +1,96 @@ +import { shuffle } from "./random-utils"; + +export function alphabeticalGenerator(index: number) { + let name = ""; + while (index > 0) { + var t = (index - 1) % 52; + var thisChar = + t >= 26 ? String.fromCharCode(65 + t - 26) : String.fromCharCode(97 + t); + name = thisChar + name; + index = ((index - t) / 52) | 0; + } + if (!name) { + name = "_"; + } + return name; +} + +export function createZeroWidthGenerator() { + var keywords = [ + "if", + "in", + "for", + "let", + "new", + "try", + "var", + "case", + "else", + "null", + "break", + "catch", + "class", + "const", + "super", + "throw", + "while", + "yield", + "delete", + "export", + "import", + "public", + "return", + "switch", + "default", + "finally", + "private", + "continue", + "debugger", + "function", + "arguments", + "protected", + "instanceof", + "await", + "async", + + // new key words and other fun stuff :P + "NaN", + "undefined", + "true", + "false", + "typeof", + "this", + "static", + "void", + "of", + ]; + + var maxSize = 0; + var currentKeyWordsArray: string[] = []; + + function generateArray() { + var result = keywords + .map( + (keyWord) => + keyWord + "\u200C".repeat(Math.max(maxSize - keyWord.length, 1)) + ) + .filter((craftedVariableName) => craftedVariableName.length == maxSize); + + if (!result.length) { + ++maxSize; + return generateArray(); + } + + return shuffle(result); + } + + function getNextVariable(): string { + if (!currentKeyWordsArray.length) { + ++maxSize; + currentKeyWordsArray = generateArray(); + } + return currentKeyWordsArray.pop(); + } + + return { generate: getNextVariable }; +} diff --git a/src/utils/random-utils.ts b/src/utils/random-utils.ts index 8aeca58..43d0e10 100644 --- a/src/utils/random-utils.ts +++ b/src/utils/random-utils.ts @@ -27,6 +27,20 @@ export function shuffle(array: any[]): any[] { return array; } +/** + * Returns a random hexadecimal string. + * + * @example getRandomHexString(6) => "CA96BF" + * @param length + * @returns + */ +export function getRandomHexString(length = 6) { + return [...Array(length)] + .map(() => Math.floor(Math.random() * 16).toString(16)) + .join("") + .toUpperCase(); +} + /** * Returns a random string. */ @@ -63,18 +77,3 @@ export function splitIntoChunks(str: string, size: number) { return chunks; } - -export function alphabeticalGenerator(index: number) { - let name = ""; - while (index > 0) { - var t = (index - 1) % 52; - var thisChar = - t >= 26 ? String.fromCharCode(65 + t - 26) : String.fromCharCode(97 + t); - name = thisChar + name; - index = ((index - t) / 52) | 0; - } - if (!name) { - name = "_"; - } - return name; -} diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts new file mode 100644 index 0000000..3058064 --- /dev/null +++ b/src/utils/scope-utils.ts @@ -0,0 +1,97 @@ +import traverse, { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; + +function captureScopeState(scope) { + return { + bindings: Object.keys(scope.bindings).reduce((acc, name) => { + acc[name] = { + identifier: scope.bindings[name].identifier.name, + kind: scope.bindings[name].kind, + path: scope.bindings[name].path.node, + }; + return acc; + }, {}), + }; +} + +function compareScopes(beforeState, afterState, node) { + let errors = []; + + // Check if bindings were removed or added + for (let name in beforeState.bindings) { + if (!afterState.bindings[name]) { + // errors.push(`"${name}" was removed from the scope at node: ${node.type}`); + } + } + + for (let name in afterState.bindings) { + if (!beforeState.bindings[name]) { + errors.push( + `"${name}" was not registered in the scope at node: ${node.type}` + ); + } + } + + return errors; +} + +/** + * Asserts all identifiers were correctly registered. + * + * The obfuscator checks after every transformation that all bindings are correctly registered. + * + * This ensures the integrity of the Babel Scope API. + * + * Should only be called in development mode. + * @param node + */ +export function assertScopeIntegrity(pluginName: string, node: t.File) { + const scopeStates = new WeakMap(); + + // Traverse to capture the initial state of all scopes + let programPath: NodePath = null; + traverse(node, { + enter(path) { + if (path.isProgram()) { + programPath = path; + } + + if (path.scope && Object.keys(path.scope.bindings).length > 0) { + scopeStates.set(path.node, captureScopeState(path.scope)); + } + }, + }); + + // Perform scope.crawl() on the Program node + programPath.scope.crawl(); + + // Traverse again to compare the scope states + let errors = []; + let checkedNewScopes = new Set(); + + traverse(node, { + enter(path) { + if (path.scope && Object.keys(path.scope.bindings).length > 0) { + if (checkedNewScopes.has(path.scope)) { + return; + } + checkedNewScopes.add(path.scope); + + const beforeState = scopeStates.get(path.node); + const afterState = captureScopeState(path.scope); + + if (beforeState) { + errors = errors.concat( + compareScopes(beforeState, afterState, path.node) + ); + } + } + }, + }); + + if (errors.length > 0) { + throw new Error( + `${pluginName} scope integrity check failed:\n${errors.join("\n")}` + ); + } +} diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index ecacad3..c7d7f14 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -3,7 +3,7 @@ import { ObfuscateOptions } from "../../../src/options"; test("Variant #1: Rename variables properly", async () => { var code = "var TEST_VARIABLE = 1;"; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -20,7 +20,7 @@ test("Variant #2: Don't rename global accessors", async () => { success(TEST_VARIABLE); // success should not be renamed `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -51,7 +51,7 @@ test("Variant #3: Rename shadowed variables properly", async () => { run(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -75,7 +75,7 @@ test("Variant #4: Don't rename member properties", async () => { input(TEST_OBJECT.TEST_PROPERTY); // "TEST_PROPERTY" should not be renamed `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -102,7 +102,7 @@ test("Variant #5: Handle variable defined with let (1)", async () => { input(TEST_OBJECT.TEST_PROPERTY); // "TEST_PROPERTY" should not be renamed `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -130,7 +130,7 @@ test("Variant #6: Handle variable defined with let (2)", async () => { `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -158,7 +158,7 @@ test("Variant #7: Handle variable defined with let (3)", async () => { `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -180,24 +180,26 @@ test("Variant #7: Handle variable defined with let (3)", async () => { expect(value).toStrictEqual(100); }); -test("Variant #8: Don't rename null (reservedIdentifiers)", async () => { +test("Variant #8: Don't rename undefined (reservedIdentifiers)", async () => { var code = ` - input(null) + input(undefined) `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, }); + expect(output).toContain("undefined"); + var value = false; function input(valueIn) { value = valueIn; } eval(output); - expect(value).toStrictEqual(null); + expect(value).toStrictEqual(undefined); }); test("Variant #9: Don't rename exported names", async () => { @@ -207,7 +209,7 @@ test("Variant #9: Don't rename exported names", async () => { } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -221,17 +223,22 @@ test("Variant #10: Call renameVariables callback properly (variables)", async () var myVariable = 1; `; - var input = []; + var input: [string, boolean] | null = null; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameGlobals: true, renameVariables: (name, isTopLevel) => { input = [name, isTopLevel]; + return false; }, }); + // Ensure custom implementation was called expect(input).toEqual(["myVariable", true]); + + // Ensure myVariable was not renamed + expect(output).toContain("myVariable"); }); test("Variant #11: Call renameVariables callback properly (variables, nested)", async () => { @@ -241,17 +248,23 @@ test("Variant #11: Call renameVariables callback properly (variables, nested)", })(); `; - var input = []; + var input: [string, boolean] | null = null; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameGlobals: true, renameVariables: (name, isTopLevel) => { input = [name, isTopLevel]; + + return true; }, }); + // Ensure custom implementation was called expect(input).toEqual(["myVariable", false]); + + // Ensure myVariable was renamed + expect(output).not.toContain("myVariable"); }); test("Variant #12: Call renameVariables callback properly (function declaration)", async () => { @@ -261,17 +274,23 @@ test("Variant #12: Call renameVariables callback properly (function declaration) } `; - var input = []; + var input: [string, boolean] | null = null; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameGlobals: true, renameVariables: (name, isTopLevel) => { input = [name, isTopLevel]; + + return true; }, }); + // Ensure custom implementation was called expect(input).toEqual(["myFunction", true]); + + // Ensure myFunction was renamed + expect(output).not.toContain("myFunction"); }); test("Variant #13: Allow excluding custom variables from being renamed", async () => { @@ -280,7 +299,7 @@ test("Variant #13: Allow excluding custom variables from being renamed", async ( var myVariable2 = 1; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: (name, isTopLevel) => { return name !== "myVariable1"; @@ -310,7 +329,7 @@ test("Variant #14: should not break global variable references", async () => { myFunction("Hello World"); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", renameVariables: true, renameGlobals: true, @@ -329,15 +348,17 @@ test("Variant #14: should not break global variable references", async () => { expect(value).toStrictEqual("Hello World"); }); -test("Variant #15: Function parameter default value", async () => { - /** - * In this case `b` is a global variable, - * - * "mangled" names are a,b,c,d... - * - * therefore make sure `b` is NOT used as it breaks program - */ - var code = ` +test.each(["randomized", "mangled"])( + "Variant #15: Function parameter default value", + async (identifierGeneratorMode) => { + /** + * In this case `b` is a global variable, + * + * "mangled" names are a,b,c,d... + * + * therefore make sure `b` is NOT used as it breaks program + */ + var code = ` var a = "Filler Variables"; var b = "Hello World"; var c = "Another incorrect string"; @@ -355,22 +376,23 @@ test("Variant #15: Function parameter default value", async () => { myFunction(); `; - var output = await JsConfuser(code, { - target: "node", - renameVariables: true, - renameGlobals: true, - identifierGenerator: "mangled", - }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + renameVariables: true, + renameGlobals: true, + identifierGenerator: identifierGeneratorMode, + }); - var value; - function input(valueIn) { - value = valueIn; - } + var value; + function input(valueIn) { + value = valueIn; + } - eval(output); + eval(output); - expect(value).toStrictEqual("Hello World"); -}); + expect(value).toStrictEqual("Hello World"); + } +); // https://github.com/MichaelXF/js-confuser/issues/24 test("Variant #16: Function with multiple parameters and a default value", async () => { @@ -386,7 +408,7 @@ test("Variant #16: Function with multiple parameters and a default value", async FuncA(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", renameVariables: true, renameGlobals: true, @@ -411,7 +433,7 @@ test("Variant #17: Function parameter and lexical variable clash", async () => { } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", renameVariables: true, renameGlobals: true, @@ -429,7 +451,7 @@ test("Variant #18: Catch parameter and lexical variable clash", async () => { } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", renameVariables: true, renameGlobals: true, @@ -440,7 +462,7 @@ test("Variant #18: Catch parameter and lexical variable clash", async () => { // https://github.com/MichaelXF/js-confuser/issues/69 test("Variant #19: Don't break Import Declarations", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` import { createHash } from 'node:crypto' @@ -478,7 +500,7 @@ test("Variant #19: Don't break Import Declarations", async () => { // https://github.com/MichaelXF/js-confuser/issues/80 test("Variant #20: Don't break code with var and let variables in same scope", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function log(param) { let message = param; @@ -502,10 +524,15 @@ test("Variant #20: Don't break code with var and let variables in same scope", a expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); -test.each(["hexadecimal", "mangled", "number", "zeroWidth"])( +test.each([ + "hexadecimal", + "mangled", + "number", + "zeroWidth", +])( "Variant #21: Work with custom identifierGenerator mode", async (identifierGeneratorMode) => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var myVar1 = "Correct Value"; @@ -521,8 +548,7 @@ test.each(["hexadecimal", "mangled", "number", "zeroWidth"])( { target: "node", renameVariables: true, - identifierGenerator: - identifierGeneratorMode as ObfuscateOptions["identifierGenerator"], + identifierGenerator: identifierGeneratorMode, } ); @@ -537,7 +563,7 @@ test.each(["hexadecimal", "mangled", "number", "zeroWidth"])( ); test("Variant #22: Don't rename variables prefixed with '__NO_JS_CONFUSER_RENAME__'", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var myValue = "Correct Value"; @@ -566,7 +592,7 @@ test("Variant #22: Don't rename variables prefixed with '__NO_JS_CONFUSER_RENAME }); test("Variant #23: Re-use previously generated names", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function log(message){ TEST_OUTPUT = message; @@ -591,7 +617,7 @@ test("Variant #23: Re-use previously generated names", async () => { }); test("Variant #24: Reference function name with parameter", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(myFunction){ myFunction.property = "Correct Value"; @@ -610,7 +636,7 @@ test("Variant #24: Reference function name with parameter", async () => { }); test("Variant #25: Reference catch parameter", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` try { throw "Correct Value"; @@ -628,7 +654,7 @@ test("Variant #25: Reference catch parameter", async () => { }); test("Variant #26: Transform __JS_CONFUSER_VAR__ to access variable mappings", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var myVar1 = "Incorrect Value"; @@ -652,7 +678,7 @@ test("Variant #26: Transform __JS_CONFUSER_VAR__ to access variable mappings", a }); test("Variant #27: Transform __JS_CONFUSER_VAR__ even when Rename Variables is disabled", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var name = "John Doe"; TEST_OUTPUT = __JS_CONFUSER_VAR__(name); @@ -669,7 +695,7 @@ test("Variant #27: Transform __JS_CONFUSER_VAR__ even when Rename Variables is d }); test("Variant #28: Transform __JS_CONFUSER_VAR__ on High Preset", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ var a From a3077c1e669dee14da863f9b785f27f693ffbcf6 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 30 Aug 2024 21:42:17 -0400 Subject: [PATCH 019/103] Improve `Rename Variables`, Modified globals --- src/transforms/controlFlowFlattening.ts | 44 ++++---- src/transforms/identifier/globalConcealing.ts | 104 ++++++++++++------ .../identifier/movedDeclarations.ts | 24 ++-- src/transforms/preparation.ts | 6 +- src/transforms/variableMasking.ts | 2 +- src/utils/scope-utils.ts | 15 +++ .../controlFlowFlattening.test.ts | 28 +++++ .../identifier/globalConcealing.test.ts | 30 +++++ .../identifier/renameVariables.test.ts | 9 +- 9 files changed, 190 insertions(+), 72 deletions(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 4e526f6..718c566 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -150,7 +150,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { interface Metadata { label?: string; - type?: string; + type?: "goto"; } interface NodeMetadata { @@ -242,18 +242,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } else { } - currentBasicBlock.body.unshift( - t.expressionStatement( - t.assignmentExpression( - "=", - t.identifier(fnName), - ControlStatement({ - type: "function", - label: fnLabel, - }).expression - ) - ) - ); + var oldBasicBlock = currentBasicBlock; var newBasicBlockOptions = { topLevel: false, fnLabel }; @@ -286,6 +275,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { options ); + oldBasicBlock.body.unshift( + t.expressionStatement( + t.assignmentExpression( + "=", + t.identifier(fnName), + createBasicBlockFunctionExpression(fnLabel) + ) + ) + ); + if (!isRedefined) { prependNodes.push( t.variableDeclaration("var", [ @@ -471,10 +470,15 @@ export default ({ Plugin }: PluginArg): PluginObj => { exit(path) { if (path.findParent((p) => p.isFunction())) return; - if (basicBlock.options.topLevel) { - if (!callableMap.has(path.node.name)) { - topLevelNames.add(path.node.name); - } + const binding = path.scope.getBinding(path.node.name); + if (!binding) return; + + if (!basicBlock.options.topLevel) { + return; + } + + if (!callableMap.has(path.node.name)) { + topLevelNames.add(path.node.name); } // Variable declaration -> Assignment expression @@ -522,13 +526,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ok(metadata); const { label, type } = metadata; - ok(["goto", "function"].includes(type)); + ok(["goto"].includes(type)); switch (type) { - case "function": - var node = createBasicBlockFunctionExpression(label); - path.replaceWith(node); - break; case "goto": const { stateValues: newStateValues } = basicBlocks.get(label); diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index a32ffb2..83838cb 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -49,13 +49,78 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); } - var inserting = true; - return { visitor: { Program: { exit(programPath: NodePath) { - inserting = false; + var illegalGlobals = new Set(); + var pendingReplacements = new Map(); + + programPath.traverse({ + "ReferencedIdentifier|BindingIdentifier"(_path) { + var identifierPath = _path as NodePath; + var identifierName = identifierPath.node.name; + + if ( + !identifierPath.scope.hasGlobal(identifierName) || + identifierPath.scope.hasOwnBinding(identifierName) + ) { + return; + } + + var assignmentChild = identifierPath.find((p) => + p.parentPath?.isAssignmentExpression() + ); + if ( + assignmentChild && + assignmentChild.parentPath.isAssignmentExpression() && + assignmentChild.parent.left === assignmentChild.node + ) { + illegalGlobals.add(identifierName); + return; + } + + if (ignoreGlobals.has(identifierName)) return; + + if (!pendingReplacements.has(identifierName)) { + pendingReplacements.set(identifierName, [identifierPath]); + } else { + pendingReplacements.get(identifierName).push(identifierPath); + } + }, + }); + + // Remove illegal globals + illegalGlobals.forEach((globalName) => { + pendingReplacements.delete(globalName); + }); + + for (var [globalName, paths] of pendingReplacements) { + var mapping = globalMapping.get(globalName); + if (!mapping) { + // Allow user to disable custom global variables + if ( + !computeProbabilityMap(me.options.globalConcealing, globalName) + ) + continue; + + mapping = gen.generate(); + globalMapping.set(globalName, mapping); + } + + // Replace global reference with getGlobal("name") + const callExpression = t.callExpression( + t.identifier(globalFnName), + [t.stringLiteral(mapping)] + ); + + paths.forEach((path) => { + path.replaceWith(t.cloneNode(callExpression)); + }); + } + + // No globals changed, no need to insert the getGlobal function + if (globalMapping.size === 0) return; // Insert the getGlobal function at the top of the program body const getGlobalFunction = createGetGlobalFunction(); @@ -88,39 +153,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { programPath.scope.registerDeclaration(varPath); }, }, - ReferencedIdentifier(path: NodePath) { - if (!inserting) return; - - var identifierName = path.node.name; - - if (ignoreGlobals.has(identifierName)) return; - - if ( - !path.scope.hasGlobal(identifierName) || - path.scope.hasOwnBinding(identifierName) - ) { - return; - } - var mapping = globalMapping.get(identifierName); - if (!mapping) { - // Allow user to disable custom global variables - if ( - !computeProbabilityMap(me.options.globalConcealing, identifierName) - ) - return; - - mapping = gen.generate(); - globalMapping.set(identifierName, mapping); - } - - // Replace global reference with getGlobal("name") - const callExpression = t.callExpression(t.identifier(globalFnName), [ - t.stringLiteral(mapping), - ]); - - path.replaceWith(callExpression); - path.skip(); - }, }, }; }; diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index a8eeb82..019fba3 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -4,6 +4,7 @@ import { PluginArg } from "../plugin"; import { NodeSymbol, PREDICTABLE } from "../../constants"; import * as t from "@babel/types"; import { isStaticValue } from "../../utils/static-utils"; +import { isFunctionStrictMode } from "../../utils/function-utils"; /** * Moved Declarations moves variables in two ways: @@ -26,17 +27,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.isFunction() ) as NodePath; + var allowDefaultParamValue = true; + if (functionPath && (functionPath.node as NodeSymbol)[PREDICTABLE]) { // Check for "use strict" directive // Strict mode disallows non-simple parameters // So we can't move the declaration to the function parameters - var strictModeDirective: t.Directive; - if (t.isBlockStatement(functionPath.node.body)) { - strictModeDirective = functionPath.node.body.directives.find( - (directive) => directive.value.value === "use strict" - ); + var isStrictMode = isFunctionStrictMode(functionPath); + if (!isStrictMode) { + allowDefaultParamValue = false; } - if (!strictModeDirective) { + + // Cannot add variables after rest element + if (!functionPath.get("params").find((p) => p.isRestElement())) { insertionMethod = "functionParameter"; } } @@ -64,7 +67,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { if ( insertionMethod === "functionParameter" && isStatic && - isDefinedAtTop + isDefinedAtTop && + allowDefaultParamValue ) { defaultParamValue = value; path.remove(); @@ -89,11 +93,15 @@ export default ({ Plugin }: PluginArg): PluginObj => { switch (insertionMethod) { case "functionParameter": var param: t.Pattern | t.Identifier = t.identifier(name); - if (defaultParamValue) { + if (allowDefaultParamValue && defaultParamValue) { param = t.assignmentPattern(param, defaultParamValue); } functionPath.node.params.push(param); + + var paramPath = functionPath.get("params").at(-1); + + functionPath.scope.registerBinding("param", paramPath); break; case "variableDeclaration": var block = path.findParent((path) => diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 09e835e..47dd8e6 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -248,14 +248,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { child.parent.right === child.node ) { var creatorName = me.getPlaceholder(); - path.insertBefore( + var insertPath = path.insertBefore( t.variableDeclaration("const", [ t.variableDeclarator( t.identifier(creatorName), t.arrowFunctionExpression([], innerPath.node, false) ), ]) - ); + )[0]; + + path.scope.parent.registerDeclaration(insertPath); innerPath.replaceWith( t.callExpression(t.identifier(creatorName), []) diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index c88f872..5b00312 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -58,7 +58,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const binding = identifierPath.scope.getBinding( identifierPath.node.name ); - if (!binding || binding.kind === "const") return; + if (!binding || binding.scope !== fnPath.scope) return; if (binding.path.isIdentifier()) { // Parameter check diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts index 3058064..678a8eb 100644 --- a/src/utils/scope-utils.ts +++ b/src/utils/scope-utils.ts @@ -47,6 +47,7 @@ function compareScopes(beforeState, afterState, node) { */ export function assertScopeIntegrity(pluginName: string, node: t.File) { const scopeStates = new WeakMap(); + const seenNodes = new WeakSet(); // Traverse to capture the initial state of all scopes let programPath: NodePath = null; @@ -56,8 +57,22 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { programPath = path; } + if (seenNodes.has(path.node)) { + throw new Error(`Duplicate node found in AST ${path.node.type}`); + } + seenNodes.add(path.node); + if (path.scope && Object.keys(path.scope.bindings).length > 0) { scopeStates.set(path.node, captureScopeState(path.scope)); + + for (var name in path.scope.bindings) { + const binding = path.scope.bindings[name]; + if (!binding.path || !binding.path.node || binding.path.removed) { + throw new Error( + `Binding "${name}" was removed from the scope at node: ${path.node.type}` + ); + } + } } }, }); diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 74e6f18..c917b73 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -1262,3 +1262,31 @@ test("Variant #35: Redefined function declaration + variable declaration", async expect(TEST_OUTPUT).toStrictEqual(["Bottom x", "Top x", "Nested x"]); }); + +test("Variant #36: Preserve modified global", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var myVar = "Correct Value"; + if(false) { + myVar = "Incorrect Value"; + } + TEST_OUTPUT = myVar; // Test modified global (ensure not shadowed) + + function scopedFunction(){ // Test inner function scope collision + var TEST_OUTPUT + TEST_OUTPUT = "Incorrect Value"; + } + + scopedFunction(); + `, + { + target: "node", + controlFlowFlattening: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/identifier/globalConcealing.test.ts b/test/transforms/identifier/globalConcealing.test.ts index f563e7a..184823d 100644 --- a/test/transforms/identifier/globalConcealing.test.ts +++ b/test/transforms/identifier/globalConcealing.test.ts @@ -140,3 +140,33 @@ test("Variant #7: Custom callback option", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); + +test("Variant #8: Don't change globals when modified", async () => { + var namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = true; + + function myFunction(){ + } + + // Reference TEST_OUTPUT + myFunction(TEST_OUTPUT) + `, + { + target: "node", + globalConcealing: (name) => { + namesCollected.push(name); + return true; + }, + } + ); + + expect(namesCollected).not.toContain("TEST_OUTPUT"); + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(true); +}); diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index c7d7f14..60a683f 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -697,10 +697,13 @@ test("Variant #27: Transform __JS_CONFUSER_VAR__ even when Rename Variables is d test("Variant #28: Transform __JS_CONFUSER_VAR__ on High Preset", async () => { var { code: output } = await JsConfuser.obfuscate( ` + var a; + var b; + var c; function myFunction(){ - var a - var b - var c + var a; + var b; + var c; return "Correct Value" } From 9fbfa389735183deaa19eea514ec87b27ff25fe8 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 1 Sep 2024 01:18:30 -0400 Subject: [PATCH 020/103] Integrity, Custom String Encodings, Opaque Predicates, Improvements --- CHANGELOG.md | 2 + README.md | 27 ++- src/obfuscator.ts | 12 +- src/options.ts | 33 ++- src/transforms/astScrambler.ts | 6 +- src/transforms/controlFlowFlattening.ts | 54 ++++- src/transforms/deadCode.ts | 2 +- .../extraction/duplicateLiteralsRemoval.ts | 11 +- src/transforms/extraction/objectExtraction.ts | 13 +- src/transforms/identifier/globalConcealing.ts | 3 +- src/{ => transforms}/lock/integrity.ts | 10 +- src/{ => transforms}/lock/lock.ts | 21 +- src/transforms/opaquePredicates.ts | 11 + src/transforms/preparation.ts | 14 +- src/transforms/rgf.ts | 48 +++- src/transforms/string/encoding.ts | 136 ++--------- src/transforms/string/stringCompression.ts | 1 - src/transforms/string/stringConcealing.ts | 101 +++++++-- src/transforms/string/stringEncoding.ts | 5 +- src/transforms/string/stringSplitting.ts | 6 +- src/utils/scope-utils.ts | 8 +- src/validateOptions.ts | 3 + test/options.test.ts | 10 +- test/transforms/astScrambler.test.ts | 31 +++ .../extraction/objectExtraction.test.ts | 38 ++++ .../identifier/globalConcealing.test.ts | 43 +++- test/transforms/preparation.test.ts | 10 +- test/transforms/rgf.test.ts | 11 +- .../string/customStringEncoding.test.ts | 213 ++++++++++++++++++ test/util/gen-utils.test.ts | 30 +++ test/util/random-utils.test.ts | 55 +---- 31 files changed, 676 insertions(+), 292 deletions(-) rename src/{ => transforms}/lock/integrity.ts (90%) rename src/{ => transforms}/lock/lock.ts (94%) create mode 100644 src/transforms/opaquePredicates.ts create mode 100644 test/transforms/string/customStringEncoding.test.ts create mode 100644 test/util/gen-utils.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b3187d..bb2c36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - Control Flow Flattening no longer flattens `For Statement`/`While Statement/`Do Statement` for performance reasons. It now applies to `FunctionDeclaration` and flattens functions. +- Removed `indent` option, `@babel/generator` does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. + # `1.7.3` Tamper Protection diff --git a/README.md b/README.md index 304fe18..f575be5 100644 --- a/README.md +++ b/README.md @@ -53,19 +53,28 @@ var AF59rI,ZgbbeaU,WDgj3I,gpR2qG,Ox61sk,pTNPNpX;AF59rI=[60,17,25,416,22,23,83,26 */ ``` -## CLI Usage - -```shell - -``` - ## Documentation Read the [documentation](https://js-confuser.com/docs) for everything to know about JS-Confuser, including: -- All the obfuscator options -- API methods -- Presets +- [Getting Started](https://new--confuser.netlify.app/docs) + +- - [What is Obfuscation?](https://new--confuser.netlify.app/docs/getting-started/what-is-obfuscation) + +- - [Playground](https://new--confuser.netlify.app/docs/getting-started/playground) + +- - [Installation](https://new--confuser.netlify.app/docs/getting-started/installation) + +- - [Usage](https://new--confuser.netlify.app/docs/getting-started/usage) + +- - [FAQ](https://new--confuser.netlify.app/docs/getting-started/faq) + +- [Options](https://new--confuser.netlify.app/docs) + +- [Presets](https://new--confuser.netlify.app/docs) + + + ## Bug report diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 10c320b..814ea98 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -31,13 +31,14 @@ import renameLabels from "./transforms/renameLabels"; import rgf from "./transforms/rgf"; import flatten from "./transforms/flatten"; import stringConcealing from "./transforms/string/stringConcealing"; -import lock from "./lock/lock"; -import integrity from "./lock/integrity"; +import lock from "./transforms/lock/lock"; +import integrity from "./transforms/lock/integrity"; import { Statement } from "@babel/types"; import controlFlowFlattening from "./transforms/controlFlowFlattening"; import variableConcealing from "./transforms/identifier/variableConcealing"; import { NameGen } from "./utils/NameGen"; import { assertScopeIntegrity } from "./utils/scope-utils"; +import opaquePredicates from "./transforms/opaquePredicates"; export default class Obfuscator { plugins: { @@ -90,7 +91,7 @@ export default class Obfuscator { push(this.options.controlFlowFlattening, controlFlowFlattening); push(this.options.calculator, calculator); push(this.options.globalConcealing, globalConcealing); - // Opaque Predicates + push(this.options.opaquePredicates, opaquePredicates); push(this.options.stringSplitting, stringSplitting); push(this.options.stringConcealing, stringConcealing); push(this.options.stringCompression, stringCompression); @@ -149,10 +150,9 @@ export default class Obfuscator { ast: babel.types.File, options?: { profiler?: ProfilerCallback; - startIndex?: number; } ): babel.types.File { - for (let i = options?.startIndex || 0; i < this.plugins.length; i++) { + for (let i = 0; i < this.plugins.length; i++) { this.index = i; const { plugin, pluginInstance } = this.plugins[i]; if (this.options.verbose) { @@ -202,7 +202,7 @@ export default class Obfuscator { static createDefaultInstance() { return new Obfuscator({ target: "node", - renameLabels: true, + compact: true, }); } diff --git a/src/options.ts b/src/options.ts index de375db..45aa1f8 100644 --- a/src/options.ts +++ b/src/options.ts @@ -37,8 +37,25 @@ export interface CustomStringEncoding { * } * ``` */ - code: string; + code?: string; encode: (str: string) => string; + + /** + * Optional. A decoder function can be provided to ensure the string is able to decode properly. + * @param str + * @returns + */ + decode?: (str: string) => string; + + /** + * Should be used when created 'shuffled' variants of the same encoding. + */ + identity?: string; + + /** + * Can be used instead of the `code` property. + */ + template?: Template; } export interface ObfuscateOptions { @@ -67,13 +84,6 @@ export interface ObfuscateOptions { */ target: "node" | "browser"; - /** - * ### `indent` - * - * Controls the indentation of the output. - */ - indent?: 2 | 4 | "tabs"; - /** * ### `compact` * @@ -422,7 +432,12 @@ export interface ObfuscateOptions { customLocks?: CustomLock[]; }; - customStringEncodings?: CustomStringEncoding[]; + customStringEncodings?: ( + | CustomStringEncoding + | ((encodingImplementations: { + [identity: string]: CustomStringEncoding; + }) => CustomStringEncoding | null) + )[]; /** * ### `movedDeclarations` diff --git a/src/transforms/astScrambler.ts b/src/transforms/astScrambler.ts index 185be05..0a68a86 100644 --- a/src/transforms/astScrambler.ts +++ b/src/transforms/astScrambler.ts @@ -3,6 +3,7 @@ import { PluginArg } from "./plugin"; import * as t from "@babel/types"; import { ok } from "assert"; import { Order } from "../order"; +import { NodeSymbol, SKIP } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.AstScrambler); @@ -50,7 +51,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { }; for (var statement of container) { - if (t.isExpressionStatement(statement)) { + if ( + t.isExpressionStatement(statement) && + !(statement as NodeSymbol)[SKIP] + ) { if (t.isSequenceExpression(statement.expression)) { expressions.push(...statement.expression.expressions); } else { diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 718c566..d41e3f5 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -204,7 +204,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { block: t.Block, options: BasicBlockOptions ) { - for (const statement of block.body) { + for (const index in block.body) { + const statement = block.body[index]; + // Keep Imports before everything else if (t.isImportDeclaration(statement)) { prependNodes.push(statement); @@ -261,9 +263,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { currentBasicBlock.body.push(statement); currentBasicBlock.body.push( t.returnStatement( - t.callExpression(t.identifier(embeddedName), [ - t.spreadElement(t.identifier(argVar)), - ]) + t.callExpression( + t.memberExpression( + t.identifier(embeddedName), + t.stringLiteral("call"), + true + ), + [ + t.thisExpression(), + t.spreadElement(t.identifier(argVar)), + ] + ) ) ); @@ -363,6 +373,18 @@ export default ({ Plugin }: PluginArg): PluginObj => { continue; } + if ( + options.topLevel && + Number(index) === block.body.length - 1 && + t.isExpressionStatement(statement) + ) { + // Return the result of the last expression for eval() purposes + currentBasicBlock.body.push( + t.returnStatement(statement.expression) + ); + continue; + } + // 3 or more statements should be split more if ( currentBasicBlock.body.length > 1 && @@ -658,14 +680,22 @@ export default ({ Plugin }: PluginArg): PluginObj => { [t.restElement(t.identifier(argVar))], t.blockStatement([ t.returnStatement( - t.callExpression(t.identifier(mainFnName), [ - ...basicBlocks - .get(label) - .stateValues.map((stateValue) => - t.numericLiteral(stateValue) - ), - t.identifier(argVar), - ]) + t.callExpression( + t.memberExpression( + t.identifier(mainFnName), + t.stringLiteral("call"), + true + ), + [ + t.thisExpression(), + ...basicBlocks + .get(label) + .stateValues.map((stateValue) => + t.numericLiteral(stateValue) + ), + t.identifier(argVar), + ] + ) ), ]) ); diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index e114645..ef43ec8 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -53,7 +53,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { PrototypeCollision[randomProperty] !== undefined ); - path.pushContainer( + path.unshiftContainer( "body", new Template(` if("${randomProperty}" in ${containingFnName}) { diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index b51ea3b..fc50ea5 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -3,6 +3,7 @@ import * as t from "@babel/types"; import { ok } from "assert"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; +import { ensureComputedExpression } from "../../utils/ast-utils"; function fail(): never { throw new Error("Assertion failed"); @@ -110,9 +111,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { index = literalsMap.size; literalsMap.set(value, index); - firstTimeMap - .get(value) - .replaceWith(createMemberExpression(index)); + var firstPath = firstTimeMap.get(value); + + ensureComputedExpression(firstPath); + + firstPath.replaceWith(createMemberExpression(index)); arrayExpression.elements.push(createLiteral(value)); } else { @@ -124,6 +127,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ok(index !== -1); // Replace literals in the code with a placeholder + ensureComputedExpression(literalPath); + literalPath.replaceWith(createMemberExpression(index)); literalPath.skip(); }, diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index c68ad74..9a62ceb 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -153,11 +153,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { const newDeclarationKind = varDecPath.node.kind === "const" ? "let" : varDecPath.node.kind; - varDecPath.replaceWithMultiple( - newDeclarations.map((declaration) => - t.variableDeclaration(newDeclarationKind, [declaration]) + varDecPath + .replaceWithMultiple( + newDeclarations.map((declaration) => + t.variableDeclaration(newDeclarationKind, [declaration]) + ) ) - ); + .forEach((path) => { + // Make sure to register the new declarations + path.scope.registerDeclaration(path); + }); // Replace all references to new singular identifiers for (const { path, replaceWith } of pendingReplacements) { diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 83838cb..6d6ad55 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -11,6 +11,7 @@ const ignoreGlobals = new Set([ "require", "__dirname", "eval", + "arguments", variableFunctionName, ]); @@ -73,7 +74,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); if ( assignmentChild && - assignmentChild.parentPath.isAssignmentExpression() && + t.isAssignmentExpression(assignmentChild.parent) && assignmentChild.parent.left === assignmentChild.node ) { illegalGlobals.add(identifierName); diff --git a/src/lock/integrity.ts b/src/transforms/lock/integrity.ts similarity index 90% rename from src/lock/integrity.ts rename to src/transforms/lock/integrity.ts index c8606bb..87da5f2 100644 --- a/src/lock/integrity.ts +++ b/src/transforms/lock/integrity.ts @@ -1,10 +1,10 @@ import { PluginObj } from "@babel/core"; -import { PluginArg } from "../transforms/plugin"; -import { Order } from "../order"; -import { getRandomInteger } from "../utils/random-utils"; -import { HashFunction } from "../templates/integrityTemplate"; +import { PluginArg } from "../plugin"; +import { Order } from "../../order"; +import { getRandomInteger } from "../../utils/random-utils"; +import { HashFunction } from "../../templates/integrityTemplate"; import * as t from "@babel/types"; -import Template from "../templates/template"; +import Template from "../../templates/template"; import { NodePath } from "@babel/traverse"; export interface IntegrityInterface { diff --git a/src/lock/lock.ts b/src/transforms/lock/lock.ts similarity index 94% rename from src/lock/lock.ts rename to src/transforms/lock/lock.ts index bae03d4..9743ce9 100644 --- a/src/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -1,14 +1,14 @@ import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "../transforms/plugin"; -import { Order } from "../order"; -import { chance, choice } from "../utils/random-utils"; -import Template from "../templates/template"; +import { PluginArg } from "../plugin"; +import { Order } from "../../order"; +import { chance, choice } from "../../utils/random-utils"; +import Template from "../../templates/template"; import * as t from "@babel/types"; -import { CustomLock } from "../options"; -import { getParentFunctionOrProgram } from "../utils/ast-utils"; +import { CustomLock } from "../../options"; +import { getParentFunctionOrProgram } from "../../utils/ast-utils"; import { INTEGRITY, NodeIntegrity } from "./integrity"; -import { HashTemplate } from "../templates/integrityTemplate"; -import { NodeSymbol, SKIP } from "../constants"; +import { HashTemplate } from "../../templates/integrityTemplate"; +import { NodeSymbol, SKIP } from "../../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Lock); @@ -225,7 +225,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { } `).compile(); - path.unshiftContainer("body", statements).forEach((p) => p.skip()); + path.unshiftContainer("body", statements).forEach((p) => { + path.scope.registerDeclaration(p); + p.skip(); + }); } if (me.options.lock.integrity) { diff --git a/src/transforms/opaquePredicates.ts b/src/transforms/opaquePredicates.ts new file mode 100644 index 0000000..b654d3e --- /dev/null +++ b/src/transforms/opaquePredicates.ts @@ -0,0 +1,11 @@ +import { PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { Order } from "../order"; + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.OpaquePredicates); + + return { + visitor: {}, + }; +}; diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 47dd8e6..5b414a6 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -11,7 +11,10 @@ import { variableFunctionName, } from "../constants"; import { ok } from "assert"; -import { getPatternIdentifierNames } from "../utils/ast-utils"; +import { + getParentFunctionOrProgram, + getPatternIdentifierNames, +} from "../utils/ast-utils"; import { isVariableFunctionIdentifier } from "../utils/function-utils"; export default ({ Plugin }: PluginArg): PluginObj => { @@ -166,8 +169,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { return newNode; }) ) - .forEach((path) => { - path.scope.registerDeclaration(path); + .forEach((newPath) => { + if (newPath.node.kind === "var") { + var functionOrProgram = + getParentFunctionOrProgram(newPath); + functionOrProgram.scope.registerDeclaration(newPath); + } + newPath.scope.registerDeclaration(newPath); }); } } diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index f4791fb..648daaf 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -1,10 +1,11 @@ -import { PluginObj } from "@babel/core"; +import { NodePath, PluginObj } from "@babel/core"; import { PluginArg } from "./plugin"; import { Order } from "../order"; import * as t from "@babel/types"; import Obfuscator from "../obfuscator"; import { computeProbabilityMap } from "../probability"; import { getFunctionName } from "../utils/ast-utils"; +import { NodeSymbol, SKIP, UNSAFE } from "../constants"; /** * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. @@ -28,7 +29,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (rgfArrayExpression.elements.length === 0) return; // Insert the RGF array at the top of the program - path.node.body.unshift( + + function prepend(node: t.Statement) { + var newPath = path.unshiftContainer("body", node)[0]; + path.scope.registerDeclaration(newPath); + } + + prepend( t.variableDeclaration("var", [ t.variableDeclarator( t.identifier(rgfArrayName), @@ -37,7 +44,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]) ); - path.node.body.unshift( + prepend( t.variableDeclaration("var", [ t.variableDeclarator( t.identifier(rgfEvalName), @@ -47,8 +54,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); }, }, - Function: { - exit(path) { + "FunctionDeclaration|FunctionExpression|ArrowFunctionExpression": { + exit(_path) { + const path = _path as NodePath; + // Skip async and generator functions if (path.node.async || path.node.generator) return; @@ -59,7 +68,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (name === me.options.lock?.countermeasures) return; me.log(name); - if (!computeProbabilityMap(me.options.rgf, (x) => x, name)) return; + if (!computeProbabilityMap(me.options.rgf, name)) return; // Skip functions with references to outside variables // Check the scope to see if this function relies on any variables defined outside the function @@ -95,6 +104,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { const replacementName = me.getPlaceholder() + "_replacement"; const thisName = me.getPlaceholder() + "_this"; + const lastNode = t.expressionStatement(t.identifier(embeddedName)); + (lastNode as NodeSymbol)[SKIP] = true; + // Transform the function const evalTree: t.Program = t.program([ t.functionDeclaration( @@ -126,14 +138,29 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]) ), - t.expressionStatement(t.identifier(embeddedName)), + lastNode, ]); const evalFile = t.file(evalTree); - me.obfuscator.obfuscateAST(evalFile, { - startIndex: me.obfuscator.index + 1, + var newObfuscator = new Obfuscator(me.options); + + var hasRan = new Set( + me.obfuscator.plugins + .filter((plugin, i) => { + return i <= me.obfuscator.index; + }) + .map((plugin) => plugin.pluginInstance.order) + ); + + newObfuscator.plugins = newObfuscator.plugins.filter((plugin) => { + return ( + plugin.pluginInstance.order == Order.Preparation || + !hasRan.has(plugin.pluginInstance.order) + ); }); + newObfuscator.obfuscateAST(evalFile); + const generated = Obfuscator.generateCode(evalFile); var functionExpression = t.callExpression(t.identifier(rgfEvalName), [ @@ -146,6 +173,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Params no longer needed, using 'arguments' instead path.node.params = []; + // Function is now unsafe + (path.node as NodeSymbol)[UNSAFE] = true; + path .get("body") .replaceWith( diff --git a/src/transforms/string/encoding.ts b/src/transforms/string/encoding.ts index 5f5205e..409d399 100644 --- a/src/transforms/string/encoding.ts +++ b/src/transforms/string/encoding.ts @@ -1,45 +1,33 @@ +import { CustomStringEncoding } from "../../options"; import Template from "../../templates/template"; import { choice, shuffle } from "../../utils/random-utils"; import * as t from "@babel/types"; -/** - * Defines an encoding implementation the Obfuscator - */ -export interface EncodingImplementation { - identity: string; +let hasAllEncodings = false; - encode(s: string): string; - decode(s: string): string; - template: Template; -} - -let _hasAllEncodings = false; -export function hasAllEncodings() { - return _hasAllEncodings; -} - -export function createEncodingImplementation(): EncodingImplementation { - if (_hasAllEncodings) { - return EncodingImplementations[ - choice(Object.keys(EncodingImplementations)) - ]; +export function createDefaultStringEncoding( + encodingImplementations +): CustomStringEncoding { + if (hasAllEncodings) { + return null; } - // create base91 encoding + // Create base91 encoding let strTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"'; - // shuffle table + // Randomize the charset strTable = shuffle(strTable.split("")).join(""); let identity = "base91_" + strTable; - if (EncodingImplementations.hasOwnProperty(identity)) { - _hasAllEncodings = true; - return EncodingImplementations[identity]; + // Check if the encoding already exists + if (typeof encodingImplementations[identity] !== "undefined") { + hasAllEncodings = true; + return null; } - var encodingImplementation = { + var encodingImplementation: CustomStringEncoding = { identity, encode(str) { const table = strTable; @@ -112,7 +100,7 @@ export function createEncodingImplementation(): EncodingImplementation { return Buffer.from(ret).toString("utf-8"); }, template: new Template(` - function {__fnName__}(str){ + function {fnName}(str){ var table = {__strTable__}; var raw = "" + (str || ""); @@ -152,99 +140,5 @@ export function createEncodingImplementation(): EncodingImplementation { }), }; - EncodingImplementations[identity] = encodingImplementation; return encodingImplementation; } - -export const EncodingImplementations: { - [encodingIdentity: string]: EncodingImplementation; -} = { - /* ascii85: { This implementation is flaky and causes decoding errors - encode(a) { - var b, c, d, e, f, g, h, i, j, k; - // @ts-ignore - for ( - // @ts-ignore - !/[^\x00-\xFF]/.test(a), - b = "\x00\x00\x00\x00".slice(a.length % 4 || 4), - a += b, - c = [], - d = 0, - e = a.length; - e > d; - d += 4 - ) - (f = - (a.charCodeAt(d) << 24) + - (a.charCodeAt(d + 1) << 16) + - (a.charCodeAt(d + 2) << 8) + - a.charCodeAt(d + 3)), - 0 !== f - ? ((k = f % 85), - (f = (f - k) / 85), - (j = f % 85), - (f = (f - j) / 85), - (i = f % 85), - (f = (f - i) / 85), - (h = f % 85), - (f = (f - h) / 85), - (g = f % 85), - c.push(g + 33, h + 33, i + 33, j + 33, k + 33)) - : c.push(122); - return ( - (function (a, b) { - for (var c = b; c > 0; c--) a.pop(); - })(c, b.length), - "<~" + String.fromCharCode.apply(String, c) + "~>" - ); - }, - decode(a) { - var c, - d, - e, - f, - g, - h = String, - l = "length", - w = 255, - x = "charCodeAt", - y = "slice", - z = "replace"; - for ( - "<~" === a[y](0, 2) && "~>" === a[y](-2), - a = a[y](2, -2)[z](/s/g, "")[z]("z", "!!!!!"), - c = "uuuuu"[y](a[l] % 5 || 5), - a += c, - e = [], - f = 0, - g = a[l]; - g > f; - f += 5 - ) - (d = - 52200625 * (a[x](f) - 33) + - 614125 * (a[x](f + 1) - 33) + - 7225 * (a[x](f + 2) - 33) + - 85 * (a[x](f + 3) - 33) + - (a[x](f + 4) - 33)), - e.push(w & (d >> 24), w & (d >> 16), w & (d >> 8), w & d); - return ( - (function (a, b) { - for (var c = b; c > 0; c--) a.pop(); - })(e, c[l]), - h.fromCharCode.apply(h, e) - ); - }, - template: Template(` - function {name}(a, LL = ["fromCharCode", "apply"]) { - var c, d, e, f, g, h = String, l = "length", w = 255, x = "charCodeAt", y = "slice", z = "replace"; - for ("<~" === a[y](0, 2) && "~>" === a[y](-2), a = a[y](2, -2)[z](/\s/g, "")[z]("z", "!!!!!"), - c = "uuuuu"[y](a[l] % 5 || 5), a += c, e = [], f = 0, g = a[l]; g > f; f += 5) d = 52200625 * (a[x](f) - 33) + 614125 * (a[x](f + 1) - 33) + 7225 * (a[x](f + 2) - 33) + 85 * (a[x](f + 3) - 33) + (a[x](f + 4) - 33), - e.push(w & d >> 24, w & d >> 16, w & d >> 8, w & d); - return function(a, b) { - for (var c = b; c > 0; c--) a.pop(); - }(e, c[l]), h[LL[0]][LL[1]](h, e); - } - `), - }, */ -}; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 1f1adb7..3ba7a81 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -26,7 +26,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { if ( !computeProbabilityMap( me.options.stringCompression, - (x) => x, originalValue ) ) { diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 238d357..8d26043 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -4,11 +4,6 @@ import Template from "../../templates/template"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; -import { - createEncodingImplementation, - EncodingImplementation, - hasAllEncodings, -} from "./encoding"; import { ok } from "assert"; import { BufferToStringTemplate } from "../../templates/bufferToStringTemplate"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; @@ -18,12 +13,15 @@ import { } from "../../utils/ast-utils"; import { chance, + choice, getRandomInteger, getRandomString, } from "../../utils/random-utils"; +import { CustomStringEncoding } from "../../options"; +import { createDefaultStringEncoding } from "./encoding"; interface StringConcealingInterface { - encodingImplementation: EncodingImplementation; + encodingImplementation: CustomStringEncoding; fnName: string; } @@ -40,16 +38,69 @@ export default ({ Plugin }: PluginArg): PluginObj => { const stringMap = new Map(); const stringArrayName = me.getPlaceholder() + "_array"; + let encodingImplementations: { [identity: string]: CustomStringEncoding } = + Object.create(null); + + let availableStringEncodings = me.options.customStringEncodings; + + // If no custom encodings are provided, use the default encoding + if (!availableStringEncodings || availableStringEncodings.length === 0) { + availableStringEncodings = [createDefaultStringEncoding]; + } + + function hasAllEncodings() { + return availableStringEncodings.length === 0; + } + + function createStringEncoding(): CustomStringEncoding { + var encodingIndex = getRandomInteger(0, availableStringEncodings.length); + var encoding = availableStringEncodings[encodingIndex]; + + if (typeof encoding === "function") { + encoding = encoding(encodingImplementations); + + var duplicateIdentity = + typeof encoding.identity !== "undefined" && + typeof encodingImplementations[encoding.identity] !== "undefined"; + + if (duplicateIdentity || encoding === null) { + // Null returned -> All encodings have been created + // Duplicate identity -> Most likely all encodings have been created + + // No longer create new encodings using this function + availableStringEncodings = availableStringEncodings.filter( + (x) => x !== encoding + ); + + // Return a random encoding already made + encoding = choice(Object.values(encodingImplementations)); + ok(encoding, "Failed to create main string encoding"); + } + } + + if (typeof encoding.identity === "undefined") { + encoding.identity = encodingIndex.toString(); + } + + if (typeof encoding.template === "undefined") { + encoding.template = new Template(encoding.code); + } + + encodingImplementations[encoding.identity] = encoding; + + return encoding; + } + return { visitor: { Program: { exit(programPath: NodePath) { - let mainEncodingImplementation: EncodingImplementation; + let mainEncodingImplementation: CustomStringEncoding; // Create a main encoder function for the Program (programPath.node as NodeStringConcealing)[STRING_CONCEALING] = { encodingImplementation: (mainEncodingImplementation = - createEncodingImplementation()), + createStringEncoding()), fnName: me.getPlaceholder() + "_MAIN_STR", }; blocks.push(programPath); @@ -115,8 +166,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Ensure not to overwrite the previous encoders if (!stringConcealingNode[STRING_CONCEALING]) { // Create a new encoding function for this block - const encodingImplementation = - createEncodingImplementation(); + const encodingImplementation = createStringEncoding(); const fnName = me.getPlaceholder() + "_STR_" + blocks.length; @@ -139,6 +189,21 @@ export default ({ Plugin }: PluginArg): PluginObj => { stringConcealingInterface.encodingImplementation.encode( originalValue ); + + // If a decoder function is provided, use it to validate each encoded string + if ( + typeof stringConcealingInterface.encodingImplementation + .decode === "function" + ) { + const decodedValue = + stringConcealingInterface.encodingImplementation.decode( + encodedValue + ); + if (decodedValue !== originalValue) { + return; + } + } + let index = stringMap.get(encodedValue); if (typeof index === "undefined") { @@ -201,7 +266,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // The decoder function const decoder = encodingImplementation.template.compile({ - __fnName__: decodeFnName, + fnName: decodeFnName, __bufferToStringFunction__: bufferToStringName, }); @@ -212,12 +277,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { } `).single(); - var newPath = block.unshiftContainer("body", [ - ...decoder, - retrieveFunctionDeclaration, - ])[0]; - block.scope.registerDeclaration(newPath); - block.skip(); + block + .unshiftContainer("body", [ + ...decoder, + retrieveFunctionDeclaration, + ]) + .forEach((path) => { + block.scope.registerDeclaration(path); + }); } }, }, diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 79214c5..88d9584 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -55,10 +55,7 @@ export default (me: PluginInstance): PluginObj => { const { value } = path.node; // Allow percentages - if ( - !computeProbabilityMap(me.options.stringEncoding, (x) => x, value) - ) - return; + if (!computeProbabilityMap(me.options.stringEncoding, value)) return; var type = choice(["hexadecimal", "unicode"]); diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 3d1f66c..39888d8 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -29,11 +29,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } if ( - !computeProbabilityMap( - me.options.stringSplitting, - (x) => x, - object.value - ) + !computeProbabilityMap(me.options.stringSplitting, object.value) ) { return; } diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts index 678a8eb..c0857ed 100644 --- a/src/utils/scope-utils.ts +++ b/src/utils/scope-utils.ts @@ -58,7 +58,9 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { } if (seenNodes.has(path.node)) { - throw new Error(`Duplicate node found in AST ${path.node.type}`); + throw new Error( + `${pluginName}: Duplicate node found in AST ${path.node.type}` + ); } seenNodes.add(path.node); @@ -69,7 +71,7 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { const binding = path.scope.bindings[name]; if (!binding.path || !binding.path.node || binding.path.removed) { throw new Error( - `Binding "${name}" was removed from the scope at node: ${path.node.type}` + `${pluginName}: Binding "${name}" was removed from the scope at node: ${path.node.type} ${binding.path}` ); } } @@ -106,7 +108,7 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { if (errors.length > 0) { throw new Error( - `${pluginName} scope integrity check failed:\n${errors.join("\n")}` + `${pluginName}: Scope integrity check failed:\n${errors.join("\n")}` ); } } diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 0d69bbe..250b5e2 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -12,6 +12,7 @@ const validProperties = new Set([ "es5", "renameVariables", "renameGlobals", + "renameLabels", "identifierGenerator", "controlFlowFlattening", "globalConcealing", @@ -37,6 +38,7 @@ const validProperties = new Set([ "preserveFunctionLength", "astScrambler", "variableConcealing", + "customStringEncodings", ]); const validLockProperties = new Set([ @@ -51,6 +53,7 @@ const validLockProperties = new Set([ "browserLock", "integrity", "countermeasures", + "customLocks", ]); export function validateOptions(options: ObfuscateOptions) { diff --git a/test/options.test.ts b/test/options.test.ts index c18233d..726a091 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -40,7 +40,7 @@ describe("options", () => { expect(output).not.toContain("TEST_VARIABLE"); }); - test("Variant #4: Work with compact false", async () => { + test("Variant #4: Work with compact set to false", async () => { var output = await JsConfuser(`var TEST_VARIABLE;`, { target: "node", renameGlobals: true, @@ -51,13 +51,10 @@ describe("options", () => { expect(output).not.toContain("TEST_VARIABLE"); }); - test("Variant #5: Work with indent set to 2 spaces", async () => { + test("Variant #5: Work with compact set to true", async () => { var output = await JsConfuser(`var TEST_VARIABLE;`, { target: "node", - renameGlobals: true, - renameVariables: true, - compact: false, - indent: 2, + compact: true, }); expect(output).not.toContain("TEST_VARIABLE"); @@ -69,7 +66,6 @@ describe("options", () => { renameGlobals: true, renameVariables: true, compact: false, - indent: 2, debugComments: true, }); diff --git a/test/transforms/astScrambler.test.ts b/test/transforms/astScrambler.test.ts index e5804b3..08c1488 100644 --- a/test/transforms/astScrambler.test.ts +++ b/test/transforms/astScrambler.test.ts @@ -50,3 +50,34 @@ test("Variant #2: Join expressions into sequence expressions", async () => { expect(TEST_OUTPUT).toStrictEqual(6); }); + +test("Variant #3: Work with RGF", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function myFunction(){ + var a,b,c; + + a = 1; + b = 2; + c = 3; + return "Correct Value"; + } + + var a,b,c; + + a = 1; + b = 2; + c = 3; + TEST_OUTPUT = myFunction(); + `, + { + target: "node", + astScrambler: true, + rgf: true, + } + ); + + var TEST_OUTPUT; + eval(code); + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/extraction/objectExtraction.test.ts b/test/transforms/extraction/objectExtraction.test.ts index 7fb1c0c..d2ee440 100644 --- a/test/transforms/extraction/objectExtraction.test.ts +++ b/test/transforms/extraction/objectExtraction.test.ts @@ -488,3 +488,41 @@ test("Variant #16: Handle const declarations", async () => { expect(TEST_OUTPUT).toStrictEqual(1); }); + +test("Variant #17: Extract properties on objects in functions", async () => { + var namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function getUserName(){ + var firstName = "John"; + var lastName = "Doe"; + + var user = { firstName, lastName }; + + return user.firstName + " " + user.lastName; + } + + TEST_OUTPUT = getUserName(); + `, + { + target: "node", + objectExtraction: (name) => { + namesCollected.push(name); + return true; + }, + } + ); + + // Ensure object was visited + expect(namesCollected).toContain("user"); + + // Ensure object was changed + expect(code).toContain("user_firstName"); + + // Make sure results are correct + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("John Doe"); +}); diff --git a/test/transforms/identifier/globalConcealing.test.ts b/test/transforms/identifier/globalConcealing.test.ts index 184823d..8e6bd31 100644 --- a/test/transforms/identifier/globalConcealing.test.ts +++ b/test/transforms/identifier/globalConcealing.test.ts @@ -5,7 +5,7 @@ test("Variant #1: Hide global names (such as Math)", async () => { var TEST_RESULT = Math.floor(10.1); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", globalConcealing: true, }); @@ -23,7 +23,7 @@ test("Variant #2: Do not hide modified identifiers", async () => { console.log(Math); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", globalConcealing: true, }); @@ -32,7 +32,7 @@ test("Variant #2: Do not hide modified identifiers", async () => { }); test("Variant #3: Properly hide in default parameter, function expression", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction( myParameter = function(){ var myVariable = true; @@ -58,7 +58,7 @@ test("Variant #4: Don't change __dirname", async function () { TEST_OUTPUT = __dirname; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", globalConcealing: true, }); @@ -72,7 +72,7 @@ test("Variant #4: Don't change __dirname", async function () { }); test("Variant #5: Hide 'global' var, even if properties are modified", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_GLOBAL_VARIANT_5_OUTPUT = global.TEST_GLOBAL_VARIANT_5_INPUT * 2; `, @@ -95,7 +95,7 @@ test("Variant #5: Hide 'global' var, even if properties are modified", async () test("Variant #6: Preserve __JS_CONFUSER_VAR__", async () => { // Covers both defined and undefined case - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var TEST_VARIABLE TEST_OUTPUT = [__JS_CONFUSER_VAR__(TEST_OUTER_VARIABLE), __JS_CONFUSER_VAR__(TEST_VARIABLE)]; @@ -117,7 +117,7 @@ test("Variant #6: Preserve __JS_CONFUSER_VAR__", async () => { test("Variant #7: Custom callback option", async () => { var namesCollected: string[] = []; - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` expect(true).toStrictEqual(true); @@ -170,3 +170,32 @@ test("Variant #8: Don't change globals when modified", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); + +test("Variant #9: Don't change arguments", async () => { + var namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function addTwo(){ + return arguments[0] + arguments[1]; + } + + TEST_OUTPUT = addTwo(10, 20); + `, + { + target: "node", + globalConcealing: (name) => { + namesCollected.push(name); + return true; + }, + } + ); + + expect(namesCollected).not.toContain("arguments"); + expect(code).toContain("arguments[0]"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(30); +}); diff --git a/test/transforms/preparation.test.ts b/test/transforms/preparation.test.ts index 4388f94..a0d6f06 100644 --- a/test/transforms/preparation.test.ts +++ b/test/transforms/preparation.test.ts @@ -122,12 +122,16 @@ test("Variant #7: Force Variable declarations to be expanded", async () => { } { - var myBlockVar1; - var myBlockVar2; - var myBlockVar3; + var myBlockVar1, + myBlockVar2, + myBlockVar3; } if(true) var myIfVar1, myIfVar2, myIfVar3; + + function myFunction(){ + var myFunctionVar1, myFunctionVar2, myFunctionVar3; + } `, { target: "node", diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index e2d07c8..61e3bd7 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -124,7 +124,7 @@ test("Variant #5: Don't convert functions that rely on outside-scoped variables }); test("Variant #6: Work on High Preset", async () => { - var { code: output } = await JsConfuser.obfuscate( + var { code } = await JsConfuser.obfuscate( ` function addTwoNumbers(a, b){ return a + b; @@ -140,7 +140,7 @@ test("Variant #6: Work on High Preset", async () => { ); var TEST_OUTPUT; - eval(output); + eval(code); expect(TEST_OUTPUT).toStrictEqual(15); }); @@ -233,6 +233,7 @@ test("Variant #9: Work with Flatten on any function", async () => { ` var outsideCounter = 0; var outsideFlag = false; + var TEST_OUTPUT function incrementOutsideCounter(){ outsideCounter++; @@ -251,6 +252,8 @@ test("Variant #9: Work with Flatten on any function", async () => { incrementOutsideCounter(); incrementTimes(8); incrementTimes(1); + + TEST_OUTPUT_OUT = TEST_OUTPUT; `, { target: "node", @@ -261,10 +264,10 @@ test("Variant #9: Work with Flatten on any function", async () => { expect(output).toContain("eval"); - var TEST_OUTPUT; + var TEST_OUTPUT_OUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual("Correct Value"); + expect(TEST_OUTPUT_OUT).toStrictEqual("Correct Value"); }); test("Variant #10: Configurable by custom function option", async () => { diff --git a/test/transforms/string/customStringEncoding.test.ts b/test/transforms/string/customStringEncoding.test.ts new file mode 100644 index 0000000..6e8c58a --- /dev/null +++ b/test/transforms/string/customStringEncoding.test.ts @@ -0,0 +1,213 @@ +import JsConfuser from "../../../src"; +import { CustomStringEncoding } from "../../../src/options"; +import Template from "../../../src/templates/template"; +import { stringLiteral } from "@babel/types"; +import { shuffle } from "../../../src/utils/random-utils"; +import { writeFileSync } from "fs"; + +test("Variant #1: Custom Base64 encoding", async () => { + var code = ` + var myString = "Hello World!"; + TEST_OUTPUT = myString; + `; + + var stringsCollected: string[] = []; + + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + stringConcealing: true, + customStringEncodings: [ + { + code: ` + function {fnName}(encoded) { + return Buffer.from(encoded, 'base64').toString('utf-8'); + } + `, + encode: (inputStr) => { + stringsCollected.push(inputStr); + + return Buffer.from(inputStr).toString("base64"); + }, + }, + ], + }); + + // Ensure encoder function was called + expect(stringsCollected).toContain("Hello World!"); + + // Ensure string was concealed + expect(output).not.toContain("Hello World!"); + + // Ensure decoder function was placed into the output + expect(output).toContain("Buffer.from"); + expect(output).toContain("base64"); + expect(output).toContain("SGVsbG8gV29ybGQh"); + + // Ensure the output is correct + var TEST_OUTPUT; + + eval(output); + + expect(TEST_OUTPUT).toStrictEqual("Hello World!"); +}); + +test("Variant #2: Custom Randomized Base64 encoding", async () => { + var stringsCollected: string[] = []; + + function createCustomStringEncoding(): CustomStringEncoding { + function encode(input, charset) { + const inputBuffer = new TextEncoder().encode(input); + let output = ""; + + for (let i = 0; i < inputBuffer.length; i += 3) { + const chunk = [inputBuffer[i], inputBuffer[i + 1], inputBuffer[i + 2]]; + + const binary = (chunk[0] << 16) | (chunk[1] << 8) | (chunk[2] || 0); + + output += charset[(binary >> 18) & 0x3f]; + output += charset[(binary >> 12) & 0x3f]; + output += + typeof chunk[1] !== "undefined" ? charset[(binary >> 6) & 0x3f] : "="; + output += + typeof chunk[2] !== "undefined" ? charset[binary & 0x3f] : "="; + } + + return output; + } + + const customCharset = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const shuffledCharset = shuffle(customCharset.split("")).join(""); + + return { + template: new Template(` + // Creates a reverse lookup table from the given charset + function createReverseCharset(charset) { + if (charset.length !== 64) { + throw new Error("Charset must be exactly 64 characters long."); + } + const reverseCharset = {}; + for (let i = 0; i < charset.length; i++) { + reverseCharset[charset[i]] = i; + } + return reverseCharset; + } + +function decode(input, charset) { + const reverseCharset = createReverseCharset(charset); + const cleanedInput = input.replace(/=+$/, ''); // Remove padding + + const byteArray = []; + let buffer = 0; + let bitsCollected = 0; + + for (let i = 0; i < cleanedInput.length; i++) { + buffer = (buffer << 6) | reverseCharset[cleanedInput[i]]; + bitsCollected += 6; + + if (bitsCollected >= 8) { + bitsCollected -= 8; + byteArray.push((buffer >> bitsCollected) & 0xFF); + } + } + + // Convert to string, ensuring no extra characters + return new TextDecoder().decode(Uint8Array.from(byteArray)); +} + +var {fnName} = (str) => decode(str, {shuffledCharset}); + + `).setDefaultVariables({ + shuffledCharset: stringLiteral(shuffledCharset), + }), + encode: (input) => { + stringsCollected.push(input); + return encode(input, shuffledCharset); + }, + + // Identity key to help distinguish between different variants + identity: shuffledCharset, + }; + } + + var sourceCode = ` + TEST_OUTPUT = [ + "Hello World! (1)", + "Hello World! (2)", + "Hello World! (3)", + "Hello World! (4)", + "Hello World! (5)", + "Hello World! (6)", + "Hello World! (7)", + "Hello World! (8)", + "Hello World! (9)", + "Hello World! (10)" + ] + `; + + var { code } = await JsConfuser.obfuscate(sourceCode, { + target: "node", + stringConcealing: true, + customStringEncodings: [createCustomStringEncoding], + }); + + var TEST_OUTPUT; + eval(code); + + writeFileSync("./dev.output.js", code, "utf-8"); + + expect(Array.isArray(TEST_OUTPUT)).toStrictEqual(true); + + for (var i = 1; i <= 10; i++) { + var testString = `Hello World! (${i})`; + expect(stringsCollected).toContain(testString); + expect(code).not.toContain(testString); + expect(TEST_OUTPUT).toContain(testString); + } +}); + +test("Variant #3: Skip strings that fail to decode", async () => { + var stringsCollected: string[] = []; + + var customStringEncoding: CustomStringEncoding = { + code: ` + function {fnName}(input){ + return Buffer.from(input, 'base64').toString('utf-8'); + } + `, + decode: (input) => { + return Buffer.from(input, "base64").toString("utf-8"); + }, + encode: (input) => { + stringsCollected.push(input); + + if (input === "Broken String") return "Invalid Base64"; + + return Buffer.from(input, "utf-8").toString("base64"); + }, + }; + + var sourceCode = ` + var myString = "Hello World!"; + var brokenString = "Broken String"; + TEST_OUTPUT = [myString, brokenString]; + `; + + var { code } = await JsConfuser.obfuscate(sourceCode, { + target: "node", + stringConcealing: true, + customStringEncodings: [customStringEncoding], + }); + + expect(stringsCollected).toContain("Hello World!"); + expect(stringsCollected).toContain("Broken String"); + + expect(code).not.toContain("Hello World!"); + expect(code).toContain(""); + expect(code).toContain("Broken String"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(["Hello World!", "Broken String"]); +}); diff --git a/test/util/gen-utils.test.ts b/test/util/gen-utils.test.ts new file mode 100644 index 0000000..de3e46d --- /dev/null +++ b/test/util/gen-utils.test.ts @@ -0,0 +1,30 @@ +import { alphabeticalGenerator } from "../../src/utils/gen-utils"; + +describe("alphabeticalGenerator()", () => { + test("Variant #1: Return correct outputs", async () => { + expect(alphabeticalGenerator(1)).toStrictEqual("a"); + expect(alphabeticalGenerator(2)).toStrictEqual("b"); + expect(alphabeticalGenerator(3)).toStrictEqual("c"); + expect(alphabeticalGenerator(4)).toStrictEqual("d"); + expect(alphabeticalGenerator(5)).toStrictEqual("e"); + expect(alphabeticalGenerator(6)).toStrictEqual("f"); + expect(alphabeticalGenerator(7)).toStrictEqual("g"); + expect(alphabeticalGenerator(8)).toStrictEqual("h"); + expect(alphabeticalGenerator(10)).toStrictEqual("j"); + expect(alphabeticalGenerator(27)).toStrictEqual("A"); + expect(alphabeticalGenerator(28)).toStrictEqual("B"); + expect(alphabeticalGenerator(29)).toStrictEqual("C"); + + expect(alphabeticalGenerator(90)).toStrictEqual("aL"); + expect(alphabeticalGenerator(900)).toStrictEqual("qp"); + + var seen = new Set(); + for (var i = 1; i < 1000; i++) { + var c = alphabeticalGenerator(i); + + expect(seen.has(c)).toStrictEqual(false); + + seen.add(c); + } + }); +}); diff --git a/test/util/random-utils.test.ts b/test/util/random-utils.test.ts index a5b7714..08743f0 100644 --- a/test/util/random-utils.test.ts +++ b/test/util/random-utils.test.ts @@ -1,10 +1,10 @@ import { - alphabeticalGenerator, choice, + getRandomHexString, getRandomString, } from "../../src/utils/random-utils"; -it("choice() should return a random element from an array", async () => { +test("choice() should return a random element from an array", async () => { var sample = [10, 20, 30, 40, 50]; var times = 50; @@ -14,54 +14,13 @@ it("choice() should return a random element from an array", async () => { } }); -it("getRandomString() should return a random string with exact length", async () => { +test("getRandomString() should return a random string with exact length", async () => { expect(typeof getRandomString(6)).toStrictEqual("string"); expect(getRandomString(6).length).toStrictEqual(6); }); -it("getRandomFalseExpression() should always eval to false", async () => { - var times = 50; - for (var i = 0; i < times; i++) { - var expr = getRandomFalseExpression(); - var code = escodegen.generate(expr); - - expect(eval("!!" + code)).toStrictEqual(false); - } -}); - -it("getRandomTrueExpression() should always eval to true", async () => { - var times = 50; - for (var i = 0; i < times; i++) { - var expr = getRandomTrueExpression(); - var code = escodegen.generate(expr); - - expect(eval("!!" + code)).toStrictEqual(true); - } -}); - -it("alphabeticalGenerator should return correct outputs", async () => { - expect(alphabeticalGenerator(1)).toStrictEqual("a"); - expect(alphabeticalGenerator(2)).toStrictEqual("b"); - expect(alphabeticalGenerator(3)).toStrictEqual("c"); - expect(alphabeticalGenerator(4)).toStrictEqual("d"); - expect(alphabeticalGenerator(5)).toStrictEqual("e"); - expect(alphabeticalGenerator(6)).toStrictEqual("f"); - expect(alphabeticalGenerator(7)).toStrictEqual("g"); - expect(alphabeticalGenerator(8)).toStrictEqual("h"); - expect(alphabeticalGenerator(10)).toStrictEqual("j"); - expect(alphabeticalGenerator(27)).toStrictEqual("A"); - expect(alphabeticalGenerator(28)).toStrictEqual("B"); - expect(alphabeticalGenerator(29)).toStrictEqual("C"); - - expect(alphabeticalGenerator(90)).toStrictEqual("aL"); - expect(alphabeticalGenerator(900)).toStrictEqual("qp"); - - var seen = new Set(); - for (var i = 1; i < 1000; i++) { - var c = alphabeticalGenerator(i); - - expect(seen.has(c)).toStrictEqual(false); - - seen.add(c); - } +test("getRandomHexString() should return a random hex string with exact length", async () => { + expect(typeof getRandomHexString(6)).toStrictEqual("string"); + expect(getRandomHexString(6).length).toStrictEqual(6); + expect(getRandomHexString(6)).toMatch(/^[0-9A-F]+$/); }); From 7a194c7636ea946edf1e8ddf3f66e5582eadf9ec Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 1 Sep 2024 03:31:00 -0400 Subject: [PATCH 021/103] Improve Rename Labels --- src/options.ts | 2 +- src/transforms/renameLabels.ts | 181 ++++++++++++----- test/transforms/renameLabels.test.ts | 182 ++++++++++++++++-- .../string/customStringEncoding.test.ts | 1 - 4 files changed, 298 insertions(+), 68 deletions(-) diff --git a/src/options.ts b/src/options.ts index 45aa1f8..66ba08d 100644 --- a/src/options.ts +++ b/src/options.ts @@ -105,7 +105,7 @@ export interface ObfuscateOptions { */ minify?: boolean; - renameLabels?: ProbabilityMap; + renameLabels?: ProbabilityMap boolean>; /** * ### `renameVariables` diff --git a/src/transforms/renameLabels.ts b/src/transforms/renameLabels.ts index 129d5d0..1599b23 100644 --- a/src/transforms/renameLabels.ts +++ b/src/transforms/renameLabels.ts @@ -2,6 +2,23 @@ import * as t from "@babel/types"; import { NodePath, PluginObj } from "@babel/core"; import { PluginArg } from "./plugin"; import { Order } from "../order"; +import { NameGen } from "../utils/NameGen"; +import { ok } from "assert"; +import { computeProbabilityMap } from "../probability"; + +const LABEL = Symbol("label"); + +interface LabelInterface { + label?: string; + renamed?: string; + removed: boolean; + required: boolean; + paths: NodePath[]; +} + +interface NodeLabel { + [LABEL]?: LabelInterface; +} export default function ({ Plugin }: PluginArg): PluginObj { const me = Plugin(Order.RenameLabels); @@ -9,71 +26,139 @@ export default function ({ Plugin }: PluginArg): PluginObj { return { visitor: { Program(path) { - const labelUsageMap = new Map(); + const allLabelInterfaces: LabelInterface[] = []; // First pass: Collect all label usages path.traverse({ LabeledStatement(labelPath) { - const labelName = labelPath.node.label.name; - labelUsageMap.set(labelName, 0); + const labelInterface = { + label: labelPath.node.label.name, + removed: false, + required: false, + paths: [], + }; + allLabelInterfaces.push(labelInterface); + (labelPath.node as NodeLabel)[LABEL] = labelInterface; }, - BreakStatement(breakPath) { - if (breakPath.node.label) { - const labelName = breakPath.node.label.name; - labelUsageMap.set( - labelName, - (labelUsageMap.get(labelName) || 0) + 1 - ); - } - }, - ContinueStatement(continuePath) { - if (continuePath.node.label) { - const labelName = continuePath.node.label.name; - labelUsageMap.set( - labelName, - (labelUsageMap.get(labelName) || 0) + 1 + "BreakStatement|ContinueStatement"(_path) { + const path = _path as NodePath< + t.BreakStatement | t.ContinueStatement + >; + + if (path.node.label) { + const labelName = path.node.label.name; + let targets: NodePath< + t.For | t.While | t.BlockStatement | t.SwitchStatement + >[] = []; + + let onlySearchLoops = path.isContinueStatement(); + + let currentPath: NodePath = path; + while (currentPath) { + if ( + currentPath.isFor() || + currentPath.isWhile() || + currentPath.isSwitchStatement() + ) { + targets.push(currentPath); + } + + if ( + currentPath.isBlockStatement() && + currentPath.parentPath.isLabeledStatement() + ) { + targets.push(currentPath); + } + + currentPath = currentPath.parentPath; + } + + const target = targets.find( + (label) => + label.parentPath && + label.parentPath.isLabeledStatement() && + label.parentPath.node.label.name === labelName ); + + if (onlySearchLoops) { + // Remove BlockStatements and SwitchStatements from the list of targets + // a continue statement only target loops + // This helps remove unnecessary labels when a continue is nested with a block statement + // ex: for-loop with if-statement continue + targets = targets.filter( + (target) => + !target.isBlockStatement() && !target.isSwitchStatement() + ); + } + + ok(target); + + const isRequired = + target.isBlockStatement() || targets[0] !== target; + + const labelInterface = (target.parentPath.node as NodeLabel)[ + LABEL + ]; + + if (isRequired) { + labelInterface.required = true; + } else { + // Label is not required here, remove it for this particular break/continue statement + path.node.label = null; + } + + if (!labelInterface.paths) { + labelInterface.paths = []; + } + labelInterface.paths.push(path); } }, }); - // Generate short names for used labels - const labelNameMap = new Map(); - let labelCounter = 0; - labelUsageMap.forEach((usageCount, labelName) => { - if (usageCount > 0) { - labelNameMap.set(labelName, `L${labelCounter++}`); + const nameGen = new NameGen(me.options.identifierGenerator); + + for (var labelInterface of allLabelInterfaces) { + const isRequired = labelInterface.required; + if (isRequired) { + var newName = labelInterface.label; + if ( + computeProbabilityMap( + me.options.renameLabels, + labelInterface.label + ) + ) { + newName = nameGen.generate(); + } + labelInterface.renamed = newName; + } else { + labelInterface.removed = true; } - }); + } // Second pass: Rename labels and remove unused ones path.traverse({ LabeledStatement(labelPath) { - const labelName = labelPath.node.label.name; - if (labelUsageMap.get(labelName) === 0) { - labelPath.remove(); - } else { - const newLabelName = labelNameMap.get(labelName); - if (newLabelName) { - labelPath.node.label.name = newLabelName; + const labelInterface = (labelPath.node as NodeLabel)[LABEL]; + if (labelInterface) { + // Remove label but replace it with its body + if (labelInterface.removed) { + labelPath.replaceWith(labelPath.node.body); } - } - }, - BreakStatement(breakPath) { - if (breakPath.node.label) { - const labelName = breakPath.node.label.name; - const newLabelName = labelNameMap.get(labelName); - if (newLabelName) { - breakPath.node.label.name = newLabelName; + + // Else keep the label but rename it + if (typeof labelInterface.renamed === "string") { + labelPath.node.label.name = labelInterface.renamed; } - } - }, - ContinueStatement(continuePath) { - if (continuePath.node.label) { - const labelName = continuePath.node.label.name; - const newLabelName = labelNameMap.get(labelName); - if (newLabelName) { - continuePath.node.label.name = newLabelName; + + // Update all break/continue statements + for (var breakPath of labelInterface.paths) { + // Remove label from break/continue statement + if (labelInterface.removed) { + breakPath.node.label = null; + } else { + // Update label name + breakPath.node.label = t.identifier(labelInterface.renamed); + } } } }, diff --git a/test/transforms/renameLabels.test.ts b/test/transforms/renameLabels.test.ts index 7f52f95..0734466 100644 --- a/test/transforms/renameLabels.test.ts +++ b/test/transforms/renameLabels.test.ts @@ -1,48 +1,77 @@ import JsConfuser from "../../src/index"; -it("should rename labels", async () => { +test("Variant #1: Rename labels", async () => { var code = ` TEST_LABEL: while(0){} `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", - objectExtraction: true, + renameLabels: true, }); expect(output).not.toContain("TEST_LABEL"); }); -it("should remove labels unused labels", async () => { +test("Variant #2: Remove unused labels", async () => { var code = ` TEST_LABEL: while(0){} `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", - objectExtraction: true, + renameLabels: true, }); expect(output).not.toContain("TEST_LABEL"); expect(output).not.toContain(":"); // No labels are required here }); -it("should use label-less break statements when possible", async () => { +test("Variant #3: Prefer label-less break/continue statements", async () => { var code = ` + TEST_OUTPUT = []; + TEST_LABEL: while(0){ break TEST_LABEL; + + TEST_OUTPUT.push(-1); + } + + TEST_LABEL: for(var i = 0; i < 10; i++){ + if(typeof i === "number") { + if(i === 0) TEST_LABEL_2: { + continue TEST_LABEL; + } + } + if(i === 6){ + continue TEST_LABEL; + } + if(i === 7) { + break TEST_LABEL; + } + + TEST_OUTPUT.push(i); } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", - objectExtraction: true, + renameLabels: true, + identifierGenerator: () => { + throw new Error("Shouldn't be invoked"); + }, }); expect(output).not.toContain("TEST_LABEL"); + expect(output).not.toContain("TEST_LABEL_2"); + expect(output).toContain("break"); + + var TEST_OUTPUT; + eval(output); + expect(TEST_OUTPUT).toStrictEqual([1, 2, 3, 4, 5]); }); -it("should not rename nested labels", async () => { +test("Variant #4: Rename nested labels", async () => { var code = ` TEST_LABEL: for ( var i =0; i < 10; i++ ) { switch(1){ @@ -52,44 +81,161 @@ it("should not rename nested labels", async () => { } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", - objectExtraction: true, + renameLabels: true, }); expect(output).not.toContain("TEST_LABEL"); expect(output).toContain(":for"); }); -it("should not remove labels on block statements", async () => { +test("Variant #5: Don't remove labels on block statements", async () => { var code = ` TEST_LABEL: { break TEST_LABEL; } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", - objectExtraction: true, + renameLabels: true, }); expect(output).not.toContain("TEST_LABEL"); expect(output).toContain(":{"); }); -it("should remove labels on block statements when the label was never used", async () => { +test("Variant #6: Remove labels on block statements when the label was never used", async () => { var code = ` TEST_LABEL: { ""; } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", - objectExtraction: true, + renameLabels: true, }); expect(output).not.toContain("TEST_LABEL"); expect(output).not.toContain(":{"); expect(output).toContain("{"); }); + +test("Variant #7: Complex label renaming", async () => { + var sourceCode = ` +TEST_OUTPUT = []; + +outer_for: for (var i = 0; i < 4; i++) { + if (i == 3) TEST_OUTPUT.push(-1); + TEST_OUTPUT.push(i * 3); + + for_label: for (var j = 1; j < 10; j++) { + if (j == 5) { + switch_label: switch (true) { + case true: + if (i == 2) { + TEST_OUTPUT.push(9); + break outer_for; + } else { + break for_label; + } + case false: + break switch_label; + default: + break switch_label; + } + } + if (j == 4) continue for_label; + if (j == 3) continue; + TEST_OUTPUT.push(j + i + i * 2); + } +} + +block_label: { + TEST_OUTPUT.push(10); + break block_label; + TEST_OUTPUT.push(-1); +} + +while (true) { + if (true) { + break; + } + + TEST_OUTPUT.push(-1); +} + `; + + var { code } = await JsConfuser.obfuscate(sourceCode, { + target: "node", + renameLabels: true, + }); + + expect(code).not.toContain("outer_for"); + expect(code).not.toContain("for_label"); + expect(code).not.toContain("switch_label"); + expect(code).not.toContain("block_label"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +}); + +test("Variant #8: Disable Rename Labels", async () => { + var code = ` + TEST_LABEL: while(1){ + TEST_OUTPUT = "Correct Value"; + break TEST_LABEL; + TEST_OUTPUT = "Incorrect Value"; + } + `; + + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + renameLabels: false, + }); + + expect(output).toContain("TEST_LABEL"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #9: Custom implementation for Rename Labels", async () => { + var labelsCollected: string[] = []; + + var sourceCode = ` + RENAME_ME: { + switch(true){ + case true: + break RENAME_ME; + } + } + KEEP_ME: { + switch(true){ + case true: + break KEEP_ME; + } + } + + `; + + var { code } = await JsConfuser.obfuscate(sourceCode, { + target: "browser", + renameLabels: (label) => { + labelsCollected.push(label); + if (label === "KEEP_ME") return false; + return true; + }, + }); + + expect(code).not.toContain("RENAME_ME"); + expect(code).toContain("KEEP_ME"); + + expect(labelsCollected).toStrictEqual(["RENAME_ME", "KEEP_ME"]); +}); diff --git a/test/transforms/string/customStringEncoding.test.ts b/test/transforms/string/customStringEncoding.test.ts index 6e8c58a..0d8dfac 100644 --- a/test/transforms/string/customStringEncoding.test.ts +++ b/test/transforms/string/customStringEncoding.test.ts @@ -203,7 +203,6 @@ test("Variant #3: Skip strings that fail to decode", async () => { expect(stringsCollected).toContain("Broken String"); expect(code).not.toContain("Hello World!"); - expect(code).toContain(""); expect(code).toContain("Broken String"); var TEST_OUTPUT; From 353693be1bdeaece3dfa0fd301fa9c07806e3832 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 2 Sep 2024 01:06:20 -0400 Subject: [PATCH 022/103] Preserve Function Length, Identifier Generator Modes, Improvements --- src/constants.ts | 2 +- src/options.ts | 7 +- src/probability.ts | 1 - src/templates/setFunctionLengthTemplate.ts | 11 ++ src/transforms/controlFlowFlattening.ts | 45 +++-- src/transforms/dispatcher.ts | 75 ++++++-- src/transforms/flatten.ts | 86 +++++---- src/transforms/lock/lock.ts | 4 +- src/transforms/plugin.ts | 68 ++++++- src/transforms/rgf.ts | 19 +- src/transforms/string/encoding.ts | 2 +- src/transforms/string/stringConcealing.ts | 8 +- src/transforms/variableMasking.ts | 16 +- src/utils/NameGen.ts | 5 +- src/utils/ast-utils.ts | 86 +++++++++ src/utils/function-utils.ts | 23 +++ test/index.test.ts | 172 ++++-------------- test/options.test.ts | 66 +++++++ test/transforms/flatten.test.ts | 4 +- .../string/customStringEncoding.test.ts | 2 +- 20 files changed, 472 insertions(+), 230 deletions(-) create mode 100644 src/templates/setFunctionLengthTemplate.ts diff --git a/src/constants.ts b/src/constants.ts index 6538024..fc366f9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -27,7 +27,7 @@ export const SKIP = Symbol("skip"); export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; - [SKIP]?: boolean; + [SKIP]?: boolean | number; } /** diff --git a/src/options.ts b/src/options.ts index 66ba08d..b4efc97 100644 --- a/src/options.ts +++ b/src/options.ts @@ -37,7 +37,7 @@ export interface CustomStringEncoding { * } * ``` */ - code?: string; + code?: string | Template; encode: (str: string) => string; /** @@ -51,11 +51,6 @@ export interface CustomStringEncoding { * Should be used when created 'shuffled' variants of the same encoding. */ identity?: string; - - /** - * Can be used instead of the `code` property. - */ - template?: Template; } export interface ObfuscateOptions { diff --git a/src/probability.ts b/src/probability.ts index 4e5a5a9..42eb883 100644 --- a/src/probability.ts +++ b/src/probability.ts @@ -22,7 +22,6 @@ export type ProbabilityMap< /** * Evaluates a ProbabilityMap. * @param map The setting object. - * @param runner Custom function to determine return value * @param customFnArgs Args given to user-implemented function, such as a variable name. */ export function computeProbabilityMap< diff --git a/src/templates/setFunctionLengthTemplate.ts b/src/templates/setFunctionLengthTemplate.ts new file mode 100644 index 0000000..c483d29 --- /dev/null +++ b/src/templates/setFunctionLengthTemplate.ts @@ -0,0 +1,11 @@ +import Template from "./template"; + +export const SetFunctionLengthTemplate = new Template(` + function {fnName}(fn, length = 1){ + Object["defineProperty"](fn, "length", { + "value": length, + "configurable": false + }); + return fn; + } +`); diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index d41e3f5..9bec12b 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -509,32 +509,37 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) as NodePath; if (!variableDeclaration) return; - var wrapInExpressionStatement = true; - - if (variableDeclaration.parentPath.isForStatement()) { - if (variableDeclaration.node.kind === "var") { - wrapInExpressionStatement = false; - } else { - // 'let'/'const' don't get extracted - return; - } + var wrapInAssignmentStatement = true; + + var forChild = variableDeclaration.find( + (p) => + (p.parentPath?.isForStatement() && + p.parentKey === "init") || + (p.parentPath?.isFor() && p.parentKey === "left") + ); + if (forChild) { + wrapInAssignmentStatement = false; } ok(variableDeclaration.node.declarations.length === 1); - const assignment = t.assignmentExpression( - "=", - variableDeclaration.node.declarations[0].id, - variableDeclaration.node.declarations[0].init || - t.identifier("undefined") + let identifier = t.cloneNode( + variableDeclaration.node.declarations[0].id ); - // Replace variable declaration with assignment expression - variableDeclaration.replaceWith( - wrapInExpressionStatement - ? t.expressionStatement(assignment) - : assignment - ); + const assignment = wrapInAssignmentStatement + ? t.expressionStatement( + t.assignmentExpression( + "=", + identifier, + variableDeclaration.node.declarations[0].init || + t.identifier("undefined") + ) + ) + : identifier; + + // Replace variable declaration with assignment expression statement + variableDeclaration.replaceWith(assignment); }, }, diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index 01c2d14..59416be 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -8,16 +8,26 @@ import { chance, getRandomString } from "../utils/random-utils"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; -import { isVariableFunctionIdentifier } from "../utils/function-utils"; +import { + computeFunctionLength, + isVariableFunctionIdentifier, +} from "../utils/function-utils"; +import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Dispatcher); var dispatcherCounter = 0; + const setFunctionLength = me.getPlaceholder("d_fnLength"); + + let active = true; + return { visitor: { "Program|Function": { exit(_path) { + if (!active) return; + const blockPath = _path as NodePath; if ((blockPath.node as NodeSymbol)[UNSAFE]) return; @@ -169,9 +179,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { }; // Replace the identifier with a call to the function - if (path.parent.type === "CallExpression") { + const parentPath = path.parentPath; + if (parentPath?.isCallExpression()) { var expressions: t.Expression[] = []; - var callArguments = path.parent.arguments; + var callArguments = parentPath.node.arguments; if (callArguments.length === 0) { expressions.push( @@ -197,7 +208,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ? expressions[0] : t.sequenceExpression(expressions); - path.parentPath.replaceWith(output); + parentPath.replaceWith(output); } else { // Replace non-invocation references with a 'cached' version of the function path.replaceWith(createDispatcherCall(newName, keys.nonCall)); @@ -206,10 +217,27 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }); + const fnLengthProperties = []; + // Create the dispatcher function const objectExpression = t.objectExpression( Array.from(newNameMapping).map(([name, newName]) => { - const originalFn = functionPaths.get(name)!.node; + const originalPath = functionPaths.get(name); + const originalFn = originalPath.node; + + if (me.options.preserveFunctionLength) { + const fnLength = computeFunctionLength(originalPath); + + if (fnLength >= 1) { + // 0 is already the default + fnLengthProperties.push( + t.objectProperty( + t.stringLiteral(newName), + t.numericLiteral(fnLength) + ) + ); + } + } const newBody = [...originalFn.body.body]; ok(Array.isArray(newBody)); @@ -237,18 +265,30 @@ export default ({ Plugin }: PluginArg): PluginObj => { }) ); + const fnLengths = t.objectExpression(fnLengthProperties); + const dispatcher = new Template(` - function ${dispatcherName}(name, flagArg, returnTypeArg) { + function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) { var output, fns = {objectExpression}; if(flagArg === "${keys.clearPayload}") { ${payloadName} = []; } if(flagArg === "${keys.nonCall}") { - output = ${cacheName}[name] || (${cacheName}[name] = function(...args){ - ${payloadName} = args; - return fns[name].apply(this); - }); + function createFunction(){ + var fn = function(...args){ + ${payloadName} = args; + return fns[name].apply(this); + } + + var fnLength = fnLengths[name]; + if(fnLength) { + ${setFunctionLength}(fn, fnLength); + } + + return fn; + } + output = ${cacheName}[name] || (${cacheName}[name] = createFunction()); } else { output = fns[name](); } @@ -261,6 +301,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } `).single({ objectExpression, + fnLengthsObjectExpression: fnLengths, }); /** @@ -268,11 +309,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { * @param node */ function prepend(node: t.Statement) { - var p = blockStatement.unshiftContainer( + var newPath = blockStatement.unshiftContainer( "body", node - ); - blockStatement.scope.registerDeclaration(p[0]); + )[0]; + blockStatement.scope.registerDeclaration(newPath); } // Insert the dispatcher function @@ -295,6 +336,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]) ); + if (blockPath.isProgram()) { + active = false; + + prepend( + SetFunctionLengthTemplate.single({ fnName: setFunctionLength }) + ); + } + // Remove original functions for (let path of functionPaths.values()) { path.remove(); diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 40f260a..1813149 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -1,17 +1,19 @@ import * as t from "@babel/types"; import { NodePath, PluginObj } from "@babel/core"; import { + ensureComputedExpression, getFunctionName, - insertIntoNearestBlockScope, - isReservedIdentifier, + prepend, } from "../utils/ast-utils"; import { PluginArg } from "./plugin"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; -import { getRandomInteger, getRandomString } from "../utils/random-utils"; +import { getRandomInteger } from "../utils/random-utils"; import { NodeSymbol, UNSAFE } from "../constants"; -import { isFunctionStrictMode } from "../utils/function-utils"; +import { computeFunctionLength } from "../utils/function-utils"; +import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { ok } from "assert"; +import { Scope } from "@babel/traverse"; const SKIP = Symbol("skip"); interface NodeSkip { @@ -28,9 +30,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { function flattenFunction(fnPath: NodePath) { // Skip if already processed - if ((fnPath.node as NodeSkip)[SKIP]) { - return; - } + if (me.isSkipped(fnPath)) return; // Don't apply to generator functions if (fnPath.node.generator) return; @@ -44,12 +44,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (t.isArrowFunctionExpression(fnPath.node)) return; if (!t.isBlockStatement(fnPath.node.body)) return; - // Do not apply to non-simple parameter functions - if (fnPath.node.params.find((x) => !t.isIdentifier(x))) return; - // Skip if marked as unsafe if ((fnPath.node as NodeSymbol)[UNSAFE]) return; + var program = fnPath.findParent((p) => + p.isProgram() + ) as NodePath; + let functionName = getFunctionName(fnPath); if (!t.isValidIdentifier(functionName, true)) { functionName = "anonymous"; @@ -67,7 +68,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { function generateProp(originalName) { var newProp; do { - newProp = originalName + getRandomInteger(0, 10); + newProp = "" + originalName + getRandomInteger(0, 10); // newProp = getRandomString(6); } while ( standardProps.has(newProp) || @@ -88,7 +89,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnPath.traverse({ Identifier: { exit(identifierPath) { - if (identifierPath.isJSXIdentifier()) return; + if ( + !identifierPath.isBindingIdentifier() && + !(identifierPath as NodePath).isReferencedIdentifier() + ) + return; + if ((identifierPath.node as NodeSymbol)[UNSAFE]) return; const identifierName = identifierPath.node.name; @@ -97,24 +103,40 @@ export default ({ Plugin }: PluginArg): PluginObj => { identifierPath.parent.id === identifierPath.node ) return; - if (identifierName === "arguments" || identifierName === "console") - return; + if (identifierName === "arguments") return; var binding = identifierPath.scope.getBinding(identifierName); if (!binding) { return; } - var cursor: NodePath = identifierPath; - var isDefinedWithin = false; + if ( + binding.kind === "param" && + binding.identifier === identifierPath.node + ) + return; + + var definedLocal = identifierPath.scope; do { - if (cursor.scope === binding.scope) { - isDefinedWithin = true; + if (definedLocal.hasOwnBinding(identifierName)) return; + if (definedLocal === fnPath.scope) break; + + definedLocal = definedLocal.parent; + if (definedLocal === program.scope) ok(false); + } while (definedLocal); + + var cursor: Scope = fnPath.scope.parent; + var isOutsideVariable = false; + + do { + if (cursor.hasBinding(identifierName)) { + isOutsideVariable = true; + break; } - cursor = cursor.parentPath; - } while (cursor && cursor !== fnPath); + cursor = cursor.parent; + } while (cursor); - if (isDefinedWithin) { + if (!isOutsideVariable) { return; } @@ -131,6 +153,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { for (var identifierPath of identifierPaths) { const identifierName = identifierPath.node.name; + if (typeof identifierName !== "string") continue; + const isTypeof = identifierPath.parentPath.isUnaryExpression({ operator: "typeof", }); @@ -179,6 +203,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { standardProps.set(identifierName, standardProp); } + ensureComputedExpression(identifierPath); + // Replace identifier with a reference to the flat object property identifierPath .replaceWith( @@ -317,31 +343,25 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]); + const originalLength = computeFunctionLength(fnPath); fnPath.node.params = [t.restElement(t.identifier(argName))]; // Ensure updated parameter gets registered in the function scope fnPath.scope.crawl(); fnPath.skip(); - (flattenedFunctionDeclaration as NodeSkip)[SKIP] = true; - // Add the new flattened function at the top level - var program = fnPath.findParent((p) => - p.isProgram() - ) as NodePath; - - var newPath = program.unshiftContainer( - "body", - flattenedFunctionDeclaration - )[0]; + var newPath = prepend(program, flattenedFunctionDeclaration)[0]; - // Register the new function declaration at the root scope - program.scope.registerDeclaration(newPath); + me.skip(newPath); // Ensure parameters are registered in the new function scope newPath.scope.crawl(); newPath.skip(); + + // Set function length + me.setFunctionLength(fnPath, originalLength); } return { diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index 9743ce9..ab62b60 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -61,7 +61,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { {countermeasures} } `).setDefaultVariables({ - regexString: t.stringLiteral(regexString.toString()), + regexString: () => t.stringLiteral(regexString.toString()), }), percentagePerBlock: 0.5, }); @@ -269,7 +269,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Don't apply to async or generator functions if (funcDecPath.node.async || funcDecPath.node.generator) return; - if (funcDecPath.find((p) => (p.node as NodeSymbol)[SKIP])) return; + if (funcDecPath.find((p) => !!(p.node as NodeSymbol)[SKIP])) return; var program = getParentFunctionOrProgram(funcDecPath); // Only top-level functions diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 7a381f9..3ca94ae 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -1,7 +1,13 @@ -import { PluginObj } from "@babel/core"; +import { NodePath, PluginObj } from "@babel/core"; import Obfuscator from "../obfuscator"; import { getRandomString } from "../utils/random-utils"; import { Order } from "../order"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { NodeSymbol, SKIP } from "../constants"; +import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; +import { prepend, prependProgram } from "../utils/ast-utils"; +import { ok } from "assert"; export type PluginFunction = (pluginArg: PluginArg) => PluginObj; @@ -31,6 +37,66 @@ export class PluginInstance { return this.obfuscator.globalState; } + skip(path: NodePath | t.Node | NodePath[]) { + if (Array.isArray(path)) { + path.forEach((p) => this.skip(p)); + } else { + let any = path as any; + let node = any.isNodeType ? any.node : any; + + (node as NodeSymbol)[SKIP] = this.order; + } + } + + isSkipped(path: NodePath | t.Node) { + let any = path as any; + let node = any.isNodeType ? any.node : any; + + return (node as NodeSymbol)[SKIP] === this.order; + } + + private setFunctionLengthName: string; + setFunctionLength(path: NodePath, originalLength: number) { + // Function length + if (this.options.preserveFunctionLength && originalLength > 0) { + if (!this.setFunctionLengthName) { + this.setFunctionLengthName = this.getPlaceholder("fnLength"); + + this.skip( + prependProgram( + path, + SetFunctionLengthTemplate.compile({ + fnName: this.setFunctionLengthName, + }) + ) + ); + } + if (t.isFunctionDeclaration(path.node)) { + prepend( + path.parentPath, + t.expressionStatement( + t.callExpression(t.identifier(this.setFunctionLengthName), [ + t.identifier(path.node.id.name), + t.numericLiteral(originalLength), + ]) + ) + ); + } else if ( + t.isFunctionExpression(path.node) || + t.isArrowFunctionExpression(path.node) + ) { + path.replaceWith( + t.callExpression(t.identifier(this.setFunctionLengthName), [ + path.node, + t.numericLiteral(originalLength), + ]) + ); + } else { + // TODO + } + } + } + getPlaceholder(suffix = "") { return "__p_" + getRandomString(4) + (suffix ? "_" + suffix : ""); } diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 648daaf..058d2ab 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -4,8 +4,10 @@ import { Order } from "../order"; import * as t from "@babel/types"; import Obfuscator from "../obfuscator"; import { computeProbabilityMap } from "../probability"; -import { getFunctionName } from "../utils/ast-utils"; +import { getFunctionName, prepend, prependProgram } from "../utils/ast-utils"; import { NodeSymbol, SKIP, UNSAFE } from "../constants"; +import { computeFunctionLength } from "../utils/function-utils"; +import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; /** * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. @@ -54,16 +56,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); }, }, - "FunctionDeclaration|FunctionExpression|ArrowFunctionExpression": { + "FunctionDeclaration|FunctionExpression": { exit(_path) { - const path = _path as NodePath; + const path = _path as NodePath< + t.FunctionDeclaration | t.FunctionExpression + >; + + if (me.isSkipped(path)) return; // Skip async and generator functions if (path.node.async || path.node.generator) return; - // Don't apply to arrow functions - if (t.isArrowFunctionExpression(path.node)) return; - const name = getFunctionName(path); if (name === me.options.lock?.countermeasures) return; me.log(name); @@ -171,11 +174,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { rgfArrayExpression.elements.push(functionExpression); // Params no longer needed, using 'arguments' instead + const originalLength = computeFunctionLength(path); path.node.params = []; // Function is now unsafe (path.node as NodeSymbol)[UNSAFE] = true; + // Update body to point to new function path .get("body") .replaceWith( @@ -202,6 +207,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]) ); + + me.setFunctionLength(path, originalLength); }, }, }, diff --git a/src/transforms/string/encoding.ts b/src/transforms/string/encoding.ts index 409d399..b4adfc2 100644 --- a/src/transforms/string/encoding.ts +++ b/src/transforms/string/encoding.ts @@ -99,7 +99,7 @@ export function createDefaultStringEncoding( return Buffer.from(ret).toString("utf-8"); }, - template: new Template(` + code: new Template(` function {fnName}(str){ var table = {__strTable__}; diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 8d26043..07eb0fc 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -82,8 +82,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { encoding.identity = encodingIndex.toString(); } - if (typeof encoding.template === "undefined") { - encoding.template = new Template(encoding.code); + if (typeof encoding.code === "string") { + encoding.code = new Template(encoding.code); } encodingImplementations[encoding.identity] = encoding; @@ -264,8 +264,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { const decodeFnName = fnName + "_d"; + ok(encodingImplementation.code instanceof Template); + // The decoder function - const decoder = encodingImplementation.template.compile({ + const decoder = encodingImplementation.code.compile({ fnName: decodeFnName, __bufferToStringFunction__: bufferToStringName, }); diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 5b00312..d4e8ea0 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -7,7 +7,10 @@ import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; import { getFunctionName } from "../utils/ast-utils"; -import { isFunctionStrictMode } from "../utils/function-utils"; +import { + computeFunctionLength, + isFunctionStrictMode, +} from "../utils/function-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.VariableMasking); @@ -118,7 +121,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - referencePath.replaceWith(t.cloneNode(memberExpression)); + if (referencePath.container) { + referencePath.replaceWith(t.cloneNode(memberExpression)); + } }); [binding.path, ...binding.constantViolations].forEach( @@ -174,7 +179,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); } - replacePath.replaceWith(replaceExpr); + if (replacePath.container) { + replacePath.replaceWith(replaceExpr); + } }, }); } @@ -186,9 +193,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (!needsStack) return; + var originalLength = computeFunctionLength(fnPath); fnPath.node.params = [t.restElement(t.identifier(stackName))]; fnPath.scope.registerBinding("param", fnPath.get("params")[0], fnPath); + + me.setFunctionLength(fnPath, originalLength); }; return { diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts index 3cabbff..e1269a1 100644 --- a/src/utils/NameGen.ts +++ b/src/utils/NameGen.ts @@ -2,6 +2,7 @@ import { ok } from "assert"; import { ObfuscateOptions } from "../options"; import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils"; import { choice, getRandomHexString, getRandomInteger } from "./random-utils"; +import { computeProbabilityMap } from "../probability"; export class NameGen { private generatedNames = new Set(); @@ -22,9 +23,11 @@ export class NameGen { return value; } + var mode = computeProbabilityMap(this.identifierGenerator); + const randomizedLength = getRandomInteger(6, 8); - switch (this.identifierGenerator) { + switch (mode) { case "randomized": var characters = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""); diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 409d0db..cfbcd23 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -260,3 +260,89 @@ export function getMemberExpressionPropertyAsString( return null; // If the property cannot be determined } + +/** + * Prepends and registers a list of nodes to the beginning of a block. + * + * - Preserves import declarations by inserting after the last import declaration. + * - Handles arrow functions + * - Handles switch cases + * @param path + * @param nodes + * @returns + */ +export function prepend( + path: NodePath, + ...nodesIn: (t.Statement | t.Statement[])[] +): NodePath[] { + var nodes: t.Statement[] = []; + if (Array.isArray(nodesIn[0])) { + ok(nodesIn.length === 1); + nodes = nodesIn[0]; + } else { + nodes = nodesIn as t.Statement[]; + } + + var listParent = path.find( + (p) => p.isFunction() || p.isBlock() || p.isSwitchCase() + ); + if (!listParent) { + throw new Error("Could not find a suitable parent to prepend to"); + } + + function registerPaths(paths: NodePath[]) { + for (var path of paths) { + path.scope.registerDeclaration(path); + } + + return paths; + } + + if (listParent.isProgram()) { + // Preserve import declarations + // Filter out import declarations + const body = listParent.get("body"); + const lastImportIndex = body.findIndex( + (path) => !path.isImportDeclaration() + ); + + if (lastImportIndex === 0 || lastImportIndex === -1) { + // No non-import declarations, so we can safely unshift everything + return registerPaths(listParent.unshiftContainer("body", nodes)); + } else { + // Insert the nodes after the last import declaration + return registerPaths(body[lastImportIndex - 1].insertAfter(nodes)); + } + } + + if (listParent.isFunction()) { + var body = listParent.get("body"); + + if (listParent.isArrowFunctionExpression() && listParent.node.expression) { + if (!body.isBlockStatement()) { + body.replaceWith( + t.blockStatement([t.returnStatement(body.node as t.Expression)]) + ); + } + } + + ok(body.isBlockStatement()); + + return registerPaths(body.unshiftContainer("body", nodes)); + } + + if (listParent.isBlock()) { + return registerPaths(listParent.unshiftContainer("body", nodes)); + } else if (listParent.isSwitchCase()) { + return registerPaths(listParent.unshiftContainer("consequent", nodes)); + } +} + +export function prependProgram( + path: NodePath, + ...nodes: (t.Statement | t.Statement[])[] +) { + var program = path.find((p) => p.isProgram()); + ok(program); + return prepend(program, ...nodes); +} diff --git a/src/utils/function-utils.ts b/src/utils/function-utils.ts index 399498c..ec01fe1 100644 --- a/src/utils/function-utils.ts +++ b/src/utils/function-utils.ts @@ -28,3 +28,26 @@ export function isVariableFunctionIdentifier(path: NodePath) { return false; } + +/** + * Computes the `function.length` property given the parameter nodes. + * + * @example function abc(a, b, c = 1, ...d) {} // abc.length = 2 + */ +export function computeFunctionLength(fnPath: NodePath): number { + var count = 0; + + for (var parameterNode of fnPath.node.params) { + if ( + parameterNode.type === "Identifier" || + parameterNode.type === "ObjectPattern" || + parameterNode.type === "ArrayPattern" + ) { + count++; + } else { + break; + } + } + + return count; +} diff --git a/test/index.test.ts b/test/index.test.ts index 7a9e45a..162ffff 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,149 +1,49 @@ -import JsConfuser, { obfuscateWithProfiler } from "../src/index"; +import JsConfuser from "../src/index"; import { ProfilerLog } from "../src/obfuscationResult"; -it("should be a function", async () => { - expect(typeof JsConfuser).toBe("function"); -}); - -it("should return be an awaited string", async () => { - var output = await JsConfuser("5+5", { - target: "browser", - opaquePredicates: true, +describe("obfuscate", () => { + test("Variant #1: Should be a function", async () => { + expect(typeof JsConfuser.obfuscate).toBe("function"); }); - expect(typeof output).toBe("string"); -}); - -it("should error when options are empty", async () => { - var invalid: any = {}; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); - -it("should error when no obfuscation options", async () => { - var invalid: any = { - target: "browser", - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); - -it("should error on invalid target values", async () => { - var invalid: any = { - target: "__invalid__target__", - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); - -it("should error when target property missing", async () => { - var invalid: any = { - objectExtraction: true, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); - -it("should error when invalid options are passed in", async () => { - var invalid: any = { - target: "browser", - __invalid__prop__: true, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); - -it("should error when browserLock is used on target 'node'", async () => { - var invalid: any = { - target: "node", - lock: { - browserLock: ["firefox"], - }, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); + test("Variant #2: Return be an awaited string", async () => { + var output = await JsConfuser.obfuscate("5+5", { + target: "browser", + opaquePredicates: true, + }); -it("should error when invalid browser names are passed in", async () => { - var invalid: any = { - target: "browser", - lock: { - browserLock: ["__invalid__browser__"], - }, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); + expect(typeof output).toBe("string"); + }); -it("should error when invalid os names are passed in", async () => { - var invalid: any = { - target: "browser", - lock: { - osLock: ["__invalid__browser__"], - }, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); + test("Variant #3: Error when options are empty", async () => { + var invalid: any = {}; -it("should error when invalid startDate is passed in", async () => { - var invalid: any = { - target: "browser", - lock: { - startDate: "__invalid__date__object__", - }, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); -it("should error when invalid endDate is passed in", async () => { - var invalid: any = { - target: "browser", - lock: { - endDate: "__invalid__date__object__", - }, - }; - - await expect(async () => { - return await JsConfuser("5+5", invalid); - }).rejects.toThrow(); -}); + test("Variant #4: Error when no obfuscation options", async () => { + var invalid: any = { + target: "browser", + }; -it("should error when source code is not a string", async () => { - await expect(async () => { - return await JsConfuser({} as any, { - target: "node", - preset: "low", - }); - }).rejects.toThrow(); -}); + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); -it("should error when invalid source code is passed in", async () => { - await expect(async () => { - return await JsConfuser("#?!if?//for:;1(function:class{))]][]", { - target: "node", - preset: "low", - }); - }).rejects.toThrow(); + test("Variant #5: Error when invalid source code is passed in", async () => { + await expect(async () => { + return await JsConfuser.obfuscate( + "#?!if?//for:;1(function:class{))]][]", + { + target: "node", + preset: "low", + } + ); + }).rejects.toThrow(); + }); }); describe("obfuscateAST", () => { diff --git a/test/options.test.ts b/test/options.test.ts index 726a091..b14efab 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -89,6 +89,72 @@ describe("options", () => { } as any) ).rejects.toThrow(); }); + + test("Variant #8: Error on invalid target values", async () => { + var invalid: any = { + target: "__invalid__target__", + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); + + test("Variant #9: Error when target property missing", async () => { + var invalid: any = { + objectExtraction: true, + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); + + test("Variant #10: Error when invalid options are passed in", async () => { + var invalid: any = { + target: "browser", + __invalid__prop__: true, + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); + + test("Variant #11: Error when invalid startDate is passed in", async () => { + var invalid: any = { + target: "browser", + lock: { + startDate: "__invalid__date__object__", + }, + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); + + test("Variant #12: Error when invalid endDate is passed in", async () => { + var invalid: any = { + target: "browser", + lock: { + endDate: "__invalid__date__object__", + }, + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); + }); + + test("Variant #13: Error when source code is not a string", async () => { + await expect(async () => { + return await JsConfuser.obfuscate({} as any, { + target: "node", + preset: "low", + }); + }).rejects.toThrow(); + }); }); describe("options.preserveFunctionLength", () => { diff --git a/test/transforms/flatten.test.ts b/test/transforms/flatten.test.ts index ee3d178..2635932 100644 --- a/test/transforms/flatten.test.ts +++ b/test/transforms/flatten.test.ts @@ -640,8 +640,8 @@ test("Variant #21: Preserve function.length property", async () => { ); expect(output).toContain("_flat_oneParameter"); - expect(output).not.toContain("_flat_twoParameters"); - expect(output).not.toContain("_flat_threeParameters"); + expect(output).toContain("_flat_twoParameters"); + expect(output).toContain("_flat_threeParameters"); var TEST_OUTPUT; eval(output); diff --git a/test/transforms/string/customStringEncoding.test.ts b/test/transforms/string/customStringEncoding.test.ts index 0d8dfac..6e37ca8 100644 --- a/test/transforms/string/customStringEncoding.test.ts +++ b/test/transforms/string/customStringEncoding.test.ts @@ -80,7 +80,7 @@ test("Variant #2: Custom Randomized Base64 encoding", async () => { const shuffledCharset = shuffle(customCharset.split("")).join(""); return { - template: new Template(` + code: new Template(` // Creates a reverse lookup table from the given charset function createReverseCharset(charset) { if (charset.length !== 64) { From cf2d683fde593dbeec7278d110e92c99d04ade02 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 2 Sep 2024 21:47:19 -0400 Subject: [PATCH 023/103] Improve Function Length, RGF Infinite recursion, Variable renaming --- CHANGELOG.md | 30 ++++++++---- README.md | 7 +-- index.d.ts | 2 - src/constants.ts | 3 ++ src/transforms/dispatcher.ts | 19 +++++-- .../extraction/duplicateLiteralsRemoval.ts | 10 +--- src/transforms/identifier/globalConcealing.ts | 49 +++++++++---------- .../identifier/movedDeclarations.ts | 2 +- src/transforms/identifier/renameVariables.ts | 14 +++++- src/transforms/plugin.ts | 6 +-- src/transforms/preparation.ts | 11 ++++- src/transforms/rgf.ts | 25 ++++++---- src/utils/ast-utils.ts | 2 + src/utils/function-utils.ts | 15 +++++- src/utils/scope-utils.ts | 18 +++++-- .../identifier/movedDeclarations.test.ts | 29 ++++++++++- .../string/customStringEncoding.test.ts | 3 -- 17 files changed, 164 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2c36d..ac84464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,31 @@ -# `2.0.0` -2.0 Rewrite +# `2.0.0-alpha.0` +2.0 Rewrite Alpha 🎉 -- New option `renameLabels` to control if/which labels get renamed. Previously enabled but was not configurable. +- Complete rewrite of JS-Confuser using Babel! -- RGF no longers uses `new Function` instead uses `eval` +- - Improved API Interface **⚠️ Breaking change** -- Improved Moved Declaration's ability to move variables as unused function parameters +- - Renamed `Stack` to `Variable Masking` **⚠️ Breaking change** -- Removed ES5 option - Use Babel Instead +- - Custom String Encoding / Custom Lock Code -- Removed Browser Lock and OS Lock - Use custom Locks instead +- - Added `Rename Labels` Learn more here -- Removed Shuffle Hash option +- - RGF no longers uses `new Function` instead uses `eval` -- Control Flow Flattening no longer flattens `For Statement`/`While Statement/`Do Statement` for performance reasons. It now applies to `FunctionDeclaration` and flattens functions. +- - Improved code transforms! -- Removed `indent` option, `@babel/generator` does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. +- - Improved `Moved Declaration`'s ability to move variables as unused function parameters + +**Removed features** + +- Removed `ES5` option - Use Babel Instead + +- Removed `Browser Lock` and `OS Lock` - Use Custom Locks instead + +- Removed `Shuffle`'s Hash option + +- Removed `Indent` option. [`@babel/generator`](https://www.npmjs.com/package/@babel/generator) does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. Be mindful if you have `integrity` or `selfDefending` enabled, as you should not alter the obfuscated code. # `1.7.3` Tamper Protection diff --git a/README.md b/README.md index f575be5..7eca297 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ - Locks (domainLock, date) - [Detect changes to source code](https://github.com/MichaelXF/js-confuser/blob/master/docs/Integrity.md) -You can extend each preset or all go without them entirely. ## Installation @@ -44,8 +43,8 @@ JsConfuser.obfuscate(` target: "node", preset: "high", stringEncoding: false, // <- Normally enabled -}).then(obfuscated => { - console.log(obfuscated) +}).then(result => { + console.log(result.code) }) /* @@ -74,8 +73,6 @@ Read the [documentation](https://js-confuser.com/docs) for everything to know ab - [Presets](https://new--confuser.netlify.app/docs) - - ## Bug report Please [open an issue](https://github.com/MichaelXF/js-confuser/issues) with the code and config used. diff --git a/index.d.ts b/index.d.ts index 44ae407..63eea68 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,3 @@ -import TransformClass from "./src/transforms/transform"; import ObfuscatorClass from "./src/obfuscator"; import { IJsConfuser as JsConfuser, @@ -16,4 +15,3 @@ export const presets: IJsConfuserPresets; export const debugTransformations: IJsConfuserDebugTransformations; export const debugObfuscation: IJsConfuserDebugObfuscation; export const Obfuscator: typeof ObfuscatorClass; -export const Transform: typeof TransformClass; diff --git a/src/constants.ts b/src/constants.ts index fc366f9..2a4a7c7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,10 +24,13 @@ export const PREDICTABLE = Symbol("predictable"); */ export const SKIP = Symbol("skip"); +export const FN_LENGTH = Symbol("fnLength"); + export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; [SKIP]?: boolean | number; + [FN_LENGTH]?: number; } /** diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index 59416be..ab90cea 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -339,9 +339,22 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (blockPath.isProgram()) { active = false; - prepend( - SetFunctionLengthTemplate.single({ fnName: setFunctionLength }) - ); + if (me.options.preserveFunctionLength) { + // Insert function length code + prepend( + SetFunctionLengthTemplate.single({ fnName: setFunctionLength }) + ); + } else { + // Don't insert function length code when disabled + // Instead insert empty function as the identifier is still referenced + prepend( + t.functionDeclaration( + t.identifier(setFunctionLength), + [], + t.blockStatement([]) + ) + ); + } } // Remove original functions diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index fc50ea5..3add7c6 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -3,7 +3,7 @@ import * as t from "@babel/types"; import { ok } from "assert"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; -import { ensureComputedExpression } from "../../utils/ast-utils"; +import { ensureComputedExpression, prepend } from "../../utils/ast-utils"; function fail(): never { throw new Error("Assertion failed"); @@ -141,13 +141,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { t.variableDeclarator(t.identifier(arrayName), arrayExpression), ]); - // Insert the array at the top of the program body - var path = programPath.unshiftContainer( - "body", - itemsArrayDeclaration - )[0]; - - programPath.scope.registerDeclaration(path); + prepend(programPath, itemsArrayDeclaration); }, }, }, diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 6d6ad55..7d10e77 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -6,6 +6,8 @@ import { PluginArg } from "../plugin"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; import { variableFunctionName } from "../../constants"; +import { prepend } from "../../utils/ast-utils"; +import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; const ignoreGlobals = new Set([ "require", @@ -24,7 +26,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { gen = new NameGen(); // Create the getGlobal function using a template - function createGetGlobalFunction(): t.FunctionDeclaration { + function createGlobalConcealingFunction(): t.FunctionDeclaration { const createSwitchStatement = () => { const cases = Array.from(globalMapping.keys()).map((originalName) => { var mappedKey = globalMapping.get(originalName); @@ -123,35 +125,28 @@ export default ({ Plugin }: PluginArg): PluginObj => { // No globals changed, no need to insert the getGlobal function if (globalMapping.size === 0) return; - // Insert the getGlobal function at the top of the program body - const getGlobalFunction = createGetGlobalFunction(); - - var newPath = programPath.unshiftContainer( - "body", - getGlobalFunction - )[0]; - programPath.scope.registerDeclaration(newPath); - - var varPath = programPath.unshiftContainer( - "body", - new Template(` - var {globalVarName} = (function (){ - try { - return window; - } catch ( e ) { - } - try { - return global; - } catch ( e ) { - } + // The Global Concealing function returns the global variable from the specified parameter + const globalConcealingFunction = createGlobalConcealingFunction(); + + prepend(programPath, globalConcealingFunction); - return this; - })(); + const getGlobalVarFnName = me.getPlaceholder(); - `).compile({ globalVarName: globalVarName }) - )[0]; + // Insert the get global function + prepend( + programPath, + createGetGlobalTemplate(me, programPath).compile({ + getGlobalFnName: getGlobalVarFnName, + }) + ); - programPath.scope.registerDeclaration(varPath); + // Call the get global function and store result in 'globalVarName' + prepend( + programPath, + new Template( + `var ${globalVarName} = ${getGlobalVarFnName}()` + ).single() + ); }, }, }, diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index 019fba3..ff03055 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -34,7 +34,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Strict mode disallows non-simple parameters // So we can't move the declaration to the function parameters var isStrictMode = isFunctionStrictMode(functionPath); - if (!isStrictMode) { + if (isStrictMode) { allowDefaultParamValue = false; } diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 1703d90..e276714 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -1,5 +1,5 @@ import { NodePath, PluginObj } from "@babel/core"; -import { Scope } from "@babel/traverse"; +import { Binding, Scope } from "@babel/traverse"; import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; @@ -15,6 +15,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { let availableNames: string[] = []; let renamedScopes = new Set(); + let renamedBindingIdentifiers = new WeakSet(); return { visitor: { @@ -36,6 +37,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { Scopable: { enter(path: NodePath) { const { scope } = path; + if (scope.path?.isClassDeclaration()) return; + if (renamedScopes.has(scope)) { return; } @@ -97,6 +100,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) continue; } + const binding = scope.bindings[identifierName]; + if (renamedBindingIdentifiers.has(binding.identifier)) continue; + renamedBindingIdentifiers.add(binding.identifier); let newName = actuallyAvailableNames.pop(); @@ -108,6 +114,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { } scope.rename(identifierName, newName); + + // Extra Class Declaration scope preserve logic needed + if (binding.path.type === "ClassDeclaration") { + var newBinding = scope.bindings[newName]; + binding.path.scope.bindings[newName] = newBinding; + } } availableNames.push(...names); diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 3ca94ae..dd9c46f 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -3,11 +3,9 @@ import Obfuscator from "../obfuscator"; import { getRandomString } from "../utils/random-utils"; import { Order } from "../order"; import * as t from "@babel/types"; -import Template from "../templates/template"; -import { NodeSymbol, SKIP } from "../constants"; +import { FN_LENGTH, NodeSymbol, SKIP } from "../constants"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { prepend, prependProgram } from "../utils/ast-utils"; -import { ok } from "assert"; export type PluginFunction = (pluginArg: PluginArg) => PluginObj; @@ -57,6 +55,8 @@ export class PluginInstance { private setFunctionLengthName: string; setFunctionLength(path: NodePath, originalLength: number) { + (path.node as NodeSymbol)[FN_LENGTH] = originalLength; + // Function length if (this.options.preserveFunctionLength && originalLength > 0) { if (!this.setFunctionLengthName) { diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 5b414a6..0e74670 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -76,6 +76,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { var binding = path.scope.getBinding(name); var predictable = true; + var maxArgLength = 0; for (var referencePath of binding.referencePaths) { if (!referencePath.parentPath.isCallExpression()) { @@ -83,15 +84,21 @@ export default ({ Plugin }: PluginArg): PluginObj => { break; } - for (var arg of referencePath.parentPath.get("arguments")) { + var argsPath = referencePath.parentPath.get("arguments"); + for (var arg of argsPath) { if (arg.isSpreadElement()) { predictable = false; break; } } + + if (argsPath.length > maxArgLength) { + maxArgLength = argsPath.length; + } } - if (predictable) { + var definedArgLength = path.get("params").length; + if (predictable && definedArgLength >= maxArgLength) { (path.node as NodeSymbol)[PREDICTABLE] = true; } }, diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 058d2ab..6ce373b 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -4,10 +4,9 @@ import { Order } from "../order"; import * as t from "@babel/types"; import Obfuscator from "../obfuscator"; import { computeProbabilityMap } from "../probability"; -import { getFunctionName, prepend, prependProgram } from "../utils/ast-utils"; +import { getFunctionName, prepend } from "../utils/ast-utils"; import { NodeSymbol, SKIP, UNSAFE } from "../constants"; import { computeFunctionLength } from "../utils/function-utils"; -import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; /** * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. @@ -32,12 +31,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Insert the RGF array at the top of the program - function prepend(node: t.Statement) { - var newPath = path.unshiftContainer("body", node)[0]; - path.scope.registerDeclaration(newPath); - } - prepend( + path, t.variableDeclaration("var", [ t.variableDeclarator( t.identifier(rgfArrayName), @@ -47,6 +42,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); prepend( + path, t.variableDeclaration("var", [ t.variableDeclarator( t.identifier(rgfEvalName), @@ -84,11 +80,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (name === rgfArrayName) return; const binding = refPath.scope.getBinding(name); - if (!binding) return; + if (!binding) { + if (me.options.globalVariables.has(name)) return; + if (name === "arguments") return; + + identifierPreventingTransform = name; + refPath.stop(); + return; + } // If the binding is not in the current scope, it is an outside reference if (binding.scope !== path.scope) { identifierPreventingTransform = name; + refPath.stop(); } }, }); @@ -97,7 +101,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { me.log( "Skipping function " + name + - " due to reference to outside variable:" + + " due to reference to outside variable: " + identifierPreventingTransform ); return; @@ -179,6 +183,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Function is now unsafe (path.node as NodeSymbol)[UNSAFE] = true; + me.skip(path); // Update body to point to new function path @@ -198,7 +203,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), [ t.arrayExpression([ - t.identifier("this"), + t.thisExpression(), t.identifier(rgfArrayName), ]), t.identifier("arguments"), diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index cfbcd23..f862dd9 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -336,6 +336,8 @@ export function prepend( } else if (listParent.isSwitchCase()) { return registerPaths(listParent.unshiftContainer("consequent", nodes)); } + + ok(false); } export function prependProgram( diff --git a/src/utils/function-utils.ts b/src/utils/function-utils.ts index ec01fe1..6fcd835 100644 --- a/src/utils/function-utils.ts +++ b/src/utils/function-utils.ts @@ -1,7 +1,15 @@ import { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; -import { variableFunctionName } from "../constants"; +import { FN_LENGTH, NodeSymbol, variableFunctionName } from "../constants"; +/** + * @example + * function abc() { + * "use strict"; + * } // true + * @param path + * @returns + */ export function isFunctionStrictMode(path: NodePath) { if ( t.isBlockStatement(path.node.body) && @@ -35,6 +43,11 @@ export function isVariableFunctionIdentifier(path: NodePath) { * @example function abc(a, b, c = 1, ...d) {} // abc.length = 2 */ export function computeFunctionLength(fnPath: NodePath): number { + var savedLength = (fnPath.node as NodeSymbol)[FN_LENGTH]; + if (typeof savedLength === "number") { + return savedLength; + } + var count = 0; for (var parameterNode of fnPath.node.params) { diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts index c0857ed..e7e5622 100644 --- a/src/utils/scope-utils.ts +++ b/src/utils/scope-utils.ts @@ -57,6 +57,7 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { programPath = path; } + // Duplicate name check if (seenNodes.has(path.node)) { throw new Error( `${pluginName}: Duplicate node found in AST ${path.node.type}` @@ -64,6 +65,14 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { } seenNodes.add(path.node); + // Identifier name check + if ( + path.node.type === "Identifier" && + ["this", "null"].includes(path.node.name) + ) { + throw new Error("Identifier cannot be named " + path.node.name); + } + if (path.scope && Object.keys(path.scope.bindings).length > 0) { scopeStates.set(path.node, captureScopeState(path.scope)); @@ -107,8 +116,11 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { }); if (errors.length > 0) { - throw new Error( - `${pluginName}: Scope integrity check failed:\n${errors.join("\n")}` - ); + const message = `${pluginName}: Scope integrity check failed:\n${errors.join( + "\n" + )}`; + + // console.warn(message); + throw new Error(message); } } diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 625a3e5..9925df2 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -178,8 +178,9 @@ test("Variant #8: Work with 'use strict'", async () => { }); // Ensure movedDeclarations applied and 'use strict' is still first - // x cannot be moved as a parameter as 'use strict' disallows non-simple parameters - expect(output).toContain('function myFunction(){"use strict";var x=1;'); + // 'x' can still be moved but we can't store the static value as a default value + // Strict mode functions disallow non-simple parameters + expect(output).toContain('function myFunction(x){"use strict";x=1;'); var TEST_OUTPUT; eval(output); @@ -274,3 +275,27 @@ test("Variant #10: Move parameters to predictable function", async () => { expect(TEST_OUTPUT).toStrictEqual(105); }); + +test("Variant #11: Predictable function called with extraneous parameters", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function addTen(myArg){ + var ten = 10; + return ten + myArg; + } + + TEST_OUTPUT = addTen(5, -5000); + `, + { + target: "node", + movedDeclarations: true, + } + ); + + expect(code).not.toContain("addTen(myArg,ten"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(15); +}); diff --git a/test/transforms/string/customStringEncoding.test.ts b/test/transforms/string/customStringEncoding.test.ts index 6e37ca8..63ce6b7 100644 --- a/test/transforms/string/customStringEncoding.test.ts +++ b/test/transforms/string/customStringEncoding.test.ts @@ -3,7 +3,6 @@ import { CustomStringEncoding } from "../../../src/options"; import Template from "../../../src/templates/template"; import { stringLiteral } from "@babel/types"; import { shuffle } from "../../../src/utils/random-utils"; -import { writeFileSync } from "fs"; test("Variant #1: Custom Base64 encoding", async () => { var code = ` @@ -154,8 +153,6 @@ var {fnName} = (str) => decode(str, {shuffledCharset}); var TEST_OUTPUT; eval(code); - writeFileSync("./dev.output.js", code, "utf-8"); - expect(Array.isArray(TEST_OUTPUT)).toStrictEqual(true); for (var i = 1; i <= 10; i++) { From 558f0d2d52a068d4cae84f25f81024f6fb4484c4 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 3 Sep 2024 14:33:42 -0400 Subject: [PATCH 024/103] Add `Minify` --- CHANGELOG.md | 4 + src/obfuscator.ts | 27 +- src/transforms/minify.ts | 513 +++++++++++++++++++++++++++++++++ test/transforms/minify.test.ts | 293 +++++++++++++------ 4 files changed, 734 insertions(+), 103 deletions(-) create mode 100644 src/transforms/minify.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ac84464..29c8271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ - - Improved code transforms! +- - Improved `Minify` + +- - - Removes unused variables and functions now + - - Improved `Moved Declaration`'s ability to move variables as unused function parameters **Removed features** diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 814ea98..bc083d9 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -17,11 +17,7 @@ import deadCode from "./transforms/deadCode"; import stringSplitting from "./transforms/string/stringSplitting"; import shuffle from "./transforms/shuffle"; import finalizer from "./transforms/finalizer"; -import { - ObfuscationResult, - ProfilerCallback, - ProfilerLog, -} from "./obfuscationResult"; +import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; import { isProbabilityMapProbable } from "./probability"; import astScrambler from "./transforms/astScrambler"; import calculator from "./transforms/calculator"; @@ -39,6 +35,12 @@ import variableConcealing from "./transforms/identifier/variableConcealing"; import { NameGen } from "./utils/NameGen"; import { assertScopeIntegrity } from "./utils/scope-utils"; import opaquePredicates from "./transforms/opaquePredicates"; +import minify from "./transforms/minify"; + +export const DEFAULT_OPTIONS: ObfuscateOptions = { + target: "node", + compact: true, +}; export default class Obfuscator { plugins: { @@ -100,7 +102,7 @@ export default class Obfuscator { push(this.options.shuffle, shuffle); push(this.options.movedDeclarations, movedDeclarations); push(this.options.renameLabels, renameLabels); - // Minify + push(this.options.minify, minify); push(this.options.astScrambler, astScrambler); push(this.options.renameVariables, renameVariables); @@ -184,9 +186,9 @@ export default class Obfuscator { this.obfuscateAST(ast); // Generate the transformed code from the modified AST with comments removed and compacted output - const code = Obfuscator.generateCode(ast, this.options); + const code = this.generateCode(ast); - if (code) { + if (typeof code === "string") { return { code: code, }; @@ -200,10 +202,7 @@ export default class Obfuscator { } static createDefaultInstance() { - return new Obfuscator({ - target: "node", - compact: true, - }); + return new Obfuscator(DEFAULT_OPTIONS); } /** @@ -218,9 +217,9 @@ export default class Obfuscator { */ static generateCode( ast: T, - options?: ObfuscateOptions + options: ObfuscateOptions = DEFAULT_OPTIONS ): string { - const compact = options ? options.compact : true; + const compact = !!options.compact; const { code } = generate(ast, { comments: false, // Remove comments diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts new file mode 100644 index 0000000..a9959a6 --- /dev/null +++ b/src/transforms/minify.ts @@ -0,0 +1,513 @@ +import { NodePath, PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import * as t from "@babel/types"; +import { Order } from "../order"; +import { + ensureComputedExpression, + getParentFunctionOrProgram, +} from "../utils/ast-utils"; +import { Binding, Scope } from "@babel/traverse"; +import { NodeSymbol, UNSAFE } from "../constants"; + +function isUndefined(path) { + if (path.isIdentifier() && path.node.name === "undefined") { + return true; + } + if ( + path.isUnaryExpression() && + path.node.operator === "void" && + path.node.argument.type === "NumericLiteral" && + path.node.argument.value === 0 + ) { + return true; + } + return false; +} + +const identifierMap = new Map t.Expression>(); +identifierMap.set("undefined", () => + t.unaryExpression("void", t.numericLiteral(0)) +); +identifierMap.set("Infinity", () => + t.binaryExpression("/", t.numericLiteral(1), t.numericLiteral(0)) +); + +/** + * Minify removes unnecessary code and shortens the length for file size. + * + * - Dead code elimination + * - Variable grouping + * - Constant folding + * - Shorten literals: True to !0, False to !1, Infinity to 1/0, Undefined to void 0 + * - Remove unused variables, functions + */ +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.Minify); + return { + visitor: { + // var a; var b; -> var a,b; + VariableDeclaration: { + exit(path) { + if (typeof path.key !== "number") return; + const kind = path.node.kind; + + // get declaration after this + const nextDeclaration = path.getSibling(path.key + 1); + if ( + nextDeclaration.isVariableDeclaration({ + kind: kind, + }) + ) { + const declarations = path.get("declarations"); + + // Preserve bindings! + // This is important for dead code elimination + const bindings: { [name: string]: Binding } = Object.create(null); + for (var declaration of declarations) { + for (var idPath of Object.values( + declaration.getBindingIdentifierPaths() + )) { + bindings[idPath.node.name] = idPath.scope.getBinding( + idPath.node.name + ); + } + } + + nextDeclaration.node.declarations.unshift( + ...declarations.map((x) => x.node) + ); + + const newBindingIdentifierPaths = + nextDeclaration.getBindingIdentifierPaths(); + + // path.remove() unfortunately removes the bindings + // We must perverse the entire binding object (referencePaths, constantViolations, etc) + // and re-add them to the new scope + path.remove(); + + // Add bindings back + function addBindingsToScope(scope: Scope) { + for (var name in bindings) { + const binding = bindings[name]; + binding.path = newBindingIdentifierPaths[name]; + scope.bindings[name] = binding; + } + } + + if (kind === "var") { + addBindingsToScope(getParentFunctionOrProgram(path).scope); + } + addBindingsToScope(path.scope); + } + }, + }, + // true -> !0, false -> !1 + BooleanLiteral: { + exit(path) { + if (path.node.value) { + path.replaceWith(t.unaryExpression("!", t.numericLiteral(0))); + } else { + path.replaceWith(t.unaryExpression("!", t.numericLiteral(1))); + } + }, + }, + // !"" -> !1 + UnaryExpression: { + exit(path) { + if (path.node.operator === "!") { + var argument = path.get("argument"); + if (argument.isNumericLiteral()) return; + const value = argument.evaluateTruthy(); + if (value === undefined) return; + + path.replaceWith( + t.unaryExpression("!", t.numericLiteral(value ? 1 : 0)) + ); + } + }, + }, + // {["key"]: 1} -> {key: 1} + // {"key": 1} -> {key: 1} + ObjectProperty: { + exit(path) { + var key = path.get("key"); + if (path.node.computed && key.isStringLiteral()) { + path.node.computed = false; + } + + if ( + !path.node.computed && + key.isStringLiteral() && + t.isValidIdentifier(key.node.value) + ) { + if (identifierMap.has(key.node.value)) { + path.node.computed = true; + key.replaceWith(identifierMap.get(key.node.value)!()); + } else { + key.replaceWith(t.identifier(key.node.value)); + } + } + }, + }, + // (a); -> a; + SequenceExpression: { + exit(path) { + if (path.node.expressions.length === 1) { + path.replaceWith(path.node.expressions[0]); + } + }, + }, + // console; -> (); + ExpressionStatement: { + exit(path) { + if (path.get("expression").isIdentifier()) { + path.remove(); + } + }, + }, + // undefined -> void 0 + // Infinity -> 1/0 + Identifier: { + exit(path) { + if (path.isReferencedIdentifier()) { + if (identifierMap.has(path.node.name)) { + ensureComputedExpression(path); + path.replaceWith(identifierMap.get(path.node.name)!()); + } + } + }, + }, + // true ? a : b -> a + ConditionalExpression: { + exit(path) { + const testValue = path.get("test").evaluateTruthy(); + if (testValue === undefined) return; + + path.replaceWith( + testValue ? path.node.consequent : path.node.alternate + ); + }, + }, + // Remove unused functions + FunctionDeclaration: { + exit(path) { + const id = path.get("id"); + if (id.isIdentifier()) { + const binding = path.scope.getBinding(id.node.name); + if ( + binding && + binding.constantViolations.length === 0 && + binding.referencePaths.length === 0 + ) { + path.remove(); + } + } + }, + }, + // var x=undefined -> var x + // Remove unused variables + // Simple destructuring + VariableDeclarator: { + exit(path) { + if (isUndefined(path.get("init"))) { + path.node.init = null; + } + + const id = path.get("id"); + const init = path.get("init"); + + // Simple array/object destructuring + if (id.isArrayPattern() && init.isArrayExpression()) { + const elements = id.get("elements"); + const initElements = init.get("elements"); + + if (elements.length === 1 && initElements.length === 1) { + id.replaceWith(elements[0]); + init.replaceWith(initElements[0]); + } + } + + if (id.isObjectPattern() && init.isObjectExpression()) { + const properties = id.get("properties"); + const initProperties = init.get("properties"); + + if (properties.length === 1 && initProperties.length === 1) { + const firstProperty = properties[0]; + const firstInitProperty = initProperties[0]; + + if ( + firstProperty.isObjectProperty() && + firstInitProperty.isObjectProperty() + ) { + const firstKey = firstProperty.get("key"); + const firstInitKey = firstInitProperty.get("key"); + if ( + firstKey.isIdentifier() && + firstInitKey.isIdentifier() && + firstKey.node.name === firstInitKey.node.name + ) { + id.replaceWith(firstProperty.node.value); + init.replaceWith(firstInitProperty.node.value); + } + } + } + } + + // Remove unused variables + // Can only remove if it's pure + if (id.isIdentifier()) { + // Do not remove variables in unsafe functions + const fn = getParentFunctionOrProgram(path); + if ((fn as NodeSymbol)[UNSAFE]) return; + + const binding = path.scope.getBinding(id.node.name); + + if ( + binding && + binding.constantViolations.length === 0 && + binding.referencePaths.length === 0 + ) { + if (!init.node || init.isPure()) { + path.remove(); + } else if ( + path.parentPath.isVariableDeclaration() && + path.parentPath.node.declarations.length === 1 + ) { + path.parentPath.replaceWith(t.expressionStatement(init.node)); + } + } + } + }, + }, + // return undefined->return + ReturnStatement: { + exit(path) { + if (isUndefined(path.get("argument"))) { + path.node.argument = null; + } + }, + }, + // while(true) {a();} -> while(true) a(); + // for(;;) {a();} -> for(;;) a(); + "While|For": { + exit(_path) { + var path = _path as NodePath; + var body = path.get("body"); + + if (body.isBlock() && body.node.body.length === 1) { + body.replaceWith(body.node.body[0]); + } + }, + }, + // if(a) a(); -> a && a(); + // if(a) { return b; } -> if(a) return b; + // if(a) { a(); } else { b(); } -> a ? a() : b(); + // if(a) { return b; } else { return c; } -> return a ? b : c; + IfStatement: { + exit(path) { + // BlockStatement to single statement + const consequent = path.get("consequent"); + const alternate = path.get("alternate"); + + const isMoveable = (node: t.Statement) => { + if (t.isDeclaration(node)) return false; + + return true; + }; + + if ( + !alternate.node && + consequent.isBlock() && + consequent.node.body.length === 1 && + isMoveable(consequent.node.body[0]) + ) { + consequent.replaceWith(consequent.node.body[0]); + } + + if ( + alternate.node && + alternate.isBlock() && + alternate.node.body.length === 1 && + isMoveable(alternate.node.body[0]) + ) { + alternate.replaceWith(alternate.node.body[0]); + } + + function getResult(path: NodePath): { + returnPath: NodePath | null; + expressions: t.Expression[]; + } { + if (!path.node) return null; + + if (path.isReturnStatement()) { + return { returnPath: path, expressions: [] }; + } + if (path.isExpressionStatement()) { + return { + returnPath: null, + expressions: [path.get("expression").node], + }; + } + + if (path.isBlockStatement()) { + var expressions = []; + for (var statement of path.get("body")) { + if (statement.isReturnStatement()) { + return { returnPath: statement, expressions: expressions }; + } else if (statement.isExpressionStatement()) { + expressions.push(statement.get("expression").node); + } else { + return null; + } + } + + return { returnPath: null, expressions: expressions }; + } + + return null; + } + + var consequentReturn = getResult(consequent); + var alternateReturn = getResult(alternate); + + if (consequentReturn && alternateReturn) { + if (consequentReturn.returnPath && alternateReturn.returnPath) { + function createReturnArgument( + resultInfo: ReturnType + ) { + return t.sequenceExpression([ + ...resultInfo.expressions, + resultInfo.returnPath.node.argument || + t.identifier("undefined"), + ]); + } + + path.replaceWith( + t.returnStatement( + t.conditionalExpression( + path.node.test, + createReturnArgument(consequentReturn), + createReturnArgument(alternateReturn) + ) + ) + ); + } else if ( + !consequentReturn.returnPath && + !alternateReturn.returnPath + ) { + path.replaceWith( + t.conditionalExpression( + path.node.test, + t.sequenceExpression(consequentReturn.expressions), + t.sequenceExpression(alternateReturn.expressions) + ) + ); + } + } + }, + }, + // Remove unreachable code + // Code after a return/throw/break/continue is unreachable + // Remove implied returns + // Remove code after if all branches are unreachable + "Block|SwitchCase": { + exit(path) { + var statementList = path.isBlock() + ? (path.get("body") as NodePath[]) + : (path.get("consequent") as NodePath[]); + + var impliedReturn: NodePath; + + function isUnreachable( + statementList: NodePath[], + topLevel = false + ) { + var unreachableState = false; + + for (var statement of statementList) { + if (unreachableState) { + statement.remove(); + continue; + } + + if (statement.isIfStatement()) { + const consequent = statement.get("consequent"); + const alternate = statement.get("alternate"); + + if ( + [consequent, alternate].every( + (x) => + x.node && + x.isBlockStatement() && + isUnreachable(x.get("body")) + ) + ) { + unreachableState = true; + if (!topLevel) { + return true; + } else { + continue; + } + } + } + + if (statement.isSwitchStatement()) { + // Can only remove switch statements if all cases are unreachable + // And all paths are exhausted + const cases = statement.get("cases"); + const hasDefaultCase = cases.some((x) => !x.node.test); + if ( + hasDefaultCase && + cases.every((x) => isUnreachable(x.get("consequent"))) + ) { + unreachableState = true; + if (!topLevel) { + return true; + } else { + continue; + } + } + } + + if ( + statement.isReturnStatement() || + statement.isThrowStatement() || + statement.isBreakStatement() || + statement.isContinueStatement() + ) { + unreachableState = true; + if (!topLevel) { + return true; + } + } + + if (topLevel) { + if ( + statement == statementList.at(-1) && + statement.isReturnStatement() && + !statement.node.argument + ) { + impliedReturn = statement; + } + } + } + return false; + } + + isUnreachable(statementList, true); + + if (impliedReturn) { + var functionParent = path.getFunctionParent(); + if ( + functionParent && + t.isBlockStatement(functionParent.node.body) && + functionParent.node.body === path.node + ) { + impliedReturn.remove(); + } + } + }, + }, + }, + }; +}; diff --git a/test/transforms/minify.test.ts b/test/transforms/minify.test.ts index bb07330..ff1ba85 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -1,17 +1,24 @@ import JsConfuser from "../../src/index"; -it("should group variable declarations together", async () => { +test("Variant #1: Group variable declarations together", async () => { var code = ` var a = 0; var b = 1; + TEST_OUTPUT = a + b; `; var output = await JsConfuser(code, { target: "browser", minify: true }); expect(output).toContain("var a=0,b=1"); + + var TEST_OUTPUT; + + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(1); }); -it("should remove block statements when not necessary", async () => { +test("Variant #2: Remove block statements when not necessary", async () => { var code = ` while(condition){ doStuff(); @@ -24,33 +31,45 @@ it("should remove block statements when not necessary", async () => { expect(output).toContain("doStuff()"); }); -it("should shorten guaranteed returns", async () => { +test("Variant #3: Shorten guaranteed returns", async () => { var code = ` - function TEST_FUNCTION(){ + function TEST_FUNCTION(condition){ if ( condition ) { return 1; } else { return 0; } } + + TEST_OUTPUT = TEST_FUNCTION(true); `; var output = await JsConfuser(code, { target: "browser", minify: true }); expect(output).not.toContain("if"); expect(output).toContain("?"); + + var TEST_OUTPUT; + + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(1); }); -it("should shorten guaranteed assignment expressions", async () => { +test("Variant #4: Shorten guaranteed assignment expressions", async () => { var code = ` - function TEST_FUNCTION(){ + function TEST_FUNCTION(condition){ var value; if ( condition ) { value = 1; } else { value = 0; } + + TEST_OUTPUT = value; } + + TEST_FUNCTION(true); `; var output = await JsConfuser(code, { target: "browser", minify: true }); @@ -58,49 +77,15 @@ it("should shorten guaranteed assignment expressions", async () => { expect(output).not.toContain("if"); expect(output).toContain("value="); expect(output).toContain("?"); -}); - -it("should convert eligible functions to arrow functions", async () => { - var code = ` - function FN(){ - return 1; - } - input( FN() ) - `; - - var output = await JsConfuser(code, { target: "browser", minify: true }); - - expect(output).toContain("=>"); - - var value = "never_called", - input = (x) => (value = x); - - eval(output); - - expect(value).toStrictEqual(1); -}); - -it("should not convert lower functions to arrow functions", async () => { - var code = ` - input( FN() ) - function FN(){ - return 1; - } - `; - - var output = await JsConfuser(code, { target: "browser", minify: true }); - - expect(output).not.toContain("=>"); - var value = "never_called", - input = (x) => (value = x); + var TEST_OUTPUT; eval(output); - expect(value).toStrictEqual(1); + expect(TEST_OUTPUT).toStrictEqual(1); }); -it("should work when shortening nested if-statements", async () => { +test("Variant #5: Work when shortening nested if-statements", async () => { var code = ` var a = false; var b = true; @@ -127,46 +112,64 @@ it("should work when shortening nested if-statements", async () => { test("Variant #8: Shorten simple array destructuring", async () => { // Valid - var output = await JsConfuser(`var [x] = [1]`, { + var output = await JsConfuser(`var [x] = [1]; TEST_OUTPUT = x;`, { target: "node", minify: true, }); expect(output).toContain("var x=1"); + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(1); + // Invalid var output2 = await JsConfuser(`var [x, y] = [1]`, { target: "node", minify: true, }); - expect(output2).toContain("var [x,y]"); + expect(output2).toContain("var[x,y]"); }); test("Variant #9: Shorten simple object destructuring", async () => { // Valid - var output = await JsConfuser(`var {x} = {x: 1}`, { + var output = await JsConfuser(`var {x} = {x: 1}; TEST_OUTPUT = x;`, { target: "node", minify: true, }); expect(output).toContain("var x=1"); + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(1); + // Valid - var output2 = await JsConfuser(`var {['x']: y} = {x: 1}`, { - target: "node", - minify: true, - }); + var output2 = await JsConfuser( + `var {['x']: y} = {x: 1}; TEST_OUTPUT_2 = y;`, + { + target: "node", + minify: true, + } + ); expect(output2).toContain("var y=1"); + var TEST_OUTPUT_2; + eval(output2); + + expect(TEST_OUTPUT_2).toStrictEqual(1); + // Invalid var output3 = await JsConfuser(`var {x,y} = {x:1}`, { target: "node", minify: true, }); - expect(output3).toContain("var {x:x,y:y}"); + expect(output3).toContain("var{x,y}="); // Invalid var output4 = await JsConfuser(`var {y} = {x:1}`, { @@ -174,25 +177,35 @@ test("Variant #9: Shorten simple object destructuring", async () => { minify: true, }); - expect(output4).toContain("var {y:y}"); + expect(output4).toContain("var{y}="); }); test("Variant #10: Shorten booleans", async () => { // Valid - var output = await JsConfuser(`var x = true;`, { + var output = await JsConfuser(`var x = true; TEST_OUTPUT = x;`, { target: "node", minify: true, }); expect(output).toContain("var x=!0"); + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(true); + // Valid - var output2 = await JsConfuser(`var x = false`, { + var output2 = await JsConfuser(`var x = false; TEST_OUTPUT_2 = x;`, { target: "node", minify: true, }); expect(output2).toContain("var x=!1"); + + var TEST_OUTPUT_2; + eval(output2); + + expect(TEST_OUTPUT_2).toStrictEqual(false); }); test("Variant #11: Shorten 'undefined' to 'void 0'", async () => { @@ -205,7 +218,7 @@ test("Variant #11: Shorten 'undefined' to 'void 0'", async () => { expect(output).toContain("x=void 0"); // Valid - var output2 = await JsConfuser(`var x = {undefined: 1}`, { + var output2 = await JsConfuser(`var x = {undefined: 1}; TEST_OUTPUT = x`, { target: "node", minify: true, }); @@ -225,15 +238,18 @@ test("Variant #11: Shorten 'undefined' to 'void 0'", async () => { test("Variant #11: Shorten 'Infinity' to 1/0", async () => { // Valid - var output = await JsConfuser(`var x = Infinity;`, { + var output = await JsConfuser(`var x = Infinity; TEST_OUTPUT = x;`, { target: "node", minify: true, }); expect(output).toContain("var x=1/0"); + var TEST_OUTPUT; + eval(output); + // Valid - var output2 = await JsConfuser(`var x = {Infinity: 1}`, { + var output2 = await JsConfuser(`var x = {Infinity: 1}; TEST_OUTPUT = x;`, { target: "node", minify: true, }); @@ -241,38 +257,53 @@ test("Variant #11: Shorten 'Infinity' to 1/0", async () => { expect(output2).toContain("var x={[1/0]:1}"); }); -test("Variant #12: Shorten '!false' to 'true'", async () => { +test("Variant #12: Shorten '!false' to '!0'", async () => { // Valid - var output = await JsConfuser(`var x = !false;`, { + var output = await JsConfuser(`var x = !false; TEST_OUTPUT = x;`, { target: "node", minify: true, }); - expect(output).toContain("var x=true"); + expect(output).toContain("var x=!0"); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(true); }); test("Variant #13: Shorten 'false ? a : b' to 'b'", async () => { // Valid - var output = await JsConfuser(`var x = false ? 10 : 15;`, { + var output = await JsConfuser(`var x = false ? 10 : 15; TEST_OUTPUT = x;`, { target: "node", minify: true, }); expect(output).toContain("var x=15"); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(15); }); test("Variant #14: Shorten 'var x = undefined' to 'var x'", async () => { // Valid - var output = await JsConfuser(`var x = undefined`, { + var output = await JsConfuser(`var x = undefined; TEST_OUTPUT = x;`, { target: "node", minify: true, }); expect(output).toContain("var x"); expect(output).not.toContain("var x="); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(undefined); }); -test("Variant #15: Removing implied 'return'", async () => { +test("Variant #15: Remove implied 'return'", async () => { // Valid var output = await JsConfuser( ` @@ -468,12 +499,12 @@ test("Variant #24: Variable grouping in switch case", async () => { ` switch(true){ case true: - var myVar1; - var myVar2; + var myVar1 = ""; + var myVar2 = ""; var myVar3 = "Correct Value"; - var myVar4; + var myVar4 = ""; - TEST_OUTPUT = myVar3; + TEST_OUTPUT = myVar1 + myVar2 + myVar3 + myVar4; break; } `, @@ -481,7 +512,7 @@ test("Variant #24: Variable grouping in switch case", async () => { ); // Ensure the variable declarations were grouped - expect(output).toContain("var myVar1,myVar2,myVar3"); + expect(output).toContain('var myVar1="",myVar2="",myVar3='); var TEST_OUTPUT; eval(output); @@ -507,27 +538,6 @@ test("Variant #25: Don't break redefined function declaration", async () => { expect(TEST_OUTPUT).toStrictEqual(3); }); -test("Variant #26: Don't break nested redefined function declaration", async () => { - var output = await JsConfuser( - ` - var a = 0; - if(true){ - function a(){ - TEST_OUTPUT = 1; - } - } - - a(); - `, - { target: "node", minify: true } - ); - - var TEST_OUTPUT; - eval(output); - - expect(TEST_OUTPUT).toStrictEqual(1); -}); - // https://github.com/MichaelXF/js-confuser/issues/91 test("Variant #27: Preserve function.length property", async () => { var output = await JsConfuser( @@ -573,3 +583,108 @@ test("Variant #28: Don't break destructuring assignment", async () => { expect(TEST_OUTPUT).toStrictEqual(6); }); + +test("Variant #28: Remove unused variables", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var x = "Incorrect Value"; + var y = "Correct Value"; + TEST_OUTPUT = y; + `, + { target: "node", minify: true } + ); + + expect(code).not.toContain("x"); + expect(code).not.toContain("Incorrect Value"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #29: Remove unused functions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function unusedFunction(){ + return "Incorrect Value" + } + + function usedFunction(){ + return "Correct Value" + } + + TEST_OUTPUT = usedFunction(); + `, + { target: "node", minify: true } + ); + + expect(code).not.toContain("unusedFunction"); + expect(code).not.toContain("Incorrect Value"); + expect(code).toContain("usedFunction"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #30: Remove unreachable code after branches", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function ifStatementBranch(condition){ + if( !condition ) { + return "Incorrect Value"; + return "Should be removed"; + } + + if( condition ) { + return "Correct Value"; + } else { + return "Incorrect Value"; + } + + "Should be removed"; + return "Should be removed"; + } + + function switchStatementBranch(condition){ + switch(condition){ + case "FakeValue1": + return "Correct Value"; + case "FakeValue2": + return "Incorrect Value"; + } + + switch(condition){ + case true: + return "Correct Value"; + case false: + if( condition ) { + return "Incorrect Value"; + } else { + return "Incorrect Value"; + } + + return "Should be removed"; + default: + return "Incorrect Value"; + return "Should be removed"; + } + + "Should be removed"; + return "Should be removed"; + } + + TEST_OUTPUT = [ifStatementBranch(true), switchStatementBranch(true)]; + `, + { target: "node", minify: true } + ); + + expect(code).not.toContain("Should be removed"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(["Correct Value", "Correct Value"]); +}); From 6d0e5b1e67c67d68f0e5245dc0bcd15a248dd342 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 5 Sep 2024 17:50:04 -0400 Subject: [PATCH 025/103] Improve API Interface **Breaking change** --- src/index.ts | 8 +- test/code/Cash.test.ts | 4 +- test/code/Dynamic.test.ts | 6 +- test/code/ES6.test.ts | 4 +- test/code/NewFeatures.test.ts | 2 +- test/code/StrictMode.test.js | 6 +- test/index.test.ts | 18 +- test/options.test.ts | 55 +++-- test/templates/template.test.ts | 23 ++ .../controlFlowFlattening.test.ts | 79 ++++--- test/transforms/deadCode.test.ts | 4 +- .../duplicateLiteralsRemoval.test.ts | 18 +- .../extraction/objectExtraction.test.ts | 32 +-- test/transforms/flatten.test.ts | 24 +- .../identifier/movedDeclarations.test.ts | 8 +- test/transforms/lock/lock.test.ts | 25 ++ test/transforms/minify.test.ts | 219 ++++++++++++------ test/transforms/opaquePredicates.test.ts | 12 +- test/transforms/renameLabels.test.ts | 30 +++ test/transforms/rgf.test.ts | 6 +- .../string/stringCompression.test.ts | 10 +- test/transforms/string/stringEncoding.test.ts | 16 +- test/transforms/variableMasking.test.ts | 38 +-- 23 files changed, 414 insertions(+), 233 deletions(-) diff --git a/src/index.ts b/src/index.ts index c070a20..b0b7bed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -81,14 +81,10 @@ export async function obfuscateWithProfiler( }; } -var oldJsConfuser = async (sourceCode: string, options: ObfuscateOptions) => { - return (await obfuscate(sourceCode, options)).code; -}; - -const JsConfuser = Object.assign(oldJsConfuser, { +const JsConfuser = { obfuscate, obfuscateAST, obfuscateWithProfiler, -}); +}; export default JsConfuser; diff --git a/test/code/Cash.test.ts b/test/code/Cash.test.ts index 1c24955..3c02041 100644 --- a/test/code/Cash.test.ts +++ b/test/code/Cash.test.ts @@ -5,7 +5,7 @@ import JsConfuser from "../../src/index"; var CASH_JS = readFileSync(join(__dirname, "./Cash.src.js"), "utf-8"); test("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { - var output = await JsConfuser(CASH_JS, { + var { code: output } = await JsConfuser.obfuscate(CASH_JS, { target: "node", preset: "high", }); @@ -79,7 +79,7 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta global[key] = window[key]; } - var output = await JsConfuser(CASH_JS, { + var { code: output } = await JsConfuser.obfuscate(CASH_JS, { target: "node", preset: "high", rgf: true, diff --git a/test/code/Dynamic.test.ts b/test/code/Dynamic.test.ts index 59a743d..1d59ed6 100644 --- a/test/code/Dynamic.test.ts +++ b/test/code/Dynamic.test.ts @@ -7,7 +7,7 @@ var SOURCE_JS = readFileSync(join(__dirname, "./Dynamic.src.js"), "utf-8"); test.concurrent("Variant #1: Dynamic.src.js on High Preset", async () => { // `input` is an embedded variable, therefore globalConcealing must be turned off - var output = await JsConfuser(SOURCE_JS, { + var { output: code } = await JsConfuser.obfuscate(SOURCE_JS, { target: "browser", preset: "high", globalConcealing: false, @@ -30,11 +30,11 @@ test.concurrent("Variant #2: Dynamic.src.js on 2x High Preset", async () => { globalConcealing: false, }; - var output = await JsConfuser(SOURCE_JS, options); + var { code: output } = await JsConfuser.obfuscate(SOURCE_JS, options); // writeFileSync("./dev.error.1.js", output, "utf-8"); - var doublyObfuscated = await JsConfuser(output, options); + var { code: doublyObfuscated } = await JsConfuser.obfuscate(output, options); // writeFileSync("./dev.error.2.js", doublyObfuscated, "utf-8"); diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index 3b92f6e..678c441 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -5,7 +5,7 @@ import JsConfuser from "../../src/index"; var ES6_JS = readFileSync(join(__dirname, "./ES6.src.js"), "utf-8"); test.concurrent("Variant #1: ES6 code on High Preset", async () => { - var output = await JsConfuser(ES6_JS, { + var { code: output } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", }); @@ -23,7 +23,7 @@ test.concurrent("Variant #1: ES6 code on High Preset", async () => { test.concurrent( "Variant #2: ES6 code on High Preset + RGF + Self Defending", async () => { - var output = await JsConfuser(ES6_JS, { + var { code: output } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", rgf: true, diff --git a/test/code/NewFeatures.test.ts b/test/code/NewFeatures.test.ts index b537027..eab418b 100644 --- a/test/code/NewFeatures.test.ts +++ b/test/code/NewFeatures.test.ts @@ -6,7 +6,7 @@ test("Variant #1: Support BigInt Literals (1n)", async () => { TEST_OUTPUT = 1n; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", renameVariables: true, }); diff --git a/test/code/StrictMode.test.js b/test/code/StrictMode.test.js index 3b6a3b3..401c738 100644 --- a/test/code/StrictMode.test.js +++ b/test/code/StrictMode.test.js @@ -8,7 +8,7 @@ var StrictMode_JS = readFileSync( ); test("Variant #1: StrictMode on High Preset", async () => { - var output = await JsConfuser(StrictMode_JS, { + var { code: output } = await JsConfuser.obfuscate(StrictMode_JS, { target: "node", preset: "high", }); @@ -19,14 +19,14 @@ test("Variant #1: StrictMode on High Preset", async () => { }); test("Variant #2: StrictMode on 2x High Preset", async () => { - var output = await JsConfuser(StrictMode_JS, { + var { code: output } = await JsConfuser.obfuscate(StrictMode_JS, { target: "node", preset: "high", }); //writeFileSync("./dev.output1.js", output); - var output2 = await JsConfuser(output, { + var { code: output2 } = await JsConfuser.obfuscate(output, { target: "node", preset: "high", }); diff --git a/test/index.test.ts b/test/index.test.ts index 162ffff..e8bb1ee 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,18 +1,20 @@ import JsConfuser from "../src/index"; import { ProfilerLog } from "../src/obfuscationResult"; +import * as t from "@babel/types"; describe("obfuscate", () => { test("Variant #1: Should be a function", async () => { expect(typeof JsConfuser.obfuscate).toBe("function"); }); - test("Variant #2: Return be an awaited string", async () => { + test("Variant #2: Return an awaited object with 'code' property", async () => { var output = await JsConfuser.obfuscate("5+5", { target: "browser", - opaquePredicates: true, + compact: true, }); - expect(typeof output).toBe("string"); + expect(typeof output).toBe("object"); + expect(typeof output.code).toBe("string"); }); test("Variant #3: Error when options are empty", async () => { @@ -48,15 +50,7 @@ describe("obfuscate", () => { describe("obfuscateAST", () => { test("Variant #1: Mutate AST", async () => { - var AST = { - type: "Program", - body: [ - { - type: "ExpressionStatement", - expression: { type: "Literal", value: true }, - }, - ], - }; + var AST = t.file(t.program([t.expressionStatement(t.numericLiteral(5))])); var before = JSON.stringify(AST); JsConfuser.obfuscateAST(AST as any, { diff --git a/test/options.test.ts b/test/options.test.ts index b14efab..86e100d 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -2,7 +2,7 @@ import JsConfuser from "../src/index"; describe("options", () => { test("Variant #1: Accept percentages", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { target: "node", renameGlobals: true, renameVariables: true, @@ -13,7 +13,7 @@ describe("options", () => { }); test("Variant #2: Accept probability arrays", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { target: "node", renameVariables: true, renameGlobals: true, @@ -24,7 +24,7 @@ describe("options", () => { }); test("Variant #3: Accept probability maps", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { target: "node", renameVariables: true, renameGlobals: true, @@ -41,27 +41,42 @@ describe("options", () => { }); test("Variant #4: Work with compact set to false", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { - target: "node", - renameGlobals: true, - renameVariables: true, - compact: false, - }); + var { code: output } = await JsConfuser.obfuscate( + ` + var a; + var b; + var c; + `, + { + target: "node", + compact: false, + } + ); - expect(output).not.toContain("TEST_VARIABLE"); + expect(output).toContain("\n"); + expect(output).toContain("var a;\nvar b;\nvar c;"); }); test("Variant #5: Work with compact set to true", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { - target: "node", - compact: true, - }); + var { code: output } = await JsConfuser.obfuscate( + ` + var a; + var b; + var c; + `, + { + target: "node", + compact: true, + } + ); - expect(output).not.toContain("TEST_VARIABLE"); + expect(output).not.toContain("\n"); + expect(output).not.toContain("\t"); + expect(output).toContain("var a;var b;var c;"); }); test("Variant #6: Work with debugComments enabled", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { target: "node", renameGlobals: true, renameVariables: true, @@ -74,14 +89,14 @@ describe("options", () => { test("Variant #7: Error on invalid lock option", async () => { expect( - JsConfuser(`var TEST_VARIABLE;`, { + JsConfuser.obfuscate(`var TEST_VARIABLE;`, { target: "node", lock: "invalid", } as any) ).rejects.toThrow(); expect( - JsConfuser(`var TEST_VARIABLE;`, { + JsConfuser.obfuscate(`var TEST_VARIABLE;`, { target: "node", lock: { invalidProperty: true, @@ -159,7 +174,7 @@ describe("options", () => { describe("options.preserveFunctionLength", () => { test("Variant #1: Enabled by default", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(a, b, c, d = "") { // Function.length = 3 @@ -179,7 +194,7 @@ describe("options.preserveFunctionLength", () => { }); test("Variant #2: Disabled", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(a, b, c, d = "") { // Function.length = 3 diff --git a/test/templates/template.test.ts b/test/templates/template.test.ts index ff9a455..456edde 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -222,4 +222,27 @@ describe("Template", () => { expect(output).toContain('return window["atob"](str)'); }); + + test("Variant #10: Error when single() encounters multiple statements", async () => { + var ListTemplate = new Template(` + var a; + var b; + var c; + `); + + expect(() => { + ListTemplate.single(); + }).toThrow(); + }); + + test("Variant #11: Handle empty statements when using single()", async () => { + var ValidTemplate = new Template(` + ; + var a; + ; + `); + + var statement = ValidTemplate.single(); + expect(statement.type).toStrictEqual("VariableDeclaration"); + }); }); diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index c917b73..3d53c73 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -18,7 +18,7 @@ test("Variant #1: Obfuscate code and still execute in correct order", async () = TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, }); @@ -44,7 +44,7 @@ test("Variant #2: Properly handle for-loop", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, }); @@ -72,7 +72,7 @@ test("Variant #3: Properly handle while-loop", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, }); @@ -102,7 +102,7 @@ test("Variant #4: Properly handle break statements", async () => { TEST_OUTPUT = TEST_ARRAY; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, }); @@ -136,7 +136,7 @@ test("Variant #5: Properly handle 'let' variables", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); @@ -161,7 +161,7 @@ test("Variant #6: Properly handle 'let' in for-loops", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); @@ -189,7 +189,7 @@ test("Variant #7: Allow option to be set a percentage threshold", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: 0.5, }); @@ -238,7 +238,7 @@ test("Variant #8: Work when obfuscated multiple times", async () => { TEST_OUTPUT = array; `; // [1,2,3,4,5,6,7,8,9,10] - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); @@ -246,7 +246,7 @@ test("Variant #8: Work when obfuscated multiple times", async () => { // Ensure Control Flow Flattening applied expect(output).toContain("while"); - var doublyObfuscated = await JsConfuser(output, { + var { code: doublyObfuscated } = await JsConfuser.obfuscate(output, { target: "node", controlFlowFlattening: true, }); @@ -259,7 +259,7 @@ test("Variant #8: Work when obfuscated multiple times", async () => { }); test("Variant #9: Don't entangle floats or NaN", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -293,7 +293,7 @@ test("Variant #9: Don't entangle floats or NaN", async () => { }); test("Variant #10: Correctly entangle property keys", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -333,7 +333,7 @@ test("Variant #10: Correctly entangle property keys", async () => { }); test("Variant #11: Flatten nested if statements", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -379,7 +379,7 @@ test("Variant #11: Flatten nested if statements", async () => { }); test("Variant #12: Properly handle nested for loops", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -413,7 +413,7 @@ test("Variant #12: Properly handle nested for loops", async () => { }); test("Variant #13: Properly handle nested while-loops", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -453,7 +453,7 @@ test("Variant #13: Properly handle nested while-loops", async () => { }); test("Variant #14: Properly handle nested switch statements", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -501,7 +501,7 @@ test("Variant #14: Properly handle nested switch statements", async () => { }); test("Variant #16: Flatten with nested break and continue statements", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -546,7 +546,7 @@ test("Variant #16: Flatten with nested break and continue statements", async () }); test("Variant #17: Flatten with infinite for loop and break", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; var i = 1; @@ -578,7 +578,7 @@ test("Variant #17: Flatten with infinite for loop and break", async () => { }); test("Variant #20: Work with redefined functions", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var counter = 0; function increment(){ @@ -625,7 +625,7 @@ test("Variant #20: Work with redefined functions", async () => { // https://github.com/MichaelXF/js-confuser/issues/70 test("Variant #21: Don't move Import Declarations", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` import {createHash} from "crypto"; var inputString = "Hash this string"; @@ -661,7 +661,7 @@ test("Variant #21: Don't move Import Declarations", async () => { // https://github.com/MichaelXF/js-confuser/issues/81 test("Variant #22: Don't break typeof expression", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OUTPUT = false; if(typeof nonExistentVariable === "undefined") { @@ -682,7 +682,7 @@ test("Variant #22: Don't break typeof expression", async () => { }); test("Variant #23: Don't break Super calls", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` class MyClass1 { constructor(val){ @@ -765,7 +765,7 @@ test("Variant #24: Nested function-calls with labeled breaks/continues", async ( TEST_OUTPUT = x;`; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, renameVariables: true, @@ -791,7 +791,7 @@ test("Variant #25: Don't break call expressions to bound functions", async () => TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); @@ -806,7 +806,7 @@ test("Variant #25: Don't break call expressions to bound functions", async () => }); test("Variant #26: Add opaque predicates and still work", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OUTPUT = []; if(true) TEST_OUTPUT.push(1); @@ -832,7 +832,7 @@ test("Variant #26: Add opaque predicates and still work", async () => { }); test("Variant #27: Work on async/generator functions", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` "use strict"; async function myAsyncFunction(){ @@ -882,7 +882,7 @@ test("Variant #28: Don't break update expressions", async () => { TEST_OUTPUT = counter; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); @@ -948,7 +948,7 @@ test("Variant #29: Nested labeled break and continue statements with RGF enabled TEST_OUTPUT = labeledBreaksAndContinues(); `; // This complex code produces the value of 15 - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, rgf: true, @@ -964,7 +964,7 @@ test("Variant #29: Nested labeled break and continue statements with RGF enabled }); test("Variant #30: Properly handle switch statements", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` switch("DON'T CHANGE ME"){} // Empty switch for testing @@ -1062,7 +1062,7 @@ test("Variant #30: Properly handle switch statements", async () => { }); test("Variant #31: Don't break nested function calls", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var i; var counter = 0; @@ -1089,7 +1089,7 @@ test("Variant #31: Don't break nested function calls", async () => { }); test("Variant #32: Don't break same name function calls", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var counter = 0; @@ -1126,7 +1126,7 @@ test("Variant #32: Don't break same name function calls", async () => { }); test("Variant #33: Don't break same name function declarations that are not ran", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var counter = 0; @@ -1163,7 +1163,7 @@ test("Variant #33: Don't break same name function declarations that are not ran" expect(TEST_OUTPUT).toStrictEqual(11); }); -test("Variant #34: Flatten If, For, While, Do-while, and Switch statements multiple times", async () => { +test("Variant #34: Flatten If Statements multiple times", async () => { var code = ` var counter = -1; @@ -1215,14 +1215,17 @@ test("Variant #34: Flatten If, For, While, Do-while, and Switch statements multi TEST_OUTPUT = counter; `; - var firstObfuscation = await JsConfuser(code, { - target: "node", - controlFlowFlattening: true, - }); - var secondObfuscation = await JsConfuser(firstObfuscation, { + var { code: firstObfuscation } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); + var { code: secondObfuscation } = await JsConfuser.obfuscate( + firstObfuscation, + { + target: "node", + controlFlowFlattening: true, + } + ); var TEST_OUTPUT; eval(secondObfuscation); @@ -1252,7 +1255,7 @@ test("Variant #35: Redefined function declaration + variable declaration", async } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, }); diff --git a/test/transforms/deadCode.test.ts b/test/transforms/deadCode.test.ts index 5f74450..30e8a86 100644 --- a/test/transforms/deadCode.test.ts +++ b/test/transforms/deadCode.test.ts @@ -22,7 +22,7 @@ test("Variant #1: Execute properly", async () => { myFunction(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", deadCode: true, }); @@ -63,7 +63,7 @@ test("Variant #2: Preserve 'use strict' directive", async () => { myFunction(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", deadCode: true, }); diff --git a/test/transforms/extraction/duplicateLiteralsRemoval.test.ts b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts index 403d061..ca56beb 100644 --- a/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +++ b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts @@ -6,7 +6,7 @@ test("Variant #1: Remove duplicate literals", async () => { var TEST_ARRAY = [5,5]; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -21,7 +21,7 @@ test("Variant #2: Remove duplicate literals and execute correctly", async () => TEST_ARRAY = [5,5]; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -42,7 +42,7 @@ test("Variant #3: Remove 'undefined' and 'null' values", async () => { TEST_ARRAY = [undefined,undefined,null,null]; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -66,7 +66,7 @@ test("Variant #4: Do not remove empty strings", async () => { TEST_ARRAY = ['','','','']; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -86,7 +86,7 @@ test("Variant #5: Work with NaN values", async () => { TEST_ARRAY = [NaN]; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -111,7 +111,7 @@ test("Variant #6: Work on property keys", async () => { TEST_VAR = myObject.myKey; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -137,7 +137,7 @@ test("Variant #7: Work on class keys", async () => { TEST_VAR = myObject.myMethod(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -167,7 +167,7 @@ test("Variant #8: Do not encode constructor key", async () => { new MyClass(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", duplicateLiteralsRemoval: true, }); @@ -180,7 +180,7 @@ test("Variant #8: Do not encode constructor key", async () => { // https://github.com/MichaelXF/js-confuser/issues/105 test("Variant #9: Undefined as variable name", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var undefined = 0; var undefined = 1; diff --git a/test/transforms/extraction/objectExtraction.test.ts b/test/transforms/extraction/objectExtraction.test.ts index d2ee440..7f63a37 100644 --- a/test/transforms/extraction/objectExtraction.test.ts +++ b/test/transforms/extraction/objectExtraction.test.ts @@ -18,7 +18,7 @@ test("Variant #1: Extract properties", async () => { input(TEST_OBJECT.TEST_1, TEST_OBJECT['TEST_2'], check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -52,7 +52,7 @@ test("Variant #2: Extract function properties correctly", async () => { input(TEST_OBJECT.isBoolean(true), TEST_OBJECT.isString(false), check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -88,7 +88,7 @@ test("Variant #3: Not extract properties on with dynamically added keys", async input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -123,7 +123,7 @@ test("Variant #4: Not extract properties on with dynamically added keys even whe input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -154,7 +154,7 @@ test("Variant #5: Not extract properties on objects with computed properties", a input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -185,7 +185,7 @@ test("Variant #6: Not extract properties on objects with computed properties (st input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -217,7 +217,7 @@ test("Variant #7: Not extract properties on objects when the object is reference input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -248,7 +248,7 @@ test("Variant #8: Not extract properties on objects when the variable gets redef input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -280,7 +280,7 @@ test("Variant #9: Not extract properties on objects when the variable gets reass input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -312,7 +312,7 @@ test("Variant #10: Not extract properties on objects with methods referencing 't input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -343,7 +343,7 @@ test("Variant #11: Not extract properties on objects when properties are dynamic input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -375,7 +375,7 @@ test("Variant #12: Not extract properties on objects with computed accessors", a input(check); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: true, }); @@ -405,7 +405,7 @@ test("Variant #13: Properly use custom callback to exclude certain names from be `; var seen = new Set(); - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", objectExtraction: (name) => { seen.add(name); @@ -437,7 +437,7 @@ test("Variant #14: Not apply to objects with non-init properties (method, set, g }; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", objectExtraction: true, }); @@ -448,7 +448,7 @@ test("Variant #14: Not apply to objects with non-init properties (method, set, g // https://github.com/MichaelXF/js-confuser/issues/78 test("Variant #15: Handle objects with spread elements", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var x = { firstName: "John", lastName: "Doe" } var y = { ...x }; @@ -469,7 +469,7 @@ test("Variant #15: Handle objects with spread elements", async () => { // https://github.com/MichaelXF/js-confuser/issues/106 test("Variant #16: Handle const declarations", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` const obj = {prop: 0}; obj.prop = 1; diff --git a/test/transforms/flatten.test.ts b/test/transforms/flatten.test.ts index 2635932..625a530 100644 --- a/test/transforms/flatten.test.ts +++ b/test/transforms/flatten.test.ts @@ -362,7 +362,7 @@ test("Variant #12: Work with RGF enabled", async () => { }); test("Variant #13: Work with assignment expression in the return statement", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var outside; @@ -388,7 +388,7 @@ test("Variant #13: Work with assignment expression in the return statement", asy }); test("Variant #14: Work with 'use strict' directive", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ "use strict"; @@ -412,7 +412,7 @@ test("Variant #14: Work with 'use strict' directive", async () => { // https://github.com/MichaelXF/js-confuser/issues/89 test("Variant #15: Work with functions with invalid identifier names", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` // Input var object = { @@ -433,7 +433,7 @@ test("Variant #15: Work with functions with invalid identifier names", async () }); test("Variant #16: Multiple test", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` "use strict"; @@ -499,7 +499,7 @@ test("Variant #16: Multiple test", async () => { }); test("Variant #17: Don't apply to generator functions", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function* myGeneratorFunction(){ yield "Correct Value"; @@ -519,7 +519,7 @@ test("Variant #17: Don't apply to generator functions", async () => { }); test("Variant #18: Redefined variable in nested scope + Rename Variables", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` (function (){ var outsideVar = "Incorrect Value 1"; @@ -562,7 +562,7 @@ test("Variant #18: Redefined variable in nested scope + Rename Variables", async }); test("Variant #19: Nested function declaration", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ TEST_OUTPUT = nestedFunctionDeclaration(); @@ -589,7 +589,7 @@ test("Variant #19: Nested function declaration", async () => { }); test("Variant #20: Don't apply to functions that use 'this' 'arguments' or 'eval'", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ usesEval(); @@ -625,7 +625,7 @@ test("Variant #20: Don't apply to functions that use 'this' 'arguments' or 'eval }); test("Variant #21: Preserve function.length property", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function oneParameter(a){}; var twoParameters = function({a},{b,c},...d){}; @@ -650,7 +650,7 @@ test("Variant #21: Preserve function.length property", async () => { }); test("Variant #22: Modify object properties", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var myObject = {}; @@ -678,7 +678,7 @@ test("Variant #22: Modify object properties", async () => { }); test("Variant #23: Reference original function name", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` (function (){ function myFunction(){ @@ -701,7 +701,7 @@ test("Variant #23: Reference original function name", async () => { }); test("Variant #24: Typeof expression", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ TEST_OUTPUT = typeof nonExistentVariable === "undefined"; diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 9925df2..4f6fa0b 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -172,7 +172,7 @@ test("Variant #8: Work with 'use strict'", async () => { TEST_OUTPUT = myFunction(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -197,14 +197,14 @@ test("Variant #9: Defined variable without an initializer + CFF + Duplicate Lite TEST_OUTPUT = x + y; `; - var output1 = await JsConfuser(code, { + var { code: output1 } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, controlFlowFlattening: true, duplicateLiteralsRemoval: true, }); - var output2 = await JsConfuser(output1, { + var { code: output2 } = await JsConfuser.obfuscate(output1, { target: "node", movedDeclarations: true, controlFlowFlattening: true, @@ -263,7 +263,7 @@ test("Variant #10: Move parameters to predictable function", async () => { TEST_OUTPUT = testFunction${predictableFunctionTag}_FN() `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); diff --git a/test/transforms/lock/lock.test.ts b/test/transforms/lock/lock.test.ts index e69de29..c1a77db 100644 --- a/test/transforms/lock/lock.test.ts +++ b/test/transforms/lock/lock.test.ts @@ -0,0 +1,25 @@ +import JsConfuser from "../../../src"; +import Obfuscator from "../../../src/obfuscator"; +import { Order } from "../../../src/order"; + +test("Variant #1: Error if lock options is not an object", async () => { + expect(async () => { + var invalidLockOptions = true as any; + + await JsConfuser.obfuscate('console.log("Hello World")', { + target: "node", + lock: invalidLockOptions, + }); + }).rejects.toThrow(); +}); + +test("Variant #2: Lock transform should be skipped when no options are provided", async () => { + var obfuscator = new Obfuscator({ + target: "node", + lock: {}, + }); + + var plugin = obfuscator.getPlugin(Order.Lock); + + expect(plugin).toBeUndefined(); +}); diff --git a/test/transforms/minify.test.ts b/test/transforms/minify.test.ts index ff1ba85..05146bf 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -7,7 +7,10 @@ test("Variant #1: Group variable declarations together", async () => { TEST_OUTPUT = a + b; `; - var output = await JsConfuser(code, { target: "browser", minify: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + minify: true, + }); expect(output).toContain("var a=0,b=1"); @@ -25,7 +28,10 @@ test("Variant #2: Remove block statements when not necessary", async () => { } `; - var output = await JsConfuser(code, { target: "browser", minify: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + minify: true, + }); expect(output).not.toContain("{"); expect(output).toContain("doStuff()"); @@ -44,7 +50,10 @@ test("Variant #3: Shorten guaranteed returns", async () => { TEST_OUTPUT = TEST_FUNCTION(true); `; - var output = await JsConfuser(code, { target: "browser", minify: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + minify: true, + }); expect(output).not.toContain("if"); expect(output).toContain("?"); @@ -72,7 +81,10 @@ test("Variant #4: Shorten guaranteed assignment expressions", async () => { TEST_FUNCTION(true); `; - var output = await JsConfuser(code, { target: "browser", minify: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + minify: true, + }); expect(output).not.toContain("if"); expect(output).toContain("value="); @@ -98,7 +110,10 @@ test("Variant #5: Work when shortening nested if-statements", async () => { } `; - var output = await JsConfuser(code, { target: "browser", minify: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + minify: true, + }); expect(output).not.toContain("=>"); @@ -112,10 +127,13 @@ test("Variant #5: Work when shortening nested if-statements", async () => { test("Variant #8: Shorten simple array destructuring", async () => { // Valid - var output = await JsConfuser(`var [x] = [1]; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var [x] = [1]; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x=1"); @@ -125,7 +143,7 @@ test("Variant #8: Shorten simple array destructuring", async () => { expect(TEST_OUTPUT).toStrictEqual(1); // Invalid - var output2 = await JsConfuser(`var [x, y] = [1]`, { + var { code: output2 } = await JsConfuser.obfuscate(`var [x, y] = [1]`, { target: "node", minify: true, }); @@ -135,10 +153,13 @@ test("Variant #8: Shorten simple array destructuring", async () => { test("Variant #9: Shorten simple object destructuring", async () => { // Valid - var output = await JsConfuser(`var {x} = {x: 1}; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var {x} = {x: 1}; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x=1"); @@ -148,7 +169,7 @@ test("Variant #9: Shorten simple object destructuring", async () => { expect(TEST_OUTPUT).toStrictEqual(1); // Valid - var output2 = await JsConfuser( + var { code: output2 } = await JsConfuser.obfuscate( `var {['x']: y} = {x: 1}; TEST_OUTPUT_2 = y;`, { target: "node", @@ -164,7 +185,7 @@ test("Variant #9: Shorten simple object destructuring", async () => { expect(TEST_OUTPUT_2).toStrictEqual(1); // Invalid - var output3 = await JsConfuser(`var {x,y} = {x:1}`, { + var { code: output3 } = await JsConfuser.obfuscate(`var {x,y} = {x:1}`, { target: "node", minify: true, }); @@ -172,7 +193,7 @@ test("Variant #9: Shorten simple object destructuring", async () => { expect(output3).toContain("var{x,y}="); // Invalid - var output4 = await JsConfuser(`var {y} = {x:1}`, { + var { code: output4 } = await JsConfuser.obfuscate(`var {y} = {x:1}`, { target: "node", minify: true, }); @@ -182,10 +203,13 @@ test("Variant #9: Shorten simple object destructuring", async () => { test("Variant #10: Shorten booleans", async () => { // Valid - var output = await JsConfuser(`var x = true; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var x = true; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x=!0"); @@ -195,10 +219,13 @@ test("Variant #10: Shorten booleans", async () => { expect(TEST_OUTPUT).toStrictEqual(true); // Valid - var output2 = await JsConfuser(`var x = false; TEST_OUTPUT_2 = x;`, { - target: "node", - minify: true, - }); + var { code: output2 } = await JsConfuser.obfuscate( + `var x = false; TEST_OUTPUT_2 = x;`, + { + target: "node", + minify: true, + } + ); expect(output2).toContain("var x=!1"); @@ -210,7 +237,7 @@ test("Variant #10: Shorten booleans", async () => { test("Variant #11: Shorten 'undefined' to 'void 0'", async () => { // Valid - var output = await JsConfuser(`x = undefined;`, { + var { code: output } = await JsConfuser.obfuscate(`x = undefined;`, { target: "node", minify: true, }); @@ -218,14 +245,17 @@ test("Variant #11: Shorten 'undefined' to 'void 0'", async () => { expect(output).toContain("x=void 0"); // Valid - var output2 = await JsConfuser(`var x = {undefined: 1}; TEST_OUTPUT = x`, { - target: "node", - minify: true, - }); + var { code: output2 } = await JsConfuser.obfuscate( + `var x = {undefined: 1}; TEST_OUTPUT = x`, + { + target: "node", + minify: true, + } + ); expect(output2).toContain("var x={[void 0]:1}"); - var output3 = await JsConfuser( + var { code: output3 } = await JsConfuser.obfuscate( `try { var undefined; (undefined) = true } catch(e) {}`, { target: "node", @@ -238,10 +268,13 @@ test("Variant #11: Shorten 'undefined' to 'void 0'", async () => { test("Variant #11: Shorten 'Infinity' to 1/0", async () => { // Valid - var output = await JsConfuser(`var x = Infinity; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var x = Infinity; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x=1/0"); @@ -249,20 +282,26 @@ test("Variant #11: Shorten 'Infinity' to 1/0", async () => { eval(output); // Valid - var output2 = await JsConfuser(`var x = {Infinity: 1}; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output2 } = await JsConfuser.obfuscate( + `var x = {Infinity: 1}; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output2).toContain("var x={[1/0]:1}"); }); test("Variant #12: Shorten '!false' to '!0'", async () => { // Valid - var output = await JsConfuser(`var x = !false; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var x = !false; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x=!0"); @@ -274,10 +313,13 @@ test("Variant #12: Shorten '!false' to '!0'", async () => { test("Variant #13: Shorten 'false ? a : b' to 'b'", async () => { // Valid - var output = await JsConfuser(`var x = false ? 10 : 15; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var x = false ? 10 : 15; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x=15"); @@ -289,10 +331,13 @@ test("Variant #13: Shorten 'false ? a : b' to 'b'", async () => { test("Variant #14: Shorten 'var x = undefined' to 'var x'", async () => { // Valid - var output = await JsConfuser(`var x = undefined; TEST_OUTPUT = x;`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `var x = undefined; TEST_OUTPUT = x;`, + { + target: "node", + minify: true, + } + ); expect(output).toContain("var x"); expect(output).not.toContain("var x="); @@ -305,7 +350,7 @@ test("Variant #14: Shorten 'var x = undefined' to 'var x'", async () => { test("Variant #15: Remove implied 'return'", async () => { // Valid - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function MyFunction(){ var output = "Hello World"; @@ -323,7 +368,7 @@ test("Variant #15: Remove implied 'return'", async () => { // Invalid // https://github.com/MichaelXF/js-confuser/issues/34 - var output2 = await JsConfuser( + var { code: output2 } = await JsConfuser.obfuscate( ` function greet(){ if(true){ @@ -345,7 +390,7 @@ test("Variant #15: Remove implied 'return'", async () => { // https://github.com/MichaelXF/js-confuser/issues/43 test("Variant #16: Handle deconstructuring in for loop", async () => { // Valid - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` for(const [a] of [[1]]) { input(a); @@ -365,7 +410,7 @@ test("Variant #16: Handle deconstructuring in for loop", async () => { }); test("Variant #17: Remove unreachable code following a return statement", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ return; @@ -379,7 +424,7 @@ test("Variant #17: Remove unreachable code following a return statement", async }); test("Variant #18: Remove unreachable code following a continue or break statement", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` for(var i =0; i < 10; i++){ continue; @@ -398,7 +443,7 @@ test("Variant #18: Remove unreachable code following a continue or break stateme }); test("Variant #19: Remove unreachable code following a throw statement", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` throw new Error("No more code to run"); unreachableStmt; @@ -411,7 +456,7 @@ test("Variant #19: Remove unreachable code following a throw statement", async ( // https://github.com/MichaelXF/js-confuser/issues/76 test("Variant #20: Properly handle objects with `, ^, [, ] as keys", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` TEST_OBJECT = { "\`": true, @@ -439,7 +484,7 @@ test("Variant #20: Properly handle objects with `, ^, [, ] as keys", async () => // https://github.com/MichaelXF/js-confuser/issues/75 test("Variant #21: Properly handle Object constructor (Function Declaration)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function MyClass() {}; @@ -457,7 +502,7 @@ test("Variant #21: Properly handle Object constructor (Function Declaration)", a }); test("Variant #22: Properly handle Object constructor (Function Expression)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var MyClass = function() {}; @@ -475,7 +520,7 @@ test("Variant #22: Properly handle Object constructor (Function Expression)", as }); test("Variant #23: Shorten property names and method names", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var myObject = { "myKey": "Correct Value" }; var myClass = class { ["myMethod"](){ return "Correct Value" } } @@ -495,7 +540,7 @@ test("Variant #23: Shorten property names and method names", async () => { }); test("Variant #24: Variable grouping in switch case", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` switch(true){ case true: @@ -521,7 +566,7 @@ test("Variant #24: Variable grouping in switch case", async () => { }); test("Variant #25: Don't break redefined function declaration", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function a(){ TEST_OUTPUT = 1 }; function a(){ TEST_OUTPUT = 2 }; @@ -540,7 +585,7 @@ test("Variant #25: Don't break redefined function declaration", async () => { // https://github.com/MichaelXF/js-confuser/issues/91 test("Variant #27: Preserve function.length property", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function oneParameter(a){}; var twoParameters = function({a},{b,c},...d){}; @@ -559,7 +604,7 @@ test("Variant #27: Preserve function.length property", async () => { // https://github.com/MichaelXF/js-confuser/issues/125 test("Variant #28: Don't break destructuring assignment", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` let objectSlice = []; objectSlice.push({ @@ -688,3 +733,47 @@ test("Variant #30: Remove unreachable code after branches", async () => { expect(TEST_OUTPUT).toStrictEqual(["Correct Value", "Correct Value"]); }); + +test("Variant #31: Dead code elimination", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function deadCodeElimination(){ + TEST_OUTPUT = []; + if( true ) { + var one = 1; + TEST_OUTPUT.push(one) + } else { + TEST_OUTPUT.push("Should be removed") + } + + if( false ) { + TEST_OUTPUT.push("Should be removed") + } else { + var two = 2; + TEST_OUTPUT.push(two) + } + + if( true ) { + } else { + TEST_OUTPUT.push("Should be removed") + } + + if(false) { + } else { + var three = 3; + TEST_OUTPUT.push(three) + } + } + + deadCodeElimination(); + `, + { target: "node", minify: true } + ); + + expect(code).not.toContain("Should be removed"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual([1, 2, 3]); +}); diff --git a/test/transforms/opaquePredicates.test.ts b/test/transforms/opaquePredicates.test.ts index fa99eac..a6aba26 100644 --- a/test/transforms/opaquePredicates.test.ts +++ b/test/transforms/opaquePredicates.test.ts @@ -9,7 +9,7 @@ it("should append logical expressions", async () => { } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", opaquePredicates: true, }); @@ -18,8 +18,8 @@ it("should append logical expressions", async () => { }); // https://github.com/MichaelXF/js-confuser/issues/45 -it("should work on default Switch cases", async ()=>{ - var code = ` +it("should work on default Switch cases", async () => { + var code = ` switch (0) { default: @@ -27,17 +27,17 @@ it("should work on default Switch cases", async ()=>{ } `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", opaquePredicates: true, }); var value; - function input(valueIn){ + function input(valueIn) { value = valueIn; } eval(output); expect(value).toStrictEqual(true); -}) +}); diff --git a/test/transforms/renameLabels.test.ts b/test/transforms/renameLabels.test.ts index 0734466..6c69326 100644 --- a/test/transforms/renameLabels.test.ts +++ b/test/transforms/renameLabels.test.ts @@ -239,3 +239,33 @@ test("Variant #9: Custom implementation for Rename Labels", async () => { expect(labelsCollected).toStrictEqual(["RENAME_ME", "KEEP_ME"]); }); + +test("Variant #10: Allow duplicate labels in the program", async () => { + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = 0; + LABEL: { + TEST_OUTPUT++; + break LABEL; + TEST_OUTPUT = "Incorrect Value"; + } + + LABEL: { + TEST_OUTPUT++; + break LABEL; + TEST_OUTPUT = "Incorrect Value"; + } + `, + { + target: "node", + renameLabels: true, + } + ); + + expect(code).not.toContain("LABEL"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(2); +}); diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index 61e3bd7..15a192f 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -273,7 +273,7 @@ test("Variant #9: Work with Flatten on any function", async () => { test("Variant #10: Configurable by custom function option", async () => { var functionNames: string[] = []; - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` "use strict"; @@ -331,7 +331,7 @@ test("Variant #10: Configurable by custom function option", async () => { }); test("Variant #11: Function containing function should both be changed", async function () { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function FunctionA(){ function FunctionB(){ @@ -359,7 +359,7 @@ test("Variant #11: Function containing function should both be changed", async f }); test("Variant #12: Preserve Function.length", async function () { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(a,b,c,d = ""){ // Function.length = 3 diff --git a/test/transforms/string/stringCompression.test.ts b/test/transforms/string/stringCompression.test.ts index 23c1d4c..34c685b 100644 --- a/test/transforms/string/stringCompression.test.ts +++ b/test/transforms/string/stringCompression.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../../src/index"; it("should work", async () => { - var output = await JsConfuser(`input("Hello World")`, { + var { code: output } = await JsConfuser.obfuscate(`input("Hello World")`, { target: "node", stringCompression: true, }); @@ -23,7 +23,7 @@ it("should work on property keys", async () => { TEST_VAR = myObject.myKey; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringCompression: true, }); @@ -47,7 +47,7 @@ it("should work on class keys", async () => { TEST_VAR = myObject.myMethod(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringCompression: true, }); @@ -69,7 +69,7 @@ it("should not encode constructor key", async () => { new MyClass(); `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringCompression: true, }); @@ -89,7 +89,7 @@ it("should be configurable by custom function option", async () => { var stringsFound: string[] = []; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringCompression: (strValue) => { stringsFound.push(strValue); diff --git a/test/transforms/string/stringEncoding.test.ts b/test/transforms/string/stringEncoding.test.ts index 0385da3..dc27c0e 100644 --- a/test/transforms/string/stringEncoding.test.ts +++ b/test/transforms/string/stringEncoding.test.ts @@ -3,7 +3,7 @@ import JsConfuser from "../../../src/index"; test("Variant #1: Encode strings", async () => { var code = `var TEST_STRING = "encoded."`; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringEncoding: true, }); @@ -20,7 +20,7 @@ test("Variant #1: Encode strings", async () => { test("Variant #2: Encode strings AND still have same result", async () => { var code = `input("encoded.")`; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", stringEncoding: true, }); @@ -46,7 +46,10 @@ test("Variant #2: Encode strings AND still have same result", async () => { test("Variant #3: Encode object property keys", async () => { var code = `TEST_OUTPUT = { myProperty1: true, "myProperty2": true }`; - var output = await JsConfuser(code, { target: "node", stringEncoding: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + stringEncoding: true, + }); // Ensure the strings got changed expect(output).not.toContain("myProperty1"); @@ -62,7 +65,10 @@ test("Variant #3: Encode object property keys", async () => { test("Variant #4: Encode object destructuring property keys", async () => { var code = `({ ["myDestructedKey"]: TEST_OUTPUT } = { myDestructedKey: true })`; - var output = await JsConfuser(code, { target: "node", stringEncoding: true }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + stringEncoding: true, + }); // Ensure the string(s) got changed expect(output).not.toContain("myDestructedKey"); @@ -86,7 +92,7 @@ test("Variant #5: Preserve 'use strict' directive", async () => { var anotherVar = "use strict"; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", preset: "high", }); diff --git a/test/transforms/variableMasking.test.ts b/test/transforms/variableMasking.test.ts index 1cc6412..707e802 100644 --- a/test/transforms/variableMasking.test.ts +++ b/test/transforms/variableMasking.test.ts @@ -1,7 +1,7 @@ import JsConfuser from "../../src/index"; test("Variant #1: Replace all variables with array indexes (single variable)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ var TEST_VARIABLE = 1; @@ -27,7 +27,7 @@ test("Variant #1: Replace all variables with array indexes (single variable)", a }); test("Variant #2: Replace all variables with array indexes (multiple variables)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ var TEST_VARIABLE_1 = 5; @@ -54,7 +54,7 @@ test("Variant #2: Replace all variables with array indexes (multiple variables)" }); test("Variant #3: Replace all variables with array indexes (uninitialized variable are made undefined)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ var TEST_VARIABLE; @@ -80,7 +80,7 @@ test("Variant #3: Replace all variables with array indexes (uninitialized variab }); test("Variant #4: Replace all variables with array indexes (parameters)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(TEST_VARIABLE_1, TEST_VARIABLE_2){ @@ -106,7 +106,7 @@ test("Variant #4: Replace all variables with array indexes (parameters)", async }); test("Variant #5: Replace all variables with array indexes (nested function)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -136,7 +136,7 @@ test("Variant #5: Replace all variables with array indexes (nested function)", a }); test("Variant #6: Replace all variables with array indexes (nested class)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -174,7 +174,7 @@ test("Variant #6: Replace all variables with array indexes (nested class)", asyn }); test("Variant #7: Replace variables defined within the function, and not run if any changes can be made", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var TEST_VARIABLE = 0; function TEST_FUNCTION(){ @@ -193,7 +193,7 @@ test("Variant #7: Replace variables defined within the function, and not run if }); test("Variant #8: Work even when differing amount of arguments passed in", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function add3(var_x, var_y, var_z){ return var_x + var_y; @@ -217,7 +217,7 @@ test("Variant #8: Work even when differing amount of arguments passed in", async }); test("Variant #9: Replace all variables with array indexes (middle indexes use array[index] syntax)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ var TEST_VARIABLE_1 = 1; @@ -250,7 +250,7 @@ test("Variant #9: Replace all variables with array indexes (middle indexes use a }); test("Variant #10: Guess execution order correctly (CallExpression, arguments run before callee)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(a,b){ var TEST_NESTED_FUNCTION = (x,y)=>{ @@ -278,7 +278,7 @@ test("Variant #10: Guess execution order correctly (CallExpression, arguments ru }); test("Variant #11: Guess execution order correctly (AssignmentExpression, right side executes first)", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(a,b){ @@ -306,7 +306,7 @@ test("Variant #11: Guess execution order correctly (AssignmentExpression, right }); test("Variant #12: Should not entangle floats or NaN", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -336,7 +336,7 @@ test("Variant #12: Should not entangle floats or NaN", async () => { }); test("Variant #13: Correctly entangle property keys", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -383,7 +383,7 @@ test("Variant #13: Correctly entangle property keys", async () => { }); test("Variant #14: Correctly entangle method definition keys", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function TEST_FUNCTION(){ @@ -451,7 +451,7 @@ test("Variant #14: Correctly entangle method definition keys", async () => { }); test("Variant #15: Function with 'use strict' directive", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function useStrictFunction(){ 'use strict'; @@ -480,7 +480,7 @@ test("Variant #15: Function with 'use strict' directive", async () => { }); test("Variant #16: Function with 'this'", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` 'use strict'; function stackFunction(){ @@ -525,7 +525,7 @@ test("Variant #16: Function with 'this'", async () => { // https://github.com/MichaelXF/js-confuser/issues/88 test("Variant #17: Syncing arguments parameter", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var TEST_OUTPUT; @@ -550,7 +550,7 @@ test("Variant #17: Syncing arguments parameter", async () => { }); test("Variant #18: Preserve function.length property", async () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function oneParameter(a){ var _ = 1; @@ -559,7 +559,7 @@ test("Variant #18: Preserve function.length property", async () => { var _ = 1; }; var myObject = { - threeParameters(a,b,c){ + threeParameters: function(a,b,c){ var _ = 1; } } From 7ce788c4536242e9f4fd2637aab98b8205e005eb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 5 Sep 2024 17:50:32 -0400 Subject: [PATCH 026/103] Add `Function Outlining` --- CHANGELOG.md | 2 + src/obfuscator.ts | 12 +- src/options.ts | 2 + src/order.ts | 2 + src/presets.ts | 1 + src/transforms/functionOutlining.ts | 220 ++++++++++++++++++++++ src/validateOptions.ts | 3 + test/transforms/functionOutlining.test.ts | 83 ++++++++ 8 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 src/transforms/functionOutlining.ts create mode 100644 test/transforms/functionOutlining.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c8271..bb80f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - - Custom String Encoding / Custom Lock Code +- - Added `Function Outlining` Learn more here + - - Added `Rename Labels` Learn more here - - RGF no longers uses `new Function` instead uses `eval` diff --git a/src/obfuscator.ts b/src/obfuscator.ts index bc083d9..1b58733 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -36,6 +36,7 @@ import { NameGen } from "./utils/NameGen"; import { assertScopeIntegrity } from "./utils/scope-utils"; import opaquePredicates from "./transforms/opaquePredicates"; import minify from "./transforms/minify"; +import functionOutlining from "./transforms/functionOutlining"; export const DEFAULT_OPTIONS: ObfuscateOptions = { target: "node", @@ -74,6 +75,12 @@ export default class Obfuscator { this.options = applyDefaultsToOptions({ ...userOptions }); this.nameGen = new NameGen(this.options.identifierGenerator); + const shouldAddLockTransform = + this.options.lock && + (Object.keys(this.options.lock).filter((key) => key !== "customLocks") + .length > 0 || + this.options.lock.customLocks.length > 0); + const allPlugins: PluginFunction[] = []; const push = (probabilityMap, ...pluginFns) => { @@ -86,7 +93,7 @@ export default class Obfuscator { push(true, preparation); push(this.options.objectExtraction, objectExtraction); push(this.options.flatten, flatten); - push(this.options.lock, lock); + push(shouldAddLockTransform, lock); push(this.options.rgf, rgf); push(this.options.dispatcher, dispatcher); push(this.options.deadCode, deadCode); @@ -94,6 +101,7 @@ export default class Obfuscator { push(this.options.calculator, calculator); push(this.options.globalConcealing, globalConcealing); push(this.options.opaquePredicates, opaquePredicates); + push(this.options.functionOutlining, functionOutlining); push(this.options.stringSplitting, stringSplitting); push(this.options.stringConcealing, stringConcealing); push(this.options.stringCompression, stringCompression); @@ -164,7 +172,7 @@ export default class Obfuscator { } babel.traverse(ast, plugin.visitor as babel.Visitor); - assertScopeIntegrity(pluginInstance.name, ast); + // assertScopeIntegrity(pluginInstance.name, ast); if (options?.profiler) { options?.profiler({ diff --git a/src/options.ts b/src/options.ts index b4efc97..3de9340 100644 --- a/src/options.ts +++ b/src/options.ts @@ -427,6 +427,8 @@ export interface ObfuscateOptions { customLocks?: CustomLock[]; }; + functionOutlining?: ProbabilityMap; + customStringEncodings?: ( | CustomStringEncoding | ((encodingImplementations: { diff --git a/src/order.ts b/src/order.ts index 83f5ff4..3c6e528 100644 --- a/src/order.ts +++ b/src/order.ts @@ -24,6 +24,8 @@ export enum Order { OpaquePredicates = 13, + FunctionOutlining = 14, + StringSplitting = 16, StringConcealing = 17, diff --git a/src/presets.ts b/src/presets.ts index 264f124..0d5b3cf 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -50,6 +50,7 @@ const highPreset: ObfuscateOptions = { stringEncoding: true, stringSplitting: 0.75, astScrambler: true, + functionOutlining: false, // Use at own risk rgf: false, diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts new file mode 100644 index 0000000..43627c2 --- /dev/null +++ b/src/transforms/functionOutlining.ts @@ -0,0 +1,220 @@ +import { NodePath, PluginObj } from "@babel/core"; +import { PluginArg } from "./plugin"; +import { Order } from "../order"; +import { ensureComputedExpression, prepend } from "../utils/ast-utils"; +import * as t from "@babel/types"; +import { NameGen } from "../utils/NameGen"; +import { getRandomInteger } from "../utils/random-utils"; +import { Binding } from "@babel/traverse"; + +interface FunctionOutliningInterface { + objectName: string; + add: (node: t.Expression) => t.Expression; +} + +const FUNCTION_OUTLINING = Symbol("functionOutlining"); + +interface NodeFunctionOutlining { + [FUNCTION_OUTLINING]?: FunctionOutliningInterface; +} + +function isSafeForOutlining(path: NodePath): { + isSafe: boolean; + bindings: Binding[]; +} { + if (path.isIdentifier() || path.isLiteral()) + return { isSafe: false, bindings: [] }; + + if ( + path.isReturnStatement() || + path.isContinueStatement() || + path.isBreakStatement() || + path.isThrowStatement() || + path.isYieldExpression() || + path.isAwaitExpression() || + path.isDebuggerStatement() || + path.isImportDeclaration() || + path.isExportDeclaration() + ) { + return { isSafe: false, bindings: [] }; + } + + var isSafe = true; + var bindings: Binding[] = []; + + path.traverse({ + ThisExpression(path) { + isSafe = false; + path.stop(); + }, + Identifier(path) { + if (["arguments", "eval"].includes(path.node.name)) { + isSafe = false; + path.stop(); + } + }, + BindingIdentifier(path) { + var binding = path.scope.getBinding(path.node.name); + if (binding) { + bindings.push(binding); + } + }, + }); + + return { isSafe, bindings }; +} + +export default ({ Plugin }: PluginArg): PluginObj => { + const me = Plugin(Order.FunctionOutlining); + + function addToOutliningObject(path: NodePath, node: t.Expression) { + var block = path.findParent((p) => p.isBlock()); + if (!block) return; + + let fnInterface = (block.node as NodeFunctionOutlining)[FUNCTION_OUTLINING]; + + if (!fnInterface) { + var objectName = me.getPlaceholder(); + + var newPath = prepend( + block, + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(objectName), + t.objectExpression([]) + ), + ]) + )[0] as NodePath; + me.skip(newPath); + + let gen = new NameGen(me.options.identifierGenerator); + + var objectExpression = newPath.node.declarations[0] + .init as t.ObjectExpression; + fnInterface = { + add: (node) => { + const property = gen.generate(); + objectExpression.properties.push( + t.objectProperty(t.identifier(property), node) + ); + me.skip(objectExpression); + + return t.memberExpression( + t.identifier(objectName), + t.stringLiteral(property), + true + ); + }, + objectName, + }; + + (block.node as NodeFunctionOutlining)[FUNCTION_OUTLINING] = fnInterface; + } + + return fnInterface.add(node); + } + + return { + visitor: { + Block: { + exit(path) { + if (path.isProgram()) { + path.scope.crawl(); + } + if (path.find((p) => me.isSkipped(p))) return; + // Extract a random number of statements + + var statements = path.get("body"); + var startIndex = getRandomInteger(0, statements.length); + var endIndex = getRandomInteger(startIndex, statements.length); + + var extractedStatements = statements.slice(startIndex, endIndex); + if (!extractedStatements.length) return; + + var bindings: Binding[] = []; + + for (var statement of extractedStatements) { + var result = isSafeForOutlining(statement); + if (!result.isSafe) { + return; + } + + bindings.push(...result.bindings); + } + + const extractedStatementSet = new Set(extractedStatements); + + for (var binding of bindings) { + for (var referencePath of binding.referencePaths) { + var found = referencePath.find((p) => + extractedStatementSet.has(p) + ); + if (!found) { + return; + } + } + for (var constantViolation of binding.constantViolations) { + var found = constantViolation.find((p) => + extractedStatementSet.has(p) + ); + if (!found) { + return; + } + } + } + + var isFirst = true; + for (var statement of extractedStatements) { + if (isFirst) { + isFirst = false; + statement.replaceWith( + addToOutliningObject( + statement, + t.functionExpression( + null, + [], + t.blockStatement(extractedStatements.map((x) => x.node)) + ) + ) + ); + continue; + } + statement.remove(); + } + }, + }, + Expression: { + exit(path) { + // Skip assignment left + if ( + path.find( + (p) => + p.key === "left" && + p.parentPath?.type === "AssignmentExpression" + ) + ) { + return; + } + + if (path.find((p) => me.isSkipped(p))) return; + if (!isSafeForOutlining(path).isSafe) return; + + var memberExpression = addToOutliningObject( + path, + t.functionExpression( + null, + [], + t.blockStatement([t.returnStatement(t.cloneNode(path.node))]) + ) + ); + + var callExpression = t.callExpression(memberExpression, []); + + ensureComputedExpression(path); + path.replaceWith(callExpression); + me.skip(path); + }, + }, + }, + }; +}; diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 250b5e2..1d8b405 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -39,6 +39,7 @@ const validProperties = new Set([ "astScrambler", "variableConcealing", "customStringEncodings", + "functionOutlining", ]); const validLockProperties = new Set([ @@ -169,6 +170,8 @@ export function applyDefaultsToOptions( } if (options.lock) { + ok(typeof options.lock === "object", "options.lock must be an object"); + if (options.lock.selfDefending) { options.compact = true; // self defending forcibly enables this } diff --git a/test/transforms/functionOutlining.test.ts b/test/transforms/functionOutlining.test.ts new file mode 100644 index 0000000..f53fff7 --- /dev/null +++ b/test/transforms/functionOutlining.test.ts @@ -0,0 +1,83 @@ +import JsConfuser from "../../src/index"; + +test("Variant #1: Outline expressions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var ten = Math.floor(5 * Number(2)) + TEST_OUTPUT = ten; + `, + { + target: "node", + functionOutlining: true, + } + ); + + expect(code).not.toContain("ten=Math.floor"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(10); +}); + +test("Variant #2: Don't outline expressions with 'eval' 'this' or 'arguments'", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function testFunction(){ + var expectedThis = {}; + + function shouldNotOutline(){ + var ten = eval("10"); + if(this === expectedThis){ + return ten + arguments[0]; + } + } + + var result = shouldNotOutline.call(expectedThis, 15); + return result; + } + + TEST_OUTPUT = testFunction(); // 25 + `, + { + target: "node", + functionOutlining: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(25); +}); + +test("Variant #2: Outline expressions in nested functions", async () => { + var { code } = await JsConfuser.obfuscate( + ` +var outsideVar = 10; +function testFunction() { + function innerFunction1(){ + var innerFunction2 = function(){ + var ten = Math.floor(5 * Number(2)); + var five = Math.floor(Number(5)); + return ten + five + outsideVar; + } + return innerFunction2(); + } + return innerFunction1(); +} + +var testResult = testFunction(); +TEST_OUTPUT = testResult; // 25 +`, + { + target: "node", + functionOutlining: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(25); +}); From f2198d9af4b86c4052e7359577ddc908c6e03d14 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 5 Sep 2024 18:04:44 -0400 Subject: [PATCH 027/103] Clean package.json --- package.json | 105 ++++++++++----------------------------------------- 1 file changed, 20 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 127ab21..d170283 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,37 @@ { "name": "js-confuser", "version": "2.0.0-alpha", + "description": "JavaScript Obfuscation Tool.", "main": "index.js", "scripts": { "build": "tsc && babel src --out-dir dist --extensions \".ts,.tsx\"", "dev": "node babel.register.js", "test": "jest --forceExit", - "test:coverage": "jest --coverage", + "test:coverage": "jest --coverage --coverageReporters=html", "prepublishOnly": "npm run build" }, - "keywords": [], + "keywords": [ + "obfuscator", + "obfuscation", + "uglify", + "code protection", + "javascript obfuscator", + "js obfuscator" + ], "author": "MichaelXF", "license": "MIT", "devDependencies": { "@babel/cli": "^7.24.8", "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", + "@babel/generator": "^7.25.6", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.24.7", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", - "@babel/traverse": "^7.25.3", - "@babel/types": "^7.25.2", + "@babel/traverse": "^7.25.6", + "@babel/types": "^7.25.6", "@eslint/js": "^9.9.0", "@types/babel__core": "^7.20.5", "@types/jest": "^29.5.12", @@ -33,85 +41,12 @@ "typescript": "^5.5.4", "typescript-eslint": "^8.1.0" }, - "dependencies": { - "ansi-styles": "^3.2.1", - "anymatch": "^3.1.3", - "babel-plugin-polyfill-corejs2": "^0.4.11", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.2", - "balanced-match": "^1.0.2", - "binary-extensions": "^2.3.0", - "brace-expansion": "^1.1.11", - "braces": "^3.0.3", - "browserslist": "^4.23.3", - "caniuse-lite": "^1.0.30001651", - "chalk": "^2.4.2", - "chokidar": "^3.6.0", - "color-convert": "^1.9.3", - "color-name": "^1.1.3", - "commander": "^6.2.1", - "concat-map": "^0.0.1", - "convert-source-map": "^2.0.0", - "core-js-compat": "^3.38.0", - "debug": "^4.3.6", - "electron-to-chromium": "^1.5.6", - "escalade": "^3.1.2", - "escape-string-regexp": "^1.0.5", - "esutils": "^2.0.3", - "fill-range": "^7.1.1", - "fs-readdir-recursive": "^1.1.0", - "fs.realpath": "^1.0.0", - "fsevents": "^2.3.3", - "function-bind": "^1.1.2", - "gensync": "^1.0.0-beta.2", - "glob": "^7.2.3", - "glob-parent": "^5.1.2", - "has-flag": "^3.0.0", - "hasown": "^2.0.2", - "inflight": "^1.0.6", - "inherits": "^2.0.4", - "is-binary-path": "^2.1.0", - "is-core-module": "^2.15.0", - "is-extglob": "^2.1.1", - "is-glob": "^4.0.3", - "is-number": "^7.0.0", - "js-tokens": "^4.0.0", - "jsesc": "^2.5.2", - "json5": "^2.2.3", - "lodash.debounce": "^4.0.8", - "lru-cache": "^5.1.1", - "make-dir": "^2.1.0", - "minimatch": "^3.1.2", - "ms": "^2.1.2", - "node-releases": "^2.0.18", - "normalize-path": "^3.0.0", - "once": "^1.4.0", - "path-is-absolute": "^1.0.1", - "path-parse": "^1.0.7", - "picocolors": "^1.0.1", - "picomatch": "^2.3.1", - "pify": "^4.0.1", - "readdirp": "^3.6.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.1", - "regenerator-runtime": "^0.14.1", - "regenerator-transform": "^0.15.2", - "regexpu-core": "^5.3.2", - "regjsparser": "^0.9.1", - "resolve": "^1.22.8", - "semver": "^6.3.1", - "slash": "^2.0.0", - "supports-color": "^5.5.0", - "supports-preserve-symlinks-flag": "^1.0.0", - "to-fast-properties": "^2.0.0", - "to-regex-range": "^5.0.1", - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0", - "unicode-property-aliases-ecmascript": "^2.1.0", - "update-browserslist-db": "^1.1.0", - "wrappy": "^1.0.2", - "yallist": "^3.1.1" + "repository": { + "type": "git", + "url": "https://github.com/MichaelXF/js-confuser" }, - "description": "" + "bugs": { + "url": "https://github.com/MichaelXF/js-confuser/issues" + }, + "homepage": "https://js-confuser.com" } From 6729c9878e0186cd671a899aa0be18d55edd754c Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 5 Sep 2024 18:05:02 -0400 Subject: [PATCH 028/103] Scope fixes, improve coverage --- src/constants.ts | 3 + src/templates/integrityTemplate.ts | 2 +- src/templates/template.ts | 11 +-- src/transforms/controlFlowFlattening.ts | 76 ++++++++++++++----- src/transforms/dispatcher.ts | 6 ++ src/transforms/extraction/objectExtraction.ts | 5 ++ src/transforms/flatten.ts | 3 + src/transforms/identifier/globalConcealing.ts | 11 ++- src/transforms/identifier/renameVariables.ts | 5 ++ src/transforms/lock/integrity.ts | 5 ++ src/transforms/minify.ts | 37 ++++++++- src/transforms/rgf.ts | 3 + src/transforms/string/stringEncoding.ts | 2 +- src/transforms/variableMasking.ts | 5 ++ src/utils/ast-utils.ts | 3 + src/utils/scope-utils.ts | 9 ++- 16 files changed, 150 insertions(+), 36 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 2a4a7c7..7e75acc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,6 +24,9 @@ export const PREDICTABLE = Symbol("predictable"); */ export const SKIP = Symbol("skip"); +/** + * Saves the original length of a function. + */ export const FN_LENGTH = Symbol("fnLength"); export interface NodeSymbol { diff --git a/src/templates/integrityTemplate.ts b/src/templates/integrityTemplate.ts index 6588b04..329dd62 100644 --- a/src/templates/integrityTemplate.ts +++ b/src/templates/integrityTemplate.ts @@ -5,7 +5,7 @@ import Template from "./template"; * @param str * @param seed */ -export function HashFunction(str, seed = 0) { +export function HashFunction(str: string, seed: number) { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < str.length; i++) { diff --git a/src/templates/template.ts b/src/templates/template.ts index 418ca01..9e520cd 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -38,19 +38,12 @@ export default class Template { matches.forEach((variable) => { const name = variable.slice(1, -1); - // $ variables are for default variables - if (name.startsWith("$")) { - this.defaultVariables[name] = `td_${ - Object.keys(this.defaultVariables).length - }`; - } else { - this.requiredVariables.add(name); - } + this.requiredVariables.add(name); }); } } - private interpolateTemplate(variables: TemplateVariables = {}) { + private interpolateTemplate(variables: TemplateVariables) { const allVariables = { ...this.defaultVariables, ...variables }; for (const requiredVariable of this.requiredVariables) { diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 9bec12b..ed83230 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -33,6 +33,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { Block: { exit(blockPath) { if (!blockPath.isProgram()) return; + if (blockPath.isProgram()) { + blockPath.scope.crawl(); + } const body = blockPath.node.body; const blockFnParent = getParentFunctionOrProgram(blockPath); @@ -80,6 +83,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const statIntGen = new IntGen(); interface BasicBlockOptions { + parent?: BasicBlock; topLevel: boolean; fnLabel: string; } @@ -90,6 +94,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { class BasicBlock { totalState: number; stateValues: number[]; + block?: t.Block; constructor( public label: string, @@ -144,6 +149,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const endLabel = me.getPlaceholder(); let currentBasicBlock = new BasicBlock(startLabel, { + parent: null, topLevel: true, fnLabel: null, }); @@ -204,6 +210,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { block: t.Block, options: BasicBlockOptions ) { + currentBasicBlock.block = block; + + // Make sure 'topLevel' is disabled when flattening IF-statements + const nestedFlattenIntoBasicBlocks = ( + block: t.Block, + options: BasicBlockOptions + ) => { + var newOptions = { + ...options, + topLevel: false, + }; + return flattenIntoBasicBlocks(block, newOptions); + }; + for (const index in block.body) { const statement = block.body[index]; @@ -246,7 +266,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { var oldBasicBlock = currentBasicBlock; - var newBasicBlockOptions = { topLevel: false, fnLabel }; + var newBasicBlockOptions = { + topLevel: false, + fnLabel, + }; endCurrentBasicBlock( { @@ -285,7 +308,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { options ); - oldBasicBlock.body.unshift( + // Add the function to the start of the 'block' + var topBasicBlock = Array.from(basicBlocks.values()).filter( + (p) => p.block === block + )[0]; + + topBasicBlock.body.unshift( t.expressionStatement( t.assignmentExpression( "=", @@ -348,7 +376,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { options ); - flattenIntoBasicBlocks(consequent, options); + nestedFlattenIntoBasicBlocks(consequent, options); if (alternate) { endCurrentBasicBlock( @@ -359,7 +387,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { options ); - flattenIntoBasicBlocks(alternate, options); + nestedFlattenIntoBasicBlocks(alternate, options); } endCurrentBasicBlock( @@ -409,7 +437,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { jumpToNext: true, nextLabel: endLabel, }, - { topLevel: true, fnLabel: null } + { parent: null, topLevel: true, fnLabel: null } ); const topLevelNames = new Set(); @@ -509,7 +537,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) as NodePath; if (!variableDeclaration) return; - var wrapInAssignmentStatement = true; + var wrapInExpressionStatement = true; var forChild = variableDeclaration.find( (p) => @@ -518,7 +546,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { (p.parentPath?.isFor() && p.parentKey === "left") ); if (forChild) { - wrapInAssignmentStatement = false; + wrapInExpressionStatement = false; } ok(variableDeclaration.node.declarations.length === 1); @@ -527,19 +555,28 @@ export default ({ Plugin }: PluginArg): PluginObj => { variableDeclaration.node.declarations[0].id ); - const assignment = wrapInAssignmentStatement - ? t.expressionStatement( - t.assignmentExpression( - "=", - identifier, - variableDeclaration.node.declarations[0].init || - t.identifier("undefined") - ) - ) - : identifier; + let replacement: t.Node = identifier as t.Expression; + // Only add name=value if the variable is initialized OR not in a for loop + if ( + variableDeclaration.node.declarations[0].init || + !forChild + ) { + replacement = t.assignmentExpression( + "=", + identifier, + variableDeclaration.node.declarations[0].init || + t.identifier("undefined") + ); + } + + // Most times we want to wrap in an expression statement + // var a = 1; -> a = 1; + if (wrapInExpressionStatement) { + replacement = t.expressionStatement(replacement); + } // Replace variable declaration with assignment expression statement - variableDeclaration.replaceWith(assignment); + variableDeclaration.replaceWith(replacement); }, }, @@ -728,6 +765,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]; + // Reset all bindings here + blockPath.scope.bindings = Object.create(null); + // Register new declarations for (var node of blockPath.get("body")) { blockPath.scope.registerDeclaration(node); diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index ab90cea..a89ae28 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -30,6 +30,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { const blockPath = _path as NodePath; + if (blockPath.isProgram()) { + blockPath.scope.crawl(); + } + if ((blockPath.node as NodeSymbol)[UNSAFE]) return; // For testing @@ -208,8 +212,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { ? expressions[0] : t.sequenceExpression(expressions); + if (!parentPath.container) return; parentPath.replaceWith(output); } else { + if (!path.container) return; // Replace non-invocation references with a 'cached' version of the function path.replaceWith(createDispatcherCall(newName, keys.nonCall)); } diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index 9a62ceb..39e98c3 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -14,6 +14,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { + Program: { + enter(path) { + path.scope.crawl(); + }, + }, VariableDeclaration(varDecPath) { if (varDecPath.node.declarations.length !== 1) return; const declaration = varDecPath.get( diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 1813149..c3d5597 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -371,6 +371,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { flattenFunction(path); }, }, + Program(path) { + path.scope.crawl(); + }, }, }; }; diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 7d10e77..e87b684 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -8,6 +8,7 @@ import { computeProbabilityMap } from "../../probability"; import { variableFunctionName } from "../../constants"; import { prepend } from "../../utils/ast-utils"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; +import { getRandomInteger, getRandomString } from "../../utils/random-utils"; const ignoreGlobals = new Set([ "require", @@ -27,6 +28,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Create the getGlobal function using a template function createGlobalConcealingFunction(): t.FunctionDeclaration { + // Create fake global mappings + + var fakeCount = getRandomInteger(20, 40); + for (var i = 0; i < fakeCount; i++) { + var fakeName = getRandomString(getRandomInteger(6, 8)); + globalMapping.set(gen.generate(), fakeName); + } + const createSwitchStatement = () => { const cases = Array.from(globalMapping.keys()).map((originalName) => { var mappedKey = globalMapping.get(originalName); @@ -130,7 +139,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { prepend(programPath, globalConcealingFunction); - const getGlobalVarFnName = me.getPlaceholder(); + const getGlobalVarFnName = me.getPlaceholder() + "_getGlobalVarFn"; // Insert the get global function prepend( diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index e276714..161795b 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -19,6 +19,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { + Program: { + enter(path) { + path.scope.crawl(); + }, + }, CallExpression: { exit(path: NodePath) { if ( diff --git a/src/transforms/lock/integrity.ts b/src/transforms/lock/integrity.ts index 87da5f2..4a3408e 100644 --- a/src/transforms/lock/integrity.ts +++ b/src/transforms/lock/integrity.ts @@ -31,6 +31,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { + Program: { + enter(path) { + path.scope.crawl(); + }, + }, FunctionDeclaration: { exit(funcDecPath) { const integrityInterface = (funcDecPath.node as NodeIntegrity)[ diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index a9959a6..b113d1a 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -7,7 +7,7 @@ import { getParentFunctionOrProgram, } from "../utils/ast-utils"; import { Binding, Scope } from "@babel/traverse"; -import { NodeSymbol, UNSAFE } from "../constants"; +import { NodeSymbol, placeholderVariablePrefix, UNSAFE } from "../constants"; function isUndefined(path) { if (path.isIdentifier() && path.node.name === "undefined") { @@ -45,6 +45,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Minify); return { visitor: { + Program(path) { + path.scope.crawl(); + }, // var a; var b; -> var a,b; VariableDeclaration: { exit(path) { @@ -192,12 +195,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { FunctionDeclaration: { exit(path) { const id = path.get("id"); - if (id.isIdentifier()) { + if ( + id.isIdentifier() && + !id.node.name.startsWith(placeholderVariablePrefix) + ) { const binding = path.scope.getBinding(id.node.name); if ( binding && binding.constantViolations.length === 0 && - binding.referencePaths.length === 0 + binding.referencePaths.length === 0 && + !binding.referenced ) { path.remove(); } @@ -333,6 +340,25 @@ export default ({ Plugin }: PluginArg): PluginObj => { alternate.replaceWith(alternate.node.body[0]); } + const testValue = path.get("test").evaluateTruthy(); + if (testValue === false) { + // if(false){} -> () + if (!alternate.node) { + path.remove(); + return; + + // if(false){a()}else{b()} -> b() + } else { + path.replaceWith(alternate.node); + return; + } + + // if(true){a()} -> {a()} + } else if (testValue === true) { + path.replaceWith(consequent.node); + return; + } + function getResult(path: NodePath): { returnPath: NodePath | null; expressions: t.Expression[]; @@ -411,6 +437,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Remove implied returns // Remove code after if all branches are unreachable "Block|SwitchCase": { + enter(path) { + if (path.isProgram()) { + path.scope.crawl(); + } + }, exit(path) { var statementList = path.isBlock() ? (path.get("body") as NodePath[]) diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 6ce373b..dd66326 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -26,6 +26,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { Program: { + enter(path) { + path.scope.crawl(); + }, exit(path) { if (rgfArrayExpression.elements.length === 0) return; diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 88d9584..3e389ec 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -65,7 +65,7 @@ export default (me: PluginInstance): PluginObj => { : toUnicodeRepresentation )(value); - path.replaceWith(t.identifier(`'${escapedString}'`)); + path.replaceWith(t.identifier(`"${escapedString}"`)); path.skip(); }, }, diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index d4e8ea0..ea209eb 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -208,6 +208,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { transformFunction(path); }, }, + Program: { + enter(path) { + path.scope.crawl(); + }, + }, }, }; }; diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index f862dd9..429d780 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -292,6 +292,9 @@ export function prepend( function registerPaths(paths: NodePath[]) { for (var path of paths) { + if (path.isVariableDeclaration() && path.node.kind === "var") { + getParentFunctionOrProgram(path).scope.registerDeclaration(path); + } path.scope.registerDeclaration(path); } diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts index e7e5622..0f2bffa 100644 --- a/src/utils/scope-utils.ts +++ b/src/utils/scope-utils.ts @@ -46,6 +46,8 @@ function compareScopes(beforeState, afterState, node) { * @param node */ export function assertScopeIntegrity(pluginName: string, node: t.File) { + t.assertFile(node); + const scopeStates = new WeakMap(); const seenNodes = new WeakSet(); @@ -79,9 +81,10 @@ export function assertScopeIntegrity(pluginName: string, node: t.File) { for (var name in path.scope.bindings) { const binding = path.scope.bindings[name]; if (!binding.path || !binding.path.node || binding.path.removed) { - throw new Error( - `${pluginName}: Binding "${name}" was removed from the scope at node: ${path.node.type} ${binding.path}` - ); + const message = `${pluginName}: Binding "${name}" was removed from the scope at node: ${path.node.type} ${binding.path?.type}`; + // console.warn(message); + + throw new Error(message); } } } From 42a453193c3bed65479d508bd904bf14a6d0fbe6 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 5 Sep 2024 18:26:17 -0400 Subject: [PATCH 029/103] Global concealing re-declared global --- src/transforms/flatten.ts | 3 +- src/transforms/identifier/globalConcealing.ts | 9 ++++- .../identifier/globalConcealing.test.ts | 37 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index c3d5597..1751dfa 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -122,7 +122,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (definedLocal === fnPath.scope) break; definedLocal = definedLocal.parent; - if (definedLocal === program.scope) ok(false); + if (definedLocal === program.scope) + ok(functionName + ":" + identifierName); } while (definedLocal); var cursor: Scope = fnPath.scope.parent; diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index e87b684..6b26b82 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -73,6 +73,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { var identifierPath = _path as NodePath; var identifierName = identifierPath.node.name; + const binding = identifierPath.scope.getBinding(identifierName); + if (binding) { + illegalGlobals.add(identifierName); + return; + } + if ( !identifierPath.scope.hasGlobal(identifierName) || identifierPath.scope.hasOwnBinding(identifierName) @@ -86,7 +92,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { if ( assignmentChild && t.isAssignmentExpression(assignmentChild.parent) && - assignmentChild.parent.left === assignmentChild.node + assignmentChild.parent.left === assignmentChild.node && + !t.isMemberExpression(identifierPath.parent) ) { illegalGlobals.add(identifierName); return; diff --git a/test/transforms/identifier/globalConcealing.test.ts b/test/transforms/identifier/globalConcealing.test.ts index 8e6bd31..e93d10f 100644 --- a/test/transforms/identifier/globalConcealing.test.ts +++ b/test/transforms/identifier/globalConcealing.test.ts @@ -199,3 +199,40 @@ test("Variant #9: Don't change arguments", async () => { expect(TEST_OUTPUT).toStrictEqual(30); }); + +test("Variant #10: Properly handle declared global variables", async () => { + var namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function console(){ + VALID_GLOBAL.TEST_PROPERTY = true; + INVALID_GLOBAL = true; + } + + console(); + `, + { + target: "node", + globalConcealing: (globalName) => { + namesCollected.push(globalName); + return true; + }, + } + ); + + expect(namesCollected).toContain("VALID_GLOBAL"); + expect(namesCollected).not.toContain("INVALID_GLOBAL"); + expect(namesCollected).not.toContain("console"); + + var VALID_GLOBAL = { TEST_PROPERTY: false }, + INVALID_GLOBAL; + + // Global Concealing directly accesses globals from the global object + global.VALID_GLOBAL = VALID_GLOBAL; + + eval(code); + + expect(VALID_GLOBAL.TEST_PROPERTY).toStrictEqual(true); + expect(INVALID_GLOBAL).toStrictEqual(true); +}); From 53de4d0919f3913a6cd7952a143713f382c50075 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 6 Sep 2024 21:31:07 -0400 Subject: [PATCH 030/103] Improve function outlining / tests --- src/templates/deadCodeTemplates.ts | 636 ++++++++++++++++++ src/transforms/functionOutlining.ts | 48 +- test/options.test.ts | 54 -- test/semantics/lastExpression.test.ts | 43 ++ test/semantics/preserveFunctionLength.test.ts | 53 ++ test/transforms/functionOutlining.test.ts | 78 +++ 6 files changed, 843 insertions(+), 69 deletions(-) create mode 100644 test/semantics/lastExpression.test.ts create mode 100644 test/semantics/preserveFunctionLength.test.ts diff --git a/src/templates/deadCodeTemplates.ts b/src/templates/deadCodeTemplates.ts index 0c3115e..fd62b90 100644 --- a/src/templates/deadCodeTemplates.ts +++ b/src/templates/deadCodeTemplates.ts @@ -1,6 +1,642 @@ import Template from "./template"; export const deadCodeTemplates = [ + new Template(` + // Modified by bryanchow for namespace control and higher compressibility +// See https://gist.github.com/1649353 for full revision history from original + +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined + * in FIPS 180-2 + * Version 2.2 Copyright Angel Marin, Paul Johnston 2000 - 2009. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + * Also http://anmar.eu.org/projects/jssha2/ + */ + +var sha256 = (function() { + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha256(s) { return rstr2hex(rstr_sha256(str2rstr_utf8(s))); } +function b64_sha256(s) { return rstr2b64(rstr_sha256(str2rstr_utf8(s))); } +function any_sha256(s, e) { return rstr2any(rstr_sha256(str2rstr_utf8(s)), e); } +function hex_hmac_sha256(k, d) + { return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); } +function b64_hmac_sha256(k, d) + { return rstr2b64(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); } +function any_hmac_sha256(k, d, e) + { return rstr2any(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d)), e); } + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha256_vm_test() +{ + return hex_sha256("abc").toLowerCase() == + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; +} + +/* + * Calculate the sha256 of a raw string + */ +function rstr_sha256(s) +{ + return binb2rstr(binb_sha256(rstr2binb(s), s.length * 8)); +} + +/* + * Calculate the HMAC-sha256 of a key and some data (raw strings) + */ +function rstr_hmac_sha256(key, data) +{ + var bkey = rstr2binb(key); + if(bkey.length > 16) bkey = binb_sha256(bkey, key.length * 8); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = binb_sha256(ipad.concat(rstr2binb(data)), 512 + data.length * 8); + return binb2rstr(binb_sha256(opad.concat(hash), 512 + 256)); +} + +/* + * Convert a raw string to a hex string + */ +function rstr2hex(input) +{ + try { hexcase } catch(e) { hexcase=0; } + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var output = ""; + var x; + for(var i = 0; i < input.length; i++) + { + x = input.charCodeAt(i); + output += hex_tab.charAt((x >>> 4) & 0x0F) + + hex_tab.charAt( x & 0x0F); + } + return output; +} + +/* + * Convert a raw string to a base-64 string + */ +function rstr2b64(input) +{ + try { b64pad } catch(e) { b64pad=''; } + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var output = ""; + var len = input.length; + for(var i = 0; i < len; i += 3) + { + var triplet = (input.charCodeAt(i) << 16) + | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) + | (i + 2 < len ? input.charCodeAt(i+2) : 0); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > input.length * 8) output += b64pad; + else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); + } + } + return output; +} + +/* + * Convert a raw string to an arbitrary string encoding + */ +function rstr2any(input, encoding) +{ + var divisor = encoding.length; + var remainders = Array(); + var i, q, x, quotient; + + /* Convert to an array of 16-bit big-endian values, forming the dividend */ + var dividend = Array(Math.ceil(input.length / 2)); + for(i = 0; i < dividend.length; i++) + { + dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); + } + + /* + * Repeatedly perform a long division. The binary array forms the dividend, + * the length of the encoding is the divisor. Once computed, the quotient + * forms the dividend for the next step. We stop when the dividend is zero. + * All remainders are stored for later use. + */ + while(dividend.length > 0) + { + quotient = Array(); + x = 0; + for(i = 0; i < dividend.length; i++) + { + x = (x << 16) + dividend[i]; + q = Math.floor(x / divisor); + x -= q * divisor; + if(quotient.length > 0 || q > 0) + quotient[quotient.length] = q; + } + remainders[remainders.length] = x; + dividend = quotient; + } + + /* Convert the remainders to the output string */ + var output = ""; + for(i = remainders.length - 1; i >= 0; i--) + output += encoding.charAt(remainders[i]); + + /* Append leading zero equivalents */ + var full_length = Math.ceil(input.length * 8 / + (Math.log(encoding.length) / Math.log(2))) + for(i = output.length; i < full_length; i++) + output = encoding[0] + output; + + return output; +} + +/* + * Encode a string as utf-8. + * For efficiency, this assumes the input is valid utf-16. + */ +function str2rstr_utf8(input) +{ + var output = ""; + var i = -1; + var x, y; + + while(++i < input.length) + { + /* Decode utf-16 surrogate pairs */ + x = input.charCodeAt(i); + y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; + if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) + { + x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); + i++; + } + + /* Encode output as utf-8 */ + if(x <= 0x7F) + output += String.fromCharCode(x); + else if(x <= 0x7FF) + output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), + 0x80 | ( x & 0x3F)); + else if(x <= 0xFFFF) + output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + else if(x <= 0x1FFFFF) + output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), + 0x80 | ((x >>> 12) & 0x3F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + } + return output; +} + +/* + * Encode a string as utf-16 + */ +function str2rstr_utf16le(input) +{ + var output = ""; + for(var i = 0; i < input.length; i++) + output += String.fromCharCode( input.charCodeAt(i) & 0xFF, + (input.charCodeAt(i) >>> 8) & 0xFF); + return output; +} + +function str2rstr_utf16be(input) +{ + var output = ""; + for(var i = 0; i < input.length; i++) + output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, + input.charCodeAt(i) & 0xFF); + return output; +} + +/* + * Convert a raw string to an array of big-endian words + * Characters >255 have their high-byte silently ignored. + */ +function rstr2binb(input) +{ + var output = Array(input.length >> 2); + for(var i = 0; i < output.length; i++) + output[i] = 0; + for(var i = 0; i < input.length * 8; i += 8) + output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); + return output; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2rstr(input) +{ + var output = ""; + for(var i = 0; i < input.length * 32; i += 8) + output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); + return output; +} + +/* + * Main sha256 function, with its support functions + */ +function sha256_S (X, n) {return ( X >>> n ) | (X << (32 - n));} +function sha256_R (X, n) {return ( X >>> n );} +function sha256_Ch(x, y, z) {return ((x & y) ^ ((~x) & z));} +function sha256_Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));} +function sha256_Sigma0256(x) {return (sha256_S(x, 2) ^ sha256_S(x, 13) ^ sha256_S(x, 22));} +function sha256_Sigma1256(x) {return (sha256_S(x, 6) ^ sha256_S(x, 11) ^ sha256_S(x, 25));} +function sha256_Gamma0256(x) {return (sha256_S(x, 7) ^ sha256_S(x, 18) ^ sha256_R(x, 3));} +function sha256_Gamma1256(x) {return (sha256_S(x, 17) ^ sha256_S(x, 19) ^ sha256_R(x, 10));} +function sha256_Sigma0512(x) {return (sha256_S(x, 28) ^ sha256_S(x, 34) ^ sha256_S(x, 39));} +function sha256_Sigma1512(x) {return (sha256_S(x, 14) ^ sha256_S(x, 18) ^ sha256_S(x, 41));} +function sha256_Gamma0512(x) {return (sha256_S(x, 1) ^ sha256_S(x, 8) ^ sha256_R(x, 7));} +function sha256_Gamma1512(x) {return (sha256_S(x, 19) ^ sha256_S(x, 61) ^ sha256_R(x, 6));} + +var sha256_K = new Array +( + 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, + -1841331548, -1424204075, -670586216, 310598401, 607225278, 1426881987, + 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522, + 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, + -1740746414, -1473132947, -1341970488, -1084653625, -958395405, -710438585, + 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, + 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, + -1035236496, -949202525, -778901479, -694614492, -200395387, 275423344, + 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, + 1537002063, 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, + -1866530822, -1538233109, -1090935817, -965641998 +); + +function binb_sha256(m, l) +{ + var HASH = new Array(1779033703, -1150833019, 1013904242, -1521486534, + 1359893119, -1694144372, 528734635, 1541459225); + var W = new Array(64); + var a, b, c, d, e, f, g, h; + var i, j, T1, T2; + + /* append padding */ + m[l >> 5] |= 0x80 << (24 - l % 32); + m[((l + 64 >> 9) << 4) + 15] = l; + + for(i = 0; i < m.length; i += 16) + { + a = HASH[0]; + b = HASH[1]; + c = HASH[2]; + d = HASH[3]; + e = HASH[4]; + f = HASH[5]; + g = HASH[6]; + h = HASH[7]; + + for(j = 0; j < 64; j++) + { + if (j < 16) W[j] = m[j + i]; + else W[j] = safe_add(safe_add(safe_add(sha256_Gamma1256(W[j - 2]), W[j - 7]), + sha256_Gamma0256(W[j - 15])), W[j - 16]); + + T1 = safe_add(safe_add(safe_add(safe_add(h, sha256_Sigma1256(e)), sha256_Ch(e, f, g)), + sha256_K[j]), W[j]); + T2 = safe_add(sha256_Sigma0256(a), sha256_Maj(a, b, c)); + h = g; + g = f; + f = e; + e = safe_add(d, T1); + d = c; + c = b; + b = a; + a = safe_add(T1, T2); + } + + HASH[0] = safe_add(a, HASH[0]); + HASH[1] = safe_add(b, HASH[1]); + HASH[2] = safe_add(c, HASH[2]); + HASH[3] = safe_add(d, HASH[3]); + HASH[4] = safe_add(e, HASH[4]); + HASH[5] = safe_add(f, HASH[5]); + HASH[6] = safe_add(g, HASH[6]); + HASH[7] = safe_add(h, HASH[7]); + } + return HASH; +} + +function safe_add (x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +return { + hex: hex_sha256, + b64: b64_hmac_sha256, + any: any_hmac_sha256, + hex_hmac: hex_hmac_sha256, + b64_hmac: b64_hmac_sha256, + any_hmac: any_hmac_sha256 +}; + +}()); + +console.log(sha256)`), + new Template(` + /*! https://mths.be/utf8js v3.0.0 by @mathias */ +;(function(root) { + + var stringFromCharCode = String.fromCharCode; + + // Taken from https://mths.be/punycode + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + var value; + var extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + // Taken from https://mths.be/punycode + function ucs2encode(array) { + var length = array.length; + var index = -1; + var value; + var output = ''; + while (++index < length) { + value = array[index]; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + } + return output; + } + + function checkScalarValue(codePoint) { + if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { + throw Error( + 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + + ' is not a scalar value' + ); + } + } + /*--------------------------------------------------------------------------*/ + + function createByte(codePoint, shift) { + return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); + } + + function encodeCodePoint(codePoint) { + if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence + return stringFromCharCode(codePoint); + } + var symbol = ''; + if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence + symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); + } + else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence + checkScalarValue(codePoint); + symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); + symbol += createByte(codePoint, 6); + } + else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence + symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); + symbol += createByte(codePoint, 12); + symbol += createByte(codePoint, 6); + } + symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); + return symbol; + } + + function utf8encode(string) { + var codePoints = ucs2decode(string); + var length = codePoints.length; + var index = -1; + var codePoint; + var byteString = ''; + while (++index < length) { + codePoint = codePoints[index]; + byteString += encodeCodePoint(codePoint); + } + return byteString; + } + + /*--------------------------------------------------------------------------*/ + + function readContinuationByte() { + if (byteIndex >= byteCount) { + throw Error('Invalid byte index'); + } + + var continuationByte = byteArray[byteIndex] & 0xFF; + byteIndex++; + + if ((continuationByte & 0xC0) == 0x80) { + return continuationByte & 0x3F; + } + + // If we end up here, it’s not a continuation byte + throw Error('Invalid continuation byte'); + } + + function decodeSymbol() { + var byte1; + var byte2; + var byte3; + var byte4; + var codePoint; + + if (byteIndex > byteCount) { + throw Error('Invalid byte index'); + } + + if (byteIndex == byteCount) { + return false; + } + + // Read first byte + byte1 = byteArray[byteIndex] & 0xFF; + byteIndex++; + + // 1-byte sequence (no continuation bytes) + if ((byte1 & 0x80) == 0) { + return byte1; + } + + // 2-byte sequence + if ((byte1 & 0xE0) == 0xC0) { + byte2 = readContinuationByte(); + codePoint = ((byte1 & 0x1F) << 6) | byte2; + if (codePoint >= 0x80) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 3-byte sequence (may include unpaired surrogates) + if ((byte1 & 0xF0) == 0xE0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; + if (codePoint >= 0x0800) { + checkScalarValue(codePoint); + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 4-byte sequence + if ((byte1 & 0xF8) == 0xF0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + byte4 = readContinuationByte(); + codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) | + (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + return codePoint; + } + } + + throw Error('Invalid UTF-8 detected'); + } + + var byteArray; + var byteCount; + var byteIndex; + function utf8decode(byteString) { + byteArray = ucs2decode(byteString); + byteCount = byteArray.length; + byteIndex = 0; + var codePoints = []; + var tmp; + while ((tmp = decodeSymbol()) !== false) { + codePoints.push(tmp); + } + return ucs2encode(codePoints); + } + + /*--------------------------------------------------------------------------*/ + + root.version = '3.0.0'; + root.encode = utf8encode; + root.decode = utf8decode; + +}(typeof exports === 'undefined' ? this.utf8 = {} : exports)); + `), + new Template(` + const bigInt = require('big-integer'); + +class RSA { + static randomPrime(bits) { + const min = bigInt.one.shiftLeft(bits - 1); + const max = bigInt.one.shiftLeft(bits).prev(); + + while (true) { + let p = bigInt.randBetween(min, max); + if (p.isProbablePrime(256)) { + return p; + } + } + } + + static generate(keysize) { + const e = bigInt(65537); + let p; + let q; + let totient; + + do { + p = this.randomPrime(keysize / 2); + q = this.randomPrime(keysize / 2); + totient = bigInt.lcm( + p.prev(), + q.prev() + ); + } while (bigInt.gcd(e, totient).notEquals(1) || p.minus(q).abs().shiftRight(keysize / 2 - 100).isZero()); + + return { + e, + n: p.multiply(q), + d: e.modInv(totient), + }; + } + + static encrypt(encodedMsg, n, e) { + return bigInt(encodedMsg).modPow(e, n); + } + + static decrypt(encryptedMsg, d, n) { + return bigInt(encryptedMsg).modPow(d, n); + } + + static encode(str) { + const codes = str + .split('') + .map(i => i.charCodeAt()) + .join(''); + + return bigInt(codes); + } + + static decode(code) { + const stringified = code.toString(); + let string = ''; + + for (let i = 0; i < stringified.length; i += 2) { + let num = Number(stringified.substr(i, 2)); + + if (num <= 30) { + string += String.fromCharCode(Number(stringified.substr(i, 3))); + i++; + } else { + string += String.fromCharCode(num); + } + } + + return string; + } +} + +module.exports = RSA; + `), new Template(` function curCSS( elem, name, computed ) { var ret; diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts index 43627c2..dd56722 100644 --- a/src/transforms/functionOutlining.ts +++ b/src/transforms/functionOutlining.ts @@ -5,7 +5,7 @@ import { ensureComputedExpression, prepend } from "../utils/ast-utils"; import * as t from "@babel/types"; import { NameGen } from "../utils/NameGen"; import { getRandomInteger } from "../utils/random-utils"; -import { Binding } from "@babel/traverse"; +import { Binding, Visitor } from "@babel/traverse"; interface FunctionOutliningInterface { objectName: string; @@ -20,10 +20,19 @@ interface NodeFunctionOutlining { function isSafeForOutlining(path: NodePath): { isSafe: boolean; - bindings: Binding[]; + bindings?: Binding[]; } { - if (path.isIdentifier() || path.isLiteral()) - return { isSafe: false, bindings: [] }; + if (path.isIdentifier() || path.isLiteral()) return { isSafe: false }; + + // Skip direct invocations ('this' will be different) + if (path.key === "callee" && path.parentPath.isCallExpression()) { + return { isSafe: false }; + } + + // Skip typeof and delete expressions (identifier behavior is different) + if (path.key === "argument" && path.parentPath.isUnaryExpression()) { + return { isSafe: false }; + } if ( path.isReturnStatement() || @@ -36,13 +45,13 @@ function isSafeForOutlining(path: NodePath): { path.isImportDeclaration() || path.isExportDeclaration() ) { - return { isSafe: false, bindings: [] }; + return { isSafe: false }; } var isSafe = true; var bindings: Binding[] = []; - path.traverse({ + var visitor: Visitor = { ThisExpression(path) { isSafe = false; path.stop(); @@ -59,7 +68,12 @@ function isSafeForOutlining(path: NodePath): { bindings.push(binding); } }, - }); + }; + + // Exclude 'ThisExpression' and semantic 'Identifier' nodes + if (visitor[path.type]) return { isSafe: false }; + + path.traverse(visitor); return { isSafe, bindings }; } @@ -134,6 +148,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { var bindings: Binding[] = []; for (var statement of extractedStatements) { + // Don't override the control node + if (me.isSkipped(statement)) return; + var result = isSafeForOutlining(statement); if (!result.isSafe) { return; @@ -167,16 +184,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { for (var statement of extractedStatements) { if (isFirst) { isFirst = false; - statement.replaceWith( - addToOutliningObject( - statement, - t.functionExpression( - null, - [], - t.blockStatement(extractedStatements.map((x) => x.node)) - ) + var memberExpression = addToOutliningObject( + statement, + t.functionExpression( + null, + [], + t.blockStatement(extractedStatements.map((x) => x.node)) ) ); + var callExpression = t.callExpression(memberExpression, []); + + statement.replaceWith(callExpression); continue; } statement.remove(); diff --git a/test/options.test.ts b/test/options.test.ts index 86e100d..ad78aa0 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -171,57 +171,3 @@ describe("options", () => { }).rejects.toThrow(); }); }); - -describe("options.preserveFunctionLength", () => { - test("Variant #1: Enabled by default", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` - function myFunction(a, b, c, d = "") { - // Function.length = 3 - } - - TEST_OUTPUT = myFunction.length; // 3 - `, - { - target: "node", - preset: "high", - } - ); - - var TEST_OUTPUT; - eval(output); - expect(TEST_OUTPUT).toStrictEqual(3); - }); - - test("Variant #2: Disabled", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` - function myFunction(a, b, c, d = "") { - // Function.length = 3 - } - - TEST_OUTPUT = myFunction.length; // 3 - `, - { - target: "node", - preset: "high", - preserveFunctionLength: false, - - stringEncoding: false, - stringCompression: false, - stringConcealing: false, - stringSplitting: false, - deadCode: false, - duplicateLiteralsRemoval: false, - - rgf: true, - } - ); - - expect(output).not.toContain("defineProperty"); - - var TEST_OUTPUT; - eval(output); - expect(TEST_OUTPUT).not.toStrictEqual(3); - }); -}); diff --git a/test/semantics/lastExpression.test.ts b/test/semantics/lastExpression.test.ts new file mode 100644 index 0000000..d380fb2 --- /dev/null +++ b/test/semantics/lastExpression.test.ts @@ -0,0 +1,43 @@ +import JsConfuser from "../../src"; + +test("Variant #1: Last expression is preserved on 'High' preset", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var a = 1; + var b = 2; + var c = 3; + a + b + c; + `, + { + target: "node", + preset: "high", + } + ); + + var lastExpression = eval(code); + + expect(lastExpression).toStrictEqual(6); +}); + +test("Variant #2: Last expression is preserved on 'High' preset with RGF", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var a = 1; + var b = 2; + var c = 3; + function compute(){ + return a + b + c; + } + compute(); + `, + { + target: "node", + preset: "high", + rgf: true, + } + ); + + var lastExpression = eval(code); + + expect(lastExpression).toStrictEqual(6); +}); diff --git a/test/semantics/preserveFunctionLength.test.ts b/test/semantics/preserveFunctionLength.test.ts new file mode 100644 index 0000000..e4b58a9 --- /dev/null +++ b/test/semantics/preserveFunctionLength.test.ts @@ -0,0 +1,53 @@ +import JsConfuser from "../../src"; + +test("Variant #1: Enabled by default", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` + function myFunction(a, b, c, d = "") { + // Function.length = 3 + } + + TEST_OUTPUT = myFunction.length; // 3 + `, + { + target: "node", + preset: "high", + } + ); + + var TEST_OUTPUT; + eval(output); + expect(TEST_OUTPUT).toStrictEqual(3); +}); + +test("Variant #2: Disabled", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` + function myFunction(a, b, c, d = "") { + // Function.length = 3 + } + + TEST_OUTPUT = myFunction.length; // 3 + `, + { + target: "node", + preset: "high", + preserveFunctionLength: false, + + stringEncoding: false, + stringCompression: false, + stringConcealing: false, + stringSplitting: false, + deadCode: false, + duplicateLiteralsRemoval: false, + + rgf: true, + } + ); + + expect(output).not.toContain("defineProperty"); + + var TEST_OUTPUT; + eval(output); + expect(TEST_OUTPUT).not.toStrictEqual(3); +}); diff --git a/test/transforms/functionOutlining.test.ts b/test/transforms/functionOutlining.test.ts index f53fff7..3c48ac5 100644 --- a/test/transforms/functionOutlining.test.ts +++ b/test/transforms/functionOutlining.test.ts @@ -81,3 +81,81 @@ TEST_OUTPUT = testResult; // 25 expect(TEST_OUTPUT).toStrictEqual(25); }); + +test("Variant #4: Handle typeof expressions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var existentialVariable = 10; + var values = [ + typeof nonexistentVariable1, + typeof nonexistentVariable2, + typeof nonexistentVariable3, + typeof nonexistentVariable4, + typeof nonexistentVariable5, + typeof nonexistentVariable6, + typeof nonexistentVariable7, + typeof nonexistentVariable8, + typeof nonexistentVariable9, + typeof nonexistentVariable10, + ]; + + TEST_OUTPUT = true; + + for(var value of values){ + if(typeof value !== "string" || value !== "undefined") { + TEST_OUTPUT = false; + break; + } + } + + if(typeof existentialVariable !== "number"){ + TEST_OUTPUT = false; + } + `, + { + target: "node", + functionOutlining: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(true); +}); + +test("Variant #5: Handle direct invocations", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function getThis(){ + return this; + } + + var obj1 = { + getThis: getThis + } + var obj2 = { + getThis: getThis + } + var obj3 = { + getThis: getThis + } + + TEST_OUTPUT = [ + getThis() === undefined, + obj1.getThis() === obj1, + obj2.getThis() === obj2, + obj3.getThis() === obj3, + ] + `, + { + target: "node", + functionOutlining: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual([true, true, true, true]); +}); From 2a6eb2643707e14cc2743c89f8b76e3fe9eb2914 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 7 Sep 2024 14:09:40 -0400 Subject: [PATCH 031/103] Improve Moved Declarations, Control Object --- src/constants.ts | 5 + src/templates/template.ts | 4 +- src/transforms/dispatcher.ts | 17 ++- src/transforms/functionOutlining.ts | 118 ++++++---------- .../identifier/movedDeclarations.ts | 41 ++++++ src/transforms/plugin.ts | 30 +++- src/transforms/shuffle.ts | 19 ++- src/utils/ControlObject.ts | 131 ++++++++++++++++++ src/utils/NameGen.ts | 2 +- test/code/Dynamic.test.ts | 2 +- .../identifier/movedDeclarations.test.ts | 58 ++++++++ 11 files changed, 329 insertions(+), 98 deletions(-) create mode 100644 src/utils/ControlObject.ts diff --git a/src/constants.ts b/src/constants.ts index 7e75acc..78993cb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,5 @@ +import ControlObject from "./utils/ControlObject"; + export const predictableFunctionTag = "__JS_PREDICT__"; /** @@ -29,11 +31,14 @@ export const SKIP = Symbol("skip"); */ export const FN_LENGTH = Symbol("fnLength"); +export const CONTROL_OBJECTS = Symbol("controlObjects"); + export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; [SKIP]?: boolean | number; [FN_LENGTH]?: number; + [CONTROL_OBJECTS]?: ControlObject[]; } /** diff --git a/src/templates/template.ts b/src/templates/template.ts index 9e520cd..17e4eae 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -135,9 +135,7 @@ export default class Template { return file.program.body; } - single( - variables: TemplateVariables = {} - ): T { + single(variables: TemplateVariables = {}): T { const nodes = this.compile(variables); if (nodes.length !== 1) { diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index a89ae28..6d85039 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -7,7 +7,7 @@ import { ok } from "assert"; import { chance, getRandomString } from "../utils/random-utils"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; -import { NodeSymbol, UNSAFE } from "../constants"; +import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; import { computeFunctionLength, isVariableFunctionIdentifier, @@ -166,15 +166,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { dispatcherArgs.push(t.stringLiteral(keys.returnAsObject)); } - var callExpression = t.callExpression( - t.identifier(dispatcherName), - dispatcherArgs - ); + var callExpression: t.CallExpression | t.NewExpression = + t.callExpression( + t.identifier(dispatcherName), + dispatcherArgs + ); if (!asObject) { return callExpression; } + if (chance(50)) { + (callExpression as t.Node).type = "NewExpression"; + } + return t.memberExpression( callExpression, t.stringLiteral(keys.returnAsObjectProperty), @@ -263,6 +268,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { t.blockStatement(newBody) ); + (functionExpression as NodeSymbol)[PREDICTABLE] = true; + return t.objectProperty( t.stringLiteral(newName), diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts index dd56722..252c08c 100644 --- a/src/transforms/functionOutlining.ts +++ b/src/transforms/functionOutlining.ts @@ -4,19 +4,9 @@ import { Order } from "../order"; import { ensureComputedExpression, prepend } from "../utils/ast-utils"; import * as t from "@babel/types"; import { NameGen } from "../utils/NameGen"; -import { getRandomInteger } from "../utils/random-utils"; +import { chance, getRandomInteger } from "../utils/random-utils"; import { Binding, Visitor } from "@babel/traverse"; - -interface FunctionOutliningInterface { - objectName: string; - add: (node: t.Expression) => t.Expression; -} - -const FUNCTION_OUTLINING = Symbol("functionOutlining"); - -interface NodeFunctionOutlining { - [FUNCTION_OUTLINING]?: FunctionOutliningInterface; -} +import { computeProbabilityMap } from "../probability"; function isSafeForOutlining(path: NodePath): { isSafe: boolean; @@ -81,64 +71,30 @@ function isSafeForOutlining(path: NodePath): { export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.FunctionOutlining); - function addToOutliningObject(path: NodePath, node: t.Expression) { - var block = path.findParent((p) => p.isBlock()); - if (!block) return; - - let fnInterface = (block.node as NodeFunctionOutlining)[FUNCTION_OUTLINING]; - - if (!fnInterface) { - var objectName = me.getPlaceholder(); - - var newPath = prepend( - block, - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier(objectName), - t.objectExpression([]) - ), - ]) - )[0] as NodePath; - me.skip(newPath); - - let gen = new NameGen(me.options.identifierGenerator); - - var objectExpression = newPath.node.declarations[0] - .init as t.ObjectExpression; - fnInterface = { - add: (node) => { - const property = gen.generate(); - objectExpression.properties.push( - t.objectProperty(t.identifier(property), node) - ); - me.skip(objectExpression); - - return t.memberExpression( - t.identifier(objectName), - t.stringLiteral(property), - true - ); - }, - objectName, - }; + var changesMade = 0; + + function checkProbability() { + if (!computeProbabilityMap(me.options.functionOutlining)) return false; - (block.node as NodeFunctionOutlining)[FUNCTION_OUTLINING] = fnInterface; - } + if (changesMade > 100 && chance(changesMade - 100)) return false; - return fnInterface.add(node); + return true; } return { visitor: { Block: { - exit(path) { - if (path.isProgram()) { - path.scope.crawl(); + exit(blockPath) { + if (blockPath.isProgram()) { + blockPath.scope.crawl(); } - if (path.find((p) => me.isSkipped(p))) return; + if (blockPath.find((p) => me.isSkipped(p))) return; + + if (!checkProbability()) return; + // Extract a random number of statements - var statements = path.get("body"); + var statements = blockPath.get("body"); var startIndex = getRandomInteger(0, statements.length); var endIndex = getRandomInteger(startIndex, statements.length); @@ -180,18 +136,21 @@ export default ({ Plugin }: PluginArg): PluginObj => { } } + changesMade++; + var isFirst = true; for (var statement of extractedStatements) { if (isFirst) { isFirst = false; - var memberExpression = addToOutliningObject( - statement, - t.functionExpression( - null, - [], - t.blockStatement(extractedStatements.map((x) => x.node)) - ) - ); + var memberExpression = me + .getControlObject(blockPath) + .addProperty( + t.functionExpression( + null, + [], + t.blockStatement(extractedStatements.map((x) => x.node)) + ) + ); var callExpression = t.callExpression(memberExpression, []); statement.replaceWith(callExpression); @@ -214,17 +173,24 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } + if (!checkProbability()) return; + if (path.find((p) => me.isSkipped(p))) return; if (!isSafeForOutlining(path).isSafe) return; - var memberExpression = addToOutliningObject( - path, - t.functionExpression( - null, - [], - t.blockStatement([t.returnStatement(t.cloneNode(path.node))]) - ) - ); + changesMade++; + + var blockPath = path.find((p) => p.isBlock()) as NodePath; + + var memberExpression = me + .getControlObject(blockPath) + .addProperty( + t.functionExpression( + null, + [], + t.blockStatement([t.returnStatement(t.cloneNode(path.node))]) + ) + ); var callExpression = t.callExpression(memberExpression, []); diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index ff03055..dfbabf7 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -5,6 +5,8 @@ import { NodeSymbol, PREDICTABLE } from "../../constants"; import * as t from "@babel/types"; import { isStaticValue } from "../../utils/static-utils"; import { isFunctionStrictMode } from "../../utils/function-utils"; +import { prepend } from "../../utils/ast-utils"; +import Template from "../../templates/template"; /** * Moved Declarations moves variables in two ways: @@ -17,6 +19,45 @@ export default ({ Plugin }: PluginArg): PluginObj => { return { visitor: { + FunctionDeclaration: { + exit(path) { + var functionPath = path.findParent((path) => + path.isFunction() + ) as NodePath; + + if (!functionPath || !(functionPath.node as NodeSymbol)[PREDICTABLE]) + return; + + // Must be direct child of the function + if (path.parentPath !== functionPath.get("body")) return; + + // Rest params check + if (functionPath.get("params").find((p) => p.isRestElement())) return; + + var isStrictMode = isFunctionStrictMode(functionPath); + + if (isStrictMode) return; + + const functionName = path.node.id.name; + + var functionExpression = path.node as t.Node as t.FunctionExpression; + functionExpression.type = "FunctionExpression"; + functionExpression.id = null; + + functionPath.node.params.push(t.identifier(functionName)); + + prepend( + functionPath, + new Template(` + if(!${functionName}) { + ${functionName} = {functionExpression}; + } + `).single({ functionExpression: functionExpression }) + ); + + path.remove(); + }, + }, VariableDeclaration: { exit(path) { if (path.node.kind !== "var") return; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index dd9c46f..878aff4 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -1,11 +1,13 @@ import { NodePath, PluginObj } from "@babel/core"; import Obfuscator from "../obfuscator"; -import { getRandomString } from "../utils/random-utils"; +import { chance, choice, getRandomString } from "../utils/random-utils"; import { Order } from "../order"; import * as t from "@babel/types"; -import { FN_LENGTH, NodeSymbol, SKIP } from "../constants"; +import { FN_LENGTH, NodeSymbol, SKIP, CONTROL_OBJECTS } from "../constants"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { prepend, prependProgram } from "../utils/ast-utils"; +import ControlObject from "../utils/ControlObject"; +import { ok } from "assert"; export type PluginFunction = (pluginArg: PluginArg) => PluginObj; @@ -111,6 +113,30 @@ export class PluginInstance { } } + getControlObject(blockPath: NodePath) { + ok(blockPath.isBlock()); + + var controlObjects = (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]; + if (!controlObjects) { + controlObjects = []; + } + + if ( + controlObjects.length === 0 || + chance(100 - controlObjects.length * 10) + ) { + var newControlObject = new ControlObject(this, blockPath); + + controlObjects.push(newControlObject); + + (blockPath.node as NodeSymbol)[CONTROL_OBJECTS] = controlObjects; + + return newControlObject; + } + + return choice(controlObjects); + } + warn(...messages: any[]) { if (this.options.verbose) { console.log(`WARN [${this.name}]`, ...messages); diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index 36a192f..4f50b59 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -37,28 +37,27 @@ export default ({ Plugin }: PluginArg): PluginObj => { shiftedElements.unshift(shiftedElements.pop()); } - var runtimeFn = me.getPlaceholder(); + var block = path.find((p) => p.isBlock()) as NodePath; - var program = path.find((p) => p.isProgram()) as NodePath; - program.unshiftContainer( - "body", + var memberExpression = me.getControlObject(block).addProperty( new Template( ` - function ${runtimeFn}(arr, shift) { - for (var i = 0; i < shift; i++) { + (function(arr) { + for (var i = 0; i < {shiftNode}; i++) { arr.push(arr.shift()); } return arr; - } + }) ` - ).single() + ).expression({ + shiftNode: t.numericLiteral(shift), + }) ); path.replaceWith( - t.callExpression(t.identifier(runtimeFn), [ + t.callExpression(memberExpression, [ t.arrayExpression(shiftedElements), - t.numericLiteral(shift), ]) ); diff --git a/src/utils/ControlObject.ts b/src/utils/ControlObject.ts new file mode 100644 index 0000000..60761b3 --- /dev/null +++ b/src/utils/ControlObject.ts @@ -0,0 +1,131 @@ +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import { PluginInstance } from "../transforms/plugin"; +import { NameGen } from "./NameGen"; +import { prepend } from "./ast-utils"; +import { chance, choice } from "./random-utils"; + +/** + * A Control Object is an object that is used to store properties that are used in multiple places. + */ +export default class ControlObject { + propertyNames = new Set(); + nameGen: NameGen; + objectName: string | null = null; + objectPath: NodePath | null = null; + objectExpression: t.ObjectExpression | null = null; + + constructor(public me: PluginInstance, public blockPath: NodePath) { + this.nameGen = new NameGen(me.options.identifierGenerator); + } + + createMemberExpression(propertyName: string): t.MemberExpression { + return t.memberExpression( + t.identifier(this.objectName), + t.stringLiteral(propertyName), + true + ); + } + + createPredicate() { + var propertyName = choice(Array.from(this.propertyNames)); + if (!propertyName || chance(50)) { + propertyName = this.nameGen.generate(); + } + + return { + node: t.binaryExpression( + "in", + t.identifier(this.objectName), + t.stringLiteral(propertyName) + ), + value: this.propertyNames.has(propertyName), + }; + } + + createTruePredicate() { + var { node, value } = this.createPredicate(); + if (!value) { + return t.unaryExpression("!", node); + } + return node; + } + + createFalsePredicate() { + var { node, value } = this.createPredicate(); + if (value) { + return t.unaryExpression("!", node); + } + return node; + } + + addProperty(node: t.Expression) { + if (!this.objectName) { + // Object hasn't been created yet + this.objectName = this.me.getPlaceholder(); + + if (t.isFunctionExpression(node) && !node.id) { + // Use function declaration as object + + let newNode: t.FunctionDeclaration = node as any; + newNode.type = "FunctionDeclaration"; + newNode.id = t.identifier(this.objectName); + + let newPath = prepend( + this.blockPath, + newNode + )[0] as NodePath; + this.me.skip(newPath); + + this.objectPath = newPath; + + return t.identifier(this.objectName); + } else { + // Create plain object + let newPath = prepend( + this.blockPath, + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(this.objectName), + t.objectExpression([]) + ), + ]) + )[0] as NodePath; + this.me.skip(newPath); + + this.objectPath = newPath; + + var objectExpression = newPath.node.declarations[0] + .init as t.ObjectExpression; + + this.objectExpression = objectExpression; + this.me.skip(this.objectExpression); + } + } + + const propertyName = this.nameGen.generate(); + this.propertyNames.add(propertyName); + + // Add an initial property + if (this.objectExpression) { + this.objectExpression.properties.push( + t.objectProperty(t.identifier(propertyName), node) + ); + } else { + // Add as assignment expression + + let assignment = t.assignmentExpression( + "=", + this.createMemberExpression(propertyName), + node + ); + + var newPath = this.objectPath.insertAfter( + t.expressionStatement(assignment) + )[0]; + this.me.skip(newPath); + } + + return this.createMemberExpression(propertyName); + } +} diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts index e1269a1..2677bfc 100644 --- a/src/utils/NameGen.ts +++ b/src/utils/NameGen.ts @@ -5,7 +5,7 @@ import { choice, getRandomHexString, getRandomInteger } from "./random-utils"; import { computeProbabilityMap } from "../probability"; export class NameGen { - private generatedNames = new Set(); + public generatedNames = new Set(); private counter = 1; private zeroWidthGenerator = createZeroWidthGenerator(); diff --git a/test/code/Dynamic.test.ts b/test/code/Dynamic.test.ts index 1d59ed6..0f20912 100644 --- a/test/code/Dynamic.test.ts +++ b/test/code/Dynamic.test.ts @@ -7,7 +7,7 @@ var SOURCE_JS = readFileSync(join(__dirname, "./Dynamic.src.js"), "utf-8"); test.concurrent("Variant #1: Dynamic.src.js on High Preset", async () => { // `input` is an embedded variable, therefore globalConcealing must be turned off - var { output: code } = await JsConfuser.obfuscate(SOURCE_JS, { + var { code: output } = await JsConfuser.obfuscate(SOURCE_JS, { target: "browser", preset: "high", globalConcealing: false, diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index 4f6fa0b..adbd40a 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -299,3 +299,61 @@ test("Variant #11: Predictable function called with extraneous parameters", asyn expect(TEST_OUTPUT).toStrictEqual(15); }); + +test("Variant #12: Move function declaration as parameter", async () => { + var code = ` + var outsideVar = "Correct Value"; + + function myFunction(){ + function getCorrectValue1(){ + return "Correct Value"; + } + + let var1 = "Correct Value"; + function getCorrectValue2(){ + return var1; + } + + let var2; + var2 = "Correct Value"; + + function getCorrectValue3(){ + return var2; + } + + function getCorrectValue4(){ + if(var2) { + return outsideVar; + } + } + + TEST_OUTPUT = [ + getCorrectValue1(), + getCorrectValue2(), + getCorrectValue3(), + getCorrectValue4() + ]; + } + + myFunction(); + `; + + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + movedDeclarations: true, + }); + + expect(output).toContain( + "myFunction(getCorrectValue1,getCorrectValue2,getCorrectValue3,getCorrectValue4" + ); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual([ + "Correct Value", + "Correct Value", + "Correct Value", + "Correct Value", + ]); +}); From c1542d43f2882f7c68c534f494435aa4778d90a8 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 9 Sep 2024 23:02:43 -0400 Subject: [PATCH 032/103] Control Flow Flattening, `numericLiteral` fix --- src/order.ts | 6 +- src/transforms/controlFlowFlattening.ts | 1281 ++++++++++++----- src/transforms/dispatcher.ts | 25 +- .../extraction/duplicateLiteralsRemoval.ts | 5 +- .../identifier/movedDeclarations.ts | 26 +- src/transforms/identifier/renameVariables.ts | 2 + src/transforms/plugin.ts | 9 +- src/transforms/rgf.ts | 3 +- src/transforms/shuffle.ts | 33 +- src/transforms/string/stringCompression.ts | 3 +- src/transforms/string/stringConcealing.ts | 3 +- src/utils/ast-utils.ts | 108 +- src/utils/node.ts | 18 + .../controlFlowFlattening.test.ts | 123 +- test/util/ast-utils.test.ts | 101 +- 15 files changed, 1260 insertions(+), 486 deletions(-) create mode 100644 src/utils/node.ts diff --git a/src/order.ts b/src/order.ts index 3c6e528..6f6b5ee 100644 --- a/src/order.ts +++ b/src/order.ts @@ -18,8 +18,6 @@ export enum Order { Calculator = 9, - ControlFlowFlattening = 10, - GlobalConcealing = 12, OpaquePredicates = 13, @@ -38,7 +36,9 @@ export enum Order { Shuffle = 24, - MovedDeclarations = 26, + MovedDeclarations = 25, + + ControlFlowFlattening = 26, RenameLabels = 27, diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index ed83230..d3c5f4f 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1,13 +1,17 @@ -import { NodePath, PluginObj, traverse, Visitor } from "@babel/core"; +import { PluginObj } from "@babel/core"; +import { NodePath, Scope, Visitor } from "@babel/traverse"; import { PluginArg } from "./plugin"; import { Order } from "../order"; import { computeProbabilityMap } from "../probability"; import { ensureComputedExpression, + getFunctionName, getParentFunctionOrProgram, - getPatternIdentifierNames, + isDefiningIdentifier, + isStrictIdentifier, } from "../utils/ast-utils"; import * as t from "@babel/types"; +import * as n from "../utils/node"; import Template from "../templates/template"; import { chance, @@ -17,95 +21,324 @@ import { } from "../utils/random-utils"; import { IntGen } from "../utils/IntGen"; import { ok } from "assert"; +import { NameGen } from "../utils/NameGen"; +import { NodeSymbol, UNSAFE } from "../constants"; /** - * Control-Flow-Flattening breaks your code into Basic Blocks. + * Breaks functions into DAGs (Directed Acyclic Graphs) * - * Basic Blocks are simple statements without any jumps or branches. + * - 1. Break functions into chunks + * - 2. Shuffle chunks but remember their original position + * - 3. Create a Switch statement inside a While loop, each case is a chunk, and the while loops exits on the last transition. + * + * The Switch statement: + * + * - 1. The state variable controls which case will run next + * - 2. At the end of each case, the state variable is updated to the next block of code. + * - 3. The while loop continues until the the state variable is the end state. */ export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.ControlFlowFlattening); - const isDebug = true; + const isDebug = false; + const flattenIfStatements = true; + const addRelativeAssignments = true; + const addDeadCode = true; + const addFakeTests = true; + const addComplexTests = true; + const mangleNumericalLiterals = true; + const mangleBooleanLiterals = true; + + const cffPrefix = me.getPlaceholder(); + let cffCounter = 0; return { visitor: { - Block: { - exit(blockPath) { - if (!blockPath.isProgram()) return; - if (blockPath.isProgram()) { - blockPath.scope.crawl(); + "Program|Function": { + exit(_path) { + let programOrFunctionPath = _path as NodePath; + + let programPath = _path.isProgram() ? _path : null; + let functionPath = _path.isFunction() ? _path : null; + + let blockPath: NodePath; + if (programPath) { + blockPath = programPath; + } else { + var fnBlockPath = functionPath.get("body"); + if (!fnBlockPath.isBlock()) return; + blockPath = fnBlockPath; } - const body = blockPath.node.body; + // Must be at least 3 statements or more + if (blockPath.node.body.length < 3) return; + + // Check user's threshold setting + if (!computeProbabilityMap(me.options.controlFlowFlattening)) { + return; + } + + // Avoid unsafe functions + if (functionPath && (functionPath.node as NodeSymbol)[UNSAFE]) return; + + programOrFunctionPath.scope.crawl(); + const blockFnParent = getParentFunctionOrProgram(blockPath); - let hasContinueOrBreak = false; + let hasIllegalNode = false; + var bindingNames = new Set(); blockPath.traverse({ - "ContinueStatement|BreakStatement"(path) { - if (getParentFunctionOrProgram(path) === blockFnParent) { - hasContinueOrBreak = true; + "Super|MetaProperty|AwaitExpression|YieldExpression"(path) { + if ( + getParentFunctionOrProgram(path).node === blockFnParent.node + ) { + hasIllegalNode = true; path.stop(); } }, - }); + VariableDeclaration(path) { + if (path.node.declarations.length !== 1) { + hasIllegalNode = true; + path.stop(); + } + }, + BindingIdentifier(path) { + const binding = path.scope.getBinding(path.node.name); + if (!binding) return; - if (hasContinueOrBreak) { - return; - } + var fnParent = path.getFunctionParent(); + if ( + path.key === "id" && + path.parentPath.isFunctionDeclaration() + ) { + fnParent = path.parentPath.getFunctionParent(); + } - // Limit how many numbers get entangled - let mangledNumericLiteralsCreated = 0; + if (fnParent !== functionPath) return; - // Must be at least 3 statements or more - if (body.length < 3) { - return; - } + if (!isDefiningIdentifier(path)) { + return; + } - // Check user's threshold setting - if (!computeProbabilityMap(me.options.controlFlowFlattening)) { + if (bindingNames.has(path.node.name)) { + hasIllegalNode = true; + path.stop(); + return; + } + bindingNames.add(path.node.name); + }, + "BreakStatement|ContinueStatement"(_path) { + var path = _path as NodePath< + t.BreakStatement | t.ContinueStatement + >; + if (path.node.label) return; + + const parent = path.findParent( + (p) => + p.isFor() || + p.isWhile() || + (path.isBreakStatement() && p.isSwitchCase()) || + p === blockPath + ); + + if (parent === blockPath) { + hasIllegalNode = true; + path.stop(); + } + }, + }); + + if (hasIllegalNode) { return; } - const prefix = me.getPlaceholder(); + // Limit how many numbers get entangled + let mangledLiteralsCreated = 0; + + const prefix = cffPrefix + "_" + cffCounter++; const mainFnName = prefix + "_main"; + const scopeVar = prefix + "_scope"; + const stateVars = new Array(isDebug ? 1 : getRandomInteger(2, 5)) .fill("") .map((_, i) => `${prefix}_state_${i}`); const argVar = prefix + "_arg"; + const didReturnVar = prefix + "_return"; + const basicBlocks = new Map(); // Map labels to states - const statIntGen = new IntGen(); + const stateIntGen = new IntGen(); + + const defaultBlockPath = blockPath; + + let scopeCounter = 0; + + const scopeNameGen = new NameGen(me.options.identifierGenerator); - interface BasicBlockOptions { - parent?: BasicBlock; - topLevel: boolean; - fnLabel: string; + class ScopeManager { + isNotUsed = true; + + nameMap = new Map(); + nameGen = new NameGen(me.options.identifierGenerator); + + preserveNames = new Set(); + + getNewName(name: string) { + if (!this.nameMap.has(name)) { + let newName = this.nameGen.generate(); + if (isDebug) { + newName = "_" + name; + } + this.nameMap.set(name, newName); + + // console.log( + // "Renaming " + + // name + + // " to " + + // newName + + // " : " + + // this.scope.path.type + // ); + + return newName; + } + return this.nameMap.get(name); + } + + getMemberExpression(name: string) { + return t.memberExpression( + t.memberExpression( + t.identifier(scopeVar), + t.stringLiteral(this.propertyName), + true + ), + t.stringLiteral(name), + true + ); + } + + propertyName: string; + constructor(public scope: Scope) { + this.propertyName = isDebug + ? "_" + scopeCounter++ + : scopeNameGen.generate(); + } + + get parent() { + return scopeToScopeManager.get(this.scope.parent); + } + + getObjectExpression(refreshLabel: string) { + var refreshScope = basicBlocks.get(refreshLabel).scopeManager; + var propertyMap: { [property: string]: t.Expression } = {}; + + var cursor = this.scope; + while (cursor) { + var parentScopeManager = scopeToScopeManager.get(cursor); + if (parentScopeManager) { + propertyMap[parentScopeManager.propertyName] = + t.memberExpression( + t.identifier(scopeVar), + t.stringLiteral(parentScopeManager.propertyName), + true + ); + } + + cursor = cursor.parent; + } + + propertyMap[refreshScope.propertyName] = isDebug + ? new Template(` + ({ + identity: "${refreshScope.propertyName}" + }) + `).expression() + : t.objectExpression([]); + + var properties: t.ObjectProperty[] = []; + for (var key in propertyMap) { + properties.push( + t.objectProperty(t.stringLiteral(key), propertyMap[key], true) + ); + } + + return t.objectExpression(properties); + } + + hasName(name: string) { + let cursor: ScopeManager = this; + while (cursor) { + if (cursor.nameMap.has(name)) { + return true; + } + cursor = cursor.parent; + } + + return false; + } } + const scopeToScopeManager = new Map(); /** * A Basic Block is a sequence of instructions with no diversion except at the entry and exit points. */ class BasicBlock { totalState: number; stateValues: number[]; - block?: t.Block; + + private createPath() { + const newPath = NodePath.get({ + hub: this.parentPath.hub, + parentPath: this.parentPath, + parent: this.parentPath.node, + container: this.parentPath.node.body, + listKey: "body", // Set the correct list key + key: "virtual", // Set the index of the new node + } as any); + + newPath.scope = this.parentPath.scope; + newPath.parentPath = this.parentPath; + newPath.node = t.blockStatement([]); + + this.thisPath = newPath; + this.thisNode = newPath.node; + } + + insertAfter(newNode: t.Statement) { + this.body.push(newNode); + } + + get scope() { + return this.parentPath.scope; + } + + get scopeManager() { + return scopeToScopeManager.get(this.scope); + } + + thisPath: NodePath; + thisNode: t.BlockStatement; + + get body(): t.Statement[] { + return this.thisPath.node.body; + } constructor( public label: string, - public options: BasicBlockOptions, - public body: t.Statement[] = [] + public parentPath: NodePath ) { + this.createPath(); + if (isDebug) { // States in debug mode are just 1, 2, 3, ... this.totalState = basicBlocks.size + 1; } else { - this.totalState = statIntGen.generate(); + this.totalState = stateIntGen.generate(); } // Correct state values @@ -137,6 +370,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Store basic block basicBlocks.set(label, this); + + // Create a new scope manager if it doesn't exist + if (!scopeToScopeManager.has(this.scope)) { + scopeToScopeManager.set( + this.scope, + new ScopeManager(this.scope) + ); + } } } @@ -148,267 +389,243 @@ export default ({ Plugin }: PluginArg): PluginObj => { const startLabel = me.getPlaceholder(); const endLabel = me.getPlaceholder(); - let currentBasicBlock = new BasicBlock(startLabel, { - parent: null, - topLevel: true, - fnLabel: null, - }); - - interface Metadata { - label?: string; - type?: "goto"; - } - - interface NodeMetadata { - metadata?: Metadata; - } - - function ControlStatement(metadata: Metadata): t.ExpressionStatement { - var exprStmt = new Template( - `ControlStatement()` - ).single(); + let currentBasicBlock = new BasicBlock(startLabel, blockPath); - (exprStmt.expression as NodeMetadata).metadata = metadata; - - return exprStmt; - } + const gotoFunctionName = + "GOTO__" + + me.getPlaceholder() + + "__IF_YOU_CAN_READ_THIS_THERE_IS_A_BUG"; function GotoControlStatement(label: string) { - return ControlStatement({ - type: "goto", - label, - }); + return new Template(` + ${gotoFunctionName}("${label}"); + `).single(); } // Ends the current block and starts a new one - function endCurrentBasicBlock( - { - jumpToNext = true, - nextLabel = me.getPlaceholder(), - prevJumpTo = null, - } = {}, - options: BasicBlockOptions - ) { + function endCurrentBasicBlock({ + jumpToNext = true, + nextLabel = me.getPlaceholder(), + prevJumpTo = null, + nextBlockPath = null, + } = {}) { + ok(nextBlockPath); + if (prevJumpTo) { - currentBasicBlock.body.push(GotoControlStatement(prevJumpTo)); + currentBasicBlock.insertAfter(GotoControlStatement(prevJumpTo)); } else if (jumpToNext) { - currentBasicBlock.body.push(GotoControlStatement(nextLabel)); + currentBasicBlock.insertAfter(GotoControlStatement(nextLabel)); } - currentBasicBlock = new BasicBlock(nextLabel, options); + currentBasicBlock = new BasicBlock(nextLabel, nextBlockPath); } - const callableMap = new Map(); - const callableOriginalFnMap = new Map< + const prependNodes: t.Statement[] = []; + const functionExpressions: [ string, - t.FunctionDeclaration - >(); - - const prependNodes = []; + string, + BasicBlock, + t.FunctionExpression + ][] = []; function flattenIntoBasicBlocks( - block: t.Block, - options: BasicBlockOptions + bodyIn: NodePath[] | NodePath ) { - currentBasicBlock.block = block; - - // Make sure 'topLevel' is disabled when flattening IF-statements - const nestedFlattenIntoBasicBlocks = ( - block: t.Block, - options: BasicBlockOptions - ) => { - var newOptions = { - ...options, - topLevel: false, - }; - return flattenIntoBasicBlocks(block, newOptions); - }; + // if (!Array.isArray(bodyIn) && bodyIn.isBlock()) { + // currentBasicBlock.parentPath = bodyIn; + // } + const body = Array.isArray(bodyIn) ? bodyIn : bodyIn.get("body"); + const nextBlockPath = Array.isArray(bodyIn) + ? currentBasicBlock.parentPath + : bodyIn; - for (const index in block.body) { - const statement = block.body[index]; + for (const index in body) { + const statement = body[index]; // Keep Imports before everything else - if (t.isImportDeclaration(statement)) { - prependNodes.push(statement); + if (statement.isImportDeclaration()) { + prependNodes.push(statement.node); continue; } - if (t.isClassDeclaration(statement)) { - prependNodes.push(statement); - continue; - } - - // Convert Function Declaration into Basic Blocks - if (t.isFunctionDeclaration(statement)) { - const fnName = statement.id.name; + if (statement.isFunctionDeclaration()) { + const fnName = statement.node.id.name; + let isIllegal = false; - // Function cannot be redefined - if (statement.async || statement.generator) { - if (options.topLevel) { - prependNodes.push(statement); - } else { - currentBasicBlock.body.push(statement); - continue; - } - continue; + if ( + statement.node.async || + statement.node.generator || + (statement.node as NodeSymbol)[UNSAFE] + ) { + isIllegal = true; } + let oldBasicBlock = currentBasicBlock; + var fnLabel = me.getPlaceholder(); - const isRedefined = callableOriginalFnMap.has(fnName); - - const afterPath = me.getPlaceholder(); - const fnLabel = me.getPlaceholder(); + let sm = currentBasicBlock.scopeManager; + let rename = sm.getNewName(fnName); - if (!isRedefined) { - callableOriginalFnMap.set(fnName, statement); - callableMap.set(fnName, fnLabel); - } else { - } + sm.scope.bindings[fnName].kind = "var"; - var oldBasicBlock = currentBasicBlock; + const hoistedBasicBlock = Array.from(basicBlocks.values()).find( + (block) => block.parentPath === currentBasicBlock.parentPath + ); - var newBasicBlockOptions = { - topLevel: false, - fnLabel, - }; + if (isIllegal) { + hoistedBasicBlock.body.unshift(statement.node); + continue; + } - endCurrentBasicBlock( - { - prevJumpTo: afterPath, - nextLabel: fnLabel, - }, - newBasicBlockOptions + const functionExpression = t.functionExpression( + null, + [], + t.blockStatement([]) ); + functionExpressions.push([ + fnName, + fnLabel, + currentBasicBlock, + functionExpression, + ]); - let embeddedName = fnLabel + "_" + statement.id.name; - statement.id.name = embeddedName; - - // Start function body - currentBasicBlock.body.push(statement); - currentBasicBlock.body.push( - t.returnStatement( - t.callExpression( - t.memberExpression( - t.identifier(embeddedName), - t.stringLiteral("call"), - true - ), - [ - t.thisExpression(), - t.spreadElement(t.identifier(argVar)), - ] + hoistedBasicBlock.body.unshift( + t.expressionStatement( + t.assignmentExpression( + "=", + sm.getMemberExpression(rename), + functionExpression ) ) ); - endCurrentBasicBlock( - { - jumpToNext: false, - nextLabel: afterPath, - }, - options + const blockStatement = statement.get("body"); + + endCurrentBasicBlock({ + nextLabel: fnLabel, + nextBlockPath: blockStatement, + jumpToNext: false, + }); + var fnTopBlock = currentBasicBlock; + + // Implicit return + blockStatement.node.body.push( + t.returnStatement(t.identifier("undefined")) ); - // Add the function to the start of the 'block' - var topBasicBlock = Array.from(basicBlocks.values()).filter( - (p) => p.block === block - )[0]; + flattenIntoBasicBlocks(blockStatement); - topBasicBlock.body.unshift( - t.expressionStatement( - t.assignmentExpression( - "=", - t.identifier(fnName), - createBasicBlockFunctionExpression(fnLabel) + // Debug label + if (isDebug) { + fnTopBlock.body.unshift( + t.expressionStatement( + t.stringLiteral( + "Function " + + statement.node.id.name + + " -> Renamed to " + + rename + ) ) - ) - ); + ); + } - if (!isRedefined) { - prependNodes.push( + // Unpack parameters + if (statement.node.params.length > 0) { + fnTopBlock.body.unshift( t.variableDeclaration("var", [ t.variableDeclarator( - t.identifier(fnName), - createBasicBlockFunctionExpression(fnLabel) + t.arrayPattern(statement.node.params), + t.identifier(argVar) ), ]) ); + + // Change bindings from 'param' to 'var' + statement.get("params").forEach((param) => { + var ids = param.getBindingIdentifierPaths(); + // Loop over the record of binding identifiers + for (const identifierName in ids) { + const identifierPath = ids[identifierName]; + if (identifierPath.getFunctionParent() === statement) { + const binding = + statement.scope.getBinding(identifierName); + + if (binding) { + binding.kind = "var"; + } + } + } + }); } + currentBasicBlock = oldBasicBlock; continue; } // Convert IF statements into Basic Blocks - if (t.isIfStatement(statement)) { - function ensureBlockStatement( - node: t.Statement - ): t.BlockStatement { - if (t.isBlockStatement(node)) { - return node; - } - return t.blockStatement([node]); - } + if (statement.isIfStatement() && flattenIfStatements) { + const test = statement.get("test"); + const consequent = statement.get("consequent"); + const alternate = statement.get("alternate"); + + // Both consequent and alternate are blocks + if ( + consequent.isBlockStatement() && + (!alternate.node || alternate.isBlockStatement()) + ) { + const consequentLabel = me.getPlaceholder(); + const alternateLabel = alternate.node + ? me.getPlaceholder() + : null; + const afterPath = me.getPlaceholder(); + + currentBasicBlock.insertAfter( + t.ifStatement( + test.node, + GotoControlStatement(consequentLabel), + alternateLabel + ? GotoControlStatement(alternateLabel) + : GotoControlStatement(afterPath) + ) + ); - const test = statement.test; - const consequent = ensureBlockStatement(statement.consequent); - const alternate = statement.alternate - ? ensureBlockStatement(statement.alternate) - : null; - - const consequentLabel = me.getPlaceholder(); - const alternateLabel = alternate ? me.getPlaceholder() : null; - const afterPath = me.getPlaceholder(); - - currentBasicBlock.body.push( - t.ifStatement( - test, - GotoControlStatement(consequentLabel), - alternateLabel - ? GotoControlStatement(alternateLabel) - : GotoControlStatement(afterPath) - ) - ); + const oldBasicBlock = currentBasicBlock; - endCurrentBasicBlock( - { + endCurrentBasicBlock({ jumpToNext: false, nextLabel: consequentLabel, - }, - options - ); + nextBlockPath: consequent, + }); - nestedFlattenIntoBasicBlocks(consequent, options); + flattenIntoBasicBlocks(consequent); - if (alternate) { - endCurrentBasicBlock( - { + if (alternate.isBlockStatement()) { + endCurrentBasicBlock({ prevJumpTo: afterPath, nextLabel: alternateLabel, - }, - options - ); + nextBlockPath: alternate, + }); - nestedFlattenIntoBasicBlocks(alternate, options); - } + flattenIntoBasicBlocks(alternate); + } - endCurrentBasicBlock( - { + endCurrentBasicBlock({ prevJumpTo: afterPath, nextLabel: afterPath, - }, - options - ); + nextBlockPath: oldBasicBlock.parentPath, + }); - continue; + continue; + } } if ( - options.topLevel && - Number(index) === block.body.length - 1 && - t.isExpressionStatement(statement) + Number(index) === body.length - 1 && + statement.isExpressionStatement() && + statement.findParent((p) => p.isBlock()) === blockPath ) { // Return the result of the last expression for eval() purposes - currentBasicBlock.body.push( - t.returnStatement(statement.expression) + currentBasicBlock.insertAfter( + t.returnStatement(statement.get("expression").node) ); continue; } @@ -418,27 +635,80 @@ export default ({ Plugin }: PluginArg): PluginObj => { currentBasicBlock.body.length > 1 && chance(50 + currentBasicBlock.body.length) ) { - endCurrentBasicBlock({}, options); + endCurrentBasicBlock({ + nextBlockPath: nextBlockPath, + }); } - currentBasicBlock.body.push(statement); + // console.log(currentBasicBlock.thisPath.type); + // console.log(currentBasicBlock.body); + currentBasicBlock.body.push(statement.node); } } // Convert our code into Basic Blocks - flattenIntoBasicBlocks(blockPath.node, { - topLevel: true, - fnLabel: null, - }); + flattenIntoBasicBlocks(blockPath.get("body")); // Ensure always jumped to the Program end - endCurrentBasicBlock( - { - jumpToNext: true, - nextLabel: endLabel, - }, - { parent: null, topLevel: true, fnLabel: null } - ); + endCurrentBasicBlock({ + jumpToNext: true, + nextLabel: endLabel, + nextBlockPath: defaultBlockPath, + }); + + if (!isDebug && addDeadCode) { + // DEAD CODE 1/3: Add fake chunks that are never reached + const fakeChunkCount = getRandomInteger(1, 5); + for (let i = 0; i < fakeChunkCount; i++) { + // These chunks just jump somewhere random, they are never executed + // so it could contain any code + const fakeBlock = new BasicBlock(me.getPlaceholder(), blockPath); + fakeBlock.insertAfter( + GotoControlStatement(choice(Array.from(basicBlocks.keys()))) + ); + } + + // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators + basicBlocks.forEach((basicBlock) => { + if (chance(25)) { + var randomLabel = choice(Array.from(basicBlocks.keys())); + + // The `false` literal will be mangled + basicBlock.insertAfter( + new Template(` + if(false){ + {goto} + } + `).single({ + goto: GotoControlStatement(randomLabel), + }) + ); + } + }); + // DEAD CODE 3/3: Clone chunks but these chunks are never ran + const cloneChunkCount = getRandomInteger(1, 5); + for (let i = 0; i < cloneChunkCount; i++) { + let randomChunk = choice(Array.from(basicBlocks.values())); + + // Don't double define functions + let hasDeclaration = randomChunk.body.find((stmt) => { + return t.isDeclaration(stmt); + }); + + if (!hasDeclaration) { + let clonedChunk = new BasicBlock( + me.getPlaceholder(), + randomChunk.parentPath + ); + + randomChunk.body + .map((x) => t.cloneNode(x)) + .forEach((node) => { + clonedChunk.insertAfter(node); + }); + } + } + } const topLevelNames = new Set(); @@ -447,38 +717,59 @@ export default ({ Plugin }: PluginArg): PluginObj => { const { stateValues: currentStateValues } = basicBlock; // Wrap the statement in a Babel path to allow traversal - const visitor: Visitor = { - FunctionDeclaration: { - exit(fnPath) { - if (!callableMap.has(fnPath.node.id.name)) { + const outerFn = getParentFunctionOrProgram(basicBlock.parentPath); + + function isWithinSameFunction(path: NodePath) { + var fn = getParentFunctionOrProgram(path); + return fn.node === outerFn.node; + } + + var visitor: Visitor = { + BooleanLiteral: { + exit(boolPath) { + // Don't mangle booleans in debug mode + if ( + isDebug || + !mangleBooleanLiterals || + me.isSkipped(boolPath) + ) return; - } - var block = fnPath.find((p) => - p.isBlock() - ) as NodePath; + if (!isWithinSameFunction(boolPath)) return; + if (chance(50 + mangledLiteralsCreated)) return; - var oldName = fnPath.node.id.name; - var newName = me.getPlaceholder(); + mangledLiteralsCreated++; - fnPath.node.id.name = newName; + const index = getRandomInteger(0, stateVars.length - 1); + const stateVar = stateVars[index]; + const stateVarValue = currentStateValues[index]; - block.node.body.unshift( - t.expressionStatement( - t.assignmentExpression( - "=", - t.identifier(oldName), - t.identifier(newName) - ) - ) + const compareValue = choice([ + getRandomInteger(-250, 250), + stateVarValue, + ]); + const compareResult = stateVarValue === compareValue; + + const newExpression = t.binaryExpression( + boolPath.node.value === compareResult ? "==" : "!=", + t.identifier(stateVar), + n.numericLiteral(compareValue) ); + + ensureComputedExpression(boolPath); + boolPath.replaceWith(newExpression); }, }, // Mangle numbers with the state values NumericLiteral: { exit(numPath) { // Don't mangle numbers in debug mode - if (isDebug) return; + if ( + isDebug || + !mangleNumericalLiterals || + me.isSkipped(numPath) + ) + return; const num = numPath.node.value; if ( @@ -489,12 +780,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) return; - const numFnParent = getParentFunctionOrProgram(numPath); - if (!numFnParent.isProgram()) return; + if (!isWithinSameFunction(numPath)) return; + if (chance(50 + mangledLiteralsCreated)) return; - if (chance(50 + mangledNumericLiteralsCreated)) return; - - mangledNumericLiteralsCreated++; + mangledLiteralsCreated++; const index = getRandomInteger(0, stateVars.length - 1); const stateVar = stateVars[index]; @@ -506,7 +795,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const diff = t.binaryExpression( "+", t.identifier(stateVar), - t.numericLiteral(num - currentStateValues[index]) + me.skip(n.numericLiteral(num - currentStateValues[index])) ); ensureComputedExpression(numPath); @@ -516,153 +805,300 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }, - BindingIdentifier: { - exit(path) { - if (path.findParent((p) => p.isFunction())) return; + Identifier: { + exit(path: NodePath) { + const type = path.isReferenced() + ? "referenced" + : path.isBindingIdentifier() + ? "binding" + : null; + if (!type) return; - const binding = path.scope.getBinding(path.node.name); + var binding = basicBlock.scope.getBinding(path.node.name); if (!binding) return; - if (!basicBlock.options.topLevel) { + if ( + binding.kind === "var" || + binding.kind === "let" || + binding.kind === "const" + ) { + } else { return; } - if (!callableMap.has(path.node.name)) { - topLevelNames.add(path.node.name); - } + var scopeManager = scopeToScopeManager.get(binding.scope); + if (!scopeManager) return; + if (scopeManager.preserveNames.has(path.node.name)) return; - // Variable declaration -> Assignment expression - var variableDeclaration = path.findParent((p) => - p.isVariableDeclaration() - ) as NodePath; - if (!variableDeclaration) return; + let newName = scopeManager.getNewName(path.node.name); - var wrapInExpressionStatement = true; + const memberExpression: t.Expression = + scopeManager.getMemberExpression(newName); - var forChild = variableDeclaration.find( - (p) => - (p.parentPath?.isForStatement() && - p.parentKey === "init") || - (p.parentPath?.isFor() && p.parentKey === "left") - ); - if (forChild) { - wrapInExpressionStatement = false; - } + scopeManager.isNotUsed = false; - ok(variableDeclaration.node.declarations.length === 1); + if (type === "binding") { + if ( + path.key === "id" && + path.parentPath.isFunctionDeclaration() + ) { + var asFunctionExpression = t.cloneNode( + path.parentPath.node + ) as t.Node as t.FunctionExpression; + asFunctionExpression.type = "FunctionExpression"; - let identifier = t.cloneNode( - variableDeclaration.node.declarations[0].id - ); + path.parentPath.replaceWith( + t.expressionStatement( + t.assignmentExpression( + "=", + memberExpression, + asFunctionExpression + ) + ) + ); + return; + } else if ( + path.key === "id" && + path.parentPath.isClassDeclaration() + ) { + var asClassExpression = t.cloneNode( + path.parentPath.node + ) as t.Node as t.ClassExpression; + asClassExpression.type = "ClassExpression"; + + path.parentPath.replaceWith( + t.expressionStatement( + t.assignmentExpression( + "=", + memberExpression, + asClassExpression + ) + ) + ); + return; + } else { + var variableDeclaration = path.find((p) => + p.isVariableDeclaration() + ) as NodePath; + if (variableDeclaration) { + ok(variableDeclaration.node.declarations.length === 1); + + const first = + variableDeclaration.get("declarations")[0]; + const id = first.get("id"); + + const init = first.get("init"); + + var newExpression: t.Node = id.node; + + if (init.node) { + newExpression = t.assignmentExpression( + "=", + id.node, + init.node + ); + } - let replacement: t.Node = identifier as t.Expression; - // Only add name=value if the variable is initialized OR not in a for loop - if ( - variableDeclaration.node.declarations[0].init || - !forChild - ) { - replacement = t.assignmentExpression( - "=", - identifier, - variableDeclaration.node.declarations[0].init || - t.identifier("undefined") - ); + if ( + variableDeclaration.key !== "init" && + variableDeclaration.key !== "left" + ) { + newExpression = t.expressionStatement( + newExpression as t.Expression + ); + } else { + } + + variableDeclaration.replaceWith(newExpression); + path.replaceWith(memberExpression); + + return; + } else { + //ok(false, "Binding not found"); + } + } } - // Most times we want to wrap in an expression statement - // var a = 1; -> a = 1; - if (wrapInExpressionStatement) { - replacement = t.expressionStatement(replacement); + if (isStrictIdentifier(path)) { + return; } + path.replaceWith(memberExpression); + }, + }, + + // Top-level returns set additional flag to indicate that the function has returned + ReturnStatement: { + exit(path) { + var functionParent = path.getFunctionParent(); + if ( + !functionParent || + functionParent.get("body") !== blockPath + ) + return; - // Replace variable declaration with assignment expression statement - variableDeclaration.replaceWith(replacement); + const returnArgument = + path.node.argument || t.identifier("undefined"); + + path.node.argument = new Template(` + (${didReturnVar} = true, {returnArgument}) + `).expression({ returnArgument }); }, }, + // goto() calls are replaced with state updates and break statements CallExpression: { exit(path) { if ( t.isIdentifier(path.node.callee) && - path.node.callee.name === "ControlStatement" + path.node.callee.name === gotoFunctionName ) { - const metadata = (path.node as any).metadata as Metadata; - ok(metadata); - - const { label, type } = metadata; - ok(["goto"].includes(type)); + const [labelNode] = path.node.arguments; - switch (type) { - case "goto": - const { stateValues: newStateValues } = - basicBlocks.get(label); + ok(t.isStringLiteral(labelNode)); + const label = labelNode.value; - const assignments = []; + const jumpBlock = basicBlocks.get(label); + ok(jumpBlock, "Label not found: " + label); - for (let i = 0; i < stateVars.length; i++) { - const oldValue = currentStateValues[i]; - const newValue = newStateValues[i]; - if (oldValue === newValue) continue; // No diff needed if the value doesn't change + const { + stateValues: newStateValues, + totalState: newTotalState, + } = jumpBlock; - let assignment = t.assignmentExpression( - "=", - t.identifier(stateVars[i]), - t.numericLiteral(newValue) - ); + const assignments = []; - if (!isDebug) { - // Use diffs to create confusing code - assignment = t.assignmentExpression( - "+=", - t.identifier(stateVars[i]), - t.numericLiteral(newValue - oldValue) - ); - } + for (let i = 0; i < stateVars.length; i++) { + const oldValue = currentStateValues[i]; + const newValue = newStateValues[i]; - assignments.push(assignment); - } + // console.log(oldValue, newValue); + if (oldValue === newValue) continue; // No diff needed if the value doesn't change - path.parentPath - .replaceWith( - t.expressionStatement( - t.sequenceExpression(assignments) - ) - )[0] - .skip(); + let assignment = t.assignmentExpression( + "=", + t.identifier(stateVars[i]), + n.numericLiteral(newValue) + ); + + if (!isDebug && addRelativeAssignments) { + // Use diffs to create confusing code + assignment = t.assignmentExpression( + "+=", + t.identifier(stateVars[i]), + n.numericLiteral(newValue - oldValue) + ); + } + + assignments.push(assignment); + } - // Debugging information - // console.log("Path:", path); - // console.log("ParentPath:", path.parentPath); + // Add debug label + if (isDebug) { + assignments.unshift( + t.stringLiteral("Goto " + newTotalState) + ); + } - path.insertAfter(breakStatement()); + path.parentPath + .replaceWith( + t.expressionStatement(t.sequenceExpression(assignments)) + )[0] + .skip(); - break; - } + // Add break after updating state variables + path.insertAfter(breakStatement()); } }, }, }; - traverse(t.file(t.program(basicBlock.body)), visitor); + basicBlock.thisPath.traverse(visitor); } let switchCases: t.SwitchCase[] = []; let blocks = Array.from(basicBlocks.values()); - if (!isDebug) { + if (!isDebug && addFakeTests) { shuffle(blocks); } for (const block of blocks) { if (block.label === endLabel) { - ok(block.body.length === 0); + // ok(block.body.length === 0); continue; } - const tests = [t.numericLiteral(block.totalState)]; + let test: t.Expression = n.numericLiteral(block.totalState); + + // Add complex tests + if (!isDebug && addComplexTests && chance(25)) { + // Create complex test expressions for each switch case + + // case STATE+X: + var stateVarIndex = getRandomInteger(0, stateVars.length); + + var stateValues = block.stateValues; + var difference = stateValues[stateVarIndex] - block.totalState; + + var conditionNodes: t.Expression[] = []; + var alreadyConditionedItems = new Set(); - if (!isDebug) { - // Add some random numbers to confuse the switch statement + // This code finds clash conditions and adds them to 'conditionNodes' array + Array.from(basicBlocks.keys()).forEach((label) => { + if (label !== block.label) { + var labelStates = basicBlocks.get(label).stateValues; + var totalState = labelStates.reduce((a, b) => a + b, 0); + + if (totalState === labelStates[stateVarIndex] - difference) { + var differentIndex = labelStates.findIndex( + (v, i) => v !== stateValues[i] + ); + if (differentIndex !== -1) { + var expressionAsString = + stateVars[differentIndex] + + "!=" + + labelStates[differentIndex]; + if (!alreadyConditionedItems.has(expressionAsString)) { + alreadyConditionedItems.add(expressionAsString); + + conditionNodes.push( + t.binaryExpression( + "!=", + t.identifier(stateVars[differentIndex]), + n.numericLiteral(labelStates[differentIndex]) + ) + ); + } + } else { + conditionNodes.push( + t.binaryExpression( + "!=", + t.cloneNode(discriminant), + n.numericLiteral(totalState) + ) + ); + } + } + } + }); + + // case STATE!=Y && STATE+X + test = t.binaryExpression( + "-", + t.identifier(stateVars[stateVarIndex]), + n.numericLiteral(difference) + ); + + // Use the 'conditionNodes' to not cause state clashing issues + conditionNodes.forEach((conditionNode) => { + test = t.logicalExpression("&&", conditionNode, test); + }); + } + + const tests = [test]; + + if (!isDebug && addFakeTests) { + // Add fake tests for (let i = 0; i < getRandomInteger(1, 3); i++) { - tests.push(t.numericLiteral(statIntGen.generate())); + tests.push(n.numericLiteral(stateIntGen.generate())); } shuffle(tests); @@ -670,14 +1106,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { const lastTest = tests.pop(); - for (var test of tests) { + for (const test of tests) { switchCases.push(t.switchCase(test, [])); } - switchCases.push(t.switchCase(lastTest, block.body)); + switchCases.push(t.switchCase(lastTest, block.thisPath.node.body)); } - if (!isDebug) { + if (!isDebug && addFakeTests) { // A random test can be 'default' choice(switchCases).test = null; } @@ -699,7 +1135,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { t.binaryExpression( "!==", t.cloneNode(discriminant), - t.numericLiteral(endTotalState) + n.numericLiteral(endTotalState) ), t.blockStatement([switchStatement]) ); @@ -716,53 +1152,98 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), ]; - function createBasicBlockFunctionExpression(label: string) { - return t.functionExpression( - null, - [t.restElement(t.identifier(argVar))], - t.blockStatement([ - t.returnStatement( - t.callExpression( - t.memberExpression( - t.identifier(mainFnName), - t.stringLiteral("call"), - true - ), - [ - t.thisExpression(), - ...basicBlocks - .get(label) - .stateValues.map((stateValue) => - t.numericLiteral(stateValue) - ), - t.identifier(argVar), - ] + var parametersNames: string[] = [...stateVars, argVar, scopeVar]; + var parameters = parametersNames.map((name) => t.identifier(name)); + + for (var [ + originalFnName, + fnLabel, + basicBlock, + fn, + ] of functionExpressions) { + const { scopeManager } = basicBlock; + const { stateValues } = basicBlocks.get(fnLabel); + + const argumentsRestName = me.getPlaceholder(); + + var argumentsNodes = []; + for (var parameterName of parametersNames) { + if (stateVars.includes(parameterName)) { + argumentsNodes.push( + n.numericLiteral( + stateValues[stateVars.indexOf(parameterName)] ) + ); + } else if (parameterName === argVar) { + argumentsNodes.push(t.identifier(argumentsRestName)); + } else if (parameterName === scopeVar) { + argumentsNodes.push(scopeManager.getObjectExpression(fnLabel)); + } else { + ok(false); + } + } + + Object.assign( + fn, + new Template(` + (function (...${argumentsRestName}){ + ${ + isDebug + ? `"Calling ${originalFnName}, Label: ${fnLabel}";` + : "" + } + return {callExpression} + }) + + `).expression({ + callExpression: t.callExpression( + t.identifier(mainFnName), + argumentsNodes ), - ]) + }) ); } const mainFnDeclaration = t.functionDeclaration( t.identifier(mainFnName), - [ - ...stateVars.map((stateVar) => t.identifier(stateVar)), - t.identifier(argVar), - ], + parameters, t.blockStatement([whileStatement]) ); + var startProgramExpression = t.callExpression( + t.identifier(mainFnName), + [ + ...startStateValues.map((stateValue) => + n.numericLiteral(stateValue) + ), + t.identifier("undefined"), + basicBlocks + .get(startLabel) + .scopeManager.getObjectExpression(startLabel), + ] + ); + + var resultVar = me.getPlaceholder(); + var allowReturns = blockPath.find((p) => p.isFunction()); + + const stateProgramStatements = new Template(` + var ${didReturnVar}; + var ${resultVar} = {startProgramExpression}; + ${ + allowReturns + ? ` + if(${didReturnVar}){ + return ${resultVar}; + }` + : "" + } + `).compile({ startProgramExpression: startProgramExpression }); + blockPath.node.body = [ ...prependNodes, ...variableDeclarations, mainFnDeclaration, - t.expressionStatement( - t.callExpression(t.identifier(mainFnName), [ - ...startStateValues.map((stateValue) => - t.numericLiteral(stateValue) - ), - ]) - ), + ...stateProgramStatements, ]; // Reset all bindings here diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index 6d85039..d791eef 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -13,6 +13,7 @@ import { isVariableFunctionIdentifier, } from "../utils/function-utils"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; +import { numericLiteral } from "../utils/node"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Dispatcher); @@ -244,7 +245,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnLengthProperties.push( t.objectProperty( t.stringLiteral(newName), - t.numericLiteral(fnLength) + numericLiteral(fnLength) ) ); } @@ -253,14 +254,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { const newBody = [...originalFn.body.body]; ok(Array.isArray(newBody)); - newBody.unshift( - t.variableDeclaration("var", [ - t.variableDeclarator( - t.arrayPattern([...originalFn.params]), - t.identifier(payloadName) - ), - ]) - ); + // Unpack parameters + if (originalFn.params.length > 0) { + newBody.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern([...originalFn.params]), + t.identifier(payloadName) + ), + ]) + ); + } const functionExpression = t.functionExpression( null, @@ -282,7 +286,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { const dispatcher = new Template(` function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) { - var output, fns = {objectExpression}; + var output; + var fns = {objectExpression}; if(flagArg === "${keys.clearPayload}") { ${payloadName} = []; diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 3add7c6..47264cc 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -4,6 +4,7 @@ import { ok } from "assert"; import { PluginArg } from "../plugin"; import { Order } from "../../order"; import { ensureComputedExpression, prepend } from "../../utils/ast-utils"; +import { numericLiteral } from "../../utils/node"; function fail(): never { throw new Error("Assertion failed"); @@ -19,7 +20,7 @@ const createLiteral = (value: LiteralValue) => { return t.stringLiteral(value); case "number": - return t.numericLiteral(value); + return numericLiteral(value); case "boolean": return t.booleanLiteral(value); @@ -49,7 +50,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const createMemberExpression = (index) => { return t.memberExpression( t.identifier(arrayName), - t.numericLiteral(index), + numericLiteral(index), true ); }; diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index dfbabf7..d1785f7 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -44,7 +44,18 @@ export default ({ Plugin }: PluginArg): PluginObj => { functionExpression.type = "FunctionExpression"; functionExpression.id = null; - functionPath.node.params.push(t.identifier(functionName)); + var identifier = t.identifier(functionName); + functionPath.node.params.push(identifier); + + var paramPath = functionPath.get("params").at(-1); + + // Update binding to point to new path + const binding = functionPath.scope.getBinding(functionName); + if (binding) { + binding.kind = "param"; + binding.path = paramPath; + binding.identifier = identifier; + } prepend( functionPath, @@ -133,7 +144,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { switch (insertionMethod) { case "functionParameter": - var param: t.Pattern | t.Identifier = t.identifier(name); + var identifier = t.identifier(name); + + var param: t.Pattern | t.Identifier = identifier; if (allowDefaultParamValue && defaultParamValue) { param = t.assignmentPattern(param, defaultParamValue); } @@ -142,7 +155,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { var paramPath = functionPath.get("params").at(-1); - functionPath.scope.registerBinding("param", paramPath); + // Update binding to point to new path + const binding = functionPath.scope.getBinding(name); + if (binding) { + binding.kind = "param"; + binding.path = paramPath; + binding.identifier = identifier; + } + break; case "variableDeclaration": var block = path.findParent((path) => diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 161795b..43030eb 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -105,6 +105,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) continue; } + const binding = scope.bindings[identifierName]; if (renamedBindingIdentifiers.has(binding.identifier)) continue; renamedBindingIdentifiers.add(binding.identifier); @@ -118,6 +119,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { names.push(newName); } + // me.log("Renaming", identifierName, "to", newName); scope.rename(identifierName, newName); // Extra Class Declaration scope preserve logic needed diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 878aff4..0747440 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -8,6 +8,7 @@ import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplat import { prepend, prependProgram } from "../utils/ast-utils"; import ControlObject from "../utils/ControlObject"; import { ok } from "assert"; +import { numericLiteral } from "../utils/node"; export type PluginFunction = (pluginArg: PluginArg) => PluginObj; @@ -37,7 +38,7 @@ export class PluginInstance { return this.obfuscator.globalState; } - skip(path: NodePath | t.Node | NodePath[]) { + skip(path: NodePath | T | NodePath[]): T { if (Array.isArray(path)) { path.forEach((p) => this.skip(p)); } else { @@ -45,6 +46,8 @@ export class PluginInstance { let node = any.isNodeType ? any.node : any; (node as NodeSymbol)[SKIP] = this.order; + + return node; } } @@ -79,7 +82,7 @@ export class PluginInstance { t.expressionStatement( t.callExpression(t.identifier(this.setFunctionLengthName), [ t.identifier(path.node.id.name), - t.numericLiteral(originalLength), + numericLiteral(originalLength), ]) ) ); @@ -90,7 +93,7 @@ export class PluginInstance { path.replaceWith( t.callExpression(t.identifier(this.setFunctionLengthName), [ path.node, - t.numericLiteral(originalLength), + numericLiteral(originalLength), ]) ); } else { diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index dd66326..ce5499f 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -7,6 +7,7 @@ import { computeProbabilityMap } from "../probability"; import { getFunctionName, prepend } from "../utils/ast-utils"; import { NodeSymbol, SKIP, UNSAFE } from "../constants"; import { computeFunctionLength } from "../utils/function-utils"; +import { numericLiteral } from "../utils/node"; /** * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. @@ -198,7 +199,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { t.memberExpression( t.memberExpression( t.identifier(rgfArrayName), - t.numericLiteral(index), + numericLiteral(index), true ), t.stringLiteral("apply"), diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index 4f50b59..dd34aad 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -6,6 +6,8 @@ import { getRandomInteger } from "../utils/random-utils"; import Template from "../templates/template"; import { Order } from "../order"; import { isStaticValue } from "../utils/static-utils"; +import { NodeSymbol, PREDICTABLE } from "../constants"; +import { numericLiteral } from "../utils/node"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Shuffle); @@ -39,21 +41,24 @@ export default ({ Plugin }: PluginArg): PluginObj => { var block = path.find((p) => p.isBlock()) as NodePath; - var memberExpression = me.getControlObject(block).addProperty( - new Template( - ` - (function(arr) { - for (var i = 0; i < {shiftNode}; i++) { - arr.push(arr.shift()); - } - - return arr; - }) + var functionExpression = new Template( ` - ).expression({ - shiftNode: t.numericLiteral(shift), - }) - ); + (function(arr) { + for (var i = 0; i < {shiftNode}; i++) { + arr.push(arr.shift()); + } + return arr; + }) + ` + ).expression({ + shiftNode: numericLiteral(shift), + }); + + (functionExpression as NodeSymbol)[PREDICTABLE] = true; + + var memberExpression = me + .getControlObject(block) + .addProperty(functionExpression); path.replaceWith( t.callExpression(memberExpression, [ diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 3ba7a81..26ae1d6 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -4,6 +4,7 @@ import * as t from "@babel/types"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; import { ensureComputedExpression } from "../../utils/ast-utils"; +import { numericLiteral } from "../../utils/node"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringCompression); @@ -40,7 +41,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.replaceWith( t.callExpression(t.identifier(stringFn), [ - t.numericLiteral(index), + numericLiteral(index), ]) ); }, diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 07eb0fc..e383abb 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -19,6 +19,7 @@ import { } from "../../utils/random-utils"; import { CustomStringEncoding } from "../../options"; import { createDefaultStringEncoding } from "./encoding"; +import { numericLiteral } from "../../utils/node"; interface StringConcealingInterface { encodingImplementation: CustomStringEncoding; @@ -218,7 +219,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.replaceWith( t.callExpression( t.identifier(stringConcealingInterface.fnName), - [t.numericLiteral(index)] + [numericLiteral(index)] ) ); diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 429d780..fb6aff1 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -83,10 +83,15 @@ export function ensureComputedExpression(path: NodePath) { * - Variable Declaration * - Object property / method * - Class property / method + * - Program returns "[Program]" + * - Default returns "anonymous" * @param path * @returns */ export function getFunctionName(path: NodePath): string { + if (!path) return "null"; + if (path.isProgram()) return "[Program]"; + // Check function declaration/expression ID if ( (t.isFunctionDeclaration(path.node) || t.isFunctionExpression(path.node)) && @@ -97,7 +102,7 @@ export function getFunctionName(path: NodePath): string { // Check for containing variable declaration if ( - path.parentPath.isVariableDeclarator() && + path.parentPath?.isVariableDeclarator() && t.isIdentifier(path.parentPath.node.id) ) { return path.parentPath.node.id.name; @@ -109,12 +114,25 @@ export function getFunctionName(path: NodePath): string { } // Check for containing property in an object - if (path.parentPath.isObjectProperty() || path.parentPath.isClassProperty()) { + if ( + path.parentPath?.isObjectProperty() || + path.parentPath?.isClassProperty() + ) { var property = getObjectPropertyAsString(path.parentPath.node); if (property) return property; } - return "anonymous"; + var output = "anonymous"; + + if (path.isFunction()) { + if (path.node.generator) { + output += "*"; + } else if (path.node.async) { + output = "async " + output; + } + } + + return output; } export function isModuleImport(path: NodePath) { @@ -139,14 +157,15 @@ export function isModuleImport(path: NodePath) { export function getParentFunctionOrProgram( path: NodePath ): NodePath { + if (path.isProgram()) return path; + // Find the nearest function-like parent const functionOrProgramPath = path.findParent( (parentPath) => parentPath.isFunction() || parentPath.isProgram() - ); - - return functionOrProgramPath as NodePath; + ) as NodePath; - ok(false); + ok(functionOrProgramPath); + return functionOrProgramPath; } export function insertIntoNearestBlockScope( @@ -351,3 +370,78 @@ export function prependProgram( ok(program); return prepend(program, ...nodes); } + +/** + * Subset of BindingIdentifier, excluding non-defined assignment expressions. + * + * @example + * var a = 1; // true + * var {c} = {} // true + * function b() {} // true + * function d([e] = [], ...f) {} // true + * + * f = 0; // false + * f(); // false + * @param path + * @returns + */ +export function isDefiningIdentifier(path: NodePath) { + if (path.key === "id" && path.parentPath.isFunction()) return true; + if (path.key === "id" && path.parentPath.isClassDeclaration) return true; + + var maxTraversalPath = path.find( + (p) => + (p.key === "id" && p.parentPath?.isVariableDeclarator()) || + (p.listKey === "params" && p.parentPath?.isFunction()) || + (p.key === "param" && p.parentPath?.isCatchClause()) + ); + + if (!maxTraversalPath) return false; + + var cursor: NodePath = path; + while (cursor && cursor !== maxTraversalPath) { + if ( + cursor.parentPath.isObjectProperty() && + cursor.parentPath.parentPath?.isObjectPattern() + ) { + if (cursor.key !== "value") { + return false; + } + } else if (cursor.parentPath.isArrayPattern()) { + if (cursor.listKey !== "elements") { + return false; + } + } else if (cursor.parentPath.isRestElement()) { + if (cursor.key !== "argument") { + return false; + } + } else if (cursor.parentPath.isAssignmentPattern()) { + if (cursor.key !== "left") { + return false; + } + } else if (cursor.parentPath.isObjectPattern()) { + } else return false; + + cursor = cursor.parentPath; + } + + return true; +} + +/** + * @example + * function id() {} // true + * class id {} // true + * var id; // false + * @param path + * @returns + */ +export function isStrictIdentifier(path: NodePath): boolean { + if ( + path.key === "id" && + (path.parentPath.isFunction() || path.parentPath.isClass()) + ) + return true; + + return false; +} diff --git a/src/utils/node.ts b/src/utils/node.ts new file mode 100644 index 0000000..616c1fb --- /dev/null +++ b/src/utils/node.ts @@ -0,0 +1,18 @@ +import * as t from "@babel/types"; +import { ok } from "assert"; + +/** + * Handles both positive and negative numeric literals + * @param value + * @returns + */ +export function numericLiteral( + value: number +): t.NumericLiteral | t.UnaryExpression { + ok(typeof value === "number"); + + if (value < 0) { + return t.unaryExpression("-", t.numericLiteral(-value)); + } + return t.numericLiteral(value); +} diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 3d53c73..946264a 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -89,7 +89,6 @@ test("Variant #3: Properly handle while-loop", async () => { test("Variant #4: Properly handle break statements", async () => { var code = ` - var TEST_ARRAY = []; for ( var i =1; i < 50; i++ ) { @@ -214,24 +213,16 @@ test("Variant #8: Work when obfuscated multiple times", async () => { if(typeof i === "number") { // Always true array.push(i); - var filler1; - var filler2; - var filler3; + var a1, a2, a3; } - var filler1; - var filler2; - var filler3; + var b1, b2, b3; } - var filler1; - var filler2; - var filler3; + var c1, c2, c3; } - var filler1; - var filler2; - var filler3; + var d1, d2, d3; break; } @@ -465,7 +456,6 @@ test("Variant #14: Properly handle nested switch statements", async () => { TEST_ARRAY.push(1); var j = 0; - var i = 0; switch(j){ case 1: TEST_ARRAY.push(-1); break; case 2: TEST_ARRAY.push(-1); break; @@ -1088,44 +1078,35 @@ test("Variant #31: Don't break nested function calls", async () => { expect(TEST_OUTPUT).toStrictEqual(10); }); -test("Variant #32: Don't break same name function calls", async () => { +test("Variant #32: Skip blocks with redefined functions", async () => { var { code: output } = await JsConfuser.obfuscate( ` - var counter = 0; - - function a(){ - // Outer a called - counter *= 2; - } - - var i; - - for(i = 0; i < 10;) { - function a(){ - // Inner a called - counter += 1; - } - - a(); // Inner a - i++; + var counter = 0; + function a() { + counter *= 2; + } + var i; + for (i = 0; i < 10; ) { + function a() { + counter += 1; } - - a(); // Inner a, Outer a got renamed - - TEST_OUTPUT = counter; + a(); + i++; + } + TEST_OUTPUT = counter; `, { target: "node", controlFlowFlattening: true } ); - expect(output).toContain("while"); + expect(output).not.toContain("while"); var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(11); + expect(TEST_OUTPUT).toStrictEqual(10); }); -test("Variant #33: Don't break same name function declarations that are not ran", async () => { +test("Variant #33: Skip blocks with name collision", async () => { var { code: output } = await JsConfuser.obfuscate( ` var counter = 0; @@ -1155,7 +1136,7 @@ test("Variant #33: Don't break same name function declarations that are not ran" { target: "node", controlFlowFlattening: true } ); - expect(output).toContain("while"); + expect(output).not.toContain("while"); var TEST_OUTPUT; eval(output); @@ -1293,3 +1274,65 @@ test("Variant #36: Preserve modified global", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); + +test("Variant #37: Nested parameter across multiple functions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function myFunction() { + function fn1(nestedParam) { + function fn2() { + return nestedParam; + } + + ("FN1 Body"); + console.log(nestedParam); + + return fn2(); + } + + ("FN2 Body"); + + return fn1("Correct Value"); +} + +var x = myFunction(); + +TEST_OUTPUT = x; + `, + { + target: "node", + controlFlowFlattening: true, + } + ); + + expect(code).toContain("while"); + + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #38: Generator function with mangled numbers", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var array; + function* genFunction() { + array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + } + genFunction().next(); + TEST_OUTPUT = array; + `, + { + target: "node", + controlFlowFlattening: true, + } + ); + expect(code).toContain("while"); + + var TEST_OUTPUT; + + eval(code); + expect(TEST_OUTPUT).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +}); diff --git a/test/util/ast-utils.test.ts b/test/util/ast-utils.test.ts index 4ea31a1..26f7e49 100644 --- a/test/util/ast-utils.test.ts +++ b/test/util/ast-utils.test.ts @@ -1,5 +1,9 @@ +import { ok } from "assert"; import Obfuscator from "../../src/obfuscator"; -import { getFunctionName } from "../../src/utils/ast-utils"; +import { + getFunctionName, + isDefiningIdentifier, +} from "../../src/utils/ast-utils"; import traverse from "@babel/traverse"; describe("getFunctionName", () => { @@ -102,3 +106,98 @@ describe("getFunctionName", () => { expect(count).toStrictEqual(4); }); }); + +describe("isDefiningIdentifier", () => { + function getIdentifierPath(sourceCode: string, targetIdentifierName = "id") { + var ast = Obfuscator.parseCode(sourceCode); + var returnPath; + + traverse(ast, { + Identifier(path) { + if (path.node.name === targetIdentifierName) { + returnPath = path; + } + }, + }); + + ok(returnPath, `${targetIdentifierName} not found in ${sourceCode}`); + + return returnPath; + } + + test("Variant #1: True examples", () => { + ` + var id = 1 + let id = 1 + const id = 1 + function id(){} + class id{} + var {id} = {} + var [id] = [] + var {id = 1} = {} + var [id = 1] = [] + var {_: id} = {} + function f({id}){} + function f([id]){} + function f({id = 1}){} + function f([id = 1]){} + function f(...id){} + function f(...[{id = _}]){} + try{}catch(id){} + try{}catch({id}){} + try{}catch([{id}]){} + try{}catch({_: id}){} + for(var id in []){} + for(var id of []){} + ` + .split("\n") + .forEach((sourceCode) => { + if (!sourceCode.trim()) return; + + const path = getIdentifierPath(sourceCode); + var result = isDefiningIdentifier(path); + if (!result) { + throw new Error( + `Expected true, got false. Source code: ${sourceCode} ${path.key}` + ); + } + }); + }); + + test("Variant #2: False examples", () => { + ` + id; + id(); + id = 1; + id++; + delete id; + typeof id; + id instanceof Object; + {id: true} + var {id: _} = {}; + function f(_ = id){} + function f(_ = {id}){} + function f(_ = [id]){} + function f(_ = [...id]){} + for(id in []){} + for(id of []){} + for(id = 0; id < 1; id++){} + try{}catch({id: _}){} + try{}catch({_: _ = id}){} + try{}catch({id: _ = _}){} + try{}catch({_ = id}){} + ` + .split("\n") + .forEach((sourceCode) => { + if (!sourceCode.trim()) return; + + const path = getIdentifierPath(sourceCode); + var result = isDefiningIdentifier(path); + if (result) { + throw new Error( + `Expected false, got true. Source code: ${sourceCode} ${path.key}` + ); + } + }); + }); +}); From 594c9cc12cebc0b6766ebbc8b674f06a1374d2bb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 12 Sep 2024 19:20:19 -0400 Subject: [PATCH 033/103] Improve Control Flow Flattening, String Compression, Identifier Tracking, Tests --- CHANGELOG.md | 61 +- README.md | 53 +- docs/RenameVariables.md | 4 +- package.json | 11 +- src/constants.ts | 3 + src/obfuscator.ts | 12 +- src/options.ts | 7 + src/order.ts | 8 +- src/templates/bufferToStringTemplate.ts | 3 +- src/templates/stringCompressionTemplate.ts | 42 ++ src/templates/template.ts | 85 ++- src/transforms/controlFlowFlattening.ts | 612 +++++++++++++----- src/transforms/dispatcher.ts | 47 +- src/transforms/flatten.ts | 13 +- src/transforms/functionOutlining.ts | 1 + .../identifier/movedDeclarations.ts | 39 +- src/transforms/identifier/renameVariables.ts | 97 ++- src/transforms/minify.ts | 26 +- src/transforms/pack.ts | 150 +++++ src/transforms/plugin.ts | 8 +- src/transforms/preparation.ts | 58 ++ src/transforms/rgf.ts | 4 +- src/transforms/string/stringCompression.ts | 58 +- src/transforms/variableMasking.ts | 9 +- src/utils/ControlObject.ts | 2 +- src/utils/ast-utils.ts | 48 ++ src/utils/function-utils.ts | 21 - src/utils/node.ts | 35 + src/validateOptions.ts | 1 + test/code/Cash.test.ts | 79 +-- test/code/Dynamic.test.ts | 31 +- test/code/ES6.test.ts | 45 +- test/code/StrictMode.test.js | 21 +- test/options.test.ts | 274 ++++---- ...nLength.test.ts => functionLength.test.ts} | 6 +- test/semantics/lastExpression.test.ts | 2 + .../newFeatures.test.ts} | 0 test/templates/template.test.ts | 345 +++++----- .../controlFlowFlattening.test.ts | 105 +-- test/transforms/dispatcher.test.ts | 1 + .../identifier/renameVariables.test.ts | 61 ++ test/transforms/lock/integrity.test.ts | 1 + test/transforms/pack.test.ts | 49 ++ test/transforms/preparation.test.ts | 62 ++ test/transforms/rgf.test.ts | 1 + .../string/stringCompression.test.ts | 66 +- .../string/stringConcealing.test.ts | 30 + test/util/ast-utils.test.ts | 3 + 48 files changed, 1925 insertions(+), 775 deletions(-) create mode 100644 src/templates/stringCompressionTemplate.ts create mode 100644 src/transforms/pack.ts rename test/semantics/{preserveFunctionLength.test.ts => functionLength.test.ts} (83%) rename test/{code/NewFeatures.test.ts => semantics/newFeatures.test.ts} (100%) create mode 100644 test/transforms/pack.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bb80f81..8f4ec42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,57 @@ # `2.0.0-alpha.0` 2.0 Rewrite Alpha 🎉 -- Complete rewrite of JS-Confuser using Babel! +**⚠️ Warning: This an alpha release. This version is not stable and the likelihood of encountering bugs is significantly higher.** -- - Improved API Interface **⚠️ Breaking change** +### Complete rewrite of JS-Confuser using Babel! -- - Renamed `Stack` to `Variable Masking` **⚠️ Breaking change** +**⚠️ Breaking changes** -- - Custom String Encoding / Custom Lock Code +- Revamped API Interface -- - Added `Function Outlining` Learn more here +- - JSConfuser.obfuscate() resolves to an object -- - Added `Rename Labels` Learn more here +| Property | Type | Description | +| --- | --- | --- | +| `code` | `string` | The obfuscated code. | + +- Renamed `Stack` to `Variable Masking` + +**2.0 Changes** + +- Added Custom String Encoding and Custom Lock Code options + +- Added `Function Outlining` Learn more here + +- Added `Rename Labels` Learn more here + +- Added `Pack` Learn more here + +- RGF no longers uses `new Function` instead uses `eval` + +- Improved code transforms! + +- Improved `Control Flow Flattening` + +- - Obfuscates the [Call Graph](https://en.wikipedia.org/wiki/Call_graph) + +- - Now supports lexical bindings (`let`, `const`, `class`) + +- - `with () { }` statement obfuscation + +- Improved `Minify` -- - RGF no longers uses `new Function` instead uses `eval` +- - Removes unused variables and functions -- - Improved code transforms! +- Improved `Moved Declaration` ability to move variables as unused function parameters -- - Improved `Minify` +- Improved `String` transforms -- - - Removes unused variables and functions now +- - [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) are now obfuscated (First converted into equivalent String Literal) -- - Improved `Moved Declaration`'s ability to move variables as unused function parameters +- - [Regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions) are now obfuscated (First converted into equivalent RegExp() constructor calls) + +- - `String Compression` now uses zlib decompression ([Pako](https://github.com/nodeca/pako)) **Removed features** @@ -31,7 +61,14 @@ - Removed `Shuffle`'s Hash option -- Removed `Indent` option. [`@babel/generator`](https://www.npmjs.com/package/@babel/generator) does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. Be mindful if you have `integrity` or `selfDefending` enabled, as you should not alter the obfuscated code. +- Removed `Indent` option. [`@babel/generator`](https://www.npmjs.com/package/@babel/generator) does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. Be mindful if you have `Integrity` or `Self Defending` enabled, as you should not alter the obfuscated code. + + +**JS-Confuser.com Revamp** + +A new UI for JS-Confuser.com, featuring an advanced playground and documentation pages. + +The previous version will remain available: [old--confuser.netlify.com](https://old--confuser.netlify.app/) # `1.7.3` Tamper Protection diff --git a/README.md b/README.md index 7eca297..0b744d5 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,34 @@ JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ [![NPM](https://img.shields.io/badge/NPM-%23000000.svg?style=for-the-badge&logo=npm&logoColor=white)](https://npmjs.com/package/js-confuser) [![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/MichaelXF/js-confuser) [![Netlify](https://img.shields.io/badge/netlify-%23000000.svg?style=for-the-badge&logo=netlify&logoColor=#00C7B7)](https://js-confuser.com) -## Key features -- Variable renaming -- Control Flow obfuscation -- String concealing -- Function obfuscation -- Locks (domainLock, date) -- [Detect changes to source code](https://github.com/MichaelXF/js-confuser/blob/master/docs/Integrity.md) +**The official documentation for this project has moved to [`JS-Confuser.com`](https://js-confuser.com)**: +- [Getting Started](https://new--confuser.netlify.app/docs) + +- - [What is Obfuscation?](https://new--confuser.netlify.app/docs/getting-started/what-is-obfuscation) + +- - [Playground](https://new--confuser.netlify.app/docs/getting-started/playground) + +- - [Installation](https://new--confuser.netlify.app/docs/getting-started/installation) + +- - [Usage](https://new--confuser.netlify.app/docs/getting-started/usage) -## Installation +- - [FAQ](https://new--confuser.netlify.app/docs/getting-started/faq) + +- [Options](https://new--confuser.netlify.app/docs) + +- [Presets](https://new--confuser.netlify.app/docs) + +## API Usage + +### Installation ```bash $ npm install js-confuser ``` -## Usage +### Usage ```js var JsConfuser = require("js-confuser"); @@ -52,26 +63,6 @@ var AF59rI,ZgbbeaU,WDgj3I,gpR2qG,Ox61sk,pTNPNpX;AF59rI=[60,17,25,416,22,23,83,26 */ ``` -## Documentation - -Read the [documentation](https://js-confuser.com/docs) for everything to know about JS-Confuser, including: - -- [Getting Started](https://new--confuser.netlify.app/docs) - -- - [What is Obfuscation?](https://new--confuser.netlify.app/docs/getting-started/what-is-obfuscation) - -- - [Playground](https://new--confuser.netlify.app/docs/getting-started/playground) - -- - [Installation](https://new--confuser.netlify.app/docs/getting-started/installation) - -- - [Usage](https://new--confuser.netlify.app/docs/getting-started/usage) - -- - [FAQ](https://new--confuser.netlify.app/docs/getting-started/faq) - -- [Options](https://new--confuser.netlify.app/docs) - -- [Presets](https://new--confuser.netlify.app/docs) - ## Bug report @@ -80,3 +71,7 @@ Please [open an issue](https://github.com/MichaelXF/js-confuser/issues) with the ## Feature request Please [open an issue](https://github.com/MichaelXF/js-confuser/issues) and be descriptive. Don't submit any PRs until approved. + +## License + +MIT License \ No newline at end of file diff --git a/docs/RenameVariables.md b/docs/RenameVariables.md index a1a7c53..78f42d8 100644 --- a/docs/RenameVariables.md +++ b/docs/RenameVariables.md @@ -2,9 +2,9 @@ Determines if variables should be renamed. (`true/false`) -Option name: `controlFlowFlattening` +Option name: `renameVariables` -Option values: `true/false` +Option values: `true/false/Function` ```js // Input diff --git a/package.json b/package.json index d170283..b5f478b 100644 --- a/package.json +++ b/package.json @@ -20,18 +20,21 @@ ], "author": "MichaelXF", "license": "MIT", + "dependencies": { + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/traverse": "^7.25.6", + "@babel/types": "^7.25.6", + "pako": "^2.1.0" + }, "devDependencies": { "@babel/cli": "^7.24.8", - "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.6", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.24.7", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", - "@babel/traverse": "^7.25.6", - "@babel/types": "^7.25.6", "@eslint/js": "^9.9.0", "@types/babel__core": "^7.20.5", "@types/jest": "^29.5.12", diff --git a/src/constants.ts b/src/constants.ts index 78993cb..3a8f8a9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -33,12 +33,15 @@ export const FN_LENGTH = Symbol("fnLength"); export const CONTROL_OBJECTS = Symbol("controlObjects"); +export const NO_RENAME = Symbol("noRename"); + export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; [SKIP]?: boolean | number; [FN_LENGTH]?: number; [CONTROL_OBJECTS]?: ControlObject[]; + [NO_RENAME]?: string; } /** diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 1b58733..9e85bfa 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -37,6 +37,7 @@ import { assertScopeIntegrity } from "./utils/scope-utils"; import opaquePredicates from "./transforms/opaquePredicates"; import minify from "./transforms/minify"; import functionOutlining from "./transforms/functionOutlining"; +import pack from "./transforms/pack"; export const DEFAULT_OPTIONS: ObfuscateOptions = { target: "node", @@ -160,6 +161,7 @@ export default class Obfuscator { ast: babel.types.File, options?: { profiler?: ProfilerCallback; + disablePack?: boolean; } ): babel.types.File { for (let i = 0; i < this.plugins.length; i++) { @@ -184,6 +186,10 @@ export default class Obfuscator { } } + if (this.options.pack && !options?.disablePack) { + ast = pack(ast, this); + } + return ast; } @@ -191,7 +197,7 @@ export default class Obfuscator { // Parse the source code into an AST let ast = Obfuscator.parseCode(sourceCode); - this.obfuscateAST(ast); + ast = this.obfuscateAST(ast); // Generate the transformed code from the modified AST with comments removed and compacted output const code = this.generateCode(ast); @@ -232,6 +238,10 @@ export default class Obfuscator { const { code } = generate(ast, { comments: false, // Remove comments minified: compact, + // jsescOption: { + // String Encoding using Babel + // escapeEverything: true, + // }, }); return code; diff --git a/src/options.ts b/src/options.ts index 3de9340..693e4d7 100644 --- a/src/options.ts +++ b/src/options.ts @@ -502,4 +502,11 @@ export interface ObfuscateOptions { astScrambler?: boolean; variableConcealing?: ProbabilityMap; + + /** + * Packs the output code into a single `Function()` call. (`true/false`) + * + * Designed to escape strict mode constraints. + */ + pack?: boolean; } diff --git a/src/order.ts b/src/order.ts index 6f6b5ee..5198995 100644 --- a/src/order.ts +++ b/src/order.ts @@ -22,8 +22,6 @@ export enum Order { OpaquePredicates = 13, - FunctionOutlining = 14, - StringSplitting = 16, StringConcealing = 17, @@ -34,11 +32,13 @@ export enum Order { DuplicateLiteralsRemoval = 22, - Shuffle = 24, + Shuffle = 23, + + ControlFlowFlattening = 24, MovedDeclarations = 25, - ControlFlowFlattening = 26, + FunctionOutlining = 26, RenameLabels = 27, diff --git a/src/templates/bufferToStringTemplate.ts b/src/templates/bufferToStringTemplate.ts index 864ee29..ba800bd 100644 --- a/src/templates/bufferToStringTemplate.ts +++ b/src/templates/bufferToStringTemplate.ts @@ -16,7 +16,8 @@ export const BufferToStringTemplate = new Template(` var result = []; return function (array) { - var codePt, byte1; + var codePt; + var byte1; var buffLen = array["length"]; result["length"] = 0; diff --git a/src/templates/stringCompressionTemplate.ts b/src/templates/stringCompressionTemplate.ts new file mode 100644 index 0000000..ec61e4d --- /dev/null +++ b/src/templates/stringCompressionTemplate.ts @@ -0,0 +1,42 @@ +import Template from "./template"; + +export const StringCompressionTemplate = new Template( + ` +var {stringFn}; + +(function (){ + {GetGlobalTemplate} + + var __globalObject = {getGlobalFnName}() || {}; + var _atob = __globalObject["atob"]; + var _Uint8Array = __globalObject["Uint8Array"]; + + function convertBase64ToArrayBuffer(base64) { + var binaryString = _atob(base64); // Decode Base64 + var len = binaryString.length; + var uint8Array = new _Uint8Array(len); + + // Convert binary string back into Uint8Array + for (var i = 0; i < len; i++) { + uint8Array[i] = binaryString["charCodeAt"](i); + } + return uint8Array; + } + + var compressedBuffer = convertBase64ToArrayBuffer({stringValue}); + var utf8String = pako["inflate"](compressedBuffer, { to: 'string' }); + var stringArray = utf8String["split"]({stringDelimiter}); + + {stringFn} = function(index){ + return stringArray[index]; + } +})(); +` +); + +export const PakoInflateMin = ` +var pako = {}; + +/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ +!function(e,t){ t(pako) }(this,(function(e){"use strict";var t=function(e,t,i,n){for(var a=65535&e|0,r=e>>>16&65535|0,o=0;0!==i;){i-=o=i>2e3?2e3:i;do{r=r+(a=a+t[n++]|0)|0}while(--o);a%=65521,r%=65521}return a|r<<16|0},i=new Uint32Array(function(){for(var e,t=[],i=0;i<256;i++){e=i;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[i]=e}return t}()),n=function(e,t,n,a){var r=i,o=a+n;e^=-1;for(var s=a;s>>8^r[255&(e^t[s])];return-1^e},a=16209,r=function(e,t){var i,n,r,o,s,l,f,d,h,c,u,w,b,m,k,_,v,g,p,y,x,E,R,A,Z=e.state;i=e.next_in,R=e.input,n=i+(e.avail_in-5),r=e.next_out,A=e.output,o=r-(t-e.avail_out),s=r+(e.avail_out-257),l=Z.dmax,f=Z.wsize,d=Z.whave,h=Z.wnext,c=Z.window,u=Z.hold,w=Z.bits,b=Z.lencode,m=Z.distcode,k=(1<>>=g=v>>>24,w-=g,0===(g=v>>>16&255))A[r++]=65535&v;else{if(!(16&g)){if(0==(64&g)){v=b[(65535&v)+(u&(1<>>=g,w-=g),w<15&&(u+=R[i++]<>>=g=v>>>24,w-=g,!(16&(g=v>>>16&255))){if(0==(64&g)){v=m[(65535&v)+(u&(1<l){e.msg="invalid distance too far back",Z.mode=a;break e}if(u>>>=g,w-=g,y>(g=r-o)){if((g=y-g)>d&&Z.sane){e.msg="invalid distance too far back",Z.mode=a;break e}if(x=0,E=c,0===h){if(x+=f-g,g2;)A[r++]=E[x++],A[r++]=E[x++],A[r++]=E[x++],p-=3;p&&(A[r++]=E[x++],p>1&&(A[r++]=E[x++]))}else{x=r-y;do{A[r++]=A[x++],A[r++]=A[x++],A[r++]=A[x++],p-=3}while(p>2);p&&(A[r++]=A[x++],p>1&&(A[r++]=A[x++]))}break}}break}}while(i>3,u&=(1<<(w-=p<<3))-1,e.next_in=i,e.next_out=r,e.avail_in=i=1&&0===B[A];A--);if(Z>A&&(Z=A),0===A)return a[r++]=20971520,a[r++]=20971520,c.bits=1,0;for(R=1;R0&&(0===e||1!==A))return-1;for(N[1]=0,x=1;x852||2===e&&U>592)return 1;for(;;){v=x-T,h[E]+1<_?(g=0,p=h[E]):h[E]>=_?(g=C[h[E]-_],p=I[h[E]-_]):(g=96,p=0),u=1<>T)+(w-=u)]=v<<24|g<<16|p|0}while(0!==w);for(u=1<>=1;if(0!==u?(D&=u-1,D+=u):D=0,E++,0==--B[x]){if(x===A)break;x=t[i+h[E]]}if(x>Z&&(D&m)!==b){for(0===T&&(T=Z),k+=R,O=1<<(S=x-T);S+T852||2===e&&U>592)return 1;a[b=D&m]=Z<<24|S<<16|k-r|0}}return 0!==D&&(a[k+D]=x-T<<24|64<<16|0),c.bits=Z,0},c={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8},u=c.Z_FINISH,w=c.Z_BLOCK,b=c.Z_TREES,m=c.Z_OK,k=c.Z_STREAM_END,_=c.Z_NEED_DICT,v=c.Z_STREAM_ERROR,g=c.Z_DATA_ERROR,p=c.Z_MEM_ERROR,y=c.Z_BUF_ERROR,x=c.Z_DEFLATED,E=16180,R=16190,A=16191,Z=16192,S=16194,T=16199,O=16200,U=16206,D=16209,I=function(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)};function B(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}var N,C,z=function(e){if(!e)return 1;var t=e.state;return!t||t.strm!==e||t.mode16211?1:0},F=function(e){if(z(e))return v;var t=e.state;return e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=E,t.last=0,t.havedict=0,t.flags=-1,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new Int32Array(852),t.distcode=t.distdyn=new Int32Array(592),t.sane=1,t.back=-1,m},L=function(e){if(z(e))return v;var t=e.state;return t.wsize=0,t.whave=0,t.wnext=0,F(e)},M=function(e,t){var i;if(z(e))return v;var n=e.state;return t<0?(i=0,t=-t):(i=5+(t>>4),t<48&&(t&=15)),t&&(t<8||t>15)?v:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=i,n.wbits=t,L(e))},H=function(e,t){if(!e)return v;var i=new B;e.state=i,i.strm=e,i.window=null,i.mode=E;var n=M(e,t);return n!==m&&(e.state=null),n},j=!0,K=function(e){if(j){N=new Int32Array(512),C=new Int32Array(32);for(var t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(h(1,e.lens,0,288,N,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;h(2,e.lens,0,32,C,0,e.work,{bits:5}),j=!1}e.lencode=N,e.lenbits=9,e.distcode=C,e.distbits=5},P=function(e,t,i,n){var a,r=e.state;return null===r.window&&(r.wsize=1<=r.wsize?(r.window.set(t.subarray(i-r.wsize,i),0),r.wnext=0,r.whave=r.wsize):((a=r.wsize-r.wnext)>n&&(a=n),r.window.set(t.subarray(i-n,i-n+a),r.wnext),(n-=a)?(r.window.set(t.subarray(i-n,i),0),r.wnext=n,r.whave=r.wsize):(r.wnext+=a,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,a.check=n(a.check,te,2,0),B=0,N=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&B)<<8)+(B>>8))%31){e.msg="incorrect header check",a.mode=D;break}if((15&B)!==x){e.msg="unknown compression method",a.mode=D;break}if(N-=4,J=8+(15&(B>>>=4)),0===a.wbits&&(a.wbits=J),J>15||J>a.wbits){e.msg="invalid window size",a.mode=D;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16182;case 16182:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>8&255,te[2]=B>>>16&255,te[3]=B>>>24&255,a.check=n(a.check,te,4,0)),B=0,N=0,a.mode=16183;case 16183:for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>8),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16184;case 16184:if(1024&a.flags){for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&((L=a.length)>d&&(L=d),L&&(a.head&&(J=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(o.subarray(l,l+L),J)),512&a.flags&&4&a.wrap&&(a.check=n(a.check,o,L,l)),d-=L,l+=L,a.length-=L),a.length))break e;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===d)break e;L=0;do{J=o[l+L++],a.head&&J&&a.length<65536&&(a.head.name+=String.fromCharCode(J))}while(J&&L>9&1,a.head.done=!0),e.adler=a.check=0,a.mode=A;break;case 16189:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>=7&N,N-=7&N,a.mode=U;break}for(;N<3;){if(0===d)break e;d--,B+=o[l++]<>>=1)){case 0:a.mode=16193;break;case 1:if(K(a),a.mode=T,i===b){B>>>=2,N-=2;break e}break;case 2:a.mode=16196;break;case 3:e.msg="invalid block type",a.mode=D}B>>>=2,N-=2;break;case 16193:for(B>>>=7&N,N-=7&N;N<32;){if(0===d)break e;d--,B+=o[l++]<>>16^65535)){e.msg="invalid stored block lengths",a.mode=D;break}if(a.length=65535&B,B=0,N=0,a.mode=S,i===b)break e;case S:a.mode=16195;case 16195:if(L=a.length){if(L>d&&(L=d),L>c&&(L=c),0===L)break e;s.set(o.subarray(l,l+L),f),d-=L,l+=L,c-=L,f+=L,a.length-=L;break}a.mode=A;break;case 16196:for(;N<14;){if(0===d)break e;d--,B+=o[l++]<>>=5,N-=5,a.ndist=1+(31&B),B>>>=5,N-=5,a.ncode=4+(15&B),B>>>=4,N-=4,a.nlen>286||a.ndist>30){e.msg="too many length or distance symbols",a.mode=D;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,N-=3}for(;a.have<19;)a.lens[ie[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,V={bits:a.lenbits},Q=h(0,a.lens,0,19,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid code lengths set",a.mode=D;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,a.lens[a.have++]=G;else{if(16===G){for($=j+2;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,0===a.have){e.msg="invalid bit length repeat",a.mode=D;break}J=a.lens[a.have-1],L=3+(3&B),B>>>=2,N-=2}else if(17===G){for($=j+3;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=3,N-=3}else{for($=j+7;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=7,N-=7}if(a.have+L>a.nlen+a.ndist){e.msg="invalid bit length repeat",a.mode=D;break}for(;L--;)a.lens[a.have++]=J}}if(a.mode===D)break;if(0===a.lens[256]){e.msg="invalid code -- missing end-of-block",a.mode=D;break}if(a.lenbits=9,V={bits:a.lenbits},Q=h(1,a.lens,0,a.nlen,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid literal/lengths set",a.mode=D;break}if(a.distbits=6,a.distcode=a.distdyn,V={bits:a.distbits},Q=h(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,V),a.distbits=V.bits,Q){e.msg="invalid distances set",a.mode=D;break}if(a.mode=T,i===b)break e;case T:a.mode=O;case O:if(d>=6&&c>=258){e.next_out=f,e.avail_out=c,e.next_in=l,e.avail_in=d,a.hold=B,a.bits=N,r(e,F),f=e.next_out,s=e.output,c=e.avail_out,l=e.next_in,o=e.input,d=e.avail_in,B=a.hold,N=a.bits,a.mode===A&&(a.back=-1);break}for(a.back=0;Y=(ee=a.lencode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,a.length=G,0===Y){a.mode=16205;break}if(32&Y){a.back=-1,a.mode=A;break}if(64&Y){e.msg="invalid literal/length code",a.mode=D;break}a.extra=15&Y,a.mode=16201;case 16201:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;Y=(ee=a.distcode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,64&Y){e.msg="invalid distance code",a.mode=D;break}a.offset=G,a.extra=15&Y,a.mode=16203;case 16203:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){e.msg="invalid distance too far back",a.mode=D;break}a.mode=16204;case 16204:if(0===c)break e;if(L=F-c,a.offset>L){if((L=a.offset-L)>a.whave&&a.sane){e.msg="invalid distance too far back",a.mode=D;break}L>a.wnext?(L-=a.wnext,M=a.wsize-L):M=a.wnext-L,L>a.length&&(L=a.length),H=a.window}else H=s,M=f-a.offset,L=a.length;L>c&&(L=c),c-=L,a.length-=L;do{s[f++]=H[M++]}while(--L);0===a.length&&(a.mode=O);break;case 16205:if(0===c)break e;s[f++]=a.length,c--,a.mode=O;break;case U:if(a.wrap){for(;N<32;){if(0===d)break e;d--,B|=o[l++]<=252?6:V>=248?5:V>=240?4:V>=224?3:V>=192?2:1;Q[254]=Q[254]=1;var $=function(e){if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(e);var t,i,n,a,r,o=e.length,s=0;for(a=0;a>>6,t[r++]=128|63&i):i<65536?(t[r++]=224|i>>>12,t[r++]=128|i>>>6&63,t[r++]=128|63&i):(t[r++]=240|i>>>18,t[r++]=128|i>>>12&63,t[r++]=128|i>>>6&63,t[r++]=128|63&i);return t},ee=function(e,t){var i,n,a=t||e.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(e.subarray(0,t));var r=new Array(2*a);for(n=0,i=0;i4)r[n++]=65533,i+=s-1;else{for(o&=2===s?31:3===s?15:7;s>1&&i1?r[n++]=65533:o<65536?r[n++]=o:(o-=65536,r[n++]=55296|o>>10&1023,r[n++]=56320|1023&o)}}}return function(e,t){if(t<65534&&e.subarray&&J)return String.fromCharCode.apply(null,e.length===t?e:e.subarray(0,t));for(var i="",n=0;ne.length&&(t=e.length);for(var i=t-1;i>=0&&128==(192&e[i]);)i--;return i<0||0===i?t:i+Q[e[i]]>t?i:t},ie={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"};var ne=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};var ae=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1},re=Object.prototype.toString,oe=c.Z_NO_FLUSH,se=c.Z_FINISH,le=c.Z_OK,fe=c.Z_STREAM_END,de=c.Z_NEED_DICT,he=c.Z_STREAM_ERROR,ce=c.Z_DATA_ERROR,ue=c.Z_MEM_ERROR;function we(e){this.options=W({chunkSize:65536,windowBits:15,to:""},e||{});var t=this.options;t.raw&&t.windowBits>=0&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(t.windowBits>=0&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),t.windowBits>15&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new ne,this.strm.avail_out=0;var i=Y.inflateInit2(this.strm,t.windowBits);if(i!==le)throw new Error(ie[i]);if(this.header=new ae,Y.inflateGetHeader(this.strm,this.header),t.dictionary&&("string"==typeof t.dictionary?t.dictionary=$(t.dictionary):"[object ArrayBuffer]"===re.call(t.dictionary)&&(t.dictionary=new Uint8Array(t.dictionary)),t.raw&&(i=Y.inflateSetDictionary(this.strm,t.dictionary))!==le))throw new Error(ie[i])}function be(e,t){var i=new we(t);if(i.push(e),i.err)throw i.msg||ie[i.err];return i.result}we.prototype.push=function(e,t){var i,n,a,r=this.strm,o=this.options.chunkSize,s=this.options.dictionary;if(this.ended)return!1;for(n=t===~~t?t:!0===t?se:oe,"[object ArrayBuffer]"===re.call(e)?r.input=new Uint8Array(e):r.input=e,r.next_in=0,r.avail_in=r.input.length;;){for(0===r.avail_out&&(r.output=new Uint8Array(o),r.next_out=0,r.avail_out=o),(i=Y.inflate(r,n))===de&&s&&((i=Y.inflateSetDictionary(r,s))===le?i=Y.inflate(r,n):i===ce&&(i=de));r.avail_in>0&&i===fe&&r.state.wrap>0&&0!==e[r.next_in];)Y.inflateReset(r),i=Y.inflate(r,n);switch(i){case he:case ce:case de:case ue:return this.onEnd(i),this.ended=!0,!1}if(a=r.avail_out,r.next_out&&(0===r.avail_out||i===fe))if("string"===this.options.to){var l=te(r.output,r.next_out),f=r.next_out-l,d=ee(r.output,l);r.next_out=f,r.avail_out=o-f,f&&r.output.set(r.output.subarray(l,l+f),0),this.onData(d)}else this.onData(r.output.length===r.next_out?r.output:r.output.subarray(0,r.next_out));if(i!==le||0!==a){if(i===fe)return i=Y.inflateEnd(this.strm),this.onEnd(i),this.ended=!0,!0;if(0===r.avail_in)break}}return!0},we.prototype.onData=function(e){this.chunks.push(e)},we.prototype.onEnd=function(e){e===le&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=q(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg};var me=we,ke=be,_e=function(e,t){return(t=t||{}).raw=!0,be(e,t)},ve=be,ge=c,pe={Inflate:me,inflate:ke,inflateRaw:_e,ungzip:ve,constants:ge};e.Inflate=me,e.constants=ge,e.default=pe,e.inflate=ke,e.inflateRaw=_e,e.ungzip=ve,Object.defineProperty(e,"__esModule",{value:!0})})); + `; diff --git a/src/templates/template.ts b/src/templates/template.ts index 17e4eae..f73a88f 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -3,6 +3,7 @@ import { parse } from "@babel/parser"; import traverse from "@babel/traverse"; import { NodePath } from "@babel/traverse"; import { ok } from "assert"; +import { getRandomString } from "../utils/random-utils"; export interface TemplateVariables { [varName: string]: @@ -18,6 +19,8 @@ export default class Template { private templates: string[]; private defaultVariables: TemplateVariables; private requiredVariables: Set; + private astVariableMappings: Map; + private astIdentifierPrefix = "__t_" + getRandomString(6); constructor(...templates: string[]) { this.templates = templates; @@ -62,12 +65,20 @@ export default class Template { this.templates[Math.floor(Math.random() * this.templates.length)]; let output = template; + this.astVariableMappings = new Map(); + Object.keys(allVariables).forEach((name) => { const bracketName = `{${name.replace("$", "\\$")}}`; let value = allVariables[name] as string; if (this.isASTVariable(value)) { - value = name; + let astIdentifierName = this.astVariableMappings.get(name); + if (typeof astIdentifierName === "undefined") { + astIdentifierName = this.astIdentifierPrefix + name; + this.astVariableMappings.set(name, astIdentifierName); + } + + value = astIdentifierName; } const reg = new RegExp(bracketName, "g"); @@ -82,40 +93,64 @@ export default class Template { } private interpolateAST(ast: babelTypes.Node, variables: TemplateVariables) { - const allVariables = { ...this.defaultVariables, ...variables }; + if (this.astVariableMappings.size === 0) return; - const astNames = new Set( - Object.keys(allVariables).filter((name) => { - return this.isASTVariable(allVariables[name]); - }) - ); + const allVariables = { ...this.defaultVariables, ...variables }; + const template = this; + + // Reverse the lookup map + // Before {name -> __t_m4H6nk_name} + // After {__t_m4H6nk_name -> name} + const reverseMappings = new Map(); + this.astVariableMappings.forEach((value, key) => { + reverseMappings.set(value, key); + }); - if (astNames.size === 0) return; + const insertedVariables = new Set(); traverse(ast, { Identifier(path: NodePath) { - const name = path.node.name; - if (astNames.has(name)) { - let value = allVariables[name]; - if (typeof value === "function") { - value = value(); - } + const idName = path.node.name; + if (!idName.startsWith(template.astIdentifierPrefix)) return; + const variableName = reverseMappings.get(idName); - if (value instanceof Template) { - value = value.compile(allVariables); - } + if (typeof variableName === "undefined") { + ok(false, `Variable ${idName} not found in mappings`); + } + + let value = allVariables[variableName]; + let isSingleUse = true; // Hard-coded nodes are deemed 'single use' + if (typeof value === "function") { + value = value(); + isSingleUse = false; + } - if (!Array.isArray(value)) { - path.replaceWith(value as babelTypes.Node); - } else { - path.replaceWithMultiple(value as babelTypes.Node[]); + if (value instanceof Template) { + value = value.compile(allVariables); + isSingleUse = false; + } + + // Duplicate node check + if (isSingleUse) { + if (insertedVariables.has(variableName)) { + ok(false, "Duplicate node inserted for variable: " + variableName); } + insertedVariables.add(variableName); } + + // Insert new nodes + if (!Array.isArray(value)) { + path.replaceWith(value as babelTypes.Node); + } else { + path.replaceWithMultiple(value as babelTypes.Node[]); + } + + path.skip(); }, }); } - compile(variables: TemplateVariables = {}): babelTypes.Statement[] { + file(variables: TemplateVariables = {}): babelTypes.File { const { output } = this.interpolateTemplate(variables); let file: babelTypes.File; @@ -132,6 +167,12 @@ export default class Template { this.interpolateAST(file, variables); + return file; + } + + compile(variables: TemplateVariables = {}): babelTypes.Statement[] { + const file = this.file(variables); + return file.program.body; } diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index d3c5f4f..9df74bf 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1,17 +1,17 @@ import { PluginObj } from "@babel/core"; -import { NodePath, Scope, Visitor } from "@babel/traverse"; +import traverse, { NodePath, Scope, Visitor } from "@babel/traverse"; import { PluginArg } from "./plugin"; import { Order } from "../order"; import { computeProbabilityMap } from "../probability"; import { ensureComputedExpression, - getFunctionName, getParentFunctionOrProgram, isDefiningIdentifier, - isStrictIdentifier, + isStrictMode, + isVariableIdentifier, } from "../utils/ast-utils"; import * as t from "@babel/types"; -import * as n from "../utils/node"; +import { numericLiteral, deepClone } from "../utils/node"; import Template from "../templates/template"; import { chance, @@ -22,7 +22,7 @@ import { import { IntGen } from "../utils/IntGen"; import { ok } from "assert"; import { NameGen } from "../utils/NameGen"; -import { NodeSymbol, UNSAFE } from "../constants"; +import { NodeSymbol, UNSAFE, NO_RENAME, PREDICTABLE } from "../constants"; /** * Breaks functions into DAGs (Directed Acyclic Graphs) @@ -40,16 +40,21 @@ import { NodeSymbol, UNSAFE } from "../constants"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.ControlFlowFlattening); + // in Debug mode, the output is much easier to read const isDebug = false; - const flattenIfStatements = true; - const addRelativeAssignments = true; - const addDeadCode = true; - const addFakeTests = true; - const addComplexTests = true; - const mangleNumericalLiterals = true; - const mangleBooleanLiterals = true; + const flattenIfStatements = true; // Converts IF-statements into equivalent 'goto style of code' + const flattenFunctionDeclarations = true; // Converts Function Declarations into equivalent 'goto style of code' + const addRelativeAssignments = true; // state += (NEW_STATE - CURRENT_STATE) + const addDeadCode = true; // add fakes chunks of code + const addFakeTests = true; // case 100: case 490: case 510: ... + const addComplexTests = true; // case s != 49 && s - 10: + const mangleNumericalLiterals = true; // 50 => state + X + const mangleBooleanLiterals = true; // true => state == X + const addWithStatement = true; // Disabling not supported yet const cffPrefix = me.getPlaceholder(); + + // Amount of blocks changed by Control Flow Flattening let cffCounter = 0; return { @@ -70,6 +75,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { blockPath = fnBlockPath; } + // Don't apply to strict mode blocks + const strictModeEnforcingBlock = programOrFunctionPath.find((path) => + isStrictMode(path as NodePath) + ); + if (strictModeEnforcingBlock) return; + // Must be at least 3 statements or more if (blockPath.node.body.length < 3) return; @@ -157,17 +168,31 @@ export default ({ Plugin }: PluginArg): PluginObj => { const prefix = cffPrefix + "_" + cffCounter++; - const mainFnName = prefix + "_main"; + const withIdentifier = (suffix) => { + var name; + if (isDebug) { + name = prefix + "_" + suffix; + } else { + name = me.obfuscator.nameGen.generate(); + } - const scopeVar = prefix + "_scope"; + var id = t.identifier(name); + + (id as NodeSymbol)[NO_RENAME] = name; + return id; + }; + + const mainFnName = withIdentifier("main"); + + const scopeVar = withIdentifier("scope"); const stateVars = new Array(isDebug ? 1 : getRandomInteger(2, 5)) .fill("") - .map((_, i) => `${prefix}_state_${i}`); + .map((_, i) => withIdentifier(`state_${i}`)); - const argVar = prefix + "_arg"; + const argVar = withIdentifier("_arg"); - const didReturnVar = prefix + "_return"; + const didReturnVar = withIdentifier("return"); const basicBlocks = new Map(); @@ -180,20 +205,42 @@ export default ({ Plugin }: PluginArg): PluginObj => { const scopeNameGen = new NameGen(me.options.identifierGenerator); + const withProperty = isDebug ? "with" : scopeNameGen.generate(); + const withDiscriminant = new Template( + `${scopeVar.name}["${withProperty}"]` + ).expression(); + + const resetWithProperty = isDebug + ? "resetWith" + : scopeNameGen.generate(); + class ScopeManager { isNotUsed = true; + requiresInitializing = true; nameMap = new Map(); - nameGen = new NameGen(me.options.identifierGenerator); + nameGen = addWithStatement + ? me.obfuscator.nameGen + : new NameGen(me.options.identifierGenerator); preserveNames = new Set(); - getNewName(name: string) { + findUsed(): ScopeManager { + if (this.isNotUsed) return this.parent?.findUsed(); + + return this; + } + + getNewName(name: string, originalNode?: t.Node) { if (!this.nameMap.has(name)) { let newName = this.nameGen.generate(); if (isDebug) { newName = "_" + name; } + if ((originalNode as NodeSymbol)?.[NO_RENAME]) { + newName = name; + } + this.nameMap.set(name, newName); // console.log( @@ -210,20 +257,49 @@ export default ({ Plugin }: PluginArg): PluginObj => { return this.nameMap.get(name); } - getMemberExpression(name: string) { + getScopeObject() { return t.memberExpression( - t.memberExpression( - t.identifier(scopeVar), - t.stringLiteral(this.propertyName), - true - ), + deepClone(scopeVar), + t.stringLiteral(this.propertyName), + true + ); + } + + getInitializingStatement() { + return t.expressionStatement( + t.assignmentExpression( + "=", + this.getScopeObject(), + this.getInitializingObjectExpression() + ) + ); + } + + getInitializingObjectExpression() { + return isDebug + ? new Template(` + ({ + identity: "${this.propertyName}" + }) + `).expression() + : t.objectExpression([]); + } + + getMemberExpression(name: string) { + var memberExpression = t.memberExpression( + this.getScopeObject(), t.stringLiteral(name), true ); + + return memberExpression; } propertyName: string; - constructor(public scope: Scope) { + constructor( + public scope: Scope, + public initializingBasicBlock: BasicBlock + ) { this.propertyName = isDebug ? "_" + scopeCounter++ : scopeNameGen.generate(); @@ -243,7 +319,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (parentScopeManager) { propertyMap[parentScopeManager.propertyName] = t.memberExpression( - t.identifier(scopeVar), + deepClone(scopeVar), t.stringLiteral(parentScopeManager.propertyName), true ); @@ -252,13 +328,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { cursor = cursor.parent; } - propertyMap[refreshScope.propertyName] = isDebug - ? new Template(` - ({ - identity: "${refreshScope.propertyName}" - }) - `).expression() - : t.objectExpression([]); + propertyMap[refreshScope.propertyName] = + refreshScope.getInitializingObjectExpression(); var properties: t.ObjectProperty[] = []; for (var key in propertyMap) { @@ -290,6 +361,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { class BasicBlock { totalState: number; stateValues: number[]; + allowWithDiscriminant = true; + bestWithDiscriminant: ScopeManager; + + get withDiscriminant() { + if (!this.allowWithDiscriminant) return null; + + return this.bestWithDiscriminant; + } private createPath() { const newPath = NodePath.get({ @@ -328,6 +407,83 @@ export default ({ Plugin }: PluginArg): PluginObj => { return this.thisPath.node.body; } + createFalsePredicate(): t.Expression { + var predicate = this.createPredicate(); + if (predicate.value) { + // Make predicate false + return t.unaryExpression("!", predicate.node); + } + return predicate.node; + } + + createTruePredicate(): t.Expression { + var predicate = this.createPredicate(); + if (!predicate.value) { + // Make predicate true + return t.unaryExpression("!", predicate.node); + } + return predicate.node; + } + + createPredicate() { + var stateVarIndex = getRandomInteger(0, stateVars.length); + var stateValue = this.stateValues[stateVarIndex]; + var compareValue = choice([ + stateValue, + getRandomInteger(-250, 250), + ]); + + var operator: t.BinaryExpression["operator"] = choice([ + "==", + "!=", + "<", + ">", + ]); + var compareResult; + switch (operator) { + case "==": + compareResult = stateValue === compareValue; + break; + case "!=": + compareResult = stateValue !== compareValue; + break; + case "<": + compareResult = stateValue < compareValue; + break; + case ">": + compareResult = stateValue > compareValue; + break; + } + + return { + node: t.binaryExpression( + operator, + deepClone(stateVars[stateVarIndex]), + numericLiteral(compareValue) + ), + value: compareResult, + }; + } + + identifier( + identifierName: string, + scopeManager = this.scopeManager + ) { + if ( + this.withDiscriminant && + this.withDiscriminant === scopeManager + ) { + var id = t.identifier(identifierName); + (id as NodeSymbol)[NO_RENAME] = identifierName; + me.skip(id); + return id; + } + + return scopeManager.getMemberExpression(identifierName); + } + + initializedScope: ScopeManager; + constructor( public label: string, public parentPath: NodePath @@ -375,9 +531,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (!scopeToScopeManager.has(this.scope)) { scopeToScopeManager.set( this.scope, - new ScopeManager(this.scope) + new ScopeManager(this.scope, this) ); } + + this.initializedScope = this.scopeManager; } } @@ -390,6 +548,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const endLabel = me.getPlaceholder(); let currentBasicBlock = new BasicBlock(startLabel, blockPath); + currentBasicBlock.allowWithDiscriminant = false; const gotoFunctionName = "GOTO__" + @@ -453,6 +612,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { let isIllegal = false; if ( + !flattenFunctionDeclarations || statement.node.async || statement.node.generator || (statement.node as NodeSymbol)[UNSAFE] @@ -513,6 +673,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); flattenIntoBasicBlocks(blockStatement); + scopeToScopeManager.get(statement.scope).requiresInitializing = + false; + basicBlocks.get(fnLabel).allowWithDiscriminant = false; // Debug label if (isDebug) { @@ -534,7 +697,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { t.variableDeclaration("var", [ t.variableDeclarator( t.arrayPattern(statement.node.params), - t.identifier(argVar) + deepClone(argVar) ), ]) ); @@ -597,6 +760,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { }); flattenIntoBasicBlocks(consequent); + currentBasicBlock.initializedScope = + oldBasicBlock.scopeManager; if (alternate.isBlockStatement()) { endCurrentBasicBlock({ @@ -656,6 +821,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { nextBlockPath: defaultBlockPath, }); + basicBlocks.get(endLabel).allowWithDiscriminant = false; + + // Add with / reset with logic + basicBlocks.get(startLabel).body.unshift( + new Template(` + ${scopeVar.name}["${resetWithProperty}"] = function(newStateValues){ + ${scopeVar.name}["${withProperty}"] = undefined; + {arrayPattern} = newStateValues + } + `).single({ + arrayPattern: t.arrayPattern(deepClone(stateVars)), + }) + ); + if (!isDebug && addDeadCode) { // DEAD CODE 1/3: Add fake chunks that are never reached const fakeChunkCount = getRandomInteger(1, 5); @@ -663,12 +842,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { // These chunks just jump somewhere random, they are never executed // so it could contain any code const fakeBlock = new BasicBlock(me.getPlaceholder(), blockPath); - fakeBlock.insertAfter( - GotoControlStatement(choice(Array.from(basicBlocks.keys()))) - ); + let fakeJump; + do { + fakeJump = choice(Array.from(basicBlocks.keys())); + } while (fakeJump === fakeBlock.label); + + fakeBlock.insertAfter(GotoControlStatement(fakeJump)); } // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators + // "irreducible control flow" basicBlocks.forEach((basicBlock) => { if (chance(25)) { var randomLabel = choice(Array.from(basicBlocks.keys())); @@ -676,11 +859,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { // The `false` literal will be mangled basicBlock.insertAfter( new Template(` - if(false){ - {goto} + if({predicate}){ + {goto} } `).single({ goto: GotoControlStatement(randomLabel), + predicate: basicBlock.createFalsePredicate(), }) ); } @@ -701,17 +885,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { randomChunk.parentPath ); - randomChunk.body - .map((x) => t.cloneNode(x)) + randomChunk.thisNode.body + .map((x) => deepClone(x)) .forEach((node) => { + if (node.type === "EmptyStatement") return; clonedChunk.insertAfter(node); }); } } } - const topLevelNames = new Set(); - // Remap 'GotoStatement' to actual state assignments and Break statements for (const basicBlock of basicBlocks.values()) { const { stateValues: currentStateValues } = basicBlock; @@ -752,8 +935,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { const newExpression = t.binaryExpression( boolPath.node.value === compareResult ? "==" : "!=", - t.identifier(stateVar), - n.numericLiteral(compareValue) + deepClone(stateVar), + numericLiteral(compareValue) ); ensureComputedExpression(boolPath); @@ -794,8 +977,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { const diff = t.binaryExpression( "+", - t.identifier(stateVar), - me.skip(n.numericLiteral(num - currentStateValues[index])) + deepClone(stateVar), + me.skip(numericLiteral(num - currentStateValues[index])) ); ensureComputedExpression(numPath); @@ -807,15 +990,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { Identifier: { exit(path: NodePath) { - const type = path.isReferenced() - ? "referenced" - : path.isBindingIdentifier() - ? "binding" - : null; - if (!type) return; + if (!isVariableIdentifier(path)) return; + if (me.isSkipped(path)) return; + + const identifierName = path.node.name; + if (identifierName === gotoFunctionName) return; - var binding = basicBlock.scope.getBinding(path.node.name); - if (!binding) return; + var binding = basicBlock.scope.getBinding(identifierName); + if (!binding) { + return; + } if ( binding.kind === "var" || @@ -826,23 +1010,28 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } + // console.log("No binding found for " + identifierName); + var scopeManager = scopeToScopeManager.get(binding.scope); if (!scopeManager) return; - if (scopeManager.preserveNames.has(path.node.name)) return; + if (scopeManager.preserveNames.has(identifierName)) return; - let newName = scopeManager.getNewName(path.node.name); + let newName = scopeManager.getNewName( + identifierName, + path.node + ); - const memberExpression: t.Expression = + let memberExpression: t.MemberExpression | t.Identifier = scopeManager.getMemberExpression(newName); scopeManager.isNotUsed = false; - if (type === "binding") { + if (path.isBindingIdentifier()) { if ( path.key === "id" && path.parentPath.isFunctionDeclaration() ) { - var asFunctionExpression = t.cloneNode( + var asFunctionExpression = deepClone( path.parentPath.node ) as t.Node as t.FunctionExpression; asFunctionExpression.type = "FunctionExpression"; @@ -861,7 +1050,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.key === "id" && path.parentPath.isClassDeclaration() ) { - var asClassExpression = t.cloneNode( + var asClassExpression = deepClone( path.parentPath.node ) as t.Node as t.ClassExpression; asClassExpression.type = "ClassExpression"; @@ -877,10 +1066,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); return; } else { - var variableDeclaration = path.find((p) => - p.isVariableDeclaration() + var variableDeclaration = path.find( + (p) => + p.isVariableDeclaration() || + p === basicBlock.parentPath ) as NodePath; - if (variableDeclaration) { + + if ( + variableDeclaration && + variableDeclaration.isVariableDeclaration() + ) { ok(variableDeclaration.node.declarations.length === 1); const first = @@ -891,22 +1086,23 @@ export default ({ Plugin }: PluginArg): PluginObj => { var newExpression: t.Node = id.node; - if (init.node) { + var isForInitializer = + (variableDeclaration.key === "init" || + variableDeclaration.key === "left") && + variableDeclaration.parentPath.isFor(); + + if (init.node || !isForInitializer) { newExpression = t.assignmentExpression( "=", id.node, - init.node + init.node || t.identifier("undefined") ); } - if ( - variableDeclaration.key !== "init" && - variableDeclaration.key !== "left" - ) { + if (!isForInitializer) { newExpression = t.expressionStatement( newExpression as t.Expression ); - } else { } variableDeclaration.replaceWith(newExpression); @@ -919,9 +1115,50 @@ export default ({ Plugin }: PluginArg): PluginObj => { } } - if (isStrictIdentifier(path)) { + if (isDefiningIdentifier(path)) { return; } + if (!path.container) return; + + var assignmentLeft = path.find( + (p) => + (p.key === "left" && + p.parentPath?.isAssignmentExpression()) || + p === basicBlock.parentPath + ); + if ( + assignmentLeft && + !assignmentLeft.parentPath?.isAssignmentExpression() + ) { + assignmentLeft = null; + } + + if ( + basicBlock.withDiscriminant && + basicBlock.withDiscriminant === scopeManager && + basicBlock.withDiscriminant.hasName(identifierName) + ) { + // console.log(identifierName, !!assignmentLeft); + if (assignmentLeft) { + // memberExpression = new Template(` + // typeof {identifierName} !== "undefined" ? {identifierName} : {memberExpression} + // `).expression({ + // memberExpression: memberExpression, + // identifierName: () => { + // var id = t.identifier(newName); + // (id as NodeSymbol)[NO_RENAME] = newName; + // me.skip(id); + // return id; + // }, + // }); + } else { + memberExpression = basicBlock.identifier( + newName, + scopeManager + ); + } + } + path.replaceWith(memberExpression); }, }, @@ -940,8 +1177,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.node.argument || t.identifier("undefined"); path.node.argument = new Template(` - (${didReturnVar} = true, {returnArgument}) - `).expression({ returnArgument }); + ({didReturnVar} = true, {returnArgument}) + `).expression({ + returnArgument, + didReturnVar: deepClone(didReturnVar), + }); }, }, @@ -965,31 +1205,66 @@ export default ({ Plugin }: PluginArg): PluginObj => { totalState: newTotalState, } = jumpBlock; - const assignments = []; + const assignments: t.Expression[] = []; + let needsIndividualAssignments = true; - for (let i = 0; i < stateVars.length; i++) { - const oldValue = currentStateValues[i]; - const newValue = newStateValues[i]; + if (jumpBlock.withDiscriminant) { + assignments.push( + t.assignmentExpression( + "=", + deepClone(withDiscriminant), + jumpBlock.withDiscriminant.getScopeObject() + ) + ); + } else if (basicBlock.withDiscriminant) { + assignments.push( + t.callExpression( + t.memberExpression( + deepClone(scopeVar), + t.stringLiteral(resetWithProperty), + true + ), + [ + t.arrayExpression( + newStateValues.map(numericLiteral) + ), + ] + ) + ); + needsIndividualAssignments = false; + } - // console.log(oldValue, newValue); - if (oldValue === newValue) continue; // No diff needed if the value doesn't change + if (needsIndividualAssignments) { + for (let i = 0; i < stateVars.length; i++) { + const oldValue = currentStateValues[i]; + const newValue = newStateValues[i]; - let assignment = t.assignmentExpression( - "=", - t.identifier(stateVars[i]), - n.numericLiteral(newValue) - ); + // console.log(oldValue, newValue); + if (oldValue === newValue) continue; // No diff needed if the value doesn't change - if (!isDebug && addRelativeAssignments) { - // Use diffs to create confusing code - assignment = t.assignmentExpression( - "+=", - t.identifier(stateVars[i]), - n.numericLiteral(newValue - oldValue) + const leftValue = jumpBlock.withDiscriminant + ? jumpBlock.withDiscriminant.getMemberExpression( + stateVars[i].name + ) + : deepClone(stateVars[i]); + + let assignment = t.assignmentExpression( + "=", + leftValue, + numericLiteral(newValue) ); - } - assignments.push(assignment); + if (!isDebug && addRelativeAssignments) { + // Use diffs to create confusing code + assignment = t.assignmentExpression( + "+=", + deepClone(stateVars[i]), + numericLiteral(newValue - oldValue) + ); + } + + assignments.push(assignment); + } } // Add debug label @@ -1015,6 +1290,34 @@ export default ({ Plugin }: PluginArg): PluginObj => { basicBlock.thisPath.traverse(visitor); } + // Select scope managers for the with statement + for (const basicBlock of basicBlocks.values()) { + basicBlock.bestWithDiscriminant = + basicBlock.initializedScope?.findUsed(); + + if (isDebug && basicBlock.withDiscriminant) { + basicBlock.body.unshift( + t.expressionStatement( + t.stringLiteral( + "With " + basicBlock.withDiscriminant.propertyName + ) + ) + ); + } + } + + // Add scope initializations: scope["_0"] = {identity: "_0"} + for (const scopeManager of scopeToScopeManager.values()) { + if (scopeManager.isNotUsed) continue; + if (!scopeManager.requiresInitializing) continue; + if (scopeManager.initializingBasicBlock.label === startLabel) + continue; + + scopeManager.initializingBasicBlock.body.unshift( + scopeManager.getInitializingStatement() + ); + } + let switchCases: t.SwitchCase[] = []; let blocks = Array.from(basicBlocks.values()); if (!isDebug && addFakeTests) { @@ -1026,7 +1329,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { continue; } - let test: t.Expression = n.numericLiteral(block.totalState); + let test: t.Expression = numericLiteral(block.totalState); // Add complex tests if (!isDebug && addComplexTests && chance(25)) { @@ -1053,7 +1356,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); if (differentIndex !== -1) { var expressionAsString = - stateVars[differentIndex] + + stateVars[differentIndex].name + "!=" + labelStates[differentIndex]; if (!alreadyConditionedItems.has(expressionAsString)) { @@ -1062,8 +1365,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { conditionNodes.push( t.binaryExpression( "!=", - t.identifier(stateVars[differentIndex]), - n.numericLiteral(labelStates[differentIndex]) + deepClone(stateVars[differentIndex]), + numericLiteral(labelStates[differentIndex]) ) ); } @@ -1071,8 +1374,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { conditionNodes.push( t.binaryExpression( "!=", - t.cloneNode(discriminant), - n.numericLiteral(totalState) + deepClone(discriminant), + numericLiteral(totalState) ) ); } @@ -1083,8 +1386,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { // case STATE!=Y && STATE+X test = t.binaryExpression( "-", - t.identifier(stateVars[stateVarIndex]), - n.numericLiteral(difference) + deepClone(stateVars[stateVarIndex]), + numericLiteral(difference) ); // Use the 'conditionNodes' to not cause state clashing issues @@ -1098,7 +1401,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (!isDebug && addFakeTests) { // Add fake tests for (let i = 0; i < getRandomInteger(1, 3); i++) { - tests.push(n.numericLiteral(stateIntGen.generate())); + tests.push(numericLiteral(stateIntGen.generate())); } shuffle(tests); @@ -1119,9 +1422,15 @@ export default ({ Plugin }: PluginArg): PluginObj => { } const discriminant = new Template(` - ${stateVars.join(" + ")} + ${stateVars.map((x) => x.name).join(" + ")} `).expression(); + traverse(t.program([t.expressionStatement(discriminant)]), { + Identifier(path) { + (path.node as NodeSymbol)[NO_RENAME] = path.node.name; + }, + }); + // Create a new SwitchStatement const switchStatement = t.labeledStatement( t.identifier(switchLabel), @@ -1134,26 +1443,26 @@ export default ({ Plugin }: PluginArg): PluginObj => { const whileStatement = t.whileStatement( t.binaryExpression( "!==", - t.cloneNode(discriminant), - n.numericLiteral(endTotalState) + deepClone(discriminant), + numericLiteral(endTotalState) ), - t.blockStatement([switchStatement]) + t.blockStatement([ + t.withStatement( + new Template( + `{withDiscriminant} || Object["create"](null)` + ).expression({ + withDiscriminant, + }), + t.blockStatement([switchStatement]) + ), + ]) ); - function variableDeclaration(name: string, value?: t.Expression) { - return t.variableDeclaration("var", [ - t.variableDeclarator(t.identifier(name), value), - ]); - } - - const variableDeclarations: t.Statement[] = [ - ...Array.from(topLevelNames).map((name) => - variableDeclaration(name) - ), - ]; + var parameters: t.Identifier[] = [...stateVars, argVar, scopeVar].map( + (id) => deepClone(id) + ); - var parametersNames: string[] = [...stateVars, argVar, scopeVar]; - var parameters = parametersNames.map((name) => t.identifier(name)); + var parametersNames: string[] = parameters.map((id) => id.name); for (var [ originalFnName, @@ -1168,15 +1477,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { var argumentsNodes = []; for (var parameterName of parametersNames) { - if (stateVars.includes(parameterName)) { - argumentsNodes.push( - n.numericLiteral( - stateValues[stateVars.indexOf(parameterName)] - ) - ); - } else if (parameterName === argVar) { + const stateIndex = stateVars + .map((x) => x.name) + .indexOf(parameterName); + if (stateIndex !== -1) { + argumentsNodes.push(numericLiteral(stateValues[stateIndex])); + } else if (parameterName === argVar.name) { argumentsNodes.push(t.identifier(argumentsRestName)); - } else if (parameterName === scopeVar) { + } else if (parameterName === scopeVar.name) { argumentsNodes.push(scopeManager.getObjectExpression(fnLabel)); } else { ok(false); @@ -1197,7 +1505,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { `).expression({ callExpression: t.callExpression( - t.identifier(mainFnName), + deepClone(mainFnName), argumentsNodes ), }) @@ -1205,45 +1513,45 @@ export default ({ Plugin }: PluginArg): PluginObj => { } const mainFnDeclaration = t.functionDeclaration( - t.identifier(mainFnName), + deepClone(mainFnName), parameters, t.blockStatement([whileStatement]) ); - var startProgramExpression = t.callExpression( - t.identifier(mainFnName), - [ - ...startStateValues.map((stateValue) => - n.numericLiteral(stateValue) - ), - t.identifier("undefined"), - basicBlocks - .get(startLabel) - .scopeManager.getObjectExpression(startLabel), - ] - ); + (mainFnDeclaration as NodeSymbol)[PREDICTABLE] = true; - var resultVar = me.getPlaceholder(); + var startProgramExpression = t.callExpression(deepClone(mainFnName), [ + ...startStateValues.map((stateValue) => numericLiteral(stateValue)), + t.identifier("undefined"), + basicBlocks + .get(startLabel) + .scopeManager.getObjectExpression(startLabel), + ]); + + var resultVar = withIdentifier("result"); var allowReturns = blockPath.find((p) => p.isFunction()); - const stateProgramStatements = new Template(` - var ${didReturnVar}; - var ${resultVar} = {startProgramExpression}; + const startProgramStatements = new Template(` + ${allowReturns ? `var {didReturnVar};` : ""} + var {resultVar} = {startProgramExpression}; ${ allowReturns ? ` - if(${didReturnVar}){ - return ${resultVar}; + if({didReturnVar}){ + return {resultVar}; }` : "" } - `).compile({ startProgramExpression: startProgramExpression }); + `).compile({ + startProgramExpression, + didReturnVar: () => deepClone(didReturnVar), + resultVar: () => deepClone(resultVar), + }); blockPath.node.body = [ ...prependNodes, - ...variableDeclarations, mainFnDeclaration, - ...stateProgramStatements, + ...startProgramStatements, ]; // Reset all bindings here diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index d791eef..4da3f9d 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -14,6 +14,7 @@ import { } from "../utils/function-utils"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { numericLiteral } from "../utils/node"; +import { prependProgram } from "../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.Dispatcher); @@ -21,18 +22,31 @@ export default ({ Plugin }: PluginArg): PluginObj => { const setFunctionLength = me.getPlaceholder("d_fnLength"); - let active = true; - return { visitor: { "Program|Function": { exit(_path) { - if (!active) return; - const blockPath = _path as NodePath; if (blockPath.isProgram()) { blockPath.scope.crawl(); + + // Don't insert function length code when disabled + // Instead insert empty function as the identifier is still referenced + var insertNode = t.functionDeclaration( + t.identifier(setFunctionLength), + [], + t.blockStatement([]) + ); + + if (me.options.preserveFunctionLength) { + // Insert function length code + insertNode = SetFunctionLengthTemplate.single({ + fnName: setFunctionLength, + }); + } + + me.skip(prependProgram(_path, insertNode)); } if ((blockPath.node as NodeSymbol)[UNSAFE]) return; @@ -71,7 +85,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } // Do not apply to functions in nested scopes - if (path.parentPath !== blockStatement) { + if (path.parentPath !== blockStatement || me.isSkipped(path)) { illegalNames.add(name); return; } @@ -322,6 +336,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnLengthsObjectExpression: fnLengths, }); + (dispatcher as NodeSymbol)[PREDICTABLE] = true; + /** * Prepends the node into the block. (And registers the declaration) * @param node @@ -354,27 +370,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]) ); - if (blockPath.isProgram()) { - active = false; - - if (me.options.preserveFunctionLength) { - // Insert function length code - prepend( - SetFunctionLengthTemplate.single({ fnName: setFunctionLength }) - ); - } else { - // Don't insert function length code when disabled - // Instead insert empty function as the identifier is still referenced - prepend( - t.functionDeclaration( - t.identifier(setFunctionLength), - [], - t.blockStatement([]) - ) - ); - } - } - // Remove original functions for (let path of functionPaths.values()) { path.remove(); diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 1751dfa..c818c28 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -3,6 +3,7 @@ import { NodePath, PluginObj } from "@babel/core"; import { ensureComputedExpression, getFunctionName, + isDefiningIdentifier, prepend, } from "../utils/ast-utils"; import { PluginArg } from "./plugin"; @@ -89,10 +90,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnPath.traverse({ Identifier: { exit(identifierPath) { - if ( - !identifierPath.isBindingIdentifier() && - !(identifierPath as NodePath).isReferencedIdentifier() - ) + const type = identifierPath.isReferencedIdentifier() + ? "referenced" + : (identifierPath as NodePath).isBindingIdentifier() + ? "binding" + : "other"; + + if (!type) return; + if (type === "binding" && isDefiningIdentifier(identifierPath)) return; if ((identifierPath.node as NodeSymbol)[UNSAFE]) return; diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts index 252c08c..41b6bb9 100644 --- a/src/transforms/functionOutlining.ts +++ b/src/transforms/functionOutlining.ts @@ -88,6 +88,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (blockPath.isProgram()) { blockPath.scope.crawl(); } + if (blockPath.find((p) => me.isSkipped(p))) return; if (!checkProbability()) return; diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index d1785f7..e036648 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -4,8 +4,7 @@ import { PluginArg } from "../plugin"; import { NodeSymbol, PREDICTABLE } from "../../constants"; import * as t from "@babel/types"; import { isStaticValue } from "../../utils/static-utils"; -import { isFunctionStrictMode } from "../../utils/function-utils"; -import { prepend } from "../../utils/ast-utils"; +import { isStrictMode, prepend } from "../../utils/ast-utils"; import Template from "../../templates/template"; /** @@ -17,6 +16,26 @@ import Template from "../../templates/template"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.MovedDeclarations); + function isFunctionEligibleForParameterPacking( + functionPath: NodePath + ) { + // Getter/setter functions must have zero or one formal parameter + // We cannot add extra parameters to them + if (functionPath.isObjectMethod() || functionPath.isClassMethod()) { + if (functionPath.node.kind !== "method") { + return false; + } + } + + // Rest params check + if (functionPath.get("params").find((p) => p.isRestElement())) return false; + + // Max 1,000 parameters + if (functionPath.get("params").length > 1_000) return false; + + return true; + } + return { visitor: { FunctionDeclaration: { @@ -31,12 +50,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Must be direct child of the function if (path.parentPath !== functionPath.get("body")) return; - // Rest params check - if (functionPath.get("params").find((p) => p.isRestElement())) return; + // Must be eligible for parameter packing + if (!isFunctionEligibleForParameterPacking(functionPath)) return; - var isStrictMode = isFunctionStrictMode(functionPath); + var strictMode = isStrictMode(functionPath); - if (isStrictMode) return; + // Default parameters are not allowed when 'use strict' is declared + if (strictMode) return; const functionName = path.node.id.name; @@ -85,13 +105,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Check for "use strict" directive // Strict mode disallows non-simple parameters // So we can't move the declaration to the function parameters - var isStrictMode = isFunctionStrictMode(functionPath); - if (isStrictMode) { + var strictMode = isStrictMode(functionPath); + if (strictMode) { allowDefaultParamValue = false; } // Cannot add variables after rest element - if (!functionPath.get("params").find((p) => p.isRestElement())) { + // Cannot add over 1,000 parameters + if (isFunctionEligibleForParameterPacking(functionPath)) { insertionMethod = "functionParameter"; } } diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 43030eb..16b4293 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -4,11 +4,17 @@ import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; import { + NodeSymbol, noRenameVariablePrefix, placeholderVariablePrefix, variableFunctionName, + NO_RENAME, } from "../../constants"; import { computeProbabilityMap } from "../../probability"; +import { + getParentFunctionOrProgram, + isDefiningIdentifier, +} from "../../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.RenameVariables); @@ -17,11 +23,15 @@ export default ({ Plugin }: PluginArg): PluginObj => { let renamedScopes = new Set(); let renamedBindingIdentifiers = new WeakSet(); + let changedScopes = new Map>(); + return { visitor: { Program: { enter(path) { path.scope.crawl(); + + availableNames = Array.from(me.obfuscator.nameGen.generatedNames); }, }, CallExpression: { @@ -41,7 +51,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { Scopable: { enter(path: NodePath) { - const { scope } = path; + let { scope } = path; if (scope.path?.isClassDeclaration()) return; if (renamedScopes.has(scope)) { @@ -54,12 +64,42 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Collect all referenced identifiers in the current scope const referencedIdentifiers = new Set(); - path.traverse({ + let traversePath: NodePath = path; + if (path.parentPath?.isForStatement()) { + traversePath = path.parentPath; + } + + traversePath.traverse({ Identifier(innerPath) { // Use Babel's built-in method to check if the identifier is a referenced variable - if (innerPath.isReferencedIdentifier()) { + var type = innerPath.isReferencedIdentifier() + ? "referenced" + : (innerPath as NodePath).isBindingIdentifier() + ? "binding" + : null; + if (type) { const binding = innerPath.scope.getBinding(innerPath.node.name); + if (type === "binding" && isDefiningIdentifier(innerPath)) { + /** + * var a; // Program bindings = {a} + * { // Block Statement bindings = {} + * var a; + * } + * + * Add variable binding to 'a' + */ + + if ( + binding && + binding.kind === "var" && + binding.scope !== scope + ) { + } else { + return; + } + } + // If the binding exists and is not defined in the current scope, it is a reference if (binding && binding.scope !== path.scope) { referencedIdentifiers.add(innerPath.node.name); @@ -68,13 +108,48 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }); + // console.log(scope.path.type, "Referenced", referencedIdentifiers); + + var preprocessedMappings = new Map(); + + for (let identifierName in scope.bindings) { + const binding = scope.bindings[identifierName]; + if (binding.kind === "hoisted" || binding.kind === "var") { + // Check if already renamed - check function context + var functionContext = getParentFunctionOrProgram(binding.path); + + const alreadyRenamed = changedScopes + .get(functionContext.scope) + ?.get(identifierName); + + // console.log( + // scope.path.type, + // "Checking already renamed", + // identifierName, + // alreadyRenamed + // ); + + if (alreadyRenamed) { + const fnBinding = + functionContext.scope.getOwnBinding(alreadyRenamed); + if ( + fnBinding && + renamedBindingIdentifiers.has(fnBinding.identifier) + ) { + preprocessedMappings.set(binding, alreadyRenamed); + referencedIdentifiers.add(alreadyRenamed); + } + } + } + } + var actuallyAvailableNames = availableNames.filter( (x) => !referencedIdentifiers.has(x) && !scope.bindings[x] ); const isGlobal = scope.path.isProgram(); - for (var identifierName in scope.bindings) { + for (let identifierName in scope.bindings) { // __NO_JS_CONFUSER_RENAME__ prefix should not be renamed if (identifierName.startsWith(noRenameVariablePrefix)) continue; @@ -110,7 +185,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (renamedBindingIdentifiers.has(binding.identifier)) continue; renamedBindingIdentifiers.add(binding.identifier); - let newName = actuallyAvailableNames.pop(); + if (!binding.path.node || binding.path.node[NO_RENAME]) continue; + + let newName = + preprocessedMappings.get(binding) ?? actuallyAvailableNames.pop(); if (!newName) { while (!newName || scope.hasGlobal(newName)) { @@ -119,7 +197,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { names.push(newName); } - // me.log("Renaming", identifierName, "to", newName); + if (!changedScopes.has(scope)) { + changedScopes.set(scope, new Map()); + } + changedScopes.get(scope).set(identifierName, newName); + + // console.log(scope.path.type, "Renamed", identifierName, newName); + + me.log("Renaming", identifierName, "to", newName); scope.rename(identifierName, newName); // Extra Class Declaration scope preserve logic needed diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index b113d1a..b67e995 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -129,6 +129,21 @@ export default ({ Plugin }: PluginArg): PluginObj => { } }, }, + // a["key"] -> a.key + MemberExpression: { + exit(path) { + if (!path.node.computed) return; + + const property = path.get("property"); + if (!property.isStringLiteral()) return; + + const key = property.node.value; + if (!t.isValidIdentifier(key)) return; + + path.node.computed = false; + path.node.property = t.identifier(key); + }, + }, // {["key"]: 1} -> {key: 1} // {"key": 1} -> {key: 1} ObjectProperty: { @@ -160,6 +175,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { } }, }, + // ; -> () + EmptyStatement: { + exit(path) { + path.remove(); + }, + }, // console; -> (); ExpressionStatement: { exit(path) { @@ -296,9 +317,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, // while(true) {a();} -> while(true) a(); // for(;;) {a();} -> for(;;) a(); - "While|For": { + // with(a) {a();} -> with(a) a(); + "While|For|WithStatement": { exit(_path) { - var path = _path as NodePath; + var path = _path as NodePath; var body = path.get("body"); if (body.isBlock() && body.node.body.length === 1) { diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts new file mode 100644 index 0000000..1b6c31b --- /dev/null +++ b/src/transforms/pack.ts @@ -0,0 +1,150 @@ +import * as t from "@babel/types"; +import Obfuscator from "../obfuscator"; +import Template from "../templates/template"; +import traverse, { NodePath } from "@babel/traverse"; +import { + isDefiningIdentifier, + isReservedIdentifier, + isVariableIdentifier, +} from "../utils/ast-utils"; + +export default function pack(ast: t.File, obfuscator: Obfuscator) { + const objectName = obfuscator.nameGen.generate(); + const mappings = new Map(); + const typeofMappings = new Map(); + + const objectProperties: t.ObjectMethod[] = []; + + const prependNodes: t.Statement[] = []; + + for (const statement of ast.program.body) { + if (t.isImportDeclaration(statement)) { + prependNodes.push(statement); + } + } + + traverse(ast, { + ImportDeclaration(path) { + path.remove(); + + // Ensure bindings are removed -> variable becomes a global -> added to mappings object + path.scope.crawl(); + }, + Program(path) { + path.scope.crawl(); + }, + + Identifier: { + exit(path) { + if (!isVariableIdentifier(path)) return; + + if (isDefiningIdentifier(path)) return; + + const identifierName = path.node.name; + if (obfuscator.options.globalVariables.has(identifierName)) return; + if (isReservedIdentifier(path.node)) return; + + if (!path.scope.hasGlobal(identifierName)) return; + + if ( + path.key === "argument" && + path.parentPath.isUnaryExpression({ operator: "typeof" }) + ) { + const unaryExpression = path.parentPath; + + let propertyName = typeofMappings.get(identifierName); + if (!propertyName) { + propertyName = obfuscator.nameGen.generate(); + typeofMappings.set(identifierName, propertyName); + + // get identifier() { return typeof identifier; } + objectProperties.push( + t.objectMethod( + "get", + t.stringLiteral(propertyName), + [], + t.blockStatement([ + t.returnStatement( + t.unaryExpression("typeof", t.identifier(identifierName)) + ), + ]) + ) + ); + } + + unaryExpression.replaceWith( + t.memberExpression( + t.identifier(objectName), + t.stringLiteral(propertyName), + true + ) + ); + return; + } + + let propertyName = mappings.get(identifierName); + if (!propertyName) { + propertyName = obfuscator.nameGen.generate(); + mappings.set(identifierName, propertyName); + + // get identifier() { return identifier; } + objectProperties.push( + t.objectMethod( + "get", + t.stringLiteral(propertyName), + [], + t.blockStatement([ + t.returnStatement(t.identifier(identifierName)), + ]) + ) + ); + + // set identifier(value) { return identifier = value; } + objectProperties.push( + t.objectMethod( + "set", + t.stringLiteral(propertyName), + [t.identifier(objectName)], + t.blockStatement([ + t.returnStatement( + t.assignmentExpression( + "=", + t.identifier(identifierName), + t.identifier(objectName) + ) + ), + ]) + ) + ); + } + + path.replaceWith( + t.memberExpression( + t.identifier(objectName), + t.stringLiteral(propertyName), + true + ) + ); + }, + }, + }); + + const objectExpression = t.objectExpression(objectProperties); + + const outputCode = Obfuscator.generateCode(ast, { + ...obfuscator.options, + compact: true, + }); + + var newAST = new Template(` + {prependNodes} + Function({objectName}, {outputCode})({objectExpression}); + `).file({ + objectName: () => t.stringLiteral(objectName), + outputCode: () => t.stringLiteral(outputCode), + objectExpression: objectExpression, + prependNodes: prependNodes, + }); + + return newAST; +} diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 0747440..5b4b9de 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -3,7 +3,13 @@ import Obfuscator from "../obfuscator"; import { chance, choice, getRandomString } from "../utils/random-utils"; import { Order } from "../order"; import * as t from "@babel/types"; -import { FN_LENGTH, NodeSymbol, SKIP, CONTROL_OBJECTS } from "../constants"; +import { + FN_LENGTH, + NodeSymbol, + SKIP, + CONTROL_OBJECTS, + NO_RENAME, +} from "../constants"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { prepend, prependProgram } from "../utils/ast-utils"; import ControlObject from "../utils/ControlObject"; diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 0e74670..0f21871 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -39,6 +39,64 @@ export default ({ Plugin }: PluginArg): PluginObj => { }, }, + // `Hello ${username}` -> "Hello " + username + TemplateLiteral: { + exit(path) { + // Check if this is a tagged template literal, if yes, skip it + if (t.isTaggedTemplateExpression(path.parent)) { + return; + } + + const { quasis, expressions } = path.node; + + // Start with the first quasi (template string part) + let binaryExpression: t.Expression = t.stringLiteral( + quasis[0].value.cooked + ); + + // Loop over the remaining quasis and expressions, concatenating them + for (let i = 0; i < expressions.length; i++) { + // Add the expression as part of the binary concatenation + binaryExpression = t.binaryExpression( + "+", + binaryExpression, + expressions[i] as t.Expression + ); + + // Add the next quasi (template string part) + if (quasis[i + 1].value.cooked !== "") { + binaryExpression = t.binaryExpression( + "+", + binaryExpression, + t.stringLiteral(quasis[i + 1].value.cooked) + ); + } + } + + // Replace the template literal with the constructed binary expression + path.replaceWith(binaryExpression); + }, + }, + + // /Hello World/g -> new RegExp("Hello World", "g") + RegExpLiteral: { + exit(path) { + const { pattern, flags } = path.node; + + // Create a new RegExp() expression using the pattern and flags + const newRegExpCall = t.newExpression( + t.identifier("RegExp"), // Identifier for RegExp constructor + [ + t.stringLiteral(pattern), // First argument: the pattern (no extra escaping needed) + flags ? t.stringLiteral(flags) : t.stringLiteral(""), // Second argument: the flags (if any) + ] + ); + + // Replace the literal regex with the new RegExp() call + path.replaceWith(newRegExpCall); + }, + }, + ReferencedIdentifier: { exit(path) { const { name } = path.node; diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index ce5499f..16d41f8 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -170,7 +170,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); }); - newObfuscator.obfuscateAST(evalFile); + newObfuscator.obfuscateAST(evalFile, { + disablePack: true, + }); const generated = Obfuscator.generateCode(evalFile); diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 26ae1d6..2deb5c9 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -3,12 +3,24 @@ import { PluginArg } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; -import { ensureComputedExpression } from "../../utils/ast-utils"; +import { + ensureComputedExpression, + prependProgram, +} from "../../utils/ast-utils"; import { numericLiteral } from "../../utils/node"; +import { + PakoInflateMin, + StringCompressionTemplate, +} from "../../templates/stringCompressionTemplate"; +import Obfuscator from "../../obfuscator"; +import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; +const pako = require("pako"); export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.StringCompression); + const stringDelimiter = "|"; + return { visitor: { Program: { @@ -21,6 +33,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { StringLiteral: { exit: (path) => { const originalValue = path.node.value; + + // Must be at least 3 characters long + if (originalValue.length < 3) return; + + // Cannot contain the string delimiter + if (originalValue.includes(stringDelimiter)) return; + let index = stringMap.get(originalValue); if (typeof index === "undefined") { // Allow user option to skip compression for certain strings @@ -51,23 +70,34 @@ export default ({ Plugin }: PluginArg): PluginObj => { // No strings changed if (stringMap.size === 0) return; - // Create the string function - var arrayExpression = t.arrayExpression( - Array.from(stringMap.keys()).map((value) => t.stringLiteral(value)) + var stringPayload = Array.from(stringMap.keys()).join( + stringDelimiter ); - var stringFunction = t.functionDeclaration( - t.identifier(stringFn), - [t.identifier("index")], - t.blockStatement([ - t.returnStatement( - t.memberExpression(arrayExpression, t.identifier("index"), true) - ), - ]) + // Compress the string + var compressedBuffer: Uint8Array = pako.deflate(stringPayload); + var compressedBase64 = + Buffer.from(compressedBuffer).toString("base64"); + + const StringToBuffer = me.getPlaceholder(); + + prependProgram( + programPath, + StringCompressionTemplate.compile({ + stringFn, + stringName: me.getPlaceholder(), + stringArray: me.getPlaceholder(), + stringDelimiter: () => t.stringLiteral(stringDelimiter), + stringValue: () => t.stringLiteral(compressedBase64), + GetGlobalTemplate: createGetGlobalTemplate(me, programPath), + getGlobalFnName: me.getPlaceholder(), + }) ); - var p = programPath.unshiftContainer("body", stringFunction); - programPath.scope.registerDeclaration(p[0]); + prependProgram( + programPath, + Obfuscator.parseCode(PakoInflateMin).program.body + ); }, }, }, diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index ea209eb..fc42eb6 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -6,11 +6,8 @@ import Template from "../templates/template"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, UNSAFE } from "../constants"; -import { getFunctionName } from "../utils/ast-utils"; -import { - computeFunctionLength, - isFunctionStrictMode, -} from "../utils/function-utils"; +import { getFunctionName, isStrictMode } from "../utils/ast-utils"; +import { computeFunctionLength } from "../utils/function-utils"; export default ({ Plugin }: PluginArg): PluginObj => { const me = Plugin(Order.VariableMasking); @@ -37,7 +34,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } // Do not apply to 'use strict' functions - if (isFunctionStrictMode(fnPath)) return; + if (isStrictMode(fnPath)) return; // Do not apply to functions marked unsafe if ((fnPath.node as NodeSymbol)[UNSAFE]) return; diff --git a/src/utils/ControlObject.ts b/src/utils/ControlObject.ts index 60761b3..9b1abff 100644 --- a/src/utils/ControlObject.ts +++ b/src/utils/ControlObject.ts @@ -62,7 +62,7 @@ export default class ControlObject { addProperty(node: t.Expression) { if (!this.objectName) { // Object hasn't been created yet - this.objectName = this.me.getPlaceholder(); + this.objectName = this.me.getPlaceholder() + "_controlObject"; if (t.isFunctionExpression(node) && !node.id) { // Use function declaration as object diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index fb6aff1..02bf0b4 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -371,6 +371,28 @@ export function prependProgram( return prepend(program, ...nodes); } +/** + * A referenced or binding identifier, only names that reflect variables. + * + * - Excludes labels + * + * @param path + * @returns + */ +export function isVariableIdentifier(path: NodePath) { + if ( + !path.isReferencedIdentifier() && + !(path as NodePath).isBindingIdentifier() + ) + return false; + + // abc: {} // not a variable identifier + if (path.key === "label" && path.parentPath?.isLabeledStatement()) + return false; + + return true; +} + /** * Subset of BindingIdentifier, excluding non-defined assignment expressions. * @@ -445,3 +467,29 @@ export function isStrictIdentifier(path: NodePath): boolean { return false; } + +/** + * @example + * function abc() { + * "use strict"; + * } // true + * @param path + * @returns + */ +export function isStrictMode(path: NodePath) { + if (path.isBlock()) { + if (path.isTSModuleBlock()) return false; + return (path.node as t.BlockStatement | t.Program).directives.some( + (directive) => directive.value.value === "use strict" + ); + } + + if (path.isFunction()) { + const fnBody = path.get("body"); + if (fnBody.isBlock()) { + return isStrictMode(fnBody); + } + } + + return false; +} diff --git a/src/utils/function-utils.ts b/src/utils/function-utils.ts index 6fcd835..1d3fa36 100644 --- a/src/utils/function-utils.ts +++ b/src/utils/function-utils.ts @@ -2,27 +2,6 @@ import { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; import { FN_LENGTH, NodeSymbol, variableFunctionName } from "../constants"; -/** - * @example - * function abc() { - * "use strict"; - * } // true - * @param path - * @returns - */ -export function isFunctionStrictMode(path: NodePath) { - if ( - t.isBlockStatement(path.node.body) && - path.node.body.directives.some( - (directive) => directive.value.value === "use strict" - ) - ) { - return true; - } - - return false; -} - /** * @example __JS_CONFUSER_VAR__(identifier) // true * @param path diff --git a/src/utils/node.ts b/src/utils/node.ts index 616c1fb..0c033b8 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -16,3 +16,38 @@ export function numericLiteral( } return t.numericLiteral(value); } + +export function deepClone(node: t.Node | t.Node[]) { + function deepClone(obj) { + // Handle non-objects like null, undefined, primitive values, or functions + if (obj === null || typeof obj !== "object") { + return obj; + } + + // Handle Date + if (obj instanceof Date) { + return new Date(obj); + } + + // Handle Array + if (Array.isArray(obj)) { + return obj.map(deepClone); + } + + // Handle Objects + const clonedObj = {}; + + // Handle string and symbol property keys + [ + ...Object.getOwnPropertyNames(obj), + ...Object.getOwnPropertySymbols(obj), + ].forEach((key) => { + const value = obj[key]; + clonedObj[key] = deepClone(value); + }); + + return clonedObj; + } + + return deepClone(node); +} diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 1d8b405..ea40938 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -40,6 +40,7 @@ const validProperties = new Set([ "variableConcealing", "customStringEncodings", "functionOutlining", + "pack", ]); const validLockProperties = new Set([ diff --git a/test/code/Cash.test.ts b/test/code/Cash.test.ts index 3c02041..c888704 100644 --- a/test/code/Cash.test.ts +++ b/test/code/Cash.test.ts @@ -4,10 +4,46 @@ import JsConfuser from "../../src/index"; var CASH_JS = readFileSync(join(__dirname, "./Cash.src.js"), "utf-8"); -test("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { +const handleError = (error, output) => { + var helperCode = `var document = { + documentElement: {}, + createElement: () => { + return { style: {} }; + }, + }; + var window = { + document, + Array, + Object, + Symbol, + Number, + parseInt, + JSON, + setTimeout, + encodeURIComponent, + RegExp, + String, + $: false, + }; + window.window = window; + global.window = window; + for (var key in window) { + global[key] = window[key]; + }`; + + console.error(error); + writeFileSync("dev.output.js", helperCode + "\n" + output, { + encoding: "utf-8", + }); + + expect(true).toStrictEqual(false); +}; + +test.only("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { var { code: output } = await JsConfuser.obfuscate(CASH_JS, { target: "node", preset: "high", + pack: true, }); // Make the required document variables for initialization @@ -40,12 +76,7 @@ test("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { try { eval(output); } catch (e) { - console.error(e); - writeFileSync("dev.output.js", output, { - encoding: "utf-8", - }); - - expect(true).toStrictEqual(false); + handleError(e, output); } expect(window).toHaveProperty("cash"); @@ -82,6 +113,7 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta var { code: output } = await JsConfuser.obfuscate(CASH_JS, { target: "node", preset: "high", + pack: true, rgf: true, lock: { integrity: true, @@ -94,38 +126,7 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta // new Function() runs in non-strict mode new Function(output)(); } catch (e) { - var helperCode = `var document = { - documentElement: {}, - createElement: () => { - return { style: {} }; - }, - }; - var window = { - document, - Array, - Object, - Symbol, - Number, - parseInt, - JSON, - setTimeout, - encodeURIComponent, - RegExp, - String, - $: false, - }; - window.window = window; - global.window = window; - for (var key in window) { - global[key] = window[key]; - }`; - - console.error(e); - writeFileSync("dev.output.js", helperCode + "\n" + output, { - encoding: "utf-8", - }); - - expect(true).toStrictEqual(false); + handleError(e, output); } expect(window).toHaveProperty("cash"); diff --git a/test/code/Dynamic.test.ts b/test/code/Dynamic.test.ts index 0f20912..b03f4e6 100644 --- a/test/code/Dynamic.test.ts +++ b/test/code/Dynamic.test.ts @@ -1,16 +1,16 @@ -import { readFileSync, writeFileSync } from "fs"; +import { readFileSync } from "fs"; import { join } from "path"; import JsConfuser from "../../src/index"; -import { ObfuscateOptions } from "../../src/options"; var SOURCE_JS = readFileSync(join(__dirname, "./Dynamic.src.js"), "utf-8"); -test.concurrent("Variant #1: Dynamic.src.js on High Preset", async () => { +test("Variant #1: Dynamic.src.js on High Preset", async () => { // `input` is an embedded variable, therefore globalConcealing must be turned off var { code: output } = await JsConfuser.obfuscate(SOURCE_JS, { target: "browser", preset: "high", globalConcealing: false, + pack: true, }); var value = "never_called"; @@ -22,28 +22,3 @@ test.concurrent("Variant #1: Dynamic.src.js on High Preset", async () => { expect(value).toStrictEqual(1738.1738); }); - -test.concurrent("Variant #2: Dynamic.src.js on 2x High Preset", async () => { - var options: ObfuscateOptions = { - target: "node", - preset: "high", - globalConcealing: false, - }; - - var { code: output } = await JsConfuser.obfuscate(SOURCE_JS, options); - - // writeFileSync("./dev.error.1.js", output, "utf-8"); - - var { code: doublyObfuscated } = await JsConfuser.obfuscate(output, options); - - // writeFileSync("./dev.error.2.js", doublyObfuscated, "utf-8"); - - var value = "never_called"; - function input(x) { - value = x; - } - - eval(doublyObfuscated); - - expect(value).toStrictEqual(1738.1738); -}); diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index 678c441..3da6903 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -1,17 +1,17 @@ -import { readFileSync, writeFileSync } from "fs"; +import { readFileSync } from "fs"; import { join } from "path"; import JsConfuser from "../../src/index"; var ES6_JS = readFileSync(join(__dirname, "./ES6.src.js"), "utf-8"); -test.concurrent("Variant #1: ES6 code on High Preset", async () => { +test("Variant #1: ES6 code on High Preset", async () => { var { code: output } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", + pack: true, }); - // Ensure 'use strict' directive is preserved - expect(output.startsWith('"use strict"')).toStrictEqual(true); + // The 'use strict' directive is removed due to being packed var ranAllTest = false; eval(output); @@ -20,23 +20,20 @@ test.concurrent("Variant #1: ES6 code on High Preset", async () => { expect(ranAllTest).toStrictEqual(true); }); -test.concurrent( - "Variant #2: ES6 code on High Preset + RGF + Self Defending", - async () => { - var { code: output } = await JsConfuser.obfuscate(ES6_JS, { - target: "node", - preset: "high", - rgf: true, - lock: { - selfDefending: true, - countermeasures: "countermeasures", - }, - }); - - var ranAllTest = false; - eval(output); - - // 'ranAllTest' is set to TRUE by the evaluated code - expect(ranAllTest).toStrictEqual(true); - } -); +test("Variant #2: ES6 code on High Preset + RGF + Self Defending", async () => { + var { code: output } = await JsConfuser.obfuscate(ES6_JS, { + target: "node", + preset: "high", + rgf: true, + lock: { + selfDefending: true, + countermeasures: "countermeasures", + }, + }); + + var ranAllTest = false; + eval(output); + + // 'ranAllTest' is set to TRUE by the evaluated code + expect(ranAllTest).toStrictEqual(true); +}); diff --git a/test/code/StrictMode.test.js b/test/code/StrictMode.test.js index 401c738..71f8ec2 100644 --- a/test/code/StrictMode.test.js +++ b/test/code/StrictMode.test.js @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync } from "fs"; +import { readFileSync } from "fs"; import { join } from "path"; import JsConfuser from "../../src/index"; @@ -11,27 +11,10 @@ test("Variant #1: StrictMode on High Preset", async () => { var { code: output } = await JsConfuser.obfuscate(StrictMode_JS, { target: "node", preset: "high", + pack: true, }); //writeFileSync("./dev.output.js", output); eval(output); }); - -test("Variant #2: StrictMode on 2x High Preset", async () => { - var { code: output } = await JsConfuser.obfuscate(StrictMode_JS, { - target: "node", - preset: "high", - }); - - //writeFileSync("./dev.output1.js", output); - - var { code: output2 } = await JsConfuser.obfuscate(output, { - target: "node", - preset: "high", - }); - - //writeFileSync("./dev.output2.js", output2); - - eval(output2); -}); diff --git a/test/options.test.ts b/test/options.test.ts index ad78aa0..54acf69 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -1,173 +1,171 @@ import JsConfuser from "../src/index"; -describe("options", () => { - test("Variant #1: Accept percentages", async () => { - var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { - target: "node", - renameGlobals: true, - renameVariables: true, - stringConcealing: 0.5, - }); - - expect(output).not.toContain("TEST_VARIABLE"); +test("Variant #1: Accept percentages", async () => { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + target: "node", + renameGlobals: true, + renameVariables: true, + stringConcealing: 0.5, }); - test("Variant #2: Accept probability arrays", async () => { - var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { - target: "node", - renameVariables: true, - renameGlobals: true, - identifierGenerator: ["hexadecimal", "mangled"], // half hexadecimal, half randomized - }); + expect(output).not.toContain("TEST_VARIABLE"); +}); - expect(output).not.toContain("TEST_VARIABLE"); +test("Variant #2: Accept probability arrays", async () => { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + target: "node", + renameVariables: true, + renameGlobals: true, + identifierGenerator: ["hexadecimal", "mangled"], // half hexadecimal, half randomized }); - test("Variant #3: Accept probability maps", async () => { - var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { - target: "node", - renameVariables: true, - renameGlobals: true, - identifierGenerator: { - // 25% each - hexadecimal: 0.25, - randomized: 0.25, - mangled: 0.25, - number: 0.25, - }, - }); + expect(output).not.toContain("TEST_VARIABLE"); +}); - expect(output).not.toContain("TEST_VARIABLE"); +test("Variant #3: Accept probability maps", async () => { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + target: "node", + renameVariables: true, + renameGlobals: true, + identifierGenerator: { + // 25% each + hexadecimal: 0.25, + randomized: 0.25, + mangled: 0.25, + number: 0.25, + }, }); - test("Variant #4: Work with compact set to false", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` + expect(output).not.toContain("TEST_VARIABLE"); +}); + +test("Variant #4: Work with compact set to false", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` var a; var b; var c; `, - { - target: "node", - compact: false, - } - ); - - expect(output).toContain("\n"); - expect(output).toContain("var a;\nvar b;\nvar c;"); - }); + { + target: "node", + compact: false, + } + ); - test("Variant #5: Work with compact set to true", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` + expect(output).toContain("\n"); + expect(output).toContain("var a;\nvar b;\nvar c;"); +}); + +test("Variant #5: Work with compact set to true", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` var a; var b; var c; `, - { - target: "node", - compact: true, - } - ); - - expect(output).not.toContain("\n"); - expect(output).not.toContain("\t"); - expect(output).toContain("var a;var b;var c;"); - }); - - test("Variant #6: Work with debugComments enabled", async () => { - var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + { target: "node", - renameGlobals: true, - renameVariables: true, - compact: false, - debugComments: true, - }); + compact: true, + } + ); - expect(output).not.toContain("TEST_VARIABLE"); - }); + expect(output).not.toContain("\n"); + expect(output).not.toContain("\t"); + expect(output).toContain("var a;var b;var c;"); +}); - test("Variant #7: Error on invalid lock option", async () => { - expect( - JsConfuser.obfuscate(`var TEST_VARIABLE;`, { - target: "node", - lock: "invalid", - } as any) - ).rejects.toThrow(); - - expect( - JsConfuser.obfuscate(`var TEST_VARIABLE;`, { - target: "node", - lock: { - invalidProperty: true, - }, - } as any) - ).rejects.toThrow(); +test("Variant #6: Work with debugComments enabled", async () => { + var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + target: "node", + renameGlobals: true, + renameVariables: true, + compact: false, + debugComments: true, }); - test("Variant #8: Error on invalid target values", async () => { - var invalid: any = { - target: "__invalid__target__", - }; + expect(output).not.toContain("TEST_VARIABLE"); +}); - await expect(async () => { - return await JsConfuser.obfuscate("5+5", invalid); - }).rejects.toThrow(); - }); +test("Variant #7: Error on invalid lock option", async () => { + expect( + JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + target: "node", + lock: "invalid", + } as any) + ).rejects.toThrow(); - test("Variant #9: Error when target property missing", async () => { - var invalid: any = { - objectExtraction: true, - }; + expect( + JsConfuser.obfuscate(`var TEST_VARIABLE;`, { + target: "node", + lock: { + invalidProperty: true, + }, + } as any) + ).rejects.toThrow(); +}); - await expect(async () => { - return await JsConfuser.obfuscate("5+5", invalid); - }).rejects.toThrow(); - }); +test("Variant #8: Error on invalid target values", async () => { + var invalid: any = { + target: "__invalid__target__", + }; - test("Variant #10: Error when invalid options are passed in", async () => { - var invalid: any = { - target: "browser", - __invalid__prop__: true, - }; + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); +}); - await expect(async () => { - return await JsConfuser.obfuscate("5+5", invalid); - }).rejects.toThrow(); - }); +test("Variant #9: Error when target property missing", async () => { + var invalid: any = { + objectExtraction: true, + }; - test("Variant #11: Error when invalid startDate is passed in", async () => { - var invalid: any = { - target: "browser", - lock: { - startDate: "__invalid__date__object__", - }, - }; + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); +}); - await expect(async () => { - return await JsConfuser.obfuscate("5+5", invalid); - }).rejects.toThrow(); - }); +test("Variant #10: Error when invalid options are passed in", async () => { + var invalid: any = { + target: "browser", + __invalid__prop__: true, + }; - test("Variant #12: Error when invalid endDate is passed in", async () => { - var invalid: any = { - target: "browser", - lock: { - endDate: "__invalid__date__object__", - }, - }; + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); +}); - await expect(async () => { - return await JsConfuser.obfuscate("5+5", invalid); - }).rejects.toThrow(); - }); +test("Variant #11: Error when invalid startDate is passed in", async () => { + var invalid: any = { + target: "browser", + lock: { + startDate: "__invalid__date__object__", + }, + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); +}); - test("Variant #13: Error when source code is not a string", async () => { - await expect(async () => { - return await JsConfuser.obfuscate({} as any, { - target: "node", - preset: "low", - }); - }).rejects.toThrow(); - }); +test("Variant #12: Error when invalid endDate is passed in", async () => { + var invalid: any = { + target: "browser", + lock: { + endDate: "__invalid__date__object__", + }, + }; + + await expect(async () => { + return await JsConfuser.obfuscate("5+5", invalid); + }).rejects.toThrow(); +}); + +test("Variant #13: Error when source code is not a string", async () => { + await expect(async () => { + return await JsConfuser.obfuscate({} as any, { + target: "node", + preset: "low", + }); + }).rejects.toThrow(); }); diff --git a/test/semantics/preserveFunctionLength.test.ts b/test/semantics/functionLength.test.ts similarity index 83% rename from test/semantics/preserveFunctionLength.test.ts rename to test/semantics/functionLength.test.ts index e4b58a9..c3311c3 100644 --- a/test/semantics/preserveFunctionLength.test.ts +++ b/test/semantics/functionLength.test.ts @@ -1,6 +1,6 @@ import JsConfuser from "../../src"; -test("Variant #1: Enabled by default", async () => { +test("Variant #1: Preserve function.length on High Preset", async () => { var { code: output } = await JsConfuser.obfuscate( ` function myFunction(a, b, c, d = "") { @@ -12,6 +12,7 @@ test("Variant #1: Enabled by default", async () => { { target: "node", preset: "high", + pack: true, } ); @@ -20,7 +21,7 @@ test("Variant #1: Enabled by default", async () => { expect(TEST_OUTPUT).toStrictEqual(3); }); -test("Variant #2: Disabled", async () => { +test("Variant #2: Allow user to disable preserving function.length", async () => { var { code: output } = await JsConfuser.obfuscate( ` function myFunction(a, b, c, d = "") { @@ -42,6 +43,7 @@ test("Variant #2: Disabled", async () => { duplicateLiteralsRemoval: false, rgf: true, + pack: true, } ); diff --git a/test/semantics/lastExpression.test.ts b/test/semantics/lastExpression.test.ts index d380fb2..48b716e 100644 --- a/test/semantics/lastExpression.test.ts +++ b/test/semantics/lastExpression.test.ts @@ -11,6 +11,7 @@ test("Variant #1: Last expression is preserved on 'High' preset", async () => { { target: "node", preset: "high", + pack: true, } ); @@ -34,6 +35,7 @@ test("Variant #2: Last expression is preserved on 'High' preset with RGF", async target: "node", preset: "high", rgf: true, + pack: true, } ); diff --git a/test/code/NewFeatures.test.ts b/test/semantics/newFeatures.test.ts similarity index 100% rename from test/code/NewFeatures.test.ts rename to test/semantics/newFeatures.test.ts diff --git a/test/templates/template.test.ts b/test/templates/template.test.ts index 456edde..d9a1211 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -2,247 +2,288 @@ import Obfuscator from "../../src/obfuscator"; import Template from "../../src/templates/template"; import * as t from "@babel/types"; -describe("Template", () => { - test("Variant #1: Error when invalid code passed in", () => { - var _consoleError = console.error; - console.error = () => {}; +test("Variant #1: Error when invalid code passed in", () => { + var _consoleError = console.error; + console.error = () => {}; - expect(() => { - new Template(`#&!#Ylet{}class)--1]?|:!@#`).compile(); - }).toThrow(); + expect(() => { + new Template(`#&!#Ylet{}class)--1]?|:!@#`).compile(); + }).toThrow(); - console.error = _consoleError; - }); + console.error = _consoleError; +}); - test("Variant #2: Error on missing variables", () => { - var _consoleError = console.error; - console.error = () => {}; +test("Variant #2: Error on missing variables", () => { + var _consoleError = console.error; + console.error = () => {}; - expect(() => { - new Template(` + expect(() => { + new Template(` function {name}(){ {global}.property = true; } `).compile({ name: "test" }); - }).toThrow(); + }).toThrow(); - console.error = _consoleError; - }); + console.error = _consoleError; +}); - test("Variant #3: Basic string interpolation", () => { - var Base64Template = new Template(` +test("Variant #3: Basic string interpolation", () => { + var Base64Template = new Template(` function {name}(str){ return window.btoa(str) }`); - var functionDeclaration = Base64Template.single({ - name: "decodeBase64", - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(functionDeclaration.id!.name).toStrictEqual("decodeBase64"); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(functionDeclaration.id!.name).toStrictEqual("decodeBase64"); - // Ensure code has no syntax errors - eval(output); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #4: AST subtree insertion", () => { - var Base64Template = new Template(` + // Ensure code has no syntax errors + eval(output); +}); + +test("Variant #4: AST subtree insertion", () => { + var Base64Template = new Template(` function {name}(str){ {getWindow} return {getWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ - name: "decodeBase64", - getWindowName: "newWindow", - getWindow: Obfuscator.parseCode("var newWindow = {}").program.body, - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(functionDeclaration.body.body[0].type).toStrictEqual( - "VariableDeclaration" - ); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + getWindowName: "newWindow", + getWindow: Obfuscator.parseCode("var newWindow = {}").program.body, + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(functionDeclaration.body.body[0].type).toStrictEqual( + "VariableDeclaration" + ); - expect(output).toContain("var newWindow={}"); - expect(output).toContain("return newWindow.btoa(str)"); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #5: AST subtree insertion (callback)", () => { - var Base64Template = new Template(` + expect(output).toContain("var newWindow={}"); + expect(output).toContain("return newWindow.btoa(str)"); +}); + +test("Variant #5: AST subtree insertion (callback)", () => { + var Base64Template = new Template(` function {name}(str){ {getWindow} return {getWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ - name: "decodeBase64", - getWindowName: "newWindow", - getWindow: () => { - return Obfuscator.parseCode("var newWindow = {}").program.body; - }, - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(t.isBlockStatement(functionDeclaration.body)).toStrictEqual(true); - expect(functionDeclaration.body.body[0].type).toStrictEqual( - "VariableDeclaration" - ); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + getWindowName: "newWindow", + getWindow: () => { + return Obfuscator.parseCode("var newWindow = {}").program.body; + }, + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(t.isBlockStatement(functionDeclaration.body)).toStrictEqual(true); + expect(functionDeclaration.body.body[0].type).toStrictEqual( + "VariableDeclaration" + ); - expect(output).toContain("var newWindow={}"); - expect(output).toContain("return newWindow.btoa(str)"); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #6: Template subtree insertion", async () => { - var NewWindowTemplate = new Template(` + expect(output).toContain("var newWindow={}"); + expect(output).toContain("return newWindow.btoa(str)"); +}); + +test("Variant #6: Template subtree insertion", async () => { + var NewWindowTemplate = new Template(` var {NewWindowName} = {}; `); - var Base64Template = new Template(` + var Base64Template = new Template(` function {name}(str){ {NewWindowTemplate} return {NewWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ - name: "atob", - NewWindowTemplate: NewWindowTemplate, - NewWindowName: "newWindow", - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(t.isBlockStatement(functionDeclaration.body)).toStrictEqual(true); - expect(functionDeclaration.body.body[0].type).toStrictEqual( - "VariableDeclaration" - ); + var functionDeclaration = Base64Template.single({ + name: "atob", + NewWindowTemplate: NewWindowTemplate, + NewWindowName: "newWindow", + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(t.isBlockStatement(functionDeclaration.body)).toStrictEqual(true); + expect(functionDeclaration.body.body[0].type).toStrictEqual( + "VariableDeclaration" + ); - expect(output).toContain("var newWindow={}"); - expect(output).toContain("return newWindow.btoa(str)"); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #7: Template subtree insertion (callback)", async () => { - var NewWindowTemplate = new Template(` + expect(output).toContain("var newWindow={}"); + expect(output).toContain("return newWindow.btoa(str)"); +}); + +test("Variant #7: Template subtree insertion (callback)", async () => { + var NewWindowTemplate = new Template(` var {NewWindowName} = {}; `); - var Base64Template = new Template(` + var Base64Template = new Template(` function {name}(str){ {NewWindowTemplate} return {NewWindowName}.btoa(str) }`); - var functionDeclaration = Base64Template.single({ - name: "atob", - NewWindowTemplate: () => NewWindowTemplate, - NewWindowName: "newWindow", - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(functionDeclaration.body.body[0].type).toStrictEqual( - "VariableDeclaration" - ); + var functionDeclaration = Base64Template.single({ + name: "atob", + NewWindowTemplate: () => NewWindowTemplate, + NewWindowName: "newWindow", + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(functionDeclaration.body.body[0].type).toStrictEqual( + "VariableDeclaration" + ); - expect(output).toContain("var newWindow={}"); - expect(output).toContain("return newWindow.btoa(str)"); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #8: AST string replacement with Literal node", async () => { - var Base64Template = new Template(` + expect(output).toContain("var newWindow={}"); + expect(output).toContain("return newWindow.btoa(str)"); +}); + +test("Variant #8: AST string replacement with Literal node", async () => { + var Base64Template = new Template(` function {name}(str){ return window[{property}](str) }`); - var functionDeclaration = Base64Template.single({ - name: "decodeBase64", - property: t.stringLiteral("atob"), - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + property: t.stringLiteral("atob"), + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(output).toContain('return window["atob"](str)'); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #9: AST string replacement with Literal node (callback)", async () => { - var Base64Template = new Template(` + expect(output).toContain('return window["atob"](str)'); +}); + +test("Variant #9: AST string replacement with Literal node (callback)", async () => { + var Base64Template = new Template(` function {name}(str){ return window[{property}](str) }`); - var functionDeclaration = Base64Template.single({ - name: "decodeBase64", - property: () => t.stringLiteral("atob"), - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + property: () => t.stringLiteral("atob"), + }); - // Generated code and check - var output = Obfuscator.generateCode(functionDeclaration, { - target: "node", - compact: true, - }); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - expect(output).toContain('return window["atob"](str)'); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, }); - test("Variant #10: Error when single() encounters multiple statements", async () => { - var ListTemplate = new Template(` + expect(output).toContain('return window["atob"](str)'); +}); + +test("Variant #10: Error when single() encounters multiple statements", async () => { + var ListTemplate = new Template(` var a; var b; var c; `); - expect(() => { - ListTemplate.single(); - }).toThrow(); - }); + expect(() => { + ListTemplate.single(); + }).toThrow(); +}); - test("Variant #11: Handle empty statements when using single()", async () => { - var ValidTemplate = new Template(` +test("Variant #11: Handle empty statements when using single()", async () => { + var ValidTemplate = new Template(` ; var a; ; `); - var statement = ValidTemplate.single(); - expect(statement.type).toStrictEqual("VariableDeclaration"); + var statement = ValidTemplate.single(); + expect(statement.type).toStrictEqual("VariableDeclaration"); +}); + +test("Variant #12: Handle Identifier and variables name collision", async () => { + var ValidTemplate = new Template(` + var myVar = {myVar} + TEST_OUTPUT = myVar; + `); + + var file = ValidTemplate.file({ + myVar: () => t.stringLiteral("Correct Value"), }); + + // Generate out code and test result + var code = Obfuscator.generateCode(file); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #13: Handle multiple AST-based variables", async () => { + var ValidTemplate = new Template(` + var {myVar} = {myVarValue} + TEST_OUTPUT = {myVar}; + `); + + var file = ValidTemplate.file({ + myVar: () => t.identifier("myActualVarName"), + myVarValue: t.stringLiteral("Correct Value"), + }); + + // Generate out code and test result + var code = Obfuscator.generateCode(file); + + // Ensure variables got inserted + expect(code).toContain("myActualVarName"); + expect(code).toContain("Correct Value"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 946264a..54992b0 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -21,6 +21,7 @@ test("Variant #1: Obfuscate code and still execute in correct order", async () = var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -47,6 +48,7 @@ test("Variant #2: Properly handle for-loop", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -75,6 +77,7 @@ test("Variant #3: Properly handle while-loop", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -104,6 +107,7 @@ test("Variant #4: Properly handle break statements", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -138,6 +142,7 @@ test("Variant #5: Properly handle 'let' variables", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -163,6 +168,7 @@ test("Variant #6: Properly handle 'let' in for-loops", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening did applied @@ -191,6 +197,7 @@ test("Variant #7: Allow option to be set a percentage threshold", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: 0.5, + pack: true, }); // Ensure the output is the exact same @@ -232,6 +239,7 @@ test("Variant #8: Work when obfuscated multiple times", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -266,6 +274,7 @@ test("Variant #9: Don't entangle floats or NaN", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -311,6 +320,7 @@ test("Variant #10: Correctly entangle property keys", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -360,6 +370,7 @@ test("Variant #11: Flatten nested if statements", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -392,6 +403,7 @@ test("Variant #12: Properly handle nested for loops", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -432,6 +444,7 @@ test("Variant #13: Properly handle nested while-loops", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -479,6 +492,7 @@ test("Variant #14: Properly handle nested switch statements", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -494,38 +508,34 @@ test("Variant #16: Flatten with nested break and continue statements", async () var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; - - for ( var i =1; i < 10; i++ ) { - if(i%2==0){ + for (let i = 1; i < 10; i++) { + if (i % 2 == 0) { continue; } - if(i==7){ + if (i == 7) { break; } - TEST_ARRAY.push(i); + TEST_ARRAY["push"](i); } - var j; - - a: for ( var i = 0; i < 5; i++ ) { - if ( i == 3 ) { - for ( j = 0; j < 5; j++ ) { - if ( j == 1 ) {break a;} - if ( j % 2 == 0 ) { continue a;} + QYZuQDZ: for (let i2 = 0; i2 < 5; i2++) { + if (i2 == 3) { + for (j = 0; j < 5; j++) { + if (j == 1) { + break QYZuQDZ; + } + if (j % 2 == 0) { + continue QYZuQDZ; + } } - TEST_ARRAY.push(-1); + TEST_ARRAY["push"](-1); } } - - var fillerExpr1; - var fillerExpr2; - var fillerExpr3; - var fillerExpr4; - var fillerExpr5; `, { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -556,6 +566,7 @@ test("Variant #17: Flatten with infinite for loop and break", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -601,6 +612,7 @@ test("Variant #20: Work with redefined functions", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -625,6 +637,7 @@ test("Variant #21: Don't move Import Declarations", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -661,6 +674,7 @@ test("Variant #22: Don't break typeof expression", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -693,7 +707,7 @@ test("Variant #23: Don't break Super calls", async () => { var myObject = new MyClass2(); TEST_OUTPUT = myObject.val; // 10 `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); var TEST_OUTPUT; @@ -761,6 +775,7 @@ test("Variant #24: Nested function-calls with labeled breaks/continues", async ( renameVariables: true, identifierGenerator: "mangled", variableMasking: true, + pack: true, }); var TEST_OUTPUT; @@ -784,6 +799,7 @@ test("Variant #25: Don't break call expressions to bound functions", async () => var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -810,7 +826,7 @@ test("Variant #26: Add opaque predicates and still work", async () => { TEST_OUTPUT.push( true ? 3 : "Incorrect Conditional Statement" ); `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); expect(output).toContain("while"); @@ -824,7 +840,6 @@ test("Variant #26: Add opaque predicates and still work", async () => { test("Variant #27: Work on async/generator functions", async () => { var { code: output } = await JsConfuser.obfuscate( ` - "use strict"; async function myAsyncFunction(){ await (1); } @@ -842,7 +857,7 @@ test("Variant #27: Work on async/generator functions", async () => { var fillerVar2; var fillerVar3; `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); // Ensure Control Flow Flattening applied @@ -875,6 +890,7 @@ test("Variant #28: Don't break update expressions", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -944,6 +960,7 @@ test("Variant #29: Nested labeled break and continue statements with RGF enabled rgf: true, renameVariables: true, identifierGenerator: "mangled", + pack: true, }); // Run the code @@ -1039,6 +1056,7 @@ test("Variant #30: Properly handle switch statements", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -1067,7 +1085,7 @@ test("Variant #31: Don't break nested function calls", async () => { TEST_OUTPUT = counter; `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); expect(output).toContain("while"); @@ -1095,7 +1113,7 @@ test("Variant #32: Skip blocks with redefined functions", async () => { } TEST_OUTPUT = counter; `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); expect(output).not.toContain("while"); @@ -1133,7 +1151,7 @@ test("Variant #33: Skip blocks with name collision", async () => { TEST_OUTPUT = counter; `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); expect(output).not.toContain("while"); @@ -1205,6 +1223,7 @@ test("Variant #34: Flatten If Statements multiple times", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -1266,6 +1285,7 @@ test("Variant #36: Preserve modified global", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -1279,29 +1299,29 @@ test("Variant #37: Nested parameter across multiple functions", async () => { var { code } = await JsConfuser.obfuscate( ` function myFunction() { - function fn1(nestedParam) { - function fn2() { - return nestedParam; - } - - ("FN1 Body"); - console.log(nestedParam); - - return fn2(); - } + function fn1(nestedParam) { + function fn2() { + var _; + ("fn2 Body"); + return nestedParam; + } - ("FN2 Body"); + ("fn1 Body"); + return fn2(); + } - return fn1("Correct Value"); -} + ("myFunction Body"); + return fn1("Correct Value"); + } -var x = myFunction(); + var x = myFunction(); -TEST_OUTPUT = x; - `, + TEST_OUTPUT = x; + `, { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -1327,6 +1347,7 @@ test("Variant #38: Generator function with mangled numbers", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); expect(code).toContain("while"); diff --git a/test/transforms/dispatcher.test.ts b/test/transforms/dispatcher.test.ts index da58674..6e55c36 100644 --- a/test/transforms/dispatcher.test.ts +++ b/test/transforms/dispatcher.test.ts @@ -321,6 +321,7 @@ test("Variant #13: Work with Control Flow Flattening", async () => { target: "browser", dispatcher: true, controlFlowFlattening: true, + pack: true, }); var value = "never_called"; diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index 60a683f..d027610 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -712,6 +712,7 @@ test("Variant #28: Transform __JS_CONFUSER_VAR__ on High Preset", async () => { { target: "node", preset: "high", + pack: true, } ); @@ -722,3 +723,63 @@ test("Variant #28: Transform __JS_CONFUSER_VAR__ on High Preset", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); + +test("Variant #29: Redefined hoisted function", async () => { + var { code } = await JsConfuser.obfuscate( + ` + "use strict"; + function a() { + return 10; + } + + TEST_OUTPUT = []; + + for (var i = 1; i <= a(); i++) { + function a() { + return 5; + } + var b, c; + let d; + TEST_OUTPUT.push(i); + } + `, + { target: "node", renameVariables: true } + ); + + var TEST_OUTPUT; + eval(code); + + // Non-strict mode: [1,2,3,4,5] + // Strict mode: [1,2,3,4,5,6,7,8,9,10] + expect(TEST_OUTPUT).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +}); + +test("Variant #30: Non-strict mode hoisted function", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function getTen() { + return 10; + } + + var counter = 0; + + for (var i = 1; i <= getTen(); i++) { + function getFive() { + return 5; + } + + counter++; + } + + TEST_FUNCTION(counter); + `, + { target: "node", renameVariables: true } + ); + + var TEST_FUNCTION = (value) => (TEST_OUTPUT = value); + var TEST_OUTPUT; + + new Function("TEST_FUNCTION", code)(TEST_FUNCTION); + + expect(TEST_OUTPUT).toStrictEqual(10); +}); diff --git a/test/transforms/lock/integrity.test.ts b/test/transforms/lock/integrity.test.ts index d00c4ef..4e08bb4 100644 --- a/test/transforms/lock/integrity.test.ts +++ b/test/transforms/lock/integrity.test.ts @@ -129,6 +129,7 @@ test("Variant #5: Work on High Preset", async () => { lock: { integrity: true, }, + pack: true, } ); diff --git a/test/transforms/pack.test.ts b/test/transforms/pack.test.ts new file mode 100644 index 0000000..3c13c3a --- /dev/null +++ b/test/transforms/pack.test.ts @@ -0,0 +1,49 @@ +import JsConfuser from "../../src"; + +test("Variant #1: Pack output code", async () => { + var { code } = await JsConfuser.obfuscate(`TEST_OUTPUT = "Correct Value"`, { + target: "node", + pack: true, + }); + + expect(code.startsWith("Function")).toStrictEqual(true); + + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #2: Handle import statements", async () => { + var { code } = await JsConfuser.obfuscate( + ` + import { createHash } from "crypto"; + + var inputString = "Hash this string"; + var hashed = createHash("sha256").update(inputString).digest("hex"); + TEST_OUTPUT = hashed; + `, + { + target: "node", + pack: true, + } + ); + + // Ensure the import declaration wasn't moved + expect(code.startsWith("import")).toStrictEqual(true); + + // Convert to runnable code + code = code.replace( + `import{createHash}from"crypto";`, + "const {createHash}=require('crypto');" + ); + + var TEST_OUTPUT = ""; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual( + "1cac63f39fd68d8c531f27b807610fb3d50f0fc3f186995767fb6316e7200a3e" + ); +}); diff --git a/test/transforms/preparation.test.ts b/test/transforms/preparation.test.ts index a0d6f06..6435620 100644 --- a/test/transforms/preparation.test.ts +++ b/test/transforms/preparation.test.ts @@ -159,3 +159,65 @@ test("Variant #7: Force Variable declarations to be expanded", async () => { expect(output).toContain("var myIfVar2;"); expect(output).toContain("var myIfVar3"); }); + +test("Variant #8: Convert Regex Literals to `new RegExp()` constructor calls", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` + const numberRegex = /-?(?:\\d+\\.\\d+|\\.\\d+|\\d+)(?=\\b)/g; + + const testString = \` + This is a test -123 with numbers 456, 78.9, and .23, -0.45, -98.76, and 0.5. + Invalid numbers include -. and text like abc. + \`; + + var numbers = testString.match(numberRegex) + + TEST_OUTPUT = numbers; + `, + { + target: "node", + compact: true, // <- Something needs to be enabled + } + ); + + // Ensure the regex literal got changed + expect(output).toContain("new RegExp"); + expect(output).not.toContain("/g"); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual([ + "-123", + "456", + "78.9", + ".23", + "-0.45", + "-98.76", + "0.5", + ]); +}); + +test("Variant #9: Convert Template Literals into equivalent String Literal", async () => { + const { code } = await JsConfuser.obfuscate( + ` + var firstName = \`John\`; + var lastName = \`Doe\`; + + var fullName = \`\${firstName} \${lastName}\`; + TEST_OUTPUT = \`Hello \${fullName}!\`; + `, + { + target: "node", + compact: true, // <- Something needs to be enabled + } + ); + + // Ensure the template literals got changed + expect(code).not.toContain("`"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Hello John Doe!"); +}); diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index 15a192f..48912f3 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -136,6 +136,7 @@ test("Variant #6: Work on High Preset", async () => { target: "node", preset: "high", rgf: true, + pack: true, } ); diff --git a/test/transforms/string/stringCompression.test.ts b/test/transforms/string/stringCompression.test.ts index 34c685b..578cd0e 100644 --- a/test/transforms/string/stringCompression.test.ts +++ b/test/transforms/string/stringCompression.test.ts @@ -1,20 +1,30 @@ import JsConfuser from "../../../src/index"; -it("should work", async () => { - var { code: output } = await JsConfuser.obfuscate(`input("Hello World")`, { - target: "node", - stringCompression: true, - }); +test("Variant #1: Compress strings", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var str1 = "Hello World"; + var str2 = "Hello World"; + + TEST_OUTPUT = str1 === str2 ? str1 : "No Match"; + `, + { + target: "node", + stringCompression: true, + } + ); - var value, - input = (x) => (value = x); + // Ensure string was compressed + expect(code).not.toContain("Hello World"); - eval(output); + // Ensure the code still works + var TEST_OUTPUT; + eval(code); - expect(value).toStrictEqual("Hello World"); + expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); -it("should work on property keys", async () => { +test("Variant #2: Handle property keys", async () => { var code = ` var myObject = { myKey: 100 @@ -34,7 +44,7 @@ it("should work on property keys", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should work on class keys", async () => { +test("Variant #3: Handle class keys", async () => { var code = ` class MyClass { myMethod(){ @@ -58,7 +68,7 @@ it("should work on class keys", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should not encode constructor key", async () => { +test("Variant #4: Don't encode constructor key", async () => { var code = ` class MyClass { constructor(){ @@ -80,7 +90,7 @@ it("should not encode constructor key", async () => { expect(TEST_VAR).toStrictEqual(100); }); -it("should be configurable by custom function option", async () => { +test("Variant #5: Allow custom function option", async () => { var code = ` TEST_OUTPUT_1 = "My String 1"; TEST_OUTPUT_2 = "My String 2"; @@ -118,3 +128,33 @@ it("should be configurable by custom function option", async () => { expect(TEST_OUTPUT_2).toStrictEqual("My String 2"); expect(TEST_OUTPUT_3).toStrictEqual("My String 3"); }); + +test("Variant #6: Template strings", async () => { + var stringsCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = \`Hello World\` + `, + { + target: "node", + stringConcealing: (strValue) => { + stringsCollected.push(strValue); + + return true; + }, + } + ); + + // Ensure the string got concealed + expect(code).not.toContain("Hello World"); + + // Ensure the custom implementation was called + expect(stringsCollected).toContain("Hello World"); + + // Ensure the code works + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Hello World"); +}); diff --git a/test/transforms/string/stringConcealing.test.ts b/test/transforms/string/stringConcealing.test.ts index 9a7712f..8123771 100644 --- a/test/transforms/string/stringConcealing.test.ts +++ b/test/transforms/string/stringConcealing.test.ts @@ -353,3 +353,33 @@ test("Variant #14: Nested, duplicate strings", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); + +test("Variant #15: Template strings", async () => { + var stringsCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = \`Hello World\` + `, + { + target: "node", + stringConcealing: (strValue) => { + stringsCollected.push(strValue); + + return true; + }, + } + ); + + // Ensure the string got concealed + expect(code).not.toContain("Hello World"); + + // Ensure the custom implementation was called + expect(stringsCollected).toContain("Hello World"); + + // Ensure the code works + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Hello World"); +}); diff --git a/test/util/ast-utils.test.ts b/test/util/ast-utils.test.ts index 26f7e49..e68117f 100644 --- a/test/util/ast-utils.test.ts +++ b/test/util/ast-utils.test.ts @@ -137,6 +137,9 @@ describe("isDefiningIdentifier", () => { var {id = 1} = {} var [id = 1] = [] var {_: id} = {} + var _, id; + function f(id){} + function f(_, id){} function f({id}){} function f([id]){} function f({id = 1}){} From c166a10db8c7b9a3dafcb38f3f26f05341ab9391 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:32:07 -0400 Subject: [PATCH 034/103] Improve test coverage --- test/semantics/functionLength.test.ts | 1 + test/templates/template.test.ts | 38 ++++++++ test/transforms/calculator.test.ts | 40 ++++---- test/transforms/deadCode.test.ts | 23 +++++ test/transforms/dispatcher.test.ts | 34 +++++++ test/transforms/flatten.test.ts | 37 ++++++-- .../identifier/globalConcealing.test.ts | 32 +++++++ .../identifier/movedDeclarations.test.ts | 39 ++++++-- test/transforms/minify.test.ts | 25 +++++ test/transforms/rgf.test.ts | 35 ++----- test/transforms/shuffle.test.ts | 29 ++++++ .../string/stringCompression.test.ts | 23 +++++ test/transforms/string/stringEncoding.test.ts | 1 + test/transforms/variableMasking.test.ts | 42 ++++++-- test/util/ast-utils.test.ts | 95 +++++++++++++++---- test/util/object-utils.test.ts | 17 ++++ 16 files changed, 422 insertions(+), 89 deletions(-) create mode 100644 test/util/object-utils.test.ts diff --git a/test/semantics/functionLength.test.ts b/test/semantics/functionLength.test.ts index c3311c3..1ec0b0e 100644 --- a/test/semantics/functionLength.test.ts +++ b/test/semantics/functionLength.test.ts @@ -41,6 +41,7 @@ test("Variant #2: Allow user to disable preserving function.length", async () => stringSplitting: false, deadCode: false, duplicateLiteralsRemoval: false, + opaquePredicates: false, rgf: true, pack: true, diff --git a/test/templates/template.test.ts b/test/templates/template.test.ts index d9a1211..100cf0a 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -1,3 +1,4 @@ +import { UNSAFE } from "../../src/constants"; import Obfuscator from "../../src/obfuscator"; import Template from "../../src/templates/template"; import * as t from "@babel/types"; @@ -287,3 +288,40 @@ test("Variant #13: Handle multiple AST-based variables", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); + +test("Variant #14: Add symbols to template", async () => { + var ValidTemplate = new Template(` + function myUnsafeFunction(){ + return eval('"Correct Value"') + } + TEST_OUTPUT = myUnsafeFunction(); + `).addSymbols(UNSAFE); + + var file = ValidTemplate.file(); + var statements = file.program.body; + expect(statements.length).toStrictEqual(2); + + expect(statements[0][UNSAFE]).toStrictEqual(true); + expect(statements[1][UNSAFE]).toStrictEqual(true); + + var code = Obfuscator.generateCode(file); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #15: Error on duplicate node insertions", async () => { + var InvalidTemplate = new Template(` + var myString1 = {str} + var myString2 = {str} + TEST_OUTPUT = {str} + `); + + expect(() => { + InvalidTemplate.compile({ + str: t.stringLiteral("Duplicate node inserted"), + }); + }).toThrow(); +}); diff --git a/test/transforms/calculator.test.ts b/test/transforms/calculator.test.ts index 29e5492..80bc310 100644 --- a/test/transforms/calculator.test.ts +++ b/test/transforms/calculator.test.ts @@ -29,7 +29,7 @@ test("Variant #2: Result with correct values", async () => { }); test("Variant #3: Execute property with complex operations", async () => { - var code = `input((40 * 35 + 4) * 4 + 2)`; + var code = `input((40 * 35 + 4) * 4 + 2 + -20)`; var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", @@ -43,51 +43,45 @@ test("Variant #3: Execute property with complex operations", async () => { eval(output); - expect(value).toStrictEqual(5618); + expect(value).toStrictEqual(5598); }); -test("Variant #4: Apply to unary operators", async () => { +test("Variant #4: Don't break typeof expressions", async () => { var code = ` - var one = +1; - var negativeOne = -one; - - var trueValue = true; - var falseValue = !trueValue; - - TEST_OUTPUT = typeof (1, falseValue) === "boolean" && negativeOne === ~~-1 && void 0 === undefined; - `; + TEST_OUTPUT = typeof nonExistentVariable === "undefined"; + `; var { code: output } = await JsConfuser.obfuscate(code, { target: "node", calculator: true, }); - expect(output).toContain("_calc"); - expect(output).not.toContain("+1"); - expect(output).not.toContain("-one"); - expect(output).not.toContain("typeof(1,falseValue)"); - expect(output).not.toContain("void 0"); + expect(output).not.toContain("_calc"); - var TEST_OUTPUT = true; + var TEST_OUTPUT; eval(output); expect(TEST_OUTPUT).toStrictEqual(true); }); -test("Variant #5: Don't break typeof expressions", async () => { +test("Variant #5: Work with all binary operators", async () => { var code = ` - TEST_OUTPUT = typeof nonExistentVariable === "undefined"; - `; + let result = ( + (((5 + 3) * 2 - (6 / 3) % 4 + (10 ** 2) - (8 << 2) + (256 >> 3) | (15 & 7) ^ 12) * 3) + + (42 | 24) + + ((9 & 5) ^ (2 ^ 1)) + ) + (14 * (18 >>> 2)) - ((35 * 2) | (7 & 3)) + (50 ^ 21) + (~5) + (9 << 1) - (100 >> 2); + + TEST_OUTPUT = result; + `; var { code: output } = await JsConfuser.obfuscate(code, { target: "node", calculator: true, }); - expect(output).not.toContain("_calc"); - var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual(440); }); diff --git a/test/transforms/deadCode.test.ts b/test/transforms/deadCode.test.ts index 30e8a86..eba3280 100644 --- a/test/transforms/deadCode.test.ts +++ b/test/transforms/deadCode.test.ts @@ -83,3 +83,26 @@ test("Variant #2: Preserve 'use strict' directive", async () => { expect(value).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); + +test("Variant #3: Custom probability function", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function myFunction(){ + return "Correct Value"; + } + TEST_OUTPUT = myFunction(); + + `, + { + target: "browser", + deadCode: () => { + return true; + }, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/dispatcher.test.ts b/test/transforms/dispatcher.test.ts index 6e55c36..2a6c02c 100644 --- a/test/transforms/dispatcher.test.ts +++ b/test/transforms/dispatcher.test.ts @@ -492,3 +492,37 @@ test("Variant #19: Lexically bound variables", async () => { expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); + +test("Variant #20: Ignore functions with 'use strict' directive", async () => { + var { code: output } = await JsConfuser.obfuscate( + ` + function myFunction(){ + "use strict"; + + // In strict mode, undefined cannot be reassigned + try { + undefined = false; + } catch(e) { + return "Correct Value"; + } + + return "Incorrect Value"; + } + + TEST_OUTPUT = myFunction(); + `, + { + target: "node", + dispatcher: true, + pack: true, // Escape Jest strict mode + } + ); + + expect(output).not.toContain("dispatcher_0"); + + var TEST_OUTPUT; + + eval(output); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/flatten.test.ts b/test/transforms/flatten.test.ts index 625a530..40140ae 100644 --- a/test/transforms/flatten.test.ts +++ b/test/transforms/flatten.test.ts @@ -387,7 +387,7 @@ test("Variant #13: Work with assignment expression in the return statement", asy expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); -test("Variant #14: Work with 'use strict' directive", async () => { +test("Variant #14: Ignore functions with 'use strict' directive", async () => { var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ @@ -401,8 +401,8 @@ test("Variant #14: Work with 'use strict' directive", async () => { { target: "node", flatten: true } ); - // Ensure flat was applied - expect(output).toContain("_flat_myFunction"); + // Ensure flatten was not applied + expect(output).not.toContain("_flat_"); var TEST_OUTPUT; eval(output); @@ -547,12 +547,12 @@ test("Variant #18: Redefined variable in nested scope + Rename Variables", async { target: "node", flatten: true, - renameVariables: (x) => !x.includes("_flat_"), + renameVariables: true, } ); - expect(output).toContain("_flat_myFunction1"); - expect(output).toContain("_flat_myFunction2"); + // Ensure flat object was found + expect(output).toContain("get"); var TEST_OUTPUT_1, TEST_OUTPUT_2; eval(output); @@ -719,3 +719,28 @@ test("Variant #24: Typeof expression", async () => { eval(output); expect(TEST_OUTPUT).toStrictEqual(true); }); + +test("Variant #25: Handle __JS_CONFUSER_VAR__ function", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var myVar = "Correct Value"; + + function myFunction(){ + TEST_OUTPUT = __JS_CONFUSER_VAR__(myVar); + } + + myFunction(); + `, + { + target: "node", + flatten: true, + renameVariables: true, + } + ); + + expect(code).not.toContain("__JS_CONFUSER_VAR__"); + + var TEST_OUTPUT; + eval(code); + expect(TEST_OUTPUT).not.toBeUndefined(); +}); diff --git a/test/transforms/identifier/globalConcealing.test.ts b/test/transforms/identifier/globalConcealing.test.ts index e93d10f..ec0553a 100644 --- a/test/transforms/identifier/globalConcealing.test.ts +++ b/test/transforms/identifier/globalConcealing.test.ts @@ -207,6 +207,7 @@ test("Variant #10: Properly handle declared global variables", async () => { ` function console(){ VALID_GLOBAL.TEST_PROPERTY = true; + VALID_GLOBAL.ANOTHER_PROPERTY = true; INVALID_GLOBAL = true; } @@ -236,3 +237,34 @@ test("Variant #10: Properly handle declared global variables", async () => { expect(VALID_GLOBAL.TEST_PROPERTY).toStrictEqual(true); expect(INVALID_GLOBAL).toStrictEqual(true); }); + +test("Variant #11: Shadowed global variable", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function testFunction(){ + var console = { + log: () => { + TEST_OUTPUT = "Correct Value"; + } + }; + function innerFunction(){ + console.log("You should not see this."); + } + + innerFunction() + } + + testFunction(); + + `, + { + target: "node", + globalConcealing: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index adbd40a..cc06eb5 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -197,22 +197,16 @@ test("Variant #9: Defined variable without an initializer + CFF + Duplicate Lite TEST_OUTPUT = x + y; `; - var { code: output1 } = await JsConfuser.obfuscate(code, { - target: "node", - movedDeclarations: true, - controlFlowFlattening: true, - duplicateLiteralsRemoval: true, - }); - - var { code: output2 } = await JsConfuser.obfuscate(output1, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, controlFlowFlattening: true, duplicateLiteralsRemoval: true, + pack: true, }); var TEST_OUTPUT; - eval(output2); + eval(output); expect(TEST_OUTPUT).toStrictEqual(3); }); @@ -357,3 +351,30 @@ test("Variant #12: Move function declaration as parameter", async () => { "Correct Value", ]); }); + +test("Variant #13: Variable and parameter with the same name", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function abc(a, b, c) { + var c = a + b + c; + + TEST_OUTPUT = c; + } + + abc(1, 2, 3); + `, + { + target: "node", + movedDeclarations: true, + + // Harden the test by renaming variables + renameVariables: true, + identifierGenerator: "mangled", + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(6); +}); diff --git a/test/transforms/minify.test.ts b/test/transforms/minify.test.ts index 05146bf..08d1cf0 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -777,3 +777,28 @@ test("Variant #31: Dead code elimination", async () => { expect(TEST_OUTPUT).toStrictEqual([1, 2, 3]); }); + +test("Variant #32: Work with Eval calls", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var localVar = false; + eval(__JS_CONFUSER_VAR__(localVar) + " = true") + if(!localVar) { + TEST_OUTPUT = "Incorrect Value"; + } + + if(!TEST_OUTPUT) { + TEST_OUTPUT = "Correct Value"; + } + `, + { + target: "node", + minify: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index 48912f3..a54edbc 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -276,33 +276,12 @@ test("Variant #10: Configurable by custom function option", async () => { var { code: output } = await JsConfuser.obfuscate( ` - "use strict"; - - // By checking strict-mode, we can check if the function was RGF or not function rgfThisFunction(){ - var isStrictMode = () => { - try { - undefined = true; - } catch (E) { - return true; - } - return false; - } - - return isStrictMode(); + return true; } - + function doNotRgfThisFunction(){ - var isStrictMode = () => { - try { - undefined = true; - } catch (E) { - return true; - } - return false; - } - - return isStrictMode(); + return true; } TEST_OUTPUT_1 = rgfThisFunction(); @@ -314,6 +293,7 @@ test("Variant #10: Configurable by custom function option", async () => { functionNames.push(name); return name !== "doNotRgfThisFunction"; }, + pack: true, } ); @@ -323,11 +303,14 @@ test("Variant #10: Configurable by custom function option", async () => { ]); expect(output).toContain("eval"); + expect(output).not.toContain("rgfThisFunction(){return true"); + expect(output).toContain("doNotRgfThisFunction(){return true"); + var TEST_OUTPUT_1; var TEST_OUTPUT_2; eval(output); - expect(TEST_OUTPUT_1).toStrictEqual(false); + expect(TEST_OUTPUT_1).toStrictEqual(true); expect(TEST_OUTPUT_2).toStrictEqual(true); }); @@ -351,7 +334,7 @@ test("Variant #11: Function containing function should both be changed", async f ); // 2 means one Function changed, 3 means two Functions changed - expect(output.split("_rgf_eval(").length).toStrictEqual(3); + expect(output.split('_rgf_eval("').length).toStrictEqual(3); var TEST_OUTPUT; eval(output); diff --git a/test/transforms/shuffle.test.ts b/test/transforms/shuffle.test.ts index 03c9314..5214273 100644 --- a/test/transforms/shuffle.test.ts +++ b/test/transforms/shuffle.test.ts @@ -83,3 +83,32 @@ test("Variant #4: Don't use common variable names like x", async () => { eval(output); expect(VALUE).toEqual([1, 2, 3, 4, 5, 6]); }); + +test("Variant #5: Don't apply to arrays with non-pure elements", async () => { + var shuffleCalled = false; + + var { code } = await JsConfuser.obfuscate( + ` + var counter = 0; + function increment(by) { + counter += by; + return counter; + } + TEST_OUTPUT = [increment(1), increment(2), increment(3), increment(4)]; + `, + { + target: "node", + shuffle: () => { + shuffleCalled = true; + return true; + }, + } + ); + + expect(shuffleCalled).toStrictEqual(false); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual([1, 3, 6, 10]); +}); diff --git a/test/transforms/string/stringCompression.test.ts b/test/transforms/string/stringCompression.test.ts index 578cd0e..5dc7553 100644 --- a/test/transforms/string/stringCompression.test.ts +++ b/test/transforms/string/stringCompression.test.ts @@ -158,3 +158,26 @@ test("Variant #6: Template strings", async () => { expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); + +test("Variant #7: Work with Rename Variables", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var myVar = "Hello World"; + TEST_OUTPUT = myVar; + `, + { + target: "node", + stringCompression: true, + renameVariables: true, + } + ); + + // Ensure String Compression applied + expect(code).not.toContain("Hello World"); + + // Ensure the code still works + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Hello World"); +}); diff --git a/test/transforms/string/stringEncoding.test.ts b/test/transforms/string/stringEncoding.test.ts index dc27c0e..fe3cd2c 100644 --- a/test/transforms/string/stringEncoding.test.ts +++ b/test/transforms/string/stringEncoding.test.ts @@ -95,6 +95,7 @@ test("Variant #5: Preserve 'use strict' directive", async () => { var { code: output } = await JsConfuser.obfuscate(code, { target: "node", preset: "high", + pack: false, }); expect(output.startsWith('"use strict"')).toStrictEqual(true); diff --git a/test/transforms/variableMasking.test.ts b/test/transforms/variableMasking.test.ts index 707e802..f9f3ab0 100644 --- a/test/transforms/variableMasking.test.ts +++ b/test/transforms/variableMasking.test.ts @@ -582,11 +582,11 @@ test("Variant #19: For, For In/Of Statement", async () => { ` function declarationInitializer(){ var items = [1,2,3,4,5], output = 0; - for(var i1 = 0; i1 < items.length; i1++) { - output += items[i1] + for(var i1_ = 0; i1_ < items.length; i1_++) { + output += items[i1_] } - for(var i2 in items) { - output += items[i2] * 2 + for(var i2_ in items) { + output += items[i2_] * 2 } for(var item of items) { output += item * 2 @@ -595,12 +595,12 @@ test("Variant #19: For, For In/Of Statement", async () => { } function nonDeclarationInitializer(){ - var items = [5,6,7,8,9,10], output = 0, i1, item; - for(i1 = 0; i1 < items.length; i1++) { - output += items[i1] + var items = [5,6,7,8,9,10], output = 0, i1_, item; + for(i1_ = 0; i1_ < items.length; i1_++) { + output += items[i1_] } - for(i1 in items) { - output += items[i1] * 2 + for(i1_ in items) { + output += items[i1_] * 2 } for(item of items) { output += item * 2 @@ -636,3 +636,27 @@ test("Variant #19: For, For In/Of Statement", async () => { expect(TEST_OUTPUT).toStrictEqual(300); }); + +test("Variant #20: Handle __JS_CONFUSER_VAR__ function", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function myFunction(){ + var myVar = "Correct Value"; + TEST_OUTPUT = __JS_CONFUSER_VAR__(myVar); + } + + myFunction(); + `, + { + target: "node", + variableMasking: true, + renameVariables: true, + } + ); + + expect(code).not.toContain("__JS_CONFUSER_VAR__"); + + var TEST_OUTPUT; + eval(code); + expect(TEST_OUTPUT).not.toBeUndefined(); +}); diff --git a/test/util/ast-utils.test.ts b/test/util/ast-utils.test.ts index e68117f..e8da1d5 100644 --- a/test/util/ast-utils.test.ts +++ b/test/util/ast-utils.test.ts @@ -2,9 +2,12 @@ import { ok } from "assert"; import Obfuscator from "../../src/obfuscator"; import { getFunctionName, + getPatternIdentifierNames, isDefiningIdentifier, + isUndefined, } from "../../src/utils/ast-utils"; -import traverse from "@babel/traverse"; +import traverse, { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; describe("getFunctionName", () => { test("Variant #1: Function Declaration / Expression", () => { @@ -107,24 +110,27 @@ describe("getFunctionName", () => { }); }); -describe("isDefiningIdentifier", () => { - function getIdentifierPath(sourceCode: string, targetIdentifierName = "id") { - var ast = Obfuscator.parseCode(sourceCode); - var returnPath; - - traverse(ast, { - Identifier(path) { - if (path.node.name === targetIdentifierName) { - returnPath = path; - } - }, - }); +function getIdentifierPath( + sourceCode: string, + targetIdentifierName = "id" +): NodePath { + const ast = Obfuscator.parseCode(sourceCode); + let returnPath; + + traverse(ast, { + Identifier(path) { + if (path.node.name === targetIdentifierName) { + returnPath = path; + } + }, + }); - ok(returnPath, `${targetIdentifierName} not found in ${sourceCode}`); + ok(returnPath, `${targetIdentifierName} not found in ${sourceCode}`); - return returnPath; - } + return returnPath; +} +describe("isDefiningIdentifier", () => { test("Variant #1: True examples", () => { ` var id = 1 @@ -152,6 +158,11 @@ describe("isDefiningIdentifier", () => { try{}catch({_: id}){} for(var id in []){} for(var id of []){} + import id from "module" + import * as id from "module" + import {id} from "module" + import {_ as id} from "module" + import {_, id} from "module" ` .split("\n") .forEach((sourceCode) => { @@ -189,6 +200,8 @@ describe("isDefiningIdentifier", () => { try{}catch({_: _ = id}){} try{}catch({id: _ = _}){} try{}catch({_ = id}){} + import {id as _} from "module" + import {_, id as __} from "module" ` .split("\n") .forEach((sourceCode) => { @@ -204,3 +217,53 @@ describe("isDefiningIdentifier", () => { }); }); }); + +describe("getPatternIdentifierNames", () => { + test("Variant #1: Function parameters", () => { + var sampleOne = ` + function abc(id1, {id2}, {_: id3}, id4 = 1, {id5 = 1}, {_: [id6] = 1}, ...id7){} + `; + + var path = getIdentifierPath(sampleOne, "id1"); + var functionPath = path.getFunctionParent()!; + + expect(functionPath.type).toStrictEqual("FunctionDeclaration"); + + var result = getPatternIdentifierNames(functionPath.get("params")); + expect(result).toStrictEqual( + new Set(["id1", "id2", "id3", "id4", "id5", "id6", "id7"]) + ); + }); +}); + +function getExpression(code) { + var ast = Obfuscator.parseCode(code); + + ok(ast.program.body.length === 1); + var returnPath: NodePath | null = null; + traverse(ast, { + Program(path) { + returnPath = path + .get("body")[0] + .get("expression") as NodePath; + + ok(returnPath.isExpression()); + }, + }); + + ok(returnPath); + + return returnPath!; +} + +describe("isUndefined", () => { + test("Variant #1: True examples", () => { + expect(isUndefined(getExpression("undefined"))).toStrictEqual(true); + expect(isUndefined(getExpression("void 0"))).toStrictEqual(true); + }); + + test("Variant #2: False examples", () => { + expect(isUndefined(getExpression("myIdentifier"))).toStrictEqual(false); + expect(isUndefined(getExpression("10+10"))).toStrictEqual(false); + }); +}); diff --git a/test/util/object-utils.test.ts b/test/util/object-utils.test.ts new file mode 100644 index 0000000..91327e6 --- /dev/null +++ b/test/util/object-utils.test.ts @@ -0,0 +1,17 @@ +import { createObject } from "../../src/utils/object-utils"; + +describe("createObject", () => { + test("Variant #1: Simple object", () => { + expect(createObject(["a", "b", "c"], [1, 2, 3])).toStrictEqual({ + a: 1, + b: 2, + c: 3, + }); + }); + + test("Variant #2: Length mismatch", () => { + expect(() => createObject(["a", "b", "c", "d"], [1, 2, 3])).toThrow( + "length mismatch" + ); + }); +}); From 679d519738d427b9b97fffa127b1e7b5e8dd574b Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:32:50 -0400 Subject: [PATCH 035/103] Test refactoring --- test/index.test.ts | 14 +++- test/obfuscator.test.ts | 35 +++++++++ test/options.test.ts | 26 ++++--- test/presets.test.ts | 31 ++++---- .../controlFlowFlattening.test.ts | 78 ++++++++++++++++++- .../identifier/renameVariables.test.ts | 31 +++++++- test/transforms/lock/countermeasures.test.ts | 20 +++++ test/transforms/lock/integrity.test.ts | 48 +++++++++++- 8 files changed, 247 insertions(+), 36 deletions(-) create mode 100644 test/obfuscator.test.ts diff --git a/test/index.test.ts b/test/index.test.ts index e8bb1ee..58dddca 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -95,8 +95,8 @@ describe("obfuscateWithProfiler", () => { var called = false; var callback = (log: ProfilerLog) => { + expect(typeof log.index).toStrictEqual("number"); expect(typeof log.currentTransform).toStrictEqual("string"); - expect(typeof log.currentTransformNumber).toStrictEqual("number"); expect(typeof log.totalTransforms).toStrictEqual("number"); if (typeof log.nextTransform !== "undefined") { expect(typeof log.nextTransform).toStrictEqual("string"); @@ -119,10 +119,16 @@ describe("obfuscateWithProfiler", () => { expect(typeof profileData.parseTime).toStrictEqual("number"); expect(typeof profileData.totalPossibleTransforms).toStrictEqual("number"); expect(typeof profileData.totalTransforms).toStrictEqual("number"); - expect(typeof profileData.transformTimeMap).toStrictEqual("object"); - expect(typeof profileData.transformTimeMap.RenameVariables).toStrictEqual( - "number" + expect(typeof profileData.transforms).toStrictEqual("object"); + expect(typeof profileData.transforms.RenameVariables).toStrictEqual( + "object" ); + expect( + typeof profileData.transforms.RenameVariables.transformTime + ).toStrictEqual("number"); + expect( + typeof profileData.transforms.RenameVariables.changeData.variables + ).toStrictEqual("number"); eval(code); expect(called).toStrictEqual(true); diff --git a/test/obfuscator.test.ts b/test/obfuscator.test.ts new file mode 100644 index 0000000..503b7fb --- /dev/null +++ b/test/obfuscator.test.ts @@ -0,0 +1,35 @@ +import Obfuscator from "../src/obfuscator"; + +describe("globalState.lock.createCountermeasuresCode", () => { + test("Variant #1: Error when lock is not enabled", () => { + const obfuscator = new Obfuscator({ target: "node", compact: false }); + + expect(() => { + obfuscator.globalState.lock.createCountermeasuresCode(); + }).toThrow("Not implemented"); + }); +}); + +describe("shouldTransformNativeFunction", () => { + test("Variant #1: Return false when tamperProtection is not enabled", () => { + const obfuscator = new Obfuscator({ target: "browser", compact: false }); + + expect(obfuscator.shouldTransformNativeFunction(["fetch"])).toStrictEqual( + false + ); + }); + + test("Variant #2: fetch() should be transformed", () => { + const obfuscator = new Obfuscator({ + target: "browser", + compact: false, + lock: { + tamperProtection: true, + }, + }); + + expect(obfuscator.shouldTransformNativeFunction(["fetch"])).toStrictEqual( + true + ); + }); +}); diff --git a/test/options.test.ts b/test/options.test.ts index 54acf69..2490fde 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -74,16 +74,24 @@ test("Variant #5: Work with compact set to true", async () => { expect(output).toContain("var a;var b;var c;"); }); -test("Variant #6: Work with debugComments enabled", async () => { - var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { - target: "node", - renameGlobals: true, - renameVariables: true, - compact: false, - debugComments: true, - }); +test("Variant #6: Verbose option", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var object = { key: 1 }; + TEST_OUTPUT = object.key; + `, + { + target: "node", + compact: false, + verbose: true, + objectExtraction: true, + } + ); - expect(output).not.toContain("TEST_VARIABLE"); + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(1); }); test("Variant #7: Error on invalid lock option", async () => { diff --git a/test/presets.test.ts b/test/presets.test.ts index a54c0a1..219b1a3 100644 --- a/test/presets.test.ts +++ b/test/presets.test.ts @@ -1,22 +1,17 @@ import presets from "../src/presets"; +import { validateOptions } from "../src/validateOptions"; -it('High preset should have "preset": "high"', async () => { - expect(presets.high.preset).toStrictEqual("high"); -}); +test.each(Object.keys(presets))( + "Variant #1: Preset is valid options", + (presetName) => { + const preset = presets[presetName]; + expect(typeof preset).toStrictEqual("object"); -it('Medium preset should have "preset": "medium"', async () => { - expect(presets.medium.preset).toStrictEqual("medium"); -}); + expect(preset.preset).toStrictEqual(presetName); -it('Low preset should have "preset": "low"', async () => { - expect(presets.low.preset).toStrictEqual("low"); -}); - -it("No preset should have eval, lock, or RGF enabled", async () => { - Object.keys(presets).forEach((key) => { - expect(typeof presets[key]).toStrictEqual("object"); - expect(!!presets[key].eval).toEqual(false); - expect(!!presets[key].lock).toEqual(false); - expect(!!presets[key].rgf).toEqual(false); - }); -}); + // Validate options + expect(() => { + validateOptions(preset); + }).not.toThrow; + } +); diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 54992b0..1d4e52d 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -691,6 +691,9 @@ test("Variant #23: Don't break Super calls", async () => { class MyClass1 { constructor(val){ this.val = val; + + // Ensure ControlFlowFlattening applies here + var a, b, c; } } class MyClass2 extends MyClass1 { @@ -698,9 +701,7 @@ test("Variant #23: Don't break Super calls", async () => { super(10); // Ensure ControlFlowFlattening applies here - var filler1; - var filler2; - var filler3; + var a, b, c; } } @@ -841,10 +842,12 @@ test("Variant #27: Work on async/generator functions", async () => { var { code: output } = await JsConfuser.obfuscate( ` async function myAsyncFunction(){ + var a,b,c; await (1); } function* myGeneratorFunction(){ + var a,b,c; yield "Correct Value"; } @@ -1357,3 +1360,72 @@ test("Variant #38: Generator function with mangled numbers", async () => { eval(code); expect(TEST_OUTPUT).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); + +test("Variant #38: Handle __JS_CONFUSER_VAR__ function", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var myVar = "Correct Value"; + var output = eval(__JS_CONFUSER_VAR__(myVar)); + TEST_OUTPUT = output; + `, + { + target: "node", + renameVariables: true, + controlFlowFlattening: true, + pack: true, + } + ); + + expect(code).not.toContain("__JS_CONFUSER_VAR__"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #39: Let/Const variable declarations", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function indexOf(str, substr) { + const len = str.length; + const sublen = substr.length; + let count = 0; + + if (sublen > len) { + return -1; + } + + for (let i = 0; i <= len - sublen; i++) { + for (let j = 0; j < sublen; j++) { + if (str[i + j] === substr[j]) { + count++; + if (count === sublen) { + return i; + } + } else { + count = 0; + break; + } + } + } + + return -1; +} + +TEST_OUTPUT = indexOf("Hello World", "World"); + `, + { + target: "node", + controlFlowFlattening: true, + calculator: true, + stringConcealing: true, + pack: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(6); +}); diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index d027610..8926e8e 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -1,6 +1,10 @@ import JsConfuser from "../../../src/index"; import { ObfuscateOptions } from "../../../src/options"; +// Used for tests #15 and #21 +const customIdentifierGenerator = () => + "_" + Math.random().toString(36).substr(2, 9); + test("Variant #1: Rename variables properly", async () => { var code = "var TEST_VARIABLE = 1;"; var { code: output } = await JsConfuser.obfuscate(code, { @@ -348,7 +352,11 @@ test("Variant #14: should not break global variable references", async () => { expect(value).toStrictEqual("Hello World"); }); -test.each(["randomized", "mangled"])( +test.each([ + "randomized", + "mangled", + customIdentifierGenerator, +])( "Variant #15: Function parameter default value", async (identifierGeneratorMode) => { /** @@ -529,6 +537,7 @@ test.each([ "mangled", "number", "zeroWidth", + customIdentifierGenerator, ])( "Variant #21: Work with custom identifierGenerator mode", async (identifierGeneratorMode) => { @@ -783,3 +792,23 @@ test("Variant #30: Non-strict mode hoisted function", async () => { expect(TEST_OUTPUT).toStrictEqual(10); }); + +test("Variant #31: Mangled identifier", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var outsideValue = "Correct Value"; + + function functionWithParameter(a) { + TEST_OUTPUT = outsideValue; + } + + functionWithParameter("Incorrect Value"); // Correct Value + `, + { target: "node", renameVariables: true, identifierGenerator: "mangled" } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/lock/countermeasures.test.ts b/test/transforms/lock/countermeasures.test.ts index 6dc717c..e37b263 100644 --- a/test/transforms/lock/countermeasures.test.ts +++ b/test/transforms/lock/countermeasures.test.ts @@ -105,3 +105,23 @@ test("Variant #5: Should work with RGF enabled", async () => { expect(TEST_OUTPUT).toStrictEqual(true); }); + +test("Variant #6: Disallow reassignments to the countermeasures function", async () => { + const sourceCode = ` + function myCountermeasuresFunction(){ + + } + myCountermeasuresFunction = function(){ + console.log("This is not allowed"); + } + `; + + expect(async () => { + return JsConfuser.obfuscate(sourceCode, { + target: "node", + lock: { + countermeasures: "myCountermeasuresFunction", + }, + }); + }).rejects.toThrow("Countermeasures function cannot be reassigned"); +}); diff --git a/test/transforms/lock/integrity.test.ts b/test/transforms/lock/integrity.test.ts index 4e08bb4..df82499 100644 --- a/test/transforms/lock/integrity.test.ts +++ b/test/transforms/lock/integrity.test.ts @@ -122,7 +122,13 @@ test("Variant #4: Error when countermeasures function doesn't exist", async () = test("Variant #5: Work on High Preset", async () => { var { code: output } = await JsConfuser.obfuscate( - `TEST_OUTPUT = ("Hello World")`, + ` + function MyFunction(){ + TEST_OUTPUT = ("Hello World") + } + + MyFunction(); + `, { target: "node", preset: "high", @@ -162,3 +168,43 @@ test("Variant #6: Work with RGF enabled", async () => { expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); + +test("Variant #7: Allow custom implementation for integrity", async () => { + var namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function countermeasures(){ + throw new Error("Countermeasures was called"); + } + function MyFunction1(){ // Will receive Integrity + + } + + function MyFunction2(){ // Will not receive Integrity + TEST_OUTPUT = "Incorrect Value"; + } + + MyFunction2(); + `, + { + target: "node", + lock: { + countermeasures: "countermeasures", + integrity: (fnName) => { + namesCollected.push(fnName); + + return fnName === "MyFunction1"; + }, + }, + } + ); + + expect(namesCollected).toStrictEqual(["MyFunction1", "MyFunction2"]); + + code = code.replace("Incorrect Value", "Correct Value"); + + var TEST_OUTPUT; + eval(code); + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); From 3550cd4a0ea0f27af282419678faab9081732903 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:34:35 -0400 Subject: [PATCH 036/103] Code refactoring --- index.d.ts | 16 +- src/constants.ts | 105 ++++++++++ src/index.ts | 35 ++-- src/obfuscationResult.ts | 27 ++- src/obfuscator.ts | 185 ++++++++++++----- src/options.ts | 256 ++++++------------------ src/order.ts | 6 +- src/presets.ts | 65 +++--- src/probability.ts | 7 +- src/templates/template.ts | 33 +++- src/utils/ControlObject.ts | 19 +- src/utils/NameGen.ts | 41 +++- src/utils/ast-utils.ts | 384 ++++++++++++++++++++++++++---------- src/utils/function-utils.ts | 7 +- src/utils/gen-utils.ts | 52 +---- src/utils/node.ts | 33 +++- src/utils/object-utils.ts | 13 +- src/utils/random-utils.ts | 12 +- src/utils/scope-utils.ts | 129 ------------ src/utils/static-utils.ts | 18 +- src/validateOptions.ts | 9 +- 21 files changed, 813 insertions(+), 639 deletions(-) delete mode 100644 src/utils/scope-utils.ts diff --git a/index.d.ts b/index.d.ts index 63eea68..7c5e0b7 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,17 +1,3 @@ -import ObfuscatorClass from "./src/obfuscator"; -import { - IJsConfuser as JsConfuser, - IJsConfuserDebugObfuscation, - IJsConfuserDebugTransformations, - IJsConfuserObfuscate, - IJsConfuserObfuscateAST, - IJsConfuserPresets, -} from "./src/types"; +import JsConfuser from "./src"; export default JsConfuser; -export const obfuscate: IJsConfuserObfuscate; -export const obfuscateAST: IJsConfuserObfuscateAST; -export const presets: IJsConfuserPresets; -export const debugTransformations: IJsConfuserDebugTransformations; -export const debugObfuscation: IJsConfuserDebugObfuscation; -export const Obfuscator: typeof ObfuscatorClass; diff --git a/src/constants.ts b/src/constants.ts index 3a8f8a9..ad8c510 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -35,6 +35,32 @@ export const CONTROL_OBJECTS = Symbol("controlObjects"); export const NO_RENAME = Symbol("noRename"); +/** + * This Identifier is used for a hexadecimal number or escaped string. + */ +export const GEN_NODE = Symbol("genNode"); + +/** + * This function is used to mark functions that when transformed will most likely cause a maximum call stack error. + * + * Examples: Native Function Check + */ +export const MULTI_TRANSFORM = Symbol("multiTransform"); + +/** + * The function contains a `with` statement. + * + * OR + * + * This identifier is used for a `with` statement. + * + * Tells Pack not to globally transform the node. + */ +export const WITH_STATEMENT = Symbol("withStatement"); + +/** + * Symbols describe precomputed semantics of a node, allowing the obfuscator to make the best choices for the node. + */ export interface NodeSymbol { [UNSAFE]?: boolean; [PREDICTABLE]?: boolean; @@ -42,6 +68,10 @@ export interface NodeSymbol { [FN_LENGTH]?: number; [CONTROL_OBJECTS]?: ControlObject[]; [NO_RENAME]?: string; + + [GEN_NODE]?: boolean; + [MULTI_TRANSFORM]?: boolean; + [WITH_STATEMENT]?: boolean; } /** @@ -51,3 +81,78 @@ export const variableFunctionName = "__JS_CONFUSER_VAR__"; export const noRenameVariablePrefix = "__NO_JS_CONFUSER_RENAME__"; export const placeholderVariablePrefix = "__p_"; + +/** + * Identifiers that are not actually variables. + */ +export const reservedIdentifiers = new Set([ + "undefined", + "null", + "NaN", + "Infinity", + "eval", + "arguments", +]); + +export const reservedObjectPrototype = new Set([ + "toString", + "valueOf", + "constructor", + "__proto__", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "toLocaleString", +]); + +/** + * For Zero Width generator - Mangled variable names + */ +export const reservedKeywords = [ + "if", + "in", + "for", + "let", + "new", + "try", + "var", + "case", + "else", + "null", + "break", + "catch", + "class", + "const", + "super", + "throw", + "while", + "yield", + "delete", + "export", + "import", + "public", + "return", + "switch", + "default", + "finally", + "private", + "continue", + "debugger", + "function", + "arguments", + "protected", + "instanceof", + "await", + "async", + + // new key words and other fun stuff :P + "NaN", + "undefined", + "true", + "false", + "typeof", + "this", + "static", + "void", + "of", +]; diff --git a/src/index.ts b/src/index.ts index b0b7bed..597f539 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,12 +7,14 @@ import { ProfilerCallback, ProfilerLog, } from "./obfuscationResult"; +import presets from "./presets"; +import Template from "./templates/template"; export async function obfuscate( sourceCode: string, options: ObfuscateOptions ): Promise { - var obfuscator = new Obfuscator(options); + const obfuscator = new Obfuscator(options); return obfuscator.obfuscate(sourceCode); } @@ -21,7 +23,7 @@ export async function obfuscateAST( ast: babelTypes.File, options: ObfuscateOptions ) { - var obfuscator = new Obfuscator(options); + const obfuscator = new Obfuscator(options); return obfuscator.obfuscateAST(ast); } @@ -36,31 +38,40 @@ export async function obfuscateWithProfiler( ): Promise { const startTime = performance.now(); - var obfuscator = new Obfuscator(options); - var totalTransforms = obfuscator.plugins.length; + const obfuscator = new Obfuscator(options); + let totalTransforms = obfuscator.plugins.length; - var transformTimeMap: { [transformName: string]: number } = - Object.create(null); - var currentTransformTime = performance.now(); + let transformMap: ProfileData["transforms"] = Object.create(null); const beforeParseTime = performance.now(); - var ast = Obfuscator.parseCode(sourceCode); + let ast = Obfuscator.parseCode(sourceCode); const parseTime = performance.now() - beforeParseTime; + let currentTransformTime = performance.now(); + ast = obfuscator.obfuscateAST(ast, { profiler: (log: ProfilerLog) => { var nowTime = performance.now(); - transformTimeMap[log.currentTransform] = nowTime - currentTransformTime; + transformMap[log.currentTransform] = { + transformTime: nowTime - currentTransformTime, + changeData: {}, + }; currentTransformTime = nowTime; profiler.callback(log); }, }); + obfuscator.plugins.forEach(({ pluginInstance }) => { + if (transformMap[pluginInstance.name]) { + transformMap[pluginInstance.name].changeData = pluginInstance.changeData; + } + }); + const beforeCompileTime = performance.now(); - var code = Obfuscator.generateCode(ast, obfuscator.options); + const code = Obfuscator.generateCode(ast, obfuscator.options); const compileTime = performance.now() - beforeCompileTime; @@ -71,7 +82,7 @@ export async function obfuscateWithProfiler( return { code: code, profileData: { - transformTimeMap: transformTimeMap, + transforms: transformMap, obfuscationTime: obfuscationTime, parseTime: parseTime, compileTime: compileTime, @@ -85,6 +96,8 @@ const JsConfuser = { obfuscate, obfuscateAST, obfuscateWithProfiler, + presets, + Template, }; export default JsConfuser; diff --git a/src/obfuscationResult.ts b/src/obfuscationResult.ts index 935e75b..028a5c2 100644 --- a/src/obfuscationResult.ts +++ b/src/obfuscationResult.ts @@ -1,22 +1,43 @@ +import { PluginInstance } from "./transforms/plugin"; + +/** + * Obfuscation result object. + */ export interface ObfuscationResult { + /** + * Obfuscated code. + */ code: string; } +/** + * Profile report for the obfuscation process. + */ export interface ProfileData { obfuscationTime: number; compileTime: number; parseTime: number; totalPossibleTransforms: number; totalTransforms: number; - transformTimeMap: { - [key: string]: number; + transforms: { + [transformName: string]: { + transformTime: number; + changeData: PluginInstance["changeData"]; + }; }; } +/** + * A callback function that is called when a transform is applied. + */ export type ProfilerCallback = (log: ProfilerLog) => void; + +/** + * The current progress of the obfuscation process. + */ export interface ProfilerLog { + index: number; currentTransform: string; - currentTransformNumber: number; nextTransform: string; totalTransforms: number; } diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 9e85bfa..a9c32fb 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -1,42 +1,45 @@ -import { ObfuscateOptions } from "./options"; +import { ok } from "assert"; import * as babel from "@babel/core"; import generate from "@babel/generator"; -import { PluginFunction, PluginInstance } from "./transforms/plugin"; -import { ok } from "assert"; +import { Node, Statement } from "@babel/types"; +import { ObfuscateOptions } from "./options"; import { applyDefaultsToOptions, validateOptions } from "./validateOptions"; +import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; +import { isProbabilityMapProbable } from "./probability"; +import { NameGen } from "./utils/NameGen"; +import { Order } from "./order"; +import { + PluginFunction, + PluginInstance, + PluginObject, +} from "./transforms/plugin"; +// Transforms import preparation from "./transforms/preparation"; import renameVariables from "./transforms/identifier/renameVariables"; import variableMasking from "./transforms/variableMasking"; import dispatcher from "./transforms/dispatcher"; import duplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval"; import objectExtraction from "./transforms/extraction/objectExtraction"; +import functionOutlining from "./transforms/functionOutlining"; import globalConcealing from "./transforms/identifier/globalConcealing"; import stringCompression from "./transforms/string/stringCompression"; import deadCode from "./transforms/deadCode"; import stringSplitting from "./transforms/string/stringSplitting"; import shuffle from "./transforms/shuffle"; -import finalizer from "./transforms/finalizer"; -import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; -import { isProbabilityMapProbable } from "./probability"; import astScrambler from "./transforms/astScrambler"; import calculator from "./transforms/calculator"; -import { Order } from "./order"; import movedDeclarations from "./transforms/identifier/movedDeclarations"; import renameLabels from "./transforms/renameLabels"; import rgf from "./transforms/rgf"; import flatten from "./transforms/flatten"; import stringConcealing from "./transforms/string/stringConcealing"; import lock from "./transforms/lock/lock"; -import integrity from "./transforms/lock/integrity"; -import { Statement } from "@babel/types"; import controlFlowFlattening from "./transforms/controlFlowFlattening"; -import variableConcealing from "./transforms/identifier/variableConcealing"; -import { NameGen } from "./utils/NameGen"; -import { assertScopeIntegrity } from "./utils/scope-utils"; import opaquePredicates from "./transforms/opaquePredicates"; import minify from "./transforms/minify"; -import functionOutlining from "./transforms/functionOutlining"; +import finalizer from "./transforms/finalizer"; +import integrity from "./transforms/lock/integrity"; import pack from "./transforms/pack"; export const DEFAULT_OPTIONS: ObfuscateOptions = { @@ -46,7 +49,7 @@ export const DEFAULT_OPTIONS: ObfuscateOptions = { export default class Obfuscator { plugins: { - plugin: babel.PluginObj; + plugin: PluginObject; pluginInstance: PluginInstance; }[] = []; options: ObfuscateOptions; @@ -56,7 +59,6 @@ export default class Obfuscator { globalState = { lock: { integrity: { - hashFnName: "", sensitivityRegex: / |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g, }, @@ -64,22 +66,102 @@ export default class Obfuscator { throw new Error("Not implemented"); }, }, + + // After RenameVariables completes, this map will contain the renamed variables + // Most use cases involve grabbing the Program(global) mappings + renamedVariables: new Map>(), + + // Internal functions, should not be renamed/removed + internals: { + stringCompressionLibraryName: "", + nativeFunctionName: "", + integrityHashName: "", + invokeCountermeasuresFnName: "", + }, }; + isInternalVariable(name: string) { + return Object.values(this.globalState.internals).includes(name); + } + + shouldTransformNativeFunction(nameAndPropertyPath: string[]) { + if (!this.options.lock?.tamperProtection) { + return false; + } + + // Custom implementation for Tamper Protection + if (typeof this.options.lock.tamperProtection === "function") { + return this.options.lock.tamperProtection(nameAndPropertyPath.join(".")); + } + + if ( + this.options.target === "browser" && + nameAndPropertyPath.length === 1 && + nameAndPropertyPath[0] === "fetch" + ) { + return true; + } + + var globalObject = {}; + try { + globalObject = + typeof globalThis !== "undefined" + ? globalThis + : typeof window !== "undefined" + ? window + : typeof global !== "undefined" + ? global + : typeof self !== "undefined" + ? self + : new Function("return this")(); + } catch (e) {} + + var fn = globalObject; + for (var item of nameAndPropertyPath) { + fn = fn?.[item]; + if (typeof fn === "undefined") return false; + } + + var hasNativeCode = + typeof fn === "function" && ("" + fn).includes("[native code]"); + + return hasNativeCode; + } + + getStringCompressionLibraryName() { + if (this.parentObfuscator) { + return this.parentObfuscator.getStringCompressionLibraryName(); + } + + return this.globalState.internals.stringCompressionLibraryName; + } + + getObfuscatedVariableName(originalName: string, programNode: Node) { + const renamedVariables = this.globalState.renamedVariables.get(programNode); + + return renamedVariables?.get(originalName) || originalName; + } + /** * The main Name Generator for `Rename Variables` */ nameGen: NameGen; - public constructor(userOptions: ObfuscateOptions) { + public constructor( + userOptions: ObfuscateOptions, + public parentObfuscator?: Obfuscator + ) { validateOptions(userOptions); this.options = applyDefaultsToOptions({ ...userOptions }); this.nameGen = new NameGen(this.options.identifierGenerator); const shouldAddLockTransform = this.options.lock && - (Object.keys(this.options.lock).filter((key) => key !== "customLocks") - .length > 0 || + (Object.keys(this.options.lock).filter( + (key) => + key !== "customLocks" && + isProbabilityMapProbable(this.options.lock[key]) + ).length > 0 || this.options.lock.customLocks.length > 0); const allPlugins: PluginFunction[] = []; @@ -116,26 +198,28 @@ export default class Obfuscator { push(this.options.renameVariables, renameVariables); push(true, finalizer); + push(this.options.pack, pack); push(this.options.lock?.integrity, integrity); - push(this.options.variableConcealing, variableConcealing); - allPlugins.map((pluginFunction) => { var pluginInstance: PluginInstance; var plugin = pluginFunction({ - Plugin: (nameOrOrder) => { - var pluginOptions; - if (typeof nameOrOrder === "string") { - pluginOptions = { name: nameOrOrder }; - } else if (typeof nameOrOrder === "number") { - pluginOptions = { name: Order[nameOrOrder], order: nameOrOrder }; - } else if (typeof nameOrOrder === "object" && nameOrOrder) { - pluginOptions = nameOrOrder; - } else { - ok(false); + Plugin: (order: Order, mergeObject?) => { + ok(typeof order === "number"); + var pluginOptions = { + order, + name: Order[order], + }; + + const newPluginInstance = new PluginInstance(pluginOptions, this); + if (typeof mergeObject === "object" && mergeObject) { + Object.assign(newPluginInstance, mergeObject); } - return (pluginInstance = new PluginInstance(pluginOptions, this)); + pluginInstance = newPluginInstance; + + // @ts-ignore + return newPluginInstance as any; }, }); @@ -153,6 +237,11 @@ export default class Obfuscator { this.plugins = this.plugins.sort( (a, b) => a.pluginInstance.order - b.pluginInstance.order ); + + if (!parentObfuscator && this.hasPlugin(Order.StringCompression)) { + this.globalState.internals.stringCompressionLibraryName = + this.nameGen.generate(false); + } } index: number = 0; @@ -164,30 +253,40 @@ export default class Obfuscator { disablePack?: boolean; } ): babel.types.File { + let finalASTHandler: PluginObject["finalASTHandler"][] = []; + for (let i = 0; i < this.plugins.length; i++) { this.index = i; const { plugin, pluginInstance } = this.plugins[i]; + + // Skip pack if disabled + if (pluginInstance.order === Order.Pack && options?.disablePack) continue; + if (this.options.verbose) { console.log( `Applying ${pluginInstance.name} (${i + 1}/${this.plugins.length})` ); } - babel.traverse(ast, plugin.visitor as babel.Visitor); - // assertScopeIntegrity(pluginInstance.name, ast); + babel.traverse(ast, plugin.visitor); + plugin.post?.(); + + if (plugin.finalASTHandler) { + finalASTHandler.push(plugin.finalASTHandler); + } if (options?.profiler) { options?.profiler({ + index: i, currentTransform: pluginInstance.name, - currentTransformNumber: i, nextTransform: this.plugins[i + 1]?.pluginInstance?.name, totalTransforms: this.plugins.length, }); } } - if (this.options.pack && !options?.disablePack) { - ast = pack(ast, this); + for (const handler of finalASTHandler) { + ast = handler(ast); } return ast; @@ -202,21 +301,17 @@ export default class Obfuscator { // Generate the transformed code from the modified AST with comments removed and compacted output const code = this.generateCode(ast); - if (typeof code === "string") { - return { - code: code, - }; - } else { - throw new Error("Failed to generate code"); - } + return { + code: code, + }; } getPlugin(order: Order) { return this.plugins.find((x) => x.pluginInstance.order === order); } - static createDefaultInstance() { - return new Obfuscator(DEFAULT_OPTIONS); + hasPlugin(order: Order) { + return !!this.getPlugin(order); } /** diff --git a/src/options.ts b/src/options.ts index 693e4d7..802fbb1 100644 --- a/src/options.ts +++ b/src/options.ts @@ -55,23 +55,11 @@ export interface CustomStringEncoding { export interface ObfuscateOptions { /** - * ### `preset` - * - * JS-Confuser comes with three presets built into the obfuscator. - * - * | Preset | Transforms | Performance Reduction | Sample | - * | --- | --- | --- | --- | - * | High | 22/25 | 98% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/high.js) | - * | Medium | 19/25 | 52% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/medium.js) | - * | Low | 15/25 | 30% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/low.js) | - * - * You can extend each preset or all go without them entirely. (`"high"/"medium"/"low"`) + * The preset to use for obfuscation. */ preset?: "high" | "medium" | "low" | false; /** - * ### `target` - * * The execution context for your output. _Required_. * * 1. `"node"` @@ -80,32 +68,27 @@ export interface ObfuscateOptions { target: "node" | "browser"; /** - * ### `compact` - * - * Remove's whitespace from the final output. (`true/false`) + * Remove's whitespace from the final output. */ compact?: boolean; /** - * ### `hexadecimalNumbers` - * - * Uses the hexadecimal representation for numbers. (`true/false`) + * Uses the hexadecimal representation for numbers. */ hexadecimalNumbers?: boolean; /** - * ### `minify` - * - * Minifies redundant code. (`true/false`) + * Minifies redundant code. */ minify?: boolean; + /** + * Renames labeled statements. Enabled by default. + */ renameLabels?: ProbabilityMap boolean>; /** - * ### `renameVariables` - * - * Determines if variables should be renamed. (`true/false`) + * Determines if variables should be renamed. */ renameVariables?: ProbabilityMap< boolean, @@ -113,50 +96,14 @@ export interface ObfuscateOptions { >; /** - * ### `renameGlobals` - * - * Renames top-level variables, turn this off for web-related scripts. Enabled by default. (`true/false`) - * + * Renames top-level variables, turn this off for web-related scripts. Enabled by default. */ - renameGlobals?: ProbabilityMap; + renameGlobals?: ProbabilityMap boolean>; /** - * ### `identifierGenerator` - * * Determines how variables are renamed. * - * | Mode | Description | Example | - * | --- | --- | --- | - * | `"hexadecimal"` | Random hex strings | \_0xa8db5 | - * | `"randomized"` | Random characters | w$Tsu4G | - * | `"zeroWidth"` | Invisible characters | U+200D | - * | `"mangled"` | Alphabet sequence | a, b, c | - * | `"number"` | Numbered sequence | var_1, var_2 | - * | `` | Write a custom name generator | See Below | - * - * ```js - * // Custom implementation - * JsConfuser.obfuscate(code, { - * target: "node", - * renameVariables: true, - * identifierGenerator: function () { - * return "$" + Math.random().toString(36).substring(7); - * }, - * }); - * - * // Numbered variables - * var counter = 0; - * JsConfuser.obfuscate(code, { - * target: "node", - * renameVariables: true, - * identifierGenerator: function () { - * return "var_" + (counter++); - * }, - * }); - * ``` - * - * JSConfuser tries to reuse names when possible, creating very potent code. - * + * JS-Confuser tries to reuse names when possible, creating very potent code. */ identifierGenerator?: ProbabilityMap< "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number", @@ -164,87 +111,71 @@ export interface ObfuscateOptions { >; /** - * ### `controlFlowFlattening` - * * ⚠️ Significantly impacts performance, use sparingly! * - * Control-flow Flattening hinders program comprehension by creating convoluted switch statements. (`true/false/0-1`) + * Control-flow Flattening hinders program comprehension by creating convoluted switch statements. * * Use a number to control the percentage from 0 to 1. - * */ controlFlowFlattening?: ProbabilityMap; /** - * ### `globalConcealing` - * - * Global Concealing hides global variables being accessed. (`true/false`) - * + * Global Concealing hides global variables being accessed. */ globalConcealing?: ProbabilityMap boolean>; /** - * ### `stringCompression` - * - * String Compression uses LZW's compression algorithm to compress strings. (`true/false/0-1`) + * String Compression uses LZW's compression algorithm to compress strings. * * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` - * */ stringCompression?: ProbabilityMap boolean>; /** - * ### `stringConcealing` - * - * String Concealing involves encoding strings to conceal plain-text values. (`true/false/0-1`) + * String Concealing involves encoding strings to conceal plain-text values. * * `"console"` -> `decrypt('<~@rH7+Dert~>')` - * */ stringConcealing?: ProbabilityMap boolean>; /** - * ### `stringEncoding` - * - * String Encoding transforms a string into an encoded representation. (`true/false/0-1`) + * Custom String Encodings allows you to define your own string encoding/decoding functions. + */ + customStringEncodings?: ( + | CustomStringEncoding + | ((encodingImplementations: { + [identity: string]: CustomStringEncoding; + }) => CustomStringEncoding | null) + )[]; + + /** + * String Encoding transforms a string into an escaped unicode representation. * * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` - * */ stringEncoding?: ProbabilityMap boolean>; /** - * ### `stringSplitting` - * - * String Splitting splits your strings into multiple expressions. (`true/false/0-1`) + * String Splitting splits your strings into multiple expressions. * * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` - * */ stringSplitting?: ProbabilityMap boolean>; /** - * ### `duplicateLiteralsRemoval` - * - * Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`) - * + * Duplicate Literals Removal replaces duplicate literals with a single variable name. */ duplicateLiteralsRemoval?: ProbabilityMap; /** - * ### `dispatcher` - * - * Creates a middleman function to process function calls. (`true/false/0-1`) - * + * Creates a middleman function to process function calls. */ dispatcher?: ProbabilityMap boolean>; /** - * ### `rgf` - * - * RGF (Runtime-Generated-Functions) uses the [`new Function(code...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) syntax to construct executable code from strings. (`"all"/true/false`) + * RGF (Runtime-Generated-Functions) uses the [`new Function(code...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) syntax to construct executable code from strings. * - * - **This can break your code. + * - **This can break your code.** * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.** * - The arbitrary code is also obfuscated. * @@ -261,11 +192,9 @@ export interface ObfuscateOptions { * ``` * */ - rgf?: ProbabilityMap boolean>; + rgf?: ProbabilityMap boolean>; /** - * ### `variableMasking` - * * Local variables are consolidated into a rotating array. * * [Similar to Jscrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) @@ -286,9 +215,7 @@ export interface ObfuscateOptions { variableMasking?: ProbabilityMap boolean>; /** - * ### `objectExtraction` - * - * Extracts object properties into separate variables. (`true/false`) + * Extracts object properties into separate variables. * * ```js * // Input @@ -307,40 +234,29 @@ export interface ObfuscateOptions { * // ... * } * ``` - * */ objectExtraction?: ProbabilityMap boolean>; /** - * ### `flatten` - * - * Brings independent declarations to the highest scope. (`true/false`) - * + * Declares functions at the top of the program, preserving their original scope. */ flatten?: ProbabilityMap boolean>; /** - * ### `deadCode` - * - * Randomly injects dead code. (`true/false/0-1`) + * Randomly injects dead code. * * Use a number to control the percentage from 0 to 1. - * */ deadCode?: ProbabilityMap; /** - * ### `calculator` - * - * Creates a calculator function to handle arithmetic and logical expressions. (`true/false/0-1`) + * Creates a calculator function to handle arithmetic and logical expressions. * */ calculator?: ProbabilityMap; lock?: { /** - * ### `lock.selfDefending` - * * Prevents the use of code beautifiers or formatters against your code. * * [Identical to Obfuscator.io's Self Defending](https://github.com/javascript-obfuscator/javascript-obfuscator#selfdefending) @@ -349,78 +265,58 @@ export interface ObfuscateOptions { selfDefending?: boolean; /** - * ### `lock.antiDebug` - * - * Adds `debugger` statements throughout the code. Additionally adds a background function for DevTools detection. (`true/false/0-1`) - * + * Adds `debugger` statements throughout the code. */ antiDebug?: ProbabilityMap; /** - * ### `lock.tamperProtection` - * - * Tamper Protection safeguards the runtime behavior from being altered by JavaScript pitfalls. (`true/false`) + * Tamper Protection safeguards the runtime behavior from being altered by JavaScript pitfalls. * * **⚠️ Tamper Protection requires eval and ran in a non-strict mode environment!** * * - **This can break your code.** * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.** * - * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md). - * + * @see https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md */ tamperProtection?: boolean | ((varName: string) => boolean); /** - * ### `lock.startDate` - * * When the program is first able to be used. (`number` or `Date`) * * Number should be in milliseconds. - * */ startDate?: number | Date | false; /** - * ### `lock.endDate` - * * When the program is no longer able to be used. (`number` or `Date`) * * Number should be in milliseconds. - * */ endDate?: number | Date | false; /** - * ### `lock.domainLock` - * Array of regex strings that the `window.location.href` must follow. (`Regex[]` or `string[]`) - * + * Array of regex strings that the `window.location.href` must follow. */ domainLock?: RegExp[] | string[] | false; /** - * ### `lock.integrity` - * - * Integrity ensures the source code is unchanged. (`true/false/0-1`) - * - * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md). + * Integrity ensures the source code is unchanged. * + * @see https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md */ integrity?: ProbabilityMap boolean>; /** - * ### `lock.countermeasures` - * * A custom callback function to invoke when a lock is triggered. (`string/false`) * * This could be due to an invalid domain, incorrect time, or code's integrity changed. * - * [Learn more about the rules of your countermeasures function](https://github.com/MichaelXF/js-confuser/blob/master/Countermeasures.md). - * * If no countermeasures function is provided (`undefined` or `true`), the obfuscator falls back to crashing the process. * * If `countermeasures` is `false`, no crash will occur. * + * @see https://github.com/MichaelXF/js-confuser/blob/master/Countermeasures.md */ countermeasures?: string | boolean; @@ -429,84 +325,46 @@ export interface ObfuscateOptions { functionOutlining?: ProbabilityMap; - customStringEncodings?: ( - | CustomStringEncoding - | ((encodingImplementations: { - [identity: string]: CustomStringEncoding; - }) => CustomStringEncoding | null) - )[]; - /** - * ### `movedDeclarations` - * - * Moves variable declarations to the top of the context. (`true/false`) - * + * Moves variable declarations to the top of the context. */ movedDeclarations?: ProbabilityMap; /** - * ### `opaquePredicates` - * * An [Opaque Predicate](https://en.wikipedia.org/wiki/Opaque_predicate) is a predicate(true/false) that is evaluated at runtime, this can confuse reverse engineers - * understanding your code. (`true/false`) - * + * understanding your code. */ opaquePredicates?: ProbabilityMap; /** - * ### `shuffle` - * * Shuffles the initial order of arrays. The order is brought back to the original during runtime. (`"hash"/true/false/0-1`) - * - * | Mode | Description | - * | --- | --- | - * | `"hash"`| Array is shifted based on hash of the elements | - * | `true`| Arrays are shifted *n* elements, unshifted at runtime | - * | `false` | Feature disabled | - * */ - shuffle?: ProbabilityMap; + shuffle?: ProbabilityMap; /** - * ### `verbose` - * - * Enable logs to view the obfuscator's state. (`true/false`) - * + * Modified functions will retain the correct `function.length` property. Enabled by default. */ - verbose?: boolean; + preserveFunctionLength?: boolean; /** - * ### `globalVariables` - * - * Set of global variables. *Optional*. (`Set`) - * + * Semantically changes the AST to bypass automated tools. */ - globalVariables?: Set; + astScrambler?: boolean; /** - * ### `debugComments` - * - * Enable debug comments. (`true/false`) + * Packs the output code into a single `Function()` call. * + * Designed to escape strict mode constraints. */ - debugComments?: boolean; + pack?: boolean; /** - * ### `preserveFunctionLength` - * - * Modified functions will retain the correct `function.length` property. Enabled by default. (`true/false`) - * + * Set of global variables. *Optional*. */ - preserveFunctionLength?: boolean; - - astScrambler?: boolean; - - variableConcealing?: ProbabilityMap; + globalVariables?: Set; /** - * Packs the output code into a single `Function()` call. (`true/false`) - * - * Designed to escape strict mode constraints. + * Enable logs to view the obfuscator's state. */ - pack?: boolean; + verbose?: boolean; } diff --git a/src/order.ts b/src/order.ts index 5198995..6491b18 100644 --- a/src/order.ts +++ b/src/order.ts @@ -8,7 +8,7 @@ export enum Order { Flatten = 2, - Lock = 3, // Includes Anti Debug + Lock = 3, RGF = 4, @@ -50,5 +50,7 @@ export enum Order { Finalizer = 35, - Integrity = 36, // Must run last + Pack = 36, + + Integrity = 37, // Must run last } diff --git a/src/presets.ts b/src/presets.ts index 0d5b3cf..e523510 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -1,27 +1,17 @@ import { ObfuscateOptions } from "./options"; /** - * - High Obfuscation preset. - * - **Average 90% performance reduction.** + * - High Obfuscation preset * - * ## **`Enabled features`** - * 1. Variable renaming - * 2. Control flow obfuscation - * 3. String concealing - * 4. Opaque predicates - * 5. Dead code - * 6. Dispatcher - * 7. Moved declarations - * 8. Object extraction - * 9. Global concealing - * 10. Minified output + * **This preset is unsafe and may break your code.** * - * ## **`Disabled features`** - * - `rgf` Use at your own risk! + * Security risks: * - * ### Potential Issues - * 1. *String Encoding* can corrupt files. Disable `stringEncoding` manually if this happens. - * 2. *Dead Code* can bloat file size. Reduce or disable `deadCode`. + * - Function constructor (`Pack`) + * - Escapes strict-mode constraints (`Pack`) + * - Use of `with` statement (`Control Flow Flattening`) + * - Object.prototype pollution (`Opaque Predicates`) + * - Bloats file size (`Dead Code` and `String Compression` can add up to 50kb) */ const highPreset: ObfuscateOptions = { target: "node", @@ -30,8 +20,8 @@ const highPreset: ObfuscateOptions = { calculator: true, compact: true, hexadecimalNumbers: true, - controlFlowFlattening: 0.75, - deadCode: 0.2, + controlFlowFlattening: 0.5, + deadCode: 0.25, dispatcher: true, duplicateLiteralsRemoval: 0.75, flatten: true, @@ -40,11 +30,11 @@ const highPreset: ObfuscateOptions = { minify: true, movedDeclarations: true, objectExtraction: true, - opaquePredicates: 0.75, + // opaquePredicates: 0.75, renameVariables: true, renameGlobals: true, shuffle: true, - variableMasking: true, + variableMasking: 0.75, stringConcealing: true, stringCompression: true, stringEncoding: true, @@ -52,13 +42,22 @@ const highPreset: ObfuscateOptions = { astScrambler: true, functionOutlining: false, - // Use at own risk - rgf: false, + // Security risks + pack: true, }; /** - * - Medium Obfuscation preset. - * - Average 50% performance reduction. + * - Medium Obfuscation preset + * + * **This preset is unsafe and may break your code.** + * + * Security risks: + * + * - Function constructor (`Pack`) + * - Escapes strict-mode constraints (`Pack`) + * - Use of `with` statement (`Control Flow Flattening`) + * - Object.prototype pollution (`Opaque Predicates`) + * - Bloats file size (`Dead Code` can add up to 50kb) */ const mediumPreset: ObfuscateOptions = { target: "node", @@ -68,7 +67,7 @@ const mediumPreset: ObfuscateOptions = { compact: true, hexadecimalNumbers: true, controlFlowFlattening: 0.25, - deadCode: 0.025, + deadCode: 0.1, dispatcher: 0.5, duplicateLiteralsRemoval: 0.5, globalConcealing: true, @@ -76,7 +75,6 @@ const mediumPreset: ObfuscateOptions = { minify: true, movedDeclarations: true, objectExtraction: true, - opaquePredicates: 0.5, renameVariables: true, renameGlobals: true, shuffle: true, @@ -84,11 +82,14 @@ const mediumPreset: ObfuscateOptions = { stringConcealing: true, stringSplitting: 0.25, astScrambler: true, + pack: true, }; /** - * - Low Obfuscation preset. - * - Average 30% performance reduction. + * - Low Obfuscation preset + * + * A balanced preset that provides basic obfuscation. + * */ const lowPreset: ObfuscateOptions = { target: "node", @@ -97,15 +98,13 @@ const lowPreset: ObfuscateOptions = { calculator: true, compact: true, hexadecimalNumbers: true, - controlFlowFlattening: 0.1, - deadCode: 0.01, + deadCode: 0.05, dispatcher: 0.25, duplicateLiteralsRemoval: 0.5, identifierGenerator: "randomized", minify: true, movedDeclarations: true, objectExtraction: true, - opaquePredicates: 0.1, renameVariables: true, renameGlobals: true, stringConcealing: true, diff --git a/src/probability.ts b/src/probability.ts index 42eb883..f57dbb5 100644 --- a/src/probability.ts +++ b/src/probability.ts @@ -16,7 +16,7 @@ type Stringed = (V extends string ? V : never) | "true" | "false"; */ export type ProbabilityMap< T, - F extends (...args: any[]) => any = (...args: any[]) => any // Default to a generic function + F extends (...args: any[]) => any = () => boolean // Default to a generic function > = false | true | number | T | T[] | { [key in Stringed]?: number } | F; /** @@ -69,7 +69,7 @@ export function computeProbabilityMap< var count = 0; var winner = null; Object.keys(percentages).forEach((key) => { - var x = parseFloat(percentages[key]); + var x = Number(percentages[key]); if (ticket >= count && ticket < count + x) { winner = key; @@ -110,6 +110,9 @@ export function isProbabilityMapProbable(map: ProbabilityMap): boolean { } } if (typeof map === "object") { + if (map instanceof Date) return true; + if (map instanceof RegExp) return true; + var keys = Object.keys(map); ok( keys.length != 0, diff --git a/src/templates/template.ts b/src/templates/template.ts index f73a88f..1f38d7c 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -4,6 +4,12 @@ import traverse from "@babel/traverse"; import { NodePath } from "@babel/traverse"; import { ok } from "assert"; import { getRandomString } from "../utils/random-utils"; +import { NodeSymbol } from "../constants"; + +// Create a union type of the symbol keys in NodeSymbol +type NodeSymbolKeys = keyof { + [K in keyof NodeSymbol as K extends symbol ? K : never]: NodeSymbol[K]; +}; export interface TemplateVariables { [varName: string]: @@ -21,6 +27,7 @@ export default class Template { private requiredVariables: Set; private astVariableMappings: Map; private astIdentifierPrefix = "__t_" + getRandomString(6); + private symbols: NodeSymbolKeys[] = []; constructor(...templates: string[]) { this.templates = templates; @@ -30,6 +37,11 @@ export default class Template { this.findRequiredVariables(); } + addSymbols(...symbols: NodeSymbolKeys[]): this { + this.symbols.push(...symbols); + return this; + } + setDefaultVariables(defaultVariables: TemplateVariables): this { this.defaultVariables = defaultVariables; return this; @@ -72,11 +84,8 @@ export default class Template { let value = allVariables[name] as string; if (this.isASTVariable(value)) { - let astIdentifierName = this.astVariableMappings.get(name); - if (typeof astIdentifierName === "undefined") { - astIdentifierName = this.astIdentifierPrefix + name; - this.astVariableMappings.set(name, astIdentifierName); - } + let astIdentifierName = this.astIdentifierPrefix + name; + this.astVariableMappings.set(name, astIdentifierName); value = astIdentifierName; } @@ -112,11 +121,9 @@ export default class Template { Identifier(path: NodePath) { const idName = path.node.name; if (!idName.startsWith(template.astIdentifierPrefix)) return; - const variableName = reverseMappings.get(idName); - if (typeof variableName === "undefined") { - ok(false, `Variable ${idName} not found in mappings`); - } + const variableName = reverseMappings.get(idName); + ok(variableName, `Variable ${idName} not found in mappings`); let value = allVariables[variableName]; let isSingleUse = true; // Hard-coded nodes are deemed 'single use' @@ -167,6 +174,14 @@ export default class Template { this.interpolateAST(file, variables); + if (this.symbols.length > 0) { + file.program.body.forEach((node) => { + for (const symbol of this.symbols) { + node[symbol] = true; + } + }); + } + return file; } diff --git a/src/utils/ControlObject.ts b/src/utils/ControlObject.ts index 9b1abff..91b7306 100644 --- a/src/utils/ControlObject.ts +++ b/src/utils/ControlObject.ts @@ -16,7 +16,10 @@ export default class ControlObject { objectExpression: t.ObjectExpression | null = null; constructor(public me: PluginInstance, public blockPath: NodePath) { - this.nameGen = new NameGen(me.options.identifierGenerator); + this.nameGen = new NameGen(me.options.identifierGenerator, { + avoidReserved: true, + avoidObjectPrototype: true, + }); } createMemberExpression(propertyName: string): t.MemberExpression { @@ -28,6 +31,8 @@ export default class ControlObject { } createPredicate() { + this.ensureCreated(); + var propertyName = choice(Array.from(this.propertyNames)); if (!propertyName || chance(50)) { propertyName = this.nameGen.generate(); @@ -36,8 +41,8 @@ export default class ControlObject { return { node: t.binaryExpression( "in", - t.identifier(this.objectName), - t.stringLiteral(propertyName) + t.stringLiteral(propertyName), + t.identifier(this.objectName) ), value: this.propertyNames.has(propertyName), }; @@ -59,12 +64,12 @@ export default class ControlObject { return node; } - addProperty(node: t.Expression) { + private ensureCreated(node?: t.Node) { if (!this.objectName) { // Object hasn't been created yet this.objectName = this.me.getPlaceholder() + "_controlObject"; - if (t.isFunctionExpression(node) && !node.id) { + if (node && t.isFunctionExpression(node) && !node.id) { // Use function declaration as object let newNode: t.FunctionDeclaration = node as any; @@ -102,6 +107,10 @@ export default class ControlObject { this.me.skip(this.objectExpression); } } + } + + addProperty(node: t.Expression) { + this.ensureCreated(node); const propertyName = this.nameGen.generate(); this.propertyNames.add(propertyName); diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts index 2677bfc..503ad2d 100644 --- a/src/utils/NameGen.ts +++ b/src/utils/NameGen.ts @@ -3,14 +3,24 @@ import { ObfuscateOptions } from "../options"; import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils"; import { choice, getRandomHexString, getRandomInteger } from "./random-utils"; import { computeProbabilityMap } from "../probability"; +import { reservedKeywords, reservedObjectPrototype } from "../constants"; +/** + * Generate random names for variables and properties. + */ export class NameGen { public generatedNames = new Set(); + public notSafeForReuseNames = new Set(); + private counter = 1; private zeroWidthGenerator = createZeroWidthGenerator(); constructor( - private identifierGenerator: ObfuscateOptions["identifierGenerator"] = "randomized" + private identifierGenerator: ObfuscateOptions["identifierGenerator"] = "randomized", + public options = { + avoidReserved: false, + avoidObjectPrototype: false, + } ) {} private attemptGenerate() { @@ -45,7 +55,12 @@ export class NameGen { return "_0x" + getRandomHexString(randomizedLength); case "mangled": - return alphabeticalGenerator(this.counter++); + var mangledName = ""; + do { + mangledName = alphabeticalGenerator(this.counter++); + } while (reservedKeywords.includes(mangledName)); + + return mangledName; case "number": return "var_" + this.counter++; @@ -60,14 +75,32 @@ export class NameGen { } } - generate(): string { + generate(isSafeForReuse = true): string { let name: string; do { name = this.attemptGenerate(); - } while (this.generatedNames.has(name)); + + // Avoid reserved keywords + if (this.options.avoidReserved && reservedKeywords.includes(name)) { + name = ""; + continue; + } + + // Avoid reserved object prototype properties + if ( + this.options.avoidObjectPrototype && + reservedObjectPrototype.has(name) + ) { + name = ""; + continue; + } + } while (!name || this.generatedNames.has(name)); this.generatedNames.add(name); + if (!isSafeForReuse) { + this.notSafeForReuseNames.add(name); + } return name; } } diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 02bf0b4..5fc1f89 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -1,31 +1,22 @@ import * as t from "@babel/types"; import { NodePath } from "@babel/core"; import { ok } from "assert"; - -export function containsLexicallyBoundVariables(path: NodePath): boolean { - var foundLexicalDeclaration = false; - - path.traverse({ - VariableDeclaration(declarationPath) { - if ( - declarationPath.node.kind === "let" || - declarationPath.node.kind === "const" - ) { - foundLexicalDeclaration = true; - declarationPath.stop(); +import { deepClone } from "./node"; + +export function getPatternIdentifierNames( + path: NodePath | NodePath[] +): Set { + if (Array.isArray(path)) { + var allNames = new Set(); + for (var p of path) { + var names = getPatternIdentifierNames(p); + for (var name of names) { + allNames.add(name); } - }, - - ClassDeclaration(declarationPath) { - foundLexicalDeclaration = true; - declarationPath.stop(); - }, - }); - - return foundLexicalDeclaration; -} + } -export function getPatternIdentifierNames(path: NodePath): string[] { + return allNames; + } var names = new Set(); var functionParent = path.find((parent) => parent.isFunction()); @@ -46,7 +37,7 @@ export function getPatternIdentifierNames(path: NodePath): string[] { names.add(path.node.name); } - return Array.from(names); + return names; } /** @@ -154,8 +145,12 @@ export function isModuleImport(path: NodePath) { return false; } +export function getBlock(path: NodePath) { + return path.find((p) => p.isBlock()) as NodePath; +} + export function getParentFunctionOrProgram( - path: NodePath + path: NodePath ): NodePath { if (path.isProgram()) return path; @@ -168,66 +163,6 @@ export function getParentFunctionOrProgram( return functionOrProgramPath; } -export function insertIntoNearestBlockScope( - path: NodePath, - ...nodesToInsert: t.Statement[] -): NodePath[] { - // Traverse up the AST until we find a BlockStatement or Program - let targetPath: NodePath = path; - - while ( - targetPath && - !t.isBlockStatement(targetPath.node) && - !t.isProgram(targetPath.node) - ) { - targetPath = targetPath.parentPath; - } - - // Ensure that we found a valid insertion point - if (t.isBlockStatement(targetPath.node)) { - // Insert before the current statement within the found block - return targetPath.insertBefore(nodesToInsert) as NodePath[]; - } else if (targetPath.isProgram()) { - // Insert at the top of the program body - return targetPath.unshiftContainer("body", nodesToInsert) as NodePath[]; - } else { - throw new Error( - "Could not find a suitable block scope to insert the nodes." - ); - } -} - -export function isReservedIdentifier( - node: t.Identifier | t.JSXIdentifier -): boolean { - return ( - node.name === "arguments" || // Check for 'arguments' - node.name === "undefined" || // Check for 'undefined' - node.name === "NaN" || // Check for 'NaN' - node.name === "Infinity" || // Check for 'Infinity' - node.name === "eval" || // Check for 'eval' - t.isThisExpression(node) || // Check for 'this' - t.isSuper(node) || // Check for 'super' - t.isMetaProperty(node) // Check for meta properties like 'new.target' - ); -} - -export function hasNestedBinding(path: NodePath, name: string): boolean { - let found = false; - - // Traverse through the child paths (nested scopes) - path.traverse({ - Scope(nestedPath) { - if (nestedPath.scope.hasOwnBinding(name)) { - found = true; - nestedPath.stop(); // Stop further traversal if found - } - }, - }); - - return found; -} - export function getObjectPropertyAsString( property: t.ObjectMember | t.ClassProperty | t.ClassMethod ): string { @@ -280,6 +215,76 @@ export function getMemberExpressionPropertyAsString( return null; // If the property cannot be determined } +function registerPaths(paths: NodePath[]) { + for (var path of paths) { + if (path.isVariableDeclaration() && path.node.kind === "var") { + getParentFunctionOrProgram(path).scope.registerDeclaration(path); + } + path.scope.registerDeclaration(path); + } + + return paths; +} + +function nodeListToNodes(nodesIn: (t.Statement | t.Statement[])[]) { + var nodes: t.Statement[] = []; + if (Array.isArray(nodesIn[0])) { + ok(nodesIn.length === 1); + nodes = nodesIn[0]; + } else { + nodes = nodesIn as t.Statement[]; + } + + return nodes; +} + +/** + * Appends to the bottom of a block. Preserving last expression for the top level. + */ +export function append( + path: NodePath, + ...nodesIn: (t.Statement | t.Statement[])[] +) { + var nodes = nodeListToNodes(nodesIn); + + var listParent = path.find( + (p) => p.isFunction() || p.isBlock() || p.isSwitchCase() + ); + if (!listParent) { + throw new Error("Could not find a suitable parent to prepend to"); + } + + if (listParent.isProgram()) { + var lastExpression = listParent.get("body").at(-1); + if (lastExpression.isExpressionStatement()) { + return registerPaths(lastExpression.insertBefore(nodes)); + } + } + + if (listParent.isSwitchCase()) { + return registerPaths(listParent.pushContainer("consequent", nodes)); + } + + if (listParent.isFunction()) { + var body = listParent.get("body"); + + if (listParent.isArrowFunctionExpression() && listParent.node.expression) { + if (!body.isBlockStatement()) { + body.replaceWith( + t.blockStatement([t.returnStatement(body.node as t.Expression)]) + ); + } + } + + ok(body.isBlockStatement()); + + return registerPaths(body.pushContainer("body", nodes)); + } + + ok(listParent.isBlock()); + return registerPaths(listParent.pushContainer("body", nodes)); +} + /** * Prepends and registers a list of nodes to the beginning of a block. * @@ -294,13 +299,7 @@ export function prepend( path: NodePath, ...nodesIn: (t.Statement | t.Statement[])[] ): NodePath[] { - var nodes: t.Statement[] = []; - if (Array.isArray(nodesIn[0])) { - ok(nodesIn.length === 1); - nodes = nodesIn[0]; - } else { - nodes = nodesIn as t.Statement[]; - } + var nodes = nodeListToNodes(nodesIn); var listParent = path.find( (p) => p.isFunction() || p.isBlock() || p.isSwitchCase() @@ -309,17 +308,6 @@ export function prepend( throw new Error("Could not find a suitable parent to prepend to"); } - function registerPaths(paths: NodePath[]) { - for (var path of paths) { - if (path.isVariableDeclaration() && path.node.kind === "var") { - getParentFunctionOrProgram(path).scope.registerDeclaration(path); - } - path.scope.registerDeclaration(path); - } - - return paths; - } - if (listParent.isProgram()) { // Preserve import declarations // Filter out import declarations @@ -342,9 +330,9 @@ export function prepend( if (listParent.isArrowFunctionExpression() && listParent.node.expression) { if (!body.isBlockStatement()) { - body.replaceWith( + body = body.replaceWith( t.blockStatement([t.returnStatement(body.node as t.Expression)]) - ); + )[0]; } } @@ -410,6 +398,13 @@ export function isVariableIdentifier(path: NodePath) { export function isDefiningIdentifier(path: NodePath) { if (path.key === "id" && path.parentPath.isFunction()) return true; if (path.key === "id" && path.parentPath.isClassDeclaration) return true; + if ( + path.key === "local" && + (path.parentPath.isImportSpecifier() || + path.parentPath.isImportDefaultSpecifier() || + path.parentPath.isImportNamespaceSpecifier()) + ) + return true; var maxTraversalPath = path.find( (p) => @@ -468,6 +463,45 @@ export function isStrictIdentifier(path: NodePath): boolean { return false; } +export function isExportedIdentifier(path: NodePath) { + // Check if the identifier is directly inside an ExportNamedDeclaration + if (path.parentPath.isExportNamedDeclaration()) { + return true; + } + + // Check if the identifier is in an ExportDefaultDeclaration + if (path.parentPath.isExportDefaultDeclaration()) { + return true; + } + + // Check if the identifier is within an ExportSpecifier + if ( + path.parentPath.isExportSpecifier() && + path.parentPath.parentPath.isExportNamedDeclaration() + ) { + return true; + } + + // Check if it's part of an exported variable declaration (e.g., export const a = 1;) + if ( + path.parentPath.isVariableDeclarator() && + path.parentPath.parentPath.parentPath.isExportNamedDeclaration() + ) { + return true; + } + + // Check if it's part of an exported function declaration (e.g., export function abc() {}) + if ( + (path.parentPath.isFunctionDeclaration() || + path.parentPath.isClassDeclaration()) && + path.parentPath.parentPath.isExportNamedDeclaration() + ) { + return true; + } + + return false; +} + /** * @example * function abc() { @@ -476,7 +510,10 @@ export function isStrictIdentifier(path: NodePath): boolean { * @param path * @returns */ -export function isStrictMode(path: NodePath) { +export function isStrictMode(path: NodePath) { + // Classes are always in strict mode + if (path.isClass()) return true; + if (path.isBlock()) { if (path.isTSModuleBlock()) return false; return (path.node as t.BlockStatement | t.Program).directives.some( @@ -493,3 +530,138 @@ export function isStrictMode(path: NodePath) { return false; } + +/** + * A modified identifier is an identifier that is assigned to or updated. + * + * - Assignment Expression + * - Update Expression + * + * @param identifierPath + */ +export function isModifiedIdentifier(identifierPath: NodePath) { + var isModification = false; + if (identifierPath.parentPath.isUpdateExpression()) { + isModification = true; + } + if ( + identifierPath.find( + (p) => p.key === "left" && p.parentPath?.isAssignmentExpression() + ) + ) { + isModification = true; + } + + return isModification; +} + +export function replaceDefiningIdentifierToMemberExpression( + path: NodePath, + memberExpression: t.MemberExpression +) { + // function id(){} -> var id = function() {} + if (path.key === "id" && path.parentPath.isFunctionDeclaration()) { + var asFunctionExpression = deepClone( + path.parentPath.node + ) as t.Node as t.FunctionExpression; + asFunctionExpression.type = "FunctionExpression"; + + path.parentPath.replaceWith( + t.expressionStatement( + t.assignmentExpression("=", memberExpression, asFunctionExpression) + ) + ); + return; + } + + // class id{} -> var id = class {} + if (path.key === "id" && path.parentPath.isClassDeclaration()) { + var asClassExpression = deepClone( + path.parentPath.node + ) as t.Node as t.ClassExpression; + asClassExpression.type = "ClassExpression"; + + path.parentPath.replaceWith( + t.expressionStatement( + t.assignmentExpression("=", memberExpression, asClassExpression) + ) + ); + return; + } + + // var id = 1 -> id = 1 + var variableDeclaratorChild = path.find( + (p) => + p.key === "id" && + p.parentPath?.isVariableDeclarator() && + p.parentPath?.parentPath?.isVariableDeclaration() + ) as NodePath; + + if (variableDeclaratorChild) { + var variableDeclarator = + variableDeclaratorChild.parentPath as NodePath; + var variableDeclaration = + variableDeclarator.parentPath as NodePath; + + if (variableDeclaration.type === "VariableDeclaration") { + ok( + variableDeclaration.node.declarations.length === 1, + "Multiple declarations not supported" + ); + } + + const id = variableDeclarator.get("id"); + const init = variableDeclarator.get("init"); + + var newExpression: t.Node = id.node; + + var isForInitializer = + (variableDeclaration.key === "init" || + variableDeclaration.key === "left") && + variableDeclaration.parentPath.isFor(); + + if (init.node || !isForInitializer) { + newExpression = t.assignmentExpression( + "=", + id.node, + init.node || t.identifier("undefined") + ); + } + + if (!isForInitializer) { + newExpression = t.expressionStatement(newExpression as t.Expression); + } + + path.replaceWith(memberExpression); + + if (variableDeclaration.isVariableDeclaration()) { + variableDeclaration.replaceWith(newExpression); + } + + return; + } + + // Safely replace the identifier with the member expression + // ensureComputedExpression(path); + // path.replaceWith(memberExpression); +} + +/** + * @example + * undefined // true + * void 0 // true + */ +export function isUndefined(path: NodePath) { + if (path.isIdentifier() && path.node.name === "undefined") { + return true; + } + if ( + path.isUnaryExpression() && + path.node.operator === "void" && + path.node.argument.type === "NumericLiteral" && + path.node.argument.value === 0 + ) { + return true; + } + return false; +} diff --git a/src/utils/function-utils.ts b/src/utils/function-utils.ts index 1d3fa36..fcf8220 100644 --- a/src/utils/function-utils.ts +++ b/src/utils/function-utils.ts @@ -8,7 +8,12 @@ import { FN_LENGTH, NodeSymbol, variableFunctionName } from "../constants"; * @returns */ export function isVariableFunctionIdentifier(path: NodePath) { - if (path.isIdentifier() && path.parentPath?.isCallExpression()) { + if ( + path.isIdentifier() && + path.listKey === "arguments" && + path.key === 0 && + path.parentPath?.isCallExpression() + ) { const callee = path.parentPath.get("callee"); return callee.isIdentifier({ name: variableFunctionName }); } diff --git a/src/utils/gen-utils.ts b/src/utils/gen-utils.ts index db5a169..8716b3d 100644 --- a/src/utils/gen-utils.ts +++ b/src/utils/gen-utils.ts @@ -1,3 +1,4 @@ +import { reservedKeywords } from "../constants"; import { shuffle } from "./random-utils"; export function alphabeticalGenerator(index: number) { @@ -16,60 +17,11 @@ export function alphabeticalGenerator(index: number) { } export function createZeroWidthGenerator() { - var keywords = [ - "if", - "in", - "for", - "let", - "new", - "try", - "var", - "case", - "else", - "null", - "break", - "catch", - "class", - "const", - "super", - "throw", - "while", - "yield", - "delete", - "export", - "import", - "public", - "return", - "switch", - "default", - "finally", - "private", - "continue", - "debugger", - "function", - "arguments", - "protected", - "instanceof", - "await", - "async", - - // new key words and other fun stuff :P - "NaN", - "undefined", - "true", - "false", - "typeof", - "this", - "static", - "void", - "of", - ]; - var maxSize = 0; var currentKeyWordsArray: string[] = []; function generateArray() { - var result = keywords + var result = reservedKeywords .map( (keyWord) => keyWord + "\u200C".repeat(Math.max(maxSize - keyWord.length, 1)) diff --git a/src/utils/node.ts b/src/utils/node.ts index 0c033b8..1eb01d0 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -1,6 +1,25 @@ import * as t from "@babel/types"; import { ok } from "assert"; +export type LiteralValue = string | number | boolean | undefined | null; +export const createLiteral = (value: LiteralValue) => { + if (value === null) return t.nullLiteral(); + if (value === undefined) return t.identifier("undefined"); + + switch (typeof value) { + case "string": + return t.stringLiteral(value); + + case "number": + return numericLiteral(value); + + case "boolean": + return t.booleanLiteral(value); + } + + ok(false); +}; + /** * Handles both positive and negative numeric literals * @param value @@ -38,14 +57,20 @@ export function deepClone(node: t.Node | t.Node[]) { const clonedObj = {}; // Handle string and symbol property keys - [ - ...Object.getOwnPropertyNames(obj), - ...Object.getOwnPropertySymbols(obj), - ].forEach((key) => { + + Object.getOwnPropertyNames(obj).forEach((key) => { const value = obj[key]; clonedObj[key] = deepClone(value); }); + // Copy simple symbols (Avoid objects = infinite recursion) + Object.getOwnPropertySymbols(obj).forEach((symbol) => { + const value = obj[symbol]; + if (typeof value !== "object") { + clonedObj[symbol] = deepClone(value); + } + }); + return clonedObj; } diff --git a/src/utils/object-utils.ts b/src/utils/object-utils.ts index 170006a..da6ec47 100644 --- a/src/utils/object-utils.ts +++ b/src/utils/object-utils.ts @@ -1,8 +1,13 @@ -export function createObject( +/** + * Creates an object from the given keys and values arrays. + * @param keys + * @param values + */ +export function createObject( keys: string[], - values: any[] -): { [key: string]: any } { - if (keys.length != values.length) { + values: T[] +): { [key: string]: T } { + if (keys.length !== values.length) { throw new Error("length mismatch"); } diff --git a/src/utils/random-utils.ts b/src/utils/random-utils.ts index 43d0e10..5414269 100644 --- a/src/utils/random-utils.ts +++ b/src/utils/random-utils.ts @@ -22,7 +22,7 @@ export function chance(percentChance: number): boolean { * **Mutates the given array** * @param array */ -export function shuffle(array: any[]): any[] { +export function shuffle(array: T[]): T[] { array.sort(() => Math.random() - 0.5); return array; } @@ -34,7 +34,7 @@ export function shuffle(array: any[]): any[] { * @param length * @returns */ -export function getRandomHexString(length = 6) { +export function getRandomHexString(length: number) { return [...Array(length)] .map(() => Math.floor(Math.random() * 16).toString(16)) .join("") @@ -44,7 +44,7 @@ export function getRandomHexString(length = 6) { /** * Returns a random string. */ -export function getRandomString(length = 10) { +export function getRandomString(length: number) { var result = ""; var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -55,11 +55,11 @@ export function getRandomString(length = 10) { return result; } -export function getRandom(min, max) { +export function getRandom(min: number, max: number) { return Math.random() * (max - min) + min; } -export function getRandomInteger(min, max) { +export function getRandomInteger(min: number, max: number) { return Math.floor(getRandom(min, max)); } @@ -69,7 +69,7 @@ export function splitIntoChunks(str: string, size: number) { ok(Math.floor(size) === size, "size must be integer"); const numChunks = Math.ceil(str.length / size); - const chunks = new Array(numChunks); + const chunks: string[] = new Array(numChunks); for (let i = 0, o = 0; i < numChunks; ++i, o += size) { chunks[i] = str.substr(o, size); diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts deleted file mode 100644 index 0f2bffa..0000000 --- a/src/utils/scope-utils.ts +++ /dev/null @@ -1,129 +0,0 @@ -import traverse, { NodePath } from "@babel/traverse"; -import * as t from "@babel/types"; - -function captureScopeState(scope) { - return { - bindings: Object.keys(scope.bindings).reduce((acc, name) => { - acc[name] = { - identifier: scope.bindings[name].identifier.name, - kind: scope.bindings[name].kind, - path: scope.bindings[name].path.node, - }; - return acc; - }, {}), - }; -} - -function compareScopes(beforeState, afterState, node) { - let errors = []; - - // Check if bindings were removed or added - for (let name in beforeState.bindings) { - if (!afterState.bindings[name]) { - // errors.push(`"${name}" was removed from the scope at node: ${node.type}`); - } - } - - for (let name in afterState.bindings) { - if (!beforeState.bindings[name]) { - errors.push( - `"${name}" was not registered in the scope at node: ${node.type}` - ); - } - } - - return errors; -} - -/** - * Asserts all identifiers were correctly registered. - * - * The obfuscator checks after every transformation that all bindings are correctly registered. - * - * This ensures the integrity of the Babel Scope API. - * - * Should only be called in development mode. - * @param node - */ -export function assertScopeIntegrity(pluginName: string, node: t.File) { - t.assertFile(node); - - const scopeStates = new WeakMap(); - const seenNodes = new WeakSet(); - - // Traverse to capture the initial state of all scopes - let programPath: NodePath = null; - traverse(node, { - enter(path) { - if (path.isProgram()) { - programPath = path; - } - - // Duplicate name check - if (seenNodes.has(path.node)) { - throw new Error( - `${pluginName}: Duplicate node found in AST ${path.node.type}` - ); - } - seenNodes.add(path.node); - - // Identifier name check - if ( - path.node.type === "Identifier" && - ["this", "null"].includes(path.node.name) - ) { - throw new Error("Identifier cannot be named " + path.node.name); - } - - if (path.scope && Object.keys(path.scope.bindings).length > 0) { - scopeStates.set(path.node, captureScopeState(path.scope)); - - for (var name in path.scope.bindings) { - const binding = path.scope.bindings[name]; - if (!binding.path || !binding.path.node || binding.path.removed) { - const message = `${pluginName}: Binding "${name}" was removed from the scope at node: ${path.node.type} ${binding.path?.type}`; - // console.warn(message); - - throw new Error(message); - } - } - } - }, - }); - - // Perform scope.crawl() on the Program node - programPath.scope.crawl(); - - // Traverse again to compare the scope states - let errors = []; - let checkedNewScopes = new Set(); - - traverse(node, { - enter(path) { - if (path.scope && Object.keys(path.scope.bindings).length > 0) { - if (checkedNewScopes.has(path.scope)) { - return; - } - checkedNewScopes.add(path.scope); - - const beforeState = scopeStates.get(path.node); - const afterState = captureScopeState(path.scope); - - if (beforeState) { - errors = errors.concat( - compareScopes(beforeState, afterState, path.node) - ); - } - } - }, - }); - - if (errors.length > 0) { - const message = `${pluginName}: Scope integrity check failed:\n${errors.join( - "\n" - )}`; - - // console.warn(message); - throw new Error(message); - } -} diff --git a/src/utils/static-utils.ts b/src/utils/static-utils.ts index 053439d..e288205 100644 --- a/src/utils/static-utils.ts +++ b/src/utils/static-utils.ts @@ -3,13 +3,24 @@ import * as t from "@babel/types"; // Function to check if a node is a static value export function isStaticValue(node: t.Node): boolean { // Check for literals which are considered static - if (t.isLiteral(node)) { + if ( + t.isStringLiteral(node) || + t.isNumericLiteral(node) || + t.isBooleanLiteral(node) || + t.isNullLiteral(node) + ) { + if (t.isDirectiveLiteral(node)) return false; + return true; } // Handle unary expressions like -42 if (t.isUnaryExpression(node)) { - return isStaticValue(node.argument); + // Only consider certain operators as static (e.g., -, +) + if (["-", "+", "!", "~", "void"].includes(node.operator)) { + return isStaticValue(node.argument); + } + return false; } // Handle binary expressions with static values only @@ -43,12 +54,13 @@ export function isStaticValue(node: t.Node): boolean { return node.properties.every((prop) => { if (t.isObjectProperty(prop)) { return isStaticValue(prop.key) && isStaticValue(prop.value); + } else if (t.isSpreadElement(prop)) { + return isStaticValue(prop.argument); } return false; }); } // Add more cases as needed, depending on what you consider "static" - return false; } diff --git a/src/validateOptions.ts b/src/validateOptions.ts index ea40938..cbf3e11 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -37,7 +37,6 @@ const validProperties = new Set([ "debugComments", "preserveFunctionLength", "astScrambler", - "variableConcealing", "customStringEncodings", "functionOutlining", "pack", @@ -46,13 +45,10 @@ const validProperties = new Set([ const validLockProperties = new Set([ "selfDefending", "antiDebug", - "context", "tamperProtection", "startDate", "endDate", "domainLock", - "osLock", - "browserLock", "integrity", "countermeasures", "customLocks", @@ -149,10 +145,6 @@ export function applyDefaultsToOptions( options = Object.assign({}, presets[options.preset], options); } - if (!options.hasOwnProperty("debugComments")) { - options.debugComments = false; // debugComments is off by default - } - if (!options.hasOwnProperty("compact")) { options.compact = true; // Compact is on by default } @@ -230,6 +222,7 @@ export function applyDefaultsToOptions( "parseFloat", "Math", "JSON", + "RegExp", "Promise", "String", "Boolean", From 6a1074d99183c69a87b3945f1e8fdc4aa63873a0 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:35:19 -0400 Subject: [PATCH 037/103] Improve templates --- src/templates/bufferToStringTemplate.ts | 2 +- src/templates/getGlobalTemplate.ts | 40 +++---- src/templates/integrityTemplate.ts | 37 ++++--- src/templates/stringCompressionTemplate.ts | 6 +- src/templates/tamperProtectionTemplates.ts | 116 +++++++++++++++++++++ 5 files changed, 164 insertions(+), 37 deletions(-) create mode 100644 src/templates/tamperProtectionTemplates.ts diff --git a/src/templates/bufferToStringTemplate.ts b/src/templates/bufferToStringTemplate.ts index ba800bd..6750f31 100644 --- a/src/templates/bufferToStringTemplate.ts +++ b/src/templates/bufferToStringTemplate.ts @@ -45,7 +45,7 @@ export const BufferToStringTemplate = new Template(` }; })(); - function {name}(buffer){ + function {BufferToString}(buffer){ if(typeof __TextDecoder !== "undefined" && __TextDecoder) { return new __TextDecoder()["decode"](new __Uint8Array(buffer)); } else if(typeof __Buffer !== "undefined" && __Buffer) { diff --git a/src/templates/getGlobalTemplate.ts b/src/templates/getGlobalTemplate.ts index 1dc935b..393ca5f 100644 --- a/src/templates/getGlobalTemplate.ts +++ b/src/templates/getGlobalTemplate.ts @@ -1,31 +1,33 @@ import { NodePath } from "@babel/traverse"; import { PluginInstance } from "../transforms/plugin"; import Template from "./template"; +import { UNSAFE } from "../constants"; export const createGetGlobalTemplate = ( pluginInstance: PluginInstance, path: NodePath ) => { - var options = pluginInstance.options; - // if (options.lock?.tamperProtection) { - // return new Template(` - // function {getGlobalFnName}(){ - // var localVar = false; - // eval(${transform.jsConfuserVar("localVar")} + " = true") - // if (!localVar) { - // {countermeasures} - // } + if (pluginInstance.options.lock?.tamperProtection) { + return new Template(` + function {getGlobalFnName}(){ + var localVar = false; + eval(__JS_CONFUSER_VAR__(localVar) + " = true") + if (!localVar) { + {countermeasures} - // const root = eval("this"); - // return root; - // } - // `).setDefaultVariables({ - // countermeasures: transform.lockTransform.getCounterMeasuresCode( - // object, - // parents - // ), - // }); - // } + return {}; + } + + const root = eval("this"); + return root; + } + `) + .addSymbols(UNSAFE) + .setDefaultVariables({ + countermeasures: + pluginInstance.globalState.lock.createCountermeasuresCode(), + }); + } return GetGlobalTemplate; }; diff --git a/src/templates/integrityTemplate.ts b/src/templates/integrityTemplate.ts index 329dd62..0efc7f1 100644 --- a/src/templates/integrityTemplate.ts +++ b/src/templates/integrityTemplate.ts @@ -1,3 +1,4 @@ +import { MULTI_TRANSFORM, SKIP, UNSAFE } from "../constants"; import Template from "./template"; /** @@ -24,20 +25,28 @@ export function HashFunction(str: string, seed: number) { // In template form to be inserted into code export const HashTemplate = new Template(` +// Must be Function Declaration for hoisting // Math.imul polyfill for ES5 -var {imul} = Math.imul || function(opA, opB){ - opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. - // floating points give us 53 bits of precision to work with plus 1 sign bit - // automatically handled for our convienence: - // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 - // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ - var result = (opA & 0x003fffff) * opB; - // 2. We can remove an integer coersion from the statement above because: - // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 - // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ - if (opA & 0xffc00000 /*!== 0*/) result += (opA & 0xffc00000) * opB |0; - return result |0; -}; +function {imul}(opA, opB) { + var MathImul = Math["imul"] || function(opA, opB){ + opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. + // floating points give us 53 bits of precision to work with plus 1 sign bit + // automatically handled for our convienence: + // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 + // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + var result = (opA & 0x003fffff) * opB; + // 2. We can remove an integer coersion from the statement above because: + // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 + // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + if (opA & 0xffc00000 /*!== 0*/) result += (opA & 0xffc00000) * opB |0; + return result |0; + }; + + var result = MathImul(opA, opB); + + return result; +} + function {hashingUtilFnName}(str, seed) { var h1 = 0xdeadbeef ^ seed; @@ -57,4 +66,4 @@ function {name}(fnObject, seed, regex={sensitivityRegex}){ var fnStringed = fnObject["toString"]()["replace"](regex, ""); return {hashingUtilFnName}(fnStringed, seed); } -`); +`).addSymbols(SKIP, MULTI_TRANSFORM); diff --git a/src/templates/stringCompressionTemplate.ts b/src/templates/stringCompressionTemplate.ts index ec61e4d..bd05b0b 100644 --- a/src/templates/stringCompressionTemplate.ts +++ b/src/templates/stringCompressionTemplate.ts @@ -24,7 +24,7 @@ var {stringFn}; } var compressedBuffer = convertBase64ToArrayBuffer({stringValue}); - var utf8String = pako["inflate"](compressedBuffer, { to: 'string' }); + var utf8String = {pakoName}["inflate"](compressedBuffer, { to: 'string' }); var stringArray = utf8String["split"]({stringDelimiter}); {stringFn} = function(index){ @@ -35,8 +35,8 @@ var {stringFn}; ); export const PakoInflateMin = ` -var pako = {}; +var {pako} = {}; /*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -!function(e,t){ t(pako) }(this,(function(e){"use strict";var t=function(e,t,i,n){for(var a=65535&e|0,r=e>>>16&65535|0,o=0;0!==i;){i-=o=i>2e3?2e3:i;do{r=r+(a=a+t[n++]|0)|0}while(--o);a%=65521,r%=65521}return a|r<<16|0},i=new Uint32Array(function(){for(var e,t=[],i=0;i<256;i++){e=i;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[i]=e}return t}()),n=function(e,t,n,a){var r=i,o=a+n;e^=-1;for(var s=a;s>>8^r[255&(e^t[s])];return-1^e},a=16209,r=function(e,t){var i,n,r,o,s,l,f,d,h,c,u,w,b,m,k,_,v,g,p,y,x,E,R,A,Z=e.state;i=e.next_in,R=e.input,n=i+(e.avail_in-5),r=e.next_out,A=e.output,o=r-(t-e.avail_out),s=r+(e.avail_out-257),l=Z.dmax,f=Z.wsize,d=Z.whave,h=Z.wnext,c=Z.window,u=Z.hold,w=Z.bits,b=Z.lencode,m=Z.distcode,k=(1<>>=g=v>>>24,w-=g,0===(g=v>>>16&255))A[r++]=65535&v;else{if(!(16&g)){if(0==(64&g)){v=b[(65535&v)+(u&(1<>>=g,w-=g),w<15&&(u+=R[i++]<>>=g=v>>>24,w-=g,!(16&(g=v>>>16&255))){if(0==(64&g)){v=m[(65535&v)+(u&(1<l){e.msg="invalid distance too far back",Z.mode=a;break e}if(u>>>=g,w-=g,y>(g=r-o)){if((g=y-g)>d&&Z.sane){e.msg="invalid distance too far back",Z.mode=a;break e}if(x=0,E=c,0===h){if(x+=f-g,g2;)A[r++]=E[x++],A[r++]=E[x++],A[r++]=E[x++],p-=3;p&&(A[r++]=E[x++],p>1&&(A[r++]=E[x++]))}else{x=r-y;do{A[r++]=A[x++],A[r++]=A[x++],A[r++]=A[x++],p-=3}while(p>2);p&&(A[r++]=A[x++],p>1&&(A[r++]=A[x++]))}break}}break}}while(i>3,u&=(1<<(w-=p<<3))-1,e.next_in=i,e.next_out=r,e.avail_in=i=1&&0===B[A];A--);if(Z>A&&(Z=A),0===A)return a[r++]=20971520,a[r++]=20971520,c.bits=1,0;for(R=1;R0&&(0===e||1!==A))return-1;for(N[1]=0,x=1;x852||2===e&&U>592)return 1;for(;;){v=x-T,h[E]+1<_?(g=0,p=h[E]):h[E]>=_?(g=C[h[E]-_],p=I[h[E]-_]):(g=96,p=0),u=1<>T)+(w-=u)]=v<<24|g<<16|p|0}while(0!==w);for(u=1<>=1;if(0!==u?(D&=u-1,D+=u):D=0,E++,0==--B[x]){if(x===A)break;x=t[i+h[E]]}if(x>Z&&(D&m)!==b){for(0===T&&(T=Z),k+=R,O=1<<(S=x-T);S+T852||2===e&&U>592)return 1;a[b=D&m]=Z<<24|S<<16|k-r|0}}return 0!==D&&(a[k+D]=x-T<<24|64<<16|0),c.bits=Z,0},c={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8},u=c.Z_FINISH,w=c.Z_BLOCK,b=c.Z_TREES,m=c.Z_OK,k=c.Z_STREAM_END,_=c.Z_NEED_DICT,v=c.Z_STREAM_ERROR,g=c.Z_DATA_ERROR,p=c.Z_MEM_ERROR,y=c.Z_BUF_ERROR,x=c.Z_DEFLATED,E=16180,R=16190,A=16191,Z=16192,S=16194,T=16199,O=16200,U=16206,D=16209,I=function(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)};function B(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}var N,C,z=function(e){if(!e)return 1;var t=e.state;return!t||t.strm!==e||t.mode16211?1:0},F=function(e){if(z(e))return v;var t=e.state;return e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=E,t.last=0,t.havedict=0,t.flags=-1,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new Int32Array(852),t.distcode=t.distdyn=new Int32Array(592),t.sane=1,t.back=-1,m},L=function(e){if(z(e))return v;var t=e.state;return t.wsize=0,t.whave=0,t.wnext=0,F(e)},M=function(e,t){var i;if(z(e))return v;var n=e.state;return t<0?(i=0,t=-t):(i=5+(t>>4),t<48&&(t&=15)),t&&(t<8||t>15)?v:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=i,n.wbits=t,L(e))},H=function(e,t){if(!e)return v;var i=new B;e.state=i,i.strm=e,i.window=null,i.mode=E;var n=M(e,t);return n!==m&&(e.state=null),n},j=!0,K=function(e){if(j){N=new Int32Array(512),C=new Int32Array(32);for(var t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(h(1,e.lens,0,288,N,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;h(2,e.lens,0,32,C,0,e.work,{bits:5}),j=!1}e.lencode=N,e.lenbits=9,e.distcode=C,e.distbits=5},P=function(e,t,i,n){var a,r=e.state;return null===r.window&&(r.wsize=1<=r.wsize?(r.window.set(t.subarray(i-r.wsize,i),0),r.wnext=0,r.whave=r.wsize):((a=r.wsize-r.wnext)>n&&(a=n),r.window.set(t.subarray(i-n,i-n+a),r.wnext),(n-=a)?(r.window.set(t.subarray(i-n,i),0),r.wnext=n,r.whave=r.wsize):(r.wnext+=a,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,a.check=n(a.check,te,2,0),B=0,N=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&B)<<8)+(B>>8))%31){e.msg="incorrect header check",a.mode=D;break}if((15&B)!==x){e.msg="unknown compression method",a.mode=D;break}if(N-=4,J=8+(15&(B>>>=4)),0===a.wbits&&(a.wbits=J),J>15||J>a.wbits){e.msg="invalid window size",a.mode=D;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16182;case 16182:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>8&255,te[2]=B>>>16&255,te[3]=B>>>24&255,a.check=n(a.check,te,4,0)),B=0,N=0,a.mode=16183;case 16183:for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>8),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16184;case 16184:if(1024&a.flags){for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&((L=a.length)>d&&(L=d),L&&(a.head&&(J=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(o.subarray(l,l+L),J)),512&a.flags&&4&a.wrap&&(a.check=n(a.check,o,L,l)),d-=L,l+=L,a.length-=L),a.length))break e;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===d)break e;L=0;do{J=o[l+L++],a.head&&J&&a.length<65536&&(a.head.name+=String.fromCharCode(J))}while(J&&L>9&1,a.head.done=!0),e.adler=a.check=0,a.mode=A;break;case 16189:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>=7&N,N-=7&N,a.mode=U;break}for(;N<3;){if(0===d)break e;d--,B+=o[l++]<>>=1)){case 0:a.mode=16193;break;case 1:if(K(a),a.mode=T,i===b){B>>>=2,N-=2;break e}break;case 2:a.mode=16196;break;case 3:e.msg="invalid block type",a.mode=D}B>>>=2,N-=2;break;case 16193:for(B>>>=7&N,N-=7&N;N<32;){if(0===d)break e;d--,B+=o[l++]<>>16^65535)){e.msg="invalid stored block lengths",a.mode=D;break}if(a.length=65535&B,B=0,N=0,a.mode=S,i===b)break e;case S:a.mode=16195;case 16195:if(L=a.length){if(L>d&&(L=d),L>c&&(L=c),0===L)break e;s.set(o.subarray(l,l+L),f),d-=L,l+=L,c-=L,f+=L,a.length-=L;break}a.mode=A;break;case 16196:for(;N<14;){if(0===d)break e;d--,B+=o[l++]<>>=5,N-=5,a.ndist=1+(31&B),B>>>=5,N-=5,a.ncode=4+(15&B),B>>>=4,N-=4,a.nlen>286||a.ndist>30){e.msg="too many length or distance symbols",a.mode=D;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,N-=3}for(;a.have<19;)a.lens[ie[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,V={bits:a.lenbits},Q=h(0,a.lens,0,19,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid code lengths set",a.mode=D;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,a.lens[a.have++]=G;else{if(16===G){for($=j+2;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,0===a.have){e.msg="invalid bit length repeat",a.mode=D;break}J=a.lens[a.have-1],L=3+(3&B),B>>>=2,N-=2}else if(17===G){for($=j+3;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=3,N-=3}else{for($=j+7;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=7,N-=7}if(a.have+L>a.nlen+a.ndist){e.msg="invalid bit length repeat",a.mode=D;break}for(;L--;)a.lens[a.have++]=J}}if(a.mode===D)break;if(0===a.lens[256]){e.msg="invalid code -- missing end-of-block",a.mode=D;break}if(a.lenbits=9,V={bits:a.lenbits},Q=h(1,a.lens,0,a.nlen,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid literal/lengths set",a.mode=D;break}if(a.distbits=6,a.distcode=a.distdyn,V={bits:a.distbits},Q=h(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,V),a.distbits=V.bits,Q){e.msg="invalid distances set",a.mode=D;break}if(a.mode=T,i===b)break e;case T:a.mode=O;case O:if(d>=6&&c>=258){e.next_out=f,e.avail_out=c,e.next_in=l,e.avail_in=d,a.hold=B,a.bits=N,r(e,F),f=e.next_out,s=e.output,c=e.avail_out,l=e.next_in,o=e.input,d=e.avail_in,B=a.hold,N=a.bits,a.mode===A&&(a.back=-1);break}for(a.back=0;Y=(ee=a.lencode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,a.length=G,0===Y){a.mode=16205;break}if(32&Y){a.back=-1,a.mode=A;break}if(64&Y){e.msg="invalid literal/length code",a.mode=D;break}a.extra=15&Y,a.mode=16201;case 16201:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;Y=(ee=a.distcode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,64&Y){e.msg="invalid distance code",a.mode=D;break}a.offset=G,a.extra=15&Y,a.mode=16203;case 16203:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){e.msg="invalid distance too far back",a.mode=D;break}a.mode=16204;case 16204:if(0===c)break e;if(L=F-c,a.offset>L){if((L=a.offset-L)>a.whave&&a.sane){e.msg="invalid distance too far back",a.mode=D;break}L>a.wnext?(L-=a.wnext,M=a.wsize-L):M=a.wnext-L,L>a.length&&(L=a.length),H=a.window}else H=s,M=f-a.offset,L=a.length;L>c&&(L=c),c-=L,a.length-=L;do{s[f++]=H[M++]}while(--L);0===a.length&&(a.mode=O);break;case 16205:if(0===c)break e;s[f++]=a.length,c--,a.mode=O;break;case U:if(a.wrap){for(;N<32;){if(0===d)break e;d--,B|=o[l++]<=252?6:V>=248?5:V>=240?4:V>=224?3:V>=192?2:1;Q[254]=Q[254]=1;var $=function(e){if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(e);var t,i,n,a,r,o=e.length,s=0;for(a=0;a>>6,t[r++]=128|63&i):i<65536?(t[r++]=224|i>>>12,t[r++]=128|i>>>6&63,t[r++]=128|63&i):(t[r++]=240|i>>>18,t[r++]=128|i>>>12&63,t[r++]=128|i>>>6&63,t[r++]=128|63&i);return t},ee=function(e,t){var i,n,a=t||e.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(e.subarray(0,t));var r=new Array(2*a);for(n=0,i=0;i4)r[n++]=65533,i+=s-1;else{for(o&=2===s?31:3===s?15:7;s>1&&i1?r[n++]=65533:o<65536?r[n++]=o:(o-=65536,r[n++]=55296|o>>10&1023,r[n++]=56320|1023&o)}}}return function(e,t){if(t<65534&&e.subarray&&J)return String.fromCharCode.apply(null,e.length===t?e:e.subarray(0,t));for(var i="",n=0;ne.length&&(t=e.length);for(var i=t-1;i>=0&&128==(192&e[i]);)i--;return i<0||0===i?t:i+Q[e[i]]>t?i:t},ie={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"};var ne=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};var ae=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1},re=Object.prototype.toString,oe=c.Z_NO_FLUSH,se=c.Z_FINISH,le=c.Z_OK,fe=c.Z_STREAM_END,de=c.Z_NEED_DICT,he=c.Z_STREAM_ERROR,ce=c.Z_DATA_ERROR,ue=c.Z_MEM_ERROR;function we(e){this.options=W({chunkSize:65536,windowBits:15,to:""},e||{});var t=this.options;t.raw&&t.windowBits>=0&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(t.windowBits>=0&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),t.windowBits>15&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new ne,this.strm.avail_out=0;var i=Y.inflateInit2(this.strm,t.windowBits);if(i!==le)throw new Error(ie[i]);if(this.header=new ae,Y.inflateGetHeader(this.strm,this.header),t.dictionary&&("string"==typeof t.dictionary?t.dictionary=$(t.dictionary):"[object ArrayBuffer]"===re.call(t.dictionary)&&(t.dictionary=new Uint8Array(t.dictionary)),t.raw&&(i=Y.inflateSetDictionary(this.strm,t.dictionary))!==le))throw new Error(ie[i])}function be(e,t){var i=new we(t);if(i.push(e),i.err)throw i.msg||ie[i.err];return i.result}we.prototype.push=function(e,t){var i,n,a,r=this.strm,o=this.options.chunkSize,s=this.options.dictionary;if(this.ended)return!1;for(n=t===~~t?t:!0===t?se:oe,"[object ArrayBuffer]"===re.call(e)?r.input=new Uint8Array(e):r.input=e,r.next_in=0,r.avail_in=r.input.length;;){for(0===r.avail_out&&(r.output=new Uint8Array(o),r.next_out=0,r.avail_out=o),(i=Y.inflate(r,n))===de&&s&&((i=Y.inflateSetDictionary(r,s))===le?i=Y.inflate(r,n):i===ce&&(i=de));r.avail_in>0&&i===fe&&r.state.wrap>0&&0!==e[r.next_in];)Y.inflateReset(r),i=Y.inflate(r,n);switch(i){case he:case ce:case de:case ue:return this.onEnd(i),this.ended=!0,!1}if(a=r.avail_out,r.next_out&&(0===r.avail_out||i===fe))if("string"===this.options.to){var l=te(r.output,r.next_out),f=r.next_out-l,d=ee(r.output,l);r.next_out=f,r.avail_out=o-f,f&&r.output.set(r.output.subarray(l,l+f),0),this.onData(d)}else this.onData(r.output.length===r.next_out?r.output:r.output.subarray(0,r.next_out));if(i!==le||0!==a){if(i===fe)return i=Y.inflateEnd(this.strm),this.onEnd(i),this.ended=!0,!0;if(0===r.avail_in)break}}return!0},we.prototype.onData=function(e){this.chunks.push(e)},we.prototype.onEnd=function(e){e===le&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=q(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg};var me=we,ke=be,_e=function(e,t){return(t=t||{}).raw=!0,be(e,t)},ve=be,ge=c,pe={Inflate:me,inflate:ke,inflateRaw:_e,ungzip:ve,constants:ge};e.Inflate=me,e.constants=ge,e.default=pe,e.inflate=ke,e.inflateRaw=_e,e.ungzip=ve,Object.defineProperty(e,"__esModule",{value:!0})})); +!function(e,t){ t({pako}) }(this,(function(e){"use strict";var t=function(e,t,i,n){for(var a=65535&e|0,r=e>>>16&65535|0,o=0;0!==i;){i-=o=i>2e3?2e3:i;do{r=r+(a=a+t[n++]|0)|0}while(--o);a%=65521,r%=65521}return a|r<<16|0},i=new Uint32Array(function(){for(var e,t=[],i=0;i<256;i++){e=i;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[i]=e}return t}()),n=function(e,t,n,a){var r=i,o=a+n;e^=-1;for(var s=a;s>>8^r[255&(e^t[s])];return-1^e},a=16209,r=function(e,t){var i,n,r,o,s,l,f,d,h,c,u,w,b,m,k,_,v,g,p,y,x,E,R,A,Z=e.state;i=e.next_in,R=e.input,n=i+(e.avail_in-5),r=e.next_out,A=e.output,o=r-(t-e.avail_out),s=r+(e.avail_out-257),l=Z.dmax,f=Z.wsize,d=Z.whave,h=Z.wnext,c=Z.window,u=Z.hold,w=Z.bits,b=Z.lencode,m=Z.distcode,k=(1<>>=g=v>>>24,w-=g,0===(g=v>>>16&255))A[r++]=65535&v;else{if(!(16&g)){if(0==(64&g)){v=b[(65535&v)+(u&(1<>>=g,w-=g),w<15&&(u+=R[i++]<>>=g=v>>>24,w-=g,!(16&(g=v>>>16&255))){if(0==(64&g)){v=m[(65535&v)+(u&(1<l){e.msg="invalid distance too far back",Z.mode=a;break e}if(u>>>=g,w-=g,y>(g=r-o)){if((g=y-g)>d&&Z.sane){e.msg="invalid distance too far back",Z.mode=a;break e}if(x=0,E=c,0===h){if(x+=f-g,g2;)A[r++]=E[x++],A[r++]=E[x++],A[r++]=E[x++],p-=3;p&&(A[r++]=E[x++],p>1&&(A[r++]=E[x++]))}else{x=r-y;do{A[r++]=A[x++],A[r++]=A[x++],A[r++]=A[x++],p-=3}while(p>2);p&&(A[r++]=A[x++],p>1&&(A[r++]=A[x++]))}break}}break}}while(i>3,u&=(1<<(w-=p<<3))-1,e.next_in=i,e.next_out=r,e.avail_in=i=1&&0===B[A];A--);if(Z>A&&(Z=A),0===A)return a[r++]=20971520,a[r++]=20971520,c.bits=1,0;for(R=1;R0&&(0===e||1!==A))return-1;for(N[1]=0,x=1;x852||2===e&&U>592)return 1;for(;;){v=x-T,h[E]+1<_?(g=0,p=h[E]):h[E]>=_?(g=C[h[E]-_],p=I[h[E]-_]):(g=96,p=0),u=1<>T)+(w-=u)]=v<<24|g<<16|p|0}while(0!==w);for(u=1<>=1;if(0!==u?(D&=u-1,D+=u):D=0,E++,0==--B[x]){if(x===A)break;x=t[i+h[E]]}if(x>Z&&(D&m)!==b){for(0===T&&(T=Z),k+=R,O=1<<(S=x-T);S+T852||2===e&&U>592)return 1;a[b=D&m]=Z<<24|S<<16|k-r|0}}return 0!==D&&(a[k+D]=x-T<<24|64<<16|0),c.bits=Z,0},c={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8},u=c.Z_FINISH,w=c.Z_BLOCK,b=c.Z_TREES,m=c.Z_OK,k=c.Z_STREAM_END,_=c.Z_NEED_DICT,v=c.Z_STREAM_ERROR,g=c.Z_DATA_ERROR,p=c.Z_MEM_ERROR,y=c.Z_BUF_ERROR,x=c.Z_DEFLATED,E=16180,R=16190,A=16191,Z=16192,S=16194,T=16199,O=16200,U=16206,D=16209,I=function(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)};function B(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}var N,C,z=function(e){if(!e)return 1;var t=e.state;return!t||t.strm!==e||t.mode16211?1:0},F=function(e){if(z(e))return v;var t=e.state;return e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=E,t.last=0,t.havedict=0,t.flags=-1,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new Int32Array(852),t.distcode=t.distdyn=new Int32Array(592),t.sane=1,t.back=-1,m},L=function(e){if(z(e))return v;var t=e.state;return t.wsize=0,t.whave=0,t.wnext=0,F(e)},M=function(e,t){var i;if(z(e))return v;var n=e.state;return t<0?(i=0,t=-t):(i=5+(t>>4),t<48&&(t&=15)),t&&(t<8||t>15)?v:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=i,n.wbits=t,L(e))},H=function(e,t){if(!e)return v;var i=new B;e.state=i,i.strm=e,i.window=null,i.mode=E;var n=M(e,t);return n!==m&&(e.state=null),n},j=!0,K=function(e){if(j){N=new Int32Array(512),C=new Int32Array(32);for(var t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(h(1,e.lens,0,288,N,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;h(2,e.lens,0,32,C,0,e.work,{bits:5}),j=!1}e.lencode=N,e.lenbits=9,e.distcode=C,e.distbits=5},P=function(e,t,i,n){var a,r=e.state;return null===r.window&&(r.wsize=1<=r.wsize?(r.window.set(t.subarray(i-r.wsize,i),0),r.wnext=0,r.whave=r.wsize):((a=r.wsize-r.wnext)>n&&(a=n),r.window.set(t.subarray(i-n,i-n+a),r.wnext),(n-=a)?(r.window.set(t.subarray(i-n,i),0),r.wnext=n,r.whave=r.wsize):(r.wnext+=a,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,a.check=n(a.check,te,2,0),B=0,N=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&B)<<8)+(B>>8))%31){e.msg="incorrect header check",a.mode=D;break}if((15&B)!==x){e.msg="unknown compression method",a.mode=D;break}if(N-=4,J=8+(15&(B>>>=4)),0===a.wbits&&(a.wbits=J),J>15||J>a.wbits){e.msg="invalid window size",a.mode=D;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16182;case 16182:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>8&255,te[2]=B>>>16&255,te[3]=B>>>24&255,a.check=n(a.check,te,4,0)),B=0,N=0,a.mode=16183;case 16183:for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>8),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16184;case 16184:if(1024&a.flags){for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&((L=a.length)>d&&(L=d),L&&(a.head&&(J=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(o.subarray(l,l+L),J)),512&a.flags&&4&a.wrap&&(a.check=n(a.check,o,L,l)),d-=L,l+=L,a.length-=L),a.length))break e;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===d)break e;L=0;do{J=o[l+L++],a.head&&J&&a.length<65536&&(a.head.name+=String.fromCharCode(J))}while(J&&L>9&1,a.head.done=!0),e.adler=a.check=0,a.mode=A;break;case 16189:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>=7&N,N-=7&N,a.mode=U;break}for(;N<3;){if(0===d)break e;d--,B+=o[l++]<>>=1)){case 0:a.mode=16193;break;case 1:if(K(a),a.mode=T,i===b){B>>>=2,N-=2;break e}break;case 2:a.mode=16196;break;case 3:e.msg="invalid block type",a.mode=D}B>>>=2,N-=2;break;case 16193:for(B>>>=7&N,N-=7&N;N<32;){if(0===d)break e;d--,B+=o[l++]<>>16^65535)){e.msg="invalid stored block lengths",a.mode=D;break}if(a.length=65535&B,B=0,N=0,a.mode=S,i===b)break e;case S:a.mode=16195;case 16195:if(L=a.length){if(L>d&&(L=d),L>c&&(L=c),0===L)break e;s.set(o.subarray(l,l+L),f),d-=L,l+=L,c-=L,f+=L,a.length-=L;break}a.mode=A;break;case 16196:for(;N<14;){if(0===d)break e;d--,B+=o[l++]<>>=5,N-=5,a.ndist=1+(31&B),B>>>=5,N-=5,a.ncode=4+(15&B),B>>>=4,N-=4,a.nlen>286||a.ndist>30){e.msg="too many length or distance symbols",a.mode=D;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,N-=3}for(;a.have<19;)a.lens[ie[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,V={bits:a.lenbits},Q=h(0,a.lens,0,19,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid code lengths set",a.mode=D;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,a.lens[a.have++]=G;else{if(16===G){for($=j+2;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,0===a.have){e.msg="invalid bit length repeat",a.mode=D;break}J=a.lens[a.have-1],L=3+(3&B),B>>>=2,N-=2}else if(17===G){for($=j+3;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=3,N-=3}else{for($=j+7;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=7,N-=7}if(a.have+L>a.nlen+a.ndist){e.msg="invalid bit length repeat",a.mode=D;break}for(;L--;)a.lens[a.have++]=J}}if(a.mode===D)break;if(0===a.lens[256]){e.msg="invalid code -- missing end-of-block",a.mode=D;break}if(a.lenbits=9,V={bits:a.lenbits},Q=h(1,a.lens,0,a.nlen,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid literal/lengths set",a.mode=D;break}if(a.distbits=6,a.distcode=a.distdyn,V={bits:a.distbits},Q=h(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,V),a.distbits=V.bits,Q){e.msg="invalid distances set",a.mode=D;break}if(a.mode=T,i===b)break e;case T:a.mode=O;case O:if(d>=6&&c>=258){e.next_out=f,e.avail_out=c,e.next_in=l,e.avail_in=d,a.hold=B,a.bits=N,r(e,F),f=e.next_out,s=e.output,c=e.avail_out,l=e.next_in,o=e.input,d=e.avail_in,B=a.hold,N=a.bits,a.mode===A&&(a.back=-1);break}for(a.back=0;Y=(ee=a.lencode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,a.length=G,0===Y){a.mode=16205;break}if(32&Y){a.back=-1,a.mode=A;break}if(64&Y){e.msg="invalid literal/length code",a.mode=D;break}a.extra=15&Y,a.mode=16201;case 16201:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;Y=(ee=a.distcode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,64&Y){e.msg="invalid distance code",a.mode=D;break}a.offset=G,a.extra=15&Y,a.mode=16203;case 16203:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){e.msg="invalid distance too far back",a.mode=D;break}a.mode=16204;case 16204:if(0===c)break e;if(L=F-c,a.offset>L){if((L=a.offset-L)>a.whave&&a.sane){e.msg="invalid distance too far back",a.mode=D;break}L>a.wnext?(L-=a.wnext,M=a.wsize-L):M=a.wnext-L,L>a.length&&(L=a.length),H=a.window}else H=s,M=f-a.offset,L=a.length;L>c&&(L=c),c-=L,a.length-=L;do{s[f++]=H[M++]}while(--L);0===a.length&&(a.mode=O);break;case 16205:if(0===c)break e;s[f++]=a.length,c--,a.mode=O;break;case U:if(a.wrap){for(;N<32;){if(0===d)break e;d--,B|=o[l++]<=252?6:V>=248?5:V>=240?4:V>=224?3:V>=192?2:1;Q[254]=Q[254]=1;var $=function(e){if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(e);var t,i,n,a,r,o=e.length,s=0;for(a=0;a>>6,t[r++]=128|63&i):i<65536?(t[r++]=224|i>>>12,t[r++]=128|i>>>6&63,t[r++]=128|63&i):(t[r++]=240|i>>>18,t[r++]=128|i>>>12&63,t[r++]=128|i>>>6&63,t[r++]=128|63&i);return t},ee=function(e,t){var i,n,a=t||e.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(e.subarray(0,t));var r=new Array(2*a);for(n=0,i=0;i4)r[n++]=65533,i+=s-1;else{for(o&=2===s?31:3===s?15:7;s>1&&i1?r[n++]=65533:o<65536?r[n++]=o:(o-=65536,r[n++]=55296|o>>10&1023,r[n++]=56320|1023&o)}}}return function(e,t){if(t<65534&&e.subarray&&J)return String.fromCharCode.apply(null,e.length===t?e:e.subarray(0,t));for(var i="",n=0;ne.length&&(t=e.length);for(var i=t-1;i>=0&&128==(192&e[i]);)i--;return i<0||0===i?t:i+Q[e[i]]>t?i:t},ie={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"};var ne=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};var ae=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1},re=Object.prototype.toString,oe=c.Z_NO_FLUSH,se=c.Z_FINISH,le=c.Z_OK,fe=c.Z_STREAM_END,de=c.Z_NEED_DICT,he=c.Z_STREAM_ERROR,ce=c.Z_DATA_ERROR,ue=c.Z_MEM_ERROR;function we(e){this.options=W({chunkSize:65536,windowBits:15,to:""},e||{});var t=this.options;t.raw&&t.windowBits>=0&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(t.windowBits>=0&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),t.windowBits>15&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new ne,this.strm.avail_out=0;var i=Y.inflateInit2(this.strm,t.windowBits);if(i!==le)throw new Error(ie[i]);if(this.header=new ae,Y.inflateGetHeader(this.strm,this.header),t.dictionary&&("string"==typeof t.dictionary?t.dictionary=$(t.dictionary):"[object ArrayBuffer]"===re.call(t.dictionary)&&(t.dictionary=new Uint8Array(t.dictionary)),t.raw&&(i=Y.inflateSetDictionary(this.strm,t.dictionary))!==le))throw new Error(ie[i])}function be(e,t){var i=new we(t);if(i.push(e),i.err)throw i.msg||ie[i.err];return i.result}we.prototype.push=function(e,t){var i,n,a,r=this.strm,o=this.options.chunkSize,s=this.options.dictionary;if(this.ended)return!1;for(n=t===~~t?t:!0===t?se:oe,"[object ArrayBuffer]"===re.call(e)?r.input=new Uint8Array(e):r.input=e,r.next_in=0,r.avail_in=r.input.length;;){for(0===r.avail_out&&(r.output=new Uint8Array(o),r.next_out=0,r.avail_out=o),(i=Y.inflate(r,n))===de&&s&&((i=Y.inflateSetDictionary(r,s))===le?i=Y.inflate(r,n):i===ce&&(i=de));r.avail_in>0&&i===fe&&r.state.wrap>0&&0!==e[r.next_in];)Y.inflateReset(r),i=Y.inflate(r,n);switch(i){case he:case ce:case de:case ue:return this.onEnd(i),this.ended=!0,!1}if(a=r.avail_out,r.next_out&&(0===r.avail_out||i===fe))if("string"===this.options.to){var l=te(r.output,r.next_out),f=r.next_out-l,d=ee(r.output,l);r.next_out=f,r.avail_out=o-f,f&&r.output.set(r.output.subarray(l,l+f),0),this.onData(d)}else this.onData(r.output.length===r.next_out?r.output:r.output.subarray(0,r.next_out));if(i!==le||0!==a){if(i===fe)return i=Y.inflateEnd(this.strm),this.onEnd(i),this.ended=!0,!0;if(0===r.avail_in)break}}return!0},we.prototype.onData=function(e){this.chunks.push(e)},we.prototype.onEnd=function(e){e===le&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=q(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg};var me=we,ke=be,_e=function(e,t){return(t=t||{}).raw=!0,be(e,t)},ve=be,ge=c,pe={Inflate:me,inflate:ke,inflateRaw:_e,ungzip:ve,constants:ge};e.Inflate=me,e.constants=ge,e.default=pe,e.inflate=ke,e.inflateRaw=_e,e.ungzip=ve,Object.defineProperty(e,"__esModule",{value:!0})})); `; diff --git a/src/templates/tamperProtectionTemplates.ts b/src/templates/tamperProtectionTemplates.ts new file mode 100644 index 0000000..b996ca6 --- /dev/null +++ b/src/templates/tamperProtectionTemplates.ts @@ -0,0 +1,116 @@ +import { NodePath } from "@babel/traverse"; +import { PluginInstance } from "../transforms/plugin"; +import Template from "./template"; +import { MULTI_TRANSFORM, UNSAFE } from "../constants"; + +export const StrictModeTemplate = new Template(` + (function(){ + function isStrictMode(){ + try { + var arr = [] + delete arr["length"] + } catch(e) { + return true; + } + return false; + } + + if(isStrictMode()) { + {countermeasures} + {nativeFunctionName} = undefined; + } + })() + `); + +export const IndexOfTemplate = new Template(` +function indexOf(str, substr) { + const len = str.length; + const sublen = substr.length; + let count = 0; + + if (sublen > len) { + return -1; + } + + for (let i = 0; i <= len - sublen; i++) { + for (let j = 0; j < sublen; j++) { + if (str[i + j] === substr[j]) { + count++; + if (count === sublen) { + return i; + } + } else { + count = 0; + break; + } + } + } + + return -1; +} +`); + +export const NativeFunctionTemplate = new Template(` +function {nativeFunctionName}() { + {IndexOfTemplate} + + function checkFunction(fn) { + if ( + indexOf("" + fn, "{ [native code] }") === -1 || + typeof Object.getOwnPropertyDescriptor(fn, "toString") !== "undefined" + ) { + {countermeasures} + + return undefined; + } + + return fn; + } + + var args = arguments; + if (args.length === 1) { + return checkFunction(args[0]); + } else if (args.length === 2) { + var object = args[0]; + var property = args[1]; + + var fn = object[property]; + fn = checkFunction(fn); + + return fn.bind(object); + } +}`).addSymbols(UNSAFE, MULTI_TRANSFORM); + +export const createEvalIntegrityTemplate = ( + pluginInstance: PluginInstance, + path: NodePath +) => { + if (pluginInstance.options.lock?.tamperProtection) { + return new Template(` + function {EvalIntegrityName}(){ + var localVar = false; + eval(__JS_CONFUSER_VAR__(localVar) + " = true") + + if (!localVar) { + // Eval was tampered! + {countermeasures} + + return false; + } + + return true; + } + `) + .addSymbols(UNSAFE) + .setDefaultVariables({ + countermeasures: + pluginInstance.globalState.lock.createCountermeasuresCode(), + }); + } + + return new Template(` + function {EvalIntegrityName}(a = true){ + return a; + } + `); +}; From 7bdc81b0050a61238ca51ecc31224edaf9e315bf Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:36:14 -0400 Subject: [PATCH 038/103] Improve Plugin API, Change Data, Transform improvements --- src/transforms/astScrambler.ts | 33 +- src/transforms/calculator.ts | 95 ++-- src/transforms/controlFlowFlattening.ts | 412 +++++++++--------- src/transforms/deadCode.ts | 30 +- src/transforms/dispatcher.ts | 70 ++- .../extraction/duplicateLiteralsRemoval.ts | 55 ++- src/transforms/extraction/objectExtraction.ts | 16 +- src/transforms/finalizer.ts | 38 +- src/transforms/flatten.ts | 203 +++++---- src/transforms/functionOutlining.ts | 17 +- src/transforms/identifier/globalConcealing.ts | 150 ++++++- .../identifier/movedDeclarations.ts | 52 ++- src/transforms/identifier/renameVariables.ts | 407 +++++++++-------- .../identifier/variableConcealing.ts | 68 --- src/transforms/lock/integrity.ts | 26 +- src/transforms/lock/lock.ts | 216 ++++++--- src/transforms/minify.ts | 82 ++-- src/transforms/opaquePredicates.ts | 111 ++++- src/transforms/pack.ts | 219 ++++++---- src/transforms/plugin.ts | 41 +- src/transforms/preparation.ts | 26 +- src/transforms/renameLabels.ts | 15 +- src/transforms/rgf.ts | 116 +++-- src/transforms/shuffle.ts | 20 +- src/transforms/string/stringCompression.ts | 32 +- src/transforms/string/stringConcealing.ts | 44 +- src/transforms/string/stringEncoding.ts | 12 +- src/transforms/string/stringSplitting.ts | 13 +- src/transforms/variableMasking.ts | 308 +++++++------ 29 files changed, 1795 insertions(+), 1132 deletions(-) delete mode 100644 src/transforms/identifier/variableConcealing.ts diff --git a/src/transforms/astScrambler.ts b/src/transforms/astScrambler.ts index 0a68a86..3751d34 100644 --- a/src/transforms/astScrambler.ts +++ b/src/transforms/astScrambler.ts @@ -1,12 +1,18 @@ -import { PluginObj, NodePath } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { ok } from "assert"; import { Order } from "../order"; import { NodeSymbol, SKIP } from "../constants"; +import Template from "../templates/template"; +import { append } from "../utils/ast-utils"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.AstScrambler); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.AstScrambler, { + changeData: { + expressions: 0, + }, + }); var callExprName: string; return { @@ -14,6 +20,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { "Block|SwitchCase": { exit(_path) { const path = _path as NodePath; + const isProgram = path.isProgram(); let containerKey: string; if (path.isSwitchCase()) { @@ -42,6 +49,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { callExprName = me.getPlaceholder() + "_ast"; } + me.changeData.expressions += expressions.length; + newContainer.push( t.expressionStatement( t.callExpression(t.identifier(callExprName), expressions) @@ -52,6 +61,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { for (var statement of container) { if ( + // Preserve last expression at the top level + (isProgram ? statement !== container.at(-1) : true) && t.isExpressionStatement(statement) && !(statement as NodeSymbol)[SKIP] ) { @@ -72,13 +83,13 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (path.isProgram()) { if (callExprName) { - var functionDeclaration = t.functionDeclaration( - t.identifier(callExprName), - [], - t.blockStatement([]) - ); - var p = path.unshiftContainer("body", functionDeclaration); - path.scope.registerDeclaration(p[0]); + var functionDeclaration = new Template(` + function ${callExprName}(){ + ${callExprName} = function(){}; + } + `).single(); + + append(path, functionDeclaration); } } }, diff --git a/src/transforms/calculator.ts b/src/transforms/calculator.ts index 4d14be3..68446cd 100644 --- a/src/transforms/calculator.ts +++ b/src/transforms/calculator.ts @@ -1,66 +1,38 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; import { ok } from "assert"; +import { NameGen } from "../utils/NameGen"; +import { prependProgram } from "../utils/ast-utils"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Calculator); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Calculator, { + changeData: { + expressions: 0, + }, + }); + + const nameGen = new NameGen(me.options.identifierGenerator); return { visitor: { Program: { - exit(path) { + exit(programPath) { const allowedBinaryOperators = new Set(["+", "-", "*", "/"]); - const allowedUnaryOperators = new Set([ - "!", - "void", - "typeof", - "-", - "~", - "+", - ]); - var operatorsMap = new Map(); var calculatorFnName = me.getPlaceholder() + "_calc"; - path.traverse({ - UnaryExpression: { - exit(path) { - const { operator } = path.node; - - if (!allowedUnaryOperators.has(operator)) return; - - // Special `typeof identifier` check - if ( - operator === "typeof" && - path.get("argument").isIdentifier() - ) - return; - - const mapKey = "unaryExpression_" + operator; - let operatorKey = operatorsMap.get(mapKey); - - // Add unary operator to the map if it doesn't exist - if (typeof operatorKey === "undefined") { - operatorKey = me.generateRandomIdentifier(); - operatorsMap.set(mapKey, operatorKey); - } - - path.replaceWith( - t.callExpression(t.identifier(calculatorFnName), [ - t.stringLiteral(operatorKey), - path.node.argument, - ]) - ); - }, - }, + programPath.traverse({ BinaryExpression: { exit(path) { const { operator } = path.node; if (t.isPrivate(path.node.left)) return; + // TODO: Improve precedence handling or remove this transformation entirely + if (!t.isNumericLiteral(path.node.right)) return; + if (!t.isNumericLiteral(path.node.left)) return; + if (!allowedBinaryOperators.has(operator)) return; const mapKey = "binaryExpression_" + operator; @@ -68,10 +40,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Add binary operator to the map if it doesn't exist if (typeof operatorKey === "undefined") { - operatorKey = me.generateRandomIdentifier(); + operatorKey = nameGen.generate(); operatorsMap.set(mapKey, operatorKey); } + ok(operatorKey); + + me.changeData.expressions++; + path.replaceWith( t.callExpression(t.identifier(calculatorFnName), [ t.stringLiteral(operatorKey), @@ -94,29 +70,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { ).map(([mapKey, key]) => { const [type, operator] = mapKey.split("_"); - let expression: t.Expression; - if (type === "binaryExpression") { - expression = t.binaryExpression( - operator as any, - t.identifier("a"), - t.identifier("b") - ); - } else if (type === "unaryExpression") { - expression = t.unaryExpression( - operator as any, - t.identifier("a") - ); - } else { - ok(false); - } + let expression = t.binaryExpression( + operator as any, + t.identifier("a"), + t.identifier("b") + ); return t.switchCase(t.stringLiteral(key), [ t.returnStatement(expression), ]); }); - var p = path.unshiftContainer( - "body", + prependProgram( + programPath, + t.functionDeclaration( t.identifier(calculatorFnName), [t.identifier("operator"), t.identifier("a"), t.identifier("b")], @@ -125,8 +92,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]) ) ); - - path.scope.registerDeclaration(p[0]); }, }, }, diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 9df74bf..13df8d9 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1,14 +1,15 @@ -import { PluginObj } from "@babel/core"; import traverse, { NodePath, Scope, Visitor } from "@babel/traverse"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { computeProbabilityMap } from "../probability"; import { ensureComputedExpression, getParentFunctionOrProgram, isDefiningIdentifier, + isModifiedIdentifier, isStrictMode, isVariableIdentifier, + replaceDefiningIdentifierToMemberExpression, } from "../utils/ast-utils"; import * as t from "@babel/types"; import { numericLiteral, deepClone } from "../utils/node"; @@ -22,7 +23,14 @@ import { import { IntGen } from "../utils/IntGen"; import { ok } from "assert"; import { NameGen } from "../utils/NameGen"; -import { NodeSymbol, UNSAFE, NO_RENAME, PREDICTABLE } from "../constants"; +import { + NodeSymbol, + UNSAFE, + NO_RENAME, + PREDICTABLE, + variableFunctionName, + WITH_STATEMENT, +} from "../constants"; /** * Breaks functions into DAGs (Directed Acyclic Graphs) @@ -37,8 +45,16 @@ import { NodeSymbol, UNSAFE, NO_RENAME, PREDICTABLE } from "../constants"; * - 2. At the end of each case, the state variable is updated to the next block of code. * - 3. The while loop continues until the the state variable is the end state. */ -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.ControlFlowFlattening); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.ControlFlowFlattening, { + changeData: { + functions: 0, + blocks: 0, + ifStatements: 0, + deadCode: 0, + variables: 0, + }, + }); // in Debug mode, the output is much easier to read const isDebug = false; @@ -48,6 +64,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const addDeadCode = true; // add fakes chunks of code const addFakeTests = true; // case 100: case 490: case 510: ... const addComplexTests = true; // case s != 49 && s - 10: + const addPredicateTests = true; // case scope.A + 10: ... const mangleNumericalLiterals = true; // 50 => state + X const mangleBooleanLiterals = true; // true => state == X const addWithStatement = true; // Disabling not supported yet @@ -57,12 +74,25 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Amount of blocks changed by Control Flow Flattening let cffCounter = 0; + const functionsModified = new Set(); + return { + post: () => { + functionsModified.forEach((node) => { + (node as NodeSymbol)[UNSAFE] = true; + }); + }, visitor: { "Program|Function": { exit(_path) { let programOrFunctionPath = _path as NodePath; + // Exclude loops + if ( + programOrFunctionPath.find((p) => p.isForStatement() || p.isWhile()) + ) + return; + let programPath = _path.isProgram() ? _path : null; let functionPath = _path.isFunction() ? _path : null; @@ -70,7 +100,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (programPath) { blockPath = programPath; } else { - var fnBlockPath = functionPath.get("body"); + let fnBlockPath = functionPath.get("body"); if (!fnBlockPath.isBlock()) return; blockPath = fnBlockPath; } @@ -97,7 +127,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const blockFnParent = getParentFunctionOrProgram(blockPath); let hasIllegalNode = false; - var bindingNames = new Set(); + const bindingNames = new Set(); blockPath.traverse({ "Super|MetaProperty|AwaitExpression|YieldExpression"(path) { if ( @@ -113,11 +143,21 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.stop(); } }, - BindingIdentifier(path) { + Identifier(path) { + if ( + path.node.name === variableFunctionName || + path.node.name === "arguments" + ) { + hasIllegalNode = true; + path.stop(); + return; + } + + if (!path.isBindingIdentifier()) return; const binding = path.scope.getBinding(path.node.name); if (!binding) return; - var fnParent = path.getFunctionParent(); + let fnParent = path.getFunctionParent(); if ( path.key === "id" && path.parentPath.isFunctionDeclaration() @@ -138,31 +178,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { } bindingNames.add(path.node.name); }, - "BreakStatement|ContinueStatement"(_path) { - var path = _path as NodePath< - t.BreakStatement | t.ContinueStatement - >; - if (path.node.label) return; - - const parent = path.findParent( - (p) => - p.isFor() || - p.isWhile() || - (path.isBreakStatement() && p.isSwitchCase()) || - p === blockPath - ); - - if (parent === blockPath) { - hasIllegalNode = true; - path.stop(); - } - }, }); if (hasIllegalNode) { return; } + me.changeData.blocks++; + // Limit how many numbers get entangled let mangledLiteralsCreated = 0; @@ -173,7 +196,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (isDebug) { name = prefix + "_" + suffix; } else { - name = me.obfuscator.nameGen.generate(); + name = me.obfuscator.nameGen.generate(false); } var id = t.identifier(name); @@ -225,15 +248,16 @@ export default ({ Plugin }: PluginArg): PluginObj => { preserveNames = new Set(); - findUsed(): ScopeManager { - if (this.isNotUsed) return this.parent?.findUsed(); + findWithBindings(): ScopeManager { + if (this.nameMap.size === 0) + return this.parent?.findWithBindings(); return this; } getNewName(name: string, originalNode?: t.Node) { if (!this.nameMap.has(name)) { - let newName = this.nameGen.generate(); + let newName = this.nameGen.generate(false); if (isDebug) { newName = "_" + name; } @@ -243,6 +267,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { this.nameMap.set(name, newName); + me.changeData.variables++; + // console.log( // "Renaming " + // name + @@ -282,11 +308,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { identity: "${this.propertyName}" }) `).expression() - : t.objectExpression([]); + : new Template(`Object["create"](null)`).expression(); } getMemberExpression(name: string) { - var memberExpression = t.memberExpression( + const memberExpression = t.memberExpression( this.getScopeObject(), t.stringLiteral(name), true @@ -310,12 +336,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { } getObjectExpression(refreshLabel: string) { - var refreshScope = basicBlocks.get(refreshLabel).scopeManager; - var propertyMap: { [property: string]: t.Expression } = {}; + let refreshScope = basicBlocks.get(refreshLabel).scopeManager; + let propertyMap: { [property: string]: t.Expression } = {}; - var cursor = this.scope; + let cursor = this.scope; while (cursor) { - var parentScopeManager = scopeToScopeManager.get(cursor); + let parentScopeManager = scopeToScopeManager.get(cursor); if (parentScopeManager) { propertyMap[parentScopeManager.propertyName] = t.memberExpression( @@ -331,8 +357,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { propertyMap[refreshScope.propertyName] = refreshScope.getInitializingObjectExpression(); - var properties: t.ObjectProperty[] = []; - for (var key in propertyMap) { + const properties: t.ObjectProperty[] = []; + for (const key in propertyMap) { properties.push( t.objectProperty(t.stringLiteral(key), propertyMap[key], true) ); @@ -341,19 +367,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { return t.objectExpression(properties); } - hasName(name: string) { - let cursor: ScopeManager = this; - while (cursor) { - if (cursor.nameMap.has(name)) { - return true; - } - cursor = cursor.parent; - } - - return false; + hasOwnName(name: string) { + return this.nameMap.has(name); } } + const getImpossibleBasicBlocks = () => { + return Array.from(basicBlocks.values()).filter( + (block) => block.options.impossible + ); + }; + const scopeToScopeManager = new Map(); /** * A Basic Block is a sequence of instructions with no diversion except at the entry and exit points. @@ -465,16 +489,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { }; } - identifier( - identifierName: string, - scopeManager = this.scopeManager - ) { + identifier(identifierName: string, scopeManager: ScopeManager) { if ( this.withDiscriminant && this.withDiscriminant === scopeManager ) { var id = t.identifier(identifierName); (id as NodeSymbol)[NO_RENAME] = identifierName; + (id as NodeSymbol)[WITH_STATEMENT] = true; me.skip(id); return id; } @@ -486,7 +508,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { constructor( public label: string, - public parentPath: NodePath + public parentPath: NodePath, + public options: { impossible?: boolean } = {} ) { this.createPath(); @@ -620,7 +643,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { isIllegal = true; } let oldBasicBlock = currentBasicBlock; - var fnLabel = me.getPlaceholder(); + let fnLabel = me.getPlaceholder(); let sm = currentBasicBlock.scopeManager; let rename = sm.getNewName(fnName); @@ -636,6 +659,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { continue; } + me.changeData.functions++; + const functionExpression = t.functionExpression( null, [], @@ -665,7 +690,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { nextBlockPath: blockStatement, jumpToNext: false, }); - var fnTopBlock = currentBasicBlock; + let fnTopBlock = currentBasicBlock; // Implicit return blockStatement.node.body.push( @@ -704,7 +729,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Change bindings from 'param' to 'var' statement.get("params").forEach((param) => { - var ids = param.getBindingIdentifierPaths(); + let ids = param.getBindingIdentifierPaths(); // Loop over the record of binding identifiers for (const identifierName in ids) { const identifierPath = ids[identifierName]; @@ -735,6 +760,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { consequent.isBlockStatement() && (!alternate.node || alternate.isBlockStatement()) ) { + me.changeData.ifStatements++; + const consequentLabel = me.getPlaceholder(); const alternateLabel = alternate.node ? me.getPlaceholder() @@ -841,20 +868,23 @@ export default ({ Plugin }: PluginArg): PluginObj => { for (let i = 0; i < fakeChunkCount; i++) { // These chunks just jump somewhere random, they are never executed // so it could contain any code - const fakeBlock = new BasicBlock(me.getPlaceholder(), blockPath); + const fakeBlock = new BasicBlock(me.getPlaceholder(), blockPath, { + impossible: true, + }); let fakeJump; do { fakeJump = choice(Array.from(basicBlocks.keys())); } while (fakeJump === fakeBlock.label); fakeBlock.insertAfter(GotoControlStatement(fakeJump)); + me.changeData.deadCode++; } // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators // "irreducible control flow" basicBlocks.forEach((basicBlock) => { - if (chance(25)) { - var randomLabel = choice(Array.from(basicBlocks.keys())); + if (chance(30 - basicBlocks.size)) { + let randomLabel = choice(Array.from(basicBlocks.keys())); // The `false` literal will be mangled basicBlock.insertAfter( @@ -867,6 +897,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { predicate: basicBlock.createFalsePredicate(), }) ); + + me.changeData.deadCode++; } }); // DEAD CODE 3/3: Clone chunks but these chunks are never ran @@ -882,7 +914,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (!hasDeclaration) { let clonedChunk = new BasicBlock( me.getPlaceholder(), - randomChunk.parentPath + randomChunk.parentPath, + { + impossible: true, + } ); randomChunk.thisNode.body @@ -891,10 +926,28 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (node.type === "EmptyStatement") return; clonedChunk.insertAfter(node); }); + + me.changeData.deadCode++; } } } + // Select scope managers for the with statement + for (const basicBlock of basicBlocks.values()) { + basicBlock.bestWithDiscriminant = + basicBlock.initializedScope?.findWithBindings(); + + if (isDebug && basicBlock.withDiscriminant) { + basicBlock.body.unshift( + t.expressionStatement( + t.stringLiteral( + "With " + basicBlock.withDiscriminant.propertyName + ) + ) + ); + } + } + // Remap 'GotoStatement' to actual state assignments and Break statements for (const basicBlock of basicBlocks.values()) { const { stateValues: currentStateValues } = basicBlock; @@ -903,11 +956,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { const outerFn = getParentFunctionOrProgram(basicBlock.parentPath); function isWithinSameFunction(path: NodePath) { - var fn = getParentFunctionOrProgram(path); + let fn = getParentFunctionOrProgram(path); return fn.node === outerFn.node; } - var visitor: Visitor = { + let visitor: Visitor = { BooleanLiteral: { exit(boolPath) { // Don't mangle booleans in debug mode @@ -1026,132 +1079,24 @@ export default ({ Plugin }: PluginArg): PluginObj => { scopeManager.isNotUsed = false; - if (path.isBindingIdentifier()) { - if ( - path.key === "id" && - path.parentPath.isFunctionDeclaration() - ) { - var asFunctionExpression = deepClone( - path.parentPath.node - ) as t.Node as t.FunctionExpression; - asFunctionExpression.type = "FunctionExpression"; - - path.parentPath.replaceWith( - t.expressionStatement( - t.assignmentExpression( - "=", - memberExpression, - asFunctionExpression - ) - ) - ); - return; - } else if ( - path.key === "id" && - path.parentPath.isClassDeclaration() - ) { - var asClassExpression = deepClone( - path.parentPath.node - ) as t.Node as t.ClassExpression; - asClassExpression.type = "ClassExpression"; - - path.parentPath.replaceWith( - t.expressionStatement( - t.assignmentExpression( - "=", - memberExpression, - asClassExpression - ) - ) - ); - return; - } else { - var variableDeclaration = path.find( - (p) => - p.isVariableDeclaration() || - p === basicBlock.parentPath - ) as NodePath; - - if ( - variableDeclaration && - variableDeclaration.isVariableDeclaration() - ) { - ok(variableDeclaration.node.declarations.length === 1); - - const first = - variableDeclaration.get("declarations")[0]; - const id = first.get("id"); - - const init = first.get("init"); - - var newExpression: t.Node = id.node; - - var isForInitializer = - (variableDeclaration.key === "init" || - variableDeclaration.key === "left") && - variableDeclaration.parentPath.isFor(); - - if (init.node || !isForInitializer) { - newExpression = t.assignmentExpression( - "=", - id.node, - init.node || t.identifier("undefined") - ); - } - - if (!isForInitializer) { - newExpression = t.expressionStatement( - newExpression as t.Expression - ); - } - - variableDeclaration.replaceWith(newExpression); - path.replaceWith(memberExpression); - - return; - } else { - //ok(false, "Binding not found"); - } - } - } - if (isDefiningIdentifier(path)) { + replaceDefiningIdentifierToMemberExpression( + path, + memberExpression + ); return; } + if (!path.container) return; - var assignmentLeft = path.find( - (p) => - (p.key === "left" && - p.parentPath?.isAssignmentExpression()) || - p === basicBlock.parentPath - ); - if ( - assignmentLeft && - !assignmentLeft.parentPath?.isAssignmentExpression() - ) { - assignmentLeft = null; - } + var isModified = isModifiedIdentifier(path); if ( basicBlock.withDiscriminant && basicBlock.withDiscriminant === scopeManager && - basicBlock.withDiscriminant.hasName(identifierName) + basicBlock.withDiscriminant.hasOwnName(identifierName) ) { - // console.log(identifierName, !!assignmentLeft); - if (assignmentLeft) { - // memberExpression = new Template(` - // typeof {identifierName} !== "undefined" ? {identifierName} : {memberExpression} - // `).expression({ - // memberExpression: memberExpression, - // identifierName: () => { - // var id = t.identifier(newName); - // (id as NodeSymbol)[NO_RENAME] = newName; - // me.skip(id); - // return id; - // }, - // }); - } else { + if (!isModified) { memberExpression = basicBlock.identifier( newName, scopeManager @@ -1290,19 +1235,38 @@ export default ({ Plugin }: PluginArg): PluginObj => { basicBlock.thisPath.traverse(visitor); } - // Select scope managers for the with statement - for (const basicBlock of basicBlocks.values()) { - basicBlock.bestWithDiscriminant = - basicBlock.initializedScope?.findUsed(); + // Create global numbers for predicates + const mainScope = basicBlocks.get(startLabel).scopeManager; + const predicateNumbers = new Map(); + const predicateNumberCount = isDebug ? 0 : getRandomInteger(2, 5); + for (let i = 0; i < predicateNumberCount; i++) { + const name = mainScope.getNewName( + me.getPlaceholder("predicate_" + i) + ); - if (isDebug && basicBlock.withDiscriminant) { - basicBlock.body.unshift( - t.expressionStatement( - t.stringLiteral( - "With " + basicBlock.withDiscriminant.propertyName - ) - ) - ); + const number = getRandomInteger(-250, 250); + predicateNumbers.set(name, number); + + const createAssignment = (value: number) => { + return new Template(` + {memberExpression} = {number} + `).single({ + memberExpression: mainScope.getMemberExpression(name), + number: numericLiteral(number), + }); + }; + + basicBlocks.get(startLabel).body.unshift(createAssignment(number)); + + // Add random assignments to impossible blocks + var fakeAssignmentCount = getRandomInteger(0, 3); + for (let i = 0; i < fakeAssignmentCount; i++) { + var impossibleBlock = choice(getImpossibleBasicBlocks()); + if (impossibleBlock) { + impossibleBlock.body.unshift( + createAssignment(getRandomInteger(-250, 250)) + ); + } } } @@ -1331,31 +1295,52 @@ export default ({ Plugin }: PluginArg): PluginObj => { let test: t.Expression = numericLiteral(block.totalState); + // Predicate tests cannot apply to the start label + // As that's when the numbers are initialized + if ( + !isDebug && + addPredicateTests && + block.label !== startLabel && + chance(50) + ) { + let predicateName = choice(Array.from(predicateNumbers.keys())); + if (predicateName) { + let number = predicateNumbers.get(predicateName); + let diff = block.totalState - number; + + test = t.binaryExpression( + "+", + mainScope.getMemberExpression(predicateName), + numericLiteral(diff) + ); + } + } + // Add complex tests - if (!isDebug && addComplexTests && chance(25)) { + if (!isDebug && addComplexTests && chance(50)) { // Create complex test expressions for each switch case // case STATE+X: - var stateVarIndex = getRandomInteger(0, stateVars.length); + let stateVarIndex = getRandomInteger(0, stateVars.length); - var stateValues = block.stateValues; - var difference = stateValues[stateVarIndex] - block.totalState; + let stateValues = block.stateValues; + let difference = stateValues[stateVarIndex] - block.totalState; - var conditionNodes: t.Expression[] = []; - var alreadyConditionedItems = new Set(); + let conditionNodes: t.Expression[] = []; + let alreadyConditionedItems = new Set(); // This code finds clash conditions and adds them to 'conditionNodes' array Array.from(basicBlocks.keys()).forEach((label) => { if (label !== block.label) { - var labelStates = basicBlocks.get(label).stateValues; - var totalState = labelStates.reduce((a, b) => a + b, 0); + let labelStates = basicBlocks.get(label).stateValues; + let totalState = labelStates.reduce((a, b) => a + b, 0); if (totalState === labelStates[stateVarIndex] - difference) { - var differentIndex = labelStates.findIndex( + let differentIndex = labelStates.findIndex( (v, i) => v !== stateValues[i] ); if (differentIndex !== -1) { - var expressionAsString = + let expressionAsString = stateVars[differentIndex].name + "!=" + labelStates[differentIndex]; @@ -1398,9 +1383,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { const tests = [test]; - if (!isDebug && addFakeTests) { + if (!isDebug && addFakeTests && chance(50)) { // Add fake tests - for (let i = 0; i < getRandomInteger(1, 3); i++) { + let fakeTestCount = getRandomInteger(1, 3); + for (let i = 0; i < fakeTestCount; i++) { tests.push(numericLiteral(stateIntGen.generate())); } @@ -1451,18 +1437,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { new Template( `{withDiscriminant} || Object["create"](null)` ).expression({ - withDiscriminant, + withDiscriminant: deepClone(withDiscriminant), }), t.blockStatement([switchStatement]) ), ]) ); - var parameters: t.Identifier[] = [...stateVars, argVar, scopeVar].map( - (id) => deepClone(id) - ); + const parameters: t.Identifier[] = [ + ...stateVars, + argVar, + scopeVar, + ].map((id) => deepClone(id)); - var parametersNames: string[] = parameters.map((id) => id.name); + const parametersNames: string[] = parameters.map((id) => id.name); for (var [ originalFnName, @@ -1475,8 +1463,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { const argumentsRestName = me.getPlaceholder(); - var argumentsNodes = []; - for (var parameterName of parametersNames) { + const argumentsNodes = []; + for (const parameterName of parametersNames) { const stateIndex = stateVars .map((x) => x.name) .indexOf(parameterName); @@ -1554,6 +1542,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ...startProgramStatements, ]; + functionsModified.add(programOrFunctionPath.node); + // Reset all bindings here blockPath.scope.bindings = Object.create(null); diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index ef43ec8..e7a8117 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -1,5 +1,4 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import { chance, choice } from "../utils/random-utils"; import { deadCodeTemplates } from "../templates/deadCodeTemplates"; import { computeProbabilityMap } from "../probability"; @@ -8,24 +7,33 @@ import * as t from "@babel/types"; import Template from "../templates/template"; import { NameGen } from "../utils/NameGen"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.DeadCode); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.DeadCode, { + changeData: { + deadCode: 0, + }, + }); let created = 0; return { visitor: { Block: { exit(path) { - if (path.node.body.length === 0) { - return; - } - if (!computeProbabilityMap(me.options.deadCode)) { return; } - if (created > 100 && chance(created - 100)) return; - created++; + if (typeof me.options.deadCode !== "function") { + let suggestedMax = 25; + if (me.obfuscator.parentObfuscator) { + // RGF should contain less dead code + suggestedMax = 5; + } + + if (created > suggestedMax && chance(created - suggestedMax)) + return; + created++; + } var template = choice(deadCodeTemplates); var nodes = template.compile(); @@ -53,6 +61,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { PrototypeCollision[randomProperty] !== undefined ); + me.changeData.deadCode++; + path.unshiftContainer( "body", new Template(` diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index 4da3f9d..22e64df 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -1,5 +1,4 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; import Template from "../templates/template"; @@ -14,14 +13,21 @@ import { } from "../utils/function-utils"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { numericLiteral } from "../utils/node"; -import { prependProgram } from "../utils/ast-utils"; +import { isStrictMode, prependProgram } from "../utils/ast-utils"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Dispatcher); - var dispatcherCounter = 0; +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Dispatcher, { + changeData: { + functions: 0, + }, + }); + let dispatcherCounter = 0; const setFunctionLength = me.getPlaceholder("d_fnLength"); + // in Debug mode, function names are preserved + const isDebug = false; + return { visitor: { "Program|Function": { @@ -90,19 +96,25 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } + if (isStrictMode(path)) { + illegalNames.add(name); + return; + } + + // Do not apply to unsafe functions, redefined functions, or internal obfuscator functions if ( + (path.node as NodeSymbol)[UNSAFE] || functionPaths.has(name) || - (path.node as NodeSymbol)[UNSAFE] + me.obfuscator.isInternalVariable(name) ) { illegalNames.add(name); return; } + // Functions with default parameters are not fully supported + // (Could be a Function Expression referencing outer scope) if ( - path.node.params.find( - (x) => x.type === "AssignmentPattern" - ) || - (path.node as NodeSymbol)[UNSAFE] + path.node.params.find((x) => x.type === "AssignmentPattern") ) { illegalNames.add(name); return; @@ -128,6 +140,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } + me.changeData.functions += functionPaths.size; + const dispatcherName = me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++; const payloadName = me.getPlaceholder() + "_payload"; @@ -135,15 +149,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { const newNameMapping = new Map(); const keys = { - placeholderNoMeaning: getRandomString(10), - clearPayload: getRandomString(10), - nonCall: getRandomString(10), - returnAsObject: getRandomString(10), - returnAsObjectProperty: getRandomString(10), + placeholderNoMeaning: isDebug ? "noMeaning" : getRandomString(10), + clearPayload: isDebug ? "clearPayload" : getRandomString(10), + nonCall: isDebug ? "nonCall" : getRandomString(10), + returnAsObject: isDebug ? "returnAsObject" : getRandomString(10), + returnAsObjectProperty: isDebug + ? "returnAsObjectProperty" + : getRandomString(10), }; for (var name of functionPaths.keys()) { - newNameMapping.set(name, getRandomString(6) /** "_" + name */); + newNameMapping.set( + name, + isDebug ? "_" + name : getRandomString(6) /** "_" + name */ + ); } // Find identifiers calling/referencing the functions @@ -204,7 +223,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Replace the identifier with a call to the function const parentPath = path.parentPath; - if (parentPath?.isCallExpression()) { + if (path.key === "callee" && parentPath?.isCallExpression()) { var expressions: t.Expression[] = []; var callArguments = parentPath.node.arguments; @@ -280,12 +299,27 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); } + // Add debug label + if (isDebug) { + newBody.unshift( + t.expressionStatement( + t.stringLiteral(`Dispatcher: ${name} -> ${newName}`) + ) + ); + } + const functionExpression = t.functionExpression( null, [], t.blockStatement(newBody) ); + for (var symbol of Object.getOwnPropertySymbols(originalFn)) { + (functionExpression as any)[symbol] = (originalFn as any)[ + symbol + ]; + } + (functionExpression as NodeSymbol)[PREDICTABLE] = true; return t.objectProperty( diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 47264cc..efcac8a 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -1,42 +1,27 @@ -import { PluginObj } from "@babel/core"; import * as t from "@babel/types"; import { ok } from "assert"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { ensureComputedExpression, prepend } from "../../utils/ast-utils"; -import { numericLiteral } from "../../utils/node"; +import { createLiteral, LiteralValue, numericLiteral } from "../../utils/node"; +import { NodePath } from "@babel/traverse"; function fail(): never { throw new Error("Assertion failed"); } -type LiteralValue = string | number | boolean | undefined | null; -const createLiteral = (value: LiteralValue) => { - if (value === null) return t.nullLiteral(); - if (value === undefined) return t.identifier("undefined"); - - switch (typeof value) { - case "string": - return t.stringLiteral(value); - - case "number": - return numericLiteral(value); - - case "boolean": - return t.booleanLiteral(value); - } - - ok(false); -}; - -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.DuplicateLiteralsRemoval); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.DuplicateLiteralsRemoval, { + changeData: { + literals: 0, + }, + }); return { visitor: { Program: { enter(programPath) { - const arrayName = me.generateRandomIdentifier(); + const arrayName = me.getPlaceholder() + "_dlrArray"; // Collect all literals const literalsMap = new Map(); @@ -57,7 +42,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Traverse through all nodes to find literals programPath.traverse({ - "Literal|Identifier"(_path) { + "StringLiteral|BooleanLiteral|NumericLiteral|NullLiteral|Identifier"( + _path + ) { const literalPath = _path as babel.NodePath< t.Literal | t.Identifier >; @@ -65,6 +52,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { let node = literalPath.node; var isUndefined = false; if (literalPath.isIdentifier()) { + // Only referenced variable names + if (!(literalPath as NodePath).isReferencedIdentifier()) return; + + // undefined = true; // Skip + if ((literalPath as NodePath).isBindingIdentifier()) return; + // Allow 'undefined' to be redefined if ( literalPath.scope.hasBinding(literalPath.node.name, { @@ -79,7 +72,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } } - if (t.isRegExpLiteral(node) || t.isTemplateLiteral(node)) return; + if ( + t.isRegExpLiteral(node) || + t.isTemplateLiteral(node) || + t.isDirectiveLiteral(node) + ) + return; const value: LiteralValue = isUndefined ? undefined @@ -114,6 +112,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { var firstPath = firstTimeMap.get(value); + me.changeData.literals++; ensureComputedExpression(firstPath); firstPath.replaceWith(createMemberExpression(index)); @@ -127,7 +126,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ok(index !== -1); - // Replace literals in the code with a placeholder + me.changeData.literals++; ensureComputedExpression(literalPath); literalPath.replaceWith(createMemberExpression(index)); diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index 39e98c3..afaf7b1 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -1,5 +1,5 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import * as t from "@babel/types"; import { @@ -9,8 +9,12 @@ import { } from "../../utils/ast-utils"; import { computeProbabilityMap } from "../../probability"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.ObjectExtraction); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.ObjectExtraction, { + changeData: { + objects: 0, + }, + }); return { visitor: { @@ -173,6 +177,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { for (const { path, replaceWith } of pendingReplacements) { path.replaceWith(replaceWith); } + + me.log("Extracted object", identifier.node.name); + + me.changeData.objects++; }, }, }; diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index 27357a5..4a56e00 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -1,10 +1,11 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; import stringEncoding from "./string/stringEncoding"; +import { GEN_NODE, NodeSymbol, variableFunctionName } from "../constants"; +import { ok } from "assert"; -export default ({ Plugin }: PluginArg): PluginObj => { +export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Finalizer); const stringEncodingPlugin = stringEncoding(me); @@ -13,6 +14,32 @@ export default ({ Plugin }: PluginArg): PluginObj => { // String encoding ...stringEncodingPlugin.visitor, + // Backup __JS_CONFUSER_VAR__ replacement + // While done in Preparation, Rename Variables + // This accounts for when Rename Variables is disabled and an inserted Template adds __JS_CONFUSER_VAR__ calls + ...(me.obfuscator.hasPlugin(Order.RenameVariables) + ? {} + : { + CallExpression: { + exit(path) { + if ( + path.get("callee").isIdentifier({ + name: variableFunctionName, + }) + ) { + var args = path.get("arguments"); + ok(args.length === 1); + + var arg = args[0]; + ok(arg.isIdentifier()); + + var name = arg.node.name; + path.replaceWith(t.stringLiteral(name)); + } + }, + }, + }), + // Hexadecimal numbers NumberLiteral: { exit(path) { @@ -34,7 +61,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { var newStr = (isNegative ? "-" : "") + "0x" + hex; - path.replaceWith(t.identifier(newStr)); + var id = t.identifier(newStr); + (id as NodeSymbol)[GEN_NODE] = true; + + path.replaceWith(id); path.skip(); } }, diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index c818c28..0527d75 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -1,33 +1,34 @@ import * as t from "@babel/types"; -import { NodePath, PluginObj } from "@babel/core"; +import { NodePath } from "@babel/core"; import { ensureComputedExpression, getFunctionName, isDefiningIdentifier, + isModifiedIdentifier, + isStrictMode, + isVariableIdentifier, prepend, + prependProgram, } from "../utils/ast-utils"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; -import { getRandomInteger } from "../utils/random-utils"; -import { NodeSymbol, UNSAFE } from "../constants"; -import { computeFunctionLength } from "../utils/function-utils"; -import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; +import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; +import { + computeFunctionLength, + isVariableFunctionIdentifier, +} from "../utils/function-utils"; import { ok } from "assert"; import { Scope } from "@babel/traverse"; +import { NameGen } from "../utils/NameGen"; -const SKIP = Symbol("skip"); -interface NodeSkip { - [SKIP]?: boolean; -} - -function skipNode(node: T): T { - (node as NodeSkip)[SKIP] = true; - return node; -} - -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Flatten); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Flatten, { + changeData: { + functions: 0, + }, + }); + const isDebug = false; function flattenFunction(fnPath: NodePath) { // Skip if already processed @@ -37,7 +38,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (fnPath.node.generator) return; // Skip getter/setter methods - if (fnPath.isObjectMethod()) { + if (fnPath.isObjectMethod() || fnPath.isClassMethod()) { if (fnPath.node.kind !== "method") return; } @@ -61,28 +62,34 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } + const strictMode = fnPath.find((path) => isStrictMode(path)); + if (strictMode === fnPath) return; + me.log("Transforming", functionName); const flatObjectName = `${me.getPlaceholder()}_flat_object`; const newFnName = `${me.getPlaceholder()}_flat_${functionName}`; - function generateProp(originalName) { - var newProp; + const nameGen = new NameGen(me.options.identifierGenerator); + + function generateProp(originalName: string, type: string) { + var newPropertyName: string; do { - newProp = "" + originalName + getRandomInteger(0, 10); - // newProp = getRandomString(6); - } while ( - standardProps.has(newProp) || - typeofProps.has(newProp) || - functionCallProps.has(newProp) - ); + newPropertyName = isDebug + ? type + "_" + originalName + : nameGen.generate(); + } while (allPropertyNames.has(newPropertyName)); - return newProp; + allPropertyNames.add(newPropertyName); + + return newPropertyName; } const standardProps = new Map(); + const setterPropsNeeded = new Set(); const typeofProps = new Map(); const functionCallProps = new Map(); + const allPropertyNames = new Set(); const identifierPaths: NodePath[] = []; @@ -90,24 +97,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnPath.traverse({ Identifier: { exit(identifierPath) { - const type = identifierPath.isReferencedIdentifier() - ? "referenced" - : (identifierPath as NodePath).isBindingIdentifier() - ? "binding" - : "other"; - - if (!type) return; - if (type === "binding" && isDefiningIdentifier(identifierPath)) + if (!isVariableIdentifier(identifierPath)) return; + + if ( + identifierPath.isBindingIdentifier() && + isDefiningIdentifier(identifierPath) + ) return; + if (isVariableFunctionIdentifier(identifierPath)) return; + if ((identifierPath.node as NodeSymbol)[UNSAFE]) return; const identifierName = identifierPath.node.name; - if ( - t.isFunctionDeclaration(identifierPath.parent) && - identifierPath.parent.id === identifierPath.node - ) - return; if (identifierName === "arguments") return; var binding = identifierPath.scope.getBinding(identifierName); @@ -115,12 +117,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - if ( - binding.kind === "param" && - binding.identifier === identifierPath.node - ) - return; - var definedLocal = identifierPath.scope; do { if (definedLocal.hasOwnBinding(identifierName)) return; @@ -171,7 +167,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (isTypeof) { var typeofProp = typeofProps.get(identifierName); if (!typeofProp) { - typeofProp = generateProp(identifierName); + typeofProp = generateProp(identifierName, "typeof"); typeofProps.set(identifierName, typeofProp); } @@ -188,7 +184,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } else if (isFunctionCall) { let functionCallProp = functionCallProps.get(identifierName); if (!functionCallProp) { - functionCallProp = generateProp(identifierName); + functionCallProp = generateProp(identifierName, "call"); functionCallProps.set(identifierName, functionCallProp); } @@ -205,10 +201,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { } else { let standardProp = standardProps.get(identifierName); if (!standardProp) { - standardProp = generateProp(identifierName); + standardProp = generateProp(identifierName, "standard"); standardProps.set(identifierName, standardProp); } + if (!setterPropsNeeded.has(identifierName)) { + // Only provide 'set' method if the variable is modified + var isModification = isModifiedIdentifier(identifierPath); + + if (isModification) { + setterPropsNeeded.add(identifierName); + } + } + ensureComputedExpression(identifierPath); // Replace identifier with a reference to the flat object property @@ -236,7 +241,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const [identifierName, objectProp] = entry; flatObjectProperties.push( - skipNode( + me.skip( t.objectMethod( "get", t.stringLiteral(objectProp), @@ -249,35 +254,38 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) ); - var valueArgName = me.getPlaceholder() + "_value"; - flatObjectProperties.push( - skipNode( - t.objectMethod( - "set", - t.stringLiteral(objectProp), - [t.identifier(valueArgName)], - t.blockStatement([ - t.expressionStatement( - t.assignmentExpression( - "=", - t.identifier(identifierName), - t.identifier(valueArgName) - ) - ), - ]), - false, - false, - false + // Not all properties need a setter + if (setterPropsNeeded.has(identifierName)) { + var valueArgName = me.getPlaceholder() + "_value"; + flatObjectProperties.push( + me.skip( + t.objectMethod( + "set", + t.stringLiteral(objectProp), + [t.identifier(valueArgName)], + t.blockStatement([ + t.expressionStatement( + t.assignmentExpression( + "=", + t.identifier(identifierName), + t.identifier(valueArgName) + ) + ), + ]), + false, + false, + false + ) ) - ) - ); + ); + } } for (const entry of typeofProps) { const [identifierName, objectProp] = entry; flatObjectProperties.push( - skipNode( + me.skip( t.objectMethod( "get", t.stringLiteral(objectProp), @@ -299,7 +307,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { const [identifierName, objectProp] = entry; flatObjectProperties.push( - skipNode( + me.skip( t.objectMethod( "method", t.stringLiteral(objectProp), @@ -357,17 +365,60 @@ export default ({ Plugin }: PluginArg): PluginObj => { fnPath.skip(); // Add the new flattened function at the top level - var newPath = prepend(program, flattenedFunctionDeclaration)[0]; + var newPath = prependProgram( + program, + flattenedFunctionDeclaration + )[0] as NodePath; me.skip(newPath); + // Copy over all properties except the predictable flag + for (var symbol of Object.getOwnPropertySymbols(fnPath.node)) { + if (symbol !== PREDICTABLE) { + newPath.node[symbol] = fnPath.node[symbol]; + } + } + + // Old function is no longer predictable (rest element parameter) + (fnPath.node as NodeSymbol)[PREDICTABLE] = false; + // Old function is unsafe (uses arguments, this) + (fnPath.node as NodeSymbol)[UNSAFE] = true; + + newPath.node[PREDICTABLE] = true; + + // Carry over 'use strict' directive if not already present + if (strictMode) { + newPath.node.body.directives.push( + t.directive(t.directiveLiteral("use strict")) + ); + + // Non-simple parameter list conversion + prepend( + newPath, + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern(newPath.node.params), + t.identifier("arguments") + ), + ]) + ); + newPath.node.params = []; + // Using 'arguments' is unsafe + (newPath.node as NodeSymbol)[UNSAFE] = true; + // Params changed and using 'arguments' + (newPath.node as NodeSymbol)[PREDICTABLE] = false; + } + // Ensure parameters are registered in the new function scope newPath.scope.crawl(); newPath.skip(); + me.skip(newPath); // Set function length me.setFunctionLength(fnPath, originalLength); + + me.changeData.functions++; } return { diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts index 41b6bb9..ee6eccf 100644 --- a/src/transforms/functionOutlining.ts +++ b/src/transforms/functionOutlining.ts @@ -1,5 +1,5 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { ensureComputedExpression, prepend } from "../utils/ast-utils"; import * as t from "@babel/types"; @@ -68,8 +68,12 @@ function isSafeForOutlining(path: NodePath): { return { isSafe, bindings }; } -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.FunctionOutlining); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.FunctionOutlining, { + changeData: { + functionsMade: 0, + }, + }); var changesMade = 0; @@ -152,6 +156,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { t.blockStatement(extractedStatements.map((x) => x.node)) ) ); + + me.changeData.functionsMade++; + var callExpression = t.callExpression(memberExpression, []); statement.replaceWith(callExpression); @@ -193,6 +200,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) ); + me.changeData.functionsMade++; + var callExpression = t.callExpression(memberExpression, []); ensureComputedExpression(path); diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 6b26b82..bea94bc 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -1,14 +1,23 @@ import * as t from "@babel/types"; -import { NodePath, PluginObj } from "@babel/core"; +import { NodePath } from "@babel/core"; import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; -import { variableFunctionName } from "../../constants"; -import { prepend } from "../../utils/ast-utils"; +import { + MULTI_TRANSFORM, + reservedIdentifiers, + variableFunctionName, +} from "../../constants"; +import { + getMemberExpressionPropertyAsString, + isVariableIdentifier, + prepend, +} from "../../utils/ast-utils"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; import { getRandomInteger, getRandomString } from "../../utils/random-utils"; +import { ok } from "assert"; const ignoreGlobals = new Set([ "require", @@ -16,10 +25,16 @@ const ignoreGlobals = new Set([ "eval", "arguments", variableFunctionName, + ...reservedIdentifiers, ]); -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.GlobalConcealing); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.GlobalConcealing, { + changeData: { + globals: 0, + nativeFunctions: 0, + }, + }); var globalMapping = new Map(), globalFnName = me.getPlaceholder() + "_getGlobal", @@ -69,20 +84,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { var pendingReplacements = new Map(); programPath.traverse({ - "ReferencedIdentifier|BindingIdentifier"(_path) { - var identifierPath = _path as NodePath; + Identifier(identifierPath) { + if (!isVariableIdentifier(identifierPath)) return; + var identifierName = identifierPath.node.name; + if (ignoreGlobals.has(identifierName)) return; + const binding = identifierPath.scope.getBinding(identifierName); if (binding) { illegalGlobals.add(identifierName); return; } - if ( - !identifierPath.scope.hasGlobal(identifierName) || - identifierPath.scope.hasOwnBinding(identifierName) - ) { + if (!identifierPath.scope.hasGlobal(identifierName)) { return; } @@ -99,8 +114,6 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - if (ignoreGlobals.has(identifierName)) return; - if (!pendingReplacements.has(identifierName)) { pendingReplacements.set(identifierName, [identifierPath]); } else { @@ -133,9 +146,112 @@ export default ({ Plugin }: PluginArg): PluginObj => { [t.stringLiteral(mapping)] ); - paths.forEach((path) => { - path.replaceWith(t.cloneNode(callExpression)); - }); + const { nativeFunctionName } = me.globalState.internals; + + for (let path of paths) { + const replaceExpression = t.cloneNode(callExpression); + me.skip(replaceExpression); + + if ( + // Native Function will only be populated if tamper protection is enabled + nativeFunctionName && + // Avoid maximum call stack error + !path.find((p) => p.node[MULTI_TRANSFORM] || me.isSkipped(p)) + ) { + // First extract the member expression chain + let nameAndPropertyPath = [globalName]; + let cursorPath = path; + let callExpressionPath: NodePath | null = + null; + + const checkForCallExpression = () => { + if ( + cursorPath.parentPath?.isCallExpression() && + cursorPath.key === "callee" + ) { + callExpressionPath = cursorPath.parentPath; + return true; + } + }; + + if (!checkForCallExpression()) { + cursorPath = cursorPath?.parentPath; + while (cursorPath?.isMemberExpression()) { + let propertyString = getMemberExpressionPropertyAsString( + cursorPath.node + ); + if (!propertyString || typeof propertyString !== "string") { + break; + } + + nameAndPropertyPath.push(propertyString); + + if (checkForCallExpression()) break; + cursorPath = cursorPath.parentPath; + } + } + + // Eligible member-expression/identifier + if (callExpressionPath) { + // Check user's custom implementation + var shouldTransform = + me.obfuscator.shouldTransformNativeFunction( + nameAndPropertyPath + ); + if (shouldTransform) { + path.replaceWith(replaceExpression); + + // console.log("Hello World") -> + // checkNative(getGlobal("console")["log"])("Hello World") + + // Parent-most member expression must be wrapped + // This to preserve proper 'this' binding in member expression invocations + let callee = callExpressionPath.get( + "callee" + ) as NodePath; + let callArgs: t.Expression[] = [callee.node]; + + if (callee.isMemberExpression()) { + const additionalPropertyString = + getMemberExpressionPropertyAsString(callee.node); + ok( + additionalPropertyString, + "Expected additional property to be a string" + ); + callee = callee.get("object"); + callArgs = [ + callee.node, + t.stringLiteral(additionalPropertyString), + ]; + } + + // Method supports two signatures: + // checkNative(fetch)(...) + // checkNative(console, "log")(...) + + callExpressionPath + .get("callee") + .replaceWith( + me.skip( + t.callExpression( + t.identifier(nativeFunctionName), + callArgs + ) + ) + ); + + me.changeData.nativeFunctions++; + continue; + } + } + } + + me.changeData.globals++; + + // Regular replacement + // console -> getGlobal("console") + path.replaceWith(replaceExpression); + } } // No globals changed, no need to insert the getGlobal function diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index e036648..22cf81b 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -1,10 +1,14 @@ -import { NodePath, PluginObj } from "@babel/core"; +import { NodePath } from "@babel/core"; import { Order } from "../../order"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import { NodeSymbol, PREDICTABLE } from "../../constants"; import * as t from "@babel/types"; import { isStaticValue } from "../../utils/static-utils"; -import { isStrictMode, prepend } from "../../utils/ast-utils"; +import { + getPatternIdentifierNames, + isStrictMode, + prepend, +} from "../../utils/ast-utils"; import Template from "../../templates/template"; /** @@ -13,11 +17,17 @@ import Template from "../../templates/template"; * 1) Move variables to top of the current block * 2) Move variables as unused function parameters */ -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.MovedDeclarations); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.MovedDeclarations, { + changeData: { + variableDeclarations: 0, + functionParameters: 0, + }, + }); function isFunctionEligibleForParameterPacking( - functionPath: NodePath + functionPath: NodePath, + proposedParameterName: string ) { // Getter/setter functions must have zero or one formal parameter // We cannot add extra parameters to them @@ -33,6 +43,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Max 1,000 parameters if (functionPath.get("params").length > 1_000) return false; + // Check for duplicate parameter names + var bindingIdentifiers = getPatternIdentifierNames( + functionPath.get("params") + ); + + // Duplicate parameter name not allowed + if (bindingIdentifiers.has(proposedParameterName)) return false; + return true; } @@ -50,16 +68,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Must be direct child of the function if (path.parentPath !== functionPath.get("body")) return; + const functionName = path.node.id.name; + // Must be eligible for parameter packing - if (!isFunctionEligibleForParameterPacking(functionPath)) return; + if ( + !isFunctionEligibleForParameterPacking(functionPath, functionName) + ) + return; var strictMode = isStrictMode(functionPath); // Default parameters are not allowed when 'use strict' is declared if (strictMode) return; - const functionName = path.node.id.name; - var functionExpression = path.node as t.Node as t.FunctionExpression; functionExpression.type = "FunctionExpression"; functionExpression.id = null; @@ -87,6 +108,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); path.remove(); + me.changeData.functionParameters++; }, }, VariableDeclaration: { @@ -99,6 +121,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.isFunction() ) as NodePath; + const declaration = path.node.declarations[0]; + if (!t.isIdentifier(declaration.id)) return; + const varName = declaration.id.name; + var allowDefaultParamValue = true; if (functionPath && (functionPath.node as NodeSymbol)[PREDICTABLE]) { @@ -112,14 +138,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Cannot add variables after rest element // Cannot add over 1,000 parameters - if (isFunctionEligibleForParameterPacking(functionPath)) { + if (isFunctionEligibleForParameterPacking(functionPath, varName)) { insertionMethod = "functionParameter"; } } - const declaration = path.node.declarations[0]; - if (!t.isIdentifier(declaration.id)) return; - const { name } = declaration.id; const value = declaration.init || t.identifier("undefined"); @@ -184,6 +207,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { binding.identifier = identifier; } + me.changeData.functionParameters++; break; case "variableDeclaration": var block = path.findParent((path) => @@ -204,6 +228,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); } + me.changeData.variableDeclarations++; + break; } }, diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 16b4293..9d83392 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -1,222 +1,279 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { Binding, Scope } from "@babel/traverse"; -import { PluginArg } from "../plugin"; +import { NodePath } from "@babel/core"; +import { Visitor } from "@babel/traverse"; +import { PluginArg, PluginObject } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; import { - NodeSymbol, noRenameVariablePrefix, placeholderVariablePrefix, variableFunctionName, - NO_RENAME, } from "../../constants"; import { computeProbabilityMap } from "../../probability"; import { getParentFunctionOrProgram, isDefiningIdentifier, + isExportedIdentifier, + isVariableIdentifier, } from "../../utils/ast-utils"; +import { isVariableFunctionIdentifier } from "../../utils/function-utils"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.RenameVariables); - let availableNames: string[] = []; +const RENAMED = Symbol("Renamed"); - let renamedScopes = new Set(); - let renamedBindingIdentifiers = new WeakSet(); +const reusePreviousNames = true; - let changedScopes = new Map>(); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.RenameVariables, { + changeData: { + variables: 0, + }, + }); - return { - visitor: { - Program: { - enter(path) { - path.scope.crawl(); + const definedMap = new Map>(); + const referencedMap = new Map>(); + const bindingMap = new Map>>(); - availableNames = Array.from(me.obfuscator.nameGen.generatedNames); - }, - }, - CallExpression: { - exit(path: NodePath) { - if ( - path.get("callee").isIdentifier({ - name: variableFunctionName, - }) - ) { - const [arg] = path.get("arguments"); - if (arg.isIdentifier()) { - path.replaceWith(t.stringLiteral(arg.node.name)); - } - } - }, - }, + const renamedVariables = new Map>(); + me.obfuscator.globalState.renamedVariables = renamedVariables; - Scopable: { - enter(path: NodePath) { - let { scope } = path; - if (scope.path?.isClassDeclaration()) return; + const generated = Array.from(me.obfuscator.nameGen.generatedNames); - if (renamedScopes.has(scope)) { - return; - } - renamedScopes.add(scope); + const VariableAnalysisVisitor: Visitor = { + Program: { + enter(path) { + // Analyze all scopes + path.traverse({ + Identifier(path) { + if (!isVariableIdentifier(path)) return; - var names = []; + let contextPaths: NodePath[] = [ + getParentFunctionOrProgram(path), + ]; - // Collect all referenced identifiers in the current scope - const referencedIdentifiers = new Set(); + let isDefined = false; - let traversePath: NodePath = path; - if (path.parentPath?.isForStatement()) { - traversePath = path.parentPath; - } + if (path.isBindingIdentifier() && isDefiningIdentifier(path)) { + isDefined = true; + + // Function ID is defined in the parent's function declaration + if ( + path.key === "id" && + path.parentPath.isFunctionDeclaration() + ) { + contextPaths = [getParentFunctionOrProgram(path.parentPath)]; + } + } - traversePath.traverse({ - Identifier(innerPath) { - // Use Babel's built-in method to check if the identifier is a referenced variable - var type = innerPath.isReferencedIdentifier() - ? "referenced" - : (innerPath as NodePath).isBindingIdentifier() - ? "binding" - : null; - if (type) { - const binding = innerPath.scope.getBinding(innerPath.node.name); - - if (type === "binding" && isDefiningIdentifier(innerPath)) { - /** - * var a; // Program bindings = {a} - * { // Block Statement bindings = {} - * var a; - * } - * - * Add variable binding to 'a' - */ - - if ( - binding && - binding.kind === "var" && - binding.scope !== scope - ) { - } else { - return; - } + contextPaths.forEach((contextPath) => { + // console.log(contextPath.node.type, path.node.name, isDefined); + + if (isDefined) { + // Add to defined map + if (!definedMap.has(contextPath.node)) { + definedMap.set(contextPath.node, new Set()); } + definedMap.get(contextPath.node).add(path.node.name); - // If the binding exists and is not defined in the current scope, it is a reference - if (binding && binding.scope !== path.scope) { - referencedIdentifiers.add(innerPath.node.name); + if (!bindingMap.has(contextPath.node)) { + bindingMap.set(contextPath.node, new Map()); } - } - }, - }); - - // console.log(scope.path.type, "Referenced", referencedIdentifiers); - - var preprocessedMappings = new Map(); - - for (let identifierName in scope.bindings) { - const binding = scope.bindings[identifierName]; - if (binding.kind === "hoisted" || binding.kind === "var") { - // Check if already renamed - check function context - var functionContext = getParentFunctionOrProgram(binding.path); - - const alreadyRenamed = changedScopes - .get(functionContext.scope) - ?.get(identifierName); - - // console.log( - // scope.path.type, - // "Checking already renamed", - // identifierName, - // alreadyRenamed - // ); - - if (alreadyRenamed) { - const fnBinding = - functionContext.scope.getOwnBinding(alreadyRenamed); - if ( - fnBinding && - renamedBindingIdentifiers.has(fnBinding.identifier) - ) { - preprocessedMappings.set(binding, alreadyRenamed); - referencedIdentifiers.add(alreadyRenamed); + bindingMap.get(contextPath.node).set(path.node.name, path); + } else { + // Add to reference map + if (!referencedMap.has(contextPath.node)) { + referencedMap.set(contextPath.node, new Set()); } + referencedMap.get(contextPath.node).add(path.node.name); } - } + }); + }, + }); + + // + }, + }, + }; + + const VariableRenamingVisitor: Visitor = { + Identifier(identifierPath) { + if (!isVariableIdentifier(identifierPath)) return; + const node = identifierPath.node; + const identifierName = node.name; + + if (node[RENAMED]) { + return; + } + + var contextPaths: NodePath[] = identifierPath.getAncestry(); + + // A Function ID is not in the same context as it's body + if ( + identifierPath.key === "id" && + identifierPath.parentPath.isFunctionDeclaration() + ) { + contextPaths = contextPaths.filter( + (x) => x !== identifierPath.parentPath + ); + } + + var newName = null; + + for (let contextPath of contextPaths) { + const { node } = contextPath; + + const defined = definedMap.get(node); + if (defined?.has(identifierName)) { + const renamed = renamedVariables.get(node); + if (renamed?.has(identifierName)) { + newName = renamed.get(identifierName); + break; } + } + } + + if (newName && typeof newName === "string") { + // __JS_CONFUSER_VAR__ function + if (isVariableFunctionIdentifier(identifierPath)) { + identifierPath.parentPath.replaceWith(t.stringLiteral(newName)); + return; + } + + // 5. Update Identifier node's 'name' property + node.name = newName; + node[RENAMED] = true; + } + }, - var actuallyAvailableNames = availableNames.filter( - (x) => !referencedIdentifiers.has(x) && !scope.bindings[x] - ); + Scopable(scopePath: NodePath) { + // 2. Notice this is on 'onEnter' (top-down) + const isGlobal = scopePath.isProgram(); + const { node } = scopePath.scope.path; + if (renamedVariables.has(node)) return; - const isGlobal = scope.path.isProgram(); - - for (let identifierName in scope.bindings) { - // __NO_JS_CONFUSER_RENAME__ prefix should not be renamed - if (identifierName.startsWith(noRenameVariablePrefix)) continue; - - const isPlaceholder = identifierName.startsWith( - placeholderVariablePrefix - ); - - if (!isPlaceholder) { - // Global variables should be checked against user's options - if (isGlobal) { - if ( - !computeProbabilityMap( - me.options.renameGlobals, - (x) => x, - identifierName - ) - ) - continue; - } + const defined = definedMap.get(node) || new Set(); + const references = referencedMap.get(node) || new Set(); + const bindings = bindingMap.get(node); - // Allow user to disable renaming certain variables - if ( - !computeProbabilityMap( - me.options.renameVariables, - identifierName, - isGlobal - ) - ) - continue; - } + // No changes needed here + if (!defined && !renamedVariables.has(node)) { + renamedVariables.set(node, Object.create(null)); + return; + } - const binding = scope.bindings[identifierName]; - if (renamedBindingIdentifiers.has(binding.identifier)) continue; - renamedBindingIdentifiers.add(binding.identifier); + const newNames = new Map(); - if (!binding.path.node || binding.path.node[NO_RENAME]) continue; + // Names possible to be re-used here + var possible = new Set(); - let newName = - preprocessedMappings.get(binding) ?? actuallyAvailableNames.pop(); + // 3. Try to re-use names when possible + if (reusePreviousNames && generated.length && !isGlobal) { + var allReferences = new Set(); + var nope = new Set(defined); - if (!newName) { - while (!newName || scope.hasGlobal(newName)) { - newName = me.obfuscator.nameGen.generate(); - } - names.push(newName); + scopePath.traverse({ + Scopable(path) { + const { node } = path.scope.path; + + var ref = referencedMap.get(node); + if (ref) { + ref.forEach((x) => allReferences.add(x)); } - if (!changedScopes.has(scope)) { - changedScopes.set(scope, new Map()); + var def = definedMap.get(node); + if (def) { + def.forEach((x) => allReferences.add(x)); + } + }, + }); + + var passed = new Set(); + + const parentPaths = scopePath.getAncestry(); + parentPaths.forEach((p) => { + if (p === scopePath) return; + + let changes = renamedVariables.get(p.node); + if (changes) { + for (let [oldName, newName] of changes) { + if (!allReferences.has(oldName) && !references.has(oldName)) { + passed.add(newName); + } else { + nope.add(newName); + } } - changedScopes.get(scope).set(identifierName, newName); + } + }); + + nope.forEach((x) => passed.delete(x)); + + possible = passed; + } + + function shouldRename(name: string) { + // __NO_JS_CONFUSER_RENAME__ + if (name.startsWith(noRenameVariablePrefix)) return false; + + // Placeholder variables should always be renamed + if (name.startsWith(placeholderVariablePrefix)) return true; + + // Do not rename exports + if (isExportedIdentifier(bindings?.get(name))) return false; + + if (name === me.obfuscator.getStringCompressionLibraryName()) + return false; + + // Global variables are additionally checked against user option + if (isGlobal) { + if (!computeProbabilityMap(me.options.renameGlobals, name)) + return false; + } + + if (!computeProbabilityMap(me.options.renameVariables, name, isGlobal)) + return false; - // console.log(scope.path.type, "Renamed", identifierName, newName); + return true; + } - me.log("Renaming", identifierName, "to", newName); - scope.rename(identifierName, newName); + // 4. Defined names to new names + for (var name of defined) { + let newName = name; - // Extra Class Declaration scope preserve logic needed - if (binding.path.type === "ClassDeclaration") { - var newBinding = scope.bindings[newName]; - binding.path.scope.bindings[newName] = newBinding; + if (shouldRename(name)) { + me.changeData.variables++; + + // Create a new name from (1) or (2) methods + do { + if (possible.size) { + // (1) Re-use previously generated name + var first = possible.values().next().value; + possible.delete(first); + newName = first; + } else { + // (2) Create a new name with global `nameGen` + var generatedName = me.obfuscator.nameGen.generate(); + + newName = generatedName; + generated.push(generatedName); } - } + } while ( + scopePath.scope.hasGlobal(newName) || + me.obfuscator.nameGen.notSafeForReuseNames.has(newName) + ); + // Ensure global names aren't overridden + } - availableNames.push(...names); - }, - }, + newNames.set(name, newName); + } + + // console.log(node.type, newNames); + renamedVariables.set(node, newNames); + }, + }; + + return { + visitor: { + ...VariableAnalysisVisitor, + + ...VariableRenamingVisitor, }, }; }; diff --git a/src/transforms/identifier/variableConcealing.ts b/src/transforms/identifier/variableConcealing.ts deleted file mode 100644 index 90365df..0000000 --- a/src/transforms/identifier/variableConcealing.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; -import { Order } from "../../order"; -import * as t from "@babel/types"; -import { ok } from "assert"; - -/** - * Variable Concealing uses the `with` statement to obfuscate variable names. - */ -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.RenameVariables); - - return { - visitor: { - FunctionDeclaration: { - exit(path) { - var scopeName = me.getPlaceholder(); - - for (var identifierName in path.scope.bindings) { - const binding = path.scope.bindings[identifierName]; - if (binding.kind === "param") { - continue; - } - - // Replace 'var' kind to simply define a property on the scope object - if (binding.kind === "var") { - var declaration = binding.path.parentPath; - ok( - declaration.isVariableDeclaration(), - "Expected variable declaration" - ); - ok( - declaration.node.declarations.length === 1, - "Expected single declaration" - ); - - declaration.replaceWith( - t.expressionStatement( - t.assignmentExpression( - "=", - t.memberExpression( - t.identifier(scopeName), - t.identifier(identifierName) - ), - declaration.node.declarations[0].init - ) - ) - ); - } - } - - path.node.body = t.blockStatement([ - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier(scopeName), - t.objectExpression([]) - ), - ]), - t.withStatement( - t.identifier(scopeName), - t.blockStatement(path.node.body.body) - ), - ]); - }, - }, - }, - }; -}; diff --git a/src/transforms/lock/integrity.ts b/src/transforms/lock/integrity.ts index 4a3408e..41dcf2f 100644 --- a/src/transforms/lock/integrity.ts +++ b/src/transforms/lock/integrity.ts @@ -1,5 +1,4 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { getRandomInteger } from "../../utils/random-utils"; import { HashFunction } from "../../templates/integrityTemplate"; @@ -26,8 +25,12 @@ export interface NodeIntegrity { * * This transformation must run last as any changes to the code will break the hash */ -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Integrity); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Integrity, { + changeData: { + functions: 0, + }, + }); return { visitor: { @@ -53,7 +56,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) return; - const { hashFnName } = me.globalState.lock.integrity; + const { integrityHashName: hashFnName } = me.globalState.internals; + const obfuscatedHashFnName = me.obfuscator.getObfuscatedVariableName( + hashFnName, + funcDecPath.find((p) => p.isProgram()).node + ); const newFnName = newFunctionDeclaration.id.name; const binding = newFnPath.scope.getBinding(newFnName); @@ -71,11 +78,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { var hashCode = HashFunction(codeTrimmed, seed); - me.log(codeTrimmed, hashCode); + // me.log(codeTrimmed, hashCode); + me.changeData.functions++; funcDecPath.node.body = t.blockStatement( new Template(` - var hash = ${hashFnName}(${newFunctionDeclaration.id.name}, ${seed}); + var hash = ${obfuscatedHashFnName}(${newFunctionDeclaration.id.name}, ${seed}); if(hash === ${hashCode}) { {originalBody} } else { @@ -85,7 +93,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { originalBody: funcDecPath.node.body.body, countermeasures: () => me.globalState.lock.createCountermeasuresCode(), - }) + }), + // Preserve directives + funcDecPath.node.body.directives ); }, }, diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index ab62b60..fae208c 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -1,17 +1,39 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { chance, choice } from "../../utils/random-utils"; import Template from "../../templates/template"; import * as t from "@babel/types"; import { CustomLock } from "../../options"; -import { getParentFunctionOrProgram } from "../../utils/ast-utils"; +import { + getFunctionName, + getParentFunctionOrProgram, + isDefiningIdentifier, + isVariableIdentifier, + prependProgram, +} from "../../utils/ast-utils"; import { INTEGRITY, NodeIntegrity } from "./integrity"; import { HashTemplate } from "../../templates/integrityTemplate"; -import { NodeSymbol, SKIP } from "../../constants"; - -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Lock); +import { + MULTI_TRANSFORM, + NodeSymbol, + PREDICTABLE, + SKIP, + UNSAFE, +} from "../../constants"; +import { + IndexOfTemplate, + NativeFunctionTemplate, + StrictModeTemplate, +} from "../../templates/tamperProtectionTemplates"; +import { computeProbabilityMap } from "../../probability"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Lock, { + changeData: { + locksInserted: 0, + }, + }); if (me.options.lock.startDate instanceof Date) { me.options.lock.customLocks.push({ @@ -109,9 +131,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (me.options.lock.countermeasures) { invokeCountermeasuresFnName = me.getPlaceholder("invokeCountermeasures"); + + me.globalState.internals.invokeCountermeasuresFnName = + invokeCountermeasuresFnName; } - me.globalState.lock.createCountermeasuresCode = () => { + var createCountermeasuresCode = () => { if (invokeCountermeasuresFnName) { return new Template(`${invokeCountermeasuresFnName}()`).compile(); } @@ -122,6 +147,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { return new Template(`while(true){}`).compile(); }; + me.globalState.lock.createCountermeasuresCode = createCountermeasuresCode; function applyLockToBlock(path: NodePath, customLock: CustomLock) { let times = timesMap.get(customLock); @@ -159,38 +185,48 @@ export default ({ Plugin }: PluginArg): PluginObj => { const template = typeof lockCode === "string" ? new Template(lockCode) : lockCode; const lockNodes = template.compile({ - countermeasures: () => me.globalState.lock.createCountermeasuresCode(), + countermeasures: () => createCountermeasuresCode(), }); var p = path.unshiftContainer("body", lockNodes); p.forEach((p) => p.skip()); + + me.changeData.locksInserted++; } return { visitor: { - Identifier: { - enter(path) { - if (path.node.name !== me.options.lock.countermeasures) { - return; - } + BindingIdentifier(path) { + if (path.node.name !== me.options.lock.countermeasures) { + return; + } - if (countermeasuresNode) { - // Disallow multiple countermeasures functions - me.error( - "Countermeasures function was already defined, it must have a unique name from the rest of your code" - ); - } + // Exclude labels + if (!isVariableIdentifier(path)) return; - if ( - path.scope.getBinding(path.node.name).scope !== - path.scope.getProgramParent() - ) { - me.error( - "Countermeasures function must be defined at the global level" - ); - } + if (!isDefiningIdentifier(path)) { + // Reassignments are not allowed - countermeasuresNode = path; - }, + me.error("Countermeasures function cannot be reassigned"); + } + + if (countermeasuresNode) { + // Disallow multiple countermeasures functions + + me.error( + "Countermeasures function was already defined, it must have a unique name from the rest of your code" + ); + } + + if ( + path.scope.getBinding(path.node.name).scope !== + path.scope.getProgramParent() + ) { + me.error( + "Countermeasures function must be defined at the global level" + ); + } + + countermeasuresNode = path; }, Block: { @@ -204,8 +240,47 @@ export default ({ Plugin }: PluginArg): PluginObj => { Program: { exit(path) { - // Insert invokeCountermeasures function + // Insert nativeFunctionCheck + if (me.options.lock.tamperProtection) { + // Disallow strict mode + // Tamper Protection uses non-strict mode features: + // - eval() with local scope assignments + const directives = path.get("directives"); + for (var directive of directives) { + if (directive.node.value.value === "use strict") { + me.error( + "Tamper Protection cannot be applied to code in strict mode. Disable strict mode by removing the 'use strict' directive, or disable Tamper Protection." + ); + } + } + + var nativeFunctionName = + me.getPlaceholder() + "_nativeFunctionCheck"; + + me.obfuscator.globalState.internals.nativeFunctionName = + nativeFunctionName; + + // Ensure program is not in strict mode + // Tamper Protection forces non-strict mode + prependProgram( + path, + StrictModeTemplate.compile({ + nativeFunctionName, + countermeasures: createCountermeasuresCode(), + }) + ); + + const nativeFunctionDeclaration = NativeFunctionTemplate.single({ + nativeFunctionName, + countermeasures: createCountermeasuresCode(), + IndexOfTemplate: IndexOfTemplate, + }); + + // Checks function's toString() value for [native code] signature + prependProgram(path, nativeFunctionDeclaration); + } + // Insert invokeCountermeasures function if (invokeCountermeasuresFnName) { if (!countermeasuresNode) { me.error( @@ -215,7 +290,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); } - var hasInvoked = me.getPlaceholder(); + var hasInvoked = me.getPlaceholder("hasInvoked"); var statements = new Template(` var ${hasInvoked} = false; function ${invokeCountermeasuresFnName}(){ @@ -223,12 +298,11 @@ export default ({ Plugin }: PluginArg): PluginObj => { ${hasInvoked} = true; ${me.options.lock.countermeasures}(); } - `).compile(); + `) + .addSymbols(MULTI_TRANSFORM) + .compile(); - path.unshiftContainer("body", statements).forEach((p) => { - path.scope.registerDeclaration(p); - p.skip(); - }); + prependProgram(path, statements).forEach((p) => p.skip()); } if (me.options.lock.integrity) { @@ -236,26 +310,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { const imulFnName = me.getPlaceholder() + "_imul"; const { sensitivityRegex } = me.globalState.lock.integrity; - me.globalState.lock.integrity.hashFnName = hashFnName; - - path - .unshiftContainer( - "body", - HashTemplate.compile({ - imul: imulFnName, - name: hashFnName, - hashingUtilFnName: me.getPlaceholder(), - sensitivityRegex: () => - t.newExpression(t.identifier("RegExp"), [ - t.stringLiteral(sensitivityRegex.source), - t.stringLiteral(sensitivityRegex.flags), - ]), - }) - ) - .forEach((path) => { - (path.node as NodeSymbol)[SKIP] = true; - path.scope.registerDeclaration(path); - }); + me.globalState.internals.integrityHashName = hashFnName; + + const hashCode = HashTemplate.compile({ + imul: imulFnName, + name: hashFnName, + hashingUtilFnName: me.getPlaceholder(), + sensitivityRegex: () => + t.newExpression(t.identifier("RegExp"), [ + t.stringLiteral(sensitivityRegex.source), + t.stringLiteral(sensitivityRegex.flags), + ]), + }); + + prependProgram(path, hashCode); } }, }, @@ -265,6 +333,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { // The extracted function is hashed in the 'integrity' plugin FunctionDeclaration: { exit(funcDecPath) { + if (!me.options.lock.integrity) return; + // Mark functions for integrity // Don't apply to async or generator functions if (funcDecPath.node.async || funcDecPath.node.generator) return; @@ -275,12 +345,33 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Only top-level functions if (!program.isProgram()) return; + // Check user's custom implementation + const functionName = getFunctionName(funcDecPath); + // Don't apply to the countermeasures function (Intended) + if ( + me.options.lock.countermeasures && + functionName === me.options.lock.countermeasures + ) + return; + // Don't apply to invokeCountermeasures function (Intended) + if (me.obfuscator.isInternalVariable(functionName)) return; + + if (!computeProbabilityMap(me.options.lock.integrity, functionName)) + return; + var newFnName = me.getPlaceholder(); var newFunctionDeclaration = t.functionDeclaration( t.identifier(newFnName), funcDecPath.node.params, funcDecPath.node.body ); + + // Clone semantic symbols like (UNSAFE, PREDICTABLE, MULTI_TRANSFORM, etc) + const source = funcDecPath.node; + Object.getOwnPropertySymbols(source).forEach((symbol) => { + newFunctionDeclaration[symbol] = source[symbol]; + }); + (newFunctionDeclaration as NodeSymbol)[SKIP] = true; var [newFnPath] = program.unshiftContainer( @@ -292,13 +383,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { // In the case Integrity cannot transform the function, the original behavior is preserved funcDecPath.node.body = t.blockStatement( new Template(` - return ${newFnName}(...arguments) - `).compile() + return ${newFnName}(...arguments); + `).compile(), + funcDecPath.node.body.directives ); // Parameters no longer needed, using 'arguments' instead funcDecPath.node.params = []; + // Mark the function as unsafe - use of 'arguments' is unsafe + (funcDecPath.node as NodeSymbol)[UNSAFE] = true; + + // Params changed - function is no longer predictable + (funcDecPath.node as NodeSymbol)[PREDICTABLE] = false; + // Mark the function for integrity (funcDecPath.node as NodeIntegrity)[INTEGRITY] = { fnPath: newFnPath, diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index b67e995..5c0eec9 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -1,28 +1,19 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; import { ensureComputedExpression, getParentFunctionOrProgram, + isUndefined, } from "../utils/ast-utils"; import { Binding, Scope } from "@babel/traverse"; -import { NodeSymbol, placeholderVariablePrefix, UNSAFE } from "../constants"; - -function isUndefined(path) { - if (path.isIdentifier() && path.node.name === "undefined") { - return true; - } - if ( - path.isUnaryExpression() && - path.node.operator === "void" && - path.node.argument.type === "NumericLiteral" && - path.node.argument.value === 0 - ) { - return true; - } - return false; -} +import { + NodeSymbol, + placeholderVariablePrefix, + SKIP, + UNSAFE, +} from "../constants"; const identifierMap = new Map t.Expression>(); identifierMap.set("undefined", () => @@ -41,7 +32,7 @@ identifierMap.set("Infinity", () => * - Shorten literals: True to !0, False to !1, Infinity to 1/0, Undefined to void 0 * - Remove unused variables, functions */ -export default ({ Plugin }: PluginArg): PluginObj => { +export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Minify); return { visitor: { @@ -92,8 +83,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { function addBindingsToScope(scope: Scope) { for (var name in bindings) { const binding = bindings[name]; - binding.path = newBindingIdentifierPaths[name]; - scope.bindings[name] = binding; + if (binding) { + binding.path = newBindingIdentifierPaths[name]; + scope.bindings[name] = binding; + } } } @@ -121,6 +114,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { var argument = path.get("argument"); if (argument.isNumericLiteral()) return; const value = argument.evaluateTruthy(); + const parent = getParentFunctionOrProgram(path); + if (parent && (parent.node as NodeSymbol)[UNSAFE]) return; + if (value === undefined) return; path.replaceWith( @@ -185,6 +181,12 @@ export default ({ Plugin }: PluginArg): PluginObj => { ExpressionStatement: { exit(path) { if (path.get("expression").isIdentifier()) { + // Preserve last expression of program for RGF + if ( + path.parentPath?.isProgram() && + path.parentPath?.get("body").at(-1) === path + ) + return; path.remove(); } }, @@ -344,25 +346,33 @@ export default ({ Plugin }: PluginArg): PluginObj => { return true; }; - if ( - !alternate.node && - consequent.isBlock() && - consequent.node.body.length === 1 && - isMoveable(consequent.node.body[0]) - ) { - consequent.replaceWith(consequent.node.body[0]); + let testValue = path.get("test").evaluateTruthy(); + + const parent = getParentFunctionOrProgram(path); + if (parent && (parent.node as NodeSymbol)[UNSAFE]) { + testValue = undefined; } - if ( - alternate.node && - alternate.isBlock() && - alternate.node.body.length === 1 && - isMoveable(alternate.node.body[0]) - ) { - alternate.replaceWith(alternate.node.body[0]); + if (typeof testValue !== "undefined") { + if ( + !alternate.node && + consequent.isBlock() && + consequent.node.body.length === 1 && + isMoveable(consequent.node.body[0]) + ) { + consequent.replaceWith(consequent.node.body[0]); + } + + if ( + alternate.node && + alternate.isBlock() && + alternate.node.body.length === 1 && + isMoveable(alternate.node.body[0]) + ) { + alternate.replaceWith(alternate.node.body[0]); + } } - const testValue = path.get("test").evaluateTruthy(); if (testValue === false) { // if(false){} -> () if (!alternate.node) { diff --git a/src/transforms/opaquePredicates.ts b/src/transforms/opaquePredicates.ts index b654d3e..3103207 100644 --- a/src/transforms/opaquePredicates.ts +++ b/src/transforms/opaquePredicates.ts @@ -1,11 +1,112 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { NameGen } from "../utils/NameGen"; +import { getBlock, prependProgram } from "../utils/ast-utils"; +import { + chance, + choice, + getRandomString, + shuffle, +} from "../utils/random-utils"; +import { computeProbabilityMap } from "../probability"; +import { NodeSymbol, PREDICTABLE } from "../constants"; +import ControlObject from "../utils/ControlObject"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.OpaquePredicates); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.OpaquePredicates, { + changeData: { + opaquePredicates: 0, + }, + }); + + function createTruePredicate(path: NodePath) { + const controlObject = me.getControlObject(getBlock(path)); + + var trueValue = controlObject.createTruePredicate(); + + return trueValue; + } + + let active = true; + let transformCount = 0; + function shouldTransform(path: NodePath) { + if (!active) return false; + if (path.find((p) => me.isSkipped(p))) return false; + + if (!computeProbabilityMap(me.options.opaquePredicates)) return false; + + transformCount++; + + const depth = path.getAncestry().length; + + return chance(1000 - transformCount - depth * 100); + } + + function wrapWithPredicate(path: NodePath) { + let newExpression = t.logicalExpression( + "&&", + createTruePredicate(path), + path.node as t.Expression + ); + + me.changeData.opaquePredicates++; + + path.replaceWith(me.skip(newExpression)); + } return { - visitor: {}, + visitor: { + // if (test) -> if (PREDICATE() && test) {} + IfStatement: { + exit(path) { + if (!shouldTransform(path)) return; + wrapWithPredicate(path.get("test")); + }, + }, + + // test ? a : b -> PREDICATE() && test ? a : b + ConditionalExpression: { + exit(path) { + if (!shouldTransform(path)) return; + + wrapWithPredicate(path.get("test")); + }, + }, + + // case test: -> case PREDICATE() && test: + SwitchCase: { + exit(path) { + if (!path.node.test) return; + if (!shouldTransform(path)) return; + + wrapWithPredicate(path.get("test")); + }, + }, + + // return test -> if (predicate()) { return test } else { return fake } + ReturnStatement: { + exit(path) { + if (!path.node.argument) return; + if (!shouldTransform(path)) return; + + me.changeData.opaquePredicates++; + + path.replaceWith( + t.ifStatement( + createTruePredicate(path), + t.blockStatement([t.returnStatement(path.node.argument)]), + t.blockStatement([ + t.returnStatement(t.stringLiteral(getRandomString(6))), + ]) + ) + ); + + me.skip(path); + }, + }, + }, }; }; diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index 1b6c31b..824dc43 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -1,104 +1,129 @@ import * as t from "@babel/types"; import Obfuscator from "../obfuscator"; import Template from "../templates/template"; -import traverse, { NodePath } from "@babel/traverse"; import { isDefiningIdentifier, - isReservedIdentifier, + isModifiedIdentifier, isVariableIdentifier, } from "../utils/ast-utils"; - -export default function pack(ast: t.File, obfuscator: Obfuscator) { - const objectName = obfuscator.nameGen.generate(); +import { + GEN_NODE, + NodeSymbol, + reservedIdentifiers, + variableFunctionName, + WITH_STATEMENT, +} from "../constants"; +import { PluginArg, PluginObject } from "./plugin"; +import { Order } from "../order"; + +export default function pack({ Plugin }: PluginArg): PluginObject { + const me = Plugin(Order.Pack, { + changeData: { + globals: 0, + }, + }); + const objectName = me.obfuscator.nameGen.generate(); const mappings = new Map(); + const setterPropsNeeded = new Set(); const typeofMappings = new Map(); - const objectProperties: t.ObjectMethod[] = []; - const prependNodes: t.Statement[] = []; - for (const statement of ast.program.body) { - if (t.isImportDeclaration(statement)) { - prependNodes.push(statement); - } - } + return { + // Transform identifiers, preserve import statements + visitor: { + ImportDeclaration(path) { + prependNodes.push(path.node); + path.remove(); - traverse(ast, { - ImportDeclaration(path) { - path.remove(); - - // Ensure bindings are removed -> variable becomes a global -> added to mappings object - path.scope.crawl(); - }, - Program(path) { - path.scope.crawl(); - }, + // Ensure bindings are removed -> variable becomes a global -> added to mappings object + path.scope.crawl(); + }, + Program(path) { + path.scope.crawl(); + }, - Identifier: { - exit(path) { - if (!isVariableIdentifier(path)) return; + Identifier: { + exit(path) { + if (!isVariableIdentifier(path)) return; - if (isDefiningIdentifier(path)) return; + if (isDefiningIdentifier(path)) return; + if ((path.node as NodeSymbol)[GEN_NODE]) return; + if ((path.node as NodeSymbol)[WITH_STATEMENT]) return; - const identifierName = path.node.name; - if (obfuscator.options.globalVariables.has(identifierName)) return; - if (isReservedIdentifier(path.node)) return; + const identifierName = path.node.name; + if (reservedIdentifiers.has(identifierName)) return; + if (me.obfuscator.options.globalVariables.has(identifierName)) return; + if (identifierName === variableFunctionName) return; - if (!path.scope.hasGlobal(identifierName)) return; + if (!path.scope.hasGlobal(identifierName)) return; - if ( - path.key === "argument" && - path.parentPath.isUnaryExpression({ operator: "typeof" }) - ) { - const unaryExpression = path.parentPath; + if ( + path.key === "argument" && + path.parentPath.isUnaryExpression({ operator: "typeof" }) + ) { + const unaryExpression = path.parentPath; - let propertyName = typeofMappings.get(identifierName); - if (!propertyName) { - propertyName = obfuscator.nameGen.generate(); - typeofMappings.set(identifierName, propertyName); + let propertyName = typeofMappings.get(identifierName); + if (!propertyName) { + propertyName = me.obfuscator.nameGen.generate(); + typeofMappings.set(identifierName, propertyName); + } - // get identifier() { return typeof identifier; } - objectProperties.push( - t.objectMethod( - "get", + unaryExpression.replaceWith( + t.memberExpression( + t.identifier(objectName), t.stringLiteral(propertyName), - [], - t.blockStatement([ - t.returnStatement( - t.unaryExpression("typeof", t.identifier(identifierName)) - ), - ]) + true ) ); + return; + } + + let propertyName = mappings.get(identifierName); + if (!propertyName) { + propertyName = me.obfuscator.nameGen.generate(); + mappings.set(identifierName, propertyName); + } + + // Only add setter if the identifier is modified + if (isModifiedIdentifier(path)) { + setterPropsNeeded.add(identifierName); } - unaryExpression.replaceWith( + path.replaceWith( t.memberExpression( t.identifier(objectName), t.stringLiteral(propertyName), true ) ); - return; - } + }, + }, + }, - let propertyName = mappings.get(identifierName); - if (!propertyName) { - propertyName = obfuscator.nameGen.generate(); - mappings.set(identifierName, propertyName); + // Final AST handler + // Very last step in the obfuscation process + finalASTHandler(ast) { + // Create object expression + // Very similar to flatten, maybe refactor to use the same code + const objectProperties: t.ObjectMethod[] = []; - // get identifier() { return identifier; } - objectProperties.push( - t.objectMethod( - "get", - t.stringLiteral(propertyName), - [], - t.blockStatement([ - t.returnStatement(t.identifier(identifierName)), - ]) - ) - ); + me.changeData.globals = mappings.size; + + for (const [identifierName, propertyName] of mappings) { + // get identifier() { return identifier; } + objectProperties.push( + t.objectMethod( + "get", + t.stringLiteral(propertyName), + [], + t.blockStatement([t.returnStatement(t.identifier(identifierName))]) + ) + ); + // Only add setter if the identifier is modified + if (setterPropsNeeded.has(identifierName)) { // set identifier(value) { return identifier = value; } objectProperties.push( t.objectMethod( @@ -117,34 +142,54 @@ export default function pack(ast: t.File, obfuscator: Obfuscator) { ) ); } - - path.replaceWith( - t.memberExpression( - t.identifier(objectName), + } + + // Add typeof mappings + for (const [identifierName, propertyName] of typeofMappings) { + // get typeof identifier() { return typeof identifier; } + objectProperties.push( + t.objectMethod( + "get", t.stringLiteral(propertyName), - true + [], + t.blockStatement([ + t.returnStatement( + t.unaryExpression("typeof", t.identifier(identifierName)) + ), + ]) ) ); - }, - }, - }); + } - const objectExpression = t.objectExpression(objectProperties); + const objectExpression = t.objectExpression(objectProperties); - const outputCode = Obfuscator.generateCode(ast, { - ...obfuscator.options, - compact: true, - }); + // Convert last expression to return statement + // This preserves the last expression in the packed code + var lastStatement = ast.program.body.at(-1); + if (lastStatement && t.isExpressionStatement(lastStatement)) { + Object.assign( + lastStatement, - var newAST = new Template(` + t.returnStatement(lastStatement.expression) + ); + } + + const outputCode = Obfuscator.generateCode(ast, { + ...me.obfuscator.options, + compact: true, + }); + + var newAST = new Template(` {prependNodes} Function({objectName}, {outputCode})({objectExpression}); `).file({ - objectName: () => t.stringLiteral(objectName), - outputCode: () => t.stringLiteral(outputCode), - objectExpression: objectExpression, - prependNodes: prependNodes, - }); + objectName: () => t.stringLiteral(objectName), + outputCode: () => t.stringLiteral(outputCode), + objectExpression: objectExpression, + prependNodes: prependNodes, + }); - return newAST; + return newAST; + }, + }; } diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 5b4b9de..fcea831 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -1,33 +1,39 @@ -import { NodePath, PluginObj } from "@babel/core"; +import { NodePath, PluginObj, Visitor } from "@babel/core"; import Obfuscator from "../obfuscator"; import { chance, choice, getRandomString } from "../utils/random-utils"; import { Order } from "../order"; import * as t from "@babel/types"; -import { - FN_LENGTH, - NodeSymbol, - SKIP, - CONTROL_OBJECTS, - NO_RENAME, -} from "../constants"; +import { FN_LENGTH, NodeSymbol, SKIP, CONTROL_OBJECTS } from "../constants"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { prepend, prependProgram } from "../utils/ast-utils"; import ControlObject from "../utils/ControlObject"; import { ok } from "assert"; import { numericLiteral } from "../utils/node"; -export type PluginFunction = (pluginArg: PluginArg) => PluginObj; +export interface PluginObject { + visitor?: Visitor; + finalASTHandler?: (ast: t.File) => t.File; + + post?: () => void; +} export type PluginArg = { - Plugin: (order: Order) => PluginInstance; + Plugin: = {}>( + order: Order, + merge?: T + ) => PluginInstance & T; }; +export type PluginFunction = (pluginArg: PluginArg) => PluginObject; + export class PluginInstance { constructor( public pluginOptions: { name?: string; order?: number }, public obfuscator: Obfuscator ) {} + public changeData: { [key: string]: number } = {}; + get name() { return this.pluginOptions.name || "unnamed"; } @@ -112,17 +118,17 @@ export class PluginInstance { return "__p_" + getRandomString(4) + (suffix ? "_" + suffix : ""); } - generateRandomIdentifier() { - return "_" + getRandomString(6); - } - log(...messages: any[]) { if (this.options.verbose) { console.log(`[${this.name}]`, ...messages); } } - getControlObject(blockPath: NodePath) { + hasControlObject(blockPath: NodePath) { + return (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]?.length > 0; + } + + getControlObject(blockPath: NodePath, createMultiple = true) { ok(blockPath.isBlock()); var controlObjects = (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]; @@ -132,7 +138,10 @@ export class PluginInstance { if ( controlObjects.length === 0 || - chance(100 - controlObjects.length * 10) + (createMultiple && + chance( + controlObjects[0].propertyNames.size - 15 * controlObjects.length + )) ) { var newControlObject = new ControlObject(this, blockPath); diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 0f21871..771c05f 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -1,6 +1,5 @@ -import { PluginObj } from "@babel/core"; import { NodePath } from "@babel/traverse"; -import { PluginArg } from "./plugin"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; import path from "path"; @@ -17,7 +16,22 @@ import { } from "../utils/ast-utils"; import { isVariableFunctionIdentifier } from "../utils/function-utils"; -export default ({ Plugin }: PluginArg): PluginObj => { +/** + * Preparation arranges the user's code into an AST the obfuscator can easily transform. + * + * ExplicitIdentifiers + * - `object.IDENTIFIER` -> `object['IDENTIFIER']` // Now String Concealing can apply on it + * - `{ IDENTIFIER: ... }` -> `{ "IDENTIFIER": ... }` + * + * ExplicitDeclarations + * - `var a,b,c` -> `var a; var b; var c;` // Now Stack can apply on it + * + * Block + * - `x => x * 2` -> `x => { return x * 2 }` // Change into Block Statements + * - `if(true) return` -> `if (true) { return }` + * - `while(a) a--;` -> `while(a) { a-- }` + */ +export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Preparation); const markFunctionUnsafe = (path: NodePath) => { @@ -106,7 +120,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { // When Rename Variables is disabled, __JS_CONFUSER_VAR__ must still be removed if ( - !me.obfuscator.getPlugin(Order.RenameVariables) && + !me.obfuscator.hasPlugin(Order.RenameVariables) && isVariableFunctionIdentifier(path) ) { ok( @@ -221,8 +235,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { path .replaceWithMultiple( path.node.declarations.map((declaration, i) => { - var names = getPatternIdentifierNames( - path.get("declarations")[i] + var names = Array.from( + getPatternIdentifierNames(path.get("declarations")[i]) ); names.forEach((name) => { path.scope.removeBinding(name); diff --git a/src/transforms/renameLabels.ts b/src/transforms/renameLabels.ts index 1599b23..4bec38d 100644 --- a/src/transforms/renameLabels.ts +++ b/src/transforms/renameLabels.ts @@ -1,6 +1,6 @@ import * as t from "@babel/types"; -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { NameGen } from "../utils/NameGen"; import { ok } from "assert"; @@ -20,8 +20,13 @@ interface NodeLabel { [LABEL]?: LabelInterface; } -export default function ({ Plugin }: PluginArg): PluginObj { - const me = Plugin(Order.RenameLabels); +export default function ({ Plugin }: PluginArg): PluginObject { + const me = Plugin(Order.RenameLabels, { + changeData: { + labelsRenamed: 0, + labelsRemoved: 0, + }, + }); return { visitor: { @@ -130,8 +135,10 @@ export default function ({ Plugin }: PluginArg): PluginObj { newName = nameGen.generate(); } labelInterface.renamed = newName; + me.changeData.labelsRenamed++; } else { labelInterface.removed = true; + me.changeData.labelsRemoved++; } } diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 16d41f8..0d04003 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -1,13 +1,28 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import * as t from "@babel/types"; import Obfuscator from "../obfuscator"; import { computeProbabilityMap } from "../probability"; -import { getFunctionName, prepend } from "../utils/ast-utils"; -import { NodeSymbol, SKIP, UNSAFE } from "../constants"; +import { + append, + getFunctionName, + isDefiningIdentifier, + isStrictMode, + isVariableIdentifier, + prepend, +} from "../utils/ast-utils"; +import { + NodeSymbol, + PREDICTABLE, + reservedIdentifiers, + SKIP, + UNSAFE, +} from "../constants"; import { computeFunctionLength } from "../utils/function-utils"; import { numericLiteral } from "../utils/node"; +import Template from "../templates/template"; +import { createEvalIntegrityTemplate } from "../templates/tamperProtectionTemplates"; /** * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. @@ -17,13 +32,19 @@ import { numericLiteral } from "../utils/node"; * 1. Does not apply to async or generator functions * 2. Does not apply to functions that reference outside variables */ -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.RGF); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.RGF, { + changeData: { + functions: 0, + }, + }); const rgfArrayName = me.getPlaceholder() + "_rgf"; const rgfEvalName = me.getPlaceholder() + "_rgf_eval"; const rgfArrayExpression = t.arrayExpression([]); + let active = true; + return { visitor: { Program: { @@ -31,10 +52,10 @@ export default ({ Plugin }: PluginArg): PluginObj => { path.scope.crawl(); }, exit(path) { + active = false; if (rgfArrayExpression.elements.length === 0) return; // Insert the RGF array at the top of the program - prepend( path, t.variableDeclaration("var", [ @@ -45,19 +66,38 @@ export default ({ Plugin }: PluginArg): PluginObj => { ]) ); + var rgfEvalIntegrity = me.getPlaceholder() + "_rgf_eval_integrity"; + prepend( path, - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier(rgfEvalName), - t.identifier("eval") - ), - ]) + new Template(` + {EvalIntegrity} + var ${rgfEvalIntegrity} = {EvalIntegrityName}(); + `).compile({ + EvalIntegrity: createEvalIntegrityTemplate(me, path), + EvalIntegrityName: me.getPlaceholder(), + }) + ); + + append( + path, + new Template( + ` + function ${rgfEvalName}(code) { + if (${rgfEvalIntegrity}) { + return eval(code); + } + } + ` + ) + .addSymbols(UNSAFE) + .single() ); }, }, "FunctionDeclaration|FunctionExpression": { exit(_path) { + if (!active) return; const path = _path as NodePath< t.FunctionDeclaration | t.FunctionExpression >; @@ -69,34 +109,46 @@ export default ({ Plugin }: PluginArg): PluginObj => { const name = getFunctionName(path); if (name === me.options.lock?.countermeasures) return; + if (me.obfuscator.isInternalVariable(name)) return; + me.log(name); - if (!computeProbabilityMap(me.options.rgf, name)) return; + if ( + !computeProbabilityMap( + me.options.rgf, + name, + path.getFunctionParent() === null + ) + ) + return; // Skip functions with references to outside variables // Check the scope to see if this function relies on any variables defined outside the function var identifierPreventingTransform: string; path.traverse({ - ReferencedIdentifier(refPath) { - const { name } = refPath.node; + Identifier(idPath) { + if (!isVariableIdentifier(idPath)) return; + if (idPath.isBindingIdentifier() && isDefiningIdentifier(idPath)) + return; + + const { name } = idPath.node; // RGF array name is allowed, it is not considered an outside reference if (name === rgfArrayName) return; + if (reservedIdentifiers.has(name)) return; + if (me.options.globalVariables.has(name)) return; - const binding = refPath.scope.getBinding(name); + const binding = idPath.scope.getBinding(name); if (!binding) { - if (me.options.globalVariables.has(name)) return; - if (name === "arguments") return; - identifierPreventingTransform = name; - refPath.stop(); + idPath.stop(); return; } // If the binding is not in the current scope, it is an outside reference if (binding.scope !== path.scope) { identifierPreventingTransform = name; - refPath.stop(); + idPath.stop(); } }, }); @@ -119,7 +171,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { (lastNode as NodeSymbol)[SKIP] = true; // Transform the function - const evalTree: t.Program = t.program([ + const evalProgram: t.Program = t.program([ t.functionDeclaration( t.identifier(embeddedName), [], @@ -151,9 +203,19 @@ export default ({ Plugin }: PluginArg): PluginObj => { ), lastNode, ]); - const evalFile = t.file(evalTree); - var newObfuscator = new Obfuscator(me.options); + const strictModeEnforcingBlock = path.find((p) => isStrictMode(p)); + if (strictModeEnforcingBlock) { + // Preserve 'use strict' directive + // This is necessary to enure subsequent transforms (Control Flow Flattening) are aware of the strict mode directive + evalProgram.directives.push( + t.directive(t.directiveLiteral("use strict")) + ); + } + + const evalFile = t.file(evalProgram); + + var newObfuscator = new Obfuscator(me.options, me.obfuscator); var hasRan = new Set( me.obfuscator.plugins @@ -189,6 +251,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { // Function is now unsafe (path.node as NodeSymbol)[UNSAFE] = true; + // Params changed and using 'arguments' + (path.node as NodeSymbol)[PREDICTABLE] = false; me.skip(path); // Update body to point to new function @@ -220,6 +284,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); me.setFunctionLength(path, originalLength); + + me.changeData.functions++; }, }, }, diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index dd34aad..fa9657b 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -1,5 +1,5 @@ -import { NodePath, PluginObj } from "@babel/core"; -import { PluginArg } from "./plugin"; +import { NodePath } from "@babel/core"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { computeProbabilityMap } from "../probability"; import { getRandomInteger } from "../utils/random-utils"; @@ -9,8 +9,12 @@ import { isStaticValue } from "../utils/static-utils"; import { NodeSymbol, PREDICTABLE } from "../constants"; import { numericLiteral } from "../utils/node"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.Shuffle); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Shuffle, { + changeData: { + arrays: 0, + }, + }); return { visitor: { @@ -19,9 +23,9 @@ export default ({ Plugin }: PluginArg): PluginObj => { if (path.node.elements.length <= 3) { return; } - var illegalElement = path.node.elements.find((element) => { - !isStaticValue(element); - }); + var illegalElement = path.node.elements.find( + (element) => !isStaticValue(element) + ); if (illegalElement) return; @@ -67,6 +71,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { ); path.skip(); + + me.changeData.arrays++; }, }, }, diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index 2deb5c9..d9f3cd9 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -1,5 +1,4 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; @@ -14,10 +13,15 @@ import { } from "../../templates/stringCompressionTemplate"; import Obfuscator from "../../obfuscator"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; +import { NO_RENAME } from "../../constants"; const pako = require("pako"); -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.StringCompression); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.StringCompression, { + changeData: { + strings: 0, + }, + }); const stringDelimiter = "|"; @@ -56,6 +60,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { stringMap.set(originalValue, index); } + me.changeData.strings++; + ensureComputedExpression(path); path.replaceWith( @@ -79,7 +85,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { var compressedBase64 = Buffer.from(compressedBuffer).toString("base64"); - const StringToBuffer = me.getPlaceholder(); + let pakoName = me.obfuscator.getStringCompressionLibraryName(); + let insertStringCompressionLibrary = !me.obfuscator.parentObfuscator; prependProgram( programPath, @@ -91,13 +98,20 @@ export default ({ Plugin }: PluginArg): PluginObj => { stringValue: () => t.stringLiteral(compressedBase64), GetGlobalTemplate: createGetGlobalTemplate(me, programPath), getGlobalFnName: me.getPlaceholder(), + pakoName: pakoName, }) ); - prependProgram( - programPath, - Obfuscator.parseCode(PakoInflateMin).program.body - ); + if (insertStringCompressionLibrary) { + // RGF function should also clone the entire decompression function + prependProgram( + programPath, + Obfuscator.parseCode(PakoInflateMin.replace(/{pako}/g, pakoName)) + .program.body + )[0] + .get("declarations")[0] + .get("id").node[NO_RENAME] = true; + } }, }, }, diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index e383abb..d994535 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -1,7 +1,7 @@ import * as t from "@babel/types"; -import { NodePath, PluginObj } from "@babel/core"; +import { NodePath } from "@babel/core"; import Template from "../../templates/template"; -import { PluginArg } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; import { ok } from "assert"; @@ -10,6 +10,8 @@ import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; import { ensureComputedExpression, isModuleImport, + prepend, + prependProgram, } from "../../utils/ast-utils"; import { chance, @@ -32,8 +34,12 @@ interface NodeStringConcealing { [STRING_CONCEALING]?: StringConcealingInterface; } -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.StringConcealing); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.StringConcealing, { + changeData: { + strings: 0, + }, + }); const blocks: NodePath[] = []; const stringMap = new Map(); @@ -212,6 +218,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { stringMap.set(encodedValue, index); } + me.changeData.strings++; + // Ensure the string is computed so we can replace it with complex call expression ensureComputedExpression(path); @@ -232,21 +240,17 @@ export default ({ Plugin }: PluginArg): PluginObj => { const bufferToStringName = me.getPlaceholder() + "_bufferToString"; const getGlobalFnName = me.getPlaceholder() + "_getGlobal"; - const bufferToString = BufferToStringTemplate.compile({ + const bufferToStringCode = BufferToStringTemplate.compile({ GetGlobalTemplate: createGetGlobalTemplate(me, programPath), getGlobalFnName: getGlobalFnName, - name: bufferToStringName, + BufferToString: bufferToStringName, }); - programPath - .unshiftContainer("body", bufferToString) - .forEach((path) => { - programPath.scope.registerDeclaration(path); - }); + prependProgram(programPath, bufferToStringCode); // Create the string array - var stringArrayPath = programPath.unshiftContainer( - "body", + prependProgram( + programPath, t.variableDeclaration("var", [ t.variableDeclarator( t.identifier(stringArrayName), @@ -255,15 +259,14 @@ export default ({ Plugin }: PluginArg): PluginObj => { ) ), ]) - )[0]; - programPath.scope.registerDeclaration(stringArrayPath); + ); for (var block of blocks) { const { encodingImplementation, fnName } = ( block.node as NodeStringConcealing )[STRING_CONCEALING] as StringConcealingInterface; - const decodeFnName = fnName + "_d"; + const decodeFnName = fnName + "_decode"; ok(encodingImplementation.code instanceof Template); @@ -280,14 +283,7 @@ export default ({ Plugin }: PluginArg): PluginObj => { } `).single(); - block - .unshiftContainer("body", [ - ...decoder, - retrieveFunctionDeclaration, - ]) - .forEach((path) => { - block.scope.registerDeclaration(path); - }); + prepend(block, [...decoder, retrieveFunctionDeclaration]); } }, }, diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 3e389ec..c5a7bce 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -1,9 +1,8 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg, PluginInstance } from "../plugin"; +import { PluginInstance, PluginObject } from "../plugin"; import * as t from "@babel/types"; import { choice } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; -import { Order } from "../../order"; +import { GEN_NODE, NodeSymbol } from "../../constants"; function pad(x: string, len: number): string { while (x.length < len) { @@ -47,7 +46,7 @@ function toUnicodeRepresentation(str: string) { return escapedString; } -export default (me: PluginInstance): PluginObj => { +export default (me: PluginInstance): PluginObject => { return { visitor: { StringLiteral: { @@ -65,7 +64,10 @@ export default (me: PluginInstance): PluginObj => { : toUnicodeRepresentation )(value); - path.replaceWith(t.identifier(`"${escapedString}"`)); + var id = t.identifier(`"${escapedString}"`); + + (id as NodeSymbol)[GEN_NODE] = true; + path.replaceWith(id); path.skip(); }, }, diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 39888d8..7a5a837 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -1,5 +1,4 @@ -import { PluginObj } from "@babel/core"; -import { PluginArg, PluginInstance } from "../plugin"; +import { PluginArg, PluginInstance, PluginObject } from "../plugin"; import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; import { binaryExpression, stringLiteral } from "@babel/types"; @@ -7,8 +6,12 @@ import { ok } from "assert"; import { Order } from "../../order"; import { ensureComputedExpression } from "../../utils/ast-utils"; -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.StringSplitting); +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.StringSplitting, { + changeData: { + strings: 0, + }, + }); return { visitor: { @@ -56,6 +59,8 @@ export default ({ Plugin }: PluginArg): PluginObj => { parent.right = stringLiteral(last); + me.changeData.strings++; + ensureComputedExpression(path); path.replaceWith(parent); diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index fc42eb6..82df8d8 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -1,16 +1,40 @@ -import { PluginObj } from "@babel/core"; -import { NodePath } from "@babel/traverse"; -import { PluginArg } from "./plugin"; +import { Binding, NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import Template from "../templates/template"; import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; -import { NodeSymbol, UNSAFE } from "../constants"; -import { getFunctionName, isStrictMode } from "../utils/ast-utils"; -import { computeFunctionLength } from "../utils/function-utils"; - -export default ({ Plugin }: PluginArg): PluginObj => { - const me = Plugin(Order.VariableMasking); +import { + NodeSymbol, + PREDICTABLE, + reservedIdentifiers, + UNSAFE, + variableFunctionName, +} from "../constants"; +import { + ensureComputedExpression, + getFunctionName, + isDefiningIdentifier, + isStrictMode, + isVariableIdentifier, + prepend, + replaceDefiningIdentifierToMemberExpression, +} from "../utils/ast-utils"; +import { + computeFunctionLength, + isVariableFunctionIdentifier, +} from "../utils/function-utils"; +import { ok } from "assert"; +import { NameGen } from "../utils/NameGen"; +import { choice, getRandomInteger } from "../utils/random-utils"; +import { createLiteral } from "../utils/node"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.VariableMasking, { + changeData: { + functions: 0, + }, + }); const transformFunction = (fnPath: NodePath) => { // Do not apply to getter/setter methods @@ -45,163 +69,181 @@ export default ({ Plugin }: PluginArg): PluginObj => { return; } - const stackName = me.generateRandomIdentifier() + "_varMask"; - const stackMap = new Map(); + const stackName = me.getPlaceholder() + "_varMask"; + const stackMap = new Map(); + const propertyGen = new NameGen("mangled"); + const stackKeys = new Set(); let needsStack = false; - for (const param of fnPath.node.params) { - stackMap.set((param as t.Identifier).name, stackMap.size); + const illegalBindings = new Set(); + + function checkBinding(binding: Binding) { + // Custom illegal check + // Variable Declarations with more than one declarator are not supported + // They can be inserted from the user's code even though Preparation phase should prevent it + // String Compression library includes such code + // TODO: Support multiple declarators + var variableDeclaration = binding.path.find((p) => + p.isVariableDeclaration() + ) as NodePath; + if ( + variableDeclaration && + variableDeclaration.node.declarations.length > 1 + ) { + return false; + } + + function checkForUnsafe(valuePath: NodePath) { + var hasUnsafeNode = false; + + valuePath.traverse({ + ThisExpression(path) { + hasUnsafeNode = true; + path.stop(); + }, + Function(path) { + if ((path.node as NodeSymbol)[UNSAFE]) { + hasUnsafeNode = true; + path.stop(); + } + }, + }); + + return hasUnsafeNode; + } + + // Check function value for 'this' + // Adding function expression to the stack (member expression) + // would break the 'this' context + if (binding.path.isVariableDeclarator()) { + let init = binding.path.get("init"); + if (init.node) { + if (checkForUnsafe(init)) return false; + } + } + + // x = function(){ return this } + // Cannot be transformed to x = stack[0] as 'this' would change + for (var assignment of binding.constantViolations) { + if (checkForUnsafe(assignment)) return false; + } + + // __JS_CONFUSER_VAR__(identifier) -> __JS_CONFUSER_VAR__(stack.identifier) + // This cannot be transformed as it would break the user's code + for (var referencePath of binding.referencePaths) { + if (isVariableFunctionIdentifier(referencePath)) { + return false; + } + } + + return true; + } + + for (const param of fnPath.get("params")) { + ok(param.isIdentifier()); + + const paramName = param.node.name; + const binding = param.scope.getBinding(paramName); + + if (!binding || !checkBinding(binding)) return; + + ok(!stackMap.has(binding)); + stackKeys.add(stackMap.size.toString()); + stackMap.set(binding, stackMap.size); } fnPath.traverse({ - BindingIdentifier(identifierPath) { - const binding = identifierPath.scope.getBinding( - identifierPath.node.name - ); + Identifier(path) { + if (!isVariableIdentifier(path)) return; + + if (reservedIdentifiers.has(path.node.name)) return; + if (me.options.globalVariables.has(path.node.name)) return; + if (path.node.name === stackName) return; + if (path.node.name === variableFunctionName) return; + + const binding = path.scope.getBinding(path.node.name); if (!binding || binding.scope !== fnPath.scope) return; + if (illegalBindings.has(binding)) return; + + needsStack = true; - if (binding.path.isIdentifier()) { - // Parameter check - if ( - !fnPath.node.params.some( - (param) => - t.isIdentifier(param) && - param.name === (binding.path.node as t.Identifier).name - ) - ) { + let index = stackMap.get(binding); + if (typeof index === "undefined") { + // Only transform var and let bindings + // Function declarations could be hoisted and changing them to declarations is breaking + if (!["var", "let"].includes(binding.kind)) { + illegalBindings.add(binding); return; } - } else if (binding.path.isVariableDeclarator()) { - if (binding.path.parentPath.node?.type !== "VariableDeclaration") - return; - if (binding.path.parentPath.node.declarations.length > 1) return; - if (!t.isIdentifier(binding.path.parentPath.node.declarations[0].id)) + + if (!checkBinding(binding)) { + illegalBindings.add(binding); return; - } else { - return; - } + } - needsStack = true; + do { + index = choice([ + stackMap.size, + propertyGen.generate(), + getRandomInteger(-250, 250), + ]); + } while (!index || stackKeys.has(index.toString())); - let stackIndex = stackMap.get(identifierPath.node.name); - if (typeof stackIndex === "undefined") { - stackIndex = stackMap.size; - stackMap.set(identifierPath.node.name, stackIndex); + stackMap.set(binding, index); + stackKeys.add(index.toString()); } - const memberExpression = new Template(` - ${stackName}[${stackIndex}] - `).expression(); - - binding.referencePaths.forEach((referencePath) => { - var callExpressionChild = referencePath; - - if ( - callExpressionChild && - callExpressionChild.parentPath?.isCallExpression() && - callExpressionChild.parentPath.node.callee === - callExpressionChild.node - ) { - callExpressionChild.parentPath.replaceWith( - t.callExpression( - t.memberExpression( - t.cloneNode(memberExpression), - t.identifier("call") - ), - [ - t.thisExpression(), - ...callExpressionChild.parentPath.node.arguments, - ] - ) - ); - - return; - } + const memberExpression = t.memberExpression( + t.identifier(stackName), + createLiteral(index), + true + ); - if (referencePath.container) { - referencePath.replaceWith(t.cloneNode(memberExpression)); - } - }); + if (isDefiningIdentifier(path)) { + replaceDefiningIdentifierToMemberExpression(path, memberExpression); - [binding.path, ...binding.constantViolations].forEach( - (constantViolation) => { - constantViolation.traverse({ - "ReferencedIdentifier|BindingIdentifier"(idPath) { - if (!idPath.isIdentifier()) return; - - const cBinding = idPath.scope.getBinding(idPath.node.name); - if (cBinding !== binding) return; - - var replacePath: NodePath = idPath; - var valueNode: t.Expression | null = null; - - var forInOfChild = idPath.find( - (p) => - p.parentPath?.isForInStatement() || - p.parentPath?.isForOfStatement() - ); - - var variableDeclarationChild = idPath.find((p) => - p.parentPath?.isVariableDeclarator() - ); - - if ( - variableDeclarationChild && - t.isVariableDeclarator(variableDeclarationChild.parent) && - variableDeclarationChild.parent.id === - variableDeclarationChild.node - ) { - replacePath = variableDeclarationChild.parentPath.parentPath; - valueNode = - variableDeclarationChild.parent.init || - t.identifier("undefined"); - } - - if ( - forInOfChild && - (t.isForInStatement(forInOfChild.parent) || - t.isForOfStatement(forInOfChild.parent)) && - forInOfChild.parent.left === forInOfChild.node - ) { - replacePath = forInOfChild; - valueNode = null; - } - - let replaceExpr: t.Node = t.cloneNode(memberExpression); - if (valueNode) { - replaceExpr = t.assignmentExpression( - "=", - replaceExpr, - valueNode - ); - } - - if (replacePath.container) { - replacePath.replaceWith(replaceExpr); - } - }, - }); - } - ); + return; + } - identifierPath.scope.removeBinding(identifierPath.node.name); + ensureComputedExpression(path); + path.replaceWith(memberExpression); }, }); if (!needsStack) return; - var originalLength = computeFunctionLength(fnPath); + const originalParamCount = fnPath.node.params.length; + const originalLength = computeFunctionLength(fnPath); + fnPath.node.params = [t.restElement(t.identifier(stackName))]; + // Discard extraneous parameters + // Predictable functions are guaranteed to not have extraneous parameters + if (!(fnPath.node as NodeSymbol)[PREDICTABLE]) { + prepend( + fnPath, + new Template(`${stackName}["length"] = {originalParamCount};`).single({ + originalParamCount: t.numericLiteral(originalParamCount), + }) + ); + } + + // Function is no longer predictable + (fnPath.node as NodeSymbol)[PREDICTABLE] = false; + fnPath.scope.registerBinding("param", fnPath.get("params")[0], fnPath); me.setFunctionLength(fnPath, originalLength); + + me.changeData.functions++; }; return { visitor: { Function: { exit(path: NodePath) { + if (!path.get("body").isBlockStatement()) return; + transformFunction(path); }, }, From 469236d1733001797a0b426e742069f0306be3de Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:36:40 -0400 Subject: [PATCH 039/103] Decrease NPM bundle size --- .npmignore | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c4fe75f --- /dev/null +++ b/.npmignore @@ -0,0 +1,135 @@ +# Decrease NPM bunlde size +src/ +test/ +docs/ +babel.config.js +babel.register.js + + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +coverage/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# VS Code prettier ignore list +.prettierignore + +dev.error.js +dev.input.js +dev.output.js +dev.ts + +package-lock.json + +Cash.output.js + +ES6.output.js + +.vscode/ + +src/dev.ts + +.DS_Store + From 82e77f6ee12b708c3f23c13cb7cc8b2fbc0f7a45 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:37:04 -0400 Subject: [PATCH 040/103] Repo update --- .github/ISSUE_TEMPLATE/bug_report.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index de7a1fe..cb85070 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: '' **Describe the bug:** -The program gets stuck in a infinite loop +The program enters an infinite loop and does not produce the expected output. **Config and Small code sample** @@ -30,12 +30,14 @@ console.log("My Small Code Sample"); **Expected behavior** -The program should output "My Small Code Sample" +Example: The program should output "My Small Code Sample" **Actual behavior** -The program stalls forever and never outputs anything +Example: The program stalls indefinitely and never outputs the expected message. **Additional context** -Seems the program gets stuck in a infinite loop due to Control Flow Flattening. Disabling the feature seems to rid of the bug. +Example: It appears that the issue is caused by Control Flow Flattening. Disabling this feature resolves the problem. + + From 87fb1f40348a4d0869761792bf5c7b644d4d565a Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 02:38:10 -0400 Subject: [PATCH 041/103] Prepare for 2.0, All Tests Passing --- CHANGELOG.md | 14 +-- Migration.md | 40 +++++-- test/code/Cash.test.ts | 45 +++++--- test/code/ES6.src.js | 109 ++++++++---------- test/code/ES6.test.ts | 61 +++++++--- test/code/StrictMode.src.js | 11 +- test/code/StrictMode.test.js | 11 ++ test/transforms/lock/tamperProtection.test.ts | 97 +++++++++++----- test/transforms/opaquePredicates.test.ts | 71 ++++++++++-- test/util/IntGen.test.ts | 13 +++ 10 files changed, 317 insertions(+), 155 deletions(-) create mode 100644 test/util/IntGen.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4ec42..06b70de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Renamed `Stack` to `Variable Masking` -**2.0 Changes** +### 2.0 Changes - Added Custom String Encoding and Custom Lock Code options @@ -53,6 +53,12 @@ - - `String Compression` now uses zlib decompression ([Pako](https://github.com/nodeca/pako)) +### JS-Confuser.com Revamp + +A new UI for JS-Confuser.com, featuring an advanced playground and documentation pages. + +The previous version will remain available: [old--confuser.netlify.com](https://old--confuser.netlify.app/) + **Removed features** - Removed `ES5` option - Use Babel Instead @@ -64,12 +70,6 @@ - Removed `Indent` option. [`@babel/generator`](https://www.npmjs.com/package/@babel/generator) does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. Be mindful if you have `Integrity` or `Self Defending` enabled, as you should not alter the obfuscated code. -**JS-Confuser.com Revamp** - -A new UI for JS-Confuser.com, featuring an advanced playground and documentation pages. - -The previous version will remain available: [old--confuser.netlify.com](https://old--confuser.netlify.app/) - # `1.7.3` Tamper Protection diff --git a/Migration.md b/Migration.md index 4f296cf..0f56357 100644 --- a/Migration.md +++ b/Migration.md @@ -2,14 +2,19 @@ JS-Confuser 2.0 is complete rewrite of the original JS-Confuser created in 2020! +## API Interface changed + ### JSConfuser.obfuscate() returns an object now The method `JSConfuser.obfuscate()` resolves to a object now instead of a string. This result object contains the obfuscated code on the `code` property. -```js -JSConfuser.obfuscate(sourceCode, options).then(result=>{ - console.log(result.code); -}); +```diff ++JSConfuser.obfuscate(sourceCode, options).then(result=>{ ++ console.log(result.code); ++}); +-JSConfuser.obfuscate(sourceCode, options).then(obfuscatedCode=>{ +- console.log(obfuscatedCode); +-}); ``` ### Removed Anti Debug Lock / Browser Lock / OS Lock @@ -18,14 +23,27 @@ These features have been removed but you can still add these locks using the `lo ```js { + target: "node", + + // ... Your obfuscator settings ... + lock: { customLocks: [ { - template: `if(window.navigator.userAgent.includes('Chrome')){ - {countermeasures} -}`, - percentage: 10, - max: 100 + code: ` + // This code will be sprinkled throughout your source code + // (Will also be obfuscated) + + if( window.navigator.userAgent.includes('Chrome') ){ + {countermeasures} + } + + // The {countermeasures} template variable is required. + // Must be placed in a Block or Switch Case body + `, + percentagePerBlock: 0.1, // = 10% + maxCount: 100, // Default = 100 - You probably don't want an excessive amount placed + minCount: 1 // Default = 1 - Ensures this custom lock is placed } ] } @@ -36,6 +54,4 @@ These features have been removed but you can still add these locks using the `lo The option `stack` has been renamed to `variableMasking` -### Flatten renamed to Function Hoisting - -The option `flatten` has been renamed to `functionHoisting` \ No newline at end of file +[Similar to JScrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) \ No newline at end of file diff --git a/test/code/Cash.test.ts b/test/code/Cash.test.ts index c888704..1aa51be 100644 --- a/test/code/Cash.test.ts +++ b/test/code/Cash.test.ts @@ -36,10 +36,10 @@ const handleError = (error, output) => { encoding: "utf-8", }); - expect(true).toStrictEqual(false); + expect("An error occurred").toStrictEqual(null); }; -test.only("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { +test("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { var { code: output } = await JsConfuser.obfuscate(CASH_JS, { target: "node", preset: "high", @@ -74,6 +74,7 @@ test.only("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { } try { + // Pack option allows the code to be executed in a strict mode eval(output); } catch (e) { handleError(e, output); @@ -110,21 +111,35 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta global[key] = window[key]; } - var { code: output } = await JsConfuser.obfuscate(CASH_JS, { - target: "node", - preset: "high", - pack: true, - rgf: true, - lock: { - integrity: true, - selfDefending: true, - tamperProtection: true, - }, - }); + var rgfCount = 0; + + const CountermeasuresCode = ` + function countermeasures() { + throw new Error("countermeasures() was called"); + } + `; + + var { code: output } = await JsConfuser.obfuscate( + CountermeasuresCode + CASH_JS, + { + target: "node", + preset: "high", + pack: true, + rgf: (fnName, depth) => { + return rgfCount++ < 10; + }, + lock: { + integrity: true, + selfDefending: true, + tamperProtection: true, + countermeasures: "countermeasures", + }, + } + ); try { - // new Function() runs in non-strict mode - new Function(output)(); + // Pack option allows the code to be executed in a strict mode + eval(output); } catch (e) { handleError(e, output); } diff --git a/test/code/ES6.src.js b/test/code/ES6.src.js index 5c2611a..06cb196 100644 --- a/test/code/ES6.src.js +++ b/test/code/ES6.src.js @@ -1,34 +1,28 @@ -"use strict"; +TEST_OUTPUT = {}; // Variant #1 Using `let` let myVariable = 1; - -expect(myVariable).toStrictEqual(1); +TEST_OUTPUT["Variant #1"] = myVariable === 1; // Variant #2 Destructing variable from object (ObjectPattern) let { key } = { key: 2 }; - -expect(key).toStrictEqual(2); +TEST_OUTPUT["Variant #2"] = key === 2; // Variant #3 Destructing variable and using differing output name (ObjectPattern) let { key: customName } = { key: 3 }; - -expect(customName).toStrictEqual(3); +TEST_OUTPUT["Variant #3"] = customName === 3; // Variant #4 Destructing variable from array (ArrayPattern) let [element] = [4]; - -expect(element).toStrictEqual(4); +TEST_OUTPUT["Variant #4"] = element === 4; // Variant #5 Destructing computed property from nested pattern let [{ ["key"]: deeplyNestedKey }] = [{ key: 5 }]; - -expect(deeplyNestedKey).toStrictEqual(5); +TEST_OUTPUT["Variant #5"] = deeplyNestedKey === 5; // Variant #6 Make sure arrow functions work const arrowFn = () => 6; - -expect(arrowFn()).toStrictEqual(6); +TEST_OUTPUT["Variant #6"] = arrowFn() === 6; // Variant #7 Make sure inline methods on object work let es6Object = { @@ -36,8 +30,7 @@ let es6Object = { return 7; }, }; - -expect(es6Object.method()).toStrictEqual(7); +TEST_OUTPUT["Variant #7"] = es6Object.method() === 7; // Variant #8 Make sure getters on object work es6Object = { @@ -45,8 +38,7 @@ es6Object = { return 8; }, }; - -expect(es6Object.getter).toStrictEqual(8); +TEST_OUTPUT["Variant #8"] = es6Object.getter === 8; // Variant #9 Make sure getters with computed properties work let customKey = "myGetter"; @@ -55,8 +47,7 @@ es6Object = { return 9; }, }; - -expect(es6Object.myGetter).toStrictEqual(9); +TEST_OUTPUT["Variant #9"] = es6Object.myGetter === 9; // Variant #10 Make sure constructor method works var value; @@ -67,14 +58,14 @@ class MyClass { } var myInstance = new MyClass(10); -expect(value).toStrictEqual(10); +TEST_OUTPUT["Variant #10"] = value === 10; // Variant #11 Make sure for-loop initializers work var sum = 0; for (var x of [3, 3, 5]) { sum += x; } -expect(sum).toStrictEqual(11); +TEST_OUTPUT["Variant #11"] = sum === 11; // Variant #12 More complex for-loop initializer var outside = 12; @@ -86,9 +77,8 @@ for ( ) {} -var TEST_OUTPUT = myFunction(); - -expect(TEST_OUTPUT).toStrictEqual(12); +var functionCall = myFunction(); +TEST_OUTPUT["Variant #12"] = functionCall === 12; function noLexicalVariables() { // Variant #13 For-in statement @@ -100,7 +90,7 @@ function noLexicalVariables() { } } - expect(sumOfKeys).toStrictEqual(13); + TEST_OUTPUT["Variant #13"] = sumOfKeys === 13; // Variant #14 For-of statement var values = [10, 20, 30, 40, -86]; @@ -109,7 +99,7 @@ function noLexicalVariables() { sumOfValues += value; } - expect(sumOfValues).toStrictEqual(14); + TEST_OUTPUT["Variant #14"] = sumOfValues === 14; } noLexicalVariables(); @@ -123,20 +113,40 @@ function useStrictFunction() { return this; } - expect(fun() === undefined).toStrictEqual(true); - expect(fun.call(2) === 2).toStrictEqual(true); - expect(fun.apply(null) === null).toStrictEqual(true); - expect(fun.call(undefined) === undefined).toStrictEqual(true); - expect(fun.bind(true)() === true).toStrictEqual(true); + TEST_OUTPUT["Variant #15"] = [ + fun() === undefined, + fun.call(2) === 2, + fun.apply(null) === null, + fun.call(undefined) === undefined, + fun.bind(true)() === true, + ]; } testThis(); function testArguments() { // Ensure arguments behaves like strict-mode - expect(() => useStrictFunction.arguments).toThrow(); - expect(() => useStrictFunction.caller).toThrow(); - expect(() => arguments.callee).toThrow(); + + TEST_OUTPUT["Variant #16: #1"] = false; + try { + useStrictFunction.arguments; + } catch (e) { + TEST_OUTPUT["Variant #16: #1"] = true; + } + + TEST_OUTPUT["Variant #16: #2"] = false; + try { + useStrictFunction.caller; + } catch (e) { + TEST_OUTPUT["Variant #16: #2"] = true; + } + + TEST_OUTPUT["Variant #16: #3"] = false; + try { + arguments.callee; + } catch (e) { + TEST_OUTPUT["Variant #16: #3"] = true; + } } testArguments(); @@ -147,9 +157,8 @@ function useStrictFunction() { // Eval will not leak names eval("var __NO_JS_CONFUSER_RENAME__myOuterVariable = 'Incorrect Value';"); - expect(__NO_JS_CONFUSER_RENAME__myOuterVariable).toStrictEqual( - "Initial Value" - ); + TEST_OUTPUT["Variant #17"] = + __NO_JS_CONFUSER_RENAME__myOuterVariable === "Initial Value"; } testEval(); @@ -200,35 +209,17 @@ function labeledBreaksAndContinues() { } } -var variant15 = labeledBreaksAndContinues(); -expect(variant15).toStrictEqual(15); +TEST_OUTPUT["Variant #18"] = labeledBreaksAndContinues() === 15; // Variant #16: Function.length property -var variant16 = function ( - n1, - n2, - n3, - n4, - n5, - n6, - n7, - n8, - n9, - n10, - n11, - n12, - n13, - n14, - n15, - n16 -) { +var variant19 = function (n1, n2, n3, n4, n5) { var _ = true; }; -expect(variant16.length).toStrictEqual(16); +TEST_OUTPUT["Variant #19"] = variant19.length === 5; // Set 'ranAllTest' to TRUE -ranAllTest = true; +TEST_OUTPUT["Variant #20"] = true; function countermeasures() { throw new Error("Countermeasures function called."); diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index 3da6903..d7bd57a 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -1,39 +1,66 @@ -import { readFileSync } from "fs"; +import { readFileSync, writeFileSync } from "fs"; import { join } from "path"; import JsConfuser from "../../src/index"; -var ES6_JS = readFileSync(join(__dirname, "./ES6.src.js"), "utf-8"); +const ES6_JS = readFileSync(join(__dirname, "./ES6.src.js"), "utf-8"); +const EXPECTED_RESULT = { + "Variant #1": true, + "Variant #2": true, + "Variant #3": true, + "Variant #4": true, + "Variant #5": true, + "Variant #6": true, + "Variant #7": true, + "Variant #8": true, + "Variant #9": true, + "Variant #10": true, + "Variant #11": true, + "Variant #12": true, + "Variant #13": true, + "Variant #14": true, + "Variant #15": [true, true, true, true, true], + "Variant #16: #1": true, + "Variant #16: #2": true, + "Variant #16: #3": true, + "Variant #17": true, + "Variant #18": true, + "Variant #19": true, + "Variant #20": true, +}; test("Variant #1: ES6 code on High Preset", async () => { - var { code: output } = await JsConfuser.obfuscate(ES6_JS, { + const { code } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", pack: true, }); - // The 'use strict' directive is removed due to being packed - - var ranAllTest = false; - eval(output); - - // 'ranAllTest' is set to TRUE by the evaluated code - expect(ranAllTest).toStrictEqual(true); + const TEST_OUTPUT = {}; + eval(code); + expect(TEST_OUTPUT).toStrictEqual(EXPECTED_RESULT); }); -test("Variant #2: ES6 code on High Preset + RGF + Self Defending", async () => { - var { code: output } = await JsConfuser.obfuscate(ES6_JS, { +test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protection + Integrity", async () => { + let rgfCount = 0; + + const { code } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", - rgf: true, + pack: true, + rgf: (fnName, depth) => { + return rgfCount++ < 10; + }, lock: { + integrity: true, selfDefending: true, + tamperProtection: true, countermeasures: "countermeasures", }, }); - var ranAllTest = false; - eval(output); + writeFileSync("./dev.output.js", code, "utf-8"); - // 'ranAllTest' is set to TRUE by the evaluated code - expect(ranAllTest).toStrictEqual(true); + const TEST_OUTPUT = {}; + eval(code); + expect(TEST_OUTPUT).toStrictEqual(EXPECTED_RESULT); }); diff --git a/test/code/StrictMode.src.js b/test/code/StrictMode.src.js index 2355733..96e2a14 100644 --- a/test/code/StrictMode.src.js +++ b/test/code/StrictMode.src.js @@ -23,10 +23,7 @@ function TestStrictMode() { count++; } - expect(count).toStrictEqual(10); - - // This function should be in strict mode - expect(isStrictMode()).toStrictEqual(true); + TEST_OUTPUT.count = count; } var isStrictMode = () => { @@ -39,7 +36,7 @@ var isStrictMode = () => { }; // Global level should be in strict mode -expect(isStrictMode()).toStrictEqual(true); +TEST_OUTPUT.globalStrictMode = isStrictMode(); TestStrictMode(); // Direct vs. Indirect eval usage @@ -57,9 +54,9 @@ isStrictMode();`; // Direct eval -> Preserve global strict-mode var directEvalResult = eval(evalString); -expect(directEvalResult).toStrictEqual(true); +TEST_OUTPUT.directEvalResult = directEvalResult; // Indirect eval -> Does not inherit context strict-mode var _eval_ = eval; var indirectEvalResult = _eval_(evalString); -expect(indirectEvalResult).toStrictEqual(false); +TEST_OUTPUT.indirectEvalResult = indirectEvalResult; diff --git a/test/code/StrictMode.test.js b/test/code/StrictMode.test.js index 71f8ec2..d5229a6 100644 --- a/test/code/StrictMode.test.js +++ b/test/code/StrictMode.test.js @@ -12,9 +12,20 @@ test("Variant #1: StrictMode on High Preset", async () => { target: "node", preset: "high", pack: true, + + // Disable global concealing for testing purposes + // TEST_OUTPUT does not live on the global object + globalConcealing: (globalName) => globalName != "TEST_OUTPUT", }); //writeFileSync("./dev.output.js", output); + var TEST_OUTPUT = {}; + eval(output); + + expect(TEST_OUTPUT.count).toStrictEqual(10); + expect(TEST_OUTPUT.globalStrictMode).toStrictEqual(true); + expect(TEST_OUTPUT.directEvalResult).toStrictEqual(true); + expect(TEST_OUTPUT.indirectEvalResult).toStrictEqual(false); }); diff --git a/test/transforms/lock/tamperProtection.test.ts b/test/transforms/lock/tamperProtection.test.ts index dedaf14..09f66d9 100644 --- a/test/transforms/lock/tamperProtection.test.ts +++ b/test/transforms/lock/tamperProtection.test.ts @@ -24,7 +24,7 @@ function evalInNonStrictMode(str: string) { } describe("Global Concealing", () => { - test("Variant #1: Detect Eval tamper (no tamper)", async () => { + test("Variant #1: Normal behavior when eval() is un-tampered", async () => { var code = ` global.TEST_GLOBAL_OUTPUT = global.TEST_GLOBAL; `; @@ -48,12 +48,19 @@ describe("Global Concealing", () => { expect(TEST_OUTPUT).toStrictEqual(TEST_GLOBAL); }); - test("Variant #2: Detect Eval tamper (tampered)", async () => { + test("Variant #2: Invoke countermeasures when eval() is tampered", async () => { var code = ` function onTamperDetected(){ - TEST_OUTPUT_SET(true); + TEST_OUTPUT_SET("Countermeasures Invoked"); + } + + function myFunction(){ + // Global Concealing finds native 'console.log' + // Adds GetGlobal template + // Detects 'eval' tamper + // Calls 'onTamperDetected' + console.log("This function is purposely never called."); } - global.TEST_GLOBAL_VARIANT_7_OUTPUT = global.TEST_GLOBAL_VARIANT_7; `; var { code: output } = await JsConfuser.obfuscate(code, { @@ -63,24 +70,24 @@ describe("Global Concealing", () => { tamperProtection: true, countermeasures: "onTamperDetected", }, + + // Ensure Eval renaming works + renameVariables: true, }); // Inject 'eval' tamper code output = - `var _eval = eval; - eval = (codeStr)=>( console.log(codeStr), _eval(codeStr) ); + `var _originalEval = eval; + var eval = (codeStr)=>( console.log("Eval Intercepted", codeStr), TEST_OUTPUT_SET(codeStr), _originalEval(codeStr) ); ` + output; - var TEST_GLOBAL_VARIANT_7 = {}; - (global as any).TEST_GLOBAL_VARIANT_7 = TEST_GLOBAL_VARIANT_7; - var { error, TEST_OUTPUT } = evalInNonStrictMode(output); expect(error).toBeNull(); - expect(TEST_OUTPUT).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual("Countermeasures Invoked"); }); - test("Variant #3: Native check on functions", async () => { + test("Variant #3: Normal behavior when a native function is un-tampered", async () => { var mockConsoleLog = (...msgs) => { console.log(...msgs); }; @@ -115,11 +122,15 @@ describe("Global Concealing", () => { expect(error).toBeNull(); }); - test("Variant #4: Native check on functions (tampered)", async () => { + test("Variant #4: Invoke countermeasures when a native function is tampered", async () => { + var mockInvoked = false; + var mockConsoleLog = (...msgs) => { console.log(...msgs); + + // Not good, the function was tampered + mockInvoked = true; }; - mockConsoleLog.toString = () => "[native code]"; (global as any).mockConsoleLog = mockConsoleLog; var { code: output } = await JsConfuser.obfuscate( @@ -134,18 +145,19 @@ describe("Global Concealing", () => { target: "node", globalConcealing: (varName) => varName != "TEST_OUTPUT_SET", lock: { - tamperProtection: true, + tamperProtection: (fnName) => fnName === "mockConsoleLog", countermeasures: "onTamperDetected", }, } ); - (global as any).mockConsoleLog = (...str) => - console.log("Tampered console.log: ", ...str); - // Unfortunately the program errors dude to console.log being tampered var { TEST_OUTPUT, error } = evalInNonStrictMode(output); + // Ensure mockConsoleLog was not called + expect(mockInvoked).toStrictEqual(false); + + // Ensure countermeasures was called expect(TEST_OUTPUT).toStrictEqual(true); // Ensure error was thrown @@ -156,6 +168,7 @@ describe("Global Concealing", () => { var { code: output } = await JsConfuser.obfuscate( ` a.b.c.d() + nonExistentFunction() `, { target: "node", @@ -232,9 +245,12 @@ describe("Global Concealing", () => { }); describe("RGF", () => { - test("Variant #1: Use Eval instead of new Function", async () => { + test("Variant #1: Detect Eval tamper (no tamper)", async () => { var { code: output } = await JsConfuser.obfuscate( ` + function countermeasures(){ + throw new Error("Countermeasures function should not be called"); + } function myFunction1(){ TEST_OUTPUT_SET(true); } @@ -246,7 +262,10 @@ describe("RGF", () => { rgf: true, lock: { tamperProtection: true, + countermeasures: "countermeasures", }, + + // Ensure renaming countermeasures works renameVariables: true, // Allow RGF to transform 'myFunction1' @@ -255,11 +274,11 @@ describe("RGF", () => { } ); + // Ensure 'myFunction1' was transformed expect(output).not.toContain( "function myFunction1(){TEST_OUTPUT_SET(true)" ); expect(output).toContain("eval"); - expect(output).not.toContain("new Function"); var { TEST_OUTPUT, error } = evalInNonStrictMode(output); @@ -267,7 +286,7 @@ describe("RGF", () => { expect(TEST_OUTPUT).toStrictEqual(true); }); - test("Variant #2: Detect Eval tamper", async () => { + test("Variant #2: Invoke countermeasures when eval() is tampered", async () => { var { code: output } = await JsConfuser.obfuscate( ` function onTamperDetected(){ @@ -286,6 +305,8 @@ describe("RGF", () => { tamperProtection: true, countermeasures: "onTamperDetected", }, + + // Ensure Eval renaming works renameVariables: true, // Allow RGF to transform 'myFunction1' @@ -299,8 +320,8 @@ describe("RGF", () => { // Inject 'eval' tamper code output = - `var _eval = eval; - eval = (code)=>( TEST_OUTPUT_SET(code), console.log(code), _eval(code) );` + + `var _originalEval = eval; + var eval = (code)=>( TEST_OUTPUT_SET(code), console.log("Eval Intercepted", code), _originalEval(code) );` + output; var { TEST_OUTPUT, error } = evalInNonStrictMode(output); @@ -311,13 +332,34 @@ describe("RGF", () => { }); describe("Strict Mode", () => { - test("Variant #1: Disallow Strict Mode", async () => { - var { code: output } = await JsConfuser.obfuscate( - ` + test("Variant #1: Error when 'use strict' directive is present", async () => { + expect(() => + JsConfuser.obfuscate( + ` "use strict"; // Note: Jest testing environment is already in Strict Mode function onTamperDetected(){ TEST_OUTPUT = true; } + `, + { + target: "node", + lock: { + tamperProtection: true, + countermeasures: "onTamperDetected", + }, + } + ) + ).rejects.toThrow( + "Tamper Protection cannot be applied to code in strict mode." + ); + }); + + test("Variant #2: Invoke countermeasures when script is executing in strict-mode", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function onTamperDetected(){ + TEST_OUTPUT = true; + } `, { target: "node", @@ -329,7 +371,10 @@ describe("Strict Mode", () => { ); var TEST_OUTPUT; - eval(output); + + try { + eval(code); + } catch {} expect(TEST_OUTPUT).toStrictEqual(true); }); diff --git a/test/transforms/opaquePredicates.test.ts b/test/transforms/opaquePredicates.test.ts index a6aba26..82e0019 100644 --- a/test/transforms/opaquePredicates.test.ts +++ b/test/transforms/opaquePredicates.test.ts @@ -1,43 +1,90 @@ import JsConfuser from "../../src/index"; -it("should append logical expressions", async () => { +test("Variant #1: Obfuscate IF-statements", async () => { var code = ` var test = false; if ( test ) { + } else { + TEST_OUTPUT = "Correct Value"; } `; var { code: output } = await JsConfuser.obfuscate(code, { - target: "browser", + target: "node", opaquePredicates: true, }); expect(output).not.toContain("(test)"); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); // https://github.com/MichaelXF/js-confuser/issues/45 -it("should work on default Switch cases", async () => { +test("Variant #2: Obfuscate Switch statements with default case", async () => { var code = ` - switch (0) { - default: - input(true); + case 1: + TEST_OUTPUT = "Incorrect Value"; + break; + default: + TEST_OUTPUT = "Correct Value"; + break; + } + `; + + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + opaquePredicates: true, + }); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #3: Obfuscate Return statements", async () => { + var code = ` + function testFunction() { + if(false) return; + + return "Correct Value"; } + + TEST_OUTPUT = testFunction(); + `; + + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + opaquePredicates: true, + }); + + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #4: Obfuscate Conditional expressions", async () => { + var code = ` + var test = true; + TEST_OUTPUT = test ? "Correct Value" : "Incorrect Value"; `; var { code: output } = await JsConfuser.obfuscate(code, { - target: "browser", + target: "node", opaquePredicates: true, }); - var value; - function input(valueIn) { - value = valueIn; - } + expect(output).not.toContain("=test?"); + var TEST_OUTPUT; eval(output); - expect(value).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); diff --git a/test/util/IntGen.test.ts b/test/util/IntGen.test.ts new file mode 100644 index 0000000..90d6fff --- /dev/null +++ b/test/util/IntGen.test.ts @@ -0,0 +1,13 @@ +import { IntGen } from "../../src/utils/IntGen"; + +test("Variant #1: Generate random integers", () => { + const intGen = new IntGen(-25, 25); + const ints = new Set(); + const count = 200; + + for (var i = 0; i < count; i++) { + ints.add(intGen.generate()); + } + + expect(ints.size).toStrictEqual(count); +}); From 3121c281d42dff15d2b9b55b2d7e21acafa06945 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 18:39:22 -0400 Subject: [PATCH 042/103] Exclude dev files --- .gitignore | 1 + .npmignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index af3c8d6..c5733c9 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,7 @@ dev.error.js dev.input.js dev.output.js dev.ts +dev.profile.json package-lock.json diff --git a/.npmignore b/.npmignore index c4fe75f..0515624 100644 --- a/.npmignore +++ b/.npmignore @@ -120,6 +120,7 @@ dev.error.js dev.input.js dev.output.js dev.ts +dev.profile.json package-lock.json From 5ce602bb68703b3fd2cd5eeb6f41ccb1f02d44a0 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 18:41:21 -0400 Subject: [PATCH 043/103] Improve Control Flow Flattening --- src/constants.ts | 2 +- src/transforms/controlFlowFlattening.ts | 71 +++++++++++++------------ src/utils/ControlObject.ts | 3 +- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index ad8c510..4f4d18e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -67,7 +67,7 @@ export interface NodeSymbol { [SKIP]?: boolean | number; [FN_LENGTH]?: number; [CONTROL_OBJECTS]?: ControlObject[]; - [NO_RENAME]?: string; + [NO_RENAME]?: string | number; [GEN_NODE]?: boolean; [MULTI_TRANSFORM]?: boolean; diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 13df8d9..8f4efe4 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -189,7 +189,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Limit how many numbers get entangled let mangledLiteralsCreated = 0; - const prefix = cffPrefix + "_" + cffCounter++; + const cffIndex = ++cffCounter; // Start from 1 + const prefix = cffPrefix + "_" + cffIndex; const withIdentifier = (suffix) => { var name; @@ -201,7 +202,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { var id = t.identifier(name); - (id as NodeSymbol)[NO_RENAME] = name; + (id as NodeSymbol)[NO_RENAME] = cffIndex; return id; }; @@ -226,16 +227,27 @@ export default ({ Plugin }: PluginArg): PluginObject => { let scopeCounter = 0; - const scopeNameGen = new NameGen(me.options.identifierGenerator); + let scopeNameGen = new NameGen(me.options.identifierGenerator); + if (!isDebug) { + scopeNameGen = me.obfuscator.nameGen; + } - const withProperty = isDebug ? "with" : scopeNameGen.generate(); - const withDiscriminant = new Template( + // Create 'with' object - Determines which scope gets top-level variable access + const withProperty = isDebug ? "with" : scopeNameGen.generate(false); + const withMemberExpression = new Template( `${scopeVar.name}["${withProperty}"]` ).expression(); + withMemberExpression.object[NO_RENAME] = cffIndex; + // Create 'resetWith' function - Safely resets the 'with' object to none const resetWithProperty = isDebug ? "resetWith" - : scopeNameGen.generate(); + : scopeNameGen.generate(false); + + const resetWithMemberExpression = new Template( + `${scopeVar.name}["${resetWithProperty}"]` + ).expression(); + resetWithMemberExpression.object[NO_RENAME] = cffIndex; class ScopeManager { isNotUsed = true; @@ -246,8 +258,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { ? me.obfuscator.nameGen : new NameGen(me.options.identifierGenerator); - preserveNames = new Set(); - findWithBindings(): ScopeManager { if (this.nameMap.size === 0) return this.parent?.findWithBindings(); @@ -261,9 +271,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (isDebug) { newName = "_" + name; } - if ((originalNode as NodeSymbol)?.[NO_RENAME]) { - newName = name; - } + + // console.log(name, newName); this.nameMap.set(name, newName); @@ -495,9 +504,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { this.withDiscriminant === scopeManager ) { var id = t.identifier(identifierName); - (id as NodeSymbol)[NO_RENAME] = identifierName; + (id as NodeSymbol)[NO_RENAME] = cffIndex; (id as NodeSymbol)[WITH_STATEMENT] = true; - me.skip(id); return id; } @@ -853,12 +861,14 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Add with / reset with logic basicBlocks.get(startLabel).body.unshift( new Template(` - ${scopeVar.name}["${resetWithProperty}"] = function(newStateValues){ - ${scopeVar.name}["${withProperty}"] = undefined; + {resetWithMemberExpression} = function(newStateValues){ + {withMemberExpression} = undefined; {arrayPattern} = newStateValues } `).single({ arrayPattern: t.arrayPattern(deepClone(stateVars)), + resetWithMemberExpression: deepClone(resetWithMemberExpression), + withMemberExpression: deepClone(withMemberExpression), }) ); @@ -902,7 +912,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { } }); // DEAD CODE 3/3: Clone chunks but these chunks are never ran - const cloneChunkCount = getRandomInteger(1, 5); + const cloneChunkCount = 0; // getRandomInteger(1, 5); for (let i = 0; i < cloneChunkCount; i++) { let randomChunk = choice(Array.from(basicBlocks.values())); @@ -1045,6 +1055,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { exit(path: NodePath) { if (!isVariableIdentifier(path)) return; if (me.isSkipped(path)) return; + if ((path.node as NodeSymbol)[NO_RENAME] === cffIndex) return; const identifierName = path.node.name; if (identifierName === gotoFunctionName) return; @@ -1067,7 +1078,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { var scopeManager = scopeToScopeManager.get(binding.scope); if (!scopeManager) return; - if (scopeManager.preserveNames.has(identifierName)) return; let newName = scopeManager.getNewName( identifierName, @@ -1104,7 +1114,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { } } + me.skip(memberExpression); + path.replaceWith(memberExpression); + path.skip(); }, }, @@ -1157,24 +1170,15 @@ export default ({ Plugin }: PluginArg): PluginObject => { assignments.push( t.assignmentExpression( "=", - deepClone(withDiscriminant), + deepClone(withMemberExpression), jumpBlock.withDiscriminant.getScopeObject() ) ); } else if (basicBlock.withDiscriminant) { assignments.push( - t.callExpression( - t.memberExpression( - deepClone(scopeVar), - t.stringLiteral(resetWithProperty), - true - ), - [ - t.arrayExpression( - newStateValues.map(numericLiteral) - ), - ] - ) + t.callExpression(deepClone(resetWithMemberExpression), [ + t.arrayExpression(newStateValues.map(numericLiteral)), + ]) ); needsIndividualAssignments = false; } @@ -1238,7 +1242,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Create global numbers for predicates const mainScope = basicBlocks.get(startLabel).scopeManager; const predicateNumbers = new Map(); - const predicateNumberCount = isDebug ? 0 : getRandomInteger(2, 5); + const predicateNumberCount = + isDebug || !addPredicateTests ? 0 : getRandomInteger(2, 5); for (let i = 0; i < predicateNumberCount; i++) { const name = mainScope.getNewName( me.getPlaceholder("predicate_" + i) @@ -1413,7 +1418,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { traverse(t.program([t.expressionStatement(discriminant)]), { Identifier(path) { - (path.node as NodeSymbol)[NO_RENAME] = path.node.name; + (path.node as NodeSymbol)[NO_RENAME] = cffIndex; }, }); @@ -1437,7 +1442,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { new Template( `{withDiscriminant} || Object["create"](null)` ).expression({ - withDiscriminant: deepClone(withDiscriminant), + withDiscriminant: deepClone(withMemberExpression), }), t.blockStatement([switchStatement]) ), diff --git a/src/utils/ControlObject.ts b/src/utils/ControlObject.ts index 91b7306..c0ade5b 100644 --- a/src/utils/ControlObject.ts +++ b/src/utils/ControlObject.ts @@ -110,7 +110,8 @@ export default class ControlObject { } addProperty(node: t.Expression) { - this.ensureCreated(node); + var initialNode = this.ensureCreated(node); + if (initialNode) return initialNode; const propertyName = this.nameGen.generate(); this.propertyNames.add(propertyName); From 9a569544613fc0460d989df1848bea9541b7fb7c Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 19:31:53 -0400 Subject: [PATCH 044/103] Opaque Predicates & Control Flow Flattening fixes --- src/presets.ts | 2 +- src/transforms/controlFlowFlattening.ts | 55 +++++++++++++++++++------ src/transforms/opaquePredicates.ts | 13 +----- src/utils/ControlObject.ts | 12 +++--- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/presets.ts b/src/presets.ts index e523510..7ef7906 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -30,7 +30,7 @@ const highPreset: ObfuscateOptions = { minify: true, movedDeclarations: true, objectExtraction: true, - // opaquePredicates: 0.75, + opaquePredicates: 0.75, renameVariables: true, renameGlobals: true, shuffle: true, diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 8f4efe4..7b81acc 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -30,6 +30,7 @@ import { PREDICTABLE, variableFunctionName, WITH_STATEMENT, + CONTROL_OBJECTS, } from "../constants"; /** @@ -258,11 +259,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { ? me.obfuscator.nameGen : new NameGen(me.options.identifierGenerator); - findWithBindings(): ScopeManager { - if (this.nameMap.size === 0) - return this.parent?.findWithBindings(); + findBestWithDiscriminant(basicBlock: BasicBlock): ScopeManager { + if (basicBlock !== this.initializingBasicBlock) { + if (this.nameMap.size > 0) return this; + } - return this; + return this.parent?.findBestWithDiscriminant(basicBlock); } getNewName(name: string, originalNode?: t.Node) { @@ -570,6 +572,18 @@ export default ({ Plugin }: PluginArg): PluginObject => { } } + /** + * Stage 1: Flatten the code into Basic Blocks + * + * This involves transforming the Control Flow / Scopes into blocks with 'goto' statements + * + * - A block is simply a sequence of statements + * - A block can have a 'goto' statement to another block + * - A block original scope is preserved + * + * 'goto' & Scopes are transformed in Stage 2 + */ + const switchLabel = me.getPlaceholder(); const breakStatement = () => { return t.breakStatement(t.identifier(switchLabel)); @@ -681,14 +695,14 @@ export default ({ Plugin }: PluginArg): PluginObject => { functionExpression, ]); + // Change the function declaration to a variable declaration hoistedBasicBlock.body.unshift( - t.expressionStatement( - t.assignmentExpression( - "=", - sm.getMemberExpression(rename), + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(fnName), functionExpression - ) - ) + ), + ]) ); const blockStatement = statement.get("body"); @@ -912,7 +926,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { } }); // DEAD CODE 3/3: Clone chunks but these chunks are never ran - const cloneChunkCount = 0; // getRandomInteger(1, 5); + const cloneChunkCount = getRandomInteger(1, 5); for (let i = 0; i < cloneChunkCount; i++) { let randomChunk = choice(Array.from(basicBlocks.values())); @@ -945,7 +959,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Select scope managers for the with statement for (const basicBlock of basicBlocks.values()) { basicBlock.bestWithDiscriminant = - basicBlock.initializedScope?.findWithBindings(); + basicBlock.initializedScope?.findBestWithDiscriminant(basicBlock); if (isDebug && basicBlock.withDiscriminant) { basicBlock.body.unshift( @@ -958,6 +972,13 @@ export default ({ Plugin }: PluginArg): PluginObject => { } } + /** + * Stage 2: Transform 'goto' statements into valid JavaScript + * + * - 'goto' is replaced with equivalent state updates and break statements + * - Original identifiers are converted into member expressions + */ + // Remap 'GotoStatement' to actual state assignments and Break statements for (const basicBlock of basicBlocks.values()) { const { stateValues: currentStateValues } = basicBlock; @@ -1239,6 +1260,13 @@ export default ({ Plugin }: PluginArg): PluginObject => { basicBlock.thisPath.traverse(visitor); } + /** + * Stage 3: Create a switch statement to handle the control flow + * + * - Add fake / impossible blocks + * - Add fake / predicates to the switch cases tests + */ + // Create global numbers for predicates const mainScope = basicBlocks.get(startLabel).scopeManager; const predicateNumbers = new Map(); @@ -1552,6 +1580,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Reset all bindings here blockPath.scope.bindings = Object.create(null); + // Bindings changed - breaking control objects + delete (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]; + // Register new declarations for (var node of blockPath.get("body")) { blockPath.scope.registerDeclaration(node); diff --git a/src/transforms/opaquePredicates.ts b/src/transforms/opaquePredicates.ts index 3103207..b47f482 100644 --- a/src/transforms/opaquePredicates.ts +++ b/src/transforms/opaquePredicates.ts @@ -2,18 +2,9 @@ import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; -import Template from "../templates/template"; -import { NameGen } from "../utils/NameGen"; -import { getBlock, prependProgram } from "../utils/ast-utils"; -import { - chance, - choice, - getRandomString, - shuffle, -} from "../utils/random-utils"; +import { getBlock } from "../utils/ast-utils"; +import { chance, getRandomString } from "../utils/random-utils"; import { computeProbabilityMap } from "../probability"; -import { NodeSymbol, PREDICTABLE } from "../constants"; -import ControlObject from "../utils/ControlObject"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.OpaquePredicates, { diff --git a/src/utils/ControlObject.ts b/src/utils/ControlObject.ts index c0ade5b..a19d96f 100644 --- a/src/utils/ControlObject.ts +++ b/src/utils/ControlObject.ts @@ -50,18 +50,18 @@ export default class ControlObject { createTruePredicate() { var { node, value } = this.createPredicate(); - if (!value) { - return t.unaryExpression("!", node); + if (value) { + return node; } - return node; + return t.unaryExpression("!", node); } createFalsePredicate() { var { node, value } = this.createPredicate(); - if (value) { - return t.unaryExpression("!", node); + if (!value) { + return node; } - return node; + return t.unaryExpression("!", node); } private ensureCreated(node?: t.Node) { From 8fa02eaf37438897d561005ca6d0dff88a2c4c5b Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 19:54:59 -0400 Subject: [PATCH 045/103] Dispatcher redefined/reassigned fix --- src/transforms/dispatcher.ts | 18 ++++++++++++++- test/transforms/dispatcher.test.ts | 36 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index 22e64df..c9282a0 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -13,7 +13,11 @@ import { } from "../utils/function-utils"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { numericLiteral } from "../utils/node"; -import { isStrictMode, prependProgram } from "../utils/ast-utils"; +import { + isStrictMode, + isVariableIdentifier, + prependProgram, +} from "../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Dispatcher, { @@ -77,6 +81,18 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Scan for function declarations blockPath.traverse({ + // Check for reassigned / redefined functions + BindingIdentifier: { + exit(path: NodePath) { + if (!isVariableIdentifier(path)) return; + + const name = path.node.name; + if (!path.parentPath?.isFunctionDeclaration()) { + illegalNames.add(name); + } + }, + }, + // Find functions eligible for dispatching FunctionDeclaration: { exit(path: NodePath) { const name = path.node.id.name; diff --git a/test/transforms/dispatcher.test.ts b/test/transforms/dispatcher.test.ts index 2a6c02c..df2c60a 100644 --- a/test/transforms/dispatcher.test.ts +++ b/test/transforms/dispatcher.test.ts @@ -526,3 +526,39 @@ test("Variant #20: Ignore functions with 'use strict' directive", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); + +test("Variant #21: Ignore reassigned & redefined functions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + // Redefining + var xyz = function () { + TEST_OUTPUT_1 = "Correct Value"; + }; + function xyz() { + TEST_OUTPUT_1 = "Incorrect Value"; + } + xyz(); + + // Reassigning + function abc(){ + TEST_OUTPUT_2 = "Incorrect Value"; + } + abc = function(){ + TEST_OUTPUT_2 = "Correct Value"; + } + abc(); + `, + { + target: "node", + dispatcher: true, + } + ); + + expect(code).not.toContain("dispatcher_0"); + + var TEST_OUTPUT_1, TEST_OUTPUT_2; + eval(code); + + expect(TEST_OUTPUT_1).toStrictEqual("Correct Value"); + expect(TEST_OUTPUT_2).toStrictEqual("Correct Value"); +}); From 524e91a8309f50b85d156d04c226af3e62d2e740 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 21:46:54 -0400 Subject: [PATCH 046/103] Update available features --- CHANGELOG.md | 2 -- src/presets.ts | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b70de..b5e795a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,6 @@ - Added Custom String Encoding and Custom Lock Code options -- Added `Function Outlining` Learn more here - - Added `Rename Labels` Learn more here - Added `Pack` Learn more here diff --git a/src/presets.ts b/src/presets.ts index 7ef7906..9c15fc7 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -40,7 +40,9 @@ const highPreset: ObfuscateOptions = { stringEncoding: true, stringSplitting: 0.75, astScrambler: true, - functionOutlining: false, + + // Experimental + // functionOutlining: false, // Security risks pack: true, From 79c0adb63ecf84fd2c5d5748795a8c805f20002d Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 21:47:18 -0400 Subject: [PATCH 047/103] Add tests, increase code coverage --- src/transforms/deadCode.ts | 15 +- src/transforms/flatten.ts | 5 +- src/transforms/functionOutlining.ts | 19 ++- src/transforms/minify.ts | 24 ++-- src/transforms/plugin.ts | 48 +++++-- test/transforms/deadCode.test.ts | 33 +++++ test/transforms/dispatcher.test.ts | 68 +++++++++ test/transforms/flatten.test.ts | 31 +++- test/transforms/functionOutlining.test.ts | 47 +++++- .../identifier/renameVariables.test.ts | 5 +- test/transforms/minify.test.ts | 136 ++++++++++++++---- test/transforms/preparation.test.ts | 43 ++++++ 12 files changed, 404 insertions(+), 70 deletions(-) diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index e7a8117..1f384e9 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -6,6 +6,7 @@ import { Order } from "../order"; import * as t from "@babel/types"; import Template from "../templates/template"; import { NameGen } from "../utils/NameGen"; +import { prepend } from "../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.DeadCode, { @@ -18,7 +19,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { return { visitor: { Block: { - exit(path) { + exit(blockPath) { + if (blockPath.find((p) => me.isSkipped(p))) return; + if (!computeProbabilityMap(me.options.deadCode)) { return; } @@ -40,7 +43,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { var containingFnName = me.getPlaceholder("dead_" + created); - var newPath = path.unshiftContainer( + var newPath = blockPath.unshiftContainer( "body", t.functionDeclaration( t.identifier(containingFnName), @@ -52,7 +55,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Overcomplicated way to get a random property name that doesn't exist on the Function var randomProperty: string; var nameGen = new NameGen("randomized"); + function PrototypeCollision() {} + PrototypeCollision(); // Call it for code coverage :D do { randomProperty = nameGen.generate(); @@ -63,8 +68,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { me.changeData.deadCode++; - path.unshiftContainer( - "body", + prepend( + blockPath, new Template(` if("${randomProperty}" in ${containingFnName}) { ${containingFnName}() @@ -72,7 +77,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { `).single() ); - path.stop(); + me.skip(blockPath); }, }, }, diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 0527d75..542680d 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -171,6 +171,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { typeofProps.set(identifierName, typeofProp); } + ensureComputedExpression(identifierPath.parentPath); + identifierPath.parentPath .replaceWith( t.memberExpression( @@ -180,7 +182,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { ) )[0] .skip(); - return; } else if (isFunctionCall) { let functionCallProp = functionCallProps.get(identifierName); if (!functionCallProp) { @@ -188,6 +189,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { functionCallProps.set(identifierName, functionCallProp); } + ensureComputedExpression(identifierPath); + // Replace identifier with a reference to the flat object property identifierPath .replaceWith( diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts index ee6eccf..b32764e 100644 --- a/src/transforms/functionOutlining.ts +++ b/src/transforms/functionOutlining.ts @@ -26,11 +26,11 @@ function isSafeForOutlining(path: NodePath): { if ( path.isReturnStatement() || + path.isYieldExpression() || + path.isAwaitExpression() || path.isContinueStatement() || path.isBreakStatement() || path.isThrowStatement() || - path.isYieldExpression() || - path.isAwaitExpression() || path.isDebuggerStatement() || path.isImportDeclaration() || path.isExportDeclaration() @@ -40,6 +40,7 @@ function isSafeForOutlining(path: NodePath): { var isSafe = true; var bindings: Binding[] = []; + var fnPath = path.getFunctionParent(); var visitor: Visitor = { ThisExpression(path) { @@ -58,6 +59,13 @@ function isSafeForOutlining(path: NodePath): { bindings.push(binding); } }, + // Function flow guard + "ReturnStatement|YieldExpression|AwaitExpression"(path) { + if (path.getFunctionParent() === fnPath) { + isSafe = false; + path.stop(); + } + }, }; // Exclude 'ThisExpression' and semantic 'Identifier' nodes @@ -100,8 +108,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Extract a random number of statements var statements = blockPath.get("body"); - var startIndex = getRandomInteger(0, statements.length); - var endIndex = getRandomInteger(startIndex, statements.length); + // var startIndex = getRandomInteger(0, statements.length); + // var endIndex = getRandomInteger(startIndex, statements.length); + + var startIndex = 0; + var endIndex = statements.length; var extractedStatements = statements.slice(startIndex, endIndex); if (!extractedStatements.length) return; diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index 5c0eec9..19b5090 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -8,12 +8,7 @@ import { isUndefined, } from "../utils/ast-utils"; import { Binding, Scope } from "@babel/traverse"; -import { - NodeSymbol, - placeholderVariablePrefix, - SKIP, - UNSAFE, -} from "../constants"; +import { NodeSymbol, placeholderVariablePrefix, UNSAFE } from "../constants"; const identifierMap = new Map t.Expression>(); identifierMap.set("undefined", () => @@ -288,7 +283,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (id.isIdentifier()) { // Do not remove variables in unsafe functions const fn = getParentFunctionOrProgram(path); - if ((fn as NodeSymbol)[UNSAFE]) return; + if ((fn.node as NodeSymbol)[UNSAFE]) return; const binding = path.scope.getBinding(id.node.name); @@ -453,11 +448,22 @@ export default ({ Plugin }: PluginArg): PluginObject => { !consequentReturn.returnPath && !alternateReturn.returnPath ) { + function joinExpressions(expressions: t.Expression[]) { + // condition?():() is invalid syntax + // Just use 0 as a placeholder + if (expressions.length === 0) return t.numericLiteral(0); + + // No need for sequence expression if there's only one expression + if (expressions.length === 1) return expressions[0]; + + return t.sequenceExpression(expressions); + } + path.replaceWith( t.conditionalExpression( path.node.test, - t.sequenceExpression(consequentReturn.expressions), - t.sequenceExpression(alternateReturn.expressions) + joinExpressions(consequentReturn.expressions), + joinExpressions(alternateReturn.expressions) ) ); } diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index fcea831..17aba97 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -63,11 +63,11 @@ export class PluginInstance { } } - isSkipped(path: NodePath | t.Node) { - let any = path as any; - let node = any.isNodeType ? any.node : any; - - return (node as NodeSymbol)[SKIP] === this.order; + /** + * Returns `true` if the given path has been skipped by this plugin. + */ + isSkipped(path: NodePath) { + return (path.node as NodeSymbol)[SKIP] === this.order; } private setFunctionLengthName: string; @@ -114,20 +114,20 @@ export class PluginInstance { } } + /** + * Returns a random string. + * + * Used for creating temporary variables names, typically before RenameVariables has ran. + * + * These long temp names will be converted to short, mangled names by RenameVariables. + */ getPlaceholder(suffix = "") { return "__p_" + getRandomString(4) + (suffix ? "_" + suffix : ""); } - log(...messages: any[]) { - if (this.options.verbose) { - console.log(`[${this.name}]`, ...messages); - } - } - - hasControlObject(blockPath: NodePath) { - return (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]?.length > 0; - } - + /** + * Retrieves (or creates) a `ControlObject` for the given `blockPath`. + */ getControlObject(blockPath: NodePath, createMultiple = true) { ok(blockPath.isBlock()); @@ -155,12 +155,30 @@ export class PluginInstance { return choice(controlObjects); } + /** + * Logs a message to the console, only if `verbose` is enabled. + * @param messages + */ + log(...messages: any[]) { + if (this.options.verbose) { + console.log(`[${this.name}]`, ...messages); + } + } + + /** + * Logs a warning to the console, only if `verbose` is enabled. + * @param messages + */ warn(...messages: any[]) { if (this.options.verbose) { console.log(`WARN [${this.name}]`, ...messages); } } + /** + * Throws an error with the given message. + * @param messages + */ error(...messages: any[]): never { throw new Error(`[${this.name}] ${messages.join(", ")}`); } diff --git a/test/transforms/deadCode.test.ts b/test/transforms/deadCode.test.ts index eba3280..8bde3e8 100644 --- a/test/transforms/deadCode.test.ts +++ b/test/transforms/deadCode.test.ts @@ -106,3 +106,36 @@ test("Variant #3: Custom probability function", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); + +test("Variant #4: Limit dead code by default", async () => { + var { code } = await JsConfuser.obfuscate( + ` + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + + { + TEST_OUTPUT = "Correct Value"; + } + + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + {};{};{};{};{};{};{};{};{};{};{};{}; + `, + { + target: "browser", + deadCode: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/dispatcher.test.ts b/test/transforms/dispatcher.test.ts index df2c60a..f621c22 100644 --- a/test/transforms/dispatcher.test.ts +++ b/test/transforms/dispatcher.test.ts @@ -562,3 +562,71 @@ test("Variant #21: Ignore reassigned & redefined functions", async () => { expect(TEST_OUTPUT_1).toStrictEqual("Correct Value"); expect(TEST_OUTPUT_2).toStrictEqual("Correct Value"); }); + +test("Variant #22: Custom implementation for Dispatcher", async () => { + const namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function transformFunction(){ + return "Correct Value"; + } + function preserveFunction(){ + TEST_OUTPUT = transformFunction(); + } + preserveFunction() + `, + { + target: "node", + dispatcher: (fnName) => { + namesCollected.push(fnName); + + return fnName === "transformFunction"; + }, + } + ); + + // Ensure custom implementation was called + expect(namesCollected).toStrictEqual([ + "transformFunction", + "preserveFunction", + ]); + + // Ensure dispatcher applied + expect(code).toContain("dispatcher_0"); + expect(code).not.toContain("transformFunction"); + + // Ensure preserveFunction was not obfuscated + expect(code).toContain("preserveFunction"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); + +test("Variant #23: Don't change async or generator functions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + async function myAsyncFunction(){} + function* myGeneratorFunction(){ + yield "Incorrect Value"; + TEST_OUTPUT = "Correct Value"; + } + + var iterator = myGeneratorFunction(); + TEST_OUTPUT = iterator.next().value; + + iterator.next(); + `, + { + target: "node", + dispatcher: true, + } + ); + + // Ensure code still works + var TEST_OUTPUT; + eval(code); + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/flatten.test.ts b/test/transforms/flatten.test.ts index 40140ae..fb43ea6 100644 --- a/test/transforms/flatten.test.ts +++ b/test/transforms/flatten.test.ts @@ -703,8 +703,13 @@ test("Variant #23: Reference original function name", async () => { test("Variant #24: Typeof expression", async () => { var { code: output } = await JsConfuser.obfuscate( ` + var outsideVar = "Is Defined"; + function myFunction(){ - TEST_OUTPUT = typeof nonExistentVariable === "undefined"; + TEST_OUTPUT = [ + typeof nonExistentVariable === "undefined", + typeof outsideVar !== "undefined" + ]; } myFunction(); @@ -717,7 +722,7 @@ test("Variant #24: Typeof expression", async () => { var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual([true, true]); }); test("Variant #25: Handle __JS_CONFUSER_VAR__ function", async () => { @@ -744,3 +749,25 @@ test("Variant #25: Handle __JS_CONFUSER_VAR__ function", async () => { eval(code); expect(TEST_OUTPUT).not.toBeUndefined(); }); + +test("Variant #26: Var declaration in nested block statement", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function myFunction(){ + if(true) { + var x = "Correct Value"; + } + TEST_OUTPUT = x; + } + + myFunction(); + `, + { target: "node", flatten: true } + ); + + expect(code).toContain("_flat_myFunction"); + + var TEST_OUTPUT; + eval(code); + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/functionOutlining.test.ts b/test/transforms/functionOutlining.test.ts index 3c48ac5..0ddfaef 100644 --- a/test/transforms/functionOutlining.test.ts +++ b/test/transforms/functionOutlining.test.ts @@ -29,7 +29,7 @@ test("Variant #2: Don't outline expressions with 'eval' 'this' or 'arguments'", function shouldNotOutline(){ var ten = eval("10"); if(this === expectedThis){ - return ten + arguments[0]; + return ten + arguments[0]; } } @@ -159,3 +159,48 @@ test("Variant #5: Handle direct invocations", async () => { expect(TEST_OUTPUT).toStrictEqual([true, true, true, true]); }); + +test("Variant #6: Handle return statements", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function fn1(){ + return true; + } + function fn2(){ + if(true) { + return true; + } + } + function fn3(){ + if(false) { + } else { + return true; + } + } + function fn4(){ + for(var i = 0; i < 10; i++){ + return true; + } + } + function fn5(){ + if(true) { + if(true) { + return true; + } + } + } + + TEST_OUTPUT = [fn1(), fn2(), fn3(), fn4(), fn5()]; + `, + { + target: "node", + functionOutlining: true, + } + ); + + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual([true, true, true, true, true]); +}); diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index 8926e8e..cdcc618 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -671,8 +671,9 @@ test("Variant #26: Transform __JS_CONFUSER_VAR__ to access variable mappings", a var myVar1 = "Correct Value"; TEST_OUTPUT = eval( __JS_CONFUSER_VAR__(myVar1) ); } - - myFunction(); + + // Work on functions too + eval( __JS_CONFUSER_VAR__(myFunction) + "()" ); // myFunction(); `, { target: "node", renameVariables: true } ); diff --git a/test/transforms/minify.test.ts b/test/transforms/minify.test.ts index 08d1cf0..a42600f 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -101,13 +101,42 @@ test("Variant #5: Work when shortening nested if-statements", async () => { var code = ` var a = false; var b = true; + + // This does nothing + if( b ) { b = true; } + if( !a ) {} else { a = false; } + if( false ) {} + if( true ) { b = b; } + if( false ) {} else { a = a; } + if( a ) { if ( b ) { } } else { - input(10) + TEST_OUTPUT[0] = true; + } + + function advanced(){ + var counter = 0; + var truthyValue = true; + if( truthyValue ) { + counter++; + return counter; + } else { + return; + } + } + + TEST_OUTPUT[1] = advanced() === 1; + + if ( true ) { + { + let shouldNotBeAccessible = true; + shouldNotBeAccessible = "Reassigned"; + } } + TEST_OUTPUT[2] = typeof shouldNotBeAccessible === "undefined"; `; var { code: output } = await JsConfuser.obfuscate(code, { @@ -117,12 +146,10 @@ test("Variant #5: Work when shortening nested if-statements", async () => { expect(output).not.toContain("=>"); - var value = "never_called", - input = (x) => (value = x); - + var TEST_OUTPUT = []; eval(output); - expect(value).toStrictEqual(10); + expect(TEST_OUTPUT).toStrictEqual([true, true, true]); }); test("Variant #8: Shorten simple array destructuring", async () => { @@ -293,40 +320,52 @@ test("Variant #11: Shorten 'Infinity' to 1/0", async () => { expect(output2).toContain("var x={[1/0]:1}"); }); -test("Variant #12: Shorten '!false' to '!0'", async () => { - // Valid - var { code: output } = await JsConfuser.obfuscate( - `var x = !false; TEST_OUTPUT = x;`, +test("Variant #12: Shorten pure logical not (!) unary expressions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var truthy = !false; + var falsy = !true; + + TEST_OUTPUT = [truthy, falsy]; + `, { target: "node", minify: true, } ); - expect(output).toContain("var x=!0"); + expect(code).not.toContain("!false"); + expect(code).not.toContain("!true"); + expect(code).not.toContain("!!"); - var TEST_OUTPUT; - eval(output); + var TEST_OUTPUT = []; + eval(code); - expect(TEST_OUTPUT).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual([true, false]); }); -test("Variant #13: Shorten 'false ? a : b' to 'b'", async () => { - // Valid - var { code: output } = await JsConfuser.obfuscate( - `var x = false ? 10 : 15; TEST_OUTPUT = x;`, +test("Variant #13: Remove deterministic conditional expressions", async () => { + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT_1 = true ? "Correct Value" : -1; + TEST_OUTPUT_2 = false ? -1 : "Correct Value"; + `, { target: "node", minify: true, } ); - expect(output).toContain("var x=15"); + // Ensure the conditional expressions were removed + expect(code).not.toContain("?"); - var TEST_OUTPUT; - eval(output); + var TEST_OUTPUT_1; + var TEST_OUTPUT_2; + + eval(code); - expect(TEST_OUTPUT).toStrictEqual(15); + expect(TEST_OUTPUT_1).toStrictEqual("Correct Value"); + expect(TEST_OUTPUT_2).toStrictEqual("Correct Value"); }); test("Variant #14: Shorten 'var x = undefined' to 'var x'", async () => { @@ -520,23 +559,33 @@ test("Variant #22: Properly handle Object constructor (Function Expression)", as }); test("Variant #23: Shorten property names and method names", async () => { - var { code: output } = await JsConfuser.obfuscate( + var { code } = await JsConfuser.obfuscate( ` var myObject = { "myKey": "Correct Value" }; var myClass = class { ["myMethod"](){ return "Correct Value" } } - TEST_OUTPUT = myObject.myKey === (new myClass()).myMethod(); + TEST_OUTPUT[0] = myObject.myKey; + TEST_OUTPUT[1] = (new myClass()).myMethod(); + + var myCustomObject = { "1": "Correct Value", "for": "Correct Value" } + TEST_OUTPUT[2] = myCustomObject[1]; + TEST_OUTPUT[3] = myCustomObject["for"]; `, { target: "node", minify: true } ); - expect(output).not.toContain("'myKey'"); - expect(output).not.toContain("'myMethod'"); + expect(code).not.toContain("'myKey'"); + expect(code).not.toContain("'myMethod'"); - var TEST_OUTPUT; - eval(output); + var TEST_OUTPUT = []; + eval(code); - expect(TEST_OUTPUT).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual([ + "Correct Value", + "Correct Value", + "Correct Value", + "Correct Value", + ]); }); test("Variant #24: Variable grouping in switch case", async () => { @@ -632,16 +681,31 @@ test("Variant #28: Don't break destructuring assignment", async () => { test("Variant #28: Remove unused variables", async () => { var { code } = await JsConfuser.obfuscate( ` - var x = "Incorrect Value"; - var y = "Correct Value"; + var correctValue; + function setCorrectValue(_unusedParameter){ + correctValue = "Correct Value"; + } + var _unusedValue = setCorrectValue(); + var _unusedString = "Incorrect Value"; + + var y = correctValue; TEST_OUTPUT = y; + + function unsafeFunction(){ + eval(" {}; ") + var keepMe = "eval() prevents removing this"; + } + + unsafeFunction(); `, { target: "node", minify: true } ); - expect(code).not.toContain("x"); + expect(code).not.toContain("_unusedValue"); expect(code).not.toContain("Incorrect Value"); + expect(code).toContain("keepMe"); + var TEST_OUTPUT; eval(code); @@ -715,6 +779,16 @@ test("Variant #30: Remove unreachable code after branches", async () => { default: return "Incorrect Value"; return "Should be removed"; + + + case "Nested Case": + switch(condition){ + default: + return "Incorrect Value"; + return "Should be removed"; + } + "Should be removed"; + return "Should be removed"; } "Should be removed"; diff --git a/test/transforms/preparation.test.ts b/test/transforms/preparation.test.ts index 6435620..81c7d07 100644 --- a/test/transforms/preparation.test.ts +++ b/test/transforms/preparation.test.ts @@ -132,6 +132,12 @@ test("Variant #7: Force Variable declarations to be expanded", async () => { function myFunction(){ var myFunctionVar1, myFunctionVar2, myFunctionVar3; } + + for(var myForVar1, myForVar2, myForVar3; ; ){ + break; + } + + export var myExportVar1, myExportVar2, myExportVar3; `, { target: "node", @@ -158,6 +164,16 @@ test("Variant #7: Force Variable declarations to be expanded", async () => { expect(output).toContain("var myIfVar1;"); expect(output).toContain("var myIfVar2;"); expect(output).toContain("var myIfVar3"); + + // Ensure the for-loop declarations got changed + expect(output).toContain("var myForVar1;"); + expect(output).toContain("var myForVar2;"); + expect(output).toContain("var myForVar3;"); + + // Ensure the export declarations got changed + expect(output).toContain("export var myExportVar1;"); + expect(output).toContain("export var myExportVar2;"); + expect(output).toContain("export var myExportVar3;"); }); test("Variant #8: Convert Regex Literals to `new RegExp()` constructor calls", async () => { @@ -221,3 +237,30 @@ test("Variant #9: Convert Template Literals into equivalent String Literal", asy expect(TEST_OUTPUT).toStrictEqual("Hello John Doe!"); }); + +test("Variant #10: Preserve Tagged Template Literal", async () => { + var { code } = await JsConfuser.obfuscate( + ` + // Define a tag function for syntax highlighting + function highlight(strings, ...values) { + return strings.reduce((result, string, i) => { + // Wrap the interpolated values in a styled span + const value = values[i] ? \`**\${values[i]}**\` : ''; + return result + string + value; + }, ''); + } + + TEST_OUTPUT = highlight\`Hello, \${ "Internet User" }!\`; + `, + { + target: "node", + compact: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + // Ensure the tagged template literal properly executed + expect(TEST_OUTPUT).toContain("Hello, **Internet User**!"); +}); From 6311c71c81765a7021026dc74bfbc4cd57a4a3fe Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 22:00:46 -0400 Subject: [PATCH 048/103] 2.0.0-alpha --- .npmignore | 2 +- README.md | 2 ++ src/options.ts | 21 ++++++++++++++++++++- src/probability.ts | 19 +------------------ 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.npmignore b/.npmignore index 0515624..083ce51 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,5 @@ # Decrease NPM bunlde size -src/ +src_old/ test/ docs/ babel.config.js diff --git a/README.md b/README.md index 0b744d5..1d2b39f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # JS Confuser +**⚠️ Warning: This an alpha release. This version is not stable and the likelihood of encountering bugs is significantly higher.** + JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ to read. [Try the web version](https://js-confuser.com). [![NPM](https://img.shields.io/badge/NPM-%23000000.svg?style=for-the-badge&logo=npm&logoColor=white)](https://npmjs.com/package/js-confuser) [![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/MichaelXF/js-confuser) [![Netlify](https://img.shields.io/badge/netlify-%23000000.svg?style=for-the-badge&logo=netlify&logoColor=#00C7B7)](https://js-confuser.com) diff --git a/src/options.ts b/src/options.ts index 802fbb1..6742872 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,6 +1,25 @@ -import { ProbabilityMap } from "./probability"; import Template from "./templates/template"; +// JS-Confuser.com imports this file for Type support, therefore some additional types are included here. + +type Stringed = (V extends string ? V : never) | "true" | "false"; + +/** + * Configurable probabilities for obfuscator options. + * - **`false`** = this feature is disabled + * - **`true`** = this feature is enabled, use default mode + * - **`0.5`** = 50% chance + * - **`"mode"`** = enabled, use specified mode + * - **`["mode1", "mode2"]`** - enabled, choose random mode each occurrence + * - **`{"mode1": 0.5, "mode2": 0.5}`** - enabled, choose based on specified probabilities + * - **`{"mode1": 50, "mode2": 50}`** - enabled, each is divided based on total + * - **`function(x){ return "custom_implementation" }`** - enabled, use specified function + */ +export type ProbabilityMap< + T, + F extends (...args: any[]) => any = () => boolean // Default to a generic function +> = false | true | number | T | T[] | { [key in Stringed]?: number } | F; + export interface CustomLock { /** * Template lock code that must contain: diff --git a/src/probability.ts b/src/probability.ts index f57dbb5..35dcaf1 100644 --- a/src/probability.ts +++ b/src/probability.ts @@ -1,23 +1,6 @@ import { ok } from "assert"; import { createObject } from "./utils/object-utils"; - -type Stringed = (V extends string ? V : never) | "true" | "false"; - -/** - * Configurable probabilities for obfuscator options. - * - **`false`** = this feature is disabled - * - **`true`** = this feature is enabled, use default mode - * - **`0.5`** = 50% chance - * - **`"mode"`** = enabled, use specified mode - * - **`["mode1", "mode2"]`** - enabled, choose random mode each occurrence - * - **`{"mode1": 0.5, "mode2": 0.5}`** - enabled, choose based on specified probabilities - * - **`{"mode1": 50, "mode2": 50}`** - enabled, each is divided based on total - * - **`function(x){ return "custom_implementation" }`** - enabled, use specified function - */ -export type ProbabilityMap< - T, - F extends (...args: any[]) => any = () => boolean // Default to a generic function -> = false | true | number | T | T[] | { [key in Stringed]?: number } | F; +import { ProbabilityMap } from "./options"; /** * Evaluates a ProbabilityMap. From 846d23baa2efd70d6f28e310fafb86b8cfb81cfb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 22:07:42 -0400 Subject: [PATCH 049/103] Tweak package.json --- package.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b5f478b..702a5e9 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "js-confuser", - "version": "2.0.0-alpha", + "version": "2.0.0-alpha.0", "description": "JavaScript Obfuscation Tool.", - "main": "index.js", + "main": "dist/index.js", + "types": "index.d.ts", "scripts": { "build": "tsc && babel src --out-dir dist --extensions \".ts,.tsx\"", "dev": "node babel.register.js", @@ -51,5 +52,8 @@ "bugs": { "url": "https://github.com/MichaelXF/js-confuser/issues" }, - "homepage": "https://js-confuser.com" + "homepage": "https://js-confuser.com", + "engines": { + "node": ">=12.0.0" + } } From 78fc46c17d60ae117429266048a3fa27896658cd Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 22:34:54 -0400 Subject: [PATCH 050/103] Remove `@babel/core` --- src/obfuscator.ts | 21 +++++++++---------- src/transforms/astScrambler.ts | 2 +- src/transforms/extraction/objectExtraction.ts | 2 +- src/transforms/flatten.ts | 2 +- src/transforms/functionOutlining.ts | 2 +- src/transforms/identifier/globalConcealing.ts | 2 +- .../identifier/movedDeclarations.ts | 2 +- src/transforms/identifier/renameVariables.ts | 2 +- src/transforms/lock/lock.ts | 2 +- src/transforms/minify.ts | 2 +- src/transforms/plugin.ts | 2 +- src/transforms/renameLabels.ts | 2 +- src/transforms/rgf.ts | 2 +- src/transforms/shuffle.ts | 2 +- src/transforms/string/stringConcealing.ts | 2 +- src/utils/ast-utils.ts | 2 +- 16 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/obfuscator.ts b/src/obfuscator.ts index a9c32fb..0d86565 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -1,7 +1,8 @@ import { ok } from "assert"; -import * as babel from "@babel/core"; +import * as t from "@babel/types"; import generate from "@babel/generator"; -import { Node, Statement } from "@babel/types"; +import traverse from "@babel/traverse"; +import { parse } from "@babel/parser"; import { ObfuscateOptions } from "./options"; import { applyDefaultsToOptions, validateOptions } from "./validateOptions"; import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; @@ -62,14 +63,14 @@ export default class Obfuscator { sensitivityRegex: / |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g, }, - createCountermeasuresCode: (): Statement[] => { + createCountermeasuresCode: (): t.Statement[] => { throw new Error("Not implemented"); }, }, // After RenameVariables completes, this map will contain the renamed variables // Most use cases involve grabbing the Program(global) mappings - renamedVariables: new Map>(), + renamedVariables: new Map>(), // Internal functions, should not be renamed/removed internals: { @@ -136,7 +137,7 @@ export default class Obfuscator { return this.globalState.internals.stringCompressionLibraryName; } - getObfuscatedVariableName(originalName: string, programNode: Node) { + getObfuscatedVariableName(originalName: string, programNode: t.Node) { const renamedVariables = this.globalState.renamedVariables.get(programNode); return renamedVariables?.get(originalName) || originalName; @@ -268,7 +269,7 @@ export default class Obfuscator { ); } - babel.traverse(ast, plugin.visitor); + traverse(ast, plugin.visitor); plugin.post?.(); if (plugin.finalASTHandler) { @@ -317,14 +318,14 @@ export default class Obfuscator { /** * Calls `Obfuscator.generateCode` with the current instance options */ - generateCode(ast: T): string { + generateCode(ast: T): string { return Obfuscator.generateCode(ast, this.options); } /** * Generates code from an AST using `@babel/generator` */ - static generateCode( + static generateCode( ast: T, options: ObfuscateOptions = DEFAULT_OPTIONS ): string { @@ -347,10 +348,8 @@ export default class Obfuscator { */ static parseCode(sourceCode: string): babel.types.File { // Parse the source code into an AST - let ast = babel.parseSync(sourceCode, { + let ast = parse(sourceCode, { sourceType: "unambiguous", - babelrc: false, - configFile: false, }); return ast; diff --git a/src/transforms/astScrambler.ts b/src/transforms/astScrambler.ts index 3751d34..565f38a 100644 --- a/src/transforms/astScrambler.ts +++ b/src/transforms/astScrambler.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { ok } from "assert"; diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index afaf7b1..f3b0862 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import * as t from "@babel/types"; diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 542680d..4a09a9e 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -1,5 +1,5 @@ import * as t from "@babel/types"; -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { ensureComputedExpression, getFunctionName, diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts index b32764e..b5d1961 100644 --- a/src/transforms/functionOutlining.ts +++ b/src/transforms/functionOutlining.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { ensureComputedExpression, prepend } from "../utils/ast-utils"; diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index bea94bc..8618985 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -1,5 +1,5 @@ import * as t from "@babel/types"; -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; import { PluginArg, PluginObject } from "../plugin"; diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index 22cf81b..8e248fc 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { Order } from "../../order"; import { PluginArg, PluginObject } from "../plugin"; import { NodeSymbol, PREDICTABLE } from "../../constants"; diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 9d83392..d27e4fd 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { Visitor } from "@babel/traverse"; import { PluginArg, PluginObject } from "../plugin"; import * as t from "@babel/types"; diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index fae208c..b1d7b78 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { chance, choice } from "../../utils/random-utils"; diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index 19b5090..b7f4177 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 17aba97..775f65e 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -1,4 +1,4 @@ -import { NodePath, PluginObj, Visitor } from "@babel/core"; +import { NodePath, Visitor } from "@babel/traverse"; import Obfuscator from "../obfuscator"; import { chance, choice, getRandomString } from "../utils/random-utils"; import { Order } from "../order"; diff --git a/src/transforms/renameLabels.ts b/src/transforms/renameLabels.ts index 4bec38d..a8cd92e 100644 --- a/src/transforms/renameLabels.ts +++ b/src/transforms/renameLabels.ts @@ -1,5 +1,5 @@ import * as t from "@babel/types"; -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { NameGen } from "../utils/NameGen"; diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 0d04003..5707da2 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import * as t from "@babel/types"; diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index fa9657b..147884a 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -1,4 +1,4 @@ -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { computeProbabilityMap } from "../probability"; diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index d994535..eee60b0 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -1,5 +1,5 @@ import * as t from "@babel/types"; -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import Template from "../../templates/template"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 5fc1f89..15351ba 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -1,5 +1,5 @@ import * as t from "@babel/types"; -import { NodePath } from "@babel/core"; +import { NodePath } from "@babel/traverse"; import { ok } from "assert"; import { deepClone } from "./node"; From 853137cff0d1024f6c8dcee457d199792c34804e Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 18 Sep 2024 22:35:01 -0400 Subject: [PATCH 051/103] 2.0.0-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 702a5e9..93551e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-confuser", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", From 3c99c89ad549ba0805347ac8b648c064501f0fbb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Thu, 19 Sep 2024 15:05:02 -0400 Subject: [PATCH 052/103] 2.0.0-alpha.2 --- package.json | 4 ++-- src/options.ts | 2 +- src/templates/stringCompressionTemplate.ts | 4 +++- src/validateOptions.ts | 12 ++++++------ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 93551e6..b35e6ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-confuser", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", @@ -9,7 +9,7 @@ "dev": "node babel.register.js", "test": "jest --forceExit", "test:coverage": "jest --coverage --coverageReporters=html", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run test && npm run build" }, "keywords": [ "obfuscator", diff --git a/src/options.ts b/src/options.ts index 6742872..8ef3bd1 100644 --- a/src/options.ts +++ b/src/options.ts @@ -144,7 +144,7 @@ export interface ObfuscateOptions { globalConcealing?: ProbabilityMap boolean>; /** - * String Compression uses LZW's compression algorithm to compress strings. + * String Compression uses zlib compression algorithm to compress strings. * * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` */ diff --git a/src/templates/stringCompressionTemplate.ts b/src/templates/stringCompressionTemplate.ts index bd05b0b..73fe033 100644 --- a/src/templates/stringCompressionTemplate.ts +++ b/src/templates/stringCompressionTemplate.ts @@ -8,7 +8,9 @@ var {stringFn}; {GetGlobalTemplate} var __globalObject = {getGlobalFnName}() || {}; - var _atob = __globalObject["atob"]; + + // Direct invocation of atob() is not allowed in some environments + var _atob = (s) => __globalObject["atob"](s); var _Uint8Array = __globalObject["Uint8Array"]; function convertBase64ToArrayBuffer(base64) { diff --git a/src/validateOptions.ts b/src/validateOptions.ts index cbf3e11..3912c42 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -5,11 +5,9 @@ import presets from "./presets"; const validProperties = new Set([ "preset", "target", - "indent", "compact", "hexadecimalNumbers", "minify", - "es5", "renameVariables", "renameGlobals", "renameLabels", @@ -158,10 +156,6 @@ export function applyDefaultsToOptions( options.renameLabels = true; // RenameLabels is on by default } - if (options.globalVariables && !(options.globalVariables instanceof Set)) { - options.globalVariables = new Set(Object.keys(options.globalVariables)); - } - if (options.lock) { ok(typeof options.lock === "object", "options.lock must be an object"); @@ -249,6 +243,12 @@ export function applyDefaultsToOptions( "WeakSet", "WeakMap", "Symbol", + "TextDecoder", + "TextEncoder", + "Uint8Array", + "Uint16Array", + "Uint32Array", + "ArrayBuffer", ].forEach((x) => options.globalVariables.add(x)); } From a5af6b94488d7a1c2d32a99e3e6d0396483b3e8a Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 22 Sep 2024 03:49:29 -0400 Subject: [PATCH 053/103] Minify concatenate strings, don't remove String Encoding functions --- src/constants.ts | 6 + src/index.ts | 1 + src/options.ts | 2 +- src/templates/template.ts | 8 +- src/transforms/minify.ts | 26 +++- src/transforms/pack.ts | 4 + src/transforms/string/stringConcealing.ts | 15 ++- src/utils/ast-utils.ts | 12 +- src/validateOptions.ts | 11 +- test/transforms/minify.test.ts | 22 ++++ test/transforms/pack.test.ts | 28 +++++ .../string/customStringEncoding.test.ts | 114 +++++++++++++++++- 12 files changed, 228 insertions(+), 21 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 4f4d18e..d57a704 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -58,6 +58,11 @@ export const MULTI_TRANSFORM = Symbol("multiTransform"); */ export const WITH_STATEMENT = Symbol("withStatement"); +/** + * Tells minify to not remove the node. + */ +export const NO_REMOVE = Symbol("noRemove"); + /** * Symbols describe precomputed semantics of a node, allowing the obfuscator to make the best choices for the node. */ @@ -72,6 +77,7 @@ export interface NodeSymbol { [GEN_NODE]?: boolean; [MULTI_TRANSFORM]?: boolean; [WITH_STATEMENT]?: boolean; + [NO_REMOVE]?: boolean; } /** diff --git a/src/index.ts b/src/index.ts index 597f539..147b4f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,3 +101,4 @@ const JsConfuser = { }; export default JsConfuser; +export { presets, Template }; diff --git a/src/options.ts b/src/options.ts index 8ef3bd1..14ec046 100644 --- a/src/options.ts +++ b/src/options.ts @@ -375,7 +375,7 @@ export interface ObfuscateOptions { * * Designed to escape strict mode constraints. */ - pack?: boolean; + pack?: ProbabilityMap boolean>; /** * Set of global variables. *Optional*. diff --git a/src/templates/template.ts b/src/templates/template.ts index 1f38d7c..a4ea1e0 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -27,7 +27,7 @@ export default class Template { private requiredVariables: Set; private astVariableMappings: Map; private astIdentifierPrefix = "__t_" + getRandomString(6); - private symbols: NodeSymbolKeys[] = []; + private symbols = new Set(); constructor(...templates: string[]) { this.templates = templates; @@ -38,7 +38,9 @@ export default class Template { } addSymbols(...symbols: NodeSymbolKeys[]): this { - this.symbols.push(...symbols); + symbols.forEach((symbol) => { + this.symbols.add(symbol); + }); return this; } @@ -174,7 +176,7 @@ export default class Template { this.interpolateAST(file, variables); - if (this.symbols.length > 0) { + if (this.symbols.size > 0) { file.program.body.forEach((node) => { for (const symbol of this.symbols) { node[symbol] = true; diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index b7f4177..edb8e6e 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -8,7 +8,12 @@ import { isUndefined, } from "../utils/ast-utils"; import { Binding, Scope } from "@babel/traverse"; -import { NodeSymbol, placeholderVariablePrefix, UNSAFE } from "../constants"; +import { + NO_REMOVE, + NodeSymbol, + placeholderVariablePrefix, + UNSAFE, +} from "../constants"; const identifierMap = new Map t.Expression>(); identifierMap.set("undefined", () => @@ -120,6 +125,19 @@ export default ({ Plugin }: PluginArg): PluginObject => { } }, }, + // "a" + "b" -> "ab" + BinaryExpression: { + exit(path) { + if (path.node.operator !== "+") return; + + const left = path.get("left"); + const right = path.get("right"); + + if (!left.isStringLiteral() || !right.isStringLiteral()) return; + + path.replaceWith(t.stringLiteral(left.node.value + right.node.value)); + }, + }, // a["key"] -> a.key MemberExpression: { exit(path) { @@ -215,7 +233,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { const id = path.get("id"); if ( id.isIdentifier() && - !id.node.name.startsWith(placeholderVariablePrefix) + !id.node.name.startsWith(placeholderVariablePrefix) && + !(path.node as NodeSymbol)[NO_REMOVE] ) { const binding = path.scope.getBinding(id.node.name); if ( @@ -285,6 +304,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { const fn = getParentFunctionOrProgram(path); if ((fn.node as NodeSymbol)[UNSAFE]) return; + // Node explicitly marked as not to be removed + if ((id as NodeSymbol)[NO_REMOVE]) return; + const binding = path.scope.getBinding(id.node.name); if ( diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index 824dc43..0d4b488 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -15,6 +15,7 @@ import { } from "../constants"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; +import { computeProbabilityMap } from "../probability"; export default function pack({ Plugin }: PluginArg): PluginObject { const me = Plugin(Order.Pack, { @@ -58,6 +59,9 @@ export default function pack({ Plugin }: PluginArg): PluginObject { if (!path.scope.hasGlobal(identifierName)) return; + // Check user's custom implementation + if (!computeProbabilityMap(me.options.pack, identifierName)) return; + if ( path.key === "argument" && path.parentPath.isUnaryExpression({ operator: "typeof" }) diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index eee60b0..e564fdb 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -22,6 +22,7 @@ import { import { CustomStringEncoding } from "../../options"; import { createDefaultStringEncoding } from "./encoding"; import { numericLiteral } from "../../utils/node"; +import { NO_REMOVE } from "../../constants"; interface StringConcealingInterface { encodingImplementation: CustomStringEncoding; @@ -271,17 +272,21 @@ export default ({ Plugin }: PluginArg): PluginObject => { ok(encodingImplementation.code instanceof Template); // The decoder function - const decoder = encodingImplementation.code.compile({ - fnName: decodeFnName, - __bufferToStringFunction__: bufferToStringName, - }); + const decoder = encodingImplementation.code + .addSymbols(NO_REMOVE) + .compile({ + fnName: decodeFnName, + __bufferToStringFunction__: bufferToStringName, + }); // The main function to get the string value const retrieveFunctionDeclaration = new Template(` function ${fnName}(index) { return ${decodeFnName}(${stringArrayName}[index]); } - `).single(); + `) + .addSymbols(NO_REMOVE) + .single(); prepend(block, [...decoder, retrieveFunctionDeclaration]); } diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 15351ba..861e38b 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -317,12 +317,12 @@ export function prepend( ); if (lastImportIndex === 0 || lastImportIndex === -1) { - // No non-import declarations, so we can safely unshift everything + // No import declarations, so we can safely unshift everything return registerPaths(listParent.unshiftContainer("body", nodes)); - } else { - // Insert the nodes after the last import declaration - return registerPaths(body[lastImportIndex - 1].insertAfter(nodes)); } + + // Insert the nodes after the last import declaration + return registerPaths(body[lastImportIndex - 1].insertAfter(nodes)); } if (listParent.isFunction()) { @@ -343,7 +343,9 @@ export function prepend( if (listParent.isBlock()) { return registerPaths(listParent.unshiftContainer("body", nodes)); - } else if (listParent.isSwitchCase()) { + } + + if (listParent.isSwitchCase()) { return registerPaths(listParent.unshiftContainer("consequent", nodes)); } diff --git a/src/validateOptions.ts b/src/validateOptions.ts index 3912c42..635691d 100644 --- a/src/validateOptions.ts +++ b/src/validateOptions.ts @@ -190,10 +190,6 @@ export function applyDefaultsToOptions( "alert", "confirm", "location", - "btoa", - "atob", - "unescape", - "encodeURIComponent", ].forEach((x) => options.globalVariables.add(x)); } else { // node @@ -248,7 +244,14 @@ export function applyDefaultsToOptions( "Uint8Array", "Uint16Array", "Uint32Array", + "Int8Array", + "Int16Array", + "Int32Array", "ArrayBuffer", + "btoa", + "atob", + "unescape", + "encodeURIComponent", ].forEach((x) => options.globalVariables.add(x)); } diff --git a/test/transforms/minify.test.ts b/test/transforms/minify.test.ts index a42600f..5dda25f 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -876,3 +876,25 @@ test("Variant #32: Work with Eval calls", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); + +test("Variant #33: Fold string concatenation", async () => { + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = "Correct" + " " + "Value" + `, + { + target: "node", + minify: true, + } + ); + + // Ensure the string concatenation was folded + expect(code).toContain("Correct Value"); + expect(code).not.toContain("+"); + + // Ensure code still works + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/pack.test.ts b/test/transforms/pack.test.ts index 3c13c3a..9193147 100644 --- a/test/transforms/pack.test.ts +++ b/test/transforms/pack.test.ts @@ -47,3 +47,31 @@ test("Variant #2: Handle import statements", async () => { "1cac63f39fd68d8c531f27b807610fb3d50f0fc3f186995767fb6316e7200a3e" ); }); + +test("Variant #3: Allow custom implementation to preserve globals", async () => { + var globalsCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = atob("SGVsbG8gV29ybGQ=") + `, + { + target: "node", + pack: (name) => { + globalsCollected.push(name); + if (name === "atob") return false; + return true; + }, + globalVariables: new Set([]), + } + ); + + expect(globalsCollected).toContain("atob"); + expect(code).toContain("Function"); + expect(code).toContain("atob("); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Hello World"); +}); diff --git a/test/transforms/string/customStringEncoding.test.ts b/test/transforms/string/customStringEncoding.test.ts index 63ce6b7..e92c20b 100644 --- a/test/transforms/string/customStringEncoding.test.ts +++ b/test/transforms/string/customStringEncoding.test.ts @@ -163,7 +163,119 @@ var {fnName} = (str) => decode(str, {shuffledCharset}); } }); -test("Variant #3: Skip strings that fail to decode", async () => { +test("Variant #4: Custom Randomized Base64 encoding on High Preset", async () => { + function createCustomStringEncoding(): CustomStringEncoding { + function encode(input, charset) { + const inputBuffer = new TextEncoder().encode(input); + let output = ""; + + for (let i = 0; i < inputBuffer.length; i += 3) { + const chunk = [inputBuffer[i], inputBuffer[i + 1], inputBuffer[i + 2]]; + + const binary = (chunk[0] << 16) | (chunk[1] << 8) | (chunk[2] || 0); + + output += charset[(binary >> 18) & 0x3f]; + output += charset[(binary >> 12) & 0x3f]; + output += + typeof chunk[1] !== "undefined" ? charset[(binary >> 6) & 0x3f] : "="; + output += + typeof chunk[2] !== "undefined" ? charset[binary & 0x3f] : "="; + } + + return output; + } + + const customCharset = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const shuffledCharset = shuffle(customCharset.split("")).join(""); + + return { + code: new Template(` + // Creates a reverse lookup table from the given charset + function createReverseCharset(charset) { + if (charset.length !== 64) { + throw new Error("Charset must be exactly 64 characters long."); + } + const reverseCharset = {}; + for (let i = 0; i < charset.length; i++) { + reverseCharset[charset[i]] = i; + } + return reverseCharset; + } + +function decode(input, charset) { + const reverseCharset = createReverseCharset(charset); + const cleanedInput = input.replace(/=+$/, ''); // Remove padding + + const byteArray = []; + let buffer = 0; + let bitsCollected = 0; + + for (let i = 0; i < cleanedInput.length; i++) { + buffer = (buffer << 6) | reverseCharset[cleanedInput[i]]; + bitsCollected += 6; + + if (bitsCollected >= 8) { + bitsCollected -= 8; + byteArray.push((buffer >> bitsCollected) & 0xFF); + } + } + + // Convert to string, ensuring no extra characters + return new TextDecoder().decode(Uint8Array.from(byteArray)); +} + +var {fnName} = (str) => decode(str, {shuffledCharset}); + + `).setDefaultVariables({ + shuffledCharset: stringLiteral(shuffledCharset), + }), + encode: (input) => { + return encode(input, shuffledCharset); + }, + + // Identity key to help distinguish between different variants + identity: shuffledCharset, + }; + } + + var sourceCode = ` + TEST_OUTPUT = [ + "Hello World! (1)", + "Hello World! (2)", + "Hello World! (3)", + "Hello World! (4)", + "Hello World! (5)", + "Hello World! (6)", + "Hello World! (7)", + "Hello World! (8)", + "Hello World! (9)", + "Hello World! (10)" + ] + `; + + var { code } = await JsConfuser.obfuscate(sourceCode, { + target: "node", + stringConcealing: true, + customStringEncodings: [createCustomStringEncoding], + + minify: true, + renameVariables: false, + }); + + var TEST_OUTPUT; + eval(code); + + expect(Array.isArray(TEST_OUTPUT)).toStrictEqual(true); + + for (var i = 1; i <= 10; i++) { + var testString = `Hello World! (${i})`; + expect(code).not.toContain(testString); + expect(TEST_OUTPUT).toContain(testString); + } +}); + +test("Variant #4: Skip strings that fail to decode", async () => { var stringsCollected: string[] = []; var customStringEncoding: CustomStringEncoding = { From 70e0cc8f206dbccf49126ee39b0b245c7081691d Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 23 Sep 2024 00:45:41 -0400 Subject: [PATCH 054/103] Control Flow Flattening scoping fix --- src/transforms/controlFlowFlattening.ts | 2 +- .../controlFlowFlattening.test.ts | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 7b81acc..d9b44fb 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1081,7 +1081,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const identifierName = path.node.name; if (identifierName === gotoFunctionName) return; - var binding = basicBlock.scope.getBinding(identifierName); + var binding = path.scope.getBinding(identifierName); if (!binding) { return; } diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 1d4e52d..98b541d 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -1429,3 +1429,37 @@ TEST_OUTPUT = indexOf("Hello World", "World"); expect(TEST_OUTPUT).toStrictEqual(6); }); + +test("Variant #40: Shadow variable in for-loop / catch clause", async () => { + var { code } = await JsConfuser.obfuscate( + ` + let i = "Correct Value"; + + function doLoop(){ + for(let i = 0; i < 10; i++) { + i = "Incorrect Value 1"; + break; + } + try { + throw new Error(); + } catch(i) { + i = "Incorrect Value 2"; + } + } + + doLoop(); + + TEST_OUTPUT = i; + `, + { + target: "node", + controlFlowFlattening: true, + pack: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); From cc1f1da5f0ad50aae624f5732b94de7410d0f8d4 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 27 Sep 2024 20:35:05 -0400 Subject: [PATCH 055/103] Add Date Lock warning, edit comments --- src/transforms/lock/lock.ts | 12 +++++++++++- src/transforms/string/stringCompression.ts | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index b1d7b78..388ca9d 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -36,6 +36,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { }); if (me.options.lock.startDate instanceof Date) { + // Ensure date is in the past + if (me.options.lock.startDate.getTime() > Date.now()) { + me.warn("lock.startDate is detected to be in the future"); + } + me.options.lock.customLocks.push({ code: [ ` @@ -54,6 +59,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { } if (me.options.lock.endDate instanceof Date) { + // Ensure date is in the future + if (me.options.lock.endDate.getTime() < Date.now()) { + me.warn("lock.endDate is detected to be in the past"); + } + me.options.lock.customLocks.push({ code: [ ` @@ -127,7 +137,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const timesMap = new WeakMap(); let countermeasuresNode: NodePath; - let invokeCountermeasuresFnName; + let invokeCountermeasuresFnName: string; if (me.options.lock.countermeasures) { invokeCountermeasuresFnName = me.getPlaceholder("invokeCountermeasures"); diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index d9f3cd9..f109fc4 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -103,7 +103,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { ); if (insertStringCompressionLibrary) { - // RGF function should also clone the entire decompression function + // RGF functions should not clone the entire decompression function prependProgram( programPath, Obfuscator.parseCode(PakoInflateMin.replace(/{pako}/g, pakoName)) From 94b6213d5eb3d3eb933fd57f58f9dac8ac811af1 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 28 Oct 2024 01:09:45 -0400 Subject: [PATCH 056/103] Use prepend instead --- src/transforms/deadCode.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index 1f384e9..c9a3b27 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -43,8 +43,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { var containingFnName = me.getPlaceholder("dead_" + created); - var newPath = blockPath.unshiftContainer( - "body", + // Insert dummy function + prepend( + blockPath, + t.functionDeclaration( t.identifier(containingFnName), [], From 5b98237f483ed83f54e0bb6529da445a758b1404 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 28 Oct 2024 01:10:12 -0400 Subject: [PATCH 057/103] Small pack fix --- src/transforms/pack.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index 0d4b488..b2e5165 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -56,6 +56,7 @@ export default function pack({ Plugin }: PluginArg): PluginObject { if (reservedIdentifiers.has(identifierName)) return; if (me.obfuscator.options.globalVariables.has(identifierName)) return; if (identifierName === variableFunctionName) return; + if (identifierName === objectName) return; if (!path.scope.hasGlobal(identifierName)) return; From 764d28592ac52e69c36af6535bbfc34fbd35ce67 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 28 Oct 2024 01:10:42 -0400 Subject: [PATCH 058/103] Improve prepend function for Import Declarations --- src/utils/ast-utils.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 861e38b..81feda5 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -312,17 +312,21 @@ export function prepend( // Preserve import declarations // Filter out import declarations const body = listParent.get("body"); - const lastImportIndex = body.findIndex( - (path) => !path.isImportDeclaration() - ); + let afterImport = 0; + for (var stmt of body) { + if (!stmt.isImportDeclaration()) { + break; + } + afterImport++; + } - if (lastImportIndex === 0 || lastImportIndex === -1) { + if (afterImport === 0) { // No import declarations, so we can safely unshift everything return registerPaths(listParent.unshiftContainer("body", nodes)); } // Insert the nodes after the last import declaration - return registerPaths(body[lastImportIndex - 1].insertAfter(nodes)); + return registerPaths(body[afterImport - 1].insertAfter(nodes)); } if (listParent.isFunction()) { @@ -358,6 +362,7 @@ export function prependProgram( ) { var program = path.find((p) => p.isProgram()); ok(program); + ok(program.isProgram()); return prepend(program, ...nodes); } From 5ef225238acab4d99c4c46d754073df208765fc0 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 28 Oct 2024 01:11:46 -0400 Subject: [PATCH 059/103] Preserve module import strings --- src/transforms/extraction/duplicateLiteralsRemoval.ts | 11 ++++++++++- src/transforms/string/stringCompression.ts | 4 ++++ src/transforms/string/stringEncoding.ts | 4 ++++ src/transforms/string/stringSplitting.ts | 10 ++++++++-- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index efcac8a..0658328 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -2,7 +2,11 @@ import * as t from "@babel/types"; import { ok } from "assert"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; -import { ensureComputedExpression, prepend } from "../../utils/ast-utils"; +import { + ensureComputedExpression, + isModuleImport, + prepend, +} from "../../utils/ast-utils"; import { createLiteral, LiteralValue, numericLiteral } from "../../utils/node"; import { NodePath } from "@babel/traverse"; @@ -49,6 +53,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { t.Literal | t.Identifier >; + // Don't change module imports + if (literalPath.isStringLiteral()) { + if (isModuleImport(literalPath)) return; + } + let node = literalPath.node; var isUndefined = false; if (literalPath.isIdentifier()) { diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index f109fc4..e6541f8 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -4,6 +4,7 @@ import { Order } from "../../order"; import { computeProbabilityMap } from "../../probability"; import { ensureComputedExpression, + isModuleImport, prependProgram, } from "../../utils/ast-utils"; import { numericLiteral } from "../../utils/node"; @@ -36,6 +37,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { programPath.traverse({ StringLiteral: { exit: (path) => { + // Don't change module imports + if (isModuleImport(path)) return; + const originalValue = path.node.value; // Must be at least 3 characters long diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index c5a7bce..280e21f 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -3,6 +3,7 @@ import * as t from "@babel/types"; import { choice } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; import { GEN_NODE, NodeSymbol } from "../../constants"; +import { isModuleImport } from "../../utils/ast-utils"; function pad(x: string, len: number): string { while (x.length < len) { @@ -51,6 +52,9 @@ export default (me: PluginInstance): PluginObject => { visitor: { StringLiteral: { exit(path) { + // Ignore module imports + if (isModuleImport(path)) return; + const { value } = path.node; // Allow percentages diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 7a5a837..5f9acca 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -1,10 +1,13 @@ -import { PluginArg, PluginInstance, PluginObject } from "../plugin"; +import { PluginArg, PluginObject } from "../plugin"; import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; import { computeProbabilityMap } from "../../probability"; import { binaryExpression, stringLiteral } from "@babel/types"; import { ok } from "assert"; import { Order } from "../../order"; -import { ensureComputedExpression } from "../../utils/ast-utils"; +import { + ensureComputedExpression, + isModuleImport, +} from "../../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.StringSplitting, { @@ -19,6 +22,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { exit(path) { var object = path.node; + // Don't change module imports + if (isModuleImport(path)) return; + var size = Math.round( Math.max(6, object.value.length / getRandomInteger(3, 8)) ); From db7e0a7003442f42e8b4cf8bf60937efbc191314 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 28 Oct 2024 01:12:07 -0400 Subject: [PATCH 060/103] Code refactoring --- .../identifier/movedDeclarations.ts | 16 +++- src/transforms/identifier/renameVariables.ts | 9 ++- src/transforms/plugin.ts | 73 ++++++++++--------- test/semantics/moduleImport.test.ts | 51 +++++++++++++ 4 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 test/semantics/moduleImport.test.ts diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index 8e248fc..f9835ee 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -113,6 +113,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, VariableDeclaration: { exit(path) { + if (me.isSkipped(path)) return; if (path.node.kind !== "var") return; if (path.node.declarations.length !== 1) return; @@ -150,7 +151,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { let isDefinedAtTop = false; const parentPath = path.parentPath; if (parentPath.isBlock()) { - isDefinedAtTop = parentPath.get("body").indexOf(path) === 0; + isDefinedAtTop = + parentPath + .get("body") + .filter((x) => x.type !== "ImportDeclaration") + .indexOf(path) === 0; } // Already at the top - nothing will change @@ -214,7 +219,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { path.isBlock() ) as NodePath; - var topNode = block.node.body[0]; + var topNode = block.node.body.filter( + (x) => x.type !== "ImportDeclaration" + )[0]; const variableDeclarator = t.variableDeclarator( t.identifier(name) ); @@ -223,8 +230,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { topNode.declarations.push(variableDeclarator); break; } else { - block.node.body.unshift( - t.variableDeclaration("var", [variableDeclarator]) + prepend( + block, + me.skip(t.variableDeclaration("var", [variableDeclarator])) ); } diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index d27e4fd..67f3bfa 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -6,7 +6,6 @@ import { Order } from "../../order"; import { noRenameVariablePrefix, placeholderVariablePrefix, - variableFunctionName, } from "../../constants"; import { computeProbabilityMap } from "../../probability"; import { @@ -215,8 +214,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Placeholder variables should always be renamed if (name.startsWith(placeholderVariablePrefix)) return true; - // Do not rename exports - if (isExportedIdentifier(bindings?.get(name))) return false; + const binding = bindings?.get(name); + + if (binding) { + // Do not rename exports + if (isExportedIdentifier(binding)) return false; + } if (name === me.obfuscator.getStringCompressionLibraryName()) return false; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 775f65e..2d9e62e 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -74,43 +74,48 @@ export class PluginInstance { setFunctionLength(path: NodePath, originalLength: number) { (path.node as NodeSymbol)[FN_LENGTH] = originalLength; - // Function length - if (this.options.preserveFunctionLength && originalLength > 0) { - if (!this.setFunctionLengthName) { - this.setFunctionLengthName = this.getPlaceholder("fnLength"); - - this.skip( - prependProgram( - path, - SetFunctionLengthTemplate.compile({ - fnName: this.setFunctionLengthName, - }) - ) - ); - } - if (t.isFunctionDeclaration(path.node)) { - prepend( - path.parentPath, - t.expressionStatement( - t.callExpression(t.identifier(this.setFunctionLengthName), [ - t.identifier(path.node.id.name), - numericLiteral(originalLength), - ]) - ) - ); - } else if ( - t.isFunctionExpression(path.node) || - t.isArrowFunctionExpression(path.node) - ) { - path.replaceWith( + // Skip if user disabled this feature + if (!this.options.preserveFunctionLength) return; + + // Skip if function has no parameters + if (originalLength === 0) return; + + // Create the function length setter if it doesn't exist + if (!this.setFunctionLengthName) { + this.setFunctionLengthName = this.getPlaceholder("fnLength"); + + this.skip( + prependProgram( + path, + SetFunctionLengthTemplate.compile({ + fnName: this.setFunctionLengthName, + }) + ) + ); + } + + if (t.isFunctionDeclaration(path.node)) { + prepend( + path.parentPath, + t.expressionStatement( t.callExpression(t.identifier(this.setFunctionLengthName), [ - path.node, + t.identifier(path.node.id.name), numericLiteral(originalLength), ]) - ); - } else { - // TODO - } + ) + ); + } else if ( + t.isFunctionExpression(path.node) || + t.isArrowFunctionExpression(path.node) + ) { + path.replaceWith( + t.callExpression(t.identifier(this.setFunctionLengthName), [ + path.node, + numericLiteral(originalLength), + ]) + ); + } else { + // TODO } } diff --git a/test/semantics/moduleImport.test.ts b/test/semantics/moduleImport.test.ts new file mode 100644 index 0000000..5352c17 --- /dev/null +++ b/test/semantics/moduleImport.test.ts @@ -0,0 +1,51 @@ +import { writeFileSync } from "fs"; +import JsConfuser from "../../src"; + +test("Variant #1: Import Declaration on High Preset", async () => { + let { code } = await JsConfuser.obfuscate( + ` + import { createHash } from "node:crypto"; + + var inputString = "Hash this string"; + var hashed = createHash("sha256").update(inputString).digest("hex"); + TEST_OUTPUT = hashed; + + console.log(TEST_OUTPUT) + `, + { + target: "node", + pack: true, + + preset: "high", + + // + renameVariables: false, + } + ); + + // Ensure the import declaration wasn't moved + expect(code.startsWith("import")).toStrictEqual(true); + + // Convert to runnable code + code = code + .replace(`import{createHash as `, "let {createHash: ") + .replace(`}from"node:crypto";`, "} = require('crypto');") + + // (When Rename Variables is disabled) + .replace( + "import{createHash} = require('crypto');", + "let {createHash} = require('crypto');" + ); + + writeFileSync("./dev.output.js", code, "utf-8"); + + console.log(code.slice(0, 100)); + + var TEST_OUTPUT = ""; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual( + "1cac63f39fd68d8c531f27b807610fb3d50f0fc3f186995767fb6316e7200a3e" + ); +}); From 5909309227af1e361f7007fa955bfa9117f050cf Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 28 Oct 2024 11:54:42 -0400 Subject: [PATCH 061/103] Fix small Pack error --- src/transforms/pack.ts | 1 + test/semantics/moduleImport.test.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index b2e5165..99e3a20 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -59,6 +59,7 @@ export default function pack({ Plugin }: PluginArg): PluginObject { if (identifierName === objectName) return; if (!path.scope.hasGlobal(identifierName)) return; + if (path.scope.hasBinding(identifierName)) return; // Check user's custom implementation if (!computeProbabilityMap(me.options.pack, identifierName)) return; diff --git a/test/semantics/moduleImport.test.ts b/test/semantics/moduleImport.test.ts index 5352c17..3c9c12b 100644 --- a/test/semantics/moduleImport.test.ts +++ b/test/semantics/moduleImport.test.ts @@ -15,14 +15,12 @@ test("Variant #1: Import Declaration on High Preset", async () => { { target: "node", pack: true, - preset: "high", - - // - renameVariables: false, } ); + // console.log(code.slice(0, 100)); + // Ensure the import declaration wasn't moved expect(code.startsWith("import")).toStrictEqual(true); @@ -31,15 +29,20 @@ test("Variant #1: Import Declaration on High Preset", async () => { .replace(`import{createHash as `, "let {createHash: ") .replace(`}from"node:crypto";`, "} = require('crypto');") + // (When Compact is disabled) + .replace(`import { createHash as `, "let {createHash: ") + .replace(` } from "node:crypto";`, "} = require('crypto');") + // (When Rename Variables is disabled) .replace( "import{createHash} = require('crypto');", "let {createHash} = require('crypto');" - ); + ) - writeFileSync("./dev.output.js", code, "utf-8"); + // (When Compact and Rename Variables is disabled) + .replace(`import { createHash}`, "let {createHash} "); - console.log(code.slice(0, 100)); + // writeFileSync("./dev.output.js", code, "utf-8"); var TEST_OUTPUT = ""; From 19d74ddaa084f234df1c4874efb6f13f842133aa Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 11:33:13 -0500 Subject: [PATCH 062/103] Remove Control Objects, Unused features --- src/constants.ts | 5 - src/transforms/controlFlowFlattening.ts | 7 +- src/transforms/functionOutlining.ts | 225 ---------------------- src/transforms/plugin.ts | 43 +---- src/transforms/shuffle.ts | 54 +++--- src/utils/ControlObject.ts | 141 -------------- test/transforms/functionOutlining.test.ts | 206 -------------------- 7 files changed, 37 insertions(+), 644 deletions(-) delete mode 100644 src/transforms/functionOutlining.ts delete mode 100644 src/utils/ControlObject.ts delete mode 100644 test/transforms/functionOutlining.test.ts diff --git a/src/constants.ts b/src/constants.ts index d57a704..3e8daaa 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,3 @@ -import ControlObject from "./utils/ControlObject"; - export const predictableFunctionTag = "__JS_PREDICT__"; /** @@ -31,8 +29,6 @@ export const SKIP = Symbol("skip"); */ export const FN_LENGTH = Symbol("fnLength"); -export const CONTROL_OBJECTS = Symbol("controlObjects"); - export const NO_RENAME = Symbol("noRename"); /** @@ -71,7 +67,6 @@ export interface NodeSymbol { [PREDICTABLE]?: boolean; [SKIP]?: boolean | number; [FN_LENGTH]?: number; - [CONTROL_OBJECTS]?: ControlObject[]; [NO_RENAME]?: string | number; [GEN_NODE]?: boolean; diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index d9b44fb..20a8022 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1,7 +1,6 @@ import traverse, { NodePath, Scope, Visitor } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; -import { computeProbabilityMap } from "../probability"; import { ensureComputedExpression, getParentFunctionOrProgram, @@ -30,7 +29,6 @@ import { PREDICTABLE, variableFunctionName, WITH_STATEMENT, - CONTROL_OBJECTS, } from "../constants"; /** @@ -116,7 +114,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (blockPath.node.body.length < 3) return; // Check user's threshold setting - if (!computeProbabilityMap(me.options.controlFlowFlattening)) { + if (!me.computeProbabilityMap(me.options.controlFlowFlattening)) { return; } @@ -1580,9 +1578,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Reset all bindings here blockPath.scope.bindings = Object.create(null); - // Bindings changed - breaking control objects - delete (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]; - // Register new declarations for (var node of blockPath.get("body")) { blockPath.scope.registerDeclaration(node); diff --git a/src/transforms/functionOutlining.ts b/src/transforms/functionOutlining.ts deleted file mode 100644 index b5d1961..0000000 --- a/src/transforms/functionOutlining.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { NodePath } from "@babel/traverse"; -import { PluginArg, PluginObject } from "./plugin"; -import { Order } from "../order"; -import { ensureComputedExpression, prepend } from "../utils/ast-utils"; -import * as t from "@babel/types"; -import { NameGen } from "../utils/NameGen"; -import { chance, getRandomInteger } from "../utils/random-utils"; -import { Binding, Visitor } from "@babel/traverse"; -import { computeProbabilityMap } from "../probability"; - -function isSafeForOutlining(path: NodePath): { - isSafe: boolean; - bindings?: Binding[]; -} { - if (path.isIdentifier() || path.isLiteral()) return { isSafe: false }; - - // Skip direct invocations ('this' will be different) - if (path.key === "callee" && path.parentPath.isCallExpression()) { - return { isSafe: false }; - } - - // Skip typeof and delete expressions (identifier behavior is different) - if (path.key === "argument" && path.parentPath.isUnaryExpression()) { - return { isSafe: false }; - } - - if ( - path.isReturnStatement() || - path.isYieldExpression() || - path.isAwaitExpression() || - path.isContinueStatement() || - path.isBreakStatement() || - path.isThrowStatement() || - path.isDebuggerStatement() || - path.isImportDeclaration() || - path.isExportDeclaration() - ) { - return { isSafe: false }; - } - - var isSafe = true; - var bindings: Binding[] = []; - var fnPath = path.getFunctionParent(); - - var visitor: Visitor = { - ThisExpression(path) { - isSafe = false; - path.stop(); - }, - Identifier(path) { - if (["arguments", "eval"].includes(path.node.name)) { - isSafe = false; - path.stop(); - } - }, - BindingIdentifier(path) { - var binding = path.scope.getBinding(path.node.name); - if (binding) { - bindings.push(binding); - } - }, - // Function flow guard - "ReturnStatement|YieldExpression|AwaitExpression"(path) { - if (path.getFunctionParent() === fnPath) { - isSafe = false; - path.stop(); - } - }, - }; - - // Exclude 'ThisExpression' and semantic 'Identifier' nodes - if (visitor[path.type]) return { isSafe: false }; - - path.traverse(visitor); - - return { isSafe, bindings }; -} - -export default ({ Plugin }: PluginArg): PluginObject => { - const me = Plugin(Order.FunctionOutlining, { - changeData: { - functionsMade: 0, - }, - }); - - var changesMade = 0; - - function checkProbability() { - if (!computeProbabilityMap(me.options.functionOutlining)) return false; - - if (changesMade > 100 && chance(changesMade - 100)) return false; - - return true; - } - - return { - visitor: { - Block: { - exit(blockPath) { - if (blockPath.isProgram()) { - blockPath.scope.crawl(); - } - - if (blockPath.find((p) => me.isSkipped(p))) return; - - if (!checkProbability()) return; - - // Extract a random number of statements - - var statements = blockPath.get("body"); - // var startIndex = getRandomInteger(0, statements.length); - // var endIndex = getRandomInteger(startIndex, statements.length); - - var startIndex = 0; - var endIndex = statements.length; - - var extractedStatements = statements.slice(startIndex, endIndex); - if (!extractedStatements.length) return; - - var bindings: Binding[] = []; - - for (var statement of extractedStatements) { - // Don't override the control node - if (me.isSkipped(statement)) return; - - var result = isSafeForOutlining(statement); - if (!result.isSafe) { - return; - } - - bindings.push(...result.bindings); - } - - const extractedStatementSet = new Set(extractedStatements); - - for (var binding of bindings) { - for (var referencePath of binding.referencePaths) { - var found = referencePath.find((p) => - extractedStatementSet.has(p) - ); - if (!found) { - return; - } - } - for (var constantViolation of binding.constantViolations) { - var found = constantViolation.find((p) => - extractedStatementSet.has(p) - ); - if (!found) { - return; - } - } - } - - changesMade++; - - var isFirst = true; - for (var statement of extractedStatements) { - if (isFirst) { - isFirst = false; - var memberExpression = me - .getControlObject(blockPath) - .addProperty( - t.functionExpression( - null, - [], - t.blockStatement(extractedStatements.map((x) => x.node)) - ) - ); - - me.changeData.functionsMade++; - - var callExpression = t.callExpression(memberExpression, []); - - statement.replaceWith(callExpression); - continue; - } - statement.remove(); - } - }, - }, - Expression: { - exit(path) { - // Skip assignment left - if ( - path.find( - (p) => - p.key === "left" && - p.parentPath?.type === "AssignmentExpression" - ) - ) { - return; - } - - if (!checkProbability()) return; - - if (path.find((p) => me.isSkipped(p))) return; - if (!isSafeForOutlining(path).isSafe) return; - - changesMade++; - - var blockPath = path.find((p) => p.isBlock()) as NodePath; - - var memberExpression = me - .getControlObject(blockPath) - .addProperty( - t.functionExpression( - null, - [], - t.blockStatement([t.returnStatement(t.cloneNode(path.node))]) - ) - ); - - me.changeData.functionsMade++; - - var callExpression = t.callExpression(memberExpression, []); - - ensureComputedExpression(path); - path.replaceWith(callExpression); - me.skip(path); - }, - }, - }, - }; -}; diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 2d9e62e..499bdf6 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -1,13 +1,11 @@ import { NodePath, Visitor } from "@babel/traverse"; import Obfuscator from "../obfuscator"; -import { chance, choice, getRandomString } from "../utils/random-utils"; +import { getRandomString } from "../utils/random-utils"; import { Order } from "../order"; import * as t from "@babel/types"; -import { FN_LENGTH, NodeSymbol, SKIP, CONTROL_OBJECTS } from "../constants"; +import { FN_LENGTH, NodeSymbol, SKIP } from "../constants"; import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; import { prepend, prependProgram } from "../utils/ast-utils"; -import ControlObject from "../utils/ControlObject"; -import { ok } from "assert"; import { numericLiteral } from "../utils/node"; export interface PluginObject { @@ -30,9 +28,14 @@ export class PluginInstance { constructor( public pluginOptions: { name?: string; order?: number }, public obfuscator: Obfuscator - ) {} + ) { + this.computeProbabilityMap = obfuscator.computeProbabilityMap.bind( + this.obfuscator + ); + } public changeData: { [key: string]: number } = {}; + public computeProbabilityMap: Obfuscator["computeProbabilityMap"]; get name() { return this.pluginOptions.name || "unnamed"; @@ -130,36 +133,6 @@ export class PluginInstance { return "__p_" + getRandomString(4) + (suffix ? "_" + suffix : ""); } - /** - * Retrieves (or creates) a `ControlObject` for the given `blockPath`. - */ - getControlObject(blockPath: NodePath, createMultiple = true) { - ok(blockPath.isBlock()); - - var controlObjects = (blockPath.node as NodeSymbol)[CONTROL_OBJECTS]; - if (!controlObjects) { - controlObjects = []; - } - - if ( - controlObjects.length === 0 || - (createMultiple && - chance( - controlObjects[0].propertyNames.size - 15 * controlObjects.length - )) - ) { - var newControlObject = new ControlObject(this, blockPath); - - controlObjects.push(newControlObject); - - (blockPath.node as NodeSymbol)[CONTROL_OBJECTS] = controlObjects; - - return newControlObject; - } - - return choice(controlObjects); - } - /** * Logs a message to the console, only if `verbose` is enabled. * @param messages diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index 147884a..e72b708 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -1,13 +1,12 @@ -import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; -import { computeProbabilityMap } from "../probability"; import { getRandomInteger } from "../utils/random-utils"; import Template from "../templates/template"; import { Order } from "../order"; import { isStaticValue } from "../utils/static-utils"; -import { NodeSymbol, PREDICTABLE } from "../constants"; +import { PREDICTABLE } from "../constants"; import { numericLiteral } from "../utils/node"; +import { prependProgram } from "../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Shuffle, { @@ -16,6 +15,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }); + let fnName: string | null = null; + return { visitor: { ArrayExpression: { @@ -29,10 +30,31 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (illegalElement) return; - if (!computeProbabilityMap(me.options.shuffle)) { + if (!me.computeProbabilityMap(me.options.shuffle)) { return; } + // Create un-shuffling function + if (!fnName) { + fnName = me.getPlaceholder() + "_shuffle"; + + prependProgram( + path, + new Template( + ` + function ${fnName}(arr, shift) { + for (var i = 0; i < shift; i++) { + arr["push"](arr["shift"]()); + } + return arr; + } + ` + ) + .addSymbols(PREDICTABLE) + .single() + ); + } + var shift = getRandomInteger( 1, Math.min(30, path.node.elements.length * 6) @@ -43,30 +65,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { shiftedElements.unshift(shiftedElements.pop()); } - var block = path.find((p) => p.isBlock()) as NodePath; - - var functionExpression = new Template( - ` - (function(arr) { - for (var i = 0; i < {shiftNode}; i++) { - arr.push(arr.shift()); - } - return arr; - }) - ` - ).expression({ - shiftNode: numericLiteral(shift), - }); - - (functionExpression as NodeSymbol)[PREDICTABLE] = true; - - var memberExpression = me - .getControlObject(block) - .addProperty(functionExpression); - path.replaceWith( - t.callExpression(memberExpression, [ + t.callExpression(t.identifier(fnName), [ t.arrayExpression(shiftedElements), + numericLiteral(shift), ]) ); diff --git a/src/utils/ControlObject.ts b/src/utils/ControlObject.ts deleted file mode 100644 index a19d96f..0000000 --- a/src/utils/ControlObject.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { NodePath } from "@babel/traverse"; -import * as t from "@babel/types"; -import { PluginInstance } from "../transforms/plugin"; -import { NameGen } from "./NameGen"; -import { prepend } from "./ast-utils"; -import { chance, choice } from "./random-utils"; - -/** - * A Control Object is an object that is used to store properties that are used in multiple places. - */ -export default class ControlObject { - propertyNames = new Set(); - nameGen: NameGen; - objectName: string | null = null; - objectPath: NodePath | null = null; - objectExpression: t.ObjectExpression | null = null; - - constructor(public me: PluginInstance, public blockPath: NodePath) { - this.nameGen = new NameGen(me.options.identifierGenerator, { - avoidReserved: true, - avoidObjectPrototype: true, - }); - } - - createMemberExpression(propertyName: string): t.MemberExpression { - return t.memberExpression( - t.identifier(this.objectName), - t.stringLiteral(propertyName), - true - ); - } - - createPredicate() { - this.ensureCreated(); - - var propertyName = choice(Array.from(this.propertyNames)); - if (!propertyName || chance(50)) { - propertyName = this.nameGen.generate(); - } - - return { - node: t.binaryExpression( - "in", - t.stringLiteral(propertyName), - t.identifier(this.objectName) - ), - value: this.propertyNames.has(propertyName), - }; - } - - createTruePredicate() { - var { node, value } = this.createPredicate(); - if (value) { - return node; - } - return t.unaryExpression("!", node); - } - - createFalsePredicate() { - var { node, value } = this.createPredicate(); - if (!value) { - return node; - } - return t.unaryExpression("!", node); - } - - private ensureCreated(node?: t.Node) { - if (!this.objectName) { - // Object hasn't been created yet - this.objectName = this.me.getPlaceholder() + "_controlObject"; - - if (node && t.isFunctionExpression(node) && !node.id) { - // Use function declaration as object - - let newNode: t.FunctionDeclaration = node as any; - newNode.type = "FunctionDeclaration"; - newNode.id = t.identifier(this.objectName); - - let newPath = prepend( - this.blockPath, - newNode - )[0] as NodePath; - this.me.skip(newPath); - - this.objectPath = newPath; - - return t.identifier(this.objectName); - } else { - // Create plain object - let newPath = prepend( - this.blockPath, - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier(this.objectName), - t.objectExpression([]) - ), - ]) - )[0] as NodePath; - this.me.skip(newPath); - - this.objectPath = newPath; - - var objectExpression = newPath.node.declarations[0] - .init as t.ObjectExpression; - - this.objectExpression = objectExpression; - this.me.skip(this.objectExpression); - } - } - } - - addProperty(node: t.Expression) { - var initialNode = this.ensureCreated(node); - if (initialNode) return initialNode; - - const propertyName = this.nameGen.generate(); - this.propertyNames.add(propertyName); - - // Add an initial property - if (this.objectExpression) { - this.objectExpression.properties.push( - t.objectProperty(t.identifier(propertyName), node) - ); - } else { - // Add as assignment expression - - let assignment = t.assignmentExpression( - "=", - this.createMemberExpression(propertyName), - node - ); - - var newPath = this.objectPath.insertAfter( - t.expressionStatement(assignment) - )[0]; - this.me.skip(newPath); - } - - return this.createMemberExpression(propertyName); - } -} diff --git a/test/transforms/functionOutlining.test.ts b/test/transforms/functionOutlining.test.ts deleted file mode 100644 index 0ddfaef..0000000 --- a/test/transforms/functionOutlining.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -import JsConfuser from "../../src/index"; - -test("Variant #1: Outline expressions", async () => { - var { code } = await JsConfuser.obfuscate( - ` - var ten = Math.floor(5 * Number(2)) - TEST_OUTPUT = ten; - `, - { - target: "node", - functionOutlining: true, - } - ); - - expect(code).not.toContain("ten=Math.floor"); - - var TEST_OUTPUT; - eval(code); - - expect(TEST_OUTPUT).toStrictEqual(10); -}); - -test("Variant #2: Don't outline expressions with 'eval' 'this' or 'arguments'", async () => { - var { code } = await JsConfuser.obfuscate( - ` - function testFunction(){ - var expectedThis = {}; - - function shouldNotOutline(){ - var ten = eval("10"); - if(this === expectedThis){ - return ten + arguments[0]; - } - } - - var result = shouldNotOutline.call(expectedThis, 15); - return result; - } - - TEST_OUTPUT = testFunction(); // 25 - `, - { - target: "node", - functionOutlining: true, - } - ); - - var TEST_OUTPUT; - eval(code); - - expect(TEST_OUTPUT).toStrictEqual(25); -}); - -test("Variant #2: Outline expressions in nested functions", async () => { - var { code } = await JsConfuser.obfuscate( - ` -var outsideVar = 10; -function testFunction() { - function innerFunction1(){ - var innerFunction2 = function(){ - var ten = Math.floor(5 * Number(2)); - var five = Math.floor(Number(5)); - return ten + five + outsideVar; - } - return innerFunction2(); - } - return innerFunction1(); -} - -var testResult = testFunction(); -TEST_OUTPUT = testResult; // 25 -`, - { - target: "node", - functionOutlining: true, - } - ); - - var TEST_OUTPUT; - eval(code); - - expect(TEST_OUTPUT).toStrictEqual(25); -}); - -test("Variant #4: Handle typeof expressions", async () => { - var { code } = await JsConfuser.obfuscate( - ` - var existentialVariable = 10; - var values = [ - typeof nonexistentVariable1, - typeof nonexistentVariable2, - typeof nonexistentVariable3, - typeof nonexistentVariable4, - typeof nonexistentVariable5, - typeof nonexistentVariable6, - typeof nonexistentVariable7, - typeof nonexistentVariable8, - typeof nonexistentVariable9, - typeof nonexistentVariable10, - ]; - - TEST_OUTPUT = true; - - for(var value of values){ - if(typeof value !== "string" || value !== "undefined") { - TEST_OUTPUT = false; - break; - } - } - - if(typeof existentialVariable !== "number"){ - TEST_OUTPUT = false; - } - `, - { - target: "node", - functionOutlining: true, - } - ); - - var TEST_OUTPUT; - eval(code); - - expect(TEST_OUTPUT).toStrictEqual(true); -}); - -test("Variant #5: Handle direct invocations", async () => { - var { code } = await JsConfuser.obfuscate( - ` - function getThis(){ - return this; - } - - var obj1 = { - getThis: getThis - } - var obj2 = { - getThis: getThis - } - var obj3 = { - getThis: getThis - } - - TEST_OUTPUT = [ - getThis() === undefined, - obj1.getThis() === obj1, - obj2.getThis() === obj2, - obj3.getThis() === obj3, - ] - `, - { - target: "node", - functionOutlining: true, - } - ); - - var TEST_OUTPUT; - eval(code); - - expect(TEST_OUTPUT).toStrictEqual([true, true, true, true]); -}); - -test("Variant #6: Handle return statements", async () => { - var { code } = await JsConfuser.obfuscate( - ` - function fn1(){ - return true; - } - function fn2(){ - if(true) { - return true; - } - } - function fn3(){ - if(false) { - } else { - return true; - } - } - function fn4(){ - for(var i = 0; i < 10; i++){ - return true; - } - } - function fn5(){ - if(true) { - if(true) { - return true; - } - } - } - - TEST_OUTPUT = [fn1(), fn2(), fn3(), fn4(), fn5()]; - `, - { - target: "node", - functionOutlining: true, - } - ); - - var TEST_OUTPUT; - - eval(code); - - expect(TEST_OUTPUT).toStrictEqual([true, true, true, true, true]); -}); From 1b055aa8d949c8eb6270756d3e3ce323b45c43c4 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 11:40:55 -0500 Subject: [PATCH 063/103] Add user defined limits to Probability Maps --- src/obfuscator.ts | 147 +++++++++++++++++- src/options.ts | 18 ++- src/order.ts | 2 - src/probability.ts | 110 ------------- src/transforms/dispatcher.ts | 3 +- src/transforms/extraction/objectExtraction.ts | 3 +- src/transforms/flatten.ts | 25 +-- src/transforms/identifier/globalConcealing.ts | 6 +- src/transforms/identifier/renameVariables.ts | 7 +- src/transforms/lock/lock.ts | 5 +- src/transforms/pack.ts | 4 +- src/transforms/renameLabels.ts | 3 +- src/transforms/string/stringEncoding.ts | 4 +- src/transforms/string/stringSplitting.ts | 3 +- src/transforms/variableMasking.ts | 3 +- test/code/Cash.test.ts | 7 +- test/code/ES6.test.ts | 7 +- test/options.test.ts | 78 +++++++++- test/probability.test.ts | 6 +- 19 files changed, 265 insertions(+), 176 deletions(-) diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 0d86565..31ffcf8 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -3,10 +3,9 @@ import * as t from "@babel/types"; import generate from "@babel/generator"; import traverse from "@babel/traverse"; import { parse } from "@babel/parser"; -import { ObfuscateOptions } from "./options"; +import { ObfuscateOptions, ProbabilityMap } from "./options"; import { applyDefaultsToOptions, validateOptions } from "./validateOptions"; import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; -import { isProbabilityMapProbable } from "./probability"; import { NameGen } from "./utils/NameGen"; import { Order } from "./order"; import { @@ -22,7 +21,6 @@ import variableMasking from "./transforms/variableMasking"; import dispatcher from "./transforms/dispatcher"; import duplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval"; import objectExtraction from "./transforms/extraction/objectExtraction"; -import functionOutlining from "./transforms/functionOutlining"; import globalConcealing from "./transforms/identifier/globalConcealing"; import stringCompression from "./transforms/string/stringCompression"; import deadCode from "./transforms/deadCode"; @@ -42,6 +40,7 @@ import minify from "./transforms/minify"; import finalizer from "./transforms/finalizer"; import integrity from "./transforms/lock/integrity"; import pack from "./transforms/pack"; +import { createObject } from "./utils/object-utils"; export const DEFAULT_OPTIONS: ObfuscateOptions = { target: "node", @@ -161,7 +160,7 @@ export default class Obfuscator { (Object.keys(this.options.lock).filter( (key) => key !== "customLocks" && - isProbabilityMapProbable(this.options.lock[key]) + this.isProbabilityMapProbable(this.options.lock[key]) ).length > 0 || this.options.lock.customLocks.length > 0); @@ -169,7 +168,7 @@ export default class Obfuscator { const push = (probabilityMap, ...pluginFns) => { this.totalPossibleTransforms += pluginFns.length; - if (!isProbabilityMapProbable(probabilityMap)) return; + if (!this.isProbabilityMapProbable(probabilityMap)) return; allPlugins.push(...pluginFns); }; @@ -185,7 +184,6 @@ export default class Obfuscator { push(this.options.calculator, calculator); push(this.options.globalConcealing, globalConcealing); push(this.options.opaquePredicates, opaquePredicates); - push(this.options.functionOutlining, functionOutlining); push(this.options.stringSplitting, stringSplitting); push(this.options.stringConcealing, stringConcealing); push(this.options.stringCompression, stringCompression); @@ -354,4 +352,141 @@ export default class Obfuscator { return ast; } + + probabilityMapCounter = new WeakMap(); + + /** + * Evaluates a ProbabilityMap. + * @param map The setting object. + * @param customFnArgs Args given to user-implemented function, such as a variable name. + */ + computeProbabilityMap< + T, + F extends (...args: any[]) => any = (...args: any[]) => any + >( + map: ProbabilityMap, + ...customImplementationArgs: F extends (...args: infer P) => any ? P : never + ): boolean | string { + // Check if this probability map uses the {value: ..., limit: ...} format + if (typeof map === "object" && "value" in map) { + // Check for the limit property + if ("limit" in map && typeof map.limit === "number") { + // Check if the limit has been reached + if (this.probabilityMapCounter.get(map) >= map.limit) { + return false; + } + } + + var value = this.computeProbabilityMap( + map.value as ProbabilityMap, + ...customImplementationArgs + ); + + if (value) { + // Increment the counter for this map + this.probabilityMapCounter.set( + map, + this.probabilityMapCounter.get(map) + 1 || 1 + ); + } + + return value; + } + + if (!map) { + return false; + } + if (map === true || map === 1) { + return true; + } + if (typeof map === "number") { + return Math.random() < map; + } + + if (typeof map === "function") { + return (map as Function)(...customImplementationArgs); + } + + if (typeof map === "string") { + return map; + } + + var asObject: { [mode: string]: number } = {}; + if (Array.isArray(map)) { + map.forEach((x: any) => { + asObject[x.toString()] = 1; + }); + } else { + asObject = map as any; + } + + var total = Object.values(asObject).reduce((a, b) => a + b); + var percentages = createObject( + Object.keys(asObject), + Object.values(asObject).map((x) => x / total) + ); + + var ticket = Math.random(); + + var count = 0; + var winner = null; + Object.keys(percentages).forEach((key) => { + var x = Number(percentages[key]); + + if (ticket >= count && ticket < count + x) { + winner = key; + } + count += x; + }); + + return winner; + } + + /** + * Determines if a probability map can return a positive result (true, or some string mode). + * - Negative probability maps are used to remove transformations from running entirely. + * @param map + */ + isProbabilityMapProbable(map: ProbabilityMap): boolean { + ok(!Number.isNaN(map), "Numbers cannot be NaN"); + + if (!map || typeof map === "undefined") { + return false; + } + if (typeof map === "function") { + return true; + } + if (typeof map === "number") { + if (map > 1 || map < 0) { + throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`); + } + } + if (Array.isArray(map)) { + ok( + map.length != 0, + "Empty arrays are not allowed for options. Use false instead." + ); + + if (map.length == 1) { + return !!map[0]; + } + } + if (typeof map === "object") { + if (map instanceof Date) return true; + if (map instanceof RegExp) return true; + if ("value" in map && !map.value) return false; + if ("limit" in map && map.limit === 0) return false; + + var keys = Object.keys(map); + ok( + keys.length != 0, + "Empty objects are not allowed for options. Use false instead." + ); + + if (keys.length == 1) { + return !!map[keys[0]]; + } + } + return true; + } } diff --git a/src/options.ts b/src/options.ts index 14ec046..7910b1f 100644 --- a/src/options.ts +++ b/src/options.ts @@ -2,7 +2,7 @@ import Template from "./templates/template"; // JS-Confuser.com imports this file for Type support, therefore some additional types are included here. -type Stringed = (V extends string ? V : never) | "true" | "false"; +type Stringed = V extends string ? V : never; /** * Configurable probabilities for obfuscator options. @@ -16,9 +16,19 @@ type Stringed = (V extends string ? V : never) | "true" | "false"; * - **`function(x){ return "custom_implementation" }`** - enabled, use specified function */ export type ProbabilityMap< - T, + T = boolean, F extends (...args: any[]) => any = () => boolean // Default to a generic function -> = false | true | number | T | T[] | { [key in Stringed]?: number } | F; +> = + | false + | true + | number + | F + | (T extends never | boolean + ? { + value: ProbabilityMap; + limit?: number; + } + : T | T[] | { [key in Stringed]?: number }); export interface CustomLock { /** @@ -342,8 +352,6 @@ export interface ObfuscateOptions { customLocks?: CustomLock[]; }; - functionOutlining?: ProbabilityMap; - /** * Moves variable declarations to the top of the context. */ diff --git a/src/order.ts b/src/order.ts index 6491b18..a6726d3 100644 --- a/src/order.ts +++ b/src/order.ts @@ -38,8 +38,6 @@ export enum Order { MovedDeclarations = 25, - FunctionOutlining = 26, - RenameLabels = 27, Minify = 28, diff --git a/src/probability.ts b/src/probability.ts index 35dcaf1..e69de29 100644 --- a/src/probability.ts +++ b/src/probability.ts @@ -1,110 +0,0 @@ -import { ok } from "assert"; -import { createObject } from "./utils/object-utils"; -import { ProbabilityMap } from "./options"; - -/** - * Evaluates a ProbabilityMap. - * @param map The setting object. - * @param customFnArgs Args given to user-implemented function, such as a variable name. - */ -export function computeProbabilityMap< - T, - F extends (...args: any[]) => any = (...args: any[]) => any ->( - map: ProbabilityMap, - ...customImplementationArgs: F extends (...args: infer P) => any ? P : never -): boolean | string { - if (!map) { - return false; - } - if (map === true || map === 1) { - return true; - } - if (typeof map === "number") { - return Math.random() < map; - } - - if (typeof map === "function") { - return (map as Function)(...customImplementationArgs); - } - - if (typeof map === "string") { - return map; - } - - var asObject: { [mode: string]: number } = {}; - if (Array.isArray(map)) { - map.forEach((x: any) => { - asObject[x.toString()] = 1; - }); - } else { - asObject = map as any; - } - - var total = Object.values(asObject).reduce((a, b) => a + b); - var percentages = createObject( - Object.keys(asObject), - Object.values(asObject).map((x) => x / total) - ); - - var ticket = Math.random(); - - var count = 0; - var winner = null; - Object.keys(percentages).forEach((key) => { - var x = Number(percentages[key]); - - if (ticket >= count && ticket < count + x) { - winner = key; - } - count += x; - }); - - return winner; -} - -/** - * Determines if a probability map can return a positive result (true, or some string mode). - * - Negative probability maps are used to remove transformations from running entirely. - * @param map - */ -export function isProbabilityMapProbable(map: ProbabilityMap): boolean { - ok(!Number.isNaN(map), "Numbers cannot be NaN"); - - if (!map || typeof map === "undefined") { - return false; - } - if (typeof map === "function") { - return true; - } - if (typeof map === "number") { - if (map > 1 || map < 0) { - throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`); - } - } - if (Array.isArray(map)) { - ok( - map.length != 0, - "Empty arrays are not allowed for options. Use false instead." - ); - - if (map.length == 1) { - return !!map[0]; - } - } - if (typeof map === "object") { - if (map instanceof Date) return true; - if (map instanceof RegExp) return true; - - var keys = Object.keys(map); - ok( - keys.length != 0, - "Empty objects are not allowed for options. Use false instead." - ); - - if (keys.length == 1) { - return !!keys[0]; - } - } - return true; -} diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index c9282a0..b1f1a8e 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -4,7 +4,6 @@ import * as t from "@babel/types"; import Template from "../templates/template"; import { ok } from "assert"; import { chance, getRandomString } from "../utils/random-utils"; -import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; import { @@ -146,7 +145,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { } for (var name of functionPaths.keys()) { - if (!computeProbabilityMap(me.options.dispatcher, name)) { + if (!me.computeProbabilityMap(me.options.dispatcher, name)) { functionPaths.delete(name); } } diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index f3b0862..cf04d5a 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -7,7 +7,6 @@ import { getObjectPropertyAsString, getParentFunctionOrProgram, } from "../../utils/ast-utils"; -import { computeProbabilityMap } from "../../probability"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.ObjectExtraction, { @@ -152,7 +151,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (!isObjectSafe) return; if ( - !computeProbabilityMap( + !me.computeProbabilityMap( me.options.objectExtraction, identifier.node.name ) diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 4a09a9e..dc8acd6 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -11,7 +11,6 @@ import { prependProgram, } from "../utils/ast-utils"; import { PluginArg, PluginObject } from "./plugin"; -import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; import { @@ -58,7 +57,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { functionName = "anonymous"; } - if (!computeProbabilityMap(me.options.flatten, functionName)) { + if (!me.computeProbabilityMap(me.options.flatten, functionName)) { return; } @@ -117,26 +116,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { return; } - var definedLocal = identifierPath.scope; - do { - if (definedLocal.hasOwnBinding(identifierName)) return; - if (definedLocal === fnPath.scope) break; - - definedLocal = definedLocal.parent; - if (definedLocal === program.scope) - ok(functionName + ":" + identifierName); - } while (definedLocal); - - var cursor: Scope = fnPath.scope.parent; - var isOutsideVariable = false; - - do { - if (cursor.hasBinding(identifierName)) { - isOutsideVariable = true; - break; - } - cursor = cursor.parent; - } while (cursor); + var isOutsideVariable = + fnPath.scope.parent.getBinding(identifierName) === binding; if (!isOutsideVariable) { return; diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 8618985..8e09bd8 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -4,7 +4,6 @@ import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; -import { computeProbabilityMap } from "../../probability"; import { MULTI_TRANSFORM, reservedIdentifiers, @@ -132,7 +131,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (!mapping) { // Allow user to disable custom global variables if ( - !computeProbabilityMap(me.options.globalConcealing, globalName) + !me.computeProbabilityMap( + me.options.globalConcealing, + globalName + ) ) continue; diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 67f3bfa..55b4c15 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -7,7 +7,6 @@ import { noRenameVariablePrefix, placeholderVariablePrefix, } from "../../constants"; -import { computeProbabilityMap } from "../../probability"; import { getParentFunctionOrProgram, isDefiningIdentifier, @@ -226,11 +225,13 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Global variables are additionally checked against user option if (isGlobal) { - if (!computeProbabilityMap(me.options.renameGlobals, name)) + if (!me.computeProbabilityMap(me.options.renameGlobals, name)) return false; } - if (!computeProbabilityMap(me.options.renameVariables, name, isGlobal)) + if ( + !me.computeProbabilityMap(me.options.renameVariables, name, isGlobal) + ) return false; return true; diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index 388ca9d..7c59771 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -26,7 +26,6 @@ import { NativeFunctionTemplate, StrictModeTemplate, } from "../../templates/tamperProtectionTemplates"; -import { computeProbabilityMap } from "../../probability"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Lock, { @@ -366,7 +365,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Don't apply to invokeCountermeasures function (Intended) if (me.obfuscator.isInternalVariable(functionName)) return; - if (!computeProbabilityMap(me.options.lock.integrity, functionName)) + if ( + !me.computeProbabilityMap(me.options.lock.integrity, functionName) + ) return; var newFnName = me.getPlaceholder(); diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index 99e3a20..a152b06 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -15,7 +15,6 @@ import { } from "../constants"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; -import { computeProbabilityMap } from "../probability"; export default function pack({ Plugin }: PluginArg): PluginObject { const me = Plugin(Order.Pack, { @@ -62,7 +61,8 @@ export default function pack({ Plugin }: PluginArg): PluginObject { if (path.scope.hasBinding(identifierName)) return; // Check user's custom implementation - if (!computeProbabilityMap(me.options.pack, identifierName)) return; + if (!me.computeProbabilityMap(me.options.pack, identifierName)) + return; if ( path.key === "argument" && diff --git a/src/transforms/renameLabels.ts b/src/transforms/renameLabels.ts index a8cd92e..fade4ab 100644 --- a/src/transforms/renameLabels.ts +++ b/src/transforms/renameLabels.ts @@ -4,7 +4,6 @@ import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { NameGen } from "../utils/NameGen"; import { ok } from "assert"; -import { computeProbabilityMap } from "../probability"; const LABEL = Symbol("label"); @@ -127,7 +126,7 @@ export default function ({ Plugin }: PluginArg): PluginObject { if (isRequired) { var newName = labelInterface.label; if ( - computeProbabilityMap( + me.computeProbabilityMap( me.options.renameLabels, labelInterface.label ) diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 280e21f..f905bb4 100644 --- a/src/transforms/string/stringEncoding.ts +++ b/src/transforms/string/stringEncoding.ts @@ -1,7 +1,6 @@ import { PluginInstance, PluginObject } from "../plugin"; import * as t from "@babel/types"; import { choice } from "../../utils/random-utils"; -import { computeProbabilityMap } from "../../probability"; import { GEN_NODE, NodeSymbol } from "../../constants"; import { isModuleImport } from "../../utils/ast-utils"; @@ -58,7 +57,8 @@ export default (me: PluginInstance): PluginObject => { const { value } = path.node; // Allow percentages - if (!computeProbabilityMap(me.options.stringEncoding, value)) return; + if (!me.computeProbabilityMap(me.options.stringEncoding, value)) + return; var type = choice(["hexadecimal", "unicode"]); diff --git a/src/transforms/string/stringSplitting.ts b/src/transforms/string/stringSplitting.ts index 5f9acca..2378f36 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -1,6 +1,5 @@ import { PluginArg, PluginObject } from "../plugin"; import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; -import { computeProbabilityMap } from "../../probability"; import { binaryExpression, stringLiteral } from "@babel/types"; import { ok } from "assert"; import { Order } from "../../order"; @@ -38,7 +37,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { } if ( - !computeProbabilityMap(me.options.stringSplitting, object.value) + !me.computeProbabilityMap(me.options.stringSplitting, object.value) ) { return; } diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 82df8d8..53c6496 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -2,7 +2,6 @@ import { Binding, NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import Template from "../templates/template"; -import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import { NodeSymbol, @@ -65,7 +64,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const functionName = getFunctionName(fnPath); - if (!computeProbabilityMap(me.options.variableMasking, functionName)) { + if (!me.computeProbabilityMap(me.options.variableMasking, functionName)) { return; } diff --git a/test/code/Cash.test.ts b/test/code/Cash.test.ts index 1aa51be..2034c8a 100644 --- a/test/code/Cash.test.ts +++ b/test/code/Cash.test.ts @@ -111,8 +111,6 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta global[key] = window[key]; } - var rgfCount = 0; - const CountermeasuresCode = ` function countermeasures() { throw new Error("countermeasures() was called"); @@ -125,8 +123,9 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta target: "node", preset: "high", pack: true, - rgf: (fnName, depth) => { - return rgfCount++ < 10; + rgf: { + value: true, + limit: 10, }, lock: { integrity: true, diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index d7bd57a..31e7465 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -41,14 +41,13 @@ test("Variant #1: ES6 code on High Preset", async () => { }); test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protection + Integrity", async () => { - let rgfCount = 0; - const { code } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", pack: true, - rgf: (fnName, depth) => { - return rgfCount++ < 10; + rgf: { + value: true, + limit: 10, }, lock: { integrity: true, diff --git a/test/options.test.ts b/test/options.test.ts index 2490fde..f53595b 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -1,4 +1,5 @@ import JsConfuser from "../src/index"; +import { ObfuscateOptions } from "../src/options"; test("Variant #1: Accept percentages", async () => { var { code: output } = await JsConfuser.obfuscate(`var TEST_VARIABLE;`, { @@ -16,7 +17,10 @@ test("Variant #2: Accept probability arrays", async () => { target: "node", renameVariables: true, renameGlobals: true, - identifierGenerator: ["hexadecimal", "mangled"], // half hexadecimal, half randomized + identifierGenerator: [ + "hexadecimal", + "mangled", + ] as ObfuscateOptions["identifierGenerator"], // half hexadecimal, half randomized }); expect(output).not.toContain("TEST_VARIABLE"); @@ -177,3 +181,75 @@ test("Variant #13: Error when source code is not a string", async () => { }); }).rejects.toThrow(); }); + +test("Variant #14: Rename Globals should accept a callback function", async () => { + var globalsCollected: string[] = []; + var { code } = await JsConfuser.obfuscate( + ` + var renameMe = true; + var doNotRenameMe = false; + + TEST_OUTPUT = [renameMe, doNotRenameMe] + `, + { + target: "node", + renameVariables: true, + renameGlobals: (globalName) => { + globalsCollected.push(globalName); + + if (globalName === "doNotRenameMe") { + return false; + } + + return true; + }, + } + ); + + // Ensure renameGlobals callback was called + expect(globalsCollected).toContain("renameMe"); + expect(globalsCollected).toContain("doNotRenameMe"); + + // Ensure code was changed correctly + expect(code).not.toContain("renameMe"); + expect(code).toContain("doNotRenameMe"); + + // Ensure code still works + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual([true, false]); +}); + +test("Variant #15: Fine-tune options using the limit property", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var renameMyVar1 = 1; + var renameMyVar2 = 2; + var keepMyVar3 = 3; + var keepMyVar4 = 4; + var keepMyVar5 = 5; + `, + { + target: "node", + renameVariables: { + value: true, + limit: 2, + }, + identifierGenerator: "mangled", + } + ); + + // Ensure the first two variables were renamed + expect(code).not.toContain("renameMyVar1"); + expect(code).not.toContain("renameMyVar2"); + + expect(code).toContain("var a"); + expect(code).toContain("var b"); + + // Ensure the remaining variables were not renamed + expect(code).toContain("keepMyVar3"); + expect(code).toContain("keepMyVar4"); + expect(code).toContain("keepMyVar5"); +}); diff --git a/test/probability.test.ts b/test/probability.test.ts index 830f79a..4307af5 100644 --- a/test/probability.test.ts +++ b/test/probability.test.ts @@ -1,4 +1,8 @@ -import { isProbabilityMapProbable } from "../src/probability"; +import Obfuscator, { DEFAULT_OPTIONS } from "../src/obfuscator"; + +const obfuscator = new Obfuscator(DEFAULT_OPTIONS); +const isProbabilityMapProbable = + obfuscator.isProbabilityMapProbable.bind(obfuscator); describe("isProbabilityMapProbable", function () { test("Variant #1: True examples", function () { From 0b3691775336baa449c5e4d520e5f2d49117ad40 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 11:41:40 -0500 Subject: [PATCH 064/103] Switch String Compression library to lz-string --- package.json | 2 +- src/templates/stringCompressionTemplate.ts | 32 +++---------------- src/transforms/string/stringCompression.ts | 26 ++++++++------- .../string/stringCompression.test.ts | 24 ++++++++++++++ 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index b35e6ef..76f2c61 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@babel/parser": "^7.25.6", "@babel/traverse": "^7.25.6", "@babel/types": "^7.25.6", - "pako": "^2.1.0" + "lz-string": "^1.5.0" }, "devDependencies": { "@babel/cli": "^7.24.8", diff --git a/src/templates/stringCompressionTemplate.ts b/src/templates/stringCompressionTemplate.ts index 73fe033..883f8fe 100644 --- a/src/templates/stringCompressionTemplate.ts +++ b/src/templates/stringCompressionTemplate.ts @@ -5,28 +5,8 @@ export const StringCompressionTemplate = new Template( var {stringFn}; (function (){ - {GetGlobalTemplate} - - var __globalObject = {getGlobalFnName}() || {}; - - // Direct invocation of atob() is not allowed in some environments - var _atob = (s) => __globalObject["atob"](s); - var _Uint8Array = __globalObject["Uint8Array"]; - - function convertBase64ToArrayBuffer(base64) { - var binaryString = _atob(base64); // Decode Base64 - var len = binaryString.length; - var uint8Array = new _Uint8Array(len); - - // Convert binary string back into Uint8Array - for (var i = 0; i < len; i++) { - uint8Array[i] = binaryString["charCodeAt"](i); - } - return uint8Array; - } - - var compressedBuffer = convertBase64ToArrayBuffer({stringValue}); - var utf8String = {pakoName}["inflate"](compressedBuffer, { to: 'string' }); + var compressedString = {stringValue}; + var utf8String = {StringCompressionLibrary}["decompressFromUTF16"](compressedString); var stringArray = utf8String["split"]({stringDelimiter}); {stringFn} = function(index){ @@ -36,9 +16,5 @@ var {stringFn}; ` ); -export const PakoInflateMin = ` -var {pako} = {}; - -/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -!function(e,t){ t({pako}) }(this,(function(e){"use strict";var t=function(e,t,i,n){for(var a=65535&e|0,r=e>>>16&65535|0,o=0;0!==i;){i-=o=i>2e3?2e3:i;do{r=r+(a=a+t[n++]|0)|0}while(--o);a%=65521,r%=65521}return a|r<<16|0},i=new Uint32Array(function(){for(var e,t=[],i=0;i<256;i++){e=i;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[i]=e}return t}()),n=function(e,t,n,a){var r=i,o=a+n;e^=-1;for(var s=a;s>>8^r[255&(e^t[s])];return-1^e},a=16209,r=function(e,t){var i,n,r,o,s,l,f,d,h,c,u,w,b,m,k,_,v,g,p,y,x,E,R,A,Z=e.state;i=e.next_in,R=e.input,n=i+(e.avail_in-5),r=e.next_out,A=e.output,o=r-(t-e.avail_out),s=r+(e.avail_out-257),l=Z.dmax,f=Z.wsize,d=Z.whave,h=Z.wnext,c=Z.window,u=Z.hold,w=Z.bits,b=Z.lencode,m=Z.distcode,k=(1<>>=g=v>>>24,w-=g,0===(g=v>>>16&255))A[r++]=65535&v;else{if(!(16&g)){if(0==(64&g)){v=b[(65535&v)+(u&(1<>>=g,w-=g),w<15&&(u+=R[i++]<>>=g=v>>>24,w-=g,!(16&(g=v>>>16&255))){if(0==(64&g)){v=m[(65535&v)+(u&(1<l){e.msg="invalid distance too far back",Z.mode=a;break e}if(u>>>=g,w-=g,y>(g=r-o)){if((g=y-g)>d&&Z.sane){e.msg="invalid distance too far back",Z.mode=a;break e}if(x=0,E=c,0===h){if(x+=f-g,g2;)A[r++]=E[x++],A[r++]=E[x++],A[r++]=E[x++],p-=3;p&&(A[r++]=E[x++],p>1&&(A[r++]=E[x++]))}else{x=r-y;do{A[r++]=A[x++],A[r++]=A[x++],A[r++]=A[x++],p-=3}while(p>2);p&&(A[r++]=A[x++],p>1&&(A[r++]=A[x++]))}break}}break}}while(i>3,u&=(1<<(w-=p<<3))-1,e.next_in=i,e.next_out=r,e.avail_in=i=1&&0===B[A];A--);if(Z>A&&(Z=A),0===A)return a[r++]=20971520,a[r++]=20971520,c.bits=1,0;for(R=1;R0&&(0===e||1!==A))return-1;for(N[1]=0,x=1;x852||2===e&&U>592)return 1;for(;;){v=x-T,h[E]+1<_?(g=0,p=h[E]):h[E]>=_?(g=C[h[E]-_],p=I[h[E]-_]):(g=96,p=0),u=1<>T)+(w-=u)]=v<<24|g<<16|p|0}while(0!==w);for(u=1<>=1;if(0!==u?(D&=u-1,D+=u):D=0,E++,0==--B[x]){if(x===A)break;x=t[i+h[E]]}if(x>Z&&(D&m)!==b){for(0===T&&(T=Z),k+=R,O=1<<(S=x-T);S+T852||2===e&&U>592)return 1;a[b=D&m]=Z<<24|S<<16|k-r|0}}return 0!==D&&(a[k+D]=x-T<<24|64<<16|0),c.bits=Z,0},c={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8},u=c.Z_FINISH,w=c.Z_BLOCK,b=c.Z_TREES,m=c.Z_OK,k=c.Z_STREAM_END,_=c.Z_NEED_DICT,v=c.Z_STREAM_ERROR,g=c.Z_DATA_ERROR,p=c.Z_MEM_ERROR,y=c.Z_BUF_ERROR,x=c.Z_DEFLATED,E=16180,R=16190,A=16191,Z=16192,S=16194,T=16199,O=16200,U=16206,D=16209,I=function(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)};function B(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}var N,C,z=function(e){if(!e)return 1;var t=e.state;return!t||t.strm!==e||t.mode16211?1:0},F=function(e){if(z(e))return v;var t=e.state;return e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=E,t.last=0,t.havedict=0,t.flags=-1,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new Int32Array(852),t.distcode=t.distdyn=new Int32Array(592),t.sane=1,t.back=-1,m},L=function(e){if(z(e))return v;var t=e.state;return t.wsize=0,t.whave=0,t.wnext=0,F(e)},M=function(e,t){var i;if(z(e))return v;var n=e.state;return t<0?(i=0,t=-t):(i=5+(t>>4),t<48&&(t&=15)),t&&(t<8||t>15)?v:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=i,n.wbits=t,L(e))},H=function(e,t){if(!e)return v;var i=new B;e.state=i,i.strm=e,i.window=null,i.mode=E;var n=M(e,t);return n!==m&&(e.state=null),n},j=!0,K=function(e){if(j){N=new Int32Array(512),C=new Int32Array(32);for(var t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(h(1,e.lens,0,288,N,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;h(2,e.lens,0,32,C,0,e.work,{bits:5}),j=!1}e.lencode=N,e.lenbits=9,e.distcode=C,e.distbits=5},P=function(e,t,i,n){var a,r=e.state;return null===r.window&&(r.wsize=1<=r.wsize?(r.window.set(t.subarray(i-r.wsize,i),0),r.wnext=0,r.whave=r.wsize):((a=r.wsize-r.wnext)>n&&(a=n),r.window.set(t.subarray(i-n,i-n+a),r.wnext),(n-=a)?(r.window.set(t.subarray(i-n,i),0),r.wnext=n,r.whave=r.wsize):(r.wnext+=a,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,a.check=n(a.check,te,2,0),B=0,N=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&B)<<8)+(B>>8))%31){e.msg="incorrect header check",a.mode=D;break}if((15&B)!==x){e.msg="unknown compression method",a.mode=D;break}if(N-=4,J=8+(15&(B>>>=4)),0===a.wbits&&(a.wbits=J),J>15||J>a.wbits){e.msg="invalid window size",a.mode=D;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16182;case 16182:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>8&255,te[2]=B>>>16&255,te[3]=B>>>24&255,a.check=n(a.check,te,4,0)),B=0,N=0,a.mode=16183;case 16183:for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>8),512&a.flags&&4&a.wrap&&(te[0]=255&B,te[1]=B>>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0,a.mode=16184;case 16184:if(1024&a.flags){for(;N<16;){if(0===d)break e;d--,B+=o[l++]<>>8&255,a.check=n(a.check,te,2,0)),B=0,N=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&((L=a.length)>d&&(L=d),L&&(a.head&&(J=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(o.subarray(l,l+L),J)),512&a.flags&&4&a.wrap&&(a.check=n(a.check,o,L,l)),d-=L,l+=L,a.length-=L),a.length))break e;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===d)break e;L=0;do{J=o[l+L++],a.head&&J&&a.length<65536&&(a.head.name+=String.fromCharCode(J))}while(J&&L>9&1,a.head.done=!0),e.adler=a.check=0,a.mode=A;break;case 16189:for(;N<32;){if(0===d)break e;d--,B+=o[l++]<>>=7&N,N-=7&N,a.mode=U;break}for(;N<3;){if(0===d)break e;d--,B+=o[l++]<>>=1)){case 0:a.mode=16193;break;case 1:if(K(a),a.mode=T,i===b){B>>>=2,N-=2;break e}break;case 2:a.mode=16196;break;case 3:e.msg="invalid block type",a.mode=D}B>>>=2,N-=2;break;case 16193:for(B>>>=7&N,N-=7&N;N<32;){if(0===d)break e;d--,B+=o[l++]<>>16^65535)){e.msg="invalid stored block lengths",a.mode=D;break}if(a.length=65535&B,B=0,N=0,a.mode=S,i===b)break e;case S:a.mode=16195;case 16195:if(L=a.length){if(L>d&&(L=d),L>c&&(L=c),0===L)break e;s.set(o.subarray(l,l+L),f),d-=L,l+=L,c-=L,f+=L,a.length-=L;break}a.mode=A;break;case 16196:for(;N<14;){if(0===d)break e;d--,B+=o[l++]<>>=5,N-=5,a.ndist=1+(31&B),B>>>=5,N-=5,a.ncode=4+(15&B),B>>>=4,N-=4,a.nlen>286||a.ndist>30){e.msg="too many length or distance symbols",a.mode=D;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,N-=3}for(;a.have<19;)a.lens[ie[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,V={bits:a.lenbits},Q=h(0,a.lens,0,19,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid code lengths set",a.mode=D;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,a.lens[a.have++]=G;else{if(16===G){for($=j+2;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j,N-=j,0===a.have){e.msg="invalid bit length repeat",a.mode=D;break}J=a.lens[a.have-1],L=3+(3&B),B>>>=2,N-=2}else if(17===G){for($=j+3;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=3,N-=3}else{for($=j+7;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=j)),B>>>=7,N-=7}if(a.have+L>a.nlen+a.ndist){e.msg="invalid bit length repeat",a.mode=D;break}for(;L--;)a.lens[a.have++]=J}}if(a.mode===D)break;if(0===a.lens[256]){e.msg="invalid code -- missing end-of-block",a.mode=D;break}if(a.lenbits=9,V={bits:a.lenbits},Q=h(1,a.lens,0,a.nlen,a.lencode,0,a.work,V),a.lenbits=V.bits,Q){e.msg="invalid literal/lengths set",a.mode=D;break}if(a.distbits=6,a.distcode=a.distdyn,V={bits:a.distbits},Q=h(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,V),a.distbits=V.bits,Q){e.msg="invalid distances set",a.mode=D;break}if(a.mode=T,i===b)break e;case T:a.mode=O;case O:if(d>=6&&c>=258){e.next_out=f,e.avail_out=c,e.next_in=l,e.avail_in=d,a.hold=B,a.bits=N,r(e,F),f=e.next_out,s=e.output,c=e.avail_out,l=e.next_in,o=e.input,d=e.avail_in,B=a.hold,N=a.bits,a.mode===A&&(a.back=-1);break}for(a.back=0;Y=(ee=a.lencode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,a.length=G,0===Y){a.mode=16205;break}if(32&Y){a.back=-1,a.mode=A;break}if(64&Y){e.msg="invalid literal/length code",a.mode=D;break}a.extra=15&Y,a.mode=16201;case 16201:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;Y=(ee=a.distcode[B&(1<>>16&255,G=65535&ee,!((j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>X)])>>>16&255,G=65535&ee,!(X+(j=ee>>>24)<=N);){if(0===d)break e;d--,B+=o[l++]<>>=X,N-=X,a.back+=X}if(B>>>=j,N-=j,a.back+=j,64&Y){e.msg="invalid distance code",a.mode=D;break}a.offset=G,a.extra=15&Y,a.mode=16203;case 16203:if(a.extra){for($=a.extra;N<$;){if(0===d)break e;d--,B+=o[l++]<>>=a.extra,N-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){e.msg="invalid distance too far back",a.mode=D;break}a.mode=16204;case 16204:if(0===c)break e;if(L=F-c,a.offset>L){if((L=a.offset-L)>a.whave&&a.sane){e.msg="invalid distance too far back",a.mode=D;break}L>a.wnext?(L-=a.wnext,M=a.wsize-L):M=a.wnext-L,L>a.length&&(L=a.length),H=a.window}else H=s,M=f-a.offset,L=a.length;L>c&&(L=c),c-=L,a.length-=L;do{s[f++]=H[M++]}while(--L);0===a.length&&(a.mode=O);break;case 16205:if(0===c)break e;s[f++]=a.length,c--,a.mode=O;break;case U:if(a.wrap){for(;N<32;){if(0===d)break e;d--,B|=o[l++]<=252?6:V>=248?5:V>=240?4:V>=224?3:V>=192?2:1;Q[254]=Q[254]=1;var $=function(e){if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(e);var t,i,n,a,r,o=e.length,s=0;for(a=0;a>>6,t[r++]=128|63&i):i<65536?(t[r++]=224|i>>>12,t[r++]=128|i>>>6&63,t[r++]=128|63&i):(t[r++]=240|i>>>18,t[r++]=128|i>>>12&63,t[r++]=128|i>>>6&63,t[r++]=128|63&i);return t},ee=function(e,t){var i,n,a=t||e.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(e.subarray(0,t));var r=new Array(2*a);for(n=0,i=0;i4)r[n++]=65533,i+=s-1;else{for(o&=2===s?31:3===s?15:7;s>1&&i1?r[n++]=65533:o<65536?r[n++]=o:(o-=65536,r[n++]=55296|o>>10&1023,r[n++]=56320|1023&o)}}}return function(e,t){if(t<65534&&e.subarray&&J)return String.fromCharCode.apply(null,e.length===t?e:e.subarray(0,t));for(var i="",n=0;ne.length&&(t=e.length);for(var i=t-1;i>=0&&128==(192&e[i]);)i--;return i<0||0===i?t:i+Q[e[i]]>t?i:t},ie={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"};var ne=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};var ae=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1},re=Object.prototype.toString,oe=c.Z_NO_FLUSH,se=c.Z_FINISH,le=c.Z_OK,fe=c.Z_STREAM_END,de=c.Z_NEED_DICT,he=c.Z_STREAM_ERROR,ce=c.Z_DATA_ERROR,ue=c.Z_MEM_ERROR;function we(e){this.options=W({chunkSize:65536,windowBits:15,to:""},e||{});var t=this.options;t.raw&&t.windowBits>=0&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(t.windowBits>=0&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),t.windowBits>15&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new ne,this.strm.avail_out=0;var i=Y.inflateInit2(this.strm,t.windowBits);if(i!==le)throw new Error(ie[i]);if(this.header=new ae,Y.inflateGetHeader(this.strm,this.header),t.dictionary&&("string"==typeof t.dictionary?t.dictionary=$(t.dictionary):"[object ArrayBuffer]"===re.call(t.dictionary)&&(t.dictionary=new Uint8Array(t.dictionary)),t.raw&&(i=Y.inflateSetDictionary(this.strm,t.dictionary))!==le))throw new Error(ie[i])}function be(e,t){var i=new we(t);if(i.push(e),i.err)throw i.msg||ie[i.err];return i.result}we.prototype.push=function(e,t){var i,n,a,r=this.strm,o=this.options.chunkSize,s=this.options.dictionary;if(this.ended)return!1;for(n=t===~~t?t:!0===t?se:oe,"[object ArrayBuffer]"===re.call(e)?r.input=new Uint8Array(e):r.input=e,r.next_in=0,r.avail_in=r.input.length;;){for(0===r.avail_out&&(r.output=new Uint8Array(o),r.next_out=0,r.avail_out=o),(i=Y.inflate(r,n))===de&&s&&((i=Y.inflateSetDictionary(r,s))===le?i=Y.inflate(r,n):i===ce&&(i=de));r.avail_in>0&&i===fe&&r.state.wrap>0&&0!==e[r.next_in];)Y.inflateReset(r),i=Y.inflate(r,n);switch(i){case he:case ce:case de:case ue:return this.onEnd(i),this.ended=!0,!1}if(a=r.avail_out,r.next_out&&(0===r.avail_out||i===fe))if("string"===this.options.to){var l=te(r.output,r.next_out),f=r.next_out-l,d=ee(r.output,l);r.next_out=f,r.avail_out=o-f,f&&r.output.set(r.output.subarray(l,l+f),0),this.onData(d)}else this.onData(r.output.length===r.next_out?r.output:r.output.subarray(0,r.next_out));if(i!==le||0!==a){if(i===fe)return i=Y.inflateEnd(this.strm),this.onEnd(i),this.ended=!0,!0;if(0===r.avail_in)break}}return!0},we.prototype.onData=function(e){this.chunks.push(e)},we.prototype.onEnd=function(e){e===le&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=q(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg};var me=we,ke=be,_e=function(e,t){return(t=t||{}).raw=!0,be(e,t)},ve=be,ge=c,pe={Inflate:me,inflate:ke,inflateRaw:_e,ungzip:ve,constants:ge};e.Inflate=me,e.constants=ge,e.default=pe,e.inflate=ke,e.inflateRaw=_e,e.ungzip=ve,Object.defineProperty(e,"__esModule",{value:!0})})); - `; +export const StringCompressionLibraryMinified = ` +var {StringCompressionLibrary}=function(){var r=String.fromCharCode,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",e={};function t(r,o){if(!e[r]){e[r]={};for(var n=0;n>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null==o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;e>=1}else{for(t=1,e=0;e>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e>=1;0==--l&&(l=Math.pow(2,h),h++),s[p]=f++,c=String(a)}if(""!==c){if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e>=1}else{for(t=1,e=0;e>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e>=1;0==--l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;e>=1;for(;;){if(m<<=1,v==o-1){d.push(n(m));break}v++}return d.join("")},decompress:function(r){return null==r?"":""==r?null:i._decompress(r.length,32768,function(o){return r.charCodeAt(o)})},_decompress:function(o,n,e){var t,i,s,u,a,p,c,l=[],f=4,h=4,d=3,m="",v=[],g={val:e(0),position:n,index:1};for(t=0;t<3;t+=1)l[t]=t;for(s=0,a=Math.pow(2,2),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 2:return""}for(l[3]=c,i=c,v.push(c);;){if(g.index>o)return"";for(s=0,a=Math.pow(2,d),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(c=s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 2:return v.join("")}if(0==f&&(f=Math.pow(2,d),d++),l[c])m=l[c];else{if(c!==h)return null;m=i+i.charAt(0)}v.push(m),l[h++]=i+m.charAt(0),i=m,0==--f&&(f=Math.pow(2,d),d++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return {StringCompressionLibrary}}):"undefined"!=typeof module&&null!=module?module.exports={StringCompressionLibrary}:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return {StringCompressionLibrary}});`; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index e6541f8..f42182c 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -1,7 +1,6 @@ import { PluginArg, PluginObject } from "../plugin"; import * as t from "@babel/types"; import { Order } from "../../order"; -import { computeProbabilityMap } from "../../probability"; import { ensureComputedExpression, isModuleImport, @@ -9,13 +8,13 @@ import { } from "../../utils/ast-utils"; import { numericLiteral } from "../../utils/node"; import { - PakoInflateMin, + StringCompressionLibraryMinified, StringCompressionTemplate, } from "../../templates/stringCompressionTemplate"; import Obfuscator from "../../obfuscator"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; import { NO_RENAME } from "../../constants"; -const pako = require("pako"); +const LZString = require("lz-string"); export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.StringCompression, { @@ -52,7 +51,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (typeof index === "undefined") { // Allow user option to skip compression for certain strings if ( - !computeProbabilityMap( + !me.computeProbabilityMap( me.options.stringCompression, originalValue ) @@ -85,11 +84,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { ); // Compress the string - var compressedBuffer: Uint8Array = pako.deflate(stringPayload); - var compressedBase64 = - Buffer.from(compressedBuffer).toString("base64"); + var compressedString = LZString.compressToUTF16(stringPayload); - let pakoName = me.obfuscator.getStringCompressionLibraryName(); + let stringCompressionLibraryName = + me.obfuscator.getStringCompressionLibraryName(); let insertStringCompressionLibrary = !me.obfuscator.parentObfuscator; prependProgram( @@ -99,10 +97,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { stringName: me.getPlaceholder(), stringArray: me.getPlaceholder(), stringDelimiter: () => t.stringLiteral(stringDelimiter), - stringValue: () => t.stringLiteral(compressedBase64), + stringValue: () => t.stringLiteral(compressedString), GetGlobalTemplate: createGetGlobalTemplate(me, programPath), getGlobalFnName: me.getPlaceholder(), - pakoName: pakoName, + StringCompressionLibrary: stringCompressionLibraryName, }) ); @@ -110,8 +108,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { // RGF functions should not clone the entire decompression function prependProgram( programPath, - Obfuscator.parseCode(PakoInflateMin.replace(/{pako}/g, pakoName)) - .program.body + Obfuscator.parseCode( + StringCompressionLibraryMinified.replace( + /{StringCompressionLibrary}/g, + stringCompressionLibraryName + ) + ).program.body )[0] .get("declarations")[0] .get("id").node[NO_RENAME] = true; diff --git a/test/transforms/string/stringCompression.test.ts b/test/transforms/string/stringCompression.test.ts index 5dc7553..68d1bcb 100644 --- a/test/transforms/string/stringCompression.test.ts +++ b/test/transforms/string/stringCompression.test.ts @@ -181,3 +181,27 @@ test("Variant #7: Work with Rename Variables", async () => { expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); + +test("Variant #8: Work with RGF", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var myVar = "Hello World"; + TEST_OUTPUT = myVar; + `, + { + target: "node", + stringCompression: true, + rgf: true, + renameVariables: true, + } + ); + + // Ensure String Compression applied + expect(code).not.toContain("Hello World"); + + // Ensure the code still works + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Hello World"); +}); From df60ade484a08f5df4596f1fbdc9c16d17b6eec5 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 11:42:09 -0500 Subject: [PATCH 065/103] Use placeholder prefix --- src/templates/tamperProtectionTemplates.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/templates/tamperProtectionTemplates.ts b/src/templates/tamperProtectionTemplates.ts index b996ca6..78f2821 100644 --- a/src/templates/tamperProtectionTemplates.ts +++ b/src/templates/tamperProtectionTemplates.ts @@ -1,7 +1,11 @@ import { NodePath } from "@babel/traverse"; import { PluginInstance } from "../transforms/plugin"; import Template from "./template"; -import { MULTI_TRANSFORM, UNSAFE } from "../constants"; +import { + MULTI_TRANSFORM, + placeholderVariablePrefix, + UNSAFE, +} from "../constants"; export const StrictModeTemplate = new Template(` (function(){ @@ -109,8 +113,8 @@ export const createEvalIntegrityTemplate = ( } return new Template(` - function {EvalIntegrityName}(a = true){ - return a; + function {EvalIntegrityName}(${placeholderVariablePrefix}_flag = true){ + return ${placeholderVariablePrefix}_flag; } `); }; From fbe344911ae4d0450b97c1c3519d33bac1d60eeb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 11:43:21 -0500 Subject: [PATCH 066/103] Add Predicate Gen for Dead Code and Opaque Predicates --- src/transforms/deadCode.ts | 28 ++++---------- src/transforms/opaquePredicates.ts | 13 +++---- src/utils/PredicateGen.ts | 61 ++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 src/utils/PredicateGen.ts diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index c9a3b27..5f8a5ef 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -1,12 +1,11 @@ import { PluginArg, PluginObject } from "./plugin"; import { chance, choice } from "../utils/random-utils"; import { deadCodeTemplates } from "../templates/deadCodeTemplates"; -import { computeProbabilityMap } from "../probability"; import { Order } from "../order"; import * as t from "@babel/types"; import Template from "../templates/template"; -import { NameGen } from "../utils/NameGen"; import { prepend } from "../utils/ast-utils"; +import PredicateGen from "../utils/PredicateGen"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.DeadCode, { @@ -15,6 +14,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }); let created = 0; + let predicateGen = new PredicateGen(me); return { visitor: { @@ -22,7 +22,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { exit(blockPath) { if (blockPath.find((p) => me.isSkipped(p))) return; - if (!computeProbabilityMap(me.options.deadCode)) { + if (!me.computeProbabilityMap(me.options.deadCode)) { return; } @@ -54,29 +54,15 @@ export default ({ Plugin }: PluginArg): PluginObject => { ) ); - // Overcomplicated way to get a random property name that doesn't exist on the Function - var randomProperty: string; - var nameGen = new NameGen("randomized"); - - function PrototypeCollision() {} - PrototypeCollision(); // Call it for code coverage :D - - do { - randomProperty = nameGen.generate(); - } while ( - !randomProperty || - PrototypeCollision[randomProperty] !== undefined - ); - - me.changeData.deadCode++; - prepend( blockPath, new Template(` - if("${randomProperty}" in ${containingFnName}) { + if({falsePredicate}) { ${containingFnName}() } - `).single() + `).single({ + falsePredicate: predicateGen.generateFalseExpression(blockPath), + }) ); me.skip(blockPath); diff --git a/src/transforms/opaquePredicates.ts b/src/transforms/opaquePredicates.ts index b47f482..245d423 100644 --- a/src/transforms/opaquePredicates.ts +++ b/src/transforms/opaquePredicates.ts @@ -2,9 +2,8 @@ import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; -import { getBlock } from "../utils/ast-utils"; import { chance, getRandomString } from "../utils/random-utils"; -import { computeProbabilityMap } from "../probability"; +import PredicateGen from "../utils/PredicateGen"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.OpaquePredicates, { @@ -13,12 +12,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }); - function createTruePredicate(path: NodePath) { - const controlObject = me.getControlObject(getBlock(path)); - - var trueValue = controlObject.createTruePredicate(); + const predicateGen = new PredicateGen(me); - return trueValue; + function createTruePredicate(path: NodePath) { + return predicateGen.generateTrueExpression(path); } let active = true; @@ -27,7 +24,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (!active) return false; if (path.find((p) => me.isSkipped(p))) return false; - if (!computeProbabilityMap(me.options.opaquePredicates)) return false; + if (!me.computeProbabilityMap(me.options.opaquePredicates)) return false; transformCount++; diff --git a/src/utils/PredicateGen.ts b/src/utils/PredicateGen.ts new file mode 100644 index 0000000..a4aebc3 --- /dev/null +++ b/src/utils/PredicateGen.ts @@ -0,0 +1,61 @@ +import { NodePath } from "@babel/traverse"; +import { PluginInstance } from "../transforms/plugin"; +import * as t from "@babel/types"; +import { prepend } from "./ast-utils"; +import { NameGen } from "./NameGen"; +import Template from "../templates/template"; + +export default class PredicateGen { + constructor(public plugin: PluginInstance) {} + + dummyFunctionName: string | null = null; + programPath: NodePath | null = null; + + ensureCreated() { + if (this.dummyFunctionName) return; + + this.dummyFunctionName = this.plugin.getPlaceholder("dummyFunction"); + + // Insert dummy function + prepend( + this.programPath, + + this.plugin.skip( + t.functionDeclaration( + t.identifier(this.dummyFunctionName), + [], + t.blockStatement([]) + ) + ) + ); + } + + generateTrueExpression(path: NodePath): t.Expression { + return t.unaryExpression("!", this.generateFalseExpression(path)); + } + + generateFalseExpression(path: NodePath): t.Expression { + this.programPath = path.find((p) => p.isProgram()) as NodePath; + this.ensureCreated(); + + // Overcomplicated way to get a random property name that doesn't exist on the Function + var randomProperty: string; + var nameGen = new NameGen("randomized"); + + function PrototypeCollision() {} + PrototypeCollision(); // Call it for code coverage :D + + do { + randomProperty = nameGen.generate(); + } while ( + !randomProperty || + PrototypeCollision[randomProperty] !== undefined + ); + + return this.plugin.skip( + new Template( + `"${randomProperty}" in ${this.dummyFunctionName}` + ).expression() + ); + } +} From dc488c79cab05b575f89a0b9568bc8a0ca93ae95 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 11:44:35 -0500 Subject: [PATCH 067/103] Improve RGF selection to improve Obfuscation times, other improvements --- .../identifier/movedDeclarations.ts | 8 ++- src/transforms/rgf.ts | 51 ++++++++++++++----- src/transforms/string/encoding.ts | 2 +- src/transforms/string/stringConcealing.ts | 3 +- src/utils/NameGen.ts | 6 ++- test/semantics/moduleImport.test.ts | 2 - test/transforms/rgf.test.ts | 21 ++++++-- 7 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/transforms/identifier/movedDeclarations.ts b/src/transforms/identifier/movedDeclarations.ts index f9835ee..5c505ed 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -65,8 +65,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (!functionPath || !(functionPath.node as NodeSymbol)[PREDICTABLE]) return; + var fnBody = functionPath.get("body"); + + if (!fnBody.isBlockStatement()) return; + // Must be direct child of the function - if (path.parentPath !== functionPath.get("body")) return; + if (path.parentPath !== fnBody) return; const functionName = path.node.id.name; @@ -99,7 +103,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { } prepend( - functionPath, + fnBody, new Template(` if(!${functionName}) { ${functionName} = {functionExpression}; diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 5707da2..ce465df 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -3,7 +3,6 @@ import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import * as t from "@babel/types"; import Obfuscator from "../obfuscator"; -import { computeProbabilityMap } from "../probability"; import { append, getFunctionName, @@ -24,6 +23,8 @@ import { numericLiteral } from "../utils/node"; import Template from "../templates/template"; import { createEvalIntegrityTemplate } from "../templates/tamperProtectionTemplates"; +const RGF_ELIGIBLE = Symbol("rgfEligible"); + /** * RGF (Runtime-Generated-Function) uses the `new Function("code")` syntax to create executable code from strings. * @@ -96,14 +97,20 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }, "FunctionDeclaration|FunctionExpression": { - exit(_path) { + enter(_path) { if (!active) return; + + // On enter, determine if Function is eligible for RGF transformation + const path = _path as NodePath< t.FunctionDeclaration | t.FunctionExpression >; if (me.isSkipped(path)) return; + // Skip nested functions if the parent function is already deemed eligible + if (path.find((p) => p.node[RGF_ELIGIBLE])) return; + // Skip async and generator functions if (path.node.async || path.node.generator) return; @@ -111,10 +118,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (name === me.options.lock?.countermeasures) return; if (me.obfuscator.isInternalVariable(name)) return; - me.log(name); - if ( - !computeProbabilityMap( + !me.computeProbabilityMap( me.options.rgf, name, path.getFunctionParent() === null @@ -140,13 +145,14 @@ export default ({ Plugin }: PluginArg): PluginObject => { const binding = idPath.scope.getBinding(name); if (!binding) { - identifierPreventingTransform = name; - idPath.stop(); + // Global variables are allowed return; } + var isOutsideVariable = + path.scope.parent.getBinding(name) === binding; // If the binding is not in the current scope, it is an outside reference - if (binding.scope !== path.scope) { + if (isOutsideVariable) { identifierPreventingTransform = name; idPath.stop(); } @@ -163,9 +169,24 @@ export default ({ Plugin }: PluginArg): PluginObject => { return; } + me.log("Function " + name + " is eligible for RGF transformation"); + path.node[RGF_ELIGIBLE] = true; + }, + exit(_path) { + if (!active) return; + + const path = _path as NodePath< + t.FunctionDeclaration | t.FunctionExpression + >; + + if (me.isSkipped(path)) return; + + // Function is not eligible for RGF transformation + if (!path.node[RGF_ELIGIBLE]) return; + const embeddedName = me.getPlaceholder() + "_embedded"; const replacementName = me.getPlaceholder() + "_replacement"; - const thisName = me.getPlaceholder() + "_this"; + const argumentsName = me.getPlaceholder() + "_args"; const lastNode = t.expressionStatement(t.identifier(embeddedName)); (lastNode as NodeSymbol)[SKIP] = true; @@ -179,10 +200,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { t.variableDeclaration("var", [ t.variableDeclarator( t.arrayPattern([ - t.identifier(thisName), t.identifier(rgfArrayName), + t.identifier(argumentsName), ]), - t.thisExpression() + t.identifier("arguments") ), ]), t.functionDeclaration( @@ -196,7 +217,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { t.identifier(replacementName), t.identifier("apply") ), - [t.identifier(thisName), t.identifier("arguments")] + [t.thisExpression(), t.identifier(argumentsName)] ) ), ]) @@ -272,17 +293,19 @@ export default ({ Plugin }: PluginArg): PluginObject => { true ), [ + t.thisExpression(), t.arrayExpression([ - t.thisExpression(), t.identifier(rgfArrayName), + t.identifier("arguments"), ]), - t.identifier("arguments"), ] ) ), ]) ); + path.skip(); + me.setFunctionLength(path, originalLength); me.changeData.functions++; diff --git a/src/transforms/string/encoding.ts b/src/transforms/string/encoding.ts index b4adfc2..3ea90dc 100644 --- a/src/transforms/string/encoding.ts +++ b/src/transforms/string/encoding.ts @@ -1,6 +1,6 @@ import { CustomStringEncoding } from "../../options"; import Template from "../../templates/template"; -import { choice, shuffle } from "../../utils/random-utils"; +import { shuffle } from "../../utils/random-utils"; import * as t from "@babel/types"; let hasAllEncodings = false; diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index e564fdb..2b37740 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -3,7 +3,6 @@ import { NodePath } from "@babel/traverse"; import Template from "../../templates/template"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; -import { computeProbabilityMap } from "../../probability"; import { ok } from "assert"; import { BufferToStringTemplate } from "../../templates/bufferToStringTemplate"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; @@ -140,7 +139,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Check user setting if ( - !computeProbabilityMap( + !me.computeProbabilityMap( me.options.stringConcealing, originalValue ) diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts index 503ad2d..d3fc749 100644 --- a/src/utils/NameGen.ts +++ b/src/utils/NameGen.ts @@ -2,8 +2,8 @@ import { ok } from "assert"; import { ObfuscateOptions } from "../options"; import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils"; import { choice, getRandomHexString, getRandomInteger } from "./random-utils"; -import { computeProbabilityMap } from "../probability"; import { reservedKeywords, reservedObjectPrototype } from "../constants"; +import Obfuscator from "../obfuscator"; /** * Generate random names for variables and properties. @@ -33,7 +33,9 @@ export class NameGen { return value; } - var mode = computeProbabilityMap(this.identifierGenerator); + var mode = Obfuscator.prototype.computeProbabilityMap( + this.identifierGenerator + ); const randomizedLength = getRandomInteger(6, 8); diff --git a/test/semantics/moduleImport.test.ts b/test/semantics/moduleImport.test.ts index 3c9c12b..0f20c90 100644 --- a/test/semantics/moduleImport.test.ts +++ b/test/semantics/moduleImport.test.ts @@ -9,8 +9,6 @@ test("Variant #1: Import Declaration on High Preset", async () => { var inputString = "Hash this string"; var hashed = createHash("sha256").update(inputString).digest("hex"); TEST_OUTPUT = hashed; - - console.log(TEST_OUTPUT) `, { target: "node", diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index a54edbc..8f1a4a7 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -314,7 +314,9 @@ test("Variant #10: Configurable by custom function option", async () => { expect(TEST_OUTPUT_2).toStrictEqual(true); }); -test("Variant #11: Function containing function should both be changed", async function () { +test("Variant #11: Functions containing functions should only transform the parent function", async function () { + var fnNamesCollected: string[] = []; + var { code: output } = await JsConfuser.obfuscate( ` function FunctionA(){ @@ -330,11 +332,22 @@ test("Variant #11: Function containing function should both be changed", async f TEST_OUTPUT = FunctionA(); `, - { target: "node", rgf: true } + { + target: "node", + rgf: (fnName) => { + fnNamesCollected.push(fnName); + + return true; + }, + } ); - // 2 means one Function changed, 3 means two Functions changed - expect(output.split('_rgf_eval("').length).toStrictEqual(3); + // Ensure only FunctionA was transformed + expect(fnNamesCollected).toContain("FunctionA"); + expect(fnNamesCollected).not.toContain("FunctionB"); + + // Only the most parent function should be changed + expect(output.split('_rgf_eval("').length).toStrictEqual(2); var TEST_OUTPUT; eval(output); From 455f75e59c1c1157573f331788064b968ddb9bc5 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 13:30:20 -0500 Subject: [PATCH 068/103] Fix small bugs, improve test coverage --- CHANGELOG.md | 16 ++++++++ src/obfuscator.ts | 12 ++++-- src/templates/getGlobalTemplate.ts | 6 ++- src/transforms/rgf.ts | 4 +- src/transforms/string/stringCompression.ts | 3 ++ test/code/Cash.test.ts | 4 +- test/code/ES6.test.ts | 4 +- test/options.test.ts | 48 ++++++++++++++++++++++ 8 files changed, 87 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e795a..e632f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,22 @@ - Renamed `Stack` to `Variable Masking` +- Added configurable limits to options: + +```js +const options = { + target: "node", + + rgf: { + value: 0.5, // = 50% of eligible functions + limit: 10 // Maximum of 10 changes for performance reasons + }, + + // Original format is still valid (No limit applied) + // rgf: 0.5 +} +``` + ### 2.0 Changes - Added Custom String Encoding and Custom Lock Code options diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 31ffcf8..f135dd7 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -186,7 +186,13 @@ export default class Obfuscator { push(this.options.opaquePredicates, opaquePredicates); push(this.options.stringSplitting, stringSplitting); push(this.options.stringConcealing, stringConcealing); - push(this.options.stringCompression, stringCompression); + // String Compression is only applied to the main obfuscator + // Any RGF functions will not have string compression due to the size of the decompression function + + push( + !parentObfuscator && this.options.stringCompression, + stringCompression + ); push(this.options.variableMasking, variableMasking); push(this.options.duplicateLiteralsRemoval, duplicateLiteralsRemoval); push(this.options.shuffle, shuffle); @@ -368,9 +374,9 @@ export default class Obfuscator { ...customImplementationArgs: F extends (...args: infer P) => any ? P : never ): boolean | string { // Check if this probability map uses the {value: ..., limit: ...} format - if (typeof map === "object" && "value" in map) { + if (typeof map === "object" && map && "value" in map) { // Check for the limit property - if ("limit" in map && typeof map.limit === "number") { + if ("limit" in map && typeof map.limit === "number" && map.limit >= 0) { // Check if the limit has been reached if (this.probabilityMapCounter.get(map) >= map.limit) { return false; diff --git a/src/templates/getGlobalTemplate.ts b/src/templates/getGlobalTemplate.ts index 393ca5f..fe55f1d 100644 --- a/src/templates/getGlobalTemplate.ts +++ b/src/templates/getGlobalTemplate.ts @@ -2,12 +2,16 @@ import { NodePath } from "@babel/traverse"; import { PluginInstance } from "../transforms/plugin"; import Template from "./template"; import { UNSAFE } from "../constants"; +import { isStrictMode } from "../utils/ast-utils"; export const createGetGlobalTemplate = ( pluginInstance: PluginInstance, path: NodePath ) => { - if (pluginInstance.options.lock?.tamperProtection) { + if ( + pluginInstance.options.lock?.tamperProtection && + !path.find((p) => isStrictMode(p)) + ) { return new Template(` function {getGlobalFnName}(){ var localVar = false; diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index ce465df..01d2e58 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -12,6 +12,7 @@ import { prepend, } from "../utils/ast-utils"; import { + MULTI_TRANSFORM, NodeSymbol, PREDICTABLE, reservedIdentifiers, @@ -109,7 +110,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (me.isSkipped(path)) return; // Skip nested functions if the parent function is already deemed eligible - if (path.find((p) => p.node[RGF_ELIGIBLE])) return; + if (path.find((p) => p.node[RGF_ELIGIBLE] || p.node[MULTI_TRANSFORM])) + return; // Skip async and generator functions if (path.node.async || path.node.generator) return; diff --git a/src/transforms/string/stringCompression.ts b/src/transforms/string/stringCompression.ts index f42182c..26f9679 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -23,6 +23,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }); + // String Compression is only applied to the main obfuscator + // Any RGF functions will not have string compression due to the size of the decompression function + const stringDelimiter = "|"; return { diff --git a/test/code/Cash.test.ts b/test/code/Cash.test.ts index 2034c8a..fbad3a7 100644 --- a/test/code/Cash.test.ts +++ b/test/code/Cash.test.ts @@ -32,9 +32,7 @@ const handleError = (error, output) => { }`; console.error(error); - writeFileSync("dev.output.js", helperCode + "\n" + output, { - encoding: "utf-8", - }); + // writeFileSync("dev.output.js", helperCode + "\n" + output, { encoding: "utf-8", }); expect("An error occurred").toStrictEqual(null); }; diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index 31e7465..abb00ce 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -47,7 +47,7 @@ test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protec pack: true, rgf: { value: true, - limit: 10, + limit: 5, }, lock: { integrity: true, @@ -57,7 +57,7 @@ test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protec }, }); - writeFileSync("./dev.output.js", code, "utf-8"); + // writeFileSync("./dev.output.js", code, "utf-8"); const TEST_OUTPUT = {}; eval(code); diff --git a/test/options.test.ts b/test/options.test.ts index f53595b..dbd5dff 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -253,3 +253,51 @@ test("Variant #15: Fine-tune options using the limit property", async () => { expect(code).toContain("keepMyVar4"); expect(code).toContain("keepMyVar5"); }); + +test("Variant #16: Limit of not should rename any variables", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var renameMyVar1 = 1; + var renameMyVar2 = 2; + `, + { + target: "node", + renameVariables: { + value: true, + limit: 0, + }, + identifierGenerator: "mangled", + } + ); + + // Ensure the variable names were preserved + expect(code).toContain("renameMyVar1"); + expect(code).toContain("renameMyVar2"); + + expect(code).not.toContain("var a"); + expect(code).not.toContain("var b"); +}); + +test("Variant #17: Limit of -1 should rename all variables", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var renameMyVar1 = 1; + var renameMyVar2 = 2; + `, + { + target: "node", + renameVariables: { + value: true, + limit: -1, + }, + identifierGenerator: "mangled", + } + ); + + // Ensure both variables were renamed + expect(code).not.toContain("renameMyVar1"); + expect(code).not.toContain("renameMyVar2"); + + expect(code).toContain("var a"); + expect(code).toContain("var b"); +}); From 355a8ec45a1f34aca7cd3cfe17f6630518bddbde Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 18:05:56 -0500 Subject: [PATCH 069/103] Update to `NumericLiteral` --- src/transforms/finalizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index 4a56e00..9ddcfc2 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -41,7 +41,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { }), // Hexadecimal numbers - NumberLiteral: { + NumericLiteral: { exit(path) { if (me.options.hexadecimalNumbers) { const { value } = path.node; From 610fde70b6989a7c6b35df0f955cc47438808c0f Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sun, 10 Nov 2024 18:10:41 -0500 Subject: [PATCH 070/103] 2.0.0-alpha.3 --- CHANGELOG.md | 16 +++++++++------- package.json | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e632f00..a50717a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,12 @@ **⚠️ Warning: This an alpha release. This version is not stable and the likelihood of encountering bugs is significantly higher.** -### Complete rewrite of JS-Confuser using Babel! +### Complete rewrite of JS-Confuser using Babel! 🎉 **⚠️ Breaking changes** +> Check out the [Migration guide](./Migration.md) on how to properly update from 1.X to 2.0. The obfuscation upgrades in 2.0 are worth the small refactoring. + - Revamped API Interface - - JSConfuser.obfuscate() resolves to an object @@ -29,17 +31,17 @@ const options = { }, // Original format is still valid (No limit applied) - // rgf: 0.5 + rgf: 0.5 } ``` ### 2.0 Changes -- Added Custom String Encoding and Custom Lock Code options +- Added [Custom String Encoding](https://new--confuser.netlify.app/docs/options/customStringEncodings) and [Custom Lock Code](https://new--confuser.netlify.app/docs/options/customLocks) options -- Added `Rename Labels` Learn more here +- Added `Rename Labels` [Learn more here](https://new--confuser.netlify.app/docs/options/renamelabels#rename-labels) -- Added `Pack` Learn more here +- Added `Pack` [Learn more here](https://new--confuser.netlify.app/docs/options/pack#pack) - RGF no longers uses `new Function` instead uses `eval` @@ -65,7 +67,7 @@ const options = { - - [Regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions) are now obfuscated (First converted into equivalent RegExp() constructor calls) -- - `String Compression` now uses zlib decompression ([Pako](https://github.com/nodeca/pako)) +- - `String Compression` now uses LZ-string compression ([lz-string](https://www.npmjs.com/package/lz-string)) ### JS-Confuser.com Revamp @@ -77,7 +79,7 @@ The previous version will remain available: [old--confuser.netlify.com](https:// - Removed `ES5` option - Use Babel Instead -- Removed `Browser Lock` and `OS Lock` - Use Custom Locks instead +- Removed `Browser Lock` and `OS Lock` - Use [Custom Locks](https://new--confuser.netlify.app/docs/options/customlocks#custom-locks) instead - Removed `Shuffle`'s Hash option diff --git a/package.json b/package.json index 76f2c61..25d0542 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-confuser", - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", From 07fd7fb176ec51b0d262a8f09141021a817d81a1 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 11 Nov 2024 00:31:41 -0500 Subject: [PATCH 071/103] Add option `chinese` for identifier generator --- src/options.ts | 7 ++++++- src/utils/NameGen.ts | 10 +++++++++- src/utils/random-utils.ts | 11 +++++++++++ test/transforms/identifier/renameVariables.test.ts | 1 + 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/options.ts b/src/options.ts index 7910b1f..c614b19 100644 --- a/src/options.ts +++ b/src/options.ts @@ -135,7 +135,12 @@ export interface ObfuscateOptions { * JS-Confuser tries to reuse names when possible, creating very potent code. */ identifierGenerator?: ProbabilityMap< - "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number", + | "hexadecimal" + | "randomized" + | "zeroWidth" + | "mangled" + | "number" + | "chinese", () => string >; diff --git a/src/utils/NameGen.ts b/src/utils/NameGen.ts index d3fc749..b5249f4 100644 --- a/src/utils/NameGen.ts +++ b/src/utils/NameGen.ts @@ -1,7 +1,12 @@ import { ok } from "assert"; import { ObfuscateOptions } from "../options"; import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils"; -import { choice, getRandomHexString, getRandomInteger } from "./random-utils"; +import { + choice, + getRandomChineseString, + getRandomHexString, + getRandomInteger, +} from "./random-utils"; import { reservedKeywords, reservedObjectPrototype } from "../constants"; import Obfuscator from "../obfuscator"; @@ -70,6 +75,9 @@ export class NameGen { case "zeroWidth": return this.zeroWidthGenerator.generate(); + case "chinese": + return getRandomChineseString(randomizedLength); + default: throw new Error( "Invalid identifier generator mode: " + this.identifierGenerator diff --git a/src/utils/random-utils.ts b/src/utils/random-utils.ts index 5414269..e8b6e9d 100644 --- a/src/utils/random-utils.ts +++ b/src/utils/random-utils.ts @@ -41,6 +41,17 @@ export function getRandomHexString(length: number) { .toUpperCase(); } +export function getRandomChineseString(length: number) { + const characters: string[] = []; + for (let i = 0; i < length; i++) + characters.push( + String.fromCharCode( + Math.floor(Math.random() * (0x9fff - 0x4e00)) + 0x4e00 + ) + ); + return characters.join(""); +} + /** * Returns a random string. */ diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index cdcc618..fc7b1f2 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -537,6 +537,7 @@ test.each([ "mangled", "number", "zeroWidth", + "chinese", customIdentifierGenerator, ])( "Variant #21: Work with custom identifierGenerator mode", From 51586132f684cbe469a7288cf870df561ac1394b Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 11 Nov 2024 23:55:02 -0500 Subject: [PATCH 072/103] Variable Masking bug fix --- src/transforms/variableMasking.ts | 1 + test/code/ES6.src.js | 9 ++++++--- test/transforms/variableMasking.test.ts | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/transforms/variableMasking.ts b/src/transforms/variableMasking.ts index 53c6496..437d7d6 100644 --- a/src/transforms/variableMasking.ts +++ b/src/transforms/variableMasking.ts @@ -154,6 +154,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { fnPath.traverse({ Identifier(path) { if (!isVariableIdentifier(path)) return; + if (fnPath.get("id") === path) return; // Skip this function's name (Test #21) if (reservedIdentifiers.has(path.node.name)) return; if (me.options.globalVariables.has(path.node.name)) return; diff --git a/test/code/ES6.src.js b/test/code/ES6.src.js index 06cb196..e57dadf 100644 --- a/test/code/ES6.src.js +++ b/test/code/ES6.src.js @@ -211,15 +211,18 @@ function labeledBreaksAndContinues() { TEST_OUTPUT["Variant #18"] = labeledBreaksAndContinues() === 15; -// Variant #16: Function.length property +// Variant #19: Function.length property var variant19 = function (n1, n2, n3, n4, n5) { var _ = true; }; TEST_OUTPUT["Variant #19"] = variant19.length === 5; -// Set 'ranAllTest' to TRUE -TEST_OUTPUT["Variant #20"] = true; +// Variant #20: Function name and parameter name collision +function fnName(fnName) { + TEST_OUTPUT["Variant #20"] = fnName === "Correct Value"; +} +fnName("Correct Value"); function countermeasures() { throw new Error("Countermeasures function called."); diff --git a/test/transforms/variableMasking.test.ts b/test/transforms/variableMasking.test.ts index f9f3ab0..00b7fc7 100644 --- a/test/transforms/variableMasking.test.ts +++ b/test/transforms/variableMasking.test.ts @@ -660,3 +660,22 @@ test("Variant #20: Handle __JS_CONFUSER_VAR__ function", async () => { eval(code); expect(TEST_OUTPUT).not.toBeUndefined(); }); + +test("Variant #21: Function name and parameter name clash", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function value(value) { + TEST_OUTPUT = value; + } + + value("Correct Value"); + `, + { target: "node", variableMasking: true } + ); + + var TEST_OUTPUT; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); From a3b8493c55341ee12b839ad026b75c1bebce3f96 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Mon, 11 Nov 2024 23:55:14 -0500 Subject: [PATCH 073/103] Small tweaks --- src/utils/random-utils.ts | 3 +++ test/presets.test.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/random-utils.ts b/src/utils/random-utils.ts index e8b6e9d..3e2dfe0 100644 --- a/src/utils/random-utils.ts +++ b/src/utils/random-utils.ts @@ -41,6 +41,9 @@ export function getRandomHexString(length: number) { .toUpperCase(); } +/** + * @see https://github.com/MichaelXF/js-confuser/issues/150#issuecomment-2466159582 + */ export function getRandomChineseString(length: number) { const characters: string[] = []; for (let i = 0; i < length; i++) diff --git a/test/presets.test.ts b/test/presets.test.ts index 219b1a3..31af797 100644 --- a/test/presets.test.ts +++ b/test/presets.test.ts @@ -12,6 +12,6 @@ test.each(Object.keys(presets))( // Validate options expect(() => { validateOptions(preset); - }).not.toThrow; + }).not.toThrow(); } ); From 12a7011d7401c81cf1a553cde237df8f738866c9 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 16 Nov 2024 15:42:42 -0500 Subject: [PATCH 074/103] Improve exported types --- index.d.ts | 18 ++++++++++++++++-- src/index.ts | 3 ++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7c5e0b7..3ecbe5f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,17 @@ -import JsConfuser from "./src"; +// Export all the types from the index file +export * from "./src/index"; +export { default } from "./src/index"; -export default JsConfuser; +// Export useful types +export type { + ObfuscateOptions, + ProbabilityMap, + CustomLock, + CustomStringEncoding, +} from "./src/options"; +export type { + ObfuscationResult, + ProfileData, + ProfilerCallback, + ProfilerLog, +} from "./src/obfuscationResult"; diff --git a/src/index.ts b/src/index.ts index 147b4f7..3c8cf64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -96,9 +96,10 @@ const JsConfuser = { obfuscate, obfuscateAST, obfuscateWithProfiler, + Obfuscator, presets, Template, }; export default JsConfuser; -export { presets, Template }; +export { Obfuscator, presets, Template }; From ca2c90e6bb1b5ea55782eebd45ba38ca1cff3a7e Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 16 Nov 2024 15:50:59 -0500 Subject: [PATCH 075/103] Improve CFF for Program level --- src/transforms/controlFlowFlattening.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 20a8022..f523e21 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1547,12 +1547,17 @@ export default ({ Plugin }: PluginArg): PluginObject => { .scopeManager.getObjectExpression(startLabel), ]); - var resultVar = withIdentifier("result"); - var allowReturns = blockPath.find((p) => p.isFunction()); + const resultVar = withIdentifier("result"); + + const isTopLevel = blockPath.isProgram(); + const allowReturns = + !isTopLevel && blockPath.find((p) => p.isFunction()); + + const startPrefix = allowReturns ? `var {resultVar} = ` : ""; const startProgramStatements = new Template(` ${allowReturns ? `var {didReturnVar};` : ""} - var {resultVar} = {startProgramExpression}; + ${startPrefix}{startProgramExpression}; ${ allowReturns ? ` From b5d12bc8588a2659c70507e63c0c31d2875c4bfa Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 16 Nov 2024 16:01:46 -0500 Subject: [PATCH 076/103] New comment syntax `/* @js-confuser-var */ "name"` --- CHANGELOG.md | 16 ++++++++++ src/transforms/preparation.ts | 32 +++++++++++++++++++ .../identifier/renameVariables.test.ts | 11 ++++--- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a50717a..4a68040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,22 @@ const options = { - - `String Compression` now uses LZ-string compression ([lz-string](https://www.npmjs.com/package/lz-string)) +- New Comment Syntax + +- - `/* @js-confuser-var */ "name"` for improved variable mappings for eval() calls + +```js +// Input +var name = "Internet User"; +eval( "console.log(" + /* @js-confuser-var */ "name" + ")" ); + +// Output +var zC3PLKu = "Internet User"; +eval("console.log(" + "zC3PLKu" + ")"); +``` + +The function `__JS_CONFUSER_VAR__` is identical in outcome and still be used, however, the comment syntax is preferred as the comment syntax's preserves the original script's behavior. + ### JS-Confuser.com Revamp A new UI for JS-Confuser.com, featuring an advanced playground and documentation pages. diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 771c05f..59d3f1b 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -15,6 +15,7 @@ import { getPatternIdentifierNames, } from "../utils/ast-utils"; import { isVariableFunctionIdentifier } from "../utils/function-utils"; +import Template from "../templates/template"; /** * Preparation arranges the user's code into an AST the obfuscator can easily transform. @@ -53,6 +54,37 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }, + // @js-confuser-var "myVar" -> __JS_CONFUSER_VAR__(myVar) + StringLiteral: { + exit(path) { + // Check for @js-confuser-var comment + if ( + path.node.leadingComments?.find((comment) => + comment.value.includes("@js-confuser-var") + ) + ) { + var identifierName = path.node.value; + ok( + t.isValidIdentifier(identifierName), + "Invalid identifier name: " + identifierName + ); + + // Create a new __JS_CONFUSER_VAR__ call with the identifier + var newExpression = new Template( + `__JS_CONFUSER_VAR__({identifier})` + ).expression({ + identifier: t.identifier(identifierName), + }); + + path.replaceWith(newExpression); + + // Remove comment and skip further processing + path.node.leadingComments = []; + path.skip(); + } + }, + }, + // `Hello ${username}` -> "Hello " + username TemplateLiteral: { exit(path) { diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index fc7b1f2..43b3bfb 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -663,14 +663,14 @@ test("Variant #25: Reference catch parameter", async () => { expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); -test("Variant #26: Transform __JS_CONFUSER_VAR__ to access variable mappings", async () => { +test("Variant #26: Transform __JS_CONFUSER_VAR__ and @js-confuser-var to access variable mappings", async () => { var { code: output } = await JsConfuser.obfuscate( ` var myVar1 = "Incorrect Value"; function myFunction(){ var myVar1 = "Correct Value"; - TEST_OUTPUT = eval( __JS_CONFUSER_VAR__(myVar1) ); + TEST_OUTPUT = eval( /* @js-confuser-var */ "myVar1" ); } // Work on functions too @@ -688,21 +688,22 @@ test("Variant #26: Transform __JS_CONFUSER_VAR__ to access variable mappings", a expect(TEST_OUTPUT).toStrictEqual("Correct Value"); }); -test("Variant #27: Transform __JS_CONFUSER_VAR__ even when Rename Variables is disabled", async () => { +test("Variant #27: Transform __JS_CONFUSER_VAR__ and @js-confuser-var even when Rename Variables is disabled", async () => { var { code: output } = await JsConfuser.obfuscate( ` var name = "John Doe"; - TEST_OUTPUT = __JS_CONFUSER_VAR__(name); + TEST_OUTPUT = __JS_CONFUSER_VAR__(name) + "-" + /* @js-confuser-var */ "name"; `, { target: "node", renameVariables: false } ); expect(output).not.toContain("__JS_CONFUSER_VAR__"); + expect(output).not.toContain("@js-confuser-var"); var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual("name"); + expect(TEST_OUTPUT).toStrictEqual("name-name"); }); test("Variant #28: Transform __JS_CONFUSER_VAR__ on High Preset", async () => { From 14b376e13148eb1fc2290594a9e27541a8dd1ab9 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 19 Nov 2024 14:45:32 -0500 Subject: [PATCH 077/103] Repo update --- .prettierrc | 4 ++++ Migration.md | 31 +++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..2e929b7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "singleQuote": false +} diff --git a/Migration.md b/Migration.md index 0f56357..f436567 100644 --- a/Migration.md +++ b/Migration.md @@ -6,15 +6,20 @@ JS-Confuser 2.0 is complete rewrite of the original JS-Confuser created in 2020! ### JSConfuser.obfuscate() returns an object now -The method `JSConfuser.obfuscate()` resolves to a object now instead of a string. This result object contains the obfuscated code on the `code` property. +The method `JSConfuser.obfuscate()` resolves to a object now instead of a string. This result object contains a property `code` which is the obfuscated code. ```diff -+JSConfuser.obfuscate(sourceCode, options).then(result=>{ -+ console.log(result.code); -+}); --JSConfuser.obfuscate(sourceCode, options).then(obfuscatedCode=>{ -- console.log(obfuscatedCode); --}); +const sourceCode = `console.log("Hello World")`; +const options = { + target: "node", + preset: "high" +}; + +JSConfuser.obfuscate(sourceCode, options).then(result=>{ + // 'result' is now an object +- console.log(result); ++ console.log(result.code); +}); ``` ### Removed Anti Debug Lock / Browser Lock / OS Lock @@ -54,4 +59,14 @@ These features have been removed but you can still add these locks using the `lo The option `stack` has been renamed to `variableMasking` -[Similar to JScrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) \ No newline at end of file +[Similar to JScrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) + +```diff +const options = { + target: "node", + preset: "high" + +- stack: true, ++ variableMasking: true +}; +``` \ No newline at end of file From e6a0fd8750989f52f7b2a518c97310c9251695ec Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 19 Nov 2024 14:58:48 -0500 Subject: [PATCH 078/103] Improve Global Concealing --- src/transforms/identifier/globalConcealing.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index 8e09bd8..da43818 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -15,7 +15,11 @@ import { prepend, } from "../../utils/ast-utils"; import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; -import { getRandomInteger, getRandomString } from "../../utils/random-utils"; +import { + getRandomInteger, + getRandomString, + shuffle, +} from "../../utils/random-utils"; import { ok } from "assert"; const ignoreGlobals = new Set([ @@ -51,19 +55,21 @@ export default ({ Plugin }: PluginArg): PluginObject => { } const createSwitchStatement = () => { - const cases = Array.from(globalMapping.keys()).map((originalName) => { - var mappedKey = globalMapping.get(originalName); - - return t.switchCase(t.stringLiteral(mappedKey), [ - t.returnStatement( - t.memberExpression( - t.identifier(globalVarName), - t.stringLiteral(originalName), - true - ) - ), - ]); - }); + const cases = shuffle(Array.from(globalMapping.keys())).map( + (originalName) => { + var mappedKey = globalMapping.get(originalName); + + return t.switchCase(t.stringLiteral(mappedKey), [ + t.returnStatement( + t.memberExpression( + t.identifier(globalVarName), + t.stringLiteral(originalName), + true + ) + ), + ]); + } + ); return t.switchStatement(t.identifier("mapping"), cases); }; From 615e19b9f0a8e93c08457081b0d32d422f68d7e5 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 19 Nov 2024 15:03:38 -0500 Subject: [PATCH 079/103] CFF test for multiple deletes --- .../controlFlowFlattening.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 98b541d..144d2dd 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -578,6 +578,35 @@ test("Variant #17: Flatten with infinite for loop and break", async () => { expect(TEST_ARRAY).toStrictEqual([1, 2, 3, 4, 5]); }); +test("Variant #18: Multiple deletes", async () => { + var { code } = await JsConfuser.obfuscate( + ` +let a = { + key1: true, + key2: true, + key3: true, + last: true, +}; + +delete a['key1']; +delete a['key2']; +delete a['key3']; + +TEST_OUTPUT = a; + `, + { + target: "node", + controlFlowFlattening: true, + pack: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual({ last: true }); +}); + test("Variant #20: Work with redefined functions", async () => { var { code: output } = await JsConfuser.obfuscate( ` From 0b10802e97657667811220dd076762c6bdf11119 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 19 Nov 2024 17:39:43 -0500 Subject: [PATCH 080/103] Remove src_old --- src_old/compiler.ts | 35 - src_old/constants.ts | 101 - src_old/index.ts | 198 -- src_old/obfuscator.ts | 165 -- src_old/options.ts | 898 ------- src_old/order.ts | 56 - src_old/parser.ts | 49 - src_old/precedence.ts | 61 - src_old/presets.ts | 120 - src_old/probability.ts | 125 - src_old/templates/bufferToString.ts | 136 -- src_old/templates/core.ts | 29 - src_old/templates/crash.ts | 23 - src_old/templates/es5.ts | 131 - src_old/templates/functionLength.ts | 32 - src_old/templates/globals.ts | 3 - src_old/templates/template.ts | 230 -- src_old/transforms/antiTooling.ts | 102 - src_old/transforms/calculator.ts | 229 -- .../controlFlowFlattening.ts | 2153 ----------------- .../expressionObfuscation.ts | 179 -- src_old/transforms/deadCode.ts | 676 ------ src_old/transforms/dispatcher.ts | 640 ----- src_old/transforms/es5/antiClass.ts | 276 --- src_old/transforms/es5/antiDestructuring.ts | 294 --- src_old/transforms/es5/antiES6Object.ts | 267 -- src_old/transforms/es5/antiSpreadOperator.ts | 56 - src_old/transforms/es5/antiTemplate.ts | 98 - src_old/transforms/es5/es5.ts | 149 -- .../transforms/extraction/classExtraction.ts | 168 -- .../extraction/duplicateLiteralsRemoval.ts | 297 --- .../transforms/extraction/objectExtraction.ts | 360 --- src_old/transforms/finalizer.ts | 75 - src_old/transforms/flatten.ts | 557 ----- .../transforms/identifier/globalAnalysis.ts | 102 - .../transforms/identifier/globalConcealing.ts | 297 --- .../identifier/movedDeclarations.ts | 153 -- .../transforms/identifier/renameVariables.ts | 300 --- .../transforms/identifier/variableAnalysis.ts | 118 - src_old/transforms/lock/antiDebug.ts | 112 - src_old/transforms/lock/integrity.ts | 282 --- src_old/transforms/lock/lock.ts | 650 ----- src_old/transforms/minify.ts | 722 ------ src_old/transforms/opaquePredicates.ts | 242 -- src_old/transforms/preparation.ts | 254 -- src_old/transforms/renameLabels.ts | 77 - src_old/transforms/rgf.ts | 424 ---- src_old/transforms/shuffle.ts | 254 -- src_old/transforms/stack.ts | 557 ----- src_old/transforms/string/encoding.ts | 250 -- .../transforms/string/stringCompression.ts | 309 --- src_old/transforms/string/stringConcealing.ts | 380 --- src_old/transforms/string/stringEncoding.ts | 95 - src_old/transforms/string/stringSplitting.ts | 86 - src_old/transforms/transform.ts | 441 ---- src_old/traverse.ts | 120 - src_old/types.ts | 133 - src_old/util/compare.ts | 181 -- src_old/util/gen.ts | 651 ----- src_old/util/guard.ts | 17 - src_old/util/identifiers.ts | 494 ---- src_old/util/insert.ts | 419 ---- src_old/util/math.ts | 15 - src_old/util/object.ts | 39 - src_old/util/random.ts | 221 -- src_old/util/scope.ts | 21 - 66 files changed, 17384 deletions(-) delete mode 100644 src_old/compiler.ts delete mode 100644 src_old/constants.ts delete mode 100644 src_old/index.ts delete mode 100644 src_old/obfuscator.ts delete mode 100644 src_old/options.ts delete mode 100644 src_old/order.ts delete mode 100644 src_old/parser.ts delete mode 100644 src_old/precedence.ts delete mode 100644 src_old/presets.ts delete mode 100644 src_old/probability.ts delete mode 100644 src_old/templates/bufferToString.ts delete mode 100644 src_old/templates/core.ts delete mode 100644 src_old/templates/crash.ts delete mode 100644 src_old/templates/es5.ts delete mode 100644 src_old/templates/functionLength.ts delete mode 100644 src_old/templates/globals.ts delete mode 100644 src_old/templates/template.ts delete mode 100644 src_old/transforms/antiTooling.ts delete mode 100644 src_old/transforms/calculator.ts delete mode 100644 src_old/transforms/controlFlowFlattening/controlFlowFlattening.ts delete mode 100644 src_old/transforms/controlFlowFlattening/expressionObfuscation.ts delete mode 100644 src_old/transforms/deadCode.ts delete mode 100644 src_old/transforms/dispatcher.ts delete mode 100644 src_old/transforms/es5/antiClass.ts delete mode 100644 src_old/transforms/es5/antiDestructuring.ts delete mode 100644 src_old/transforms/es5/antiES6Object.ts delete mode 100644 src_old/transforms/es5/antiSpreadOperator.ts delete mode 100644 src_old/transforms/es5/antiTemplate.ts delete mode 100644 src_old/transforms/es5/es5.ts delete mode 100644 src_old/transforms/extraction/classExtraction.ts delete mode 100644 src_old/transforms/extraction/duplicateLiteralsRemoval.ts delete mode 100644 src_old/transforms/extraction/objectExtraction.ts delete mode 100644 src_old/transforms/finalizer.ts delete mode 100644 src_old/transforms/flatten.ts delete mode 100644 src_old/transforms/identifier/globalAnalysis.ts delete mode 100644 src_old/transforms/identifier/globalConcealing.ts delete mode 100644 src_old/transforms/identifier/movedDeclarations.ts delete mode 100644 src_old/transforms/identifier/renameVariables.ts delete mode 100644 src_old/transforms/identifier/variableAnalysis.ts delete mode 100644 src_old/transforms/lock/antiDebug.ts delete mode 100644 src_old/transforms/lock/integrity.ts delete mode 100644 src_old/transforms/lock/lock.ts delete mode 100644 src_old/transforms/minify.ts delete mode 100644 src_old/transforms/opaquePredicates.ts delete mode 100644 src_old/transforms/preparation.ts delete mode 100644 src_old/transforms/renameLabels.ts delete mode 100644 src_old/transforms/rgf.ts delete mode 100644 src_old/transforms/shuffle.ts delete mode 100644 src_old/transforms/stack.ts delete mode 100644 src_old/transforms/string/encoding.ts delete mode 100644 src_old/transforms/string/stringCompression.ts delete mode 100644 src_old/transforms/string/stringConcealing.ts delete mode 100644 src_old/transforms/string/stringEncoding.ts delete mode 100644 src_old/transforms/string/stringSplitting.ts delete mode 100644 src_old/transforms/transform.ts delete mode 100644 src_old/traverse.ts delete mode 100644 src_old/types.ts delete mode 100644 src_old/util/compare.ts delete mode 100644 src_old/util/gen.ts delete mode 100644 src_old/util/guard.ts delete mode 100644 src_old/util/identifiers.ts delete mode 100644 src_old/util/insert.ts delete mode 100644 src_old/util/math.ts delete mode 100644 src_old/util/object.ts delete mode 100644 src_old/util/random.ts delete mode 100644 src_old/util/scope.ts diff --git a/src_old/compiler.ts b/src_old/compiler.ts deleted file mode 100644 index 43b7c7c..0000000 --- a/src_old/compiler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ok } from "assert"; -import { writeFileSync } from "fs"; -import { ObfuscateOptions } from "./options"; -import { Node } from "./util/gen"; - -const escodegen = require("escodegen"); - -export default async function compileJs(tree: any, options: ObfuscateOptions) { - return compileJsSync(tree, options); -} - -export function compileJsSync(tree: any, options: ObfuscateOptions): string { - var api: any = { - format: { - ...escodegen.FORMAT_MINIFY, - }, - }; - - if (!options.compact) { - api = {}; - - if (options.indent && options.indent != 4) { - api.format = {}; - api.format.indent = { - style: { 2: " ", tabs: "\t" }[options.indent] || " ", - }; - } - } - - if (options.debugComments) { - api.comment = true; - } - - return escodegen.generate(tree, api); -} diff --git a/src_old/constants.ts b/src_old/constants.ts deleted file mode 100644 index b7256e9..0000000 --- a/src_old/constants.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Keywords disallowed for variable names in ES5 and under. - */ -export const reservedKeywords = new Set([ - "abstract", - "arguments", - "await", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "double", - "else", - "enum", - "eval", - "export", - "extends", - "false", - "final", - "finally", - "float", - "for", - "function", - "goto", - "if", - "implements", - "import", - "in", - "instanceof", - "int", - "interface", - "let", - "long", - "native", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "short", - "static", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "true", - "try", - "typeof", - "var", - "void", - "volatile", - "while", - "with", - "yield", -]); - -/** - * Identifiers that are not actually variables. - */ -export const reservedIdentifiers = new Set([ - "undefined", - "null", - "NaN", - "Infinity", - "eval", - "arguments", -]); - -export const noRenameVariablePrefix = "__NO_JS_CONFUSER_RENAME__"; -export const placeholderVariablePrefix = "__p_"; - -/** - * Tells the obfuscator this function is predictable: - * - Never called with extraneous parameters - */ -export const predictableFunctionTag = "__JS_PREDICT__"; - -/** - * Tells the obfuscator this function is critical for the Obfuscated code. - * - Example: string decryption function - */ -export const criticalFunctionTag = "__JS_CRITICAL__"; - -/** - * Allows the user to grab the variable name of a renamed variable. - */ -export const variableFunctionName = "__JS_CONFUSER_VAR__"; diff --git a/src_old/index.ts b/src_old/index.ts deleted file mode 100644 index 9347ecf..0000000 --- a/src_old/index.ts +++ /dev/null @@ -1,198 +0,0 @@ -import compileJs, { compileJsSync } from "./compiler"; -import parseJS, { parseSync } from "./parser"; -import Obfuscator from "./obfuscator"; -import Transform from "./transforms/transform"; -import Template from "./templates/template"; -import { remove$Properties } from "./util/object"; -import presets from "./presets"; - -import * as assert from "assert"; -import { correctOptions, ObfuscateOptions, validateOptions } from "./options"; -import { - IJsConfuser, - IJsConfuserDebugObfuscation, - IJsConfuserDebugTransformations, -} from "./types"; - -/** - * **JsConfuser**: Obfuscates JavaScript. - * @param code - The code to be obfuscated. - * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. - */ -export async function obfuscate(code: string, options: ObfuscateOptions) { - return await JsConfuser(code, options); -} - -/** - * Obfuscates an [ESTree](https://github.com/estree/estree) compliant AST. - * - * **Note:** Mutates the object. - * - * @param AST - The [ESTree](https://github.com/estree/estree) compliant AST. This object will be mutated. - * @param options - The obfuscation options. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser#options) - */ -export async function obfuscateAST(AST, options: ObfuscateOptions) { - assert.ok(typeof AST === "object", "AST must be type object"); - assert.ok(AST.type == "Program", "AST.type must be equal to 'Program'"); - validateOptions(options); - - options = await correctOptions(options); - - var obfuscator = new Obfuscator(options as any); - - await obfuscator.apply(AST); - - options.verbose && console.log("* Removing $ properties"); - - remove$Properties(AST); -} - -/** - * **JsConfuser**: Obfuscates JavaScript. - * @param code - The code to be obfuscated. - * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. - */ -var JsConfuser: IJsConfuser = async function ( - code: string, - options: ObfuscateOptions -): Promise { - if (typeof code !== "string") { - throw new TypeError("code must be type string"); - } - validateOptions(options); - - options = await correctOptions(options); - - options.verbose && console.log("* Parsing source code"); - - var tree = await parseJS(code); - - options.verbose && console.log("* Obfuscating..."); - - var obfuscator = new Obfuscator(options as any); - - await obfuscator.apply(tree); - - options.verbose && console.log("* Removing $ properties"); - - remove$Properties(tree); - - options.verbose && console.log("* Generating code"); - - var result = await compileJs(tree, options); - - return result; -} as any; - -export const debugTransformations: IJsConfuserDebugTransformations = - async function ( - code: string, - options: ObfuscateOptions - ): Promise<{ name: string; code: string; ms: number }[]> { - validateOptions(options); - options = await correctOptions(options); - - var frames = []; - - var tree = parseSync(code); - var obfuscator = new Obfuscator(options as any); - - var time = Date.now(); - - obfuscator.on("debug", (name: string, tree: Node) => { - frames.push({ - name: name, - code: compileJsSync(tree, options), - ms: Date.now() - time, - }); - - time = Date.now(); - }); - - await obfuscator.apply(tree, true); - - return frames; - }; - -/** - * This method is used by the obfuscator website to display a progress bar and additional information - * about the obfuscation. - * - * @param code - Source code to obfuscate - * @param options - Options - * @param callback - Progress callback, called after each transformation - * @returns - */ -export const debugObfuscation: IJsConfuserDebugObfuscation = async function ( - code: string, - options: ObfuscateOptions, - callback: (name: string, complete: number, totalTransforms: number) => void, - performance: Performance -) { - const startTime = performance.now(); - - validateOptions(options); - options = await correctOptions(options); - - const beforeParseTime = performance.now(); - - var tree = parseSync(code); - - const parseTime = performance.now() - beforeParseTime; - - var obfuscator = new Obfuscator(options as any); - var totalTransforms = obfuscator.array.length; - - var transformationTimes = Object.create(null); - var currentTransformTime = performance.now(); - - obfuscator.on("debug", (name: string, tree: Node, i: number) => { - var nowTime = performance.now(); - transformationTimes[name] = nowTime - currentTransformTime; - currentTransformTime = nowTime; - - callback(name, i, totalTransforms); - }); - - await obfuscator.apply(tree, true); - - const beforeCompileTime = performance.now(); - - var output = await compileJs(tree, options); - - const compileTime = performance.now() - beforeCompileTime; - - const endTime = performance.now(); - - return { - obfuscated: output, - transformationTimes: transformationTimes, - obfuscationTime: endTime - startTime, - parseTime: parseTime, - compileTime: compileTime, - totalTransforms: totalTransforms, - totalPossibleTransforms: obfuscator.totalPossibleTransforms, - }; -}; - -JsConfuser.obfuscate = obfuscate; -JsConfuser.obfuscateAST = obfuscateAST; -JsConfuser.presets = presets; -JsConfuser.debugTransformations = debugTransformations; -JsConfuser.debugObfuscation = debugObfuscation; -JsConfuser.Obfuscator = Obfuscator; -JsConfuser.Transform = Transform; -JsConfuser.Template = Template; - -if (typeof window !== "undefined") { - window["JsConfuser"] = JsConfuser; -} -if (typeof global !== "undefined") { - global["JsConfuser"] = JsConfuser; -} - -export default JsConfuser; - -export { presets, Obfuscator, Transform, Template }; diff --git a/src_old/obfuscator.ts b/src_old/obfuscator.ts deleted file mode 100644 index 995d19b..0000000 --- a/src_old/obfuscator.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { ok } from "assert"; -import { EventEmitter } from "events"; -import { Node } from "./util/gen"; -import traverse from "./traverse"; -import { ObfuscateOptions } from "./options"; -import { ProbabilityMap, isProbabilityMapProbable } from "./probability"; - -import Transform from "./transforms/transform"; - -import Preparation from "./transforms/preparation"; -import ObjectExtraction from "./transforms/extraction/objectExtraction"; -import Lock from "./transforms/lock/lock"; -import Dispatcher from "./transforms/dispatcher"; -import DeadCode from "./transforms/deadCode"; -import OpaquePredicates from "./transforms/opaquePredicates"; -import Calculator from "./transforms/calculator"; -import ControlFlowFlattening from "./transforms/controlFlowFlattening/controlFlowFlattening"; -import GlobalConcealing from "./transforms/identifier/globalConcealing"; -import StringSplitting from "./transforms/string/stringSplitting"; -import StringConcealing from "./transforms/string/stringConcealing"; -import StringCompression from "./transforms/string/stringCompression"; -import DuplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval"; -import Shuffle from "./transforms/shuffle"; -import MovedDeclarations from "./transforms/identifier/movedDeclarations"; -import RenameVariables from "./transforms/identifier/renameVariables"; -import RenameLabels from "./transforms/renameLabels"; -import Minify from "./transforms/minify"; -import ES5 from "./transforms/es5/es5"; -import RGF from "./transforms/rgf"; -import Flatten from "./transforms/flatten"; -import Stack from "./transforms/stack"; -import AntiTooling from "./transforms/antiTooling"; -import Finalizer from "./transforms/finalizer"; - -/** - * The parent transformation holding the `state`. - */ -export default class Obfuscator extends EventEmitter { - varCount: number; - transforms: { [name: string]: Transform }; - array: Transform[]; - - state: "transform" | "eval" = "transform"; - generated: Set; - - totalPossibleTransforms: number; - - constructor(public options: ObfuscateOptions) { - super(); - - this.varCount = 0; - this.transforms = Object.create(null); - this.generated = new Set(); - this.totalPossibleTransforms = 0; - - const test = (map: ProbabilityMap, ...transformers: any[]) => { - this.totalPossibleTransforms += transformers.length; - - if (isProbabilityMapProbable(map)) { - // options.verbose && console.log("+ Added " + transformer.name); - - transformers.forEach((Transformer) => this.push(new Transformer(this))); - } else { - // options.verbose && console.log("- Skipped adding " + transformer.name); - } - }; - - // Optimization: Only add needed transformers. If a probability always return false, no need in running that extra code. - test(true, Preparation); - test(true, RenameLabels); - - test(options.objectExtraction, ObjectExtraction); - test(options.flatten, Flatten); - test(options.rgf, RGF); - test(options.dispatcher, Dispatcher); - test(options.deadCode, DeadCode); - test(options.calculator, Calculator); - test(options.controlFlowFlattening, ControlFlowFlattening); - test(options.globalConcealing, GlobalConcealing); - test(options.opaquePredicates, OpaquePredicates); - test(options.stringSplitting, StringSplitting); - test(options.stringConcealing, StringConcealing); - test(options.stringCompression, StringCompression); - test(options.stack, Stack); - test(options.duplicateLiteralsRemoval, DuplicateLiteralsRemoval); - test(options.shuffle, Shuffle); - test(options.movedDeclarations, MovedDeclarations); - test(options.minify, Minify); - test(options.renameVariables, RenameVariables); - test(options.es5, ES5); - - test(true, AntiTooling); - test(true, Finalizer); // String Encoding, Hexadecimal Numbers, BigInt support is included - - if ( - options.lock && - Object.keys(options.lock).filter((x) => - x == "domainLock" - ? options.lock.domainLock && options.lock.domainLock.length - : options.lock[x] - ).length - ) { - test(true, Lock); - } - - // Make array - this.array = Object.values(this.transforms); - - // Sort transformations based on their priority - this.array.sort((a, b) => a.priority - b.priority); - } - - push(transform: Transform) { - if (transform.className) { - ok( - !this.transforms[transform.className], - "Already have " + transform.className - ); - } - this.transforms[transform.className] = transform; - } - - resetState() { - this.varCount = 0; - this.generated = new Set(); - this.state = "transform"; - } - - async apply(tree: Node, debugMode = false) { - ok(tree.type == "Program", "The root node must be type 'Program'"); - ok(Array.isArray(tree.body), "The root's body property must be an array"); - ok(Array.isArray(this.array)); - - this.resetState(); - - var completed = 0; - for (var transform of this.array) { - await transform.apply(tree); - completed++; - - if (debugMode) { - this.emit("debug", transform.className, tree, completed); - } - } - - if (this.options.verbose) { - console.log("-> Check for Eval Callbacks"); - } - - this.state = "eval"; - - // Find eval callbacks - traverse(tree, (o, p) => { - if (o.$eval) { - return () => { - o.$eval(o, p); - }; - } - }); - - if (this.options.verbose) { - console.log("<- Done"); - } - } -} diff --git a/src_old/options.ts b/src_old/options.ts deleted file mode 100644 index a184a35..0000000 --- a/src_old/options.ts +++ /dev/null @@ -1,898 +0,0 @@ -import { ok } from "assert"; -import presets from "./presets"; -import { ProbabilityMap } from "./probability"; - -export interface ObfuscateOptions { - /** - * ### `preset` - * - * JS-Confuser comes with three presets built into the obfuscator. - * - * | Preset | Transforms | Performance Reduction | Sample | - * | --- | --- | --- | --- | - * | High | 22/25 | 98% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/high.js) | - * | Medium | 19/25 | 52% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/medium.js) | - * | Low | 15/25 | 30% | [Sample](https://github.com/MichaelXF/js-confuser/blob/master/samples/low.js) | - * - * You can extend each preset or all go without them entirely. (`"high"/"medium"/"low"`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - preset?: "high" | "medium" | "low" | false; - - /** - * ### `target` - * - * The execution context for your output. _Required_. - * - * 1. `"node"` - * 2. `"browser"` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - target: "node" | "browser"; - - /** - * ### `indent` - * - * Controls the indentation of the output. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - indent?: 2 | 4 | "tabs"; - - /** - * ### `compact` - * - * Remove's whitespace from the final output. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - compact?: boolean; - - /** - * ### `hexadecimalNumbers` - * - * Uses the hexadecimal representation for numbers. (`true/false`) - */ - hexadecimalNumbers?: boolean; - - /** - * ### `minify` - * - * Minifies redundant code. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - minify?: boolean; - - /** - * ### `es5` - * - * Converts output to ES5-compatible code. (`true/false`) - * - * Does not cover all cases such as Promises or Generator functions. Use [Babel](https://babel.dev/). - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - es5?: boolean; - - /** - * ### `renameVariables` - * - * Determines if variables should be renamed. (`true/false`) - * - Potency High - * - Resilience High - * - Cost Medium - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - renameVariables?: ProbabilityMap; - - /** - * ### `renameGlobals` - * - * Renames top-level variables, turn this off for web-related scripts. Enabled by default. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - renameGlobals?: ProbabilityMap; - - /** - * ### `identifierGenerator` - * - * Determines how variables are renamed. - * - * | Mode | Description | Example | - * | --- | --- | --- | - * | `"hexadecimal"` | Random hex strings | \_0xa8db5 | - * | `"randomized"` | Random characters | w$Tsu4G | - * | `"zeroWidth"` | Invisible characters | U+200D | - * | `"mangled"` | Alphabet sequence | a, b, c | - * | `"number"` | Numbered sequence | var_1, var_2 | - * | `` | Write a custom name generator | See Below | - * - * ```js - * // Custom implementation - * JsConfuser.obfuscate(code, { - * target: "node", - * renameVariables: true, - * identifierGenerator: function () { - * return "$" + Math.random().toString(36).substring(7); - * }, - * }); - * - * // Numbered variables - * var counter = 0; - * JsConfuser.obfuscate(code, { - * target: "node", - * renameVariables: true, - * identifierGenerator: function () { - * return "var_" + (counter++); - * }, - * }); - * ``` - * - * JSConfuser tries to reuse names when possible, creating very potent code. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - identifierGenerator?: ProbabilityMap< - "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number" - >; - - /** - * ### `controlFlowFlattening` - * - * ⚠️ Significantly impacts performance, use sparingly! - * - * Control-flow Flattening hinders program comprehension by creating convoluted switch statements. (`true/false/0-1`) - * - * Use a number to control the percentage from 0 to 1. - * - * - Potency High - * - Resilience High - * - Cost High - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - controlFlowFlattening?: ProbabilityMap; - - /** - * ### `globalConcealing` - * - * Global Concealing hides global variables being accessed. (`true/false`) - * - * - Potency Medium - * - Resilience High - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - globalConcealing?: ProbabilityMap; - - /** - * ### `stringCompression` - * - * String Compression uses LZW's compression algorithm to compress strings. (`true/false/0-1`) - * - * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` - * - * - Potency High - * - Resilience Medium - * - Cost Medium - */ - stringCompression?: ProbabilityMap; - - /** - * ### `stringConcealing` - * - * String Concealing involves encoding strings to conceal plain-text values. (`true/false/0-1`) - * - * `"console"` -> `decrypt('<~@rH7+Dert~>')` - * - * - Potency High - * - Resilience Medium - * - Cost Medium - */ - stringConcealing?: ProbabilityMap; - - /** - * ### `stringEncoding` - * - * String Encoding transforms a string into an encoded representation. (`true/false/0-1`) - * - * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'` - * - * - Potency Low - * - Resilience Low - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - stringEncoding?: ProbabilityMap; - - /** - * ### `stringSplitting` - * - * String Splitting splits your strings into multiple expressions. (`true/false/0-1`) - * - * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'` - * - * - Potency Medium - * - Resilience Medium - * - Cost Medium - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - stringSplitting?: ProbabilityMap; - - /** - * ### `duplicateLiteralsRemoval` - * - * Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`) - * - * - Potency Medium - * - Resilience Low - * - Cost High - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - duplicateLiteralsRemoval?: ProbabilityMap; - - /** - * ### `dispatcher` - * - * Creates a middleman function to process function calls. (`true/false/0-1`) - * - * - Potency Medium - * - Resilience Medium - * - Cost High - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - dispatcher?: ProbabilityMap; - - /** - * ### `rgf` - * - * RGF (Runtime-Generated-Functions) uses the [`new Function(code...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) syntax to construct executable code from strings. (`"all"/true/false`) - * - * - **This can break your code. - * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.** - * - The arbitrary code is also obfuscated. - * - * ```js - * // Input - * function log(x){ - * console.log(x) - * } - * - * log("Hello World") - * - * // Output - * var C6z0jyO=[new Function('a2Fjjl',"function OqNW8x(OqNW8x){console['log'](OqNW8x)}return OqNW8x(...Array.prototype.slice.call(arguments,1))")];(function(){return C6z0jyO[0](C6z0jyO,...arguments)}('Hello World')) - * ``` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - rgf?: ProbabilityMap; - - /** - * ### `stack` - * - * Local variables are consolidated into a rotating array. - * - * [Similar to Jscrambler's Variable Masking](https://docs.jscrambler.com/code-integrity/documentation/transformations/variable-masking) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * ```js - * // Input - * function add3(x, y, z){ - * return x + y + z; - * } - * - * // Output - * function iVQoGQD(...iVQoGQD){ - * ~(iVQoGQD.length = 3, iVQoGQD[215] = iVQoGQD[2], iVQoGQD[75] = 227, iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - 75)] = iVQoGQD[75] - (iVQoGQD[75] - 239), iVQoGQD[iVQoGQD[iVQoGQD[75] - 164] - 127] = iVQoGQD[iVQoGQD[75] - 238], iVQoGQD[iVQoGQD[75] - 104] = iVQoGQD[75] - 482, iVQoGQD[iVQoGQD[135] + 378] = iVQoGQD[iVQoGQD[135] + 318] - 335, iVQoGQD[21] = iVQoGQD[iVQoGQD[135] + 96], iVQoGQD[iVQoGQD[iVQoGQD[75] - 104] - (iVQoGQD[75] - 502)] = iVQoGQD[iVQoGQD[75] - 164] - 440); - * return iVQoGQD[75] > iVQoGQD[75] + 90 ? iVQoGQD[iVQoGQD[135] - (iVQoGQD[135] + 54)] : iVQoGQD[iVQoGQD[135] + 117] + iVQoGQD[iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - (iVQoGQD[75] - 104))] - (iVQoGQD[135] - 112)] + iVQoGQD[215]; - * }; - * ``` - */ - stack?: ProbabilityMap; - - /** - * ### `objectExtraction` - * - * Extracts object properties into separate variables. (`true/false`) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * ```js - * // Input - * var utils = { - * isString: x=>typeof x === "string", - * isBoolean: x=>typeof x === "boolean" - * } - * if ( utils.isString("Hello") ) { - * // ... - * } - * - * // Output - * var utils_isString = x=>typeof x === "string"; - * var utils_isBoolean = x=>typeof x === "boolean" - * if ( utils_isString("Hello") ) { - * // ... - * } - * ``` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - objectExtraction?: ProbabilityMap; - - /** - * ### `flatten` - * - * Brings independent declarations to the highest scope. (`true/false`) - * - * - Potency Medium - * - Resilience Medium - * - Cost High - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - flatten?: ProbabilityMap; - - /** - * ### `deadCode` - * - * Randomly injects dead code. (`true/false/0-1`) - * - * Use a number to control the percentage from 0 to 1. - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - deadCode?: ProbabilityMap; - - /** - * ### `calculator` - * - * Creates a calculator function to handle arithmetic and logical expressions. (`true/false/0-1`) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - calculator?: ProbabilityMap; - - lock?: { - /** - * ### `lock.selfDefending` - * - * Prevents the use of code beautifiers or formatters against your code. - * - * [Identical to Obfuscator.io's Self Defending](https://github.com/javascript-obfuscator/javascript-obfuscator#selfdefending) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - selfDefending?: boolean; - - /** - * ### `lock.antiDebug` - * - * Adds `debugger` statements throughout the code. Additionally adds a background function for DevTools detection. (`true/false/0-1`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - antiDebug?: ProbabilityMap; - - /** - * ### `lock.context` - * - * Properties that must be present on the `window` object (or `global` for NodeJS). (`string[]`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - context?: string[]; - - /** - * ### `lock.tamperProtection` - * - * Tamper Protection safeguards the runtime behavior from being altered by JavaScript pitfalls. (`true/false`) - * - * **⚠️ Tamper Protection requires eval and ran in a non-strict mode environment!** - * - * - **This can break your code.** - * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.** - * - * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md). - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - tamperProtection?: boolean | ((varName: string) => boolean); - - /** - * ### `lock.startDate` - * - * When the program is first able to be used. (`number` or `Date`) - * - * Number should be in milliseconds. - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - startDate?: number | Date | false; - - /** - * ### `lock.endDate` - * - * When the program is no longer able to be used. (`number` or `Date`) - * - * Number should be in milliseconds. - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - endDate?: number | Date | false; - - /** - * ### `lock.domainLock` - * Array of regex strings that the `window.location.href` must follow. (`Regex[]` or `string[]`) - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - domainLock?: RegExp[] | string[] | false; - - /** - * ### `lock.osLock` - * Array of operating-systems where the script is allowed to run. (`string[]`) - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * Allowed values: `"linux"`, `"windows"`, `"osx"`, `"android"`, `"ios"` - * - * Example: `["linux", "windows"]` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - osLock?: ("linux" | "windows" | "osx" | "android" | "ios")[] | false; - - /** - * ### `lock.browserLock` - * Array of browsers where the script is allowed to run. (`string[]`) - * - * - Potency Low - * - Resilience Medium - * - Cost Medium - * - * Allowed values: `"firefox"`, `"chrome"`, `"iexplorer"`, `"edge"`, `"safari"`, `"opera"` - * - * Example: `["firefox", "chrome"]` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - browserLock?: - | ("firefox" | "chrome" | "iexplorer" | "edge" | "safari" | "opera")[] - | false; - - /** - * ### `lock.integrity` - * - * Integrity ensures the source code is unchanged. (`true/false/0-1`) - * - * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md). - * - * - Potency Medium - * - Resilience High - * - Cost High - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - integrity?: ProbabilityMap; - - /** - * ### `lock.countermeasures` - * - * A custom callback function to invoke when a lock is triggered. (`string/false`) - * - * This could be due to an invalid domain, incorrect time, or code's integrity changed. - * - * [Learn more about the rules of your countermeasures function](https://github.com/MichaelXF/js-confuser/blob/master/Countermeasures.md). - * - * Otherwise, the obfuscator falls back to crashing the process. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - countermeasures?: string | boolean; - }; - - /** - * ### `movedDeclarations` - * - * Moves variable declarations to the top of the context. (`true/false`) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - movedDeclarations?: ProbabilityMap; - - /** - * ### `opaquePredicates` - * - * An [Opaque Predicate](https://en.wikipedia.org/wiki/Opaque_predicate) is a predicate(true/false) that is evaluated at runtime, this can confuse reverse engineers - * understanding your code. (`true/false`) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - opaquePredicates?: ProbabilityMap; - - /** - * ### `shuffle` - * - * Shuffles the initial order of arrays. The order is brought back to the original during runtime. (`"hash"/true/false/0-1`) - * - * - Potency Medium - * - Resilience Low - * - Cost Low - * - * | Mode | Description | - * | --- | --- | - * | `"hash"`| Array is shifted based on hash of the elements | - * | `true`| Arrays are shifted *n* elements, unshifted at runtime | - * | `false` | Feature disabled | - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - shuffle?: ProbabilityMap; - - /** - * ### `verbose` - * - * Enable logs to view the obfuscator's state. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - verbose?: boolean; - - /** - * ### `globalVariables` - * - * Set of global variables. *Optional*. (`Set`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - globalVariables?: Set; - - /** - * ### `debugComments` - * - * Enable debug comments. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - debugComments?: boolean; - - /** - * ### `preserveFunctionLength` - * - * Modified functions will retain the correct `function.length` property. Enabled by default. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) - */ - preserveFunctionLength?: boolean; -} - -const validProperties = new Set([ - "preset", - "target", - "indent", - "compact", - "hexadecimalNumbers", - "minify", - "es5", - "renameVariables", - "renameGlobals", - "identifierGenerator", - "controlFlowFlattening", - "globalConcealing", - "stringCompression", - "stringConcealing", - "stringEncoding", - "stringSplitting", - "duplicateLiteralsRemoval", - "dispatcher", - "rgf", - "objectExtraction", - "flatten", - "deadCode", - "calculator", - "lock", - "movedDeclarations", - "opaquePredicates", - "shuffle", - "stack", - "verbose", - "globalVariables", - "debugComments", - "preserveFunctionLength", -]); - -const validLockProperties = new Set([ - "selfDefending", - "antiDebug", - "context", - "tamperProtection", - "startDate", - "endDate", - "domainLock", - "osLock", - "browserLock", - "integrity", - "countermeasures", -]); - -const validOses = new Set(["windows", "linux", "osx", "ios", "android"]); -const validBrowsers = new Set([ - "firefox", - "chrome", - "iexplorer", - "edge", - "safari", - "opera", -]); - -export function validateOptions(options: ObfuscateOptions) { - if (!options || Object.keys(options).length <= 1) { - /** - * Give a welcoming introduction to those who skipped the documentation. - */ - var line = `You provided zero obfuscation options. By default everything is disabled.\nYou can use a preset with:\n\n> {target: '${ - options.target || "node" - }', preset: 'high' | 'medium' | 'low'}.\n\n\nView all settings here:\nhttps://github.com/MichaelXF/js-confuser#options`; - throw new Error( - `\n\n` + - line - .split("\n") - .map((x) => `\t${x}`) - .join("\n") + - `\n\n` - ); - } - - ok(options, "options cannot be null"); - ok( - options.target, - "Missing options.target option (required, must one the following: 'browser' or 'node')" - ); - - ok( - ["browser", "node"].includes(options.target), - `'${options.target}' is not a valid target mode` - ); - - Object.keys(options).forEach((key) => { - if (!validProperties.has(key)) { - throw new TypeError("Invalid option: '" + key + "'"); - } - }); - - if ( - options.target === "node" && - options.lock && - options.lock.browserLock && - options.lock.browserLock.length - ) { - throw new TypeError('browserLock can only be used when target="browser"'); - } - - if (options.lock) { - ok(typeof options.lock === "object", "options.lock must be an object"); - Object.keys(options.lock).forEach((key) => { - if (!validLockProperties.has(key)) { - throw new TypeError("Invalid lock option: '" + key + "'"); - } - }); - - // Validate browser-lock option - if ( - options.lock.browserLock && - typeof options.lock.browserLock !== "undefined" - ) { - ok( - Array.isArray(options.lock.browserLock), - "browserLock must be an array" - ); - ok( - !options.lock.browserLock.find( - (browserName) => !validBrowsers.has(browserName) - ), - 'Invalid browser name. Allowed: "firefox", "chrome", "iexplorer", "edge", "safari", "opera"' - ); - } - // Validate os-lock option - if (options.lock.osLock && typeof options.lock.osLock !== "undefined") { - ok(Array.isArray(options.lock.osLock), "osLock must be an array"); - ok( - !options.lock.osLock.find((osName) => !validOses.has(osName)), - 'Invalid OS name. Allowed: "windows", "linux", "osx", "ios", "android"' - ); - } - // Validate domain-lock option - if ( - options.lock.domainLock && - typeof options.lock.domainLock !== "undefined" - ) { - ok(Array.isArray(options.lock.domainLock), "domainLock must be an array"); - } - - // Validate context option - if (options.lock.context && typeof options.lock.context !== "undefined") { - ok(Array.isArray(options.lock.context), "context must be an array"); - } - - // Validate start-date option - if ( - typeof options.lock.startDate !== "undefined" && - options.lock.startDate - ) { - ok( - typeof options.lock.startDate === "number" || - options.lock.startDate instanceof Date, - "startDate must be Date object or number" - ); - } - - // Validate end-date option - if (typeof options.lock.endDate !== "undefined" && options.lock.endDate) { - ok( - typeof options.lock.endDate === "number" || - options.lock.endDate instanceof Date, - "endDate must be Date object or number" - ); - } - } - - if (options.preset) { - if (!presets[options.preset]) { - throw new TypeError("Unknown preset of '" + options.preset + "'"); - } - } -} - -/** - * Corrects the user's options. Sets the default values and validates the configuration. - * @param options - * @returns - */ -export async function correctOptions( - options: ObfuscateOptions -): Promise { - if (options.preset) { - // Clone and allow overriding - options = Object.assign({}, presets[options.preset], options); - } - - if (!options.hasOwnProperty("debugComments")) { - options.debugComments = false; // debugComments is off by default - } - - if (!options.hasOwnProperty("compact")) { - options.compact = true; // Compact is on by default - } - if (!options.hasOwnProperty("renameGlobals")) { - options.renameGlobals = true; // RenameGlobals is on by default - } - if (!options.hasOwnProperty("preserveFunctionLength")) { - options.preserveFunctionLength = true; // preserveFunctionLength is on by default - } - - if (options.globalVariables && !(options.globalVariables instanceof Set)) { - options.globalVariables = new Set(Object.keys(options.globalVariables)); - } - - if (options.lock) { - if (options.lock.selfDefending) { - options.compact = true; // self defending forcibly enables this - } - } - - // options.globalVariables outlines generic globals that should be present in the execution context - if (!options.hasOwnProperty("globalVariables")) { - options.globalVariables = new Set([]); - - if (options.target == "browser") { - // browser - [ - "window", - "document", - "postMessage", - "alert", - "confirm", - "location", - "btoa", - "atob", - "unescape", - "encodeURIComponent", - ].forEach((x) => options.globalVariables.add(x)); - } else { - // node - [ - "global", - "Buffer", - "require", - "process", - "exports", - "module", - "__dirname", - "__filename", - ].forEach((x) => options.globalVariables.add(x)); - } - - [ - "globalThis", - "console", - "parseInt", - "parseFloat", - "Math", - "JSON", - "Promise", - "String", - "Boolean", - "Function", - "Object", - "Array", - "Proxy", - "Error", - "TypeError", - "ReferenceError", - "RangeError", - "EvalError", - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "queueMicrotask", - "isNaN", - "isFinite", - "Set", - "Map", - "WeakSet", - "WeakMap", - "Symbol", - ].forEach((x) => options.globalVariables.add(x)); - } - - return options; -} diff --git a/src_old/order.ts b/src_old/order.ts deleted file mode 100644 index 69e7aa4..0000000 --- a/src_old/order.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Describes the order of transformations. - */ -export enum ObfuscateOrder { - Preparation = 0, - - ObjectExtraction = 1, - - Flatten = 2, - - RGF = 3, - - Lock = 4, // Includes Integrity & Anti Debug - - Dispatcher = 6, - - DeadCode = 8, - - Calculator = 9, - - ControlFlowFlattening = 10, - - Eval = 11, - - GlobalConcealing = 12, - - OpaquePredicates = 13, - - StringSplitting = 16, - - StringConcealing = 17, - - StringCompression = 18, - - Stack = 20, - - DuplicateLiteralsRemoval = 22, - - Shuffle = 24, - - NameRecycling = 25, - - MovedDeclarations = 26, - - RenameLabels = 27, - - Minify = 28, - - AntiTooling = 29, - - RenameVariables = 30, - - ES5 = 31, - - Finalizer = 35, -} diff --git a/src_old/parser.ts b/src_old/parser.ts deleted file mode 100644 index 3e5b99e..0000000 --- a/src_old/parser.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as assert from "assert"; -import { Node } from "./util/gen"; - -const acorn = require("acorn"); - -/** - * Uses `acorn` to parse Javascript Code. Returns an AST tree. - * @param code - * @returns - */ -export default async function parseJS(code: string): Promise<{ - type: "Program"; - body: Node[]; -}> { - assert.ok(typeof code === "string", "code must be a string"); - - try { - var parsed = parseSync(code); - return parsed as any; - } catch (e) { - throw e; - } -} - -/** - * Parses a snippet code. Returns an AST Tree. - * @param code - * @returns - */ -export function parseSnippet(code: string): Node { - return acorn.parse(code, { - ecmaVersion: "latest", - allowReturnOutsideFunction: true, - sourceType: "module", - }); -} - -/** - * Parses synchronously. Attempts to parse as a es-module, then fallbacks to a script. - * @param code - * @returns - */ -export function parseSync(code): Node { - try { - return acorn.parse(code, { ecmaVersion: "latest", sourceType: "module" }); - } catch (e) { - return acorn.parse(code, { ecmaVersion: "latest", sourceType: "script" }); - } -} diff --git a/src_old/precedence.ts b/src_old/precedence.ts deleted file mode 100644 index 4d729a2..0000000 --- a/src_old/precedence.ts +++ /dev/null @@ -1,61 +0,0 @@ -export const OPERATOR_PRECEDENCE = { - "||": 3, - "&&": 4, - "|": 5, - "^": 6, - "&": 7, - "==": 8, - "!=": 8, - "===": 8, - "!==": 8, - "<": 9, - ">": 9, - "<=": 9, - ">=": 9, - in: 9, - instanceof: 9, - "<<": 10, - ">>": 10, - ">>>": 10, - "+": 11, - "-": 11, - "*": 12, - "%": 12, - "/": 12, - "**": 13, -}; - -// Enables parenthesis regardless of precedence -export const NEEDS_PARENTHESES = 17; - -export const EXPRESSIONS_PRECEDENCE = { - // Definitions - ArrayExpression: 20, - TaggedTemplateExpression: 20, - ThisExpression: 20, - Identifier: 20, - Literal: 18, - TemplateLiteral: 20, - Super: 20, - SequenceExpression: 20, - // Operations - MemberExpression: 19, - ChainExpression: 19, - CallExpression: 19, - NewExpression: 19, - // Other definitions - ArrowFunctionExpression: NEEDS_PARENTHESES, - ClassExpression: NEEDS_PARENTHESES, - FunctionExpression: NEEDS_PARENTHESES, - ObjectExpression: NEEDS_PARENTHESES, - // Other operations - UpdateExpression: 16, - UnaryExpression: 15, - AwaitExpression: 15, - BinaryExpression: 14, - LogicalExpression: 13, - ConditionalExpression: 4, - AssignmentExpression: 3, - YieldExpression: 2, - RestElement: 1, -}; diff --git a/src_old/presets.ts b/src_old/presets.ts deleted file mode 100644 index 2fb3081..0000000 --- a/src_old/presets.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { ObfuscateOptions } from "./options"; - -/** - * - High Obfuscation preset. - * - **Average 90% performance reduction.** - * - * ## **`Enabled features`** - * 1. Variable renaming - * 2. Control flow obfuscation - * 3. String concealing - * 4. Opaque predicates - * 5. Dead code - * 6. Dispatcher - * 7. Moved declarations - * 8. Object extraction - * 9. Global concealing - * 10. Minified output - * - * ## **`Disabled features`** - * - `rgf` Use at your own risk! - * - * ### Potential Issues - * 1. *String Encoding* can corrupt files. Disable `stringEncoding` manually if this happens. - * 2. *Dead Code* can bloat file size. Reduce or disable `deadCode`. - */ -const highPreset: ObfuscateOptions = { - target: "node", - preset: "high", - - calculator: true, - compact: true, - hexadecimalNumbers: true, - controlFlowFlattening: 0.75, - deadCode: 0.2, - dispatcher: true, - duplicateLiteralsRemoval: 0.75, - flatten: true, - globalConcealing: true, - identifierGenerator: "randomized", - minify: true, - movedDeclarations: true, - objectExtraction: true, - opaquePredicates: 0.75, - renameVariables: true, - renameGlobals: true, - shuffle: { hash: 0.5, true: 0.5 }, - stack: true, - stringConcealing: true, - stringCompression: true, - stringEncoding: true, - stringSplitting: 0.75, - - // Use at own risk - rgf: false, -}; - -/** - * - Medium Obfuscation preset. - * - Average 50% performance reduction. - */ -const mediumPreset: ObfuscateOptions = { - target: "node", - preset: "medium", - - calculator: true, - compact: true, - hexadecimalNumbers: true, - controlFlowFlattening: 0.25, - deadCode: 0.025, - dispatcher: 0.5, - duplicateLiteralsRemoval: 0.5, - globalConcealing: true, - identifierGenerator: "randomized", - minify: true, - movedDeclarations: true, - objectExtraction: true, - opaquePredicates: 0.5, - renameVariables: true, - renameGlobals: true, - shuffle: true, - stack: 0.5, - stringConcealing: true, - stringSplitting: 0.25, -}; - -/** - * - Low Obfuscation preset. - * - Average 30% performance reduction. - */ -const lowPreset: ObfuscateOptions = { - target: "node", - preset: "low", - - calculator: true, - compact: true, - hexadecimalNumbers: true, - controlFlowFlattening: 0.1, - deadCode: 0.01, - dispatcher: 0.25, - duplicateLiteralsRemoval: 0.5, - identifierGenerator: "randomized", - minify: true, - movedDeclarations: true, - objectExtraction: true, - opaquePredicates: 0.1, - renameVariables: true, - renameGlobals: true, - stringConcealing: true, -}; - -/** - * Built-in obfuscator presets. - */ -const presets = { - high: highPreset, - medium: mediumPreset, - low: lowPreset, -}; - -export default presets; diff --git a/src_old/probability.ts b/src_old/probability.ts deleted file mode 100644 index 6463a27..0000000 --- a/src_old/probability.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { ok } from "assert"; -import { createObject } from "./util/object"; - -type Stringed = (V extends string ? V : never) | "true" | "false"; - -/** - * Configurable probabilities for obfuscator options. - * - **`false`** = this feature is disabled - * - **`true`** = this feature is enabled, use default mode - * - **`0.5`** = 50% chance - * - **`"mode"`** = enabled, use specified mode - * - **`["mode1", "mode2"]`** - enabled, choose random mode each occurrence - * - **`{"mode1": 0.5, "mode2": 0.5}`** - enabled, choose based on specified probabilities - * - **`{"mode1": 50, "mode2": 50}`** - enabled, each is divided based on total - * - **`function(x){ return "custom_implementation" }`** - enabled, use specified function - */ -export type ProbabilityMap = - | false - | true - | number - | T - | T[] - | { [key in Stringed]?: number } - | ((...params: any[]) => any); - -/** - * Evaluates a ProbabilityMap. - * @param map The setting object. - * @param runner Custom function to determine return value - * @param customFnArgs Args given to user-implemented function, such as a variable name. - */ -export function ComputeProbabilityMap( - map: ProbabilityMap, - runner: (mode?: T) => any = (x?: T) => x, - ...customFnArgs: any[] -): any { - if (!map) { - return runner(); - } - if (map === true || map === 1) { - return runner(true as any); - } - if (typeof map === "number") { - return runner((Math.random() < map) as any); - } - - if (typeof map === "function") { - return (map as any)(...customFnArgs); - } - if (typeof map === "string") { - return runner(map); - } - var asObject: { [mode: string]: number } = {}; - if (Array.isArray(map)) { - map.forEach((x: any) => { - asObject[x.toString()] = 1; - }); - } else { - asObject = map as any; - } - - var total = Object.values(asObject).reduce((a, b) => a + b); - var percentages = createObject( - Object.keys(asObject), - Object.values(asObject).map((x) => x / total) - ); - - var ticket = Math.random(); - - var count = 0; - var winner = null; - Object.keys(percentages).forEach((key) => { - var x = parseFloat(percentages[key]); - - if (ticket >= count && ticket < count + x) { - winner = key; - } - count += x; - }); - - return runner(winner); -} - -/** - * Determines if a probability map can return a positive result (true, or some string mode). - * - Negative probability maps are used to remove transformations from running entirely. - * @param map - */ -export function isProbabilityMapProbable(map: ProbabilityMap): boolean { - ok(!Number.isNaN(map), "Numbers cannot be NaN"); - - if (!map || typeof map === "undefined") { - return false; - } - if (typeof map === "function") { - return true; - } - if (typeof map === "number") { - if (map > 1 || map < 0) { - throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`); - } - } - if (Array.isArray(map)) { - ok( - map.length != 0, - "Empty arrays are not allowed for options. Use false instead." - ); - - if (map.length == 1) { - return !!map[0]; - } - } - if (typeof map === "object") { - var keys = Object.keys(map); - ok( - keys.length != 0, - "Empty objects are not allowed for options. Use false instead." - ); - - if (keys.length == 1) { - return !!keys[0]; - } - } - return true; -} diff --git a/src_old/templates/bufferToString.ts b/src_old/templates/bufferToString.ts deleted file mode 100644 index 7f0f03d..0000000 --- a/src_old/templates/bufferToString.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - placeholderVariablePrefix, - predictableFunctionTag, -} from "../constants"; -import Transform from "../transforms/transform"; -import { Node } from "../util/gen"; -import Template from "./template"; - -export const createGetGlobalTemplate = ( - transform: Transform, - object: Node, - parents: Node[] -) => { - var options = transform.options; - if (options.lock?.tamperProtection) { - return new Template(` - function {getGlobalFnName}(){ - var localVar = false; - eval(${transform.jsConfuserVar("localVar")} + " = true") - if (!localVar) { - {countermeasures} - } - - const root = eval("this"); - return root; - } - `).setDefaultVariables({ - countermeasures: transform.lockTransform.getCounterMeasuresCode( - object, - parents - ), - }); - } - - return GetGlobalTemplate; -}; - -const GetGlobalTemplate = new Template(` - function ${placeholderVariablePrefix}CFG__getGlobalThis${predictableFunctionTag}(){ - return globalThis - } - - function ${placeholderVariablePrefix}CFG__getGlobal${predictableFunctionTag}(){ - return global - } - - function ${placeholderVariablePrefix}CFG__getWindow${predictableFunctionTag}(){ - return window - } - - function ${placeholderVariablePrefix}CFG__getThisFunction${predictableFunctionTag}(){ - return new Function("return this")() - } - - function {getGlobalFnName}(array = [ - ${placeholderVariablePrefix}CFG__getGlobalThis${predictableFunctionTag}, - ${placeholderVariablePrefix}CFG__getGlobal${predictableFunctionTag}, - ${placeholderVariablePrefix}CFG__getWindow${predictableFunctionTag}, - ${placeholderVariablePrefix}CFG__getThisFunction${predictableFunctionTag} - ]){ - var bestMatch - var itemsToSearch = [] - try { - bestMatch = Object - itemsToSearch["push"](("")["__proto__"]["constructor"]["name"]) - } catch(e) { - - } - A: for(var i = 0; i < array["length"]; i++) { - try { - bestMatch = array[i]() - for(var j = 0; j < itemsToSearch["length"]; j++) { - if(typeof bestMatch[itemsToSearch[j]] === "undefined") continue A; - } - return bestMatch - } catch(e) {} - } - - return bestMatch || this; - } -`); - -export const BufferToStringTemplate = new Template(` - {GetGlobalTemplate} - - var __globalObject = {getGlobalFnName}() || {}; - var __TextDecoder = __globalObject["TextDecoder"]; - var __Uint8Array = __globalObject["Uint8Array"]; - var __Buffer = __globalObject["Buffer"]; - var __String = __globalObject["String"] || String; - var __Array = __globalObject["Array"] || Array; - - var utf8ArrayToStr = (function () { - var charCache = new __Array(128); // Preallocate the cache for the common single byte chars - var charFromCodePt = __String["fromCodePoint"] || __String["fromCharCode"]; - var result = []; - - return function (array) { - var codePt, byte1; - var buffLen = array["length"]; - - result["length"] = 0; - - for (var i = 0; i < buffLen;) { - byte1 = array[i++]; - - if (byte1 <= 0x7F) { - codePt = byte1; - } else if (byte1 <= 0xDF) { - codePt = ((byte1 & 0x1F) << 6) | (array[i++] & 0x3F); - } else if (byte1 <= 0xEF) { - codePt = ((byte1 & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F); - } else if (__String["fromCodePoint"]) { - codePt = ((byte1 & 0x07) << 18) | ((array[i++] & 0x3F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F); - } else { - codePt = 63; // Cannot convert four byte code points, so use "?" instead - i += 3; - } - - result["push"](charCache[codePt] || (charCache[codePt] = charFromCodePt(codePt))); - } - - return result["join"](''); - }; - })(); - - function {name}(buffer){ - if(typeof __TextDecoder !== "undefined" && __TextDecoder) { - return new __TextDecoder()["decode"](new __Uint8Array(buffer)); - } else if(typeof __Buffer !== "undefined" && __Buffer) { - return __Buffer["from"](buffer)["toString"]("utf-8"); - } else { - return utf8ArrayToStr(buffer); - } - } -`); diff --git a/src_old/templates/core.ts b/src_old/templates/core.ts deleted file mode 100644 index a2b3069..0000000 --- a/src_old/templates/core.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Template from "./template"; - -export const IndexOfTemplate = new Template(` -function indexOf(str, substr) { - const len = str.length; - const sublen = substr.length; - let count = 0; - - if (sublen > len) { - return -1; - } - - for (let i = 0; i <= len - sublen; i++) { - for (let j = 0; j < sublen; j++) { - if (str[i + j] === substr[j]) { - count++; - if (count === sublen) { - return i; - } - } else { - count = 0; - break; - } - } - } - - return -1; -} -`); diff --git a/src_old/templates/crash.ts b/src_old/templates/crash.ts deleted file mode 100644 index ec4f5df..0000000 --- a/src_old/templates/crash.ts +++ /dev/null @@ -1,23 +0,0 @@ -import Template from "./template"; - -export const CrashTemplate1 = new Template(` -var {var} = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_\`{|}~"'; -while(true){ - {var} = {var}; - if(!{var}) break; -} -`); - -export const CrashTemplate2 = new Template(` -while(true) { - var {var} = 99; - for({var} = 99; {var} == {var}; {var} *= {var}) { - !{var} && console.log({var}); - if ({var} <= 10){ - break; - } - }; - if({var} === 100) { - {var}-- - } - };`); diff --git a/src_old/templates/es5.ts b/src_old/templates/es5.ts deleted file mode 100644 index dbabe83..0000000 --- a/src_old/templates/es5.ts +++ /dev/null @@ -1,131 +0,0 @@ -import Template from "./template"; - -/** - * Provides ES5 polyfills for Array methods - * - * Source: https://vanillajstoolkit.com/polyfills/ - */ -export const ES5Template = new Template(` -if (!Array.prototype.forEach) { - Array.prototype.forEach = function forEach (callback, thisArg) { - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - var array = this; - thisArg = thisArg || this; - for (var i = 0, l = array.length; i !== l; ++i) { - callback.call(thisArg, array[i], i, array); - } - }; -} -if (!Array.prototype.filter) - Array.prototype.filter = function(func, thisArg) { - 'use strict'; - if ( ! ((typeof func === 'Function' || typeof func === 'function') && this) ) - throw new TypeError(); - - var len = this.length >>> 0, - res = new Array(len), // preallocate array - t = this, c = 0, i = -1; - if (thisArg === undefined) - while (++i !== len) - // checks to see if the key was set - if (i in this) - if (func(t[i], i, t)) - res[c++] = t[i]; - else - while (++i !== len) - // checks to see if the key was set - if (i in this) - if (func.call(thisArg, t[i], i, t)) - res[c++] = t[i]; - - res.length = c; // shrink down array to proper size - return res; -}; -if (!Array.prototype.map) { - Array.prototype.map = function(callback, thisArg) { - var T, A, k; - - if (this == null) { - throw new TypeError('this is null or not defined'); - } - - // 1. Let O be the result of calling ToObject passing the |this| - // value as the argument. - var O = Object(this); - - // 2. Let lenValue be the result of calling the Get internal - // method of O with the argument "length". - // 3. Let len be ToUint32(lenValue). - var len = O.length >>> 0; - - // 4. If IsCallable(callback) is false, throw a TypeError exception. - // See: http://es5.github.com/#x9.11 - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - - // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. - if (arguments.length > 1) { - T = arguments[1]; - } - - // 6. Let A be a new array created as if by the expression new Array(len) - // where Array is the standard built-in constructor with that name and - // len is the value of len. - A = new Array(len); - - // 7. Let k be 0 - k = 0; - - // 8. Repeat, while k < len - while (k < len) { - - var kValue, mappedValue; - - // a. Let Pk be ToString(k). - // This is implicit for LHS operands of the in operator - // b. Let kPresent be the result of calling the HasProperty internal - // method of O with argument Pk. - // This step can be combined with c - // c. If kPresent is true, then - if (k in O) { - - // i. Let kValue be the result of calling the Get internal - // method of O with argument Pk. - kValue = O[k]; - - // ii. Let mappedValue be the result of calling the Call internal - // method of callback with T as the this value and argument - // list containing kValue, k, and O. - mappedValue = callback.call(T, kValue, k, O); - - // iii. Call the DefineOwnProperty internal method of A with arguments - // Pk, Property Descriptor - // { Value: mappedValue, - // Writable: true, - // Enumerable: true, - // Configurable: true }, - // and false. - - // In browsers that support Object.defineProperty, use the following: - // Object.defineProperty(A, k, { - // value: mappedValue, - // writable: true, - // enumerable: true, - // configurable: true - // }); - - // For best browser support, use the following: - A[k] = mappedValue; - } - // d. Increase k by 1. - k++; - } - - // 9. return A - return A; - }; -} -`); diff --git a/src_old/templates/functionLength.ts b/src_old/templates/functionLength.ts deleted file mode 100644 index 6a00abc..0000000 --- a/src_old/templates/functionLength.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Template from "./template"; - -/** - * Helper function to set `function.length` property. - */ -export const FunctionLengthTemplate = new Template( - ` -function {name}(functionObject, functionLength){ - {ObjectDefineProperty}(functionObject, "length", { - "value": functionLength, - "configurable": true - }); - return functionObject; -} -`, - ` -function {name}(functionObject, functionLength){ - return {ObjectDefineProperty}(functionObject, "length", { - "value": functionLength, - "configurable": true - }); -} -`, - ` -function {name}(functionObject, functionLength){ - return {ObjectDefineProperty}["call"](null, functionObject, "length", { - "value": functionLength, - "configurable": true - }); -} -` -); diff --git a/src_old/templates/globals.ts b/src_old/templates/globals.ts deleted file mode 100644 index 343add6..0000000 --- a/src_old/templates/globals.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Template from "./template"; - -export const ObjectDefineProperty = new Template(`Object["defineProperty"]`); diff --git a/src_old/templates/template.ts b/src_old/templates/template.ts deleted file mode 100644 index 6b8fd01..0000000 --- a/src_old/templates/template.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { Node } from "../util/gen"; -import { parseSnippet, parseSync } from "../parser"; -import { ok } from "assert"; -import { choice } from "../util/random"; -import { placeholderVariablePrefix } from "../constants"; -import traverse from "../traverse"; - -export interface TemplateVariables { - [varName: string]: - | string - | (() => Node | Node[] | Template) - | Node - | Node[] - | Template; -} - -/** - * Templates provides an easy way to parse code snippets into AST subtrees. - * - * These AST subtrees can added to the obfuscated code, tailored with variable names. - * - * 1. Basic string interpolation - * - * ```js - * var Base64Template = new Template(` - * function {name}(str){ - * return btoa(str) - * } - * `); - * - * var functionDeclaration = Base64Template.single({ name: "atob" }); - * ``` - * - * 2. AST subtree insertion - * - * ```js - * var Base64Template = new Template(` - * function {name}(str){ - * {getWindow} - * - * return {getWindowName}btoa(str) - * }`) - * - * var functionDeclaration = Base64Template.single({ - * name: "atob", - * getWindowName: "newWindow", - * getWindow: () => { - * return acorn.parse("var newWindow = {}").body[0]; - * } - * }); - * ``` - * - * Here, the `getWindow` variable is a function that returns an AST subtree. This must be a `Node[]` array or Template. - * Optionally, the function can be replaced with just the `Node[]` array or Template if it's already computed. - * - * 3. Template subtree insertion - * - * ```js - * var NewWindowTemplate = new Template(` - * var newWindow = {}; - * `); - * - * var Base64Template = new Template(` - * function {name}(str){ - * {NewWindowTemplate} - * - * return newWindow.btoa(str) - * }`) - * - * var functionDeclaration = Base64Template.single({ - * name: "atob", - * NewWindowTemplate: NewWindowTemplate - * }); - * ``` - */ -export default class Template { - templates: string[]; - defaultVariables: TemplateVariables; - requiredVariables: Set; - - constructor(...templates: string[]) { - this.templates = templates; - this.defaultVariables = Object.create(null); - this.requiredVariables = new Set(); - - this.findRequiredVariables(); - } - - setDefaultVariables(defaultVariables: TemplateVariables): this { - this.defaultVariables = defaultVariables; - return this; - } - - private findRequiredVariables() { - var matches = this.templates[0].match(/{[$A-z0-9_]+}/g); - if (matches !== null) { - matches.forEach((variable) => { - var name = variable.slice(1, -1); - - // $ variables are for default variables - if (name.startsWith("$")) { - throw new Error("Default variables are no longer supported."); - } else { - this.requiredVariables.add(name); - } - }); - } - } - - /** - * Interpolates the template with the given variables. - * - * Prepares the template string for AST parsing. - * - * @param variables - */ - private interpolateTemplate(variables: TemplateVariables = {}) { - var allVariables = { ...this.defaultVariables, ...variables }; - - // Validate all variables were passed in - for (var requiredVariable of this.requiredVariables) { - if (typeof allVariables[requiredVariable] === "undefined") { - throw new Error( - this.templates[0] + - " missing variable: " + - requiredVariable + - " from " + - JSON.stringify(allVariables) - ); - } - } - - var template = choice(this.templates); - var output = template; - - Object.keys(allVariables).forEach((name) => { - var bracketName = "{" + name.replace("$", "\\$") + "}"; - - var value = allVariables[name] + ""; - if (typeof allVariables[name] !== "string") { - value = name; - } - - var reg = new RegExp(bracketName, "g"); - - output = output.replace(reg, value); - }); - - return { output, template }; - } - - /** - * Finds the variables in the AST and replaces them with the given values. - * - * Note: Mutates the AST. - * @param ast - * @param variables - */ - private interpolateAST(ast: Node, variables: TemplateVariables) { - var allVariables = { ...this.defaultVariables, ...variables }; - - var astNames = new Set( - Object.keys(allVariables).filter((name) => { - return typeof allVariables[name] !== "string"; - }) - ); - - if (astNames.size === 0) return; - - traverse(ast, (o, p) => { - if (o.type === "Identifier" && allVariables[o.name]) { - return () => { - var value = allVariables[o.name]; - ok(typeof value !== "string"); - - var insertNodes = typeof value === "function" ? value() : value; - if (insertNodes instanceof Template) { - insertNodes = insertNodes.compile(allVariables); - } - - if (!Array.isArray(insertNodes)) { - // Replace with expression - - Object.assign(o, insertNodes); - } else { - // Insert multiple statements/declarations - var expressionStatement: Node = p[0]; - var body: Node[] = p[1] as any; - - ok(expressionStatement.type === "ExpressionStatement"); - ok(Array.isArray(body)); - - var index = body.indexOf(expressionStatement); - - body.splice(index, 1, ...insertNodes); - } - }; - } - }); - } - - compile(variables: TemplateVariables = {}): Node[] { - var { output, template } = this.interpolateTemplate(variables); - - var program: Node; - try { - program = parseSnippet(output); - } catch (e) { - throw new Error(output + "\n" + "Template failed to parse: " + e.message); - } - - this.interpolateAST(program, variables); - - return program.body; - } - - single(variables: TemplateVariables = {}): Node { - var nodes = this.compile(variables); - - if (nodes.length !== 1) { - nodes = nodes.filter((node) => node.type !== "EmptyStatement"); - ok( - nodes.length === 1, - `Expected single node, got ${nodes.map((node) => node.type).join(", ")}` - ); - } - - return nodes[0]; - } -} diff --git a/src_old/transforms/antiTooling.ts b/src_old/transforms/antiTooling.ts deleted file mode 100644 index 9212f4f..0000000 --- a/src_old/transforms/antiTooling.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { ObfuscateOrder } from "../order"; -import Template from "../templates/template"; -import { isBlock } from "../traverse"; -import { - Node, - ExpressionStatement, - CallExpression, - Identifier, -} from "../util/gen"; -import { prepend } from "../util/insert"; -import Transform from "./transform"; - -// JsNice.org tries to separate sequence expressions into multiple lines, this stops that. -export default class AntiTooling extends Transform { - fnName: string; - - constructor(o) { - super(o, ObfuscateOrder.AntiTooling); - } - - apply(tree: Node) { - super.apply(tree); - - if (typeof this.fnName === "string") { - prepend( - tree, - new Template(` - function {fnName}(){ - } - `).single({ fnName: this.fnName }) - ); - } - } - - match(object, parents) { - return isBlock(object) || object.type == "SwitchCase"; - } - - transform(object, parents) { - return () => { - var exprs: Node[] = []; - var deleteExprs: Node[] = []; - - var body: Node[] = - object.type == "SwitchCase" ? object.consequent : object.body; - - const end = () => { - function flatten(expr: Node) { - if (expr.type == "ExpressionStatement") { - flatten(expr.expression); - } else if (expr.type == "SequenceExpression") { - expr.expressions.forEach(flatten); - } else { - flattened.push(expr); - } - } - - var flattened = []; - exprs.forEach(flatten); - - if (flattened.length > 1) { - flattened[0] = { ...flattened[0] }; - - if (!this.fnName) { - this.fnName = this.getPlaceholder(); - } - - // (expr1,expr2,expr3) -> F(expr1, expr2, expr3) - this.replace( - exprs[0], - ExpressionStatement( - CallExpression(Identifier(this.fnName), [...flattened]) - ) - ); - - deleteExprs.push(...exprs.slice(1)); - } - exprs = []; - }; - - body.forEach((stmt, i) => { - if (stmt.hidden || stmt.directive) { - return; - } - if (stmt.type == "ExpressionStatement") { - exprs.push(stmt); - } else { - end(); - } - }); - - end(); - - deleteExprs.forEach((expr) => { - var index = body.indexOf(expr); - if (index !== -1) { - body.splice(index, 1); - } - }); - }; - } -} diff --git a/src_old/transforms/calculator.ts b/src_old/transforms/calculator.ts deleted file mode 100644 index e05a2de..0000000 --- a/src_old/transforms/calculator.ts +++ /dev/null @@ -1,229 +0,0 @@ -import Transform from "./transform"; -import { - Node, - FunctionDeclaration, - ReturnStatement, - CallExpression, - Identifier, - Literal, - BinaryExpression, - SwitchCase, - SwitchStatement, - AssignmentExpression, - VariableDeclaration, - VariableDeclarator, - UnaryExpression, -} from "../util/gen"; -import { prepend } from "../util/insert"; -import { choice, getRandomInteger } from "../util/random"; -import { ObfuscateOrder } from "../order"; -import { ok } from "assert"; -import { OPERATOR_PRECEDENCE } from "../precedence"; -import Template from "../templates/template"; -import { ComputeProbabilityMap } from "../probability"; - -const allowedBinaryOperators = new Set(["+", "-", "*", "/"]); -const allowedUnaryOperators = new Set(["!", "void", "typeof", "-", "~", "+"]); - -export default class Calculator extends Transform { - gen: ReturnType; - ops: { [operator: string]: number }; - statesUsed: Set; - calculatorFn: string; - calculatorOpVar: string; - calculatorSetOpFn: string; - - constructor(o) { - super(o, ObfuscateOrder.Calculator); - - this.ops = Object.create(null); - this.statesUsed = new Set(); - this.calculatorFn = this.getPlaceholder() + "_calc"; - this.calculatorOpVar = this.getPlaceholder(); - this.calculatorSetOpFn = this.getPlaceholder(); - - this.gen = this.getGenerator(); - } - - apply(tree) { - super.apply(tree); - - if (Object.keys(this.ops).length == 0) { - return; - } - - var leftArg = this.getPlaceholder(); - var rightArg = this.getPlaceholder(); - var switchCases = []; - - Object.keys(this.ops).forEach((opKey) => { - var [type, operator] = opKey.split("_"); - - var code = this.ops[opKey]; - var body = []; - - if (type === "Binary") { - body = [ - ReturnStatement( - BinaryExpression( - operator, - Identifier(leftArg), - Identifier(rightArg) - ) - ), - ]; - } else if (type === "Unary") { - body = [ - ReturnStatement(UnaryExpression(operator, Identifier(leftArg))), - ]; - } else { - throw new Error("Unknown type: " + type); - } - - switchCases.push(SwitchCase(Literal(code), body)); - }); - - var func = FunctionDeclaration( - this.calculatorFn, - [leftArg, rightArg].map((x) => Identifier(x)), - [SwitchStatement(Identifier(this.calculatorOpVar), switchCases)] - ); - - prepend( - tree, - VariableDeclaration(VariableDeclarator(this.calculatorOpVar)) - ); - - prepend( - tree, - new Template(`function {name}(a){ - a = {b} + ({b}=a, 0); - return a; - }`).single({ name: this.calculatorSetOpFn, b: this.calculatorOpVar }) - ); - - prepend(tree, func); - } - - match(object: Node, parents: Node[]) { - return ( - object.type === "BinaryExpression" || object.type === "UnaryExpression" - ); - } - - transform(object: Node, parents: Node[]) { - // Allow percentage - if (!ComputeProbabilityMap(this.options.calculator)) { - return; - } - - var operator = object.operator; - - var type; - - if (object.type === "BinaryExpression") { - type = "Binary"; - - if (!allowedBinaryOperators.has(operator)) { - return; - } - - // Additional checks to ensure complex expressions still work - var myPrecedence = - OPERATOR_PRECEDENCE[operator] + - Object.keys(OPERATOR_PRECEDENCE).indexOf(operator) / 100; - var precedences = parents.map( - (x) => - x.type == "BinaryExpression" && - OPERATOR_PRECEDENCE[x.operator] + - Object.keys(OPERATOR_PRECEDENCE).indexOf(x.operator) / 100 - ); - - // corrupt AST - if (precedences.find((x) => x >= myPrecedence)) { - return; - } - if ( - parents.find( - (x) => x.$multiTransformSkip || x.type == "BinaryExpression" - ) - ) { - return; - } - } - - if (object.type === "UnaryExpression") { - type = "Unary"; - - if (!allowedUnaryOperators.has(operator)) { - return; - } - - // Typeof expression fix - if (operator === "typeof" && object.argument.type === "Identifier") { - // `typeof name` is special because it can reference the variable `name` without - // throwing any errors. If changed, an error could be thrown, breaking the users code - return; - } - } - - return () => { - const opKey = type + "_" + operator; - - if (typeof this.ops[opKey] !== "number") { - var newState; - do { - newState = getRandomInteger( - -50, - 50 + Object.keys(this.ops).length * 5 - ); - } while (this.statesUsed.has(newState)); - - ok(!isNaN(newState)); - - this.statesUsed.add(newState); - this.ops[opKey] = newState; - - if (type === "Binary") { - this.log( - `left ${operator} right ->`, - `${this.calculatorFn}((${newState}, left, right)` - ); - } else if (type === "Unary") { - this.log( - `${operator}(argument) ->`, - `${this.calculatorFn}(${newState}, argument)` - ); - } - } - - // The operator expression sets the operator to be used - var operatorExpression = choice([ - AssignmentExpression( - "=", - Identifier(this.calculatorOpVar), - Literal(this.ops[opKey]) - ), - CallExpression(Identifier(this.calculatorSetOpFn), [ - Literal(this.ops[opKey]), - ]), - ]); - - var newExpression; - if (type === "Binary") { - newExpression = CallExpression(Identifier(this.calculatorFn), [ - object.left, - object.right, - operatorExpression, - ]); - } else { - newExpression = CallExpression(Identifier(this.calculatorFn), [ - object.argument, - operatorExpression, - ]); - } - - this.replace(object, newExpression); - }; - } -} diff --git a/src_old/transforms/controlFlowFlattening/controlFlowFlattening.ts b/src_old/transforms/controlFlowFlattening/controlFlowFlattening.ts deleted file mode 100644 index 7da1bf5..0000000 --- a/src_old/transforms/controlFlowFlattening/controlFlowFlattening.ts +++ /dev/null @@ -1,2153 +0,0 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { ComputeProbabilityMap } from "../../probability"; -import Template from "../../templates/template"; -import { isBlock, walk } from "../../traverse"; -import { - ArrayExpression, - AssignmentExpression, - AssignmentPattern, - BinaryExpression, - BreakStatement, - CallExpression, - ConditionalExpression, - ExpressionStatement, - FunctionExpression, - Identifier, - IfStatement, - LabeledStatement, - Literal, - Location, - LogicalExpression, - MemberExpression, - Node, - ObjectExpression, - Property, - ReturnStatement, - SequenceExpression, - SwitchCase, - SwitchStatement, - UnaryExpression, - VariableDeclaration, - VariableDeclarator, - WhileStatement, -} from "../../util/gen"; -import { - containsLexicallyBoundVariables, - getIdentifierInfo, -} from "../../util/identifiers"; -import { - clone, - getBlockBody, - isContext, - isForInitialize, - isFunction, - isVarContext, -} from "../../util/insert"; -import { chance, choice, getRandomInteger, shuffle } from "../../util/random"; -import Transform from "../transform"; -import ExpressionObfuscation from "./expressionObfuscation"; -import { reservedIdentifiers, variableFunctionName } from "../../constants"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { isJSConfuserVar } from "../../util/guard"; - -const flattenStructures = new Set([ - "IfStatement", - "ForStatement", - "WhileStatement", - "DoWhileStatement", -]); - -/** - * A chunk represents a small segment of code - */ -interface Chunk { - label: string; - body: Node[]; - - impossible?: boolean; -} - -/** - * Breaks functions into DAGs (Directed Acyclic Graphs) - * - * - 1. Break functions into chunks - * - 2. Shuffle chunks but remember their original position - * - 3. Create a Switch statement inside a While loop, each case is a chunk, and the while loops exits on the last transition. - * - * The Switch statement: - * - * - 1. The state variable controls which case will run next - * - 2. At the end of each case, the state variable is updated to the next block of code. - * - 3. The while loop continues until the the state variable is the end state. - */ -export default class ControlFlowFlattening extends Transform { - // in Debug mode, the output is much easier to read - isDebug = false; - flattenControlStructures = true; // Flatten if-statements, for-loops, etc - addToControlObject = true; // var control = { str1, num1 } - mangleNumberLiterals = true; // 50 => state + X - mangleBooleanLiterals = true; // true => state == X - mangleIdentifiers = true; // console => (state == X ? console : _) - outlineStatements = true; // Tries to outline entire chunks - outlineExpressions = true; // Tries to outline expressions found in chunks - addComplexTest = true; // case s != 49 && s - 10: - addFakeTest = true; // case 100: case 490: case 510: ... - addDeadCode = true; // add fakes chunks of code - addOpaquePredicates = true; // predicate ? REAL : FAKE - addFlaggedLabels = true; // s=NEXT_STATE,flag=true,break - - // Limit amount of mangling - mangledExpressionsMade = 0; - - // Amount of blocks changed by Control Flow Flattening - cffCount = 0; - - constructor(o) { - super(o, ObfuscateOrder.ControlFlowFlattening); - - if (!this.isDebug) { - this.before.push(new ExpressionObfuscation(o)); - } else { - console.warn("Debug mode enabled"); - } - } - - match(object, parents) { - return ( - isBlock(object) && - (!parents[0] || !flattenStructures.has(parents[0].type)) && - (!parents[1] || !flattenStructures.has(parents[1].type)) - ); - } - - transform(object, parents) { - // Must be at least 3 statements or more - if (object.body.length < 3) { - return; - } - // No 'let'/'const' allowed (These won't work in Switch cases!) - if (containsLexicallyBoundVariables(object, parents)) { - return; - } - // Check user's threshold setting - if (!ComputeProbabilityMap(this.options.controlFlowFlattening, (x) => x)) { - return; - } - - var objectBody = getBlockBody(object.body); - if (!objectBody.length) { - return; - } - - // Purely for naming purposes - var cffIndex = this.cffCount++; - - // The controlVar is an object containing: - // - Strings found in chunks - // - Numbers found in chunks - // - Helper functions to adjust the state - // - Outlined expressions changed into functions - var controlVar = this.getPlaceholder() + `_c${cffIndex}_CONTROL`; - var controlProperties: Node[] = []; - var controlConstantMap = new Map(); - var controlGen = this.getGenerator("mangled"); - var controlTestKey = controlGen.generate(); - - // This 'controlVar' can be accessed by child-nodes - object.$controlVar = controlVar; - object.$controlConstantMap = controlConstantMap; - object.$controlProperties = controlProperties; - object.$controlGen = controlGen; - - return () => { - ok(Array.isArray(objectBody)); - - // The state variable names (and quantity) - var stateVars = Array(this.isDebug ? 1 : getRandomInteger(2, 5)) - .fill(0) - .map((_, i) => this.getPlaceholder() + `_c${cffIndex}_S${i}`); - - // How often should chunks be split up? - // Percentage between 10% and 90% based on block size - var splitPercent = Math.max(10, 90 - objectBody.length * 5); - - // Find functions and import declarations - var importDeclarations: Node[] = []; - var functionDeclarationNames = new Set(); - var functionDeclarationValues = new Map(); - - // Find all parent control-nodes - const allControlNodes = [object]; - parents - .filter((x) => x.$controlVar) - .forEach((node) => allControlNodes.push(node)); - - const addControlMapConstant = (literalValue: number | string) => { - // Choose a random control node to add to - var controlNode = choice(allControlNodes); - var selectedControlVar = controlNode.$controlVar; - var selectedControlConstantMap = controlNode.$controlConstantMap; - var selectedControlProperties = controlNode.$controlProperties; - - var key = selectedControlConstantMap.get(literalValue)?.key; - - // Not found, create - if (!key) { - key = controlNode.$controlGen.generate(); - selectedControlConstantMap.set(literalValue, { key: key }); - - selectedControlProperties.push( - Property(Literal(key), Literal(literalValue), false) - ); - } - - return getControlMember(key, selectedControlVar); - }; - - // Helper function to easily make control object accessors - const getControlMember = (key: string, objectName = controlVar) => - MemberExpression(Identifier(objectName), Literal(key), true); - - // This function recursively calls itself to flatten and split up code into 'chunks' - const flattenBody = (body: Node[], startingLabel: string): Chunk[] => { - var chunks: Chunk[] = []; - var currentBody: Node[] = []; - var currentLabel = startingLabel; - - // This function ends the current chunk being created ('currentBody') - const finishCurrentChunk = ( - pointingLabel?: string, - newLabel?: string, - addGotoStatement = true - ) => { - if (!newLabel) { - newLabel = this.getPlaceholder(); - } - if (!pointingLabel) { - pointingLabel = newLabel; - } - - if (addGotoStatement) { - currentBody.push({ type: "GotoStatement", label: pointingLabel }); - } - - chunks.push({ - label: currentLabel, - body: [...currentBody], - }); - - // Random chance of this chunk being flagged (First label cannot be flagged) - if ( - !this.isDebug && - this.addFlaggedLabels && - currentLabel !== startLabel && - chance(25) - ) { - flaggedLabels[currentLabel] = { - flagKey: controlGen.generate(), - flagValue: choice([true, false]), - }; - } - - walk(currentBody, [], (o, p) => { - if (o.type === "Literal" && !this.isDebug) { - // Add strings to the control object - if ( - this.addToControlObject && - typeof o.value === "string" && - o.value.length >= 3 && - o.value.length <= 100 && - !isModuleSource(o, p) && - !isDirective(o, p) && - !o.regex && - chance( - 50 - - controlConstantMap.size - - this.mangledExpressionsMade / 100 - ) - ) { - return () => { - this.replaceIdentifierOrLiteral( - o, - addControlMapConstant(o.value), - p - ); - }; - } - - // Add numbers to the control object - if ( - this.addToControlObject && - typeof o.value === "number" && - Math.floor(o.value) === o.value && - Math.abs(o.value) < 100_000 && - chance( - 50 - - controlConstantMap.size - - this.mangledExpressionsMade / 100 - ) - ) { - return () => { - this.replaceIdentifierOrLiteral( - o, - addControlMapConstant(o.value), - p - ); - }; - } - } - }); - - currentLabel = newLabel; - currentBody = []; - }; - - if (body !== objectBody) { - // This code is nested. Move function declarations up - - var newBody = []; - for (var stmt of body) { - if (stmt.type === "FunctionDeclaration") { - newBody.unshift(stmt); - } else { - newBody.push(stmt); - } - } - - body = newBody; - } - - body.forEach((stmt, i) => { - if (stmt.type === "ImportDeclaration") { - // The 'importDeclarations' hold statements that are required to be left untouched at the top of the block - importDeclarations.push(stmt); - return; - } else if (stmt.type === "FunctionDeclaration") { - var functionName = stmt.id.name; - - stmt.type = "FunctionExpression"; - stmt.id = null; - - functionDeclarationNames.add(functionName); - if (objectBody === body) { - functionDeclarationValues.set(functionName, stmt); - return; - } else { - currentBody.push( - ExpressionStatement( - AssignmentExpression("=", Identifier(functionName), stmt) - ) - ); - } - - return; - } else if (stmt.directive) { - if (objectBody === body) { - importDeclarations.push(stmt); - } else { - this.error(new Error("Unimplemented directive support.")); - } - return; - } - - if (stmt.type == "GotoStatement" && i !== body.length - 1) { - finishCurrentChunk(stmt.label); - return; - } - - // The Preparation transform adds labels to every Control-Flow node - if ( - this.flattenControlStructures && - stmt.type == "LabeledStatement" - ) { - var lbl = stmt.label.name; - var control: Node = stmt.body; - - var isSwitchStatement = control.type === "SwitchStatement"; - - if ( - isSwitchStatement || - ((control.type == "ForStatement" || - control.type == "WhileStatement" || - control.type == "DoWhileStatement") && - control.body.type == "BlockStatement") - ) { - if (isSwitchStatement) { - if (control.cases.length == 0) { - currentBody.push(stmt); - return; - } - } - - var isLoop = !isSwitchStatement; - var supportContinueStatement = isLoop; - - var testPath = this.getPlaceholder(); - var updatePath = this.getPlaceholder(); - var bodyPath = this.getPlaceholder(); - var afterPath = this.getPlaceholder(); - var possible = true; - var toReplace = []; - - // Find all break; and continue; statements and change them into 'GotoStatement's - walk(control.body || control.cases, [], (o, p) => { - if ( - o.type === "BreakStatement" || - o.type === "ContinueStatement" - ) { - var allowedLabels = new Set( - p - .filter( - (x) => - x.type === "LabeledStatement" && - x.body.type === "SwitchStatement" - ) - .map((x) => x.label.name) - ); - - var isUnsupportedContinue = - !supportContinueStatement && o.type === "ContinueStatement"; - - var isInvalidLabel = - !o.label || - (o.label.name !== lbl && !allowedLabels.has(o.label.name)); - - // This seems like the best solution: - if (isUnsupportedContinue || isInvalidLabel) { - possible = false; - return "EXIT"; - } - if (o.label.name === lbl) { - return () => { - toReplace.push([ - o, - { - type: "GotoStatement", - label: - o.type == "BreakStatement" ? afterPath : updatePath, - }, - ]); - }; - } - } - }); - if (!possible) { - currentBody.push(stmt); - return; - } - toReplace.forEach((v) => this.replace(v[0], v[1])); - - if (isSwitchStatement) { - var switchDiscriminantName = this.getPlaceholder() + "_switchD"; // Stores the value of the discriminant - var switchTestName = this.getPlaceholder() + "_switchT"; // Set to true when a Switch case is matched - - currentBody.push( - VariableDeclaration( - VariableDeclarator( - switchDiscriminantName, - control.discriminant - ) - ) - ); - - currentBody.push( - VariableDeclaration( - VariableDeclarator(switchTestName, Literal(false)) - ) - ); - - // case labels are: - // `${caseLabelPrefix}_test_${index}` - // `${caseLabelPrefix}_entry_${index}` - var caseLabelPrefix = this.getPlaceholder(); - var defaultCaseIndex = control.cases.findIndex( - (x) => x.test === null - ); - - control.cases.forEach((switchCase, i) => { - var testPath = caseLabelPrefix + "_test_" + i; - var entryPath = caseLabelPrefix + "_entry_" + i; - var nextEntryPath = - i === control.cases.length - 1 // Last path goes to afterPath - ? afterPath // Else go to next entry path (fall-through behavior) - : caseLabelPrefix + "_entry_" + (i + 1); - var nextTestPath = - i === control.cases.length - 1 - ? afterPath - : caseLabelPrefix + "_test_" + (i + 1); - - finishCurrentChunk(testPath, testPath, i == 0); - - if (switchCase.test) { - // Check the case condition and goto statement - currentBody.push( - IfStatement( - BinaryExpression( - "===", - Identifier(switchDiscriminantName), - switchCase.test - ), - [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(switchTestName), - Literal(true) - ) - ), - { - type: "GotoStatement", - label: entryPath, - }, - ] - ) - ); - } else { - // Default case: No test needed. - } - - // If default case, on last test, if no case was matched, goto default case - if ( - i === control.cases.length - 1 && - defaultCaseIndex !== -1 - ) { - currentBody.push( - IfStatement( - UnaryExpression("!", Identifier(switchTestName)), - [ - { - type: "GotoStatement", - label: - caseLabelPrefix + "_entry_" + defaultCaseIndex, - }, - ] - ) - ); - } - - // Jump to next test - currentBody.push({ - type: "GotoStatement", - label: nextTestPath, - }); - - chunks.push( - ...flattenBody( - [ - ...switchCase.consequent, - { - type: "GotoStatement", - label: nextEntryPath, - }, - ], - entryPath - ) - ); - }); - - finishCurrentChunk(afterPath, afterPath, false); - return; - } else if (isLoop) { - var isPostTest = control.type == "DoWhileStatement"; - - // add initializing section to current chunk - if (control.init) { - if (control.init.type == "VariableDeclaration") { - currentBody.push(control.init); - } else { - currentBody.push(ExpressionStatement(control.init)); - } - } - - // create new label called `testPath` and have current chunk point to it (goto testPath) - finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath); - - currentBody.push( - ExpressionStatement( - AssignmentExpression( - "=", - getControlMember(controlTestKey), - control.test || Literal(true) - ) - ) - ); - - finishCurrentChunk(); - - currentBody.push( - IfStatement(getControlMember(controlTestKey), [ - { - type: "GotoStatement", - label: bodyPath, - }, - ]) - ); - - // create new label called `bodyPath` and have test body point to afterPath (goto afterPath) - finishCurrentChunk(afterPath, bodyPath); - - var innerBothPath = this.getPlaceholder(); - chunks.push( - ...flattenBody( - [ - ...control.body.body, - { - type: "GotoStatement", - label: updatePath, - }, - ], - innerBothPath - ) - ); - - finishCurrentChunk(innerBothPath, updatePath); - - if (control.update) { - currentBody.push(ExpressionStatement(control.update)); - } - - finishCurrentChunk(testPath, afterPath); - return; - } - } - } - - if ( - this.flattenControlStructures && - stmt.type == "IfStatement" && - stmt.consequent.type == "BlockStatement" && - (!stmt.alternate || stmt.alternate.type == "BlockStatement") - ) { - finishCurrentChunk(); - - currentBody.push( - ExpressionStatement( - AssignmentExpression( - "=", - getControlMember(controlTestKey), - stmt.test - ) - ) - ); - - finishCurrentChunk(); - - var hasAlternate = !!stmt.alternate; - ok(!(hasAlternate && stmt.alternate.type !== "BlockStatement")); - - var yesPath = this.getPlaceholder(); - var noPath = this.getPlaceholder(); - var afterPath = this.getPlaceholder(); - - currentBody.push( - IfStatement(getControlMember(controlTestKey), [ - { - type: "GotoStatement", - label: yesPath, - }, - ]) - ); - - chunks.push( - ...flattenBody( - [ - ...stmt.consequent.body, - { - type: "GotoStatement", - label: afterPath, - }, - ], - yesPath - ) - ); - - if (hasAlternate) { - chunks.push( - ...flattenBody( - [ - ...stmt.alternate.body, - { - type: "GotoStatement", - label: afterPath, - }, - ], - noPath - ) - ); - - finishCurrentChunk(noPath, afterPath); - } else { - finishCurrentChunk(afterPath, afterPath); - } - - return; - } - - if (!currentBody.length || !chance(splitPercent)) { - currentBody.push(stmt); - } else { - // Start new chunk - finishCurrentChunk(); - currentBody.push(stmt); - } - }); - - finishCurrentChunk(); - chunks[chunks.length - 1].body.pop(); - - return chunks; - }; - - /** - * Executable code segments are broken down into `chunks` typically 1-3 statements each - * - * Chunked Code has a special `GotoStatement` node that get processed later on - * This allows more complex control structures like `IfStatement`s and `ForStatement`s to be converted into basic - * conditional jumps and flattened in the switch body - * - * IfStatement would be converted like this: - * - * MAIN: - * if ( TEST ) { - * GOTO consequent_label; - * } else? { - * GOTO alternate_label; - * } - * GOTO NEXT_CHUNK; - */ - const chunks: Chunk[] = []; - - // Flagged labels have addition code protecting the control state - const flaggedLabels: { - [label: string]: { flagKey: string; flagValue: boolean }; - } = Object.create(null); - - /** - * label: switch(a+b+c){...break label...} - */ - const switchLabel = this.getPlaceholder(); - - const startLabel = this.getPlaceholder(); - - chunks.push(...flattenBody(objectBody, startLabel)); - chunks[chunks.length - 1].body.push({ - type: "GotoStatement", - label: "END_LABEL", - }); - chunks.push({ - label: "END_LABEL", - body: [], - }); - - const endLabel = chunks[Object.keys(chunks).length - 1].label; - - if (!this.isDebug && this.addDeadCode) { - // DEAD CODE 1/3: Add fake chunks that are never reached - var fakeChunkCount = getRandomInteger(1, 5); - for (var i = 0; i < fakeChunkCount; i++) { - // These chunks just jump somewhere random, they are never executed - // so it could contain any code - var fakeChunkBody = [ - // This a fake assignment expression - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(choice(stateVars)), - Literal(getRandomInteger(-150, 150)) - ) - ), - - { - type: "GotoStatement", - label: choice(chunks).label, - }, - ]; - - chunks.push({ - label: this.getPlaceholder(), - body: fakeChunkBody, - impossible: true, - }); - } - - // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators - chunks.forEach((chunk) => { - if (chance(25)) { - var randomLabel = choice(chunks).label; - - // The `false` literal will be mangled - chunk.body.unshift( - IfStatement(Literal(false), [ - { - type: "GotoStatement", - label: randomLabel, - impossible: true, - }, - ]) - ); - } - }); - - // DEAD CODE 3/3: Clone chunks but these chunks are never ran - var cloneChunkCount = getRandomInteger(1, 5); - for (var i = 0; i < cloneChunkCount; i++) { - var randomChunk = choice(chunks); - var clonedChunk = { - body: clone(randomChunk.body), - label: this.getPlaceholder(), - impossible: true, - }; - - // Don't double define functions - var hasDeclaration = clonedChunk.body.find((stmt) => { - return ( - stmt.type === "FunctionDeclaration" || - stmt.type === "ClassDeclaration" - ); - }); - - if (!hasDeclaration) { - chunks.unshift(clonedChunk); - } - } - } - - // Generate a unique 'state' number for each chunk - var caseSelection: Set = new Set(); - var uniqueStatesNeeded = chunks.length; - - do { - var newState = getRandomInteger(1, chunks.length * 15); - if (this.isDebug) { - newState = caseSelection.size; - } - caseSelection.add(newState); - } while (caseSelection.size !== uniqueStatesNeeded); - - ok(caseSelection.size == uniqueStatesNeeded); - - /** - * The accumulated state values - * - * index -> total state value - */ - var caseStates = Array.from(caseSelection); - - /** - * The individual state values for each label - * - * labels right now are just chunk indexes (numbers) - * - * but will expand to if statements and functions when `goto statement` obfuscation is added - */ - var labelToStates: { [label: string]: number[] } = Object.create(null); - - var lastLabel; - - Object.values(chunks).forEach((chunk, i) => { - var state = caseStates[i]; - - var stateValues = Array(stateVars.length) - .fill(0) - .map((_, i) => - lastLabel && chance(95) // Try to make state changes not as drastic (If last label, re-use some of it's values) - ? labelToStates[lastLabel][i] - : getRandomInteger(-500, 500) - ); - - const getCurrentState = () => { - return stateValues.reduce((a, b) => b + a, 0); - }; - - var correctIndex = getRandomInteger(0, stateValues.length); - stateValues[correctIndex] = - state - (getCurrentState() - stateValues[correctIndex]); - - labelToStates[chunk.label] = stateValues; - lastLabel = chunk.label; - }); - - var initStateValues = [...labelToStates[startLabel]]; - var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0); - - // Creates a predicate based on the state-variables and control-object properties - const createPredicate = ( - stateValues: number[] - ): { test: Node; testValue: boolean } => { - this.mangledExpressionsMade++; - - var index = getRandomInteger(0, stateVars.length); - - var compareValue = choice([ - stateValues[index], - getRandomInteger(-100, 100), - ]); - - // 'state equality' test - var test: Node = BinaryExpression( - "==", - Identifier(stateVars[index]), - createStateBoundNumberLiteral(compareValue, stateValues) - ); - var testValue = stateValues[index] === compareValue; - - // 'control' equality test - if (controlConstantMap.size && chance(50)) { - // The controlMap maps LITERAL-values to STRING property names - var actualValue = choice(Array.from(controlConstantMap.keys())); - var controlKey = controlConstantMap.get(actualValue)?.key; - - var controlCompareValue = choice([ - actualValue, - stateValues[index], - getRandomInteger(-100, 100), - controlGen.generate(), - ]); - - // 'control equality' test - test = BinaryExpression( - "==", - getControlMember(controlKey), - Literal(controlCompareValue) - ); - testValue = actualValue == controlCompareValue; - - // 'control typeof' test - if (chance(10)) { - var compareTypeofValue = choice([ - "number", - "string", - "object", - "function", - "undefined", - ]); - - test = BinaryExpression( - "==", - UnaryExpression("typeof", getControlMember(controlKey)), - Literal(compareTypeofValue) - ); - testValue = typeof actualValue === compareTypeofValue; - } - - // 'control hasOwnProperty' test - if (chance(10)) { - var hasOwnProperty = choice([controlKey, controlGen.generate()]); - test = CallExpression( - MemberExpression( - Identifier(controlVar), - Literal("hasOwnProperty"), - true - ), - [Literal(hasOwnProperty)] - ); - testValue = hasOwnProperty === controlKey; - } - } - - return { test, testValue }; - }; - - // A "state-less" number literal is a Number Literal that is mangled in with the Control properties. - // Example: X = CONTROL.Y + Z. These can be used anywhere because control properties are constant (unlike state variables) - const createStatelessNumberLiteral = (num: number, depth = 0) => { - if ( - !controlConstantMap.size || - depth > 4 || - chance(75 + depth * 5 + this.mangledExpressionsMade / 25) - ) { - // Add to control constant map? - if ( - chance( - 25 - controlConstantMap.size - this.mangledExpressionsMade / 100 - ) - ) { - return addControlMapConstant(num); - } - return Literal(num); - } - this.mangledExpressionsMade++; - - if (controlConstantMap.has(num)) { - return getControlMember(controlConstantMap.get(num)?.key); - } - - var allControlNodes = [object]; - parents - .filter((x) => x.$controlVar && x.$controlConstantMap.size > 0) - .forEach((node) => allControlNodes.push(node)); - - var controlNode = choice(allControlNodes); - var selectedControlConstantMap = controlNode.$controlConstantMap; - var selectedControlVar = controlNode.$controlVar; - - var actualValue = choice(Array.from(selectedControlConstantMap.keys())); - var controlKey = selectedControlConstantMap.get(actualValue)?.key; - - if (typeof actualValue === "number") { - var difference = actualValue - num; - - return BinaryExpression( - "-", - getControlMember(controlKey, selectedControlVar), - createStatelessNumberLiteral(difference, depth + 1) - ); - } else if (typeof actualValue === "string") { - // 'control string length' test - var compareValue = choice([ - actualValue.length, - getRandomInteger(0, 50), - ]); - - var test = BinaryExpression( - "==", - MemberExpression( - getControlMember(controlKey, selectedControlVar), - Literal("length"), - true - ), - createStatelessNumberLiteral(compareValue, depth + 1) - ); - var testValue = actualValue.length == compareValue; - - var consequent: Node = createStatelessNumberLiteral(num, depth + 1); - var alternate: Node = Literal(getRandomInteger(-100, 100)); - - return ConditionalExpression( - test, - testValue ? consequent : alternate, - !testValue ? consequent : alternate - ); - } else { - throw new Error("Unknown: " + typeof actualValue); - } - }; - - // A "state-bound" number literal is a Number Literal that is mangled in with the current state variables - // Example: X = STATE + Y. This can only be used when the state-values are guaranteed to be known. - const createStateBoundNumberLiteral = ( - num: number, - stateValues: number[], - depth = 0 - ): Node => { - ok(Array.isArray(stateValues)); - - // Base case: After 4 depth, OR random chance - if ( - depth > 4 || - chance(75 + depth * 5 + this.mangledExpressionsMade / 25) - ) { - // Add this number to the control object? - // Add to control constant map? - if (chance(25 - controlConstantMap.size)) { - return addControlMapConstant(num); - } - - return Literal(num); - } - this.mangledExpressionsMade++; - - if (chance(10)) { - return createStatelessNumberLiteral(num, depth + 1); - } - - // Terminated predicate - if (chance(50)) { - var { test, testValue } = createPredicate(stateValues); - - var alternateNode = choice([ - Literal(getRandomInteger(-100, 100)), - Literal(controlGen.generate()), - getControlMember(controlGen.generate()), - ]); - - return ConditionalExpression( - test, - testValue ? Literal(num) : alternateNode, - !testValue ? Literal(num) : alternateNode - ); - } - - // Recursive predicate - var opposing = getRandomInteger(0, stateVars.length); - - if (chance(10)) { - // state > compare ? real : fake - - var compareValue: number = choice([ - stateValues[opposing], - getRandomInteger(-150, 150), - ]); - - var operator = choice(["<", ">", "==", "!="]); - var answer: boolean = { - ">": compareValue > stateValues[opposing], - "<": compareValue < stateValues[opposing], - "==": compareValue === stateValues[opposing], - "!=": compareValue !== stateValues[opposing], - }[operator]; - - var correct = createStateBoundNumberLiteral( - num, - stateValues, - depth + 1 - ); - var incorrect = createStateBoundNumberLiteral( - getRandomInteger(-150, 150), - stateValues, - depth + 1 - ); - - return ConditionalExpression( - BinaryExpression( - operator, - createStateBoundNumberLiteral( - compareValue, - stateValues, - depth + 1 - ), - Identifier(stateVars[opposing]) - ), - answer ? correct : incorrect, - answer ? incorrect : correct - ); - } - - // state + 10 = - var difference = num - stateValues[opposing]; - - if (difference === 0) { - return Identifier(stateVars[opposing]); - } - - return BinaryExpression( - "+", - Identifier(stateVars[opposing]), - createStateBoundNumberLiteral(difference, stateValues, depth + 1) - ); - }; - - var outlinesCreated = 0; - - const isExpression = (object: Node, parents: Node[]) => { - var fnIndex = parents.findIndex((x) => isFunction(x)); - if (fnIndex != -1) { - // This does NOT mutate - parents = parents.slice(0, fnIndex); - } - var assignmentIndex = parents.findIndex( - (x) => x.type === "AssignmentExpression" - ); - - // Left-hand assignment validation - if (assignmentIndex != -1) { - if ( - parents[assignmentIndex].left === - (parents[assignmentIndex - 1] || object) - ) { - return false; - } - } - - // For in/of left validation - var forInOfIndex = parents.findIndex( - (x) => x.type === "ForInStatement" || x.type === "ForOfStatement" - ); - if (forInOfIndex != -1) { - if ( - parents[forInOfIndex].left === (parents[forInOfIndex - 1] || object) - ) { - return false; - } - } - - // Bound call-expression validation - var callExpressionIndex = parents.findIndex( - (x) => x.type === "CallExpression" - ); - if (callExpressionIndex != -1) { - if ( - parents[callExpressionIndex].callee == - (parents[callExpressionIndex - 1] || object) - ) { - var callee = parents[callExpressionIndex].callee; - - // Detected bound call expression. Not supported. - if (callee.type === "MemberExpression") { - return false; - } - } - } - - // Update-expression validation: - var updateExpressionIndex = parents.findIndex( - (x) => x.type === "UpdateExpression" - ); - if (updateExpressionIndex !== -1) return false; - - return true; - }; - - // This function checks if the expression or statements is possible to be outlined - const canOutline = (object: Node | Node[], parents: Node[]) => { - var isIllegal = false; - - var breakStatements: Location[] = []; - var returnStatements: Location[] = []; - - if (!Array.isArray(object) && !isExpression(object, parents)) { - return { isIllegal: true, breakStatements: [], returnStatements: [] }; - } - - walk(object, parents, (o, p) => { - if ( - o.type === "ThisExpression" || - o.type === "MetaProperty" || - o.type === "Super" - ) { - isIllegal = true; - return "EXIT"; - } - - if (o.type === "BreakStatement") { - // This can be safely outlined - if (o.label && o.label.name === switchLabel) { - breakStatements.push([o, p]); - } else { - isIllegal = true; - return "EXIT"; - } - } - - if ( - (o.type === "ContinueStatement" || - o.type === "AwaitExpression" || - o.type === "YieldExpression" || - o.type === "ReturnStatement" || - o.type === "VariableDeclaration" || - o.type === "FunctionDeclaration" || - o.type === "ClassDeclaration") && - !p.find((x) => isVarContext(x)) - ) { - // This can be safely outlined - if (o.type === "ReturnStatement") { - returnStatements.push([o, p]); - } else { - isIllegal = true; - return "EXIT"; - } - } - - if (o.type === "Identifier") { - if (o.name === "arguments") { - isIllegal = true; - return "EXIT"; - } - } - }); - - return { isIllegal, breakStatements, returnStatements }; - }; - - const createOutlineFunction = ( - body: Node[], - stateValues: number[], - label: string - ) => { - var key = controlGen.generate(); - - var functionExpression = FunctionExpression([], body); - if (!this.options.es5 && chance(50)) { - functionExpression.type = "ArrowFunctionExpression"; - } - - controlProperties.push( - Property(Literal(key), functionExpression, false) - ); - - // Add dead code to function - if (!this.isDebug && chance(25)) { - var { test, testValue } = createPredicate(stateValues); - var deadCodeVar = this.getPlaceholder(); - functionExpression.params.push( - AssignmentPattern(Identifier(deadCodeVar), test) - ); - var alternate = [ - ReturnStatement( - choice([ - BinaryExpression( - "==", - Identifier(choice(stateVars)), - Literal(getRandomInteger(-100, 100)) - ), - Literal(controlGen.generate()), - Identifier("arguments"), - Identifier(choice(stateVars)), - Identifier(controlVar), - CallExpression(getControlMember(controlGen.generate()), []), - ]) - ), - ]; - - functionExpression.body.body.unshift( - IfStatement( - testValue - ? UnaryExpression("!", Identifier(deadCodeVar)) - : Identifier(deadCodeVar), - alternate - ) - ); - } - - outlinesCreated++; - - return key; - }; - - const attemptOutlineStatements = ( - statements: Node[], - parentBlock: Node[], - stateValues: number[], - label: string - ) => { - if ( - this.isDebug || - !this.outlineStatements || - chance(75 + outlinesCreated - this.mangledExpressionsMade / 25) - ) { - return; - } - - var index = parentBlock.indexOf(statements[0]); - if (index === -1) return; - - var outlineInfo = canOutline(statements, parentBlock); - if (outlineInfo.isIllegal) return; - - var breakFlag = controlGen.generate(); - - outlineInfo.breakStatements.forEach(([breakStatement, p]) => { - this.replace(breakStatement, ReturnStatement(Literal(breakFlag))); - }); - - var returnFlag = controlGen.generate(); - - outlineInfo.returnStatements.forEach(([returnStatement, p]) => { - var argument = returnStatement.argument || Identifier("undefined"); - - this.replace( - returnStatement, - ReturnStatement( - ObjectExpression([Property(Literal(returnFlag), argument, false)]) - ) - ); - }); - - // Outline these statements! - var key = createOutlineFunction(clone(statements), stateValues, label); - var callExpression = CallExpression(getControlMember(key), []); - - var newStatements: Node[] = []; - if ( - outlineInfo.breakStatements.length === 0 && - outlineInfo.returnStatements.length === 0 - ) { - newStatements.push(ExpressionStatement(callExpression)); - } else if (outlineInfo.returnStatements.length === 0) { - newStatements.push( - IfStatement( - BinaryExpression("==", callExpression, Literal(breakFlag)), - [BreakStatement(switchLabel)] - ) - ); - } else { - var tempVar = this.getPlaceholder(); - newStatements.push( - VariableDeclaration(VariableDeclarator(tempVar, callExpression)) - ); - - const t = (str): Node => new Template(str).single().expression; - - newStatements.push( - IfStatement( - t(`${tempVar} === "${breakFlag}"`), - [BreakStatement(switchLabel)], - [ - IfStatement(t(`typeof ${tempVar} == "object"`), [ - ReturnStatement(t(`${tempVar}["${returnFlag}"]`)), - ]), - ] - ) - ); - } - - // Remove the original statements from the block and replace it with the call expression - parentBlock.splice(index, statements.length, ...newStatements); - }; - - const attemptOutlineExpression = ( - expression: Node, - expressionParents: Node[], - stateValues: number[], - label: string - ) => { - if ( - this.isDebug || - !this.outlineExpressions || - chance(75 + outlinesCreated - this.mangledExpressionsMade / 25) - ) { - return; - } - - var outlineInfo = canOutline(expression, expressionParents); - if ( - outlineInfo.isIllegal || - outlineInfo.breakStatements.length || - outlineInfo.returnStatements.length - ) - return; - - // Outline this expression! - var key = createOutlineFunction( - [ReturnStatement(clone(expression))], - stateValues, - label - ); - - var callExpression = CallExpression(getControlMember(key), []); - - this.replaceIdentifierOrLiteral( - expression, - callExpression, - expressionParents - ); - }; - - const createTransitionExpression = ( - index: number, - add: number, - mutatingStateValues: number[], - label: string - ) => { - var beforeStateValues = [...mutatingStateValues]; - var newValue = mutatingStateValues[index] + add; - - var expr = null; - - if (this.isDebug) { - // state = NEW_STATE - expr = AssignmentExpression( - "=", - Identifier(stateVars[index]), - Literal(newValue) - ); - } else if (chance(90)) { - // state += (NEW_STATE - CURRENT_STATE) - expr = AssignmentExpression( - "+=", - Identifier(stateVars[index]), - createStateBoundNumberLiteral(add, mutatingStateValues) - ); - } else { - // state *= 2 - // state -= DIFFERENCE - var double = mutatingStateValues[index] * 2; - var diff = double - newValue; - - var first = AssignmentExpression( - "*=", - Identifier(stateVars[index]), - createStateBoundNumberLiteral(2, mutatingStateValues) - ); - mutatingStateValues[index] = double; - - expr = SequenceExpression([ - first, - AssignmentExpression( - "-=", - Identifier(stateVars[index]), - createStateBoundNumberLiteral(diff, mutatingStateValues) - ), - ]); - } - - mutatingStateValues[index] = newValue; - - // These are lower quality outlines vs. the entire transition outline - if (chance(50)) { - attemptOutlineExpression(expr, [], [...beforeStateValues], label); - } - - return expr; - }; - - interface Case { - state: number; - body: Node[]; - label: string; - } - - var cases: Case[] = []; - - chunks.forEach((chunk, i) => { - // skip last case, its empty and never ran - if (chunk.label === endLabel) { - return; - } - - ok(labelToStates[chunk.label]); - var state = caseStates[i]; - - var staticStateValues = [...labelToStates[chunk.label]]; - var potentialBranches = new Set(); - - [...chunk.body].forEach((stmt) => { - walk(stmt, [], (o, p) => { - // This mangles certain literals with the state variables - // Ex: A number literal (50) changed to a expression (stateVar + 40), when stateVar = 10 - if ( - !this.isDebug && - o.type === "Literal" && - !p.find((x) => isVarContext(x)) - ) { - if ( - typeof o.value === "number" && - Math.floor(o.value) === o.value && // Only whole numbers - Math.abs(o.value) < 100_000 && // Hard-coded limit - this.mangleNumberLiterals && - chance(50 - this.mangledExpressionsMade / 100) - ) { - // 50 -> state1 - 10, when state1 = 60. The result is still 50 - - return () => { - this.replaceIdentifierOrLiteral( - o, - createStateBoundNumberLiteral(o.value, staticStateValues), - p - ); - }; - } - - if ( - typeof o.value === "boolean" && - this.mangleBooleanLiterals && - chance(50 - this.mangledExpressionsMade / 100) - ) { - // true -> state1 == 10, when state1 = 10. The result is still true - - // Choose a random state var to compare again - var index = getRandomInteger(0, stateVars.length); - - var compareValue = staticStateValues[index]; - - // When false, always choose a different number, so the expression always equals false - while (!o.value && compareValue === staticStateValues[index]) { - compareValue = getRandomInteger(-150, 150); - } - - var mangledExpression: Node = BinaryExpression( - "==", - Identifier(stateVars[index]), - createStateBoundNumberLiteral(compareValue, staticStateValues) - ); - - return () => { - this.replaceIdentifierOrLiteral(o, mangledExpression, p); - - attemptOutlineExpression( - o, - p, - staticStateValues, - chunk.label - ); - }; - } - } - - // Mangle certain referenced identifiers - // console.log("hi") -> (x ? console : window).log("hi"), when is x true. The result is the same - if ( - !this.isDebug && - o.type === "Identifier" && - this.mangleIdentifiers && - !reservedIdentifiers.has(o.name) && - chance(50 - this.mangledExpressionsMade / 100) && - !p.find((x) => isVarContext(x)) - ) { - // ONLY referenced identifiers (like actual variable names) can be changed - var info = getIdentifierInfo(o, p); - if ( - !info.spec.isReferenced || - info.spec.isDefined || - info.spec.isModified || - info.spec.isExported - ) { - return; - } - - // Ignore __JS_CONFUSER_VAR__() - if (isJSConfuserVar(p)) { - return; - } - - // TYPEOF expression check - if ( - p[0] && - p[0].type === "UnaryExpression" && - p[0].operator === "typeof" && - p[0].argument === o - ) { - return; - } - - // Update expression check - if (p[0] && p[0].type === "UpdateExpression") { - return; - } - - // FOR-in/of initializer check - if (isForInitialize(o, p) === "left-hand") { - return; - } - - var { test, testValue } = createPredicate(staticStateValues); - - // test && real - var mangledExpression: Node = LogicalExpression( - testValue ? "&&" : "||", - test, - Identifier(o.name) - ); - - // control.fake = real - if (chance(50)) { - mangledExpression = AssignmentExpression( - "=", - getControlMember(controlGen.generate()), - Identifier(o.name) - ); - } - - // test ? real : fake - if (chance(50)) { - var alternateName = choice([ - controlVar, - ...stateVars, - ...this.options.globalVariables, - ...reservedIdentifiers, - ]); - - // Don't use 'arguments' - if (alternateName === "arguments") alternateName = "undefined"; - - mangledExpression = ConditionalExpression( - test, - Identifier(testValue ? o.name : alternateName), - Identifier(!testValue ? o.name : alternateName) - ); - } - - return () => { - this.replaceIdentifierOrLiteral(o, mangledExpression, p); - }; - } - - // Function outlining: bring out certain expressions - if ( - !this.isDebug && - o.type && - [ - "BinaryExpression", - "LogicalExpression", - "CallExpression", - "AssignmentExpression", - "MemberExpression", - "ObjectExpression", - "ConditionalExpression", - ].includes(o.type) && - !chance(p.length * 5) && // The further down the tree the lower quality of expression - !p.find((x) => isContext(x) || x.$outlining) - ) { - o.$outlining = true; - return () => { - attemptOutlineExpression(o, p, staticStateValues, chunk.label); - }; - } - - // Opaque predicates: If Statements, Conditional Statements, Switch Case test - if ( - !this.isDebug && - this.addOpaquePredicates && - p[0] && - chance(50 - outlinesCreated - this.mangledExpressionsMade / 100) - ) { - var isTestExpression = - (p[0].type == "IfStatement" && p[0].test === o) || - (p[0].type === "ConditionalExpression" && p[0].test === o) || - (p[0].type === "SwitchCase" && p[0].test === o); - - if (isTestExpression && !p.find((x) => isContext(x))) { - return () => { - var { test, testValue } = createPredicate(staticStateValues); - - this.replace( - o, - LogicalExpression(testValue ? "&&" : "||", test, clone(o)) - ); - }; - } - } - - if (o.type == "StateIdentifier") { - return () => { - ok(labelToStates[o.label]); - this.replace( - o, - ArrayExpression(labelToStates[o.label].map(Literal)) - ); - }; - } - - if (o.type == "GotoStatement") { - return () => { - var blockIndex = p.findIndex( - (node) => isBlock(node) || node.type === "SwitchCase" - ); - if (blockIndex === -1) { - var index = chunk.body.indexOf(stmt); - ok(index != -1); - - // Top level: Insert break statement in the chunk body - // This is OKAY because this forEach uses a cloned version of the body `[...chunk.body]` - chunk.body.splice(index + 1, 0, BreakStatement(switchLabel)); - } else { - var block = p[blockIndex]; - - if (block.type === "SwitchCase") { - // Handle switch case break placement (Important!) - block.consequent.splice( - block.consequent.indexOf(p[blockIndex - 2] || o) + 1, - 0, - BreakStatement(switchLabel) - ); - } else { - // Standard block placement - var child = p[blockIndex - 2] || o; - var childIndex = block.body.indexOf(child); - - block.body.splice( - childIndex + 1, - 0, - BreakStatement(switchLabel) - ); - } - } - - if (!o.impossible) { - potentialBranches.add(o.label); - } - - var mutatingStateValues = [...labelToStates[chunk.label]]; - var nextStateValues = labelToStates[o.label]; - ok(nextStateValues, o.label); - - var transitionExpressions: Node[] = []; - for ( - var stateValueIndex = 0; - stateValueIndex < stateVars.length; - stateValueIndex++ - ) { - var diff = - nextStateValues[stateValueIndex] - - mutatingStateValues[stateValueIndex]; - - // Only add if state value changed - // If pointing to itself then always add to ensure SequenceExpression isn't empty - if (diff !== 0 || o.label === chunk.label) { - transitionExpressions.push( - createTransitionExpression( - stateValueIndex, - diff, - mutatingStateValues, - chunk.label - ) - ); - } - } - - ok(transitionExpressions.length !== 0); - - var sequenceExpression = SequenceExpression( - transitionExpressions - ); - - // Check if flagged and additional code here - if (typeof flaggedLabels[o.label] === "object") { - var { flagKey, flagValue } = flaggedLabels[o.label]; - - sequenceExpression.expressions.push( - AssignmentExpression( - "=", - getControlMember(flagKey), - Literal(flagValue) - ) - ); - } - - attemptOutlineExpression( - sequenceExpression, - [], - staticStateValues, - chunk.label - ); - - this.replace(o, ExpressionStatement(sequenceExpression)); - }; - } - }); - }); - - attemptOutlineStatements( - chunk.body, - chunk.body, - staticStateValues, - chunk.label - ); - - if (!chunk.impossible) { - // FUTURE OBFUSCATION IDEA: Update controlObject based on 'potentialBranches' code - // This idea would require a lot of work but would make some seriously effective obfuscation - // for protecting the data. In 'inactive' states the data could be overwritten to fake values - // And in the 'active' state the data would brought back just in time. This would require the controlObject - // state to be known in all chunks - } - - var caseObject: Case = { - body: chunk.body, - state: state, - label: chunk.label, - }; - - cases.push(caseObject); - }); - - if (!this.isDebug && this.addDeadCode) { - // Add fake control object updates - chunks.forEach((chunk) => { - if (chance(10)) { - // These deadCode variants can NOT break the state/control variables - // They are executed! - var deadCodeChoices = [ - ExpressionStatement( - AssignmentExpression( - "=", - getControlMember(controlGen.generate()), - Literal(controlGen.generate()) - ) - ), - ExpressionStatement( - UnaryExpression( - "delete", - getControlMember(controlGen.generate()) - ) - ), - ]; - - // These deadCode variants can make breaking changes - // because they are never ran - if (chunk.impossible) { - var randomControlKey = - choice( - controlProperties - .map((prop) => prop.key?.value) - .filter((x) => x && typeof x === "string") - ) || controlGen.generate(); - - deadCodeChoices = deadCodeChoices.concat([ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(controlVar), - Literal(false) - ) - ), - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(controlVar), - Identifier("undefined") - ) - ), - ExpressionStatement( - AssignmentExpression( - "=", - getControlMember(randomControlKey), - Identifier("undefined") - ) - ), - ExpressionStatement( - UnaryExpression("delete", getControlMember(randomControlKey)) - ), - ]); - } - - chunk.body.unshift(choice(deadCodeChoices)); - } - }); - } - - if (!this.isDebug) { - shuffle(cases); - shuffle(controlProperties); - } - - var discriminant = new Template(`${stateVars.join("+")}`).single() - .expression; - - objectBody.length = 0; - // Perverse position of import declarations - for (var importDeclaration of importDeclarations) { - objectBody.push(importDeclaration); - } - - // As well as functions are brought up - for (var functionName of functionDeclarationNames) { - objectBody.push( - VariableDeclaration( - VariableDeclarator( - functionName, - functionDeclarationValues.get(functionName) - ) - ) - ); - } - - var defaultCaseIndex = getRandomInteger(0, cases.length); - var switchCases: Node[] = []; - - cases.forEach((caseObject, i) => { - // Empty case OR single break statement is skipped - if ( - caseObject.body.length === 0 || - (caseObject.body.length === 1 && - caseObject.body[0].type === "BreakStatement" && - caseObject.body[0].label?.name === switchLabel) - ) - return; - - var test = Literal(caseObject.state); - var isEligibleForOutlining = false; - - // Check if Control Map has this value - if (!this.isDebug && controlConstantMap.has(caseObject.state)) { - test = getControlMember( - controlConstantMap.get(caseObject.state)?.key - ); - } - - // Create complex test expressions for each switch case - if (!this.isDebug && this.addComplexTest && chance(25)) { - isEligibleForOutlining = true; - - // case STATE+X: - var stateVarIndex = getRandomInteger(0, stateVars.length); - - var stateValues = labelToStates[caseObject.label]; - var difference = stateValues[stateVarIndex] - caseObject.state; - - var conditionNodes: Node[] = []; - var alreadyConditionedItems = new Set(); - - // This code finds clash conditions and adds them to 'conditionNodes' array - Object.keys(labelToStates).forEach((label) => { - if (label !== caseObject.label) { - var labelStates = labelToStates[label]; - var totalState = labelStates.reduce((a, b) => a + b, 0); - - if (totalState === labelStates[stateVarIndex] - difference) { - var differentIndex = labelStates.findIndex( - (v, i) => v !== stateValues[i] - ); - if (differentIndex !== -1) { - var expressionAsString = - stateVars[differentIndex] + - "!=" + - labelStates[differentIndex]; - if (!alreadyConditionedItems.has(expressionAsString)) { - alreadyConditionedItems.add(expressionAsString); - - conditionNodes.push( - BinaryExpression( - "!=", - Identifier(stateVars[differentIndex]), - Literal(labelStates[differentIndex]) - ) - ); - } - } else { - conditionNodes.push( - BinaryExpression( - "!=", - clone(discriminant), - Literal(totalState) - ) - ); - } - } - } - }); - - // case STATE!=Y && STATE+X - test = BinaryExpression( - "-", - Identifier(stateVars[stateVarIndex]), - Literal(difference) - ); - - // Use the 'conditionNodes' to not cause state clashing issues - conditionNodes.forEach((conditionNode) => { - test = LogicalExpression("&&", conditionNode, test); - }); - } - - // A 'flagged' label has addition 'flagKey' that gets switched before jumped to - if (flaggedLabels[caseObject.label]) { - isEligibleForOutlining = true; - - var { flagKey, flagValue } = flaggedLabels[caseObject.label]; - - var alternateNum: number; - do { - alternateNum = getRandomInteger(-1000, 1000 + chunks.length); - } while (caseSelection.has(alternateNum)); - - var alternate = Literal(alternateNum); - - // case FLAG ? : : - test = ConditionalExpression( - getControlMember(flagKey), - - flagValue ? test : alternate, - !flagValue ? test : alternate - ); - } - - // Outline this switch case test - if ( - !this.isDebug && - this.outlineExpressions && - isEligibleForOutlining && - chance(75 - outlinesCreated - this.mangledExpressionsMade / 25) - ) { - this.mangledExpressionsMade++; - - // Selected a random parent node (or this node) to insert this function in - var selectedControlNode = choice(allControlNodes); - var selectedControlProperties = - selectedControlNode.$controlProperties; - var selectedControlVar = selectedControlNode.$controlVar; - var selectedControlGen = selectedControlNode.$controlGen; - - var fnKey = selectedControlGen.generate(); - - // Pass in the: - // - controlVar for 'flagged labels' code check - // - stateVars for 'complex test expressions' - // (Check which identifiers are actually needed) - var argumentList = [], - watchingFor = new Set([controlVar, ...stateVars]); - walk(test, [], (o, p) => { - if (o.type === "Identifier" && watchingFor.has(o.name)) { - watchingFor.delete(o.name); - argumentList.push(Identifier(o.name)); - } - }); - - selectedControlProperties.push( - Property( - Literal(fnKey), - FunctionExpression(argumentList, [ReturnStatement(test)]), - true - ) - ); - - // case control.a(control, s1, s2): - test = CallExpression( - getControlMember(fnKey, selectedControlVar), - clone(argumentList) - ); - } - - // One random case gets to be default - if (!this.isDebug && i === defaultCaseIndex) test = null; - - var testArray: Node[] = [test]; - if (!this.isDebug && this.addFakeTest && chance(50)) { - // Add fake test - // case : - // case : - // case : - var fakeTestCount = getRandomInteger(1, 4); - for (var i = 0; i < fakeTestCount; i++) { - // Create a fake test number that doesn't interfere with the actual states - var fakeTestNum; - do { - fakeTestNum = getRandomInteger(1, 1000 + caseSelection.size); - } while (caseSelection.has(fakeTestNum)); - - // Add this fake test - testArray.push(Literal(fakeTestNum)); - } - - shuffle(testArray); - } - - testArray.forEach((test, i) => { - var body = i === testArray.length - 1 ? caseObject.body : []; - - switchCases.push(SwitchCase(test, body)); - }); - }); - - // switch(state) { case ... } - var switchStatement: Node = SwitchStatement(discriminant, switchCases); - - var declarations: Node[] = []; - - // var state = START_STATE - declarations.push( - ...stateVars.map((stateVar, i) => { - return VariableDeclarator(stateVar, Literal(initStateValues[i])); - }) - ); - - // var control = { strings, numbers, outlined functions, etc... } - var objectExpression = ObjectExpression(controlProperties); - declarations.push(VariableDeclarator(controlVar, objectExpression)); - - objectBody.push( - // Use individual variable declarations instead so Stack can apply - ...declarations.map((declaration) => - VariableDeclaration(declaration, "var") - ) - ); - - // while (state != END_STATE) {...} - var whileTest = BinaryExpression( - "!=", - clone(discriminant), - Literal(endState) - ); - - objectBody.push( - WhileStatement(whileTest, [ - LabeledStatement(switchLabel, switchStatement), - ]) - ); - - // mark this object for switch case obfuscation - switchStatement.$controlFlowFlattening = true; - }; - } -} diff --git a/src_old/transforms/controlFlowFlattening/expressionObfuscation.ts b/src_old/transforms/controlFlowFlattening/expressionObfuscation.ts deleted file mode 100644 index 4683ae5..0000000 --- a/src_old/transforms/controlFlowFlattening/expressionObfuscation.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { criticalFunctionTag } from "../../constants"; -import Template from "../../templates/template"; -import { isBlock } from "../../traverse"; -import { - CallExpression, - Identifier, - Node, - SequenceExpression, -} from "../../util/gen"; -import { prepend } from "../../util/insert"; -import Transform from "../transform"; - -/** - * Expression Obfuscation runs before Control Flow Flattening - */ -export default class ExpressionObfuscation extends Transform { - fnName: string; - - constructor(o) { - super(o); - } - - apply(tree: Node): void { - super.apply(tree); - - if (typeof this.fnName === "string") { - prepend( - tree, - new Template(` - function {fnName}(...args){ - return args[args["length"] - 1] - } - `).single({ fnName: this.fnName }) - ); - } - } - - createSequenceExpression(expressions: Node[]): Node { - if (!this.fnName) { - this.fnName = this.getPlaceholder() + criticalFunctionTag; - } - - return CallExpression(Identifier(this.fnName), [...expressions]); - } - - match(object, parents) { - return isBlock(object); - } - - transform(object, parents) { - return () => { - var exprs = []; - var deleteExprs = []; - - object.body.forEach((stmt, i) => { - if (stmt.type == "ExpressionStatement" && !stmt.directive) { - var expr = stmt.expression; - - if ( - expr.type == "UnaryExpression" && - !( - expr.operator === "typeof" && expr.argument.type === "Identifier" - ) && - exprs.length // typeof is special - ) { - expr.argument = SequenceExpression([ - ...exprs, - { ...expr.argument }, - ]); - deleteExprs.push(...exprs); - - exprs = []; - } else { - exprs.push(expr); - } - } else { - if (exprs.length) { - if (stmt.type == "IfStatement") { - if ( - stmt.test.type == "BinaryExpression" && - stmt.test.operator !== "**" - ) { - if ( - stmt.test.left.type == "UnaryExpression" && - !( - stmt.test.left.operator === "typeof" && - stmt.test.left.argument.type === "Identifier" - ) // typeof is special - ) { - stmt.test.left.argument = this.createSequenceExpression([ - ...exprs, - { ...stmt.test.left.argument }, - ]); - } else { - stmt.test.left = this.createSequenceExpression([ - ...exprs, - { ...stmt.test.left }, - ]); - } - } else if ( - stmt.test.type == "LogicalExpression" && - stmt.test.left.type == "BinaryExpression" && - stmt.test.operator !== "**" && - stmt.test.left.left.type == "UnaryExpression" - ) { - stmt.test.left.left.argument = this.createSequenceExpression([ - ...exprs, - { ...stmt.test.left.left.argument }, - ]); - } else { - stmt.test = this.createSequenceExpression([ - ...exprs, - { ...stmt.test }, - ]); - } - deleteExprs.push(...exprs); - } else if ( - stmt.type == "ForStatement" || - (stmt.type == "LabeledStatement" && - stmt.body.type == "ForStatement") - ) { - var init = (stmt.type == "LabeledStatement" ? stmt.body : stmt) - .init; - - if (init) { - if (init.type == "VariableDeclaration") { - init.declarations[0].init = this.createSequenceExpression([ - ...exprs, - { - ...(init.declarations[0].init || Identifier("undefined")), - }, - ]); - deleteExprs.push(...exprs); - } else if (init.type == "AssignmentExpression") { - init.right = this.createSequenceExpression([ - ...exprs, - { - ...(init.right || Identifier("undefined")), - }, - ]); - deleteExprs.push(...exprs); - } - } - } else if (stmt.type == "VariableDeclaration") { - stmt.declarations[0].init = this.createSequenceExpression([ - ...exprs, - { - ...(stmt.declarations[0].init || Identifier("undefined")), - }, - ]); - deleteExprs.push(...exprs); - } else if (stmt.type == "ThrowStatement") { - stmt.argument = this.createSequenceExpression([ - ...exprs, - { ...stmt.argument }, - ]); - deleteExprs.push(...exprs); - } else if (stmt.type == "ReturnStatement") { - stmt.argument = this.createSequenceExpression([ - ...exprs, - { ...(stmt.argument || Identifier("undefined")) }, - ]); - deleteExprs.push(...exprs); - } - } - - exprs = []; - } - }); - - deleteExprs.forEach((expr) => { - var index = object.body.findIndex((x) => x.expression === expr); - if (index !== -1) { - object.body.splice(index, 1); - } - }); - }; - } -} diff --git a/src_old/transforms/deadCode.ts b/src_old/transforms/deadCode.ts deleted file mode 100644 index ad456e3..0000000 --- a/src_old/transforms/deadCode.ts +++ /dev/null @@ -1,676 +0,0 @@ -import { ObfuscateOrder } from "../order"; -import { ComputeProbabilityMap } from "../probability"; -import Template from "../templates/template"; -import { isBlock } from "../traverse"; -import { - AssignmentExpression, - BinaryExpression, - ExpressionStatement, - Identifier, - IfStatement, - Literal, - MemberExpression, - Node, - ObjectExpression, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { getBlockBody, isFunction, prepend } from "../util/insert"; -import { chance, choice, getRandomInteger } from "../util/random"; -import Transform from "./transform"; - -const templates = [ - new Template(` - function curCSS( elem, name, computed ) { - var ret; - - computed = computed || getStyles( elem ); - - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = redacted.style( elem, name ); - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11+ - // IE returns zIndex value as an integer. - ret + "" : - ret; - }`), - new Template(` - function Example() { - var state = redacted.useState(false); - return x( - ErrorBoundary, - null, - x( - DisplayName, - null, - ) - ); - }`), - - new Template(` - const path = require('path'); -const { version } = require('../../package'); -const { version: dashboardPluginVersion } = require('@redacted/enterprise-plugin/package'); -const { version: componentsVersion } = require('@redacted/components/package'); -const { sdkVersion } = require('@redacted/enterprise-plugin'); -const isStandaloneExecutable = require('../utils/isStandaloneExecutable'); -const resolveLocalRedactedPath = require('./resolve-local-redacted-path'); - -const redactedPath = path.resolve(__dirname, '../redacted.js');`), - - new Template(` -module.exports = async (resolveLocalRedactedPath = ()=>{throw new Error("No redacted path provided")}) => { - const cliParams = new Set(process.argv.slice(2)); - if (!cliParams.has('--version')) { - if (cliParams.size !== 1) return false; - if (!cliParams.has('-v')) return false; - } - - const installationModePostfix = await (async (isStandaloneExecutable, redactedPath) => { - if (isStandaloneExecutable) return ' (standalone)'; - if (redactedPath === (await resolveLocalRedactedPath())) return ' (local)'; - return ''; - })(); - - return true; -};`), - new Template(` -function setCookie(cname, cvalue, exdays) { - var d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - var expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; -}`), - - new Template(`function getCookie(cname) { - var name = cname + "="; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for(var i = 0; i 1 - ) { - return Infinity; - } - - const currentHeight = Math.max(leftTreeHeight, rightTreeHeight) + 1; - return currentHeight; - } - - window["__GLOBAL__HELPERS__"] = { - buildCharacterMap, - isAnagrams, - isBalanced, - getHeightBalanced, - }; - `), - new Template(` - function ListNode(){} - var addTwoNumbers = function(l1, l2) { - var carry = 0; - var sum = 0; - var head = new ListNode(0); - var now = head; - var a = l1; - var b = l2; - while (a !== null || b !== null) { - sum = (a ? a.val : 0) + (b ? b.val : 0) + carry; - carry = Math.floor(sum / 10); - now.next = new ListNode(sum % 10); - now = now.next; - a = a ? a.next : null; - b = b ? b.next : null; - } - if (carry) now.next = new ListNode(carry); - return head.next; - }; - - console.log(addTwoNumbers) - `), - new Template(` - var threeSum = function(nums) { - var len = nums.length; - var res = []; - var l = 0; - var r = 0; - nums.sort((a, b) => (a - b)); - for (var i = 0; i < len; i++) { - if (i > 0 && nums[i] === nums[i - 1]) continue; - l = i + 1; - r = len - 1; - while (l < r) { - if (nums[i] + nums[l] + nums[r] < 0) { - l++; - } else if (nums[i] + nums[l] + nums[r] > 0) { - r--; - } else { - res.push([nums[i], nums[l], nums[r]]); - while (l < r && nums[l] === nums[l + 1]) l++; - while (l < r && nums[r] === nums[r - 1]) r--; - l++; - r--; - } - } - } - return res; - }; - console.log(threeSum) - `), - new Template(` - var combinationSum2 = function(candidates, target) { - var res = []; - var len = candidates.length; - candidates.sort((a, b) => (a - b)); - dfs(res, [], 0, len, candidates, target); - return res; - }; - - var dfs = function (res, stack, index, len, candidates, target) { - var tmp = null; - if (target < 0) return; - if (target === 0) return res.push(stack); - for (var i = index; i < len; i++) { - if (candidates[i] > target) break; - if (i > index && candidates[i] === candidates[i - 1]) continue; - tmp = Array.from(stack); - tmp.push(candidates[i]); - dfs(res, tmp, i + 1, len, candidates, target - candidates[i]); - } - }; - - console.log(combinationSum2); - `), - new Template(` - var isScramble = function(s1, s2) { - return helper({}, s1, s2); - }; - - var helper = function (dp, s1, s2) { - var map = {}; - - if (dp[s1 + s2] !== undefined) return dp[s1 + s2]; - if (s1 === s2) return true; - - for (var j = 0; j < s1.length; j++) { - if (map[s1[j]] === undefined) map[s1[j]] = 0; - if (map[s2[j]] === undefined) map[s2[j]] = 0; - map[s1[j]]++; - map[s2[j]]--; - } - - for (var key in map) { - if (map[key] !== 0) { - dp[s1 + s2] = false; - return false; - } - } - - for (var i = 1; i < s1.length; i++) { - if ((helper(dp, s1.substr(0, i), s2.substr(0, i)) - && helper(dp, s1.substr(i), s2.substr(i))) || - (helper(dp, s1.substr(0, i), s2.substr(s2.length - i)) - && helper(dp, s1.substr(i), s2.substr(0, s2.length - i)))) { - dp[s1 + s2] = true; - return true; - } - } - - dp[s1 + s2] = false; - return false; - }; - - console.log(isScramble); - `), - new Template(` - var candy = function(ratings) { - var len = ratings.length; - var res = []; - var sum = 0; - for (var i = 0; i < len; i++) { - res.push((i !== 0 && ratings[i] > ratings[i - 1]) ? (res[i - 1] + 1) : 1); - } - for (var j = len - 1; j >= 0; j--) { - if (j !== len - 1 && ratings[j] > ratings[j + 1]) res[j] = Math.max(res[j], res[j + 1] + 1); - sum += res[j]; - } - return sum; - }; - - console.log(candy) - `), - new Template(` - var maxPoints = function(points) { - var max = 0; - var map = {}; - var localMax = 0; - var samePoint = 0; - var k = 0; - var len = points.length; - for (var i = 0; i < len; i++) { - map = {}; - localMax = 0; - samePoint = 1; - for (var j = i + 1; j < len; j++) { - if (points[i].x === points[j].x && points[i].y === points[j].y) { - samePoint++; - continue; - } - if (points[i].y === points[j].y) k = Number.MAX_SAFE_INTEGER; - else k = (points[i].x - points[j].x) / (points[i].y - points[j].y); - if (!map[k]) map[k] = 0; - map[k]++; - localMax = Math.max(localMax, map[k]); - } - localMax += samePoint; - max = Math.max(max, localMax); - } - return max; - }; - - console.log(maxPoints) - `), - new Template(` - var maximumGap = function(nums) { - var len = nums.length; - if (len < 2) return 0; - - var max = Math.max(...nums); - var min = Math.min(...nums); - if (max === min) return 0; - - var minBuckets = Array(len - 1).fill(Number.MAX_SAFE_INTEGER); - var maxBuckets = Array(len - 1).fill(Number.MIN_SAFE_INTEGER); - var gap = Math.ceil((max - min) / (len - 1)); - var index = 0; - for (var i = 0; i < len; i++) { - if (nums[i] === min || nums[i] === max) continue; - index = Math.floor((nums[i] - min) / gap); - minBuckets[index] = Math.min(minBuckets[index], nums[i]); - maxBuckets[index] = Math.max(maxBuckets[index], nums[i]); - } - - var maxGap = Number.MIN_SAFE_INTEGER; - var preVal = min; - for (var j = 0; j < len - 1; j++) { - if (minBuckets[j] === Number.MAX_SAFE_INTEGER && maxBuckets[j] === Number.MIN_SAFE_INTEGER) continue; - maxGap = Math.max(maxGap, minBuckets[j] - preVal); - preVal = maxBuckets[j]; - } - maxGap = Math.max(maxGap, max - preVal); - - return maxGap; - }; - - console.log(maximumGap); - `), - new Template(` - var LRUCache = function(capacity) { - this.capacity = capacity; - this.length = 0; - this.map = {}; - this.head = null; - this.tail = null; - }; - - LRUCache.prototype.get = function(key) { - var node = this.map[key]; - if (node) { - this.remove(node); - this.insert(node.key, node.val); - return node.val; - } else { - return -1; - } - }; - - LRUCache.prototype.put = function(key, value) { - if (this.map[key]) { - this.remove(this.map[key]); - this.insert(key, value); - } else { - if (this.length === this.capacity) { - this.remove(this.head); - this.insert(key, value); - } else { - this.insert(key, value); - this.length++; - } - } - }; - - /** - * Your LRUCache object will be instantiated and called as such: - * var obj = Object.create(LRUCache).createNew(capacity) - * var param_1 = obj.get(key) - * obj.put(key,value) - */ - - LRUCache.prototype.remove = function (node) { - var prev = node.prev; - var next = node.next; - if (next) next.prev = prev; - if (prev) prev.next = next; - if (this.head === node) this.head = next; - if (this.tail === node) this.tail = prev; - delete this.map[node.key]; - }; - - LRUCache.prototype.insert = function (key, val) { - var node = new List(key, val); - if (!this.tail) { - this.tail = node; - this.head = node; - } else { - this.tail.next = node; - node.prev = this.tail; - this.tail = node; - } - this.map[key] = node; - }; - - console.log(LRUCache); - `), - new Template(` - var isInterleave = function(s1, s2, s3) { - var dp = {}; - if (s3.length !== s1.length + s2.length) return false; - return helper(s1, s2, s3, 0, 0, 0, dp); - }; - - var helper = function (s1, s2, s3, i, j, k, dp) { - var res = false; - - if (k >= s3.length) return true; - if (dp['' + i + j + k] !== undefined) return dp['' + i + j + k]; - - if (s3[k] === s1[i] && s3[k] === s2[j]) { - res = helper(s1, s2, s3, i + 1, j, k + 1, dp) || helper(s1, s2, s3, i, j + 1, k + 1, dp); - } else if (s3[k] === s1[i]) { - res = helper(s1, s2, s3, i + 1, j, k + 1, dp); - } else if (s3[k] === s2[j]) { - res = helper(s1, s2, s3, i, j + 1, k + 1, dp); - } - - dp['' + i + j + k] = res; - - return res; - }; - - console.log(isInterleave); - `), - new Template(` - var solveNQueens = function(n) { - var res = []; - if (n === 1 || n >= 4) dfs(res, [], n, 0); - return res; - }; - - var dfs = function (res, points, n, index) { - for (var i = index; i < n; i++) { - if (points.length !== i) return; - for (var j = 0; j < n; j++) { - if (isValid(points, [i, j])) { - points.push([i, j]); - dfs(res, points, n, i + 1); - if (points.length === n) res.push(buildRes(points)); - points.pop(); - } - } - } - }; - - var buildRes = function (points) { - var res = []; - var n = points.length; - for (var i = 0; i < n; i++) { - res[i] = ''; - for (var j = 0; j < n; j++) { - res[i] += (points[i][1] === j ? 'Q' : '.'); - } - } - return res; - }; - - var isValid = function (oldPoints, newPoint) { - var len = oldPoints.length; - for (var i = 0; i < len; i++) { - if (oldPoints[i][0] === newPoint[0] || oldPoints[i][1] === newPoint[1]) return false; - if (Math.abs((oldPoints[i][0] - newPoint[0]) / (oldPoints[i][1] - newPoint[1])) === 1) return false; - } - return true; - }; - - console.log(solveNQueens); - `), -]; - -/** - * Adds dead code to blocks. - * - * - Adds fake predicates. - * - Adds fake code from various samples. - */ -export default class DeadCode extends Transform { - made: number; - - compareObjectName: string; - gen = this.getGenerator("randomized"); - - constructor(o) { - super(o, ObfuscateOrder.DeadCode); - - this.made = 0; - } - - match(object: Node, parents: Node[]) { - return ( - isFunction(object) && - isBlock(object.body) && - !object.$multiTransformSkip && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object: Node, parents: Node[]) { - if (!ComputeProbabilityMap(this.options.deadCode)) { - return; - } - - // Hard-coded limit of 100 Dead Code insertions - this.made++; - if (this.made > 100) { - return; - } - - return () => { - var body = getBlockBody(object); - - // Do not place code before Import statements or 'use strict' directives - var safeOffset = 0; - for (var node of body) { - if (node.type === "ImportDeclaration" || node.directive) safeOffset++; - else break; - } - - var index = getRandomInteger(safeOffset, body.length); - - if (!this.compareObjectName) { - this.compareObjectName = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || object, - VariableDeclaration( - VariableDeclarator( - this.compareObjectName, - new Template(`Object["create"](null)`).single().expression - ) - ) - ); - } - - var name = this.getPlaceholder(); - var variableDeclaration = VariableDeclaration( - VariableDeclarator( - name, - BinaryExpression( - "in", - Literal(this.gen.generate()), - Identifier(this.compareObjectName) - ) - ) - ); - - var template: Template; - do { - template = choice(templates); - } while (this.options.es5 && template.templates[0].includes("async")); - - var nodes = template.compile(); - - if (chance(50)) { - nodes.unshift( - ExpressionStatement( - AssignmentExpression( - "=", - MemberExpression( - Identifier(this.compareObjectName), - Literal(this.gen.generate()), - true - ), - Literal(this.gen.generate()) - ) - ) - ); - } - - var ifStatement = IfStatement(Identifier(name), nodes, null); - - body.splice(index, 0, ifStatement); - - prepend(object, variableDeclaration); - }; - } -} diff --git a/src_old/transforms/dispatcher.ts b/src_old/transforms/dispatcher.ts deleted file mode 100644 index b237777..0000000 --- a/src_old/transforms/dispatcher.ts +++ /dev/null @@ -1,640 +0,0 @@ -import { walk } from "../traverse"; -import { - ArrayExpression, - AssignmentExpression, - BinaryExpression, - CallExpression, - ExpressionStatement, - FunctionDeclaration, - FunctionExpression, - Identifier, - IfStatement, - Literal, - Node, - Location, - MemberExpression, - ObjectExpression, - Property, - ReturnStatement, - VariableDeclaration, - SequenceExpression, - NewExpression, - UnaryExpression, - BlockStatement, - LogicalExpression, - ThisExpression, - VariableDeclarator, - RestElement, -} from "../util/gen"; -import { getIdentifierInfo } from "../util/identifiers"; -import { - deleteDirect, - getBlockBody, - getVarContext, - isVarContext, - prepend, - append, - computeFunctionLength, - isFunction, -} from "../util/insert"; -import Transform from "./transform"; -import { isInsideType } from "../util/compare"; -import { choice, shuffle } from "../util/random"; -import { ComputeProbabilityMap } from "../probability"; -import { predictableFunctionTag, reservedIdentifiers } from "../constants"; -import { ObfuscateOrder } from "../order"; -import Template from "../templates/template"; -import { FunctionLengthTemplate } from "../templates/functionLength"; -import { ObjectDefineProperty } from "../templates/globals"; -import { getLexicalScope } from "../util/scope"; -import { isJSConfuserVar } from "../util/guard"; - -/** - * A Dispatcher processes function calls. All the function declarations are brought into a dictionary. - * - * ```js - * var param1; - * function dispatcher(key){ - * var fns = { - * 'fn1': function(){ - * var [arg1] = [param1]; - * console.log(arg1); - * } - * } - * return fns[key](); - * }; - * param1 = "Hello World"; - * dispatcher('fn1'); // > "Hello World" - * ``` - * - * Can break code with: - * - * 1. testing function equality, - * 2. using `arguments.callee`, - * 3. using `this` - */ -export default class Dispatcher extends Transform { - // Debug mode preserves function names - isDebug = false; - count: number; - - functionLengthName: string; - - constructor(o) { - super(o, ObfuscateOrder.Dispatcher); - - this.count = 0; - } - - apply(tree: Node): void { - super.apply(tree); - - if (this.options.preserveFunctionLength && this.functionLengthName) { - prepend( - tree, - FunctionLengthTemplate.single({ - name: this.functionLengthName, - ObjectDefineProperty: this.createInitVariable(ObjectDefineProperty, [ - tree, - ]), - }) - ); - } - } - - match(object: Node, parents: Node[]) { - if (isInsideType("AwaitExpression", object, parents)) { - return false; - } - - return ( - isVarContext(object) && - object.type !== "ArrowFunctionExpression" && - !object.$multiTransformSkip && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (ComputeProbabilityMap(this.options.dispatcher, (mode) => mode)) { - if (object.type != "Program" && object.body.type != "BlockStatement") { - return; - } - - // Map of FunctionDeclarations - var functionDeclarations: { [name: string]: Location } = - Object.create(null); - - // Array of Identifier nodes - var identifiers: Location[] = []; - var illegalFnNames: Set = new Set(); - - // New Names for Functions - var newFnNames: { [name: string]: string } = Object.create(null); // [old name]: randomized name - - var context = isVarContext(object) - ? object - : getVarContext(object, parents); - - var lexicalScope = isFunction(context) ? context.body : context; - - walk(object, parents, (o: Node, p: Node[]) => { - if (object == o) { - // Fix 1 - return; - } - - var c = getVarContext(o, p); - if (o.type == "FunctionDeclaration") { - c = getVarContext(p[0], p.slice(1)); - } - - if (context === c) { - if (o.type == "FunctionDeclaration" && o.id.name) { - var name = o.id.name; - - if ( - o.$requiresEval || - o.async || - o.generator || - p.find( - (x) => x.$multiTransformSkip || x.type == "MethodDefinition" - ) || - o.body.type != "BlockStatement" - ) { - illegalFnNames.add(name); - return; - } - - // Must defined in the same block as the current function being scanned - // Solves 'let' and 'class' declaration issue - var ls = getLexicalScope(o, p); - if (ls !== lexicalScope) { - illegalFnNames.add(name); - return; - } - - // If dupe, no routing - if (functionDeclarations[name]) { - illegalFnNames.add(name); - return; - } - - walk(o, p, (oo, pp) => { - if ( - (oo.type == "Identifier" && oo.name == "arguments") || - oo.type == "ThisExpression" || - oo.type == "Super" - ) { - if (getVarContext(oo, pp) === o) { - illegalFnNames.add(name); - return "EXIT"; - } - } - - // Avoid functions with function expressions as they have a different scope - if ( - (oo.type === "FunctionExpression" || - oo.type === "ArrowFunctionExpression") && - pp.find((x) => x == o.params) - ) { - illegalFnNames.add(name); - return "EXIT"; - } - }); - - functionDeclarations[name] = [o, p]; - } - } - - if (o.type == "Identifier") { - if (reservedIdentifiers.has(o.name)) { - return; - } - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced) { - return; - } - if (isJSConfuserVar(p)) { - illegalFnNames.add(o.name); - } - if (info.spec.isDefined) { - if (info.isFunctionDeclaration) { - if ( - p[0].id && - (!functionDeclarations[p[0].id.name] || - functionDeclarations[p[0].id.name][0] !== p[0]) - ) { - illegalFnNames.add(o.name); - } - } else { - illegalFnNames.add(o.name); - } - } else if (info.spec.isModified) { - illegalFnNames.add(o.name); - } else { - identifiers.push([o, p]); - } - } - }); - - illegalFnNames.forEach((name) => { - delete functionDeclarations[name]; - }); - - // map original name->new game - var gen = this.getGenerator(); - Object.keys(functionDeclarations).forEach((name) => { - newFnNames[name] = this.isDebug - ? "_dispatcher_" + this.count + "_" + name - : gen.generate(); - }); - // set containing new name - var set = new Set(Object.keys(newFnNames)); - - // Only make a dispatcher function if it caught any functions - if (set.size > 0) { - if (!this.functionLengthName) { - this.functionLengthName = this.getPlaceholder(); - } - - var payloadArg = - this.getPlaceholder() + "_dispatcher_" + this.count + "_payload"; - - var dispatcherFnName = - this.getPlaceholder() + - "_dispatcher_" + - this.count + - predictableFunctionTag; - - this.log(dispatcherFnName, set); - this.count++; - - var expectedGet = gen.generate(); - var expectedClearArgs = gen.generate(); - var expectedNew = gen.generate(); - - var returnProp = gen.generate(); - var newReturnMemberName = gen.generate(); - - var shuffledKeys = shuffle(Object.keys(functionDeclarations)); - var mapName = this.getPlaceholder(); - - var cacheName = this.getPlaceholder(); - - // creating the dispatcher function - // 1. create function map - var map = VariableDeclaration( - VariableDeclarator( - mapName, - ObjectExpression( - shuffledKeys.map((name) => { - var [def, defParents] = functionDeclarations[name]; - var body = getBlockBody(def.body); - - var functionExpression: Node = { - ...def, - expression: false, - type: "FunctionExpression", - id: null, - params: [], - [predictableFunctionTag]: true, - }; - this.addComment(functionExpression, name); - - if (def.params.length > 0) { - const fixParam = (param: Node) => { - return param; - }; - - var variableDeclaration = VariableDeclaration( - VariableDeclarator( - { - type: "ArrayPattern", - elements: def.params.map(fixParam), - }, - Identifier(payloadArg) - ) - ); - - prepend(def.body, variableDeclaration); - } - - // For logging purposes - var signature = - name + - "(" + - def.params.map((x) => x.name || "<>").join(",") + - ")"; - this.log("Added", signature); - - // delete ref in block - if (defParents.length) { - deleteDirect(def, defParents[0]); - } - - this.addComment(functionExpression, signature); - return Property( - Literal(newFnNames[name]), - functionExpression, - false - ); - }) - ) - ) - ); - - var getterArgName = this.getPlaceholder(); - - var x = this.getPlaceholder(); - var y = this.getPlaceholder(); - var z = this.getPlaceholder(); - - function getAccessor() { - return MemberExpression(Identifier(mapName), Identifier(x), true); - } - - // 2. define it - var fn = FunctionDeclaration( - dispatcherFnName, - [Identifier(x), Identifier(y), Identifier(z)], - [ - // Define map of callable functions - map, - - // Set returning variable to undefined - VariableDeclaration(VariableDeclarator(returnProp)), - - // Arg to clear the payload - IfStatement( - BinaryExpression( - "==", - Identifier(y), - Literal(expectedClearArgs) - ), - [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(payloadArg), - ArrayExpression([]) - ) - ), - ], - null - ), - - VariableDeclaration( - VariableDeclarator( - Identifier("lengths"), - ObjectExpression( - !this.options.preserveFunctionLength - ? [] - : shuffledKeys - .map((name) => { - var [def, defParents] = functionDeclarations[name]; - - return { - key: newFnNames[name], - value: computeFunctionLength(def.params), - }; - }) - .filter((item) => item.value !== 0) - .map((item) => - Property(Literal(item.key), Literal(item.value)) - ) - ) - ) - ), - - new Template(` - function makeFn${predictableFunctionTag}(){ - var fn = function(...args){ - ${payloadArg} = args; - return ${mapName}[${x}].call(this) - }, a = lengths[${x}] - - ${ - this.options.preserveFunctionLength - ? `if(a){ - return ${this.functionLengthName}(fn, a) - }` - : "" - } - - return fn - } - `).single(), - - // Arg to get a function reference - IfStatement( - BinaryExpression("==", Identifier(y), Literal(expectedGet)), - [ - // Getter flag: return the function object - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(returnProp), - LogicalExpression( - "||", - MemberExpression( - Identifier(cacheName), - Identifier(x), - true - ), - AssignmentExpression( - "=", - MemberExpression( - Identifier(cacheName), - Identifier(x), - true - ), - CallExpression( - Identifier(`makeFn${predictableFunctionTag}`), - [] - ) - ) - ) - ) - ), - ], - [ - // Call the function, return result - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(returnProp), - CallExpression(getAccessor(), []) - ) - ), - ] - ), - - // Check how the function was invoked (new () vs ()) - IfStatement( - BinaryExpression("==", Identifier(z), Literal(expectedNew)), - [ - // Wrap in object - ReturnStatement( - ObjectExpression([ - Property( - Identifier(newReturnMemberName), - Identifier(returnProp), - false - ), - ]) - ), - ], - [ - // Return raw result - ReturnStatement(Identifier(returnProp)), - ] - ), - ] - ); - - append(object, fn); - - if (payloadArg) { - prepend( - object, - VariableDeclaration( - VariableDeclarator(payloadArg, ArrayExpression([])) - ) - ); - } - - identifiers.forEach(([o, p]) => { - if (o.type != "Identifier") { - return; - } - - var newName = newFnNames[o.name]; - if (!newName || typeof newName !== "string") { - return; - } - - if (!functionDeclarations[o.name]) { - this.error(new Error("newName, missing function declaration")); - } - - var info = getIdentifierInfo(o, p); - if ( - info.isFunctionCall && - p[0].type == "CallExpression" && - p[0].callee === o - ) { - // Invoking call expression: `a();` - - if (o.name == dispatcherFnName) { - return; - } - - this.log( - `${o.name}(${p[0].arguments - .map((_) => "<>") - .join(",")}) -> ${dispatcherFnName}('${newName}')` - ); - - var assignmentExpressions: Node[] = []; - var dispatcherArgs: Node[] = [Literal(newName)]; - - if (p[0].arguments.length) { - assignmentExpressions = [ - AssignmentExpression( - "=", - Identifier(payloadArg), - ArrayExpression(p[0].arguments) - ), - ]; - } else { - dispatcherArgs.push(Literal(expectedClearArgs)); - } - - var type = choice(["CallExpression", "NewExpression"]); - var callExpression = null; - - switch (type) { - case "CallExpression": - callExpression = CallExpression( - Identifier(dispatcherFnName), - dispatcherArgs - ); - break; - - case "NewExpression": - if (dispatcherArgs.length == 1) { - dispatcherArgs.push(Identifier("undefined")); - } - callExpression = MemberExpression( - NewExpression(Identifier(dispatcherFnName), [ - ...dispatcherArgs, - Literal(expectedNew), - ]), - Identifier(newReturnMemberName), - false - ); - break; - } - - this.addComment( - callExpression, - "Calling " + - o.name + - "(" + - p[0].arguments.map((x) => x.name).join(", ") + - ")" - ); - - var expr: Node = assignmentExpressions.length - ? SequenceExpression([...assignmentExpressions, callExpression]) - : callExpression; - - // Replace the parent call expression - this.replace(p[0], expr); - } else { - // Non-invoking reference: `a` - - if (info.spec.isDefined) { - if (info.isFunctionDeclaration) { - this.log( - "Skipped getter " + o.name + " (function declaration)" - ); - } else { - this.log("Skipped getter " + o.name + " (defined)"); - } - return; - } - if (info.spec.isModified) { - this.log("Skipped getter " + o.name + " (modified)"); - return; - } - - this.log( - `(getter) ${o.name} -> ${dispatcherFnName}('${newName}')` - ); - this.replace( - o, - CallExpression(Identifier(dispatcherFnName), [ - Literal(newName), - Literal(expectedGet), - ]) - ); - } - }); - - prepend( - object, - VariableDeclaration( - VariableDeclarator( - Identifier(cacheName), - new Template(`Object.create(null)`).single().expression - ) - ) - ); - } - } - }; - } -} diff --git a/src_old/transforms/es5/antiClass.ts b/src_old/transforms/es5/antiClass.ts deleted file mode 100644 index 6698b16..0000000 --- a/src_old/transforms/es5/antiClass.ts +++ /dev/null @@ -1,276 +0,0 @@ -import Transform from "../transform"; -import Template from "../../templates/template"; -import { walk } from "../../traverse"; -import { - AssignmentExpression, - CallExpression, - ExpressionStatement, - FunctionExpression, - Identifier, - MemberExpression, - Node, - ReturnStatement, - SpreadElement, - ThisExpression, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { ok } from "assert"; - -export default class AntiClass extends Transform { - constructor(o) { - super(o); - } - - match(o, p) { - return o.type == "ClassDeclaration" || o.type == "ClassExpression"; - } - - transform(object, parents) { - return () => { - var body = object.body; - if (body.type !== "ClassBody") { - return; - } - if (!Array.isArray(body.body)) { - return; - } - - var isDeclaration = object.type == "ClassDeclaration"; - - var virtualName = "virtual" + this.getPlaceholder(); - - var staticBody: Node[] = []; - var virtualBody: Node[] = []; - var superName; - var thisName = "this" + this.getPlaceholder(); - - // self this - virtualBody.push(new Template(`var ${thisName} = this;`).single()); - virtualBody.push( - new Template(`${thisName}["constructor"] = null;`).single() - ); - var superArguments; - var superBody = []; - - if (object.superClass) { - superName = "super" + this.getPlaceholder(); - } - - var virtualDescriptorsName = this.getPlaceholder(); - var staticDescriptorsName = this.getPlaceholder(); - - // getters/setters - virtualBody.push( - new Template( - `var ${virtualDescriptorsName} = {getters: {}, setters: {}}` - ).single() - ); - - // getters/setters - staticBody.push( - new Template( - `var ${staticDescriptorsName} = {getters: {}, setters: {}}` - ).single() - ); - - body.body.forEach((methodDefinition) => { - if (!methodDefinition.key) { - return; - } - var isStatic = methodDefinition.static; - - var key = MemberExpression( - isStatic ? Identifier(virtualName) : ThisExpression(), - methodDefinition.key, - methodDefinition.computed - ); - var value = methodDefinition.value; - - var pushingTo = isStatic ? staticBody : virtualBody; - - if (superName && value.type == "FunctionExpression") { - var first = value.body.body[0]; - if ( - first.type == "ExpressionStatement" && - first.expression.type == "CallExpression" - ) { - if (first.expression.callee.type == "Super") { - /// super(...args) - superArguments = first.expression.arguments; - value.body.body.shift(); - } else if ( - // F(super(...args)) - first.expression.arguments[0] && - first.expression.arguments[0].type === "CallExpression" && - first.expression.arguments[0].callee.type === "Super" - ) { - superArguments = first.expression.arguments[0].arguments; - first.expression.arguments[0] = Identifier("undefined"); - } - } - - walk( - value.body, - [value, methodDefinition, body.body, body, object, ...parents], - (o, p) => { - if (o.type == "Super") { - this.replace(o, Identifier(superName)); - } - } - ); - } - - // Support class fields - if (methodDefinition.type === "PropertyDefinition") { - var assignmentExpression = AssignmentExpression( - "=", - key, - value || Identifier("undefined") - ); - - pushingTo.push(ExpressionStatement(assignmentExpression)); - } else if ( - methodDefinition.kind == "constructor" || - methodDefinition.kind == "method" - ) { - pushingTo.push( - ExpressionStatement(AssignmentExpression("=", key, value)) - ); - } else if ( - methodDefinition.kind == "get" || - methodDefinition.kind == "set" - ) { - var id = Identifier( - methodDefinition.kind == "get" ? "getters" : "setters" - ); - var type = MemberExpression( - Identifier( - isStatic ? staticDescriptorsName : virtualDescriptorsName - ), - id, - false - ); - - var assignmentExpression = AssignmentExpression( - "=", - - MemberExpression( - type, - methodDefinition.key, - methodDefinition.computed - ), - value - ); - - pushingTo.push(ExpressionStatement(assignmentExpression)); - } else { - console.log(methodDefinition); - throw new Error("Unsupported method definition"); - } - }); - - virtualBody.push( - new Template(` - [...Object.keys(${virtualDescriptorsName}.getters), ...Object.keys(${virtualDescriptorsName}.setters)].forEach(key=>{ - - if( !${thisName}.hasOwnProperty(key) ) { - var getter = ${virtualDescriptorsName}.getters[key]; - var setter = ${virtualDescriptorsName}.setters[key]; - Object.defineProperty(${thisName}, key, { - get: getter, - set: setter, - configurable: true - }) - } - - }) - - `).single() - ); - - staticBody.push( - new Template(` - [...Object.keys(${staticDescriptorsName}.getters), ...Object.keys(${staticDescriptorsName}.setters)].forEach(key=>{ - - if( !${virtualName}.hasOwnProperty(key) ) { - var getter = ${staticDescriptorsName}.getters[key]; - var setter = ${staticDescriptorsName}.setters[key]; - Object.defineProperty(${virtualName}, key, { - get: getter, - set: setter, - configurable: true - }) - } - - }) - - `).single() - ); - - if (superName) { - ok(superArguments, "Failed to find super() arguments"); - - // save the super state - virtualBody.unshift( - new Template( - ` - Object.keys(this).forEach(key=>{ - var descriptor = Object.getOwnPropertyDescriptor(this, key); - if ( descriptor) { - Object.defineProperty(${superName}, key, descriptor) - } else { - ${superName}[key] = this[key]; - } - })` - ).single() - ); - - virtualBody.unshift( - ExpressionStatement( - CallExpression( - MemberExpression(object.superClass, Identifier("call"), false), - [ThisExpression(), ...superArguments] - ) - ) - ); - - virtualBody.unshift(new Template(`var ${superName} = {}`).single()); - } - - virtualBody.push( - new Template( - `if(!this["constructor"]){this["constructor"] = ()=>{}};` - ).single() - ); - if (object.id && object.id.name) { - virtualBody.push( - new Template(`Object.defineProperty(this["constructor"], 'name', { - writable: true, - configurable: true, - value: '${object.id.name}' - });`).single() - ); - } - - virtualBody.push( - new Template(`this["constructor"](...arguments)`).single() - ); - - var virtualFunction = FunctionExpression([], virtualBody); - - var completeBody = [ - VariableDeclaration(VariableDeclarator(virtualName, virtualFunction)), - ...staticBody, - ReturnStatement(Identifier(virtualName)), - ]; - - var expr: Node = CallExpression(FunctionExpression([], completeBody), []); - if (isDeclaration) { - expr = VariableDeclaration(VariableDeclarator(object.id, expr)); - } - - this.replace(object, expr); - }; - } -} diff --git a/src_old/transforms/es5/antiDestructuring.ts b/src_old/transforms/es5/antiDestructuring.ts deleted file mode 100644 index d4953bd..0000000 --- a/src_old/transforms/es5/antiDestructuring.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { ok } from "assert"; -import Template from "../../templates/template"; -import { getBlock, walk } from "../../traverse"; -import { - ArrayExpression, - ArrayPattern, - AssignmentExpression, - BinaryExpression, - BlockStatement, - CallExpression, - ConditionalExpression, - Identifier, - Literal, - MemberExpression, - Node, - ReturnStatement, - SequenceExpression, - ThisExpression, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { - getBlockBody, - prepend, - clone, - getIndexDirect, -} from "../../util/insert"; -import Transform from "../transform"; - -/** - * Removes destructuring from function parameters. - * - * ``` - * // input - * function({property}){ - * } - * - * // output - * function(){ - * var [{property}] = arguments; - * } - * - * // input - * var fn = ({property})=>{}; - * - * // output - * var fn = (_)=>{ - * var [{property}] = [_]; - * } - * ``` - */ -class AntiDestructuringParameters extends Transform { - constructor(o) { - super(o); - } - - match(object: Node, parents: Node[]) { - return (object.param || object.params) && object.body; - } - - transform(object: Node, parents: Node[]) { - return () => { - if (object.param) { - // Catch clause - if (object.param.type != "Identifier") { - var catchName = this.getPlaceholder(); - var cloned = { ...object.param }; - - object.param = Identifier(catchName); - - getBlockBody(object.body).unshift( - VariableDeclaration([ - VariableDeclarator(cloned, Identifier(catchName)), - ]) - ); - } - - return; - } - - // For function parameters - var isDestructed = false; - var parameters = object.params; - - walk(parameters, [object, ...parents], (o, p) => { - if ( - o.type == "ArrayPattern" || - o.type == "ObjectPattern" || - o.type == "AssignmentPattern" || - o.type == "RestElement" - ) { - isDestructed = true; - return "EXIT"; - } - }); - - if (isDestructed) { - if (object.expression) { - object.body = BlockStatement([ReturnStatement({ ...object.body })]); - } else if (object.body.type != "BlockStatement") { - object.body = BlockStatement([{ ...object.body }]); - } - - var arrayPattern = ArrayPattern(parameters); - - // `arguments` is not allowed in arrow functions - if ( - object.type == "ArrowFunctionExpression" && - !object.params.find((x) => x.type == "RestElement") - ) { - // new names - - object.params = Array(object.params.length) - .fill(0) - .map(() => Identifier(this.getPlaceholder())); - - getBlockBody(object.body).unshift( - VariableDeclaration( - VariableDeclarator(arrayPattern, ArrayExpression(object.params)) - ) - ); - } else { - object.params = []; - - getBlockBody(object.body).unshift( - VariableDeclaration( - VariableDeclarator( - arrayPattern, - new Template(`Array.prototype.slice.call(arguments)`).single() - .expression - ) - ) - ); - - if (object.type == "ArrowFunctionExpression") { - object.type = "FunctionExpression"; - object.expression = false; - - this.replace( - object, - CallExpression( - MemberExpression(clone(object), Identifier("bind"), false), - [ThisExpression()] - ) - ); - } - } - } - }; - } -} - -/** - * Removes destructuring so the script can work in ES5 environments. - */ -export default class AntiDestructuring extends Transform { - constructor(o) { - super(o); - - this.before.push(new AntiDestructuringParameters(o)); - } - - match(object: Node, parents: Node[]) { - return ( - object.type == "AssignmentExpression" || - object.type == "VariableDeclarator" - ); - } - - transform(object: Node, parents: Node[]) { - var block = getBlock(object, parents); - - var body = getBlockBody(block); - - var temp = this.getPlaceholder(); - - var exprs = []; - var names: Set = new Set(); - var operator = "="; - - var id = null; // The object being set - var extracting = null; // The object being extracted from - if (object.type == "AssignmentExpression") { - id = object.left; - extracting = object.right; - operator = object.operator; - } else if (object.type == "VariableDeclarator") { - id = object.id; - extracting = object.init; - } else { - ok(false); - } - - var should = false; - walk(id, [], (o, p) => { - if (o.type && o.type.includes("Pattern")) { - should = true; - } - }); - - if (should) { - prepend( - block, - VariableDeclaration([VariableDeclarator(Identifier(temp))]) - ); - - const recursive = (x: Node, realm: Node) => { - realm = clone(realm); - - if (x.type == "Identifier") { - exprs.push(AssignmentExpression(operator, clone(x), realm)); - - names.add(x.name); - } else if (x.type == "MemberExpression") { - exprs.push(AssignmentExpression(operator, clone(x), realm)); - } else if (x.type == "ObjectPattern") { - x.properties.forEach((property) => { - recursive( - property.value, - MemberExpression(realm, property.key, property.computed) - ); - }); - } else if (x.type == "ArrayPattern") { - x.elements.forEach((element, i) => { - if (element) { - if (element.type == "RestElement") { - if (i != x.elements.length - 1) { - this.error( - new Error( - "Uncaught SyntaxError: Rest element must be last element" - ) - ); - } - recursive( - element.argument, - CallExpression( - MemberExpression(realm, Identifier("slice"), false), - [Literal(i)] - ) - ); - } else { - recursive(element, MemberExpression(realm, Literal(i), true)); - } - } - }); - } else if (x.type == "AssignmentPattern") { - var condition = ConditionalExpression( - BinaryExpression("===", realm, Identifier("undefined")), - x.right, - realm - ); - recursive(x.left, condition); - } else { - throw new Error("unknown type: " + x.type); - } - }; - - recursive(id, Identifier(temp)); - - return () => { - var seq = SequenceExpression([ - AssignmentExpression( - "=", - Identifier(temp), - clone(extracting) || Identifier("undefined") - ), - ...exprs, - ]); - - if (object.type == "VariableDeclarator") { - var i = getIndexDirect(object, parents[0]); - - var extra = Array.from(names).map((x) => { - return { - type: "VariableDeclarator", - id: Identifier(x), - init: null, - }; - }); - - extra.push({ - type: "VariableDeclarator", - id: Identifier(this.getPlaceholder()), - init: seq, - }); - - parents[0].splice(i, 1, ...extra); - } else { - this.replace(object, seq); - } - }; - } - } -} diff --git a/src_old/transforms/es5/antiES6Object.ts b/src_old/transforms/es5/antiES6Object.ts deleted file mode 100644 index 37becbf..0000000 --- a/src_old/transforms/es5/antiES6Object.ts +++ /dev/null @@ -1,267 +0,0 @@ -import Transform from "../transform"; -import { - Node, - Literal, - Identifier, - CallExpression, - ObjectExpression, - ArrayExpression, - Property, - MemberExpression, - SpreadElement, -} from "../../util/gen"; -import { prepend } from "../../util/insert"; -import { getBlock } from "../../traverse"; -import Template from "../../templates/template"; - -var HelperFunctions = new Template( - ` - function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } - -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } - -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; } - ` -); - -class AntiSpread extends Transform { - helper: boolean; - - constructor(o) { - super(o); - - this.helper = false; - } - - match(object: Node, parents: Node[]) { - return ( - object.type == "ObjectExpression" || object.type == "ArrayExpression" - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (object.type == "ArrayExpression") { - var spreadIndex = object.elements.findIndex( - (x) => x.type == "SpreadElement" - ); - if (spreadIndex !== -1) { - var after = object.elements.slice(spreadIndex); - var groups = []; - - after.forEach((element) => { - if (element.type === "SpreadElement") { - groups.push(element.argument); - } else { - if (!groups.length) { - groups.push(ArrayExpression([])); - } - if (groups[groups.length - 1].type != "ArrayExpression") { - groups.push(ArrayExpression([])); - } - groups[groups.length - 1].elements.push(element); - } - }); - - this.replace( - object, - CallExpression( - MemberExpression( - ArrayExpression(object.elements.slice(0, spreadIndex)), - Identifier("concat"), - false - ), - groups.map((group) => { - // [].concat(arguments) -> [].concat(Array.prototype.slice.call(arguments)) - return CallExpression( - MemberExpression( - MemberExpression( - MemberExpression( - Identifier("Array"), - Identifier("prototype"), - false - ), - Identifier("slice"), - false - ), - Identifier("call"), - false - ), - [group] - ); - }) - ) - ); - } - } else if (object.type == "ObjectExpression") { - var spreadIndex; - while (true) { - spreadIndex = object.properties.findIndex( - (x) => x.type == "SpreadElement" - ); - if (spreadIndex === -1) { - break; - } - - // add helper functions only once - if (!this.helper) { - this.helper = true; - prepend(parents[parents.length - 1], ...HelperFunctions.compile()); - } - - var before = object.properties.slice(0, spreadIndex); - var after = object.properties.slice(spreadIndex + 1); - - var call = CallExpression(Identifier("_objectSpread"), [ - ObjectExpression(before), - object.properties[spreadIndex].argument, - ]); - - if (after.length) { - var newObject = ObjectExpression(after); - this.replace( - object, - CallExpression(Identifier("_objectSpread"), [call, newObject]) - ); - - object = newObject; - } else { - this.replace(object, call); - break; - } - } - } - }; - } -} - -export default class AntiES6Object extends Transform { - makerFn: string; - - constructor(o) { - super(o); - - this.makerFn = null; - - this.after.push(new AntiSpread(o)); - } - - match(object: Node, parents: Node[]) { - return object.type == "ObjectExpression"; - } - - transform(object: Node, parents: Node[]) { - return () => { - var block = getBlock(object, parents); - var needsChanging = false; - - object.properties.forEach((property) => { - if (property.type == "SpreadElement") { - needsChanging = true; - return; - } - - // AntiShorthand - object.shorthand = false; - - if (!property.key) { - this.error(new Error("Property missing key")); - } - - if (!["Literal", "Identifier"].includes(property.key.type)) { - property.computed = true; - } - - if (property.computed && property.key.type == "Literal") { - property.computed = false; - } - - if (property.kind != "init" || property.method || property.computed) { - needsChanging = true; - } - }); - - if (needsChanging) { - if (!this.makerFn) { - this.makerFn = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || block, - new Template(` - function {name}(base, computedProps, getters, setters){ - - for ( var i = 0; i < computedProps.length; i++ ) { - base[computedProps[i][0]] = computedProps[i][1]; - } - - var keys=Object.create(null); - Object.keys(getters).forEach(key=>(keys[key] = 1)) - Object.keys(setters).forEach(key=>(keys[key] = 1)) - - Object.keys(keys).forEach(key=>{ - Object.defineProperty(base, key, { - set: setters[key], - get: getters[key], - configurable: true - }); - }) - return base; - } - `).single({ name: this.makerFn }) - ); - } - - // {a: 1} Es5 compliant properties - var baseProps = []; - // {[a]: 1} -> Computed props to array [a, 1] - var computedProps = []; - // {get a(){}} -> Property descriptors - var getters = ObjectExpression([]); - var setters = ObjectExpression([]); - - object.properties.forEach((prop) => { - var key = prop.key; - if (!key) { - return; - } - - if (key.type == "Identifier" && !prop.computed) { - key = Literal(key.name); - } - - if (prop.computed) { - var array = [prop.key, prop.value]; - - computedProps.push(ArrayExpression(array)); - } else if (prop.kind == "get" || prop.kind == "set") { - if (prop.kind == "get") { - getters.properties.push(Property(key, prop.value)); - } else { - setters.properties.push(Property(key, prop.value)); - } - } else { - prop.method = false; - - baseProps.push(prop); - } - }); - - if ( - setters.properties.length || - getters.properties.length || - computedProps.length - ) { - this.objectAssign( - object, - CallExpression(Identifier(this.makerFn), [ - ObjectExpression(baseProps), - ArrayExpression(computedProps), - getters, - setters, - ]) - ); - } - } - }; - } -} diff --git a/src_old/transforms/es5/antiSpreadOperator.ts b/src_old/transforms/es5/antiSpreadOperator.ts deleted file mode 100644 index 11a04c4..0000000 --- a/src_old/transforms/es5/antiSpreadOperator.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - ArrayExpression, - AssignmentExpression, - CallExpression, - Identifier, - MemberExpression, - Node, - ThisExpression, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { prepend } from "../../util/insert"; -import Transform from "../transform"; - -// fn(...args) -> fn.apply(this, [...args]) -export default class AntiSpreadOperator extends Transform { - constructor(o) { - super(o); - } - - match(object, parents) { - return ( - object.type == "CallExpression" && - object.arguments.find((x) => x.type == "SpreadElement") - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - var ref; - - if (object.callee.type == "MemberExpression") { - ref = this.getPlaceholder(); - - object.callee.object = AssignmentExpression("=", Identifier(ref), { - ...object.callee.object, - }); - prepend( - parents[parents.length - 1], - VariableDeclaration(VariableDeclarator(ref)) - ); - } - - this.replace( - object, - CallExpression( - MemberExpression({ ...object.callee }, Identifier("apply"), false), - [ - ref ? Identifier(ref) : ThisExpression(), - ArrayExpression(object.arguments), - ] - ) - ); - }; - } -} diff --git a/src_old/transforms/es5/antiTemplate.ts b/src_old/transforms/es5/antiTemplate.ts deleted file mode 100644 index 399d6f1..0000000 --- a/src_old/transforms/es5/antiTemplate.ts +++ /dev/null @@ -1,98 +0,0 @@ -import Template from "../../templates/template"; -import { - ArrayExpression, - BinaryExpression, - CallExpression, - Identifier, - Literal, -} from "../../util/gen"; -import { prepend } from "../../util/insert"; -import Transform from "../transform"; - -export default class AntiTemplate extends Transform { - makerFn: string; - - constructor(o) { - super(o); - } - - match(object, parents) { - return ( - object.type == "TemplateLiteral" || - object.type == "TaggedTemplateExpression" - ); - } - - transform(object, parents) { - return () => { - if (object.type == "TemplateLiteral") { - if ( - parents[0].type == "TaggedTemplateExpression" && - parents[0].quasi == object - ) { - return; - } - - if (object.quasis.length == 1 && object.expressions.length == 0) { - this.replace(object, Literal(object.quasis[0].value.cooked)); - } else { - var binaryExpression = null; - - object.quasis.forEach((q, i) => { - var expr = object.expressions[i]; - var str = Literal(q.value.cooked); - - if (!binaryExpression) { - binaryExpression = BinaryExpression("+", str, expr); - } else { - if (expr) { - binaryExpression.right = BinaryExpression( - "+", - binaryExpression.right, - BinaryExpression("+", str, expr) - ); - } else { - binaryExpression.right = BinaryExpression( - "+", - binaryExpression.right, - str - ); - } - } - }); - - this.replace(object, binaryExpression); - } - } else if (object.type == "TaggedTemplateExpression") { - var literal = object.quasi; - - if (!this.makerFn) { - this.makerFn = "es6_template" + this.getPlaceholder(); - - prepend( - parents[parents.length - 1], - new Template(` - function {name}(arr, raw){ - arr.raw = raw; - return arr; - } - `).single({ name: this.makerFn }) - ); - } - - this.replace( - object, - CallExpression(object.tag, [ - CallExpression(Identifier(this.makerFn), [ - ArrayExpression( - literal.quasis.map((x) => Literal(x.value.cooked)) - ), - ArrayExpression(literal.quasis.map((x) => Literal(x.value.raw))), - ]), - ...literal.expressions, - ]) - ); - } - }; - } -} diff --git a/src_old/transforms/es5/es5.ts b/src_old/transforms/es5/es5.ts deleted file mode 100644 index 9a56197..0000000 --- a/src_old/transforms/es5/es5.ts +++ /dev/null @@ -1,149 +0,0 @@ -import Transform from "../transform"; -import { - Node, - Literal, - Identifier, - MemberExpression, - BlockStatement, - ReturnStatement, - CallExpression, - ThisExpression, -} from "../../util/gen"; -import { clone, prepend } from "../../util/insert"; -import { isBlock, walk } from "../../traverse"; -import { ObfuscateOrder } from "../../order"; -import { ok } from "assert"; -import { reservedKeywords } from "../../constants"; -import AntiDestructuring from "./antiDestructuring"; -import AntiTemplate from "./antiTemplate"; -import AntiClass from "./antiClass"; -import AntiES6Object from "./antiES6Object"; -import AntiSpreadOperator from "./antiSpreadOperator"; -import { ES5Template } from "../../templates/es5"; - -/** - * `Const` and `Let` are not allowed in ES5. - */ -class AntiConstLet extends Transform { - constructor(o) { - super(o); - } - - match(object, parents) { - return object.type == "VariableDeclaration" && object.kind != "var"; - } - - transform(object) { - object.kind = "var"; - } -} - -/** - * Converts arrow functions - */ -export class AntiArrowFunction extends Transform { - constructor(o) { - super(o); - } - - match(object, parents) { - return object.type == "ArrowFunctionExpression"; - } - - transform(object, parents) { - return () => { - var usesThis = false; - - if (object.body.type != "BlockStatement" && object.expression) { - object.body = BlockStatement([ReturnStatement(clone(object.body))]); - object.expression = false; - } - - walk(object.body, [object, ...parents], (o, p) => { - if (p.filter((x) => isBlock(x))[0] == object.body) { - if ( - o.type == "ThisExpression" || - (o.type == "Identifier" && o.name == "this") - ) { - usesThis = true; - } - } - }); - - ok(object.body.type == "BlockStatement", "Should be a BlockStatement"); - ok(Array.isArray(object.body.body), "Body should be an array"); - ok( - !object.body.body.find((x) => Array.isArray(x)), - "All elements should be statements" - ); - - object.type = "FunctionExpression"; - object.expression = false; - - if (usesThis) { - this.objectAssign( - object, - CallExpression( - MemberExpression(clone(object), Identifier("bind"), false), - [ThisExpression()] - ) - ); - } - }; - } -} - -/** - * The ES5 options aims to convert ES6 and up features down to ES5-compatible code. - * - * The obfuscator regularly adds ES6 code (variable destructuring, spread element, etc.) - * This transformations goal is undo only these things. - */ -export default class ES5 extends Transform { - constructor(o) { - super(o, ObfuscateOrder.ES5); - - this.before.push(new AntiClass(o)); - this.before.push(new AntiTemplate(o)); - this.before.push(new AntiSpreadOperator(o)); - this.before.push(new AntiES6Object(o)); - this.before.push(new AntiArrowFunction(o)); - this.before.push(new AntiDestructuring(o)); - this.before.push(new AntiConstLet(o)); - } - - apply(tree: Node) { - super.apply(tree); - - var nodesToAdd = ES5Template.compile(); - prepend(tree, ...nodesToAdd); - } - - // FixedExpressions - match(object: Node, parents: Node[]) { - return !!object.type; - } - - transform(object: Node, parents: Node[]) { - return () => { - // Object.keyword -> Object["keyword"] - if (object.type == "MemberExpression") { - if (!object.computed && object.property.type == "Identifier") { - if (reservedKeywords.has(object.property.name)) { - object.property = Literal(object.property.name); - object.computed = true; - } - } - } - - // { keyword: ... } -> { "keyword": ... } - if (object.type == "Property") { - if (!object.computed && object.key.type == "Identifier") { - if (reservedKeywords.has(object.key.name)) { - object.key = Literal(object.key.name); - } - } - } - }; - } -} diff --git a/src_old/transforms/extraction/classExtraction.ts b/src_old/transforms/extraction/classExtraction.ts deleted file mode 100644 index e13bcc8..0000000 --- a/src_old/transforms/extraction/classExtraction.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { ok } from "assert"; -import { ExitCallback, getBlock, walk } from "../../traverse"; -import { - CallExpression, - FunctionDeclaration, - FunctionExpression, - Identifier, - Literal, - MemberExpression, - MethodDefinition, - Node, - ReturnStatement, - Super, - ThisExpression, -} from "../../util/gen"; -import { isStringLiteral } from "../../util/guard"; -import { isClass, prepend } from "../../util/insert"; -import { getLexicalScope } from "../../util/scope"; -import Transform from "../transform"; - -export default class ClassExtraction extends Transform { - constructor(o) { - super(o); - } - - match(object: Node, parents: Node[]): boolean { - return ( - object.type === "ClassDeclaration" || object.type === "ClassExpression" - ); - } - - extractKeyString(property: Node): string | null { - if (property.key.type === "Identifier" && !property.key.computed) { - return property.key.name; - } - - if (isStringLiteral(property.key)) { - return property.key.value; - } - - return null; - } - - transform(object: Node, parents: Node[]): void | ExitCallback { - return () => { - var classBody = object.body; - var className = object.id?.type === "Identifier" && object.id?.name; - - if (!className) className = this.getPlaceholder(); - - var lexicalScope = getLexicalScope(object, parents); - - var superMethodName: string; - - for (var methodDefinition of classBody.body) { - if ( - methodDefinition.type === "MethodDefinition" && - methodDefinition.value.type === "FunctionExpression" - ) { - // Don't change constructors calling super() - if (methodDefinition.kind === "constructor" && object.superClass) - continue; - - var functionExpression: Node = methodDefinition.value; - - var fnName = - className + - "_" + - methodDefinition.kind + - "_" + - this.extractKeyString(methodDefinition) || this.getPlaceholder(); - - walk( - functionExpression, - [methodDefinition, object, ...parents], - (o, p) => { - if (o.type === "Super") { - var classContext = p.find((node) => isClass(node)); - if (classContext !== object) return; - - return () => { - if (!superMethodName) { - superMethodName = - this.getGenerator("randomized").generate(); - } - - var memberExpression = p[0]; - if (memberExpression.type === "CallExpression") { - throw new Error("Failed to detect super() usage"); - } - ok(memberExpression.type === "MemberExpression"); - - var propertyArg = memberExpression.computed - ? memberExpression.property - : (ok(memberExpression.property.type === "Identifier"), - Literal(memberExpression.property.name)); - - var getSuperExpression = CallExpression( - MemberExpression( - ThisExpression(), - Literal(superMethodName), - true - ), - [propertyArg] - ); - - if (p[1].type === "CallExpression" && p[1].callee === p[0]) { - getSuperExpression = CallExpression( - MemberExpression( - getSuperExpression, - Literal("bind"), - true - ), - [ThisExpression()] - ); - } - - this.replace(p[0], getSuperExpression); - }; - } - } - ); - - var originalParams = functionExpression.params; - var originalBody = functionExpression.body.body; - - functionExpression.body.body = [ - ReturnStatement( - CallExpression( - MemberExpression(Identifier(fnName), Literal("apply"), true), - [ThisExpression(), Identifier("arguments")] - ) - ), - ]; - - functionExpression.params = []; - if (methodDefinition.kind === "set") { - functionExpression.params = [Identifier(this.getPlaceholder())]; - } - - prepend( - lexicalScope, - FunctionDeclaration(fnName, [...originalParams], [...originalBody]) - ); - } - } - - if (superMethodName) { - classBody.body.push( - MethodDefinition( - Literal(superMethodName), - FunctionExpression( - [Identifier("key")], - [ - ReturnStatement( - MemberExpression(Super(), Identifier("key"), true) - ), - ] - ), - "method", - false, - true - ) - ); - } - }; - } -} diff --git a/src_old/transforms/extraction/duplicateLiteralsRemoval.ts b/src_old/transforms/extraction/duplicateLiteralsRemoval.ts deleted file mode 100644 index 1a6fbd6..0000000 --- a/src_old/transforms/extraction/duplicateLiteralsRemoval.ts +++ /dev/null @@ -1,297 +0,0 @@ -import Transform from "../transform"; -import { - Identifier, - Literal, - VariableDeclaration, - Node, - ArrayExpression, - MemberExpression, - VariableDeclarator, - Location, - ReturnStatement, - CallExpression, - BinaryExpression, - FunctionDeclaration, - ConditionalExpression, -} from "../../util/gen"; -import { append, clone, prepend } from "../../util/insert"; -import { isDirective, isModuleSource, isPrimitive } from "../../util/compare"; -import { ObfuscateOrder } from "../../order"; -import { ComputeProbabilityMap } from "../../probability"; -import { ok } from "assert"; -import { chance, choice, getRandomInteger } from "../../util/random"; -import { getBlock } from "../../traverse"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { predictableFunctionTag } from "../../constants"; - -/** - * [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a variable name. - * - * - Potency Medium - * - Resilience Medium - * - Cost Medium - * - * ```js - * // Input - * var foo = "http://www.example.xyz"; - * bar("http://www.example.xyz"); - * - * // Output - * var a = "http://www.example.xyz"; - * var foo = a; - * bar(a); - * ``` - */ -export default class DuplicateLiteralsRemoval extends Transform { - // The array holding all the duplicate literals - arrayName: string; - // The array expression node to be inserted into the program - arrayExpression: Node; - - /** - * Literals in the array - */ - map: Map; - - /** - * Literals are saved here the first time they are seen. - */ - first: Map; - - /** - * Block -> { functionName, indexShift } - */ - functions: Map; - - constructor(o) { - super(o, ObfuscateOrder.DuplicateLiteralsRemoval); - - this.map = new Map(); - this.first = new Map(); - - this.functions = new Map(); - } - - apply(tree) { - super.apply(tree); - - if (this.arrayName && this.arrayExpression.elements.length > 0) { - // This function simply returns the array - var getArrayFn = this.getPlaceholder(); - append( - tree, - FunctionDeclaration( - getArrayFn, - [], - [ReturnStatement(this.arrayExpression)] - ) - ); - - // This variable holds the array - prepend( - tree, - VariableDeclaration( - VariableDeclarator( - this.arrayName, - CallExpression(Identifier(getArrayFn), []) - ) - ) - ); - - // Create all the functions needed - for (var blockNode of this.functions.keys()) { - var { functionName, indexShift } = this.functions.get(blockNode); - - var propertyNode: Node = BinaryExpression( - "-", - Identifier("index_param"), - Literal(indexShift) - ); - - var indexRangeInclusive = [ - 0 + indexShift - 1, - this.map.size + indexShift, - ]; - - // The function uses mangling to hide the index being accessed - var mangleCount = getRandomInteger(1, 5); - for (var i = 0; i < mangleCount; i++) { - var operator = choice([">", "<"]); - var compareValue = choice(indexRangeInclusive); - - var test = BinaryExpression( - operator, - Identifier("index_param"), - Literal(compareValue) - ); - - var alternate = BinaryExpression( - "-", - Identifier("index_param"), - Literal(getRandomInteger(-100, 100)) - ); - - var testValue = - (operator === ">" && compareValue === indexRangeInclusive[0]) || - (operator === "<" && compareValue === indexRangeInclusive[1]); - - propertyNode = ConditionalExpression( - test, - testValue ? propertyNode : alternate, - !testValue ? propertyNode : alternate - ); - } - - var returnArgument = MemberExpression( - Identifier(this.arrayName), - propertyNode, - true - ); - - prepend( - blockNode, - FunctionDeclaration( - functionName, - [Identifier("index_param")], - [ReturnStatement(returnArgument)] - ) - ); - } - } - } - - match(object: Node, parents: Node[]) { - return ( - isPrimitive(object) && - !isDirective(object, parents) && - !isModuleSource(object, parents) && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - /** - * Converts ordinary literal to go through a getter function. - * @param object - * @param parents - * @param index - */ - transformLiteral(object: Node, parents: Node[], index: number) { - var blockNode = choice(parents.filter((x) => this.functions.has(x))); - - // Create initial function if none exist - if (this.functions.size === 0) { - var root = parents[parents.length - 1]; - var rootFunctionName = this.getPlaceholder() + "_dLR_0"; - this.functions.set(root, { - functionName: rootFunctionName + predictableFunctionTag, - indexShift: getRandomInteger(-100, 100), - }); - - blockNode = root; - } - - // If no function here exist, possibly create new chained function - var block = getBlock(object, parents); - if (!this.functions.has(block) && chance(50 - this.functions.size)) { - var newFunctionName = - this.getPlaceholder() + - "_dLR_" + - this.functions.size + - predictableFunctionTag; - - this.functions.set(block, { - functionName: newFunctionName, - indexShift: getRandomInteger(-100, 100), - }); - - blockNode = block; - } - - // Derive the function to call from the selected blockNode - var { functionName, indexShift } = this.functions.get(blockNode); - - // Call the function given it's indexShift - var callExpression = CallExpression(Identifier(functionName), [ - Literal(index + indexShift), - ]); - - this.replaceIdentifierOrLiteral(object, callExpression, parents); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (object.type === "Identifier") { - var info = getIdentifierInfo(object, parents); - if (info.isLabel || info.spec.isDefined || info.spec.isModified) return; - } - if (object.regex) { - return; - } - - if (!ComputeProbabilityMap(this.options.duplicateLiteralsRemoval)) { - return; - } - - if ( - this.arrayName && - parents[0].object && - parents[0].object.name == this.arrayName - ) { - return; - } - - var stringValue; - if (object.type == "Literal") { - stringValue = typeof object.value + ":" + object.value; - if (object.value === null) { - stringValue = "null:null"; - } else { - // Skip empty strings - if (typeof object.value === "string" && !object.value) { - return; - } - } - } else if (object.type == "Identifier") { - stringValue = "identifier:" + object.name; - } else { - throw new Error("Unsupported primitive type: " + object.type); - } - - ok(stringValue); - - if (this.map.has(stringValue) || this.first.has(stringValue)) { - // Create the array if not already made - if (!this.arrayName) { - this.arrayName = this.getPlaceholder(); - this.arrayExpression = ArrayExpression([]); - } - - // Delete with first location - var firstLocation = this.first.get(stringValue); - if (firstLocation) { - var index = this.map.size; - - ok(!this.map.has(stringValue)); - this.map.set(stringValue, index); - this.first.delete(stringValue); - - var pushing = clone(object); - this.arrayExpression.elements.push(pushing); - - ok(this.arrayExpression.elements[index] === pushing); - - this.transformLiteral(firstLocation[0], firstLocation[1], index); - } - - var index = this.map.get(stringValue); - ok(typeof index === "number"); - - this.transformLiteral(object, parents, index); - return; - } - - // Save this, maybe a duplicate will be found. - this.first.set(stringValue, [object, parents]); - }; - } -} diff --git a/src_old/transforms/extraction/objectExtraction.ts b/src_old/transforms/extraction/objectExtraction.ts deleted file mode 100644 index 5d3bfba..0000000 --- a/src_old/transforms/extraction/objectExtraction.ts +++ /dev/null @@ -1,360 +0,0 @@ -import Transform from "../transform"; -import { walk } from "../../traverse"; -import { Node, Location, Identifier, VariableDeclarator } from "../../util/gen"; -import { getVarContext, isVarContext } from "../../util/insert"; -import { ObfuscateOrder } from "../../order"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { isValidIdentifier } from "../../util/compare"; -import { ComputeProbabilityMap } from "../../probability"; -import { ok } from "assert"; -import { isStringLiteral } from "../../util/guard"; - -/** - * Extracts keys out of an object if possible. - * ```js - * // Input - * var utils = { - * isString: x=>typeof x === "string", - * isBoolean: x=>typeof x === "boolean" - * } - * if ( utils.isString("Hello") ) { - * ... - * } - * - * // Output - * var utils_isString = x=>typeof x === "string"; - * var utils_isBoolean = x=>typeof x === "boolean" - * - * if ( utils_isString("Hello") ) { - * ... - * } - * ``` - */ -export default class ObjectExtraction extends Transform { - constructor(o) { - super(o, ObfuscateOrder.ObjectExtraction); - } - - match(object: Node, parents: Node[]) { - return isVarContext(object); - } - - transform(context: Node, contextParents: Node[]) { - // ObjectExpression Extractor - - return () => { - // First pass through to find the maps - var objectDefs: { [name: string]: Location } = Object.create(null); - var objectDefiningIdentifiers: { [name: string]: Location } = - Object.create(null); - - var illegal = new Set(); - - walk(context, contextParents, (object: Node, parents: Node[]) => { - if (object.type == "ObjectExpression") { - // this.log(object, parents); - if ( - parents[0].type == "VariableDeclarator" && - parents[0].init == object && - parents[0].id.type == "Identifier" - ) { - var name = parents[0].id.name; - if (name) { - if (getVarContext(object, parents) != context) { - illegal.add(name); - return; - } - if (!object.properties.length) { - illegal.add(name); - return; - } - - // duplicate name - if (objectDefiningIdentifiers[name]) { - illegal.add(name); - return; - } - - // check for computed properties - // Change String literals to non-computed - object.properties.forEach((prop) => { - if (prop.computed && isStringLiteral(prop.key)) { - prop.computed = false; - } - }); - - var nonInitOrComputed = object.properties.find( - (x) => x.kind !== "init" || x.computed - ); - - if (nonInitOrComputed) { - if (nonInitOrComputed.key) { - this.log( - name + - " has non-init/computed property: " + - nonInitOrComputed.key.name || nonInitOrComputed.key.value - ); - } else { - this.log( - name + " has spread-element or other type of property" - ); - } - - illegal.add(name); - return; - } else { - var illegalName = object.properties - .map((x) => - x.computed ? x.key.value : x.key.name || x.key.value - ) - .find((x) => !x || !isValidIdentifier(x)); - - if (illegalName) { - this.log( - name + " has an illegal property '" + illegalName + "'" - ); - illegal.add(name); - return; - } else { - var isIllegal = false; - walk(object, parents, (o, p) => { - if (o.type == "ThisExpression" || o.type == "Super") { - isIllegal = true; - return "EXIT"; - } - }); - if (isIllegal) { - illegal.add(name); - return; - } - - objectDefs[name] = [object, parents]; - objectDefiningIdentifiers[name] = [ - parents[0].id, - [...parents], - ]; - } - } - } - } - } - }); - - illegal.forEach((name) => { - delete objectDefs[name]; - delete objectDefiningIdentifiers[name]; - }); - - // this.log("object defs", objectDefs); - // huge map of changes - var objectDefChanges: { - [name: string]: { key: string; object: Node; parents: Node[] }[]; - } = {}; - - if (Object.keys(objectDefs).length) { - // A second pass through is only required when extracting object keys - - // Second pass through the exclude the dynamic map (counting keys, re-assigning) - walk(context, contextParents, (object: any, parents: Node[]) => { - if (object.type == "Identifier") { - var info = getIdentifierInfo(object, parents); - if (!info.spec.isReferenced) { - return; - } - var def = objectDefs[object.name]; - if (def) { - var isIllegal = false; - - if (info.spec.isDefined) { - if (objectDefiningIdentifiers[object.name][0] !== object) { - this.log(object.name, "you can't redefine the object"); - isIllegal = true; - } - } else { - var isMemberExpression = - parents[0].type == "MemberExpression" && - parents[0].object == object; - - if ( - (parents.find((x) => x.type == "AssignmentExpression") && - !isMemberExpression) || - parents.find( - (x) => x.type == "UnaryExpression" && x.operator == "delete" - ) - ) { - this.log(object.name, "you can't re-assign the object"); - - isIllegal = true; - } else if (isMemberExpression) { - var key = - parents[0].property.value || parents[0].property.name; - - if ( - parents[0].computed && - parents[0].property.type !== "Literal" - ) { - this.log( - object.name, - "object[expr] detected, only object['key'] is allowed" - ); - - isIllegal = true; - } else if ( - !parents[0].computed && - parents[0].property.type !== "Identifier" - ) { - this.log( - object.name, - "object. detected, only object.key is allowed" - ); - - isIllegal = true; - } else if ( - !key || - !def[0].properties.some( - (x) => (x.key.value || x.key.name) == key - ) - ) { - // check if initialized property - // not in initialized object. - this.log( - object.name, - "not in initialized object.", - def[0].properties, - key - ); - isIllegal = true; - } - - if (!isIllegal && key) { - // allowed. - // start the array if first time - if (!objectDefChanges[object.name]) { - objectDefChanges[object.name] = []; - } - // add to array - objectDefChanges[object.name].push({ - key: key, - object: object, - parents: parents, - }); - } - } else { - this.log( - object.name, - "you must access a property on the when referring to the identifier (accessors must be hard-coded literals), parent is " + - parents[0].type - ); - - isIllegal = true; - } - } - - if (isIllegal) { - // this is illegal, delete it from being moved and delete accessor changes from happening - this.log(object.name + " is illegal"); - delete objectDefs[object.name]; - delete objectDefChanges[object.name]; - } - } - } - }); - - Object.keys(objectDefs).forEach((name) => { - if ( - !ComputeProbabilityMap( - this.options.objectExtraction, - (x) => x, - name - ) - ) { - //continue; - return; - } - - var [object, parents] = objectDefs[name]; - var declarator = parents[0]; - var declaration = parents[2]; - - ok(declarator.type === "VariableDeclarator"); - ok(declaration.type === "VariableDeclaration"); - - var properties = object.properties; - // change the prop names while extracting - var newPropNames: { [key: string]: string } = {}; - - var variableDeclarators = []; - - properties.forEach((property: Node) => { - var keyName = property.key.name || property.key.value; - - var nn = name + "_" + keyName; - newPropNames[keyName] = nn; - - var v = property.value; - - variableDeclarators.push( - VariableDeclarator(nn, this.addComment(v, `${name}.${keyName}`)) - ); - }); - - declaration.declarations.splice( - declaration.declarations.indexOf(declarator), - 1, - ...variableDeclarators - ); - - // const can only be safely changed to let - if (declaration.kind === "const") { - declaration.kind = "let"; - } - - // update all identifiers that pointed to the old object - objectDefChanges[name] && - objectDefChanges[name].forEach((change) => { - if (!change.key) { - this.error(new Error("key is undefined")); - } - if (newPropNames[change.key]) { - var memberExpression = change.parents[0]; - if (memberExpression.type == "MemberExpression") { - this.replace( - memberExpression, - this.addComment( - Identifier(newPropNames[change.key]), - `Original Accessor: ${name}.${change.key}` - ) - ); - } else { - // Provide error with more information: - console.log(memberExpression); - this.error( - new Error( - `should be MemberExpression, found type=${memberExpression.type}` - ) - ); - } - } else { - console.log(objectDefChanges[name], newPropNames); - this.error( - new Error( - `"${change.key}" not found in [${Object.keys( - newPropNames - ).join(", ")}] while flattening ${name}.` - ) - ); - } - }); - - this.log( - `Extracted ${ - Object.keys(newPropNames).length - } properties from ${name}, affecting ${ - Object.keys(objectDefChanges[name] || {}).length - } line(s) of code.` - ); - }); - } - }; - } -} diff --git a/src_old/transforms/finalizer.ts b/src_old/transforms/finalizer.ts deleted file mode 100644 index c07c446..0000000 --- a/src_old/transforms/finalizer.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ObfuscateOrder } from "../order"; -import { ExitCallback } from "../traverse"; -import { Identifier, Node } from "../util/gen"; -import StringEncoding from "./string/stringEncoding"; -import Transform from "./transform"; - -/** - * The Finalizer is the last transformation before the code is ready to be generated. - * - * Hexadecimal numbers: - * - Convert integer literals into `Identifier` nodes with the name being a hexadecimal number - * - * BigInt support: - * - Convert BigInt literals into `Identifier` nodes with the name being the raw BigInt string value + "n" - * - * String Encoding: - * - Convert String literals into `Identifier` nodes with the name being a unicode escaped string - */ -export default class Finalizer extends Transform { - stringEncoding: StringEncoding; - - constructor(o) { - super(o, ObfuscateOrder.Finalizer); - - this.stringEncoding = new StringEncoding(o); - } - - isNumberLiteral(object: Node) { - return ( - object.type === "Literal" && - typeof object.value === "number" && - Math.floor(object.value) === object.value - ); - } - - isBigIntLiteral(object: Node) { - return object.type === "Literal" && typeof object.value === "bigint"; - } - - match(object, parents) { - return object.type === "Literal"; - } - - transform(object: Node, parents: Node[]): void | ExitCallback { - // Hexadecimal Numbers - if (this.options.hexadecimalNumbers && this.isNumberLiteral(object)) { - return () => { - // Technically, a Literal will never be negative because it's supposed to be inside a UnaryExpression with a "-" operator. - // This code handles it regardless - var isNegative = object.value < 0; - var hex = Math.abs(object.value).toString(16); - - var newStr = (isNegative ? "-" : "") + "0x" + hex; - - this.replace(object, Identifier(newStr)); - }; - } - - // BigInt support - if (this.isBigIntLiteral(object)) { - // https://github.com/MichaelXF/js-confuser/issues/79 - return () => { - // Use an Identifier with the raw string - this.replace(object, Identifier(object.raw)); - }; - } - - if ( - this.options.stringEncoding && - this.stringEncoding.match(object, parents) - ) { - return this.stringEncoding.transform(object, parents); - } - } -} diff --git a/src_old/transforms/flatten.ts b/src_old/transforms/flatten.ts deleted file mode 100644 index 99aa47f..0000000 --- a/src_old/transforms/flatten.ts +++ /dev/null @@ -1,557 +0,0 @@ -import { ok } from "assert"; -import { - noRenameVariablePrefix, - predictableFunctionTag, - reservedIdentifiers, -} from "../constants"; -import { ObfuscateOrder } from "../order"; -import { walk } from "../traverse"; -import { - Identifier, - ReturnStatement, - VariableDeclaration, - VariableDeclarator, - CallExpression, - MemberExpression, - ExpressionStatement, - AssignmentExpression, - Node, - BlockStatement, - ArrayPattern, - FunctionExpression, - ObjectExpression, - Property, - Literal, - AwaitExpression, - FunctionDeclaration, - SpreadElement, - UnaryExpression, - RestElement, -} from "../util/gen"; -import { getIdentifierInfo } from "../util/identifiers"; -import { - getBlockBody, - prepend, - clone, - getDefiningContext, - computeFunctionLength, -} from "../util/insert"; -import { shuffle } from "../util/random"; -import Transform from "./transform"; -import { FunctionLengthTemplate } from "../templates/functionLength"; -import { ObjectDefineProperty } from "../templates/globals"; - -/** - * Flatten takes functions and isolates them from their original scope, and brings it to the top level of the program. - * - * An additional `flatObject` parameter is passed in, giving access to the original scoped variables. - * - * The `flatObject` uses `get` and `set` properties to allow easy an AST transformation: - * - * ```js - * // Input - * function myFunction(myParam){ - * modified = true; - * if(reference) { - * - * } - * ... - * console.log(myParam); - * } - * - * // Output - * function myFunction_flat([myParam], flatObject){ - * flatObject["set_modified"] = true; - * if(flatObject["get_reference"]) { - * - * } - * ... - * console.log(myParam) - * } - * - * function myFunction(){ - * var flatObject = { - * set set_modified(v) { modified = v } - * get get_reference() { return reference } - * } - * return myFunction_flat([...arguments], flatObject) - * } - * ``` - * - * Flatten is used to make functions eligible for the RGF transformation. - * - * - `myFunction_flat` is now eligible because it does not rely on outside scoped variables - */ -export default class Flatten extends Transform { - isDebug = false; - - definedNames: Map>; - - // Array of FunctionDeclaration nodes - flattenedFns: Node[]; - gen: ReturnType; - - functionLengthName: string; - - constructor(o) { - super(o, ObfuscateOrder.Flatten); - - this.definedNames = new Map(); - this.flattenedFns = []; - this.gen = this.getGenerator("mangled"); - - if (this.isDebug) { - console.warn("Flatten debug mode"); - } - } - - apply(tree) { - super.apply(tree); - - if (this.flattenedFns.length) { - prepend(tree, ...this.flattenedFns); - } - } - - match(object: Node, parents: Node[]) { - return ( - (object.type == "FunctionDeclaration" || - object.type === "FunctionExpression") && - object.body.type == "BlockStatement" && - !object.$requiresEval && - !object.generator && - !object.params.find((x) => x.type !== "Identifier") - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (parents[0]) { - // Don't change class methods - if ( - parents[0].type === "MethodDefinition" && - parents[0].value === object - ) { - return; - } - - // Don't change getter/setter methods - if ( - parents[0].type === "Property" && - parents[0].value === object && - (parents[0].kind !== "init" || parents[0].method) - ) { - return; - } - } - - ok( - object.type === "FunctionDeclaration" || - object.type === "FunctionExpression" - ); - - // The name is purely for debugging purposes - var currentFnName = - object.type === "FunctionDeclaration" - ? object.id?.name - : parents[0]?.type === "VariableDeclarator" && - parents[0].id?.type === "Identifier" && - parents[0].id?.name; - - if (parents[0]?.type === "Property" && parents[0]?.key) { - currentFnName = currentFnName || String(parents[0]?.key?.name); - } - - if (!currentFnName) currentFnName = "unnamed"; - - var definedMap = new Map>(); - - var illegal = new Set(); - var isIllegal = false; - - var identifierNodes: [ - Node, - Node[], - ReturnType - ][] = []; - - walk(object, parents, (o, p) => { - if ( - (o.type === "Identifier" && o.name === "arguments") || - (o.type === "UnaryExpression" && o.operator === "delete") || - o.type == "ThisExpression" || - o.type == "Super" || - o.type == "MetaProperty" - ) { - isIllegal = true; - return "EXIT"; - } - - if ( - o.type == "Identifier" && - o !== object.id && - !this.options.globalVariables.has(o.name) && - !reservedIdentifiers.has(o.name) - ) { - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced) { - return; - } - - if ( - info.spec.isExported || - o.name.startsWith(noRenameVariablePrefix) - ) { - illegal.add(o.name); - - return; - } - - if (info.spec.isDefined) { - var definingContext = getDefiningContext(o, p); - - if (!definedMap.has(definingContext)) { - definedMap.set(definingContext, new Set([o.name])); - } else { - definedMap.get(definingContext).add(o.name); - } - return; - } - - var isDefined = p.find( - (x) => definedMap.has(x) && definedMap.get(x).has(o.name) - ); - - if (!isDefined) { - identifierNodes.push([o, p, info]); - } - } - - if (o.type == "TryStatement") { - isIllegal = true; - return "EXIT"; - } - }); - - if (isIllegal) { - return; - } - if (illegal.size) { - return; - } - - var newFnName = - this.getPlaceholder() + - "_flat_" + - currentFnName + - predictableFunctionTag; - var flatObjectName = this.getPlaceholder() + "_flat_object"; - - const getFlatObjectMember = (propertyName: string) => { - return MemberExpression( - Identifier(flatObjectName), - Literal(propertyName), - true - ); - }; - - var getterPropNames: { [identifierName: string]: string } = - Object.create(null); - var setterPropNames: { [identifierName: string]: string } = - Object.create(null); - var typeofPropNames: { [identifierName: string]: string } = - Object.create(null); - var callPropNames: { [identifierName: string]: string } = - Object.create(null); - - for (var [o, p, info] of identifierNodes) { - var identifierName: string = o.name; - if ( - p.find( - (x) => definedMap.has(x) && definedMap.get(x).has(identifierName) - ) - ) - continue; - - ok(!info.spec.isDefined); - - var type = info.spec.isModified ? "setter" : "getter"; - - switch (type) { - case "setter": - var setterPropName = setterPropNames[identifierName]; - if (typeof setterPropName === "undefined") { - // No getter function made yet, make it (Try to re-use getter name if available) - setterPropName = - getterPropNames[identifierName] || - (this.isDebug ? "set_" + identifierName : this.gen.generate()); - setterPropNames[identifierName] = setterPropName; - } - - // If an update expression, ensure a getter function is also available. Ex: a++ - if (p[0].type === "UpdateExpression") { - getterPropNames[identifierName] = setterPropName; - } else { - // If assignment on member expression, ensure a getter function is also available: Ex. myObject.property = ... - var assignmentIndex = p.findIndex( - (x) => x.type === "AssignmentExpression" - ); - if ( - assignmentIndex !== -1 && - p[assignmentIndex].left.type !== "Identifier" - ) { - getterPropNames[identifierName] = setterPropName; - } - } - - // calls flatObject.set_identifier_value(newValue) - this.replace(o, getFlatObjectMember(setterPropName)); - break; - - case "getter": - var getterPropName = getterPropNames[identifierName]; - if (typeof getterPropName === "undefined") { - // No getter function made yet, make it (Try to re-use setter name if available) - getterPropName = - setterPropNames[identifierName] || - (this.isDebug ? "get_" + identifierName : this.gen.generate()); - getterPropNames[identifierName] = getterPropName; - } - - // Typeof expression check - if ( - p[0].type === "UnaryExpression" && - p[0].operator === "typeof" && - p[0].argument === o - ) { - var typeofPropName = typeofPropNames[identifierName]; - if (typeof typeofPropName === "undefined") { - // No typeof getter function made yet, make it (Don't re-use getter/setter names) - typeofPropName = this.isDebug - ? "get_typeof_" + identifierName - : this.gen.generate(); - typeofPropNames[identifierName] = typeofPropName; - } - - // Replace the entire unary expression not just the identifier node - // calls flatObject.get_typeof_identifier() - this.replace(p[0], getFlatObjectMember(typeofPropName)); - break; - } - - // Bound call-expression check - if (p[0].type === "CallExpression" && p[0].callee === o) { - var callPropName = callPropNames[identifierName]; - if (typeof callPropName === "undefined") { - callPropName = this.isDebug - ? "call_" + identifierName - : this.gen.generate(); - callPropNames[identifierName] = callPropName; - } - - // Replace the entire call expression not just the identifier node - // calls flatObject.call_identifier(...arguments) - this.replace( - p[0], - CallExpression( - getFlatObjectMember(callPropName), - p[0].arguments - ) - ); - break; - } - - // calls flatObject.get_identifier_value() - this.replace(o, getFlatObjectMember(getterPropName)); - break; - } - } - - // Create the getter and setter functions - var flatObjectProperties: Node[] = []; - - // Getter functions - for (var identifierName in getterPropNames) { - var getterPropName = getterPropNames[identifierName]; - - flatObjectProperties.push( - Property( - Literal(getterPropName), - FunctionExpression( - [], - [ReturnStatement(Identifier(identifierName))] - ), - true, - "get" - ) - ); - } - - // Get typeof functions - for (var identifierName in typeofPropNames) { - var typeofPropName = typeofPropNames[identifierName]; - - flatObjectProperties.push( - Property( - Literal(typeofPropName), - FunctionExpression( - [], - [ - ReturnStatement( - UnaryExpression("typeof", Identifier(identifierName)) - ), - ] - ), - true, - "get" - ) - ); - } - - // Call functions - for (var identifierName in callPropNames) { - var callPropName = callPropNames[identifierName]; - var argumentsName = this.getPlaceholder(); - flatObjectProperties.push( - Property( - Literal(callPropName), - FunctionExpression( - [RestElement(Identifier(argumentsName))], - [ - ReturnStatement( - CallExpression(Identifier(identifierName), [ - SpreadElement(Identifier(argumentsName)), - ]) - ), - ] - ), - true - ) - ); - } - - // Setter functions - for (var identifierName in setterPropNames) { - var setterPropName = setterPropNames[identifierName]; - var newValueParameterName = this.getPlaceholder(); - - flatObjectProperties.push( - Property( - Literal(setterPropName), - FunctionExpression( - [Identifier(newValueParameterName)], - [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(identifierName), - Identifier(newValueParameterName) - ) - ), - ] - ), - true, - "set" - ) - ); - } - - if (!this.isDebug) { - shuffle(flatObjectProperties); - } - - var newBody = getBlockBody(object.body); - - // Remove 'use strict' directive - if (newBody.length > 0 && newBody[0].directive) { - newBody.shift(); - } - - var newFunctionDeclaration = FunctionDeclaration( - newFnName, - [ArrayPattern(clone(object.params)), Identifier(flatObjectName)], - newBody - ); - - newFunctionDeclaration.async = !!object.async; - newFunctionDeclaration.generator = false; - - this.flattenedFns.push(newFunctionDeclaration); - - var argumentsName = this.getPlaceholder(); - - // newFn.call([...arguments], flatObject) - var callExpression = CallExpression(Identifier(newFnName), [ - Identifier(argumentsName), - Identifier(flatObjectName), - ]); - - var newObjectBody: Node[] = [ - // var flatObject = { get(), set() }; - VariableDeclaration([ - VariableDeclarator( - flatObjectName, - ObjectExpression(flatObjectProperties) - ), - ]), - - ReturnStatement( - newFunctionDeclaration.async - ? AwaitExpression(callExpression) - : callExpression - ), - ]; - - object.body = BlockStatement(newObjectBody); - - // Preserve function.length property - var originalFunctionLength = computeFunctionLength(object.params); - - object.params = [RestElement(Identifier(argumentsName))]; - - if (this.options.preserveFunctionLength && originalFunctionLength !== 0) { - if (!this.functionLengthName) { - this.functionLengthName = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || object, - FunctionLengthTemplate.single({ - name: this.functionLengthName, - ObjectDefineProperty: this.createInitVariable( - ObjectDefineProperty, - parents - ), - }) - ); - } - - if (object.type === "FunctionDeclaration") { - var body = parents[0]; - if (Array.isArray(body)) { - var index = body.indexOf(object); - - body.splice( - index + 1, - 0, - ExpressionStatement( - CallExpression(Identifier(this.functionLengthName), [ - Identifier(object.id.name), - Literal(originalFunctionLength), - ]) - ) - ); - } - } else { - ok(object.type === "FunctionExpression"); - this.replace( - object, - CallExpression(Identifier(this.functionLengthName), [ - { ...object }, - Literal(originalFunctionLength), - ]) - ); - } - } - }; - } -} diff --git a/src_old/transforms/identifier/globalAnalysis.ts b/src_old/transforms/identifier/globalAnalysis.ts deleted file mode 100644 index 06a9b38..0000000 --- a/src_old/transforms/identifier/globalAnalysis.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { reservedKeywords } from "../../constants"; -import { Location, Node } from "../../util/gen"; -import { isJSConfuserVar } from "../../util/guard"; -import { getIdentifierInfo } from "../../util/identifiers"; -import Transform from "../transform"; - -/** - * Global Analysis is responsible for finding all the global variables used in the code. - * - * A 'global variable' is one that is: - * - Referenced - * - Never defined or overridden - */ -export default class GlobalAnalysis extends Transform { - notGlobals: Set; - globals: { [name: string]: Location[] }; - - constructor(o) { - super(o); - - this.globals = Object.create(null); - this.notGlobals = new Set(); - } - - match(object: Node, parents: Node[]) { - return object.type == "Identifier" && !reservedKeywords.has(object.name); - } - - transform(object: Node, parents: Node[]) { - // no touching `import()` or `import x from ...` - var importIndex = parents.findIndex( - (x) => x.type == "ImportExpression" || x.type == "ImportDeclaration" - ); - if (importIndex !== -1) { - if ( - parents[importIndex].source === (parents[importIndex - 1] || object) - ) { - return; - } - } - - var info = getIdentifierInfo(object, parents); - if (!info.spec.isReferenced) { - return; - } - - if (isJSConfuserVar(parents)) { - delete this.globals[object.name]; - this.notGlobals.add(object.name); - return; - } - - // Cannot be defined or overridden - if (info.spec.isDefined || info.spec.isModified) { - if (info.spec.isModified) { - // Only direct overwrites should be considered - // Changing object properties is allowed - if ( - parents[0].type === "MemberExpression" && - parents[0].object === object - ) { - return; - } - } - - delete this.globals[object.name]; - this.notGlobals.add(object.name); - return; - } - - // Add to globals - if (!this.notGlobals.has(object.name)) { - if (!this.globals[object.name]) { - this.globals[object.name] = []; - } - - this.globals[object.name].push([object, parents]); - } - - var assignmentIndex = parents.findIndex( - (x) => x.type == "AssignmentExpression" - ); - var updateIndex = parents.findIndex((x) => x.type == "UpdateExpression"); - - if ( - (assignmentIndex != -1 && - parents[assignmentIndex].left === - (parents[assignmentIndex - 1] || object)) || - updateIndex != -1 - ) { - var memberIndex = parents.findIndex((x) => x.type == "MemberExpression"); - if ( - memberIndex == -1 || - memberIndex > (assignmentIndex == -1 ? assignmentIndex : updateIndex) - ) { - delete this.globals[object.name]; - - this.notGlobals.add(object.name); - } - } - } -} diff --git a/src_old/transforms/identifier/globalConcealing.ts b/src_old/transforms/identifier/globalConcealing.ts deleted file mode 100644 index f04b4fd..0000000 --- a/src_old/transforms/identifier/globalConcealing.ts +++ /dev/null @@ -1,297 +0,0 @@ -import Template from "../../templates/template"; -import Transform from "../transform"; -import { ObfuscateOrder } from "../../order"; -import { - Node, - Location, - CallExpression, - Identifier, - Literal, - FunctionDeclaration, - ReturnStatement, - MemberExpression, - SwitchStatement, - SwitchCase, - LogicalExpression, - VariableDeclarator, - FunctionExpression, - ExpressionStatement, - AssignmentExpression, - VariableDeclaration, - BreakStatement, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; -import { chance, getRandomInteger } from "../../util/random"; -import { - predictableFunctionTag, - reservedIdentifiers, - variableFunctionName, -} from "../../constants"; -import { ComputeProbabilityMap } from "../../probability"; -import GlobalAnalysis from "./globalAnalysis"; -import { createGetGlobalTemplate } from "../../templates/bufferToString"; -import { isJSConfuserVar } from "../../util/guard"; - -/** - * Global Concealing hides global variables being accessed. - * - * - Any variable that is not defined is considered "global" - */ -export default class GlobalConcealing extends Transform { - globalAnalysis: GlobalAnalysis; - ignoreGlobals = new Set([ - "require", - "__dirname", - "eval", - variableFunctionName, - ]); - - constructor(o) { - super(o, ObfuscateOrder.GlobalConcealing); - - this.globalAnalysis = new GlobalAnalysis(o); - this.before.push(this.globalAnalysis); - } - - match(object: Node, parents: Node[]) { - return object.type == "Program"; - } - - transform(object: Node, parents: Node[]) { - return () => { - var globals: { [name: string]: Location[] } = this.globalAnalysis.globals; - this.globalAnalysis.notGlobals.forEach((del) => { - delete globals[del]; - }); - - for (var varName of this.ignoreGlobals) { - delete globals[varName]; - } - - reservedIdentifiers.forEach((x) => { - delete globals[x]; - }); - - Object.keys(globals).forEach((x) => { - if (this.globalAnalysis.globals[x].length < 1) { - delete globals[x]; - } else if ( - !ComputeProbabilityMap(this.options.globalConcealing, (x) => x, x) - ) { - delete globals[x]; - } - }); - - if (Object.keys(globals).length > 0) { - var usedStates = new Set(); - - // Make getter function - - // holds "window" or "global" - var globalVar = this.getPlaceholder(); - - var getGlobalVariableFnName = - this.getPlaceholder() + predictableFunctionTag; - - // Returns global variable or fall backs to `this` - var getGlobalVariableFn = createGetGlobalTemplate( - this, - object, - parents - ).compile({ - getGlobalFnName: getGlobalVariableFnName, - }); - - // 2. Replace old accessors - var globalFn = this.getPlaceholder() + predictableFunctionTag; - - var newNames: { [globalVarName: string]: number } = Object.create(null); - - Object.keys(globals).forEach((name) => { - var locations: Location[] = globals[name]; - var state: number; - do { - state = getRandomInteger(-1000, 1000 + usedStates.size); - } while (usedStates.has(state)); - usedStates.add(state); - - newNames[name] = state; - - locations.forEach(([node, p]) => { - if (p.find((x) => x.$multiTransformSkip)) { - return; - } - - var newExpression = CallExpression(Identifier(globalFn), [ - Literal(state), - ]); - - this.replace(node, newExpression); - - if ( - this.options.lock?.tamperProtection && - this.lockTransform.nativeFunctionName - ) { - var isMemberExpression = false; - var nameAndPropertyPath = [name]; - var callExpression: Node; - - var index = 0; - do { - if (p[index].type === "CallExpression") { - callExpression = p[index]; - break; - } - - var memberExpression = p[index]; - if (memberExpression.type !== "MemberExpression") return; - var property = memberExpression.property; - var stringValue = - property.type === "Literal" - ? property.value - : memberExpression.computed - ? null - : property.type === "Identifier" - ? property.name - : null; - - if (!stringValue) return; - - isMemberExpression = true; - nameAndPropertyPath.push(stringValue); - index++; - } while (index < p.length); - - if ( - !this.lockTransform.shouldTransformNativeFunction( - nameAndPropertyPath - ) - ) - return; - - if (callExpression && callExpression.type === "CallExpression") { - if (isMemberExpression) { - callExpression.callee = CallExpression( - Identifier(this.lockTransform.nativeFunctionName), - [ - callExpression.callee.object, - callExpression.callee.computed - ? callExpression.callee.property - : Literal( - callExpression.callee.property.name || - callExpression.callee.property.value - ), - ] - ); - } else { - callExpression.callee = CallExpression( - Identifier(this.lockTransform.nativeFunctionName), - [{ ...callExpression.callee }] - ); - } - } - } - }); - }); - - // Adds all global variables to the switch statement - this.options.globalVariables.forEach((name) => { - if (!newNames[name]) { - var state; - do { - state = getRandomInteger( - 0, - 1000 + usedStates.size + this.options.globalVariables.size * 100 - ); - } while (usedStates.has(state)); - usedStates.add(state); - - newNames[name] = state; - } - }); - - var indexParamName = this.getPlaceholder(); - var returnName = this.getPlaceholder(); - - var functionDeclaration = FunctionDeclaration( - globalFn, - [Identifier(indexParamName)], - [ - VariableDeclaration(VariableDeclarator(returnName)), - SwitchStatement( - Identifier(indexParamName), - Object.keys(newNames).map((name) => { - var code = newNames[name]; - var body: Node[] = [ - ReturnStatement( - MemberExpression(Identifier(globalVar), Literal(name), true) - ), - ]; - if (chance(50)) { - body = [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(returnName), - LogicalExpression( - "||", - Literal(name), - MemberExpression( - Identifier(globalVar), - Literal(name), - true - ) - ) - ) - ), - BreakStatement(), - ]; - } - - return SwitchCase(Literal(code), body); - }) - ), - ReturnStatement( - MemberExpression( - Identifier(globalVar), - Identifier(returnName), - true - ) - ), - ] - ); - - var tempVar = this.getPlaceholder(); - - var variableDeclaration = new Template(` - var ${globalVar}; - `).single(); - - variableDeclaration.declarations.push( - VariableDeclarator( - tempVar, - CallExpression( - MemberExpression( - FunctionExpression( - [], - [ - ...getGlobalVariableFn, - new Template( - `return ${globalVar} = ${getGlobalVariableFnName}["call"](this)` - ).single(), - ] - ), - Literal("call"), - true - ), - [] - ) - ) - ); - - prepend(object, variableDeclaration); - append(object, functionDeclaration); - } - }; - } -} diff --git a/src_old/transforms/identifier/movedDeclarations.ts b/src_old/transforms/identifier/movedDeclarations.ts deleted file mode 100644 index a86208b..0000000 --- a/src_old/transforms/identifier/movedDeclarations.ts +++ /dev/null @@ -1,153 +0,0 @@ -import Transform from "../transform"; -import { isBlock } from "../../traverse"; -import { - ExpressionStatement, - AssignmentExpression, - Identifier, - Node, - VariableDeclarator, - AssignmentPattern, -} from "../../util/gen"; -import { - isForInitialize, - isFunction, - isStrictModeFunction, - prepend, -} from "../../util/insert"; -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { choice } from "../../util/random"; -import { predictableFunctionTag } from "../../constants"; -import { isIndependent, isMoveable } from "../../util/compare"; -import { getFunctionParameters } from "../../util/identifiers"; -import { isLexicalScope } from "../../util/scope"; - -/** - * Defines all the names at the top of every lexical block. - */ -export default class MovedDeclarations extends Transform { - constructor(o) { - super(o, ObfuscateOrder.MovedDeclarations); - } - - match(object, parents) { - return ( - object.type === "VariableDeclaration" && - object.kind === "var" && - object.declarations.length === 1 && - object.declarations[0].id.type === "Identifier" - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - var forInitializeType = isForInitialize(object, parents); - - // Get the block statement or Program node - var blockIndex = parents.findIndex((x) => isLexicalScope(x)); - var block = parents[blockIndex]; - var body: Node[] = - block.type === "SwitchCase" ? block.consequent : block.body; - ok(Array.isArray(body), "No body array found."); - - var bodyObject = parents[blockIndex - 2] || object; - var index = body.indexOf(bodyObject); - - var varName = object.declarations[0].id.name; - ok(typeof varName === "string"); - - var predictableFunctionIndex = parents.findIndex((x) => isFunction(x)); - var predictableFunction = parents[predictableFunctionIndex]; - - var deleteStatement = false; - - if ( - predictableFunction && - ((predictableFunction.id && - predictableFunction.id.name.includes(predictableFunctionTag)) || - predictableFunction[predictableFunctionTag]) && // Must have predictableFunctionTag in the name, or on object - predictableFunction[predictableFunctionTag] !== false && // If === false, the function is deemed not predictable - predictableFunction.params.length < 1000 && // Max 1,000 parameters - !predictableFunction.params.find((x) => x.type === "RestElement") && // Cannot add parameters after spread operator - !( - ["Property", "MethodDefinition"].includes( - parents[predictableFunctionIndex + 1]?.type - ) && parents[predictableFunctionIndex + 1]?.kind !== "init" - ) && // Preserve getter/setter methods - !getFunctionParameters( - predictableFunction, - parents.slice(predictableFunctionIndex) - ).find((entry) => entry[0].name === varName) // Ensure not duplicate param name - ) { - // Use function f(..., x, y, z) to declare name - - var value = object.declarations[0].init; - var isPredictablyComputed = - predictableFunction.body === block && - !isStrictModeFunction(predictableFunction) && - value && - isIndependent(value, []) && - isMoveable(value, [object.declarations[0], object, ...parents]); - - var defineWithValue = isPredictablyComputed; - - if (defineWithValue) { - predictableFunction.params.push( - AssignmentPattern(Identifier(varName), value) - ); - object.declarations[0].init = null; - deleteStatement = true; - } else { - predictableFunction.params.push(Identifier(varName)); - } - } else { - // Use 'var x, y, z' to declare name - - // Make sure in the block statement, and not already at the top of it - if (index === -1 || index === 0) return; - - var topVariableDeclaration; - if (body[0].type === "VariableDeclaration" && body[0].kind === "var") { - topVariableDeclaration = body[0]; - } else { - topVariableDeclaration = { - type: "VariableDeclaration", - declarations: [], - kind: "var", - }; - - prepend(block, topVariableDeclaration); - } - - // Add `var x` at the top of the block - topVariableDeclaration.declarations.push( - VariableDeclarator(Identifier(varName)) - ); - } - - var assignmentExpression = AssignmentExpression( - "=", - Identifier(varName), - object.declarations[0].init || Identifier(varName) - ); - - if (forInitializeType) { - if (forInitializeType === "initializer") { - // Replace `for (var i = 0...)` to `for (i = 0...)` - this.replace(object, assignmentExpression); - } else if (forInitializeType === "left-hand") { - // Replace `for (var k in...)` to `for (k in ...)` - - this.replace(object, Identifier(varName)); - } - } else { - if (deleteStatement && index !== -1) { - body.splice(index, 1); - } else { - // Replace `var x = value` to `x = value` - this.replace(object, ExpressionStatement(assignmentExpression)); - } - } - }; - } -} diff --git a/src_old/transforms/identifier/renameVariables.ts b/src_old/transforms/identifier/renameVariables.ts deleted file mode 100644 index 7d7a5b3..0000000 --- a/src_old/transforms/identifier/renameVariables.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { walk } from "../../traverse"; -import { Literal, Node } from "../../util/gen"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { - isVarContext, - isContext, - isLexContext, - clone, - isFunction, -} from "../../util/insert"; -import Transform from "../transform"; -import { - noRenameVariablePrefix, - placeholderVariablePrefix, - reservedIdentifiers, - variableFunctionName, -} from "../../constants"; -import { ComputeProbabilityMap } from "../../probability"; -import VariableAnalysis from "./variableAnalysis"; - -/** - * Rename variables to randomly generated names. - * - * - 1. First collect data on identifiers in all scope using 'VariableAnalysis' - * - 2. After 'VariableAnalysis' is finished start applying to each scope (top-down) - * - 3. Each scope, find the all names used here and exclude those names from being re-named - * - 4. Now loop through all the defined names in this scope and set it to a random name (or re-use previously generated name) - * - 5. Update all the Identifiers node's 'name' property to reflect this change - */ -export default class RenameVariables extends Transform { - // Names already used - generated: string[]; - - // Map of Context->Object of changes - changed: Map; - - // Ref to VariableAnalysis data - variableAnalysis: VariableAnalysis; - - // Option to re-use previously generated names - reusePreviousNames = true; - - constructor(o) { - super(o, ObfuscateOrder.RenameVariables); - - this.changed = new Map(); - - // 1. - this.variableAnalysis = new VariableAnalysis(o); - this.before.push(this.variableAnalysis); - this.generated = []; - } - - match(object: Node, parents: Node[]) { - return isContext(object) || object.type === "Identifier"; - } - - transformContext(object: Node, parents: Node[]) { - // 2. Notice this is on 'onEnter' (top-down) - var isGlobal = object.type == "Program"; - var type = isGlobal - ? "root" - : isVarContext(object) - ? "var" - : isLexContext(object) - ? "lex" - : undefined; - - ok(type); - - var newNames = Object.create(null); - - var defined = this.variableAnalysis.defined.get(object) || new Set(); - var references = this.variableAnalysis.references.get(object) || new Set(); - - // No changes needed here - if (!defined && !this.changed.has(object)) { - this.changed.set(object, Object.create(null)); - return; - } - - // Names possible to be re-used here - var possible = new Set(); - - // 3. Try to re-use names when possible - if (this.reusePreviousNames && this.generated.length && !isGlobal) { - var allReferences = new Set(); - var nope = new Set(defined); - walk(object, [], (o, p) => { - var ref = this.variableAnalysis.references.get(o); - if (ref) { - ref.forEach((x) => allReferences.add(x)); - } - - var def = this.variableAnalysis.defined.get(o); - if (def) { - def.forEach((x) => allReferences.add(x)); - } - }); - - var passed = new Set(); - parents.forEach((p) => { - var changes = this.changed.get(p); - if (changes) { - Object.keys(changes).forEach((x) => { - var name = changes[x]; - - if (!allReferences.has(x) && !references.has(x)) { - passed.add(name); - } else { - nope.add(name); - } - }); - } - }); - - nope.forEach((x) => passed.delete(x)); - - possible = passed; - } - - // 4. Defined names to new names - for (var name of defined) { - if ( - !name.startsWith(noRenameVariablePrefix) && // Variables prefixed with '__NO_JS_CONFUSER_RENAME__' are never renamed - (isGlobal && !name.startsWith(placeholderVariablePrefix) // Variables prefixed with '__p_' are created by the obfuscator, always renamed - ? ComputeProbabilityMap(this.options.renameGlobals, (x) => x, name) - : true) && - ComputeProbabilityMap( - // Check the user's option for renaming variables - this.options.renameVariables, - (x) => x, - name, - isGlobal - ) - ) { - // Create a new name from (1) or (2) methods - var newName: string; - do { - if (possible.size) { - // (1) Re-use previously generated name - var first = possible.values().next().value; - possible.delete(first); - newName = first; - } else { - // (2) Create a new name with `generateIdentifier` function - var generatedName = this.generateIdentifier(); - - newName = generatedName; - this.generated.push(generatedName); - } - } while (this.variableAnalysis.globals.has(newName)); // Ensure global names aren't overridden - - newNames[name] = newName; - } else { - // This variable name was deemed not to be renamed. - newNames[name] = name; - } - } - - // console.log(object.type, newNames); - this.changed.set(object, newNames); - } - - transformIdentifier(object: Node, parents: Node[]) { - const identifierName = object.name; - if ( - reservedIdentifiers.has(identifierName) || - this.options.globalVariables.has(identifierName) - ) { - return; - } - - if (object.$renamed) { - return; - } - - var info = getIdentifierInfo(object, parents); - - if (info.spec.isExported) { - return; - } - - if (!info.spec.isReferenced) { - return; - } - - var contexts = [object, ...parents].filter((x) => isContext(x)); - var newName = null; - - // Function default parameter check! - var functionIndices = []; - for (var i in parents) { - if (isFunction(parents[i])) { - functionIndices.push(i); - } - } - - for (var functionIndex of functionIndices) { - if (parents[functionIndex].id === object) { - // This context is not referenced, so remove it - contexts = contexts.filter( - (context) => context != parents[functionIndex] - ); - continue; - } - if (parents[functionIndex].params === parents[functionIndex - 1]) { - var isReferencedHere = true; - - var slicedParents = parents.slice(0, functionIndex); - var forIndex = 0; - for (var parent of slicedParents) { - var childNode = slicedParents[forIndex - 1] || object; - - if ( - parent.type === "AssignmentPattern" && - parent.right === childNode - ) { - isReferencedHere = false; - break; - } - - forIndex++; - } - - if (!isReferencedHere) { - // This context is not referenced, so remove it - contexts = contexts.filter( - (context) => context != parents[functionIndex] - ); - } - } - } - - for (var check of contexts) { - if ( - this.variableAnalysis.defined.has(check) && - this.variableAnalysis.defined.get(check).has(identifierName) - ) { - if ( - this.changed.has(check) && - this.changed.get(check)[identifierName] - ) { - newName = this.changed.get(check)[identifierName]; - break; - } - } - } - - if (newName && typeof newName === "string") { - // Strange behavior where the `local` and `imported` objects are the same - if (info.isImportSpecifier) { - var importSpecifierIndex = parents.findIndex( - (x) => x.type === "ImportSpecifier" - ); - if ( - importSpecifierIndex != -1 && - parents[importSpecifierIndex].imported === - (parents[importSpecifierIndex - 1] || object) && - parents[importSpecifierIndex].imported && - parents[importSpecifierIndex].imported.type === "Identifier" - ) { - parents[importSpecifierIndex].imported = clone( - parents[importSpecifierIndex - 1] || object - ); - } - } - - if ( - parents[1] && - parents[1].type === "CallExpression" && - parents[1].arguments === parents[0] - ) { - if ( - parents[1].callee.type === "Identifier" && - parents[1].callee.name === variableFunctionName - ) { - this.replace(parents[1], Literal(newName)); - return; - } - } - - // console.log(o.name, "->", newName); - // 5. Update Identifier node's 'name' property - object.name = newName; - object.$renamed = true; - } - } - - transform(object: Node, parents: Node[]) { - var matchType = object.type === "Identifier" ? "Identifier" : "Context"; - if (matchType === "Identifier") { - this.transformIdentifier(object, parents); - } else { - this.transformContext(object, parents); - } - } -} diff --git a/src_old/transforms/identifier/variableAnalysis.ts b/src_old/transforms/identifier/variableAnalysis.ts deleted file mode 100644 index d238f02..0000000 --- a/src_old/transforms/identifier/variableAnalysis.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { ok } from "assert"; -import { reservedIdentifiers } from "../../constants"; -import { isValidIdentifier } from "../../util/compare"; -import { Node } from "../../util/gen"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { - getReferencingContexts, - getAllDefiningContexts, -} from "../../util/insert"; -import Transform from "../transform"; - -/** - * Keeps track of what identifiers are defined and referenced in each context. - */ -export default class VariableAnalysis extends Transform { - /** - * Node being the context. - */ - defined: Map>; - - /** - * Context->Nodes referenced (does not include nested) - */ - references: Map>; - - /** - * Set of global identifiers to never be redefined - * - * - Used to not accidentally block access to a global variable - */ - globals: Set; - - /** - * Set of identifiers that are defined within the program - */ - notGlobals: Set; - - constructor(o) { - super(o); - - this.defined = new Map(); - this.references = new Map(); - this.globals = new Set(); - this.notGlobals = new Set(); - } - - match(object, parents) { - return object.type === "Identifier"; - } - - transform(object: Node, parents: Node[]) { - var name = object.name; - ok(typeof name === "string"); - if (!isValidIdentifier(name)) { - return; - } - - if (reservedIdentifiers.has(name)) { - return; - } - if (this.options.globalVariables.has(name)) { - return; - } - - var info = getIdentifierInfo(object, parents); - if (!info.spec.isReferenced) { - return; - } - - if (info.spec.isExported) { - return; - } - - var isDefined = info.spec.isDefined; - - // Keep track of defined names within the program - if (isDefined) { - this.notGlobals.add(object.name); - this.globals.delete(object.name); - } else if (!this.notGlobals.has(object.name)) { - this.globals.add(object.name); - } - - var definingContexts = info.spec.isDefined - ? getAllDefiningContexts(object, parents) - : getReferencingContexts(object, parents, info); - - ok(definingContexts.length); - - definingContexts.forEach((definingContext) => { - // ok( - // isContext(definingContext), - // `${definingContext.type} is not a context` - // ); - - if (isDefined) { - // Add to defined Map - if (!this.defined.has(definingContext)) { - this.defined.set(definingContext, new Set()); - } - this.defined.get(definingContext).add(name); - this.references.has(definingContext) && - this.references.get(definingContext).delete(name); - } else { - // Add to references Map - if ( - !this.defined.has(definingContext) || - !this.defined.get(definingContext).has(name) - ) { - if (!this.references.has(definingContext)) { - this.references.set(definingContext, new Set()); - } - this.references.get(definingContext).add(name); - } - } - }); - } -} diff --git a/src_old/transforms/lock/antiDebug.ts b/src_old/transforms/lock/antiDebug.ts deleted file mode 100644 index b9af5fd..0000000 --- a/src_old/transforms/lock/antiDebug.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { ObfuscateOrder } from "../../order"; -import Template from "../../templates/template"; -import { isBlock } from "../../traverse"; -import { - AssignmentExpression, - DebuggerStatement, - ExpressionStatement, - FunctionDeclaration, - Identifier, - IfStatement, - Literal, - WhileStatement, -} from "../../util/gen"; -import { getBlockBody, prepend } from "../../util/insert"; -import { getRandomInteger } from "../../util/random"; -import Transform from "../transform"; -import Lock from "./lock"; - -var DevToolsDetection = new Template( - ` - try { - if ( setInterval ) { - setInterval(()=>{ - {functionName}(); - }, 4000); - } - } catch ( e ) { - - } -` -); - -export default class AntiDebug extends Transform { - made: number; - lock: Lock; - - constructor(o, lock) { - super(o, ObfuscateOrder.Lock); - - this.lock = lock; - this.made = 0; - } - - apply(tree) { - super.apply(tree); - - var fnName = this.getPlaceholder(); - var startTimeName = this.getPlaceholder(); - var endTimeName = this.getPlaceholder(); - var isDevName = this.getPlaceholder(); - var functionDeclaration = FunctionDeclaration( - fnName, - [], - [ - ...new Template(` - var ${startTimeName} = new Date(); - debugger; - var ${endTimeName} = new Date(); - var ${isDevName} = ${endTimeName}-${startTimeName} > 1000; - `).compile(), - - IfStatement( - Identifier(isDevName), - this.options.lock.countermeasures - ? this.lock.getCounterMeasuresCode(tree.body, [tree]) - : [ - WhileStatement(Identifier(isDevName), [ - ExpressionStatement( - AssignmentExpression( - "=", - Identifier(startTimeName), - Identifier(endTimeName) - ) - ), - ]), - ], - null - ), - ] - ); - - tree.body.unshift(...DevToolsDetection.compile({ functionName: fnName })); - tree.body.push(functionDeclaration); - } - - match(object, parents) { - return isBlock(object); - } - - transform(object, parents) { - return () => { - var body = getBlockBody(object.body); - - [...body].forEach((stmt, i) => { - var addDebugger = Math.random() < 0.1 / (this.made || 1); - - if (object.type == "Program" && i == 0) { - addDebugger = true; - } - - if (addDebugger) { - var index = getRandomInteger(0, body.length); - if (body[index].type != "DebuggerStatement") { - body.splice(index, 0, DebuggerStatement()); - - this.made++; - } - } - }); - }; - } -} diff --git a/src_old/transforms/lock/integrity.ts b/src_old/transforms/lock/integrity.ts deleted file mode 100644 index 507ab6a..0000000 --- a/src_old/transforms/lock/integrity.ts +++ /dev/null @@ -1,282 +0,0 @@ -import Transform from "../transform"; -import Template from "../../templates/template"; -import { - VariableDeclaration, - IfStatement, - Identifier, - BinaryExpression, - Literal, - CallExpression, - BlockStatement, - ExpressionStatement, - Node, - FunctionExpression, - VariableDeclarator, -} from "../../util/gen"; -import { clone, isFunction } from "../../util/insert"; -import { getRandomInteger } from "../../util/random"; -import Lock from "./lock"; -import { ok } from "assert"; -import { compileJsSync } from "../../compiler"; - -/** - * Hashing Algorithm for function integrity - * @param str - * @param seed - */ -function cyrb53(str, seed = 0) { - let h1 = 0xdeadbeef ^ seed, - h2 = 0x41c6ce57 ^ seed; - for (let i = 0, ch; i < str.length; i++) { - ch = str.charCodeAt(i); - h1 = Math.imul(h1 ^ ch, 2654435761); - h2 = Math.imul(h2 ^ ch, 1597334677); - } - h1 = - Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ - Math.imul(h2 ^ (h2 >>> 13), 3266489909); - h2 = - Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ - Math.imul(h1 ^ (h1 >>> 13), 3266489909); - return 4294967296 * (2097151 & h2) + (h1 >>> 0); -} - -// In template form to be inserted into code -const HashTemplate = new Template(` -function {name}(str, seed) { - var h1 = 0xdeadbeef ^ seed; - var h2 = 0x41c6ce57 ^ seed; - for (var i = 0, ch; i < str.length; i++) { - ch = str.charCodeAt(i); - h1 = {imul}(h1 ^ ch, 2654435761); - h2 = {imul}(h2 ^ ch, 1597334677); - } - h1 = {imul}(h1 ^ (h1>>>16), 2246822507) ^ {imul}(h2 ^ (h2>>>13), 3266489909); - h2 = {imul}(h2 ^ (h2>>>16), 2246822507) ^ {imul}(h1 ^ (h1>>>13), 3266489909); - return 4294967296 * (2097151 & h2) + (h1>>>0); -};`); - -// Math.imul polyfill for ES5 -const ImulTemplate = new Template(` -var {name} = Math.imul || function(opA, opB){ - opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. - // floating points give us 53 bits of precision to work with plus 1 sign bit - // automatically handled for our convienence: - // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 - // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ - var result = (opA & 0x003fffff) * opB; - // 2. We can remove an integer coersion from the statement above because: - // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 - // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ - if (opA & 0xffc00000 /*!== 0*/) result += (opA & 0xffc00000) * opB |0; - return result |0; -};`); - -// Simple function that returns .toString() value with spaces replaced out -const StringTemplate = new Template(` - function {name}(x){ - return x.toString().replace(/ |\\n|;|,|\\{|\\}|\\(|\\)|\\.|\\[|\\]/g, ""); - } -`); - -/** - * Integrity protects functions by using checksum techniques to verify their code has not changed. - * - * If an attacker modifies a function, the modified function will not execute. - * - * How it works: - * - * - By using `.toString()` JavaScript will expose a function's source code. - * - We can hash it and use an if statement in the code to ensure the function's code is unchanged. - * - * This is the most complicated Transformation for JSConfuser so here I'll explain: - * - The Program is wrapped in an IIFE (Function Expression that is called instantly) - * - Every function including ^ are generated out and evaluated for their .toString() value - * - Hashed using cyrb53's hashing algorithm - * - Check the checksum before running the code. - * - * - The hashing function is placed during this transformation, - * - A hidden identifier is placed to keep track of the name. - */ -export default class Integrity extends Transform { - hashFn: Node; - imulFn: Node; - stringFn: Node; - seed: number; - lock: Lock; - - constructor(o, lock) { - super(o); - this.lock = lock; - - this.seed = getRandomInteger(0, 1000); - } - - match(object: Node, parents: Node[]) { - // ArrowFunctions are excluded! - return ( - object.type == "Program" || - (isFunction(object) && object.type !== "ArrowFunctionExpression") - ); - } - - transform(object: Node, parents: Node[]) { - if (object.type == "Program") { - return () => { - var hashingUtils: Node[] = []; - - var imulName = this.getPlaceholder(); - var imulVariableDeclaration = ImulTemplate.single({ name: imulName }); - - imulVariableDeclaration.$multiTransformSkip = true; - - this.imulFn = imulVariableDeclaration._hiddenId = Identifier(imulName); - hashingUtils.push(imulVariableDeclaration); - - var hashName = this.getPlaceholder(); - var hashFunctionDeclaration = HashTemplate.single({ - name: hashName, - imul: imulName, - }); - this.hashFn = hashFunctionDeclaration._hiddenId = Identifier(hashName); - hashingUtils.push(hashFunctionDeclaration); - - hashFunctionDeclaration.$multiTransformSkip = true; - - var stringName = this.getPlaceholder(); - var stringFunctionDeclaration = StringTemplate.single({ - name: stringName, - }); - this.stringFn = stringFunctionDeclaration._hiddenId = - Identifier(stringName); - hashingUtils.push(stringFunctionDeclaration); - - stringFunctionDeclaration.$multiTransformSkip = true; - - var functionExpression = FunctionExpression([], clone(object.body)); - - object.body = [ - ExpressionStatement(CallExpression(functionExpression, [])), - ]; - - object.$multiTransformSkip = true; - - object._hiddenHashingUtils = hashingUtils; - - var ok = this.transform(functionExpression, [ - object.body[0], - object.body, - object, - ]); - if (ok) { - ok(); - } - - object.$eval = () => { - if ( - isFunction(functionExpression) && - functionExpression.body.type == "BlockStatement" - ) { - if (this.lock.counterMeasuresNode) { - functionExpression.body.body.unshift( - clone(this.lock.counterMeasuresNode[0]) - ); - } - - functionExpression.body.body.unshift(...hashingUtils); - } - }; - }; - } - ok(isFunction(object)); - - if (object.generator || object.async) { - return; - } - - return () => { - object.__hiddenCountermeasures = this.lock.getCounterMeasuresCode( - object, - parents - ); - - object.$eval = () => { - var functionName = this.generateIdentifier(); - var hashName = this.generateIdentifier(); - - var functionDeclaration = { - ...clone(object), - type: "FunctionDeclaration", - id: Identifier(functionName), - params: object.params || [], - body: object.body || BlockStatement([]), - expression: false, - $multiTransformSkip: true, - }; - - var toString = compileJsSync(functionDeclaration, this.options); - - if (!toString) { - return; - } - - var minified = toString.replace(/ |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g, ""); - var hash = cyrb53(minified, this.seed); - - this.log( - (object.id ? object.id.name : "function") + " -> " + hash, - minified - ); - - var ifStatement = IfStatement( - BinaryExpression("==", Identifier(hashName), Literal(hash)), - [ - new Template(`return {functionName}.apply(this, arguments)`).single( - { - functionName: functionName, - } - ), - ] - ); - if ( - object.__hiddenCountermeasures && - object.__hiddenCountermeasures.length - ) { - ifStatement.alternate = BlockStatement( - object.__hiddenCountermeasures - ); - } - - object.body = BlockStatement([ - functionDeclaration, - VariableDeclaration( - VariableDeclarator( - hashName, - CallExpression(clone(this.hashFn), [ - CallExpression(clone(this.stringFn), [ - Identifier(functionName), - ]), - Literal(this.seed), - ]) - ) - ), - ifStatement, - ]); - - // Make sure the countermeasures activation variable is present - if (this.lock.counterMeasuresActivated) { - object.body.body.unshift( - VariableDeclaration( - VariableDeclarator(this.lock.counterMeasuresActivated) - ) - ); - } - - if (object.type == "ArrowFunctionExpression") { - object.type = "FunctionExpression"; - object.expression = false; - } - }; - }; - } -} diff --git a/src_old/transforms/lock/lock.ts b/src_old/transforms/lock/lock.ts deleted file mode 100644 index 7d3bba5..0000000 --- a/src_old/transforms/lock/lock.ts +++ /dev/null @@ -1,650 +0,0 @@ -import Transform from "../transform"; -import { - Node, - IfStatement, - ExpressionStatement, - AssignmentExpression, - Identifier, - BinaryExpression, - CallExpression, - MemberExpression, - Literal, - UnaryExpression, - NewExpression, - VariableDeclaration, - ThisExpression, - VariableDeclarator, - Location, - LogicalExpression, - SequenceExpression, -} from "../../util/gen"; -import traverse, { getBlock, isBlock } from "../../traverse"; -import { choice, getRandomInteger } from "../../util/random"; -import { CrashTemplate1, CrashTemplate2 } from "../../templates/crash"; -import { getBlockBody, getVarContext, prepend } from "../../util/insert"; -import Template from "../../templates/template"; -import { ObfuscateOrder } from "../../order"; -import Integrity from "./integrity"; -import AntiDebug from "./antiDebug"; -import { getIdentifierInfo } from "../../util/identifiers"; -import { isLoop, isValidIdentifier } from "../../util/compare"; -import { ok } from "assert"; -import { variableFunctionName } from "../../constants"; -import { IndexOfTemplate } from "../../templates/core"; - -/** - * Applies browser & date locks. - */ -export default class Lock extends Transform { - globalVar: string; - counterMeasuresNode: Location; - iosDetectFn: string; - - /** - * This is a boolean variable injected into the source code determining wether the countermeasures function has been called. - * This is used to prevent infinite loops from happening - */ - counterMeasuresActivated: string; - - /** - * The name of the native function that is used to check runtime calls for tampering - */ - nativeFunctionName: string; - - made: number; - - shouldTransformNativeFunction(nameAndPropertyPath: string[]) { - if (!this.options.lock.tamperProtection) { - return false; - } - - if (typeof this.options.lock.tamperProtection === "function") { - return this.options.lock.tamperProtection(nameAndPropertyPath.join(".")); - } - - if ( - this.options.target === "browser" && - nameAndPropertyPath.length === 1 && - nameAndPropertyPath[0] === "fetch" - ) { - return true; - } - - // TODO: Allow user to customize this behavior - var globalObject = typeof window !== "undefined" ? window : global; - var fn = globalObject; - for (var item of nameAndPropertyPath) { - fn = fn[item]; - if (typeof fn === "undefined") return false; - } - - var hasNativeCode = - typeof fn === "function" && ("" + fn).includes("[native code]"); - - return hasNativeCode; - } - - constructor(o) { - super(o, ObfuscateOrder.Lock); - - // Removed feature - // if (this.options.lock.startDate && this.options.lock.endDate) { - // this.before.push(new LockStrings(o)); - // } - - if (this.options.lock.integrity) { - this.before.push(new Integrity(o, this)); - } - - if (this.options.lock.antiDebug) { - this.before.push(new AntiDebug(o, this)); - } - - this.made = 0; - } - - apply(tree) { - if ( - typeof this.options.lock.countermeasures === "string" && - isValidIdentifier(this.options.lock.countermeasures) - ) { - traverse(tree, (object, parents) => { - if ( - object.type == "Identifier" && - object.name === this.options.lock.countermeasures - ) { - var info = getIdentifierInfo(object, parents); - if (info.spec.isDefined) { - if (this.counterMeasuresNode) { - throw new Error( - "Countermeasures function was already defined, it must have a unique name from the rest of your code" - ); - } else { - var definingContext = getVarContext(parents[0], parents.slice(1)); - if (definingContext != tree) { - throw new Error( - "Countermeasures function must be defined at the global level" - ); - } - var chain: Location = [object, parents]; - if (info.isFunctionDeclaration) { - chain = [parents[0], parents.slice(1)]; - } else if (info.isVariableDeclaration) { - chain = [parents[1], parents.slice(2)]; - } - - this.counterMeasuresNode = chain; - } - } - } - }); - - if (!this.counterMeasuresNode) { - throw new Error( - "Countermeasures function named '" + - this.options.lock.countermeasures + - "' was not found." - ); - } - } - - super.apply(tree); - - if (this.options.lock.tamperProtection) { - this.nativeFunctionName = this.getPlaceholder() + "_lockNative"; - - // Ensure program is not in strict mode - // Tamper Protection forces non-strict mode - - var strictModeCheck = new Template(` - (function(){ - function isStrictMode(){ - try { - var arr = [] - delete arr["length"] - } catch(e) { - return true; - } - return false; - } - - if(isStrictMode()) { - {countermeasures} - ${this.nativeFunctionName} = undefined; - } - })() - `).single({ - countermeasures: this.getCounterMeasuresCode(tree, []), - }); - - // $multiTransformSkip is used to prevent scoping between transformations - strictModeCheck.$multiTransformSkip = true; - - prepend(tree, strictModeCheck); - - var nativeFunctionCheck = new Template(` - function ${this.nativeFunctionName}() { - {IndexOfTemplate} - - function checkFunction(fn){ - if (indexOf("" + fn, '{ [native code] }') === -1 - || - typeof Object.getOwnPropertyDescriptor(fn, "toString") !== "undefined" - ) { - {countermeasures} - return undefined - } - - return fn; - } - - var args = arguments - if(args.length === 1) { - return checkFunction(args[0]); - } else if (args.length === 2) { - var object = args[0]; - var property = args[1]; - - var fn = object[property]; - fn = checkFunction(fn); - - return fn.bind(object); - } - }`).single({ - IndexOfTemplate: IndexOfTemplate, - countermeasures: this.getCounterMeasuresCode(tree, []), - }); - - // $multiTransformSkip is used to prevent scoping between transformations - nativeFunctionCheck.$multiTransformSkip = true; - - prepend(tree, nativeFunctionCheck); - } - } - - getCounterMeasuresCode(object: Node, parents: Node[]): Node[] { - var opt = this.options.lock.countermeasures; - - if (opt === false) { - return null; - } - - // Call function - if (typeof opt === "string") { - if (!this.counterMeasuresActivated) { - this.counterMeasuresActivated = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || object, - VariableDeclaration(VariableDeclarator(this.counterMeasuresActivated)) - ); - } - - // Since Lock occurs before variable renaming, we are using the pre-obfuscated function name - return [ - ExpressionStatement( - LogicalExpression( - "||", - Identifier(this.counterMeasuresActivated), - SequenceExpression([ - AssignmentExpression( - "=", - Identifier(this.counterMeasuresActivated), - Literal(true) - ), - CallExpression(new Template(opt).single().expression, []), - ]) - ) - ), - ]; - } - - // Default fallback to infinite loop - var varName = this.getPlaceholder(); - return choice([CrashTemplate1, CrashTemplate2]).compile({ - var: varName, - }); - } - - /** - * Converts Dates to numbers, then applies some randomness - * @param object - */ - getTime(object: Date | number | false): number { - if (!object) { - return 0; - } - if (object instanceof Date) { - return this.getTime(object.getTime()); - } - - return object + getRandomInteger(-4000, 4000); - } - - match(object: Node, parents: Node[]) { - return isBlock(object); - } - - transform(object: Node, parents: Node[]) { - if (parents.find((x) => isLoop(x) && x.type != "SwitchStatement")) { - return; - } - - // no check in countermeasures code, otherwise it will infinitely call itself - if ( - this.counterMeasuresNode && - (object == this.counterMeasuresNode[0] || - parents.indexOf(this.counterMeasuresNode[0]) !== -1) - ) { - return; - } - - var block = getBlock(object, parents); - - var choices = []; - if (this.options.lock.startDate) { - choices.push("startDate"); - } - if (this.options.lock.endDate) { - choices.push("endDate"); - } - if (this.options.lock.domainLock && this.options.lock.domainLock.length) { - choices.push("domainLock"); - } - - if (this.options.lock.context && this.options.lock.context.length) { - choices.push("context"); - } - if (this.options.lock.browserLock && this.options.lock.browserLock.length) { - choices.push("browserLock"); - } - if (this.options.lock.osLock && this.options.lock.osLock.length) { - choices.push("osLock"); - } - if (this.options.lock.selfDefending) { - choices.push("selfDefending"); - } - - if (!choices.length) { - return; - } - - return () => { - this.made++; - if (this.made > 150) { - return; - } - - var type = choice(choices); - var nodes = []; - - var dateNow: Node = CallExpression( - MemberExpression(Identifier("Date"), Literal("now"), true), - [] - ); - if (Math.random() > 0.5) { - dateNow = CallExpression( - MemberExpression( - NewExpression(Identifier("Date"), []), - Literal("getTime") - ), - [] - ); - } - if (Math.random() > 0.5) { - dateNow = CallExpression( - MemberExpression( - MemberExpression( - MemberExpression(Identifier("Date"), Literal("prototype"), true), - Literal("getTime"), - true - ), - Literal("call"), - true - ), - [NewExpression(Identifier("Date"), [])] - ); - } - - var test; - var offset = 0; - - switch (type) { - case "selfDefending": - // A very simple mechanism inspired from https://github.com/javascript-obfuscator/javascript-obfuscator/blob/master/src/custom-code-helpers/self-defending/templates/SelfDefendingNoEvalTemplate.ts - // regExp checks for a newline, formatters add these - var callExpression = new Template( - ` - ( - function(){ - // Breaks JSNice.org, beautifier.io - var namedFunction = function(){ - const test = function(){ - const regExp=new RegExp('\\n'); - return regExp['test'](namedFunction) - }; - return test() - } - - return namedFunction(); - } - )() - ` - ).single().expression; - - nodes.push( - IfStatement( - callExpression, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); - - break; - - case "startDate": - test = BinaryExpression( - "<", - dateNow, - Literal(this.getTime(this.options.lock.startDate)) - ); - - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); - - break; - - case "endDate": - test = BinaryExpression( - ">", - dateNow, - Literal(this.getTime(this.options.lock.endDate)) - ); - - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); - - break; - - case "context": - var prop = choice(this.options.lock.context); - - var code = this.getCounterMeasuresCode(object, parents) || []; - - // Todo: Alternative to `this` - if (!this.globalVar) { - offset = 1; - this.globalVar = this.getPlaceholder(); - prepend( - parents[parents.length - 1] || block, - VariableDeclaration( - VariableDeclarator( - this.globalVar, - LogicalExpression( - "||", - Identifier( - this.options.globalVariables.keys().next().value - ), - ThisExpression() - ) - ) - ) - ); - } - - test = UnaryExpression( - "!", - MemberExpression(Identifier(this.globalVar), Literal(prop), true) - ); - nodes.push(IfStatement(test, code, null)); - - break; - - case "osLock": - var navigatorUserAgent = new Template( - `window.navigator.userAgent.toLowerCase()` - ).single().expression; - - ok(this.options.lock.osLock); - - var code = this.getCounterMeasuresCode(object, parents) || []; - - this.options.lock.osLock.forEach((osName) => { - var agentMatcher = { - windows: "Win", - linux: "Linux", - osx: "Mac", - android: "Android", - ios: "---", - }[osName]; - var thisTest: Node = CallExpression( - MemberExpression(navigatorUserAgent, Literal("match"), true), - [Literal(agentMatcher.toLowerCase())] - ); - if (osName == "ios" && this.options.target === "browser") { - if (!this.iosDetectFn) { - this.iosDetectFn = this.getPlaceholder(); - prepend( - parents[parents.length - 1] || object, - new Template(`function ${this.iosDetectFn}() { - return [ - 'iPad Simulator', - 'iPhone Simulator', - 'iPod Simulator', - 'iPad', - 'iPhone', - 'iPod' - ].includes(navigator.platform) - // iPad on iOS 13 detection - || (navigator.userAgent.includes("Mac") && "ontouchend" in document) - }`).single() - ); - } - - thisTest = CallExpression(Identifier(this.iosDetectFn), []); - } - - if (this.options.target === "node") { - var platformName = - { windows: "win32", osx: "darwin", ios: "darwin" }[osName] || - osName; - thisTest = new Template( - `require('os').platform()==="${platformName}"` - ).single().expression; - } - - if (!test) { - test = thisTest; - } else { - test = LogicalExpression("||", { ...test }, thisTest); - } - }); - - test = UnaryExpression("!", { ...test }); - nodes.push(IfStatement(test, code, null)); - break; - - case "browserLock": - var navigatorUserAgent = new Template( - `window.navigator.userAgent.toLowerCase()` - ).single().expression; - - ok(this.options.lock.browserLock); - - this.options.lock.browserLock.forEach((browserName) => { - var thisTest: Node = CallExpression( - MemberExpression(navigatorUserAgent, Literal("match"), true), - [ - Literal( - browserName == "iexplorer" - ? "msie" - : browserName.toLowerCase() - ), - ] - ); - - if (browserName === "safari") { - thisTest = new Template( - `/^((?!chrome|android).)*safari/i.test(navigator.userAgent)` - ).single().expression; - } - - if (!test) { - test = thisTest; - } else { - test = LogicalExpression("||", { ...test }, thisTest); - } - }); - - test = UnaryExpression("!", { ...test }); - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); - break; - - case "domainLock": - function removeSlashes(path: string) { - var count = path.length - 1; - var index = 0; - - while (path.charCodeAt(index) === 47 && ++index); - while (path.charCodeAt(count) === 47 && --count); - - return path.slice(index, count + 1); - } - - var locationHref = MemberExpression( - Identifier("location"), - Literal("href"), - true - ); - - var random = choice(this.options.lock.domainLock as any); - if (random) { - test = CallExpression( - MemberExpression(locationHref, Literal("match"), true), - [ - { - type: "Literal", - regex: { - pattern: - random instanceof RegExp - ? random.source - : removeSlashes(random + ""), - flags: random instanceof RegExp ? "" : "", - }, - }, - ] - ); - - test = UnaryExpression("!", test); - if (Math.random() > 0.5) { - test = LogicalExpression( - "||", - BinaryExpression( - "==", - UnaryExpression("typeof", Identifier("location")), - Literal("undefined") - ), - test - ); - } - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); - } - - break; - } - - if (nodes.length) { - var body = getBlockBody(block); - var randomIndex = getRandomInteger(0, body.length) + offset; - - if (randomIndex >= body.length) { - body.push(...nodes); - } else { - body.splice(randomIndex, 0, ...nodes); - } - } - }; - } -} diff --git a/src_old/transforms/minify.ts b/src_old/transforms/minify.ts deleted file mode 100644 index 5e06805..0000000 --- a/src_old/transforms/minify.ts +++ /dev/null @@ -1,722 +0,0 @@ -import Transform from "./transform"; -import { ObfuscateOrder } from "../order"; -import { - Node, - VariableDeclaration, - BinaryExpression, - ExpressionStatement, - SequenceExpression, - Literal, - UnaryExpression, - ConditionalExpression, - BlockStatement, - ReturnStatement, - AssignmentExpression, - VariableDeclarator, - Identifier, - CallExpression, -} from "../util/gen"; -import { - getBlockBody, - clone, - isForInitialize, - append, - isVarContext, - computeFunctionLength, -} from "../util/insert"; -import { isValidIdentifier, isEquivalent } from "../util/compare"; -import { walk, isBlock } from "../traverse"; -import { ok } from "assert"; -import { isLexicalScope } from "../util/scope"; -import Template from "../templates/template"; -import { ObjectDefineProperty } from "../templates/globals"; -import { getIdentifierInfo } from "../util/identifiers"; - -/** - * Basic transformations to reduce code size. - * - * Examples: - * - `if(a) { b() }` **->** `a && b()` - * - `if(a){b()}else{c()}` **->** `a?b():c()` - * - `x['y']` **->** `x.y` - */ -export default class Minify extends Transform { - /** - * A helper function that is introduced preserve function semantics - */ - arrowFunctionName: string; - - constructor(o) { - super(o, ObfuscateOrder.Minify); - } - - match(object: Node, parents: Node[]) { - return object.hasOwnProperty("type"); - } - - transform(object: Node, parents: Node[]) { - if (isLexicalScope(object)) { - return () => { - var body = - object.type == "SwitchCase" - ? object.consequent - : getBlockBody(object); - var earlyReturn = body.length; - var fnDecs: [Node, number][] = []; - - body.forEach((stmt, i) => { - if ( - stmt.type === "ReturnStatement" || - stmt.type === "BreakStatement" || - stmt.type === "ContinueStatement" || - stmt.type === "ThrowStatement" - ) { - if (earlyReturn > i + 1) { - earlyReturn = i + 1; - } - } - - if (stmt.type == "FunctionDeclaration") { - fnDecs.push([stmt, i]); - } - }); - - if (earlyReturn < body.length) { - body.length = earlyReturn; - body.push( - ...fnDecs.filter((x) => x[1] >= earlyReturn).map((x) => x[0]) - ); - } - - // Now combine ExpressionStatements - - if (body.length > 1) { - var exprs = []; - var startIndex = -1; - - var sequences: { index: number; exprs: Node[] }[] = []; - - body.forEach((stmt, i) => { - if (stmt.type == "ExpressionStatement" && !stmt.directive) { - exprs.push(stmt.expression); - if (startIndex == -1) { - startIndex = i; - } - } else { - if (exprs.length) { - sequences.push({ exprs: exprs, index: startIndex }); - } - exprs = []; - startIndex = -1; - } - }); - - if (exprs.length) { - sequences.push({ exprs: exprs, index: startIndex }); - } - - sequences.reverse().forEach((seq) => { - ok(seq.index != -1); - body.splice( - seq.index, - seq.exprs.length, - ExpressionStatement( - seq.exprs.length == 1 - ? seq.exprs[0] - : SequenceExpression(seq.exprs) - ) - ); - }); - } - - // Unnecessary return - if ( - parents[0] && - isVarContext(parents[0]) && - body.length && - body[body.length - 1] - ) { - var last = body[body.length - 1]; - if (last.type == "ReturnStatement") { - var isUndefined = last.argument == null; - - if (isUndefined) { - body.pop(); - } - } - } - - // Variable declaration grouping - // var a = 1; - // var b = 1; - // var c = 1; - // - // var a=1,b=1,c=1; - var lastDec = null; - - var remove = []; - body.forEach((x, i) => { - if (x.type === "VariableDeclaration") { - if ( - !lastDec || - lastDec.kind !== x.kind || - !lastDec.declarations.length - ) { - lastDec = x; - } else { - lastDec.declarations.push(...clone(x.declarations)); - remove.unshift(i); - } - } else { - lastDec = null; - } - }); - - remove.forEach((x) => { - body.splice(x, 1); - }); - }; - } - - /** - * ES6 and higher only - * - `function(){}` -> `()=>{}` - * - `function abc(){}` -> `var abc = ()=>{}` - */ - if ( - !this.options.es5 && - (object.type == "FunctionExpression" || - object.type == "FunctionDeclaration") - ) { - return () => { - // Don't touch `{get key(){...}}` - var propIndex = parents.findIndex( - (x) => x.type == "Property" || x.type == "MethodDefinition" - ); - if (propIndex !== -1) { - if (parents[propIndex].value === (parents[propIndex - 1] || object)) { - if ( - parents[propIndex].kind !== "init" || - parents[propIndex].method - ) { - return; - } - } - } - - if (object.type === "FunctionDeclaration") { - var body = parents[0]; - if (!Array.isArray(body)) { - return; - } - - var index = body.indexOf(object); - if (index == -1) { - return; - } - - var before = body.slice(0, index); - ok(!before.includes(object)); - - var beforeTypes = new Set(before.map((x) => x.type)); - beforeTypes.delete("FunctionDeclaration"); - - if (beforeTypes.size > 0) { - return; - } - - // Test Variant #25: Don't break redefined function declaration - if ( - object.id && - body.find( - (x) => - x.type === "FunctionDeclaration" && - x !== object && - x.id && - x.id.name === object.id.name - ) - ) { - return; - } - } - - var canTransform = true; - walk(object.body, [], ($object, $parents) => { - if ($object.type == "ThisExpression") { - canTransform = false; - } else if ($object.type == "Identifier") { - if ($object.name == "arguments") { - canTransform = false; - } - if ($object.name == "this") { - this.error(new Error("Use ThisExpression instead")); - } - } - }); - - if (canTransform) { - if (!this.arrowFunctionName) { - this.arrowFunctionName = this.getPlaceholder(); - - append( - parents[parents.length - 1] || object, - new Template(` - function ${this.arrowFunctionName}(arrowFn, functionLength = 0){ - var functionObject = function(){ return arrowFn(...arguments) }; - - ${ - this.options.preserveFunctionLength - ? `return {ObjectDefineProperty}(functionObject, "length", { - "value": functionLength, - "configurable": true - });` - : `return functionObject` - } - - } - `).single({ - ObjectDefineProperty: this.options.preserveFunctionLength - ? this.createInitVariable(ObjectDefineProperty, parents) - : undefined, - }) - ); - } - - const wrap = (object: Node) => { - var args: Node[] = [clone(object)]; - var fnLength = computeFunctionLength(object.params); - if (this.options.preserveFunctionLength && fnLength != 0) { - args.push(Literal(fnLength)); - } - return CallExpression(Identifier(this.arrowFunctionName), args); - }; - - if (object.type == "FunctionExpression") { - object.type = "ArrowFunctionExpression"; - - this.replace(object, wrap(clone(object))); - } else { - var arrow = { ...clone(object), type: "ArrowFunctionExpression" }; - this.replace( - object, - VariableDeclaration( - VariableDeclarator(object.id.name, wrap(arrow)) - ) - ); - - var x = this.transform(arrow, []); - if (typeof x === "function") { - x(); - } - } - } - }; - } - - /** - * ()=>{ expr } -> ()=>expr - */ - if ( - object.type == "ArrowFunctionExpression" && - object.body.type == "BlockStatement" - ) { - return () => { - var body = getBlockBody(object.body); - var stmt1 = body[0]; - - if (body.length == 1 && stmt1.type == "ReturnStatement") { - // x=>{a: 1} // Invalid syntax - if (stmt1.argument && stmt1.argument.type != "ObjectExpression") { - object.body = stmt1.argument; - object.expression = true; - } - } else { - // ()=>{exprStmt;exprStmt;} -> ()=>(expr, expr, expr, undefined) - var exprs = body.filter((x) => x.type == "ExpressionStatement"); - if (exprs.length == body.length) { - var array: Node[] = []; - function flatten(expr) { - if (expr.type == "SequenceExpression") { - expr.expressions.forEach(flatten); - } else if (expr.type == "ExpressionStatement") { - flatten(expr.expression); - } else { - array.push(expr); - } - } - - body.forEach(flatten); - - object.body = SequenceExpression([ - ...clone(array), - UnaryExpression("void", Literal(0)), - ]); - } - } - }; - } - - // (a()) -> a() - if (object.type == "SequenceExpression") { - return () => { - if (object.expressions.length == 1) { - this.replace(object, clone(object.expressions[0])); - } - }; - } - - // a += -1 -> a -= 1 - if (object.type == "AssignmentExpression") { - return () => { - if ( - object.operator == "+=" && - object.right.type == "UnaryExpression" && - object.right.operator == "-" - ) { - object.operator = "-="; - object.right = object.right.argument; - } else if ( - // a -= -1 -> a += 1 - object.operator == "-=" && - object.right.type == "UnaryExpression" && - object.right.operator == "-" - ) { - object.operator = "+="; - object.right = object.right.argument; - } - }; - } - - // a + -b -> a - b - if (object.type == "BinaryExpression") { - return () => { - if ( - object.operator == "+" && - object.right.type == "UnaryExpression" && - object.right.operator == "-" - ) { - object.operator = "-"; - object.right = object.right.argument; - } else if ( - // a - -1 -> a + 1 - object.operator == "-" && - object.right.type == "UnaryExpression" && - object.right.operator == "-" - ) { - object.operator = "+"; - object.right = object.right.argument; - } - }; - } - - if ( - object.type == "ForStatement" || - object.type == "ForInStatement" || - object.type == "ForOfStatement" || - object.type == "WhileStatement" - ) { - if (object.body.type == "BlockStatement") { - return () => { - if (object.body.body.length === 1) { - object.body = object.body.body[0]; - } - }; - } - } - - // Last switch case does not need break - if (object.type == "SwitchStatement") { - var last = object.cases[object.cases.length - 1]; - if (last) { - var lastStatement = last.consequent[last.consequent.length - 1]; - if ( - lastStatement && - lastStatement.type == "BreakStatement" && - lastStatement.label == null - ) { - last.consequent.pop(); - } - } else { - if ( - object.cases.length == 0 && - (object.discriminant.type == "Literal" || - object.discriminant.type == "Identifier") - ) { - if ( - parents[0].type == "LabeledStatement" && - Array.isArray(parents[1]) - ) { - return () => { - parents[1].splice(parents[1].indexOf(parents[0]), 1); - }; - } else if (Array.isArray(parents[0])) { - return () => { - parents[0].splice(parents[0].indexOf(object), 1); - }; - } - } - } - } - - // if ( x ) { y() } -> x && y() - // Todo Make this shit readable - if (object.type == "IfStatement") { - if (object.consequent.type != "BlockStatement") { - this.replace( - object.consequent, - BlockStatement([clone(object.consequent)]) - ); - } - if (object.alternate && object.alternate.type != "BlockStatement") { - this.replace( - object.alternate, - BlockStatement([clone(object.alternate)]) - ); - } - var body = getBlockBody(object.consequent); - - // Check for hard-coded if statements - if (object.test.type == "Literal") { - if (object.test.value || object.test.regex) { - // Why would anyone test just a regex literal - object.alternate = null; - } else { - object.consequent = BlockStatement([]); - } - } - - return () => { - // if ( a ) { } else {b()} -> if ( !a ) b(); - if (body.length == 0 && object.alternate) { - object.test = UnaryExpression("!", clone(object.test)); - if ( - object.alternate.type == "BlockStatement" && - object.alternate.body.length == 1 - ) { - object.alternate = clone(object.alternate.body[0]); - } - object.consequent = object.alternate; - object.alternate = null; - } - - if ( - object.consequent && - object.consequent.body && - object.consequent.body.length == 1 && - object.alternate && - object.alternate.body.length == 1 - ) { - var stmt1 = clone(object.consequent.body[0]); - var stmt2 = clone(object.alternate.body[0]); - - // if (a) {return b;} else {return c;} -> return a ? b : c; - if ( - stmt1.type == "ReturnStatement" && - stmt2.type == "ReturnStatement" - ) { - this.replace( - object, - ReturnStatement( - ConditionalExpression( - clone(object.test), - stmt1.argument || Identifier("undefined"), - stmt2.argument || Identifier("undefined") - ) - ) - ); - } - - // if (a) {b = 0} else {b = 1} -> b = a ? 0 : 1; - if ( - stmt1.type == "ExpressionStatement" && - stmt2.type == "ExpressionStatement" - ) { - var e1 = stmt1.expression; - var e2 = stmt2.expression; - - if ( - e1.type == "AssignmentExpression" && - e2.type == "AssignmentExpression" - ) { - if ( - e1.operator === e2.operator && - isEquivalent(e1.left, e2.left) - ) { - this.replace( - object, - ExpressionStatement( - AssignmentExpression( - e1.operator, - e1.left, - ConditionalExpression( - clone(object.test), - e1.right, - e2.right - ) - ) - ) - ); - } - } - } - } - }; - } - - // x["abc"] -> x.abc - if (object.type == "MemberExpression") { - var { object: obj, property } = object; - - if (property.type == "Literal" && isValidIdentifier(property.value)) { - object.computed = false; - object.property.type = "Identifier"; - object.property.name = clone(object.property.value); - - // obj.name && - // this.log( - // obj.name + - // "['" + - // object.property.name + - // "'] -> " + - // obj.name + - // "." + - // object.property.name - // ); - } - } - - if (object.type == "CallExpression") { - if (object.callee.type == "MemberExpression") { - var key = object.callee.computed - ? object.callee.property.value - : object.callee.property.name; - if (key == "toString" && object.arguments.length == 0) { - this.replace( - object, - BinaryExpression("+", Literal(""), clone(object.callee.object)) - ); - } - } - } - - // { "x": 1 } -> {x: 1} - if (object.type === "Property" || object.type === "MethodDefinition") { - if ( - object.key.type == "SequenceExpression" && - object.key.expressions.length == 1 - ) { - object.key = object.key.expressions[0]; - object.computed = true; - } - - if ( - object.key.type == "Literal" && - typeof object.key.value === "string" && - isValidIdentifier(object.key.value) - ) { - object.key.type = "Identifier"; - object.key.name = object.key.value; - object.computed = false; - } - } - - if (object.type == "VariableDeclarator") { - // undefined is not necessary - if (object.init && object.init.type == "Identifier") { - if (object.init.name == "undefined") { - object.init = null; - } - } - - if ( - object.id.type == "ObjectPattern" && - object.init && - object.init.type == "ObjectExpression" - ) { - if ( - object.id.properties.length === 1 && - object.init.properties.length === 1 - ) { - var key1 = object.id.properties[0].computed - ? object.id.properties[0].key.value - : object.id.properties[0].key.name; - var key2 = object.init.properties[0].computed - ? object.init.properties[0].key.value - : object.init.properties[0].key.name; - - // console.log(key1, key2); - - if (key1 && key2 && key1 === key2) { - object.id = object.id.properties[0].value; - object.init = object.init.properties[0].value; - } - } - } - - // check for redundant patterns - if ( - object.id.type == "ArrayPattern" && - object.init && - typeof object.init === "object" && - object.init.type == "ArrayExpression" - ) { - if ( - object.id.elements.length == 1 && - object.init.elements.length == 1 - ) { - object.id = object.id.elements[0]; - object.init = object.init.elements[0]; - } - } - } - - if (object.type == "Literal") { - return () => { - switch (typeof object.value) { - case "boolean": - this.replaceIdentifierOrLiteral( - object, - UnaryExpression("!", Literal(object.value ? 0 : 1)), - parents - ); - break; - } - }; - } - if (object.type == "Identifier") { - return () => { - var info = getIdentifierInfo(object, parents); - if (info.spec.isDefined || info.spec.isModified) return; - - if (object.name == "undefined" && !isForInitialize(object, parents)) { - this.replaceIdentifierOrLiteral( - object, - UnaryExpression("void", Literal(0)), - parents - ); - } else if (object.name == "Infinity") { - this.replaceIdentifierOrLiteral( - object, - BinaryExpression("/", Literal(1), Literal(0)), - parents - ); - } - }; - } - - if (object.type == "UnaryExpression" && object.operator == "!") { - if (object.argument.type == "Literal" && !object.argument.regex) { - this.replace(object, Literal(!object.argument.value)); - } - } - - if (object.type == "ConditionalExpression") { - if (object.test.type == "Literal" && !object.test.regex) { - this.replace( - object, - object.test.value ? object.consequent : object.alternate - ); - } - } - } -} diff --git a/src_old/transforms/opaquePredicates.ts b/src_old/transforms/opaquePredicates.ts deleted file mode 100644 index e6e5b72..0000000 --- a/src_old/transforms/opaquePredicates.ts +++ /dev/null @@ -1,242 +0,0 @@ -import Transform from "./transform"; -import { - Node, - BinaryExpression, - MemberExpression, - Identifier, - CallExpression, - Literal, - VariableDeclaration, - ObjectExpression, - Property, - FunctionExpression, - ArrayExpression, - LogicalExpression, - VariableDeclarator, - ConditionalExpression, - UnaryExpression, - ReturnStatement, - AssignmentPattern, -} from "../util/gen"; -import { - choice, - getRandomInteger, - getRandomString, - shuffle, -} from "../util/random"; -import { ObfuscateOrder } from "../order"; -import { clone, prepend } from "../util/insert"; -import Template from "../templates/template"; -import { ComputeProbabilityMap } from "../probability"; -import { ok } from "assert"; - -const testTypes = new Set([ - "ForStatement", - "WhileStatement", - "DoWhileStatement", - "IfStatement", - "ConditionExpression", -]); - -function isTestExpression(object: Node, parents: Node[]) { - if (!object || !parents[0]) { - return false; - } - - if (testTypes.has(parents[0].type) && parents[0].test === object) { - return true; - } - - return false; -} - -/** - * Changes test expression (such as if statements, for loops) to add predicates. - * - * Predicates are computed at runtime. - */ -export default class OpaquePredicates extends Transform { - undefinedVar: string; - nullVar: string; - numberVar: string; - - predicateName: string; - predicate: Node; - predicates: { [name: string]: Node }; - - gen: ReturnType; - made: number; - - constructor(o) { - super(o, ObfuscateOrder.OpaquePredicates); - - this.predicates = Object.create(null); - this.gen = this.getGenerator(); - this.made = 0; - } - - match(object: Node, parents: Node[]) { - return ( - (isTestExpression(object, parents) || object.type == "SwitchCase") && - !parents.find((x) => x.$multiTransformSkip || x.type == "AwaitExpression") - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - if (!ComputeProbabilityMap(this.options.opaquePredicates)) { - return; - } - this.made++; - if (this.made > 150) { - return; - } - - if (!this.predicate) { - this.predicateName = this.getPlaceholder(); - this.predicate = ObjectExpression([]); - - var tempName = this.getPlaceholder(); - - prepend( - parents[parents.length - 1] || object, - VariableDeclaration( - VariableDeclarator( - this.predicateName, - CallExpression( - FunctionExpression( - [], - [ - VariableDeclaration( - VariableDeclarator(tempName, this.predicate) - ), - ReturnStatement(Identifier(tempName)), - ] - ), - [] - ) - ) - ) - ); - } - - var expr = choice(Object.values(this.predicates)); - - if ( - !expr || - Math.random() < 0.5 / (Object.keys(this.predicates).length || 1) - ) { - var prop = this.gen.generate(); - var accessor = MemberExpression( - Identifier(this.predicateName), - Identifier(prop), - false - ); - switch (choice(["array", "number", "string"])) { - case "array": - var arrayProp = this.gen.generate(); - this.predicate.properties.push( - Property(Identifier(arrayProp), ArrayExpression([])) - ); - - var paramName = this.getPlaceholder(); - - this.predicate.properties.push( - Property( - Identifier(prop), - FunctionExpression( - [AssignmentPattern(Identifier(paramName), Literal("length"))], - new Template(` - if ( !${this.predicateName}.${arrayProp}[0] ) { - ${this.predicateName}.${arrayProp}.push(${getRandomInteger( - -100, - 100 - )}); - } - return ${this.predicateName}.${arrayProp}[${paramName}]; - `).compile() - ) - ) - ); - expr = CallExpression(accessor, []); - break; - - case "number": - this.predicate.properties.push( - Property(Identifier(prop), Literal(getRandomInteger(15, 90))) - ); - expr = BinaryExpression( - ">", - accessor, - Literal(getRandomInteger(-90, 10)) - ); - break; - - case "string": - var str = this.gen.generate(); - var index = getRandomInteger(0, str.length); - var fn = Math.random() > 0.5 ? "charAt" : "charCodeAt"; - - this.predicate.properties.push( - Property(Identifier(prop), Literal(str)) - ); - expr = BinaryExpression( - "==", - CallExpression(MemberExpression(accessor, Literal(fn), true), [ - Literal(index), - ]), - Literal(str[fn](index)) - ); - break; - } - - ok(expr); - this.predicates[prop] = expr; - - if (Math.random() > 0.8) { - shuffle(this.predicate.properties); - } - } - - var cloned = clone(expr); - if (object.type == "SwitchCase" && object.test) { - var matching: Node = Identifier(choice(["undefined", "null"])); - - var test = object.test; - - if (test.type == "Literal") { - if (typeof test.value === "number") { - matching = Literal(getRandomInteger(-250, 250)); - } else if (typeof test.value === "string") { - matching = Literal(getRandomString(4)); - } - } - - var conditionalExpression = ConditionalExpression( - cloned, - clone(test), - matching - ); - if (Math.random() > 0.5) { - conditionalExpression = ConditionalExpression( - UnaryExpression("!", cloned), - matching, - clone(test) - ); - } - - this.replace(test, conditionalExpression); - } else if (isTestExpression(object, parents)) { - if (object.type == "Literal" && !object.regex) { - if (object.value) { - this.replace(object, cloned); - } else { - this.replace(object, UnaryExpression("!", cloned)); - } - } else { - this.replace(object, LogicalExpression("&&", clone(object), cloned)); - } - } - }; - } -} diff --git a/src_old/transforms/preparation.ts b/src_old/transforms/preparation.ts deleted file mode 100644 index 31f7f20..0000000 --- a/src_old/transforms/preparation.ts +++ /dev/null @@ -1,254 +0,0 @@ -import Transform from "./transform"; - -import { - BlockStatement, - Identifier, - LabeledStatement, - Literal, - Node, - ReturnStatement, -} from "../util/gen"; -import { ObfuscateOrder } from "../order"; -import { clone, getFunction } from "../util/insert"; -import { getIdentifierInfo } from "../util/identifiers"; -import { isLoop } from "../util/compare"; -import { ExitCallback, walk } from "../traverse"; -import { variableFunctionName } from "../constants"; - -/** - * Preparation arranges the user's code into an AST the obfuscator can easily transform. - * - * ExplicitIdentifiers - * - `object.IDENTIFIER` -> `object['IDENTIFIER']` // Now String Concealing can apply on it - * - `{ IDENTIFIER: ... }` -> `{ "IDENTIFIER": ... }` - * - * ExplicitDeclarations - * - `var a,b,c` -> `var a; var b; var c;` // Now Stack can apply on it - * - * Block - * - `x => x * 2` -> `x => { return x * 2 }` // Change into Block Statements - * - `if(true) return` -> `if (true) { return }` - * - `while(a) a--;` -> `while(a) { a-- }` - * - * Label - * - `for(...) { break; }` -> `_1: for(...) { break _1; }` - * - `switch(v) { case 1...break }` -> `_2: switch(v) { case 1...break _2; }` - * - // Control Flow Flattening can safely apply now - */ -export default class Preparation extends Transform { - constructor(o) { - super(o, ObfuscateOrder.Preparation); - } - - match(object: Node, parents: Node[]) { - return !!object.type; - } - - transform(object: Node, parents: Node[]): void | ExitCallback { - // ExplicitIdentifiers - if (object.type === "Identifier") { - return this.transformExplicitIdentifiers(object, parents); - } - - // __JS_CONFUSER_VAR__ - Remove when Rename Variables is disabled - if ( - object.type === "CallExpression" && - object.callee.type === "Identifier" && - object.callee.name === variableFunctionName - ) { - if (object.arguments[0].type === "Identifier") { - if (!this.obfuscator.transforms["RenameVariables"]) { - return () => { - this.replace(object, Literal(object.arguments[0].name)); - }; - } - } - } - - // ExplicitDeclarations - if (object.type === "VariableDeclaration") { - return this.transformExplicitDeclarations(object, parents); - } - - // Block - switch (object.type) { - /** - * People use shortcuts and its harder to parse. - * - * - `if (a) b()` -> `if (a) { b() }` - * - Ensures all bodies are `BlockStatement`, not individual expression statements - */ - case "IfStatement": - if (object.consequent.type != "BlockStatement") { - object.consequent = BlockStatement([clone(object.consequent)]); - } - if (object.alternate && object.alternate.type != "BlockStatement") { - object.alternate = BlockStatement([clone(object.alternate)]); - } - break; - - case "WhileStatement": - case "WithStatement": - case "ForStatement": - case "ForOfStatement": - case "ForInStatement": - if (object.body.type != "BlockStatement") { - object.body = BlockStatement([clone(object.body)]); - } - break; - - case "ArrowFunctionExpression": - if (object.body.type !== "BlockStatement" && object.expression) { - object.body = BlockStatement([ReturnStatement(clone(object.body))]); - object.expression = false; - } - break; - } - - // Label - if ( - isLoop(object) || - (object.type == "BlockStatement" && - parents[0] && - parents[0].type == "LabeledStatement" && - parents[0].body === object) - ) { - return this.transformLabel(object, parents); - } - } - - /** - * Ensures every break; statement has a label to point to. - * - * This is because Control Flow Flattening adds For Loops which label-less break statements point to the nearest, - * when they actually need to point to the original statement. - */ - transformLabel(object: Node, parents: Node[]) { - return () => { - var currentLabel = - parents[0].type == "LabeledStatement" && parents[0].label.name; - - var label = currentLabel || this.getPlaceholder(); - - walk(object, parents, (o, p) => { - if (o.type == "BreakStatement" || o.type == "ContinueStatement") { - function isContinuableStatement(x) { - return isLoop(x) && x.type !== "SwitchStatement"; - } - function isBreakableStatement(x) { - return isLoop(x) || (o.label && x.type == "BlockStatement"); - } - - var fn = - o.type == "ContinueStatement" - ? isContinuableStatement - : isBreakableStatement; - - var loop = p.find(fn); - if (object == loop) { - if (!o.label) { - o.label = Identifier(label); - } - } - } - }); - - // Append label statement as this loop has none - if (!currentLabel) { - this.replace(object, LabeledStatement(label, { ...object })); - } - }; - } - - /** - * Transforms Identifiers (a.IDENTIFIER, {IDENTIFIER:...}) into string properties - */ - transformExplicitIdentifiers(object: Node, parents: Node[]) { - // Mark functions containing 'eval' - // Some transformations avoid functions that have 'eval' to not break them - if (object.name === "eval") { - var fn = getFunction(object, parents); - if (fn) { - fn.$requiresEval = true; - } - } - - var info = getIdentifierInfo(object, parents); - if (info.isPropertyKey || info.isAccessor) { - var propIndex = parents.findIndex( - (x) => x.type == "MethodDefinition" || x.type == "Property" - ); - - // Don't change constructor! - if (propIndex !== -1) { - if ( - parents[propIndex].type == "MethodDefinition" && - parents[propIndex].kind == "constructor" - ) { - return; - } - } - - this.replace(object, Literal(object.name)); - parents[0].computed = true; - parents[0].shorthand = false; - } - } - - /** - * Transforms VariableDeclaration into single declarations. - */ - transformExplicitDeclarations(object: Node, parents: Node[]) { - // for ( var x in ... ) {...} - var forIndex = parents.findIndex( - (x) => x.type == "ForInStatement" || x.type == "ForOfStatement" - ); - if ( - forIndex != -1 && - parents[forIndex].left == (parents[forIndex - 1] || object) - ) { - object.declarations.forEach((x) => { - x.init = null; - }); - return; - } - - var body = parents[0]; - if (isLoop(body) || body.type == "LabeledStatement") { - return; - } - - if (body.type == "ExportNamedDeclaration") { - return; - } - - if (!Array.isArray(body)) { - this.error(new Error("body is " + body.type)); - } - - if (object.declarations.length > 1) { - // Make singular - - var index = body.indexOf(object); - if (index == -1) { - this.error(new Error("index is -1")); - } - - var after = object.declarations.slice(1); - - body.splice( - index + 1, - 0, - ...after.map((x) => { - return { - type: "VariableDeclaration", - declarations: [clone(x)], - kind: object.kind, - }; - }) - ); - - object.declarations.length = 1; - } - } -} diff --git a/src_old/transforms/renameLabels.ts b/src_old/transforms/renameLabels.ts deleted file mode 100644 index 7b8bc55..0000000 --- a/src_old/transforms/renameLabels.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ObfuscateOrder } from "../order"; -import { walk } from "../traverse"; -import { isLoop } from "../util/compare"; -import { Identifier } from "../util/gen"; -import { clone } from "../util/insert"; -import Transform from "./transform"; - -/** - * Renames the labels to shorter names. - */ -export default class RenameLabels extends Transform { - gen: ReturnType; - - constructor(o) { - super(o, ObfuscateOrder.RenameLabels); - - this.gen = this.getGenerator("randomized"); - } - - match(object, parents) { - return object.type == "LabeledStatement"; - } - - transform(object, parents) { - return () => { - var newName = null; - var isRemovable = object.body.type !== "BlockStatement"; - var labelNeverUsed = true; - - walk(object, parents, (o, p) => { - if (o.type == "BreakStatement" || o.type == "ContinueStatement") { - function isContinuableStatement(x, stmtParents) { - return isLoop(x) && x.type !== "SwitchStatement"; - } - function isBreakableStatement(x, stmtParents) { - return ( - isLoop(x) || - (x.type == "BlockStatement" && - o.label && - stmtParents[0] && - stmtParents[0].type == "LabeledStatement") - ); - } - - var fn = - o.type == "ContinueStatement" - ? isContinuableStatement - : isBreakableStatement; - - var labelStatement = p.find((node, i) => { - return fn(node, p.slice(i + 1)); - }); - - if (o.label && o.label.name == object.label.name) { - if (object.body == labelStatement && isRemovable) { - // In same loop - - o.label = null; - } else { - if (!newName) { - newName = this.gen.generate(); - } - o.label = Identifier(newName); - labelNeverUsed = false; - } - } - } - }); - - if (newName) { - object.label = Identifier(newName); - } else if (isRemovable || labelNeverUsed) { - this.replace(object, clone(object.body)); - } - }; - } -} diff --git a/src_old/transforms/rgf.ts b/src_old/transforms/rgf.ts deleted file mode 100644 index b2cd1f6..0000000 --- a/src_old/transforms/rgf.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { compileJsSync } from "../compiler"; -import { predictableFunctionTag, reservedIdentifiers } from "../constants"; -import Obfuscator from "../obfuscator"; -import { ObfuscateOrder } from "../order"; -import { ComputeProbabilityMap } from "../probability"; -import { FunctionLengthTemplate } from "../templates/functionLength"; -import { ObjectDefineProperty } from "../templates/globals"; -import Template from "../templates/template"; -import { walk } from "../traverse"; -import { - ArrayExpression, - BlockStatement, - CallExpression, - ExpressionStatement, - Identifier, - Literal, - LogicalExpression, - MemberExpression, - NewExpression, - Node, - ReturnStatement, - ThisExpression, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { getIdentifierInfo } from "../util/identifiers"; -import { - prepend, - getDefiningContext, - computeFunctionLength, -} from "../util/insert"; -import Integrity from "./lock/integrity"; -import Transform from "./transform"; - -/** - * Converts function to `new Function("..code..")` syntax as an alternative to `eval`. Eval is disabled in many environments. - * - * `new Function("..code..")` runs in an isolated context, meaning all local variables are undefined and throw errors. - * - * Rigorous checks are in place to only include pure functions. - * - * `flatten` can attempt to make function reference-less. Recommended to have flatten enabled with RGF. - */ -export default class RGF extends Transform { - // Array of all the `new Function` calls - arrayExpressionElements: Node[]; - // The name of the array holding all the `new Function` expressions - arrayExpressionName: string; - - functionLengthName: string; - - getFunctionLengthName(parents: Node[]) { - if (!this.functionLengthName) { - this.functionLengthName = this.getPlaceholder(); - } - - return this.functionLengthName; - } - - constructor(o) { - super(o, ObfuscateOrder.RGF); - - this.arrayExpressionName = this.getPlaceholder() + "_rgf"; - this.arrayExpressionElements = []; - } - - apply(tree: Node): void { - super.apply(tree); - - // Only add the array if there were converted functions - if (this.arrayExpressionElements.length > 0) { - var variableDeclaration = VariableDeclaration( - VariableDeclarator( - Identifier(this.arrayExpressionName), - ArrayExpression(this.arrayExpressionElements) - ) - ); - - var nodes: Node[] = [variableDeclaration]; - - if (this.options.lock?.tamperProtection) { - // The name of the variable flag if eval is safe to use - var tamperProtectionCheckName = this.getPlaceholder() + "_rgfEvalCheck"; - - variableDeclaration.declarations[0].init = LogicalExpression( - "&&", - Identifier(tamperProtectionCheckName), - { ...variableDeclaration.declarations[0].init } - ); - - nodes.unshift( - ...new Template(` - var ${tamperProtectionCheckName} = false; - eval(${this.jsConfuserVar(tamperProtectionCheckName)} + "=true"); - if(!${tamperProtectionCheckName}) { - {countermeasures} - } - `).compile({ - countermeasures: this.lockTransform.getCounterMeasuresCode( - tree, - [] - ), - }) - ); - } - - prepend(tree, ...nodes); - } - - // The function.length helper function must be placed last - if (this.functionLengthName) { - prepend( - tree, - FunctionLengthTemplate.single({ - name: this.functionLengthName, - ObjectDefineProperty: this.createInitVariable(ObjectDefineProperty, [ - tree, - ]), - }) - ); - } - } - - match(object, parents) { - return ( - (object.type === "FunctionDeclaration" || - object.type === "FunctionExpression") && // Does not apply to Arrow functions - !object.async && // Does not apply to async/generator functions - !object.generator - ); - } - - transform(object: Node, parents: Node[]) { - // Discard getter/setter methods - if (parents[0].type === "Property" && parents[0].value === object) { - if ( - parents[0].method || - parents[0].kind === "get" || - parents[0].kind === "set" - ) { - return; - } - } - - // Discard class methods - if (parents[0].type === "MethodDefinition" && parents[0].value === object) { - return; - } - - // Avoid applying to the countermeasures function - if (typeof this.options.lock?.countermeasures === "string") { - // function countermeasures(){...} - if ( - object.type === "FunctionDeclaration" && - object.id.type === "Identifier" && - object.id.name === this.options.lock.countermeasures - ) { - return; - } - - // var countermeasures = function(){...} - if ( - parents[0].type === "VariableDeclarator" && - parents[0].init === object && - parents[0].id.type === "Identifier" && - parents[0].id.name === this.options.lock.countermeasures - ) { - return; - } - } - - // Check user option - if (!ComputeProbabilityMap(this.options.rgf, (x) => x, object?.id?.name)) - return; - - // Discard functions that use 'eval' function - if (object.$requiresEval) return; - - // Check for 'this', 'arguments' (not allowed!) - var isIllegal = false; - walk(object, parents, (o, p) => { - if ( - o.type === "ThisExpression" || - o.type === "Super" || - (o.type === "Identifier" && o.name === "arguments") - ) { - isIllegal = true; - return "EXIT"; - } - }); - - if (isIllegal) return; - - return () => { - // Make sure function is 'reference-less' - var definedMap = new Map>(); - var isReferenceLess = true; - var identifierPreventingTransformation: string; - - walk(object, parents, (o, p) => { - if ( - o.type === "Identifier" && - o.name !== this.arrayExpressionName && - !reservedIdentifiers.has(o.name) && - !this.options.globalVariables.has(o.name) - ) { - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced) { - return; - } - - if (info.spec.isDefined) { - // Add to defined map - var definingContext = getDefiningContext(o, p); - - if (!definedMap.has(definingContext)) { - definedMap.set(definingContext, new Set([o.name])); - } else { - definedMap.get(definingContext).add(o.name); - } - } else { - // This approach is dirty and does not account for hoisted FunctionDeclarations - var isDefinedAbove = false; - for (var pNode of p) { - if (definedMap.has(pNode)) { - if (definedMap.get(pNode).has(o.name)) { - isDefinedAbove = true; - break; - } - } - } - - if (!isDefinedAbove) { - isReferenceLess = false; - identifierPreventingTransformation = o.name; - - return "EXIT"; - } - } - } - }); - - // This function is not 'reference-less', cannot be RGF'd - if (!isReferenceLess) { - if (object.id) { - this.log( - `${object?.id?.name}() cannot be transformed because of ${identifierPreventingTransformation}` - ); - } - return; - } - - // Since `new Function` is completely isolated, create an entire new obfuscator and run remaining transformations. - // RGF runs early and needs completed code before converting to a string. - // (^ the variables haven't been renamed yet) - var obfuscator = new Obfuscator({ - ...this.options, - stringEncoding: false, - compact: true, - }); - - if (obfuscator.options.lock) { - obfuscator.options.lock = { ...obfuscator.options.lock }; - delete obfuscator.options.lock.countermeasures; - - // Integrity will not recursively apply to RGF'd functions. This is intended. - var lockTransform = obfuscator.transforms["Lock"]; - if (lockTransform) { - lockTransform.before = lockTransform.before.filter( - (beforeTransform) => !(beforeTransform instanceof Integrity) - ); - } - } - - var transforms = obfuscator.array.filter( - (x) => x.priority > this.priority - ); - - var embeddedFunctionName = this.getPlaceholder(); - - var embeddedFunction = { - type: "FunctionDeclaration", - id: Identifier(embeddedFunctionName), - body: BlockStatement([...object.body.body]), - params: object.params, - async: false, - generator: false, - }; - - // The new program will look like this - // new Function(` - // var rgf_array = this[0] - // function greet(message){ - // console.log(message) - // } - // return greet.apply(this[1], arguments) - // `) - // - // And called like - // f.apply([ rgf_array, this ], arguments) - var tree = { - type: "Program", - body: [ - VariableDeclaration( - VariableDeclarator( - this.arrayExpressionName, - MemberExpression(ThisExpression(), Literal(0)) - ) - ), - embeddedFunction, - ReturnStatement( - CallExpression( - MemberExpression( - Identifier(embeddedFunctionName), - Literal("apply"), - true - ), - [ - MemberExpression(ThisExpression(), Literal(1)), - Identifier("arguments"), - ] - ) - ), - ], - }; - - transforms.forEach((transform) => { - transform.apply(tree); - }); - - var toString = compileJsSync(tree, obfuscator.options); - - // new Function(code) - var newFunctionExpression: Node = NewExpression(Identifier("Function"), [ - Literal(toString), - ]); - - if (this.options.lock?.tamperProtection) { - // If tamper protection is enabled, wrap the function in an eval - var randomName = this.getGenerator("randomized").generate(); - newFunctionExpression = CallExpression(Identifier("eval"), [ - Literal(`function ${randomName}(){ ${toString} } ${randomName}`), - ]); - } - - // The index where this function is placed in the array - var newFunctionExpressionIndex = this.arrayExpressionElements.length; - - // Add it to the array - this.arrayExpressionElements.push(newFunctionExpression); - - // The member expression to retrieve this function - var memberExpression: Node = MemberExpression( - Identifier(this.arrayExpressionName), - Literal(newFunctionExpressionIndex), - true - ); - - var originalFunctionLength = computeFunctionLength(object.params); - - // Replace based on type - - // (1) Function Declaration: - // - Replace body with call to new function - if (object.type === "FunctionDeclaration") { - object.body = BlockStatement([ - ReturnStatement( - CallExpression( - MemberExpression(memberExpression, Literal("apply"), true), - [ - ArrayExpression([ - Identifier(this.arrayExpressionName), - ThisExpression(), - ]), - Identifier("arguments"), - ] - ) - ), - ]); - - // The parameters are no longer needed ('arguments' is used to capture them) - object.params = []; - - // The function is no longer guaranteed to not have extraneous parameters passed in - object[predictableFunctionTag] = false; - - if ( - this.options.preserveFunctionLength && - originalFunctionLength !== 0 - ) { - var body = parents[0] as unknown as Node[]; - - body.splice( - body.indexOf(object), - 0, - ExpressionStatement( - CallExpression(Identifier(this.getFunctionLengthName(parents)), [ - Identifier(object.id.name), - Literal(originalFunctionLength), - ]) - ) - ); - } - return; - } - - // (2) Function Expression: - // - Replace expression with member expression pointing to new function - if (object.type === "FunctionExpression") { - if ( - this.options.preserveFunctionLength && - originalFunctionLength !== 0 - ) { - memberExpression = CallExpression( - Identifier(this.getFunctionLengthName(parents)), - [memberExpression, Literal(originalFunctionLength)] - ); - } - this.replace(object, memberExpression); - return; - } - }; - } -} diff --git a/src_old/transforms/shuffle.ts b/src_old/transforms/shuffle.ts deleted file mode 100644 index 86e0a07..0000000 --- a/src_old/transforms/shuffle.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../order"; -import { ComputeProbabilityMap } from "../probability"; -import Template from "../templates/template"; -import { - BinaryExpression, - CallExpression, - ExpressionStatement, - ForStatement, - FunctionExpression, - Identifier, - Literal, - MemberExpression, - ReturnStatement, - UpdateExpression, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { clone, prepend } from "../util/insert"; -import { getRandomInteger } from "../util/random"; -import Transform from "./transform"; - -var Hash = function (s) { - var a = 1, - c = 0, - h, - o; - if (s) { - a = 0; - for (h = s.length - 1; h >= 0; h--) { - o = s.charCodeAt(h); - a = ((a << 6) & 268435455) + o + (o << 14); - c = a & 266338304; - a = c !== 0 ? a ^ (c >> 21) : a; - } - } - return ~~String(a).slice(0, 3); -}; - -var HashTemplate = new Template( - ` - var {name} = function(arr) { - var s = arr.map(x=>x+"").join(''), a = 1, c = 0, h, o; - if (s) { - a = 0; - for (h = s.length - 1; h >= 0; h--) { - o = s.charCodeAt(h); - a = (a<<6&268435455) + o + (o<<14); - c = a & 266338304; - a = c!==0?a^c>>21:a; - } - } - return ~~String(a).slice(0, 3); -};` -); - -/** - * Shuffles arrays initial order of elements. - * - * "Un-shuffles" the array at runtime. - */ -export default class Shuffle extends Transform { - hashName: string; - constructor(o) { - super(o, ObfuscateOrder.Shuffle); - } - - match(object, parents) { - return ( - object.type == "ArrayExpression" && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object, parents) { - return () => { - if (object.elements.length < 3) { - // Min: 4 elements - return; - } - - function isAllowed(e) { - return ( - e.type == "Literal" && - { number: 1, boolean: 1, string: 1 }[typeof e.value] - ); - } - - // Only arrays with only literals - var illegal = object.elements.find((x) => !isAllowed(x)); - - if (illegal) { - return; - } - - var mapped = object.elements.map((x) => x.value); - - var mode = ComputeProbabilityMap(this.options.shuffle, (x) => x, mapped); - if (mode) { - var shift = getRandomInteger( - 1, - Math.min(60, object.elements.length * 6) - ); - - var expr = Literal(shift); - var name = this.getPlaceholder(); - - if (mode == "hash") { - var str = mapped.join(""); - shift = Hash(str); - - if (!this.hashName) { - prepend( - parents[parents.length - 1], - HashTemplate.single({ - name: (this.hashName = this.getPlaceholder()), - }) - ); - } - - for (var i = 0; i < shift; i++) { - object.elements.push(object.elements.shift()); - } - - var shiftedHash = Hash( - object.elements.map((x) => x.value + "").join("") - ); - - expr = BinaryExpression( - "-", - CallExpression(Identifier(this.hashName), [Identifier(name)]), - Literal(shiftedHash - shift) - ); - } else { - for (var i = 0; i < shift; i++) { - object.elements.push(object.elements.shift()); - } - } - - var code = []; - - var iName = this.getPlaceholder(); - - var inPlace = false; - var inPlaceName; - var inPlaceBody; - var inPlaceIndex; - - var varDeclarator = parents[0]; - if (varDeclarator.type == "VariableDeclarator") { - var varDec = parents[2]; - if (varDec.type == "VariableDeclaration" && varDec.kind !== "const") { - var body = parents[3]; - if ( - varDec.declarations.length == 1 && - Array.isArray(body) && - varDeclarator.id.type === "Identifier" && - varDeclarator.init === object - ) { - inPlaceIndex = body.indexOf(varDec); - inPlaceBody = body; - inPlace = inPlaceIndex !== -1; - inPlaceName = varDeclarator.id.name; - } - } - } - - if (mode !== "hash") { - var varPrefix = this.getPlaceholder(); - code.push( - new Template(` - for ( var ${varPrefix}x = 16; ${varPrefix}x%4 === 0; ${varPrefix}x++) { - var ${varPrefix}z = 0; - ${ - inPlace ? `${inPlaceName} = ${name}` : name - } = ${name}.concat((function(){ - ${varPrefix}z++; - if(${varPrefix}z === 1){ - return []; - } - - for( var ${varPrefix}i = ${getRandomInteger( - 5, - 105 - )}; ${varPrefix}i; ${varPrefix}i-- ){ - ${name}.unshift(${name}.pop()); - } - return []; - })()); - } - `).single() - ); - } - - code.push( - ForStatement( - VariableDeclaration(VariableDeclarator(iName, expr)), - Identifier(iName), - UpdateExpression("--", Identifier(iName), false), - [ - // ${name}.unshift(${name}.pop()); - ExpressionStatement( - CallExpression( - MemberExpression( - Identifier(name), - Identifier("unshift"), - false - ), - [ - CallExpression( - MemberExpression( - Identifier(name), - Identifier("pop"), - false - ), - [] - ), - ] - ) - ), - ] - ) - ); - - if (inPlace) { - var varDeclarator = parents[0]; - ok(i != -1); - - inPlaceBody.splice( - inPlaceIndex + 1, - 0, - VariableDeclaration( - VariableDeclarator(name, Identifier(varDeclarator.id.name)) - ), - ...code - ); - } - - if (!inPlace) { - this.replace( - object, - CallExpression( - FunctionExpression( - [Identifier(name)], - [...code, ReturnStatement(Identifier(name))] - ), - [clone(object)] - ) - ); - } - } - }; - } -} diff --git a/src_old/transforms/stack.ts b/src_old/transforms/stack.ts deleted file mode 100644 index c5f5573..0000000 --- a/src_old/transforms/stack.ts +++ /dev/null @@ -1,557 +0,0 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../order"; -import { ComputeProbabilityMap } from "../probability"; -import Template from "../templates/template"; -import { walk } from "../traverse"; -import { - AssignmentExpression, - BinaryExpression, - CallExpression, - ExpressionStatement, - Identifier, - IfStatement, - Literal, - MemberExpression, - Node, - RestElement, - ReturnStatement, - SequenceExpression, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { getIdentifierInfo } from "../util/identifiers"; -import { - computeFunctionLength, - getBlockBody, - getDefiningContext, - getReferencingContexts, - isForInitialize, - isFunction, - isVarContext, - prepend, -} from "../util/insert"; -import { chance, choice, getRandomInteger } from "../util/random"; -import Transform from "./transform"; -import { noRenameVariablePrefix } from "../constants"; -import { FunctionLengthTemplate } from "../templates/functionLength"; -import { ObjectDefineProperty } from "../templates/globals"; -import { isJSConfuserVar } from "../util/guard"; - -export default class Stack extends Transform { - mangledExpressionsMade: number; - - functionLengthName: string; - - constructor(o) { - super(o, ObfuscateOrder.Stack); - - this.mangledExpressionsMade = 0; - } - - match(object: Node, parents: Node[]) { - return ( - isFunction(object) && - !object.params.find((x) => x.type !== "Identifier") && - object.body.type === "BlockStatement" && - !parents.find((x) => x.$multiTransformSkip) && - !object.$requiresEval - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - // Uncaught SyntaxError: Getter must not have any formal parameters. - // Uncaught SyntaxError: Setter must have exactly one formal parameter - var propIndex = parents.findIndex( - (x) => x.type === "Property" || x.type === "MethodDefinition" - ); - if (propIndex !== -1) { - if (parents[propIndex].value === (parents[propIndex - 1] || object)) { - if (parents[propIndex].kind !== "init" || parents[propIndex].method) { - return; - } - } - } - - // Don't apply to functions with 'use strict' directive - if (getBlockBody(object.body)[0]?.directive) { - return; - } - - if (!ComputeProbabilityMap(this.options.stack)) { - return; - } - - var defined = new Set(); - var referenced = new Set(); - var illegal = new Set(); - - /** - * Maps old names to new indices - */ - var subscripts = new Map(); - var deadValues = Object.create(null); - - var propertyGen = this.getGenerator(); - - function isTransformableFunction(functionNode: Node) { - if (functionNode.$requiresEval) return false; - - // Check for 'this' - var isIllegal = false; - walk(functionNode.body, [], (o, p) => { - if (o.type === "ThisExpression") { - isIllegal = true; - return "EXIT"; - } - }); - - return !isIllegal; - } - - function setSubscript(string, index) { - subscripts.set(string, index + ""); - } - - object.params.forEach((param) => { - ok(param.name); - defined.add(param.name); - - setSubscript(param.name, subscripts.size); - }); - - var startingSize = subscripts.size; - var isIllegal = false; - - walk(object.body, [object, ...parents], (o, p) => { - if (o.type === "Identifier" && o.name === "arguments") { - isIllegal = true; - return "EXIT"; - } - - if (o.type == "Identifier") { - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced || info.spec.isExported) { - return; - } - - var c = info.spec.isDefined - ? getDefiningContext(o, p) - : getReferencingContexts(o, p).find((x) => isVarContext(x)); - - if (c !== object) { - // this.log(o.name + " is illegal due to different context"); - illegal.add(o.name); - } - - if (o.name.startsWith(noRenameVariablePrefix)) { - illegal.add(o.name); - } - - // Ignore __JS_CONFUSER_VAR__() - if (isJSConfuserVar(p)) { - illegal.add(o.name); - } - - if ( - info.isClauseParameter || - info.isFunctionParameter || - isForInitialize(o, p) - ) { - // this.log( - // o.name + " is illegal due to clause parameter/function parameter" - // ); - illegal.add(o.name); - } - if (o.hidden) { - illegal.add(o.name); - } - - if (info.spec.isDefined) { - if (defined.has(o.name)) { - illegal.add(o.name); - } - - if (info.isFunctionDeclaration) { - ok(p[0].type === "FunctionDeclaration"); - if ( - p[0] !== object.body.body[0] || - !isTransformableFunction(p[0]) - ) { - illegal.add(o.name); - } - } - - // The new accessors will either be numbered: [index] or as a string .string - var newSubscript = choice([ - subscripts.size, - propertyGen.generate(), - ]); - - setSubscript(o.name, newSubscript); - defined.add(o.name); - - // Stack can only process single VariableDeclarations - var varIndex = p.findIndex((x) => x.type == "VariableDeclaration"); - - if (varIndex !== -1) { - // Invalid 'id' property (must be Identifier) - if (varIndex !== 2) { - illegal.add(o.name); - } else if (p[varIndex].declarations.length > 1) { - illegal.add(o.name); - } else { - var value = p[varIndex].declarations[0].init; - if (value && !isTransformableFunction(value)) { - illegal.add(o.name); - } - } - } - } else if (info.spec.isReferenced) { - if (info.spec.isModified) { - var assignmentIndex = p.findIndex( - (x) => x.type === "AssignmentExpression" - ); - if (assignmentIndex !== -1) { - var value = p[assignmentIndex].right; - if (value && !isTransformableFunction(value)) { - illegal.add(o.name); - } - } - } - - referenced.add(o.name); - } - } - }); - - if (isIllegal) return; - - illegal.forEach((name) => { - defined.delete(name); - referenced.delete(name); - subscripts.delete(name); - }); - - referenced.forEach((name) => { - if (!defined.has(name)) { - subscripts.delete(name); - } - }); - - if (object.params.find((x) => illegal.has(x.name))) { - return; - } - - if (!subscripts.size) { - return; - } - - const numberLiteral = (number: number | string, depth = 0): Node => { - ok(number === number); - if ( - typeof number !== "number" || - !Object.keys(deadValues).length || - depth > 4 || - chance(75 + depth * 15 + this.mangledExpressionsMade / 25) - ) { - return Literal(number); - } - this.mangledExpressionsMade++; - - var opposingIndex = choice(Object.keys(deadValues)); - if (typeof opposingIndex === "undefined") { - return Literal(number); - } - var actualValue = deadValues[opposingIndex]; - - ok(typeof actualValue === "number"); - - return BinaryExpression( - "-", - MemberExpression( - Identifier(stackName), - numberLiteral( - isNaN(parseFloat(opposingIndex)) - ? opposingIndex - : parseFloat(opposingIndex), - depth + 1 - ), - true - ), - numberLiteral(actualValue - number, depth + 1) - ); - }; - - function getMemberExpression(index) { - ok(typeof index === "string", typeof index); - return MemberExpression( - Identifier(stackName), - numberLiteral(isNaN(parseFloat(index)) ? index : parseFloat(index)), - true - ); - } - - var stackName = this.getPlaceholder() + "_stack"; - - const scan = (o, p) => { - if (o.type == "Identifier") { - var index = subscripts.get(o.name); - if (typeof index !== "undefined") { - var info = getIdentifierInfo(o, p); - if (!info.spec.isReferenced) { - return; - } - - var member = getMemberExpression(index); - - if (info.spec.isDefined) { - if (info.isVariableDeclaration) { - walk(p[2], p.slice(3), (oo, pp) => { - if (oo != o) { - return scan(oo, pp); - } - }); - - this.replace( - p[2], - ExpressionStatement( - AssignmentExpression( - "=", - member, - p[0].init || Identifier("undefined") - ) - ) - ); - return; - } else if (info.isFunctionDeclaration) { - walk(p[0], p.slice(1), (oo, pp) => { - if (oo != o) { - return scan(oo, pp); - } - }); - - this.replace( - p[0], - ExpressionStatement( - AssignmentExpression("=", member, { - ...p[0], - type: "FunctionExpression", - id: null, - expression: false, - }) - ) - ); - return; - } else if (info.isClassDeclaration) { - walk(p[0], p.slice(1), (oo, pp) => { - if (oo != o) { - return scan(oo, pp); - } - }); - - this.replace( - p[0], - ExpressionStatement( - AssignmentExpression("=", member, { - ...p[0], - type: "ClassExpression", - }) - ) - ); - return; - } - } - - if (info.spec.isReferenced) { - this.replace(o, member); - } - } - } - - if ( - o.type == "Literal" && - typeof o.value === "number" && - Math.floor(o.value) === o.value && - Math.abs(o.value) < 100_000 && - p.find((x) => isFunction(x)) === object && - chance(50) - ) { - return () => { - this.replaceIdentifierOrLiteral(o, numberLiteral(o.value, 0), p); - }; - } - }; - - var rotateNodes: { [index: number]: Node } = Object.create(null); - - object.body.body.forEach((stmt, index) => { - var isFirst = index == 0; - - if (isFirst || chance(50 - index * 10)) { - var exprs = []; - - var changes = getRandomInteger(1, 3); - - for (var i = 0; i < changes; i++) { - var expr; - var type = choice(["set", "deadValue"]); - - var valueSet = new Set([ - ...Array.from(subscripts.values()), - ...Object.keys(deadValues), - ]); - var newIndex; - var i = 0; - do { - newIndex = choice([ - propertyGen.generate(), - getRandomInteger(0, 250 + subscripts.size + i * 1000) + "", - ]); - i++; - } while (valueSet.has(newIndex)); - - switch (type) { - case "set": - var randomName = choice(Array.from(subscripts.keys())); - var currentIndex = subscripts.get(randomName); - - expr = AssignmentExpression( - "=", - getMemberExpression(newIndex), - getMemberExpression(currentIndex) - ); - - ok( - typeof deadValues[newIndex] === "undefined", - deadValues[newIndex] - ); - setSubscript(randomName, newIndex); - break; - - case "deadValue": - var rand = getRandomInteger(-150, 150); - - // modify an already existing dead value index - if (chance(50)) { - var alreadyExisting = choice(Object.keys(deadValues)); - - if (typeof alreadyExisting === "string") { - newIndex = alreadyExisting; - } - } - - expr = AssignmentExpression( - "=", - getMemberExpression(newIndex), - numberLiteral(rand) - ); - - deadValues[newIndex] = rand; - break; - } - - exprs.push(expr); - } - rotateNodes[index] = ExpressionStatement(SequenceExpression(exprs)); - } - - walk( - stmt, - [object.body.body, object.body, object, ...parents], - (o, p) => { - return scan(o, p); - } - ); - - if (stmt.type == "ReturnStatement") { - var opposing = choice(Object.keys(deadValues)); - if (typeof opposing === "string") { - this.replace( - stmt, - IfStatement( - BinaryExpression( - ">", - getMemberExpression(opposing), - numberLiteral( - deadValues[opposing] + getRandomInteger(40, 140) - ) - ), - [ - ReturnStatement( - getMemberExpression(getRandomInteger(-250, 250) + "") - ), - ], - [ReturnStatement(stmt.argument)] - ) - ); - } - } - }); - - // Add in the rotation nodes - Object.keys(rotateNodes).forEach((index, i) => { - object.body.body.splice(parseInt(index) + i, 0, rotateNodes[index]); - }); - - // Preserve function.length property - var originalFunctionLength = computeFunctionLength(object.params); - - // Set the params for this function to be the stack array - object.params = [RestElement(Identifier(stackName))]; - - // Ensure the array is correct length - prepend( - object.body, - new Template(`${stackName}["length"] = ${startingSize}`).single() - ); - - if (this.options.preserveFunctionLength && originalFunctionLength !== 0) { - if (!this.functionLengthName) { - this.functionLengthName = this.getPlaceholder(); - prepend( - parents[parents.length - 1] || object, - FunctionLengthTemplate.single({ - name: this.functionLengthName, - ObjectDefineProperty: this.createInitVariable( - ObjectDefineProperty, - parents - ), - }) - ); - } - - if (object.type === "FunctionDeclaration") { - var body = parents[0]; - if (Array.isArray(body)) { - var index = body.indexOf(object); - - body.splice( - index, - 0, - ExpressionStatement( - CallExpression(Identifier(this.functionLengthName), [ - Identifier(object.id.name), - Literal(originalFunctionLength), - ]) - ) - ); - } - } else { - ok( - object.type === "FunctionExpression" || - object.type === "ArrowFunctionExpression" - ); - - this.replace( - object, - CallExpression(Identifier(this.functionLengthName), [ - { ...object }, - Literal(originalFunctionLength), - ]) - ); - } - } - }; - } -} diff --git a/src_old/transforms/string/encoding.ts b/src_old/transforms/string/encoding.ts deleted file mode 100644 index dfb9a88..0000000 --- a/src_old/transforms/string/encoding.ts +++ /dev/null @@ -1,250 +0,0 @@ -import Template from "../../templates/template"; -import { Literal } from "../../util/gen"; -import { choice, shuffle } from "../../util/random"; - -/** - * Defines an encoding implementation the Obfuscator - */ -export interface EncodingImplementation { - identity: string; - - encode(s: string): string; - decode(s: string): string; - template: Template; -} - -let _hasAllEncodings = false; -export function hasAllEncodings() { - return _hasAllEncodings; -} - -export function createEncodingImplementation(): EncodingImplementation { - if (_hasAllEncodings) { - return EncodingImplementations[ - choice(Object.keys(EncodingImplementations)) - ]; - } - - // create base91 encoding - let strTable = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"'; - - // shuffle table - strTable = shuffle(strTable.split("")).join(""); - - let identity = "base91_" + strTable; - - if (EncodingImplementations.hasOwnProperty(identity)) { - _hasAllEncodings = true; - return EncodingImplementations[identity]; - } - - var encodingImplementation = { - identity, - encode(str) { - const table = strTable; - - const raw = Buffer.from(str, "utf-8"); - const len = raw.length; - let ret = ""; - - let n = 0; - let b = 0; - - for (let i = 0; i < len; i++) { - b |= raw[i] << n; - n += 8; - - if (n > 13) { - let v = b & 8191; - if (v > 88) { - b >>= 13; - n -= 13; - } else { - v = b & 16383; - b >>= 14; - n -= 14; - } - ret += table[v % 91] + table[(v / 91) | 0]; - } - } - - if (n) { - ret += table[b % 91]; - if (n > 7 || b > 90) ret += table[(b / 91) | 0]; - } - - return ret; - }, - decode(str) { - const table = strTable; - - const raw = "" + (str || ""); - const len = raw.length; - const ret = []; - - let b = 0; - let n = 0; - let v = -1; - - for (let i = 0; i < len; i++) { - const p = table.indexOf(raw[i]); - if (p === -1) continue; - if (v < 0) { - v = p; - } else { - v += p * 91; - b |= v << n; - n += (v & 8191) > 88 ? 13 : 14; - do { - ret.push(b & 0xff); - b >>= 8; - n -= 8; - } while (n > 7); - v = -1; - } - } - - if (v > -1) { - ret.push((b | (v << n)) & 0xff); - } - - return Buffer.from(ret).toString("utf-8"); - }, - template: new Template(` - function {__fnName__}(str){ - var table = {__strTable__}; - - var raw = "" + (str || ""); - var len = raw.length; - var ret = []; - - var b = 0; - var n = 0; - var v = -1; - - for (var i = 0; i < len; i++) { - var p = table.indexOf(raw[i]); - if (p === -1) continue; - if (v < 0) { - v = p; - } else { - v += p * 91; - b |= v << n; - n += (v & 8191) > 88 ? 13 : 14; - do { - ret.push(b & 0xff); - b >>= 8; - n -= 8; - } while (n > 7); - v = -1; - } - } - - if (v > -1) { - ret.push((b | (v << n)) & 0xff); - } - - return {__bufferToString__}(ret); - } - `).setDefaultVariables({ - __strTable__: Literal(strTable), - }), - }; - - EncodingImplementations[identity] = encodingImplementation; - return encodingImplementation; -} - -export const EncodingImplementations: { - [encodingIdentity: string]: EncodingImplementation; -} = { - /* ascii85: { This implementation is flaky and causes decoding errors - encode(a) { - var b, c, d, e, f, g, h, i, j, k; - // @ts-ignore - for ( - // @ts-ignore - !/[^\x00-\xFF]/.test(a), - b = "\x00\x00\x00\x00".slice(a.length % 4 || 4), - a += b, - c = [], - d = 0, - e = a.length; - e > d; - d += 4 - ) - (f = - (a.charCodeAt(d) << 24) + - (a.charCodeAt(d + 1) << 16) + - (a.charCodeAt(d + 2) << 8) + - a.charCodeAt(d + 3)), - 0 !== f - ? ((k = f % 85), - (f = (f - k) / 85), - (j = f % 85), - (f = (f - j) / 85), - (i = f % 85), - (f = (f - i) / 85), - (h = f % 85), - (f = (f - h) / 85), - (g = f % 85), - c.push(g + 33, h + 33, i + 33, j + 33, k + 33)) - : c.push(122); - return ( - (function (a, b) { - for (var c = b; c > 0; c--) a.pop(); - })(c, b.length), - "<~" + String.fromCharCode.apply(String, c) + "~>" - ); - }, - decode(a) { - var c, - d, - e, - f, - g, - h = String, - l = "length", - w = 255, - x = "charCodeAt", - y = "slice", - z = "replace"; - for ( - "<~" === a[y](0, 2) && "~>" === a[y](-2), - a = a[y](2, -2)[z](/s/g, "")[z]("z", "!!!!!"), - c = "uuuuu"[y](a[l] % 5 || 5), - a += c, - e = [], - f = 0, - g = a[l]; - g > f; - f += 5 - ) - (d = - 52200625 * (a[x](f) - 33) + - 614125 * (a[x](f + 1) - 33) + - 7225 * (a[x](f + 2) - 33) + - 85 * (a[x](f + 3) - 33) + - (a[x](f + 4) - 33)), - e.push(w & (d >> 24), w & (d >> 16), w & (d >> 8), w & d); - return ( - (function (a, b) { - for (var c = b; c > 0; c--) a.pop(); - })(e, c[l]), - h.fromCharCode.apply(h, e) - ); - }, - template: Template(` - function {name}(a, LL = ["fromCharCode", "apply"]) { - var c, d, e, f, g, h = String, l = "length", w = 255, x = "charCodeAt", y = "slice", z = "replace"; - for ("<~" === a[y](0, 2) && "~>" === a[y](-2), a = a[y](2, -2)[z](/\s/g, "")[z]("z", "!!!!!"), - c = "uuuuu"[y](a[l] % 5 || 5), a += c, e = [], f = 0, g = a[l]; g > f; f += 5) d = 52200625 * (a[x](f) - 33) + 614125 * (a[x](f + 1) - 33) + 7225 * (a[x](f + 2) - 33) + 85 * (a[x](f + 3) - 33) + (a[x](f + 4) - 33), - e.push(w & d >> 24, w & d >> 16, w & d >> 8, w & d); - return function(a, b) { - for (var c = b; c > 0; c--) a.pop(); - }(e, c[l]), h[LL[0]][LL[1]](h, e); - } - `), - }, */ -}; diff --git a/src_old/transforms/string/stringCompression.ts b/src_old/transforms/string/stringCompression.ts deleted file mode 100644 index 11b440e..0000000 --- a/src_old/transforms/string/stringCompression.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { ComputeProbabilityMap } from "../../probability"; -import Template from "../../templates/template"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { - AssignmentExpression, - BinaryExpression, - CallExpression, - ExpressionStatement, - FunctionDeclaration, - FunctionExpression, - Identifier, - IfStatement, - Literal, - MemberExpression, - ObjectExpression, - Property, - ReturnStatement, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; -import Transform from "../transform"; -import { predictableFunctionTag } from "../../constants"; -import { - chance, - choice, - getRandomFalseExpression, - getRandomInteger, - getRandomString, - splitIntoChunks, -} from "../../util/random"; - -function LZ_encode(c) { - ok(c); - var x = "charCodeAt", - b, - e = {}, - f = c.split(""), - d = [], - a = f[0], - g = 256; - for (b = 1; b < f.length; b++) - (c = f[b]), - null != e[a + c] - ? (a += c) - : (d.push(1 < a.length ? e[a] : a[x](0)), (e[a + c] = g), g++, (a = c)); - d.push(1 < a.length ? e[a] : a[x](0)); - for (b = 0; b < d.length; b++) d[b] = String.fromCharCode(d[b]); - return d.join(""); -} - -function LZ_decode(b) { - ok(b); - var o, - f, - a, - e = {}, - d = b.split(""), - c = (f = d[0]), - g = [c], - h = (o = 256); - for (var i = 1; i < d.length; i++) - (a = d[i].charCodeAt(0)), - (a = h > a ? d[i] : e[a] ? e[a] : f + c), - g.push(a), - (c = a.charAt(0)), - (e[o] = f + c), - o++, - (f = a); - return g.join(""); -} - -const DecodeTemplate = new Template( - `function {name}(b){ - var o, - f, - a, - e = {}, - d = b.split(""), - c = (f = d[0]), - g = [c], - h = (o = 256); - for (b = 1; b < d.length; b++) - (a = d[b].charCodeAt(0)), - (a = h > a ? d[b] : e[a] ? e[a] : f + c), - g.push(a), - (c = a.charAt(0)), - (e[o] = f + c), - o++, - (f = a); - return g.join("").split("{delimiter}"); - }` -); - -export default class StringCompression extends Transform { - map: Map; - ignore: Set; - string: string; - delimiter = "|"; - - fnName: string; - - constructor(o) { - super(o, ObfuscateOrder.StringCompression); - - this.map = new Map(); - this.ignore = new Set(); - this.string = ""; - this.fnName = this.getPlaceholder() + predictableFunctionTag; - } - - apply(tree) { - super.apply(tree); - - this.string = this.string.slice(0, this.string.length - 1); - if (!this.string.length) { - return; - } - - var split = this.getPlaceholder(); - var decoder = this.getPlaceholder(); - var getStringName = this.getPlaceholder() + predictableFunctionTag; // Returns the string payload - - var encoded = LZ_encode(this.string); - if (LZ_decode(encoded) !== this.string) { - this.error( - new Error( - "String failed to be decoded. Try disabling the 'stringCompression' option." - ) - ); - } - - var getStringParamName = this.getPlaceholder(); - var decoderParamName = this.getPlaceholder(); - - var callExpression = CallExpression(Identifier(decoderParamName), [ - CallExpression(Identifier(getStringParamName), []), - ]); - - prepend( - tree, - VariableDeclaration( - VariableDeclarator( - split, - CallExpression( - FunctionExpression( - [Identifier(getStringParamName), Identifier(decoderParamName)], - [ReturnStatement(callExpression)] - ), - [Identifier(getStringName), Identifier(decoder)] - ) - ) - ) - ); - - var keys = new Set(); - var keysToMake = getRandomInteger(4, 14); - for (var i = 0; i < keysToMake; i++) { - keys.add(getRandomString(getRandomInteger(4, 14))); - } - - var objectExpression = ObjectExpression( - Array.from(keys).map((key) => { - return Property(Literal(key), getRandomFalseExpression(), true); - }) - ); - - // Get string function - var getStringBody = []; - var splits = splitIntoChunks( - encoded, - Math.floor(encoded.length / getRandomInteger(3, 6)) - ); - - getStringBody.push( - VariableDeclaration(VariableDeclarator("str", Literal(splits.shift()))) - ); - - getStringBody.push( - VariableDeclaration(VariableDeclarator("objectToTest", objectExpression)) - ); - - const addIfStatement = (testingFor, literalValueToBeAppended) => { - getStringBody.push( - IfStatement( - BinaryExpression( - "in", - Literal(testingFor), - Identifier("objectToTest") - ), - [ - ExpressionStatement( - AssignmentExpression( - "+=", - Identifier("str"), - Literal(literalValueToBeAppended) - ) - ), - ] - ) - ); - }; - - for (const split of splits) { - if (chance(50)) { - var fakeKey; - do { - fakeKey = getRandomString(getRandomInteger(4, 14)); - } while (keys.has(fakeKey) || fakeKey in {}); - - addIfStatement(fakeKey, getRandomString(split.length)); - } - - addIfStatement(choice(Array.from(keys)), split); - } - - // Return computed string - getStringBody.push(ReturnStatement(Identifier("str"))); - - append(tree, FunctionDeclaration(getStringName, [], getStringBody)); - - append( - tree, - FunctionDeclaration( - this.fnName, - [Identifier("index")], - [ - ReturnStatement( - MemberExpression(Identifier(split), Identifier("index"), true) - ), - ] - ) - ); - - append( - tree, - DecodeTemplate.single({ name: decoder, delimiter: this.delimiter }) - ); - } - - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value && - object.value.length > 3 && - !isDirective(object, parents) && - !isModuleSource(object, parents) && - !parents.find((x) => x.$multiTransformSkip) - ); - } - - transform(object, parents) { - if (!object.value) { - return; - } - if ( - this.ignore.has(object.value) || - object.value.includes(this.delimiter) - ) { - return; - } - - if ( - !parents[0] || - (parents[0].type == "CallExpression" && - parents[0].callee.type == "Identifier" && - parents[0].callee.name == this.fnName) - ) { - return; - } - - if ( - !ComputeProbabilityMap( - this.options.stringCompression, - (x) => x, - object.value - ) - ) { - return; - } - - var index = this.map.get(object.value); - - // New string, add it! - if (typeof index !== "number") { - // Ensure the string gets properly decoded - if (LZ_decode(LZ_encode(object.value)) !== object.value) { - this.ignore.add(object.value); - return; - } - - index = this.map.size; - this.map.set(object.value, index); - this.string += object.value + this.delimiter; - } - ok(typeof index === "number"); - - return () => { - this.replaceIdentifierOrLiteral( - object, - CallExpression(Identifier(this.fnName), [Literal(index)]), - parents - ); - }; - } -} diff --git a/src_old/transforms/string/stringConcealing.ts b/src_old/transforms/string/stringConcealing.ts deleted file mode 100644 index db07308..0000000 --- a/src_old/transforms/string/stringConcealing.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import Template from "../../templates/template"; -import { getBlock } from "../../traverse"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { - ArrayExpression, - CallExpression, - Identifier, - Literal, - MemberExpression, - Node, - ObjectExpression, - Property, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; -import { - chance, - choice, - getRandomInteger, - getRandomString, - shuffle, -} from "../../util/random"; -import Transform from "../transform"; -import { - EncodingImplementation, - EncodingImplementations, - createEncodingImplementation, - hasAllEncodings, -} from "./encoding"; -import { ComputeProbabilityMap } from "../../probability"; -import { - BufferToStringTemplate, - createGetGlobalTemplate, -} from "../../templates/bufferToString"; -import { criticalFunctionTag, predictableFunctionTag } from "../../constants"; - -interface FunctionObject { - block: Node; - fnName: string; - encodingImplementation: EncodingImplementation; -} - -export default class StringConcealing extends Transform { - arrayExpression: Node; - set: Set; - index: { [str: string]: [number, string, Node] }; // index, fnName, block - - arrayName = this.getPlaceholder(); - ignore = new Set(); - variablesMade = 1; - gen: ReturnType; - - functionObjects: FunctionObject[] = []; - - constructor(o) { - super(o, ObfuscateOrder.StringConcealing); - - this.set = new Set(); - this.index = Object.create(null); - this.arrayExpression = ArrayExpression([]); - this.gen = this.getGenerator(); - } - - apply(tree) { - super.apply(tree); - - // Pad array with useless strings - var dead = getRandomInteger(50, 200); - for (var i = 0; i < dead; i++) { - var str = getRandomString(getRandomInteger(5, 40)); - var fn = this.transform(Literal(str), [tree]); - if (fn) { - fn(); - } - } - - var cacheName = this.getPlaceholder(); - var bufferToStringName = this.getPlaceholder() + predictableFunctionTag; - - // This helper functions convert UInt8 Array to UTf-string - prepend( - tree, - ...BufferToStringTemplate.compile({ - name: bufferToStringName, - getGlobalFnName: this.getPlaceholder() + predictableFunctionTag, - GetGlobalTemplate: createGetGlobalTemplate(this, tree, []), - }) - ); - - for (var functionObject of this.functionObjects) { - var { - block, - fnName: getterFnName, - encodingImplementation, - } = functionObject; - - var decodeFn = - this.getPlaceholder() + predictableFunctionTag + criticalFunctionTag; - - append( - block, - encodingImplementation.template.single({ - __fnName__: decodeFn, - __bufferToString__: bufferToStringName, - }) - ); - // All these are fake and never ran - var ifStatements = new Template(`if ( z == x ) { - return y[${cacheName}[z]] = ${getterFnName}(x, y); - } - if ( y ) { - [b, y] = [a(b), x || z] - return ${getterFnName}(x, b, z) - } - if ( z && a !== ${decodeFn} ) { - ${getterFnName} = ${decodeFn} - return ${getterFnName}(x, -1, z, a, b) - } - if ( a === ${getterFnName} ) { - ${decodeFn} = y - return ${decodeFn}(z) - } - if( a === undefined ) { - ${getterFnName} = b - } - if( z == a ) { - return y ? x[b[y]] : ${cacheName}[x] || (z=(b[x] || a), ${cacheName}[x] = z(${this.arrayName}[x])) - } - `).compile(); - - // Not all fake if-statements are needed - ifStatements = ifStatements.filter(() => chance(50)); - - // This one is always used - ifStatements.push( - new Template(` - if ( x !== y ) { - return b[x] || (b[x] = a(${this.arrayName}[x])) - } - `).single() - ); - - shuffle(ifStatements); - - var varDeclaration = new Template(` - var ${getterFnName} = (x, y, z, a, b)=>{ - if(typeof a === "undefined") { - a = ${decodeFn} - } - if(typeof b === "undefined") { - b = ${cacheName} - } - } - `).single(); - - varDeclaration.declarations[0].init.body.body.push(...ifStatements); - - prepend(block, varDeclaration); - } - - prepend( - tree, - VariableDeclaration([ - VariableDeclarator(cacheName, ArrayExpression([])), - VariableDeclarator(this.arrayName, this.arrayExpression), - ]) - ); - } - - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length >= 3 && - !isModuleSource(object, parents) && - !isDirective(object, parents) //&& - /*!parents.find((x) => x.$multiTransformSkip)*/ - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - // Empty strings are discarded - if ( - !object.value || - this.ignore.has(object.value) || - object.value.length == 0 - ) { - return; - } - - // Allow user to choose which strings get changed - if ( - !ComputeProbabilityMap( - this.options.stringConcealing, - (x) => x, - object.value - ) - ) { - return; - } - - var currentBlock = getBlock(object, parents); - - // Find created functions - var functionObjects: FunctionObject[] = parents - .filter((node) => node.$stringConcealingFunctionObject) - .map((item) => item.$stringConcealingFunctionObject); - - // Choose random functionObject to use - var functionObject = choice(functionObjects); - - if ( - !functionObject || - (!hasAllEncodings() && - chance(25 / this.functionObjects.length) && - !currentBlock.$stringConcealingFunctionObject) - ) { - // No functions, create one - - var newFunctionObject: FunctionObject = { - block: currentBlock, - encodingImplementation: createEncodingImplementation(), - fnName: this.getPlaceholder() + predictableFunctionTag, - }; - - this.functionObjects.push(newFunctionObject); - currentBlock.$stringConcealingFunctionObject = newFunctionObject; - functionObject = newFunctionObject; - } - - var { fnName, encodingImplementation } = functionObject; - - var index = -1; - - // String already decoded? - if (this.set.has(object.value)) { - var row = this.index[object.value]; - if (parents.includes(row[2])) { - [index, fnName] = row; - ok(typeof index === "number"); - } - } - - if (index == -1) { - // The decode function must return correct result - var encoded = encodingImplementation.encode(object.value); - if (encodingImplementation.decode(encoded) !== object.value) { - this.ignore.add(object.value); - this.warn( - encodingImplementation.identity, - object.value.slice(0, 100) - ); - delete EncodingImplementations[encodingImplementation.identity]; - - return; - } - - this.arrayExpression.elements.push(Literal(encoded)); - index = this.arrayExpression.elements.length - 1; - this.index[object.value] = [index, fnName, currentBlock]; - - this.set.add(object.value); - } - - ok(index != -1, "index == -1"); - - var callExpr = CallExpression(Identifier(fnName), [Literal(index)]); - - // use `.apply` to fool automated de-obfuscators - if (chance(10)) { - callExpr = CallExpression( - MemberExpression(Identifier(fnName), Literal("apply"), true), - [Identifier("undefined"), ArrayExpression([Literal(index)])] - ); - } - - // use `.call` - else if (chance(10)) { - callExpr = CallExpression( - MemberExpression(Identifier(fnName), Literal("call"), true), - [Identifier("undefined"), Literal(index)] - ); - } - - var referenceType = "call"; - if (parents.length && chance(50 - this.variablesMade)) { - referenceType = "constantReference"; - } - - var newExpr: Node = callExpr; - - if (referenceType === "constantReference") { - // Define the string earlier, reference the name here - this.variablesMade++; - - var constantReferenceType = choice(["variable", "array", "object"]); - - var place = currentBlock; - if (!place) { - this.error(new Error("No lexical block to insert code")); - } - - switch (constantReferenceType) { - case "variable": - var name = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration(VariableDeclarator(name, callExpr)) - ); - - newExpr = Identifier(name); - break; - case "array": - if (!place.$stringConcealingArray) { - place.$stringConcealingArray = ArrayExpression([]); - place.$stringConcealingArrayName = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration( - VariableDeclarator( - place.$stringConcealingArrayName, - place.$stringConcealingArray - ) - ) - ); - } - - var arrayIndex = place.$stringConcealingArray.elements.length; - - place.$stringConcealingArray.elements.push(callExpr); - - var memberExpression = MemberExpression( - Identifier(place.$stringConcealingArrayName), - Literal(arrayIndex), - true - ); - - newExpr = memberExpression; - break; - case "object": - if (!place.$stringConcealingObject) { - place.$stringConcealingObject = ObjectExpression([]); - place.$stringConcealingObjectName = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration( - VariableDeclarator( - place.$stringConcealingObjectName, - place.$stringConcealingObject - ) - ) - ); - } - - var propName = this.gen.generate(); - var property = Property(Literal(propName), callExpr, true); - place.$stringConcealingObject.properties.push(property); - - var memberExpression = MemberExpression( - Identifier(place.$stringConcealingObjectName), - Literal(propName), - true - ); - - newExpr = memberExpression; - break; - } - } - - this.replaceIdentifierOrLiteral(object, newExpr, parents); - }; - } -} diff --git a/src_old/transforms/string/stringEncoding.ts b/src_old/transforms/string/stringEncoding.ts deleted file mode 100644 index 04418e9..0000000 --- a/src_old/transforms/string/stringEncoding.ts +++ /dev/null @@ -1,95 +0,0 @@ -import Transform from "../transform"; -import { choice } from "../../util/random"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { ComputeProbabilityMap } from "../../probability"; -import { Identifier } from "../../util/gen"; - -function pad(x: string, len: number): string { - while (x.length < len) { - x = "0" + x; - } - return x; -} - -function even(x: string) { - if (x.length % 2 != 0) { - return "0" + x; - } - return x; -} - -function toHexRepresentation(str: string) { - var escapedString = ""; - str.split("").forEach((char) => { - var code = char.charCodeAt(0); - if (code < 128) { - escapedString += "\\x" + even(pad(code.toString(16), 2)); - } else { - escapedString += char; - } - }); - - return escapedString; -} - -function toUnicodeRepresentation(str: string) { - var escapedString = ""; - str.split("").forEach((char) => { - var code = char.charCodeAt(0); - if (code < 128) { - escapedString += "\\u" + even(pad(code.toString(16), 4)); - } else { - escapedString += char; - } - }); - - return escapedString; -} - -/** - * [String Encoding](https://docs.jscrambler.com/code-integrity/documentation/transformations/string-encoding) transforms a string into an encoded representation. - * - * - Potency Low - * - Resilience Low - * - Cost Low - */ -export default class StringEncoding extends Transform { - constructor(o) { - super(o); - } - - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length > 0 && - !isModuleSource(object, parents) && - !isDirective(object, parents) - ); - } - - transform(object, parents) { - // Allow percentages - if ( - !ComputeProbabilityMap( - this.options.stringEncoding, - (x) => x, - object.value - ) - ) - return; - - var type = choice(["hexadecimal", "unicode"]); - - var escapedString = ( - type == "hexadecimal" ? toHexRepresentation : toUnicodeRepresentation - )(object.value); - - return () => { - if (object.type !== "Literal") return; - - // ESCodeGen tries to escape backslashes, here is a work-around - this.replace(object, Identifier(`'${escapedString}'`)); - }; - } -} diff --git a/src_old/transforms/string/stringSplitting.ts b/src_old/transforms/string/stringSplitting.ts deleted file mode 100644 index 765ceb1..0000000 --- a/src_old/transforms/string/stringSplitting.ts +++ /dev/null @@ -1,86 +0,0 @@ -import Transform from "../transform"; -import { Node, Literal, BinaryExpression } from "../../util/gen"; -import { clone } from "../../util/insert"; -import { getRandomInteger, shuffle, splitIntoChunks } from "../../util/random"; -import { ObfuscateOrder } from "../../order"; -import { isDirective, isModuleSource } from "../../util/compare"; -import { ok } from "assert"; -import { ComputeProbabilityMap } from "../../probability"; - -export default class StringSplitting extends Transform { - joinPrototype: string; - strings: { [value: string]: string }; - - adders: Node[][]; - vars: Node[]; - - constructor(o) { - super(o, ObfuscateOrder.StringSplitting); - - this.joinPrototype = null; - this.strings = Object.create(null); - - this.adders = []; - this.vars = []; - } - - match(object: Node, parents: Node[]) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length >= 8 && - !isModuleSource(object, parents) && - !isDirective(object, parents) - ); - } - - transform(object: Node, parents: Node[]) { - return () => { - var size = Math.round( - Math.max(6, object.value.length / getRandomInteger(3, 8)) - ); - if (object.value.length <= size) { - return; - } - - var chunks = splitIntoChunks(object.value, size); - if (!chunks || chunks.length <= 1) { - return; - } - - if ( - !ComputeProbabilityMap( - this.options.stringSplitting, - (x) => x, - object.value - ) - ) { - return; - } - - var binaryExpression; - var parent; - var last = chunks.pop(); - chunks.forEach((chunk, i) => { - if (i == 0) { - parent = binaryExpression = BinaryExpression( - "+", - Literal(chunk), - Literal("") - ); - } else { - binaryExpression.left = BinaryExpression( - "+", - clone(binaryExpression.left), - Literal(chunk) - ); - ok(binaryExpression); - } - }); - - parent.right = Literal(last); - - this.replaceIdentifierOrLiteral(object, parent, parents); - }; - } -} diff --git a/src_old/transforms/transform.ts b/src_old/transforms/transform.ts deleted file mode 100644 index c8f318d..0000000 --- a/src_old/transforms/transform.ts +++ /dev/null @@ -1,441 +0,0 @@ -import traverse, { ExitCallback } from "../traverse"; -import { - AddComment, - Node, - VariableDeclaration, - VariableDeclarator, -} from "../util/gen"; -import { - alphabeticalGenerator, - choice, - createZeroWidthGenerator, - getRandomInteger, - shuffle, -} from "../util/random"; -import { ok } from "assert"; -import Obfuscator from "../obfuscator"; -import { ComputeProbabilityMap } from "../probability"; -import { - placeholderVariablePrefix, - reservedIdentifiers, - reservedKeywords, -} from "../constants"; -import { ObfuscateOrder } from "../order"; -import { prepend } from "../util/insert"; -import Lock from "./lock/lock"; -import Template from "../templates/template"; - -/** - * Base-class for all transformations. - * - Transformations can have preparation transformations `.before` - * - Transformations can have cleanup transformations `.after` - * - * - `match()` function returns true/false if possible candidate - * - `transform()` function modifies the object - * - * ```js - * class Example extends Transform { - * constructor(o){ - * super(o); - * } - * - * match(object, parents){ - * return object.type == "..."; - * } - * - * transform(object, parents){ - * // onEnter - * - * return ()=>{ - * // onExit - * } - * } - * - * apply(tree){ - * // onStart - * - * super.apply(tree); - * - * // onEnd - * } - * } - * ``` - */ -export default class Transform { - /** - * The obfuscator. - */ - obfuscator: Obfuscator; - - /** - * The user's options. - */ - options: Obfuscator["options"]; - - /** - * Only required for top-level transformations. - */ - priority: number; - - /** - * Transforms to run before, such as `Variable Analysis`. - */ - before: Transform[]; - - /** - * Transforms to run after. - */ - after: Transform[]; - - initVariables = new Map(); - - zeroWidthGenerator = createZeroWidthGenerator(); - - constructor(obfuscator, priority: number = -1) { - ok(obfuscator instanceof Obfuscator, "obfuscator should be an Obfuscator"); - - this.obfuscator = obfuscator; - this.options = this.obfuscator.options; - - this.priority = priority; - - this.before = []; - this.after = []; - } - - /** - * The transformation name. - */ - get className() { - return ( - ObfuscateOrder[this.priority] || (this as any).__proto__.constructor.name - ); - } - - /** - * Gets the `Lock` transformation. - */ - get lockTransform(): Lock { - var transform = this.obfuscator.transforms["Lock"] as Lock; - - ok(transform, "Lock transform not created"); - - return transform; - } - - /** - * Wraps the given name with the `__JS_CONFUSER_VAR__` function call. - * - * If `Rename Variables` is disabled, the name is returned as-is. - * @param name - * @returns - */ - jsConfuserVar(name: string) { - if (!this.obfuscator.transforms["RenameVariables"]) { - return `"${name}"`; - } - - return `__JS_CONFUSER_VAR__(${name})`; - } - - /** - * Run an AST through the transformation (including `pre` and `post` transforms) - * @param tree - */ - apply(tree: Node) { - if (tree.type == "Program" && this.options.verbose) { - if (this.priority === -1) { - console.log("#", ">", this.className); - } else { - console.log("#", this.priority, this.className); - } - } - - /** - * Run through pre-transformations - */ - this.before.forEach((x) => x.apply(tree)); - - /** - * Run this transformation - */ - traverse(tree, (object, parents) => { - return this.input(object, parents); - }); - - /** - * Cleanup transformations - */ - this.after.forEach((x) => x.apply(tree)); - } - - /** - * The `match` function filters for possible candidates. - * - * - If `true`, the node is sent to the `transform()` method - * - else it's discarded. - * - * @param object - * @param parents - * @param block - */ - match(object: Node, parents: Node[]): boolean { - throw new Error("not implemented"); - } - - /** - * Modifies the given node. - * - * - Return a function to be ran when the node is exited. - * - The node is safe to modify in most cases. - * - * @param object - Current node - * @param parents - Array of ancestors `[Closest, ..., Root]` - * @param block - */ - transform(object: Node, parents: Node[]): ExitCallback | void { - throw new Error("not implemented"); - } - - /** - * Calls `.match` with the given parameters, and then `.transform` if satisfied. - * @private - */ - input(object: Node, parents: Node[]): ExitCallback | void { - if (this.match(object, parents)) { - return this.transform(object, parents); - } - } - - /** - * Returns a random string. - * - * Used for creating temporary variables names, typically before RenameVariables has ran. - * - * These long temp names will be converted to short, mangled names by RenameVariables. - */ - getPlaceholder() { - const genRanHex = (size) => - [...Array(size)] - .map(() => Math.floor(Math.random() * 10).toString(10)) - .join(""); - return placeholderVariablePrefix + genRanHex(10); - } - - /** - * Returns an independent name generator with it's own counter. - * @param overrideMode - Override the user's `identifierGenerator` option - * @returns - */ - getGenerator(overrideMode?: string) { - var count = 0; - var identifiers = new Set(); - return { - generate: () => { - var retValue: string; - do { - count++; - retValue = this.generateIdentifier(-1, count, overrideMode); - } while (identifiers.has(retValue)); - - identifiers.add(retValue); - - return retValue; - }, - }; - } - - /** - * Generates a valid variable name. - * @param length Default length is 6 to 10 characters. - * @returns **`string`** - */ - generateIdentifier( - length: number = -1, - count = -1, - overrideMode?: string - ): string { - if (length == -1) { - length = getRandomInteger(6, 8); - } - - var set = new Set(); - - if (count == -1) { - this.obfuscator.varCount++; - count = this.obfuscator.varCount; - set = this.obfuscator.generated; - } - - var identifier; - do { - identifier = ComputeProbabilityMap( - overrideMode || this.options.identifierGenerator, - (mode = "randomized") => { - switch (mode) { - case "randomized": - var characters = - "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split( - "" - ); - var numbers = "0123456789".split(""); - - var combined = [...characters, ...numbers]; - - var result = ""; - for (var i = 0; i < length; i++) { - result += choice(i == 0 ? characters : combined); - } - return result; - - case "hexadecimal": - const genRanHex = (size) => - [...Array(size)] - .map(() => Math.floor(Math.random() * 16).toString(16)) - .join(""); - - return "_0x" + genRanHex(length).toUpperCase(); - - case "mangled": - while (1) { - var result = alphabeticalGenerator(count); - count++; - - if ( - reservedKeywords.has(result) || - reservedIdentifiers.has(result) - ) { - } else { - return result; - } - } - - throw new Error("impossible but TypeScript insists"); - - case "number": - return "var_" + count; - - case "zeroWidth": - return this.zeroWidthGenerator.generate(); - } - - throw new Error("Invalid 'identifierGenerator' mode: " + mode); - } - ); - } while (set.has(identifier)); - - if (!identifier) { - throw new Error("identifier null"); - } - - set.add(identifier); - - return identifier; - } - - createInitVariable = (value: Template, parents: Node[]) => { - var key = value.templates[0]; - if (this.initVariables.has(key)) { - return this.initVariables.get(key); - } - - var root = parents[parents.length - 1]; - ok(root.type === "Program"); - - var name = this.getPlaceholder(); - this.initVariables.set(key, name); - - prepend( - root, - VariableDeclaration(VariableDeclarator(name, value.single().expression)) - ); - - return name; - }; - - /** - * Smartly appends a comment to a Node. - * - Includes the transformation's name. - * @param node - * @param text - * @param i - */ - addComment(node: Node, text: string) { - if (this.options.debugComments) { - return AddComment(node, `[${this.className}] ${text}`); - } - return node; - } - - replace(node1: Node, node2: Node) { - for (var key in node1) { - delete node1[key]; - } - - this.objectAssign(node1, node2); - } - - replaceIdentifierOrLiteral(node1: Node, node2: Node, parents: Node[]) { - // Fix 2. Make parent property key computed - if ( - parents[0] && - (parents[0].type == "Property" || - parents[0].type == "MethodDefinition") && - parents[0].key == node1 - ) { - parents[0].computed = true; - parents[0].shorthand = false; - } - this.replace(node1, node2); - } - - /** - * Smartly merges two Nodes. - * - Null checking - * - Preserves comments - * @param node1 - * @param node2 - */ - objectAssign(node1: Node, node2: Node): Node { - ok(node1); - ok(node2); - - var comments1 = node1.leadingComments || []; - var comments2 = node2.leadingComments || []; - var comments = [...comments1, ...comments2]; - - node2.leadingComments = comments; - - node1._transform = node2._transform = this.className; - - return Object.assign(node1, node2); - } - - /** - * Verbose logging for this transformation. - * @param messages - */ - log(...messages: any[]) { - if (this.options.verbose) { - console.log("[" + this.className + "]", ...messages); - } - } - - /** - * Verbose logging for warning/important messages. - * @param messages - */ - warn(...messages: any[]) { - if (this.options.verbose) { - console.log("[ WARN " + this.className + " ]", ...messages); - } - } - - /** - * Throws an error. Appends the transformation's name to the error's message. - * @param error - */ - error(error: Error): never { - throw new Error(`${this.className} Error: ${error.message}`); - } -} diff --git a/src_old/traverse.ts b/src_old/traverse.ts deleted file mode 100644 index 1b8d401..0000000 --- a/src_old/traverse.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Node } from "./util/gen"; -import { validateChain } from "./util/identifiers"; - -/** - * A block refers to any object that has a **`.body`** property where code is nested. - * - * Types: `BlockStatement`, `Program` - * - * @param object - * @param parents - */ -export function getBlock(object: any, parents: any[]) { - if (!Array.isArray(parents)) { - throw new Error("parents must be an array"); - } - return [object, ...parents].find((node) => isBlock(node)); -} - -/** - * Must have a **`.body`** property and be an array. - * - * - "BlockStatement" - * - "Program" - * - * @param object - */ -export function isBlock(object: any) { - return ( - object && (object.type == "BlockStatement" || object.type == "Program") - ); -} - -export type EnterCallback = ( - object: Node, - parents: Node[] -) => ExitCallback | "EXIT" | void; -export type ExitCallback = () => void; - -export function walk( - object: Node | Node[], - parents: Node[], - onEnter: EnterCallback -): "EXIT" | void { - if (typeof object === "object" && object) { - var newParents: Node[] = [object as Node, ...parents]; - - // if (!Array.isArray(object)) { - // validateChain(object, parents); - // } - - // 1. Call `onEnter` function and remember any onExit callback returned - var onExit = onEnter(object as Node, parents); - - // 2. Traverse children - if (Array.isArray(object)) { - var copy = [...object]; - for (var element of copy) { - if (walk(element, newParents, onEnter) === "EXIT") { - return "EXIT"; - } - } - } else { - var keys = Object.keys(object); - for (var key of keys) { - if (!key.startsWith("$")) { - if (walk(object[key], newParents, onEnter) === "EXIT") { - return "EXIT"; - } - } - } - } - - if (onExit === "EXIT") { - return "EXIT"; - } - - // 3. Done with children, call `onExit` callback - if (onExit) { - onExit(); - } - } -} - -/** - * The bare-bones walker. - * - * - Recursively traverse an AST object. - * - Calls the `onEnter` function with: - * - - `object` - The current node - * - - `parents` - Array of ancestors `[closest, ..., root]` - * - The `onEnter` callback can return an `onExit` callback for that node. - * - * - *Note*: Does not validate the property names. - * - * @param tree - * @param onEnter - */ -export default function traverse(tree, onEnter: EnterCallback) { - walk(tree, [], onEnter); -} - -/** - * This is debugging function used to test for circular references. - */ -export function assertNoCircular(object) { - var seen = new Set(); - - traverse(object, (node, nodeParents) => { - if (node && typeof node === "object") { - if (seen.has(node)) { - console.log(nodeParents); - console.log(node); - - throw new Error("FOUND CIRCULAR REFERENCE"); - } - - seen.add(node); - } - }); -} diff --git a/src_old/types.ts b/src_old/types.ts deleted file mode 100644 index fc32bfd..0000000 --- a/src_old/types.ts +++ /dev/null @@ -1,133 +0,0 @@ -import Obfuscator from "./obfuscator"; -import { ObfuscateOptions } from "./options"; -import Template from "./templates/template"; -import Transform from "./transforms/transform"; - -/** - * **JsConfuser**: Obfuscates JavaScript. - * @param code - The code to be obfuscated. - * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser#options) - */ -export interface IJsConfuser { - obfuscate: IJsConfuserObfuscate; - obfuscateAST: IJsConfuserObfuscateAST; - - presets: IJsConfuserPresets; - debugTransformations: IJsConfuserDebugTransformations; - debugObfuscation: IJsConfuserDebugObfuscation; - - (code: string, options: ObfuscateOptions): Promise; - - Transform: typeof Transform; - Obfuscator: typeof Obfuscator; - Template: typeof Template; -} - -/** - * **JsConfuser**: Obfuscates JavaScript. - * @param code - The code to be obfuscated. - * @param options - An object of obfuscation options: `{preset: "medium", target: "browser"}`. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser#options) - */ -export interface IJsConfuserObfuscate { - (code: string, options: ObfuscateOptions): Promise; -} - -/** - * Obfuscates an [ESTree](https://github.com/estree/estree) compliant AST. - * - * **Note:** Mutates the object. - * - * @param AST - The [ESTree](https://github.com/estree/estree) compliant AST. This object will be mutated. - * @param options - The obfuscation options. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser#options) - */ -export interface IJsConfuserObfuscateAST { - (AST: any, options: ObfuscateOptions): Promise; -} - -export interface IJsConfuserPresets { - high: ObfuscateOptions; - medium: ObfuscateOptions; - low: ObfuscateOptions; -} - -/** - * Obfuscates code but returns an array of `frames` - * - * ```js - * [ - * { - * name: "Preparation", - * code: "console['log']('Hello World')", - * ms: 4 - * }, { - * name: "ControlFlowFlattening", - * code: "var....", - * ms: 400 - * }, - * // .... - * ] - * ``` - */ -export type IJsConfuserDebugTransformations = ( - code: string, - options: ObfuscateOptions -) => Promise<{ name: string; code: string; ms: number }[]>; - -/** - * Obfuscates code but calls the callback function after each transform. - * - * This is used to display a progress bar to the user on the official website. - * - * `callback(name: string, complete: number, totalTransforms: number)` - * - * ```js - * var callback = (name, complete, totalTransforms) => { - * console.log(name, complete, totalTransforms) - * }; - * ``` - * - * ```js - * // Preparation 1 22 - * // ObjectExtraction 2 22 - * // Flatten 3 22 - * // Dispatcher 4 22 - * // DeadCode 5 22 - * // Calculator 6 22 - * // ControlFlowFlattening 7 22 - * // GlobalConcealing 8 22 - * // OpaquePredicates 9 22 - * // StringSplitting 10 22 - * // StringConcealing 11 22 - * // StringCompression 12 22 - * // HideInitializingCode 13 22 - * // Stack 14 22 - * // DuplicateLiteralsRemoval 15 22 - * // Shuffle 16 22 - * // MovedDeclarations 17 22 - * // RenameVariables 18 22 - * // RenameLabels 19 22 - * // Minify 20 22 - * // StringEncoding 21 22 - * // AntiTooling 22 22 - * ``` - */ -export type IJsConfuserDebugObfuscation = ( - code: string, - options: ObfuscateOptions, - callback: (name: string, complete: number, totalTransforms: number) => void, - performance: Performance -) => Promise<{ - obfuscated: string; - transformationTimes: { [transformName: string]: number }; - parseTime: number; - compileTime: number; - obfuscationTime: number; - totalTransforms: number; - totalPossibleTransforms: number; -}>; diff --git a/src_old/util/compare.ts b/src_old/util/compare.ts deleted file mode 100644 index 6524b11..0000000 --- a/src_old/util/compare.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { walk } from "../traverse"; -import { Node } from "./gen"; -import { getBlockBody, isFunction } from "./insert"; - -export function isEquivalent(first: Node, second: Node) { - var extra = { - start: 1, - end: 1, - loc: 1, - }; - - function removeExtra(obj) { - if (typeof obj === "object") { - for (var property in obj) { - if (obj && obj.hasOwnProperty(property)) { - if (typeof obj[property] == "object") { - removeExtra(obj[property]); - } else { - if (extra[property]) { - delete obj[property]; - } - } - } - } - } - - return obj; - } - return ( - JSON.stringify(removeExtra(first)) == JSON.stringify(removeExtra(second)) - ); -} -/** - * Statements that allowed `break;` and `continue;` statements - * @param object - */ -export function isLoop(object: Node) { - return [ - "SwitchStatement", - "WhileStatement", - "DoWhileStatement", - "ForStatement", - "ForInStatement", - "ForOfStatement", - ].includes(object.type); -} - -export function isValidIdentifier(name: string): boolean { - if (typeof name !== "string") { - return false; - } - if (name.includes(".") || name.includes(" ")) { - return false; - } - - var x = name.match(/^[A-Za-z$_][A-Za-z0-9$_]*/); - return !!(x && x[0] == name); -} - -export function isInsideType( - type: string, - object: Node, - parents: Node[] -): boolean { - return [object, ...parents].some((x) => x.type == type); -} - -export function isDirective(object: Node, parents: Node[]) { - var dIndex = parents.findIndex((x) => x.directive); - if (dIndex == -1) { - return false; - } - - return parents[dIndex].expression == (parents[dIndex - 1] || object); -} - -export function isModuleSource(object: Node, parents: Node[]) { - if (!parents[0]) { - return false; - } - - if (parents[0].type == "ImportDeclaration" && parents[0].source == object) { - return true; - } - - if (parents[0].type == "ImportExpression" && parents[0].source == object) { - return true; - } - - if ( - parents[1] && - parents[1].type == "CallExpression" && - parents[1].arguments[0] === object && - parents[1].callee.type == "Identifier" - ) { - if ( - parents[1].callee.name == "require" || - parents[1].callee.name == "import" - ) { - return true; - } - } - - return false; -} - -export function isMoveable(object: Node, parents: Node[]) { - return !isDirective(object, parents) && !isModuleSource(object, parents); -} - -export function isIndependent(object: Node, parents: Node[]) { - if (object.type == "Literal") { - return true; - } - - if (object.type == "Identifier") { - if (primitiveIdentifiers.has(object.name)) { - return true; - } - - var parent = parents[0]; - if (parent && parent.type == "Property") { - if (!parent.computed && parent.key == object) { - return true; - } - } - - return false; - } - - if ( - object.type == "ArrayExpression" || - object.type == "ObjectExpression" || - object.type == "Property" - ) { - var allowIt = true; - walk(object, parents, ($object, $parents) => { - if (object != $object) { - if (!Array.isArray($object) && !isIndependent($object, $parents)) { - allowIt = false; - return "EXIT"; - } - } - }); - - return allowIt; - } - - return false; -} - -var primitiveIdentifiers = new Set(["undefined", "NaN"]); - -/** - * booleans, numbers, string, null, undefined, NaN, infinity - * - * Types: - * - `Literal` with typeof `node.value` = `"number" | "string" | "boolean"` - * - `Identifier` with `name` = `"undefined" | "NaN"` - * - * - * @param node - * @returns - */ -export function isPrimitive(node: Node) { - if (node.type == "Literal") { - if (node.value === null) { - return true; - } else if (typeof node.value === "number") { - return true; - } else if (typeof node.value === "string") { - return true; - } else if (typeof node.value === "boolean") { - return true; - } - } else if (node.type == "Identifier") { - return primitiveIdentifiers.has(node.name); - } - - return false; -} diff --git a/src_old/util/gen.ts b/src_old/util/gen.ts deleted file mode 100644 index 14f622e..0000000 --- a/src_old/util/gen.ts +++ /dev/null @@ -1,651 +0,0 @@ -import { ok } from "assert"; -import { predictableFunctionTag } from "../constants"; -import { isValidIdentifier } from "./compare"; - -export type Type = - | "Identifier" - | "Literal" - | "VariableDeclaration" - | "VariableDeclarator" - | "IfStatement" - | "SwitchStatement" - | "SwitchCase" - | "FunctionDeclaration" - | "ClassDeclaration" - | "ClassExpression" - | "ClassBody" - | "BlockStatement" - | "Program" - | "ThisExpression" - | "Super" - | "ForInStatement" - | "ForOfStatement" - | "WhileStatement" - | "DoWhileStatement" - | "UnaryExpression" - | "ExpressionStatement" - | "AssignmentExpression" - | "NewExpression" - | "CallExpression" - | "ArrayPattern" - | "LogicalExpression" - | "BinaryExpression" - | "UpdateExpression" - | "ThrowStatement" - | "MethodDefinition" - | "LabeledStatement"; - -export type Node = { type: string; [key: string]: any }; - -/** - * 0. First index is the Node. - * 1. Second index is the parents as an array. - */ -export type Location = [Node, Node[]]; - -/** - * Eval Callbacks are called once all transformations are done. - * - * - Called with object, and parents. - */ -export type EvalCallback = { - $eval: (object: Node, parents: Node[]) => void; - [key: string]: any; -}; - -/** - * - 0. First index is the Node. - * - ...1 Parent nodes. - */ -export type Chain = Node[]; - -export function Literal(value: string | number | boolean): Node { - if (typeof value === "undefined") { - throw new Error("value is undefined"); - } - if (typeof value == "number" && value < 0) { - return UnaryExpression("-", Literal(Math.abs(value))); - } - ok(value === value, "NaN value is disallowed"); - - return { - type: "Literal", - value: value, - }; -} - -export function RegexLiteral(pattern: string, flags: string) { - return { - type: "Literal", - regex: { - pattern, - flags, - }, - }; -} - -export function Identifier(name: string) { - if (!name) { - throw new Error("name is null/empty"); - } - if (name == "this") { - throw new Error("Use ThisExpression"); - } - if (name == "super") { - throw new Error("Use Super"); - } - return { - type: "Identifier", - name: name.toString(), - }; -} - -export function BlockStatement(body: Node[]) { - if (!Array.isArray(body)) { - throw new Error("not array"); - } - return { - type: "BlockStatement", - body: body, - }; -} - -export function LogicalExpression(operator: string, left: Node, right: Node) { - return { - type: "LogicalExpression", - operator, - left, - right, - }; -} - -export function BinaryExpression(operator: string, left: Node, right: Node) { - if (operator == "||" || operator == "&&") { - throw new Error("invalid operator, use LogicalExpression"); - } - return { - type: "BinaryExpression", - operator, - left, - right, - }; -} - -export function ThisExpression() { - return { type: "ThisExpression" }; -} - -export function SwitchCase(test: any, consequent: Node[]) { - ok(test === null || test); - ok(Array.isArray(consequent)); - return { - type: "SwitchCase", - test, - consequent, - }; -} - -export function SwitchDefaultCase(consequent: Node[]) { - return SwitchCase(null, consequent); -} - -export function LabeledStatement(label: string, body: Node) { - return { - type: "LabeledStatement", - label: Identifier(label), - body: body, - }; -} - -export function SwitchStatement(discriminant: any, cases: Node[]) { - return { - type: "SwitchStatement", - discriminant: discriminant, - cases: cases, - }; -} - -export function BreakStatement(label?: string) { - return { - type: "BreakStatement", - label: label ? Identifier(label) : null, - }; -} - -export function Property( - key: Node, - value: Node, - computed = false, - kind: "init" | "set" | "get" = "init" -) { - if (!key) { - throw new Error("key is undefined"); - } - if (!value) { - throw new Error("value is undefined"); - } - return { - type: "Property", - key: key, - computed: computed, - value: value, - kind: kind, - method: false, - shorthand: false, - }; -} - -export function ObjectExpression(properties: Node[]) { - if (!properties) { - throw new Error("properties is null"); - } - return { - type: "ObjectExpression", - properties: properties, - }; -} - -export function VariableDeclarator(id: string | Node, init: Node = null) { - if (typeof id === "string") { - id = Identifier(id); - } - return { - type: "VariableDeclarator", - id, - init, - }; -} - -export function VariableDeclaration( - declarations: Node | Node[], - kind: "var" | "const" | "let" = "var" -) { - if (!Array.isArray(declarations)) { - declarations = [declarations]; - } - - ok(Array.isArray(declarations)); - ok(declarations.length); - - ok(!declarations.find((x) => x.type == "ExpressionStatement")); - - return { - type: "VariableDeclaration", - declarations: declarations, - kind: kind, - }; -} - -export function ForStatement( - variableDeclaration: any, - test: any, - update: any, - body: any[] -) { - ok(variableDeclaration); - ok(test); - ok(update); - return { - type: "ForStatement", - init: variableDeclaration, - test: test, - update: update, - body: BlockStatement(body), - }; -} - -export function WhileStatement(test: any, body: Node[]) { - ok(test); - return { - type: "WhileStatement", - test, - body: BlockStatement(body), - }; -} - -export function IfStatement( - test: Node, - consequent: Node[], - alternate: Node[] | null = null -): Node { - if (!test) { - throw new Error("test is undefined"); - } - - if (!consequent) { - throw new Error("consequent undefined, use empty array instead"); - } - - if (!Array.isArray(consequent)) { - throw new Error( - "consequent needs to be array, found " + (consequent as any).type - ); - } - - if (alternate && !Array.isArray(alternate)) { - throw new Error( - "alternate needs to be array, found " + (alternate as any).type - ); - } - - return { - type: "IfStatement", - test: test, - consequent: BlockStatement(consequent), - alternate: alternate ? BlockStatement(alternate) : null, - }; -} - -export function FunctionExpression(params: Node[], body: any[]) { - ok(Array.isArray(params), "params should be an array"); - - return { - type: "FunctionExpression", - id: null, - params: params, - body: BlockStatement(body), - generator: false, - expression: false, - async: false, - [predictableFunctionTag]: true, - }; -} - -/** - * ```js - * function name(p[0], p[1], p[2], ...p[4]){ - * body[0]; - * body[1]... - * } - * ``` - * @param name - * @param params - * @param body - */ -export function FunctionDeclaration( - name: string, - params: Node[], - body: Node[] -) { - if (!body) { - throw new Error("undefined body"); - } - if (body && Array.isArray(body[0])) { - throw new Error("nested array"); - } - ok(Array.isArray(params), "params should be an array"); - return { - type: "FunctionDeclaration", - id: Identifier(name), - params: params, - body: BlockStatement(body), - generator: false, - expression: false, - async: false, - [predictableFunctionTag]: true, - }; -} - -export function DebuggerStatement() { - return { - type: "DebuggerStatement", - }; -} - -export function ReturnStatement(argument: Node = null) { - if (argument) { - ok(argument.type, "Argument should be a node"); - } - return { - type: "ReturnStatement", - argument: argument, - }; -} - -export function AwaitExpression(argument: Node) { - ok(argument.type, "Argument should be a node"); - return { - type: "AwaitExpression", - argument, - }; -} - -export function ConditionalExpression( - test: Node, - consequent: Node, - alternate: Node -) { - ok(test); - ok(consequent); - ok(alternate); - return { - type: "ConditionalExpression", - test, - consequent, - alternate, - }; -} - -export function ExpressionStatement(expression: Node) { - ok(expression.type); - return { - type: "ExpressionStatement", - expression: expression, - } as Node; -} - -export function UnaryExpression(operator: string, argument: Node) { - ok(typeof operator === "string"); - ok(argument.type); - - return { - type: "UnaryExpression", - operator, - argument, - } as Node; -} - -export function UpdateExpression( - operator: string, - argument: Node, - prefix = false -) { - return { - type: "UpdateExpression", - operator, - argument, - prefix, - } as Node; -} - -export function SequenceExpression(expressions: Node[]) { - if (!expressions) { - throw new Error("expressions undefined"); - } - if (!expressions.length) { - throw new Error("expressions length = 0"); - } - return { - type: "SequenceExpression", - expressions: expressions, - }; -} - -export function MemberExpression( - object: Node, - property: Node, - computed = true -) { - if (!object) { - throw new Error("object undefined"); - } - if (!property) { - throw new Error("property undefined"); - } - if (!computed && property.type == "Literal") { - throw new Error("literal must be computed property"); - } - if (object.name == "new" && property.name == "target") { - throw new Error("new.target is a MetaProperty"); - } - return { - type: "MemberExpression", - computed: computed, - object: object, - property: property, - }; -} - -export function CallExpression(callee: Node, args: Node[]) { - ok(Array.isArray(args), "args should be an array"); - return { - type: "CallExpression", - callee: callee, - arguments: args, - }; -} - -export function NewExpression(callee: Node, args: Node[]) { - return { - type: "NewExpression", - callee, - arguments: args, - }; -} - -export function AssignmentExpression( - operator: string, - left: Node, - right: Node -) { - return { - type: "AssignmentExpression", - operator: operator, - left: left, - right: right, - }; -} - -export function ArrayPattern(elements: Node[]) { - ok(Array.isArray(elements)); - return { - type: "ArrayPattern", - elements: elements, - }; -} - -export function ArrayExpression(elements: Node[]) { - ok(Array.isArray(elements)); - return { - type: "ArrayExpression", - elements, - }; -} - -export function AssignmentPattern(left: Node, right: Node) { - ok(left); - ok(right); - return { - type: "AssignmentPattern", - left: left, - right: right, - }; -} - -export function AddComment(node: Node, text: string) { - if ((node as any).leadingComments) { - (node as any).leadingComments.push({ - type: "Block", - value: text, - }); - } else { - Object.assign(node, { - leadingComments: [ - { - type: "Block", - value: text, - }, - ], - }); - } - - return node; -} - -export function Super() { - return { type: "Super" }; -} - -export function MethodDefinition( - key: Node, - functionExpression: Node, - kind: "method" | "constructor" | "get" | "set", - isStatic = false, - computed = false -) { - return { - type: "MethodDefinition", - key: key, - computed: computed, - value: functionExpression, - kind: kind, - static: isStatic, - } as Node; -} - -export function ClassDeclaration( - id: Node, - superClass: Node = null, - body: Node[] = [] -) { - return { - type: "ClassDeclaration", - id: id, - superClass: superClass, - body: { - type: "ClassBody", - body: body, - }, - } as Node; -} - -export function ClassExpression( - id: Node | null, - superClass: Node = null, - body: Node[] = [] -) { - return { - type: "ClassExpression", - id: id, - superClass: superClass, - body: { - type: "ClassBody", - body: body, - }, - } as Node; -} - -export function ThrowStatement(argument: Node) { - return { - type: "ThrowStatement", - argument: argument, - } as Node; -} - -export function WithStatement(object: Node, body: Node[]) { - ok(object, "object"); - ok(object.type, "object should be node"); - - return { - type: "WithStatement", - object, - body: BlockStatement(body), - }; -} - -/** - * `fn(...args)` - * @param argument - * @returns - */ -export function SpreadElement(argument: Node) { - return { - type: "SpreadElement", - argument, - }; -} - -/** - * `function fn(...params){}` - * @param argument - * @returns - */ -export function RestElement(argument: Node) { - return { - type: "RestElement", - argument, - }; -} - -export function CatchClause(param: Node = null, body) { - return { - type: "CatchClause", - param: param, - body: BlockStatement(body), - }; -} - -export function TryStatement( - body: Node[], - handler: Node, - finallyBody?: Node[] -) { - ok(handler); - ok(handler.type == "CatchClause"); - return { - type: "TryStatement", - block: BlockStatement(body), - handler: handler, - finalizer: finallyBody ? BlockStatement(finallyBody) : null, - }; -} diff --git a/src_old/util/guard.ts b/src_old/util/guard.ts deleted file mode 100644 index 9bbdbe5..0000000 --- a/src_old/util/guard.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { variableFunctionName } from "../constants"; -import { Node } from "./gen"; - -export function isStringLiteral(node: Node) { - return ( - node.type === "Literal" && typeof node.value === "string" && !node.regex - ); -} - -export function isJSConfuserVar(p: Node[]) { - return p.find( - (x) => - x.type === "CallExpression" && - x.callee.type === "Identifier" && - x.callee.name == variableFunctionName - ); -} diff --git a/src_old/util/identifiers.ts b/src_old/util/identifiers.ts deleted file mode 100644 index f1486f0..0000000 --- a/src_old/util/identifiers.ts +++ /dev/null @@ -1,494 +0,0 @@ -import { ok } from "assert"; -import traverse, { walk } from "../traverse"; -import { Location, Node } from "./gen"; -import { getVarContext, isVarContext, isFunction } from "./insert"; - -/** - * Ensures the chain (object and parents) are connected. - * @param object - * @param parents - */ -export function validateChain(object: Node, parents: Node[]) { - if (!Array.isArray(parents)) { - throw new Error("parents need to be an array"); - } - - if (!object) { - throw new Error("object must be a node (not null)"); - } - - if (parents.length > 0) { - if (object == parents[0]) { - throw new Error("parent overlap"); - } - if (!Object.values(parents[0]).includes(object)) { - console.log("parents=", parents); - console.log("object=", object); - - throw new Error("parents[0] is not connected to object"); - } - } -} - -function objectPatternCheck(object: Node, parents: Node[]) { - var objectPatternIndex = parents.findIndex((x) => x.type === "ObjectPattern"); - if (objectPatternIndex == -1) { - return true; - } - - var property = parents[objectPatternIndex].properties.find( - (property) => parents[objectPatternIndex - 2] === property - ); - - if (property.key === (parents[objectPatternIndex - 3] || object)) { - return false; - } - - return true; -} - -/** - * Returns detailed information about the given Identifier node. - * @param object - * @param parents - */ -export function getIdentifierInfo(object: Node, parents: Node[]) { - if (object.type != "Identifier") { - console.log(object); - throw new Error("object is not an Identifier, its a type=" + object.type); - } - - var parent = parents[0] || ({} as Node); - - var isAccessor = - parent.type == "MemberExpression" && - parent.object != object && - parent.property === object && - !parent.computed; - - var propIndex = parents.findIndex( - (x) => x.type == "Property" || x.type == "MethodDefinition" - ); - var isPropertyKey = - propIndex != -1 && - parents[propIndex].key == (parents[propIndex - 1] || object) && - !parents[propIndex].computed; - - var objectPatternIndex = parents.findIndex((x) => x.type == "ObjectPattern"); - if ( - objectPatternIndex !== -1 && - parents[objectPatternIndex].properties == parents[objectPatternIndex - 1] - ) { - if (objectPatternIndex - propIndex == 2) { - if (parents[propIndex].value === (parents[propIndex - 1] || object)) { - isPropertyKey = false; - } - } - } - - var varIndex = parents.findIndex((x) => x.type == "VariableDeclarator"); - - var isVariableDeclaration = - varIndex != -1 && - parents[varIndex].id == (parents[varIndex - 1] || object) && - parents.find((x) => x.type == "VariableDeclaration") && - objectPatternCheck(object, parents); - - var functionIndex = parents.findIndex((x) => isFunction(x)); - - // Assignment pattern check! - if (isVariableDeclaration) { - var slicedParents = parents.slice( - 0, - functionIndex != -1 ? Math.min(varIndex, functionIndex) : varIndex - ); - var i = 0; - for (var parent of slicedParents) { - var childNode = slicedParents[i - 1] || object; - if (parent.type === "AssignmentPattern" && parent.right === childNode) { - isVariableDeclaration = false; - break; - } - i++; - } - } - - var forIndex = parents.findIndex((x) => x.type == "ForStatement"); - var isForInitializer = - forIndex != -1 && - parents[forIndex].init == (parents[forIndex - 1] || object); - - var isFunctionDeclaration = - functionIndex != -1 && - parents[functionIndex].type == "FunctionDeclaration" && - parents[functionIndex].id == object; - - var isNamedFunctionExpression = - functionIndex != -1 && - parents[functionIndex].type === "FunctionExpression" && - parents[functionIndex].id === object; - - var isAFunctionParameter = isFunctionParameter(object, parents); - - var isClauseParameter = false; - - // Special case for Catch clauses - var clauseIndex = parents.findIndex((x) => x.type == "CatchClause"); - if (clauseIndex != -1) { - if (parents[clauseIndex].param == (parents[clauseIndex - 1] || object)) { - isClauseParameter = true; - } - } - - var isImportSpecifier = - (parent.type == "ImportDefaultSpecifier" || - parent.type == "ImportSpecifier") && - parent.local == object; - - var isFunctionCall = parent.callee == object; // NewExpression and CallExpression - - var assignmentIndex = parents.findIndex( - (p) => p.type === "AssignmentExpression" - ); - - var isAssignmentLeft = - assignmentIndex !== -1 && - parents[assignmentIndex].left === - (parents[assignmentIndex - 1] || object) && - objectPatternCheck(object, parents); - var isAssignmentValue = - assignmentIndex !== -1 && - parents[assignmentIndex].right === (parents[assignmentIndex - 1] || object); - - var isUpdateExpression = parent.type == "UpdateExpression"; - - var isClassDeclaration = - (parent.type == "ClassDeclaration" || parent.type == "ClassExpression") && - parent.id == object; - var isMethodDefinition = - parent.type == "MethodDefinition" && - parent.key == object && - !parent.computed; - - var isMetaProperty = parent.type == "MetaProperty"; - - var isLabel = parent.type == "LabeledStatement" && parent.label == object; - - // Fix 1: Labels are properly identified - if (parent.type == "BreakStatement" || parent.type == "ContinueStatement") { - if (parent.label == object) { - isLabel = true; - } - } - - var isDeleteExpression = false; - var deleteIndex = parents.findIndex( - (x) => x.type == "UnaryExpression" && x.operator == "delete" - ); - - if (deleteIndex != -1) { - isDeleteExpression = true; - } - - var isReferenced = - !isAccessor && - !isPropertyKey && - !isMetaProperty && - !isLabel && - !object.name.startsWith("0") && - !object.name.startsWith("'"); - - return { - /** - * MemberExpression: `parent.identifier` - */ - isAccessor, - /** - * Property: `{identifier: ...}` - */ - isPropertyKey, - - /** - * `var identifier = ...` - */ - isVariableDeclaration, - /** - * `function identifier(){...}` - */ - isFunctionDeclaration, - /** - * `function a(identifier){...}` - */ - isFunctionParameter: isAFunctionParameter, - - /** - * ```js - * try ... catch ( identifier ) { - * ... - * } - * ``` - */ - isClauseParameter, - - /** - * CallExpression: `identifier()` - */ - isFunctionCall, - /** - * AssignmentExpression: `identifier = ...` - */ - isAssignmentLeft, - /** - * AssignmentExpression (right): `x = identifier` - */ - isAssignmentValue, - /** - * UpdateExpression: `identifier++` - */ - isUpdateExpression, - /** - * ClassDeclaration `class identifier {...}` - */ - isClassDeclaration, - /** - * Method Definition inside a class body - * ```js - * class Rectangle { - * identifier(){...} - * - * get identifier(){...} - * } - * ``` - */ - isMethodDefinition, - - /** - * `new.target` or `yield.input` - */ - isMetaProperty, - - /** - * LabelStatement: `identifier: for ( var i...)` - */ - isLabel, - - /** - * ```js - * for (var i=0; ...) { - * ... - * } - * ``` - */ - isForInitializer, - - /** - * ```js - * import identifier from "..."; - * import {key as identifier} from "..."; - * ``` - */ - isImportSpecifier, - - /** - * ```js - * delete identifier[identifier] - * ``` - */ - isDeleteExpression: isDeleteExpression, - - spec: { - /** - * - `export function identifier()...` - * - `export var identifier = ...` - */ - isExported: - (isVariableDeclaration && - parents[3] && - parents[3].type == "ExportNamedDeclaration") || - (isFunctionDeclaration && - parents[1] && - parents[1].type == "ExportNamedDeclaration"), - - /** - * Is the Identifier defined, i.e a variable declaration, function declaration, parameter, or class definition - */ - isDefined: - isVariableDeclaration || - isFunctionDeclaration || - isNamedFunctionExpression || - isAFunctionParameter || - isClassDeclaration || - isClauseParameter || - isMethodDefinition || - isImportSpecifier, - - /** - * Is the Identifier modified, either by an `AssignmentExpression` or `UpdateExpression` - */ - isModified: isAssignmentLeft || isUpdateExpression || isDeleteExpression, - - /** - * Is the Identifier referenced as a variable. - * - * - true: `if ( identifier ) {...}` - * - false `if ( obj.identifier ) {...}` - * - false `identifier: for ( var ...)` - * - false `var {identifier: ...}` - * - false `break identifier;` - */ - isReferenced: isReferenced, - }, - }; -} - -export function getDefiningIdentifier(object: Node, parents: Node[]): Location { - ok(object.type == "Identifier", "must be identifier"); - ok(typeof object.name === "string"); - ok( - parents[parents.length - 1].type == "Program", - "root node must be type Program. Found '" + - parents[parents.length - 1].type + - "'" - ); - - var seen = new Set(); - var i = 0; - for (var parent of parents) { - var l; - var bestScore = Infinity; - walk(parent, parents.slice(i + 1), (o, p) => { - // if (p.find((x) => seen.has(x))) { - // return "EXIT"; - // } - - if (o.type == "Identifier" && o.name === object.name && o !== object) { - var info = getIdentifierInfo(o, p); - if (info.spec.isDefined) { - var contexts = p.filter((x) => isVarContext(x)); - var definingContext = info.isFunctionDeclaration - ? getVarContext(p[0], p.slice(1)) - : getVarContext(o, p); - - if (parents.includes(definingContext)) { - var index = contexts.indexOf(definingContext); - - if (index < bestScore) { - l = [o, p]; - bestScore = index; - } - } - } - } - }); - - if (l) { - // console.log(l[0].name, "->", l[0], bestScore); - - return l; - } - - seen.add(parent); - i++; - } -} - -export function isFunctionParameter(o: Node, p: Node[], c?: Node) { - ok(o); - ok(p); - validateChain(o, p); - - if (o.type !== "Identifier") { - return false; - } - var object = p.find((x) => isFunction(x) && x.params); - if (!object) { - return false; - } - - c = c || getVarContext(o, p); - if (c === object) { - var pIndex = p.indexOf(object.params); - if (pIndex == -1) { - return false; - } - - var param = p[pIndex - 1] || o; - var paramIndex = object.params.indexOf(param); - ok(paramIndex !== -1); - - var sliced = p.slice(0, pIndex); - - var isReferenced = true; - var i = 0; - for (var node of sliced) { - var down = sliced[i - 1] || o; - ok(down); - - if (node.type) { - if (node.type == "AssignmentPattern" && node.right === down) { - isReferenced = false; - break; - } - - if ( - node.type == "Property" && - node.key === down && - sliced[i + 2] && - sliced[i + 2].type == "ObjectPattern" - ) { - isReferenced = false; - break; - } - } - - i++; - } - - if (isReferenced) { - return true; - } - } - - return false; -} - -export function getFunctionParameters( - object: Node, - parents: Node[] -): [{ type: "Identifier"; name: string }, Node[]][] { - ok(isFunction(object)); - ok(object.params); - - var locations = []; - - walk(object.params, [object, ...parents], (o, p) => { - if (o.type == "Identifier") { - if (isFunctionParameter(o, p, object)) { - locations.push([o, p]); - } - } - }); - - return locations; -} - -export function containsLexicallyBoundVariables(object: Node, parents: Node[]) { - var contains = false; - walk(object, parents, (o, p) => { - if (o.type == "VariableDeclaration") { - if (o.kind === "let" || o.kind === "const") { - // Control Flow Flattening changes the lexical block, therefore this is not possible - // Maybe a transformation to remove let - contains = true; - return "EXIT"; - } - } - - if (o.type == "ClassDeclaration") { - contains = true; - return "EXIT"; - } - }); - - return contains; -} diff --git a/src_old/util/insert.ts b/src_old/util/insert.ts deleted file mode 100644 index ffae140..0000000 --- a/src_old/util/insert.ts +++ /dev/null @@ -1,419 +0,0 @@ -import { ok } from "assert"; -import { getBlock, isBlock } from "../traverse"; -import { Node } from "./gen"; -import { getIdentifierInfo, validateChain } from "./identifiers"; - -export function isClass(object: Node): boolean { - return ( - object.type === "ClassDeclaration" || object.type === "ClassExpression" - ); -} - -/** - * - `FunctionDeclaration` - * - `FunctionExpression` - * - `ArrowFunctionExpression` - * @param object - * @returns - */ -export function isFunction(object: Node): boolean { - return [ - "FunctionDeclaration", - "FunctionExpression", - "ArrowFunctionExpression", - ].includes(object && object.type); -} - -export function isStrictModeFunction(object: Node): boolean { - ok(isFunction(object)); - - return ( - object.body.type === "BlockStatement" && - object.body.body[0] && - object.body.body[0].type === "ExpressionStatement" && - object.body.body[0].directive === "use strict" - ); -} - -/** - * The function context where the object is. - * - * - Determines if async context. - * - Determines variable context. - * - * @param object - * @param parents - */ -export function getFunction(object: Node, parents: Node[]): Node { - return parents.find((x) => isFunction(x)); -} - -/** - * Refers to the current function or Root node - * @param parents - */ -export function getVarContext(object: Node, parents: Node[]): Node { - var fn = getFunction(object, parents); - if (fn) { - return fn; - } - - var top = parents[parents.length - 1] || object; - - if (top) { - ok(top.type == "Program", "Root node not program, its " + top.type); - return top; - } - - throw new Error("Missing root node"); -} - -/** - * `Function` or root node - * @param object - * @returns - */ -export function isVarContext(object: Node) { - return ( - isFunction(object) || - object.type == "Program" || - object.type == "DoExpression" - ); // Stage 1 -} - -/** - * `Block` or root node - * @param object - * @returns - */ -export function isLexContext(object: Node): boolean { - return isBlock(object) || object.type == "Program"; -} - -/** - * Either a `var context` or `lex context` - * @param object - * @returns - */ -export function isContext(object: Node): boolean { - return isVarContext(object) || isLexContext(object); -} - -export function getContexts(object: Node, parents: Node[]): Node[] { - return [object, ...parents].filter((x) => isContext(x)); -} - -/** - * Refers to the current lexical block or Root node. - * @param parents - */ -export function getLexContext(object: Node, parents: Node[]): Node { - var block = getBlock(object, parents); - if (block) { - return block; - } - - var top = parents[parents.length - 1]; - if (!top) { - throw new Error("Missing root node"); - } -} - -export function getDefiningContext(o: Node, p: Node[]): Node { - validateChain(o, p); - ok(o.type == "Identifier"); - var info = getIdentifierInfo(o, p); - - ok(info.spec.isDefined); - - if (info.isVariableDeclaration) { - var variableDeclaration = p.find((x) => x.type == "VariableDeclaration"); - ok(variableDeclaration); - - if ( - variableDeclaration.kind === "let" || - variableDeclaration.kind === "const" - ) { - var context = getVarContext(o, p); - if (context && context.type === "Program") { - return getLexContext(o, p); - } - } - } - - if (info.isFunctionDeclaration) { - return getVarContext(p[0], p.slice(1)); - } - - return getVarContext(o, p); -} - -/** - * A more accurate context finding function. - * @param o Object - * @param p Parents - * @returns Contexts - */ -export function getAllDefiningContexts(o: Node, p: Node[]): Node[] { - var contexts = [getDefiningContext(o, p)]; - - var info = getIdentifierInfo(o, p); - if (info.isFunctionParameter) { - // Get Function - var fn = getFunction(o, p); - - // contexts.push(fn.body); - } - - if (info.isClauseParameter) { - var catchClause = p.find((x) => x.type === "CatchClause"); - if (catchClause) { - return [catchClause]; - } - } - - return contexts; -} - -export function getReferencingContexts( - o: Node, - p: Node[], - info?: ReturnType -): Node[] { - validateChain(o, p); - ok(o.type == "Identifier"); - - if (!info) { - info = getIdentifierInfo(o, p); - } - ok(info.spec.isReferenced); - - return [getVarContext(o, p), getLexContext(o, p)]; -} - -export function getBlockBody(block: Node): Node[] { - if (!block) { - throw new Error("no block body"); - } - if (Array.isArray(block)) { - return block; - } - return getBlockBody(block.body); -} - -export function getIndexDirect(object: Node, parent: Node): string { - return Object.keys(parent).find((x) => parent[x] == object); -} - -/** - * Attempts to a delete a variable/functions declaration. - * @param object - * @param parents - */ -export function deleteDeclaration(object: Node, parents: Node[]) { - validateChain(object, parents); - - // variables - var list = [object, ...parents]; - - var declaratorIndex = list.findIndex((x) => x.type == "VariableDeclarator"); - if (declaratorIndex != -1) { - var declarator = list[declaratorIndex]; // {type: VariableDeclarator, id: Identifier, init: Literal|Expression...} - var declarations = list[declaratorIndex + 1]; // declarator[] - var VariableDeclaration = list[declaratorIndex + 2]; - var body = list[declaratorIndex + 3]; - - deleteDirect(declarator, declarations); - - if (VariableDeclaration.declarations.length == 0) { - deleteDirect(VariableDeclaration, body); - } - } else { - if (object.type != "FunctionDeclaration") { - throw new Error("No method to delete: " + object.type); - } - - deleteDirect(object, parents[0]); - } -} - -/** - * Object must be directly nested in parent - */ -export function deleteDirect(object: Node, parent: Node) { - if (!object) { - throw new Error("object undefined"); - } - - if (!parent) { - throw new Error("parent undefined"); - } - - validateChain(object, [parent]); - - if (typeof parent === "object") { - if (Array.isArray(parent)) { - var index = parent.indexOf(object); - if (index != -1) { - // delete - parent.splice(index, 1); - } else { - console.log("parent=", parent); - console.log("object=", object); - throw new Error("index -1"); - } - } else { - var keyName = Object.keys(parent).find((x) => parent[x] == object); - - if (keyName) { - delete parent[keyName]; - } else { - throw new Error("keyName undefined"); - } - } - } -} - -export function prepend(block: Node, ...nodes: Node[]) { - ok(!Array.isArray(block), "block should not be array"); - - if (block.type == "Program") { - var moveBy = 0; - block.body.forEach((stmt, i) => { - if (stmt.type == "ImportDeclaration") { - if (moveBy == i) { - moveBy++; - } - } - - if ( - stmt.type === "ExpressionStatement" && - typeof stmt.directive === "string" - ) { - if (moveBy == i) { - moveBy++; - } - } - }); - - block.body.splice(moveBy, 0, ...nodes); - } else if (block.type === "SwitchCase") { - block.consequent.unshift(...nodes); - } else { - var bodyArray = getBlockBody(block); - - // Check for 'use strict' - if (bodyArray[0] && bodyArray[0].directive) { - // Insert under 'use strict' directive - bodyArray.splice(1, 0, ...nodes); - } else { - // Prepend at the top of the block - bodyArray.unshift(...nodes); - } - } -} - -export function append(block: Node, ...nodes: Node[]) { - ok(!Array.isArray(block), "block should not be array"); - getBlockBody(block).push(...nodes); -} - -export function clone(object: T): T { - if (typeof object === "object" && object) { - if (Array.isArray(object)) { - var newArray = [] as unknown as any; - object.forEach((element) => { - newArray.push(clone(element)); - }); - - return newArray; - } else { - var newObject = {} as T; - - Object.keys(object).forEach((key) => { - if (!(key + "").startsWith("$")) { - newObject[key] = clone(object[key]); - } - }); - - return newObject; - } - } - - return object as any; -} - -/** - * | Return Value | Description | - * | --- | --- | - * | `"initializer"` | For-statement initializer (`.init`) | - * | `"left-hand"` | For-In/Of-statement left-hand (`.left`) | - * | `false` | None of the above | - * - * Determines if given node is a for-loop initializer. - * - * @param o - * @param p - * @returns - */ -export function isForInitialize( - o: Node, - p: Node[] -): "initializer" | "left-hand" | false { - validateChain(o, p); - - var forIndex = p.findIndex( - (x) => - x.type == "ForStatement" || - x.type == "ForInStatement" || - x.type == "ForOfStatement" - ); - - if ( - p - .slice(0, forIndex) - .find((x) => - ["ArrowFunctionExpression", "BlockStatement"].includes(x.type) - ) - ) { - return false; - } - - if (forIndex !== -1) { - if (p[forIndex].type == "ForStatement") { - if (p[forIndex].init == (p[forIndex - 1] || o)) { - return "initializer"; - } - } else { - if (p[forIndex].left == (p[forIndex - 1] || o)) { - return "left-hand"; - } - } - } - - return false; -} - -/** - * Computes the `function.length` property given the parameter nodes. - * - * @param params - * @returns - */ -export function computeFunctionLength(params: Node[]): number { - var count = 0; - - for (var parameterNode of params) { - if ( - parameterNode.type === "Identifier" || - parameterNode.type === "ObjectPattern" || - parameterNode.type === "ArrayPattern" - ) { - count++; - } else { - break; - } - } - - return count; -} diff --git a/src_old/util/math.ts b/src_old/util/math.ts deleted file mode 100644 index 578f708..0000000 --- a/src_old/util/math.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function getFactors(num: number) { - const isEven = num % 2 === 0; - const max = Math.sqrt(num); - const inc = isEven ? 1 : 2; - let factors = [1, num]; - - for (let curFactor = isEven ? 2 : 3; curFactor <= max; curFactor += inc) { - if (num % curFactor !== 0) continue; - factors.push(curFactor); - let compliment = num / curFactor; - if (compliment !== curFactor) factors.push(compliment); - } - - return factors; -} diff --git a/src_old/util/object.ts b/src_old/util/object.ts deleted file mode 100644 index 9b11404..0000000 --- a/src_old/util/object.ts +++ /dev/null @@ -1,39 +0,0 @@ -export function createObject( - keys: string[], - values: any[] -): { [key: string]: any } { - if (keys.length != values.length) { - throw new Error("length mismatch"); - } - - var newObject = {}; - - keys.forEach((x, i) => { - newObject[x] = values[i]; - }); - - return newObject; -} - -/** - * Removes all `$`-prefixed properties on a deeply nested object. - * - * - Modifies the object. - */ -export function remove$Properties(object: any, seen = new Set()) { - if (typeof object === "object" && object) { - if (seen.has(object)) { - // console.log(object); - // throw new Error("Already seen"); - } - seen.add(object); - - Object.keys(object).forEach((key) => { - if (key.charAt(0) == "$") { - delete object[key]; - } else { - remove$Properties(object[key], seen); - } - }); - } -} diff --git a/src_old/util/random.ts b/src_old/util/random.ts deleted file mode 100644 index eb195dd..0000000 --- a/src_old/util/random.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { ok } from "assert"; -import { - Literal, - ObjectExpression, - Identifier, - Property, - Node, - ArrayExpression, -} from "./gen"; - -/** - * Returns a random element from the given array - * @param choices Array of items - * @returns One of the items in the array at random - */ -export function choice(choices: T[]): T { - var index = Math.floor(Math.random() * choices.length); - return choices[index]; -} - -/** - * Returns a true/false based on the percent chance (0%-100%) - * @param percentChance AS A PERCENTAGE 0 - 100% - */ -export function chance(percentChance: number): boolean { - return Math.random() < percentChance / 100; -} - -/** - * **Mutates the given array** - * @param array - */ -export function shuffle(array: any[]): any[] { - array.sort(() => Math.random() - 0.5); - return array; -} - -/** - * Returns a random string. - */ -export function getRandomString(length: number) { - var result = ""; - var characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var charactersLength = characters.length; - for (var i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} - -export function getRandom(min, max) { - return Math.random() * (max - min) + min; -} - -export function getRandomInteger(min, max) { - return Math.floor(getRandom(min, max)); -} - -export function splitIntoChunks(str: string, size: number) { - ok(typeof str === "string", "str must be typeof string"); - ok(typeof size === "number", "size must be typeof number"); - ok(Math.floor(size) === size, "size must be integer"); - - const numChunks = Math.ceil(str.length / size); - const chunks: string[] = new Array(numChunks); - - for (let i = 0, o = 0; i < numChunks; ++i, o += size) { - chunks[i] = str.substr(o, size); - } - - return chunks; -} - -/** - * Returns a random expression that will test to `false`. - */ -export function getRandomFalseExpression() { - var type = choice(["0", "false", "null", "undefined", "NaN", "emptyString"]); - - switch (type) { - case "0": - return Literal(0); - case "false": - return Literal(false); - case "null": - return Identifier("null"); - case "undefined": - return Identifier("undefined"); - case "NaN": - return Identifier("NaN"); - default: - // case "emptyString": - return Literal(""); - } -} - -/** - * Returns a random expression that will test to `true` - */ -export function getRandomTrueExpression() { - var type = choice([ - "number", - "true", - "Infinity", - "nonEmptyString", - "array", - "object", - ]); - - switch (type) { - case "number": - return Literal(getRandomInteger(1, 100)); - case "true": - return Identifier("true"); - case "Infinity": - return Identifier("Infinity"); - case "nonEmptyString": - return Literal(getRandomString(getRandomInteger(3, 9))); - case "array": - return ArrayExpression([]); - default: - //case "object": - return ObjectExpression([]); - } -} - -export function alphabeticalGenerator(index: number) { - let name = ""; - while (index > 0) { - var t = (index - 1) % 52; - var thisChar = - t >= 26 ? String.fromCharCode(65 + t - 26) : String.fromCharCode(97 + t); - name = thisChar + name; - index = ((index - t) / 52) | 0; - } - if (!name) { - name = "_"; - } - return name; -} - -export function createZeroWidthGenerator() { - var keywords = [ - "if", - "in", - "for", - "let", - "new", - "try", - "var", - "case", - "else", - "null", - "break", - "catch", - "class", - "const", - "super", - "throw", - "while", - "yield", - "delete", - "export", - "import", - "public", - "return", - "switch", - "default", - "finally", - "private", - "continue", - "debugger", - "function", - "arguments", - "protected", - "instanceof", - "await", - "async", - - // new key words and other fun stuff :P - "NaN", - "undefined", - "true", - "false", - "typeof", - "this", - "static", - "void", - "of", - ]; - - var maxSize = 0; - var currentKeyWordsArray: string[] = []; - - function generateArray() { - var result = keywords - .map( - (keyWord) => - keyWord + "\u200C".repeat(Math.max(maxSize - keyWord.length, 1)) - ) - .filter((craftedVariableName) => craftedVariableName.length == maxSize); - - if (!result.length) { - ++maxSize; - return generateArray(); - } - - return shuffle(result); - } - - function getNextVariable(): string { - if (!currentKeyWordsArray.length) { - ++maxSize; - currentKeyWordsArray = generateArray(); - } - return currentKeyWordsArray.pop(); - } - - return { generate: getNextVariable }; -} diff --git a/src_old/util/scope.ts b/src_old/util/scope.ts deleted file mode 100644 index 1fed3cc..0000000 --- a/src_old/util/scope.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ok } from "assert"; -import { isBlock } from "../traverse"; -import { Node } from "./gen"; - -export function isLexicalScope(object: Node) { - return isBlock(object) || object.type == "SwitchCase"; -} - -export function getLexicalScope(object: Node, parents: Node[]): Node { - return [object, ...parents].find((node) => isLexicalScope(node)); -} - -export function getLexicalScopeBody(object: Node): Node[] { - ok(isLexicalScope(object)); - - return isBlock(object) - ? object.body - : object.type === "SwitchCase" - ? object.consequent - : ok("Unhandled case"); -} From 4a2fd32bfed377d0bcfec0816b9cd72c1805c16f Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 19 Nov 2024 18:13:22 -0500 Subject: [PATCH 081/103] Improve Node Module support / New AES Test --- src/constants.ts | 9 + src/transforms/identifier/globalConcealing.ts | 3 +- src/transforms/pack.ts | 10 +- test/code/AES.src.js | 1415 +++++++++++++++++ test/code/AES.test.js | 83 + 5 files changed, 1518 insertions(+), 2 deletions(-) create mode 100644 test/code/AES.src.js create mode 100644 test/code/AES.test.js diff --git a/src/constants.ts b/src/constants.ts index 3e8daaa..52ff416 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -95,6 +95,15 @@ export const reservedIdentifiers = new Set([ "arguments", ]); +/** + * Reserved Node.JS module identifiers. + */ +export const reservedNodeModuleIdentifiers = new Set([ + "module", + "exports", + "require", +]); + export const reservedObjectPrototype = new Set([ "toString", "valueOf", diff --git a/src/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index da43818..e176692 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -7,6 +7,7 @@ import { Order } from "../../order"; import { MULTI_TRANSFORM, reservedIdentifiers, + reservedNodeModuleIdentifiers, variableFunctionName, } from "../../constants"; import { @@ -23,7 +24,7 @@ import { import { ok } from "assert"; const ignoreGlobals = new Set([ - "require", + ...reservedNodeModuleIdentifiers, "__dirname", "eval", "arguments", diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index a152b06..f3c78b9 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -10,6 +10,7 @@ import { GEN_NODE, NodeSymbol, reservedIdentifiers, + reservedNodeModuleIdentifiers, variableFunctionName, WITH_STATEMENT, } from "../constants"; @@ -53,7 +54,14 @@ export default function pack({ Plugin }: PluginArg): PluginObject { const identifierName = path.node.name; if (reservedIdentifiers.has(identifierName)) return; - if (me.obfuscator.options.globalVariables.has(identifierName)) return; + if ( + me.options.target === "node" && + reservedNodeModuleIdentifiers.has(identifierName) + ) { + // Allow module.exports and require + } else { + if (me.options.globalVariables.has(identifierName)) return; + } if (identifierName === variableFunctionName) return; if (identifierName === objectName) return; diff --git a/test/code/AES.src.js b/test/code/AES.src.js new file mode 100644 index 0000000..2030bef --- /dev/null +++ b/test/code/AES.src.js @@ -0,0 +1,1415 @@ +/*! MIT License. Copyright 2015-2018 Richard Moore . See LICENSE.txt. */ +(function (root) { + "use strict"; + + function checkInt(value) { + return parseInt(value) === value; + } + + function checkInts(arrayish) { + if (!checkInt(arrayish.length)) { + return false; + } + + for (var i = 0; i < arrayish.length; i++) { + if (!checkInt(arrayish[i]) || arrayish[i] < 0 || arrayish[i] > 255) { + return false; + } + } + + return true; + } + + function coerceArray(arg, copy) { + // ArrayBuffer view + if (arg.buffer && arg.name === "Uint8Array") { + if (copy) { + if (arg.slice) { + arg = arg.slice(); + } else { + arg = Array.prototype.slice.call(arg); + } + } + + return arg; + } + + // It's an array; check it is a valid representation of a byte + if (Array.isArray(arg)) { + if (!checkInts(arg)) { + throw new Error("Array contains invalid value: " + arg); + } + + return new Uint8Array(arg); + } + + // Something else, but behaves like an array (maybe a Buffer? Arguments?) + if (checkInt(arg.length) && checkInts(arg)) { + return new Uint8Array(arg); + } + + throw new Error("unsupported array-like object"); + } + + function createArray(length) { + return new Uint8Array(length); + } + + function copyArray( + sourceArray, + targetArray, + targetStart, + sourceStart, + sourceEnd + ) { + if (sourceStart != null || sourceEnd != null) { + if (sourceArray.slice) { + sourceArray = sourceArray.slice(sourceStart, sourceEnd); + } else { + sourceArray = Array.prototype.slice.call( + sourceArray, + sourceStart, + sourceEnd + ); + } + } + targetArray.set(sourceArray, targetStart); + } + + var convertUtf8 = (function () { + function toBytes(text) { + var result = [], + i = 0; + text = encodeURI(text); + while (i < text.length) { + var c = text.charCodeAt(i++); + + // if it is a % sign, encode the following 2 bytes as a hex value + if (c === 37) { + result.push(parseInt(text.substr(i, 2), 16)); + i += 2; + + // otherwise, just the actual byte + } else { + result.push(c); + } + } + + return coerceArray(result); + } + + function fromBytes(bytes) { + var result = [], + i = 0; + + while (i < bytes.length) { + var c = bytes[i]; + + if (c < 128) { + result.push(String.fromCharCode(c)); + i++; + } else if (c > 191 && c < 224) { + result.push( + String.fromCharCode(((c & 0x1f) << 6) | (bytes[i + 1] & 0x3f)) + ); + i += 2; + } else { + result.push( + String.fromCharCode( + ((c & 0x0f) << 12) | + ((bytes[i + 1] & 0x3f) << 6) | + (bytes[i + 2] & 0x3f) + ) + ); + i += 3; + } + } + + return result.join(""); + } + + return { + toBytes: toBytes, + fromBytes: fromBytes, + }; + })(); + + var convertHex = (function () { + function toBytes(text) { + var result = []; + for (var i = 0; i < text.length; i += 2) { + result.push(parseInt(text.substr(i, 2), 16)); + } + + return result; + } + + // http://ixti.net/development/javascript/2011/11/11/base64-encodedecode-of-utf8-in-browser-with-js.html + var Hex = "0123456789abcdef"; + + function fromBytes(bytes) { + var result = []; + for (var i = 0; i < bytes.length; i++) { + var v = bytes[i]; + result.push(Hex[(v & 0xf0) >> 4] + Hex[v & 0x0f]); + } + return result.join(""); + } + + return { + toBytes: toBytes, + fromBytes: fromBytes, + }; + })(); + + // Number of rounds by keysize + var numberOfRounds = { 16: 10, 24: 12, 32: 14 }; + + // Round constant words + var rcon = [ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, + 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, + 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, + ]; + + // S-box and Inverse S-box (S is for Substitution) + var S = [ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, + 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, + 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, + 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, + 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, + 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, + 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, + 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, + 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, + 0xb0, 0x54, 0xbb, 0x16, + ]; + var Si = [ + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, + 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, + 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, + 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, + 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, + 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, + 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, + 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, + 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, + 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, + 0x55, 0x21, 0x0c, 0x7d, + ]; + + // Transformations for encryption + var T1 = [ + 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, + 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, + 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, + 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, + 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, + 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, + 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, + 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, + 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, + 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, + 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, + 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, + 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, + 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, + 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, + 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, + 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, + 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, + 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, + 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, + 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, + 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, + 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, + 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, + 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, + 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, + 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, + 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, + 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, + 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, + 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, + 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, + 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, + ]; + var T2 = [ + 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, + 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, + 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, + 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, + 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, + 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, + 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, + 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, + 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, + 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, + 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, + 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, + 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, + 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, + 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, + 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, + 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, + 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, + 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, + 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, + 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, + 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, + 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, + 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, + 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, + 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, + 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, + 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, + 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, + 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, + 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, + 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, + 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, + 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, + 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, + 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, + 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, + 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, + 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, + ]; + var T3 = [ + 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, + 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, + 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, + 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, + 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, + 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, + 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, + 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, + 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, + 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, + 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, + 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, + 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, + 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, + 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, + 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, + 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, + 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, + 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, + 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, + 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, + 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, + 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, + 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, + 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, + 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, + 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, + 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, + 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, + 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, + 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, + 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, + 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, + 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, + 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, + 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, + 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, + 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, + 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, + ]; + var T4 = [ + 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, + 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, + 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, + 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, + 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, + 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, + 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, + 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, + 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, + 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, + 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, + 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, + 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, + 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, + 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, + 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, + 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, + 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, + 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, + 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, + 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, + 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, + 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, + 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, + 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, + 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, + 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, + 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, + 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, + 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, + 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, + 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, + 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, + 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, + 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, + 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, + 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, + 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, + 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, + 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, + ]; + + // Transformations for decryption + var T5 = [ + 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, + 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, + 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, + 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, + 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, + 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, + 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, + 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, + 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, + 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, + 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, + 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, + 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, + 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, + 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, + 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, + 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, + 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, + 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, + 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, + 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, + 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, + 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, + 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, + 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, + 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, + 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, + 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, + 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, + 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, + 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, + 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, + 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, + 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, + 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, + 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, + 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, + 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, + ]; + var T6 = [ + 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, + 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, + 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, + 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, + 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, + 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, + 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, + 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, + 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, + 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, + 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, + 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, + 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, + 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, + 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, + 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, + 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, + 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, + 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, + 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, + 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, + 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, + 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, + 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, + 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, + 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, + 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, + 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, + 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, + 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, + 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, + 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, + 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, + 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, + 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, + 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, + 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, + 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, + 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, + ]; + var T7 = [ + 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, + 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, + 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, + 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, + 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, + 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, + 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, + 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, + 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, + 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, + 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, + 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, + 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, + 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, + 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, + 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, + 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, + 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, + 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, + 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, + 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, + 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, + 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, + 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, + 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, + 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, + 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, + 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, + 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, + 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, + 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, + 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, + 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, + 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, + 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, + 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, + 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, + 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, + 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, + 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, + 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, + 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, + ]; + var T8 = [ + 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, + 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, + 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, + 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, + 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, + 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, + 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, + 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, + 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, + 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, + 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, + 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, + 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, + 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, + 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, + 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, + 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, + 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, + 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, + 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, + 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, + 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, + 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, + 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, + 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, + 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, + 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, + 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, + 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, + 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, + 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, + 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, + 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, + 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, + 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, + 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, + 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, + 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, + 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, + ]; + + // Transformations for decryption key expansion + var U1 = [ + 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, + 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, + 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, + 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, + 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, + 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, + 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, + 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, + 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, + 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, + 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, + 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, + 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, + 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, + 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, + 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, + 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, + 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, + 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, + 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, + 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, + 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, + 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, + 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, + 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, + 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, + 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, + 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, + 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, + 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, + 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, + 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, + 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, + 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, + 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, + 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, + 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, + 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, + 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, + 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, + 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, + 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, + 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3, + ]; + var U2 = [ + 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, + 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, + 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, + 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, + 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, + 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, + 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, + 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, + 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, + 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, + 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, + 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, + 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, + 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, + 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, + 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, + 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, + 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, + 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, + 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, + 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, + 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, + 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, + 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, + 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, + 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, + 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, + 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, + 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, + 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, + 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, + 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, + 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, + 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, + 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, + 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, + 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, + 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, + 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, + 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, + 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, + 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, + 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697, + ]; + var U3 = [ + 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, + 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, + 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, + 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, + 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, + 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, + 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, + 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, + 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, + 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, + 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, + 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, + 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, + 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, + 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, + 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, + 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, + 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, + 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, + 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, + 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, + 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, + 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, + 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, + 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, + 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, + 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, + 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, + 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, + 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, + 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, + 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, + 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, + 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, + 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, + 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, + 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, + 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, + 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, + 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, + 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, + 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, + 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46, + ]; + var U4 = [ + 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, + 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, + 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, + 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, + 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, + 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, + 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, + 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, + 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, + 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, + 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, + 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, + 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, + 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, + 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, + 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, + 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, + 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, + 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, + 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, + 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, + 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, + 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, + 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, + 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, + 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, + 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, + 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, + 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, + 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, + 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, + 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, + 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, + 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, + 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, + 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, + 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, + 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, + 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, + 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, + 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, + 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, + 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d, + ]; + + function convertToInt32(bytes) { + var result = []; + for (var i = 0; i < bytes.length; i += 4) { + result.push( + (bytes[i] << 24) | + (bytes[i + 1] << 16) | + (bytes[i + 2] << 8) | + bytes[i + 3] + ); + } + return result; + } + + var AES = function (key) { + if (!(this instanceof AES)) { + throw Error("AES must be instanitated with `new`"); + } + + Object.defineProperty(this, "key", { + value: coerceArray(key, true), + }); + + this._prepare(); + }; + + AES.prototype._prepare = function () { + var rounds = numberOfRounds[this.key.length]; + if (rounds == null) { + throw new Error("invalid key size (must be 16, 24 or 32 bytes)"); + } + + // encryption round keys + this._Ke = []; + + // decryption round keys + this._Kd = []; + + for (var i = 0; i <= rounds; i++) { + this._Ke.push([0, 0, 0, 0]); + this._Kd.push([0, 0, 0, 0]); + } + + var roundKeyCount = (rounds + 1) * 4; + var KC = this.key.length / 4; + + // convert the key into ints + var tk = convertToInt32(this.key); + + // copy values into round key arrays + var index; + for (var i = 0; i < KC; i++) { + index = i >> 2; + this._Ke[index][i % 4] = tk[i]; + this._Kd[rounds - index][i % 4] = tk[i]; + } + + // key expansion (fips-197 section 5.2) + var rconpointer = 0; + var t = KC, + tt; + while (t < roundKeyCount) { + tt = tk[KC - 1]; + tk[0] ^= + (S[(tt >> 16) & 0xff] << 24) ^ + (S[(tt >> 8) & 0xff] << 16) ^ + (S[tt & 0xff] << 8) ^ + S[(tt >> 24) & 0xff] ^ + (rcon[rconpointer] << 24); + rconpointer += 1; + + // key expansion (for non-256 bit) + if (KC != 8) { + for (var i = 1; i < KC; i++) { + tk[i] ^= tk[i - 1]; + } + + // key expansion for 256-bit keys is "slightly different" (fips-197) + } else { + for (var i = 1; i < KC / 2; i++) { + tk[i] ^= tk[i - 1]; + } + tt = tk[KC / 2 - 1]; + + tk[KC / 2] ^= + S[tt & 0xff] ^ + (S[(tt >> 8) & 0xff] << 8) ^ + (S[(tt >> 16) & 0xff] << 16) ^ + (S[(tt >> 24) & 0xff] << 24); + + for (var i = KC / 2 + 1; i < KC; i++) { + tk[i] ^= tk[i - 1]; + } + } + + // copy values into round key arrays + var i = 0, + r, + c; + while (i < KC && t < roundKeyCount) { + r = t >> 2; + c = t % 4; + this._Ke[r][c] = tk[i]; + this._Kd[rounds - r][c] = tk[i++]; + t++; + } + } + + // inverse-cipher-ify the decryption round key (fips-197 section 5.3) + for (var r = 1; r < rounds; r++) { + for (var c = 0; c < 4; c++) { + tt = this._Kd[r][c]; + this._Kd[r][c] = + U1[(tt >> 24) & 0xff] ^ + U2[(tt >> 16) & 0xff] ^ + U3[(tt >> 8) & 0xff] ^ + U4[tt & 0xff]; + } + } + }; + + AES.prototype.encrypt = function (plaintext) { + if (plaintext.length != 16) { + throw new Error("invalid plaintext size (must be 16 bytes)"); + } + + var rounds = this._Ke.length - 1; + var a = [0, 0, 0, 0]; + + // convert plaintext to (ints ^ key) + var t = convertToInt32(plaintext); + for (var i = 0; i < 4; i++) { + t[i] ^= this._Ke[0][i]; + } + + // apply round transforms + for (var r = 1; r < rounds; r++) { + for (var i = 0; i < 4; i++) { + a[i] = + T1[(t[i] >> 24) & 0xff] ^ + T2[(t[(i + 1) % 4] >> 16) & 0xff] ^ + T3[(t[(i + 2) % 4] >> 8) & 0xff] ^ + T4[t[(i + 3) % 4] & 0xff] ^ + this._Ke[r][i]; + } + t = a.slice(); + } + + // the last round is special + var result = createArray(16), + tt; + for (var i = 0; i < 4; i++) { + tt = this._Ke[rounds][i]; + result[4 * i] = (S[(t[i] >> 24) & 0xff] ^ (tt >> 24)) & 0xff; + result[4 * i + 1] = + (S[(t[(i + 1) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff; + result[4 * i + 2] = (S[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff; + result[4 * i + 3] = (S[t[(i + 3) % 4] & 0xff] ^ tt) & 0xff; + } + + return result; + }; + + AES.prototype.decrypt = function (ciphertext) { + if (ciphertext.length != 16) { + throw new Error("invalid ciphertext size (must be 16 bytes)"); + } + + var rounds = this._Kd.length - 1; + var a = [0, 0, 0, 0]; + + // convert plaintext to (ints ^ key) + var t = convertToInt32(ciphertext); + for (var i = 0; i < 4; i++) { + t[i] ^= this._Kd[0][i]; + } + + // apply round transforms + for (var r = 1; r < rounds; r++) { + for (var i = 0; i < 4; i++) { + a[i] = + T5[(t[i] >> 24) & 0xff] ^ + T6[(t[(i + 3) % 4] >> 16) & 0xff] ^ + T7[(t[(i + 2) % 4] >> 8) & 0xff] ^ + T8[t[(i + 1) % 4] & 0xff] ^ + this._Kd[r][i]; + } + t = a.slice(); + } + + // the last round is special + var result = createArray(16), + tt; + for (var i = 0; i < 4; i++) { + tt = this._Kd[rounds][i]; + result[4 * i] = (Si[(t[i] >> 24) & 0xff] ^ (tt >> 24)) & 0xff; + result[4 * i + 1] = + (Si[(t[(i + 3) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff; + result[4 * i + 2] = (Si[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff; + result[4 * i + 3] = (Si[t[(i + 1) % 4] & 0xff] ^ tt) & 0xff; + } + + return result; + }; + + /** + * Mode Of Operation - Electonic Codebook (ECB) + */ + var ModeOfOperationECB = function (key) { + if (!(this instanceof ModeOfOperationECB)) { + throw Error("AES must be instanitated with `new`"); + } + + this.description = "Electronic Code Block"; + this.name = "ecb"; + + this._aes = new AES(key); + }; + + ModeOfOperationECB.prototype.encrypt = function (plaintext) { + plaintext = coerceArray(plaintext); + + if (plaintext.length % 16 !== 0) { + throw new Error("invalid plaintext size (must be multiple of 16 bytes)"); + } + + var ciphertext = createArray(plaintext.length); + var block = createArray(16); + + for (var i = 0; i < plaintext.length; i += 16) { + copyArray(plaintext, block, 0, i, i + 16); + block = this._aes.encrypt(block); + copyArray(block, ciphertext, i); + } + + return ciphertext; + }; + + ModeOfOperationECB.prototype.decrypt = function (ciphertext) { + ciphertext = coerceArray(ciphertext); + + if (ciphertext.length % 16 !== 0) { + throw new Error("invalid ciphertext size (must be multiple of 16 bytes)"); + } + + var plaintext = createArray(ciphertext.length); + var block = createArray(16); + + for (var i = 0; i < ciphertext.length; i += 16) { + copyArray(ciphertext, block, 0, i, i + 16); + block = this._aes.decrypt(block); + copyArray(block, plaintext, i); + } + + return plaintext; + }; + + /** + * Mode Of Operation - Cipher Block Chaining (CBC) + */ + var ModeOfOperationCBC = function (key, iv) { + if (!(this instanceof ModeOfOperationCBC)) { + throw Error("AES must be instanitated with `new`"); + } + + this.description = "Cipher Block Chaining"; + this.name = "cbc"; + + if (!iv) { + iv = createArray(16); + } else if (iv.length != 16) { + throw new Error("invalid initialation vector size (must be 16 bytes)"); + } + + this._lastCipherblock = coerceArray(iv, true); + + this._aes = new AES(key); + }; + + ModeOfOperationCBC.prototype.encrypt = function (plaintext) { + plaintext = coerceArray(plaintext); + + if (plaintext.length % 16 !== 0) { + throw new Error("invalid plaintext size (must be multiple of 16 bytes)"); + } + + var ciphertext = createArray(plaintext.length); + var block = createArray(16); + + for (var i = 0; i < plaintext.length; i += 16) { + copyArray(plaintext, block, 0, i, i + 16); + + for (var j = 0; j < 16; j++) { + block[j] ^= this._lastCipherblock[j]; + } + + this._lastCipherblock = this._aes.encrypt(block); + copyArray(this._lastCipherblock, ciphertext, i); + } + + return ciphertext; + }; + + ModeOfOperationCBC.prototype.decrypt = function (ciphertext) { + ciphertext = coerceArray(ciphertext); + + if (ciphertext.length % 16 !== 0) { + throw new Error("invalid ciphertext size (must be multiple of 16 bytes)"); + } + + var plaintext = createArray(ciphertext.length); + var block = createArray(16); + + for (var i = 0; i < ciphertext.length; i += 16) { + copyArray(ciphertext, block, 0, i, i + 16); + block = this._aes.decrypt(block); + + for (var j = 0; j < 16; j++) { + plaintext[i + j] = block[j] ^ this._lastCipherblock[j]; + } + + copyArray(ciphertext, this._lastCipherblock, 0, i, i + 16); + } + + return plaintext; + }; + + /** + * Mode Of Operation - Cipher Feedback (CFB) + */ + var ModeOfOperationCFB = function (key, iv, segmentSize) { + if (!(this instanceof ModeOfOperationCFB)) { + throw Error("AES must be instanitated with `new`"); + } + + this.description = "Cipher Feedback"; + this.name = "cfb"; + + if (!iv) { + iv = createArray(16); + } else if (iv.length != 16) { + throw new Error("invalid initialation vector size (must be 16 size)"); + } + + if (!segmentSize) { + segmentSize = 1; + } + + this.segmentSize = segmentSize; + + this._shiftRegister = coerceArray(iv, true); + + this._aes = new AES(key); + }; + + ModeOfOperationCFB.prototype.encrypt = function (plaintext) { + if (plaintext.length % this.segmentSize != 0) { + throw new Error("invalid plaintext size (must be segmentSize bytes)"); + } + + var encrypted = coerceArray(plaintext, true); + + var xorSegment; + for (var i = 0; i < encrypted.length; i += this.segmentSize) { + xorSegment = this._aes.encrypt(this._shiftRegister); + for (var j = 0; j < this.segmentSize; j++) { + encrypted[i + j] ^= xorSegment[j]; + } + + // Shift the register + copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize); + copyArray( + encrypted, + this._shiftRegister, + 16 - this.segmentSize, + i, + i + this.segmentSize + ); + } + + return encrypted; + }; + + ModeOfOperationCFB.prototype.decrypt = function (ciphertext) { + if (ciphertext.length % this.segmentSize != 0) { + throw new Error("invalid ciphertext size (must be segmentSize bytes)"); + } + + var plaintext = coerceArray(ciphertext, true); + + var xorSegment; + for (var i = 0; i < plaintext.length; i += this.segmentSize) { + xorSegment = this._aes.encrypt(this._shiftRegister); + + for (var j = 0; j < this.segmentSize; j++) { + plaintext[i + j] ^= xorSegment[j]; + } + + // Shift the register + copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize); + copyArray( + ciphertext, + this._shiftRegister, + 16 - this.segmentSize, + i, + i + this.segmentSize + ); + } + + return plaintext; + }; + + /** + * Mode Of Operation - Output Feedback (OFB) + */ + var ModeOfOperationOFB = function (key, iv) { + if (!(this instanceof ModeOfOperationOFB)) { + throw Error("AES must be instanitated with `new`"); + } + + this.description = "Output Feedback"; + this.name = "ofb"; + + if (!iv) { + iv = createArray(16); + } else if (iv.length != 16) { + throw new Error("invalid initialation vector size (must be 16 bytes)"); + } + + this._lastPrecipher = coerceArray(iv, true); + this._lastPrecipherIndex = 16; + + this._aes = new AES(key); + }; + + ModeOfOperationOFB.prototype.encrypt = function (plaintext) { + var encrypted = coerceArray(plaintext, true); + + for (var i = 0; i < encrypted.length; i++) { + if (this._lastPrecipherIndex === 16) { + this._lastPrecipher = this._aes.encrypt(this._lastPrecipher); + this._lastPrecipherIndex = 0; + } + encrypted[i] ^= this._lastPrecipher[this._lastPrecipherIndex++]; + } + + return encrypted; + }; + + // Decryption is symetric + ModeOfOperationOFB.prototype.decrypt = ModeOfOperationOFB.prototype.encrypt; + + /** + * Counter object for CTR common mode of operation + */ + var Counter = function (initialValue) { + if (!(this instanceof Counter)) { + throw Error("Counter must be instanitated with `new`"); + } + + // We allow 0, but anything false-ish uses the default 1 + if (initialValue !== 0 && !initialValue) { + initialValue = 1; + } + + if (typeof initialValue === "number") { + this._counter = createArray(16); + this.setValue(initialValue); + } else { + this.setBytes(initialValue); + } + }; + + Counter.prototype.setValue = function (value) { + if (typeof value !== "number" || parseInt(value) != value) { + throw new Error("invalid counter value (must be an integer)"); + } + + // We cannot safely handle numbers beyond the safe range for integers + if (value > Number.MAX_SAFE_INTEGER) { + throw new Error("integer value out of safe range"); + } + + for (var index = 15; index >= 0; --index) { + this._counter[index] = value % 256; + value = parseInt(value / 256); + } + }; + + Counter.prototype.setBytes = function (bytes) { + bytes = coerceArray(bytes, true); + + if (bytes.length != 16) { + throw new Error("invalid counter bytes size (must be 16 bytes)"); + } + + this._counter = bytes; + }; + + Counter.prototype.increment = function () { + for (var i = 15; i >= 0; i--) { + if (this._counter[i] === 255) { + this._counter[i] = 0; + } else { + this._counter[i]++; + break; + } + } + }; + + /** + * Mode Of Operation - Counter (CTR) + */ + var ModeOfOperationCTR = function (key, counter) { + if (!(this instanceof ModeOfOperationCTR)) { + throw Error("AES must be instanitated with `new`"); + } + + this.description = "Counter"; + this.name = "ctr"; + + if (!(counter instanceof Counter)) { + counter = new Counter(counter); + } + + this._counter = counter; + + this._remainingCounter = null; + this._remainingCounterIndex = 16; + + this._aes = new AES(key); + }; + + ModeOfOperationCTR.prototype.encrypt = function (plaintext) { + var encrypted = coerceArray(plaintext, true); + + for (var i = 0; i < encrypted.length; i++) { + if (this._remainingCounterIndex === 16) { + this._remainingCounter = this._aes.encrypt(this._counter._counter); + this._remainingCounterIndex = 0; + this._counter.increment(); + } + encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++]; + } + + return encrypted; + }; + + // Decryption is symetric + ModeOfOperationCTR.prototype.decrypt = ModeOfOperationCTR.prototype.encrypt; + + /////////////////////// + // Padding + + // See:https://tools.ietf.org/html/rfc2315 + function pkcs7pad(data) { + data = coerceArray(data, true); + var padder = 16 - (data.length % 16); + var result = createArray(data.length + padder); + copyArray(data, result); + for (var i = data.length; i < result.length; i++) { + result[i] = padder; + } + return result; + } + + function pkcs7strip(data) { + data = coerceArray(data, true); + if (data.length < 16) { + throw new Error("PKCS#7 invalid length"); + } + + var padder = data[data.length - 1]; + if (padder > 16) { + throw new Error("PKCS#7 padding byte out of range"); + } + + var length = data.length - padder; + for (var i = 0; i < padder; i++) { + if (data[length + i] !== padder) { + throw new Error("PKCS#7 invalid padding byte"); + } + } + + var result = createArray(length); + copyArray(data, result, 0, 0, length); + return result; + } + + /////////////////////// + // Exporting + + // The block cipher + var aesjs = { + AES: AES, + Counter: Counter, + + ModeOfOperation: { + ecb: ModeOfOperationECB, + cbc: ModeOfOperationCBC, + cfb: ModeOfOperationCFB, + ofb: ModeOfOperationOFB, + ctr: ModeOfOperationCTR, + }, + + utils: { + hex: convertHex, + utf8: convertUtf8, + }, + + padding: { + pkcs7: { + pad: pkcs7pad, + strip: pkcs7strip, + }, + }, + + _arrayTest: { + coerceArray: coerceArray, + createArray: createArray, + copyArray: copyArray, + }, + }; + + // node.js + if (typeof exports !== "undefined") { + module.exports = aesjs; + + // RequireJS/AMD + // http://www.requirejs.org/docs/api.html + // https://github.com/amdjs/amdjs-api/wiki/AMD + } else if (typeof define === "function" && define.amd) { + define([], function () { + return aesjs; + }); + + // Web Browsers + } else { + // If there was an existing library at "aesjs" make sure it's still available + if (root.aesjs) { + aesjs._aesjs = root.aesjs; + } + + root.aesjs = aesjs; + } +})(this); diff --git a/test/code/AES.test.js b/test/code/AES.test.js new file mode 100644 index 0000000..2767362 --- /dev/null +++ b/test/code/AES.test.js @@ -0,0 +1,83 @@ +import { readFileSync, writeFileSync } from "fs"; +import { join } from "path"; +import JsConfuser from "../../src/index"; +import { ok } from "assert"; + +var AES_JS = readFileSync(join(__dirname, "./AES.src.js"), "utf-8"); + +test("Variant #1: AES-JS on 'High' Preset", async () => { + var { code } = await JsConfuser.obfuscate(AES_JS, { + target: "node", + preset: "high", + }); + + // Simulate Node-JS Module import + var exports = {}; + var module = { exports }; + var require = () => { + ok(false, "require() is disabled"); + }; + + eval(code); + + // Module.exports is now AES-JS + var aesjs = module.exports; + + // Ensure that the module is properly loaded + expect(typeof aesjs).toStrictEqual("object"); + expect(Object.keys(aesjs)).toStrictEqual([ + "AES", + "Counter", + "ModeOfOperation", + "utils", + "padding", + "_arrayTest", + ]); + expect(Object.keys(aesjs.utils.hex)).toStrictEqual(["toBytes", "fromBytes"]); + expect(Object.keys(aesjs.utils.utf8)).toStrictEqual(["toBytes", "fromBytes"]); + expect(Object.keys(aesjs.ModeOfOperation)).toStrictEqual([ + "ecb", + "cbc", + "cfb", + "ofb", + "ctr", + ]); + + // Test AES Encryption/Decryption + + // An example 256-bit key + var key = Buffer.from( + "55e3af2655dd72b9f32456042f39bae9accff6259159e608be55a1aa313c598d", + "hex" + ); + + // Convert text to bytes + var text = "Text may be any length you wish, no padding is required."; + var textBytes = aesjs.utils.utf8.toBytes(text); + + // The counter is optional, and if omitted will begin at 1 + var aesCtr = new aesjs.ModeOfOperation.ctr(key); + var encryptedBytes = aesCtr.encrypt(textBytes); + + // To print or store the binary data, you may convert it to hex + var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes); + expect(encryptedHex).toStrictEqual( + "7a0ed7de317642c742bf4d0e3bfead255a2a986e86644b7c14b2fd54feea5cea06970e41e6e391a3d653ae836e7240147f547b028df59efb" + ); + // "7a0ed7de317642c742bf4d0e3bfead255a2a986e86644b7c14b2fd54feea5cea06970e41e6e391a3d653ae836e7240147f547b028df59efb + + // When ready to decrypt the hex string, convert it back to bytes + var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex); + + // The counter mode of operation maintains internal state, so to + // decrypt a new instance must be instantiated. + var aesCtr = new aesjs.ModeOfOperation.ctr(key); + var decryptedBytes = aesCtr.decrypt(encryptedBytes); + + // Convert our bytes back into text + var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); + expect(decryptedText).toStrictEqual( + "Text may be any length you wish, no padding is required." + ); + // "Text may be any length you wish, no padding is required." +}); From 85addf3a37ee5dfe548662486d2a0289ba2a647f Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 01:53:46 -0500 Subject: [PATCH 082/103] Add Prepack Breaker to Self Defending --- src/transforms/lock/lock.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index 7c59771..3fb5fee 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -116,6 +116,13 @@ export default ({ Plugin }: PluginArg): PluginObject => { } } + // Prepack Breaker + if(Math.random() > 1) { + while(true) { + console.log("Prepack Breaker"); + } + } + return namedFunction(); } )(); @@ -159,14 +166,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { me.globalState.lock.createCountermeasuresCode = createCountermeasuresCode; function applyLockToBlock(path: NodePath, customLock: CustomLock) { - let times = timesMap.get(customLock); - - if (typeof times === "undefined") { - times = 0; - } + let times = timesMap.get(customLock) || 0; - let maxCount = customLock.maxCount || 100; // 100 is default max count - let minCount = customLock.minCount || 1; // 1 is default min count + let maxCount = customLock.maxCount ?? 100; // 100 is default max count + let minCount = customLock.minCount ?? 1; // 1 is default min count if (maxCount >= 0 && times > maxCount) { // Limit creation, allowing -1 to disable the limit entirely From 9525579c4433150dbec3b6c90b41a277b85a680f Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 01:55:18 -0500 Subject: [PATCH 083/103] Improve `Pack` and `RGF` for sharing global variables --- src/index.ts | 18 +++++++++++++----- src/obfuscationResult.ts | 8 +++++++- src/obfuscator.ts | 11 +++++------ src/transforms/pack.ts | 30 ++++++++++++++++++++++++++---- src/transforms/rgf.ts | 22 +++++++++++++--------- test/transforms/pack.test.ts | 22 ++++++++++++++++++++++ 6 files changed, 86 insertions(+), 25 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3c8cf64..22f36dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,10 +32,16 @@ export async function obfuscateWithProfiler( sourceCode: string, options: ObfuscateOptions, profiler: { - callback: ProfilerCallback; - performance: { now(): number }; - } + callback?: ProfilerCallback; + performance?: { now(): number }; + } = {} ): Promise { + if (!profiler.performance) { + profiler.performance = { + now: () => Date.now(), + }; + } + const startTime = performance.now(); const obfuscator = new Obfuscator(options); @@ -54,12 +60,14 @@ export async function obfuscateWithProfiler( ast = obfuscator.obfuscateAST(ast, { profiler: (log: ProfilerLog) => { var nowTime = performance.now(); - transformMap[log.currentTransform] = { + let entry = { transformTime: nowTime - currentTransformTime, changeData: {}, }; + + transformMap[log.currentTransform] = entry; currentTransformTime = nowTime; - profiler.callback(log); + profiler.callback?.(log, entry, ast); }, }); diff --git a/src/obfuscationResult.ts b/src/obfuscationResult.ts index 028a5c2..ad3b9de 100644 --- a/src/obfuscationResult.ts +++ b/src/obfuscationResult.ts @@ -1,4 +1,5 @@ import { PluginInstance } from "./transforms/plugin"; +import { File } from "@babel/types"; /** * Obfuscation result object. @@ -23,6 +24,7 @@ export interface ProfileData { [transformName: string]: { transformTime: number; changeData: PluginInstance["changeData"]; + fileSize?: string; }; }; } @@ -30,7 +32,11 @@ export interface ProfileData { /** * A callback function that is called when a transform is applied. */ -export type ProfilerCallback = (log: ProfilerLog) => void; +export type ProfilerCallback = ( + log: ProfilerLog, + transformEntry?: object, + ast?: File +) => void; /** * The current progress of the obfuscation process. diff --git a/src/obfuscator.ts b/src/obfuscator.ts index f135dd7..b44cf0a 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -13,6 +13,7 @@ import { PluginInstance, PluginObject, } from "./transforms/plugin"; +import { createObject } from "./utils/object-utils"; // Transforms import preparation from "./transforms/preparation"; @@ -39,8 +40,7 @@ import opaquePredicates from "./transforms/opaquePredicates"; import minify from "./transforms/minify"; import finalizer from "./transforms/finalizer"; import integrity from "./transforms/lock/integrity"; -import pack from "./transforms/pack"; -import { createObject } from "./utils/object-utils"; +import pack, { PackInterface } from "./transforms/pack"; export const DEFAULT_OPTIONS: ObfuscateOptions = { target: "node", @@ -80,6 +80,9 @@ export default class Obfuscator { }, }; + // Pack Interface for sharing globals across RGF functions + packInterface: PackInterface; + isInternalVariable(name: string) { return Object.values(this.globalState.internals).includes(name); } @@ -255,7 +258,6 @@ export default class Obfuscator { ast: babel.types.File, options?: { profiler?: ProfilerCallback; - disablePack?: boolean; } ): babel.types.File { let finalASTHandler: PluginObject["finalASTHandler"][] = []; @@ -264,9 +266,6 @@ export default class Obfuscator { this.index = i; const { plugin, pluginInstance } = this.plugins[i]; - // Skip pack if disabled - if (pluginInstance.order === Order.Pack && options?.disablePack) continue; - if (this.options.verbose) { console.log( `Applying ${pluginInstance.name} (${i + 1}/${this.plugins.length})` diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index f3c78b9..a7e663f 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -17,16 +17,36 @@ import { import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; +export interface PackInterface { + objectName: string; + mappings: Map; + setterPropsNeeded: Set; + typeofMappings: Map; +} + export default function pack({ Plugin }: PluginArg): PluginObject { const me = Plugin(Order.Pack, { changeData: { globals: 0, }, }); - const objectName = me.obfuscator.nameGen.generate(); - const mappings = new Map(); - const setterPropsNeeded = new Set(); - const typeofMappings = new Map(); + + // RGF functions will re-use parent Pack Interface + let packInterface = me.obfuscator.parentObfuscator?.packInterface; + + // Create new Pack Interface (root) + if (!packInterface) { + packInterface = { + objectName: me.obfuscator.nameGen.generate(), + mappings: new Map(), + setterPropsNeeded: new Set(), + typeofMappings: new Map(), + }; + me.obfuscator.packInterface = packInterface; + } + + const { objectName, mappings, setterPropsNeeded, typeofMappings } = + packInterface; const prependNodes: t.Statement[] = []; @@ -119,6 +139,8 @@ export default function pack({ Plugin }: PluginArg): PluginObject { // Final AST handler // Very last step in the obfuscation process finalASTHandler(ast) { + if (me.obfuscator.parentObfuscator) return ast; // Only for root obfuscator + // Create object expression // Very similar to flatten, maybe refactor to use the same code const objectProperties: t.ObjectMethod[] = []; diff --git a/src/transforms/rgf.ts b/src/transforms/rgf.ts index 01d2e58..8a1c4de 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -248,16 +248,20 @@ export default ({ Plugin }: PluginArg): PluginObject => { .map((plugin) => plugin.pluginInstance.order) ); - newObfuscator.plugins = newObfuscator.plugins.filter((plugin) => { - return ( - plugin.pluginInstance.order == Order.Preparation || - !hasRan.has(plugin.pluginInstance.order) - ); - }); + // Global Concealing will likely cause issues when Pack is also enabled + const disallowedTransforms = new Set([Order.GlobalConcealing]); + + newObfuscator.plugins = newObfuscator.plugins.filter( + ({ pluginInstance }) => { + return ( + (pluginInstance.order == Order.Preparation || + !hasRan.has(pluginInstance.order)) && + !disallowedTransforms.has(pluginInstance.order) + ); + } + ); - newObfuscator.obfuscateAST(evalFile, { - disablePack: true, - }); + newObfuscator.obfuscateAST(evalFile); const generated = Obfuscator.generateCode(evalFile); diff --git a/test/transforms/pack.test.ts b/test/transforms/pack.test.ts index 9193147..d0b5fb3 100644 --- a/test/transforms/pack.test.ts +++ b/test/transforms/pack.test.ts @@ -75,3 +75,25 @@ test("Variant #3: Allow custom implementation to preserve globals", async () => expect(TEST_OUTPUT).toStrictEqual("Hello World"); }); + +test("Variant #4: RGF functions can access globals", async () => { + var { code } = await JsConfuser.obfuscate( + ` + function abc() { + TEST_OUTPUT = "Correct Value"; + } + + abc(); + `, + { + target: "node", + rgf: true, + pack: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); From e4cb8ac5b50e6e089ac0732dfd7d3b99d8ee53aa Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 01:56:38 -0500 Subject: [PATCH 084/103] Improve `Minify` for simple object/array destructuring --- src/transforms/minify.ts | 97 +++++++++++++++++++++------------- test/transforms/minify.test.ts | 48 +++++++++++------ 2 files changed, 93 insertions(+), 52 deletions(-) diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index edb8e6e..a1d7125 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -23,6 +23,45 @@ identifierMap.set("Infinity", () => t.binaryExpression("/", t.numericLiteral(1), t.numericLiteral(0)) ); +function trySimpleDestructuring(id, init) { + // Simple array/object destructuring + if (id.isArrayPattern() && init.isArrayExpression()) { + const elements = id.get("elements"); + const initElements = init.get("elements"); + + if (elements.length === 1 && initElements.length === 1) { + id.replaceWith(elements[0]); + init.replaceWith(initElements[0]); + } + } + + if (id.isObjectPattern() && init.isObjectExpression()) { + const properties = id.get("properties"); + const initProperties = init.get("properties"); + + if (properties.length === 1 && initProperties.length === 1) { + const firstProperty = properties[0]; + const firstInitProperty = initProperties[0]; + + if ( + firstProperty.isObjectProperty() && + firstInitProperty.isObjectProperty() + ) { + const firstKey = firstProperty.get("key"); + const firstInitKey = firstInitProperty.get("key"); + if ( + firstKey.isIdentifier() && + firstInitKey.isIdentifier() && + firstKey.node.name === firstInitKey.node.name + ) { + id.replaceWith(firstProperty.node.value); + init.replaceWith(firstInitProperty.node.value); + } + } + } + } +} + /** * Minify removes unnecessary code and shortens the length for file size. * @@ -260,42 +299,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const id = path.get("id"); const init = path.get("init"); - // Simple array/object destructuring - if (id.isArrayPattern() && init.isArrayExpression()) { - const elements = id.get("elements"); - const initElements = init.get("elements"); - - if (elements.length === 1 && initElements.length === 1) { - id.replaceWith(elements[0]); - init.replaceWith(initElements[0]); - } - } - - if (id.isObjectPattern() && init.isObjectExpression()) { - const properties = id.get("properties"); - const initProperties = init.get("properties"); - - if (properties.length === 1 && initProperties.length === 1) { - const firstProperty = properties[0]; - const firstInitProperty = initProperties[0]; - - if ( - firstProperty.isObjectProperty() && - firstInitProperty.isObjectProperty() - ) { - const firstKey = firstProperty.get("key"); - const firstInitKey = firstInitProperty.get("key"); - if ( - firstKey.isIdentifier() && - firstInitKey.isIdentifier() && - firstKey.node.name === firstInitKey.node.name - ) { - id.replaceWith(firstProperty.node.value); - init.replaceWith(firstInitProperty.node.value); - } - } - } - } + trySimpleDestructuring(id, init); // Remove unused variables // Can only remove if it's pure @@ -326,6 +330,27 @@ export default ({ Plugin }: PluginArg): PluginObject => { } }, }, + // Simple destructuring + // Simple arithmetic operations + AssignmentExpression: { + exit(path) { + if (path.node.operator === "=") { + trySimpleDestructuring(path.get("left"), path.get("right")); + } + if (path.node.operator === "+=") { + const left = path.get("left"); + const right = path.get("right"); + + // a += 1 -> a++ + if (right.isNumericLiteral({ value: 1 })) { + if (left.isIdentifier() || left.isMemberExpression()) { + path.replaceWith(t.updateExpression("++", left.node)); + } + } + } + }, + }, + // return undefined->return ReturnStatement: { exit(path) { diff --git a/test/transforms/minify.test.ts b/test/transforms/minify.test.ts index 5dda25f..0fc79a3 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -152,48 +152,56 @@ test("Variant #5: Work when shortening nested if-statements", async () => { expect(TEST_OUTPUT).toStrictEqual([true, true, true]); }); -test("Variant #8: Shorten simple array destructuring", async () => { +test("Variant #8: Shorten simple arithmetic", async () => { // Valid var { code: output } = await JsConfuser.obfuscate( - `var [x] = [1]; TEST_OUTPUT = x;`, + `var x = 1; x += 1; TEST_OUTPUT = x`, { target: "node", minify: true, } ); - expect(output).toContain("var x=1"); + expect(output).toContain("var x=1;x++"); var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(1); - - // Invalid - var { code: output2 } = await JsConfuser.obfuscate(`var [x, y] = [1]`, { - target: "node", - minify: true, - }); - - expect(output2).toContain("var[x,y]"); + expect(TEST_OUTPUT).toStrictEqual(2); }); -test("Variant #9: Shorten simple object destructuring", async () => { +test("Variant #9: Shorten simple object and array destructuring", async () => { // Valid var { code: output } = await JsConfuser.obfuscate( - `var {x} = {x: 1}; TEST_OUTPUT = x;`, + ` + var {firstName} = {firstName: "John"}; + var [firstElement] = ["Doe"]; + + // Assignment expressions + var accountType; + ({type: accountType} = {type: "Checking"}); + + var balance; + [balance] = [100]; + + TEST_OUTPUT = firstName + " " + firstElement + " has a " + accountType + " account with a balance of $" + balance; + `, { target: "node", minify: true, } ); - expect(output).toContain("var x=1"); + expect(output).toContain('var firstName="John",firstElement="Doe"'); + expect(output).toContain('accountType="Checking"'); + expect(output).toContain("balance=100"); var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(1); + expect(TEST_OUTPUT).toStrictEqual( + "John Doe has a Checking account with a balance of $100" + ); // Valid var { code: output2 } = await JsConfuser.obfuscate( @@ -226,6 +234,14 @@ test("Variant #9: Shorten simple object destructuring", async () => { }); expect(output4).toContain("var{y}="); + + // Invalid + var { code: output5 } = await JsConfuser.obfuscate(`var [x, y] = [1]`, { + target: "node", + minify: true, + }); + + expect(output5).toContain("var[x,y]"); }); test("Variant #10: Shorten booleans", async () => { From 86886153b49705ff362cdc228304f5f0e206a0fb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 01:57:47 -0500 Subject: [PATCH 085/103] Improve default limit logic on Dead Code and Opaque Predicates --- src/transforms/deadCode.ts | 23 ++++++++++++++++------- src/transforms/opaquePredicates.ts | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/transforms/deadCode.ts b/src/transforms/deadCode.ts index 5f8a5ef..5524add 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -1,5 +1,5 @@ import { PluginArg, PluginObject } from "./plugin"; -import { chance, choice } from "../utils/random-utils"; +import { choice } from "../utils/random-utils"; import { deadCodeTemplates } from "../templates/deadCodeTemplates"; import { Order } from "../order"; import * as t from "@babel/types"; @@ -13,7 +13,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { deadCode: 0, }, }); - let created = 0; let predicateGen = new PredicateGen(me); return { @@ -26,22 +25,32 @@ export default ({ Plugin }: PluginArg): PluginObject => { return; } - if (typeof me.options.deadCode !== "function") { - let suggestedMax = 25; + // Default limit on dead code + // May be overridden by user + if ( + typeof me.options.deadCode !== "function" && + typeof me.options.deadCode !== "object" + ) { + let suggestedMax = 20; if (me.obfuscator.parentObfuscator) { // RGF should contain less dead code suggestedMax = 5; } - if (created > suggestedMax && chance(created - suggestedMax)) + if (me.changeData.deadCode >= suggestedMax) { return; - created++; + } } + // Increment dead code counter + me.changeData.deadCode++; + var template = choice(deadCodeTemplates); var nodes = template.compile(); - var containingFnName = me.getPlaceholder("dead_" + created); + var containingFnName = me.getPlaceholder( + "dead_" + me.changeData.deadCode + ); // Insert dummy function prepend( diff --git a/src/transforms/opaquePredicates.ts b/src/transforms/opaquePredicates.ts index 245d423..886e341 100644 --- a/src/transforms/opaquePredicates.ts +++ b/src/transforms/opaquePredicates.ts @@ -30,7 +30,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const depth = path.getAncestry().length; - return chance(1000 - transformCount - depth * 100); + return chance(500 - transformCount - depth * 100); } function wrapWithPredicate(path: NodePath) { From 94b54d05cb591532544c64c7bb86af55dc336c2c Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 01:58:17 -0500 Subject: [PATCH 086/103] Improve Integrity performance --- src/templates/integrityTemplate.ts | 33 +++++++++++++----------------- src/transforms/lock/integrity.ts | 12 ++++++++++- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/templates/integrityTemplate.ts b/src/templates/integrityTemplate.ts index 0efc7f1..fea053e 100644 --- a/src/templates/integrityTemplate.ts +++ b/src/templates/integrityTemplate.ts @@ -27,26 +27,21 @@ export function HashFunction(str: string, seed: number) { export const HashTemplate = new Template(` // Must be Function Declaration for hoisting // Math.imul polyfill for ES5 -function {imul}(opA, opB) { - var MathImul = Math["imul"] || function(opA, opB){ - opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. - // floating points give us 53 bits of precision to work with plus 1 sign bit - // automatically handled for our convienence: - // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 - // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ - var result = (opA & 0x003fffff) * opB; - // 2. We can remove an integer coersion from the statement above because: - // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 - // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ - if (opA & 0xffc00000 /*!== 0*/) result += (opA & 0xffc00000) * opB |0; - return result |0; - }; - - var result = MathImul(opA, opB); - - return result; -} +function MathImulPolyfill(opA, opB){ + opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. + // floating points give us 53 bits of precision to work with plus 1 sign bit + // automatically handled for our convienence: + // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 + // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + var result = (opA & 0x003fffff) * opB; + // 2. We can remove an integer coersion from the statement above because: + // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 + // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + if (opA & 0xffc00000 /*!== 0*/) result += (opA & 0xffc00000) * opB |0; + return result |0; +}; +var {imul} = Math["imul"] || MathImulPolyfill; function {hashingUtilFnName}(str, seed) { var h1 = 0xdeadbeef ^ seed; diff --git a/src/transforms/lock/integrity.ts b/src/transforms/lock/integrity.ts index 41dcf2f..92776f6 100644 --- a/src/transforms/lock/integrity.ts +++ b/src/transforms/lock/integrity.ts @@ -5,6 +5,7 @@ import { HashFunction } from "../../templates/integrityTemplate"; import * as t from "@babel/types"; import Template from "../../templates/template"; import { NodePath } from "@babel/traverse"; +import { NameGen } from "../../utils/NameGen"; export interface IntegrityInterface { fnPath: NodePath; @@ -32,6 +33,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { }, }); + const nameGen = new NameGen(me.options.identifierGenerator, { + avoidObjectPrototype: true, + avoidReserved: true, + }); + return { visitor: { Program: { @@ -78,12 +84,16 @@ export default ({ Plugin }: PluginArg): PluginObject => { var hashCode = HashFunction(codeTrimmed, seed); + const selfName = funcDecPath.node.id.name; + const selfCacheProperty = nameGen.generate(); + const selfCacheString = `${selfName}.${selfCacheProperty}`; + // me.log(codeTrimmed, hashCode); me.changeData.functions++; funcDecPath.node.body = t.blockStatement( new Template(` - var hash = ${obfuscatedHashFnName}(${newFunctionDeclaration.id.name}, ${seed}); + var hash = ${selfCacheString} || (${selfCacheString} = ${obfuscatedHashFnName}(${newFunctionDeclaration.id.name}, ${seed})); if(hash === ${hashCode}) { {originalBody} } else { From c354d1978a9c30e58394004cc1e03b513239d7fc Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 02:02:00 -0500 Subject: [PATCH 087/103] New solution to Rename Variables default parameter --- src/transforms/identifier/renameVariables.ts | 38 +++++++++++++++ src/transforms/preparation.ts | 47 +------------------ .../identifier/renameVariables.test.ts | 41 ++++++++-------- 3 files changed, 60 insertions(+), 66 deletions(-) diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 55b4c15..67c7156 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -28,6 +28,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const definedMap = new Map>(); const referencedMap = new Map>(); + const paramMap = new Map>(); // Used for default function parameter special case const bindingMap = new Map>>(); const renamedVariables = new Map>(); @@ -48,9 +49,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { ]; let isDefined = false; + let isParameter = false; if (path.isBindingIdentifier() && isDefiningIdentifier(path)) { isDefined = true; + const binding = path.scope.getBinding(path.node.name); + if (binding?.kind === "param") isParameter = true; // Function ID is defined in the parent's function declaration if ( @@ -115,7 +119,30 @@ export default ({ Plugin }: PluginArg): PluginObject => { var newName = null; + const skippedPaths = new Set(); + for (let contextPath of contextPaths) { + if (skippedPaths.has(contextPath)) continue; + + if (contextPath.isFunction()) { + var assignmentPattern = contextPath.find( + (p) => p.listKey === "params" && p.parentPath.isFunction() + ); + + if (assignmentPattern?.isAssignmentPattern()) { + var functionPath = assignmentPattern.getFunctionParent(); + + if (functionPath) { + // The parameters can be still accessed... + const params = paramMap.get(functionPath.node); + if (params?.has(identifierName)) { + } else { + skippedPaths.add(functionPath); + } + } + } + } + const { node } = contextPath; const defined = definedMap.get(node); @@ -138,6 +165,17 @@ export default ({ Plugin }: PluginArg): PluginObject => { // 5. Update Identifier node's 'name' property node.name = newName; node[RENAMED] = true; + + // 6. Additional parameter mapping + const binding = identifierPath.scope.getBinding(identifierName); + if (binding?.kind === "param") { + var mapNode = binding.scope.path.node; + if (!paramMap.has(mapNode)) { + paramMap.set(mapNode, new Set([identifierName])); + } else { + paramMap.get(mapNode).add(identifierName); + } + } } }, diff --git a/src/transforms/preparation.ts b/src/transforms/preparation.ts index 59d3f1b..c06132d 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -2,7 +2,6 @@ import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import * as t from "@babel/types"; import { Order } from "../order"; -import path from "path"; import { NodeSymbol, PREDICTABLE, @@ -13,6 +12,7 @@ import { ok } from "assert"; import { getParentFunctionOrProgram, getPatternIdentifierNames, + isVariableIdentifier, } from "../utils/ast-utils"; import { isVariableFunctionIdentifier } from "../utils/function-utils"; import Template from "../templates/template"; @@ -342,51 +342,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { } }, }, - - // function a(param = ()=>b) - // _getB = ()=> ()=>b - // function a(param = _getB()) - // Basically Babel scope.rename misses this edge case, so we need to manually handle it - // Here were essentially making the variables easier to understand - Function: { - exit(path) { - for (var param of path.get("params")) { - param.traverse({ - "FunctionExpression|ArrowFunctionExpression"(_innerPath) { - let innerPath = _innerPath as NodePath< - t.FunctionExpression | t.ArrowFunctionExpression - >; - const child = innerPath.find((path) => - path.parentPath?.isAssignmentPattern() - ); - - if (!child) return; - - if ( - t.isAssignmentPattern(child.parent) && - child.parent.right === child.node - ) { - var creatorName = me.getPlaceholder(); - var insertPath = path.insertBefore( - t.variableDeclaration("const", [ - t.variableDeclarator( - t.identifier(creatorName), - t.arrowFunctionExpression([], innerPath.node, false) - ), - ]) - )[0]; - - path.scope.parent.registerDeclaration(insertPath); - - innerPath.replaceWith( - t.callExpression(t.identifier(creatorName), []) - ); - } - }, - }); - } - }, - }, }, }; }; diff --git a/test/transforms/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index 43b3bfb..e0fe23d 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -366,39 +366,40 @@ test.each([ * * therefore make sure `b` is NOT used as it breaks program */ - var code = ` - var a = "Filler Variables"; - var b = "Hello World"; - var c = "Another incorrect string"; + const sourceCode = ` + var outsideVariable = "Correct Value"; - function myFunction(param1 = ()=>{ - return b; + function accessOutsideScope(paramFn = ()=>{ + return outsideVariable; }){ - var b = param1(); - if(false){ - a,c; - } - input(b); + var outsideVariable = 'Incorrect Value'; + TEST_OUTPUT["Variant #1"] = paramFn(); } - myFunction(); + accessOutsideScope(); + + function accessParameter(store = "Incorrect Value", paramFn = ()=> (store = "Correct Value") ){ + paramFn(); + TEST_OUTPUT["Variant #2"] = store; + } + + accessParameter(); `; - var { code: output } = await JsConfuser.obfuscate(code, { + const { code } = await JsConfuser.obfuscate(sourceCode, { target: "node", renameVariables: true, renameGlobals: true, identifierGenerator: identifierGeneratorMode, }); - var value; - function input(valueIn) { - value = valueIn; - } - - eval(output); + let TEST_OUTPUT = {}; + eval(code); - expect(value).toStrictEqual("Hello World"); + expect(TEST_OUTPUT).toStrictEqual({ + "Variant #1": "Correct Value", + "Variant #2": "Correct Value", + }); } ); From a464196a0a7eec46abedf08c8b9b0ea5c6c0adb8 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 02:14:02 -0500 Subject: [PATCH 088/103] Improve default limit on lock, with option to change it --- src/options.ts | 7 ++++++- src/probability.ts | 0 src/transforms/lock/lock.ts | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 src/probability.ts diff --git a/src/options.ts b/src/options.ts index c614b19..d46f8aa 100644 --- a/src/options.ts +++ b/src/options.ts @@ -313,7 +313,7 @@ export interface ObfuscateOptions { * * @see https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md */ - tamperProtection?: boolean | ((varName: string) => boolean); + tamperProtection?: ProbabilityMap boolean>; /** * When the program is first able to be used. (`number` or `Date`) @@ -355,6 +355,11 @@ export interface ObfuscateOptions { countermeasures?: string | boolean; customLocks?: CustomLock[]; + + /** + * The default 'maxCount' for obfuscator and custom locks. Defaults to 25. + */ + defaultMaxCount?: number; }; /** diff --git a/src/probability.ts b/src/probability.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index 3fb5fee..03fbe80 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -165,10 +165,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { }; me.globalState.lock.createCountermeasuresCode = createCountermeasuresCode; + const defaultMaxCount = me.options.lock.defaultMaxCount ?? 25; + function applyLockToBlock(path: NodePath, customLock: CustomLock) { let times = timesMap.get(customLock) || 0; - let maxCount = customLock.maxCount ?? 100; // 100 is default max count + let maxCount = customLock.maxCount ?? defaultMaxCount; // 25 is default max count let minCount = customLock.minCount ?? 1; // 1 is default min count if (maxCount >= 0 && times > maxCount) { From bec1c414768abb4be3bae8ad0418a9156340fa4a Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 02:14:53 -0500 Subject: [PATCH 089/103] Improve Control Flow Flattening --- src/transforms/controlFlowFlattening.ts | 194 ++++++++++++------ test/code/ES6.src.js | 19 ++ test/code/ES6.test.ts | 10 +- .../controlFlowFlattening.test.ts | 32 +++ 4 files changed, 184 insertions(+), 71 deletions(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index f523e21..92b659b 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -31,6 +31,9 @@ import { WITH_STATEMENT, } from "../constants"; +// Function deemed unsafe for CFF +const CFF_UNSAFE = Symbol("CFF_UNSAFE"); + /** * Breaks functions into DAGs (Directed Acyclic Graphs) * @@ -75,13 +78,44 @@ export default ({ Plugin }: PluginArg): PluginObject => { const functionsModified = new Set(); + function flagFunctionToAvoid(path: NodePath, reason: string) { + var fnOrProgram = getParentFunctionOrProgram(path); + + fnOrProgram.node[CFF_UNSAFE] = reason; + } + return { post: () => { functionsModified.forEach((node) => { (node as NodeSymbol)[UNSAFE] = true; }); }, + visitor: { + // Unsafe detection + ThisExpression(path) { + flagFunctionToAvoid(path, "this"); + }, + VariableDeclaration(path) { + if (path.node.declarations.length !== 1) { + path.getAncestry().forEach((p) => { + p.node[CFF_UNSAFE] = "multipleDeclarations"; + }); + } + }, + Identifier(path) { + if ( + path.node.name === variableFunctionName || + path.node.name === "arguments" + ) { + flagFunctionToAvoid(path, "arguments"); + } + }, + "Super|MetaProperty|AwaitExpression|YieldExpression"(path) { + flagFunctionToAvoid(path, "functionSpecific"); + }, + + // Main CFF transformation "Program|Function": { exit(_path) { let programOrFunctionPath = _path as NodePath; @@ -92,6 +126,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { ) return; + // Exclude 'CFF_UNSAFE' functions + if (programOrFunctionPath.node[CFF_UNSAFE]) return; + let programPath = _path.isProgram() ? _path : null; let functionPath = _path.isFunction() ? _path : null; @@ -118,9 +155,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { return; } - // Avoid unsafe functions - if (functionPath && (functionPath.node as NodeSymbol)[UNSAFE]) return; + if (functionPath) { + // Avoid unsafe functions + if ((functionPath.node as NodeSymbol)[UNSAFE]) return; + if (functionPath.node.async || functionPath.node.generator) return; + } programOrFunctionPath.scope.crawl(); const blockFnParent = getParentFunctionOrProgram(blockPath); @@ -128,30 +168,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { let hasIllegalNode = false; const bindingNames = new Set(); blockPath.traverse({ - "Super|MetaProperty|AwaitExpression|YieldExpression"(path) { - if ( - getParentFunctionOrProgram(path).node === blockFnParent.node - ) { - hasIllegalNode = true; - path.stop(); - } - }, - VariableDeclaration(path) { - if (path.node.declarations.length !== 1) { - hasIllegalNode = true; - path.stop(); - } - }, Identifier(path) { - if ( - path.node.name === variableFunctionName || - path.node.name === "arguments" - ) { - hasIllegalNode = true; - path.stop(); - return; - } - if (!path.isBindingIdentifier()) return; const binding = path.scope.getBinding(path.node.name); if (!binding) return; @@ -317,7 +334,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { identity: "${this.propertyName}" }) `).expression() - : new Template(`Object["create"](null)`).expression(); + : new Template(`({})`).expression(); } getMemberExpression(name: string) { @@ -373,6 +390,25 @@ export default ({ Plugin }: PluginArg): PluginObject => { ); } + if (this === mainScope) { + // Reset With logic + properties.push( + t.objectProperty( + t.stringLiteral(resetWithProperty), + new Template(` + (function(newStateValues, alwaysUndefined){ + {withMemberExpression} = alwaysUndefined; + {arrayPattern} = newStateValues + }) + `).expression({ + withMemberExpression: deepClone(withMemberExpression), + arrayPattern: t.arrayPattern(deepClone(stateVars)), + }), + true + ) + ); + } + return t.objectExpression(properties); } @@ -658,7 +694,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { !flattenFunctionDeclarations || statement.node.async || statement.node.generator || - (statement.node as NodeSymbol)[UNSAFE] + (statement.node as NodeSymbol)[UNSAFE] || + (statement.node as NodeSymbol)[CFF_UNSAFE] || + isStrictMode(statement) ) { isIllegal = true; } @@ -870,20 +908,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { basicBlocks.get(endLabel).allowWithDiscriminant = false; - // Add with / reset with logic - basicBlocks.get(startLabel).body.unshift( - new Template(` - {resetWithMemberExpression} = function(newStateValues){ - {withMemberExpression} = undefined; - {arrayPattern} = newStateValues - } - `).single({ - arrayPattern: t.arrayPattern(deepClone(stateVars)), - resetWithMemberExpression: deepClone(resetWithMemberExpression), - withMemberExpression: deepClone(withMemberExpression), - }) - ); - if (!isDebug && addDeadCode) { // DEAD CODE 1/3: Add fake chunks that are never reached const fakeChunkCount = getRandomInteger(1, 5); @@ -1137,6 +1161,17 @@ export default ({ Plugin }: PluginArg): PluginObject => { path.replaceWith(memberExpression); path.skip(); + + // Preserve proper 'this' context when directly calling functions + // X.Y.Z() -> (1, X.Y.Z)() + if ( + path.parentPath.isCallExpression() && + path.key === "callee" + ) { + path.replaceWith( + t.sequenceExpression([t.numericLiteral(1), path.node]) + ); + } }, }, @@ -1269,7 +1304,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { const mainScope = basicBlocks.get(startLabel).scopeManager; const predicateNumbers = new Map(); const predicateNumberCount = - isDebug || !addPredicateTests ? 0 : getRandomInteger(2, 5); + isDebug || !addPredicateTests ? 0 : getRandomInteger(1, 4); + for (let i = 0; i < predicateNumberCount; i++) { const name = mainScope.getNewName( me.getPlaceholder("predicate_" + i) @@ -1277,27 +1313,47 @@ export default ({ Plugin }: PluginArg): PluginObject => { const number = getRandomInteger(-250, 250); predicateNumbers.set(name, number); + } - const createAssignment = (value: number) => { - return new Template(` - {memberExpression} = {number} - `).single({ - memberExpression: mainScope.getMemberExpression(name), - number: numericLiteral(number), - }); - }; + const predicateSymbol = Symbol("predicate"); + + const createAssignment = (values: number[]) => { + var exprStmt = new Template(` + ({predicateVariables} = {values}) + `).single({ + predicateVariables: t.arrayPattern( + Array.from(predicateNumbers.keys()).map((name) => + mainScope.getMemberExpression(name) + ) + ), + values: t.arrayExpression( + values.map((value) => numericLiteral(value)) + ), + }); - basicBlocks.get(startLabel).body.unshift(createAssignment(number)); + exprStmt[predicateSymbol] = true; - // Add random assignments to impossible blocks - var fakeAssignmentCount = getRandomInteger(0, 3); - for (let i = 0; i < fakeAssignmentCount; i++) { - var impossibleBlock = choice(getImpossibleBasicBlocks()); - if (impossibleBlock) { - impossibleBlock.body.unshift( - createAssignment(getRandomInteger(-250, 250)) - ); - } + return exprStmt; + }; + + basicBlocks + .get(startLabel) + .body.unshift( + createAssignment(Array.from(predicateNumbers.values())) + ); + + // Add random assignments to impossible blocks + var fakeAssignmentCount = getRandomInteger(1, 3); + + for (let i = 0; i < fakeAssignmentCount; i++) { + var impossibleBlock = choice(getImpossibleBasicBlocks()); + if (impossibleBlock) { + if (impossibleBlock.body[0]?.[predicateSymbol]) continue; + + var fakeValues = new Array(predicateNumberCount) + .fill(0) + .map(() => getRandomInteger(-250, 250)); + impossibleBlock.body.unshift(createAssignment(fakeValues)); } } @@ -1465,9 +1521,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { ), t.blockStatement([ t.withStatement( - new Template( - `{withDiscriminant} || Object["create"](null)` - ).expression({ + new Template(`{withDiscriminant} || {}`).expression({ withDiscriminant: deepClone(withMemberExpression), }), t.blockStatement([switchStatement]) @@ -1531,6 +1585,17 @@ export default ({ Plugin }: PluginArg): PluginObject => { ); } + const startProgramObjectExpression = basicBlocks + .get(startLabel) + .scopeManager.getObjectExpression(startLabel); + + const mainParameters: t.FunctionDeclaration["params"] = parameters; + const scopeParameter = mainParameters.pop() as t.Identifier; + + mainParameters.push( + t.assignmentPattern(scopeParameter, startProgramObjectExpression) + ); + const mainFnDeclaration = t.functionDeclaration( deepClone(mainFnName), parameters, @@ -1542,9 +1607,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { var startProgramExpression = t.callExpression(deepClone(mainFnName), [ ...startStateValues.map((stateValue) => numericLiteral(stateValue)), t.identifier("undefined"), - basicBlocks - .get(startLabel) - .scopeManager.getObjectExpression(startLabel), ]); const resultVar = withIdentifier("result"); diff --git a/test/code/ES6.src.js b/test/code/ES6.src.js index e57dadf..05d806d 100644 --- a/test/code/ES6.src.js +++ b/test/code/ES6.src.js @@ -224,6 +224,25 @@ function fnName(fnName) { } fnName("Correct Value"); +// Variant #21, #22: Default parameter function that accesses parameter scope +var d = "Correct Value"; +function variant21And22( + a, + b = function () { + a = "Correct Value"; + }, + c = function () { + return d; + } +) { + var d = "Incorrect Value"; + b(); + TEST_OUTPUT["Variant #21"] = a === "Correct Value"; + TEST_OUTPUT["Variant #22"] = c() === "Correct Value"; +} + +variant21And22(); + function countermeasures() { throw new Error("Countermeasures function called."); } diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index abb00ce..58e570f 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -26,6 +26,8 @@ const EXPECTED_RESULT = { "Variant #18": true, "Variant #19": true, "Variant #20": true, + "Variant #21": true, + "Variant #22": true, }; test("Variant #1: ES6 code on High Preset", async () => { @@ -45,10 +47,7 @@ test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protec target: "node", preset: "high", pack: true, - rgf: { - value: true, - limit: 5, - }, + rgf: true, lock: { integrity: true, selfDefending: true, @@ -57,7 +56,8 @@ test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protec }, }); - // writeFileSync("./dev.output.js", code, "utf-8"); + // let newCode = `var TEST_OUTPUT;\n${code}\n\nconsole.log(TEST_OUTPUT);`; + // writeFileSync("./dev.output.js", newCode, "utf-8"); const TEST_OUTPUT = {}; eval(code); diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index 144d2dd..a210b64 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -607,6 +607,33 @@ TEST_OUTPUT = a; expect(TEST_OUTPUT).toStrictEqual({ last: true }); }); +test("Variant #19: Work with this keyword", async () => { + var { code } = await JsConfuser.obfuscate( + ` + TEST_OUTPUT = []; + + function myFunction() { + TEST_OUTPUT.push( + this === global + ? "global" + : typeof this === "string" || this instanceof String + ? "" + this + : "?" + ); + } + + myFunction(); + myFunction.call(String("Other")); + `, + { target: "node", controlFlowFlattening: true, pack: true } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(["global", "Other"]); +}); + test("Variant #20: Work with redefined functions", async () => { var { code: output } = await JsConfuser.obfuscate( ` @@ -870,6 +897,11 @@ test("Variant #26: Add opaque predicates and still work", async () => { test("Variant #27: Work on async/generator functions", async () => { var { code: output } = await JsConfuser.obfuscate( ` + // Unused functions + async function dummyAsync(){ var a,b,c }; + function* dummyGenerator(){ var a,b,c }; + + // Used with specific keywords 'await' and 'yield' async function myAsyncFunction(){ var a,b,c; await (1); From fe995f8f5025939edba7a2e6123ad70eb2fb5303 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 21:28:35 -0500 Subject: [PATCH 090/103] Fix scoping issue in edge case --- src/transforms/controlFlowFlattening.ts | 8 +------- src/transforms/dispatcher.ts | 26 ++++++++++++++++++++++--- test/code/ES6.src.js | 20 +++++++++---------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 92b659b..7c088a3 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -1245,15 +1245,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { // console.log(oldValue, newValue); if (oldValue === newValue) continue; // No diff needed if the value doesn't change - const leftValue = jumpBlock.withDiscriminant - ? jumpBlock.withDiscriminant.getMemberExpression( - stateVars[i].name - ) - : deepClone(stateVars[i]); - let assignment = t.assignmentExpression( "=", - leftValue, + deepClone(stateVars[i]), numericLiteral(newValue) ); diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index b1f1a8e..e5f1053 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -126,11 +126,31 @@ export default ({ Plugin }: PluginArg): PluginObject => { return; } + var hasAssignmentPattern = false; + + for (var param of path.get("params")) { + if (param.isAssignmentPattern()) { + hasAssignmentPattern = true; + break; + } + param.traverse({ + AssignmentPattern(innerPath) { + var fn = innerPath.getFunctionParent(); + if (fn === path) { + hasAssignmentPattern = true; + innerPath.stop(); + } else { + innerPath.skip(); + } + }, + }); + + if (hasAssignmentPattern) break; + } + // Functions with default parameters are not fully supported // (Could be a Function Expression referencing outer scope) - if ( - path.node.params.find((x) => x.type === "AssignmentPattern") - ) { + if (hasAssignmentPattern) { illegalNames.add(name); return; } diff --git a/test/code/ES6.src.js b/test/code/ES6.src.js index 05d806d..1f5d897 100644 --- a/test/code/ES6.src.js +++ b/test/code/ES6.src.js @@ -225,20 +225,20 @@ function fnName(fnName) { fnName("Correct Value"); // Variant #21, #22: Default parameter function that accesses parameter scope -var d = "Correct Value"; +var _v__d = "Correct Value"; function variant21And22( - a, - b = function () { - a = "Correct Value"; + _v__a, + _v__b = function () { + _v__a = "Correct Value"; }, - c = function () { - return d; + _v__c = function () { + return _v__d; } ) { - var d = "Incorrect Value"; - b(); - TEST_OUTPUT["Variant #21"] = a === "Correct Value"; - TEST_OUTPUT["Variant #22"] = c() === "Correct Value"; + var _v__d = "Incorrect Value"; + _v__b(); + TEST_OUTPUT["Variant #21"] = _v__a === "Correct Value"; + TEST_OUTPUT["Variant #22"] = _v__c() === "Correct Value"; } variant21And22(); From a22d2661328eb95858a52519bb41f8093d04257b Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 21:29:27 -0500 Subject: [PATCH 091/103] Improve String Concealing changeData --- src/transforms/string/stringConcealing.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 2b37740..4ab1fa0 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -38,6 +38,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.StringConcealing, { changeData: { strings: 0, + decryptionFunctions: 0, }, }); @@ -93,6 +94,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { encoding.code = new Template(encoding.code); } + me.changeData.decryptionFunctions++; encodingImplementations[encoding.identity] = encoding; return encoding; From 24d073d9f73b51ef2fc8ae8c678ef02456df413a Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Wed, 20 Nov 2024 21:34:00 -0500 Subject: [PATCH 092/103] 2.0.0-alpha.4 --- CHANGELOG.md | 4 ++-- README.md | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a68040..bbbe3d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,7 +87,7 @@ The function `__JS_CONFUSER_VAR__` is identical in outcome and still be used, ho ### JS-Confuser.com Revamp -A new UI for JS-Confuser.com, featuring an advanced playground and documentation pages. +A new UI for [JS-Confuser.com](https://js-confuser.com), featuring an advanced playground and documentation pages. The previous version will remain available: [old--confuser.netlify.com](https://old--confuser.netlify.app/) @@ -99,7 +99,7 @@ The previous version will remain available: [old--confuser.netlify.com](https:// - Removed `Shuffle`'s Hash option -- Removed `Indent` option. [`@babel/generator`](https://www.npmjs.com/package/@babel/generator) does not allow customizing the indentation size. Use Prettier if you still wish for 4 space or tabs. Be mindful if you have `Integrity` or `Self Defending` enabled, as you should not alter the obfuscated code. +- Removed `Indent` option # `1.7.3` diff --git a/README.md b/README.md index 1d2b39f..2af5e54 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ - - [FAQ](https://new--confuser.netlify.app/docs/getting-started/faq) -- [Options](https://new--confuser.netlify.app/docs) +- [Options](https://new--confuser.netlify.app/docs/options) -- [Presets](https://new--confuser.netlify.app/docs) +- [Presets](https://new--confuser.netlify.app/docs/presets) ## API Usage diff --git a/package.json b/package.json index 25d0542..69189b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-confuser", - "version": "2.0.0-alpha.3", + "version": "2.0.0-alpha.4", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", From bb595bf9856787fcac99bb8affc3e5a25e805290 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 22 Nov 2024 18:53:27 -0500 Subject: [PATCH 093/103] Set Function Length improvement --- src/transforms/plugin.ts | 26 ++++++++++++++++++-------- test/transforms/rgf.test.ts | 6 ++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index 499bdf6..fc12a79 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -97,14 +97,24 @@ export class PluginInstance { ); } + const createCallArguments = (node: t.Expression): t.Expression[] => { + var args = [node]; + + // 1 is the default value in the setFunction template, can exclude it + if (originalLength !== 1) { + args.push(numericLiteral(originalLength)); + } + return args; + }; + if (t.isFunctionDeclaration(path.node)) { prepend( path.parentPath, t.expressionStatement( - t.callExpression(t.identifier(this.setFunctionLengthName), [ - t.identifier(path.node.id.name), - numericLiteral(originalLength), - ]) + t.callExpression( + t.identifier(this.setFunctionLengthName), + createCallArguments(t.identifier(path.node.id.name)) + ) ) ); } else if ( @@ -112,10 +122,10 @@ export class PluginInstance { t.isArrowFunctionExpression(path.node) ) { path.replaceWith( - t.callExpression(t.identifier(this.setFunctionLengthName), [ - path.node, - numericLiteral(originalLength), - ]) + t.callExpression( + t.identifier(this.setFunctionLengthName), + createCallArguments(path.node) + ) ); } else { // TODO diff --git a/test/transforms/rgf.test.ts b/test/transforms/rgf.test.ts index 8f1a4a7..57346cc 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -362,8 +362,10 @@ test("Variant #12: Preserve Function.length", async function () { } + function oneParam(a){} // Function.length = 1 + myFunction() - TEST_OUTPUT = myFunction.length + TEST_OUTPUT = myFunction.length + oneParam.length `, { target: "node", @@ -374,5 +376,5 @@ test("Variant #12: Preserve Function.length", async function () { var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(3); + expect(TEST_OUTPUT).toStrictEqual(4); }); From 06d77666222bf15bab820b11fed5dcbe2bb2383a Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Fri, 22 Nov 2024 18:54:18 -0500 Subject: [PATCH 094/103] Add reserved keyword `do`, improve profiling --- src/constants.ts | 4 ++-- src/index.ts | 7 ++++++- .../transforms/identifier/movedDeclarations.test.ts | 13 ++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 52ff416..2238eb7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,3 @@ -export const predictableFunctionTag = "__JS_PREDICT__"; - /** * A function is 'unsafe' if it requires 'eval', 'arguments' or 'this' * @@ -121,6 +119,7 @@ export const reservedObjectPrototype = new Set([ export const reservedKeywords = [ "if", "in", + "do", "for", "let", "new", @@ -129,6 +128,7 @@ export const reservedKeywords = [ "case", "else", "null", + "with", "break", "catch", "class", diff --git a/src/index.ts b/src/index.ts index 22f36dd..053a593 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,8 +66,13 @@ export async function obfuscateWithProfiler( }; transformMap[log.currentTransform] = entry; - currentTransformTime = nowTime; + + // (JS-Confuser.com can run performance benchmark tests here) profiler.callback?.(log, entry, ast); + + // The profiler.callback() function may take a long time to execute, + // so we need to update the currentTransformTime here for accurate profiling. + currentTransformTime = performance.now(); }, }); diff --git a/test/transforms/identifier/movedDeclarations.test.ts b/test/transforms/identifier/movedDeclarations.test.ts index cc06eb5..d60a4d5 100644 --- a/test/transforms/identifier/movedDeclarations.test.ts +++ b/test/transforms/identifier/movedDeclarations.test.ts @@ -1,4 +1,3 @@ -import { predictableFunctionTag } from "../../../src/constants"; import JsConfuser from "../../../src/index"; test("Variant #1: Move variable 'y' to top", async () => { @@ -212,19 +211,19 @@ test("Variant #9: Defined variable without an initializer + CFF + Duplicate Lite test("Variant #10: Move parameters to predictable function", async () => { var code = ` - function testFunction${predictableFunctionTag}_FN(){ + function testFunction_FN(){ var values = [10,20,35,"40", null] var parseIntKey = "parseInt" var output = 0 var utils = { - get parser${predictableFunctionTag}(){ + get parser(){ var fn = (value)=>{ return global[parseIntKey](value) } return fn }, - set setter_fn${predictableFunctionTag}(newValue){ + set setter_fn(newValue){ var fakeVar } } @@ -233,7 +232,7 @@ test("Variant #10: Move parameters to predictable function", async () => { constructor(){ } - get fakeGet${predictableFunctionTag}(){ + get fakeGet(){ var fakeVar } } @@ -245,7 +244,7 @@ test("Variant #10: Move parameters to predictable function", async () => { default: var stringifiedValue = "" + value - var parsedValue = utils.parser${predictableFunctionTag}(stringifiedValue) + var parsedValue = utils.parser(stringifiedValue) output += parsedValue; break; } @@ -254,7 +253,7 @@ test("Variant #10: Move parameters to predictable function", async () => { return output } - TEST_OUTPUT = testFunction${predictableFunctionTag}_FN() + TEST_OUTPUT = testFunction_FN() `; var { code: output } = await JsConfuser.obfuscate(code, { From ee443be5f819768d04457004525f33a80b922fbb Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 26 Nov 2024 22:04:04 -0500 Subject: [PATCH 095/103] Remove unnecessary scoping adjustment --- src/templates/integrityTemplate.ts | 2 +- src/transforms/plugin.ts | 2 +- src/utils/ast-utils.ts | 31 ++++++++++-------------------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/templates/integrityTemplate.ts b/src/templates/integrityTemplate.ts index fea053e..790cf46 100644 --- a/src/templates/integrityTemplate.ts +++ b/src/templates/integrityTemplate.ts @@ -1,4 +1,4 @@ -import { MULTI_TRANSFORM, SKIP, UNSAFE } from "../constants"; +import { MULTI_TRANSFORM, SKIP } from "../constants"; import Template from "./template"; /** diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts index fc12a79..6203a32 100644 --- a/src/transforms/plugin.ts +++ b/src/transforms/plugin.ts @@ -168,6 +168,6 @@ export class PluginInstance { * @param messages */ error(...messages: any[]): never { - throw new Error(`[${this.name}] ${messages.join(", ")}`); + throw new Error(`[${this.name}] ${messages.join(" ")}`); } } diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 81feda5..e95f1be 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -215,17 +215,6 @@ export function getMemberExpressionPropertyAsString( return null; // If the property cannot be determined } -function registerPaths(paths: NodePath[]) { - for (var path of paths) { - if (path.isVariableDeclaration() && path.node.kind === "var") { - getParentFunctionOrProgram(path).scope.registerDeclaration(path); - } - path.scope.registerDeclaration(path); - } - - return paths; -} - function nodeListToNodes(nodesIn: (t.Statement | t.Statement[])[]) { var nodes: t.Statement[] = []; if (Array.isArray(nodesIn[0])) { @@ -257,12 +246,12 @@ export function append( if (listParent.isProgram()) { var lastExpression = listParent.get("body").at(-1); if (lastExpression.isExpressionStatement()) { - return registerPaths(lastExpression.insertBefore(nodes)); + return lastExpression.insertBefore(nodes); } } if (listParent.isSwitchCase()) { - return registerPaths(listParent.pushContainer("consequent", nodes)); + return listParent.pushContainer("consequent", nodes); } if (listParent.isFunction()) { @@ -278,11 +267,11 @@ export function append( ok(body.isBlockStatement()); - return registerPaths(body.pushContainer("body", nodes)); + return body.pushContainer("body", nodes); } ok(listParent.isBlock()); - return registerPaths(listParent.pushContainer("body", nodes)); + return listParent.pushContainer("body", nodes); } /** @@ -322,11 +311,11 @@ export function prepend( if (afterImport === 0) { // No import declarations, so we can safely unshift everything - return registerPaths(listParent.unshiftContainer("body", nodes)); + return listParent.unshiftContainer("body", nodes); } // Insert the nodes after the last import declaration - return registerPaths(body[afterImport - 1].insertAfter(nodes)); + return body[afterImport - 1].insertAfter(nodes); } if (listParent.isFunction()) { @@ -342,15 +331,15 @@ export function prepend( ok(body.isBlockStatement()); - return registerPaths(body.unshiftContainer("body", nodes)); + return body.unshiftContainer("body", nodes); } if (listParent.isBlock()) { - return registerPaths(listParent.unshiftContainer("body", nodes)); + return listParent.unshiftContainer("body", nodes); } if (listParent.isSwitchCase()) { - return registerPaths(listParent.unshiftContainer("consequent", nodes)); + return listParent.unshiftContainer("consequent", nodes); } ok(false); @@ -564,7 +553,7 @@ export function isModifiedIdentifier(identifierPath: NodePath) { export function replaceDefiningIdentifierToMemberExpression( path: NodePath, - memberExpression: t.MemberExpression + memberExpression: t.MemberExpression | t.Identifier ) { // function id(){} -> var id = function() {} if (path.key === "id" && path.parentPath.isFunctionDeclaration()) { From 58c1283a4f1713d6a2a358417a7ae5cb560e2920 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 26 Nov 2024 22:22:11 -0500 Subject: [PATCH 096/103] Improve CFF --- src/transforms/controlFlowFlattening.ts | 162 +++++++++++++----------- 1 file changed, 88 insertions(+), 74 deletions(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 7c088a3..71dec09 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -5,7 +5,6 @@ import { ensureComputedExpression, getParentFunctionOrProgram, isDefiningIdentifier, - isModifiedIdentifier, isStrictMode, isVariableIdentifier, replaceDefiningIdentifierToMemberExpression, @@ -163,8 +162,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { } programOrFunctionPath.scope.crawl(); - const blockFnParent = getParentFunctionOrProgram(blockPath); - let hasIllegalNode = false; const bindingNames = new Set(); blockPath.traverse({ @@ -231,6 +228,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { .map((_, i) => withIdentifier(`state_${i}`)); const argVar = withIdentifier("_arg"); + let usedArgVar = false; const didReturnVar = withIdentifier("return"); @@ -255,16 +253,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { ).expression(); withMemberExpression.object[NO_RENAME] = cffIndex; - // Create 'resetWith' function - Safely resets the 'with' object to none - const resetWithProperty = isDebug - ? "resetWith" - : scopeNameGen.generate(false); - - const resetWithMemberExpression = new Template( - `${scopeVar.name}["${resetWithProperty}"]` - ).expression(); - resetWithMemberExpression.object[NO_RENAME] = cffIndex; - class ScopeManager { isNotUsed = true; requiresInitializing = true; @@ -275,8 +263,11 @@ export default ({ Plugin }: PluginArg): PluginObject => { : new NameGen(me.options.identifierGenerator); findBestWithDiscriminant(basicBlock: BasicBlock): ScopeManager { + // This initializing block is forbidden to have a with discriminant + // (As no previous code is able to prepare the with discriminant) if (basicBlock !== this.initializingBasicBlock) { - if (this.nameMap.size > 0) return this; + // If no variables were defined in this scope, don't use it + if (Object.keys(this.scope.bindings).length > 0) return this; } return this.parent?.findBestWithDiscriminant(basicBlock); @@ -337,9 +328,12 @@ export default ({ Plugin }: PluginArg): PluginObject => { : new Template(`({})`).expression(); } - getMemberExpression(name: string) { + getMemberExpression( + name: string, + object: t.Expression = this.getScopeObject() + ) { const memberExpression = t.memberExpression( - this.getScopeObject(), + object, t.stringLiteral(name), true ); @@ -353,7 +347,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { public initializingBasicBlock: BasicBlock ) { this.propertyName = isDebug - ? "_" + scopeCounter++ + ? "_" + cffIndex + "_" + scopeCounter++ : scopeNameGen.generate(); } @@ -390,25 +384,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { ); } - if (this === mainScope) { - // Reset With logic - properties.push( - t.objectProperty( - t.stringLiteral(resetWithProperty), - new Template(` - (function(newStateValues, alwaysUndefined){ - {withMemberExpression} = alwaysUndefined; - {arrayPattern} = newStateValues - }) - `).expression({ - withMemberExpression: deepClone(withMemberExpression), - arrayPattern: t.arrayPattern(deepClone(stateVars)), - }), - true - ) - ); - } - return t.objectExpression(properties); } @@ -434,7 +409,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { bestWithDiscriminant: ScopeManager; get withDiscriminant() { - if (!this.allowWithDiscriminant) return null; + if (!this.allowWithDiscriminant) return; return this.bestWithDiscriminant; } @@ -774,8 +749,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { ); } - // Unpack parameters + // Unpack parameters from the parameter 'argVar' if (statement.node.params.length > 0) { + usedArgVar = true; fnTopBlock.body.unshift( t.variableDeclaration("var", [ t.variableDeclarator( @@ -1099,6 +1075,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (!isVariableIdentifier(path)) return; if (me.isSkipped(path)) return; if ((path.node as NodeSymbol)[NO_RENAME] === cffIndex) return; + // For identifiers using implicit with discriminant, skip + if ((path.node as NodeSymbol)[WITH_STATEMENT]) return; const identifierName = path.node.name; if (identifierName === gotoFunctionName) return; @@ -1117,8 +1095,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { return; } - // console.log("No binding found for " + identifierName); - var scopeManager = scopeToScopeManager.get(binding.scope); if (!scopeManager) return; @@ -1132,6 +1108,18 @@ export default ({ Plugin }: PluginArg): PluginObject => { scopeManager.isNotUsed = false; + // Scope object as with discriminant? Use identifier + if (typeof basicBlock.withDiscriminant === "undefined") { + const id = t.identifier(scopeManager.propertyName); + (id as NodeSymbol)[WITH_STATEMENT] = true; + (id as NodeSymbol)[NO_RENAME] = cffIndex; + + memberExpression = scopeManager.getMemberExpression( + newName, + id + ); + } + if (isDefiningIdentifier(path)) { replaceDefiningIdentifierToMemberExpression( path, @@ -1142,14 +1130,16 @@ export default ({ Plugin }: PluginArg): PluginObject => { if (!path.container) return; - var isModified = isModifiedIdentifier(path); - if ( basicBlock.withDiscriminant && basicBlock.withDiscriminant === scopeManager && basicBlock.withDiscriminant.hasOwnName(identifierName) ) { - if (!isModified) { + // The defining mode must directly append to the scope object + // Subsequent uses can use the identifier + const isDefiningNode = path.node === binding.identifier; + + if (!isDefiningNode) { memberExpression = basicBlock.identifier( newName, scopeManager @@ -1218,7 +1208,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { } = jumpBlock; const assignments: t.Expression[] = []; - let needsIndividualAssignments = true; if (jumpBlock.withDiscriminant) { assignments.push( @@ -1229,39 +1218,47 @@ export default ({ Plugin }: PluginArg): PluginObject => { ) ); } else if (basicBlock.withDiscriminant) { + // Reset the with discriminant to undefined using fake property + // scope["fake"] -> undefined + + const fakeProperty = scopeNameGen.generate(); + assignments.push( - t.callExpression(deepClone(resetWithMemberExpression), [ - t.arrayExpression(newStateValues.map(numericLiteral)), - ]) + t.assignmentExpression( + "=", + deepClone(withMemberExpression), + t.memberExpression( + deepClone(scopeVar), + t.stringLiteral(fakeProperty), + true + ) + ) ); - needsIndividualAssignments = false; } - if (needsIndividualAssignments) { - for (let i = 0; i < stateVars.length; i++) { - const oldValue = currentStateValues[i]; - const newValue = newStateValues[i]; + for (let i = 0; i < stateVars.length; i++) { + const oldValue = currentStateValues[i]; + const newValue = newStateValues[i]; - // console.log(oldValue, newValue); - if (oldValue === newValue) continue; // No diff needed if the value doesn't change + // console.log(oldValue, newValue); + if (oldValue === newValue) continue; // No diff needed if the value doesn't change - let assignment = t.assignmentExpression( - "=", + let assignment = t.assignmentExpression( + "=", + deepClone(stateVars[i]), + numericLiteral(newValue) + ); + + if (!isDebug && addRelativeAssignments) { + // Use diffs to create confusing code + assignment = t.assignmentExpression( + "+=", deepClone(stateVars[i]), - numericLiteral(newValue) + numericLiteral(newValue - oldValue) ); - - if (!isDebug && addRelativeAssignments) { - // Use diffs to create confusing code - assignment = t.assignmentExpression( - "+=", - deepClone(stateVars[i]), - numericLiteral(newValue - oldValue) - ); - } - - assignments.push(assignment); } + + assignments.push(assignment); } // Add debug label @@ -1515,8 +1512,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { ), t.blockStatement([ t.withStatement( - new Template(`{withDiscriminant} || {}`).expression({ + new Template(`{withDiscriminant} || {scopeVar}`).expression({ withDiscriminant: deepClone(withMemberExpression), + scopeVar: deepClone(scopeVar), }), t.blockStatement([switchStatement]) ), @@ -1525,8 +1523,8 @@ export default ({ Plugin }: PluginArg): PluginObject => { const parameters: t.Identifier[] = [ ...stateVars, - argVar, scopeVar, + argVar, ].map((id) => deepClone(id)); const parametersNames: string[] = parameters.map((id) => id.name); @@ -1558,6 +1556,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { } } + // Ensure parameter is added (No effect if not added in this case) + usedArgVar = true; + Object.assign( fn, new Template(` @@ -1584,23 +1585,36 @@ export default ({ Plugin }: PluginArg): PluginObject => { .scopeManager.getObjectExpression(startLabel); const mainParameters: t.FunctionDeclaration["params"] = parameters; - const scopeParameter = mainParameters.pop() as t.Identifier; - mainParameters.push( - t.assignmentPattern(scopeParameter, startProgramObjectExpression) + // First state values use the default parameter for initialization + // function main(..., scope = { mainScope: {} }, ...){...} + mainParameters.splice( + (mainParameters as t.Identifier[]).findIndex( + (p) => p.name === scopeVar.name + ), + 1, + t.assignmentPattern( + deepClone(scopeVar), + startProgramObjectExpression + ) ); + // Remove parameter 'argVar' if never used (No function calls obfuscated) + if (!usedArgVar) { + mainParameters.pop(); + } + const mainFnDeclaration = t.functionDeclaration( deepClone(mainFnName), parameters, t.blockStatement([whileStatement]) ); + // The main function is always called with same number of arguments (mainFnDeclaration as NodeSymbol)[PREDICTABLE] = true; var startProgramExpression = t.callExpression(deepClone(mainFnName), [ ...startStateValues.map((stateValue) => numericLiteral(stateValue)), - t.identifier("undefined"), ]); const resultVar = withIdentifier("result"); From f15bfa685e53279e830b0fe8737c0372883453df Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Tue, 26 Nov 2024 22:23:29 -0500 Subject: [PATCH 097/103] 2.0.0-alpha.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69189b5..c7e8ead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-confuser", - "version": "2.0.0-alpha.4", + "version": "2.0.0-alpha.5", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", From 6d4bb17349647b75f1de1166bce2108af6eb17c9 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 30 Nov 2024 19:20:30 -0500 Subject: [PATCH 098/103] Add cache to String Concealing --- src/transforms/string/stringConcealing.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/transforms/string/stringConcealing.ts b/src/transforms/string/stringConcealing.ts index 4ab1fa0..f7e3595 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -45,6 +45,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { const blocks: NodePath[] = []; const stringMap = new Map(); const stringArrayName = me.getPlaceholder() + "_array"; + const stringArrayCacheName = me.getPlaceholder() + "_cache"; let encodingImplementations: { [identity: string]: CustomStringEncoding } = Object.create(null); @@ -263,6 +264,16 @@ export default ({ Plugin }: PluginArg): PluginObject => { ]) ); + // Create the string cache + prependProgram( + programPath, + new Template(` + var {stringArrayCacheName} = {}; + `).single({ + stringArrayCacheName, + }) + ); + for (var block of blocks) { const { encodingImplementation, fnName } = ( block.node as NodeStringConcealing @@ -283,7 +294,10 @@ export default ({ Plugin }: PluginArg): PluginObject => { // The main function to get the string value const retrieveFunctionDeclaration = new Template(` function ${fnName}(index) { - return ${decodeFnName}(${stringArrayName}[index]); + if (typeof ${stringArrayCacheName}[index] === 'undefined') { + return ${stringArrayCacheName}[index] = ${decodeFnName}(${stringArrayName}[index]); + } + return ${stringArrayCacheName}[index]; } `) .addSymbols(NO_REMOVE) From 27ae847266888bcc9e6ae3922e96a61a1ec49a72 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 30 Nov 2024 19:21:01 -0500 Subject: [PATCH 099/103] Generator functions for CFF --- src/transforms/controlFlowFlattening.ts | 40 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts index 71dec09..6822f01 100644 --- a/src/transforms/controlFlowFlattening.ts +++ b/src/transforms/controlFlowFlattening.ts @@ -69,13 +69,14 @@ export default ({ Plugin }: PluginArg): PluginObject => { const mangleNumericalLiterals = true; // 50 => state + X const mangleBooleanLiterals = true; // true => state == X const addWithStatement = true; // Disabling not supported yet + const addGeneratorFunction = true; // Wrap in generator function? const cffPrefix = me.getPlaceholder(); // Amount of blocks changed by Control Flow Flattening let cffCounter = 0; - const functionsModified = new Set(); + const functionsModified: t.Node[] = []; function flagFunctionToAvoid(path: NodePath, reason: string) { var fnOrProgram = getParentFunctionOrProgram(path); @@ -85,9 +86,9 @@ export default ({ Plugin }: PluginArg): PluginObject => { return { post: () => { - functionsModified.forEach((node) => { + for (var node of functionsModified) { (node as NodeSymbol)[UNSAFE] = true; - }); + } }, visitor: { @@ -1572,10 +1573,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { }) `).expression({ - callExpression: t.callExpression( - deepClone(mainFnName), - argumentsNodes - ), + callExpression: createCallExpression(argumentsNodes), }) ); } @@ -1607,15 +1605,33 @@ export default ({ Plugin }: PluginArg): PluginObject => { const mainFnDeclaration = t.functionDeclaration( deepClone(mainFnName), parameters, - t.blockStatement([whileStatement]) + t.blockStatement([whileStatement]), + addGeneratorFunction ); // The main function is always called with same number of arguments (mainFnDeclaration as NodeSymbol)[PREDICTABLE] = true; - var startProgramExpression = t.callExpression(deepClone(mainFnName), [ - ...startStateValues.map((stateValue) => numericLiteral(stateValue)), - ]); + function createCallExpression(argumentNodes: t.Expression[]) { + const callExpression = t.callExpression( + deepClone(mainFnName), + argumentNodes + ); + + if (!addGeneratorFunction) { + return callExpression; + } + + return new Template(` + ({callExpression})["next"]()["value"]; + `).expression({ + callExpression: callExpression, + }); + } + + var startProgramExpression = createCallExpression( + startStateValues.map((stateValue) => numericLiteral(stateValue)) + ); const resultVar = withIdentifier("result"); @@ -1648,7 +1664,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { ...startProgramStatements, ]; - functionsModified.add(programOrFunctionPath.node); + functionsModified.push(programOrFunctionPath.node); // Reset all bindings here blockPath.scope.bindings = Object.create(null); From c3d8b29e11cf9502274afac48f369d9e373783dc Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 30 Nov 2024 19:22:08 -0500 Subject: [PATCH 100/103] Remove 'hash' variable from Integrity --- src/transforms/lock/integrity.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/transforms/lock/integrity.ts b/src/transforms/lock/integrity.ts index 92776f6..d288972 100644 --- a/src/transforms/lock/integrity.ts +++ b/src/transforms/lock/integrity.ts @@ -91,16 +91,19 @@ export default ({ Plugin }: PluginArg): PluginObject => { // me.log(codeTrimmed, hashCode); me.changeData.functions++; + const hashName = nameGen.generate(); + funcDecPath.node.body = t.blockStatement( new Template(` - var hash = ${selfCacheString} || (${selfCacheString} = ${obfuscatedHashFnName}(${newFunctionDeclaration.id.name}, ${seed})); - if(hash === ${hashCode}) { + var {hashName} = ${selfCacheString} || (${selfCacheString} = ${obfuscatedHashFnName}(${newFunctionDeclaration.id.name}, ${seed})); + if({hashName} === ${hashCode}) { {originalBody} } else { {countermeasures} } `).compile({ originalBody: funcDecPath.node.body.body, + hashName, countermeasures: () => me.globalState.lock.createCountermeasuresCode(), }), From c6f49385170bd76ab6c40033fb61c8ec407ea148 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 30 Nov 2024 19:22:53 -0500 Subject: [PATCH 101/103] Minor tweaks --- src/transforms/flatten.ts | 2 -- src/transforms/lock/lock.ts | 9 +-------- src/transforms/pack.ts | 8 ++++++++ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index dc8acd6..3b5a214 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -17,8 +17,6 @@ import { computeFunctionLength, isVariableFunctionIdentifier, } from "../utils/function-utils"; -import { ok } from "assert"; -import { Scope } from "@babel/traverse"; import { NameGen } from "../utils/NameGen"; export default ({ Plugin }: PluginArg): PluginObject => { diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index 03fbe80..ad328a5 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -107,7 +107,7 @@ export default ({ Plugin }: PluginArg): PluginObject => { // Breaks any code formatter var namedFunction = function(){ const test = function(){ - const regExp=new RegExp('\\n'); + const regExp= new RegExp('\\n'); return regExp['test'](namedFunction) }; @@ -116,13 +116,6 @@ export default ({ Plugin }: PluginArg): PluginObject => { } } - // Prepack Breaker - if(Math.random() > 1) { - while(true) { - console.log("Prepack Breaker"); - } - } - return namedFunction(); } )(); diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts index a7e663f..1fa225a 100644 --- a/src/transforms/pack.ts +++ b/src/transforms/pack.ts @@ -60,6 +60,14 @@ export default function pack({ Plugin }: PluginArg): PluginObject { // Ensure bindings are removed -> variable becomes a global -> added to mappings object path.scope.crawl(); }, + + // TODO: Add support for export statements + "ExportNamedDeclaration|ExportDefaultDeclaration|ExportAllDeclaration"( + path + ) { + me.error("Export statements are not supported in packed code."); + }, + Program(path) { path.scope.crawl(); }, From a35eda51055a832b1a19bba513f1fbe23963a4b7 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 30 Nov 2024 19:54:22 -0500 Subject: [PATCH 102/103] 2.0 --- CHANGELOG.md | 22 ++++++++++------------ Migration.md | 3 ++- README.md | 32 ++++++++++++++++++++------------ package.json | 2 +- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbbe3d3..61c7c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ -# `2.0.0-alpha.0` -2.0 Rewrite Alpha 🎉 - -**⚠️ Warning: This an alpha release. This version is not stable and the likelihood of encountering bugs is significantly higher.** +# `2.0.0` +2.0 Rewrite 🎉 ### Complete rewrite of JS-Confuser using Babel! 🎉 @@ -37,11 +35,11 @@ const options = { ### 2.0 Changes -- Added [Custom String Encoding](https://new--confuser.netlify.app/docs/options/customStringEncodings) and [Custom Lock Code](https://new--confuser.netlify.app/docs/options/customLocks) options +- Added [Custom String Encoding](https://js-confuser.com/docs/options/customStringEncodings) and [Custom Lock Code](https://js-confuser.com/docs/options/customLocks) options -- Added `Rename Labels` [Learn more here](https://new--confuser.netlify.app/docs/options/renamelabels#rename-labels) +- Added `Rename Labels` [Learn more here](https://js-confuser.com/docs/options/renamelabels#rename-labels) -- Added `Pack` [Learn more here](https://new--confuser.netlify.app/docs/options/pack#pack) +- Added `Pack` [Learn more here](https://js-confuser.com/docs/options/pack#pack) - RGF no longers uses `new Function` instead uses `eval` @@ -71,7 +69,7 @@ const options = { - New Comment Syntax -- - `/* @js-confuser-var */ "name"` for improved variable mappings for eval() calls +- - `/* @js-confuser-var */ "name"` for improved variable mappings, such as eval() calls ```js // Input @@ -83,19 +81,19 @@ var zC3PLKu = "Internet User"; eval("console.log(" + "zC3PLKu" + ")"); ``` -The function `__JS_CONFUSER_VAR__` is identical in outcome and still be used, however, the comment syntax is preferred as the comment syntax's preserves the original script's behavior. +Note: The function `__JS_CONFUSER_VAR__` is still supported. ### JS-Confuser.com Revamp A new UI for [JS-Confuser.com](https://js-confuser.com), featuring an advanced playground and documentation pages. -The previous version will remain available: [old--confuser.netlify.com](https://old--confuser.netlify.app/) +The previous version will remain available at [old--confuser.netlify.com](https://old--confuser.netlify.app/). **Removed features** - Removed `ES5` option - Use Babel Instead -- Removed `Browser Lock` and `OS Lock` - Use [Custom Locks](https://new--confuser.netlify.app/docs/options/customlocks#custom-locks) instead +- Removed `Browser Lock` and `OS Lock` - Use [Custom Locks](https://js-confuser.com/docs/options/customlocks#custom-locks) instead - Removed `Shuffle`'s Hash option @@ -134,7 +132,7 @@ eval('console.log(' + 'CA1HU0' + ')'); - Improve `String Compression` -- New Web UI sneak peak: https://new--confuser.netlify.app/ +- New Web UI sneak peak: https://js-confuser.com/ # `1.7.2` diff --git a/Migration.md b/Migration.md index f436567..0ddc63f 100644 --- a/Migration.md +++ b/Migration.md @@ -9,6 +9,7 @@ JS-Confuser 2.0 is complete rewrite of the original JS-Confuser created in 2020! The method `JSConfuser.obfuscate()` resolves to a object now instead of a string. This result object contains a property `code` which is the obfuscated code. ```diff +const JSConfuser = require("js-confuser"); const sourceCode = `console.log("Hello World")`; const options = { target: "node", @@ -47,7 +48,7 @@ These features have been removed but you can still add these locks using the `lo // Must be placed in a Block or Switch Case body `, percentagePerBlock: 0.1, // = 10% - maxCount: 100, // Default = 100 - You probably don't want an excessive amount placed + maxCount: 25, // Default = 25 - You probably don't want an excessive amount placed minCount: 1 // Default = 1 - Ensures this custom lock is placed } ] diff --git a/README.md b/README.md index 2af5e54..23b41cb 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,37 @@ # JS Confuser -**⚠️ Warning: This an alpha release. This version is not stable and the likelihood of encountering bugs is significantly higher.** - JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ to read. [Try the web version](https://js-confuser.com). [![NPM](https://img.shields.io/badge/NPM-%23000000.svg?style=for-the-badge&logo=npm&logoColor=white)](https://npmjs.com/package/js-confuser) [![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/MichaelXF/js-confuser) [![Netlify](https://img.shields.io/badge/netlify-%23000000.svg?style=for-the-badge&logo=netlify&logoColor=#00C7B7)](https://js-confuser.com) +## Key features + +- Variable renaming +- Control Flow obfuscation +- String concealing +- Function obfuscation +- Locks (domainLock, date) +- [Detect changes to source code](https://new--confuser.netlify.app/docs/options/integrity#integrity) + +## Documentation -**The official documentation for this project has moved to [`JS-Confuser.com`](https://js-confuser.com)**: +Get started in the [`JS-Confuser Docs`](https://js-confuser.com/docs/). -- [Getting Started](https://new--confuser.netlify.app/docs) +- [Getting Started](https://js-confuser.com/docs) -- - [What is Obfuscation?](https://new--confuser.netlify.app/docs/getting-started/what-is-obfuscation) +- - [What is Obfuscation?](https://js-confuser.com/docs/getting-started/what-is-obfuscation) -- - [Playground](https://new--confuser.netlify.app/docs/getting-started/playground) +- - [Playground](https://js-confuser.com/docs/getting-started/playground) -- - [Installation](https://new--confuser.netlify.app/docs/getting-started/installation) +- - [Installation](https://js-confuser.com/docs/getting-started/installation) -- - [Usage](https://new--confuser.netlify.app/docs/getting-started/usage) +- - [Usage](https://js-confuser.com/docs/getting-started/usage) -- - [FAQ](https://new--confuser.netlify.app/docs/getting-started/faq) +- - [FAQ](https://js-confuser.com/docs/getting-started/faq) -- [Options](https://new--confuser.netlify.app/docs/options) +- [Options](https://js-confuser.com/docs/options) -- [Presets](https://new--confuser.netlify.app/docs/presets) +- [Presets](https://js-confuser.com/docs/presets) ## API Usage @@ -61,7 +69,7 @@ JsConfuser.obfuscate(` }) /* -var AF59rI,ZgbbeaU,WDgj3I,gpR2qG,Ox61sk,pTNPNpX;AF59rI=[60,17,25,416,22,23,83,26,27,28,18,382,66,29,30,31,2,5,33,4,13,16,10,11,24,1,3,15,6,7,8,167,50,9,21,35,12,14,116],ZgbbeaU=AF59rI;for(var TlMIASm=62;TlMIASm;TlMIASm--)ZgbbeaU.unshift(ZgbbeaU.pop());WDgj3I=MBh_HcM("length1charCodeAt1slice1replaĕ1!ğğ1uģģ1<~A8bt#D.RU,~>Ħ~E,ol,ATMnijĵ@rH7+DertŀħDKTtlBhE[ŋ~@q]:k6Z6LHŖ6$*Ŗ7n#j;20AŖ;g3Cn<]'Ŗ-Q:c8Ŗ?SF2m2*!WQŖ2)RIJƐ~ž<ƿĴmČuĀ1 (local)").split('1');function pprWr0(ZgbbeaU){var WDgj3I,gpR2qG,Ox61sk,pTNPNpX,TlMIASm,pprWr0,M1ClYmT,kHWl72,xw_ohrD,sT8e3fv,bxd0KVG;WDgj3I=void 0,gpR2qG=void 0,Ox61sk=void 0,pTNPNpX=void 0,TlMIASm=void 0,pprWr0=String,M1ClYmT=CVH25o3(0),kHWl72=255,xw_ohrD=CVH25o3(1),sT8e3fv=CVH25o3(AF59rI[0]),bxd0KVG=CVH25o3(3);for('<~'===ZgbbeaU[sT8e3fv](0,AF59rI[0])&&'~>'===ZgbbeaU[sT8e3fv](-AF59rI[0]),ZgbbeaU=ZgbbeaU[sT8e3fv](AF59rI[0],-AF59rI[0])[bxd0KVG](/s/g,'')[bxd0KVG]('z',CVH25o3(AF59rI[3])),WDgj3I=CVH25o3(AF59rI[1])[sT8e3fv](ZgbbeaU[M1ClYmT]%AF59rI[1]||AF59rI[1]),ZgbbeaU+=WDgj3I,Ox61sk=[],pTNPNpX=0,TlMIASm=ZgbbeaU[M1ClYmT];TlMIASm>pTNPNpX;pTNPNpX+=AF59rI[1])gpR2qG=52200625*(ZgbbeaU[xw_ohrD](pTNPNpX)-AF59rI[2])+614125*(ZgbbeaU[xw_ohrD](pTNPNpX+AF59rI[9])-AF59rI[2])+7225*(ZgbbeaU[xw_ohrD](pTNPNpX+AF59rI[0])-AF59rI[2])+85*(ZgbbeaU[xw_ohrD](pTNPNpX+AF59rI[10])-AF59rI[2])+(ZgbbeaU[xw_ohrD](pTNPNpX+AF59rI[3])-AF59rI[2]),Ox61sk.push(kHWl72&gpR2qG>>AF59rI[8],kHWl72&gpR2qG>>AF59rI[5],kHWl72&gpR2qG>>8,kHWl72&gpR2qG);return function(ZgbbeaU,Ox61sk){for(var WDgj3I=Ox61sk;WDgj3I>0;WDgj3I--)ZgbbeaU.pop()}(Ox61sk,WDgj3I[M1ClYmT]),pprWr0.fromCharCode.apply(pprWr0,Ox61sk)}gpR2qG=[CVH25o3(AF59rI[12]),CVH25o3(AF59rI[13]),CVH25o3(8),CVH25o3(AF59rI[17]),CVH25o3(AF59rI[6]),CVH25o3(AF59rI[7]),CVH25o3(AF59rI[20]),'<~AQO1jBl7V~>',CVH25o3(AF59rI[4]),CVH25o3(AF59rI[21]),CVH25o3(AF59rI[4]),CVH25o3(9),CVH25o3(AF59rI[11]),CVH25o3(AF59rI[5]),CVH25o3(AF59rI[24]),CVH25o3(AF59rI[33]),'<~E%u9/13QC~>',CVH25o3(AF59rI[6]),CVH25o3(AF59rI[7]),CVH25o3(19),CVH25o3(20),CVH25o3(AF59rI[18]),CVH25o3(AF59rI[27]),CVH25o3(AF59rI[28]),CVH25o3(AF59rI[8]),'<~?T9_t1,(IC~>','<~1bpf~>',CVH25o3(AF59rI[25]),CVH25o3(AF59rI[30]),CVH25o3(AF59rI[31]),CVH25o3(14),CVH25o3(AF59rI[8])];function M1ClYmT(AF59rI){return pprWr0(gpR2qG[AF59rI])}function kHWl72(){try{return global}catch(AF59rI){return this}}Ox61sk=kHWl72.call(this);function xw_ohrD(ZgbbeaU){switch(ZgbbeaU){case 608:return Ox61sk[M1ClYmT(0)];case-884:return Ox61sk[CVH25o3(AF59rI[32])];case AF59rI[26]:return Ox61sk[M1ClYmT(AF59rI[9])];case-AF59rI[35]:return Ox61sk[M1ClYmT(2)]}}function sT8e3fv(ZgbbeaU,WDgj3I,gpR2qG){var Ox61sk;Ox61sk=11;while(Ox61sk!=51){var pTNPNpX,TlMIASm,pprWr0,kHWl72;pTNPNpX=Ox61sk*-244+217;switch(pTNPNpX){case-2467:TlMIASm=false,Ox61sk+=37;break;case-4175:kHWl72=WDgj3I==M1ClYmT(AF59rI[10])&&ziPI9L.qzUvJu1[M1ClYmT(4)+M1ClYmT(AF59rI[1])](AF59rI[9])==48?function(...WDgj3I){var gpR2qG;gpR2qG=AF59rI[1];while(gpR2qG!=AF59rI[11]){var Ox61sk;Ox61sk=gpR2qG*41+199;switch(Ox61sk){case 732:return pprWr0[ZgbbeaU].call(this,M1ClYmT(AF59rI[12]));case 404:IZftqI=WDgj3I,gpR2qG+=AF59rI[14]}}}:pprWr0[ZgbbeaU](M1ClYmT(AF59rI[13])),Ox61sk-=AF59rI[10];break;case-11495:pprWr0={[M1ClYmT(AF59rI[14])]:function(ZgbbeaU,WDgj3I,gpR2qG){var Ox61sk;Ox61sk=64;while(Ox61sk!=AF59rI[16]){var pTNPNpX,TlMIASm,pprWr0;pTNPNpX=Ox61sk*AF59rI[15]+144;switch(pTNPNpX){case 10832:TlMIASm=822,Ox61sk+=AF59rI[10];break;case 812:pprWr0[AF59rI[10]]=pprWr0[0],Ox61sk+=47;break;case 8661:while(TlMIASm!=772){var kHWl72;kHWl72=TlMIASm*234+191;switch(kHWl72){case 207515:TlMIASm-=528;break;case 129593:pprWr0[3]=bxd0KVG(AF59rI[15],pprWr0[AF59rI[9]],pprWr0[AF59rI[0]]),pprWr0[AF59rI[9]]=pprWr0[AF59rI[0]],pprWr0[AF59rI[0]]=pprWr0[AF59rI[10]],TlMIASm+=333;break;case 83963:TlMIASm+=bxd0KVG(-AF59rI[29],pprWr0[0]--,AF59rI[9])&&ziPI9L.U1LXDgJ()?195:414;break;case 192539:TlMIASm-=464}}Ox61sk-=AF59rI[16];break;case 10999:[...pprWr0]=IZftqI,pprWr0.length=1,Ox61sk-=AF59rI[38];break;case 6824:return[];case 11333:if(!ZgbbeaU){return WDgj3I(this,gpR2qG)}Ox61sk-=AF59rI[0];break;case 311:return[pprWr0[AF59rI[10]]];case 5822:pprWr0[1]=0,pprWr0[AF59rI[0]]=AF59rI[9],Ox61sk-=AF59rI[37]}}},[M1ClYmT(AF59rI[17])]:function(ZgbbeaU,WDgj3I,gpR2qG){var Ox61sk;Ox61sk=AF59rI[18];while(Ox61sk!=38){var pTNPNpX,TlMIASm,pprWr0;pTNPNpX=Ox61sk*182+-139;switch(pTNPNpX){case 4047:pprWr0[AF59rI[9]]=sT8e3fv(M1ClYmT(AF59rI[6]),M1ClYmT(AF59rI[7])).call([],pprWr0[0]),Ox61sk+=AF59rI[19];break;case 3683:TlMIASm=false,Ox61sk+=21;break;case 7505:[...pprWr0]=IZftqI,Ox61sk-=10;break;case 225:return pprWr0[AF59rI[9]].pop();case 10417:if(TlMIASm){var kHWl72=(ZgbbeaU,WDgj3I,gpR2qG)=>{var Ox61sk;Ox61sk=32;while(Ox61sk!=AF59rI[19]){var pTNPNpX,TlMIASm,pprWr0;pTNPNpX=Ox61sk*38+90;switch(pTNPNpX){case 508:TlMIASm=bxd0KVG(AF59rI[15],M1ClYmT(AF59rI[20]),pprWr0.toUTCString()),Ox61sk+=AF59rI[21];break;case 584:pprWr0.setTime(bxd0KVG(AF59rI[15],pprWr0.getTime(),bxd0KVG(AF59rI[22],bxd0KVG(116,bxd0KVG(AF59rI[22],bxd0KVG(116,gpR2qG,AF59rI[8]),AF59rI[23]),AF59rI[23]),1e3))),Ox61sk-=2;break;case 1040:xw_ohrD(608).cookie=bxd0KVG(AF59rI[15],bxd0KVG(AF59rI[15],bxd0KVG(AF59rI[15],bxd0KVG(AF59rI[15],bxd0KVG(AF59rI[15],ZgbbeaU,M1ClYmT(AF59rI[4])),WDgj3I),M1ClYmT(AF59rI[21])),TlMIASm),M1ClYmT(15)),Ox61sk+=AF59rI[6];break;case 1306:pprWr0=new Date,Ox61sk-=19}}}}Ox61sk-=56;break;case 5685:pprWr0.length=1,Ox61sk-=AF59rI[17]}}}},Ox61sk-=43;break;case-14179:kHWl72=void 0;if(WDgj3I==M1ClYmT(AF59rI[5])&&ziPI9L.VNaV0wv[M1ClYmT(AF59rI[24])+M1ClYmT(18)](AF59rI[6])==AF59rI[16]){IZftqI=[]}Ox61sk-=41;break;case-1003:if(TlMIASm){xw_ohrD(-884).exports=async()=>{var ZgbbeaU;ZgbbeaU=33;while(ZgbbeaU!=AF59rI[7]){var WDgj3I,gpR2qG,Ox61sk;WDgj3I=ZgbbeaU*95+-150;switch(WDgj3I){case 3175:gpR2qG=await(async()=>{var ZgbbeaU;ZgbbeaU=14;while(ZgbbeaU!=AF59rI[13]){var WDgj3I;WDgj3I=ZgbbeaU*100+-59;switch(WDgj3I){case 1341:if(isStandaloneExecutable){return M1ClYmT(19)+M1ClYmT(20)}ZgbbeaU+=AF59rI[6];break;case 2341:if(redactedPath===await resolveLocalredactedPath()){return CVH25o3(AF59rI[36])}ZgbbeaU-=AF59rI[13];break;case 1641:return''}}})(),ZgbbeaU-=AF59rI[25];break;case 2985:Ox61sk=new Set(xw_ohrD(AF59rI[26]).argv.slice(2)),ZgbbeaU-=AF59rI[6];break;case 2035:if(!Ox61sk.has(M1ClYmT(AF59rI[18])+M1ClYmT(AF59rI[27]))){var pTNPNpX;pTNPNpX=AF59rI[14];while(pTNPNpX!=9){var TlMIASm;TlMIASm=pTNPNpX*-204+38;switch(TlMIASm){case-1594:if(bxd0KVG(427,Ox61sk.size,1)){return false}pTNPNpX-=AF59rI[3];break;case-778:if(!Ox61sk.has(M1ClYmT(AF59rI[28]))){return false}pTNPNpX+=AF59rI[1]}}}ZgbbeaU+=AF59rI[20];break;case 800:return true}}}}Ox61sk+=54;break;case-3443:return gpR2qG==M1ClYmT(AF59rI[8])&&ziPI9L.U1LXDgJ()?{QVbrqy9:kHWl72}:kHWl72}}}function bxd0KVG(ZgbbeaU,WDgj3I,gpR2qG){switch(ZgbbeaU){case-AF59rI[34]:return WDgj3I<=gpR2qG;case-AF59rI[29]:return WDgj3I>gpR2qG;case AF59rI[15]:return WDgj3I+gpR2qG;case AF59rI[22]:return WDgj3I*gpR2qG;case 427:return WDgj3I!==gpR2qG}}pTNPNpX=AF59rI[12];while(pTNPNpX!=AF59rI[24]){var kBznIi,sCb8UYh,ziPI9L,IZftqI;kBznIi=pTNPNpX*-55+-214;switch(kBznIi){case-544:sCb8UYh=846,pTNPNpX+=AF59rI[0];break;case-654:ziPI9L={wHDYSl:[],U1LXDgJ:function(){if(!ziPI9L.wHDYSl[0]){ziPI9L.wHDYSl.push(87)}return ziPI9L.wHDYSl.length},VNaV0wv:M1ClYmT(AF59rI[25])+M1ClYmT(AF59rI[30]),qzUvJu1:M1ClYmT(AF59rI[31])+M1ClYmT(AF59rI[32])},pTNPNpX+=AF59rI[33];break;case-1644:IZftqI=[],pTNPNpX-=7;break;case-1259:while(sCb8UYh!=316){var XsBuZX,mgjtps2;XsBuZX=sCb8UYh*229+-125;switch(XsBuZX){case 193609:mgjtps2=AF59rI[9],sCb8UYh-=733;break;case 25752:sCb8UYh+=bxd0KVG(-AF59rI[34],mgjtps2,25)&&ziPI9L.U1LXDgJ()?662:203;break;case 177350:xw_ohrD(-AF59rI[35])[M1ClYmT(AF59rI[36])](mgjtps2,(IZftqI=[mgjtps2],new sT8e3fv(M1ClYmT(AF59rI[37]),void 0,M1ClYmT(AF59rI[38])).QVbrqy9)),sCb8UYh-=569;break;case 47049:mgjtps2++,sCb8UYh-=93}}pTNPNpX-=AF59rI[0]}}function CVH25o3(AF59rI){return WDgj3I[AF59rI]}function MBh_HcM(ZgbbeaU){var WDgj3I,gpR2qG,Ox61sk,pTNPNpX,TlMIASm,pprWr0,M1ClYmT,kHWl72;WDgj3I=void 0,gpR2qG=void 0,Ox61sk=void 0,pTNPNpX={},TlMIASm=ZgbbeaU.split(''),pprWr0=gpR2qG=TlMIASm[0],M1ClYmT=[pprWr0],kHWl72=WDgj3I=256;for(ZgbbeaU=AF59rI[9];ZgbbeaUOx61sk?TlMIASm[ZgbbeaU]:pTNPNpX[Ox61sk]?pTNPNpX[Ox61sk]:gpR2qG+pprWr0,M1ClYmT.push(Ox61sk),pprWr0=Ox61sk.charAt(0),pTNPNpX[WDgj3I]=gpR2qG+pprWr0,WDgj3I++,gpR2qG=Ox61sk;return M1ClYmT.join('')} +Function("tZzJOP","var iSDaP3,MyrZnn,PugOol,zlitc1Y,NqvmoV,w01dEg,PEGf2Ir,wQdUIp,YwPLty,BNOmOCh,CHDWybv,MsWp2Eg,q5kdT5,tDyAgBo,Nmc7b1;const WydP2H=[\"length\",0x2,0x0,0x1,\"c\",0x3e,null,0x20,\"a\",0x100,0x6,0x3,0x8,0x10,0x4,\"undefined\",\"LZString\",0x25,\"h\",0xe2,0x8d,0x34,0x5,\"f\",0xf5,0xff,0x7,0xd,0xe,0xf,0x58,0x5b,0x4f,0xf1,0x31,0xc1,0x80,void 0x0,\"d\",\"b\",0x7f,0x1f,0x3f,0xc,0xc7,0x12,0xbd,0xc9,0x9d,0x48,\"i\",\"g\",0x1fff,0x77,0x15,0x84,0x24,0x9,\"e\",0x63,0x64,0x22,0xaa,0xfa,0x65,0x66,0x67,0x68,0x69,0xcf,0x6a,0x6b,0xe9,0xf2,0xd4,0xd8,0x6c,0x6d,0x28,0xa,0x8a,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x81,0x82,0x83,0x85,0x86,0x87,0x88,0x89,0x8b,0x8c,0x8e,0x8f,0x90,0x91,\"8\",0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9e,0x9f,0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xab,0xac,0xad,0xae,0xaf,0xc4,0x40,0xed,0xb0,0xe8,0xb1,0xb2,0xb3,0xb4,0xbf,0xb6,0xb5,0xb7,0xea,0xf6,0xb8,0x53,0xb9,\"t\",!0x1,0xba,0xbb,0xbc,0xbe,0xc0,0xc2,0xc3,0xc5,0xc6,0x4d,0xc8,0xca,0xcb,0xcc,0xcd,0x19];Fj4SwuY(YVrVbv(be5E4W),YVrVbv(g7PNjlU),YVrVbv(Hxpsdj_),YVrVbv(putCzy));function YVrVbv(MyrZnn,PugOol=WydP2H[0x3]){Object.defineProperty(MyrZnn,WydP2H[0x0],{value:PugOol,configurable:WydP2H[0x9f]});return MyrZnn}Fj4SwuY(iSDaP3=function(...MyrZnn){Fj4SwuY(MyrZnn[WydP2H[0x0]]=WydP2H[0x2],YVrVbv(PEGf2Ir,WydP2H[0x1]));var PugOol=String.fromCharCode,zlitc1Y=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",NqvmoV=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$\",w01dEg={};function PEGf2Ir(...MyrZnn){var PugOol,zlitc1Y;function*NqvmoV(zlitc1Y,NqvmoV,PEGf2Ir,wQdUIp,YwPLty={r78IiA:{}}){while(zlitc1Y+NqvmoV+PEGf2Ir+wQdUIp!==0x1e)with(YwPLty.Vohu0Q||YwPLty)switch(zlitc1Y+NqvmoV+PEGf2Ir+wQdUIp){case 0x5f:case 0xef:Fj4SwuY(YwPLty.r78IiA.FpCHxeU=-0x4,MyrZnn[WydP2H[0x0]]=WydP2H[zlitc1Y+0x10]);if(!w01dEg[MyrZnn[WydP2H[0x2]]]){Fj4SwuY(YwPLty.Vohu0Q=YwPLty.r78IiA,zlitc1Y+=-0xb1,NqvmoV+=0x7b,PEGf2Ir+=-0x4c,wQdUIp+=0x4a);break}else{Fj4SwuY(YwPLty.Vohu0Q=YwPLty.r78IiA,zlitc1Y+=-0x28,NqvmoV+=0x11b,PEGf2Ir+=-0x152,wQdUIp+=0x4a);break}case YwPLty.r78IiA.FpCHxeU+0x4e:return PugOol=!0x0,w01dEg[MyrZnn[WydP2H[0x2]]][MyrZnn[WydP2H[PEGf2Ir+0xda]]];case-0x93:case 0x27:case 0x2a:w01dEg[MyrZnn[WydP2H[zlitc1Y+0xc2]]]={};for(MyrZnn[WydP2H[zlitc1Y+0xc1]]=WydP2H[0x2];MyrZnn[WydP2H[0x1]]>>WydP2H[0xc],YwPLty[WydP2H[0x1]*BNOmOCh+WydP2H[0x3]]=PugOol[WydP2H[0x8]]%WydP2H[0x9])}return YwPLty}),decompressFromUint8Array:YVrVbv(function(...zlitc1Y){zlitc1Y[WydP2H[0x0]]=WydP2H[0x3];if(WydP2H[0x6]==zlitc1Y[WydP2H[0x2]])return MyrZnn[WydP2H[0x4]].decompress(zlitc1Y[WydP2H[0x2]]);for(var wQdUIp=new Array(zlitc1Y[WydP2H[0x2]].length/WydP2H[0x1]),YwPLty=WydP2H[0x2],BNOmOCh=wQdUIp.length;YwPLty>=WydP2H[0x3]}else{for(CHDWybv=WydP2H[0x3],BNOmOCh=WydP2H[0x2];BNOmOCh>=WydP2H[0x3]}Fj4SwuY(WydP2H[0x2]==--UkVIMy&&(UkVIMy=Math.pow(WydP2H[0x1],RT3qJW_),RT3qJW_++),delete tDyAgBo[Hxpsdj_])}else for(CHDWybv=q5kdT5[Hxpsdj_],BNOmOCh=WydP2H[0x2];BNOmOCh>=WydP2H[0x3];Fj4SwuY(WydP2H[0x2]==--UkVIMy&&(UkVIMy=Math.pow(WydP2H[0x1],RT3qJW_),RT3qJW_++),q5kdT5[putCzy]=g7PNjlU++,Hxpsdj_=String(YVrVbv))}if(\"\"!==Hxpsdj_){if(Object.prototype.hasOwnProperty.call(tDyAgBo,Hxpsdj_)){if(Hxpsdj_.charCodeAt(WydP2H[0x2])>=WydP2H[0x3]}else{for(CHDWybv=WydP2H[0x3],BNOmOCh=WydP2H[0x2];BNOmOCh>=WydP2H[0x3]}Fj4SwuY(WydP2H[0x2]==--UkVIMy&&(UkVIMy=Math.pow(WydP2H[0x1],RT3qJW_),RT3qJW_++),delete tDyAgBo[Hxpsdj_])}else for(CHDWybv=q5kdT5[Hxpsdj_],BNOmOCh=WydP2H[0x2];BNOmOCh>=WydP2H[0x3];WydP2H[0x2]==--UkVIMy&&(UkVIMy=Math.pow(WydP2H[0x1],RT3qJW_),RT3qJW_++)}for(CHDWybv=WydP2H[0x1],BNOmOCh=WydP2H[0x2];BNOmOCh>=WydP2H[0x3];for(;;){if(be5E4W<<=WydP2H[0x3],GpA0Rz==wQdUIp-WydP2H[0x3]){qLPG1Tw.push(YwPLty(be5E4W));break}GpA0Rz++}return qLPG1Tw.join(\"\")},decompress:YVrVbv(function(...PugOol){PugOol[WydP2H[0x0]]=WydP2H[0x3];return WydP2H[0x6]==PugOol[WydP2H[0x2]]?\"\":\"\"==PugOol[WydP2H[0x2]]?WydP2H[0x6]:MyrZnn[WydP2H[0x4]]._decompress(PugOol[WydP2H[0x2]].length,0x8000,YVrVbv(function(...MyrZnn){MyrZnn[WydP2H[0x0]]=WydP2H[0x3];return PugOol[WydP2H[0x2]].charCodeAt(MyrZnn[WydP2H[0x2]])}))}),_decompress:function(MyrZnn,wQdUIp,YwPLty){var BNOmOCh,CHDWybv,MsWp2Eg,q5kdT5,tDyAgBo,YVrVbv,putCzy,Hxpsdj_=[],UkVIMy=WydP2H[0xe],g7PNjlU=WydP2H[0xe],RT3qJW_=WydP2H[0xb],qLPG1Tw=\"\",be5E4W=[],GpA0Rz={val:YwPLty(WydP2H[0x2]),position:wQdUIp,index:WydP2H[0x3]};for(BNOmOCh=WydP2H[0x2];BNOmOCh>=WydP2H[0x3],WydP2H[0x2]==GpA0Rz.position&&(GpA0Rz.position=wQdUIp,GpA0Rz.val=YwPLty(GpA0Rz.index++)),MsWp2Eg|=(q5kdT5>WydP2H[0x2]?WydP2H[0x3]:WydP2H[0x2])*YVrVbv,YVrVbv<<=WydP2H[0x3];switch(MsWp2Eg){case WydP2H[0x2]:for(MsWp2Eg=WydP2H[0x2],tDyAgBo=Math.pow(WydP2H[0x1],WydP2H[0xc]),YVrVbv=WydP2H[0x3];YVrVbv!=tDyAgBo;)q5kdT5=GpA0Rz.val&GpA0Rz.position,GpA0Rz.position>>=WydP2H[0x3],WydP2H[0x2]==GpA0Rz.position&&(GpA0Rz.position=wQdUIp,GpA0Rz.val=YwPLty(GpA0Rz.index++)),MsWp2Eg|=(q5kdT5>WydP2H[0x2]?WydP2H[0x3]:WydP2H[0x2])*YVrVbv,YVrVbv<<=WydP2H[0x3];putCzy=PugOol(MsWp2Eg);break;case WydP2H[0x3]:for(MsWp2Eg=WydP2H[0x2],tDyAgBo=Math.pow(WydP2H[0x1],WydP2H[0xd]),YVrVbv=WydP2H[0x3];YVrVbv!=tDyAgBo;)q5kdT5=GpA0Rz.val&GpA0Rz.position,GpA0Rz.position>>=WydP2H[0x3],WydP2H[0x2]==GpA0Rz.position&&(GpA0Rz.position=wQdUIp,GpA0Rz.val=YwPLty(GpA0Rz.index++)),MsWp2Eg|=(q5kdT5>WydP2H[0x2]?WydP2H[0x3]:WydP2H[0x2])*YVrVbv,YVrVbv<<=WydP2H[0x3];putCzy=PugOol(MsWp2Eg);break;case WydP2H[0x1]:return\"\"}for(Hxpsdj_[WydP2H[0xb]]=putCzy,CHDWybv=putCzy,be5E4W.push(putCzy);;){if(GpA0Rz.index>MyrZnn)return\"\";for(MsWp2Eg=WydP2H[0x2],tDyAgBo=Math.pow(WydP2H[0x1],RT3qJW_),YVrVbv=WydP2H[0x3];YVrVbv!=tDyAgBo;)q5kdT5=GpA0Rz.val&GpA0Rz.position,GpA0Rz.position>>=WydP2H[0x3],WydP2H[0x2]==GpA0Rz.position&&(GpA0Rz.position=wQdUIp,GpA0Rz.val=YwPLty(GpA0Rz.index++)),MsWp2Eg|=(q5kdT5>WydP2H[0x2]?WydP2H[0x3]:WydP2H[0x2])*YVrVbv,YVrVbv<<=WydP2H[0x3];switch(putCzy=MsWp2Eg){case WydP2H[0x2]:for(MsWp2Eg=WydP2H[0x2],tDyAgBo=Math.pow(WydP2H[0x1],WydP2H[0xc]),YVrVbv=WydP2H[0x3];YVrVbv!=tDyAgBo;)q5kdT5=GpA0Rz.val&GpA0Rz.position,GpA0Rz.position>>=WydP2H[0x3],WydP2H[0x2]==GpA0Rz.position&&(GpA0Rz.position=wQdUIp,GpA0Rz.val=YwPLty(GpA0Rz.index++)),MsWp2Eg|=(q5kdT5>WydP2H[0x2]?WydP2H[0x3]:WydP2H[0x2])*YVrVbv,YVrVbv<<=WydP2H[0x3];Fj4SwuY(Hxpsdj_[g7PNjlU++]=PugOol(MsWp2Eg),putCzy=g7PNjlU-WydP2H[0x3],UkVIMy--);break;case WydP2H[0x3]:for(MsWp2Eg=WydP2H[0x2],tDyAgBo=Math.pow(WydP2H[0x1],WydP2H[0xd]),YVrVbv=WydP2H[0x3];YVrVbv!=tDyAgBo;)q5kdT5=GpA0Rz.val&GpA0Rz.position,GpA0Rz.position>>=WydP2H[0x3],WydP2H[0x2]==GpA0Rz.position&&(GpA0Rz.position=wQdUIp,GpA0Rz.val=YwPLty(GpA0Rz.index++)),MsWp2Eg|=(q5kdT5>WydP2H[0x2]?WydP2H[0x3]:WydP2H[0x2])*YVrVbv,YVrVbv<<=WydP2H[0x3];Fj4SwuY(Hxpsdj_[g7PNjlU++]=PugOol(MsWp2Eg),putCzy=g7PNjlU-WydP2H[0x3],UkVIMy--);break;case WydP2H[0x1]:return be5E4W.join(\"\")}if(WydP2H[0x2]==UkVIMy&&(UkVIMy=Math.pow(WydP2H[0x1],RT3qJW_),RT3qJW_++),Hxpsdj_[putCzy])qLPG1Tw=Hxpsdj_[putCzy];else{if(putCzy!==g7PNjlU)return WydP2H[0x6];qLPG1Tw=CHDWybv+CHDWybv.charAt(WydP2H[0x2])}Fj4SwuY(be5E4W.push(qLPG1Tw),Hxpsdj_[g7PNjlU++]=CHDWybv+qLPG1Tw.charAt(WydP2H[0x2]),CHDWybv=qLPG1Tw,WydP2H[0x2]==--UkVIMy&&(UkVIMy=Math.pow(WydP2H[0x1],RT3qJW_),RT3qJW_++))}}};return MyrZnn[WydP2H[0x4]]}(),\"function\"==tZzJOP[\"TE0C7CR\"]&&tZzJOP[\"_8iE0h\"].amd?tZzJOP[\"_8iE0h\"](function(){return iSDaP3}):WydP2H[0xf]!=tZzJOP[\"soSW4YC\"]&&WydP2H[0x6]!=tZzJOP[\"pXH_Js\"]?tZzJOP[\"pXH_Js\"].exports=iSDaP3:WydP2H[0xf]!=tZzJOP[\"xOGz1y\"]&&WydP2H[0x6]!=tZzJOP[\"ZQZggA\"]&&tZzJOP[\"ZQZggA\"].module(WydP2H[0x10],[]).factory(WydP2H[0x10],function(){return iSDaP3}),MyrZnn=void 0x0,function(){var PugOol=\"\\u15E1\\u6C29\\u416C\\u0680\\u4B30\\u5823\\u703F<\\u0298\\u0460\\u3CB4\\u03C0\\u0440\\u1EA0\\u04A0\\u1621\\u4E21\\u75F0\\u024D\\u592A\\u0238\\u0800\\u0CC2\\u404Bf\\u09D8\\u08E0\\u0E80\\u0BE0\\u0800\\u0D69\\u447C\\u205B\\u09B4e\\u147A\\xC2|\\u198C\\xC4h8\\u01A4\\u0154\\xDC\\u018D\\u4820\\u5C84\\u0824\\u72A8\\u1026\\u602C\\u202A\\u2021\\u202B\\u11F6\\u6027\\u4031\\u602A\\u6023\\u0821\\u1821\\u52A1\\u4433\\u0EA0\\u0660\\u02C6\\u3024\\u52C04\\u5420\\u4980\\u4032\\u42A0\\u0103\\u5292\\u1823\\u0C20\\u02E8\\u6748]\\u53B3\\u68AE\\u0EE0\\u0409\\u7515\\u422A\\u0520\\u1CB6\\u40A3\\u1468\\u7027\\u0961\\u4421\\u6E20\\u1B55\\u1273\\u1C78\\u29EF\\u7180\\xF0\\u3B31\\u7E11\\u3220\\u375F\\u4CE2\\u4320\\u2635\\u7BC0\\xF4\\xF6\\u62F8\\u7031\\u43DF\\u46E7\\u4946\\u0821\\u4894t\\u61B2\\u3698\\u5820\\u0CE1\\u22CD\\u1019\\u12F4\\u6620\\u17E9\\u4043\\u1CEE\\u3FAF\\u0170\\xA26\\u402D\\u57B9\\u6E97\\u25AA\\u4022\\u5570\\u58C2\\u04B0\\u11A6\\u3D4B\\u4A45\\u21A2\\u25A9\\u4346\\u3890\\u61BC\\u1A36\\u71A6\\u5466\\u2020\\u1A55\\u5880\\u1FC2\\u2319\\u7323\\u0F01\\u232C\\u1064\\u1485 \\u6F13#\\u6FE0\\u0128\\u1967\\u13E3\\u04E2\\u4443\\u4CAB\\u0A58\\u0764\\u1BA2\\u225C\\u0E5C\\u5248\\u4161\\u2932\\u106A\\u6255\\u4E8B\\u0DA1\\u509D\\u618C\\u4064\\u22D5\\u683D\\u1049\\u73A8\\u1994\\u0A48\\u7820\\u1AF8\\u3661\\u2379\\u08E1\\u3627\\u6539\\u0E20\\u37CD\\u1002\\u1D14%\\u5548\\u1254\\u4AF4\\u5FBE\\u7411\\u4E80\\u0223\\u02C4\\u2C94\\u17E3\\u0588\\u42D0\\u1C49\\u6021\\u6A2A\\u0CA6\\u0BC6\\u5027\\u0930\\u0A37\\u79F8\\u1337\\u1FA9\\u74BC\\u25BB\\u1461\\u4CC3\\u1272\\u6172\\u6CDE\\u25A8\\u4322\\u7CFC\\u373F\\u61A5\\u40E3\\u0EBB\\u43E8\\u54A8\\u2738\\u3150\\u2099\\u3CE7\\u2528\\u48C8\\u1D5B\\u01A7\\u108A\\u052B\\u2548\\u289A\\u3626\\u2729\\u56C9\\u2228\\u6501\\u20B8\\u127F\\u4806\\u3C98\\u3BA3\\u7696\\u62C5\\u1137\\u190A\\u7C7A\\u20BE\\u15D2\\u0180\\u2C67\\u2352\\u30B6\\u11A6\\u0BAE\\u5167\\u22C2\\u4B23\\u1254\\u24AA\\u092E\\u7E31\\u0B20\\u472A\\u41C8\\u232F\\u035A\\u62BD\\u0258\\u573B\\u4038\\u03A0\\u42C2\\u513B\\u02E4\\u5439\\u6BD8\\u4124\\u40D0\\u5A38\\u0150\\u23AE\\u0950 ... */ ``` diff --git a/package.json b/package.json index c7e8ead..77abedd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-confuser", - "version": "2.0.0-alpha.5", + "version": "2.0.0", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", From 1777042d39f454c1dc179cd7708e64a46e3e0198 Mon Sep 17 00:00:00 2001 From: MichaelXF Date: Sat, 30 Nov 2024 20:00:58 -0500 Subject: [PATCH 103/103] Upgrade Node Versions --- .github/workflows/node.js.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 65b1178..e5cfa9b 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - node-version: [14.15, 16.10, 18.0, 20.16] + node-version: [18.0, 20.16, 22.11, 23.3] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/package.json b/package.json index 77abedd..9c82450 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,6 @@ }, "homepage": "https://js-confuser.com", "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }