AHS@PSUVM.BITNET (06/28/88)
June 26, 1988 -- Fourth and final dissassembly -- How KBDL swaps the keys. Tom Almy, toma@tekgvs.TEK.COM, writes of his set of KBD*.com programs: All of these [AT only] programs were written by me, using the CFORTH Forth Compiler, and are placed in public domain for the benefit of other PC users who are frustrated with these keyboard designs. I am not supplying sources, the programs are small enough that DEBUG can be used to reverse-engineer them. Tom Almy toma@tekgvs.TEK.COM ----------------------------------- Earlier, I attempted several dissassemblies of: KBDL.COM 332 6-15-88 7:03a. My motivation was to see if Tom knew a better way than using interrupt 9 -- as is done in a similar program, IBMFIX, published by PC Magazine -- to move keys around the keyboard. The short answer is: Tom knows. Here is my final dissassembly, hopefully. (Note: A few hours after I completed this commented dissassembly, I downloaded a dissassembly sent by Tom. Thank you Tom. Tom's dissassebly was not commented, except for the manipulation of return addresses following a call. The two dissassemblies, made independently, do agree). The problem that threw me off in my previous dissassemblies was: What is the code that is being executed after returning from the call made a label L017F (at offset 017F) ??? This was solved by explanations supplied first by Kchula-Rrit, thank you Kchula-Rrit, and later by Tom Almy himself in his first and second replies, thank you Tom. I had one other question. What is so special about this code that it will run successfully only on an AT ??. This was solved; it is explained in the dissassembly of the new interrupt handler. In sum: Making the dissassembly required some tidbits of information beyond the information provided by Debug's U command. (The Trace command is of limited and difficult use when dissassembling a TSR). I expect that when I rewrite this code in assembly, I will need: the 38h=56 bytes of the handler, plus roughly 35 bytes to set the vectors, and roughly 35 bytes to install the code in memory, or roughly 56+40+24=126 bytes. That is, to install the TSR, I will need: very roughly 35+35=70 bytes of straighforward code versus 332-56=276 bytes of not exactly straighforward code, but probably good compiled Forth code -- I understand that AI has made promises but has not yet fully delivered in compilerdom -- by the way, how large and straightforward would a QBasic EXE be?. When I am done, I think I will post both the COM and assembly. Finally, and most importantly, my sincere thanks to Tom Almy for showing us how to use interrupt 15. --------------------------------------------------------------------- TITLE KBDL RET_NEAR MACRO DB 0C3H ENDM .RADIX 16 S0000 SEGMENT ASSUME DS:S0000, SS:S0000 ,CS:S0000 ,ES:S0000 ORG 0100H L0100: JMP L010B DB 4C,2 L0105 DW 0000 L0107 DW 0000,000A L010B: MOV SP, FF9A MOV WORD PTR L0105, FF98 MOV BP, FFFE MOV L0107, BP CLD CALL L017F MOV AX, 4C00 ;exit program, with errorlevel 00 INT 21 ;The following DW is for data write and data immediate L0124 DW 0000 ;For Offst of old vect 15 ;Will be moved into PSP at offst 80 ;The following DW is for data write from offset 01DA. ;Since it is not data read, nor data immediate, is it executed ?? ;No, but it is moved to PSP to be used there, as part of the mov 3C L0126 DW 0000 ;For Segmt of old vect 15 ;Will be moved into PSP at offst 82 ;(At offst 84 begins new hdlr 15) ;New int 15 hdlr ;Offset: 0128 ;Moved in PSP at offst 84 by t mov 3C (160-128=38h is enough) ; ;Int 15 is not documented in Duncan's Advanced MS DOS ;However, thanks to Ralph Brown, we learn that: ; ; Ralf Brown ; Arpa: ralf@cs.cmu.edu ; UUCP: {uunet,ucbvax,harvard}!cs.cmu.edu!ralf ; BIT: ralf%cs.cmu.edu@cmuccvma ; FIDO: Ralf Brown at 1:129/31 (more subject to change than the others ; ; Last edited 1/30/88 ; ; ----------------------------------------------------------- ; INT 15 - HOOK - KEYBOARD INTERCEPT (AT model 3x9,XT2,XT286,CONV,PS) ; AH = 4Fh ; AL = scan code ; CF set ; Return: AL = scan code ; CF set ; Note: Called by INT 9 handler to translate scan codes ; ----------------------------------------------------------- ; ;Thus, this TSR should work with the following machines: ; AT-3x9, XT2, XT286, Convertible, PS2 ; ;New int 15 hdlr ;Offset: 0128 ;Moved in PSP at offst 84 by t mov 3C (160-128=38h is enough) CMP AH, 4F ;Is it a kbd intercept ? JNZ L015A ;No CMP AL, 3A ;Is it t scancode of CapsLock ? JNZ L014B ;No ;It is a CapsLock PUSH ES CS: MOV ES, [007E] ;Map ES onto DOS segmt ;([7E] was earlier set to 00) ;Not exactly a straightforward ;manner to set ES to 00 ES: TEST BYTE PTR [0417], 02 ;Is LeftShiftKey down ? JNZ L0141 ;No ;LeftShiftKey is down, and CapsLock was pressed MOV AL,1D ;Make keypress a Ctrl keypress ;It is CapsLock but LeftShift was not pressed L0141: MOV [007D],AL ;Store resultg Ctrl or CapsLock ;at 7D in PSP POP ES STC CS: JMP FAR [0080] ;Execute old hdlr 15 ;It was not CapsLock L014B: CMP AL,BA ;Is it t scancode of the ??1??-key ;Is BA t scancode of "CapsLockRelease"? ;BA=(80+3A)=(80+CapsLockScanCode) ;My book stops at scancode 53h..... JNZ L015A ;No ;It is key ??1?? ;It is a ?CapsLockRelease MOV AL,[007D] ;Make it the Ctrl or CapsLock ;that was earlier stored at 7D in PSP ADD AL,80 ;Make it now into a ??2?? keypress ;But BA=(80+3A)=(80+CapsLockScanCode) STC ;?Make it now into a ?CtrlRelease or ;a ?CapsLockRelease CS: JMP FAR [0080] ;Execute old hdlr 15 ;It was not a kbd intercept ;It was not key ??1?? ;It was not a ?CapsLockRelease L015A: STC CS: JMP FAR [0080] ;Execute old hdlr 15 ;End of new int 15 hdlr ;The following code is called from offset 01F8 L0160: POP SI POP AX POP DX POP BX PUSH DS MOV DS,BX MOV AH,25 ;Get vector INT 21 POP DS JMP SI L016E: MOV AH,35 ;Set vector INT 21 MOV AX,BX MOV BX,ES RET_NEAR ;Byte: C3 L0177: MOV DX,0C MOV AX,T3100 ;Exit but remain memory residt INT 21 L017F: CALL L01FE ;Bytes: E8 7C 00 ;Called from: L011C ;Now, THE question is: ------------------------------------------------------- ; What is the code that follows this call and ; that we shall execute when we return from this call ??? ; Answer: execution does not return here, but at the end of the text, ; at CALL L0210, because the call's return addr was manipulated ; by the routine at L01FE ;Here is the source ;0180 7C 00 33 43 41 50 53 4C-4F 43 4B 20 2D 3E 20 43 | 3CAPSLOCK -> C ;0190 54 52 4C 2C 20 20 4C 45-46 54 53 48 49 46 54 20 TRL, LEFTSHIFT ;01A0 2B 20 43 41 50 53 4C 4F-43 4B 20 2D 3E 20 43 41 + CAPSLOCK -> CA ;01B0 50 53 4C 4F 43 4B E8 57-00 E8 42 00 0B 42 79 20 PSLOCKhW hB By ;Now, how do we ever reach those two calls? ;Answer: By manipulating the stack in the routine at L01FE ;However, this is only one of several return address generated by routine L01FE CALL L0210 ;Bytes: E8 57 00 CALL L01FE ;Bytes: E8 42 00 ;Now, comes more text. ;Here is the source: ;01B0 50 53 4C 4F 43 4B E8 57-00 E8 42 00 0B 42 79 20 PSLOCKhW hB By ;01C0 54 6F 6D 20 41 6C 6D 79-E8 45 00 C7 06 7E 00 00 Tom AlmyhE G^ ;Here is the text: "By Tom Almy" DB 42,79 DB 20,54,6F,6DH,20 DB 41,6C,6DH,79 ;which ends here. ;Now, how do we ever reach the following line ?? ;There are no explicit jumps or calls to offset 01C8. ;Answer: By now we know, the CALL L01FE manipulated the call's return address. ;However, this is only one of several return address generated by routine L01FE CALL L0210 ;Offset: 01C8 ;;L0210: MOV AX,0DH ;A copy of the routine is placed here for clarity ;; CALL L021F ;;;;L021C DB 1,0 ;Another copy for clarity ;;;; ;;;;L021E DB 0 ;;;; ;;;;L021F: MOV L021E,AL ;AL=0D ;;;; MOV AH,40 ;Write byte at L021E to handle in BX ;;;; MOV CX,1 ;;;; MOV DX,OFFSET L021E ;;;; MOV BX,L021C ;;;; INT 21 ;;;; RET_NEAR ;; MOV AX,0A ;; JMP L021F ;; ;;L021C DB 1,0 ;; ;;L021E DB 0 ;; ;;L021F: MOV L021E,AL ;AL=0A ;; MOV AH,40 ;Write byte at L021E to handle in BX ;; MOV CX,1 ;; MOV DX,OFFSET L021E ;; MOV BX,L021C ;; INT 21 ;; RET_NEAR MOV WORD PTR DS:7E, 0 ;In PSP, set [7E]=00 = DOS segmt MOV AX,15 ;15 in AL when exctg funct 35 CALL L016E ;---- To execute Funct 35 --------------- ;----- AL is overwritn aftr funct --- ;; ;1<<<<<<<<<<<<<<<<<<<<< ;;L016E: MOV AH,35 ;Get vect, AL=15 ;A copy placed here for clarity ;; INT 21 ;; MOV AX,BX ;AX= Offst of old vect 15 ;; MOV BX,ES ;BX= Segmt of old vect 15 ;; RET_NEAR ;Byte: C3 MOV L0124,AX ;Offst of old vect 15 MOV L0126,BX ;Segmt of old vect 15 ;Offset: 01DA ;; ;A copy of the routine placed here for clarity ;;;The following DW is for data write and data immediate ;; ;;L0124 DW 0000 ;Offst of old vect 15 ;; ;;;The following DW is for data write from offset 01DA. ;; ;;L0126 DW 0000 ;Segmt of old vect 15 3 MOV DX,OFFSET L0124 ;Offst of old vect 15 3 PUSH DX 2 MOV DX,80 2 PUSH DX 1 MOV DX,3C ;Nuber of bytes to be moved 1 PUSH DX CALL L023E ;Offset: 01EA ;;L023E: ;A copy of the routine placed here for clarity ;;CRA POP BX ;Remove t call's return address (CRA) ;; 1 POP CX ;3C ;; 2 POP DI ;80 ;; 3 POP SI ;Offst of old vect 15, ie: L0124 ;; JCXZ L024A ;Offset: 0242 ;; MOV AX,DS ;; MOV ES,AX ;Make ES = DS ;; REPZ MOVSB ;Mov 3C bytes of code to [80] into PSP's cmdline area ;; ; ;; ;Code being stuffed into PSP: ;; ; ;; ;At 7D The Resulting CapsLock or Ctrl keypress ;; ;At 7E Value=00 = DOS segmt ;; ;At 80 Offst of old vect 15 ;; ;At 84 New int 15 hdlr ;; ;At (80+3C) End of hdlr ;; ;;;Next line is jumped at from offset 0242 ;; ;;L024A: JMP BX ;BX=CRA. Was set at L023E ;; ;This jmp is t actual return from the call 3 MOV AX,CS 3 PUSH AX 2 MOV DX,84 2 PUSH DX 1 MOV DX,15 ;The int to be re-vectored 1 PUSH DX CALL L0160 ;Offset: 01F8 ;;L0160: ;A copy of the routine is placed here for clarity ;;CRA POP SI ;Remove t call's return address (CRA) ;; 1 POP AX ;15 the int to be re-vectored ;; 2 POP DX ;84 Offst of new hdlr 15 in PSP cmdline area ;; ; (at 80 we store offst o old int 15 vect) ;; ; When was new hdlr 15 placed in PSP ??? ;; ; It was moved there by t mov 3C ;; 3 POP BX ;CS ;;1 PUSH DS ;Save t true DS addr ;; MOV DS,BX ;DS=CS=PSP ;; MOV AH,25 ;Set vector ;2<<<<<<<<<<<<<<<<<<<<<< ;; INT 21 ;;1 POP DS ;; JMP SI ;SI=CRA. Was set at L0160 JMP L0177 L01FE: POP BX MOV AL,[BX] XOR AH,AH INC BX PUSH BX PUSH AX ADD BX,AX POP AX POP DX PUSH BX MOV BX,DX JMP L0231 ;Offset: 020D L0210: MOV AX,0DH CALL L021F MOV AX,0A JMP L021F L021C DB 1,0 L021E DB 0 L021F: MOV L021E,AL MOV AH,40 ;Write byte at L021E to handle in BX MOV CX,1 MOV DX,OFFSET L021E MOV BX,L021C INT 21 RET_NEAR ;Jump to next line is from offset 020D L0231: MOV CX,AX MOV DX,BX MOV BX,L021C MOV AH,40 INT 21 RET_NEAR ;Next line is called from offset 01EA L023E: POP BX POP CX POP DI POP SI JCXZ L024A ;Offset: 0242 MOV AX,DS MOV ES,AX REPZ MOVSB ;Next line is jumped at from offset 0242 L024A: JMP BX ;Bytes: FF E3 S0000 ENDS ; END L0100