mirror of
https://github.com/opsxcq/mirror-textfiles.com.git
synced 2025-08-11 04:14:05 +02:00
547 lines
26 KiB
Plaintext
547 lines
26 KiB
Plaintext
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
|
||
|
||
|