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. + + 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/.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 new file mode 100644 index 0000000..083ce51 --- /dev/null +++ b/.npmignore @@ -0,0 +1,136 @@ +# Decrease NPM bunlde size +src_old/ +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 +dev.profile.json + +package-lock.json + +Cash.output.js + +ES6.output.js + +.vscode/ + +src/dev.ts + +.DS_Store + 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/CHANGELOG.md b/CHANGELOG.md index dc81546..61c7c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,105 @@ +# `2.0.0` +2.0 Rewrite 🎉 + +### 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 + +| Property | Type | Description | +| --- | --- | --- | +| `code` | `string` | The obfuscated code. | + +- 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](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://js-confuser.com/docs/options/renamelabels#rename-labels) + +- Added `Pack` [Learn more here](https://js-confuser.com/docs/options/pack#pack) + +- 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` + +- - Removes unused variables and functions + +- Improved `Moved Declaration` ability to move variables as unused function parameters + +- Improved `String` transforms + +- - [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) are now obfuscated (First converted into equivalent String Literal) + +- - [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 LZ-string compression ([lz-string](https://www.npmjs.com/package/lz-string)) + +- New Comment Syntax + +- - `/* @js-confuser-var */ "name"` for improved variable mappings, such as eval() calls + +```js +// Input +var name = "Internet User"; +eval( "console.log(" + /* @js-confuser-var */ "name" + ")" ); + +// Output +var zC3PLKu = "Internet User"; +eval("console.log(" + "zC3PLKu" + ")"); +``` + +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 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://js-confuser.com/docs/options/customlocks#custom-locks) instead + +- Removed `Shuffle`'s Hash option + +- Removed `Indent` option + + # `1.7.3` Tamper Protection @@ -30,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 new file mode 100644 index 0000000..0ddc63f --- /dev/null +++ b/Migration.md @@ -0,0 +1,73 @@ +## Migration guide to JS-Confuser 2.0 + +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 a property `code` which is the obfuscated code. + +```diff +const JSConfuser = require("js-confuser"); +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 + +These features have been removed but you can still add these locks using the `lock.customLocks` option. + +```js +{ + target: "node", + + // ... Your obfuscator settings ... + + lock: { + customLocks: [ + { + 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: 25, // Default = 25 - You probably don't want an excessive amount placed + minCount: 1 // Default = 1 - Ensures this custom lock is placed + } + ] + } +} +``` + +### Stack renamed to Variable Masking + +The option `stack` has been renamed to `variableMasking` + +[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 diff --git a/README.md b/README.md index 80704c9..23b41cb 100644 --- a/README.md +++ b/README.md @@ -11,27 +11,37 @@ JS-Confuser is a JavaScript obfuscation tool to make your programs _impossible_ - String concealing - Function obfuscation - Locks (domainLock, date) -- [Detect changes to source code](https://github.com/MichaelXF/js-confuser/blob/master/docs/Integrity.md) +- [Detect changes to source code](https://new--confuser.netlify.app/docs/options/integrity#integrity) -## Presets +## Documentation -JS-Confuser comes with three presets built into the obfuscator. +Get started in the [`JS-Confuser Docs`](https://js-confuser.com/docs/). -| 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) | +- [Getting Started](https://js-confuser.com/docs) -You can extend each preset or all go without them entirely. +- - [What is Obfuscation?](https://js-confuser.com/docs/getting-started/what-is-obfuscation) -## Installation +- - [Playground](https://js-confuser.com/docs/getting-started/playground) + +- - [Installation](https://js-confuser.com/docs/getting-started/installation) + +- - [Usage](https://js-confuser.com/docs/getting-started/usage) + +- - [FAQ](https://js-confuser.com/docs/getting-started/faq) + +- [Options](https://js-confuser.com/docs/options) + +- [Presets](https://js-confuser.com/docs/presets) + +## API Usage + +### Installation ```bash $ npm install js-confuser ``` -## Usage +### Usage ```js var JsConfuser = require("js-confuser"); @@ -54,891 +64,15 @@ JsConfuser.obfuscate(` target: "node", preset: "high", stringEncoding: false, // <- Normally enabled -}).then(obfuscated => { - console.log(obfuscated) +}).then(result => { + console.log(result.code) }) /* -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 ... */ ``` -## CLI Usage - -```shell - -``` - -## `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. - -```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 -} -``` - -## 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). ## Bug report @@ -948,38 +82,6 @@ Please [open an issue](https://github.com/MichaelXF/js-confuser/issues) with the 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. +## License -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. +MIT License \ No newline at end of file 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..13ec1a6 --- /dev/null +++ b/babel.register.js @@ -0,0 +1,7 @@ +require("@babel/register")({ + extensions: [".ts", ".tsx", ".js", ".jsx"], + // Automatically merges babel.config.js for us +}); + +// 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/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/index.d.ts b/index.d.ts index 44ae407..3ecbe5f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,19 +1,17 @@ -import TransformClass from "./src/transforms/transform"; -import ObfuscatorClass from "./src/obfuscator"; -import { - IJsConfuser as JsConfuser, - IJsConfuserDebugObfuscation, - IJsConfuserDebugTransformations, - IJsConfuserObfuscate, - IJsConfuserObfuscateAST, - IJsConfuserPresets, -} from "./src/types"; +// Export all the types from the index file +export * from "./src/index"; +export { default } from "./src/index"; -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; -export const Transform: typeof TransformClass; +// Export useful types +export type { + ObfuscateOptions, + ProbabilityMap, + CustomLock, + CustomStringEncoding, +} from "./src/options"; +export type { + ObfuscationResult, + ProfileData, + ProfilerCallback, + ProfilerLog, +} from "./src/obfuscationResult"; diff --git a/package.json b/package.json index 050d8fa..9c82450 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { "name": "js-confuser", - "version": "1.7.3", + "version": "2.0.0", "description": "JavaScript Obfuscation Tool.", "main": "dist/index.js", "types": "index.d.ts", "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" + "test:coverage": "jest --coverage --coverageReporters=html", + "prepublishOnly": "npm run test && npm run build" }, "keywords": [ "obfuscator", @@ -22,21 +22,28 @@ "author": "MichaelXF", "license": "MIT", "dependencies": { - "acorn": "^8.12.1", - "escodegen": "^2.1.0" + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/traverse": "^7.25.6", + "@babel/types": "^7.25.6", + "lz-string": "^1.5.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/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", + "@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", @@ -46,9 +53,7 @@ "url": "https://github.com/MichaelXF/js-confuser/issues" }, "homepage": "https://js-confuser.com", - "jest": { - "coverageReporters": [ - "html" - ] + "engines": { + "node": ">=18.0.0" } } 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 { + const 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); +) { + const obfuscator = new Obfuscator(options); - return frames; - }; + return obfuscator.obfuscateAST(ast); +} -/** - * 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, +export async function obfuscateWithProfiler( + sourceCode: string, options: ObfuscateOptions, - callback: (name: string, complete: number, totalTransforms: number) => void, - performance: Performance -) { + profiler: { + callback?: ProfilerCallback; + performance?: { now(): number }; + } = {} +): Promise { + if (!profiler.performance) { + profiler.performance = { + now: () => Date.now(), + }; + } + const startTime = performance.now(); - validateOptions(options); - options = await correctOptions(options); + const obfuscator = new Obfuscator(options); + let totalTransforms = obfuscator.plugins.length; + + let transformMap: ProfileData["transforms"] = Object.create(null); const beforeParseTime = performance.now(); - var tree = parseSync(code); + let ast = Obfuscator.parseCode(sourceCode); const parseTime = performance.now() - beforeParseTime; - var obfuscator = new Obfuscator(options as any); - var totalTransforms = obfuscator.array.length; + let currentTransformTime = performance.now(); + + ast = obfuscator.obfuscateAST(ast, { + profiler: (log: ProfilerLog) => { + var nowTime = performance.now(); + let entry = { + transformTime: nowTime - currentTransformTime, + changeData: {}, + }; - var transformationTimes = Object.create(null); - var currentTransformTime = performance.now(); + transformMap[log.currentTransform] = entry; - obfuscator.on("debug", (name: string, tree: Node, i: number) => { - var nowTime = performance.now(); - transformationTimes[name] = nowTime - currentTransformTime; - currentTransformTime = nowTime; + // (JS-Confuser.com can run performance benchmark tests here) + profiler.callback?.(log, entry, ast); - callback(name, i, totalTransforms); + // 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(); + }, }); - await obfuscator.apply(tree, true); + obfuscator.plugins.forEach(({ pluginInstance }) => { + if (transformMap[pluginInstance.name]) { + transformMap[pluginInstance.name].changeData = pluginInstance.changeData; + } + }); const beforeCompileTime = performance.now(); - var output = await compileJs(tree, options); + const code = Obfuscator.generateCode(ast, obfuscator.options); const compileTime = performance.now() - beforeCompileTime; const endTime = performance.now(); + const obfuscationTime = endTime - startTime; + return { - obfuscated: output, - transformationTimes: transformationTimes, - obfuscationTime: endTime - startTime, - parseTime: parseTime, - compileTime: compileTime, - totalTransforms: totalTransforms, - totalPossibleTransforms: obfuscator.totalPossibleTransforms, + code: code, + profileData: { + transforms: transformMap, + obfuscationTime: obfuscationTime, + 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; +const JsConfuser = { + obfuscate, + obfuscateAST, + obfuscateWithProfiler, + Obfuscator, + presets, + Template, +}; -export { presets, Obfuscator, Transform, Template }; +export default JsConfuser; +export { Obfuscator, presets, Template }; diff --git a/src/obfuscationResult.ts b/src/obfuscationResult.ts new file mode 100644 index 0000000..ad3b9de --- /dev/null +++ b/src/obfuscationResult.ts @@ -0,0 +1,49 @@ +import { PluginInstance } from "./transforms/plugin"; +import { File } from "@babel/types"; + +/** + * 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; + transforms: { + [transformName: string]: { + transformTime: number; + changeData: PluginInstance["changeData"]; + fileSize?: string; + }; + }; +} + +/** + * A callback function that is called when a transform is applied. + */ +export type ProfilerCallback = ( + log: ProfilerLog, + transformEntry?: object, + ast?: File +) => void; + +/** + * The current progress of the obfuscation process. + */ +export interface ProfilerLog { + index: number; + currentTransform: string; + nextTransform: string; + totalTransforms: number; +} diff --git a/src/obfuscator.ts b/src/obfuscator.ts index 995d19b..b44cf0a 100644 --- a/src/obfuscator.ts +++ b/src/obfuscator.ts @@ -1,165 +1,497 @@ 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 t from "@babel/types"; +import generate from "@babel/generator"; +import traverse from "@babel/traverse"; +import { parse } from "@babel/parser"; +import { ObfuscateOptions, ProbabilityMap } from "./options"; +import { applyDefaultsToOptions, validateOptions } from "./validateOptions"; +import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult"; +import { NameGen } from "./utils/NameGen"; +import { Order } from "./order"; +import { + PluginFunction, + PluginInstance, + PluginObject, +} from "./transforms/plugin"; +import { createObject } from "./utils/object-utils"; + +// 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 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 astScrambler from "./transforms/astScrambler"; +import calculator from "./transforms/calculator"; +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 controlFlowFlattening from "./transforms/controlFlowFlattening"; +import opaquePredicates from "./transforms/opaquePredicates"; +import minify from "./transforms/minify"; +import finalizer from "./transforms/finalizer"; +import integrity from "./transforms/lock/integrity"; +import pack, { PackInterface } from "./transforms/pack"; + +export const DEFAULT_OPTIONS: ObfuscateOptions = { + target: "node", + compact: true, +}; + +export default class Obfuscator { + plugins: { + plugin: PluginObject; + pluginInstance: PluginInstance; + }[] = []; + options: ObfuscateOptions; + + totalPossibleTransforms: number = 0; + + globalState = { + lock: { + integrity: { + sensitivityRegex: / |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g, + }, + + 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>(), + + // Internal functions, should not be renamed/removed + internals: { + stringCompressionLibraryName: "", + nativeFunctionName: "", + integrityHashName: "", + invokeCountermeasuresFnName: "", + }, + }; + + // Pack Interface for sharing globals across RGF functions + packInterface: PackInterface; + + isInternalVariable(name: string) { + return Object.values(this.globalState.internals).includes(name); + } + + shouldTransformNativeFunction(nameAndPropertyPath: string[]) { + if (!this.options.lock?.tamperProtection) { + return false; + } - // 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 + // Custom implementation for Tamper Protection + if (typeof this.options.lock.tamperProtection === "function") { + return this.options.lock.tamperProtection(nameAndPropertyPath.join(".")); + } if ( - options.lock && - Object.keys(options.lock).filter((x) => - x == "domainLock" - ? options.lock.domainLock && options.lock.domainLock.length - : options.lock[x] - ).length + this.options.target === "browser" && + nameAndPropertyPath.length === 1 && + nameAndPropertyPath[0] === "fetch" ) { - test(true, Lock); + 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(); } - // Make array - this.array = Object.values(this.transforms); + return this.globalState.internals.stringCompressionLibraryName; + } - // Sort transformations based on their priority - this.array.sort((a, b) => a.priority - b.priority); + getObfuscatedVariableName(originalName: string, programNode: t.Node) { + const renamedVariables = this.globalState.renamedVariables.get(programNode); + + return renamedVariables?.get(originalName) || originalName; } - push(transform: Transform) { - if (transform.className) { + /** + * The main Name Generator for `Rename Variables` + */ + nameGen: NameGen; + + 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" && + this.isProbabilityMapProbable(this.options.lock[key]) + ).length > 0 || + this.options.lock.customLocks.length > 0); + + const allPlugins: PluginFunction[] = []; + + const push = (probabilityMap, ...pluginFns) => { + this.totalPossibleTransforms += pluginFns.length; + if (!this.isProbabilityMapProbable(probabilityMap)) return; + + allPlugins.push(...pluginFns); + }; + + push(true, preparation); + push(this.options.objectExtraction, objectExtraction); + push(this.options.flatten, flatten); + push(shouldAddLockTransform, 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.opaquePredicates, opaquePredicates); + push(this.options.stringSplitting, stringSplitting); + push(this.options.stringConcealing, stringConcealing); + // 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); + push(this.options.movedDeclarations, movedDeclarations); + push(this.options.renameLabels, renameLabels); + push(this.options.minify, minify); + push(this.options.astScrambler, astScrambler); + push(this.options.renameVariables, renameVariables); + + push(true, finalizer); + push(this.options.pack, pack); + push(this.options.lock?.integrity, integrity); + + allPlugins.map((pluginFunction) => { + var pluginInstance: PluginInstance; + var plugin = pluginFunction({ + 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); + } + + pluginInstance = newPluginInstance; + + // @ts-ignore + return newPluginInstance as any; + }, + }); + ok( - !this.transforms[transform.className], - "Already have " + transform.className + pluginInstance, + "Plugin instance not created: " + pluginFunction.toString() ); + + this.plugins.push({ + plugin, + pluginInstance, + }); + }); + + 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); } - this.transforms[transform.className] = transform; } - resetState() { - this.varCount = 0; - this.generated = new Set(); - this.state = "transform"; + index: number = 0; + + obfuscateAST( + ast: babel.types.File, + options?: { + profiler?: ProfilerCallback; + } + ): 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]; + + if (this.options.verbose) { + console.log( + `Applying ${pluginInstance.name} (${i + 1}/${this.plugins.length})` + ); + } + + traverse(ast, plugin.visitor); + plugin.post?.(); + + if (plugin.finalASTHandler) { + finalASTHandler.push(plugin.finalASTHandler); + } + + if (options?.profiler) { + options?.profiler({ + index: i, + currentTransform: pluginInstance.name, + nextTransform: this.plugins[i + 1]?.pluginInstance?.name, + totalTransforms: this.plugins.length, + }); + } + } + + for (const handler of finalASTHandler) { + ast = handler(ast); + } + + return ast; + } + + async obfuscate(sourceCode: string): Promise { + // Parse the source code into an AST + let ast = Obfuscator.parseCode(sourceCode); + + ast = this.obfuscateAST(ast); + + // Generate the transformed code from the modified AST with comments removed and compacted output + const code = this.generateCode(ast); + + return { + code: code, + }; } - 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)); + getPlugin(order: Order) { + return this.plugins.find((x) => x.pluginInstance.order === order); + } - this.resetState(); + hasPlugin(order: Order) { + return !!this.getPlugin(order); + } - var completed = 0; - for (var transform of this.array) { - await transform.apply(tree); - completed++; + /** + * 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` + */ + static generateCode( + ast: T, + options: ObfuscateOptions = DEFAULT_OPTIONS + ): string { + const compact = !!options.compact; - if (debugMode) { - this.emit("debug", transform.className, tree, completed); + const { code } = generate(ast, { + comments: false, // Remove comments + minified: compact, + // jsescOption: { + // String Encoding using Babel + // escapeEverything: true, + // }, + }); + + return code; + } + + /** + * 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 = parse(sourceCode, { + sourceType: "unambiguous", + }); + + 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" && map && "value" in map) { + // Check for the limit property + 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; + } + } + + 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 (this.options.verbose) { - console.log("-> Check for Eval Callbacks"); + 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) + ); - this.state = "eval"; + var ticket = Math.random(); - // Find eval callbacks - traverse(tree, (o, p) => { - if (o.$eval) { - return () => { - o.$eval(o, p); - }; + 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; }); - if (this.options.verbose) { - console.log("<- Done"); + 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 a184a35..d46f8aa 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,264 +1,215 @@ -import { ok } from "assert"; -import presets from "./presets"; -import { ProbabilityMap } from "./probability"; +import Template from "./templates/template"; -export interface ObfuscateOptions { +// JS-Confuser.com imports this file for Type support, therefore some additional types are included here. + +type Stringed = V extends string ? V : never; + +/** + * 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 = boolean, + F extends (...args: any[]) => any = () => boolean // Default to a generic function +> = + | false + | true + | number + | F + | (T extends never | boolean + ? { + value: ProbabilityMap; + limit?: number; + } + : T | T[] | { [key in Stringed]?: number }); + +export interface CustomLock { /** - * ### `preset` + * Template lock code that must contain: * - * JS-Confuser comes with three presets built into the obfuscator. + * - `{countermeasures}` * - * | 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) | + * The countermeasures function will be invoked when the lock is triggered. * - * You can extend each preset or all go without them entirely. (`"high"/"medium"/"low"`) + * ```js + * if(window.navigator.userAgent.includes('Chrome')){ + * {countermeasures} + * } + * ``` * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * Multiple templates can be passed a string array, a random one will be chosen each time. */ - preset?: "high" | "medium" | "low" | false; + code: string | string[] | Template; + percentagePerBlock: number; + maxCount?: number; + minCount?: number; +} +export interface CustomStringEncoding { /** - * ### `target` + * Template string decoder that must contain: * - * The execution context for your output. _Required_. + * - `{fnName}` * - * 1. `"node"` - * 2. `"browser"` + * This function will be invoked by the obfuscated code to DECODE the string. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * ```js + * function {fnName}(str){ + * return Buffer.from(str, 'base64').toString('utf-8') + * } + * ``` */ - target: "node" | "browser"; + code?: string | Template; + encode: (str: string) => string; /** - * ### `indent` - * - * Controls the indentation of the output. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * Optional. A decoder function can be provided to ensure the string is able to decode properly. + * @param str + * @returns */ - indent?: 2 | 4 | "tabs"; + decode?: (str: string) => string; /** - * ### `compact` - * - * Remove's whitespace from the final output. (`true/false`) + * Should be used when created 'shuffled' variants of the same encoding. + */ + identity?: string; +} + +export interface ObfuscateOptions { + /** + * The preset to use for obfuscation. + */ + preset?: "high" | "medium" | "low" | false; + + /** + * The execution context for your output. _Required_. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * 1. `"node"` + * 2. `"browser"` + */ + target: "node" | "browser"; + + /** + * 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`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * Minifies redundant code. */ 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) + * Renames labeled statements. Enabled by default. */ - es5?: boolean; + renameLabels?: ProbabilityMap 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) + * Determines if variables should be renamed. */ - renameVariables?: ProbabilityMap; + renameVariables?: ProbabilityMap< + boolean, + (variableName: string, topLevel: boolean) => boolean + >; /** - * ### `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) + * 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. - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * 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 >; /** - * ### `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. - * - * - 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) + * Global Concealing hides global variables being accessed. */ - globalConcealing?: ProbabilityMap; + globalConcealing?: ProbabilityMap boolean>; /** - * ### `stringCompression` - * - * String Compression uses LZW's compression algorithm to compress strings. (`true/false/0-1`) + * String Compression uses zlib compression algorithm to compress strings. * * `"console"` -> `inflate('replaĕ!ğğuģģ<~@')` - * - * - Potency High - * - Resilience Medium - * - Cost Medium */ - stringCompression?: ProbabilityMap; + 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~>')` - * - * - Potency High - * - Resilience Medium - * - Cost Medium */ - stringConcealing?: ProbabilityMap; + 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'` - * - * - Potency Low - * - Resilience Low - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ - stringEncoding?: ProbabilityMap; + 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'` - * - * - Potency Medium - * - Resilience Medium - * - Cost Medium - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ - stringSplitting?: ProbabilityMap; + stringSplitting?: ProbabilityMap boolean>; /** - * ### `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) + * 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`) - * - * - Potency Medium - * - Resilience Medium - * - Cost High - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * Creates a middleman function to process function calls. */ - dispatcher?: ProbabilityMap; + 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. * @@ -274,21 +225,14 @@ 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; + rgf?: ProbabilityMap boolean>; /** - * ### `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){ @@ -302,16 +246,10 @@ export interface ObfuscateOptions { * }; * ``` */ - stack?: ProbabilityMap; + variableMasking?: ProbabilityMap boolean>; /** - * ### `objectExtraction` - * - * Extracts object properties into separate variables. (`true/false`) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low + * Extracts object properties into separate variables. * * ```js * // Input @@ -330,569 +268,140 @@ export interface ObfuscateOptions { * // ... * } * ``` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) */ - objectExtraction?: ProbabilityMap; + objectExtraction?: ProbabilityMap boolean>; /** - * ### `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) + * Declares functions at the top of the program, preserving their original scope. */ - flatten?: ProbabilityMap; + 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. - * - * - 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 + * Creates a calculator function to handle arithmetic and logical expressions. * - * [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) + * Adds `debugger` statements throughout the code. */ 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 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 all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * @see https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md */ - tamperProtection?: boolean | ((varName: string) => boolean); + tamperProtection?: ProbabilityMap 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) + * Array of regex strings that the `window.location.href` must follow. */ 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"` + * Integrity ensures the source code is unchanged. * - * Example: `["linux", "windows"]` - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * @see https://github.com/MichaelXF/js-confuser/blob/master/Integrity.md */ - osLock?: ("linux" | "windows" | "osx" | "android" | "ios")[] | false; + integrity?: ProbabilityMap boolean>; /** - * ### `lock.browserLock` - * Array of browsers where the script is allowed to run. (`string[]`) + * A custom callback function to invoke when a lock is triggered. (`string/false`) * - * - Potency Low - * - Resilience Medium - * - Cost Medium + * This could be due to an invalid domain, incorrect time, or code's integrity changed. * - * Allowed values: `"firefox"`, `"chrome"`, `"iexplorer"`, `"edge"`, `"safari"`, `"opera"` + * If no countermeasures function is provided (`undefined` or `true`), the obfuscator falls back to crashing the process. * - * Example: `["firefox", "chrome"]` + * If `countermeasures` is `false`, no crash will occur. * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * @see https://github.com/MichaelXF/js-confuser/blob/master/Countermeasures.md */ - browserLock?: - | ("firefox" | "chrome" | "iexplorer" | "edge" | "safari" | "opera")[] - | false; + countermeasures?: string | boolean; - /** - * ### `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; + customLocks?: CustomLock[]; /** - * ### `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) + * The default 'maxCount' for obfuscator and custom locks. Defaults to 25. */ - countermeasures?: string | boolean; + defaultMaxCount?: number; }; /** - * ### `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) + * 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`) - * - * - Potency Medium - * - Resilience Medium - * - Cost Low - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * 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`) - * - * - 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; + 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) + * Modified functions will retain the correct `function.length` property. Enabled by default. */ - verbose?: boolean; + preserveFunctionLength?: boolean; /** - * ### `globalVariables` - * - * Set of global variables. *Optional*. (`Set`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * Semantically changes the AST to bypass automated tools. */ - globalVariables?: Set; + astScrambler?: boolean; /** - * ### `debugComments` + * Packs the output code into a single `Function()` call. * - * Enable debug comments. (`true/false`) - * - * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options) + * Designed to escape strict mode constraints. */ - debugComments?: boolean; + pack?: ProbabilityMap 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) + * Set of global variables. *Optional*. */ - 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 + "'"); - } - } -} + globalVariables?: Set; -/** - * 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; + /** + * Enable logs to view the obfuscator's state. + */ + verbose?: boolean; } diff --git a/src/order.ts b/src/order.ts index 69e7aa4..a6726d3 100644 --- a/src/order.ts +++ b/src/order.ts @@ -1,16 +1,16 @@ /** * Describes the order of transformations. */ -export enum ObfuscateOrder { +export enum Order { Preparation = 0, ObjectExtraction = 1, Flatten = 2, - RGF = 3, + Lock = 3, - Lock = 4, // Includes Integrity & Anti Debug + RGF = 4, Dispatcher = 6, @@ -18,10 +18,6 @@ export enum ObfuscateOrder { Calculator = 9, - ControlFlowFlattening = 10, - - Eval = 11, - GlobalConcealing = 12, OpaquePredicates = 13, @@ -32,25 +28,27 @@ export enum ObfuscateOrder { StringCompression = 18, - Stack = 20, + VariableMasking = 20, DuplicateLiteralsRemoval = 22, - Shuffle = 24, + Shuffle = 23, - NameRecycling = 25, + ControlFlowFlattening = 24, - MovedDeclarations = 26, + MovedDeclarations = 25, RenameLabels = 27, Minify = 28, - AntiTooling = 29, + AstScrambler = 29, RenameVariables = 30, - ES5 = 31, - Finalizer = 35, + + Pack = 36, + + Integrity = 37, // Must run last } diff --git a/src/parser.ts b/src/parser.ts deleted file mode 100644 index 3e5b99e..0000000 --- a/src/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/precedence.ts b/src/precedence.ts deleted file mode 100644 index 4d729a2..0000000 --- a/src/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/presets.ts b/src/presets.ts index 2fb3081..9c15fc7 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, @@ -43,20 +33,33 @@ const highPreset: ObfuscateOptions = { opaquePredicates: 0.75, renameVariables: true, renameGlobals: true, - shuffle: { hash: 0.5, true: 0.5 }, - stack: true, + shuffle: true, + variableMasking: 0.75, stringConcealing: true, stringCompression: true, stringEncoding: true, stringSplitting: 0.75, + astScrambler: true, + + // Experimental + // 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", @@ -66,7 +69,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, @@ -74,18 +77,21 @@ const mediumPreset: ObfuscateOptions = { minify: true, movedDeclarations: true, objectExtraction: true, - opaquePredicates: 0.5, renameVariables: true, renameGlobals: true, shuffle: true, - stack: 0.5, + variableMasking: 0.5, 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", @@ -94,18 +100,17 @@ 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, + astScrambler: true, }; /** diff --git a/src/probability.ts b/src/probability.ts deleted file mode 100644 index 6463a27..0000000 --- a/src/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/templates/bufferToString.ts b/src/templates/bufferToString.ts deleted file mode 100644 index 7f0f03d..0000000 --- a/src/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/templates/bufferToStringTemplate.ts b/src/templates/bufferToStringTemplate.ts new file mode 100644 index 0000000..6750f31 --- /dev/null +++ b/src/templates/bufferToStringTemplate.ts @@ -0,0 +1,57 @@ +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; + var 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 {BufferToString}(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/core.ts b/src/templates/core.ts deleted file mode 100644 index a2b3069..0000000 --- a/src/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/templates/crash.ts b/src/templates/crash.ts deleted file mode 100644 index ec4f5df..0000000 --- a/src/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/templates/deadCodeTemplates.ts b/src/templates/deadCodeTemplates.ts new file mode 100644 index 0000000..fd62b90 --- /dev/null +++ b/src/templates/deadCodeTemplates.ts @@ -0,0 +1,1185 @@ +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; + + 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/es5.ts b/src/templates/es5.ts deleted file mode 100644 index dbabe83..0000000 --- a/src/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/templates/functionLength.ts b/src/templates/functionLength.ts deleted file mode 100644 index 6a00abc..0000000 --- a/src/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/templates/getGlobalTemplate.ts b/src/templates/getGlobalTemplate.ts new file mode 100644 index 0000000..fe55f1d --- /dev/null +++ b/src/templates/getGlobalTemplate.ts @@ -0,0 +1,76 @@ +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 && + !path.find((p) => isStrictMode(p)) + ) { + return new Template(` + function {getGlobalFnName}(){ + var localVar = false; + eval(__JS_CONFUSER_VAR__(localVar) + " = true") + if (!localVar) { + {countermeasures} + + return {}; + } + + const root = eval("this"); + return root; + } + `) + .addSymbols(UNSAFE) + .setDefaultVariables({ + countermeasures: + pluginInstance.globalState.lock.createCountermeasuresCode(), + }); + } + + 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/globals.ts b/src/templates/globals.ts deleted file mode 100644 index 343add6..0000000 --- a/src/templates/globals.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Template from "./template"; - -export const ObjectDefineProperty = new Template(`Object["defineProperty"]`); diff --git a/src/templates/integrityTemplate.ts b/src/templates/integrityTemplate.ts new file mode 100644 index 0000000..790cf46 --- /dev/null +++ b/src/templates/integrityTemplate.ts @@ -0,0 +1,64 @@ +import { MULTI_TRANSFORM, SKIP } from "../constants"; +import Template from "./template"; + +/** + * Hashing Algorithm for Integrity: `cyrb53` + * @param str + * @param seed + */ +export function HashFunction(str: string, seed: number) { + 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(` +// Must be Function Declaration for hoisting +// Math.imul polyfill for ES5 +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; + 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); +} +`).addSymbols(SKIP, MULTI_TRANSFORM); 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/templates/stringCompressionTemplate.ts b/src/templates/stringCompressionTemplate.ts new file mode 100644 index 0000000..883f8fe --- /dev/null +++ b/src/templates/stringCompressionTemplate.ts @@ -0,0 +1,20 @@ +import Template from "./template"; + +export const StringCompressionTemplate = new Template( + ` +var {stringFn}; + +(function (){ + var compressedString = {stringValue}; + var utf8String = {StringCompressionLibrary}["decompressFromUTF16"](compressedString); + var stringArray = utf8String["split"]({stringDelimiter}); + + {stringFn} = function(index){ + return stringArray[index]; + } +})(); +` +); + +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/templates/tamperProtectionTemplates.ts b/src/templates/tamperProtectionTemplates.ts new file mode 100644 index 0000000..78f2821 --- /dev/null +++ b/src/templates/tamperProtectionTemplates.ts @@ -0,0 +1,120 @@ +import { NodePath } from "@babel/traverse"; +import { PluginInstance } from "../transforms/plugin"; +import Template from "./template"; +import { + MULTI_TRANSFORM, + placeholderVariablePrefix, + 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}(${placeholderVariablePrefix}_flag = true){ + return ${placeholderVariablePrefix}_flag; + } + `); +}; diff --git a/src/templates/template.ts b/src/templates/template.ts index 6b8fd01..a4ea1e0 100644 --- a/src/templates/template.ts +++ b/src/templates/template.ts @@ -1,82 +1,33 @@ -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"; +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]: | 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; + private astVariableMappings: Map; + private astIdentifierPrefix = "__t_" + getRandomString(6); + private symbols = new Set(); constructor(...templates: string[]) { this.templates = templates; @@ -86,145 +37,188 @@ export default class Template { this.findRequiredVariables(); } + addSymbols(...symbols: NodeSymbolKeys[]): this { + symbols.forEach((symbol) => { + this.symbols.add(symbol); + }); + return this; + } + setDefaultVariables(defaultVariables: TemplateVariables): this { this.defaultVariables = defaultVariables; return this; } 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."); - } else { - this.requiredVariables.add(name); - } + 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) { + private interpolateTemplate(variables: TemplateVariables) { + const allVariables = { ...this.defaultVariables, ...variables }; + + 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; + + this.astVariableMappings = new Map(); 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") { - value = name; - } + if (this.isASTVariable(value)) { + let astIdentifierName = this.astIdentifierPrefix + name; + this.astVariableMappings.set(name, astIdentifierName); - var reg = new RegExp(bracketName, "g"); + value = astIdentifierName; + } + 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( - 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); - } + private isASTVariable(variable: any): boolean { + return typeof variable !== "string" && typeof variable !== "number"; + } + + private interpolateAST(ast: babelTypes.Node, variables: TemplateVariables) { + if (this.astVariableMappings.size === 0) return; + + const allVariables = { ...this.defaultVariables, ...variables }; + const template = this; - if (!Array.isArray(insertNodes)) { - // Replace with expression + // 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); + }); + + const insertedVariables = new Set(); - Object.assign(o, insertNodes); - } else { - // Insert multiple statements/declarations - var expressionStatement: Node = p[0]; - var body: Node[] = p[1] as any; + traverse(ast, { + Identifier(path: NodePath) { + const idName = path.node.name; + if (!idName.startsWith(template.astIdentifierPrefix)) return; - ok(expressionStatement.type === "ExpressionStatement"); - ok(Array.isArray(body)); + const variableName = reverseMappings.get(idName); + ok(variableName, `Variable ${idName} not found in mappings`); - var index = body.indexOf(expressionStatement); + let value = allVariables[variableName]; + let isSingleUse = true; // Hard-coded nodes are deemed 'single use' + if (typeof value === "function") { + value = value(); + isSingleUse = false; + } + + if (value instanceof Template) { + value = value.compile(allVariables); + isSingleUse = false; + } - body.splice(index, 1, ...insertNodes); + // 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 = {}): Node[] { - var { output, template } = this.interpolateTemplate(variables); + file(variables: TemplateVariables = {}): babelTypes.File { + const { output } = this.interpolateTemplate(variables); - var program: Node; + let file: babelTypes.File; try { - program = parseSnippet(output); + file = parse(output, { + sourceType: "module", + allowReturnOutsideFunction: true, + }); } 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(file, variables); + + if (this.symbols.size > 0) { + file.program.body.forEach((node) => { + for (const symbol of this.symbols) { + node[symbol] = true; + } + }); } - this.interpolateAST(program, variables); + return file; + } + + compile(variables: TemplateVariables = {}): babelTypes.Statement[] { + const file = this.file(variables); - return program.body; + return file.program.body; } - single(variables: TemplateVariables = {}): Node { - var nodes = this.compile(variables); + single(variables: TemplateVariables = {}): T { + 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] as T; } - return nodes[0]; + return nodes[0] as T; + } + + expression( + variables: TemplateVariables = {} + ): T { + const statement = this.single(variables); + + babelTypes.assertExpressionStatement(statement); + + return statement.expression as T; } } diff --git a/src/transforms/antiTooling.ts b/src/transforms/antiTooling.ts deleted file mode 100644 index 9212f4f..0000000 --- a/src/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/transforms/astScrambler.ts b/src/transforms/astScrambler.ts new file mode 100644 index 0000000..565f38a --- /dev/null +++ b/src/transforms/astScrambler.ts @@ -0,0 +1,99 @@ +import { NodePath } from "@babel/traverse"; +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): PluginObject => { + const me = Plugin(Order.AstScrambler, { + changeData: { + expressions: 0, + }, + }); + var callExprName: string; + + return { + visitor: { + "Block|SwitchCase": { + exit(_path) { + const path = _path as NodePath; + const isProgram = path.isProgram(); + + 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"; + } + + me.changeData.expressions += expressions.length; + + newContainer.push( + t.expressionStatement( + t.callExpression(t.identifier(callExprName), expressions) + ) + ); + expressions = []; + }; + + 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] + ) { + 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 = new Template(` + function ${callExprName}(){ + ${callExprName} = function(){}; + } + `).single(); + + append(path, functionDeclaration); + } + } + }, + }, + }, + }; +}; diff --git a/src/transforms/calculator.ts b/src/transforms/calculator.ts index e05a2de..68446cd 100644 --- a/src/transforms/calculator.ts +++ b/src/transforms/calculator.ts @@ -1,229 +1,99 @@ -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 { PluginArg, PluginObject } from "./plugin"; +import * as t from "@babel/types"; +import { Order } 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) +import { NameGen } from "../utils/NameGen"; +import { prependProgram } from "../utils/ast-utils"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Calculator, { + changeData: { + expressions: 0, + }, + }); + + const nameGen = new NameGen(me.options.identifierGenerator); + + return { + visitor: { + Program: { + exit(programPath) { + const allowedBinaryOperators = new Set(["+", "-", "*", "/"]); + var operatorsMap = new Map(); + var calculatorFnName = me.getPlaceholder() + "_calc"; + + 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; + let operatorKey = operatorsMap.get(mapKey); + + // Add binary operator to the map if it doesn't exist + if (typeof operatorKey === "undefined") { + operatorKey = nameGen.generate(); + operatorsMap.set(mapKey, operatorKey); + } + + ok(operatorKey); + + me.changeData.expressions++; + + path.replaceWith( + t.callExpression(t.identifier(calculatorFnName), [ + t.stringLiteral(operatorKey), + path.node.left, + path.node.right, + ]) + ); + }, + }, + }); + + // 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.binaryExpression( + operator as any, + t.identifier("a"), + t.identifier("b") + ); + + return t.switchCase(t.stringLiteral(key), [ + t.returnStatement(expression), + ]); + }); + + prependProgram( + programPath, + + t.functionDeclaration( + t.identifier(calculatorFnName), + [t.identifier("operator"), t.identifier("a"), t.identifier("b")], + t.blockStatement([ + t.switchStatement(t.identifier("operator"), switchCases), + ]) ) - ), - ]; - } 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/transforms/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening.ts new file mode 100644 index 0000000..6822f01 --- /dev/null +++ b/src/transforms/controlFlowFlattening.ts @@ -0,0 +1,1680 @@ +import traverse, { NodePath, Scope, Visitor } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; +import { Order } from "../order"; +import { + ensureComputedExpression, + getParentFunctionOrProgram, + isDefiningIdentifier, + isStrictMode, + isVariableIdentifier, + replaceDefiningIdentifierToMemberExpression, +} from "../utils/ast-utils"; +import * as t from "@babel/types"; +import { numericLiteral, deepClone } from "../utils/node"; +import Template from "../templates/template"; +import { + chance, + choice, + getRandomInteger, + shuffle, +} from "../utils/random-utils"; +import { IntGen } from "../utils/IntGen"; +import { ok } from "assert"; +import { NameGen } from "../utils/NameGen"; +import { + NodeSymbol, + UNSAFE, + NO_RENAME, + PREDICTABLE, + variableFunctionName, + WITH_STATEMENT, +} from "../constants"; + +// Function deemed unsafe for CFF +const CFF_UNSAFE = Symbol("CFF_UNSAFE"); + +/** + * 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 ({ 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; + 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 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 + const addGeneratorFunction = true; // Wrap in generator function? + + const cffPrefix = me.getPlaceholder(); + + // Amount of blocks changed by Control Flow Flattening + let cffCounter = 0; + + const functionsModified: t.Node[] = []; + + function flagFunctionToAvoid(path: NodePath, reason: string) { + var fnOrProgram = getParentFunctionOrProgram(path); + + fnOrProgram.node[CFF_UNSAFE] = reason; + } + + return { + post: () => { + for (var node of functionsModified) { + (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; + + // Exclude loops + if ( + programOrFunctionPath.find((p) => p.isForStatement() || p.isWhile()) + ) + return; + + // Exclude 'CFF_UNSAFE' functions + if (programOrFunctionPath.node[CFF_UNSAFE]) return; + + let programPath = _path.isProgram() ? _path : null; + let functionPath = _path.isFunction() ? _path : null; + + let blockPath: NodePath; + if (programPath) { + blockPath = programPath; + } else { + let fnBlockPath = functionPath.get("body"); + if (!fnBlockPath.isBlock()) return; + 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; + + // Check user's threshold setting + if (!me.computeProbabilityMap(me.options.controlFlowFlattening)) { + return; + } + + if (functionPath) { + // Avoid unsafe functions + if ((functionPath.node as NodeSymbol)[UNSAFE]) return; + + if (functionPath.node.async || functionPath.node.generator) return; + } + programOrFunctionPath.scope.crawl(); + + let hasIllegalNode = false; + const bindingNames = new Set(); + blockPath.traverse({ + Identifier(path) { + if (!path.isBindingIdentifier()) return; + const binding = path.scope.getBinding(path.node.name); + if (!binding) return; + + let fnParent = path.getFunctionParent(); + if ( + path.key === "id" && + path.parentPath.isFunctionDeclaration() + ) { + fnParent = path.parentPath.getFunctionParent(); + } + + if (fnParent !== functionPath) return; + + if (!isDefiningIdentifier(path)) { + return; + } + + if (bindingNames.has(path.node.name)) { + hasIllegalNode = true; + path.stop(); + return; + } + bindingNames.add(path.node.name); + }, + }); + + if (hasIllegalNode) { + return; + } + + me.changeData.blocks++; + + // Limit how many numbers get entangled + let mangledLiteralsCreated = 0; + + const cffIndex = ++cffCounter; // Start from 1 + const prefix = cffPrefix + "_" + cffIndex; + + const withIdentifier = (suffix) => { + var name; + if (isDebug) { + name = prefix + "_" + suffix; + } else { + name = me.obfuscator.nameGen.generate(false); + } + + var id = t.identifier(name); + + (id as NodeSymbol)[NO_RENAME] = cffIndex; + return id; + }; + + const mainFnName = withIdentifier("main"); + + const scopeVar = withIdentifier("scope"); + + const stateVars = new Array(isDebug ? 1 : getRandomInteger(2, 5)) + .fill("") + .map((_, i) => withIdentifier(`state_${i}`)); + + const argVar = withIdentifier("_arg"); + let usedArgVar = false; + + const didReturnVar = withIdentifier("return"); + + const basicBlocks = new Map(); + + // Map labels to states + const stateIntGen = new IntGen(); + + const defaultBlockPath = blockPath; + + let scopeCounter = 0; + + let scopeNameGen = new NameGen(me.options.identifierGenerator); + if (!isDebug) { + scopeNameGen = me.obfuscator.nameGen; + } + + // 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; + + class ScopeManager { + isNotUsed = true; + requiresInitializing = true; + + nameMap = new Map(); + nameGen = addWithStatement + ? me.obfuscator.nameGen + : 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 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); + } + + getNewName(name: string, originalNode?: t.Node) { + if (!this.nameMap.has(name)) { + let newName = this.nameGen.generate(false); + if (isDebug) { + newName = "_" + name; + } + + // console.log(name, newName); + + this.nameMap.set(name, newName); + + me.changeData.variables++; + + // console.log( + // "Renaming " + + // name + + // " to " + + // newName + + // " : " + + // this.scope.path.type + // ); + + return newName; + } + return this.nameMap.get(name); + } + + getScopeObject() { + return t.memberExpression( + 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() + : new Template(`({})`).expression(); + } + + getMemberExpression( + name: string, + object: t.Expression = this.getScopeObject() + ) { + const memberExpression = t.memberExpression( + object, + t.stringLiteral(name), + true + ); + + return memberExpression; + } + + propertyName: string; + constructor( + public scope: Scope, + public initializingBasicBlock: BasicBlock + ) { + this.propertyName = isDebug + ? "_" + cffIndex + "_" + scopeCounter++ + : scopeNameGen.generate(); + } + + get parent() { + return scopeToScopeManager.get(this.scope.parent); + } + + getObjectExpression(refreshLabel: string) { + let refreshScope = basicBlocks.get(refreshLabel).scopeManager; + let propertyMap: { [property: string]: t.Expression } = {}; + + let cursor = this.scope; + while (cursor) { + let parentScopeManager = scopeToScopeManager.get(cursor); + if (parentScopeManager) { + propertyMap[parentScopeManager.propertyName] = + t.memberExpression( + deepClone(scopeVar), + t.stringLiteral(parentScopeManager.propertyName), + true + ); + } + + cursor = cursor.parent; + } + + propertyMap[refreshScope.propertyName] = + refreshScope.getInitializingObjectExpression(); + + const properties: t.ObjectProperty[] = []; + for (const key in propertyMap) { + properties.push( + t.objectProperty(t.stringLiteral(key), propertyMap[key], true) + ); + } + + return t.objectExpression(properties); + } + + 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. + */ + class BasicBlock { + totalState: number; + stateValues: number[]; + allowWithDiscriminant = true; + bestWithDiscriminant: ScopeManager; + + get withDiscriminant() { + if (!this.allowWithDiscriminant) return; + + return this.bestWithDiscriminant; + } + + 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; + } + + 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: ScopeManager) { + if ( + this.withDiscriminant && + this.withDiscriminant === scopeManager + ) { + var id = t.identifier(identifierName); + (id as NodeSymbol)[NO_RENAME] = cffIndex; + (id as NodeSymbol)[WITH_STATEMENT] = true; + return id; + } + + return scopeManager.getMemberExpression(identifierName); + } + + initializedScope: ScopeManager; + + constructor( + public label: string, + public parentPath: NodePath, + public options: { impossible?: boolean } = {} + ) { + this.createPath(); + + if (isDebug) { + // States in debug mode are just 1, 2, 3, ... + this.totalState = basicBlocks.size + 1; + } else { + this.totalState = stateIntGen.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); + + // Create a new scope manager if it doesn't exist + if (!scopeToScopeManager.has(this.scope)) { + scopeToScopeManager.set( + this.scope, + new ScopeManager(this.scope, this) + ); + } + + this.initializedScope = this.scopeManager; + } + } + + /** + * 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)); + }; + + const startLabel = me.getPlaceholder(); + const endLabel = me.getPlaceholder(); + + let currentBasicBlock = new BasicBlock(startLabel, blockPath); + currentBasicBlock.allowWithDiscriminant = false; + + const gotoFunctionName = + "GOTO__" + + me.getPlaceholder() + + "__IF_YOU_CAN_READ_THIS_THERE_IS_A_BUG"; + + function GotoControlStatement(label: string) { + return new Template(` + ${gotoFunctionName}("${label}"); + `).single(); + } + + // Ends the current block and starts a new one + function endCurrentBasicBlock({ + jumpToNext = true, + nextLabel = me.getPlaceholder(), + prevJumpTo = null, + nextBlockPath = null, + } = {}) { + ok(nextBlockPath); + + if (prevJumpTo) { + currentBasicBlock.insertAfter(GotoControlStatement(prevJumpTo)); + } else if (jumpToNext) { + currentBasicBlock.insertAfter(GotoControlStatement(nextLabel)); + } + + currentBasicBlock = new BasicBlock(nextLabel, nextBlockPath); + } + + const prependNodes: t.Statement[] = []; + const functionExpressions: [ + string, + string, + BasicBlock, + t.FunctionExpression + ][] = []; + + function flattenIntoBasicBlocks( + bodyIn: NodePath[] | NodePath + ) { + // 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 body) { + const statement = body[index]; + + // Keep Imports before everything else + if (statement.isImportDeclaration()) { + prependNodes.push(statement.node); + continue; + } + + if (statement.isFunctionDeclaration()) { + const fnName = statement.node.id.name; + let isIllegal = false; + + if ( + !flattenFunctionDeclarations || + statement.node.async || + statement.node.generator || + (statement.node as NodeSymbol)[UNSAFE] || + (statement.node as NodeSymbol)[CFF_UNSAFE] || + isStrictMode(statement) + ) { + isIllegal = true; + } + let oldBasicBlock = currentBasicBlock; + let fnLabel = me.getPlaceholder(); + + let sm = currentBasicBlock.scopeManager; + let rename = sm.getNewName(fnName); + + sm.scope.bindings[fnName].kind = "var"; + + const hoistedBasicBlock = Array.from(basicBlocks.values()).find( + (block) => block.parentPath === currentBasicBlock.parentPath + ); + + if (isIllegal) { + hoistedBasicBlock.body.unshift(statement.node); + continue; + } + + me.changeData.functions++; + + const functionExpression = t.functionExpression( + null, + [], + t.blockStatement([]) + ); + functionExpressions.push([ + fnName, + fnLabel, + currentBasicBlock, + functionExpression, + ]); + + // Change the function declaration to a variable declaration + hoistedBasicBlock.body.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(fnName), + functionExpression + ), + ]) + ); + + const blockStatement = statement.get("body"); + + endCurrentBasicBlock({ + nextLabel: fnLabel, + nextBlockPath: blockStatement, + jumpToNext: false, + }); + let fnTopBlock = currentBasicBlock; + + // Implicit return + blockStatement.node.body.push( + t.returnStatement(t.identifier("undefined")) + ); + + flattenIntoBasicBlocks(blockStatement); + scopeToScopeManager.get(statement.scope).requiresInitializing = + false; + basicBlocks.get(fnLabel).allowWithDiscriminant = false; + + // Debug label + if (isDebug) { + fnTopBlock.body.unshift( + t.expressionStatement( + t.stringLiteral( + "Function " + + statement.node.id.name + + " -> Renamed to " + + rename + ) + ) + ); + } + + // Unpack parameters from the parameter 'argVar' + if (statement.node.params.length > 0) { + usedArgVar = true; + fnTopBlock.body.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern(statement.node.params), + deepClone(argVar) + ), + ]) + ); + + // Change bindings from 'param' to 'var' + statement.get("params").forEach((param) => { + let 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 (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()) + ) { + me.changeData.ifStatements++; + + 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 oldBasicBlock = currentBasicBlock; + + endCurrentBasicBlock({ + jumpToNext: false, + nextLabel: consequentLabel, + nextBlockPath: consequent, + }); + + flattenIntoBasicBlocks(consequent); + currentBasicBlock.initializedScope = + oldBasicBlock.scopeManager; + + if (alternate.isBlockStatement()) { + endCurrentBasicBlock({ + prevJumpTo: afterPath, + nextLabel: alternateLabel, + nextBlockPath: alternate, + }); + + flattenIntoBasicBlocks(alternate); + } + + endCurrentBasicBlock({ + prevJumpTo: afterPath, + nextLabel: afterPath, + nextBlockPath: oldBasicBlock.parentPath, + }); + + continue; + } + } + + if ( + Number(index) === body.length - 1 && + statement.isExpressionStatement() && + statement.findParent((p) => p.isBlock()) === blockPath + ) { + // Return the result of the last expression for eval() purposes + currentBasicBlock.insertAfter( + t.returnStatement(statement.get("expression").node) + ); + continue; + } + + // 3 or more statements should be split more + if ( + currentBasicBlock.body.length > 1 && + chance(50 + currentBasicBlock.body.length) + ) { + endCurrentBasicBlock({ + nextBlockPath: nextBlockPath, + }); + } + + // console.log(currentBasicBlock.thisPath.type); + // console.log(currentBasicBlock.body); + currentBasicBlock.body.push(statement.node); + } + } + + // Convert our code into Basic Blocks + flattenIntoBasicBlocks(blockPath.get("body")); + + // Ensure always jumped to the Program end + endCurrentBasicBlock({ + jumpToNext: true, + nextLabel: endLabel, + nextBlockPath: defaultBlockPath, + }); + + basicBlocks.get(endLabel).allowWithDiscriminant = false; + + 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, { + 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(30 - basicBlocks.size)) { + let randomLabel = choice(Array.from(basicBlocks.keys())); + + // The `false` literal will be mangled + basicBlock.insertAfter( + new Template(` + if({predicate}){ + {goto} + } + `).single({ + goto: GotoControlStatement(randomLabel), + predicate: basicBlock.createFalsePredicate(), + }) + ); + + me.changeData.deadCode++; + } + }); + // 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, + { + impossible: true, + } + ); + + randomChunk.thisNode.body + .map((x) => deepClone(x)) + .forEach((node) => { + 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?.findBestWithDiscriminant(basicBlock); + + if (isDebug && basicBlock.withDiscriminant) { + basicBlock.body.unshift( + t.expressionStatement( + t.stringLiteral( + "With " + basicBlock.withDiscriminant.propertyName + ) + ) + ); + } + } + + /** + * 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; + // Wrap the statement in a Babel path to allow traversal + + const outerFn = getParentFunctionOrProgram(basicBlock.parentPath); + + function isWithinSameFunction(path: NodePath) { + let fn = getParentFunctionOrProgram(path); + return fn.node === outerFn.node; + } + + let visitor: Visitor = { + BooleanLiteral: { + exit(boolPath) { + // Don't mangle booleans in debug mode + if ( + isDebug || + !mangleBooleanLiterals || + me.isSkipped(boolPath) + ) + return; + + if (!isWithinSameFunction(boolPath)) return; + if (chance(50 + mangledLiteralsCreated)) return; + + mangledLiteralsCreated++; + + const index = getRandomInteger(0, stateVars.length - 1); + const stateVar = stateVars[index]; + const stateVarValue = currentStateValues[index]; + + const compareValue = choice([ + getRandomInteger(-250, 250), + stateVarValue, + ]); + const compareResult = stateVarValue === compareValue; + + const newExpression = t.binaryExpression( + boolPath.node.value === compareResult ? "==" : "!=", + deepClone(stateVar), + 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 || + !mangleNumericalLiterals || + me.isSkipped(numPath) + ) + return; + + const num = numPath.node.value; + if ( + Math.floor(num) !== num || + Math.abs(num) > 100_000 || + !Number.isFinite(num) || + Number.isNaN(num) + ) + return; + + if (!isWithinSameFunction(numPath)) return; + if (chance(50 + mangledLiteralsCreated)) return; + + mangledLiteralsCreated++; + + const index = getRandomInteger(0, stateVars.length - 1); + const stateVar = stateVars[index]; + + // num = 50 + // stateVar = 30 + // stateVar + 30 + + const diff = t.binaryExpression( + "+", + deepClone(stateVar), + me.skip(numericLiteral(num - currentStateValues[index])) + ); + + ensureComputedExpression(numPath); + + numPath.replaceWith(diff); + numPath.skip(); + }, + }, + + Identifier: { + exit(path: NodePath) { + 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; + + var binding = path.scope.getBinding(identifierName); + if (!binding) { + return; + } + + if ( + binding.kind === "var" || + binding.kind === "let" || + binding.kind === "const" + ) { + } else { + return; + } + + var scopeManager = scopeToScopeManager.get(binding.scope); + if (!scopeManager) return; + + let newName = scopeManager.getNewName( + identifierName, + path.node + ); + + let memberExpression: t.MemberExpression | t.Identifier = + scopeManager.getMemberExpression(newName); + + 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, + memberExpression + ); + return; + } + + if (!path.container) return; + + if ( + basicBlock.withDiscriminant && + basicBlock.withDiscriminant === scopeManager && + basicBlock.withDiscriminant.hasOwnName(identifierName) + ) { + // 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 + ); + } + } + + me.skip(memberExpression); + + 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]) + ); + } + }, + }, + + // 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; + + const returnArgument = + path.node.argument || t.identifier("undefined"); + + path.node.argument = new Template(` + ({didReturnVar} = true, {returnArgument}) + `).expression({ + returnArgument, + didReturnVar: deepClone(didReturnVar), + }); + }, + }, + + // goto() calls are replaced with state updates and break statements + CallExpression: { + exit(path) { + if ( + t.isIdentifier(path.node.callee) && + path.node.callee.name === gotoFunctionName + ) { + const [labelNode] = path.node.arguments; + + ok(t.isStringLiteral(labelNode)); + const label = labelNode.value; + + const jumpBlock = basicBlocks.get(label); + ok(jumpBlock, "Label not found: " + label); + + const { + stateValues: newStateValues, + totalState: newTotalState, + } = jumpBlock; + + const assignments: t.Expression[] = []; + + if (jumpBlock.withDiscriminant) { + assignments.push( + t.assignmentExpression( + "=", + deepClone(withMemberExpression), + jumpBlock.withDiscriminant.getScopeObject() + ) + ); + } else if (basicBlock.withDiscriminant) { + // Reset the with discriminant to undefined using fake property + // scope["fake"] -> undefined + + const fakeProperty = scopeNameGen.generate(); + + assignments.push( + t.assignmentExpression( + "=", + deepClone(withMemberExpression), + t.memberExpression( + deepClone(scopeVar), + t.stringLiteral(fakeProperty), + true + ) + ) + ); + } + + 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 + + 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 - oldValue) + ); + } + + assignments.push(assignment); + } + + // Add debug label + if (isDebug) { + assignments.unshift( + t.stringLiteral("Goto " + newTotalState) + ); + } + + path.parentPath + .replaceWith( + t.expressionStatement(t.sequenceExpression(assignments)) + )[0] + .skip(); + + // Add break after updating state variables + path.insertAfter(breakStatement()); + } + }, + }, + }; + + 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(); + const predicateNumberCount = + isDebug || !addPredicateTests ? 0 : getRandomInteger(1, 4); + + for (let i = 0; i < predicateNumberCount; i++) { + const name = mainScope.getNewName( + me.getPlaceholder("predicate_" + i) + ); + + const number = getRandomInteger(-250, 250); + predicateNumbers.set(name, 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)) + ), + }); + + exprStmt[predicateSymbol] = true; + + 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)); + } + } + + // 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) { + shuffle(blocks); + } + for (const block of blocks) { + if (block.label === endLabel) { + // ok(block.body.length === 0); + continue; + } + + 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(50)) { + // Create complex test expressions for each switch case + + // case STATE+X: + let stateVarIndex = getRandomInteger(0, stateVars.length); + + let stateValues = block.stateValues; + let difference = stateValues[stateVarIndex] - block.totalState; + + 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) { + let labelStates = basicBlocks.get(label).stateValues; + let totalState = labelStates.reduce((a, b) => a + b, 0); + + if (totalState === labelStates[stateVarIndex] - difference) { + let differentIndex = labelStates.findIndex( + (v, i) => v !== stateValues[i] + ); + if (differentIndex !== -1) { + let expressionAsString = + stateVars[differentIndex].name + + "!=" + + labelStates[differentIndex]; + if (!alreadyConditionedItems.has(expressionAsString)) { + alreadyConditionedItems.add(expressionAsString); + + conditionNodes.push( + t.binaryExpression( + "!=", + deepClone(stateVars[differentIndex]), + numericLiteral(labelStates[differentIndex]) + ) + ); + } + } else { + conditionNodes.push( + t.binaryExpression( + "!=", + deepClone(discriminant), + numericLiteral(totalState) + ) + ); + } + } + } + }); + + // case STATE!=Y && STATE+X + test = t.binaryExpression( + "-", + deepClone(stateVars[stateVarIndex]), + 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 && chance(50)) { + // Add fake tests + let fakeTestCount = getRandomInteger(1, 3); + for (let i = 0; i < fakeTestCount; i++) { + tests.push(numericLiteral(stateIntGen.generate())); + } + + shuffle(tests); + } + + const lastTest = tests.pop(); + + for (const test of tests) { + switchCases.push(t.switchCase(test, [])); + } + + switchCases.push(t.switchCase(lastTest, block.thisPath.node.body)); + } + + if (!isDebug && addFakeTests) { + // A random test can be 'default' + choice(switchCases).test = null; + } + + const discriminant = new Template(` + ${stateVars.map((x) => x.name).join(" + ")} + `).expression(); + + traverse(t.program([t.expressionStatement(discriminant)]), { + Identifier(path) { + (path.node as NodeSymbol)[NO_RENAME] = cffIndex; + }, + }); + + // 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( + "!==", + deepClone(discriminant), + numericLiteral(endTotalState) + ), + t.blockStatement([ + t.withStatement( + new Template(`{withDiscriminant} || {scopeVar}`).expression({ + withDiscriminant: deepClone(withMemberExpression), + scopeVar: deepClone(scopeVar), + }), + t.blockStatement([switchStatement]) + ), + ]) + ); + + const parameters: t.Identifier[] = [ + ...stateVars, + scopeVar, + argVar, + ].map((id) => deepClone(id)); + + const parametersNames: string[] = parameters.map((id) => id.name); + + for (var [ + originalFnName, + fnLabel, + basicBlock, + fn, + ] of functionExpressions) { + const { scopeManager } = basicBlock; + const { stateValues } = basicBlocks.get(fnLabel); + + const argumentsRestName = me.getPlaceholder(); + + const argumentsNodes = []; + for (const parameterName of parametersNames) { + 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.name) { + argumentsNodes.push(scopeManager.getObjectExpression(fnLabel)); + } else { + ok(false); + } + } + + // Ensure parameter is added (No effect if not added in this case) + usedArgVar = true; + + Object.assign( + fn, + new Template(` + (function (...${argumentsRestName}){ + ${ + isDebug + ? `"Calling ${originalFnName}, Label: ${fnLabel}";` + : "" + } + return {callExpression} + }) + + `).expression({ + callExpression: createCallExpression(argumentsNodes), + }) + ); + } + + const startProgramObjectExpression = basicBlocks + .get(startLabel) + .scopeManager.getObjectExpression(startLabel); + + const mainParameters: t.FunctionDeclaration["params"] = parameters; + + // 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]), + addGeneratorFunction + ); + + // The main function is always called with same number of arguments + (mainFnDeclaration as NodeSymbol)[PREDICTABLE] = true; + + 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"); + + const isTopLevel = blockPath.isProgram(); + const allowReturns = + !isTopLevel && blockPath.find((p) => p.isFunction()); + + const startPrefix = allowReturns ? `var {resultVar} = ` : ""; + + const startProgramStatements = new Template(` + ${allowReturns ? `var {didReturnVar};` : ""} + ${startPrefix}{startProgramExpression}; + ${ + allowReturns + ? ` + if({didReturnVar}){ + return {resultVar}; + }` + : "" + } + `).compile({ + startProgramExpression, + didReturnVar: () => deepClone(didReturnVar), + resultVar: () => deepClone(resultVar), + }); + + blockPath.node.body = [ + ...prependNodes, + mainFnDeclaration, + ...startProgramStatements, + ]; + + functionsModified.push(programOrFunctionPath.node); + + // 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/controlFlowFlattening/controlFlowFlattening.ts b/src/transforms/controlFlowFlattening/controlFlowFlattening.ts deleted file mode 100644 index 7da1bf5..0000000 --- a/src/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/transforms/controlFlowFlattening/expressionObfuscation.ts b/src/transforms/controlFlowFlattening/expressionObfuscation.ts deleted file mode 100644 index 4683ae5..0000000 --- a/src/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/transforms/deadCode.ts b/src/transforms/deadCode.ts index ad456e3..5524add 100644 --- a/src/transforms/deadCode.ts +++ b/src/transforms/deadCode.ts @@ -1,676 +1,82 @@ -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 { PluginArg, PluginObject } from "./plugin"; +import { choice } from "../utils/random-utils"; +import { deadCodeTemplates } from "../templates/deadCodeTemplates"; +import { Order } from "../order"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { prepend } from "../utils/ast-utils"; +import PredicateGen from "../utils/PredicateGen"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.DeadCode, { + changeData: { + deadCode: 0, + }, + }); + let predicateGen = new PredicateGen(me); + + return { + visitor: { + Block: { + exit(blockPath) { + if (blockPath.find((p) => me.isSkipped(p))) return; + + if (!me.computeProbabilityMap(me.options.deadCode)) { + return; + } + + // 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 (me.changeData.deadCode >= suggestedMax) { + return; + } + } + + // Increment dead code counter + me.changeData.deadCode++; + + var template = choice(deadCodeTemplates); + var nodes = template.compile(); + + var containingFnName = me.getPlaceholder( + "dead_" + me.changeData.deadCode + ); + + // Insert dummy function + prepend( + blockPath, + + t.functionDeclaration( + t.identifier(containingFnName), + [], + t.blockStatement([...nodes]) + ) + ); + + prepend( + blockPath, + new Template(` + if({falsePredicate}) { + ${containingFnName}() + } + `).single({ + falsePredicate: predicateGen.generateFalseExpression(blockPath), + }) + ); + + me.skip(blockPath); + }, + }, + }, + }; +}; diff --git a/src/transforms/dispatcher.ts b/src/transforms/dispatcher.ts index b237777..e5f1053 100644 --- a/src/transforms/dispatcher.ts +++ b/src/transforms/dispatcher.ts @@ -1,640 +1,450 @@ -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 { PluginArg, PluginObject } 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 { Order } from "../order"; +import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; +import { + computeFunctionLength, + isVariableFunctionIdentifier, +} from "../utils/function-utils"; +import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; +import { numericLiteral } from "../utils/node"; +import { + isStrictMode, + isVariableIdentifier, + prependProgram, +} from "../utils/ast-utils"; + +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": { + exit(_path) { + 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; + + // For testing + // 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: + // - 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({ + // 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; + // 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; + } + + // Do not apply to functions in nested scopes + if (path.parentPath !== blockStatement || me.isSkipped(path)) { + illegalNames.add(name); + 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) || + me.obfuscator.isInternalVariable(name) + ) { + illegalNames.add(name); + 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 (hasAssignmentPattern) { + illegalNames.add(name); + return; + } + + functionPaths.set(name, path); + }, + }, + }); + + for (let name of illegalNames) { + functionPaths.delete(name); + } + + for (var name of functionPaths.keys()) { + if (!me.computeProbabilityMap(me.options.dispatcher, name)) { + functionPaths.delete(name); + } + } + + // No functions here to change + if (functionPaths.size === 0) { + return; + } + + me.changeData.functions += functionPaths.size; + + const dispatcherName = + me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++; + const payloadName = me.getPlaceholder() + "_payload"; + const cacheName = me.getPlaceholder() + "_cache"; + const newNameMapping = new Map(); + + const keys = { + 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, + isDebug ? "_" + name : getRandomString(6) /** "_" + name */ + ); + } + + // Find identifiers calling/referencing the functions + blockPath.traverse({ + ReferencedIdentifier: { + exit(path: NodePath) { + if (path.isJSX()) return; + if (isVariableFunctionIdentifier(path)) return; + const name = path.node.name; + + var fnPath = functionPaths.get(name); + if (!fnPath) return; + + 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) { + 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.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), + true + ); + }; + + // Replace the identifier with a call to the function + const parentPath = path.parentPath; + if (path.key === "callee" && parentPath?.isCallExpression()) { + var expressions: t.Expression[] = []; + var callArguments = parentPath.node.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); + + 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)); + } + }, + }, + }); + + const fnLengthProperties = []; + + // Create the dispatcher function + const objectExpression = t.objectExpression( + Array.from(newNameMapping).map(([name, newName]) => { + 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), + numericLiteral(fnLength) + ) + ); + } + } + + const newBody = [...originalFn.body.body]; + ok(Array.isArray(newBody)); + + // Unpack parameters + if (originalFn.params.length > 0) { + newBody.unshift( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern([...originalFn.params]), + t.identifier(payloadName) + ), + ]) + ); + } + + // 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( + t.stringLiteral(newName), + + functionExpression + ); + }) + ); + + const fnLengths = t.objectExpression(fnLengthProperties); + + const dispatcher = new Template(` + function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) { + var output; + var fns = {objectExpression}; + + if(flagArg === "${keys.clearPayload}") { + ${payloadName} = []; + } + if(flagArg === "${keys.nonCall}") { + 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](); + } + + if(returnTypeArg === "${keys.returnAsObject}") { + return { "${keys.returnAsObjectProperty}": output }; + } else { + return output; + } + } + `).single({ + objectExpression, + fnLengthsObjectExpression: fnLengths, + }); + + (dispatcher as NodeSymbol)[PREDICTABLE] = true; + + /** + * Prepends the node into the block. (And registers the declaration) + * @param node + */ + function prepend(node: t.Statement) { + var newPath = blockStatement.unshiftContainer( + "body", + node + )[0]; + blockStatement.scope.registerDeclaration(newPath); + } + + // Insert the dispatcher function + prepend(dispatcher); + + // Insert the payload variable + prepend( + t.variableDeclaration("var", [ + t.variableDeclarator(t.identifier(payloadName)), + ]) + ); + + // Insert the cache variable + prepend( + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(cacheName), + new Template(`Object["create"](null)`).expression() + ), + ]) + ); + + // Remove original functions + for (let path of functionPaths.values()) { + path.remove(); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/es5/antiClass.ts b/src/transforms/es5/antiClass.ts deleted file mode 100644 index 6698b16..0000000 --- a/src/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/transforms/es5/antiDestructuring.ts b/src/transforms/es5/antiDestructuring.ts deleted file mode 100644 index d4953bd..0000000 --- a/src/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/transforms/es5/antiES6Object.ts b/src/transforms/es5/antiES6Object.ts deleted file mode 100644 index 37becbf..0000000 --- a/src/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/transforms/es5/antiSpreadOperator.ts b/src/transforms/es5/antiSpreadOperator.ts deleted file mode 100644 index 11a04c4..0000000 --- a/src/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/transforms/es5/antiTemplate.ts b/src/transforms/es5/antiTemplate.ts deleted file mode 100644 index 399d6f1..0000000 --- a/src/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/transforms/es5/es5.ts b/src/transforms/es5/es5.ts deleted file mode 100644 index 9a56197..0000000 --- a/src/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/transforms/extraction/classExtraction.ts b/src/transforms/extraction/classExtraction.ts deleted file mode 100644 index e13bcc8..0000000 --- a/src/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/transforms/extraction/duplicateLiteralsRemoval.ts b/src/transforms/extraction/duplicateLiteralsRemoval.ts index 1a6fbd6..0658328 100644 --- a/src/transforms/extraction/duplicateLiteralsRemoval.ts +++ b/src/transforms/extraction/duplicateLiteralsRemoval.ts @@ -1,297 +1,158 @@ -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 t 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"; - -/** - * [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]); - }; - } +import { PluginArg, PluginObject } from "../plugin"; +import { Order } from "../../order"; +import { + ensureComputedExpression, + isModuleImport, + prepend, +} from "../../utils/ast-utils"; +import { createLiteral, LiteralValue, numericLiteral } from "../../utils/node"; +import { NodePath } from "@babel/traverse"; + +function fail(): never { + throw new Error("Assertion failed"); } + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.DuplicateLiteralsRemoval, { + changeData: { + literals: 0, + }, + }); + + return { + visitor: { + Program: { + enter(programPath) { + const arrayName = me.getPlaceholder() + "_dlrArray"; + + // Collect all literals + const literalsMap = new Map(); + const firstTimeMap = new Map< + LiteralValue, + babel.NodePath + >(); + + const arrayExpression = t.arrayExpression([]); + + const createMemberExpression = (index) => { + return t.memberExpression( + t.identifier(arrayName), + numericLiteral(index), + true + ); + }; + + // Traverse through all nodes to find literals + programPath.traverse({ + "StringLiteral|BooleanLiteral|NumericLiteral|NullLiteral|Identifier"( + _path + ) { + const literalPath = _path as babel.NodePath< + 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()) { + // 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, { + noGlobals: true, + }) + ) + return; + + if (literalPath.node.name === "undefined") { + isUndefined = true; + } else { + return; + } + } + if ( + t.isRegExpLiteral(node) || + t.isTemplateLiteral(node) || + t.isDirectiveLiteral(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" && + value !== null && + value !== undefined + ) { + return; + } + + // Skip empty strings + if (typeof value === "string" && value.length === 0) 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); + + var firstPath = firstTimeMap.get(value); + + me.changeData.literals++; + ensureComputedExpression(firstPath); + + firstPath.replaceWith(createMemberExpression(index)); + + arrayExpression.elements.push(createLiteral(value)); + } else { + firstTimeMap.set(value, literalPath); + + return; + } + + ok(index !== -1); + + me.changeData.literals++; + ensureComputedExpression(literalPath); + + literalPath.replaceWith(createMemberExpression(index)); + literalPath.skip(); + }, + }); + + if (arrayExpression.elements.length === 0) return; + + // Create the literals array declaration + const itemsArrayDeclaration = t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier(arrayName), arrayExpression), + ]); + + prepend(programPath, itemsArrayDeclaration); + }, + }, + }, + }; +}; diff --git a/src/transforms/extraction/objectExtraction.ts b/src/transforms/extraction/objectExtraction.ts index 5d3bfba..cf04d5a 100644 --- a/src/transforms/extraction/objectExtraction.ts +++ b/src/transforms/extraction/objectExtraction.ts @@ -1,360 +1,186 @@ -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"; +import { NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "../plugin"; +import { Order } from "../../order"; +import * as t from "@babel/types"; +import { + getMemberExpressionPropertyAsString, + getObjectPropertyAsString, + getParentFunctionOrProgram, +} from "../../utils/ast-utils"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.ObjectExtraction, { + changeData: { + objects: 0, + }, + }); + + return { + visitor: { + Program: { + enter(path) { + path.scope.crawl(); + }, + }, + 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; + } -/** - * 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); - } + 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); + } - match(object: Node, parents: Node[]) { - return isVarContext(object); - } + // Check function for referencing 'this' + const value = property.get("value"); + if (value.isFunction()) { + var referencesThis = false; - transform(context: Node, contextParents: Node[]) { - // ObjectExpression Extractor + value.traverse({ + ThisExpression(thisPath) { + referencesThis = true; + }, + }); - return () => { - // First pass through to find the maps - var objectDefs: { [name: string]: Location } = Object.create(null); - var objectDefiningIdentifiers: { [name: string]: Location } = - Object.create(null); + if (referencesThis) { + // Function references 'this', not allowed + // When extracted, this will not refer to the original object + return; + } + } - var illegal = new Set(); + newDeclarations.push( + t.variableDeclarator( + t.identifier(newPropertyName), + value.node as t.Expression + ) + ); + } + + var isObjectSafe = true; - 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); + 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; } - if (!object.properties.length) { - illegal.add(name); + const property = getMemberExpressionPropertyAsString( + memberExpression.node + ); + if (!property) { + isObjectSafe = false; return; } - // duplicate name - if (objectDefiningIdentifiers[name]) { - illegal.add(name); + // Delete expression check + if ( + memberExpression.parentPath.isUnaryExpression({ + operator: "delete", + }) + ) { + // Deleting object properties is not allowed + isObjectSafe = false; 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); + let newPropertyName = newPropertyMappings.get(property); + if (!newPropertyName) { + // Property added later on, not allowed + isObjectSafe = false; 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; + const extractedIdentifier = t.identifier(newPropertyName); - 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]; - } - } - } + pendingReplacements.push({ + path: memberExpression, + replaceWith: extractedIdentifier, + }); + }, + }, }); - 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 = []; + // Object references are too complex to safely extract + if (!isObjectSafe) return; - properties.forEach((property: Node) => { - var keyName = property.key.name || property.key.value; + if ( + !me.computeProbabilityMap( + me.options.objectExtraction, + identifier.node.name + ) + ) + return; - var nn = name + "_" + keyName; - newPropNames[keyName] = nn; + const newDeclarationKind = + varDecPath.node.kind === "const" ? "let" : varDecPath.node.kind; - var v = property.value; - - variableDeclarators.push( - VariableDeclarator(nn, this.addComment(v, `${name}.${keyName}`)) - ); + varDecPath + .replaceWithMultiple( + newDeclarations.map((declaration) => + t.variableDeclaration(newDeclarationKind, [declaration]) + ) + ) + .forEach((path) => { + // Make sure to register the new declarations + path.scope.registerDeclaration(path); }); - declaration.declarations.splice( - declaration.declarations.indexOf(declarator), - 1, - ...variableDeclarators - ); - - // const can only be safely changed to let - if (declaration.kind === "const") { - declaration.kind = "let"; - } + // Replace all references to new singular identifiers + for (const { path, replaceWith } of pendingReplacements) { + path.replaceWith(replaceWith); + } - // 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}.` - ) - ); - } - }); + me.log("Extracted object", identifier.node.name); - this.log( - `Extracted ${ - Object.keys(newPropNames).length - } properties from ${name}, affecting ${ - Object.keys(objectDefChanges[name] || {}).length - } line(s) of code.` - ); - }); - } - }; - } -} + me.changeData.objects++; + }, + }, + }; +}; diff --git a/src/transforms/finalizer.ts b/src/transforms/finalizer.ts index c07c446..9ddcfc2 100644 --- a/src/transforms/finalizer.ts +++ b/src/transforms/finalizer.ts @@ -1,75 +1,74 @@ -import { ObfuscateOrder } from "../order"; -import { ExitCallback } from "../traverse"; -import { Identifier, Node } from "../util/gen"; -import StringEncoding from "./string/stringEncoding"; -import Transform from "./transform"; +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"; -/** - * 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; +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Finalizer); + const stringEncodingPlugin = stringEncoding(me); - constructor(o) { - super(o, ObfuscateOrder.Finalizer); + return { + visitor: { + // String encoding + ...stringEncodingPlugin.visitor, - this.stringEncoding = new StringEncoding(o); - } + // 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); - isNumberLiteral(object: Node) { - return ( - object.type === "Literal" && - typeof object.value === "number" && - Math.floor(object.value) === object.value - ); - } + var arg = args[0]; + ok(arg.isIdentifier()); - isBigIntLiteral(object: Node) { - return object.type === "Literal" && typeof object.value === "bigint"; - } + var name = arg.node.name; + path.replaceWith(t.stringLiteral(name)); + } + }, + }, + }), - match(object, parents) { - return object.type === "Literal"; - } + // Hexadecimal numbers + NumericLiteral: { + exit(path) { + if (me.options.hexadecimalNumbers) { + const { value } = path.node; - 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); + if ( + Number.isNaN(value) || + !Number.isFinite(value) || + Math.floor(value) !== value + ) { + return; + } - var newStr = (isNegative ? "-" : "") + "0x" + hex; + // 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); - this.replace(object, Identifier(newStr)); - }; - } + var newStr = (isNegative ? "-" : "") + "0x" + hex; - // 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)); - }; - } + var id = t.identifier(newStr); + (id as NodeSymbol)[GEN_NODE] = true; - if ( - this.options.stringEncoding && - this.stringEncoding.match(object, parents) - ) { - return this.stringEncoding.transform(object, parents); - } - } -} + path.replaceWith(id); + path.skip(); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/flatten.ts b/src/transforms/flatten.ts index 99aa47f..3b5a214 100644 --- a/src/transforms/flatten.ts +++ b/src/transforms/flatten.ts @@ -1,557 +1,418 @@ -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 } from "@babel/traverse"; +import { + ensureComputedExpression, + getFunctionName, + isDefiningIdentifier, + isModifiedIdentifier, + isStrictMode, + isVariableIdentifier, + prepend, + prependProgram, +} from "../utils/ast-utils"; +import { PluginArg, PluginObject } from "./plugin"; +import { Order } from "../order"; +import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants"; +import { + computeFunctionLength, + isVariableFunctionIdentifier, +} from "../utils/function-utils"; +import { NameGen } from "../utils/NameGen"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Flatten, { + changeData: { + functions: 0, + }, + }); + const isDebug = false; + + function flattenFunction(fnPath: NodePath) { + // Skip if already processed + if (me.isSkipped(fnPath)) return; + + // Don't apply to generator functions + if (fnPath.node.generator) return; + + // Skip getter/setter methods + if (fnPath.isObjectMethod() || fnPath.isClassMethod()) { + 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; + + // 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"; + } + + if (!me.computeProbabilityMap(me.options.flatten, functionName)) { + 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}`; + + const nameGen = new NameGen(me.options.identifierGenerator); + + function generateProp(originalName: string, type: string) { + var newPropertyName: string; + do { + newPropertyName = isDebug + ? type + "_" + originalName + : nameGen.generate(); + } while (allPropertyNames.has(newPropertyName)); + + 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[] = []; + + // Traverse function to identify variables to be replaced with flat object properties + fnPath.traverse({ + Identifier: { + exit(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 (identifierName === "arguments") return; + + var binding = identifierPath.scope.getBinding(identifierName); + if (!binding) { + return; + } + + var isOutsideVariable = + fnPath.scope.parent.getBinding(identifierName) === binding; + + if (!isOutsideVariable) { + return; + } + + 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; + if (typeof identifierName !== "string") continue; + + 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, "typeof"); + typeofProps.set(identifierName, typeofProp); + } + + ensureComputedExpression(identifierPath.parentPath); + + identifierPath.parentPath + .replaceWith( + t.memberExpression( + t.identifier(flatObjectName), + t.stringLiteral(typeofProp), + true + ) + )[0] + .skip(); + } else if (isFunctionCall) { + let functionCallProp = functionCallProps.get(identifierName); + if (!functionCallProp) { + functionCallProp = generateProp(identifierName, "call"); + functionCallProps.set(identifierName, functionCallProp); + } + + ensureComputedExpression(identifierPath); + + // 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, "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 + identifierPath + .replaceWith( + t.memberExpression( + t.identifier(flatObjectName), + t.stringLiteral(standardProp), + true + ) + )[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( + me.skip( + t.objectMethod( + "get", + t.stringLiteral(objectProp), + [], + t.blockStatement([t.returnStatement(t.identifier(identifierName))]), + 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( + me.skip( + t.objectMethod( + "get", + t.stringLiteral(objectProp), + [], + t.blockStatement([ + t.returnStatement( + t.unaryExpression("typeof", t.identifier(identifierName)) + ), + ]), + false, + false, + false + ) + ) + ); + } + + for (const entry of functionCallProps) { + const [identifierName, objectProp] = entry; + + flatObjectProperties.push( + me.skip( + 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", [ + t.variableDeclarator( + t.identifier(flatObjectName), + t.objectExpression(flatObjectProperties) + ), + ]); + + var argName = me.getPlaceholder() + "_args"; + + // Replace original function body with a call to the flattened function + fnPath.node.body = t.blockStatement([ + flatObjectDeclaration, + t.returnStatement( + t.callExpression(t.identifier(newFnName), [ + t.identifier(argName), + t.identifier(flatObjectName), + ]) + ), + ]); + + 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(); + + // Add the new flattened function at the top level + 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 { + visitor: { + Function: { + exit(path: NodePath) { + flattenFunction(path); + }, + }, + Program(path) { + path.scope.crawl(); + }, + }, + }; +}; diff --git a/src/transforms/identifier/globalAnalysis.ts b/src/transforms/identifier/globalAnalysis.ts deleted file mode 100644 index 06a9b38..0000000 --- a/src/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/transforms/identifier/globalConcealing.ts b/src/transforms/identifier/globalConcealing.ts index f04b4fd..e176692 100644 --- a/src/transforms/identifier/globalConcealing.ts +++ b/src/transforms/identifier/globalConcealing.ts @@ -1,297 +1,295 @@ +import * as t from "@babel/types"; +import { NodePath } from "@babel/traverse"; +import { NameGen } from "../../utils/NameGen"; import Template from "../../templates/template"; -import Transform from "../transform"; -import { ObfuscateOrder } from "../../order"; +import { PluginArg, PluginObject } from "../plugin"; +import { Order } 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, + MULTI_TRANSFORM, reservedIdentifiers, + reservedNodeModuleIdentifiers, 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); - } +import { + getMemberExpressionPropertyAsString, + isVariableIdentifier, + prepend, +} from "../../utils/ast-utils"; +import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; +import { + getRandomInteger, + getRandomString, + shuffle, +} from "../../utils/random-utils"; +import { ok } from "assert"; + +const ignoreGlobals = new Set([ + ...reservedNodeModuleIdentifiers, + "__dirname", + "eval", + "arguments", + variableFunctionName, + ...reservedIdentifiers, +]); + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.GlobalConcealing, { + changeData: { + globals: 0, + nativeFunctions: 0, + }, + }); + + var globalMapping = new Map(), + globalFnName = me.getPlaceholder() + "_getGlobal", + globalVarName = me.getPlaceholder() + "_globalVar", + gen = new NameGen(); + + // 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 = 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); + }; - match(object: Node, parents: Node[]) { - return object.type == "Program"; + return t.functionDeclaration( + t.identifier(globalFnName), + [t.identifier("mapping")], + t.blockStatement([createSwitchStatement()]) + ); } - 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]; - } - }); + return { + visitor: { + Program: { + exit(programPath: NodePath) { + var illegalGlobals = new Set(); + var pendingReplacements = new Map(); - if (Object.keys(globals).length > 0) { - var usedStates = new Set(); + programPath.traverse({ + Identifier(identifierPath) { + if (!isVariableIdentifier(identifierPath)) return; - // Make getter function + var identifierName = identifierPath.node.name; - // holds "window" or "global" - var globalVar = this.getPlaceholder(); + if (ignoreGlobals.has(identifierName)) return; - var getGlobalVariableFnName = - this.getPlaceholder() + predictableFunctionTag; + const binding = identifierPath.scope.getBinding(identifierName); + if (binding) { + illegalGlobals.add(identifierName); + return; + } - // Returns global variable or fall backs to `this` - var getGlobalVariableFn = createGetGlobalTemplate( - this, - object, - parents - ).compile({ - getGlobalFnName: getGlobalVariableFnName, - }); + if (!identifierPath.scope.hasGlobal(identifierName)) { + return; + } - // 2. Replace old accessors - var globalFn = this.getPlaceholder() + predictableFunctionTag; + var assignmentChild = identifierPath.find((p) => + p.parentPath?.isAssignmentExpression() + ); + if ( + assignmentChild && + t.isAssignmentExpression(assignmentChild.parent) && + assignmentChild.parent.left === assignmentChild.node && + !t.isMemberExpression(identifierPath.parent) + ) { + illegalGlobals.add(identifierName); + return; + } - var newNames: { [globalVarName: string]: number } = Object.create(null); + if (!pendingReplacements.has(identifierName)) { + pendingReplacements.set(identifierName, [identifierPath]); + } else { + pendingReplacements.get(identifierName).push(identifierPath); + } + }, + }); - 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); + // Remove illegal globals + illegalGlobals.forEach((globalName) => { + pendingReplacements.delete(globalName); + }); - newNames[name] = state; + for (var [globalName, paths] of pendingReplacements) { + var mapping = globalMapping.get(globalName); + if (!mapping) { + // Allow user to disable custom global variables + if ( + !me.computeProbabilityMap( + me.options.globalConcealing, + globalName + ) + ) + continue; - locations.forEach(([node, p]) => { - if (p.find((x) => x.$multiTransformSkip)) { - return; + mapping = gen.generate(); + globalMapping.set(globalName, mapping); } - 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; - } + // Replace global reference with getGlobal("name") + const callExpression = t.callExpression( + t.identifier(globalFnName), + [t.stringLiteral(mapping)] + ); - 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); + const { nativeFunctionName } = me.globalState.internals; - if ( - !this.lockTransform.shouldTransformNativeFunction( - nameAndPropertyPath - ) - ) - return; + for (let path of paths) { + const replaceExpression = t.cloneNode(callExpression); + me.skip(replaceExpression); - 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 }] - ); + 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; + } } - } - } - }); - }); - - // 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 + // 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 ) ) - ) - ), - BreakStatement(), - ]; + ); + + me.changeData.nativeFunctions++; + continue; + } } + } - 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); - } - }; - } -} + me.changeData.globals++; + + // Regular replacement + // console -> getGlobal("console") + path.replaceWith(replaceExpression); + } + } + + // No globals changed, no need to insert the getGlobal function + if (globalMapping.size === 0) return; + + // The Global Concealing function returns the global variable from the specified parameter + const globalConcealingFunction = createGlobalConcealingFunction(); + + prepend(programPath, globalConcealingFunction); + + const getGlobalVarFnName = me.getPlaceholder() + "_getGlobalVarFn"; + + // Insert the get global function + prepend( + programPath, + createGetGlobalTemplate(me, programPath).compile({ + getGlobalFnName: getGlobalVarFnName, + }) + ); + + // 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 a86208b..5c505ed 100644 --- a/src/transforms/identifier/movedDeclarations.ts +++ b/src/transforms/identifier/movedDeclarations.ts @@ -1,153 +1,251 @@ -import Transform from "../transform"; -import { isBlock } from "../../traverse"; +import { NodePath } from "@babel/traverse"; +import { Order } from "../../order"; +import { PluginArg, PluginObject } from "../plugin"; +import { NodeSymbol, PREDICTABLE } from "../../constants"; +import * as t from "@babel/types"; +import { isStaticValue } from "../../utils/static-utils"; import { - ExpressionStatement, - AssignmentExpression, - Identifier, - Node, - VariableDeclarator, - AssignmentPattern, -} from "../../util/gen"; -import { - isForInitialize, - isFunction, - isStrictModeFunction, + getPatternIdentifierNames, + isStrictMode, 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"; +} from "../../utils/ast-utils"; +import Template from "../../templates/template"; /** - * Defines all the names at the top of every lexical block. + * 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 class MovedDeclarations extends Transform { - constructor(o) { - super(o, ObfuscateOrder.MovedDeclarations); - } +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.MovedDeclarations, { + changeData: { + variableDeclarations: 0, + functionParameters: 0, + }, + }); + + function isFunctionEligibleForParameterPacking( + functionPath: NodePath, + proposedParameterName: string + ) { + // 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; + } + } - match(object, parents) { - return ( - object.type === "VariableDeclaration" && - object.kind === "var" && - object.declarations.length === 1 && - object.declarations[0].id.type === "Identifier" + // 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; + + // Check for duplicate parameter names + var bindingIdentifiers = getPatternIdentifierNames( + functionPath.get("params") ); + + // Duplicate parameter name not allowed + if (bindingIdentifiers.has(proposedParameterName)) return false; + + return true; } - 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) + return { + visitor: { + FunctionDeclaration: { + exit(path) { + var functionPath = path.findParent((path) => + path.isFunction() + ) as NodePath; + + 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 !== fnBody) return; + + const functionName = path.node.id.name; + + // Must be eligible for parameter packing + if ( + !isFunctionEligibleForParameterPacking(functionPath, functionName) + ) + return; + + var strictMode = isStrictMode(functionPath); + + // Default parameters are not allowed when 'use strict' is declared + if (strictMode) return; + + var functionExpression = path.node as t.Node as t.FunctionExpression; + functionExpression.type = "FunctionExpression"; + functionExpression.id = null; + + 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( + fnBody, + new Template(` + if(!${functionName}) { + ${functionName} = {functionExpression}; + } + `).single({ functionExpression: functionExpression }) ); - 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)); - } - } - }; - } -} + path.remove(); + me.changeData.functionParameters++; + }, + }, + VariableDeclaration: { + exit(path) { + if (me.isSkipped(path)) return; + if (path.node.kind !== "var") return; + if (path.node.declarations.length !== 1) return; + + var insertionMethod = "variableDeclaration"; + var functionPath = path.findParent((path) => + 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]) { + // Check for "use strict" directive + // Strict mode disallows non-simple parameters + // So we can't move the declaration to the function parameters + var strictMode = isStrictMode(functionPath); + if (strictMode) { + allowDefaultParamValue = false; + } + + // Cannot add variables after rest element + // Cannot add over 1,000 parameters + if (isFunctionEligibleForParameterPacking(functionPath, varName)) { + insertionMethod = "functionParameter"; + } + } + + 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") + .filter((x) => x.type !== "ImportDeclaration") + .indexOf(path) === 0; + } + + // Already at the top - nothing will change + if (insertionMethod === "variableDeclaration" && isDefinedAtTop) { + return; + } + + let defaultParamValue: t.Expression; + + if ( + insertionMethod === "functionParameter" && + isStatic && + isDefinedAtTop && + allowDefaultParamValue + ) { + defaultParamValue = value; + path.remove(); + } else { + // For-in / For-of can only reference the variable name + 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 identifier = t.identifier(name); + + var param: t.Pattern | t.Identifier = identifier; + if (allowDefaultParamValue && defaultParamValue) { + param = t.assignmentPattern(param, defaultParamValue); + } + + functionPath.node.params.push(param); + + var paramPath = functionPath.get("params").at(-1); + + // Update binding to point to new path + const binding = functionPath.scope.getBinding(name); + if (binding) { + binding.kind = "param"; + binding.path = paramPath; + binding.identifier = identifier; + } + + me.changeData.functionParameters++; + break; + case "variableDeclaration": + var block = path.findParent((path) => + path.isBlock() + ) as NodePath; + + var topNode = block.node.body.filter( + (x) => x.type !== "ImportDeclaration" + )[0]; + const variableDeclarator = t.variableDeclarator( + t.identifier(name) + ); + + if (t.isVariableDeclaration(topNode) && topNode.kind === "var") { + topNode.declarations.push(variableDeclarator); + break; + } else { + prepend( + block, + me.skip(t.variableDeclaration("var", [variableDeclarator])) + ); + } + + me.changeData.variableDeclarations++; + + break; + } + }, + }, + }, + }; +}; diff --git a/src/transforms/identifier/renameVariables.ts b/src/transforms/identifier/renameVariables.ts index 7d7a5b3..67c7156 100644 --- a/src/transforms/identifier/renameVariables.ts +++ b/src/transforms/identifier/renameVariables.ts @@ -1,300 +1,321 @@ -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 { NodePath } from "@babel/traverse"; +import { Visitor } from "@babel/traverse"; +import { PluginArg, PluginObject } from "../plugin"; +import * as t from "@babel/types"; +import { Order } from "../../order"; 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)); - } +import { + getParentFunctionOrProgram, + isDefiningIdentifier, + isExportedIdentifier, + isVariableIdentifier, +} from "../../utils/ast-utils"; +import { isVariableFunctionIdentifier } from "../../utils/function-utils"; + +const RENAMED = Symbol("Renamed"); + +const reusePreviousNames = true; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.RenameVariables, { + changeData: { + variables: 0, + }, + }); + + 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>(); + me.obfuscator.globalState.renamedVariables = renamedVariables; + + const generated = Array.from(me.obfuscator.nameGen.generatedNames); + + const VariableAnalysisVisitor: Visitor = { + Program: { + enter(path) { + // Analyze all scopes + path.traverse({ + Identifier(path) { + if (!isVariableIdentifier(path)) return; + + let contextPaths: NodePath[] = [ + getParentFunctionOrProgram(path), + ]; + + 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 ( + path.key === "id" && + path.parentPath.isFunctionDeclaration() + ) { + contextPaths = [getParentFunctionOrProgram(path.parentPath)]; + } + } - var def = this.variableAnalysis.defined.get(o); - if (def) { - def.forEach((x) => allReferences.add(x)); - } - }); + 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 (!bindingMap.has(contextPath.node)) { + bindingMap.set(contextPath.node, new Map()); + } + 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 passed = new Set(); - parents.forEach((p) => { - var changes = this.changed.get(p); - if (changes) { - Object.keys(changes).forEach((x) => { - var name = changes[x]; + var contextPaths: NodePath[] = identifierPath.getAncestry(); - if (!allReferences.has(x) && !references.has(x)) { - passed.add(name); - } else { - nope.add(name); + // 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; + + 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); + } } - }); + } } - }); - nope.forEach((x) => passed.delete(x)); + const { node } = contextPath; - possible = passed; - } + const defined = definedMap.get(node); + if (defined?.has(identifierName)) { + const renamed = renamedVariables.get(node); + if (renamed?.has(identifierName)) { + newName = renamed.get(identifierName); + break; + } + } + } - // 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(); + if (newName && typeof newName === "string") { + // __JS_CONFUSER_VAR__ function + if (isVariableFunctionIdentifier(identifierPath)) { + identifierPath.parentPath.replaceWith(t.stringLiteral(newName)); + return; + } - newName = generatedName; - this.generated.push(generatedName); - } - } while (this.variableAnalysis.globals.has(newName)); // Ensure global names aren't overridden + // 5. Update Identifier node's 'name' property + node.name = newName; + node[RENAMED] = true; - newNames[name] = newName; - } else { - // This variable name was deemed not to be renamed. - newNames[name] = name; + // 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); + } + } } - } - - // 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); + }, + + 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 defined = definedMap.get(node) || new Set(); + const references = referencedMap.get(node) || new Set(); + const bindings = bindingMap.get(node); + + // No changes needed here + if (!defined && !renamedVariables.has(node)) { + renamedVariables.set(node, Object.create(null)); + return; } - } - 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; + const newNames = new Map(); + + // Names possible to be re-used here + var possible = new Set(); + + // 3. Try to re-use names when possible + if (reusePreviousNames && generated.length && !isGlobal) { + var allReferences = new Set(); + var nope = new Set(defined); + + scopePath.traverse({ + Scopable(path) { + const { node } = path.scope.path; + + var ref = referencedMap.get(node); + if (ref) { + ref.forEach((x) => allReferences.add(x)); + } + + 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); + } + } } + }); + + 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; - forIndex++; + const binding = bindings?.get(name); + + if (binding) { + // Do not rename exports + if (isExportedIdentifier(binding)) return false; } - if (!isReferencedHere) { - // This context is not referenced, so remove it - contexts = contexts.filter( - (context) => context != parents[functionIndex] - ); + if (name === me.obfuscator.getStringCompressionLibraryName()) + return false; + + // Global variables are additionally checked against user option + if (isGlobal) { + if (!me.computeProbabilityMap(me.options.renameGlobals, name)) + return false; } - } - } - 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; - } + !me.computeProbabilityMap(me.options.renameVariables, name, isGlobal) + ) + return false; + + return true; } - } - 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 + // 4. Defined names to new names + for (var name of defined) { + let newName = name; + + 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 } - } - 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; - } + newNames.set(name, newName); } - // 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); - } - } -} + // console.log(node.type, newNames); + renamedVariables.set(node, newNames); + }, + }; + + return { + visitor: { + ...VariableAnalysisVisitor, + + ...VariableRenamingVisitor, + }, + }; +}; diff --git a/src/transforms/identifier/variableAnalysis.ts b/src/transforms/identifier/variableAnalysis.ts deleted file mode 100644 index d238f02..0000000 --- a/src/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/transforms/lock/antiDebug.ts b/src/transforms/lock/antiDebug.ts deleted file mode 100644 index b9af5fd..0000000 --- a/src/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/transforms/lock/integrity.ts b/src/transforms/lock/integrity.ts index 507ab6a..d288972 100644 --- a/src/transforms/lock/integrity.ts +++ b/src/transforms/lock/integrity.ts @@ -1,282 +1,117 @@ -import Transform from "../transform"; +import { PluginArg, PluginObject } 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 { - 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"; +import { NodePath } from "@babel/traverse"; +import { NameGen } from "../../utils/NameGen"; -/** - * 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); +export interface IntegrityInterface { + fnPath: NodePath; + fnName: string; } -// 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; -};`); +export const INTEGRITY = Symbol("Integrity"); -// Simple function that returns .toString() value with spaces replaced out -const StringTemplate = new Template(` - function {name}(x){ - return x.toString().replace(/ |\\n|;|,|\\{|\\}|\\(|\\)|\\.|\\[|\\]/g, ""); - } -`); +export interface NodeIntegrity { + [INTEGRITY]?: IntegrityInterface; +} /** - * 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. + * Integrity has two passes: * - * How it works: + * - First in the 'lock' plugin to select functions and prepare them for Integrity + * - Secondly here to apply the integrity check * - * - 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. + * This transformation must run last as any changes to the code will break the hash */ -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 = () => { +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Integrity, { + changeData: { + functions: 0, + }, + }); + + const nameGen = new NameGen(me.options.identifierGenerator, { + avoidObjectPrototype: true, + avoidReserved: true, + }); + + return { + visitor: { + Program: { + enter(path) { + path.scope.crawl(); + }, + }, + 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 ( - 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; - } + !newFunctionDeclaration || + !t.isFunctionDeclaration(newFunctionDeclaration) + ) + return; + + const { integrityHashName: hashFnName } = me.globalState.internals; + const obfuscatedHashFnName = me.obfuscator.getObfuscatedVariableName( + hashFnName, + funcDecPath.find((p) => p.isProgram()).node + ); - return () => { - object.__hiddenCountermeasures = this.lock.getCounterMeasuresCode( - object, - parents - ); + const newFnName = newFunctionDeclaration.id.name; + const binding = newFnPath.scope.getBinding(newFnName); - object.$eval = () => { - var functionName = this.generateIdentifier(); - var hashName = this.generateIdentifier(); + // Function is redefined, do not apply integrity + if (!binding || binding.constantViolations.length > 0) return; - var functionDeclaration = { - ...clone(object), - type: "FunctionDeclaration", - id: Identifier(functionName), - params: object.params || [], - body: object.body || BlockStatement([]), - expression: false, - $multiTransformSkip: true, - }; + var code = me.obfuscator.generateCode(newFunctionDeclaration); + var codeTrimmed = code.replace( + me.globalState.lock.integrity.sensitivityRegex, + "" + ); - var toString = compileJsSync(functionDeclaration, this.options); + var seed = getRandomInteger(0, 10000000); - if (!toString) { - return; - } + var hashCode = HashFunction(codeTrimmed, seed); - var minified = toString.replace(/ |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g, ""); - var hash = cyrb53(minified, this.seed); + const selfName = funcDecPath.node.id.name; + const selfCacheProperty = nameGen.generate(); + const selfCacheString = `${selfName}.${selfCacheProperty}`; - this.log( - (object.id ? object.id.name : "function") + " -> " + hash, - minified - ); + // me.log(codeTrimmed, hashCode); + me.changeData.functions++; - 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 - ); - } + const hashName = nameGen.generate(); - object.body = BlockStatement([ - functionDeclaration, - VariableDeclaration( - VariableDeclarator( + funcDecPath.node.body = t.blockStatement( + new Template(` + var {hashName} = ${selfCacheString} || (${selfCacheString} = ${obfuscatedHashFnName}(${newFunctionDeclaration.id.name}, ${seed})); + if({hashName} === ${hashCode}) { + {originalBody} + } else { + {countermeasures} + } + `).compile({ + originalBody: funcDecPath.node.body.body, 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) - ) + countermeasures: () => + me.globalState.lock.createCountermeasuresCode(), + }), + // Preserve directives + funcDecPath.node.body.directives ); - } - - if (object.type == "ArrowFunctionExpression") { - object.type = "FunctionExpression"; - object.expression = false; - } - }; - }; - } -} + }, + }, + }, + }; +}; diff --git a/src/transforms/lock/lock.ts b/src/transforms/lock/lock.ts index 7d3bba5..ad328a5 100644 --- a/src/transforms/lock/lock.ts +++ b/src/transforms/lock/lock.ts @@ -1,650 +1,418 @@ -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 { NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "../plugin"; +import { Order } from "../../order"; +import { chance, choice } from "../../utils/random-utils"; 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; +import * as t from "@babel/types"; +import { CustomLock } from "../../options"; +import { + getFunctionName, + getParentFunctionOrProgram, + isDefiningIdentifier, + isVariableIdentifier, + prependProgram, +} from "../../utils/ast-utils"; +import { INTEGRITY, NodeIntegrity } from "./integrity"; +import { HashTemplate } from "../../templates/integrityTemplate"; +import { + MULTI_TRANSFORM, + NodeSymbol, + PREDICTABLE, + SKIP, + UNSAFE, +} from "../../constants"; +import { + IndexOfTemplate, + NativeFunctionTemplate, + StrictModeTemplate, +} from "../../templates/tamperProtectionTemplates"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Lock, { + changeData: { + locksInserted: 0, + }, + }); + + 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"); } - var hasNativeCode = - typeof fn === "function" && ("" + fn).includes("[native code]"); - - return hasNativeCode; + 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, + }); } - 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)); + 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"); } - 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." - ); + me.options.lock.customLocks.push({ + code: [ + ` + if(Date.now()>${me.options.lock.endDate.getTime()}) { + {countermeasures} } - } - - 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 + `, + ` + if((new Date()).getTime()>${me.options.lock.endDate.getTime()}) { + {countermeasures} + } + `, + ], + percentagePerBlock: 0.5, + }); + } - var strictModeCheck = new Template(` - (function(){ - function isStrictMode(){ - try { - var arr = [] - delete arr["length"] - } catch(e) { - return true; - } - return false; - } + if (me.options.lock.domainLock) { + var domainArray = Array.isArray(me.options.lock.domainLock) + ? me.options.lock.domainLock + : [me.options.lock.domainLock]; - if(isStrictMode()) { + for (const regexString of domainArray) { + me.options.lock.customLocks.push({ + code: new Template(` + if(!new RegExp({regexString}).test(window.location.href)) { {countermeasures} - ${this.nativeFunctionName} = undefined; } - })() - `).single({ - countermeasures: this.getCounterMeasuresCode(tree, []), + `).setDefaultVariables({ + regexString: () => t.stringLiteral(regexString.toString()), + }), + percentagePerBlock: 0.5, }); + } + } - // $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" - ) { + 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 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]; + return namedFunction(); + } + )(); + `, + percentagePerBlock: 0.5, + }); + } - var fn = object[property]; - fn = checkFunction(fn); + if (me.options.lock.antiDebug) { + me.options.lock.customLocks.push({ + code: ` + debugger; + `, + percentagePerBlock: 0.5, + }); + } - return fn.bind(object); - } - }`).single({ - IndexOfTemplate: IndexOfTemplate, - countermeasures: this.getCounterMeasuresCode(tree, []), - }); + const timesMap = new WeakMap(); - // $multiTransformSkip is used to prevent scoping between transformations - nativeFunctionCheck.$multiTransformSkip = true; + let countermeasuresNode: NodePath; + let invokeCountermeasuresFnName: string; - prepend(tree, nativeFunctionCheck); - } - } + if (me.options.lock.countermeasures) { + invokeCountermeasuresFnName = me.getPlaceholder("invokeCountermeasures"); - getCounterMeasuresCode(object: Node, parents: Node[]): Node[] { - var opt = this.options.lock.countermeasures; + me.globalState.internals.invokeCountermeasuresFnName = + invokeCountermeasuresFnName; + } - if (opt === false) { - return null; + var createCountermeasuresCode = () => { + if (invokeCountermeasuresFnName) { + return new Template(`${invokeCountermeasuresFnName}()`).compile(); } - // 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, []), - ]) - ) - ), - ]; + if (me.options.lock.countermeasures === false) { + return []; } - // Default fallback to infinite loop - var varName = this.getPlaceholder(); - return choice([CrashTemplate1, CrashTemplate2]).compile({ - var: varName, - }); - } + return new Template(`while(true){}`).compile(); + }; + me.globalState.lock.createCountermeasuresCode = createCountermeasuresCode; - /** - * 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()); - } + const defaultMaxCount = me.options.lock.defaultMaxCount ?? 25; - return object + getRandomInteger(-4000, 4000); - } + function applyLockToBlock(path: NodePath, customLock: CustomLock) { + let times = timesMap.get(customLock) || 0; - match(object: Node, parents: Node[]) { - return isBlock(object); - } + let maxCount = customLock.maxCount ?? defaultMaxCount; // 25 is default max count + let minCount = customLock.minCount ?? 1; // 1 is default min count - transform(object: Node, parents: Node[]) { - if (parents.find((x) => isLoop(x) && x.type != "SwitchStatement")) { + if (maxCount >= 0 && times > maxCount) { + // Limit creation, allowing -1 to disable the limit entirely return; } - // no check in countermeasures code, otherwise it will infinitely call itself + // The Program always gets a lock + // Else based on the percentage + // Try to reach the minimum count if ( - this.counterMeasuresNode && - (object == this.counterMeasuresNode[0] || - parents.indexOf(this.counterMeasuresNode[0]) !== -1) + !path.isProgram() && + !chance(customLock.percentagePerBlock * 100) && + times >= minCount ) { 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"); - } + // Increment the times + timesMap.set(customLock, times + 1); - 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"); - } + const lockCode = Array.isArray(customLock.code) + ? choice(customLock.code) + : customLock.code; - if (!choices.length) { - return; - } + 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 () => { - this.made++; - if (this.made > 150) { - return; - } + me.changeData.locksInserted++; + } - 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"), [])] - ); - } + return { + visitor: { + BindingIdentifier(path) { + if (path.node.name !== me.options.lock.countermeasures) { + return; + } - 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() - } + // Exclude labels + if (!isVariableIdentifier(path)) return; - return namedFunction(); - } - )() - ` - ).single().expression; - - nodes.push( - IfStatement( - callExpression, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); + if (!isDefiningIdentifier(path)) { + // Reassignments are not allowed - break; + me.error("Countermeasures function cannot be reassigned"); + } - case "startDate": - test = BinaryExpression( - "<", - dateNow, - Literal(this.getTime(this.options.lock.startDate)) - ); + if (countermeasuresNode) { + // Disallow multiple countermeasures functions - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) + me.error( + "Countermeasures function was already defined, it must have a unique name from the rest of your code" ); + } - break; - - case "endDate": - test = BinaryExpression( - ">", - dateNow, - Literal(this.getTime(this.options.lock.endDate)) + if ( + path.scope.getBinding(path.node.name).scope !== + path.scope.getProgramParent() + ) { + me.error( + "Countermeasures function must be defined at the global level" ); + } - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); + countermeasuresNode = path; + }, - 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() - ) - ) - ) - ); + Block: { + exit(path) { + var customLock = choice(me.options.lock.customLocks); + if (customLock) { + applyLockToBlock(path, customLock); } - - 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() + }, + }, + + Program: { + exit(path) { + // 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." ); } - - 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; - } + var nativeFunctionName = + me.getPlaceholder() + "_nativeFunctionCheck"; - if (!test) { - test = thisTest; - } else { - test = LogicalExpression("||", { ...test }, thisTest); - } - }); + me.obfuscator.globalState.internals.nativeFunctionName = + nativeFunctionName; - 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() - ), - ] + // Ensure program is not in strict mode + // Tamper Protection forces non-strict mode + prependProgram( + path, + StrictModeTemplate.compile({ + nativeFunctionName, + countermeasures: createCountermeasuresCode(), + }) ); - if (browserName === "safari") { - thisTest = new Template( - `/^((?!chrome|android).)*safari/i.test(navigator.userAgent)` - ).single().expression; - } + const nativeFunctionDeclaration = NativeFunctionTemplate.single({ + nativeFunctionName, + countermeasures: createCountermeasuresCode(), + IndexOfTemplate: IndexOfTemplate, + }); - if (!test) { - test = thisTest; - } else { - test = LogicalExpression("||", { ...test }, thisTest); - } - }); + // Checks function's toString() value for [native code] signature + prependProgram(path, nativeFunctionDeclaration); + } - test = UnaryExpression("!", { ...test }); - nodes.push( - IfStatement( - test, - this.getCounterMeasuresCode(object, parents) || [], - null - ) - ); - break; + // Insert invokeCountermeasures function + if (invokeCountermeasuresFnName) { + if (!countermeasuresNode) { + me.error( + "Countermeasures function named '" + + me.options.lock.countermeasures + + "' was not found." + ); + } - case "domainLock": - function removeSlashes(path: string) { - var count = path.length - 1; - var index = 0; + var hasInvoked = me.getPlaceholder("hasInvoked"); + var statements = new Template(` + var ${hasInvoked} = false; + function ${invokeCountermeasuresFnName}(){ + if(${hasInvoked}) return; + ${hasInvoked} = true; + ${me.options.lock.countermeasures}(); + } + `) + .addSymbols(MULTI_TRANSFORM) + .compile(); - while (path.charCodeAt(index) === 47 && ++index); - while (path.charCodeAt(count) === 47 && --count); + prependProgram(path, statements).forEach((p) => p.skip()); + } - return path.slice(index, count + 1); + if (me.options.lock.integrity) { + const hashFnName = me.getPlaceholder() + "_hash"; + const imulFnName = me.getPlaceholder() + "_imul"; + + const { sensitivityRegex } = me.globalState.lock.integrity; + 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); } + }, + }, + + // 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) { + 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; + + if (funcDecPath.find((p) => !!(p.node as NodeSymbol)[SKIP])) return; + + var program = getParentFunctionOrProgram(funcDecPath); + // 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 ( + !me.computeProbabilityMap(me.options.lock.integrity, functionName) + ) + return; - var locationHref = MemberExpression( - Identifier("location"), - Literal("href"), - true + var newFnName = me.getPlaceholder(); + var newFunctionDeclaration = t.functionDeclaration( + t.identifier(newFnName), + funcDecPath.node.params, + funcDecPath.node.body ); - 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 ? "" : "", - }, - }, - ] - ); + // Clone semantic symbols like (UNSAFE, PREDICTABLE, MULTI_TRANSFORM, etc) + const source = funcDecPath.node; + Object.getOwnPropertySymbols(source).forEach((symbol) => { + newFunctionDeclaration[symbol] = source[symbol]; + }); - 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 - ) - ); - } + (newFunctionDeclaration as NodeSymbol)[SKIP] = true; - break; - } + var [newFnPath] = program.unshiftContainer( + "body", + newFunctionDeclaration + ); - if (nodes.length) { - var body = getBlockBody(block); - var randomIndex = getRandomInteger(0, body.length) + offset; + // 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(), + funcDecPath.node.body.directives + ); - if (randomIndex >= body.length) { - body.push(...nodes); - } else { - body.splice(randomIndex, 0, ...nodes); - } - } - }; - } -} + // 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, + fnName: newFnName, + }; + }, + }, + }, + }; +}; diff --git a/src/transforms/minify.ts b/src/transforms/minify.ts index 5e06805..a1d7125 100644 --- a/src/transforms/minify.ts +++ b/src/transforms/minify.ts @@ -1,722 +1,629 @@ -import Transform from "./transform"; -import { ObfuscateOrder } from "../order"; +import { NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; +import * as t from "@babel/types"; +import { Order } from "../order"; import { - Node, - VariableDeclaration, - BinaryExpression, - ExpressionStatement, - SequenceExpression, - Literal, - UnaryExpression, - ConditionalExpression, - BlockStatement, - ReturnStatement, - AssignmentExpression, - VariableDeclarator, - Identifier, - CallExpression, -} from "../util/gen"; + ensureComputedExpression, + getParentFunctionOrProgram, + isUndefined, +} from "../utils/ast-utils"; +import { Binding, Scope } from "@babel/traverse"; 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); + NO_REMOVE, + NodeSymbol, + placeholderVariablePrefix, + UNSAFE, +} from "../constants"; + +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)) +); + +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]); + } } - match(object: Node, parents: Node[]) { - return object.hasOwnProperty("type"); + 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); + } + } + } } +} - 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) => { +/** + * 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): PluginObject => { + const me = Plugin(Order.Minify); + return { + visitor: { + Program(path) { + path.scope.crawl(); + }, + // 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 ( - stmt.type === "ReturnStatement" || - stmt.type === "BreakStatement" || - stmt.type === "ContinueStatement" || - stmt.type === "ThrowStatement" + nextDeclaration.isVariableDeclaration({ + kind: kind, + }) ) { - if (earlyReturn > i + 1) { - earlyReturn = i + 1; + 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 + ); + } } - } - - 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[] }[] = []; + nextDeclaration.node.declarations.unshift( + ...declarations.map((x) => x.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 }); + 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]; + if (binding) { + binding.path = newBindingIdentifierPaths[name]; + scope.bindings[name] = binding; + } } - exprs = []; - startIndex = -1; } - }); - if (exprs.length) { - sequences.push({ exprs: exprs, index: startIndex }); + if (kind === "var") { + addBindingsToScope(getParentFunctionOrProgram(path).scope); + } + addBindingsToScope(path.scope); } - - 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) - ) + }, + }, + // 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(); + const parent = getParentFunctionOrProgram(path); + if (parent && (parent.node as NodeSymbol)[UNSAFE]) return; + + if (value === undefined) return; + + path.replaceWith( + t.unaryExpression("!", t.numericLiteral(value ? 1 : 0)) ); - }); - } - - // 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; + } + }, + }, + // "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) { + 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: { + exit(path) { + var key = path.get("key"); + if (path.node.computed && key.isStringLiteral()) { + path.node.computed = false; + } - if (isUndefined) { - body.pop(); + 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)); } } - } - - // 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") { + }, + }, + // (a); -> a; + SequenceExpression: { + exit(path) { + if (path.node.expressions.length === 1) { + path.replaceWith(path.node.expressions[0]); + } + }, + }, + // ; -> () + EmptyStatement: { + exit(path) { + path.remove(); + }, + }, + // console; -> (); + ExpressionStatement: { + exit(path) { + if (path.get("expression").isIdentifier()) { + // Preserve last expression of program for RGF if ( - !lastDec || - lastDec.kind !== x.kind || - !lastDec.declarations.length - ) { - lastDec = x; - } else { - lastDec.declarations.push(...clone(x.declarations)); - remove.unshift(i); + path.parentPath?.isProgram() && + path.parentPath?.get("body").at(-1) === path + ) + return; + 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)!()); } - } 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)) { + }, + }, + // 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() && + !id.node.name.startsWith(placeholderVariablePrefix) && + !(path.node as NodeSymbol)[NO_REMOVE] + ) { + const binding = path.scope.getBinding(id.node.name); if ( - parents[propIndex].kind !== "init" || - parents[propIndex].method + binding && + binding.constantViolations.length === 0 && + binding.referencePaths.length === 0 && + !binding.referenced ) { - return; + path.remove(); } } - } - - if (object.type === "FunctionDeclaration") { - var body = parents[0]; - if (!Array.isArray(body)) { - return; + }, + }, + // var x=undefined -> var x + // Remove unused variables + // Simple destructuring + VariableDeclarator: { + exit(path) { + if (isUndefined(path.get("init"))) { + path.node.init = null; } - var index = body.indexOf(object); - if (index == -1) { - return; - } + const id = path.get("id"); + const init = path.get("init"); - var before = body.slice(0, index); - ok(!before.includes(object)); + trySimpleDestructuring(id, init); - var beforeTypes = new Set(before.map((x) => x.type)); - beforeTypes.delete("FunctionDeclaration"); + // 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.node as NodeSymbol)[UNSAFE]) return; - if (beforeTypes.size > 0) { - return; - } + // Node explicitly marked as not to be removed + if ((id as NodeSymbol)[NO_REMOVE]) 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; - } - } + const binding = path.scope.getBinding(id.node.name); - 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 ( + 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)); + } } } - }); - - 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` + }, + }, + // 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)); } - } - `).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); + }, + }, + + // 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(); + // with(a) {a();} -> with(a) a(); + "While|For|WithStatement": { + 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 (object.type == "FunctionExpression") { - object.type = "ArrowFunctionExpression"; + let testValue = path.get("test").evaluateTruthy(); - 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(); - } + const parent = getParentFunctionOrProgram(path); + if (parent && (parent.node as NodeSymbol)[UNSAFE]) { + testValue = undefined; } - } - }; - } - /** - * ()=>{ 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); - } + 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]); } - body.forEach(flatten); - - object.body = SequenceExpression([ - ...clone(array), - UnaryExpression("void", Literal(0)), - ]); + if ( + alternate.node && + alternate.isBlock() && + alternate.node.body.length === 1 && + isMoveable(alternate.node.body[0]) + ) { + alternate.replaceWith(alternate.node.body[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; - } - }; - } + if (testValue === false) { + // if(false){} -> () + if (!alternate.node) { + path.remove(); + return; - // 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(false){a()}else{b()} -> b() + } else { + path.replaceWith(alternate.node); + return; + } - 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]; + // if(true){a()} -> {a()} + } else if (testValue === true) { + path.replaceWith(consequent.node); + return; } - }; - } - } - // 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); - }; - } - } - } - } + function getResult(path: NodePath): { + returnPath: NodePath | null; + expressions: t.Expression[]; + } { + if (!path.node) return null; - // 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([]); - } - } + if (path.isReturnStatement()) { + return { returnPath: path, expressions: [] }; + } + if (path.isExpressionStatement()) { + return { + returnPath: null, + expressions: [path.get("expression").node], + }; + } - 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 (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; + } + } - 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]); + return { returnPath: null, expressions: expressions }; + } - // 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") - ) - ) - ); + return null; } - // 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; + var consequentReturn = getResult(consequent); + var alternateReturn = getResult(alternate); - if ( - e1.type == "AssignmentExpression" && - e2.type == "AssignmentExpression" - ) { - if ( - e1.operator === e2.operator && - isEquivalent(e1.left, e2.left) + if (consequentReturn && alternateReturn) { + if (consequentReturn.returnPath && alternateReturn.returnPath) { + function createReturnArgument( + resultInfo: ReturnType ) { - this.replace( - object, - ExpressionStatement( - AssignmentExpression( - e1.operator, - e1.left, - ConditionalExpression( - clone(object.test), - e1.right, - e2.right - ) - ) + 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 + ) { + 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, + joinExpressions(consequentReturn.expressions), + joinExpressions(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": { + enter(path) { + if (path.isProgram()) { + path.scope.crawl(); + } + }, + exit(path) { + var statementList = path.isBlock() + ? (path.get("body") as NodePath[]) + : (path.get("consequent") as NodePath[]); - // 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 - // ); - } - } + var impliedReturn: NodePath; - 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)) - ); - } - } - } + function isUnreachable( + statementList: NodePath[], + topLevel = false + ) { + var unreachableState = false; - // { "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; - } + for (var statement of statementList) { + if (unreachableState) { + statement.remove(); + continue; + } - 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 (statement.isIfStatement()) { + const consequent = statement.get("consequent"); + const alternate = statement.get("alternate"); - if (object.type == "VariableDeclarator") { - // undefined is not necessary - if (object.init && object.init.type == "Identifier") { - if (object.init.name == "undefined") { - object.init = null; - } - } + if ( + [consequent, alternate].every( + (x) => + x.node && + x.isBlockStatement() && + isUnreachable(x.get("body")) + ) + ) { + unreachableState = true; + if (!topLevel) { + return true; + } else { + continue; + } + } + } - 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; - } - } - } + 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; + } + } + } - // 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 ( + statement.isReturnStatement() || + statement.isThrowStatement() || + statement.isBreakStatement() || + statement.isContinueStatement() + ) { + unreachableState = true; + if (!topLevel) { + return true; + } + } - 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 (topLevel) { + if ( + statement == statementList.at(-1) && + statement.isReturnStatement() && + !statement.node.argument + ) { + impliedReturn = statement; + } + } + } + return false; + } - if (object.type == "UnaryExpression" && object.operator == "!") { - if (object.argument.type == "Literal" && !object.argument.regex) { - this.replace(object, Literal(!object.argument.value)); - } - } + isUnreachable(statementList, true); - if (object.type == "ConditionalExpression") { - if (object.test.type == "Literal" && !object.test.regex) { - this.replace( - object, - object.test.value ? object.consequent : object.alternate - ); - } - } - } -} + if (impliedReturn) { + var functionParent = path.getFunctionParent(); + if ( + functionParent && + t.isBlockStatement(functionParent.node.body) && + functionParent.node.body === path.node + ) { + impliedReturn.remove(); + } + } + }, + }, + }, + }; +}; diff --git a/src/transforms/opaquePredicates.ts b/src/transforms/opaquePredicates.ts index e6e5b72..886e341 100644 --- a/src/transforms/opaquePredicates.ts +++ b/src/transforms/opaquePredicates.ts @@ -1,242 +1,100 @@ -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; +import { PluginArg, PluginObject } from "./plugin"; +import { Order } from "../order"; +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import { chance, getRandomString } from "../utils/random-utils"; +import PredicateGen from "../utils/PredicateGen"; + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.OpaquePredicates, { + changeData: { + opaquePredicates: 0, + }, + }); + + const predicateGen = new PredicateGen(me); + + function createTruePredicate(path: NodePath) { + return predicateGen.generateTrueExpression(path); } - if (testTypes.has(parents[0].type) && parents[0].test === object) { - return true; - } - - return false; -} + let active = true; + let transformCount = 0; + function shouldTransform(path: NodePath) { + if (!active) return false; + if (path.find((p) => me.isSkipped(p))) 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; + if (!me.computeProbabilityMap(me.options.opaquePredicates)) return false; - predicateName: string; - predicate: Node; - predicates: { [name: string]: Node }; + transformCount++; - gen: ReturnType; - made: number; + const depth = path.getAncestry().length; - constructor(o) { - super(o, ObfuscateOrder.OpaquePredicates); - - this.predicates = Object.create(null); - this.gen = this.getGenerator(); - this.made = 0; + return chance(500 - transformCount - depth * 100); } - match(object: Node, parents: Node[]) { - return ( - (isTestExpression(object, parents) || object.type == "SwitchCase") && - !parents.find((x) => x.$multiTransformSkip || x.type == "AwaitExpression") + function wrapWithPredicate(path: NodePath) { + let newExpression = t.logicalExpression( + "&&", + createTruePredicate(path), + path.node as t.Expression ); + + me.changeData.opaquePredicates++; + + path.replaceWith(me.skip(newExpression)); } - 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)), - ] - ), - [] - ) + return { + 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))), + ]) ) - ) - ); - } - - 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)); - } - } - }; - } -} + + me.skip(path); + }, + }, + }, + }; +}; diff --git a/src/transforms/pack.ts b/src/transforms/pack.ts new file mode 100644 index 0000000..1fa225a --- /dev/null +++ b/src/transforms/pack.ts @@ -0,0 +1,239 @@ +import * as t from "@babel/types"; +import Obfuscator from "../obfuscator"; +import Template from "../templates/template"; +import { + isDefiningIdentifier, + isModifiedIdentifier, + isVariableIdentifier, +} from "../utils/ast-utils"; +import { + GEN_NODE, + NodeSymbol, + reservedIdentifiers, + reservedNodeModuleIdentifiers, + variableFunctionName, + WITH_STATEMENT, +} from "../constants"; +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, + }, + }); + + // 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[] = []; + + return { + // Transform identifiers, preserve import statements + visitor: { + ImportDeclaration(path) { + prependNodes.push(path.node); + path.remove(); + + // 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(); + }, + + Identifier: { + exit(path) { + if (!isVariableIdentifier(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 (reservedIdentifiers.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; + + if (!path.scope.hasGlobal(identifierName)) return; + if (path.scope.hasBinding(identifierName)) return; + + // Check user's custom implementation + if (!me.computeProbabilityMap(me.options.pack, identifierName)) + return; + + if ( + path.key === "argument" && + path.parentPath.isUnaryExpression({ operator: "typeof" }) + ) { + const unaryExpression = path.parentPath; + + let propertyName = typeofMappings.get(identifierName); + if (!propertyName) { + propertyName = me.obfuscator.nameGen.generate(); + typeofMappings.set(identifierName, propertyName); + } + + unaryExpression.replaceWith( + t.memberExpression( + t.identifier(objectName), + t.stringLiteral(propertyName), + 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); + } + + path.replaceWith( + t.memberExpression( + t.identifier(objectName), + t.stringLiteral(propertyName), + true + ) + ); + }, + }, + }, + + // 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[] = []; + + 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( + "set", + t.stringLiteral(propertyName), + [t.identifier(objectName)], + t.blockStatement([ + t.returnStatement( + t.assignmentExpression( + "=", + t.identifier(identifierName), + 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), + [], + t.blockStatement([ + t.returnStatement( + t.unaryExpression("typeof", t.identifier(identifierName)) + ), + ]) + ) + ); + } + + const objectExpression = t.objectExpression(objectProperties); + + // 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, + + 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, + }); + + return newAST; + }, + }; +} diff --git a/src/transforms/plugin.ts b/src/transforms/plugin.ts new file mode 100644 index 0000000..6203a32 --- /dev/null +++ b/src/transforms/plugin.ts @@ -0,0 +1,173 @@ +import { NodePath, Visitor } from "@babel/traverse"; +import Obfuscator from "../obfuscator"; +import { getRandomString } from "../utils/random-utils"; +import { Order } from "../order"; +import * as t from "@babel/types"; +import { FN_LENGTH, NodeSymbol, SKIP } from "../constants"; +import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate"; +import { prepend, prependProgram } from "../utils/ast-utils"; +import { numericLiteral } from "../utils/node"; + +export interface PluginObject { + visitor?: Visitor; + finalASTHandler?: (ast: t.File) => t.File; + + post?: () => void; +} + +export type PluginArg = { + 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 + ) { + this.computeProbabilityMap = obfuscator.computeProbabilityMap.bind( + this.obfuscator + ); + } + + public changeData: { [key: string]: number } = {}; + public computeProbabilityMap: Obfuscator["computeProbabilityMap"]; + + get name() { + return this.pluginOptions.name || "unnamed"; + } + + get order() { + return this.pluginOptions.order; + } + + get options() { + return this.obfuscator.options; + } + + get globalState() { + return this.obfuscator.globalState; + } + + skip(path: NodePath | T | NodePath[]): T { + 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; + + return node; + } + } + + /** + * 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; + setFunctionLength(path: NodePath, originalLength: number) { + (path.node as NodeSymbol)[FN_LENGTH] = originalLength; + + // 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, + }) + ) + ); + } + + 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), + createCallArguments(t.identifier(path.node.id.name)) + ) + ) + ); + } else if ( + t.isFunctionExpression(path.node) || + t.isArrowFunctionExpression(path.node) + ) { + path.replaceWith( + t.callExpression( + t.identifier(this.setFunctionLengthName), + createCallArguments(path.node) + ) + ); + } else { + // TODO + } + } + + /** + * 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 : ""); + } + + /** + * 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/src/transforms/preparation.ts b/src/transforms/preparation.ts index 31f7f20..c06132d 100644 --- a/src/transforms/preparation.ts +++ b/src/transforms/preparation.ts @@ -1,19 +1,21 @@ -import Transform from "./transform"; - +import { NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; +import * as t from "@babel/types"; +import { Order } from "../order"; +import { + NodeSymbol, + PREDICTABLE, + UNSAFE, + variableFunctionName, +} from "../constants"; +import { ok } from "assert"; 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"; + getParentFunctionOrProgram, + getPatternIdentifierNames, + isVariableIdentifier, +} 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. @@ -29,226 +31,317 @@ import { variableFunctionName } from "../constants"; * - `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"; +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.Preparation); + + const markFunctionUnsafe = (path: NodePath) => { + const functionPath = path.findParent( + (path) => path.isFunction() || path.isProgram() + ); + if (!functionPath) return; + + const functionNode = functionPath.node; + + (functionNode as NodeSymbol)[UNSAFE] = true; + }; + + return { + visitor: { + "ThisExpression|Super": { + exit(path) { + markFunctionUnsafe(path); + }, + }, + + // @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(); } - function isBreakableStatement(x) { - return isLoop(x) || (o.label && x.type == "BlockStatement"); + }, + }, + + // `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; } - var fn = - o.type == "ContinueStatement" - ? isContinuableStatement - : isBreakableStatement; + 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 + ); - var loop = p.find(fn); - if (object == loop) { - if (!o.label) { - o.label = Identifier(label); + // Add the next quasi (template string part) + if (quasis[i + 1].value.cooked !== "") { + binaryExpression = t.binaryExpression( + "+", + binaryExpression, + t.stringLiteral(quasis[i + 1].value.cooked) + ); } } - } - }); - - // 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; - } - } -} + + // 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; + if (["arguments", "eval"].includes(name)) { + markFunctionUnsafe(path); + } + + // When Rename Variables is disabled, __JS_CONFUSER_VAR__ must still be removed + if ( + !me.obfuscator.hasPlugin(Order.RenameVariables) && + isVariableFunctionIdentifier(path) + ) { + 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)); + } + }, + }, + + 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; + var maxArgLength = 0; + + for (var referencePath of binding.referencePaths) { + if (!referencePath.parentPath.isCallExpression()) { + predictable = false; + break; + } + + var argsPath = referencePath.parentPath.get("arguments"); + for (var arg of argsPath) { + if (arg.isSpreadElement()) { + predictable = false; + break; + } + } + + if (argsPath.length > maxArgLength) { + maxArgLength = argsPath.length; + } + } + + var definedArgLength = path.get("params").length; + if (predictable && definedArgLength >= maxArgLength) { + (path.node as NodeSymbol)[PREDICTABLE] = true; + } + }, + }, + + // 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; + } + }, + }, + + // { key: true } -> { "key": true } + "Property|Method": { + exit(_path) { + let path = _path as NodePath; + + 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; + } + }, + }, + + // var a,b,c -> var a; var b; var c; + VariableDeclaration: { + exit(path) { + if (path.node.declarations.length > 1) { + // 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, i) => { + var names = Array.from( + getPatternIdentifierNames(path.get("declarations")[i]) + ); + names.forEach((name) => { + path.scope.removeBinding(name); + }); + + var newNode = t.variableDeclaration(path.node.kind, [ + declaration, + ]); + return newNode; + }) + ) + .forEach((newPath) => { + if (newPath.node.kind === "var") { + var functionOrProgram = + getParentFunctionOrProgram(newPath); + functionOrProgram.scope.registerDeclaration(newPath); + } + newPath.scope.registerDeclaration(newPath); + }); + } + } + } + }, + }, + + // () => 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]); + } + + 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(); } + // 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/renameLabels.ts b/src/transforms/renameLabels.ts index 7b8bc55..fade4ab 100644 --- a/src/transforms/renameLabels.ts +++ b/src/transforms/renameLabels.ts @@ -1,77 +1,175 @@ -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)); - } - }; - } -} +import * as t from "@babel/types"; +import { NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; +import { Order } from "../order"; +import { NameGen } from "../utils/NameGen"; +import { ok } from "assert"; + +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): PluginObject { + const me = Plugin(Order.RenameLabels, { + changeData: { + labelsRenamed: 0, + labelsRemoved: 0, + }, + }); + + return { + visitor: { + Program(path) { + const allLabelInterfaces: LabelInterface[] = []; + + // First pass: Collect all label usages + path.traverse({ + LabeledStatement(labelPath) { + const labelInterface = { + label: labelPath.node.label.name, + removed: false, + required: false, + paths: [], + }; + allLabelInterfaces.push(labelInterface); + (labelPath.node as NodeLabel)[LABEL] = labelInterface; + }, + "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); + } + }, + }); + + const nameGen = new NameGen(me.options.identifierGenerator); + + for (var labelInterface of allLabelInterfaces) { + const isRequired = labelInterface.required; + if (isRequired) { + var newName = labelInterface.label; + if ( + me.computeProbabilityMap( + me.options.renameLabels, + labelInterface.label + ) + ) { + newName = nameGen.generate(); + } + labelInterface.renamed = newName; + me.changeData.labelsRenamed++; + } else { + labelInterface.removed = true; + me.changeData.labelsRemoved++; + } + } + + // Second pass: Rename labels and remove unused ones + path.traverse({ + LabeledStatement(labelPath) { + 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); + } + + // Else keep the label but rename it + if (typeof labelInterface.renamed === "string") { + labelPath.node.label.name = labelInterface.renamed; + } + + // 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/src/transforms/rgf.ts b/src/transforms/rgf.ts index b2cd1f6..8a1c4de 100644 --- a/src/transforms/rgf.ts +++ b/src/transforms/rgf.ts @@ -1,424 +1,322 @@ -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; - } - }; - } -} +import { NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; +import { Order } from "../order"; +import * as t from "@babel/types"; +import Obfuscator from "../obfuscator"; +import { + append, + getFunctionName, + isDefiningIdentifier, + isStrictMode, + isVariableIdentifier, + prepend, +} from "../utils/ast-utils"; +import { + MULTI_TRANSFORM, + 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"; + +const RGF_ELIGIBLE = Symbol("rgfEligible"); + +/** + * 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): 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: { + enter(path) { + 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", [ + t.variableDeclarator( + t.identifier(rgfArrayName), + rgfArrayExpression + ), + ]) + ); + + var rgfEvalIntegrity = me.getPlaceholder() + "_rgf_eval_integrity"; + + prepend( + path, + 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": { + 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] || p.node[MULTI_TRANSFORM])) + return; + + // Skip async and generator functions + if (path.node.async || path.node.generator) return; + + const name = getFunctionName(path); + if (name === me.options.lock?.countermeasures) return; + if (me.obfuscator.isInternalVariable(name)) return; + + if ( + !me.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({ + 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 = idPath.scope.getBinding(name); + if (!binding) { + // 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 (isOutsideVariable) { + identifierPreventingTransform = name; + idPath.stop(); + } + }, + }); + + if (identifierPreventingTransform) { + me.log( + "Skipping function " + + name + + " due to reference to outside variable: " + + identifierPreventingTransform + ); + 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 argumentsName = me.getPlaceholder() + "_args"; + + const lastNode = t.expressionStatement(t.identifier(embeddedName)); + (lastNode as NodeSymbol)[SKIP] = true; + + // Transform the function + const evalProgram: t.Program = t.program([ + t.functionDeclaration( + t.identifier(embeddedName), + [], + t.blockStatement([ + t.variableDeclaration("var", [ + t.variableDeclarator( + t.arrayPattern([ + t.identifier(rgfArrayName), + t.identifier(argumentsName), + ]), + t.identifier("arguments") + ), + ]), + 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.thisExpression(), t.identifier(argumentsName)] + ) + ), + ]) + ), + lastNode, + ]); + + 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 + .filter((plugin, i) => { + return i <= me.obfuscator.index; + }) + .map((plugin) => 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); + + const generated = Obfuscator.generateCode(evalFile); + + var functionExpression = t.callExpression(t.identifier(rgfEvalName), [ + t.stringLiteral(generated), + ]); + + var index = rgfArrayExpression.elements.length; + 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; + // Params changed and using 'arguments' + (path.node as NodeSymbol)[PREDICTABLE] = false; + me.skip(path); + + // Update body to point to new function + path + .get("body") + .replaceWith( + t.blockStatement([ + t.returnStatement( + t.callExpression( + t.memberExpression( + t.memberExpression( + t.identifier(rgfArrayName), + numericLiteral(index), + true + ), + t.stringLiteral("apply"), + true + ), + [ + t.thisExpression(), + t.arrayExpression([ + t.identifier(rgfArrayName), + t.identifier("arguments"), + ]), + ] + ) + ), + ]) + ); + + path.skip(); + + me.setFunctionLength(path, originalLength); + + me.changeData.functions++; + }, + }, + }, + }; +}; diff --git a/src/transforms/shuffle.ts b/src/transforms/shuffle.ts index 86e0a07..e72b708 100644 --- a/src/transforms/shuffle.ts +++ b/src/transforms/shuffle.ts @@ -1,254 +1,82 @@ -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 { PluginArg, PluginObject } from "./plugin"; +import * as t from "@babel/types"; +import { getRandomInteger } from "../utils/random-utils"; +import Template from "../templates/template"; +import { Order } from "../order"; +import { isStaticValue } from "../utils/static-utils"; +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, { + changeData: { + arrays: 0, + }, + }); + + let fnName: string | null = null; + + return { + visitor: { + ArrayExpression: { + exit(path) { + if (path.node.elements.length <= 3) { + return; + } + var illegalElement = path.node.elements.find( + (element) => !isStaticValue(element) + ); + + if (illegalElement) return; + + 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) + ); + + var shiftedElements = [...path.node.elements]; + for (var i = 0; i < shift; i++) { + shiftedElements.unshift(shiftedElements.pop()); + } + + path.replaceWith( + t.callExpression(t.identifier(fnName), [ + t.arrayExpression(shiftedElements), + numericLiteral(shift), + ]) + ); + + path.skip(); + + me.changeData.arrays++; + }, + }, + }, + }; +}; diff --git a/src/transforms/stack.ts b/src/transforms/stack.ts deleted file mode 100644 index c5f5573..0000000 --- a/src/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/transforms/string/encoding.ts b/src/transforms/string/encoding.ts index dfb9a88..3ea90dc 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 { 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; -} +import { shuffle } from "../../utils/random-utils"; +import * as t from "@babel/types"; -let _hasAllEncodings = false; -export function hasAllEncodings() { - return _hasAllEncodings; -} +let hasAllEncodings = false; -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; @@ -111,8 +99,8 @@ export function createEncodingImplementation(): EncodingImplementation { return Buffer.from(ret).toString("utf-8"); }, - template: new Template(` - function {__fnName__}(str){ + code: new Template(` + function {fnName}(str){ var table = {__strTable__}; var raw = "" + (str || ""); @@ -145,106 +133,12 @@ export function createEncodingImplementation(): EncodingImplementation { ret.push((b | (v << n)) & 0xff); } - return {__bufferToString__}(ret); + return {__bufferToStringFunction__}(ret); } `).setDefaultVariables({ - __strTable__: Literal(strTable), + __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 11b440e..26f9679 100644 --- a/src/transforms/string/stringCompression.ts +++ b/src/transforms/string/stringCompression.ts @@ -1,309 +1,128 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; -import { ComputeProbabilityMap } from "../../probability"; -import Template from "../../templates/template"; -import { isDirective, isModuleSource } from "../../util/compare"; +import { PluginArg, PluginObject } from "../plugin"; +import * as t from "@babel/types"; +import { Order } from "../../order"; 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"; + ensureComputedExpression, + isModuleImport, + prependProgram, +} from "../../utils/ast-utils"; +import { numericLiteral } from "../../utils/node"; 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 - ); - }; - } -} + StringCompressionLibraryMinified, + StringCompressionTemplate, +} from "../../templates/stringCompressionTemplate"; +import Obfuscator from "../../obfuscator"; +import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; +import { NO_RENAME } from "../../constants"; +const LZString = require("lz-string"); + +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.StringCompression, { + changeData: { + strings: 0, + }, + }); + + // 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 { + visitor: { + Program: { + exit(programPath) { + const stringFn = me.getPlaceholder() + "_SC"; + const stringMap = new Map(); + + // Find all the strings + 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 + 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 + if ( + !me.computeProbabilityMap( + me.options.stringCompression, + originalValue + ) + ) { + return; + } + + index = stringMap.size; + stringMap.set(originalValue, index); + } + + me.changeData.strings++; + + ensureComputedExpression(path); + + path.replaceWith( + t.callExpression(t.identifier(stringFn), [ + numericLiteral(index), + ]) + ); + }, + }, + }); + + // No strings changed + if (stringMap.size === 0) return; + + var stringPayload = Array.from(stringMap.keys()).join( + stringDelimiter + ); + + // Compress the string + var compressedString = LZString.compressToUTF16(stringPayload); + + let stringCompressionLibraryName = + me.obfuscator.getStringCompressionLibraryName(); + let insertStringCompressionLibrary = !me.obfuscator.parentObfuscator; + + prependProgram( + programPath, + StringCompressionTemplate.compile({ + stringFn, + stringName: me.getPlaceholder(), + stringArray: me.getPlaceholder(), + stringDelimiter: () => t.stringLiteral(stringDelimiter), + stringValue: () => t.stringLiteral(compressedString), + GetGlobalTemplate: createGetGlobalTemplate(me, programPath), + getGlobalFnName: me.getPlaceholder(), + StringCompressionLibrary: stringCompressionLibraryName, + }) + ); + + if (insertStringCompressionLibrary) { + // RGF functions should not clone the entire decompression function + prependProgram( + programPath, + Obfuscator.parseCode( + StringCompressionLibraryMinified.replace( + /{StringCompressionLibrary}/g, + stringCompressionLibraryName + ) + ).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 db07308..f7e3595 100644 --- a/src/transforms/string/stringConcealing.ts +++ b/src/transforms/string/stringConcealing.ts @@ -1,380 +1,312 @@ -import { ok } from "assert"; -import { ObfuscateOrder } from "../../order"; +import * as t from "@babel/types"; +import { NodePath } from "@babel/traverse"; import Template from "../../templates/template"; -import { getBlock } from "../../traverse"; -import { isDirective, isModuleSource } from "../../util/compare"; +import { PluginArg, PluginObject } from "../plugin"; +import { Order } from "../../order"; +import { ok } from "assert"; +import { BufferToStringTemplate } from "../../templates/bufferToStringTemplate"; +import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate"; import { - ArrayExpression, - CallExpression, - Identifier, - Literal, - MemberExpression, - Node, - ObjectExpression, - Property, - VariableDeclaration, - VariableDeclarator, -} from "../../util/gen"; -import { append, prepend } from "../../util/insert"; + ensureComputedExpression, + isModuleImport, + prepend, + prependProgram, +} from "../../utils/ast-utils"; 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; +} from "../../utils/random-utils"; +import { CustomStringEncoding } from "../../options"; +import { createDefaultStringEncoding } from "./encoding"; +import { numericLiteral } from "../../utils/node"; +import { NO_REMOVE } from "../../constants"; + +interface StringConcealingInterface { + encodingImplementation: CustomStringEncoding; 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[] = []; +const STRING_CONCEALING = Symbol("StringConcealing"); - constructor(o) { - super(o, ObfuscateOrder.StringConcealing); +interface NodeStringConcealing { + [STRING_CONCEALING]?: StringConcealingInterface; +} - this.set = new Set(); - this.index = Object.create(null); - this.arrayExpression = ArrayExpression([]); - this.gen = this.getGenerator(); - } +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.StringConcealing, { + changeData: { + strings: 0, + decryptionFunctions: 0, + }, + }); - apply(tree) { - super.apply(tree); + const blocks: NodePath[] = []; + const stringMap = new Map(); + const stringArrayName = me.getPlaceholder() + "_array"; + const stringArrayCacheName = me.getPlaceholder() + "_cache"; - // 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(); + let encodingImplementations: { [identity: string]: CustomStringEncoding } = + Object.create(null); - varDeclaration.declarations[0].init.body.body.push(...ifStatements); + let availableStringEncodings = me.options.customStringEncodings; - prepend(block, varDeclaration); - } - - prepend( - tree, - VariableDeclaration([ - VariableDeclarator(cacheName, ArrayExpression([])), - VariableDeclarator(this.arrayName, this.arrayExpression), - ]) - ); + // If no custom encodings are provided, use the default encoding + if (!availableStringEncodings || availableStringEncodings.length === 0) { + availableStringEncodings = [createDefaultStringEncoding]; } - 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)*/ - ); + function hasAllEncodings() { + return availableStringEncodings.length === 0; } - 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]; + function createStringEncoding(): CustomStringEncoding { + var encodingIndex = getRandomInteger(0, availableStringEncodings.length); + var encoding = availableStringEncodings[encodingIndex]; - this.set.add(object.value); - } + if (typeof encoding === "function") { + encoding = encoding(encodingImplementations); - ok(index != -1, "index == -1"); + var duplicateIdentity = + typeof encoding.identity !== "undefined" && + typeof encodingImplementations[encoding.identity] !== "undefined"; - var callExpr = CallExpression(Identifier(fnName), [Literal(index)]); + if (duplicateIdentity || encoding === null) { + // Null returned -> All encodings have been created + // Duplicate identity -> Most likely all encodings have been created - // use `.apply` to fool automated de-obfuscators - if (chance(10)) { - callExpr = CallExpression( - MemberExpression(Identifier(fnName), Literal("apply"), true), - [Identifier("undefined"), ArrayExpression([Literal(index)])] + // No longer create new encodings using this function + availableStringEncodings = availableStringEncodings.filter( + (x) => x !== encoding ); - } - - // 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"; + // Return a random encoding already made + encoding = choice(Object.values(encodingImplementations)); + ok(encoding, "Failed to create main string encoding"); } + } - var newExpr: Node = callExpr; - - if (referenceType === "constantReference") { - // Define the string earlier, reference the name here - this.variablesMade++; + if (typeof encoding.identity === "undefined") { + encoding.identity = encodingIndex.toString(); + } - var constantReferenceType = choice(["variable", "array", "object"]); + if (typeof encoding.code === "string") { + encoding.code = new Template(encoding.code); + } - var place = currentBlock; - if (!place) { - this.error(new Error("No lexical block to insert code")); - } + me.changeData.decryptionFunctions++; + encodingImplementations[encoding.identity] = encoding; - switch (constantReferenceType) { - case "variable": - var name = this.getPlaceholder(); + return encoding; + } - prepend( - place, - VariableDeclaration(VariableDeclarator(name, callExpr)) + return { + visitor: { + Program: { + exit(programPath: NodePath) { + let mainEncodingImplementation: CustomStringEncoding; + + // Create a main encoder function for the Program + (programPath.node as NodeStringConcealing)[STRING_CONCEALING] = { + encodingImplementation: (mainEncodingImplementation = + createStringEncoding()), + 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 ); - - newExpr = Identifier(name); - break; - case "array": - if (!place.$stringConcealingArray) { - place.$stringConcealingArray = ArrayExpression([]); - place.$stringConcealingArrayName = this.getPlaceholder(); - - prepend( - place, - VariableDeclaration( - VariableDeclarator( - place.$stringConcealingArrayName, - place.$stringConcealingArray + } + + 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 ( + !me.computeProbabilityMap( + me.options.stringConcealing, + originalValue ) - ) - ); - } - - 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 + ) { + 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 = createStringEncoding(); + 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 + ); + + // 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") { + index = stringMap.size; + stringMap.set(encodedValue, index); + } + + me.changeData.strings++; + + // 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), + [numericLiteral(index)] ) + ); + + // Skip the transformation for the newly created node + path.skip(); + }, + }, + }); + + const bufferToStringName = me.getPlaceholder() + "_bufferToString"; + const getGlobalFnName = me.getPlaceholder() + "_getGlobal"; + + const bufferToStringCode = BufferToStringTemplate.compile({ + GetGlobalTemplate: createGetGlobalTemplate(me, programPath), + getGlobalFnName: getGlobalFnName, + BufferToString: bufferToStringName, + }); + + prependProgram(programPath, bufferToStringCode); + + // Create the string array + prependProgram( + programPath, + t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier(stringArrayName), + t.arrayExpression( + Array.from(stringMap.keys()).map((x) => t.stringLiteral(x)) ) - ); - } - - 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; - } - } + // Create the string cache + prependProgram( + programPath, + new Template(` + var {stringArrayCacheName} = {}; + `).single({ + stringArrayCacheName, + }) + ); - this.replaceIdentifierOrLiteral(object, newExpr, parents); - }; - } -} + for (var block of blocks) { + const { encodingImplementation, fnName } = ( + block.node as NodeStringConcealing + )[STRING_CONCEALING] as StringConcealingInterface; + + const decodeFnName = fnName + "_decode"; + + ok(encodingImplementation.code instanceof Template); + + // The decoder function + 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) { + if (typeof ${stringArrayCacheName}[index] === 'undefined') { + return ${stringArrayCacheName}[index] = ${decodeFnName}(${stringArrayName}[index]); + } + return ${stringArrayCacheName}[index]; + } + `) + .addSymbols(NO_REMOVE) + .single(); + + prepend(block, [...decoder, retrieveFunctionDeclaration]); + } + }, + }, + }, + }; +}; diff --git a/src/transforms/string/stringEncoding.ts b/src/transforms/string/stringEncoding.ts index 04418e9..f905bb4 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 { PluginInstance, PluginObject } from "../plugin"; +import * as t from "@babel/types"; +import { choice } from "../../utils/random-utils"; +import { GEN_NODE, NodeSymbol } from "../../constants"; +import { isModuleImport } from "../../utils/ast-utils"; function pad(x: string, len: number): string { while (x.length < len) { @@ -46,50 +46,35 @@ 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); - } +export default (me: PluginInstance): PluginObject => { + return { + visitor: { + StringLiteral: { + exit(path) { + // Ignore module imports + if (isModuleImport(path)) return; - match(object, parents) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length > 0 && - !isModuleSource(object, parents) && - !isDirective(object, parents) - ); - } + const { value } = path.node; - transform(object, parents) { - // Allow percentages - if ( - !ComputeProbabilityMap( - this.options.stringEncoding, - (x) => x, - object.value - ) - ) - return; + // Allow percentages + if (!me.computeProbabilityMap(me.options.stringEncoding, value)) + return; - var type = choice(["hexadecimal", "unicode"]); + var type = choice(["hexadecimal", "unicode"]); - var escapedString = ( - type == "hexadecimal" ? toHexRepresentation : toUnicodeRepresentation - )(object.value); + var escapedString = ( + type == "hexadecimal" + ? toHexRepresentation + : toUnicodeRepresentation + )(value); - return () => { - if (object.type !== "Literal") return; + var id = t.identifier(`"${escapedString}"`); - // ESCodeGen tries to escape backslashes, here is a work-around - this.replace(object, 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 765ceb1..2378f36 100644 --- a/src/transforms/string/stringSplitting.ts +++ b/src/transforms/string/stringSplitting.ts @@ -1,86 +1,77 @@ -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 { PluginArg, PluginObject } from "../plugin"; +import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils"; +import { binaryExpression, stringLiteral } from "@babel/types"; import { ok } from "assert"; -import { ComputeProbabilityMap } from "../../probability"; +import { Order } from "../../order"; +import { + ensureComputedExpression, + isModuleImport, +} from "../../utils/ast-utils"; -export default class StringSplitting extends Transform { - joinPrototype: string; - strings: { [value: string]: string }; +export default ({ Plugin }: PluginArg): PluginObject => { + const me = Plugin(Order.StringSplitting, { + changeData: { + strings: 0, + }, + }); - adders: Node[][]; - vars: Node[]; + return { + visitor: { + StringLiteral: { + exit(path) { + var object = path.node; - constructor(o) { - super(o, ObfuscateOrder.StringSplitting); + // Don't change module imports + if (isModuleImport(path)) return; - this.joinPrototype = null; - this.strings = Object.create(null); - - this.adders = []; - this.vars = []; - } + var size = Math.round( + Math.max(6, object.value.length / getRandomInteger(3, 8)) + ); + if (object.value.length <= size) { + return; + } - match(object: Node, parents: Node[]) { - return ( - object.type == "Literal" && - typeof object.value === "string" && - object.value.length >= 8 && - !isModuleSource(object, parents) && - !isDirective(object, parents) - ); - } + var chunks = splitIntoChunks(object.value, size); + if (!chunks || chunks.length <= 1) { + return; + } - 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; - } + if ( + !me.computeProbabilityMap(me.options.stringSplitting, object.value) + ) { + return; + } - var chunks = splitIntoChunks(object.value, size); - if (!chunks || chunks.length <= 1) { - return; - } + 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); + } + }); - if ( - !ComputeProbabilityMap( - this.options.stringSplitting, - (x) => x, - object.value - ) - ) { - return; - } + parent.right = stringLiteral(last); - 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); - } - }); + me.changeData.strings++; - parent.right = Literal(last); + ensureComputedExpression(path); - this.replaceIdentifierOrLiteral(object, parent, parents); - }; - } -} + path.replaceWith(parent); + path.skip(); + }, + }, + }, + }; +}; diff --git a/src/transforms/transform.ts b/src/transforms/transform.ts deleted file mode 100644 index c8f318d..0000000 --- a/src/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/transforms/variableMasking.ts b/src/transforms/variableMasking.ts new file mode 100644 index 0000000..437d7d6 --- /dev/null +++ b/src/transforms/variableMasking.ts @@ -0,0 +1,257 @@ +import { Binding, NodePath } from "@babel/traverse"; +import { PluginArg, PluginObject } from "./plugin"; +import * as t from "@babel/types"; +import Template from "../templates/template"; +import { Order } from "../order"; +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 + if (fnPath.isObjectMethod() && fnPath.node.kind !== "method") { + return; + } + + // Do not apply to class getters/setters + if (fnPath.isClassMethod() && fnPath.node.kind !== "method") { + return; + } + + // Do not apply to async or generator functions + if (fnPath.node.generator || fnPath.node.async) { + return; + } + + // Do not apply to functions with rest parameters or destructuring + if (fnPath.node.params.some((param) => !t.isIdentifier(param))) { + return; + } + + // Do not apply to 'use strict' functions + if (isStrictMode(fnPath)) return; + + // Do not apply to functions marked unsafe + if ((fnPath.node as NodeSymbol)[UNSAFE]) return; + + const functionName = getFunctionName(fnPath); + + if (!me.computeProbabilityMap(me.options.variableMasking, functionName)) { + return; + } + + const stackName = me.getPlaceholder() + "_varMask"; + const stackMap = new Map(); + const propertyGen = new NameGen("mangled"); + const stackKeys = new Set(); + let needsStack = false; + + 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({ + 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; + 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; + + 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; + } + + if (!checkBinding(binding)) { + illegalBindings.add(binding); + return; + } + + do { + index = choice([ + stackMap.size, + propertyGen.generate(), + getRandomInteger(-250, 250), + ]); + } while (!index || stackKeys.has(index.toString())); + + stackMap.set(binding, index); + stackKeys.add(index.toString()); + } + + const memberExpression = t.memberExpression( + t.identifier(stackName), + createLiteral(index), + true + ); + + if (isDefiningIdentifier(path)) { + replaceDefiningIdentifierToMemberExpression(path, memberExpression); + + return; + } + + ensureComputedExpression(path); + path.replaceWith(memberExpression); + }, + }); + + if (!needsStack) return; + + 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); + }, + }, + Program: { + enter(path) { + path.scope.crawl(); + }, + }, + }, + }; +}; diff --git a/src/traverse.ts b/src/traverse.ts deleted file mode 100644 index 1b8d401..0000000 --- a/src/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/types.ts b/src/types.ts deleted file mode 100644 index fc32bfd..0000000 --- a/src/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/util/compare.ts b/src/util/compare.ts deleted file mode 100644 index 6524b11..0000000 --- a/src/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/util/gen.ts b/src/util/gen.ts deleted file mode 100644 index 14f622e..0000000 --- a/src/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/util/guard.ts b/src/util/guard.ts deleted file mode 100644 index 9bbdbe5..0000000 --- a/src/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/util/identifiers.ts b/src/util/identifiers.ts deleted file mode 100644 index f1486f0..0000000 --- a/src/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/util/insert.ts b/src/util/insert.ts deleted file mode 100644 index ffae140..0000000 --- a/src/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/util/math.ts b/src/util/math.ts deleted file mode 100644 index 578f708..0000000 --- a/src/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/util/object.ts b/src/util/object.ts deleted file mode 100644 index 9b11404..0000000 --- a/src/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/util/random.ts b/src/util/random.ts deleted file mode 100644 index eb195dd..0000000 --- a/src/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/util/scope.ts b/src/util/scope.ts deleted file mode 100644 index 1fed3cc..0000000 --- a/src/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"); -} 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..b5249f4 --- /dev/null +++ b/src/utils/NameGen.ts @@ -0,0 +1,116 @@ +import { ok } from "assert"; +import { ObfuscateOptions } from "../options"; +import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils"; +import { + choice, + getRandomChineseString, + getRandomHexString, + getRandomInteger, +} from "./random-utils"; +import { reservedKeywords, reservedObjectPrototype } from "../constants"; +import Obfuscator from "../obfuscator"; + +/** + * 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", + public options = { + avoidReserved: false, + avoidObjectPrototype: false, + } + ) {} + + private attemptGenerate() { + if (typeof this.identifierGenerator === "function") { + var value = this.identifierGenerator(); + ok( + typeof value === "string", + "Custom identifier generator must return a string" + ); + return value; + } + + var mode = Obfuscator.prototype.computeProbabilityMap( + this.identifierGenerator + ); + + const randomizedLength = getRandomInteger(6, 8); + + switch (mode) { + 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": + var mangledName = ""; + do { + mangledName = alphabeticalGenerator(this.counter++); + } while (reservedKeywords.includes(mangledName)); + + return mangledName; + + case "number": + return "var_" + this.counter++; + + case "zeroWidth": + return this.zeroWidthGenerator.generate(); + + case "chinese": + return getRandomChineseString(randomizedLength); + + default: + throw new Error( + "Invalid identifier generator mode: " + this.identifierGenerator + ); + } + } + + generate(isSafeForReuse = true): string { + let name: string; + + do { + name = this.attemptGenerate(); + + // 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/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() + ); + } +} diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts new file mode 100644 index 0000000..e95f1be --- /dev/null +++ b/src/utils/ast-utils.ts @@ -0,0 +1,663 @@ +import * as t from "@babel/types"; +import { NodePath } from "@babel/traverse"; +import { ok } from "assert"; +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); + } + } + + return allNames; + } + 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 names; +} + +/** + * 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 + * - 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)) && + 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; + } + + 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) { + // 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 getBlock(path: NodePath) { + return path.find((p) => p.isBlock()) as 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() + ) as NodePath; + + ok(functionOrProgramPath); + return functionOrProgramPath; +} + +export function getObjectPropertyAsString( + property: t.ObjectMember | t.ClassProperty | t.ClassMethod +): string { + ok( + t.isObjectMember(property) || + t.isClassProperty(property) || + t.isClassMethod(property) + ); + + if (!property.computed && t.isIdentifier(property.key)) { + return property.key.name; + } + + if (t.isStringLiteral(property.key)) { + return property.key.value; + } + + if (t.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: t.MemberExpression +): string | null { + t.assertMemberExpression(member); + + const property = member.property; + + if (!member.computed && t.isIdentifier(property)) { + return property.name; + } + + if (t.isStringLiteral(property)) { + return property.value; + } + + if (t.isNumericLiteral(property)) { + return property.value.toString(); + } + + return null; // If the property cannot be determined +} + +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 lastExpression.insertBefore(nodes); + } + } + + if (listParent.isSwitchCase()) { + return 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 body.pushContainer("body", nodes); + } + + ok(listParent.isBlock()); + return listParent.pushContainer("body", nodes); +} + +/** + * 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 = 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()) { + // Preserve import declarations + // Filter out import declarations + const body = listParent.get("body"); + let afterImport = 0; + for (var stmt of body) { + if (!stmt.isImportDeclaration()) { + break; + } + afterImport++; + } + + if (afterImport === 0) { + // No import declarations, so we can safely unshift everything + return listParent.unshiftContainer("body", nodes); + } + + // Insert the nodes after the last import declaration + return body[afterImport - 1].insertAfter(nodes); + } + + if (listParent.isFunction()) { + var body = listParent.get("body"); + + if (listParent.isArrowFunctionExpression() && listParent.node.expression) { + if (!body.isBlockStatement()) { + body = body.replaceWith( + t.blockStatement([t.returnStatement(body.node as t.Expression)]) + )[0]; + } + } + + ok(body.isBlockStatement()); + + return body.unshiftContainer("body", nodes); + } + + if (listParent.isBlock()) { + return listParent.unshiftContainer("body", nodes); + } + + if (listParent.isSwitchCase()) { + return listParent.unshiftContainer("consequent", nodes); + } + + ok(false); +} + +export function prependProgram( + path: NodePath, + ...nodes: (t.Statement | t.Statement[])[] +) { + var program = path.find((p) => p.isProgram()); + ok(program); + ok(program.isProgram()); + 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. + * + * @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; + if ( + path.key === "local" && + (path.parentPath.isImportSpecifier() || + path.parentPath.isImportDefaultSpecifier() || + path.parentPath.isImportNamespaceSpecifier()) + ) + 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; +} + +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() { + * "use strict"; + * } // true + * @param path + * @returns + */ +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( + (directive) => directive.value.value === "use strict" + ); + } + + if (path.isFunction()) { + const fnBody = path.get("body"); + if (fnBody.isBlock()) { + return isStrictMode(fnBody); + } + } + + 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 | t.Identifier +) { + // 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 new file mode 100644 index 0000000..fcf8220 --- /dev/null +++ b/src/utils/function-utils.ts @@ -0,0 +1,50 @@ +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import { FN_LENGTH, NodeSymbol, variableFunctionName } from "../constants"; + +/** + * @example __JS_CONFUSER_VAR__(identifier) // true + * @param path + * @returns + */ +export function isVariableFunctionIdentifier(path: NodePath) { + if ( + path.isIdentifier() && + path.listKey === "arguments" && + path.key === 0 && + path.parentPath?.isCallExpression() + ) { + const callee = path.parentPath.get("callee"); + return callee.isIdentifier({ name: variableFunctionName }); + } + + 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 savedLength = (fnPath.node as NodeSymbol)[FN_LENGTH]; + if (typeof savedLength === "number") { + return savedLength; + } + + 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/src/utils/gen-utils.ts b/src/utils/gen-utils.ts new file mode 100644 index 0000000..8716b3d --- /dev/null +++ b/src/utils/gen-utils.ts @@ -0,0 +1,48 @@ +import { reservedKeywords } from "../constants"; +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 maxSize = 0; + var currentKeyWordsArray: string[] = []; + + function generateArray() { + var result = reservedKeywords + .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/node.ts b/src/utils/node.ts new file mode 100644 index 0000000..1eb01d0 --- /dev/null +++ b/src/utils/node.ts @@ -0,0 +1,78 @@ +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 + * @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); +} + +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).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; + } + + return deepClone(node); +} diff --git a/src/utils/object-utils.ts b/src/utils/object-utils.ts new file mode 100644 index 0000000..da6ec47 --- /dev/null +++ b/src/utils/object-utils.ts @@ -0,0 +1,21 @@ +/** + * Creates an object from the given keys and values arrays. + * @param keys + * @param values + */ +export function createObject( + keys: string[], + values: T[] +): { [key: string]: T } { + 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..3e2dfe0 --- /dev/null +++ b/src/utils/random-utils.ts @@ -0,0 +1,93 @@ +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: T[]): T[] { + array.sort(() => Math.random() - 0.5); + return array; +} + +/** + * Returns a random hexadecimal string. + * + * @example getRandomHexString(6) => "CA96BF" + * @param length + * @returns + */ +export function getRandomHexString(length: number) { + return [...Array(length)] + .map(() => Math.floor(Math.random() * 16).toString(16)) + .join("") + .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++) + characters.push( + String.fromCharCode( + Math.floor(Math.random() * (0x9fff - 0x4e00)) + 0x4e00 + ) + ); + return characters.join(""); +} + +/** + * 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: number, max: number) { + return Math.random() * (max - min) + min; +} + +export function getRandomInteger(min: number, max: number) { + 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; +} diff --git a/src/utils/static-utils.ts b/src/utils/static-utils.ts new file mode 100644 index 0000000..e288205 --- /dev/null +++ b/src/utils/static-utils.ts @@ -0,0 +1,66 @@ +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.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)) { + // 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 + 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); + } 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 new file mode 100644 index 0000000..635691d --- /dev/null +++ b/src/validateOptions.ts @@ -0,0 +1,259 @@ +import { ok } from "assert"; +import { ObfuscateOptions } from "./options"; +import presets from "./presets"; + +const validProperties = new Set([ + "preset", + "target", + "compact", + "hexadecimalNumbers", + "minify", + "renameVariables", + "renameGlobals", + "renameLabels", + "identifierGenerator", + "controlFlowFlattening", + "globalConcealing", + "stringCompression", + "stringConcealing", + "stringEncoding", + "stringSplitting", + "duplicateLiteralsRemoval", + "dispatcher", + "rgf", + "objectExtraction", + "flatten", + "deadCode", + "calculator", + "lock", + "movedDeclarations", + "opaquePredicates", + "shuffle", + "variableMasking", + "verbose", + "globalVariables", + "debugComments", + "preserveFunctionLength", + "astScrambler", + "customStringEncodings", + "functionOutlining", + "pack", +]); + +const validLockProperties = new Set([ + "selfDefending", + "antiDebug", + "tamperProtection", + "startDate", + "endDate", + "domainLock", + "integrity", + "countermeasures", + "customLocks", +]); + +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.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 domain-lock option + if ( + options.lock.domainLock && + typeof options.lock.domainLock !== "undefined" + ) { + ok(Array.isArray(options.lock.domainLock), "domainLock 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("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.hasOwnProperty("renameLabels")) { + options.renameLabels = true; // RenameLabels is on by default + } + + 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 + } + + 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 + if (!options.hasOwnProperty("globalVariables")) { + options.globalVariables = new Set([]); + + if (options.target == "browser") { + // browser + [ + "window", + "document", + "postMessage", + "alert", + "confirm", + "location", + ].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", + "RegExp", + "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", + "TextDecoder", + "TextEncoder", + "Uint8Array", + "Uint16Array", + "Uint32Array", + "Int8Array", + "Int16Array", + "Int32Array", + "ArrayBuffer", + "btoa", + "atob", + "unescape", + "encodeURIComponent", + ].forEach((x) => options.globalVariables.add(x)); + } + + return options; +} 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." +}); diff --git a/test/code/Cash.test.ts b/test/code/Cash.test.ts index 1c24955..fbad3a7 100644 --- a/test/code/Cash.test.ts +++ b/test/code/Cash.test.ts @@ -4,19 +4,13 @@ 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, { - target: "node", - preset: "high", - }); - - // Make the required document variables for initialization - var document = { +const handleError = (error, output) => { + var helperCode = `var document = { documentElement: {}, createElement: () => { return { style: {} }; }, - } as any as Document; + }; var window = { document, Array, @@ -30,28 +24,26 @@ test("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { RegExp, String, $: false, - } as any; + }; window.window = window; global.window = window; for (var key in window) { global[key] = window[key]; - } + }`; - try { - eval(output); - } catch (e) { - console.error(e); - writeFileSync("dev.output.js", output, { - encoding: "utf-8", - }); + console.error(error); + // writeFileSync("dev.output.js", helperCode + "\n" + output, { encoding: "utf-8", }); - expect(true).toStrictEqual(false); - } + expect("An error occurred").toStrictEqual(null); +}; - expect(window).toHaveProperty("cash"); -}); +test("Variant #1: Cash.js on High Preset (Strict Mode)", async () => { + var { code: output } = await JsConfuser.obfuscate(CASH_JS, { + target: "node", + preset: "high", + pack: true, + }); -test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Tamper Protection", async () => { // Make the required document variables for initialization var document = { documentElement: {}, @@ -79,27 +71,24 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta global[key] = window[key]; } - var output = await JsConfuser(CASH_JS, { - target: "node", - preset: "high", - rgf: true, - lock: { - integrity: true, - selfDefending: true, - tamperProtection: true, - }, - }); - 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) { - var helperCode = `var document = { + handleError(e, output); + } + + expect(window).toHaveProperty("cash"); +}); + +test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Tamper Protection", async () => { + // Make the required document variables for initialization + var document = { documentElement: {}, createElement: () => { return { style: {} }; }, - }; + } as any as Document; var window = { document, Array, @@ -113,19 +102,43 @@ test("Variant #2: Cash.js on High Preset + Integrity + Self Defending + RGF + Ta RegExp, String, $: false, - }; + } as any; window.window = window; global.window = window; for (var key in window) { global[key] = window[key]; - }`; + } + + const CountermeasuresCode = ` + function countermeasures() { + throw new Error("countermeasures() was called"); + } + `; - console.error(e); - writeFileSync("dev.output.js", helperCode + "\n" + output, { - encoding: "utf-8", - }); + var { code: output } = await JsConfuser.obfuscate( + CountermeasuresCode + CASH_JS, + { + target: "node", + preset: "high", + pack: true, + rgf: { + value: true, + limit: 10, + }, + lock: { + integrity: true, + selfDefending: true, + tamperProtection: true, + countermeasures: "countermeasures", + }, + } + ); - expect(true).toStrictEqual(false); + try { + // Pack option allows the code to be executed in a strict mode + eval(output); + } catch (e) { + handleError(e, output); } expect(window).toHaveProperty("cash"); diff --git a/test/code/Dynamic.test.ts b/test/code/Dynamic.test.ts index 59a743d..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 output = await JsConfuser(SOURCE_JS, { + 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 output = await JsConfuser(SOURCE_JS, options); - - // writeFileSync("./dev.error.1.js", output, "utf-8"); - - var doublyObfuscated = await JsConfuser(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.src.js b/test/code/ES6.src.js index 5c2611a..1f5d897 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,39 @@ function labeledBreaksAndContinues() { } } -var variant15 = labeledBreaksAndContinues(); -expect(variant15).toStrictEqual(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 -) { +TEST_OUTPUT["Variant #18"] = labeledBreaksAndContinues() === 15; + +// Variant #19: Function.length property +var variant19 = function (n1, n2, n3, n4, n5) { var _ = true; }; -expect(variant16.length).toStrictEqual(16); +TEST_OUTPUT["Variant #19"] = variant19.length === 5; + +// Variant #20: Function name and parameter name collision +function fnName(fnName) { + TEST_OUTPUT["Variant #20"] = fnName === "Correct Value"; +} +fnName("Correct Value"); + +// Variant #21, #22: Default parameter function that accesses parameter scope +var _v__d = "Correct Value"; +function variant21And22( + _v__a, + _v__b = function () { + _v__a = "Correct Value"; + }, + _v__c = function () { + return _v__d; + } +) { + var _v__d = "Incorrect Value"; + _v__b(); + TEST_OUTPUT["Variant #21"] = _v__a === "Correct Value"; + TEST_OUTPUT["Variant #22"] = _v__c() === "Correct Value"; +} -// Set 'ranAllTest' to TRUE -ranAllTest = true; +variant21And22(); function countermeasures() { throw new Error("Countermeasures function called."); diff --git a/test/code/ES6.test.ts b/test/code/ES6.test.ts index e003e97..58e570f 100644 --- a/test/code/ES6.test.ts +++ b/test/code/ES6.test.ts @@ -2,41 +2,64 @@ 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, + "Variant #21": true, + "Variant #22": true, +}; -test.concurrent("Variant #1: ES6 code on High Preset", async () => { - var output = await JsConfuser(ES6_JS, { +test("Variant #1: ES6 code on High Preset", async () => { + const { code } = await JsConfuser.obfuscate(ES6_JS, { target: "node", preset: "high", + pack: true, }); - // Ensure 'use strict' directive is preserved - expect(output.startsWith("'use strict'")).toStrictEqual(true); - - 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.concurrent( - "Variant #2: ES6 code on High Preset + RGF + Self Defending", - async () => { - var output = await JsConfuser(ES6_JS, { - target: "node", - preset: "high", - rgf: true, - lock: { - selfDefending: true, - countermeasures: "countermeasures", - }, - }); +test("Variant #2: ES6 code on High Preset + RGF + Self Defending + Tamper Protection + Integrity", async () => { + const { code } = await JsConfuser.obfuscate(ES6_JS, { + target: "node", + preset: "high", + pack: true, + rgf: true, + lock: { + integrity: true, + selfDefending: true, + tamperProtection: true, + countermeasures: "countermeasures", + }, + }); - var ranAllTest = false; - eval(output); + // let newCode = `var TEST_OUTPUT;\n${code}\n\nconsole.log(TEST_OUTPUT);`; + // writeFileSync("./dev.output.js", newCode, "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 3b6a3b3..d5229a6 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"; @@ -8,30 +8,24 @@ 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", - }); - - //writeFileSync("./dev.output.js", output); + pack: true, - eval(output); -}); - -test("Variant #2: StrictMode on 2x High Preset", async () => { - var output = await JsConfuser(StrictMode_JS, { - target: "node", - preset: "high", + // Disable global concealing for testing purposes + // TEST_OUTPUT does not live on the global object + globalConcealing: (globalName) => globalName != "TEST_OUTPUT", }); - //writeFileSync("./dev.output1.js", output); + //writeFileSync("./dev.output.js", output); - var output2 = await JsConfuser(output, { - target: "node", - preset: "high", - }); + var TEST_OUTPUT = {}; - //writeFileSync("./dev.output2.js", output2); + eval(output); - eval(output2); + 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/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..58dddca 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,167 +1,62 @@ -import JsConfuser, { - debugObfuscation, - debugTransformations, -} from "../src/index"; +import JsConfuser from "../src/index"; +import { ProfilerLog } from "../src/obfuscationResult"; +import * as t from "@babel/types"; -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 an awaited object with 'code' property", async () => { + var output = await JsConfuser.obfuscate("5+5", { + target: "browser", + compact: 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("object"); + expect(typeof output.code).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", () => { 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, { target: "node", es5: true }); + JsConfuser.obfuscateAST(AST as any, { + target: "node", + renameVariables: true, + }); var after = JSON.stringify(AST); @@ -174,7 +69,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", }); @@ -187,7 +82,7 @@ describe("obfuscateAST", () => { type: "NotProgram", }; - return await JsConfuser.obfuscateAST(invalidAST, { + return await JsConfuser.obfuscateAST(invalidAST as any, { target: "node", preset: "low", }); @@ -195,55 +90,47 @@ 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 () => { +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.index).toStrictEqual("number"); + expect(typeof log.currentTransform).toStrictEqual("string"); + expect(typeof log.totalTransforms).toStrictEqual("number"); + if (typeof log.nextTransform !== "undefined") { + expect(typeof log.nextTransform).toStrictEqual("string"); + } called = true; }; - var output = await debugObfuscation( + var { code, profileData } = 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"); - 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( - "number" + 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.transforms).toStrictEqual("object"); + expect(typeof profileData.transforms.RenameVariables).toStrictEqual( + "object" ); - - eval(output.obfuscated); + 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 c18233d..dbd5dff 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -1,150 +1,303 @@ import JsConfuser from "../src/index"; +import { ObfuscateOptions } from "../src/options"; -describe("options", () => { - test("Variant #1: Accept percentages", async () => { - var output = await JsConfuser(`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 output = await JsConfuser(`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", + ] as ObfuscateOptions["identifierGenerator"], // half hexadecimal, half randomized }); - test("Variant #3: Accept probability maps", async () => { - var output = await JsConfuser(`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 false", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { + 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", - renameGlobals: true, - renameVariables: true, 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 indent set to 2 spaces", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { +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: Verbose option", async () => { + var { code } = await JsConfuser.obfuscate( + ` + var object = { key: 1 }; + TEST_OUTPUT = object.key; + `, + { target: "node", - renameGlobals: true, - renameVariables: true, compact: false, - indent: 2, + verbose: true, + objectExtraction: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(1); +}); + +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 #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(); +}); - expect(output).not.toContain("TEST_VARIABLE"); - }); +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("Variant #6: Work with debugComments enabled", async () => { - var output = await JsConfuser(`var TEST_VARIABLE;`, { + TEST_OUTPUT = [renameMe, doNotRenameMe] + `, + { target: "node", - renameGlobals: true, renameVariables: true, - compact: false, - indent: 2, - debugComments: true, - }); + renameGlobals: (globalName) => { + globalsCollected.push(globalName); - expect(output).not.toContain("TEST_VARIABLE"); - }); + if (globalName === "doNotRenameMe") { + return false; + } - test("Variant #7: Error on invalid lock option", async () => { - expect( - JsConfuser(`var TEST_VARIABLE;`, { - target: "node", - lock: "invalid", - } as any) - ).rejects.toThrow(); - - expect( - JsConfuser(`var TEST_VARIABLE;`, { - target: "node", - lock: { - invalidProperty: true, - }, - } as any) - ).rejects.toThrow(); - }); + 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]); }); -describe("options.preserveFunctionLength", () => { - test("Variant #1: Enabled by default", async () => { - var output = await JsConfuser( - ` - function myFunction(a, b, c, d = "") { - // Function.length = 3 +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", } + ); - TEST_OUTPUT = myFunction.length; // 3 - `, - { - target: "node", - preset: "high", - } - ); - - var TEST_OUTPUT; - eval(output); - expect(TEST_OUTPUT).toStrictEqual(3); - }); + // 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"); - test("Variant #2: Disabled", async () => { - var output = await JsConfuser( - ` - function myFunction(a, b, c, d = "") { - // Function.length = 3 + // Ensure the remaining variables were not renamed + expect(code).toContain("keepMyVar3"); + 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"); - TEST_OUTPUT = myFunction.length; // 3 + 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", - 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); - }); + { + 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"); }); diff --git a/test/presets.test.ts b/test/presets.test.ts index a54c0a1..31af797 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/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 () { diff --git a/test/semantics/functionLength.test.ts b/test/semantics/functionLength.test.ts new file mode 100644 index 0000000..1ec0b0e --- /dev/null +++ b/test/semantics/functionLength.test.ts @@ -0,0 +1,56 @@ +import JsConfuser from "../../src"; + +test("Variant #1: Preserve function.length on High Preset", 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", + pack: true, + } + ); + + var TEST_OUTPUT; + eval(output); + expect(TEST_OUTPUT).toStrictEqual(3); +}); + +test("Variant #2: Allow user to disable preserving function.length", 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, + opaquePredicates: false, + + rgf: true, + pack: 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..48b716e --- /dev/null +++ b/test/semantics/lastExpression.test.ts @@ -0,0 +1,45 @@ +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", + pack: true, + } + ); + + 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, + pack: true, + } + ); + + var lastExpression = eval(code); + + expect(lastExpression).toStrictEqual(6); +}); diff --git a/test/semantics/moduleImport.test.ts b/test/semantics/moduleImport.test.ts new file mode 100644 index 0000000..0f20c90 --- /dev/null +++ b/test/semantics/moduleImport.test.ts @@ -0,0 +1,52 @@ +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; + `, + { + target: "node", + pack: true, + preset: "high", + } + ); + + // console.log(code.slice(0, 100)); + + // 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 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');" + ) + + // (When Compact and Rename Variables is disabled) + .replace(`import { createHash}`, "let {createHash} "); + + // writeFileSync("./dev.output.js", code, "utf-8"); + + var TEST_OUTPUT = ""; + + eval(code); + + expect(TEST_OUTPUT).toStrictEqual( + "1cac63f39fd68d8c531f27b807610fb3d50f0fc3f186995767fb6316e7200a3e" + ); +}); diff --git a/test/code/NewFeatures.test.ts b/test/semantics/newFeatures.test.ts similarity index 86% rename from test/code/NewFeatures.test.ts rename to test/semantics/newFeatures.test.ts index b537027..eab418b 100644 --- a/test/code/NewFeatures.test.ts +++ b/test/semantics/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/templates/template.test.ts b/test/templates/template.test.ts index 6cb463b..100cf0a 100644 --- a/test/templates/template.test.ts +++ b/test/templates/template.test.ts @@ -1,224 +1,327 @@ -import { compileJsSync } from "../../src/compiler"; -import { parseSnippet } from "../../src/parser"; +import { UNSAFE } from "../../src/constants"; +import Obfuscator from "../../src/obfuscator"; import Template from "../../src/templates/template"; -import { Literal } from "../../src/util/gen"; +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 = compileJsSync(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: parseSnippet("var newWindow = {}").body, - }); + 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" - ); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(functionDeclaration.body.body[0].type).toStrictEqual( + "VariableDeclaration" + ); - // Generated code and check - var output = compileJsSync(functionDeclaration, { - target: "node", - compact: true, - }); - - 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 parseSnippet("var newWindow = {}").body; - }, - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - 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 = compileJsSync(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(functionDeclaration.body.body[0].type).toStrictEqual( - "VariableDeclaration" - ); + var functionDeclaration = Base64Template.single({ + name: "atob", + NewWindowTemplate: NewWindowTemplate, + NewWindowName: "newWindow", + }); - // Generated code and check - var output = compileJsSync(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 = compileJsSync(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: Literal("atob"), - }); - - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + property: t.stringLiteral("atob"), + }); - // Generated code and check - var output = compileJsSync(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: () => Literal("atob"), - }); + var functionDeclaration = Base64Template.single({ + name: "decodeBase64", + property: () => t.stringLiteral("atob"), + }); - expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); + expect(functionDeclaration.type).toStrictEqual("FunctionDeclaration"); - // Generated code and check - var output = compileJsSync(functionDeclaration, { - target: "node", - compact: true, - }); + // Generated code and check + var output = Obfuscator.generateCode(functionDeclaration, { + target: "node", + compact: true, + }); + + 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(output).toContain("return window['atob'](str)"); +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"); +}); + +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"); +}); + +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/antiTooling.test.ts b/test/transforms/astScrambler.test.ts similarity index 54% rename from test/transforms/antiTooling.test.ts rename to test/transforms/astScrambler.test.ts index dde7ead..08c1488 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++"); @@ -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/calculator.test.ts b/test/transforms/calculator.test.ts index 9acf738..80bc310 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 () => { - var code = `input((40 * 35 + 4) * 4 + 2)`; +test("Variant #3: Execute property with complex operations", async () => { + var code = `input((40 * 35 + 4) * 4 + 2 + -20)`; - 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) { @@ -34,45 +43,45 @@ it("should execute property with complex operations", async () => { eval(output); - expect(value).toStrictEqual(5618); + expect(value).toStrictEqual(5598); }); -it("should 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 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"); - 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); }); -it("should not 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); - var output = await JsConfuser(code, { target: "node", calculator: true }); + TEST_OUTPUT = result; + `; - expect(output).not.toContain("_calc"); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", + calculator: true, + }); var TEST_OUTPUT; eval(output); - expect(TEST_OUTPUT).toStrictEqual(true); + expect(TEST_OUTPUT).toStrictEqual(440); }); diff --git a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts index d31dd83..a210b64 100644 --- a/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +++ b/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts @@ -18,9 +18,10 @@ 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, + pack: true, }); // Ensure Control Flow Flattening applied @@ -33,7 +34,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 = []; @@ -44,17 +45,15 @@ test("Variant #2: Obfuscate for loops", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // 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 +61,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; @@ -75,13 +74,14 @@ test("Variant #3: Obfuscate while loops", async () => { TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied - expect(output).toContain("while"); + expect(output).toContain("switch"); // Ensure the output is the exact same var TEST_OUTPUT; @@ -90,9 +90,8 @@ 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 = []; for ( var i =1; i < 50; i++ ) { @@ -105,9 +104,10 @@ test("Variant #4: Work with break statements", async () => { TEST_OUTPUT = TEST_ARRAY; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", controlFlowFlattening: true, + pack: true, }); // Ensure Control Flow Flattening applied @@ -121,7 +121,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 = []; @@ -139,13 +139,14 @@ test("Variant #5: Don't obfuscate code with `let` (Lexically bound variables)", TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: 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 +155,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++ ) { @@ -164,13 +165,14 @@ test("Variant #6: Don't obfuscate code with `let` (Lexically bound variables)", TEST_OUTPUT = array; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", controlFlowFlattening: true, + pack: 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; @@ -192,9 +194,10 @@ 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, + pack: true, }); // Ensure the output is the exact same @@ -217,39 +220,32 @@ 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; } 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, + pack: true, }); // 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, }); @@ -262,7 +258,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(){ @@ -278,6 +274,7 @@ test("Variant #9: Don't entangle floats or NaN", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -296,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(){ @@ -323,6 +320,7 @@ test("Variant #10: Correctly entangle property keys", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -336,7 +334,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 = []; @@ -372,6 +370,7 @@ test("Variant #11: Flatten nested if statements", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -381,8 +380,8 @@ 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 () => { - var output = await JsConfuser( +test("Variant #12: Properly handle nested for loops", async () => { + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -404,11 +403,11 @@ test("Variant #12: Flatten nested for loops", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); - expect(output).not.toContain("for(var i)"); - expect(output).not.toContain("for(var j)"); + expect(output).toContain("switch"); var TEST_ARRAY; @@ -416,8 +415,8 @@ 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 () => { - var output = await JsConfuser( +test("Variant #13: Properly handle nested while-loops", async () => { + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -445,20 +444,20 @@ test("Variant #13: Flatten nested while loops", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); 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 () => { - var output = await JsConfuser( +test("Variant #14: Properly handle nested switch statements", async () => { + var { code: output } = await JsConfuser.obfuscate( ` TEST_ARRAY = []; @@ -470,7 +469,6 @@ test("Variant #14: Flatten 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; @@ -494,11 +492,11 @@ test("Variant #14: Flatten nested switch statements", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); - expect(output).not.toContain("switch(i"); - expect(output).not.toContain("switch(j"); + expect(output).toContain("while"); var TEST_ARRAY; @@ -507,41 +505,37 @@ test("Variant #14: Flatten 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 = []; - - 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, } ); @@ -552,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; @@ -572,10 +566,11 @@ test("Variant #17: Flatten with infinite for loop and break", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); - expect(output).not.toContain("for(;"); + expect(output).toContain("while"); var TEST_ARRAY; @@ -583,8 +578,64 @@ 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 #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 output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` var counter = 0; function increment(){ @@ -617,6 +668,7 @@ test("Variant #20: Work with redefined functions", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -631,7 +683,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"; @@ -641,6 +693,7 @@ test("Variant #21: Don't move Import Declarations", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -652,7 +705,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');" ); @@ -667,7 +720,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") { @@ -677,6 +730,7 @@ test("Variant #22: Don't break typeof expression", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); @@ -688,11 +742,14 @@ 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){ this.val = val; + + // Ensure ControlFlowFlattening applies here + var a, b, c; } } class MyClass2 extends MyClass1 { @@ -700,16 +757,14 @@ 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; } } var myObject = new MyClass2(); TEST_OUTPUT = myObject.val; // 10 `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); var TEST_OUTPUT; @@ -771,12 +826,13 @@ 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, identifierGenerator: "mangled", - stack: true, + variableMasking: true, + pack: true, }); var TEST_OUTPUT; @@ -797,9 +853,10 @@ 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, + pack: true, }); // Ensure Control Flow Flattening applied @@ -812,7 +869,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); @@ -826,7 +883,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"); @@ -838,14 +895,20 @@ 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"; + // 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); } function* myGeneratorFunction(){ + var a,b,c; yield "Correct Value"; } @@ -858,7 +921,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 @@ -888,9 +951,10 @@ 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, + pack: true, }); // Ensure Control Flow Flattening applied @@ -954,12 +1018,13 @@ 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, renameVariables: true, identifierGenerator: "mangled", + pack: true, }); // Run the code @@ -969,8 +1034,8 @@ test("Variant #29: Nested labeled break and continue statements with RGF enabled expect(TEST_OUTPUT).toStrictEqual(15); }); -test("Variant #30: Obfuscate switch statements", async () => { - var output = await JsConfuser( +test("Variant #30: Properly handle switch statements", async () => { + var { code: output } = await JsConfuser.obfuscate( ` switch("DON'T CHANGE ME"){} // Empty switch for testing @@ -1055,16 +1120,13 @@ test("Variant #30: Obfuscate switch statements", async () => { { target: "node", controlFlowFlattening: true, + pack: true, } ); // 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); @@ -1072,7 +1134,7 @@ test("Variant #30: Obfuscate 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; @@ -1087,7 +1149,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"); @@ -1098,45 +1160,36 @@ 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 () => { - var output = await JsConfuser( +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 } + { target: "node", controlFlowFlattening: true, pack: 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 () => { - var output = await JsConfuser( +test("Variant #33: Skip blocks with name collision", async () => { + var { code: output } = await JsConfuser.obfuscate( ` var counter = 0; @@ -1162,10 +1215,10 @@ test("Variant #33: Don't break same name function declarations that are not ran" TEST_OUTPUT = counter; `, - { target: "node", controlFlowFlattening: true } + { target: "node", controlFlowFlattening: true, pack: true } ); - expect(output).toContain("while"); + expect(output).not.toContain("while"); var TEST_OUTPUT; eval(output); @@ -1173,7 +1226,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; @@ -1225,14 +1278,18 @@ 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, + pack: true, + } + ); var TEST_OUTPUT; eval(secondObfuscation); @@ -1262,7 +1319,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, }); @@ -1272,3 +1329,198 @@ 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, + pack: true, + } + ); + + var TEST_OUTPUT; + eval(code); + + 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() { + var _; + ("fn2 Body"); + return nestedParam; + } + + ("fn1 Body"); + return fn2(); + } + + ("myFunction Body"); + return fn1("Correct Value"); + } + + var x = myFunction(); + + TEST_OUTPUT = x; + `, + { + target: "node", + controlFlowFlattening: true, + pack: 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, + pack: true, + } + ); + expect(code).toContain("while"); + + var TEST_OUTPUT; + + 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); +}); + +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"); +}); 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/deadCode.test.ts b/test/transforms/deadCode.test.ts index f4b35a4..8bde3e8 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,13 +63,13 @@ 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, }); // 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"); @@ -83,3 +83,59 @@ 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"); +}); + +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 2cd6370..f621c22 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,10 +317,11 @@ 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, + pack: true, }); var value = "never_called"; @@ -302,7 +336,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 +349,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 +368,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 +392,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 +413,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 +439,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 +467,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: @@ -455,3 +492,141 @@ 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"); +}); + +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"); +}); + +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/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/extraction/duplicateLiteralsRemoval.test.ts b/test/transforms/extraction/duplicateLiteralsRemoval.test.ts index 5760fa2..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,12 +66,12 @@ 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, }); - expect(output).toContain("'','','',''"); + expect(output).toContain('"","","",""'); var TEST_ARRAY; @@ -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; @@ -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); }); diff --git a/test/transforms/extraction/objectExtraction.test.ts b/test/transforms/extraction/objectExtraction.test.ts index 76711b9..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, }); @@ -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(){ @@ -313,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, }); @@ -344,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, }); @@ -376,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, }); @@ -406,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); @@ -438,18 +437,18 @@ 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, }); expect(output).toContain("TEST_OBJECT"); - expect(output).toContain("set "); + expect(output).toContain("set["); }); // 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 }; @@ -470,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; @@ -489,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/flatten.test.ts b/test/transforms/flatten.test.ts index 110e809..fb43ea6 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"; @@ -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); @@ -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; @@ -387,8 +387,8 @@ 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 () => { - var output = await JsConfuser( +test("Variant #14: Ignore functions with 'use strict' directive", async () => { + var { code: output } = await JsConfuser.obfuscate( ` function myFunction(){ "use strict"; @@ -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); @@ -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"; @@ -518,8 +518,8 @@ 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 () => { - var output = await JsConfuser( +test("Variant #18: Redefined variable in nested scope + Rename Variables", async () => { + var { code: output } = await JsConfuser.obfuscate( ` (function (){ var outsideVar = "Incorrect Value 1"; @@ -547,12 +547,12 @@ test("Variant #18: Redefined variable in nested scope", 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); @@ -562,7 +562,7 @@ test("Variant #18: Redefined variable in nested scope", 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){}; @@ -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); @@ -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,10 +701,15 @@ test("Variant #23: Reference original function name", async () => { }); test("Variant #24: Typeof expression", async () => { - var output = await JsConfuser( + 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,5 +722,52 @@ 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 () => { + 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(); +}); + +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/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/globalConcealing.test.ts b/test/transforms/identifier/globalConcealing.test.ts index af3c74f..ec0553a 100644 --- a/test/transforms/identifier/globalConcealing.test.ts +++ b/test/transforms/identifier/globalConcealing.test.ts @@ -5,14 +5,14 @@ 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, }); expect(output).not.toContain("Math.floor"); expect(output).not.toContain("=Math"); - expect(output).toContain("['Math']"); + expect(output).toContain('["Math"]'); expect(output).toContain("window"); }); @@ -23,16 +23,16 @@ 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, }); - expect(output).toContain("log'](Math)"); + expect(output).toContain('log"](Math)'); }); 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); @@ -140,3 +140,131 @@ 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); +}); + +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); +}); + +test("Variant #10: Properly handle declared global variables", async () => { + var namesCollected: string[] = []; + + var { code } = await JsConfuser.obfuscate( + ` + function console(){ + VALID_GLOBAL.TEST_PROPERTY = true; + VALID_GLOBAL.ANOTHER_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); +}); + +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 8ab9bfd..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 () => { @@ -8,7 +7,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 +29,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,12 +51,12 @@ 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, }); - expect(output).toContain("var [y]=[15];"); + expect(output).toContain("var[y]=[15];"); var TEST_VARIABLE; eval(output); @@ -77,7 +76,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 +98,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 +120,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 +144,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, }); @@ -172,13 +171,15 @@ 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, }); // Ensure movedDeclarations applied and 'use strict' is still first - expect(output).toContain("function myFunction(){'use strict';var x;"); + // '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); @@ -186,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; @@ -195,40 +196,34 @@ test("Variant #9: Defined variable without an initializer", async () => { TEST_OUTPUT = x + y; `; - var output1 = await JsConfuser(code, { - target: "node", - movedDeclarations: true, - controlFlowFlattening: true, - duplicateLiteralsRemoval: true, - }); - - var output2 = await JsConfuser(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); }); 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 } } @@ -237,7 +232,7 @@ test("Variant #10: Move parameters to predictable function", async () => { constructor(){ } - get fakeGet${predictableFunctionTag}(){ + get fakeGet(){ var fakeVar } } @@ -249,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; } @@ -258,10 +253,10 @@ test("Variant #10: Move parameters to predictable function", async () => { return output } - TEST_OUTPUT = testFunction${predictableFunctionTag}_FN() + TEST_OUTPUT = testFunction_FN() `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", movedDeclarations: true, }); @@ -273,3 +268,112 @@ 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); +}); + +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", + ]); +}); + +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/identifier/renameVariables.test.ts b/test/transforms/identifier/renameVariables.test.ts index ecacad3..e0fe23d 100644 --- a/test/transforms/identifier/renameVariables.test.ts +++ b/test/transforms/identifier/renameVariables.test.ts @@ -1,9 +1,13 @@ 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 output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "browser", renameVariables: true, renameGlobals: true, @@ -20,7 +24,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 +55,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 +79,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 +106,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 +134,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 +162,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 +184,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 +213,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 +227,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 +252,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 +278,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 +303,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 +333,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,48 +352,56 @@ 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 = ` - var a = "Filler Variables"; - var b = "Hello World"; - var c = "Another incorrect string"; +test.each([ + "randomized", + "mangled", + customIdentifierGenerator, +])( + "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 + */ + 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(); - var output = await JsConfuser(code, { - target: "node", - renameVariables: true, - renameGlobals: true, - identifierGenerator: "mangled", - }); + function accessParameter(store = "Incorrect Value", paramFn = ()=> (store = "Correct Value") ){ + paramFn(); + TEST_OUTPUT["Variant #2"] = store; + } - var value; - function input(valueIn) { - value = valueIn; - } + accessParameter(); + `; - eval(output); + const { code } = await JsConfuser.obfuscate(sourceCode, { + target: "node", + renameVariables: true, + renameGlobals: true, + identifierGenerator: identifierGeneratorMode, + }); - expect(value).toStrictEqual("Hello World"); -}); + let TEST_OUTPUT = {}; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual({ + "Variant #1": "Correct Value", + "Variant #2": "Correct Value", + }); + } +); // https://github.com/MichaelXF/js-confuser/issues/24 test("Variant #16: Function with multiple parameters and a default value", async () => { @@ -386,7 +417,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 +442,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 +460,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 +471,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 +509,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 +533,17 @@ 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", + "chinese", + customIdentifierGenerator, +])( "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 +559,7 @@ test.each(["hexadecimal", "mangled", "number", "zeroWidth"])( { target: "node", renameVariables: true, - identifierGenerator: - identifierGeneratorMode as ObfuscateOptions["identifierGenerator"], + identifierGenerator: identifierGeneratorMode, } ); @@ -537,7 +574,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 +603,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 +628,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 +647,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"; @@ -627,17 +664,18 @@ 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 () => { - var output = await JsConfuser( +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" ); } - - myFunction(); + + // Work on functions too + eval( __JS_CONFUSER_VAR__(myFunction) + "()" ); // myFunction(); `, { target: "node", renameVariables: true } ); @@ -651,30 +689,34 @@ 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 () => { - var output = await JsConfuser( +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 () => { - var output = await JsConfuser( + 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" } @@ -683,6 +725,7 @@ test("Variant #28: Transform __JS_CONFUSER_VAR__ on High Preset", async () => { { target: "node", preset: "high", + pack: true, } ); @@ -693,3 +736,83 @@ 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); +}); + +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/antiDebug.test.ts b/test/transforms/lock/antiDebug.test.ts index a6c6975..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 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 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 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 1dbb5e3..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 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 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 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 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..e37b263 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,29 @@ test("Variant #5: Should work with RGF enabled", async () => { rgf: true, } ); + + var TEST_OUTPUT; + eval(code); + + 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/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..df82499 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,24 @@ 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( + ` + function MyFunction(){ + TEST_OUTPUT = ("Hello World") + } + + MyFunction(); + `, + { + target: "node", + preset: "high", + lock: { + integrity: true, + }, + pack: true, + } + ); var TEST_OUTPUT; eval(output); @@ -136,8 +145,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"; @@ -159,3 +168,43 @@ it("should 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"); +}); diff --git a/test/transforms/lock/lock.test.ts b/test/transforms/lock/lock.test.ts index 5a26150..c1a77db 100644 --- a/test/transforms/lock/lock.test.ts +++ b/test/transforms/lock/lock.test.ts @@ -1,204 +1,25 @@ -import JsConfuser from "../../../src/index"; +import JsConfuser from "../../../src"; +import Obfuscator from "../../../src/obfuscator"; +import { Order } from "../../../src/order"; -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; - } +test("Variant #1: Error if lock options is not an object", async () => { + expect(async () => { + var invalidLockOptions = true as any; - 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) } `, - { + await JsConfuser.obfuscate('console.log("Hello World")', { 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"); + lock: invalidLockOptions, + }); + }).rejects.toThrow(); }); -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 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 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 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 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 output = await JsConfuser.obfuscate( - ` function countermeasures(){ input(true) } `, - { - target: "node", - lock: { - context: ["missingProperty"], - countermeasures: "countermeasures", - }, - } - ); +test("Variant #2: Lock transform should be skipped when no options are provided", async () => { + var obfuscator = new Obfuscator({ + target: "node", + lock: {}, + }); - var value = "never_called"; - function input(valueIn) { - value = valueIn; - } + var plugin = obfuscator.getPlugin(Order.Lock); - eval(output); - expect(value).toStrictEqual(true); + expect(plugin).toBeUndefined(); }); diff --git a/test/transforms/lock/osLock.test.ts b/test/transforms/lock/osLock.test.ts deleted file mode 100644 index 0ae9333..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 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 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 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 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 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 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 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 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 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 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 bda0286..d40f96c 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; @@ -37,8 +37,8 @@ test("Variant #2: SelfDefending should not crash when unchanged", async () => { expect(TEST_CAUGHT).toStrictEqual(undefined); }); -test("Variant #2: SelfDefending should crash when changed", async () => { - var output = await JsConfuser.obfuscate( +test("Variant #3: SelfDefending should crash when changed", async () => { + var { code: output } = await JsConfuser.obfuscate( ` function caught(){ TEST_CAUGHT = true; @@ -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..09f66d9 100644 --- a/test/transforms/lock/tamperProtection.test.ts +++ b/test/transforms/lock/tamperProtection.test.ts @@ -24,12 +24,12 @@ 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; `; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", globalConcealing: true, lock: { @@ -48,46 +48,53 @@ 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 output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", globalConcealing: (varName) => varName != "TEST_OUTPUT_SET", lock: { 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); }; 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); @@ -115,14 +122,18 @@ 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 output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` function onTamperDetected(){ TEST_OUTPUT_SET(true); @@ -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 @@ -153,9 +165,10 @@ 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() + nonExistentFunction() `, { target: "node", @@ -168,8 +181,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 +222,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)); `, @@ -232,9 +245,12 @@ describe("Global Concealing", () => { }); describe("RGF", () => { - test("Variant #1: Use Eval instead of new Function", async () => { - var output = await JsConfuser( + 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,8 +286,8 @@ describe("RGF", () => { expect(TEST_OUTPUT).toStrictEqual(true); }); - test("Variant #2: Detect Eval tamper", async () => { - var output = await JsConfuser( + test("Variant #2: Invoke countermeasures when eval() is tampered", async () => { + var { code: output } = await JsConfuser.obfuscate( ` function onTamperDetected(){ TEST_OUTPUT_SET("Correct Value"); @@ -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 output = await JsConfuser( - ` + 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/minify.test.ts b/test/transforms/minify.test.ts index bb07330..0fc79a3 100644 --- a/test/transforms/minify.test.ts +++ b/test/transforms/minify.test.ts @@ -1,203 +1,286 @@ 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 }); + var { code: output } = await JsConfuser.obfuscate(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(); } `; - 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()"); }); -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 }); + var { code: output } = await JsConfuser.obfuscate(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 }); + var { code: output } = await JsConfuser.obfuscate(code, { + target: "browser", + minify: true, + }); 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; + + // 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 output = await JsConfuser(code, { target: "browser", minify: true }); + var { code: output } = await JsConfuser.obfuscate(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(10); + expect(TEST_OUTPUT).toStrictEqual([true, true, true]); }); -test("Variant #8: Shorten simple array destructuring", async () => { +test("Variant #8: Shorten simple arithmetic", async () => { // Valid - var output = await JsConfuser(`var [x] = [1]`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + `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++"); - // Invalid - var output2 = await JsConfuser(`var [x, y] = [1]`, { - target: "node", - minify: true, - }); + var TEST_OUTPUT; + eval(output); - 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 output = await JsConfuser(`var {x} = {x: 1}`, { - target: "node", - minify: true, - }); + var { code: output } = await JsConfuser.obfuscate( + ` + 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( + "John Doe has a Checking account with a balance of $100" + ); // Valid - var output2 = await JsConfuser(`var {['x']: y} = {x: 1}`, { + var { code: output2 } = await JsConfuser.obfuscate( + `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 { code: output3 } = await JsConfuser.obfuscate(`var {x,y} = {x:1}`, { target: "node", minify: true, }); - expect(output2).toContain("var y=1"); + expect(output3).toContain("var{x,y}="); // Invalid - var output3 = await JsConfuser(`var {x,y} = {x:1}`, { + var { code: output4 } = await JsConfuser.obfuscate(`var {y} = {x:1}`, { target: "node", minify: true, }); - expect(output3).toContain("var {x:x,y:y}"); + expect(output4).toContain("var{y}="); // Invalid - var output4 = await JsConfuser(`var {y} = {x:1}`, { + var { code: output5 } = await JsConfuser.obfuscate(`var [x, y] = [1]`, { target: "node", minify: true, }); - expect(output4).toContain("var {y:y}"); + expect(output5).toContain("var[x,y]"); }); test("Variant #10: Shorten booleans", async () => { // Valid - var output = await JsConfuser(`var x = true;`, { - 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"); + var TEST_OUTPUT; + eval(output); + + expect(TEST_OUTPUT).toStrictEqual(true); + // Valid - var output2 = await JsConfuser(`var x = false`, { - 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"); + + var TEST_OUTPUT_2; + eval(output2); + + expect(TEST_OUTPUT_2).toStrictEqual(false); }); 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, }); @@ -205,14 +288,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}`, { - 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", @@ -225,56 +311,101 @@ 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;`, { - 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"); + var TEST_OUTPUT; + eval(output); + // Valid - var output2 = await JsConfuser(`var x = {Infinity: 1}`, { - 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 'true'", async () => { - // Valid - var output = await JsConfuser(`var x = !false;`, { - target: "node", - minify: true, - }); +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(code).not.toContain("!false"); + expect(code).not.toContain("!true"); + expect(code).not.toContain("!!"); + + var TEST_OUTPUT = []; + eval(code); - expect(output).toContain("var x=true"); + expect(TEST_OUTPUT).toStrictEqual([true, false]); }); -test("Variant #13: Shorten 'false ? a : b' to 'b'", async () => { - // Valid - var output = await JsConfuser(`var x = false ? 10 : 15;`, { - target: "node", - minify: true, - }); +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_1; + var TEST_OUTPUT_2; + + eval(code); + + 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 () => { // Valid - var output = await JsConfuser(`var x = undefined`, { - 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="); + + 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( + var { code: output } = await JsConfuser.obfuscate( ` function MyFunction(){ var output = "Hello World"; @@ -292,7 +423,7 @@ test("Variant #15: Removing 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){ @@ -314,7 +445,7 @@ test("Variant #15: Removing 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); @@ -334,7 +465,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; @@ -348,7 +479,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; @@ -367,7 +498,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; @@ -380,7 +511,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, @@ -408,7 +539,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() {}; @@ -426,7 +557,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() {}; @@ -444,36 +575,46 @@ 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 } = 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 () => { - var output = await JsConfuser( + var { code: output } = await JsConfuser.obfuscate( ` 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 +622,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); @@ -490,7 +631,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 }; @@ -507,30 +648,9 @@ 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( + var { code: output } = await JsConfuser.obfuscate( ` function oneParameter(a){}; var twoParameters = function({a},{b,c},...d){}; @@ -549,7 +669,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({ @@ -573,3 +693,224 @@ 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 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("_unusedValue"); + expect(code).not.toContain("Incorrect Value"); + + expect(code).toContain("keepMe"); + + 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"; + + + case "Nested Case": + switch(condition){ + default: + return "Incorrect Value"; + return "Should be removed"; + } + "Should be removed"; + 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"]); +}); + +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]); +}); + +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"); +}); + +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/opaquePredicates.test.ts b/test/transforms/opaquePredicates.test.ts index fa99eac..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 output = await JsConfuser(code, { - target: "browser", + var { code: output } = await JsConfuser.obfuscate(code, { + 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 ()=>{ - var code = ` - +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 output = await JsConfuser(code, { - target: "browser", + var { code: output } = await JsConfuser.obfuscate(code, { + target: "node", opaquePredicates: true, }); - var value; - function input(valueIn){ - value = valueIn; - } + 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: "node", + opaquePredicates: true, + }); + + expect(output).not.toContain("=test?"); + + var TEST_OUTPUT; eval(output); - expect(value).toStrictEqual(true); -}) + expect(TEST_OUTPUT).toStrictEqual("Correct Value"); +}); diff --git a/test/transforms/pack.test.ts b/test/transforms/pack.test.ts new file mode 100644 index 0000000..d0b5fb3 --- /dev/null +++ b/test/transforms/pack.test.ts @@ -0,0 +1,99 @@ +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" + ); +}); + +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"); +}); + +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"); +}); diff --git a/test/transforms/preparation.test.ts b/test/transforms/preparation.test.ts index 8144cca..81c7d07 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(); @@ -75,11 +75,11 @@ 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 () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` console.log("Hello World") `, @@ -90,11 +90,11 @@ 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 () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var myObject = { myKey: 1 @@ -107,11 +107,11 @@ 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 () => { - var output = await JsConfuser.obfuscate( + var { code: output } = await JsConfuser.obfuscate( ` var myVar1, myVar2, myVar3; @@ -122,12 +122,22 @@ 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; + } + + for(var myForVar1, myForVar2, myForVar3; ; ){ + break; + } + + export var myExportVar1, myExportVar2, myExportVar3; `, { target: "node", @@ -154,4 +164,103 @@ 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 () => { + 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!"); +}); + +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**!"); }); diff --git a/test/transforms/renameLabels.test.ts b/test/transforms/renameLabels.test.ts index 7f52f95..6c69326 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,191 @@ 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"]); +}); + +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 5757f0c..57346cc 100644 --- a/test/transforms/rgf.test.ts +++ b/test/transforms/rgf.test.ts @@ -1,8 +1,8 @@ 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( +test("Variant #1: Convert Function Declaration into 'eval' code", async () => { + var { code: output } = await JsConfuser.obfuscate( ` function addTwoNumbers(a, b){ return 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,8 +24,8 @@ 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 () => { - var output = await JsConfuser.obfuscate( +test("Variant #2: Convert Function Expression into 'eval' code", async () => { + var { code: output } = await JsConfuser.obfuscate( ` var addTwoNumbers = function(a, b){ return 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); @@ -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); @@ -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); @@ -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; @@ -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); @@ -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; @@ -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); @@ -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 } = await JsConfuser.obfuscate( ` function addTwoNumbers(a, b){ return a + b; @@ -136,17 +136,18 @@ test("Variant #6: Work on High Preset", async () => { target: "node", preset: "high", rgf: true, + pack: true, } ); var TEST_OUTPUT; - eval(output); + eval(code); expect(TEST_OUTPUT).toStrictEqual(15); }); 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(){ @@ -164,7 +165,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); @@ -173,7 +174,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; @@ -195,7 +196,7 @@ test("Variant #8: Modified Function", async () => { } ); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -204,7 +205,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; @@ -220,7 +221,7 @@ test("Variant #8: Modified Function (non function value)", async () => { } ); - expect(output).toContain("new Function"); + expect(output).toContain("eval"); var TEST_OUTPUT; eval(output); @@ -229,10 +230,11 @@ 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; + var TEST_OUTPUT function incrementOutsideCounter(){ outsideCounter++; @@ -251,6 +253,8 @@ test("Variant #9: Work with Flatten on any function", async () => { incrementOutsideCounter(); incrementTimes(8); incrementTimes(1); + + TEST_OUTPUT_OUT = TEST_OUTPUT; `, { target: "node", @@ -259,46 +263,25 @@ test("Variant #9: Work with Flatten on any function", async () => { } ); - expect(output).toContain("new Function"); + 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 () => { var functionNames: string[] = []; - var output = await JsConfuser( + 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(); @@ -310,6 +293,7 @@ test("Variant #10: Configurable by custom function option", async () => { functionNames.push(name); return name !== "doNotRgfThisFunction"; }, + pack: true, } ); @@ -317,18 +301,23 @@ test("Variant #10: Configurable by custom function option", async () => { "rgfThisFunction", "doNotRgfThisFunction", ]); - expect(output).toContain("new Function"); + 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); }); -test("Variant #11: Function containing function should both be changed", async function () { - var output = await JsConfuser( +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(){ function FunctionB(){ @@ -343,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("new Function").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); @@ -356,14 +356,16 @@ 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 } + 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); }); diff --git a/test/transforms/shuffle.test.ts b/test/transforms/shuffle.test.ts index 161f87f..5214273 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, }); @@ -133,3 +83,32 @@ it("Should not 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/customStringEncoding.test.ts b/test/transforms/string/customStringEncoding.test.ts new file mode 100644 index 0000000..e92c20b --- /dev/null +++ b/test/transforms/string/customStringEncoding.test.ts @@ -0,0 +1,321 @@ +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"; + +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 { + 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) => { + 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); + + 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 #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 = { + 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("Broken String"); + + var TEST_OUTPUT; + eval(code); + + expect(TEST_OUTPUT).toStrictEqual(["Hello World!", "Broken String"]); +}); diff --git a/test/transforms/string/stringCompression.test.ts b/test/transforms/string/stringCompression.test.ts index afa7730..68d1bcb 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 output = await JsConfuser(`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 @@ -23,7 +33,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, }); @@ -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(){ @@ -47,7 +57,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, }); @@ -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(){ @@ -69,7 +79,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, }); @@ -80,16 +90,16 @@ 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"; TEST_OUTPUT_3 = "My String 3"; `; - var stringsFound = []; + var stringsFound: string[] = []; - var output = await JsConfuser(code, { + var { code: output } = await JsConfuser.obfuscate(code, { target: "node", stringCompression: (strValue) => { stringsFound.push(strValue); @@ -105,9 +115,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; @@ -118,3 +128,80 @@ 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"); +}); + +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"); +}); + +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"); +}); diff --git a/test/transforms/string/stringConcealing.test.ts b/test/transforms/string/stringConcealing.test.ts index eb43055..8123771 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,83 @@ 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); +}); + +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/transforms/string/stringEncoding.test.ts b/test/transforms/string/stringEncoding.test.ts index cdf3183..fe3cd2c 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,10 +92,11 @@ 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", + pack: false, }); - expect(output.startsWith("'use strict'")).toStrictEqual(true); + expect(output.startsWith('"use strict"')).toStrictEqual(true); }); 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/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/transforms/stack.test.ts b/test/transforms/variableMasking.test.ts similarity index 70% rename from test/transforms/stack.test.ts rename to test/transforms/variableMasking.test.ts index 9bc1f96..00b7fc7 100644 --- a/test/transforms/stack.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; @@ -13,7 +13,7 @@ test("Variant #1: Replace all variables with array indexes (single variable)", a `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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; @@ -40,7 +40,7 @@ test("Variant #2: Replace all variables with array indexes (multiple variables)" `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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; @@ -66,7 +66,7 @@ test("Variant #3: Replace all variables with array indexes (uninitialized variab `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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){ @@ -91,7 +91,7 @@ test("Variant #4: Replace all variables with array indexes (parameters)", async `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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(){ @@ -121,7 +121,7 @@ test("Variant #5: Replace all variables with array indexes (nested function)", a `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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(){ @@ -158,7 +158,7 @@ test("Variant #6: Replace all variables with array indexes (nested class)", asyn `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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(){ @@ -184,7 +184,7 @@ test("Variant #7: Replace variables defined within the function, and not run if `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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; @@ -203,7 +203,7 @@ test("Variant #8: Work even when differing amount of arguments passed in", async `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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; @@ -235,12 +235,12 @@ test("Variant #9: Replace all variables with array indexes (middle indexes use a `, { target: "node", - stack: true, + variableMasking: true, } ); expect(output).not.toContain("TEST_VARIABLE"); - expect(output).toContain("]='Updated'"); + expect(output).toContain(']="Updated"'); var value = "never_called", input = (x) => (value = x); @@ -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)=>{ @@ -264,7 +264,7 @@ test("Variant #10: Guess execution order correctly (CallExpression, arguments ru `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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){ @@ -292,7 +292,7 @@ test("Variant #11: Guess execution order correctly (AssignmentExpression, right `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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(){ @@ -318,7 +318,7 @@ test("Variant #12: Should not entangle floats or NaN", async () => { `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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(){ @@ -369,7 +369,7 @@ test("Variant #13: Correctly entangle property keys", async () => { `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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(){ @@ -437,7 +437,7 @@ test("Variant #14: Correctly entangle method definition keys", async () => { `, { target: "node", - stack: true, + variableMasking: true, } ); @@ -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'; @@ -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 @@ -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(){ @@ -496,20 +496,23 @@ 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; `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); // 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"); @@ -522,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; @@ -533,7 +536,7 @@ test("Variant #17: Syncing arguments parameter", async () => { syncingArguments("Incorrect Value"); `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); function evalNoStrictMode(evalCode) { @@ -547,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; @@ -556,14 +559,14 @@ test("Variant #18: Preserve function.length property", async () => { var _ = 1; }; var myObject = { - threeParameters(a,b,c){ + threeParameters: function(a,b,c){ var _ = 1; } } TEST_OUTPUT = oneParameter.length + twoParameters.length + myObject.threeParameters.length; `, - { target: "node", stack: true } + { target: "node", variableMasking: true } ); var TEST_OUTPUT; @@ -571,3 +574,108 @@ 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); +}); + +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(); +}); + +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"); +}); 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/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); +}); diff --git a/test/util/ast-utils.test.ts b/test/util/ast-utils.test.ts new file mode 100644 index 0000000..e8da1d5 --- /dev/null +++ b/test/util/ast-utils.test.ts @@ -0,0 +1,269 @@ +import { ok } from "assert"; +import Obfuscator from "../../src/obfuscator"; +import { + getFunctionName, + getPatternIdentifierNames, + isDefiningIdentifier, + isUndefined, +} from "../../src/utils/ast-utils"; +import traverse, { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; + +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); + }); +}); + +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}`); + + return returnPath; +} + +describe("isDefiningIdentifier", () => { + 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} = {} + var _, id; + function f(id){} + function f(_, 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 []){} + 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) => { + 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}){} + import {id as _} from "module" + import {_, id as __} from "module" + ` + .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}` + ); + } + }); + }); +}); + +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/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-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/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/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/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/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" + ); + }); +}); diff --git a/test/util/random-utils.test.ts b/test/util/random-utils.test.ts new file mode 100644 index 0000000..08743f0 --- /dev/null +++ b/test/util/random-utils.test.ts @@ -0,0 +1,26 @@ +import { + choice, + getRandomHexString, + getRandomString, +} from "../../src/utils/random-utils"; + +test("choice() should return a random element from an array", async () => { + var sample = [10, 20, 30, 40, 50]; + + var times = 50; + for (var i = 0; i < times; i++) { + var random = choice(sample); + expect(sample).toContain(random); + } +}); + +test("getRandomString() should return a random string with exact length", async () => { + expect(typeof getRandomString(6)).toStrictEqual("string"); + expect(getRandomString(6).length).toStrictEqual(6); +}); + +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]+$/); +}); diff --git a/test/util/random.test.ts b/test/util/random.test.ts deleted file mode 100644 index 5594ec6..0000000 --- a/test/util/random.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - alphabeticalGenerator, - choice, - getRandomFalseExpression, - getRandomString, - getRandomTrueExpression, -} from "../../src/util/random"; - -const escodegen = require("escodegen"); - -it("choice() should return a random element from an array", async () => { - var sample = [10, 20, 30, 40, 50]; - - var times = 50; - for (var i = 0; i < times; i++) { - var random = choice(sample); - expect(sample).toContain(random); - } -}); - -it("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); - } -}); 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/**/*"] +}