Wednesday, June 16, 2010

Ultimate toString() override

As shown in my last talk on malware analysis, automatic malware detectors could be easily beaten by detecting their emulation layer. For example, malware could always use Function.toString() method to check if any function has been emulated by the sandbox. Today I raise the bar a little - we'll switch the toString() method in a way that is significantly harder to detect by malware authors.

I discussed the sandboxing & detection techiques currently used with Blake Hartstein, jsunpack author and we came to an obvious conclusion, that the problem is unsolvable within JavaScript layer. Every trick used by sandbox engine could be ultimately detected by malware. In other words - in JavaScript, you can't hack the JavaScript itself. That is the reason Blake modified the underlying Spidermonkey code (written in C) to allow for perfect eval() overloading (see my attempts for a comparison).

To see why it is so hard, let's look at the example:

toString() chain

Current version of jsunpack emulates window.open by doing:
window.open = function (url){
    print("//jsunpack.url open = " + url) 
};
But malware author could easily detect this by doing:
window.open.toString().match(/print/)
To protect from detection, jsunpack could do:
window.open.toString = function () {
return "function toString() {\n\t[native_code]\n}";
}
but then again malware could check window.open.toString.toString() ... and so on, ad infinitum.

The problem is that if we modify toString() for function x, the modifications will be visible in the x.toString.toString() output. In JavaScript you could always look at the insides of a function if you're clever enough.

Practical point of view

Because of this, jsunpack takes the reactive strategy - if some technique is captured in the wild, countermeasures will be taken. Nonetheless, if someone comes up with a better emulation layer, requiring bigger JS skills to detect, it would only be for good.

In this spirit, I present to you...

The (almost) ultimate toString() override

Using JavaScript getters (introduced in JS 1.5) we could do something like this:
var emulateToStringRecursive = function(object, fn_header) {
    object.__defineGetter__("toString", function() {
        var original = this.__proto__.toString;
    
        this.__proto__.toString = function() {
            var a = original.call(this);

            if (a.match("_RANDOMIZE_ME"))
                return original.toString();
            return a;
        };

        return function() {
            // dummy operation to avoid removal by the optimizer
            ["_RANDOMIZE_ME"] 
            var BODY = " {\n\t[native code]\n}";
            return fn_header + BODY;
        }
     });
};
and use it like:
emulateToStringRecursive(window.open, 'function open()');
This will make sure that window.open.toString() returns:
function open() {
    [native code]
}
but window.open.toString.toString(), window.open.toString.toString.toString() and below return:
function toString() {
    [native code]
}
just like original functions would. With this code in place it is now harder for malware authors to detect the sandbox.

To make the protection more bullet proof, we should introduce
  • randomization ("RANDOMIZE_ME" should be changed into random string by some preprocessor)
  • anonymization (don't put it in a global variable)
  • monitoring checking/modifying getters & setters at Spidermonkey layer - just like with eval(). So that if malware tries to detect this technique by looking at the getters, we'll catch it.
You could look at the code and the example script here http://github.com/koto/owasp-malicious-javascript/tree/master/tostring/

To test it within jsunpack, put the relevant code in the end of pre.js.

Disclaimer

Of course, I know that this is also trivially easy to detect (I won't publish the code for this though), but I think it does require JS skills from the bad guys, and we could hope that this will slow them down a little. With monitoring in place we could also make this almost perfect.

3 comments:

Anonymous said...

How intresting. It really supprises me that people go thru great lenths and now what should i do with all this info.? Thank you. SOPHIA A.

-M said...

Hi,

Nice post, thanks.Another idea to mask function overriding would be:
window.open.toString = function() {return "function open() {\n\t[native code]\n}";}
window.open.toString.toString = function() {return "function toString() {\n\t[native code]\n}";}
window.open.toString.toString.toString = window.open.toString.toString;Can you find anything wrong with it?

-M

kkotowicz said...

Cool, i like it :)

BTW I've just found method of detecting override for both methods (mine and yours):

window.open.toString == window.open.toString.toString
false
window.close.toString == window.close.toString.toStringtrue