0

A common trick sites use to protect against reverse engineering is to prevent Javascript from executing correctly when the developer tools are open. Usually, this relies on an object logged to the console with a getter or custom toString function that only gets called when the console is visible. A simple workaround is to proxy console.* functions and discard offending objects.

I recently stumbled across a site that is a bit more difficult - it detects if you've proxied or otherwise messed around with native functions. Unfortunately, the site's scripts are heavily obfuscated, so I haven't been able to determine the exact method they use. However, the following seems to be a reliable way to determine if a native function is proxied in Chrome:

function isProxied(func) {
  return func.toString().length === 29;
}

console.log(isProxied(eval));

eval = new Proxy(eval, {});

console.log(isProxied(eval));

How can I make a proxied object appear identical to its target?

6
  • You also need to proxy toString of the proxied object.
    – woxxom
    Commented Feb 19 at 8:04
  • Seems to be easily tricked by eval.toString = () => "". We don't know what else they're checking though so we can't recommend anything specific.
    – Bergi
    Commented Feb 19 at 10:13
  • "How can I make a proxied object appear identical to its target?" - you cannot. A proxy always has a different identity than its target.
    – Bergi
    Commented Feb 19 at 10:16
  • @Bergi In this case the object will be proxied before they can grab a copy of the original, so the identity doesn't matter. Is there any reason why a proxied object doesn't retain the toString of the original? It seems to work that way in Firefox. Commented Feb 19 at 12:13
  • It does retain the original toString (which is Function.prototype.toString), it's just that the native .toString() method returns a different value for the proxy function.
    – Bergi
    Commented Feb 19 at 12:15

1 Answer 1

1

[This site] detects if you've proxied or otherwise messed around with native functions. The object will be proxied before they can grab a copy of the original, so the identity doesn't matter.

If you can inject your code before theirs, you can overwrite all of the native objects with a convincing-enough emulation. You don't even need to use proxies. Depending on their sophistication and your needs, you may need to build an entire virtual environment of globals and builtins though.

For replacing console and its methods, it's simpler. To convincingly emulate a function, you need to consider its .name, .length (arity), .prototype, its code (via .toString()) and its behaviour (side effects and return values). So you'll first need to replace Function.prototype.toString itself:

{
  const originals = new WeakMap();
  const replace = (target, methods) => {
    for (const name in methods) {
      originals.set(methods[name], target[name]);
      // assert(typeof target[name] === typeof methods[name]);
      // assert(target[name].name === methods[name].name);
      // assert(target[name].length === methods[name].length);
      // assert(target[name].prototype === methods[name].prototype);
      // assert(Object.getPrototypeOf(target[name]) === Object.getPrototypeOf(methods[name]));
      target[name] = methods[name];
    }
  };
  const toString = Function.prototype.call.bind(Function.prototype.toString);
  replace(Function.prototype, {
    toString() {
      const orig = originals.get(this);
      return toString(orig ?? this);
    },
  });

  replace(console, {
    log() {
      // do nothing
    },
  });
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.