Statistics

Members: 1925
News: 293
Web Links: 1
Visitors: 3821652

Who's Online

Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home
Extending DOS Executables
User Rating: / 1
PoorBest 
Written 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: { This e-mail address is being protected from spam bots, you need JavaScript enabled to view it }
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------------------------------------