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.
To test it within jsunpack, put the relevant code in the end of pre.js.
3 comments:
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.
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
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
Post a Comment