Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: block-level calculator/dispatcher #100

Closed
Le0Developer opened this issue Jul 4, 2023 · 4 comments
Closed

feat: block-level calculator/dispatcher #100

Le0Developer opened this issue Jul 4, 2023 · 4 comments
Labels
enhancement New feature or request

Comments

@Le0Developer
Copy link

Is your feature request related to a problem? Please describe.

In addition to the global calculator function, local/block-level calculator functions are very annoying to deal with.

This is an example from a script obfuscated by Cloudflare's proprietary solution:

  function hl(d, e) {
    e = {
      IODHZ: strLookup(1495),
      ZaiCw: function (f, g, h) {
        return f(g, h);
      }
    };
    e[strLookup(801)](hh, d, function (f) {
      f[strLookup(1846)][strLookup(1875)] = strLookup(347);
      f[strLookup(1846)].visibility = e.IODHZ;
    });
  }

As you can see, the e object contains a block-level dispatcher and variable, but it can also include calculators and even the same one multiple times:

      f = {
        VmRPf: function (j, k) {
          return k === j;
        },
        jzNUF: "complete",
        InfVk: function (j, k) {
          return k | j;
        },
        XFOiM: function (j, k) {
          return j << k;
        },
        EpYUF: function (j, k) {
          return k ^ j;
        },
        TGnJl: function (j, k) {
          return j - k;
        },
        ihSaf: function (j, k) {
          return k ^ j;
        },
        netJL: function (j, k) {
          return j & k;
        },
        ftAEZ: function (j, k) {
          return j - k;
        },
        KqEBC: function (j, k) {
          return j ^ k;
        },
        ZKdga: function (j, k) {
          return j < k;
        },
        WxnhP: function (j, k) {
          return k ^ j;
        },
        buCzB: function (j, k) {
          return k ^ j;
        },
        ANDzz: function (j, k) {
          return j + k;
        },
        yGbbD: function (j, k) {
          return j + k;
        },
        xUDZY: function (j, k) {
          return k ^ j;
        },
        sZhYi: function (j, k) {
          return j ^ k;
        },
        OOYGk: function (j, k) {
          return j & k;
        },
        APBrv: function (j, k) {
          return j ^ k;
        },
        UNIAH: function (j, k) {
          return k ^ j;
        },
        nXtSj: function (j, k) {
          return j ^ k;
        },
        xlBks: function (j, k) {
          return j - k;
        },
        PetUx: function (j, k) {
          return j ^ k;
        },
        AnGxH: function (j, k) {
          return j ^ k;
        },
        RbfOG: function (j, k) {
          return k === j;
        },
        fzjxF: function (j, k) {
          return k === j;
        },
        oNtLY: strLookup(1018),
        mlhOQ: strLookup(522),
        ehmzL: function (j, k) {
          return j(k);
        },
        KexZm: strLookup(1471),
        Iajza: strLookup(1762),
        oHdAV: strLookup(1217),
        IrhEi: strLookup(1560),
        ecuWj: function (j, k) {
          return j === k;
        },
        mQGaA: function (j, k) {
          return j !== k;
        },
        BLreP: strLookup(567)
      };

Sometimes in deeply nested functions, a block-level dispatcher/calculator also calls another higher-up one:

f = {
  Hcgyw: strLookup(1027),
  kZhQS: function (l, m) {
    return l + m;
  },
  njgAA: function (l) {
    return l();
  },
  vSaUg: function (l, m) {
    return m === l;
  },
  GddGi: function (l, m) {
    return l < m;
  },
  gTouW: strLookup(347),
  bxzGp: strLookup(1349),
  cCbfw: strLookup(1072),
  TDQGI: strLookup(729),
  UtfmT: function (l, m) {
    return m === l;
  },
  BCvmI: strLookup(447),
  BeEUo: function (l, m) {
    return m === l;
  },
  QxEvm: strLookup(984),
  kcjNA: function (l, m) {
    return l === m;
  },
  YaqZh: strLookup(1329),
  EQVuX: strLookup(1876),
  wRfVC: strLookup(459),
  awDrx: function (l) {
    return l();
  },
  AvtlX: function (l, m) {
    return l(m);
  },
  SPHDi: strLookup(1316),
  SCEKk: function (l, m) {
    return l(m);
  },
  gxpYE: function (l) {
    return l();
  },
  IUoch: strLookup(1707),
  jMlyM: strLookup(1328),
  cVtpB: strLookup(1140),
  WcJaN: strLookup(1464),
  BRVga: function (l, m) {
    return l + m;
  }
};

/* later */

  l = {
    phzoe: function (B, C) {
      return f[strLookup(1910)](B, C); // higher-up
    },
    eFbES: strLookup(1103),
    aocNW: function (B, C) {
      return f[strLookup(661)](B, C); // higher-up
    },
    CzTgP: strLookup(493),
    PyEZj: "error code: 1020",
    YyLwb: f[strLookup(634)], // higher-up
    RjTgN: strLookup(1560)
  };
@Le0Developer Le0Developer added the enhancement New feature or request label Jul 4, 2023
@MichaelXF
Copy link
Owner

Yes that could added to calculator. What I notice is the e object contains strings and other types of functions on it. This could also be implemented in JS-Confuser. What do you think should change?

  • Should calculator do this?
  • Should another transformation do this? Dispatcher could be a great candidate for this as it already implements the nested feature.

@Le0Developer
Copy link
Author

Le0Developer commented Jul 9, 2023

I am not too familiar with the entire codebase, so you should do the call where best to implement this.

Just a few more notes:

  • cf's dispatcher functions always take the real function as first argument, making them trivial to reverse:
e.ZaiCw(hh, d, function (f) {
// does
hh(d, function(f) {

Maybe specialized dispatchers make more sense?

// dispatcher only for hh
function dispatcher_hh(arg1, arg2) {
  return hh(arg1, arg2)
}
// dispatcher with an array
// problem: might leak all functions used in the scope, so extra fake values required
function dispatcher(idx, arg1, arg2) {
  var fn = [hh][idx];
  return fn(arg1, arg2);
}
  • cf's minifier removes all var statements (really, not a single one in a 5k loc file) by putting variables into unused function arguments
function test() {
  var a = 10;
  return a;
}
// out
function test(a) {
  a = 10;
  return a;
}
  • cf sometimes has an extra transformation that replaces object literals with a bunch of assignments, eg:
var example = {
  abc: "def"
}
// after
var example = {};
example["abc"] = "def"; // most often with string concealment here

Which can get really annoying with >50 assignments.

  • cf currently always uses these 5 char random indexes which are inserted in their string concealment array. I think it'd make more sense to just reuse random strings we already have in the string concealment array. This will reduce the size but also make reversing more confusing.
// example, after undoing string concealment
// only one string (ignoring "log") and a lot more confusing imo than an extra random string
var e  = {
  "hello world": function(a, b) {
    return a(b)
  }
}
e["hello world"](console.log, "hello world")

@MichaelXF
Copy link
Owner

  1. Specialized dispatchers, yes!
  2. Removing var and putting it a function parameter is a very cool idea.
  3. transformObjectKeys is neat but it's weak obfuscation.

I think going about this would be to buff up dispatcher. It will probably not be exactly like cf's obfuscation but I'll see what ideas could be implemented when I have the time. The var idea is also nice and would possibly be added to movedDeclarations as it's appropriate transformation.

@MichaelXF
Copy link
Owner

Closing for now.

1- Dispatchers are recursively applied
2- Added to Moved Declarations in 2.0
3- Not planned

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants