JS code obfuscation and restore (kuizuo.cn)

Obfuscation and restoration of JS code based on Babel (kuizuo.cn)

Restore the preface

AST is only static analysis, but it can replace the original code with restored code for better dynamic analysis to find relevant points. In the restoration, not all code can be restored to a glance at the code execution logic, AST is not everything, if you have a strong JS reverse ability, sometimes dynamic debugging is even more effective than AST static analysis.

Can’t restore the original code

Identifiers can be arbitrarily defined, as long as the variables do not conflict, I can arbitrarily define, so we have decided that we can not restore the variable name of the source code, so we can restore only some flower instructions, make the code look good, easy to debug.

Reduction is not a panacea

There are many ways to obfuscate, and there are many ways to restore the obfuscated code. The above obfuscated restore may only be for that one set of obfuscated code. If you take another obfuscated code and execute the restore program, it is likely to report errors. So there is absolutely no universal restore code, all restore procedures, need to deal with different obfuscation means.

I just put the obfuscation methods I encountered into one set of code, not all obfuscation methods can be restored.

At the same time, don’t be too eager to restore, because restoring can easily break the original code and cause unknown bugs.

:::tip

If you need to customize the restore, you can also contact. (Again, there is absolutely no way to restore the original code)

: : :

example

Some of the most popular obfuscation methods (at least the ones that are relatively easy to revert to in the ones I encountered) will be covered below, along with the corresponding code for reference (without placing the source code).

I’m going to show you how to restore an obfuscation code. This is my first example of obfuscation, and it’s probably the best one I’ve ever done. I’ve done it 4 or 5 times.

Post code git address js – DE – obfuscator/example/deobfuscator/cx

Note: This JS file was obfuscated using the JavaScript Obfuscator Tool.

Analysis the AST

Be sure to parse the obfuscated code into an AST tree structure first, as is the case with any obfuscated restore. Take a quick look at the code. It’s not hard to see that there are hexadecimal characters like ‘\x6a\x4b\x71\x4b’ in the code. You can use off-the-shelf tools

When viewing the node node through the AST, we can find that value is exactly the data we want, but it is actually extra-. raw. In fact, we just need to traverse the corresponding node and delete the extra attribute.

The specific traversal code is as follows

// Convert all hexadecimal and Unicode codes to normal characters
hexUnicodeToString() {
		traverse(this.ast, {
			StringLiteral(path) {
				var curNode = path.node;
				delete curNode.extra;
			},
			NumericLiteral(path) {
				var curNode = path.node;
				deletecurNode.extra; }})}Copy the code

Then replace the code processed after traversal with demo.js for the convenience of subsequent restoration. However, there are still many unknown strings that need to be decrypted, as well as some unprocessed code.

Find decryption function

If you try to analyze the code statically, you will notice that some parameters are called _0x3028, like this

_0x3028['nfkbEK'];
_0x3028('0x0'.'jKqK');
_0x3028('0x1'.')bls');
Copy the code

However, if you look carefully, you will find something like MemberExpression statement _28 [“nfkbEK”], but function _28 is defined in the third statement. The following code can add a custom attribute to a function

let add = function (a, b) {
  add['abc'] = 123
  return a + b
}

console.log(add(1.2))
console.log(add['abc'])

/ / 3
/ / 123
Copy the code

But no impact, here is just a mouth, not the code problem. And **_0x3028 is the decryption function ** that iterates the expression called by _0x3028 with two parameter CallExpression.

The next step is to focus on the first three statements, as these are the key to the confusion.

var _0x34ba = ["JcOFw4ITY8KX"."EHrDoHNfwrDCosO6Rkw=". ] (function(_0x2684bf, _0x5d23f1) {
    // This is just an out-of-order function, but the call comes later
    var _0x20d0a1 = function(_0x17cf70) {
        while (--_0x17cf70) {
            _0x2684bf['push'](_0x2684bf['shift']());
        }
    };
    var _0x1b4e1d = function() {
        var _0x3dfe79 = {
            'data': {
                'key': 'cookie'.'value': 'timeout'
            },
            "setCookie": function (_0x41fad3, _0x155a1e, _0x2003ae, _0x48bb02) {... },"removeCookie": function () {
                return "dev";
            },
            "getCookie": function (_0x23cc41, _0x5ea286) {
                _0x23cc41 = _0x23cc41 || function (_0x20a5ee) {
                    return _0x20a5ee;
                };

                // Here we define a flower-instruction function call to call
                var _0x267892 = function (_0x51e60d, _0x57f223) {
                    _0x51e60d(++_0x57f223);
                };
				// Where the actual call is made
                _0x267892(_0x20d0a1, _0x5d23f1);

                return _0x1c1cc3 ? decodeURIComponent(_0x1c1cc3[1) :undefined; }}}; }; _0x1b4e1d(); }(_0x34ba,296));
