Ein altes Sample lag schon seit Wochen in meinem Homedirectory, da ich es einmal analysieren wollte - der Hash F0B0224B75E899440C15EE05B59B6013. Obgleich es Virenscanner als Backdoor.Win32.Dumador.at erkennen gehe ich ans Werk um es einmal selbst zu zerlegen. Ein Sample mit 21536 Bytes Kampfgewicht und einer Behandlung mit dem FSG Packer sollten schon knackbar sein. Der defekte PE- und der fast winzige MZ-Header sind ein sicheres Zeichen dafür dass jemand etwas verstecken möchte.

Das Tolle an den Packern, wie im vorherigen Post schon beschrieben ist, dass sie den Entpackungsmechanismus und den Key gleich frei Haus mitliefern. FSG ist da keine Ausnahme und mit ein bisschen Trickserei gibt das Paket auch seinen Inhalt Preis.

Beim Start kopiert sich das Sample als winldra.exe in den Ordner %windir%\system32, wo es ein paar DLL Dateien ablegt und sich schliesslich in der Registry unter HKLM\Software\Microsoft\Windows\CurrentVersion\Run einnistet, was den Autostart sichert. Was die DLLs genau tun kann ich jedoch nur vermuten, scheint jedoch etwas mit einer Funktion namens Windows-Hook CBTProc zu tun zu haben, was eine Art Keylogger für das aktive Fenster zu sein scheint. Laut Microsoft ist es ein (dubioses) Windows-feature für Computer Based Training, über das ein Prozess beobachten kann, was im aktiven Fenster vor sich geht:

"Das System ruft diese Funktionen auf, bevor es ein Fenster aktiviert, erzeugt, vernichtet, minimiert, maximiert, bewegt oder seine Größe verändert; bevor es System-Kommandos beendet; bevor es Maus- oder Tastatureingaben aus der Message Queue entfernt; bevor es den Tastaturfokus setzt; oder bevor es die Message Queue des Systems synchronisiert. Ein computergestütztes Trainingsprogramm (CBT) benutzt diesen Hook, um vom System nützliche Benachrichtigungen zu erhalten."

Das gefährliche an dieser Funktion ist, dass sie wie für diesen Virus geschaffen ist und die Vorgänge im Browser bei einer enthaltenen Liste von Webseiten genauestens beobachten kann. Diese Liste beinhaltet Seiten wie cbonline.co.uk, volkswagenbank.de oder raiba-nu-wh.de... Nett oder? Was passiert weiter?

Die gesammelten Daten werden per eMail, codiert versendet und ein Registry Key gesetzt: HKCU\Software\SARS\mailsended = 1

Altbekannte Maneuver des Samples sind dazu noch das nachträgliche Bearbeiten der Datei %windir%\system32\drivers\etc\hosts, mit welcher man die DNS Auflösung diverser Dateien effektiv unterbinden kann. Dass dies in erster Linie Seiten von Antivirus-Herstellern sind, wird wohl jedem klar sein.

Nach diesem groben Überblick fand ich in dem File noch ein paar Ungereimtheiten, welche mich in die Tiefen des Codes zwangen, welcher sich nun im Debugger vor mir ausbreitete. Ich begann zu lesen...

Bei der Adresse 004030B2 angelangt stiess ich auf InternetConnectA() und HttpSendRequestA() WinInet API, was eindeutig auf das Senden von Daten ausgelegt war, welche wiederum ihre Daten mit einer netten Routine bei 00403187 mit Hilfe der Funktion strcpy() und logdata= als Argument verschlüsselt. Meine Neugierde wächst und ich grabe tiefer und folge den Spuren im Sample in der Hoffnung nichts zu übersehen.

Die der Verschlüsselung übergebenen Argumente, namentlich EAX (ein unverschküsselter String), EDX (das Ergebnis - String) und ECX, die Länge von EAX. Jedes Byte von EAX wird in einer Schleife abgearbeitet, welche so lange läuft bis alle Zeichen verschlüsselt sind.

00401935 ; ::::::::::::::: S U B R O U T I N E :::::::::::::::
00401935
00401935 EncryptLogs
00401935     push    ebx
00401936     push    esi
00401937     push    edi
00401938     push    ebp
00401939     mov     edi, offset aAbcdefghijklmn ;
0040193E     xor     esi, esi
00401940     test    ecx, ecx
00401942     jle     loc_4019D8

In diesem ersten Block passiert nichts Unheimliches: Register werden gespeichert und Variablen gesetzt. Desweiteren wird geprüft ob der Wert von ECX grösser als Null ist. Die Variable EDI welche hier auftaucht zeigt auf einen alphanumerischen string mit den Zeichen A-Z, a-z, 0-9 und "+/") - sein Sinn scheint nur Verwirrung zu sein.

