💾 Archived View for gemini.spam.works › mirrors › textfiles › hacking › MICROSOFT › backdoor.upl captured on 2022-06-12 at 08:48:40.

View Raw

More Information

-=-=-=-=-=-=-

Following is an article and companion program source code that 
describes two quirks of DOS:

1.  The DOS INT21 function has an alternate entry point which could
        be used by a virus to cause damage.  This alternate entry 
        point is explained and the program source code removes it
        from service.

2.  Function 13h of INT21 (delete FCB) has a *SEVERE* quirk (it would
        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.

I'm a little bit unsure about posting this because of the potential for
        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  
security becomes more important.  Although totally protecting any 
IBM  PC from infection is impossible, MS-DOS complicates  matters 
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 
21h)  through two far pointers easily accessible by any  program.  
The  first  of these pointers is in low memory,  at  the  address 
reserved  for interrupts 30h and 31h (0:00C0 thru 0:00C7).   Nor-
mally,  an entry in the interrupt vector table contains  a  dword 
pointer to the interrupt's handler; however, INT30 and INT31  are 
in 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 
0274:1446).  This allows direct access to DOS, without having  to 
go through INT21.

This  alternative DOS handler, however, has different  entry  re-
quirements  than  a  normal INT21 call.  Its  use  requires  some 
special  handling and an understanding of the functions  that  it 
allows.  In example 1 is the alternative entry point as it exists 
in 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  
interrupt occurs, the CPU pushes the flags onto the stack  first, 
followed  by  the segment and offset of the caller's  return  ad-
dress.  However, this entry point expects the flags to be  pushed 
last,  after the offset and segment of the return address.  Since 
this  routine  eventually transfers control to the  normal  INT21 
handler,  the handler's first job is to translate the stack  into 
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 
register is destroyed immediately upon entry, the function number 
is 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 
stack  with the flags and a proper far return address.  With  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 
return address on the stack as normal.

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 
program's PSP (program segment prefix), is a far call that  theo-
retically  allows  access  to DOS by doing a JMP  0005,  as  CP/M 
allowed.  However, looking at offset usually shows an instruction 
similar to the following:

                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 
has been documented since the beginnings of MS-DOS, most program-
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 
seen at the INT30 vector (JMP FAR 0274:1446).  By setting up  the 
stack  and  registers as described for the first back  door,  the 
dword pointer above can be inserted into the program's data  area.
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) 
return address to be pushed onto the stack, crashing the  program 
when the function returned.  

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 
ignore these alternative entry points into DOS.  A secure  system 
must  close these back doors.  For the back door at 0:00C0,  this 
is  trivial.   Simply replace the JMP FAR instruction  at  0:00C0 
with a JMP FAR into a new handler that can refuse or execute  the 
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 
is  not necessary.  Doing some calculations on the  modified  ad-
dress given in the PSP (F5C2:A4A0) shows that it translates  into 
0:00C0, because of the quirks of "wrap-around" memory  addressing 
found in the real-mode of the Intel IBM processors:

            offset A4A0 translates into segment 0A4A
so          segment F5C2 plus segment  A4A = segment 1000C
            segment 1000C wraps around to segment 000C 
which       translates into address 0:00C0.

Thus,  changing the PSP pointers is unnecessary, since both  back 
doors are different pointers to the same memory location.  Chang-
ing the vector at 0:00C0 will adequately protect against both.

Both  of  these back doors have existed in PC-DOS  since  version 
1.0, and in most versions of MS-DOS.  Given that they have exist-
ed  for over eight years without causing any  apparent  problems, 
and that the alternative DOS handler is limited in its scope, how 
serious  a danger do these backdoors present?  Only the  standard 
input/output and FCB functions are allowed, and although FCBs can 
delete  and rewrite files, they can be used only on files in  the 
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 
13h--delete  an  FCB, has a special case that could  destroy  all  
files on a hard disk.  The special case requires that the FCB use 
a filename of "???????????" and an attribute of 1Fh.  Seeing this 
specific  combination, function 13h will delete all files in  the 
current directory, including files marked with the read-only  and 
subdirectory  attributes.  To make matters worse,  this  function 
replaces  the first character of the deleted filenames with a  0, 
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 
root  directory,  it would effectively delete all  files  on  the 
disk.  Since subdirectories are simply special files that contain 
directory  information, these also would be deleted.  This  would 
prevent  any access to the files that were in those  directories, 
including any deeper subdirectories.  Note that the files in  the 
subdirectories  are not deleted; only the  directory  information 
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 
with the subdirectory attribute.  That an FCB function is allowed 
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 
root  directory will obliterate all files on the disk,  requiring 
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 
INT 21h calls, if it doesn't change the vector at 0:00C0 destroy-
ing 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 
it is installed before any other programs can run.   BACKDOOR.SYS 
simply  replaces  the vector at 0:00C0h with a pointer to  a  new 
handler and then installs itself as a character device.  The  new 
handler refuses any requests for DOS services through the  alter-
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 
if it occurs.

(see BACKDOOR.ASM for example 4)

No  IBM PC or compatible running in real mode can  be  completely 
safe  from  destructive  programs, whether  intentional  or  not.  
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
INSTALL_MSG  DB 0Dh,0Ah
             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


INSTALL_BACKDOOR PROC NEAR             ; 
             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                       ; 
INSTALL_BACKDOOR ENDP


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