var _0x3028 = function (_0x2308a4, _0x573528) {
  _0x2308a4 = _0x2308a4 - 0;
  var _0x29a1e7 = _0x34ba[_0x2308a4];

  // omit 100 lines of code...

  return _0x29a1e7;
};
Copy the code

The omitted code does not need to be read closely, since all three statements are written into Node memory (eval) and then called. Next, analyze what each statement does.

Large arrays

Basically 99% of obfuscations the first statement is a big array of all of these encrypted strings, and all we have to do is find all of the encrypted strings and restore them.

An array of random sequence

And then the second statement is usually a self-calling function that takes a large array and the number of arrays out of order. What it does is it gets the array out of order, which is where the above code highlights it, but it just defines a function _0x20D0A1, The actual call from _0x3dFe79.getcookie in _0x1B4e1D is commented in the above code. If you do it in a normal way and you don’t really do it in a normal way, that’s where it gets confusing and disgusting.

Regardless of the confusion, the second statement is used as an array out of order, and we can always call eval to see what happens next.

Decryption function

_0x34BA [0] = _0x34BA [0] = _0x28 (“0x0”, “jKqK”) There is actually a second parameter involved.

Restore string

The final object is to convert _0x3028(“0x0”, “jKqK”) to the original string and replace the current node’s. All you need to do is walk through _0x3028(“0x0”, “jKqK”), execute the decryption function once to get the decrypted result, and then replace it. So how to perform the decryption function is important.

Adds the decryption function to memory

Eval is used to execute string code at runtime, but eval is not scoped. If eval is not scoped, eval is invalid. If eval is not scoped, eval is invalid. Eval can be used to write to the global scope using either window.eval or global.eval. Since this is a Node environment, global.eval is used.

Intercept the first three statements and write to memory using eval

// Get the node where the decryption function resides
let stringDecryptFuncAst = this.ast.program.body[2];
// Get the name of the function _0x3028
let DecryptFuncName = stringDecryptFuncAst.declarations[0].id.name;

let newAst = parser.parse(' ');
newAst.program.body.push(this.ast.program.body[0]);
newAst.program.body.push(this.ast.program.body[1]);
newAst.program.body.push(stringDecryptFuncAst);
// Convert the three parts of the code to a string. Because formatting checks exist, you need to specify options to compress the code
let stringDecryptFunc = generator(newAst, { compact: true }).code;
// Execute the code as a string so that the decryption function can be run in nodeJS
global.eval(stringDecryptFunc);
Copy the code

Call decryption function

At this point, _0x3028(“0x0”, “jKqK”) can be used to output the decrypted result, but it is too much trouble to input one by one manually. You can completely find all the places called _0x3028, and then determine whether the expression CallExpression, Eval (‘_0x3028(“0x0”, “jKqK”)’) is used to obtain the decryption result. So here’s an example of traversal.

traverse(this.ast, {
  VariableDeclarator(path) {
    // When the variable name is the same as the decryption function name
    if (path.node.id.name == DecryptFuncName) {
      let binding = path.scope.getBinding(DecryptFuncName);
      All the references can be retrieved by using the referencePaths
      binding &&
        binding.referencePaths.map((p) = > {
          // Check that the parent node is a call expression with two arguments
          if (p.parentPath.isCallExpression()) {
            // Output parameters and decrypted results
            let args = p.parentPath.node.arguments.map((a) = > a.value).join(' ');
            let str = eval(p.parentPath.toString());
            console.log(args, str); p.parentPath.replaceWith(t.stringLiteral(str)); }}); }}});Copy the code

Binding can retrieve the scope of the current variable, and binding. ReferencePaths can retrieve all the call locations, so we just need to check whether it is a call expression with two parameters, and then execute the entire node through eval. Eval (‘_0x3028(“0x0”, “jKqK”)’) and replaceWith DecStringArr (); decStringArr(); decStringArr()

