From : Dan Bridges 3:640/820.2 Subj : NEXT_MCB Antivirus prog ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Another snippet from Learning Assembler Using DEBUG Part 10. (The articles were rather large so I'm only presenting some of the programs and a few connecting comments.) In Part 9 I discussed the NO FRILLS V3 virus and Memory Control Blocks, showing the effect of the NF3 virus on the size field of a program's MCB and the location of the "next" MCB after a program had requested all available low memory. This got me to thinking about using this method to detect resident viruses (the vast majority of viruses go memory-resident). It will detect viruses both known and unknown/yet-to-be-written and it takes less than a second to run. First off, the layout of the MCB's 16 bytes: Offset Size Value Comment 00h Byte TYPE "M"; "Z" if last MCB. 01h Word OWNER PSP of the owner. 03h Word SIZE In paras. 05h 3 Bytes UNUSED 08h 8 Bytes OWNER NAME Filename of owner, for program MCB only, if DOS 4 or better. By adding a program's MCB size to its PSP you get the upper limit of its low memory usage. If you then increment this you get the starting location of the next MCB. There are three common virus memory residence locations: 1. At top of low mem. Reduce TOM (top of mem) value in BDA (BIOS Data Area) at 40:13h from typical 280h (640 decimal; specifies total low mem in K) to say 27Dh (638 decimal) if 2K is required by virus. All BSVs (Boot Sector Viruses) do this. Examples: Michelangelo, Junkie, Monkey. 2. Top-of-mem exe infector. Don't alter TOM. Alter a prog's MCB size so that, if it requests all free mem (most do), it won't overwrite virus sitting just below TOM. Example: No Frills. 3. Bottom-of mem exe infector. Don't alter TOM. Virsus resides just after the resident portion of command processor, as would other typical TSRs. Example: Jerusalem. Assume now that DOS and other basic stuff was consuming 16K from 0000-03FF (paras) and therfore 9C00h paras (624K) is free out of the typical A000h (640K) total. Here are the kind of figures you'd expect for free mem with a 2K (80h paras) resident virus (ignores the program's copy of the environment and the program's & its environment's PSP): 1. BSV. TOM/Next MCB=9F80h Free Mem=9B80h (all in paras) 2. Top of mem exe infector. Next MCB=9F80h Free Mem=9B80h 3. Bottom of mem exe inf. Next MCB=A000h Free Mem=9B80h So the first two types of virus could be detected by their reduction on the expected NEXT MCB value of A000h while all three could be detected by the reduction in the "free" mem value available to the program. This method will not work for non-resident viruses ("direct infectors"). It will also not work for viruses that load themselves in UMBs. Perhaps someone could present a program to total allocated UMBs. To investigate the Next MCB concept here is a simple program (NEXTMCB1.COM) that returns an errorlevel of 1 if the Next MCB's segment is less than expected: Next_MCB_Should_Be EQU 0A000h ; In paragraphs. 640K. MainProg SEGMENT PUBLIC WORD 'CODE' ASSUME CS:MainProg, DS:MainProg ORG 0100h ; Starting offset for a COM. Starting_Point: Mov bx, ds ; In a COM file CS, DS, ES are all set to ; the PSP seg. Here we'll use DS. Dec bx ; Decrement it to get Prog MCB seg. Mov ds, bx ; ES now holds Prog MCB seg. Add bx, word ptr ds:[3] ; Add the size of the Prog's ; MCB to the Prog MCB Seg to get end of the last seg. Inc bx ; Increment this to get the next "free" seg. Mov ax, 4C00h ; Int 21h, Func 4Ch being set up. ; Terminate Program with Errorlevel. ; Default errorlevel in AL is set to 00h. Cmp bx, Next_MCB_Should_Be ; Compare free seg with Jae Exit ; expected value and exit if not less. Inc al ; Was less than expected. Errorlevel to Exit: ; be transmitted is 01h. Int 21h ; 4Ch is still in AH. Errorlevel in AL. MainProg ENDS END Starting_Point Note: Some systems will have a TOM of less than A000h. To test this program you need to have a method of reducing the size of a expected free low memory value. Here is a DEBUG scriptfile to do that: A100 MOV AX, DS DEC AX MOV DS, AX DS: SUB WO [3], 100 MOV AH, 4C INT 21 N DROP100.COM RCX 10 W100 Q To create DROP100.COM: DEBUG < DROP100.SCR Remember to leave a blank line in the middle to terminate assembler mode and to press Enter after Q(uit) when typing it in. Each time you run this you'll reduce memory a further 100h paras (4K). It can be run more than once. To set up the following program type in excessive intial values (assuming you're running a low mem greater than 640K) such as NEXT_MCB B000 B000. You see something like (I'm here running and OS/2 DOS session I've specified as 639K - they can get much bigger) : [0] (601K) f:\> NEXT_MCB B000 B000 The Next MCB should have started at B000 but now starts at 9FC0 Free paras have been reduced by 1040 Now rerun with the correct first parameter to determine the second parameters value: [0] (601K) f:\> NEXT_MCB 9FC0 B000 Free low memory (paras) should be B000 but is now 9651 Free paras have been reduced by 19AF Finally check with NEXT_MCB 9FC0 9651 ; NEXT_MCB.COM by Dan Bridges. Checks the loc of the next MCB ; and optionally checks the amount of free mem. If either is ; less than expected it shows the "should be" value, the actual ; value, the memory reduction and produces a pulsed beep until ; you press any key. It defaults to a next MCB seg value of ; A000h. It can have up to two parms. ; Examples: ; NEXT_MCB Next MCB = A000h paras ; NEXT_MCB 9F80 Next MCB = 9F80h paras ; NEXT_MCB 9F80 9C74 Next MCB = 9F80h paras Free Mem = 9C74h paras Beep_Freq EQU 1000 ; 1000 Hz beep. Beep_Duration EQU 2 ; Beep Duration. Pause_Duration EQU 1 ; Pause Duration. Sq_Wave_Period EQU 1193180/Beep_Freq ; TASM 2.0 will evaluate this. MASM 5.1 won't. With ; MASM 5.1 use the calculated value e.g. ; Sq_Wave_Period EQU 1193 ; Note: I've recently found that TASM 3.1 doesn't evaluate it! Program SEGMENT WORD PUBLIC 'CODE' ASSUME CS:Program,DS:Program,ES:Program ORG 0100h ; COM file. Start_Point: ; DS & ES are set to PSP. Mov [OrigStackPtr],sp ; Store current stack ; pointer position so it can be restored ; in the event of jumping to the Exit ; section from within a procedure. Cmp byte ptr es:[80h],00h ; Location in PSP of Jnz Check_Cmd_Line ; of num of parmater chars. Mov cx,0A000h ; No parms. Set 640K. Jmp short Check_Next_MCB Check_Cmd_Line: ; Parms were supplied. Mov bl,byte ptr es:[80h] ; Number of characters. Cmp bl,05h ; e.g. " 9F80" Jnz Check_Parm2 Mov [Num_OF_Parms],01h ; 5 characters supplied. Jmp short Calculate_First_Parm Check_Parm2: ; Number of chars wasn't 5. Cmp bl,0Ah ; 10 = 2 parms e.g. " 9F80 9C74". Jnz Exit_Jump_Island ; Jump if 10 chars were not ; supplied. A conditional jump can only be ; to a label up to -127/+128 bytes away. Mov [Num_OF_Parms],02h ; There were 2 parms. Calculate_First_Parm: Mov si,85h ; End of 1st parm in PSP. Call AscWord2Bin ; Work out its value. ; Afterwards, CX contains value e.g. 9F80h. Check_Next_MCB: Mov bx,es ; BX = PSP seg. Dec bx ; BX = Prog MCB seg. Mov es,bx ; ES = Prog MCB seg. Add bx,word ptr es:[3] ; Add size of the Prog's MCB ; to the Prog Seg for Seg end. Inc bx ; Inc this to get the next seg. Cmp bx,cx ; Temp subtract: Should_Be - Actual. Jae No_Problem ; Jump if big enough. Call NL ; Next seg was less than expected. Call NL Mov dx,offset Next_MCB_Msg1 Call Show_Msg ; Show 1st msg line. Mov ax,cx Call Show_Word ; Show Should_Be from AX. Mov dx,offset Next_MCB_Msg2 Call Show_Msg ; Show 2nd msg line. Mov ax,bx Call Show_Word ; Show actual value. Call NL Mov dx,offset Problem_Msg3 Call Show_Msg ; Show 3rd msg line. Sub cx,bx ; Work out reduction. Mov ax,cx ; Put in AX for displaying. Call Show_Word ; Show reduction. Mov dx,offset Message Call Show_Msg ; Show "Press any key..." Jmp Check_Keyboard_Status No_Problem: ; No problem with next MCB. Cmp [Num_Of_Parms],02h ; 2nd parm given? Jne Exit ; If no 2nd parm, finish up. Jmp short Calculate_Second_Parm Exit_Jump_Island: Jmp short Exit ; Stepping stone. Calculate_Second_Parm: Mov si,8Ah ; End of 2nd parm. Call AscWord2Bin ; Value ends up in CX. Check_Free_Mem: Cmp word ptr es:[3],cx ; Temp Subtract: Prog MCB size - Expected size. Jae Exit ; Exit if enough mem. Call NL ; Prepare to show the bad news. Call NL Mov dx,offset Free_Mem_Msg1 Call Show_Msg Mov ax,cx ; Should_Be value. Call Show_Word Mov dx,offset Free_Mem_Msg2 Call Show_Msg Mov ax,word ptr es:[3] ; Actual value. Call Show_Word Call NL Mov dx,offset Problem_Msg3 Call Show_Msg Sub cx,word ptr es:[3] Mov ax,cx Call Show_Word ; Reduction in free mem. Mov dx,offset Message Call Show_Msg Check_Keyboard_Status: ; Int 21h,Func 0Bh. Mov ah,0Bh ; Check STDIN status. Int 21h ; AL = FFh if char ready. Cmp al,0FFh ; Key pressed yet? Jz Got_A_Keystroke ; Yes. Call Produce_A_Beep ; No. Keep beeping. Mov cx,Pause_Duration Call Wait_Routine Jmp short Check_Keyboard_Status ; Time to check the keyboard again. Got_A_Keystroke: ; Key pressed. Use Int 21h Func 07h Mov ah,07h ; (Input Char with No Echo) to Int 21h ; "swallow" keystroke. Exit: Mov sp,[OrigStackPtr] ; Ensure that the stack ; is in same state as when started. Mov ah,4Ch Int 21h ; Int 21h, Func 4Ch. End Prog. ;******************************************************* AscWord2Bin PROC NEAR ; "9F80" -> 9F80h. Std ; Reverse Direction flag. Work Xor cx,cx ; from end of string forward. Call Hex2Bin ; After call, AX = bin value. Mov cx,ax ; CX holds running total. Call Hex2Bin ; Process the 2nd number. Mov dx,10h ; Multiply it by 16. Mul dx Add cx,ax ; Add it to total. Call Hex2Bin ; Process the 3rd number. Mov dx,100h ; Multiply it by 256. Mul dx Add cx,ax ; Add it to total. Call Hex2Bin ; Process the last number. Mov dx,1000h ; Multipy it by 4,096. Mul dx Add cx,ax ; Add it to total. Cld ; Set Dir flag to forward. Ret AscWord2Bin ENDP Hex2Bin PROC NEAR ; Hex num -> bin value. Xor ax,ax ; Clear AX. Lodsb ; Load AL from [SI]. Dec SI. Cmp al,"0" ; Was ASCII char a number? Jb Exit Cmp al,"9" Ja Try_A_to_F ; If not "0" to "9" might be "A" to "F". Sub al,30h ; "0"-"9" (30h-39h) -> 00h-09h in AL. Jmp short Hex2Bin_1 Try_A_to_F: And al,0DFh ; Bit mask to covert letter to ; uppercase. Only difference between "a" (01100001) ; and "A" (01000001) is that Bit 5 (counting ; from zero) is 0 in uppercase lettrrs. ANDing with ; DFh (11011111) ensures that Bit 5 is 0. Cmp al,"A" Jb Exit Cmp al,"F" Ja Exit Sub al,37h ; "A"-"F" (41h-46h) -> 0Ah-0Fh. Hex2Bin_1: Ret Hex2Bin ENDP Show_Word PROC NEAR ; Show 2 bytes in word. Push ax Mov al,ah ; Show the higher byte 1st. Call Show_Byte ; Show what's in AL. Pop ax Call Show_Byte ; Now show the lower byte. Call NL Ret Show_Word ENDP Show_Byte PROC NEAR ; Byte in AL will be displayed. Push ax Shr al,01h ; Consider only high nibble Shr al,01h ; of AX by dividing it by Shr al,01h ; 16 i.e. divide by 2^4. Shr al,01h Add al,30h ; Make char "0" or greater. Cmp al,3Ah ; Is it "A" or greater? Jb Set_High_Char ; It's "0" to "9". Add al,07h ; It's "A" to "F" so add 7h ; since A" is 41h. 3Ah + 07h = 41h. Set_High_Char: Mov byte ptr [Hold_2Chars],al ; Put char in the character display buffer. Pop ax ; Restore AX for low nibble. And al,0Fh ; Consider low nibble in AL. Add al,30h ; Make the char "0" or greater. Cmp al,3Ah ; Is it "A" or greater? Jb Set_Low_Char ; It's "0" to "9". Add al,07h ; It's "A" to "F". Set_Low_Char: ; Put in display buffer. Mov byte ptr [Hold_2Chars+1],al Show_Them: Mov ah,09h Mov dx,Offset Hold_2Chars ; Show 2 chars. Int 21h Ret Show_Byte ENDP NL PROC NEAR ; Sends CR/LF to screen. Mov dx,offset Newline Mov ah,09h Int 21h Ret NL ENDP Show_Msg PROC NEAR ; Display "$"-terminated string Mov ah,09h ; whose location is already in Int 21h ; DX. Ret Show_Msg ENDP Produce_A_Beep PROC NEAR Mov al, 0B6h ; Prepare to set Timer2 for Periodic ; Square Wave mode with Binary Counting Out 43h, al ; and LSB-then-MSB input. Set mode. Mov ax, Sq_Wave_Period ; Period value is a word. Call IO_Delay Out 42h, al ; Set Timer 2 Period value, Low Byte (first). Mov al, ah ; Transfer count high byte to AL. Call IO_Delay Out 42h, al ; Set Timer 2 Period value, High Byte (next). Call IO_Delay In al, 61h ; Get current Speaker & Misc. setting. Mov ah, al ; Save this in AH as well. Or al, 03h ; Prepare to set Speaker ctrl bits 1&0. Call IO_Delay Out 61h, al ; Make sure Speaker ctrl bits 1&0 are ON Mov cx, Beep_Duration Call Wait_Routine Mov al, ah ; Transfer stored original ctrl value. Out 61h, al ; Reset original control byte value. Ret Produce_A_Beep ENDP Wait_Routine PROC NEAR ; Produces a constant delay. Mov ah, 86h ; Int 15h, Func 86h. Mov dx, 0000h ; Wait for CX:DX æs. Int 15h ; Each increment of CX is worth 66ms. Ret Wait_Routine ENDP IO_Delay PROC NEAR ; Produces a delay of at least 1 æs. Mov cx, 64h ; Suitable for 90Mhz Pentium or less. A_Waste_Of_Time: ; 486 and less could use 0Eh instead. Loop A_Waste_Of_Time Ret IO_Delay ENDP Newline DB 0Dh, 0Ah, "$" Message DB 0Dh, 0Ah, "Press any key to continue...$" Free_Mem_Msg1 DB " Free low memory (paras) should be $" Free_Mem_Msg2 DB " but is now $" Next_MCB_Msg1 DB "The Next MCB should have started at $" Next_MCB_Msg2 DB " but now starts at $" Problem_Msg3 DB " Free paras have been reduced by $" Hold_2Chars DB 0, 0, "$" Num_Of_Parms DB 0 OrigStackPtr DW ? Program ENDS END Start_Point