We help IT Professionals succeed at work.

PC key fake

yadinf asked
Medium Priority
Last Modified: 2008-02-26
How can I fake keystrokes in PC under MSDOS using interrupt 9 ?

I had to write a TSR to fake the keyboard.
I did it by stuffing data into the Bios Keyboard Buffer, which works fine.
However, this approach is not good enough for programs that handle interrupt 9 themselves. So, the next thing I tried was using OUTP to port 0x60 with the appropriate data, then use INT 9. However, the data that is read from that port (with INP) by program reacting to interrupt 9 is always 0xFE, regardsless of the data which was written with OUTP.


Watch Question

Yadin, here's what you need to do:

Write another interrupt handler that can remap INT 9 to your INT9 handler *AFTER*, the INT9 has been remapped by any other software.

You can do this by saving the status/location of the INT9 handler before it gets modified, then monitor it either via Int21 or Int8(I think Int8 is the timer interrupt, it's been a while..).. I'd recommend monitoring it with Int21 function 2509.. (set int handler for int9)... so when the handle change is detected, you set a flag in your program that says "int9 has been changed", thats when YOU map INT9 to your program...

Note: you cannot use Int21 within a TSR, but you can use a call to it.. I'm not sure whether you're aware of this, but it is crucial when dealing with changing interrupts etc within a TSR.

Another alternative is to find a product that already exists that does what you want.

What is the exact purpose of the program (if its not confidential!) ?


Either I didn't understand the answer, or, more likely, it's not quite a reply to the question asked. May be the reply meant to expalin how to INTERCEPT keystrokes, using INT 9 handler, which is not what I need. By the way, I wrote such a handler..

I need a way to write a TSR to FAKE KEYSTOROKES, using INT 9.

The purpose: To enable a checks reader to be integrated with any Dos application, that nomally expects the check's details typed from the keyboard

Some clarifications I need:
The interrupts mentioned, are they in Hex or in Decimal ?

Also, what is "Int21 function 2509" ?

yadinf:  Heres a muchly annotated example of a keyboard stuffer written in basic.  Maybe it will help you.
'********* PDQKEY.BAS - keyboard macro substitution program

'Copyright (c) 1990 Crescent Software
'Written by Ethan Winer and Nash Bly

'WARNING: This program does NOT work in the QuickBASIC editing environment,
'and it should be uninstalled before entering QB.  Please understand that few
'TSR programs cooperate fully with QB, and this is not a failing of P.D.Q.
'PDQKEY is a TSR program that lets you define keyboard macros that are typed
'automatically when you press a single "hot-key".  It also expands the PC's
'15-character keyboard buffer to nearly any size.  PDQKEY examines each key
'stroke that is entered, and if it is one of those that have been defined,
'substitutes a replacement character or string of characters.  Any number of
'macros may be defined, and each may be as long as BASIC's string memory can
'accommodate.  As each recognized hot-key is detected, a "master" macro is
'concatenated with the new contents.  This allows multiple macros to be typed
'in succession, even if earlier ones have not yet been processed by the
'underlying application.  Both the macros you define and non-macro keys are
'stuffed into the keyboard buffer by PDQKEY.
'The macros defined below are simple examples which you can replace with your
'own.  This version of PDQKEY uses about 6.5K of memory, compared to 59K
'taken by Borland's SuperKey.  PDQKEY is of course much less capable than
'SuperKey, but it performs all that is usually needed.
'It is important to point out that the method used here may not cooperate
'with other TSR keyboard handlers.  For example, PDQKEY does not work with
'the QuickBASIC editor, because it installs its own replacement keyboard
'handler.  However, I have not encountered any problems using PDQKEY with any
'other programs.
'Also see the MACRO.BAS program which is newer, and is more direct if you
'only need to handle keyboard macros.
'To take as little memory from DOS as possible, compile and link as follows:
'   BC PDQKEY /o;

'*******************  PDQKEY.BAS - Keyboard Macro TSR  *********************

'-- Recommended statements for any P.D.Q. program.

DEFINT A-Z                              'declare integer variables

'$INCLUDE: 'PDQDECL.BAS'                'declarations and type definitions

'-- Define variables and constants

DEF SEG = 0                             'for peeking keyboard info
CONST NumMacros = 10                    'the number of macros we're handling
DIM Registers9 AS RegType               'RegType for interrupt 9 processing
DIM Registers8 AS RegType               'RegType for interrupt 8 prcessing
DIM Macro$(0 TO NumMacros)              'dimension array of macro strings
Zero$ = CHR$(0)                         'this saves a few bytes later by
                                        '  avoiding calls to CHR$ repeatedly
                                        '  (CHR$ is a called routine)
'-- Start Execution

    Id$ = "PDQKEY 1.00 Copyright (c) 1990 Crescent Software"
    PRINT Id$

'-- Install, UnInstall, and Help

    Segment = TSRInstalled(Id$)

    Switch$ = UCASE$(COMMAND$)
    IF Switch$ = "" THEN
        IF Segment <> 0 THEN
            PRINT "PDQKEY is Already Installed"
            PRINT "Use PDQKEY /U to Uninstall"
        END IF
    ELSEIF Switch$ = "/U" THEN
        IF Segment = 0 THEN
            PRINT "PDQKEY Is Not Resident and Cannot be Uninstalled"
            Success = UnHookInt(Registers9, Segment)
            GOSUB Check
            Success = UnHookInt(Registers8, Segment)
            GOSUB Check
            Success = DeInstallTSR(Segment, Id$)
            GOSUB Check
            PRINT "PDQKEY is Removed from Memory"
        END IF
        PRINT "PDQKEY Usage:   Install   - PDQKEY"
        PRINT "                UnInstall - PDQKEY /U"
    END IF

    PRINT "PDQKey is Resident in Memory"

'-- Define Macros.  The CHR$(13) below shows how to also type an Enter.  To
'   define an extended key, use a CHR$(0) plus the extended key's code.  For
'   example, to stuff the F1 key use CHR$(0) + CHR$(59).  The second example
'   below enters an up arrow after the string.

    Macro$(1) = "REM  -  F1 Key Hit  -  F1 Key Hit" + CHR$(13)
    Macro$(2) = "REM  -  F2 Key Hit  -  F2 Key Hit" + CHR$(0) + CHR$(72)
    Macro$(3) = "REM  -  F3 Key Hit  -  F3 Key Hit  -  F3 Key Hit"
    Macro$(4) = "REM  -  F4 Key Hit  -  F4 Key Hit  -  F4 Key Hit"
    Macro$(5) = "REM  -  F5 Key Hit  -  F5 Key Hit  -  F5 Key Hit"
    Macro$(6) = "REM  -  F6 Key Hit  -  F6 Key Hit  -  F6 Key Hit"
    Macro$(7) = "REM  -  F7 Key Hit  -  F7 Key Hit  -  F7 Key Hit"
    Macro$(8) = "REM  -  F8 Key Hit  -  F8 Key Hit  -  F8 Key Hit"
    Macro$(9) = "REM  -  F9 Key Hit  -  F9 Key Hit  -  F9 Key Hit"
    Macro$(10) = "REM  - F10 Key Hit  -  F10 Key Hit  -  F10 Key Hit"

'-- Set up keyboard Interrupt 9, jump to set up Int 8.

    Registers9.IntNum = 9                   'specify keyboard interrupt 9
    PointIntHere Registers9                 'setup interrupt entry point
    GOTO Install8                           'skip to interrupt 8 setup
10                                     'this line number is needed for VB/DOS

'-- This block of code receives control each time a key is pressed.

    IntEntry1                               'mandatory first two steps for
    IntEntry2 Registers9, Zero              '  any P.D.Q. interrupt routine

    ScanCode = INP(&H60)                    'first get the key press manually
    ShiftMask = PEEK(&H417) AND &HF         'then get the shift status

    SELECT CASE ScanCode + 256 * ShiftMask  'this is the key that was pressed
    'see the P.D.Q. manual "hot key" section for an explanation of hot keys

    CASE &H3B
        ThisKey = 1                         'F1
    CASE &H3C
        ThisKey = 2                         'F2 Key
    CASE &H3D
        ThisKey = 3                         'F3 Key
    CASE &H3E
        ThisKey = 4                         'F4 Key
    CASE &H3F
        ThisKey = 5                         'F5 Key
    CASE &H40
        ThisKey = 6                         'F6 Key
    CASE &H41
        ThisKey = 7                         'F7 Key
    CASE &H42
        ThisKey = 8                         'F8 Key
    CASE &H43
        ThisKey = 9                         'F9 Key
    CASE &H44
        ThisKey = 10                        'F10 Key


'-- The key pressed was not one we are recognizing, so call the original
'   interrupt handler to translate the scan code to an ASCII or extended
'   value.  This would place characters into the keyboard buffer and disturb
'   characters which may already be there.  For this reason, we reserve
'   keyboard buffer addresses &H3A - &H3D for interpreting key presses.
'   The buffer head and tail are saved, moved and restored for this purpose.

        ThisKey = 0
        OldHead = PDQPeek2%(&H41A)          'save keyboard buffer head
        OldTail = PDQPeek2%(&H41C)          'save keyboard buffer tail
        PDQPoke2 &H41A, &H3A                'move head to reserved area
        PDQPoke2 &H41C, &H3A                'move tail also
        CallOldInt Registers9               'call the BIOS to interpret press
                                            'if no characters end up in the
                                            'buffer (Shift, Alt, etc.) then
                                            'simply ignore
        IF PDQPeek2%(&H41A) = PDQPeek2%(&H41C) THEN GOTO Ignore

'-- Get ASCII code from the buffer and convert to a string.
        ThisChar = PDQPeek2%(&H400 + PDQPeek2%(&H41A))

        SELECT CASE ThisChar AND 255        'check the low byte
           CASE 0, 224, 240                 'treat these as extended
                Macro$(0) = Zero$ + CHR$(ThisChar \ 256)    'alt. cursor keys
           CASE ELSE                        'it's a normal key
                Macro$(0) = CHR$(ThisChar)
        END SELECT


'-- This code adds the macro or other key to the master keypress queue.

    Queue$ = Queue$ + Macro$(ThisKey)       'add the macro to master queue
    QueueEmpty = 0                          'set macro pending flag


    IF ThisKey = 0 THEN                     'if not a macro key then
        PDQPoke2 &H41A, OldHead             'reset keyboard buffer head
        PDQPoke2 &H41C, OldTail             'reset keyboard buffer tail
        ResetKeyboard                       'we're handling this ourselves
    END IF
    ReturnFromInt Registers9                'return to the underlying program

'-- Set up the intercept for timer Interrupt 8.


    Registers8.IntNum = 8                   'specify Interrupt 8
    PointIntHere Registers8                 'setup interrupt entry point
    GOTO EndIt                              'jump to finish installation
20                                     'this line number is needed for VB/DOS

'-- This block of code receives control each time a timer tick occurs.
    IntEntry1                               'required first two steps for any
    IntEntry2 Registers8, Zero              'P.D.Q. interrupt service routine

'-- See if we need to do anything, get out as quickly as possible if not.
'   We'll stuff the keystrokes one by one, but only when the keyboard buffer
'   is empty.

    IF QueueEmpty GOTO Done8                'no macro is pending, bye bye

    IF PDQPeek2%(&H41A) = PDQPeek2%(&H41C) THEN 'if the keyboard buffer empty
        PDQPoke2 &H41A, &H1E                'set buffer head to start address
        PDQPoke2 &H41C, &H1E                'set the buffer tail there too
        IF LEN(Queue$) < 14 THEN            'if 13 or less keys in queue then
            stuff$ = Queue$                 'stuff entire queue
            Queue$ = ""                     'clear the queue
            QueueEmpty = -1                 'set queue empty flag
        ELSE                                'if more than 13 chars in queue
            IF MID$(Queue$, 13, 1) <> Zero$ THEN 'if last char isn't CHR$(0)
                stuff$ = LEFT$(Queue$, 13)       'stuff only 12 characters
                Queue$ = MID$(Queue$, 14)        'and update the queue
                stuff$ = LEFT$(stuff$, 12)       'stuff 13 characters
                Queue$ = MID$(Queue$, 13)        'update the queue
            END IF
        END IF
        StuffBuf stuff$                     'stuff the keyboard buffer
    END IF


    GotoOldInt Registers8                   'return to underlying program


    EndTSR Id$                              'exit while staying resident

'****************************  Subroutine  ******************************

                                            'check the uninstall success
    IF NOT Success THEN
        PRINT "Error Uninstalling - Please Reboot"
    END IF




thanks a lot for your program. it seems like a good one.
However, it I might have a few problems using it:

1) May be be I am wrong, but it seems that the key fake data is stuffed to the bios keyboard buffer and it doesn't generate INT 9 for each byte. I need to to use interrupt 9,
to fake a real key stroke.

