Adblock Plus and (a little) more

Are undetectable changes to a native prototype possible? · 2011-07-11 14:27 by Wladimir Palant

This is a follow-up to Do JavaScript proxies allow undetectable function wrappers?. After that blog post I managed to solve the main problem: with Function.toString() and Function.toSource() being the only information leaks (bug 650299) one only needs to wrap these functions as well to get undetectable function proxies. However, the remaining problem is manipulating Window.prototype.open so that it actually returns my wrapper and the webpage can neither detect nor revert this manipulation.

Object.defineProperty() looks very promising, you can actually set a value for Window.prototype.open that won’t be deletable. However, the results are still inconsistent. It seems that the behavior for “real” methods of native prototypes is this:

  1. Properties are writable, they can be set to a different value.
  2. They are configurable, so deleting a property actually does something.
  3. A deleted method is automatically “recovered”, you get a new instance of the same function (comparing it to the original value yields in “not equal”).

It’s that last feature that apparently cannot be emulated. It might be possible to use Object.watch() but the webpage can always call Object.unwatch() to kill my handler and to detect the manipulation then. __noSuchMethod__ doesn’t have the desired effect, it will only cover calls to deleted methods but not accessing a method property without calling it. Is there anything that I am missing?

Tags:

Comment [5]

  1. Colby Russell · 2011-07-11 16:35 · #

    It would be straightforward to achieve what you wanted if you could set a proxy for Window’s prototype property, and implement the delete trap to do what you want (just return true?). Too bad messing with Window.prototype is off limits.

    Is there a case against changing the privileges here, so that chrome code can do that type of thing?

    Reply from Wladimir Palant:

    Nah, the whole thing is a hack – if somebody is going to fix it then the time is better spent on making the pop-up blocker extendable in the first place.

  2. Gareth Heyes · 2011-07-11 23:19 · #

    What’s the problem with using proxies? Just create a closure with your original function then use the proxy on delete of the property and restore the original property. The proxy only then contains the reference via the closure to the original code.

    Reply from Wladimir Palant:

    Gareth, I already figured out how to create the proxy of a function that would be indistinguishable from the original function – that was actually the question in my previous blog post that I linked to. The problem now is actually assigning this proxy to @Window.prototype.open@ and preventing the website from detecting that something is “wrong”.

  3. Gareth Heyes · 2011-07-12 09:38 · #

    I know. But as you state, in the original correct environment you can overwrite the native method and if you use delete you can get the original native method back. You can simulate this in your environment by using a delete listener with proxies, this will allow you to make the property over rewritable yet when it’s deleted retain you original method.

    function handlerMaker(obj) {
    return {delete:function(name){
       if(name == ‘open’) {
         obj[name] = function originalFunc(){
         }
       } else {
         delete object[name];
       }
    },
    getOwnPropertyDescriptor: function(name) {
          var desc = Object.getOwnPropertyDescriptor(obj, name);
          if (desc !== undefined) { desc.configurable = true; }
          return desc;
        },
        getPropertyDescriptor:  function(name) {
          var desc = Object.getPropertyDescriptor(obj, name);
          if (desc !== undefined) { desc.configurable = true; }
          return desc;
        },
        getOwnPropertyNames: function() {
          return Object.getOwnPropertyNames(obj);
        },
        getPropertyNames: function() {
          return Object.getPropertyNames(obj);
        },
        defineProperty: function(name, desc) {
          Object.defineProperty(obj, name, desc);
        },
    fix:          function() {
          if (Object.isFrozen(obj)) {
            return Object.getOwnPropertyNames(obj).map(function(name) {
              return Object.getOwnPropertyDescriptor(obj, name);
            });
          }
          return undefined;
        },
        has:          function(name) { return name in obj; },
        hasOwn:       function(name) { return Object.prototype.hasOwnProperty.call(obj, name); },
        get:          function(receiver, name) { return obj[name]; },
        set:          function(receiver, name, val) { obj[name] = val; return true; },
        enumerate:    function() {
          var result = [];
          for (name in obj) { result.push(name); };
          return result;
        },
        keys: function() { return Object.keys(obj) }
    }
    };
    o = Object.create({});
    var proxy = Proxy.create(handlerMaker°);
    proxy.open=function originalFunc() {};
    proxy.open=123;
    alert(proxy.open);
    delete proxy.open;
    alert(proxy.open);

    Reply from Wladimir Palant:

    As Colby Russell noted above – “Window.prototype is read-only”, you cannot replace it by your own proxy.

  4. Gareth Heyes · 2011-07-12 10:36 · #

    function Window(){};
    Window.prototype={open:‘O really’}
    alert(Window.prototype.open);

    Reply from Wladimir Palant:

    In other words, wrap Window, Window.prototype, make sure that window instanceof Window still results in true and that window.__proto__ is the wrapped prototype. And probably a bunch of other things that I didn’t think of right now…

  5. Gareth Heyes · 2011-07-12 12:20 · #

    Yeah I tend to communicate in code rather than words hehe :)

Commenting is closed for this article.