Z zadaniem dotyczącym analizy obfuskowanego skryptu poradziło sobie już kilka osób. Udało mi się zainspirować tym zadaniem powstanie ciekawego wpisu: Beating JavaScript obfuscators with Firebug. Ja ze swojej strony przygotowałem drobną modyfikację zadania, która dostępne jest pod adresem http://bootcamp.threats.pl/lesson20/main1.php. Pora również podzielić się kilkoma dodatkowymi spostrzeżeniami związanymi z tym zadaniem.
Bootcamp XX: Strona, która coś robi III
Z jakiego obfuskatora korzystałem
Niespodzianka - nie korzystałem z żadnego dostępnego obfuskatora, kod zaciemniałem ręcznie. No, prawie ręcznie, bo w przypadku kilku przekształceń dla swojej wygody korzystałem jednak ze skryptów. Chciałem nie tylko pokazać nieczytelny kod, ale również stworzyć kilka zagadek typowych (kiedyś) dla pierwszych lat studiów informatycznych: jak wykona się kod albo jaka będzie wartość zmiennych.
Sposób rozwiązania zadania przez Krzysztofa Kotowicza skutecznie obszedł większość moich zasadzek, ale spodziewałem się tego. W końcu "myśleniem" nad tym jak wykona się kod zajmuje się komputer. Podejście takie może być jednak ryzykowne w przypadku analizy "prawdziwego" malware, bo można się przypadkiem zarazić, jeśli analizy nie będzie się prowadzić w odpowiednio izolowanym środowisku.
Korzystanie z emulatorów
Alternatywną metodą do śledzenia wykonania kodu w przeglądarce, jest skorzystanie z "emulatorów". Tego typu narzędziem może być wielokrotnie wspominana przeze mnie Malzilla, ale można korzystać również z modyfikowanych wersji SpiderMonkey albo innych tego typów rozwiązań. W tej chwili pojawia się pytanie - czy kod, który wykona się w tego typu emulatorze wykona się tak samo, jak w przeglądarce? Nie koniecznie...
Przykłady różnic w wykonaniu kodu i inne pułapki
Od razu zwracam uwagę, że nie wszystkie przypadki pojawiają się w udostępnionych przykładach. Nie są to oczywiście wszystkie przypadki różnic, chodzi mi po prostu o zwrócenie uwagi na ten temat.
Nie wszystkie funkcje/obiekty istnieją w emulatorach
Pierwszy prosty przykład:
var a = Array(); a[0] = eval(String.fromCharCode(97,108,101,114,116)); if (a[0]){}
Co dzieje się w tym kodzie? Tworzona jest nowa tablica, do której jest coś zapisywane. Element zapisywany do tablicy jest dynamicznie tworzony w trakcie wykonania skryptu przy pomocy funkcji eval (który jest ZŁY, to tak przy okazji). Parametr przekazywany do funkcji eval jest również "zaciemniony" z wykorzystaniem funkcji fromCharCode. Gdyby pominąć eval kod ten wyglądałby tak:
var a = Array(); a[0] = alert; if (a[0]){}
Co stanie się w przeglądarce? Nic szczególnego, bo ten if w zasadzie nic nie robi. A co zrobi Malzilla? Radośnie poinformuje, że kod (skrypt) nie może zostać wykonany, bo alert is not defined. Malzilla jest jednak na tyle miła, że pokazuje rezultaty eval, a przynajmniej tych przypadków, do których jest w stanie dotrzeć. Dla ułatwienia przykład:
var a = Array(); a[0] = eval(String.fromCharCode(97,108,101,114,116)); if (a[0]){eval(String.fromCharCode(47, 47, 116, 101, 115, 116, 49));} eval(String.fromCharCode(47, 47, 116,
Malzilla jest w stanie dotrzeć tylko do jednego eval, rezultaty (a właściwie parametry przekazywane do funkcji) pozostałych wywołań funkcji eval pozostaje nieznany. Będzie to wyglądało mniej więcej tak:

Jakie wnioski można wyciągnąć z tego przykładu? Bazując na różnicach między interpreterami można wymusić pożądany execution flow.
A tu różnice między przeglądarkami
Starałem się, by przykład, który przygotowywałem działał w kilku różnych przeglądarkach. Mogłem się jednak skupić na jednej, ignorując pozostałe. W przypadku malware takie zachowanie może być uzasadnione, jeśli próbujemy wykorzystać podatność, która istnieje w IE, po co kod ma się wykonać poprawnie w innych przeglądarkach? Przykład:
var x = 0; try { x = 1; new ActiveXObject('MSXML2.DOMDocument.4.0'); } catch (e) { x = 3; }
Jaka będzie wartość parametru x po wykonaniu tego kodu? Odpowiedź jest prosta - to zależy. Zależy od tego, w jakiej przeglądarce kod ten się wykona, zależy również od tego, czy stworzenie obiektu powiedzie się.
Zmienna globalna, zmienna lokalna...
Skoro już jesteśmy przy temacie wartości, jaką będzie miała zmienna w danym momencie:
var x = 0; function y() { try { x = 1; new ActiveXObject('xyz'); } catch (e) { x = 3; } } y();
W tym przykładzie w zasadzie każda przeglądarka wejdzie do sekcji catch, gdzie zostanie ustawiona wartość zmiennej x na 3. I to jest w miarę oczywiste. A co się stanie w tym przypadku:
var x = 0; function y() { try { x = 1; new ActiveXObject('xyz'); } catch (e) { x = 3; } if (false) var x; } y();
Różnica jest (chyba) niewielka i chyba mało intuicyjna. Choć fragment kodu definiujący lokalną zmienną x znajduje się w kodzie "dalej" i w dodatku nigdy się nie wykona, to okazuje się, że w ciele funkcji x oznacza zmienną lokalną, a nie globalną, która pozostaje niezmieniona. A przecież fragment var x można dość skutecznie ukryć w ciele dłuższej, mocno rozgałęzionej funkcji. I to w obie strony:
var x = 0; function y() { try { x = 1; new ActiveXObject('xyz'); } catch (e) { x = 3; } this.a = function () {var x,z} } y();
W powyższym przypadku co prawda w ciele funkcji y znajduje się fragment var x, ale znajduje się on wewnątrz innej funkcji zdefiniowanej wewnątrz funkcji y.
Kolejność wykonania
Gdyby komputery były jednowątkowe, wszystko byłoby takie proste. Ale nie jest. Przykład?
var x = 0; function a() { x = 1; } function b() { x = 2; } var i = new Image(); var j = new Image(); i.onerror = a; j.onerror = b; i.src = "http://bootcamp.threats.pl/bootcamp20/"; j.src = "http://domain.invalid/abc.jpg"; setTimeout("alert(x)",5000);
Jaka będzie wartość wyświetlona przez skrypt? W zasadzie to zależy, ale najprawdopodobniej będzie to wartość 2. Dlaczego? Przeglądarka stara się pobrać dwa obrazki z dwóch różnych adresów. W obu przypadkach próba ta kończy się niepowodzeniem, przy czym prawdopodobnie szybciej się zakończy próba pobrania obrazka dla obiektu i niż obiektu j, w związku z czym pierwsza wykona się funkcja a obsługująca zdarzenie onerror. Po prostu domena domain.invalid nie istnieje, ale system stara się upewnić, czy aby na pewno nie istniej, więc próba pobrania obrazka trwa dłużej. Jeśli jednak zmienić opóźnienie w funkcji setTimeout z 5 sekund na 1 sekundę, prawdopodobnie wyświetlona zostanie wartość 1, ponieważ próba pobrania obrazka do obiektu j jeszcze trwa, więc funkcja b się jeszcze w tej chwili nie wykonała, wykonać się natomiast najprawdopodobniej zdążyła funkcja a.
Zachowanie to mogę wykorzystać w nieco bardziej wyrafinowany sposób. Na przykład zamiast zdarzenia onerror mogę obsłużyć zdarzenie onload, przy czym po stronie serwera mogę kontrolować to, kiedy obrazek zostanie wysłany do przeglądarki. W ten sposób mogę ustawić na przykład klucz szyfrowania (niech nawet to będzie XOR z kluczem o długości jednego bajta) na pożądaną przeze mnie wartość.
Czy to wszystko?
Oczywiście, że nie. Po prostu chciałem pokazać, że analiza tego typu skryptów może być niezłym wyzwaniem ćwiczeniem dla szarych komórek. Równie ciekawym ćwiczeniem jest tworzenie tego typu zagadek. Może ktoś z czytelników przygotuje własne?
Za to napotkałem po drodze ciekawy problem (na razie nie do obejścia) z przeładowaniem eval()a - więcej tutaj... Może jakiś JS guru tu pomoże...
http://blog.kotowicz.net/2010/04/when-overloading-eval-fails.html
var e = eval; e('alert(/xxx/)'); // działa
var a = new Array(); a[0] = eval; a[0]('alert(/xxx/)'); // nie działa
Komunikat błędu: "Error: function eval must be called directly, and not by way of a function of another name".
Taka ciekawostka.