00401948 loc_401948:
00401948     cmp     esi, 20
0040194B     jbe     short loc_401957
0040194D     mov     byte ptr [edx], 0Dh
00401950     inc     edx
00401951     mov     byte ptr [edx], 0Ah
00401954     inc     edx
00401955     xor     esi, esi

Der zweite Block formatiert die Zeilen der Ausgabe, sodass das Ergebnis keine überlangen Zeilen enthält - was sich durchaus als Vorteil erweisen sollte. ESI dabei stellt einen Zähler dar, welcher nach 20 verschlüsselten Zeichen zurückgesetzt wird, nachdem ein CRLF in die Verschlüsselung eingefügt wurde.

00401957 loc_401957:
00401957     xor     ebx, ebx
00401959     mov     bl, [eax]
0040195B     sar     ebx, 2
0040195E     mov     bl, [edi+ebx]
00401961     mov     [edx], bl

Der dritte Teil hat auch nichts Magisches an sich: Bedenken wir, dass EAX auf den zu verschlüsselnden String zeigt. Daraufhin wird EBX gelöscht und das erste Zeichen nach BL geladen und EBX um 2 Bit verschoben. Das Ergebnis wird in ein Byte verwandelt und im Ergebnisstring EDX abgelegt. Klingt komplex, ist es aber nicht:

Nehmen wir an, unser Zeichen lautet 'I', was nach der ASCII-Tabelle in Binär so aussieht: 01001001 (Schön, nicht?) Nun verschieben wir um 2 Bit und das Ergebnis lautet 00010010. Da wir jedoch nur 8 Bit zur Verfügung haben, verlieren wir 2 Stellen und wir haben einen Wert den wir in der ASCII Tabelle ermitteln können...

Wir schreiten zum nächsten Stück:

00401963     xor     ebx, ebx
00401965     mov     bl, [eax]
00401967     inc     edx
00401968     and     ebx, 3
0040196B     shl     ebx, 4
0040196E     cmp     ecx, 1
00401971     jle     short loc_40197C
00401973     movzx   ebp, byte ptr [eax+1]
00401977     sar     ebp, 4
0040197A     jmp     short loc_40197E
0040197C ; ------------------------------------------------
0040197C loc_40197C:
0040197C     xor     ebp, ebp
0040197E
0040197E loc_40197E:
0040197E     or      ebx, ebp
00401980     mov     bl, [edi+ebx]
00401983     mov     [edx], bl

Huh? Das kennen wir doch schon? Exakt: Als erstes wird wie auch im vorherigen Abschnitt EBX gelöscht und das erste zu verschlüsselnde Zeichen geladen. EDX wird daraufhin erhöht und eine AND-Operation an EBX mit dem Wert 3 ausgeführt. Um uns nun richtig zu verwirren wird dann das Zeichen wieder verschoben, diesmal um 4. Danach passiert ein Check ob wir nicht schon beim letzten Byte angelangt sind.

Sehen wir uns das Ganze wieder an unserem Beispiel von vorhin an. 3 entspricht dem ASCII Wert 00000011 und 'I' kennen wir bereits als 01001001. Die logische AND Operation sieht wie folgt aus: 01001001 AND 00000011 = 00000001. Mit diesem Ergebnis und der Verschiebung landen wir schliesslich bei 00010000... Was ist hier passiert? - Ganz langsam zum Mitschreiben: Die 2 LSB (Least Significant Bits) des ersten Bytes wurden gespeichert und um 4 nach rechts verschoben. Der aufmerksame Leser wird sich die Zeichen ansehen und bemerken, dass dies die Zeichen sind, welche uns bei der ersten Operation verloren gingen.

Was den Check des letzten Bytes im String betrifft ignoriere ich ihn für's erste einmal, da die Stelle 00401973 meine Aufmerksamkeit auf sich zieht. Was hier passiert ist, dass das nächste Byte genommen wird, es um 4 Stellen nach Rechts verschoben wird um schliesslich mit 2 in der logischen Operation OR zu landen.

Um es zu veranschaulichen sei unser nächstes Byte der Buchstabe 'D', welcher in der ASCII Tabelle unter dem Binärwert 01000100 zu finden ist. Verschieben wir einmal und wir erhalten 00000100. Die OR-Operation gegen die Bitfolge 00010000 führt uns zum Ergebnis 00010100. Hier halten wir die ersten 2 Bits des ersten Byte und die letzten 4 des zweiten Bytes in den Händen. Wie auch vorhin verlieren wir ein paar Bits bei den Operatonen, hier die MSB (Most Significant Bits)...

Wie dem auch sei, wir wissen wie die fehlenden Bits der ersten Manipulation aussehen, was uns ermögicht, das erste Byte zu decoden: Wir suchen uns den Index der ersten beiden Bytes im verschlüsselten String und verschieben. Unser Beispiel mit dem Zeichen 'I' sollte uns dabei gute Dienste leisten. Das Ergebnis dabei ist 01001000, was uns das Zeichen bis auf die LSB liefert. Um diese zu bekommen verschieben wir den zweiten Index um 4 nach Rechts und bekommen 01001000. Diese zwei Ergebnisse addieren wir in einer AND Operation und erhalten - Oh Wunder - 01001001 was dem Zeichen 'I' entspricht.

