In dieser mehrteiligen Serie möchten wir Ihnen Techniken vorstellen, die das Leben eines Malwareanalysten erschweren, sprich abwechslungsreich und spannend machen. Im ersten Teil behandeln wir Anti-Disassembly und führen in die Thematik ein.
Warum macht ein Computer eigentlich, was er macht? Und was hat das mit dem Thema dieser Reihe zu tun? Zwei zentrale Fragen, welche im Folgenden beantwortet werden.
Entwickler erstellen Computerprogramme meist in einer Sprache, die intuitiv verständlich ist. Man bezeichnet das Ergebnis dieser Schreibbemühungen als Quellcode.
10 REM Hello World in BASIC 20 PRINT "Hello World!"
Viel kann der Computer (oder genauer gesagt die CPU) damit nicht anfangen. Der Quellcode muss in eine Sprache übersetzt werden, welche die CPU versteht – in eine Abfolge von Maschinenbefehlen.
6A 40 68 00 30 40 00 68 17 30 40 00 6A 00 E8 07 00 00 00 [...] 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00
Diese Maschinenbefehle sind eher schwierig zu lesen, aber üblicherweise ist dies das einzige, was den Malwareanalysten zur Verfügung steht. Diese Bytes sind natürlich nicht zufällig, sondern bestehen aus definierten Befehlen bzw. Opcodes und zugehörigen Adressen / Konstanten [1]. Das erste Byte 6A der obigen Reihe entspricht einem solchen Opcode. Die CPU interpretiert aus diesem Grund das zweite Byte 40 als Konstante 40h. Das dritte Byte 68 entspricht wieder einem Opcode, allerdings interpretiert die CPU in diesem Fall die nächsten vier Bytes als eine Konstante. Damit die Abarbeitung für Menschen einigermassen nachvollziehbar ist, wurde einem Opcode (6A) jeweils ein besser lesbares Kürzel (6A = push) zugewiesen, ein sogenanntes Mnemonic [2]. Programme, welche die Bytereihe (Maschinenbefehle) interpretieren und in Mnemonic-Ausdrücke (Assemblerbefehle) umwandeln können, nennt man Disassembler [3]. Das Ergebnis sieht dann wie folgt aus:
Position Bytes Assemblerbefehl .data:0x00000000 6A 40 push 0x40 .data:0x00000002 68 00 30 40 00 push 0x00403000 .data:0x00000007 68 17 30 40 00 push 0x00403017 .data:0x0000000c 6A 00 push 0x0 .data:0x0000000e E8 07 00 00 00 call func_0000001a ;MessageBoxA
Disassemblieren ist, trotz aller Spezifikationen, eine Kunstform. Mit gezielten Manipulationen der Bytefolge kann das Ergebnis dieses Prozesses variieren – so stark, dass die eigentliche Funktionalität vom Betrachter versteckt wird. Diese Art der Manipulation nennt man Anti-Disassembly.
Mit Hilfe eines einfachen Beispiels wird der Mechanismus gezeigt. Die unmodifizierte Bytereihe lautet:
B8 00 00 00 00 50 EB 00 E8 07 00 00 00 6A 00 E8 1D 00 00 00
Der Online-Disassembler[3] übersetzt uns dies in folgende Assemblerbefehle:
Position Bytes Assemblerbefehl .data:0x00000000 B8 00 00 00 00 mov eax,0x0 .data:0x00000005 50 push eax ┏ .data:0x00000006 EB 00 jmp loc_00000008 ┃ .data:0x00000008 ┃ .data:0x00000008 loc_00000008: ┗▶ .data:0x00000008 E8 07 00 00 00 call func_00000014 ;beliebige Funktion .data:0x0000000d 6A 00 push 0x0 .data:0x0000000f E8 1D 00 00 00 call func_00000031 ;ExitProcess
Deutlich sind die beiden Funktionsaufrufe (call) zu sehen. Die folgende manipulierte Bytereihe ändert nichts an der Programmfunktionalität, verschleiert aber die Ausführung der Funktion func_00000014 in dieser sogenannten statischen Analyse. Ausgenutzt wird hierbei, dass das Byte 68, wie oben gezeigt, die folgenden vier Bytes beansprucht.
B8 00 00 00 00 50 EB 01 68 E8 07 00 00 00 6A 00 E8 1D 00 00 00
Der Online-Disassembler[3] übersetzt uns die Bytes in folgende Assemblerbefehle:
Position Bytes Assemblerbefehl .data:0x00000000 B8 00 00 00 00 mov eax,0x0 .data:0x00000005 50 push eax .data:0x00000006 EB 01 jmp 0x00000009 .data:0x00000008 68 E8 07 00 00 push 0x7e8 .data:0x0000000d 00 6A 00 add BYTE PTR [edx+0x0],ch .data:0x00000010 E8 1D 00 00 00 call func_00000032 ;ExitProcess
Das geübte Auge erkennt, dass der Sprung jmp 0x00000009 innerhalb des Maschinenbefehls 68 E8 07 00 00 landet, konkret beim zweiten Byte E8, und das etwas nicht stimmen kann. Entsprechend kennzeichnet der Online-Disassembler [3] diesen Sprung auch mit roter Farbe. Wie erwähnt ist das Disassemblieren eine Kunstform, entsprechend werden auch unterschiedliche Techniken hierfür eingesetzt. Professionelle Tools prüfen den Pfad der Ausführung und erkennen solche Manipulationen, im folgenden Screenshot schön mit den Abtrennungskommentaren zu sehen.

Natürlich sind heutige Anti-Disassembly-Methoden deutlich weiterentwickelter als dieses einfache Beispiel. Eine schöne Übersicht wurde letztes Jahr an der Blackhat gezeigt [4]. Im Buch ‘Practical Malware Analysis’ [5] werden einige kreative Beispiele aufgeführt, beispielsweise Bytes, welche gleichzeitig in zwei Maschinenbefehlen eine Rolle spielen.
Das Bestreben, die Disassemblierung zu erschweren, muss nicht bösartig sein: Entwickler, die ihre Programme bzw. ihr geistiges Eigentum schützen möchten, verwenden legitimerweise Obfuscator-Tools, welche unter anderem auch Anti-Disassembly-Methoden einsetzen.
In der nächsten Folge befassen wir uns mit einer Technik, welche sich Anti-Debugging nennt und die sogenannte dynamische Malware-Analyse aushebelt.