├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── transforms ├── __testfixtures__ │ ├── RenameObjectPattern.output.js │ ├── obfuscate.io.output.js │ ├── obfuscate.io.orig.js │ ├── RenameObjectPattern.input.js │ ├── RenameRequireResults.input.js │ ├── RenameRequireResults.output.js │ ├── async_care.output.js │ ├── async_care.input.js │ ├── missing_parens.input.js │ ├── obfuscate.io.input.js │ ├── missing_parens.output.js │ ├── breakthecode.output.js │ ├── breakthecode.simplified.output.js │ ├── breakthecode.simplified.input.js │ ├── obfuscate.io.complex.output.js │ ├── breakthecode.input.js │ └── obfuscate.io.complex.input.js ├── __tests__ │ ├── guess-names.ts │ └── constant-fold.ts ├── jsunscramble.js ├── goto-to-structured.ts ├── guess-names.ts ├── rename-nested.ts └── constant-fold.ts ├── babel.config.js ├── README.md ├── package.json ├── patches └── ast-types+0.14.2.patch ├── data ├── jscrambler_short.js └── breakthecode.js └── breakthecode.orig.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vscode-profile* 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabCompletion": "on" 3 | } -------------------------------------------------------------------------------- /transforms/__testfixtures__/RenameObjectPattern.output.js: -------------------------------------------------------------------------------- 1 | var { foo: foo } = baz; 2 | foo + 1; 3 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/obfuscate.io.output.js: -------------------------------------------------------------------------------- 1 | function f2() { 2 | console.log("Hello World!"); 3 | } 4 | f2(); 5 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/obfuscate.io.orig.js: -------------------------------------------------------------------------------- 1 | // Paste your JavaScript code here 2 | function hi() { 3 | console.log("Hello World!"); 4 | } 5 | hi(); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/RenameObjectPattern.input.js: -------------------------------------------------------------------------------- 1 | var { foo: a } = baz; 2 | a + 1; 3 | 4 | function ff() { 5 | var b = a; 6 | return b + a 7 | } 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/RenameRequireResults.input.js: -------------------------------------------------------------------------------- 1 | var xyz = require('foo/bar'); 2 | var ww = require('./baz'); 3 | var zz = require('7zip'); 4 | 5 | xyz.get() -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/RenameRequireResults.output.js: -------------------------------------------------------------------------------- 1 | var foo_bar = require('foo/bar'); 2 | var _baz = require('./baz'); 3 | var _7zip = require('7zip'); 4 | 5 | foo_bar.get() -------------------------------------------------------------------------------- /transforms/__testfixtures__/async_care.output.js: -------------------------------------------------------------------------------- 1 | for ( 2 | var v1 = async function (anon1_arg1, anon1_arg2) { 3 | return { 4 | foo: await _0x9df065[a0_0x5dad("0x3aaa")](anon1_arg1), 5 | }; 6 | }, 7 | v2 = 0; 8 | v2 < _0x3dfba[a0_0x5dad("0x4035")]; 9 | v2++ 10 | ) { 11 | foo() 12 | } 13 | 14 | async function f1() {} -------------------------------------------------------------------------------- /transforms/__tests__/guess-names.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from "jscodeshift/dist/testUtils"; 2 | 3 | describe("guess-names", () => { 4 | // defineTest(__dirname, "guess-names", null, `RenameObjectPattern`, { 5 | // parser: "babel", 6 | // }); 7 | defineTest(__dirname, "guess-names", null, `RenameRequireResults`, { 8 | parser: "babel", 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/async_care.input.js: -------------------------------------------------------------------------------- 1 | for ( 2 | var _0x206535 = async function (_0x189adc, _0x380300) { 3 | return ( 4 | { 5 | foo: await _0x9df065[a0_0x5dad("0x3aaa")](_0x189adc), 6 | } 7 | ); 8 | }, 9 | _0x3f66a6 = 0x0; 10 | _0x3f66a6 < _0x3dfba[a0_0x5dad("0x4035")]; 11 | _0x3f66a6++ 12 | ) { 13 | foo() 14 | } 15 | 16 | var baz = async function () {} -------------------------------------------------------------------------------- /transforms/__tests__/constant-fold.ts: -------------------------------------------------------------------------------- 1 | // transforms/__tests__/constant-fold.ts 2 | import { defineTest } from "jscodeshift/dist/testUtils"; 3 | 4 | describe("deobfuscate-array-code", () => { 5 | defineTest(__dirname, "constant-fold", null, `breakthecode`, { 6 | parser: "babel", 7 | }); 8 | defineTest(__dirname, "constant-fold", null, `breakthecode.simplified`, { 9 | parser: "babel", 10 | }); 11 | defineTest(__dirname, "constant-fold", null, `missing_parens`, { 12 | parser: "babel", 13 | }); 14 | defineTest(__dirname, "constant-fold", null, `async_care`, { 15 | parser: "babel", 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Experiments with writing a js deobfuscator 2 | 3 | The code is in no way production ready, but I've managed to get some success out of it for myself. 4 | In particular, it is using `safe-eval` internally, as a simple way to unscramble the array before proceeding. So probably don't run this on malicious code. 5 | 6 | The main deobfuscator is `transformers/constant-fold.ts` (yes, bad name, I know) and can be run with: 7 | 8 | ``` 9 | cp path/to/file{.min,}.js 10 | yarn jscodeshift -t transforms/constant-fold.ts path/to/file.js 11 | ``` 12 | 13 | The cp is so you can keep the original, since jscodeshift by default overwrites the file it transforms. 14 | 15 | Warning: It can be very slow on large files (and use a lot of memory). I'm not completely sure why. 16 | 17 | There are also a few other random transformations that I found useful, mostly for renaming. 18 | 19 | If the obfuscated file was a bundle, it can be very helpful to run it through a [debundler](https://github.com/anka-213/debundle). 20 | 21 | # Alternatives 22 | Here are a few other js deobfuscators 23 | * https://github.com/lelinhtinh/de4js 24 | * https://github.com/LostMyCode/javascript-deobfuscator 25 | * https://github.com/sd-soleaio/javascript-deobfuscator 26 | * https://github.com/uwu/synchrony -------------------------------------------------------------------------------- /transforms/__testfixtures__/missing_parens.input.js: -------------------------------------------------------------------------------- 1 | null != (_0x4fda33 = _0x432a5f["_w"])["GG"]; 2 | 3 | _0x47f3d0 = 4 | (_0x1aa8ba) => 5 | ( 6 | _0x5511f6, 7 | _0x1a16de, 8 | _0x899f2, 9 | _0x30b3b5, 10 | _0x87b160, 11 | _0x2b1a1c, 12 | _0x4942fb, 13 | _0x3b57cb, 14 | _0x4d5278, 15 | _0x2a7f55, 16 | _0x4e13a9, 17 | _0x5acb76, 18 | _0x21eca1 19 | ) => 20 | ((_0x1a16de = _0x1c396f(_0x899f2) 21 | ? "" 22 | : _0x1c396f(_0x30b3b5) 23 | ? ">=" + _0x899f2 + a0_0x5dad("0x32b1") + (_0x1aa8ba ? "-0" : "") 24 | : _0x1c396f(_0x87b160) 25 | ? ">=" + _0x899f2 + "." + _0x30b3b5 + ".0" + (_0x1aa8ba ? "-0" : "") 26 | : _0x2b1a1c 27 | ? ">=" + _0x1a16de 28 | : ">=" + _0x1a16de + (_0x1aa8ba ? "-0" : "")) + 29 | "\x20" + 30 | (_0x3b57cb = _0x1c396f(_0x4d5278) 31 | ? "" 32 | : _0x1c396f(_0x2a7f55) 33 | ? "<" + (+_0x4d5278 + 0x1) + a0_0x5dad("0x323a") 34 | : _0x1c396f(_0x4e13a9) 35 | ? "<" + _0x4d5278 + "." + (+_0x2a7f55 + 0x1) + a0_0x5dad("0x7d") 36 | : _0x5acb76 37 | ? "<=" + _0x4d5278 + "." + _0x2a7f55 + "." + _0x4e13a9 + "-" + _0x5acb76 38 | : _0x1aa8ba 39 | ? "<" + _0x4d5278 + "." + _0x2a7f55 + "." + (+_0x4e13a9 + 0x1) + "-0" 40 | : "<=" + _0x3b57cb))["foobar"](); 41 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/obfuscate.io.input.js: -------------------------------------------------------------------------------- 1 | var _0x4c76 = [ 2 | "398633vnqtti", 3 | "1617702DqUueN", 4 | "1254764mLIbIV", 5 | "log", 6 | "183JVGwyR", 7 | "1fbeVqf", 8 | "Hello\x20World!", 9 | "1633779JWdixb", 10 | "18127fOysak", 11 | "313354erJwxF", 12 | "29obirqK", 13 | "2zDHFEH", 14 | "947eCBYHG", 15 | ]; 16 | function _0x5217(_0x4c76b2, _0x521782) { 17 | _0x4c76b2 = _0x4c76b2 - 0xad; 18 | var _0x182b04 = _0x4c76[_0x4c76b2]; 19 | return _0x182b04; 20 | } 21 | (function (_0x4a81e9, _0x5767a1) { 22 | var _0xec67fb = _0x5217; 23 | while (!![]) { 24 | try { 25 | var _0x1a3cb5 = 26 | -parseInt(_0xec67fb(0xb7)) + 27 | parseInt(_0xec67fb(0xb9)) * -parseInt(_0xec67fb(0xb5)) + 28 | parseInt(_0xec67fb(0xb0)) * -parseInt(_0xec67fb(0xae)) + 29 | parseInt(_0xec67fb(0xb2)) + 30 | parseInt(_0xec67fb(0xad)) * parseInt(_0xec67fb(0xb8)) + 31 | -parseInt(_0xec67fb(0xaf)) * -parseInt(_0xec67fb(0xb4)) + 32 | parseInt(_0xec67fb(0xb1)); 33 | if (_0x1a3cb5 === _0x5767a1) break; 34 | else _0x4a81e9["push"](_0x4a81e9["shift"]()); 35 | } catch (_0x4d2063) { 36 | _0x4a81e9["push"](_0x4a81e9["shift"]()); 37 | } 38 | } 39 | })(_0x4c76, 0xc9eab); 40 | function hi() { 41 | var _0xbfcc18 = _0x5217; 42 | console[_0xbfcc18(0xb3)](_0xbfcc18(0xb6)); 43 | } 44 | hi(); 45 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/missing_parens.output.js: -------------------------------------------------------------------------------- 1 | null != (_0x4fda33 = _0x432a5f._w).GG; 2 | 3 | _0x47f3d0 = 4 | (anon1_arg1) => 5 | ( 6 | anon2_arg1, 7 | anon2_arg2, 8 | anon2_arg3, 9 | anon2_arg4, 10 | anon2_arg5, 11 | anon2_arg6, 12 | anon2_arg7, 13 | anon2_arg8, 14 | anon2_arg9, 15 | anon2_arg10, 16 | anon2_arg11, 17 | anon2_arg12, 18 | anon2_arg13 19 | ) => 20 | ((anon2_arg2 = _0x1c396f(anon2_arg3) 21 | ? "" 22 | : _0x1c396f(anon2_arg4) 23 | ? ">=" + anon2_arg3 + a0_0x5dad("0x32b1") + (anon1_arg1 ? "-0" : "") 24 | : _0x1c396f(anon2_arg5) 25 | ? ">=" + anon2_arg3 + "." + anon2_arg4 + ".0" + (anon1_arg1 ? "-0" : "") 26 | : anon2_arg6 27 | ? ">=" + anon2_arg2 28 | : ">=" + anon2_arg2 + (anon1_arg1 ? "-0" : "")) + 29 | "\x20" + 30 | (anon2_arg8 = _0x1c396f(anon2_arg9) 31 | ? "" 32 | : _0x1c396f(anon2_arg10) 33 | ? "<" + (+anon2_arg9 + 1) + a0_0x5dad("0x323a") 34 | : _0x1c396f(anon2_arg11) 35 | ? "<" + anon2_arg9 + "." + (+anon2_arg10 + 1) + a0_0x5dad("0x7d") 36 | : anon2_arg12 37 | ? "<=" + anon2_arg9 + "." + anon2_arg10 + "." + anon2_arg11 + "-" + anon2_arg12 38 | : anon1_arg1 39 | ? "<" + anon2_arg9 + "." + anon2_arg10 + "." + (+anon2_arg11 + 1) + "-0" 40 | : "<=" + anon2_arg8)).foobar(); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "constant-fold", 3 | "version": "1.0.0", 4 | "description": "Constant folding js", 5 | "main": "main.ts", 6 | "dependencies": { 7 | "@babel/helper-validator-identifier": "^7.14.0", 8 | "@types/jscodeshift": "^0.11.0", 9 | "babel": "^6.23.0", 10 | "babel-plugin-constant-folding": "^1.0.1", 11 | "babel-plugin-minify-constant-folding": "^0.5.0", 12 | "jscodeshift": "^0.12.0", 13 | "safe-eval": "^0.4.1" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.14.3", 17 | "@babel/preset-env": "^7.14.4", 18 | "@types/jest": "^26.0.23", 19 | "babel-cli": "^6.26.0", 20 | "babel-jest": "^27.0.2", 21 | "jest": "^27.0.4", 22 | "patch-package": "^6.4.7", 23 | "postinstall-postinstall": "^2.1.0" 24 | }, 25 | "scripts": { 26 | "postinstall": "patch-package", 27 | "prepare": "npx crlf --set=LF node_modules/.bin/jscodeshift", 28 | "test": "jest", 29 | "ex": "jscodeshift -t transforms/constant-fold.ts data/breakthecode.js --dry --print", 30 | "updateTest1": "/bin/cp transforms/__testfixtures__/breakthecode.{input,output}.js ; yarn jscodeshift -t transforms/constant-fold.ts transforms/__testfixtures__/breakthecode.output.js", 31 | "updateTest2": "/bin/cp transforms/__testfixtures__/breakthecode.simplified.{input,output}.js ; yarn jscodeshift -t transforms/constant-fold.ts transforms/__testfixtures__/breakthecode.simplified.output.js" 32 | }, 33 | "author": "", 34 | "license": "ISC" 35 | } 36 | -------------------------------------------------------------------------------- /patches/ast-types+0.14.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/ast-types/lib/node-path.d.ts b/node_modules/ast-types/lib/node-path.d.ts 2 | index b8b11d1..b48b959 100644 3 | --- a/node_modules/ast-types/lib/node-path.d.ts 4 | +++ b/node_modules/ast-types/lib/node-path.d.ts 5 | @@ -5,8 +5,11 @@ import { Scope } from "./scope"; 6 | export interface NodePath extends Path { 7 | node: N; 8 | parent: any; 9 | - scope: any; 10 | + scope: Scope | null; 11 | replace: Path['replace']; 12 | + get(name: K): NodePath; 13 | + get(name: K1, name2 : K2): NodePath; 14 | + // get(...names: string[]): NodePath; 15 | prune(...args: any[]): any; 16 | _computeNode(): any; 17 | _computeParent(): any; 18 | diff --git a/node_modules/ast-types/lib/path.d.ts b/node_modules/ast-types/lib/path.d.ts 19 | index feef1f1..34c8b8d 100644 20 | --- a/node_modules/ast-types/lib/path.d.ts 21 | +++ b/node_modules/ast-types/lib/path.d.ts 22 | @@ -3,10 +3,12 @@ import { ASTNode } from "./types"; 23 | export interface Path { 24 | value: V; 25 | parentPath: any; 26 | - name: any; 27 | + name: string; 28 | __childCache: object | null; 29 | getValueProperty(name: any): any; 30 | - get(...names: any[]): any; 31 | + get(name: K): Path; 32 | + get(name: K1, name2 : K2): Path; 33 | + get(...names: string[]): Path; 34 | each(callback: any, context: any): any; 35 | map(callback: any, context: any): any; 36 | filter(callback: any, context: any): any; 37 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/breakthecode.output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var v4 = new Vue({ 3 | 'el': "#app", 4 | 'data': { 5 | 'input': '', 6 | 'flags': { 7 | 'cooldown': false, 8 | 'loading': false, 9 | 'error': false 10 | } 11 | }, 12 | 'computed': { 13 | 'disableSubmit': function disableSubmit() { 14 | return this.input == '' || this.flags.loading || this.flags.cooldown; 15 | } 16 | }, 17 | 'methods': { 18 | 'submit': function submit() { 19 | var v7 = this; 20 | !this.disableSubmit && this.input !== '' && (this.flags.error = false, 21 | this.flags.loading = true, 22 | setTimeout(function() { 23 | axios.post("/puzzle-new", { 24 | 'answer': v7.input 25 | }, { 26 | 'headers': { 27 | 'X-CSRF-TOKEN': document.querySelector('meta[name=\x22csrf-token\x22]').getAttribute('content'), 28 | 'Content-Type': "application/json" 29 | } 30 | }).then(function(anon2_arg1) { 31 | anon2_arg1.data && (v7.flags.loading = false, 32 | document.querySelector('html,\x20body').classList.add("modal-open"), 33 | document.querySelector('.answer-popup__div').innerHTML = anon2_arg1.data.content, 34 | document.querySelector(".answer-popup-success").classList.remove("hidden")); 35 | })['catch'](function(anon3_arg1) { 36 | anon3_arg1.response && ((anon3_arg1.response.status == 400 || anon3_arg1.response.status == 422 || anon3_arg1.response.status == 429) && (v7.flags.loading = false, 37 | v7.flags.error = true, 38 | v7.flags.cooldown = true, 39 | setTimeout(function() { 40 | v7.flags.cooldown = false; 41 | }, 5000))); 42 | }); 43 | }, 1500)); 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/breakthecode.simplified.output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var v2 = new Vue({ 3 | 'el': "#app", 4 | 'data': { 5 | 'input': '', 6 | 'flags': { 7 | 'cooldown': false, 8 | 'loading': false, 9 | 'error': false 10 | } 11 | }, 12 | 'computed': { 13 | 'disableSubmit': function disableSubmit() { 14 | return this.input == '' || this.flags.loading || this.flags.cooldown; 15 | } 16 | }, 17 | 'methods': { 18 | 'submit': function submit() { 19 | var v3 = this; 20 | !this.disableSubmit && this.input !== '' && (this.flags.error = false, 21 | this.flags.loading = true, 22 | setTimeout(function() { 23 | axios.post("/puzzle-new", { 24 | 'answer': v3.input 25 | }, { 26 | 'headers': { 27 | 'X-CSRF-TOKEN': document.querySelector('meta[name=\x22csrf-token\x22]').getAttribute('content'), 28 | 'Content-Type': "application/json" 29 | } 30 | }).then(function(anon2_arg1) { 31 | anon2_arg1.data && (v3.flags.loading = false, 32 | document.querySelector('html,\x20body').classList.add("modal-open"), 33 | document.querySelector('.answer-popup__div').innerHTML = anon2_arg1.data.content, 34 | document.querySelector(".answer-popup-success").classList.remove("hidden")); 35 | })['catch'](function(anon3_arg1) { 36 | anon3_arg1.response && ((anon3_arg1.response.status == 400 || anon3_arg1.response.status == 422 || anon3_arg1.response.status == 429) && (v3.flags.loading = false, 37 | v3.flags.error = true, 38 | v3.flags.cooldown = true, 39 | setTimeout(function() { 40 | v3.flags.cooldown = false; 41 | }, 5000))); 42 | }); 43 | }, 1500)); 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /transforms/jsunscramble.js: -------------------------------------------------------------------------------- 1 | fs = require("fs"); 2 | 3 | // Experiment with decoding the obfuscation on jscrambler.com 4 | // Currently hard-coded with the key on the main-website 5 | 6 | Number.prototype.mod = function (n) { 7 | return ((this % n) + n) % n; 8 | }; 9 | 10 | // Decoder with the specific key used in the js-file on jscrambler.com 11 | calcIdx = (a, w) => (+w - +15 * a).mod(48); 12 | 13 | // matcher = /[a-zA-Z0-9]*\.[Ir]8L\(\)\[([0-9]*)\]\[([0-9]*)\]\[([0-9]*)\]/g; 14 | matcher = /[a-zA-Z0-9]*\.[Ir]8L\(\)(\[([0-9]*)\])*/g; 15 | innerMatch = /\[([0-9]*)\]/g; 16 | 17 | deobf = (str) => str.replace(matcher, replacer); 18 | 19 | replacer = (str) => { 20 | let numbers = [...str.matchAll(innerMatch)].map(([_, x]) => +x); 21 | return numbers.reduce(calcIdx); 22 | }; 23 | 24 | var regexpSpecial = [ 25 | "[", 26 | "]", 27 | "(", 28 | ")", 29 | "{", 30 | "}", 31 | "*", 32 | "+", 33 | "?", 34 | "|", 35 | "^", 36 | "$", 37 | ".", 38 | "\\", 39 | ]; 40 | 41 | // replacer = (_, a, w, inner_w) => { 42 | // let inner_a = calcIdx(a, w); 43 | // return (result = calcIdx(inner_a, inner_w)); 44 | // }; 45 | 46 | function unescapeAll(str) { 47 | // return str.replace(/\\(x|u00)([0-9a-f]{2})/gi, (a,b,nr) => String.fromCharCode(parseInt(nr,16))) 48 | return str.replace(/\\(x|u00)([0-9a-f]{2})/gi, (orig, b, hex_nr) => { 49 | const nr = parseInt(hex_nr, 16); 50 | if (nr < 32 || nr >= 128) return orig; 51 | const value = String.fromCharCode(nr); 52 | return regexpSpecial.includes(value) ? "\\" + value : value; 53 | }); 54 | } 55 | 56 | fs.readFile("data/jscrambler.js", "utf8", (e, x) => { 57 | global.code = x; 58 | global.lines = x.split("\n"); 59 | let result = deobf(x); 60 | result = unescapeAll(result); 61 | 62 | fs.writeFile("data/jscrambler.deobf1.js", result, "utf8", () => null); 63 | }); 64 | 65 | // [_, a, w, inner_w] = lines[87088].match(matcher); 66 | // 67 | // inner_a = calcIdx(a, w); 68 | // result = calcIdx(inner_a, inner_w); 69 | 70 | // lines.filter(x=>x.match(/[rI]8L/)) 71 | 72 | // See this paper for an algorithm for decompilation: 73 | // https://net.cs.uni-bonn.de/fileadmin/ag/martini/Staff/yakdan/dream_ndss2015.pdf 74 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/breakthecode.simplified.input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Version with the function for rotating the array already (manually) executed 3 | var v1 = ["680575VSjskW", "hidden", "#app", "input", "add", "data", "application/json", "1138771DnkNzm", "querySelector", "2XDTygo", "1079yilIzd", "368168VvQIgG", "14081gJOvPZ", "getAttribute", "208866QssNBc", "/puzzle-new", "cooldown", "response", "remove", "modal-open", "status", "flags", "1512231DKtOVj", "then", "loading", "error", "disableSubmit", "3171IGRaWO", ".answer-popup-success", "127LGCEKU"] 4 | var v6 = new Vue({ 5 | 'el': v1[2], 6 | 'data': { 7 | 'input': '', 8 | 'flags': { 9 | 'cooldown': false, 10 | 'loading': false, 11 | 'error': false 12 | } 13 | }, 14 | 'computed': { 15 | 'disableSubmit': function disableSubmit() { 16 | return this.input == '' || this.flags[v1[24]] || this[v1[21]][v1[16]]; 17 | } 18 | }, 19 | 'methods': { 20 | 'submit': function submit() { 21 | var v9 = this; 22 | !this[v1[26]] && this[v1[3]] !== '' && (this[v1[21]][v1[25]] = false, 23 | this[v1[21]].loading = true, 24 | setTimeout(function() { 25 | axios.post(v1[15], { 26 | 'answer': v9[v1[3]] 27 | }, { 28 | 'headers': { 29 | 'X-CSRF-TOKEN': document[v1[8]]('meta[name=\x22csrf-token\x22]')[v1[13]]('content'), 30 | 'Content-Type': v1[6] 31 | } 32 | })[v1[23]](function(anon3_arg1) { 33 | anon3_arg1.data && (v9.flags[v1[24]] = false, 34 | document[v1[8]]('html,\x20body').classList[v1[4]](v1[19]), 35 | document[v1[8]]('.answer-popup__div').innerHTML = anon3_arg1[v1[5]].content, 36 | document[v1[8]](v1[28]).classList[v1[18]](v1[1])); 37 | })['catch'](function(anon4_arg1) { 38 | anon4_arg1[v1[17]] && ((anon4_arg1.response[v1[20]] == 400 || anon4_arg1[v1[17]][v1[20]] == 422 || anon4_arg1[v1[17]][v1[20]] == 429) && (v9[v1[21]][v1[24]] = false, 39 | v9.flags[v1[25]] = true, 40 | v9[v1[21]][v1[16]] = true, 41 | setTimeout(function() { 42 | v9[v1[21]][v1[16]] = false; 43 | }, 5000))); 44 | }); 45 | }, 1500)); 46 | } 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /transforms/goto-to-structured.ts: -------------------------------------------------------------------------------- 1 | // transforms/implicit-icons-to-explicit-imports.ts 2 | import core, { 3 | ASTNode, 4 | ASTPath, 5 | Identifier, 6 | JSCodeshift, 7 | Transform, 8 | } from "jscodeshift"; 9 | import { Type } from "ast-types/lib/types"; 10 | 11 | // See this paper for an algorithm for decompilation: 12 | // https://net.cs.uni-bonn.de/fileadmin/ag/martini/Staff/yakdan/dream_ndss2015.pdf 13 | 14 | // Convert code like 15 | /* 16 | var z = 2; 17 | for (; z !== 32; ) { 18 | switch (z) { 19 | case 2: var sum = 0; z = 14; break; 20 | case 14: x = 0; z = 13; break; 21 | case 13: z = x < 48 ? 12 : 33; break; 22 | case 12: sum += x; z = 11; break; 23 | case 11: x += 1; z = 13; break; 24 | case 33: return sum; break; 25 | } 26 | } 27 | */ 28 | // into 29 | /* 30 | var sum = 0 31 | for (var x = 0; x < 48; x += 1) { 32 | sum += x; 33 | } 34 | 35 | */ 36 | 37 | const transform: Transform = (file, api, options) => { 38 | // Alias the jscodeshift API for ease of use. 39 | const j = api.jscodeshift; 40 | 41 | // Convert the entire file source into a collection of nodes paths. 42 | console.log("Parsing"); 43 | const root = j(file.source, {}); 44 | console.log("Parsed"); 45 | 46 | // const dscr = root.find(j.SwitchStatement).paths()[0].value.discriminant 47 | root.find(j.SwitchStatement).forEach((pth) => { 48 | const jpth = j(pth); 49 | const dscr = pth.value.discriminant; 50 | if (dscr.type != "Identifier") return; 51 | const scope = jpth.closestScope(); 52 | const vds = scope.findVariableDeclarators(dscr.name); 53 | if (vds.length != 1) return; 54 | const [vd] = vds.paths(); 55 | console.log(j(vd).toSource()); 56 | 57 | const initiator = vd.value.init; 58 | if (initiator?.type !== "Literal") return; 59 | const initial_state = initiator.value; 60 | 61 | if ( 62 | !checkPath(j.BlockStatement)(pth.parent) || 63 | pth.parent.node.body.length != 1 64 | ) 65 | return; 66 | const parent = pth.parent.parent; 67 | if (!checkPath(j.ForStatement)(parent)) return; 68 | const end_test = parent.node.test; 69 | console.log(j(end_test).toSource()); 70 | 71 | j.BinaryExpression.check(end_test) 72 | root.map 73 | j(parent).every 74 | 75 | console.log({initial_state}) 76 | 77 | return; 78 | }); 79 | 80 | const result = root.toSource(); 81 | return result; 82 | // decl.paths()[0] 83 | }; 84 | 85 | export default transform; 86 | 87 | const checkPath = 88 | (t: Type) => 89 | (pth: ASTPath): pth is ASTPath => { 90 | return t.check(pth.value); 91 | }; 92 | -------------------------------------------------------------------------------- /data/jscrambler_short.js: -------------------------------------------------------------------------------- 1 | // See this paper for an algorithm for decompilation: 2 | // https://net.cs.uni-bonn.de/fileadmin/ag/martini/Staff/yakdan/dream_ndss2015.pdf 3 | 4 | c6DD[20358] = (function(C, E, O) { 5 | var K = 2; 6 | for (; K !== 1; ) { 7 | switch (K) { 8 | case 2: 9 | return { 10 | l8L: (function o(C, u, V) { 11 | var z = 2; 12 | for (; z !== 32; ) { 13 | switch (z) { 14 | // var J = []; 15 | case 2: var J = []; z = 1; break; 16 | // var x, a n, i, B, F, w; 17 | case 1: var x; var a; var n; z = 3; break; 18 | case 3: var i; var B; var f; z = 7; break; 19 | case 7: var F; var w; z = 14; break; 20 | 21 | // for (x = 0; x < 48; x+=1) { 22 | case 14: x = 0; z = 13; break; 23 | case 13: z = x < 48 ? 12 : 10; break; 24 | // J[x] = [] 25 | case 12: J[x] = []; z = 11; break; 26 | case 11: x += 1; z = 13; break; 27 | // } 28 | 29 | // for (a = 0; a < 48; a++) { 30 | case 10: a = 0; z = 20; break; 31 | case 20: z = a < 48 ? 19 : 33; break; 32 | // n = 47 33 | case 19: n = 48 - 1; z = 18; break; 34 | // while (n >= 0) { 35 | case 18: z = n >= 0 ? 17 : 34; break; 36 | // i = 0; B = 0; 37 | case 17: i = 0; B = 0; z = 15; break; 38 | // f = B; 39 | case 15: f = B; z = 27; break; 40 | // do { 41 | // f = B; B = V[i]; F = B - f; i++; 42 | // f = 0; B = 48; F = 48; 43 | case 27: f = B; B = V[i]; F = B - f; i++; z = 23; break; 44 | case 23: z = n >= B ? 27 : 22; break; 45 | // } while (n >= B) // Loop until B fits in n 46 | 47 | // w = f + (n - f + u * a) % F; 48 | // w = (n + 15 * a) % 48 49 | // inverse: n = (((w - 15 * a) % 48) + 48) % 48 50 | case 22: w = f + (n - f + u * a) % F; 51 | J[a][w] = J[n]; z = 35; break; 52 | case 35: n -= 1; z = 18; break; 53 | // } 54 | case 34: a += 1; z = 20; break; 55 | // } 56 | 57 | case 33: return J; break; 58 | 59 | } 60 | } 61 | } 62 | )(48, E, O) 63 | }; 64 | break; 65 | } 66 | } 67 | } 68 | )(48, 15, [48]); -------------------------------------------------------------------------------- /transforms/guess-names.ts: -------------------------------------------------------------------------------- 1 | import core, { 2 | ASTNode, 3 | ASTPath, 4 | Identifier, 5 | JSCodeshift, 6 | Transform, 7 | } from "jscodeshift"; 8 | import { Type } from "ast-types/lib/types"; 9 | 10 | // Guess the name of variables based on context 11 | // For use together with the `debundle` tool 12 | // since you probably won't get standalone obfuscated modules very often 13 | 14 | // One easy case is: 15 | // var { foo: v3856 } = baz; 16 | // v3856 + 1; 17 | // => 18 | // var { foo: foo } = baz; 19 | // foo + 1; 20 | 21 | // We can also guess that imports from a module should be named the same as the module: 22 | // var v324 = require('foo/bar'); 23 | // => 24 | // var bar = require('foo/bar'); 25 | // 26 | // (or foo? or fooBar?) 27 | 28 | // When we import a single item, use that for name. 29 | // var v324 = require('foo/bar').Baz; 30 | // => 31 | // var Baz = require('foo/bar').Baz; 32 | 33 | // We could generalize this to not just requires: 34 | // var aaa = foo().bar; 35 | // => 36 | // var bar = foo().bar; 37 | 38 | // Remember to check for duplicate names! 39 | // var v324 = require('foo/bar'); 40 | // var v538 = require('baz/bar'); 41 | // => 42 | // var bar = require('foo/bar'); 43 | // var bar1 = require('baz/bar'); 44 | 45 | // More ideas: 46 | // When an object {foo: a, bar: b} is returned 47 | // we can rename a to foo and b to bar (just check that the name is not already used) 48 | 49 | // Similarly: 50 | // app.updatedDate = foo 51 | // => 52 | // app.updatedDate = updatedDate 53 | 54 | // For Angular: 55 | // Figure out names based on dependency injection 56 | /* 57 | nest3_arg1.controller("BaseCtrl", [ 58 | "$scope", 59 | "$rootScope", 60 | function (nest4_arg1, nest4_arg2) { 61 | // ... 62 | } 63 | ]) 64 | */ 65 | // => 66 | /* 67 | nest3_arg1.controller("BaseCtrl", [ 68 | "$scope", 69 | "$rootScope", 70 | function ($scope, $rootScope) { 71 | // ... 72 | } 73 | ]) 74 | */ 75 | 76 | const transform: Transform = (file, api, options) => { 77 | // Alias the jscodeshift API for ease of use. 78 | const j = api.jscodeshift; 79 | 80 | // Convert the entire file source into a collection of nodes paths. 81 | // console.log("Parsing"); 82 | const root = j(file.source, {}); 83 | // console.log("Parsed"); 84 | 85 | // j('var a = 1; function(){ var b = 2;}') 86 | // does the outer scope declare b? 87 | 88 | const vds = root.findVariableDeclarators() // .map(x=>x.get("id")).filter(x => j.ObjectPattern.check(x)) 89 | 90 | vds.forEach(pth => { 91 | 92 | const init = pth.node.init 93 | if (!(init && init.type == 'CallExpression' 94 | && init.callee.type == 'Identifier' 95 | && init.callee.name == 'require' 96 | && init.arguments.length === 1 97 | && init.arguments[0].type === 'Literal' 98 | && typeof init.arguments[0].value === 'string')) return 99 | const newName = init.arguments[0].value 100 | .replace(/[\/.@-]+/g,'_') 101 | .replace(/^([0-9])/,'_$1') 102 | j(pth).renameTo(newName) 103 | }) 104 | // vds.paths()[0].scope.declares("v3856") 105 | // console.log(vds.paths()[0].node) 106 | const result = root.toSource(); 107 | return result; 108 | }; 109 | 110 | export default transform; 111 | 112 | const checkPath = 113 | (t: Type) => 114 | (pth: ASTPath): pth is ASTPath => { 115 | return t.check(pth.value); 116 | }; 117 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/obfuscate.io.complex.output.js: -------------------------------------------------------------------------------- 1 | function f1() { 2 | var v3 = (function () { 3 | var v4 = true; 4 | return function (anon2_arg1, anon2_arg2) { 5 | var v5 = v4 6 | ? function () { 7 | if (anon2_arg2) { 8 | var v7 = anon2_arg2.apply( 9 | anon2_arg1, 10 | arguments 11 | ); 12 | return (anon2_arg2 = null), v7; 13 | } 14 | } 15 | : function () {}; 16 | return (v4 = false), v5; 17 | }; 18 | })(), 19 | v8 = v3(this, function () { 20 | function f2() { 21 | var v10 = f2.constructor("return /\" + this + \"/")().constructor("^([^ ]+( +[^ ]+)+)+[^ ]}"); 22 | return !v10.test(v8); 23 | } 24 | return f2(); 25 | }); 26 | v8(); 27 | var v11 = (function () { 28 | var v12 = true; 29 | return function (anon7_arg1, anon7_arg2) { 30 | var v13 = v12 31 | ? function () { 32 | if (anon7_arg2) { 33 | var v15 = anon7_arg2.apply(anon7_arg1, arguments); 34 | return (anon7_arg2 = null), v15; 35 | } 36 | } 37 | : function () {}; 38 | return (v12 = false), v13; 39 | }; 40 | })(); 41 | (function () { 42 | v11(this, function () { 43 | var v17 = new RegExp("function *\\( *\\)"), 44 | v18 = new RegExp( 45 | "\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", 46 | "i" 47 | ), 48 | v19 = f3("init"); 49 | !v17.test(v19 + "chain") || 50 | !v18.test(v19 + "input") 51 | ? v19("0") 52 | : f3(); 53 | })(); 54 | })(); 55 | var v20 = (function () { 56 | var v21 = true; 57 | return function (anon13_arg1, anon13_arg2) { 58 | var v22 = v21 59 | ? function () { 60 | if (anon13_arg2) { 61 | var v24 = anon13_arg2.apply( 62 | anon13_arg1, 63 | arguments 64 | ); 65 | return (anon13_arg2 = null), v24; 66 | } 67 | } 68 | : function () {}; 69 | return (v21 = false), v22; 70 | }; 71 | })(), 72 | v25 = v20(this, function () { 73 | var v27; 74 | try { 75 | var v28 = Function("return (function() " + "{}.constructor(\"return this\")( )" + ");"); 76 | v27 = v28(); 77 | } catch (_0x5849c3) { 78 | v27 = window; 79 | } 80 | var v29 = (v27.console = 81 | v27.console || {}), 82 | v30 = [ 83 | "log", 84 | "warn", 85 | "info", 86 | "error", 87 | "exception", 88 | "table", 89 | "trace", 90 | ]; 91 | for ( 92 | var v31 = 0; 93 | v31 < v30.length; 94 | v31++ 95 | ) { 96 | var v32 = 97 | v20.constructor.prototype.bind(v20), 98 | v33 = v30[v31], 99 | v34 = v29[v33] || v32; 100 | (v32.__proto__ = v20.bind(v20)), 101 | (v32.toString = 102 | v34.toString.bind(v34)), 103 | (v29[v33] = v32); 104 | } 105 | }); 106 | v25(), console.log("Hello World!"); 107 | } 108 | f1(); 109 | function f3(f3_arg1) { 110 | function f4(f4_arg1) { 111 | if (typeof f4_arg1 === "string") 112 | return function (anon17_arg1) {}.constructor("while\x20(true)\x20{}").apply("counter"); 113 | else 114 | ("" + f4_arg1 / f4_arg1).length !== 1 || 115 | f4_arg1 % 20 === 0 116 | ? function () { 117 | return true; 118 | }.constructor("debu" + "gger").call("action") 119 | : function () { 120 | return false; 121 | }.constructor("debu" + "gger").apply("stateObject"); 122 | f4(++f4_arg1); 123 | } 124 | try { 125 | if (f3_arg1) return f4; 126 | else f4(0); 127 | } catch (_0x27d4d2) {} 128 | } 129 | setInterval(function () { 130 | f3(); 131 | }, 4000); 132 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/breakthecode.input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _0x2889 = ['2XDTygo', '1079yilIzd', '368168VvQIgG', '14081gJOvPZ', 'getAttribute', '208866QssNBc', '/puzzle-new', 'cooldown', 'response', 'remove', 'modal-open', 'status', 'flags', '1512231DKtOVj', 'then', 'loading', 'error', 'disableSubmit', '3171IGRaWO', '.answer-popup-success', '127LGCEKU', '680575VSjskW', 'hidden', '#app', 'input', 'add', 'data', 'application/json', '1138771DnkNzm', 'querySelector']; 3 | var _0x5da1ab = _0x2d95; 4 | function _0x2d95(_0xae0ee9, _0x35d517) { 5 | _0xae0ee9 = _0xae0ee9 - 0x148; 6 | var _0x288923 = _0x2889[_0xae0ee9]; 7 | return _0x288923; 8 | } 9 | (function(_0x291c92, _0x1a9ab1) { 10 | var _0x11122d = _0x2d95; 11 | while (!![]) { 12 | try { 13 | var _0x19959c = -parseInt(_0x11122d(0x15e)) + parseInt(_0x11122d(0x14f)) + parseInt(_0x11122d(0x148)) + -parseInt(_0x11122d(0x165)) * parseInt(_0x11122d(0x154)) + -parseInt(_0x11122d(0x156)) + -parseInt(_0x11122d(0x151)) * parseInt(_0x11122d(0x153)) + -parseInt(_0x11122d(0x152)) * -parseInt(_0x11122d(0x163)); 14 | if (_0x19959c === _0x1a9ab1) 15 | break; 16 | else 17 | _0x291c92['push'](_0x291c92['shift']()); 18 | } catch (_0x9a3e53) { 19 | _0x291c92['push'](_0x291c92['shift']()); 20 | } 21 | } 22 | }(_0x2889, 0xf2f3f)); 23 | var app = new Vue({ 24 | 'el': _0x5da1ab(0x14a), 25 | 'data': { 26 | 'input': '', 27 | 'flags': { 28 | 'cooldown': ![], 29 | 'loading': ![], 30 | 'error': ![] 31 | } 32 | }, 33 | 'computed': { 34 | 'disableSubmit': function disableSubmit() { 35 | var _0x468bce = _0x5da1ab; 36 | return this['input'] == '' || this['flags'][_0x468bce(0x160)] || this[_0x468bce(0x15d)][_0x468bce(0x158)]; 37 | } 38 | }, 39 | 'methods': { 40 | 'submit': function submit() { 41 | var _0x3176b0 = _0x5da1ab 42 | , _0x19e8bc = this; 43 | !this[_0x3176b0(0x162)] && this[_0x3176b0(0x14b)] !== '' && (this[_0x3176b0(0x15d)][_0x3176b0(0x161)] = ![], 44 | this[_0x3176b0(0x15d)]['loading'] = !![], 45 | setTimeout(function() { 46 | var _0x22faee = _0x3176b0; 47 | axios['post'](_0x22faee(0x157), { 48 | 'answer': _0x19e8bc[_0x22faee(0x14b)] 49 | }, { 50 | 'headers': { 51 | 'X-CSRF-TOKEN': document[_0x22faee(0x150)]('meta[name=\x22csrf-token\x22]')[_0x22faee(0x155)]('content'), 52 | 'Content-Type': _0x22faee(0x14e) 53 | } 54 | })[_0x22faee(0x15f)](function(_0x2f653a) { 55 | var _0xc793eb = _0x22faee; 56 | _0x2f653a['data'] && (_0x19e8bc['flags'][_0xc793eb(0x160)] = ![], 57 | document[_0xc793eb(0x150)]('html,\x20body')['classList'][_0xc793eb(0x14c)](_0xc793eb(0x15b)), 58 | document[_0xc793eb(0x150)]('.answer-popup__div')['innerHTML'] = _0x2f653a[_0xc793eb(0x14d)]['content'], 59 | document[_0xc793eb(0x150)](_0xc793eb(0x164))['classList'][_0xc793eb(0x15a)](_0xc793eb(0x149))); 60 | })['catch'](function(_0x3b2622) { 61 | var _0x10d1da = _0x22faee; 62 | _0x3b2622[_0x10d1da(0x159)] && ((_0x3b2622['response'][_0x10d1da(0x15c)] == 0x190 || _0x3b2622[_0x10d1da(0x159)][_0x10d1da(0x15c)] == 0x1a6 || _0x3b2622[_0x10d1da(0x159)][_0x10d1da(0x15c)] == 0x1ad) && (_0x19e8bc[_0x10d1da(0x15d)][_0x10d1da(0x160)] = ![], 63 | _0x19e8bc['flags'][_0x10d1da(0x161)] = !![], 64 | _0x19e8bc[_0x10d1da(0x15d)][_0x10d1da(0x158)] = !![], 65 | setTimeout(function() { 66 | var _0x53d4de = _0x10d1da; 67 | _0x19e8bc[_0x53d4de(0x15d)][_0x53d4de(0x158)] = ![]; 68 | }, 0x1388))); 69 | }); 70 | }, 0x5dc)); 71 | } 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /breakthecode.orig.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _0x2889 = [ 3 | "2XDTygo", 4 | "1079yilIzd", 5 | "368168VvQIgG", 6 | "14081gJOvPZ", 7 | "getAttribute", 8 | "208866QssNBc", 9 | "/puzzle-new", 10 | "cooldown", 11 | "response", 12 | "remove", 13 | "modal-open", 14 | "status", 15 | "flags", 16 | "1512231DKtOVj", 17 | "then", 18 | "loading", 19 | "error", 20 | "disableSubmit", 21 | "3171IGRaWO", 22 | ".answer-popup-success", 23 | "127LGCEKU", 24 | "680575VSjskW", 25 | "hidden", 26 | "#app", 27 | "input", 28 | "add", 29 | "data", 30 | "application/json", 31 | "1138771DnkNzm", 32 | "querySelector", 33 | ]; 34 | var _0x5da1ab = _0x2d95; 35 | function _0x2d95(_0xae0ee9, _0x35d517) { 36 | _0xae0ee9 = _0xae0ee9 - 0x148; 37 | var _0x288923 = _0x2889[_0xae0ee9]; 38 | return _0x288923; 39 | } 40 | (function (_0x291c92, _0x1a9ab1) { 41 | var _0x11122d = _0x2d95; 42 | while (!![]) { 43 | try { 44 | var _0x19959c = 45 | -parseInt(_0x11122d(0x15e)) + 46 | parseInt(_0x11122d(0x14f)) + 47 | parseInt(_0x11122d(0x148)) + 48 | -parseInt(_0x11122d(0x165)) * parseInt(_0x11122d(0x154)) + 49 | -parseInt(_0x11122d(0x156)) + 50 | -parseInt(_0x11122d(0x151)) * parseInt(_0x11122d(0x153)) + 51 | -parseInt(_0x11122d(0x152)) * -parseInt(_0x11122d(0x163)); 52 | if (_0x19959c === _0x1a9ab1) break; 53 | else _0x291c92.push(_0x291c92.shift()); 54 | } catch (_0x9a3e53) { 55 | _0x291c92.push(_0x291c92.shift()); 56 | } 57 | } 58 | })(_0x2889, 0xf2f3f); 59 | var app = new Vue({ 60 | el: _0x5da1ab(0x14a), 61 | data: { 62 | input: "", 63 | flags: { 64 | cooldown: ![], 65 | loading: ![], 66 | error: ![], 67 | }, 68 | }, 69 | computed: { 70 | disableSubmit: function disableSubmit() { 71 | var _0x468bce = _0x5da1ab; 72 | return ( 73 | this.input == "" || 74 | this.flags[_0x468bce(0x160)] || 75 | this[_0x468bce(0x15d)][_0x468bce(0x158)] 76 | ); 77 | }, 78 | }, 79 | methods: { 80 | submit: function submit() { 81 | var _0x3176b0 = _0x5da1ab, 82 | _0x19e8bc = this; 83 | !this[_0x3176b0(0x162)] && 84 | this[_0x3176b0(0x14b)] !== "" && 85 | ((this[_0x3176b0(0x15d)][_0x3176b0(0x161)] = ![]), 86 | (this[_0x3176b0(0x15d)].loading = !![]), 87 | setTimeout(function () { 88 | var _0x22faee = _0x3176b0; 89 | axios 90 | .post( 91 | _0x22faee(0x157), 92 | { 93 | answer: _0x19e8bc[_0x22faee(0x14b)], 94 | }, 95 | { 96 | headers: { 97 | "X-CSRF-TOKEN": document[_0x22faee(0x150)]( 98 | "meta[name=\x22csrf-token\x22]" 99 | )[_0x22faee(0x155)]("content"), 100 | "Content-Type": _0x22faee(0x14e), 101 | }, 102 | } 103 | ) 104 | [_0x22faee(0x15f)](function (_0x2f653a) { 105 | var _0xc793eb = _0x22faee; 106 | _0x2f653a.data && 107 | ((_0x19e8bc.flags[_0xc793eb(0x160)] = ![]), 108 | document[_0xc793eb(0x150)]("html,\x20body").classList[ 109 | _0xc793eb(0x14c) 110 | ](_0xc793eb(0x15b)), 111 | (document[_0xc793eb(0x150)](".answer-popup__div").innerHTML = 112 | _0x2f653a[_0xc793eb(0x14d)].content), 113 | document[_0xc793eb(0x150)](_0xc793eb(0x164)).classList[ 114 | _0xc793eb(0x15a) 115 | ](_0xc793eb(0x149))); 116 | }) 117 | .catch(function (_0x3b2622) { 118 | var _0x10d1da = _0x22faee; 119 | _0x3b2622[_0x10d1da(0x159)] && 120 | (_0x3b2622.response[_0x10d1da(0x15c)] == 0x190 || 121 | _0x3b2622[_0x10d1da(0x159)][_0x10d1da(0x15c)] == 0x1a6 || 122 | _0x3b2622[_0x10d1da(0x159)][_0x10d1da(0x15c)] == 0x1ad) && 123 | ((_0x19e8bc[_0x10d1da(0x15d)][_0x10d1da(0x160)] = ![]), 124 | (_0x19e8bc.flags[_0x10d1da(0x161)] = !![]), 125 | (_0x19e8bc[_0x10d1da(0x15d)][_0x10d1da(0x158)] = !![]), 126 | setTimeout(function () { 127 | var _0x53d4de = _0x10d1da; 128 | _0x19e8bc[_0x53d4de(0x15d)][_0x53d4de(0x158)] = ![]; 129 | }, 0x1388)); 130 | }); 131 | }, 0x5dc)); 132 | }, 133 | }, 134 | }); 135 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "Launch Program", 10 | "args": [ 11 | "-t", 12 | "transforms/constant-fold.ts", 13 | "data/breakthecode.js", 14 | "--dry", 15 | "--print", 16 | ], 17 | "program": "${workspaceFolder}/node_modules/.bin/jscodeshift", 18 | "request": "launch", 19 | "skipFiles": [ 20 | "/**" 21 | ], 22 | "type": "pwa-node" 23 | } 24 | , 25 | { 26 | "name": "New example", 27 | "args": [ 28 | "-t", 29 | // "transforms/constant-fold.ts", 30 | "transforms/rename-nested.ts", 31 | "/Users/anka/tmp/portingkit/header.out.js", 32 | "--dry", 33 | "--print", 34 | ], 35 | "program": "${workspaceFolder}/node_modules/.bin/jscodeshift", 36 | "request": "launch", 37 | "skipFiles": [ 38 | "/**" 39 | ], 40 | "type": "pwa-node" 41 | } 42 | , 43 | { 44 | "name": "Full example", 45 | "args": [ 46 | "-t", 47 | "transforms/constant-fold.ts", 48 | "/Users/anka/projekt/reversing/portingkit/appo/app.js", 49 | "--dry", 50 | "--print", 51 | ], 52 | "program": "${workspaceFolder}/node_modules/.bin/jscodeshift", 53 | "request": "launch", 54 | "skipFiles": [ 55 | "${workspaceFolder}/node_modules/@babel/template/lib/builder.js", 56 | // "${workspaceFolder}/node_modules/**/*.js", 57 | "/**" 58 | ], 59 | "runtimeArgs": ["--max-old-space-size=4096"], 60 | "type": "pwa-node" 61 | } 62 | , 63 | { 64 | "name": "Rename full example", 65 | "args": [ 66 | "-t", 67 | "transforms/rename-nested.ts", 68 | "/Users/anka/projekt/reversing/portingkit/appo/app.out.js", 69 | "--dry", 70 | "--print", 71 | ], 72 | "program": "${workspaceFolder}/node_modules/.bin/jscodeshift", 73 | "request": "launch", 74 | "skipFiles": [ 75 | "${workspaceFolder}/node_modules/@babel/template/lib/builder.js", 76 | // "${workspaceFolder}/node_modules/**/*.js", 77 | "/**" 78 | ], 79 | "runtimeArgs": ["--max-old-space-size=4096", "--stack-size=16000"], 80 | "type": "pwa-node" 81 | } 82 | , 83 | { 84 | "name": "Goto to structured", 85 | "args": [ 86 | "-t", 87 | "transforms/goto-to-structured.ts", 88 | "data/jscrambler_short.js", 89 | "--dry", 90 | "--print", 91 | ], 92 | "program": "${workspaceFolder}/node_modules/.bin/jscodeshift", 93 | "request": "launch", 94 | "skipFiles": [ 95 | "${workspaceFolder}/node_modules/@babel/template/lib/builder.js", 96 | // "${workspaceFolder}/node_modules/**/*.js", 97 | "/**" 98 | ], 99 | "runtimeArgs": ["--max-old-space-size=4096"], 100 | "type": "pwa-node" 101 | } 102 | 103 | , 104 | { 105 | "type": "node", 106 | "request": "launch", 107 | "name": "Jest Current File", 108 | "program": "${workspaceFolder}/node_modules/.bin/jest", 109 | "args": [ 110 | // "${fileBasenameNoExtension}", 111 | // "--config", 112 | // "jest.config.js" 113 | ], 114 | "console": "integratedTerminal", 115 | "internalConsoleOptions": "neverOpen", 116 | "disableOptimisticBPs": true, 117 | "windows": { 118 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 119 | } 120 | } 121 | 122 | 123 | 124 | // { 125 | // "type": "pwa-node", 126 | // "request": "launch", 127 | // "name": "Launch Program", 128 | // "skipFiles": [ 129 | // "/**" 130 | // ], 131 | // "program": "${workspaceFolder}/transforms/constant-fold.ts", 132 | // "outFiles": [ 133 | // "${workspaceFolder}/**/*.js" 134 | // ] 135 | // } 136 | ] 137 | } -------------------------------------------------------------------------------- /data/breakthecode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _0x2889 = ['2XDTygo', '1079yilIzd', '368168VvQIgG', '14081gJOvPZ', 'getAttribute', '208866QssNBc', '/puzzle-new', 'cooldown', 'response', 'remove', 'modal-open', 'status', 'flags', '1512231DKtOVj', 'then', 'loading', 'error', 'disableSubmit', '3171IGRaWO', '.answer-popup-success', '127LGCEKU', '680575VSjskW', 'hidden', '#app', 'input', 'add', 'data', 'application/json', '1138771DnkNzm', 'querySelector']; 3 | var _0x5da1ab = _0x2d95; 4 | function _0x2d95(_0xae0ee9, _0x35d517) { 5 | _0xae0ee9 = _0xae0ee9 - 0x148; 6 | var _0x288923 = _0x2889[_0xae0ee9]; 7 | return _0x288923; 8 | } 9 | (function(_0x291c92, _0x1a9ab1) { 10 | var _0x11122d = _0x2d95; 11 | while (!![]) { 12 | try { 13 | var _0x19959c = -parseInt(_0x11122d(0x15e)) + parseInt(_0x11122d(0x14f)) + parseInt(_0x11122d(0x148)) + -parseInt(_0x11122d(0x165)) * parseInt(_0x11122d(0x154)) + -parseInt(_0x11122d(0x156)) + -parseInt(_0x11122d(0x151)) * parseInt(_0x11122d(0x153)) + -parseInt(_0x11122d(0x152)) * -parseInt(_0x11122d(0x163)); 14 | if (_0x19959c === _0x1a9ab1) 15 | break; 16 | else 17 | _0x291c92['push'](_0x291c92['shift']()); 18 | } catch (_0x9a3e53) { 19 | _0x291c92['push'](_0x291c92['shift']()); 20 | } 21 | } 22 | }(_0x2889, 0xf2f3f)); 23 | 24 | null != (_0x4fda33 = _0x432a5f['_w'])['GG'] 25 | 26 | _0x47f3d0 = 27 | (_0x1aa8ba) => 28 | ( 29 | _0x5511f6, 30 | _0x1a16de, 31 | _0x899f2, 32 | _0x30b3b5, 33 | _0x87b160, 34 | _0x2b1a1c, 35 | _0x4942fb, 36 | _0x3b57cb, 37 | _0x4d5278, 38 | _0x2a7f55, 39 | _0x4e13a9, 40 | _0x5acb76, 41 | _0x21eca1 42 | ) => 43 | ((_0x1a16de = _0x1c396f(_0x899f2) 44 | ? "" 45 | : _0x1c396f(_0x30b3b5) 46 | ? ">=" + _0x899f2 + a0_0x5dad("0x32b1") + (_0x1aa8ba ? "-0" : "") 47 | : _0x1c396f(_0x87b160) 48 | ? ">=" + _0x899f2 + "." + _0x30b3b5 + ".0" + (_0x1aa8ba ? "-0" : "") 49 | : _0x2b1a1c 50 | ? ">=" + _0x1a16de 51 | : ">=" + _0x1a16de + (_0x1aa8ba ? "-0" : "")) + 52 | "\x20" + 53 | (_0x3b57cb = _0x1c396f(_0x4d5278) 54 | ? "" 55 | : _0x1c396f(_0x2a7f55) 56 | ? "<" + (+_0x4d5278 + 0x1) + a0_0x5dad("0x323a") 57 | : _0x1c396f(_0x4e13a9) 58 | ? "<" + _0x4d5278 + "." + (+_0x2a7f55 + 0x1) + a0_0x5dad("0x7d") 59 | : _0x5acb76 60 | ? "<=" + _0x4d5278 + "." + _0x2a7f55 + "." + _0x4e13a9 + "-" + _0x5acb76 61 | : _0x1aa8ba 62 | ? "<" + _0x4d5278 + "." + _0x2a7f55 + "." + (+_0x4e13a9 + 0x1) + "-0" 63 | : "<=" + _0x3b57cb))[a0_0x5dad("0x3b8d")](); 64 | 65 | 66 | var app = new Vue({ 67 | 'el': _0x5da1ab(0x14a), 68 | 'data': { 69 | 'input': '', 70 | 'flags': { 71 | 'cooldown': ![], 72 | 'loading': ![], 73 | 'error': ![] 74 | } 75 | }, 76 | 'computed': { 77 | 'disableSubmit': function disableSubmit() { 78 | var _0x468bce = _0x5da1ab; 79 | return this['input'] == '' || this['flags'][_0x468bce(0x160)] || this[_0x468bce(0x15d)][_0x468bce(0x158)]; 80 | } 81 | }, 82 | 'methods': { 83 | 'submit': function submit() { 84 | var _0x3176b0 = _0x5da1ab 85 | , _0x19e8bc = this; 86 | !this[_0x3176b0(0x162)] && this[_0x3176b0(0x14b)] !== '' && (this[_0x3176b0(0x15d)][_0x3176b0(0x161)] = ![], 87 | this[_0x3176b0(0x15d)]['loading'] = !![], 88 | setTimeout(function() { 89 | var _0x22faee = _0x3176b0; 90 | axios['post'](_0x22faee(0x157), { 91 | 'answer': _0x19e8bc[_0x22faee(0x14b)] 92 | }, { 93 | 'headers': { 94 | 'X-CSRF-TOKEN': document[_0x22faee(0x150)]('meta[name=\x22csrf-token\x22]')[_0x22faee(0x155)]('content'), 95 | 'Content-Type': _0x22faee(0x14e) 96 | } 97 | })[_0x22faee(0x15f)](function(_0x2f653a) { 98 | var _0xc793eb = _0x22faee; 99 | _0x2f653a['data'] && (_0x19e8bc['flags'][_0xc793eb(0x160)] = ![], 100 | document[_0xc793eb(0x150)]('html,\x20body')['classList'][_0xc793eb(0x14c)](_0xc793eb(0x15b)), 101 | document[_0xc793eb(0x150)]('.answer-popup__div')['innerHTML'] = _0x2f653a[_0xc793eb(0x14d)]['content'], 102 | document[_0xc793eb(0x150)](_0xc793eb(0x164))['classList'][_0xc793eb(0x15a)](_0xc793eb(0x149))); 103 | })['catch'](function(_0x3b2622) { 104 | var _0x10d1da = _0x22faee; 105 | _0x3b2622[_0x10d1da(0x159)] && ((_0x3b2622['response'][_0x10d1da(0x15c)] == 0x190 || _0x3b2622[_0x10d1da(0x159)][_0x10d1da(0x15c)] == 0x1a6 || _0x3b2622[_0x10d1da(0x159)][_0x10d1da(0x15c)] == 0x1ad) && (_0x19e8bc[_0x10d1da(0x15d)][_0x10d1da(0x160)] = ![], 106 | _0x19e8bc['flags'][_0x10d1da(0x161)] = !![], 107 | _0x19e8bc[_0x10d1da(0x15d)][_0x10d1da(0x158)] = !![], 108 | setTimeout(function() { 109 | var _0x53d4de = _0x10d1da; 110 | _0x19e8bc[_0x53d4de(0x15d)][_0x53d4de(0x158)] = ![]; 111 | }, 0x1388))); 112 | }); 113 | }, 0x5dc)); 114 | } 115 | } 116 | }); 117 | -------------------------------------------------------------------------------- /transforms/rename-nested.ts: -------------------------------------------------------------------------------- 1 | // transforms/implicit-icons-to-explicit-imports.ts 2 | import core, { 3 | ASTNode, 4 | ASTPath, 5 | Identifier, 6 | JSCodeshift, 7 | Transform, 8 | } from "jscodeshift"; 9 | import { Type } from "ast-types/lib/types"; 10 | 11 | 12 | const transform: Transform = (file, api, options) => { 13 | // Alias the jscodeshift API for ease of use. 14 | const j = api.jscodeshift; 15 | 16 | // Convert the entire file source into a collection of nodes paths. 17 | Object; 18 | console.log("Parsing"); 19 | const root = j(file.source, {}); 20 | console.log("Parsed"); 21 | 22 | // Rename functions 23 | { 24 | let funNr = 1; 25 | const funDecls = root.find(j.FunctionDeclaration); 26 | funDecls.forEach((vd) => { 27 | // const vd: Collection = j(pth); 28 | const oldName = vd.value.id.name; 29 | // const newName = "f" + funNr++; 30 | const funId = vd.scope.parent._numFuns = vd.scope.parent._numFuns + 1 || 1 31 | const newName = `nest${vd.scope.depth}_f${funId}` 32 | // const newName = "nest" + "f" + funNr++; 33 | const rootScope = vd.parentPath.scope; 34 | const jScope = j(vd.parentPath).closestScope(); 35 | 36 | funNr++; 37 | // const vd = funDecls.at(i); 38 | // pth.value.id.name 39 | // vd.renameTo("f" + varNr); 40 | jScope 41 | .find(j.Identifier, { name: oldName }) 42 | .filter(isVariable(j)) 43 | .forEach((path) => { 44 | // identifier must refer to declared variable 45 | let scope = path.scope; 46 | while (scope && scope !== rootScope) { 47 | if (scope.declares(oldName)) return; 48 | scope = scope.parent; 49 | } 50 | if (!scope) return; // The variable must be declared 51 | path.get("name").replace(newName as unknown as ASTNode); 52 | }); 53 | // varNr++; 54 | }); 55 | console.log(`Renamed ${funNr} functions`); 56 | } 57 | 58 | // j.Function 59 | 60 | // Rename variables 61 | { 62 | let varNr = 1; 63 | const varDecls = root.findVariableDeclarators(); 64 | varDecls.forEach((pth, i) => { 65 | // const vd: Collection = j(pth); 66 | const varId = pth.scope._numVars = pth.scope._numVars + 1 || 1 67 | const vd = varDecls.at(i); 68 | // vd.renameTo("v" + varNr); 69 | vd.renameTo(`n${pth.scope.depth+1}_v${varId}`); 70 | varNr++; 71 | }); 72 | console.log(`Renamed ${varNr} variables`); 73 | } 74 | 75 | 76 | // Rename arguments 77 | const funDeclsA = root.find(j.Function); 78 | let anon = 1; 79 | funDeclsA.forEach((fd, i, pths) => { 80 | // const vd: Collection = j(pth); 81 | let argNr = 1; 82 | const jvd = funDeclsA.at(i); 83 | // const funId = fd.scope.numAnons = fd.scope.numAnons + 1 || 1 84 | // const newName = `nest${fd.scope.depth+1}_anon${funId}` 85 | const newName = `nest${fd.scope.depth+1}` 86 | const fname = fd.value.id?.name ?? newName; 87 | // const fname = fd.value.id?.name ?? "anon" + anon++; 88 | const args = jvd 89 | .map((x) => x.get("params").map((x) => x, null)) 90 | .filter(checkPath(j.Identifier)); 91 | // console.log(args); 92 | args.forEach((vd) => { 93 | const oldName = vd.value.name; 94 | const newName = fname + "_arg" + argNr++; 95 | const rootScope = vd.scope; 96 | const jScope = j(vd).closestScope(); 97 | // const vd = funDecls.at(i); 98 | // pth.value.id.name 99 | // vd.renameTo("f" + varNr); 100 | jScope 101 | .find(j.Identifier, { name: oldName }) 102 | .filter(isVariable(j)) 103 | .forEach((path) => { 104 | // identifier must refer to declared variable 105 | let scope = path.scope; 106 | while (scope && scope !== rootScope) { 107 | if (scope.declares(oldName)) return; 108 | scope = scope.parent; 109 | } 110 | if (!scope) return; // The variable must be declared 111 | // path.get("name").replace(newName as unknown as ASTNode); 112 | path.replace(j.identifier(newName)); 113 | }); 114 | }); 115 | }); 116 | console.log(`Renamed arguments for ${funDeclsA.length} functions`); 117 | 118 | const result = root.toSource(); 119 | return result; 120 | // decl.paths()[0] 121 | }; 122 | 123 | const isVariable = (j: JSCodeshift) => (path: ASTPath) => { 124 | const j = core; 125 | // ignore non-variables 126 | const parent: unknown = path.parent.node; 127 | 128 | if ( 129 | j.MemberExpression.check(parent) && 130 | parent.property === path.node && 131 | !parent.computed 132 | ) { 133 | // obj.oldName 134 | return false; 135 | } 136 | 137 | if ( 138 | j.Property.check(parent) && 139 | parent.key === path.node && 140 | !parent.computed 141 | ) { 142 | // { oldName: 3 } 143 | return false; 144 | } 145 | 146 | if ( 147 | j.MethodDefinition.check(parent) && 148 | parent.key === path.node && 149 | !parent.computed 150 | ) { 151 | // class A { oldName() {} } 152 | return false; 153 | } 154 | 155 | if ( 156 | j.ClassProperty.check(parent) && 157 | parent.key === path.node && 158 | !parent.computed 159 | ) { 160 | // class A { oldName = 3 } 161 | return false; 162 | } 163 | 164 | /* 165 | if ( 166 | j.JSXAttribute.check(parent) && 167 | parent.name === path.node && 168 | # !parent.computed 169 | ) { 170 | # // 171 | return false; 172 | } 173 | */ 174 | 175 | return true; 176 | }; 177 | 178 | const checkPath = 179 | (t: Type) => 180 | (pth: ASTPath): pth is ASTPath => { 181 | return t.check(pth.value); 182 | }; 183 | 184 | export default transform; 185 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/obfuscate.io.complex.input.js: -------------------------------------------------------------------------------- 1 | var _0x5e98 = [ 2 | "91ZDnrwL", 3 | "input", 4 | "12891sEHxyA", 5 | "prototype", 6 | "debu", 7 | "function\x20*\x5c(\x20*\x5c)", 8 | "1Eoaoaz", 9 | "warn", 10 | "return\x20(function()\x20", 11 | "bind", 12 | "gger", 13 | "1teMyUU", 14 | "init", 15 | "1387XENjqG", 16 | "{}.constructor(\x22return\x20this\x22)(\x20)", 17 | "console", 18 | "table", 19 | "stateObject", 20 | "return\x20/\x22\x20+\x20this\x20+\x20\x22/", 21 | "1476591cYbOKc", 22 | "1ZykUsw", 23 | "length", 24 | "^([^\x20]+(\x20+[^\x20]+)+)+[^\x20]}", 25 | "apply", 26 | "801rDJGrU", 27 | "__proto__", 28 | "toString", 29 | "test", 30 | "string", 31 | "Hello\x20World!", 32 | "exception", 33 | "info", 34 | "counter", 35 | "148559oWJwSB", 36 | "action", 37 | "constructor", 38 | "1217722uKbAsf", 39 | "26487viENJc", 40 | "log", 41 | "612676zbKCns", 42 | ]; 43 | (function (_0x572110, _0x20a6a4) { 44 | var _0x54fe68 = _0x4f96; 45 | while (!![]) { 46 | try { 47 | var _0x167224 = 48 | -parseInt(_0x54fe68(0x1b8)) * -parseInt(_0x54fe68(0x19d)) + 49 | parseInt(_0x54fe68(0x195)) * parseInt(_0x54fe68(0x1ae)) + 50 | parseInt(_0x54fe68(0x1b4)) * -parseInt(_0x54fe68(0x1b2)) + 51 | -parseInt(_0x54fe68(0x1ab)) + 52 | parseInt(_0x54fe68(0x19e)) * parseInt(_0x54fe68(0x1b1)) + 53 | -parseInt(_0x54fe68(0x1af)) + 54 | parseInt(_0x54fe68(0x1a2)) * -parseInt(_0x54fe68(0x197)); 55 | if (_0x167224 === _0x20a6a4) break; 56 | else _0x572110["push"](_0x572110["shift"]()); 57 | } catch (_0x452307) { 58 | _0x572110["push"](_0x572110["shift"]()); 59 | } 60 | } 61 | })(_0x5e98, 0xcf003); 62 | function hi() { 63 | var _0x2a2700 = _0x4f96, 64 | _0x3c5957 = (function () { 65 | var _0x3f53d6 = !![]; 66 | return function (_0x26a21f, _0x5d1916) { 67 | var _0xd529ef = _0x3f53d6 68 | ? function () { 69 | var _0x458434 = _0x4f96; 70 | if (_0x5d1916) { 71 | var _0x546caa = _0x5d1916[_0x458434(0x1a1)]( 72 | _0x26a21f, 73 | arguments 74 | ); 75 | return (_0x5d1916 = null), _0x546caa; 76 | } 77 | } 78 | : function () {}; 79 | return (_0x3f53d6 = ![]), _0xd529ef; 80 | }; 81 | })(), 82 | _0x46bce9 = _0x3c5957(this, function () { 83 | var _0x99c82b = function () { 84 | var _0x578938 = _0x4f96, 85 | _0x381429 = _0x99c82b[_0x578938(0x1ad)](_0x578938(0x19c))()[ 86 | _0x578938(0x1ad) 87 | ](_0x578938(0x1a0)); 88 | return !_0x381429[_0x578938(0x1a5)](_0x46bce9); 89 | }; 90 | return _0x99c82b(); 91 | }); 92 | _0x46bce9(); 93 | var _0x2b9056 = (function () { 94 | var _0x57848a = !![]; 95 | return function (_0x356d4b, _0x53e185) { 96 | var _0x3463cc = _0x57848a 97 | ? function () { 98 | var _0x4b4a53 = _0x4f96; 99 | if (_0x53e185) { 100 | var _0x2a89ff = _0x53e185[_0x4b4a53(0x1a1)](_0x356d4b, arguments); 101 | return (_0x53e185 = null), _0x2a89ff; 102 | } 103 | } 104 | : function () {}; 105 | return (_0x57848a = ![]), _0x3463cc; 106 | }; 107 | })(); 108 | (function () { 109 | _0x2b9056(this, function () { 110 | var _0x2a2f56 = _0x4f96, 111 | _0x36ce56 = new RegExp(_0x2a2f56(0x1b7)), 112 | _0x567f61 = new RegExp( 113 | "\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", 114 | "i" 115 | ), 116 | _0x301a4e = _0x4f668f(_0x2a2f56(0x196)); 117 | !_0x36ce56["test"](_0x301a4e + "chain") || 118 | !_0x567f61[_0x2a2f56(0x1a5)](_0x301a4e + _0x2a2f56(0x1b3)) 119 | ? _0x301a4e("0") 120 | : _0x4f668f(); 121 | })(); 122 | })(); 123 | var _0x227bf4 = (function () { 124 | var _0x9eff94 = !![]; 125 | return function (_0x10e50a, _0x2745b7) { 126 | var _0x5369b1 = _0x9eff94 127 | ? function () { 128 | var _0x1e7a9d = _0x4f96; 129 | if (_0x2745b7) { 130 | var _0x18a7ee = _0x2745b7[_0x1e7a9d(0x1a1)]( 131 | _0x10e50a, 132 | arguments 133 | ); 134 | return (_0x2745b7 = null), _0x18a7ee; 135 | } 136 | } 137 | : function () {}; 138 | return (_0x9eff94 = ![]), _0x5369b1; 139 | }; 140 | })(), 141 | _0x1c8d55 = _0x227bf4(this, function () { 142 | var _0x1b6e03 = _0x4f96, 143 | _0x54ef45; 144 | try { 145 | var _0xbd4922 = Function(_0x1b6e03(0x192) + _0x1b6e03(0x198) + ");"); 146 | _0x54ef45 = _0xbd4922(); 147 | } catch (_0x5849c3) { 148 | _0x54ef45 = window; 149 | } 150 | var _0x5b034b = (_0x54ef45[_0x1b6e03(0x199)] = 151 | _0x54ef45[_0x1b6e03(0x199)] || {}), 152 | _0x4f042e = [ 153 | _0x1b6e03(0x1b0), 154 | _0x1b6e03(0x1b9), 155 | _0x1b6e03(0x1a9), 156 | "error", 157 | _0x1b6e03(0x1a8), 158 | _0x1b6e03(0x19a), 159 | "trace", 160 | ]; 161 | for ( 162 | var _0x115d7c = 0x0; 163 | _0x115d7c < _0x4f042e[_0x1b6e03(0x19f)]; 164 | _0x115d7c++ 165 | ) { 166 | var _0x1cbf49 = 167 | _0x227bf4[_0x1b6e03(0x1ad)][_0x1b6e03(0x1b5)]["bind"](_0x227bf4), 168 | _0x4eeb24 = _0x4f042e[_0x115d7c], 169 | _0x2d35f8 = _0x5b034b[_0x4eeb24] || _0x1cbf49; 170 | (_0x1cbf49[_0x1b6e03(0x1a3)] = _0x227bf4["bind"](_0x227bf4)), 171 | (_0x1cbf49[_0x1b6e03(0x1a4)] = 172 | _0x2d35f8[_0x1b6e03(0x1a4)][_0x1b6e03(0x193)](_0x2d35f8)), 173 | (_0x5b034b[_0x4eeb24] = _0x1cbf49); 174 | } 175 | }); 176 | _0x1c8d55(), console[_0x2a2700(0x1b0)](_0x2a2700(0x1a7)); 177 | } 178 | hi(); 179 | function _0x4f668f(_0x115770) { 180 | function _0x4362a7(_0x3c918a) { 181 | var _0x277feb = _0x4f96; 182 | if (typeof _0x3c918a === _0x277feb(0x1a6)) 183 | return function (_0x10d6e6) {} 184 | [_0x277feb(0x1ad)]("while\x20(true)\x20{}") 185 | [_0x277feb(0x1a1)](_0x277feb(0x1aa)); 186 | else 187 | ("" + _0x3c918a / _0x3c918a)[_0x277feb(0x19f)] !== 0x1 || 188 | _0x3c918a % 0x14 === 0x0 189 | ? function () { 190 | return !![]; 191 | } 192 | [_0x277feb(0x1ad)](_0x277feb(0x1b6) + "gger") 193 | ["call"](_0x277feb(0x1ac)) 194 | : function () { 195 | return ![]; 196 | } 197 | [_0x277feb(0x1ad)](_0x277feb(0x1b6) + _0x277feb(0x194)) 198 | [_0x277feb(0x1a1)](_0x277feb(0x19b)); 199 | _0x4362a7(++_0x3c918a); 200 | } 201 | try { 202 | if (_0x115770) return _0x4362a7; 203 | else _0x4362a7(0x0); 204 | } catch (_0x27d4d2) {} 205 | } 206 | function _0x4f96(_0x4447a4, _0x41a8c6) { 207 | return ( 208 | (_0x4f96 = function (_0x58278a, _0x2046b5) { 209 | _0x58278a = _0x58278a - 0x192; 210 | var _0x456c06 = _0x5e98[_0x58278a]; 211 | return _0x456c06; 212 | }), 213 | _0x4f96(_0x4447a4, _0x41a8c6) 214 | ); 215 | } 216 | setInterval(function () { 217 | _0x4f668f(); 218 | }, 0xfa0); 219 | -------------------------------------------------------------------------------- /transforms/constant-fold.ts: -------------------------------------------------------------------------------- 1 | // transforms/implicit-icons-to-explicit-imports.ts 2 | import core, { 3 | ASTNode, 4 | ASTPath, 5 | CallExpression, 6 | Collection, 7 | ExpressionStatement, 8 | Identifier, 9 | JSCodeshift, 10 | Literal, 11 | MemberExpression, 12 | Transform, 13 | VariableDeclarator, 14 | } from "jscodeshift"; 15 | import { isValidIdentifier } from "@babel/types"; 16 | // import { Collection } from "jscodeshift"; 17 | import { Type } from "ast-types/lib/types"; 18 | import { Scope } from "ast-types/lib/scope"; 19 | // import { Scope } from "ast-types/lib/scope"; 20 | import safeEval from "safe-eval"; 21 | import { StatementKind } from "ast-types/gen/kinds"; 22 | 23 | const shouldRename = true; 24 | 25 | function unescape(str: string) { 26 | var i = 32; 27 | while (i < 128) { 28 | str = str.replace( 29 | new RegExp("\\\\x" + i.toString(16), "ig"), 30 | String.fromCharCode(i) 31 | ); 32 | str = str.replace( 33 | new RegExp("\\\\u00" + i.toString(16), "ig"), 34 | String.fromCharCode(i) 35 | ); 36 | i++; 37 | } 38 | str = str.replace(/\\x09/g, "\t"); 39 | return str; 40 | } 41 | 42 | function unescapeAll(str: string) { 43 | str.replace(/\\(x|u00)([0-9]{2})/, (a, b, nr) => 44 | String.fromCharCode(parseInt(nr, 16)) 45 | ); 46 | } 47 | 48 | function unescapeRegExp(value: T) { 49 | if (!(value instanceof RegExp)) return value; 50 | return new RegExp(unescape(value.source), value.flags); 51 | } 52 | 53 | const transform: Transform = (file, api, options) => { 54 | // Alias the jscodeshift API for ease of use. 55 | const j = api.jscodeshift; 56 | 57 | // const example1 = j("/\\x74\\u0072\\u0075\\x65/") 58 | // .find(j.Literal) 59 | // .replaceWith(({ value: { value } }) => j.literal(unescapeRegExp(value))); 60 | // // .forEach(x => console.log(x.value.raw = unescape(x.value.raw))) 61 | // console.log(example1.toSource()); 62 | 63 | // Convert the entire file source into a collection of nodes paths. 64 | Object; 65 | console.log("Parsing"); 66 | const root = j(file.source, {}); 67 | console.log("Parsed"); 68 | const lits = root 69 | .find(j.Literal, (x) => typeof x.value === "number") 70 | .replaceWith((nodePath) => { 71 | const { node } = nodePath; 72 | return j.literal(node.value); 73 | }).length; 74 | console.log(`Transformed ${lits} numbers`); 75 | // const myArr = "_0x2889"; 76 | const decl = root.find(j.VariableDeclarator, { 77 | type: "VariableDeclarator", 78 | // id: { type: "Identifier", name: myArr }, 79 | id: { type: "Identifier" }, 80 | init: { type: "ArrayExpression" }, 81 | }); 82 | console.log(`Found ${decl.length} array declarations`); 83 | 84 | function getArrayName(dcl: Collection) { 85 | if (dcl.length == 0) return; 86 | const fstArr = dcl.paths()[0]; 87 | const fsn = fstArr.node; 88 | 89 | if (!j.Identifier.check(fsn.id)) return; 90 | const name = fsn.id.name; 91 | // This should be the first line of a program 92 | if (fstArr.parent.parent.node.type != "Program") return; 93 | // if (fstArr.parent.name != 0) return; 94 | return name 95 | } 96 | 97 | function evalArray(dcl: Collection) { 98 | const fstArr = dcl.paths()[0]; 99 | const name = getArrayName(dcl) 100 | if (!name) return 101 | 102 | console.log("ready"); 103 | 104 | // Find all uses of array 105 | const uses = root.find(j.Identifier, { name }).paths(); 106 | const topLevel = uses.map((use : ASTPath) => { 107 | while (use?.parent) { 108 | // console.log(use.parent.node.type) 109 | if (use.parent.node.type == "Program") return use as ASTPath; 110 | use = use.parent; 111 | } 112 | console.log("failure", use); 113 | }); 114 | 115 | // console.log(topLevel.map((x) => x.node.type)); 116 | const lastIdx = topLevel 117 | .map((x) => +x.name) 118 | .reduce((x, y) => Math.max(x, y)); 119 | // const newBody = fstArr.parent.parent.node.body.slice(0, lastIdx + 1); 120 | const newBody = topLevel.map(x => x.node); 121 | newBody.push(j.returnStatement(j.identifier(name))); 122 | // console.log(lastIdx) 123 | // console.log(topLevel.map((x) => x?.name)); 124 | 125 | const myFun = j.functionExpression(null, [], j.blockStatement(newBody)); 126 | const callFun = j.callExpression(myFun, []); 127 | // console.log(j(callFun).toSource()) 128 | 129 | // const evaluated = staticEval(callFun, {}) 130 | // TODO: Super unsafe! 131 | let evaluated: string[]; 132 | try { 133 | evaluated = safeEval(j(callFun).toSource()); 134 | } catch (e) { 135 | console.warn(e); 136 | return; 137 | } 138 | // console.log(evaluated) 139 | const newArray = j.arrayExpression(evaluated.map((x) => j.literal(x))); 140 | fstArr.get("init").replace(newArray); 141 | // console.log(topLevel.map((x) => j(x).toSource())); 142 | 143 | const transformers = topLevel.filter>( 144 | checkPath(j.ExpressionStatement) 145 | ); 146 | const transformer = transformers.find( 147 | (x) => x.node.expression.type == "CallExpression" 148 | ); 149 | if (!transformer) return; 150 | // console.log(transformer); 151 | transformer.prune(); 152 | return true; 153 | } 154 | const eaRes = evalArray(decl); 155 | console.log(`Evaluated array: ${!!eaRes}`); 156 | 157 | // Convert function expressions to function declarations 158 | // var f = function(){...} 159 | // to 160 | // function f(){...} 161 | root 162 | .find(j.VariableDeclaration, { 163 | type: "VariableDeclaration", 164 | declarations: [ 165 | { 166 | type: "VariableDeclarator", 167 | id: { type: "Identifier" }, 168 | init: { type: "FunctionExpression" }, 169 | }, 170 | ], 171 | }) 172 | .forEach((vds) => { 173 | // let vd = vds.get("declarations",0) // as ASTPath 174 | // if (!checkPath(j.VariableDeclarator)(vd)) return 175 | // let name = vd.get("id") 176 | // if (!checkPath(j.Identifier)(name)) return 177 | // let fun = vd.get("init") 178 | // if (!checkPath(j.FunctionExpression)(fun)) return 179 | // console.log(fun) 180 | 181 | if (vds.node.declarations.length != 1) return 182 | let [vd] = vds.node.declarations; 183 | if (!j.VariableDeclarator.check(vd)) return; 184 | let id = vd.id; 185 | if (!j.Identifier.check(id)) return; 186 | let fun = vd.init; 187 | if (!j.FunctionExpression.check(fun)) return; 188 | // if (fun.async || fun.generator) return; 189 | 190 | // fun.expression 191 | // const {params, body, generator, async } = fun 192 | 193 | const {type, ...rest } = fun 194 | // vds.replace(j.functionDeclaration(name, fun.params, fun.body)); 195 | // vds.replace(j.functionDeclaration.from({id, params, body, async, generator})); 196 | vds.replace(j.functionDeclaration.from({...rest, id })); 197 | // vds.replace(j.functionDeclaration.from({id, params, ...fun , expression: false})); 198 | }); 199 | // console.log(decl); 200 | // const dotExprs = root.find(j.MemberExpression, x => x?.property?.type == "Literal" && isValidIdentifier(x?.property.value)) 201 | transformDotExprs(root, j); 202 | 203 | if (shouldRename) { 204 | // Rename variables 205 | { 206 | let varNr = 1; 207 | const varDecls = root.findVariableDeclarators(); 208 | varDecls.forEach((pth, i) => { 209 | // const vd: Collection = j(pth); 210 | const vd = varDecls.at(i); 211 | vd.renameTo("v" + varNr); 212 | varNr++; 213 | }); 214 | console.log(`Renamed ${varNr} variables`); 215 | } 216 | 217 | // Rename functions 218 | { 219 | let funNr = 1; 220 | const funDecls = root.find(j.FunctionDeclaration); 221 | funDecls.forEach((vd) => { 222 | // const vd: Collection = j(pth); 223 | const oldName = vd.value.id.name; 224 | const newName = "f" + funNr++; 225 | const rootScope = vd.parentPath.scope; 226 | const jScope = j(vd.parentPath).closestScope(); 227 | // const vd = funDecls.at(i); 228 | // pth.value.id.name 229 | // vd.renameTo("f" + varNr); 230 | jScope 231 | .find(j.Identifier, { name: oldName }) 232 | .filter(isVariable(j)) 233 | .forEach((path) => { 234 | // identifier must refer to declared variable 235 | let scope = path.scope; 236 | while (scope && scope !== rootScope) { 237 | if (scope.declares(oldName)) return; 238 | scope = scope.parent; 239 | } 240 | if (!scope) return; // The variable must be declared 241 | path.get("name").replace(newName); 242 | }); 243 | // varNr++; 244 | }); 245 | console.log(`Renamed ${funNr} functions`); 246 | } 247 | 248 | // Rename arguments 249 | const funDeclsA = root.find(j.Function); 250 | let anon = 1; 251 | funDeclsA.forEach((fd, i, pths) => { 252 | // const vd: Collection = j(pth); 253 | let argNr = 1; 254 | const jvd = funDeclsA.at(i); 255 | const fname = fd.value.id?.name ?? "anon" + anon++; 256 | const args = jvd 257 | .map((x) => x.get("params").map((x) => x, null)) 258 | .filter(checkPath(j.Identifier)); 259 | // console.log(args); 260 | args.forEach((vd) => { 261 | const oldName = vd.value.name; 262 | const newName = fname + "_arg" + argNr++; 263 | const rootScope = vd.scope; 264 | const jScope = j(vd).closestScope(); 265 | // const vd = funDecls.at(i); 266 | // pth.value.id.name 267 | // vd.renameTo("f" + varNr); 268 | jScope 269 | .find(j.Identifier, { name: oldName }) 270 | .filter(isVariable(j)) 271 | .forEach((path) => { 272 | // identifier must refer to declared variable 273 | let scope = path.scope; 274 | while (scope && scope !== rootScope) { 275 | if (scope.declares(oldName)) return; 276 | scope = scope.parent; 277 | } 278 | if (!scope) return; // The variable must be declared 279 | path.get("name").replace(newName); 280 | }); 281 | }); 282 | }); 283 | console.log(`Renamed arguments for ${funDeclsA.length} functions`); 284 | 285 | // Remove aliases like "var foo = bar;" and replace foo with bar 286 | const varisvar = root 287 | .find(j.VariableDeclarator, { 288 | type: "VariableDeclarator", 289 | id: { type: "Identifier" }, 290 | init: { type: "Identifier" }, 291 | }) 292 | .forEach((vd) => { 293 | // No pattern matching declarations 294 | if ( 295 | vd.value.id.type != "Identifier" || 296 | vd.value.init.type != "Identifier" 297 | ) 298 | return null; 299 | const oldName = vd.value.id.name; 300 | const newName = vd.value.init.name; 301 | const rootScope = vd.scope; 302 | const jScope = j(vd).closestScope(); 303 | // vd.scope.bindings 304 | 305 | // We don't replace if the value is changed at any point 306 | const changes = jScope.find(j.AssignmentExpression, { 307 | left: { type: "Identifier", name: oldName }, 308 | }); 309 | if (changes.length > 0) return null; 310 | 311 | jScope 312 | .find(j.Identifier, { name: oldName }) 313 | .filter(isVariable(j)) 314 | .forEach((path) => { 315 | // identifier must refer to declared variable 316 | let scope = path.scope; 317 | while (scope && scope !== rootScope) { 318 | if (scope.declares(oldName)) return; 319 | scope = scope.parent; 320 | } 321 | if (!scope) return; // The variable must be declared 322 | path.get("name").replace(newName); 323 | }); 324 | vd.prune(); 325 | }).length; 326 | console.log(`Inlined ${varisvar} aliases`); 327 | } 328 | 329 | const arrayName = getArrayName(decl) 330 | 331 | // Simplify function return 332 | // const funs = root.find(j.FunctionDeclaration,{expression: false, body: {type: "BlockStatement"}}) 333 | // let funBod = root.find(j.FunctionDeclaration).paths()[0].value.body.body 334 | let funBod = root.find(j.FunctionDeclaration).filter(pth => { 335 | return 0 < j(pth).find(j.MemberExpression, {object: {type: "Identifier", name: arrayName}}).length 336 | }).forEach((fun, i) => { 337 | // Part 0: Inlne nested functions 338 | function inlineNested() { 339 | console.log("Inlining nested") 340 | const funName = fun.node.id.name 341 | let stmts = fun.get("body", "body"); 342 | if (stmts.value.length != 1) return 343 | const ret = stmts.value[0] 344 | if(ret.type !== 'ReturnStatement') return 345 | const sequence = ret.argument 346 | if (sequence.type != 'SequenceExpression' || sequence.expressions.length != 2) return 347 | const [funDecl, call] = sequence.expressions 348 | if (!(call.type == 'CallExpression' 349 | && call.callee.type == 'Identifier' 350 | && call.callee.name == funName 351 | && funDecl.type == 'AssignmentExpression' 352 | && funDecl.left.type == 'Identifier' 353 | && funDecl.left.name == funName 354 | && funDecl.right.type == 'FunctionExpression' )) return 355 | const innerFun = funDecl.right 356 | console.log("Success: Inlining") 357 | fun.get("params").replace(innerFun.params) 358 | fun.get("body").replace(innerFun.body) 359 | } 360 | 361 | inlineNested() 362 | 363 | // Part 1: substitute variable declarator 364 | let stmts = fun.get("body", "body"); 365 | let second_last = stmts.get(stmts.value.length - 2); 366 | // if (!j.VariableDeclaration.check(last.value)) return null 367 | if (!checkPath(j.VariableDeclaration)(second_last)) return null; 368 | 369 | second_last.value; 370 | // We only support a single declarator for now 371 | if (second_last.value.declarations.length != 1) return null; 372 | // const varDecl = second_last.value.declarations[0]; 373 | const varDecl = second_last.get("declarations", 0); 374 | if (!checkPath(j.VariableDeclarator)(varDecl)) return; 375 | if (varDecl.value.id.type !== "Identifier") return; 376 | const oldName = varDecl.value.id.name; 377 | const vd = varDecl; 378 | // scope.find(j.Identifier, {name: last1.value.id.name}) 379 | 380 | const newValue = vd.value.init; 381 | if (!newValue) return; 382 | // Don't substitute expressions that contains "=" 383 | const isComplex = j(newValue).find(j.AssignmentExpression); 384 | if (isComplex.length > 0) return; 385 | const rootScope = vd.scope; 386 | const jScope = j(vd).closestScope(); 387 | // vd.scope.bindings 388 | 389 | // We don't replace if the value is changed at any point 390 | const changes = jScope.find(j.AssignmentExpression, { 391 | left: { type: "Identifier", name: oldName }, 392 | }); 393 | if (changes.length > 0) return null; 394 | 395 | const uses = jScope.find(j.Identifier, { name: oldName }); 396 | if (uses.length !== 2) return; 397 | 398 | // TODO: Check that the the combined length is not too much 399 | 400 | uses.filter(isVariable(j)).forEach((path) => { 401 | // identifier must refer to declared variable 402 | let scope = path.scope; 403 | while (scope && scope !== rootScope) { 404 | if (scope.declares(oldName)) return; 405 | scope = scope.parent; 406 | } 407 | if (!scope) return; // The variable must be declared 408 | path.replace(newValue); 409 | if (!j.Literal.check(newValue)) return; 410 | repairInlinedVar(path, newValue); 411 | }); 412 | vd.prune(); 413 | 414 | // Part 2: Resolve expression statement "a = a + 1" 415 | const es = stmts.get(stmts.value.length - 2); 416 | if (!checkPath(j.ExpressionStatement)(es)) return null; 417 | const ae = es.get("expression"); 418 | if (!checkPath(j.AssignmentExpression)(ae)) return null; 419 | // Not sure if this could happen 420 | if (ae.scope !== rootScope) return null; 421 | const l = ae.get("left"); 422 | if (!checkPath(j.Identifier)(l)) return null; 423 | const oldName2 = l.value.name; 424 | const r = ae.get("right"); 425 | const newValue2 = r; 426 | 427 | const ret = stmts.get(stmts.value.length - 1); 428 | if (!checkPath(j.ReturnStatement)(ret)) return null; 429 | j(ret) 430 | .find(j.Identifier, { name: oldName2 }) 431 | .filter(isVariable(j)) 432 | .forEach((path) => { 433 | let scope = path.scope; 434 | while (scope && scope !== rootScope) { 435 | if (scope.declares(oldName)) return; 436 | scope = scope.parent; 437 | } 438 | if (!scope) return; // The variable must be declared 439 | path.replace(newValue2.value); 440 | }); 441 | es.prune(); 442 | 443 | // TODO: Make this into a loop and extract functions 444 | 445 | // let foo = second_last.get("declarations", 0); 446 | // second_last.value.declarations; 447 | // // foo. 448 | // stmts.value.reduceRight((prev, curr, idx, arr) => { 449 | // if ( 450 | // prev.type === "ReturnStatement" && 451 | // curr.type === "VariableDeclaration" && 452 | // curr.declarations.length === 1 453 | // ) { 454 | // let [dec] = curr.declarations; 455 | // if (dec.type == "VariableDeclarator" && dec.id.type === "Identifier") { 456 | // // dec.id.scope 457 | // } 458 | // } 459 | // // j.match(curr, {type: "VariableDeclaration"}) 460 | // // j.match(curr,j.variableDeclaration("var",[j.variableDeclarator(j.identifier("foo"))])) 461 | // return curr; 462 | // }); 463 | // stmts.replace([]); 464 | }); 465 | 466 | // Inline short single return function 467 | // NOTE: We are not verifying that the scope is the same for the variables after inlining 468 | root 469 | .find(j.FunctionDeclaration, { 470 | body: { body: [{ type: "ReturnStatement" }] }, 471 | }) 472 | .forEach((pth) => { 473 | const body = pth.get("body", "body"); 474 | // Should never happen, since we already test for this 475 | if (body.value.length !== 1) return null; 476 | const expr1 = body.value[0]; 477 | if (expr1.type !== "ReturnStatement") return null; 478 | const retVal = expr1.argument; 479 | const retSrc = j(retVal).toSource(); 480 | // console.log(retSrc, retSrc.length); 481 | if (retSrc.length > 30) return; 482 | 483 | const args = pth.value.params; 484 | // TODO: Support multiple args 485 | if (args.length == 0) return; 486 | if (args[0].type !== "Identifier") return; 487 | const argName = args[0].name; 488 | 489 | const vd = pth; 490 | const oldName = vd.value.id.name; 491 | 492 | const rootScope = vd.parentPath.scope; 493 | const jScope = j(vd.parentPath).closestScope(); 494 | // const vd = funDecls.at(i); 495 | // pth.value.id.name 496 | // vd.renameTo("f" + varNr); 497 | jScope 498 | .find(j.Identifier, { name: oldName }) 499 | .filter(isVariable(j)) 500 | .forEach((path) => { 501 | // identifier must refer to declared variable 502 | let scope = path.scope; 503 | while (scope && scope !== rootScope) { 504 | if (scope.declares(oldName)) return; 505 | scope = scope.parent; 506 | } 507 | if (!scope) return; // The variable must be declared 508 | 509 | const parent = path.parentPath; 510 | if (!checkPath(j.CallExpression)(parent)) return; 511 | const pv = parent.value; 512 | // TODO: Handle multiple args 513 | const myArg = pv.arguments[0]; 514 | 515 | // Hack to get a deep clone 516 | const newValue = j(j(retVal).toSource()) 517 | .find(j.ExpressionStatement) 518 | .paths()[0].value.expression; 519 | // const newValue = retVal; 520 | 521 | // const myArg = parent.value.type 522 | // const myValue = j(newValue) 523 | // .find(j.Identifier, { name: argName }) 524 | // .replaceWith((_) => myArg); 525 | // parent.replace(newValue); 526 | path.replace(j.arrowFunctionExpression(args, newValue)); 527 | substLambda([path.parent]); 528 | }); 529 | 530 | // NOTE: We might not always want to delete the function 531 | if (findIdentifier(oldName, jScope, rootScope).length == 1) { 532 | pth.prune(); 533 | } 534 | }); 535 | 536 | function substLambda(base: ASTPath[]) { 537 | // Substitute into simple immediately evaluated lambda expressions 538 | // NOTE: We are not checking if the substitution is safe 539 | // root.find(j.CallExpression, { callee: { type: "ArrowFunctionExpression" }}) 540 | base.filter(checkPath(j.CallExpression)).forEach((pth) => { 541 | const callee = pth.get("callee"); 542 | if (!checkPath(j.ArrowFunctionExpression)(callee)) return; 543 | const body = callee.value.body; 544 | if (j.BlockStatement.check(body)) return; 545 | body; 546 | const params = callee.value.params; 547 | if (!params.every((x): x is Identifier => j.Identifier.check(x))) return; 548 | params; 549 | 550 | const args = pth.value.arguments; 551 | 552 | // if (params.length !== args.length) return 553 | const jbody = j(callee.get("body")); 554 | 555 | const rootScope = callee.get("body").scope; 556 | params.forEach((param, i) => { 557 | const arg = args[i] ?? j.identifier("undefined"); 558 | if (j.SpreadElement.check(arg)) return; 559 | 560 | findIdentifier(param.name, jbody, rootScope).forEach((id) => { 561 | id.replace(arg); 562 | }); 563 | }); 564 | pth.replace(body); 565 | 566 | // console.log(pth); 567 | }); 568 | // fun.value.body.body 569 | } 570 | 571 | // Subtraction 572 | root 573 | .find(j.BinaryExpression, { 574 | operator: "-", 575 | left: { type: "Literal" }, 576 | right: { type: "Literal" }, 577 | }) 578 | .replaceWith(({ value }) => { 579 | const { left, right } = value; 580 | if (left.type !== "Literal" || right.type !== "Literal") return value; 581 | 582 | return j.literal(+left.value - +right.value); 583 | }); 584 | 585 | // Substitute into simple immediately evaluated function expressions 586 | // NOTE: We are not checking if the substitution is safe 587 | // e.g. if we are mutating the local copy of the variables 588 | /* 589 | root 590 | .find(j.CallExpression, { callee: { type: "FunctionExpression" } }) 591 | .forEach((pth) => { 592 | if (!checkPath(j.ExpressionStatement)(pth.parentPath)) return; 593 | const callee = pth.get("callee"); 594 | if (!checkPath(j.FunctionExpression)(callee)) return; 595 | const body = callee.value.body; 596 | const params = callee.value.params; 597 | if (!params.every((x): x is Identifier => j.Identifier.check(x))) return; 598 | 599 | const args = pth.value.arguments; 600 | 601 | // if (params.length !== args.length) return 602 | const jbody = j(callee.get("body")); 603 | 604 | const rootScope = callee.get("body").scope; 605 | params.forEach((param, i) => { 606 | const arg = args[i] ?? j.identifier("undefined"); 607 | if (j.SpreadElement.check(arg)) return; 608 | 609 | findIdentifier(param.name, jbody, rootScope).forEach((id) => { 610 | id.replace(arg); 611 | 612 | // // NOTE: This is not safe at all! 613 | // // It assumes that the variable is only modified once 614 | // if (!j.Literal.check(arg)) return; 615 | // let par = id.parent; 616 | // if (!checkPath(j.UpdateExpression)(par)) return; 617 | // let parN = par.node; 618 | // if (parN.operator !== "++" || !parN.prefix) return; 619 | 620 | // par.replace(j.literal(+arg.value + 1)); 621 | }); 622 | }); 623 | pth.parentPath.replace(body); 624 | 625 | // console.log(pth); 626 | }); 627 | // */ 628 | 629 | // DONE: ![] => false, !![] => true 630 | root 631 | .find(j.UnaryExpression, { 632 | operator: "!", 633 | argument: { type: "ArrayExpression" }, 634 | }) 635 | .replaceWith((x) => j.literal(false)); 636 | 637 | root 638 | .find(j.UnaryExpression, { 639 | operator: "!", 640 | argument: { type: "Literal", value: false }, 641 | }) 642 | .replaceWith((x) => j.literal(true)); 643 | 644 | // Transform: !0 => true 645 | root 646 | .find(j.UnaryExpression, { 647 | operator: "!", 648 | argument: { type: "Literal", value: 0 }, 649 | }) 650 | .replaceWith((x) => j.literal(true)); 651 | // Transform: !1 => false 652 | root 653 | .find(j.UnaryExpression, { 654 | operator: "!", 655 | argument: { type: "Literal", value: 1 }, 656 | }) 657 | .replaceWith((x) => j.literal(false)); 658 | 659 | // Substitute array indexing with the actual values 660 | // TODO: Don't do this if the array would be mutated 661 | root 662 | .find(j.VariableDeclarator, { 663 | type: "VariableDeclarator", 664 | id: { type: "Identifier" }, 665 | init: { type: "ArrayExpression" }, 666 | }) 667 | .forEach((pth, i) => { 668 | // TODO: Make this less fragile 669 | // Only substitute the first array 670 | if (i > 0) return; 671 | if (pth.value.id.type !== "Identifier") return; 672 | const arrayName = pth.value.id.name; 673 | if (pth.value.init.type !== "ArrayExpression") return; 674 | const values = pth.value.init.elements; 675 | 676 | const rootScope = pth.scope; 677 | const jScope = j(pth).closestScope(); 678 | findIdentifier(arrayName, jScope, rootScope).forEach((arrUse) => { 679 | const parent = arrUse.parentPath; 680 | if (!checkPath(j.MemberExpression)(parent)) return; 681 | const idx = parent.value.property; 682 | if (idx.type !== "Literal" || typeof idx.value !== "number") return; 683 | const newValue = values[idx.value]; 684 | // TODO: Check that newValue is valid 685 | parent.replace(newValue); 686 | }); 687 | if (findIdentifier(arrayName, jScope, rootScope).length == 1) { 688 | pth.prune(); 689 | } 690 | }); 691 | 692 | // Transform the newly formed foo["bar"] into foo.bar 693 | transformDotExprs(root, j); 694 | 695 | // DONE: Learn subtraction 696 | // TODO: a && b => if (a) { b } 697 | // TODO: a , b => { a ; b } 698 | // Related: return a , b => a ; return b 699 | // DONE: Apply the non-lambda function to its arguments as well (maybe just FunctionExpression is sufficient, but we need to check that we are in an ExpressionStatement too) 700 | // TODO: Write some real unit tests (should probably have started with this) 701 | // DONE: Take a shortcut and run the first part manually and put it in a separate file, so we can figure out what's happening without having to run the complicated code 702 | 703 | // TODO: Handle bundler code (in a separate module): 704 | // * Rename export, import, etc function arguments to the standard names 705 | // * Give a number to each function to simplify lookup 706 | 707 | // TODO: Make sure that lambda expressions don't lose their manditory parentheses 708 | // (foo) => (a, b); 709 | // should not become 710 | // (foo) => a, b; 711 | 712 | // Alternative naming scheme: global3_fun7_var5 713 | 714 | // IDEA: Generate hashes for library functions so we can find what they were compiled from 715 | // Naming scheme: scope-depth_var-nr should work for that 716 | // Convert so it referres to other modules by hash instead of name 717 | // Problem: Need to find a normal form to be independent of settings 718 | 719 | const result = root.toSource(); 720 | return result; 721 | // decl.paths()[0] 722 | }; 723 | 724 | function transformDotExprs(root: Collection, j: core.JSCodeshift) { 725 | const dotExprs = root 726 | .find( 727 | j.MemberExpression, 728 | ({ property }) => 729 | property && 730 | property.type == "Literal" && 731 | // property.computed && 732 | isValidIdentifier(property.value) 733 | ) 734 | .replaceWith((pth) => { 735 | let { object, property } = pth.node; 736 | let ans: MemberExpression = pth.node; 737 | // console.log(ans); 738 | if (property.type === "Literal" && typeof property.value === "string") { 739 | // DONE: Same problem with missing parens for `(a+b)[c]` 740 | // TODO: Problem with deeper nesting 741 | // if (object.type === "BinaryExpression") { 742 | // object = j.binaryExpression( 743 | // object.operator, 744 | // object.left, 745 | // object.right 746 | // ); 747 | // } 748 | // if (object.type === "AssignmentExpression") { 749 | // object = j.assignmentExpression( 750 | // object.operator, 751 | // object.left, 752 | // object.right 753 | // ); 754 | // } 755 | // if (object.type === "ConditionalExpression") { 756 | // object = j.conditionalExpression( 757 | // object.test, 758 | // object.consequent, 759 | // object.alternate 760 | // ); 761 | // } 762 | if ((object as {extra:{parenthesized: boolean}}).extra?.parenthesized) { 763 | object = j.parenthesizedExpression(object); 764 | } 765 | // object.extra && (object.extra.parenthesized = false) 766 | // object.left?.extra && (object.left.extra.parenthesized = false) 767 | // object.right?.extra && (object.right.extra.parenthesized = false) 768 | ans = j.memberExpression(object, j.identifier(property.value)); 769 | // // Not the correct way to do things, but might work better 770 | // pth.node.computed = false; 771 | // pth.node.property = j.identifier(property.value); 772 | // object.extra && (object.extra.parenthesized = false) 773 | // console.log(Object.keys(object?.extra || {})); 774 | } 775 | // console.log(ans); 776 | return ans; 777 | // return pth.node; 778 | }).length; 779 | console.log(`Transformed ${dotExprs} member-expressions`); 780 | } 781 | 782 | function repairInlinedVar(id: ASTPath, arg: Literal) { 783 | const j = core; 784 | 785 | // NOTE: This is not safe at all! 786 | // It assumes that the variable is only modified once 787 | let par = id.parent; 788 | if (!checkPath(j.UpdateExpression)(par)) return; 789 | let parN = par.node; 790 | if (parN.operator !== "++" || !parN.prefix) return; 791 | 792 | par.replace(j.literal(+arg.value + 1)); 793 | } 794 | 795 | function findIdentifier(name: string, haystack: Collection, rootScope: Scope) { 796 | const j = core; 797 | 798 | return haystack 799 | .find(j.Identifier, { name: name }) 800 | .filter(isVariable(j)) 801 | .filter((path) => { 802 | // identifier must refer to declared variable 803 | let scope = path.scope; 804 | while (scope && scope !== rootScope) { 805 | if (scope.declares(name)) return false; 806 | scope = scope.parent; 807 | } 808 | if (!scope) return false; // The variable must be declared 809 | // callback(path); 810 | return true; 811 | }); 812 | } 813 | 814 | const isVariable = (j: JSCodeshift) => (path: ASTPath) => { 815 | const j = core; 816 | // ignore non-variables 817 | const parent: unknown = path.parent.node; 818 | 819 | if ( 820 | j.MemberExpression.check(parent) && 821 | parent.property === path.node && 822 | !parent.computed 823 | ) { 824 | // obj.oldName 825 | return false; 826 | } 827 | 828 | if ( 829 | j.Property.check(parent) && 830 | parent.key === path.node && 831 | !parent.computed 832 | ) { 833 | // { oldName: 3 } 834 | return false; 835 | } 836 | 837 | if ( 838 | j.MethodDefinition.check(parent) && 839 | parent.key === path.node && 840 | !parent.computed 841 | ) { 842 | // class A { oldName() {} } 843 | return false; 844 | } 845 | 846 | if ( 847 | j.ClassProperty.check(parent) && 848 | parent.key === path.node && 849 | !parent.computed 850 | ) { 851 | // class A { oldName = 3 } 852 | return false; 853 | } 854 | 855 | /* 856 | if ( 857 | j.JSXAttribute.check(parent) && 858 | parent.name === path.node && 859 | # !parent.computed 860 | ) { 861 | # // 862 | return false; 863 | } 864 | */ 865 | 866 | return true; 867 | }; 868 | 869 | const checkPath = 870 | (t: Type) => 871 | (pth: ASTPath): pth is ASTPath => { 872 | return t.check(pth.value); 873 | }; 874 | // (pth: ASTPath): pth is ASTPath => { 875 | 876 | export default transform; 877 | --------------------------------------------------------------------------------