Das erste Zeichen in der Theorie zu entschlüsseln war ja nicht mal so hart, oder? Lesen wir weiter und sehen wir uns an, wie es weitergeht:

0040198D     mov     bl, [eax+1]
00401990     shl     ebx, 2
00401993     and     ebx, 3Ch
00401996     cmp     ecx, 2
00401999     jle     short loc_4019A4
0040199B     movzx   ebp, byte ptr [eax+2]
0040199F     sar     ebp, 6
004019A2     jmp     short loc_4019A6
004019A4 ; ------------------------------------------------
004019A4 loc_4019A4:
004019A4     xor     ebp, ebp

Mal sehen... In diesem Block sieht es ähnlich aus wie zuvor und wir haben gute Chancen mit den aktuellen Überlegungen durchzukommen. Das 2. Byte wird um 2 Verschoben und in einer AND Operation gegen 0x3c gerechnet. Das heisst für unser Beispiel:

'D' = 01000100 < < 2 = 00010000,
0x3c = 00111100
00010000 & 00111100 = 00010000.

Dann wird das 3. Byte angegriffen und um 6 Stellen verschoben. Das 3. Byte nehmen wir als ':' an, was 00111010 entspricht. Nach dem verschieben der Stellen haben wir 00000000. Die im Code vorgeschriebene OR Operation gegen dieses Ergebnis ergibt 00010000 und einen Heisshunger auf eine Tasse Kaffee.

Nun wissen wir die ersten 4 Bit des zweiten Bytes, haben aber die ersten 6 Bit des dritten Zeichens verloren. Ähnlich wie wir dem ersten Byte zu Leibe gegangen sind decoden wir das Zweite indem wir das Bitmuster des Zweiten und Dritten Bytes in unserem String suchen um ersteres um 4 Bit nach links und zweiteres um 2 Bit nach rechts zu versetzen. Die Ergebnisse vereinen wir mit einer AND Operation und - Heureka, das 2. Byte ist leserlich.

Die Decodierung des 3. Bytes sollte erratbar sein, aber um sicher zu gehen - wir sind ja paranoid - prüfen wir es in den nächsten Zeilen des Codes nach:

004019B9     mov     bl, [eax+2]
004019BC     and     ebx, 3Fh
004019BF     mov     bl, [edi+ebx]

Was hier passiert ist wirklich simpel und Debuggerfreundlich: Das 3. Byte wird in BL geladen und in einer AND Operation mit 0x3f vereint. Kurz und schmerzlos testen wir das an unserem Beispiel aus:

Unser 3. Byte 00111010 und 0x3F ergibt 00111111. 00111010 & 00111111 = 00111010.

Das wäre ja im Grunde genommen unser Byte in Klartext, aber die letzten 2 Bits gingen durch das Shiften verloren. Analog zum vorigen Codeblock shiften wir das 3. Byte um 6 Stellen nach links und vereinen es mit einer OR Operation mit dem 4. Wir wissen nun, wie wir den String decrypten, aber es fehlt noch eine Kleinigkeit. Diese cmp ECX welche in unseren Blöcken herumgeistern zeigen die Stringlänge, damit wir nicht ins Leere greifen. Doch was tun wenn keine Bytes mehr da sind? Der Code zeigt dassentweder 0x0 oder 0x3D verwendet wird, was dem Zeichen '=' entspricht...

Wir wissen nun die händische Methode um die verschlüsselten Daten zu bekommen, doch ist dies nicht rentabel wenn es darum geht, etwas zu decoden, da die gewünschte Information selten kleiner als 3 Bytes sein wird - daher eine Funktion welche uns die Arbeit abnehmen wird:

void decrypt (char *in)
{
    char first, second, third, fourth;
    int lf_count = 0,
        i        = 0;
    int byte_count = strlen(in);

    while (byte_count > 0)
    {
        first  = index(&in[i]);
        second = index(&in[i + 1]);
        third  = index(&in[i + 2]);
        fourth = index(&in[i + 3]);

        if (byte_count > 2)
            first = (first < < 2) | (second >> 4);
        else
            first = (first < < 2);

        printf("%c", first);

        if (byte_count > 2)
        {
            if (byte_count > 3)
                second = (second < < 4) | (third >> 2);
            else
                second = (second < < 4);
        }

        printf("%c", second);

        if (byte_count > 3)
        {
            third = (third < < 6) | fourth;
            printf("%c", third);
        }

        i += 4;
        byte_count -= 4;
        lf_count++;
    }
}

Vorheriger Beitrag Nächster Beitrag