Following is an article and companion program source code that
be used by a virus to cause damage. This alternate entry
point is explained and the program source code removes it
from service.
be a bug, except it seems to be deliberate) that allows it
to totally destroy a disk's directory structure, almost beyond
repair. The program source code also shows how to safeguard
against it.
misuse, but if I can't put something like this in BIX, then
we're all in trouble, I think. If PC Tech Journal were still
alive, I might send it to them, but there isn't really anywhere
else it seems appropriate.
One warning... PLEASE do not try the examples given on a hard disk drive.
Use them only on a floppy. I managed to wipe out a goodly portion
of my hard disk playing around while writing this article, including
my only copy of the article! You've been warned.
And, of course, any comments, elaborations, questions, etc. would be
welcome in ibm.dos/secrets.2 or bixmail me.
Good luck - John Switzer (jswitzer)
Closing DOS's Backdoor
by John Switzer. (c) 1989, all rights reserved.
With each new story of a virus or worm infection, the issue of PC
by having two "back doors" that allow access to INT 21h func-
tions. These back doors are poorly documented, but still present
a huge gap in a PC's security. To have a secure system, it is
essential to lock and close these back doors.
These backdoors allow access to the DOS function handler (INT
The first of these pointers is in low memory, at the address
mally, an entry in the interrupt vector table contains a dword
n the form of a JMP FAR instruction that points to the alterna-
tive DOS function dispatcher (in my version of DOS 3.30: JMP FAR
This alternative DOS handler, however, has different entry re-
quirements than a normal INT21 call. Its use requires some
allows. In example 1 is the alternative entry point as it exists
n MS-DOS 3.30, with some changes for clarity:
ALT_DOS_ENTRY:
POP AX ; get rid of flags
POP AX ; save caller's segment
POP CS:TEMP ; save caller's offset
PUSHF ; save flags
CLI ; kill interrupts
PUSH AX ; save caller's segment
PUSH CS:TEMP ; save caller's offset
CMP CL,24h ; is CL < max #?
JA REFUSE_RQST ; no, so invalid
MOV AH,CL ; yes, AH=function #
JMP CONT_INT21 ; and continue INT21
(end example 1)
The first thing to notice is that the handler expects the return
address to be on the stack in an unusual order. Normally when an
nterrupt occurs, the CPU pushes the flags onto the stack first,
followed by the segment and offset of the caller's return ad-
last, after the offset and segment of the return address. Since
this routine eventually transfers control to the normal INT21
an acceptable form for the eventual IRET.
The second thing of importance is that this handler allows only
functions 0 through 24h to be executed. Also, since the AX
s passed through CL and not AH. Therefore, function 0Ch (CLEAR
KEYBOARD BUFFER AND GET STDIN) is also unavailable as it uses AL
for a subfunction value. These limitations may be familiar to
former CP/M programmers as they are result of the original MS-
DOS designers' desire for CP/M compatibility.
To use this call, therefore, the caller must manually setup the
function number in CL, a far jump to 0:00C0 executes the call.
After completion, the INT 21 dispatcher will then IRET to the
Example 2 demonstrates this technique:
MOV AX,offset RETURN ; get return address' offset
PUSH AX ; push flags and return address
PUSH CS ; onto stack in reverse order
PUSHF ;
MOV CL,9 ; display DOS string
MOV DX,offset MSG ; this is the message
PUSH CS
POP DS ; verify that DS = local code
JMP dword ptr ALT_DOS_PTR ; and execute the function
RETURN:
MOV AH,4Ch ; terminate a process
INT 21h ; via DOS
ALT_DOS_PTR DW 00C0h,0000 ; entry point for alternative
; DOS handler (0:00C0h)
MSG DB 0Dh,0Ah,"Example of backdoor MS-DOS "
DB "function call.",0Dh,0Ah,7,"$"
(end example 2)
Note that CP/M programmers, however, used a near jump to execute
their DOS function calls. The second back door into MS-DOS
exists precisely to emulate this procedure. At offset 5 in every
allowed. However, looking at offset usually shows an instruction
CALL FAR F5C2:A496
This seems to reference a location in what seems to be either the
BIOS or an impossibly high RAM memory area, and the code at this
address is usually garbage. So though this pointer in the PSP
mers have ignored it.
The problem is that the address shown in the PSP is not accurate
and needs to be rounded up to the nearest paragraph before being
used. Using the above example, this results in:
CALL FAR F5C2:A4A0
Looking at the code at this address reveals the same instruction
A JMP FAR instruction can then be used to execute a basic DOS
function. Ironically, the simpler JMP 0005 approach that was
used in CP/M cannot be used here as that would then execute the
CALL FAR instruction. This would cause another (and invalid)
Now that the alternative DOS handler is understood, its use must
be prevented. Most security programs do an admirable job of
closing the door to normal DOS calls using INT 21h, but many
gnore these alternative entry points into DOS. A secure system
must close these back doors. For the back door at 0:00C0, this
s trivial. Simply replace the JMP FAR instruction at 0:00C0
function, as appropriate.
The second back door, however, seems to be more difficult. Since
any number of PSPs can exist in memory at one time, patching each
one with a new vector could be very difficult. Fortunately, this
s not necessary. Doing some calculations on the modified ad-
found in the real-mode of the Intel IBM processors:
offset A4A0 translates into segment 0A4A
segment 1000C wraps around to segment 000C
Thus, changing the PSP pointers is unnecessary, since both back
ng the vector at 0:00C0 will adequately protect against both.
Both of these back doors have existed in PC-DOS since version
ed for over eight years without causing any apparent problems,
and that the alternative DOS handler is limited in its scope, how
nput/output and FCB functions are allowed, and although FCBs can
current directory. Although a trojan horse program could use
these back door approaches to do some damage, it would not seem
to pose a major problem.
This would be true, except that one FCB call, function
files on a hard disk. The special case requires that the FCB use
a filename of "???????????" and an attribute of 1Fh. Seeing this
current directory, including files marked with the read-only and
not the usual 0E5h. This prevents most "undelete" utilities from
being able to undo this call's severe damage.
Consider the potential damage of this call. If executed at the
ncluding any deeper subdirectories. Note that the files in the
about them has been erased. CHKDSK will therefore report these
orphaned files as being unallocated clusters.
This behavior of MS-DOS is truly bizarre. Normally, only MS-
DOS's internal routines can update or delete the files marked
to delete these files is an unbelievable quirk of MS-DOS.
Example 3 shows the use of this special case, using the first DOS
back door:
MOV AX,offset RETURN ; get return address' offset
PUSH AX ; push flags and return address
PUSH CS ; onto stack in reverse order
PUSHF ;
MOV CL,13h ; DELETE FCB function
MOV DX,offset FCB ; this is the special FCB
PUSH CS
POP DS ; verify that DS = local code
JMP dword ptr ALT_DOS_PTR ; and execute the function
RETURN:
MOV AH,4Ch ; terminate the process
INT 21h ; via DOS
ALT_DOS_PTR DW 00C0h,0000 ; entry point for alternative
; DOS handler (0:00C0h)
FCB DB 0FFh ; extended FCB
DB 5 dup(0) ; reserved bytes
DB 1Fh ; all attribute bits set
DB 0 ; default drive ID
DB "???????????" ; match all files
DB 19h dup(0) ; rest of FCB
(end example 3)
WARNING! If you experiment with this call, please do so only on a
floppy and not a hard disk. Calling this function while at the
very tedious work with a disk editor to restore them.
This dangerous call, therefore, provides the answer to the ques-
tion asked above: MS-DOS's back doors present a severe threat to
an unsecure system. Even though an anti-viral program may filter
ng all files on the hard disk would be trivial.
Example 4 shows one approach with a device driver called BACK-
DOOR.SYS. Install the device driver in the first line of your
CONFIG.SYS file as "DEVICE=BACKDOOR.SYS" and you will insure that
t is installed before any other programs can run. BACKDOOR.SYS
native DOS function dispatcher. This effectively closes both of
MS-DOS's back doors. It also filters INT 21h to specifically
look for this function 13h call, and rejects the function request
f it occurs.
(see BACKDOOR.ASM for example 4)
No IBM PC or compatible running in real mode can be completely
However, it makes no sense to allow known dangers to continue to
exist. Closing MS-DOS's back doors removes one of the more
obscure dangers to your computer and its data.
end of article. John Switzer 10/15/89
BACKDOOR.ASM source code follows:
TITLE - BACKDOOR.SYS - closes DOS's back doors
PAGE 60,132
.RADIX 16
; BACKDOOR.SYS closes two "back doors" into the MS-DOS INT 21h function
; dispatcher that could be used by a virus or trojan horse to cause damage.
; It also filters INT 21h directly to reject a special case of function 13h
; which could destroy all data on a disk.
; Written October, 1989 by John Switzer (jswitzer).
; Assembled using MASM 5.1
ASSUME CS:CSEG, DS:CSEG
CSEG SEGMENT PARA PUBLIC 'CODE'
ORG 0000h ; device driver starts at 0
DW 0FFFFh,0FFFFh ; far pointer to next device
DW 8000h ; character device driver
DW offset DEV_STRAT_RTN ; pointer to the strategy routine
DW offset DEV_INT_RTN ; pointer to the interrupt routine
DB "B"+80h,"ACKDOOR" ; device name with high bit set will
; avoid any filename conflicts
DB "BACKDOOR is installed at $"
DEV_HDR_BX DW 0000 ; pointer for ES:BX for device
DEV_HDR_ES DW 0000 ; request header
ORIG_INT21_OFF DW 0000 ;
ORIG_INT21_SEG DW 0000 ;
TEMP DW 0000 ; used for temporary storage
REFUSE_RQST PROC FAR ;
POP AX ; get rid of flags on stack
POP AX ; get the return segment
POP CS:TEMP ; and save offset
PUSH AX ; save the return address in proper
PUSH CS:TEMP ; order
STC ; return STC for error
MOV AX,0FFFFh ; return AX=-1
RET ; and do FAR RET back to caller
REFUSE_RQST ENDP
NEW_INT21 PROC NEAR
PUSH AX ; save original registers first thing
PUSH BX
CMP AH,13h ; is this the DELETE FCB function?
JNZ CONT_ORIG_INT21 ; no, so continue on
MOV BX,DX ; point BX to the FCB
CMP byte ptr DS:[BX],0FFh ; got an extended FCB?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP byte ptr DS:[BX+6],1Fh; yes, so got the special attribute?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP word ptr DS:[BX+8],"??"; yes, so filename starts with "??" ?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP word ptr DS:[BX+0Ah],"??"; yes, so filename = "??" ?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP word ptr DS:[BX+0Ch],"??"; yes, so filename = "??" ?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP word ptr DS:[BX+0Eh],"??"; yes, so filename = "??" ?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP word ptr DS:[BX+10h],"??"; yes, so filename = "??" ?
JNZ CONT_ORIG_INT21 ; no, so continue on
CMP byte ptr DS:[BX+12h],"?"; yes, so filename ends with "??" ?
JNZ CONT_ORIG_INT21 ; no, so continue on
POP BX ; yes, so reject it altogether
POP AX ;
MOV AL,0FFh ; return match not found
STC ; STC just for the heck of it
RETF 0002 ; and IRET with new flags
CONT_ORIG_INT21:
POP BX ; restore original registers
POP AX ;
JMP dword ptr CS:ORIG_INT21_OFF; continue with original handler
NEW_INT21 ENDP
DEV_STRAT_RTN PROC FAR ;
MOV CS:DEV_HDR_BX,BX ; save the ES:BX pointer to the
MOV CS:DEV_HDR_ES,ES ; device request header
RET ;
DEV_STRAT_RTN ENDP
DEV_INT_RTN PROC FAR ;
PUSH AX ; save all registers
PUSH BX ;
PUSH CX ;
PUSH DX ;
PUSH DS ;
PUSH ES ;
PUSH DI ;
PUSH SI ;
PUSH BP ;
PUSH CS ;
POP DS ; point DS to local code
LES DI,dword ptr DEV_HDR_BX; ES:DI=device request header
MOV BL,ES:[DI+02] ; get the command code
XOR BH,BH ; clear out high byte
CMP BX,00h ; doing an INSTALL?
JNZ DEV_IGNORE ; no, so just ignore the call then
CALL INSTALL_BACKDOOR ; yes, so install code in memory
DEV_IGNORE: ;
MOV AX,0100h ; return STATUS of DONE
LDS BX,dword ptr CS:DEV_HDR_BX; DS:BX=device request header
MOV [BX+03],AX ; return STATUS in the header
POP BP ; restore original registers
POP SI ;
POP DI ;
POP ES ;
POP DS ;
POP DX ;
POP CX ;
POP BX ;
POP AX ;
RET ; and RETF to DOS
DEV_INT_RTN ENDP
CALL CLOSE_BACK_DOOR ; install new handler to close back
; door
CALL HOOK_INT21 ; and hook INT21 filter
MOV AH,09h ; DOS display string
MOV DX,offset INSTALL_MSG ; show installation message
INT 21h ; via DOS
MOV AX,CS ; display current code segment
CALL OUTPUT_AX_AS_HEX ; output AX as two HEX digits
MOV AL,3Ah ; now output a colon
CALL DISPLAY_TTY ; to the screen
MOV AX,offset REFUSE_RQST ; show new handler's offset
CALL OUTPUT_AX_AS_HEX ; output AX as two HEX digits
CALL DISPLAY_NEWLINE ; output a newline to finish display
LES DI,dword ptr DEV_HDR_BX; ES:DI=device request header
MOV Word Ptr ES:[DI+0Eh],offset INSTALL_BACKDOOR; this is the
MOV ES:[DI+10h],CS ; end of resident code
RET ;
CLOSE_BACK_DOOR PROC NEAR ;
PUSH ES ; save original registers
PUSH AX ;
PUSH BX ;
XOR AX,AX ; point ES to the interrupt vector
MOV ES,AX ; table
MOV BX,00C1h ; install new handler at INT30 + 1
MOV AX,offset REFUSE_RQST ; get new offset for the handler
MOV ES:[BX],AX ; save it in interrupt vector table
MOV AX,CS ; get the segment for the handler
MOV ES:[BX+02],AX ; and save it, too
POP BX ; restore original registers
POP AX ;
POP ES ;
RET ; and RET to caller
CLOSE_BACK_DOOR ENDP
HOOK_INT21 PROC NEAR
PUSH AX
PUSH BX
PUSH ES
MOV AX,3521h ; get current INT21 vector
INT 21h ; via DOS
MOV CS:ORIG_INT21_OFF,BX ; save the offset
MOV BX,ES ;
MOV CS:ORIG_INT21_SEG,BX ; and the segment
PUSH CS
POP DS ; make sure DS=local code
MOV DX,offset NEW_INT21 ; point to new handler
MOV AX,2521h ; install new handler
INT 21h ; via DOS
POP ES ; and restore original registers
POP BX
POP AX
RET ; and RET to caller
HOOK_INT21 ENDP
OUTPUT_AX_AS_HEX PROC NEAR ;
PUSH AX ; save original registers
PUSH BX ;
PUSH CX ;
PUSH AX ; save number for output
MOV AL,AH ; output high byte first
CALL OUTPUT_AL_AS_HEX ; output AL as two HEX digits
POP AX ; output low byte next
CALL OUTPUT_AL_AS_HEX ; output AL as two HEX digits
POP CX ; restore original registers
POP BX ;
POP AX ;
RET ; and RET to caller
OUTPUT_AX_AS_HEX ENDP
OUTPUT_AL_AS_HEX PROC NEAR ;
PUSH AX ; save original registers
PUSH BX ;
PUSH CX ;
PUSH AX ; save the number for output (in AL)
MOV CL,04h ; first output high nibble
SHR AL,CL ; get digit into low nibble
ADD AL,30h ; convert to ASCII
CMP AL,39h ; got a decimal digit?
JBE OUTPUT_FIRST_DIGIT ; yes, so continue
ADD AL,07h ; no, so convert to HEX ASCII
OUTPUT_FIRST_DIGIT: ;
CALL DISPLAY_TTY ; output it via BIOS
POP AX ; get number back
AND AL,0Fh ; keep only low digit now
ADD AL,30h ; convert to ASCII
CMP AL,39h ; got a decimal digit?
JBE OUTPUT_SECOND_DIGIT ; yes, so continue
ADD AL,07h ; no, so convert to HEX ASCII
OUTPUT_SECOND_DIGIT:
CALL DISPLAY_TTY ; output it via BIOS
POP CX ; restore original registers
POP BX ;
POP AX ;
RET ; and RET to caller
OUTPUT_AL_AS_HEX ENDP
DISPLAY_NEWLINE PROC NEAR ;
PUSH AX ; save original AX
MOV AL,0Dh ; first do CR
CALL DISPLAY_TTY ; output it via the BIOS
MOV AL,0Ah ; do LF next
CALL DISPLAY_TTY ; output it via the BIOS
POP AX ; restore original AX
RET ; and RET to caller
DISPLAY_NEWLINE ENDP
DISPLAY_TTY PROC NEAR ;
PUSH AX ;
PUSH BX ;
MOV AH,0Eh ; display TTY
MOV BX,0007h ; on page 0, normal attribute
INT 10h ; via BIOS
POP BX ;
POP AX ;
RET ;
DISPLAY_TTY ENDP
CSEG ENDS
END