487 lines
17 KiB
Plaintext
487 lines
17 KiB
Plaintext
|
|
ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
|
|
EXE Self-Disinfection
|
|
ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
|
|
By Dark Angel
|
|
Phalcon/Skism
|
|
ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
|
|
|
|
In the last issue of 40Hex, Demogorgon presented an article on self-
|
|
disinfecting COM files. COM file disinfection is simplistic and very
|
|
straightforward. In this article, we shall deal with the somewhat more
|
|
complex topic of EXE file self-disinfection.
|
|
|
|
You should already be familiar with the EXE file header and how each of the
|
|
fields work. A brief summary follows (a fuller treatment may be found in
|
|
40Hex-8.007):
|
|
|
|
Offset Description
|
|
00 'MZ' or 'ZM' EXE signature word
|
|
02 Bytes in last page of the image
|
|
04 Number of pages in the file
|
|
06 Number of relocation items
|
|
08 Size of the header in paragraphs
|
|
0A Minimum memory required in paragraphs
|
|
0C Maximum memory requested in paragraphs
|
|
0E Initial SS, offset from header in paragraphs
|
|
10 Initial SP
|
|
12 Negative checksum (ignored)
|
|
14 Initial IP
|
|
16 Initial CS, offset from header in paragraphs
|
|
18 Offset of relocation table from start of file
|
|
1A Overlay number (ignored)
|
|
|
|
There are several methods which allow a virus to infect an EXE file. The
|
|
most common method involves the virus twiddling with the entry point of the
|
|
program to point to the virus. Another involves the virus altering the code
|
|
at the original entry point to jmp to its own code. A further method
|
|
involves the virus simply overwriting the code at the entry point and
|
|
storing the original code somewhere else, possibly at the end of the file.
|
|
A final method involves altering the structure of the EXE file so it is
|
|
instead recognised as a COM file. The ideal self-check routine should be
|
|
able to handle all these cases.
|
|
|
|
Part 1 - Detection
|
|
~~~~~~~~~~~~~~~~~~
|
|
The strategy for detection is simple; one simply needs to store a copy of
|
|
the original header and the first few bytes located at the entry code. When
|
|
the program executes, simply check these bytes to those found in the copy
|
|
of the program located on the disk. If they differ, then there is clearly
|
|
something amiss. This is essentially the same as the process for COM self-
|
|
checking, but an extra layer of complexity is added since the header is not
|
|
loaded into memory at startup. This minor difficulty may be readily
|
|
overcome by simply physically storing the header at some point in the
|
|
program.
|
|
|
|
Since the header is not known before assembling the file, it is necessary
|
|
to patch the header into the file after assembly. This may be done rather
|
|
easily with a simple utility called 40patch. It will insert the header and
|
|
the first 20h (32d) bytes at the entry point of an EXE file at the first
|
|
occurence of the string 'Dark Angel eats goat cheese.' in the program. This
|
|
string is exactly the length of the header, so be sure to allocate an
|
|
additional 20h bytes after the string for the entry point code.
|
|
|
|
A sample self-checking program follows:
|
|
|
|
----EXE Self-Check Program 1 begin----
|
|
.model small
|
|
.radix 16
|
|
.code
|
|
; Self-Checking EXE 1
|
|
; Written by Dark Angel of Phalcon/Skism
|
|
; For 40Hex #13
|
|
|
|
; To assemble: (tested with TASM 2.0)
|
|
; tasm <filename>
|
|
; tlink <filename>
|
|
entry_point: mov ah,51 ; Get current PSP to BX
|
|
int 21
|
|
mov ds,bx
|
|
|
|
mov bx,ds:2c ; Search the environment for
|
|
mov es,bx ; our own filename. Note that
|
|
mov di,1 ; this only works in DOS 3+.
|
|
xor ax,ax
|
|
dec di ; It also won't work if the
|
|
scasw ; environment has been
|
|
jnz $ - 2 ; released.
|
|
|
|
xchg dx,di
|
|
inc dx
|
|
inc dx
|
|
push es ; filename to ds:dx
|
|
pop ds
|
|
mov ax,3d02 ; unless this handler is
|
|
int 21 ; tunneled, a virus may
|
|
xchg ax,bx ; infect it
|
|
mov ax,_DATA
|
|
mov ds,ax ; restore DS and ES
|
|
mov es,ax
|
|
jc error
|
|
|
|
mov cx,1c ; check the header for
|
|
mov si,offset header ; corruption
|
|
call read_buffer
|
|
jc close_error
|
|
|
|
mov ax,4200 ; go to the entry point
|
|
xor cx,cx
|
|
mov dx,word ptr [header+8]
|
|
add dx,word ptr [header+16]
|
|
rept 4
|
|
shl dx,1
|
|
adc cx,0
|
|
endm
|
|
add dx,word ptr [header+14] ; add this to the entry point
|
|
adc cx,0 ; offset from header
|
|
int 21
|
|
jc close_error
|
|
|
|
mov cx,20 ; now check the first 32 bytes
|
|
mov si,offset first20 ; for corruption
|
|
call read_buffer
|
|
jc close_error
|
|
|
|
close_error: pushf
|
|
mov ah,3e ; close the file
|
|
int 21
|
|
popf
|
|
jc error
|
|
|
|
mov dx,offset good ; In an actual program, replace
|
|
; this line with a JMP to the
|
|
jmp short $+5 ; program entry point
|
|
error: mov dx,offset bad
|
|
mov ah,9
|
|
int 21
|
|
|
|
mov ax,4c00
|
|
int 21
|
|
|
|
read_buffer: mov ah,3f
|
|
mov dx,offset readbuffer
|
|
int 21
|
|
jc error_read
|
|
clc
|
|
cmp ax,cx
|
|
jnz error_read
|
|
|
|
xchg dx,di
|
|
rep cmpsb
|
|
clc
|
|
jz $+3
|
|
error_read: stc
|
|
ret
|
|
|
|
.data
|
|
good db 'Self-check passed with flying colours.',0Dh,0A,'$'
|
|
bad db 'Self-check failed. Program may be infected!'
|
|
db 0Dh,0A,'$'
|
|
;0123456789ABCDEF0123456789AB
|
|
header db 'Dark Angel eats goat cheese.'
|
|
first20 db 20 dup (0)
|
|
readbuffer db 20 dup (?)
|
|
|
|
.stack
|
|
db 100 dup (?)
|
|
end entry_point
|
|
----EXE Self-Check Program 1 end----
|
|
|
|
----40patch begin----
|
|
.model tiny
|
|
.code
|
|
.radix 16
|
|
org 100
|
|
; 40patch
|
|
; Written by Dark Angel of Phalcon/Skism
|
|
; For 40Hex #13
|
|
|
|
; To assemble: (tested with TASM 2.0)
|
|
; tasm /m 40patch
|
|
; tlink /t 40patch
|
|
|
|
; Syntax:
|
|
; 40patch filename.exe
|
|
|
|
; 40patch will take the executable <filename.exe> and patch in the
|
|
; header and the first 32d bytes at the entry point in the first
|
|
; occurence of the string 'Dark Angel eats goat cheese.' in the
|
|
; executable.
|
|
patch: mov ah,9
|
|
mov dx,offset welcome
|
|
int 21
|
|
|
|
mov si,82
|
|
back: lodsb
|
|
cmp al,0dh
|
|
jnz back
|
|
dec si
|
|
xchg si,di
|
|
mov byte ptr [di],0
|
|
|
|
mov dx,82
|
|
mov ax,3d02
|
|
int 21
|
|
xchg ax,bx
|
|
jnc open_okay
|
|
|
|
mov si,offset extension
|
|
movsw
|
|
movsw
|
|
movsb
|
|
|
|
mov dx,82
|
|
mov ax,3d02
|
|
int 21
|
|
xchg ax,bx
|
|
jnc open_okay
|
|
|
|
mov dx,offset syntax
|
|
error: mov ah,9
|
|
int 21
|
|
|
|
mov ax,4c01
|
|
int 21
|
|
|
|
open_okay: mov ah,3f
|
|
mov cx,1c
|
|
mov dx,offset header
|
|
int 21
|
|
|
|
mov ah,3f
|
|
mov cx,20
|
|
mov dx,offset scratchbuffer
|
|
int 21
|
|
find_signature: xor ax,ax
|
|
mov di,offset scratchbuffer + 20
|
|
mov cx,(100 - 20) / 2
|
|
rep stosw
|
|
|
|
mov ah,3f
|
|
mov cx,100 - 20
|
|
mov dx,offset scratchbuffer + 20
|
|
int 21
|
|
or ax,ax
|
|
jz signature_not_found
|
|
add ax,offset scratchbuffer - signature_length + 20
|
|
xchg bp,ax
|
|
mov ax,'aD'
|
|
mov di,offset scratchbuffer
|
|
try_again: scasw
|
|
jz signature_check
|
|
dec di
|
|
cmp di,bp
|
|
ja try_next_bytes
|
|
jmp short try_again
|
|
signature_check:mov si,offset signature + 2
|
|
mov cx,signature_length - 2
|
|
rep cmpsb
|
|
jz signature_found
|
|
jmp short try_again
|
|
try_next_bytes: mov si,offset scratchbuffer + 100 - 20
|
|
mov di,offset scratchbuffer
|
|
mov cx,10
|
|
rep movsw
|
|
jmp short find_signature
|
|
|
|
signature_not_found:
|
|
mov dx,offset no_signature
|
|
jmp short error
|
|
|
|
signature_found:sub di,bp
|
|
sub di,1c * 2
|
|
xchg dx,di
|
|
or cx,-1
|
|
mov ax,4201
|
|
int 21
|
|
|
|
mov ah,40
|
|
mov dx,offset header
|
|
mov cx,1c
|
|
int 21
|
|
|
|
mov ax,4201
|
|
xor cx,cx
|
|
cwd
|
|
int 21
|
|
push dx ax
|
|
|
|
mov ax,4200 ; go to the entry point
|
|
xor cx,cx
|
|
mov dx,word ptr [header+8]
|
|
add dx,word ptr [header+16]
|
|
rept 4
|
|
shl dx,1
|
|
adc cx,0
|
|
endm
|
|
add dx,word ptr [header+14]
|
|
adc cx,0
|
|
int 21
|
|
|
|
mov ah,3f
|
|
mov dx,offset first20
|
|
mov cx,20
|
|
int 21
|
|
|
|
pop dx cx
|
|
mov ax,4200
|
|
int 21
|
|
|
|
mov ah,40
|
|
mov dx,offset first20
|
|
mov cx,20
|
|
int 21
|
|
|
|
mov ah,3e
|
|
int 21
|
|
|
|
mov ah,9
|
|
mov dx,offset graceful_exit
|
|
int 21
|
|
|
|
mov ax,4c00
|
|
int 21
|
|
|
|
welcome db '40patch',0Dh,0A,'$'
|
|
graceful_exit db 'Completed!',0Dh,0A,'$'
|
|
syntax db 'Syntax:',0Dh,0A,' 40patch filename.exe',0Dh,0A,'$'
|
|
no_signature db 'Error: Signature not found.',0Dh,0A,'$'
|
|
extension db '.EXE',0
|
|
signature db 'Dark Angel eats goat cheese.'
|
|
signature_length = $ - signature
|
|
header db 1c dup (?)
|
|
first20 db 20 dup (?)
|
|
|
|
scratchbuffer db 100 dup (?)
|
|
|
|
end patch
|
|
----40patch end----
|
|
|
|
To test out the programs above, first assemble them both. Next, run 40patch
|
|
on the EXE file. If the EXE file is -subsequently- altered in any way, then
|
|
it will alert the user of the problem. Note that this will do nothing for a
|
|
program that is infected prior to 40patching, so be sure to run it on a
|
|
clean system.
|
|
|
|
This simple self-checking mechanism won't catch spawning viruses. However,
|
|
it is trivial to add such a check.
|
|
|
|
Part 2 - Disinfection
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
Usual methods (for there are many oddball variants) of infecting an EXE
|
|
file involve appending the virus code to the end of the executable. With
|
|
this knowledge in hand, it is sometimes possible to reconstruct an infected
|
|
EXE file without too much difficulty. A simple modification of the previous
|
|
program will suffice:
|
|
|
|
----EXE Self-Check Program 2 begin----
|
|
.model small
|
|
.radix 16
|
|
.code
|
|
; Self-Checking EXE 2
|
|
; Written by Dark Angel of Phalcon/Skism
|
|
; For 40Hex #13
|
|
|
|
; To assemble: (tested with TASM 2.0)
|
|
; tasm <filename>
|
|
; tlink <filename>
|
|
entry_point: mov ah,51 ; Get current PSP to BX
|
|
int 21
|
|
mov ds,bx
|
|
|
|
mov bx,ds:2c ; Search the environment for
|
|
mov es,bx ; our own filename. Note that
|
|
mov di,1 ; this only works in DOS 3+.
|
|
xor ax,ax
|
|
dec di ; It also won't work if the
|
|
scasw ; environment has been
|
|
jnz $ - 2 ; released.
|
|
|
|
xchg dx,di
|
|
inc dx
|
|
inc dx
|
|
push es ; filename to ds:dx
|
|
pop ds
|
|
mov ax,3d02 ; unless this handler is
|
|
int 21 ; tunneled, a virus may
|
|
xchg ax,bx ; infect it
|
|
mov ax,_DATA
|
|
mov ds,ax ; restore DS and ES
|
|
mov es,ax
|
|
mov errorcount,0
|
|
|
|
mov cx,1c ; check the header for
|
|
mov si,offset header ; corruption
|
|
call read_buffer
|
|
|
|
mov ax,4200 ; go to the entry point
|
|
xor cx,cx
|
|
mov dx,word ptr [header+8]
|
|
add dx,word ptr [header+16]
|
|
rept 4
|
|
shl dx,1
|
|
adc cx,0
|
|
endm
|
|
add dx,word ptr [header+14] ; add this to the entry point
|
|
adc cx,0 ; offset from header
|
|
int 21
|
|
|
|
mov cx,20 ; now check the first 32 bytes
|
|
mov si,offset first20 ; for corruption
|
|
call read_buffer
|
|
|
|
mov ah,3e ; close the file
|
|
int 21
|
|
|
|
mov dx,offset good
|
|
cmp errorcount,0
|
|
jz $+5
|
|
mov dx,offset errors
|
|
|
|
mov ah,9
|
|
int 21
|
|
|
|
mov ax,4c00
|
|
int 21
|
|
|
|
read_buffer: mov ah,3f
|
|
mov dx,offset readbuffer
|
|
int 21
|
|
jc error_read
|
|
clc
|
|
cmp ax,cx
|
|
jnz error_read
|
|
|
|
xchg dx,di
|
|
mov bp,si
|
|
rep cmpsb
|
|
jz read_buffer_ok
|
|
|
|
push ax
|
|
xchg ax,dx
|
|
neg dx
|
|
or cx,-1
|
|
mov ax,4201
|
|
int 21
|
|
|
|
mov ah,40
|
|
xchg bp,dx
|
|
pop cx
|
|
int 21
|
|
|
|
mov dx,offset bad
|
|
inc errorcount
|
|
jmp short $+5
|
|
error_read: mov dx,offset read_error
|
|
mov ah,9
|
|
int 21
|
|
|
|
read_buffer_ok: ret
|
|
|
|
.data
|
|
good db 'Self-check passed.',0Dh,0A,'$'
|
|
errors db 'Errors were detected.',0Dh,0A,'$'
|
|
bad db 'Self-check failed. Fixing (may not work).'
|
|
db 0Dh,0A,'$'
|
|
read_error db 'Error reading file.',0Dh,0A,'$'
|
|
;0123456789ABCDEF0123456789AB
|
|
header db 'Dark Angel eats goat cheese.'
|
|
first20 db 20 dup (0)
|
|
readbuffer db 20 dup (?)
|
|
errorcount db ?
|
|
|
|
.stack
|
|
db 100 dup (?)
|
|
end entry_point
|
|
----EXE Self-Check Program 2 end----
|
|
|
|
Summary
|
|
~~~~~~~
|
|
In general, it is poor practise to rely upon self-disinfection. The ancient
|
|
(!) adage 'restore from backups' is best followed upon the discovery of an
|
|
infection. However, it is helpful for programs to have a degree of self-
|
|
awareness in order to alert the user of a virus's presence before it has a
|
|
chance to spread too far. Disinfection will allow the user to continue
|
|
using some programs (under certain circumstances) without fear of further
|
|
spreading the virus.
|