2) My program is written in C, so I wonder, how I can integrate your routines, with mine.

3) I have never used BC. How can I obtain it?

Waiting for your comments.



Thanks for your info, however it doesn't help me with my problem, so I have to reject it.

If I understand your problem correctly, you need:

(1) Trigger int 09 by something other than a bonafide keystroke.

Easy.  (int 09h)

(2) Force port 0x60 to simulate another keystroke.

This is only possible if you write a protected mode ISR which
traps IO read/write accesses.  (Simply 'out'ing to a port
doesn't mean that 'in'ing it will return the value you put
there.)  386's and above, however, can actually trap IO
accesses, and you can simulate its response to queries.  
Clearly this will not work under Win 95, Win NT, or OS/2, which
obviously already do this to redirect keystrokes only to focus
windows.  However it should work under DOS or protected mode
DOS without a problem.

Unfortunately, I don't know the details, however if you look in
the Pentium architectural manuals available from Intel, I think
they cover what you need to know.

Not the solution you were looking for? Getting a personalized solution is easy.

Ask the Experts



Sorry for not responding, so far. I couldn't check your tip, to use protected mode .exe. I use Microsoft C 6.0 for Dos. However, I don't have the install disks (I only have QC25 disks). It seems that I need SLIBCEP.LIB file. Do you have that file ? If so could you send me ?
I don't use Microsoft C 6.0.  But since you are, you are clearly
operating in real mode or v86 mode.  As I said before, in v86
mode you are dead (Windows, OS/2) but in real mode what you need
to do is pop into protected mode yourself, and set up an IO trap
on the keyboard port (0x60) and return execution to v86 mode.
(You become the DOS extender.)  Of course you have to set up
some backdoor comunication to activate your TSR as usual.

For this to work you need your other program to be running in
v86 mode.  This can be a problem for "DOS extended appliations."
If they don't find a DPMI server they will assume none is present
and try to load their own and may simply override your IO trap.

In a sense this would probably be easiest if you were starting
from inside of another framework such as EMM386 or a DPMI server.
I don't know all the details, but in theory, if the DPMI server
will let you, the easiest way is to simply run a DPMI server,
then patch your TSR on top of it.

As I tried to indicate, this is not easy.  What you are trying
to do is quite complicated.  You have to basically control the
keyboard at a level equivalent to what protected mode OS's like
Windows and OS/2 do.  For me this would be at the level of
"research project".  I don't know all the details, but I know
roughly where to look.
Access more of Experts Exchange with a free account
Thanks for using Experts Exchange.

Create a free account to continue.

Limited access with a free account allows you to:

  • View three pieces of content (articles, solutions, posts, and videos)
  • Ask the experts questions (counted toward content limit)
  • Customize your dashboard and profile

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.


Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.