0x0 jKqK PdAlB 0x1 )bls jtvLV 0x2 M10H SjQMk 0x3 2Q@E length 0x4 [YLR length 0x5 QvlS charCodeAt 0x6 YvHw IrwYd 0x7 iLkl  ClOby 0x8 DSlT console ...Copy the code

Code comparison between the two

Comparison of original code with processed code (part)

var _0x505b30 = (function () {
  if (_0x3028('0x0'.'jKqK') !== _0x3028('0x1'.')bls')) {
    var_0x104ede = !! [];return function (_0x3d32a2, _0x35fd15) {
      if ('bKNqX' === _0x3028('0x2'.'M10H')) {
        var _0x46992c,
          _0x1efd4e = 0,
          _0x5cae2b = d(f);

        if (0 === _0xb2c58f[_0x3028('0x3'.'2Q@E')]) return _0x1efd4e;

        for (_0x46992c = 0; _0x46992c < _0xb2c58f[_0x3028('0x4'.'[YLR')]; _0x46992c++)
          (_0x1efd4e = (_0x1efd4e << (_0x5cae2b ? 5 : 16)) - _0x1efd4e + _0xb2c58f[_0x3028('0x5'.'QvlS')](_0x46992c)), (_0x1efd4e = _0x5cae2b ? _0x1efd4e : ~_0x1efd4e);

        return 2147483647 & _0x1efd4e;
      } else {
        var _0x45a8ce = _0x104ede
          ? function () {
              if (_0x3028('0x6'.'YvHw') === _0x3028('0x7'.'iLkl')) {
                that[_0x3028('0x8'.'DSlT'] ['log'] = func;
                that[_0x3028('0x9'.'YW6h')][_0x3028('0xa'.'&12i')] = func;
                that[_0x3028('0xb'.'1jb4'] ['debug'] = func;
                that[_0x3028('0xc'.'k9U[')][_0x3028('0xd'.'nUsA')] = func;
                that[_0x3028('0xe'.')bls')][_0x3028('0xf'.'PZDB')] = func;
                that['console'][_0x3028('0x10'.'r8Qx')] = func;
                that[_0x3028('0x11'.'AIMj')][_0x3028('0x12'.'[YLR')] = func;
              } else {
                if (_0x35fd15) {
                  if (_0x3028('0x13'.'r8Qx') !== _0x3028('0x14'.'YLF%')) {
                    var _0x1fa1e3 = _0x35fd15[_0x3028('0x15'.'sLdn')](_0x3d32a2, arguments);

                    _0x35fd15 = null;
                    return _0x1fa1e3;
                  } else{ _0x142a1e(); }}}} :function () {}; _0x104ede = ! [];return_0x45a8ce; }}; }else{(function () {
      return! []; } [_0x3028('0x16'.'Yp5j')](_0x3028('0x17'.']R4I') + _0x3028('0x18'.'M10H'))
      [_0x3028('0x19'.'%#u0'(a)]'stateObject'));
  }
})();
Copy the code
var _0x505b30 = (function () {
  if ('PdAlB'! = ='jtvLV') {
    var_0x104ede = !! [];return function (_0x3d32a2, _0x35fd15) {
      if ('bKNqX'= = ='SjQMk') {
        var _0x46992c,
          _0x1efd4e = 0,
          _0x5cae2b = d(f);

        if (0 === _0xb2c58f['length']) return _0x1efd4e;

        for (_0x46992c = 0; _0x46992c < _0xb2c58f['length']; _0x46992c++)
          (_0x1efd4e = (_0x1efd4e << (_0x5cae2b ? 5 : 16)) - _0x1efd4e + _0xb2c58f['charCodeAt'](_0x46992c)), (_0x1efd4e = _0x5cae2b ? _0x1efd4e : ~_0x1efd4e);

        return 2147483647 & _0x1efd4e;
      } else {
        var _0x45a8ce = _0x104ede
          ? function () {
              if ('IrwYd'= = ='ClOby') {
                that['console'] ['log'] = func;
                that['console'] ['warn'] = func;
                that['console'] ['debug'] = func;
                that['console'] ['info'] = func;
                that['console'] ['error'] = func;
                that['console'] ['exception'] = func;
                that['console'] ['trace'] = func;
              } else {
                if (_0x35fd15) {
                  if ('WuEjf'! = ='qpuuN') {
                    var _0x1fa1e3 = _0x35fd15['apply'](_0x3d32a2, arguments);

                    _0x35fd15 = null;
                    return _0x1fa1e3;
                  } else{ _0x142a1e(); }}}} :function () {}; _0x104ede = ! [];return_0x45a8ce; }}; }else{(function () {
      return! []; } ['constructor'] ('debu' + 'gger')
      ['apply'] ('stateObject'));
  }
})();
Copy the code

You can see that the processed code at least does not need to dynamically invoke the decrypted result, and that things like if (“PdAlB”! == “jtvLV”) if (_0x3028(“0x0”, “jKqK”)! == _0x3028(“0x1”, “) BLS “)), which is the advantage of AST static analysis.

Delete obfuscated statements

After the string decryption is done, the large array and decryption function are no longer needed, so shift can be used to remove the first three statements.

// Remove the decryption code from the source code
this.ast.program.body.shift();
this.ast.program.body.shift();
this.ast.program.body.shift();
Copy the code

However, deletion is generally not recommended, because we may need to replace the restored code with the confused code in the website, and then conduct dynamic debugging analysis. However, if these three confused statements are deleted, the code execution may be wrong. I used to delete before, but until I came across a website…

Finally the entire code is completed in the class method decStringArr

Find decryption function optimization

There is one such code in the code above

    // When the variable name is the same as the decryption function name
    if (path.node.id.name == DecryptFuncName) {
    // ...
Copy the code

The DecryptFuncName here corresponds to the function name _28 of the decryption function, which is artificially defined and loaded with the first three statements at the same time. In case the decryption function is in the fourth statement or there are multiple decryption functions, the code needs to be changed

// Get the node where the decryption function resides
let stringDecryptFuncAst = this.ast.program.body[2];
// Get the name of the function _0x3028
let DecryptFuncName = stringDecryptFuncAst.declarations[0].id.name;

let newAst = parser.parse(' ');
newAst.program.body.push(this.ast.program.body[0]);
newAst.program.body.push(this.ast.program.body[1]);
newAst.program.body.push(stringDecryptFuncAst);
// Convert the three parts of the code to a string. Because formatting checks exist, you need to specify options to compress the code
let stringDecryptFunc = generator(newAst, { compact: true }).code;
Copy the code

When I accidentally looked through the code, I had an Epiphany, the decryption function was called so frequently. I directly traversed all the functions and sorted their referencePaths from high to low, so I knew that it was the decryption function, so I had the findDecFunctionArr method

findDecFunctionArr

In general, decryption functions are defined after large arrays and arrays are out of order. In the above code, we can see that the decryption function this.ast.program.body is located by subscripting [2]; So as long as you can intercept this 2, the code

/** * Find decryption function */
  findDecFunction() {
    let decFunctionArr = [];
    let index = 0; // Define the index of the statement in which the decryption function resides

    // Start with all functions (scope in Program) and determine whether the function is decrypted according to the number of references
    traverse(this.ast, {
      Program(p) {
        p.traverse({
          'FunctionDeclaration|VariableDeclarator'(path) {
            if(! (t.isFunctionDeclaration(path.node) || t.isFunctionExpression(path.node.init))) {return;
            }

            let name = path.node.id.name;
            let binding = path.scope.getBinding(name);
            if(! binding)return;

            // Call more than 100 times is probably a decryption function, depending on the actual situation
            if (binding.referencePaths.length > 100) {
              decFunctionArr.push(name);

              // Define the statement index of the decryption function according to the last decryption function
              let binding = p.scope.getBinding(name);
              if(! binding)return;

              let parent = binding.path.findParent((_p) = > _p.isFunctionDeclaration() || _p.isVariableDeclaration());
              if(! parent)return;
              let body = p.scope.block.body;
              for (let i = 0; i < body.length; i++) {
                const node = body[i];
                if (node.start == parent.node.start) {
                  index = i + 1;
                  break; }}// After traversing the current node, the child node is no longer traversedpath.skip(); }}}); }});let newAst = parser.parse(' ');
    // Insert a few statements before the decryption function
    newAst.program.body = this.ast.program.body.slice(0, index);
    // Convert this part of the code to a string. Since formatting detection is possible, you need to specify the option to compress the code
    let code = generator(newAst, { compact: true }).code;
    // Execute the code as a string so that the decryption function can be run in nodeJS
    global.eval(code);
   
    this.decFunctionArr = decFunctionArr;
  }
Copy the code

Add the decFunctionArr property to indicate that the array of decryption functions is used by decStringArr, and you can eliminate the need to determine the decryption function.

Optimize the restored code

At this point, the restored code can be basically statically analyzed, followed by subtle optimization of the code.

Object [‘ property ‘] is changed to object. attribute

This is the opposite of obfuscating object properties, but it doesn’t have to be, just that the code is relatively nice and doesn’t have much impact. The specific code is as follows

changeObjectAccessMode() {
		traverse(this.ast, {
			MemberExpression(path) {
				if (t.isStringLiteral(path.node.property)) {
					let name = path.node.property.value
					path.node.property = t.identifier(name)
					path.node.computed = false}}})}Copy the code

Restore to a Boolean

The restored code still exists!! [] with! [] Or yes! 0 and! 1, this is true and false in JS, so you can iterate through this part of the code, and then restore it to Boolean, like this expression is not detailed (similar to jSFUCK), ast structure analysis. The specific code is as follows

traverseUnaryExpression() {
		traverse(this.ast, {
			UnaryExpression(path) {
				if(path.node.operator ! = ='! ') return // 避免判断成 void

				// Check whether the second symbol is!
				if (t.isUnaryExpression(path.node.argument)) {
					if (t.isArrayExpression(path.node.argument.argument)) { / /!!!!! []
						if (path.node.argument.argument.elements.length == 0) {
							path.replaceWith(t.booleanLiteral(true))
							path.skip()
						}
					}
				} else if (t.isArrayExpression(path.node.argument)) { / /! []
					if (path.node.argument.elements.length == 0) {
						path.replaceWith(t.booleanLiteral(false))
						path.skip()
					}
				} else if (t.isNumericLiteral(path.node.argument)) { / /! 0 or ! 1
					if (path.node.argument.value === 0) {
						path.replaceWith(t.booleanLiteral(true))}else if (path.node.argument.value === 1) {
						path.replaceWith(t.booleanLiteral(false))}}else{}}})}Copy the code

Compute binomial literals

After restore code is [] “constructor” (” debu “+” gger “) (” call “) (” action “); A statement in which the debugger is intentionally split into two parts, which can also be restored to a full string using the AST, as well as similar 1 + 2 literals that can be merged. The restore program code is as follows

	traverseLiteral() {
		traverse(this.ast, {
			BinaryExpression(path) {
				let { left, right } = path.node
				// Check whether the left and right sides are literals
				if (t.isLiteral(left) && t.isLiteral(right)) {
					let { confident, value } = path.evaluate() // Compute binomials
					confident && path.replaceWith(t.valueToNode(value))
					path.skip()
				}
			}
		});
	}
Copy the code

Where, confident indicates whether it is computable or not. For example, a variable + 1 is not computable because the program does not know its value at this time. Therefore, it is false.

At the same time, the calculated binomial literal can undo some relatively simple confusions, such as numeric xor confusions 706526 ^ 706516, which can be evaluated as 10 and replaced directly with the original node. So this step needs to be traversed earlier than the other restores.

String and numeric constants are replaced directly by the corresponding variable references

Some variables may be assigned once and never change, just like constants, as shown in the following code.

let a =100
console.log(a)
Copy the code

If a is assigned only once, it must be a variable. If a is assigned only once, it may fail. Binding allows you to check the assignment history of variable A.

traverseStrNumValue() {
		traverse(this.ast, {
			'AssignmentExpression|VariableDeclarator'(path) {
				let _name = null;
				let _initValue = null;
				if (path.isAssignmentExpression()) {
					_name = path.node.left.name;
					_initValue = path.node.right;
				} else {
					_name = path.node.id.name;
					_initValue = path.node.init;
				}
				if (t.isStringLiteral(_initValue) || t.isNumericLiteral(_initValue)) {
					let binding = path.scope.getBinding(_name);
					if (binding && binding.constant && binding.constantViolations.length == 0) {
						for (let i = 0; i < binding.referencePaths.length; i++) { binding.referencePaths[i].replaceWith(_initValue); }}}},}); }Copy the code

Remove useless variables and code blocks

Some string substitutions with numeric constants are for variables that have only been assigned once, but there may also be cases where the variables have not been used. In such cases, we can determine whether the constantViolations members are empty and remove them.

	removeUnusedValue() {
		traverse(this.ast, {
			VariableDeclarator(path) {
				const { id, init } = path.node;
				if(! (t.isLiteral(init) || t.isObjectExpression(init)))return;
				const binding = path.scope.getBinding(id.name);
				if(! binding || binding.constantViolations.length >0) return

				if (binding.referencePaths.length > 0) return
				path.remove();
			},
            FunctionDeclaration(path){
				const binding = path.scope.getBinding(path.node.id.name);
				if(! binding || binding.constantViolations.length >0) return

				if (binding.referencePaths.length > 0) returnpath.remove(); }}); }Copy the code

There are also useless code blocks, such as

function test() {
  if (true) {
    return '123';
  } else {
    return Math.floor(10 * Math.random());
  }
}
test();
Copy the code

The second statement is never executed, so it can be removed. Although the code editor will darken it to indicate that it will not be executed, it is still necessary to operate through the AST if the amount of code in the confusion is not less.

	removeUnusedBlockStatement() {
		traverse(this.ast, {
			IfStatement(path) {
				if (t.isBooleanLiteral(path.node.test)) {
					let testValue = path.node.test.value
					if (testValue === true) {
						path.replaceInline(path.node.consequent)
					} else if (testValue === false) {
						path.replaceInline(path.node.alternate)
					}
				}
			},
		});
	}
Copy the code

If (1===1) isBoolean. If (1===1) isBoolean. If (1===1) isBoolean, but if(1===1) isBoolean. The final result will remove the if block and retain the BlockStatement, as shown below

function test() {
  {
    return "123";
  }
}

test();
Copy the code

Add comments

Some critical code will be hidden in the debugger, setTimeout, setInterval, etc. During debugging, you need to pay extra attention to whether there is critical code, so at this time you can add a comment to add a tag such as TOLOOK to locate. Depending on the identifier to be specified, the following code, as a demonstration, will comment // TOLOOK in these places

addComments() {
		traverse(this.ast, {
			DebuggerStatement(path) {
				path.addComment('leading'.' TOLOOK'.true);
			},
			CallExpression(path) {
				if(! ['setTimeout'.'setInterval'].includes(path.node.callee.name)) return;
				path.addComment('leading'.' TOLOOK'.true);
			},
			StringLiteral(path) {
				if (path.node.value === 'debugger') {
					path.addComment('leading'.' TOLOOK'.true); }}}); }Copy the code

Hexadecimal and Unicode encoding to normal characters

Reduction in the beginning when this method is invoked, but here are purposely say again, because of the confusion hexadecimal is the final processing, that is to say we started directly using the reduction is no problem, but if the encrypted string of hexadecimal code characters, and this step is in front of the decryption string, Some strings may still be rendered in hexadecimal, so this method is purposely placed later and can also be called last.

hexUnicodeToString() {
		traverse(this.ast, {
			StringLiteral(path) {
				var curNode = path.node;
				delete curNode.extra;
			},
			NumericLiteral(path) {
				var curNode = path.node;
				deletecurNode.extra; }}); }Copy the code

Identifier optimization

Most obtruse identifiers are _0x123456, but some are more unusual, such as OOOO0o, which is easier to read than the previous one, so you can rename all identifiers uniformly.

	renameIdentifier() {
		let code = this.code
		let newAst = parser.parse(code);
		traverse(newAst, {
			'Program|FunctionExpression|FunctionDeclaration'(path) {
				path.traverse({
					Identifier(p) {
						path.scope.rename(p.node.name, path.scope.generateUidIdentifier('_0xabc').name); }}}}));this.ast = newAst;
	}
Copy the code

But it is impossible to know the identifiers of the source code, so it is impossible to understand the code from its semantics.

But there are also specific substitutions, like for I

for (var _0x1e5665 = 0, _0x3620b9 = this["JIyEgF"] ["length"]; _0x1e5665 < _0x3620b9; _0x1e5665++) {
 	this["JIyEgF"] ["push"] (Math["round"] (Math["random"] ())); _0x3620b9 =this["JIyEgF"] ["length"];
}
Copy the code

In code like this, it is perfectly possible to replace _0x1e5665 with I, but the overall reading effect is minimal.

Other reduction means

There are some reduction methods that I won’t go into detail (not used in this example), for example

  • Change parameters to arguments
  • Restore the switch execution process
  • Processing object flower instructions
  • Dealing with eval code

Wait, you can optimize whatever you want, but the restored code is not necessarily readable. If you can’t decrypt a string, it’s all in vain, compared to decrypting a string.

You can see the operation on the AST in the source code example for an example.

Run the restored code

In the end, the whole restored code can be viewed in newcode.js, but so far there is no test on whether the restored code can run normally or whether the replacement of nodes leads to syntax errors, so we need to replace the restored code with the confused code to test. I won’t put the specific execution process here (because I really don’t want to deal with this JS file…).

JS confusion with restore site

In view of the above restore operation is not obvious enough, so we wrote an online JS code obfuscation and restore website (mainly for restore) – JS code obfuscation and restore (kuizuo.cn)

In fact, it is to package the above restore code into a tool.