j4k0xb / webcrack

Deobfuscate obfuscator.io, unminify and unpack bundled javascript
https://webcrack.netlify.app
MIT License
1.07k stars 129 forks source link

feature split variable declarations in for loop #35

Closed kuizuo closed 11 months ago

kuizuo commented 11 months ago

The purpose of this feature is as follows.

before

for (var a = "0|1|2|3"["split"]("|"), i=0;;) {
  // ...
}

after

var a = "0|1|2|3"["split"]("|");
var i = 0;
for (;;) {
   // ...
}

I've come across a piece of obfuscated code in which the qt identifier is the decoder. I think with this transform, the control-flow-switch can be handled better.

function l(n) {
    for (var t = qt, r = {
        WzHjn: t(1835) + "1",
        UwEQS: function (n, t, r) {
            return n(t, r)
        },
        CoZgR: function (n, t) {
            return n >>> t
        },
        TRZvx: function (n, t) {
            return n >>> t
        },
        XwGzX: function (n, t, r) {
            return n(t, r)
        },
        cVxKc: function (n, t) {
            return n >>> t
        },
        WvIaT: function (n, t, r) {
            return n(t, r)
        }
    }, e = r[t(1701)][t(601)]("|"), u = 0; ;) {
        switch (e[u++]) {
            case "0":
                n = r[t(369)](h, n, [0, r[t(598)](n[0], 1)]);
                continue;
            case "1":
                return n;
            case "2":
                n = r[t(369)](f, n, [4283543511, 3981806797]);
                continue;
            case "3":
                n = r[t(369)](h, n, [0, r[t(1320)](n[0], 1)]);
                continue;
            case "4":
                n = r[t(1475)](h, n, [0, r[t(2043)](n[0], 1)]);
                continue;
            case "5":
                n = r[t(1554)](f, n, [3301882366, 444984403]);
                continue
        }
        break
    }
}

At last, I tried to write a piece of visitor code, but I couldn't guarantee whether it was safe or not. This can cause variable names to be duplicated. And the above code should run before split-variable-declarations

export default {
  name: 'forLoopHoist',
  visitor() {
    return {
      ForStatement: {
        exit(path) {
          if (t.isVariableDeclaration(path.node.init)) {
            path.insertBefore(path.node.init)

            path.node.init = null
          }
        },
      },
    }
  },
} satisfies Transform
j4k0xb commented 11 months ago

Yeah that would be a nice improvement

And the above code should run before split-variable-declarations

The order mostly doesn't matter, babel requeues nodes after replacing/moving and all unminify visitors are merged into a single one. But applying it on exit instead of enter is required in many cases because the checks/matchers involve child nodes which other visitors could have modified.

I couldn't guarantee whether it was safe or not. This can cause variable names to be duplicated

I'd only handle var to be safe, let/const have a lot of edge cases with scoping, for loops and closures. For readability it should ignore normal for loops like for (var u = 0, v = o.length; u < v; u++) { that have either node.test or node.update Wanna create a PR with these changes and a few simple test cases?

kuizuo commented 11 months ago

I noticed there is a test segment in the split-variable-declarations.test.ts file. I think we can implement this feature for a for loop in the split-variable-declarations.ts file.

https://github.com/j4k0xb/webcrack/blob/e1aff388d308f3123cf3ea9a50b24fb9ae569043/packages/webcrack/src/unminify/test/split-variable-declarations.test.ts#L25-L33

j4k0xb commented 11 months ago

Putting it in that file is fine