Einführung in die Anti-Malware-Analyse – Teil 1: Anti-Disassembly


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.

Pfadorientierte Disassemblierung
Ausgabe der pfadorientierten Disassemblierung. Das eingefügte Byte 68h wird erkannt.

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.

[1] http://ref.x86asm.net/
[2] http://www.artofasm.com/
[3] http://www.onlinedisassembler.com/
[4] http://research.dissect.pe/docs/blackhat2012-paper.pdf
[5] Michael Sikorski and Andrew Honig: Practical Malware Analysis, Chapter 15, Impossible Disassembly
%d bloggers like this: