Solved

Keyboard hooking problem in MFC DLL

Posted on 2010-11-21
23
1,266 Views
Last Modified: 2012-05-10
Hi,

I have an MFC based DLL which is injected into a game process I know nothing about, and I want to hook the keyboard capture to perform my own actions when certain keys are pressed.  I'm using other API hooks here and there on the OpenGL libraries in this game without issue, however the keyboard hook appears not to be working.

I'm decalrinig the hook procedure as follows:

_declspec(dllexport) LRESULT CALLBACK KBHookProc(int Code, WPARAM wParam, LPARAM lParam)
{
if (Code < 0) return(CallNextHookEx(hhook, Code, wParam, lParam));

 if (lParam & (1 << 31))
{
  // do something
}

 return(CallNextHookEx(hhook, Code, wParam, lParam));
}

and in the InitInstance method of my MFC DLL (where I'm successfully hooking other functions using IAT patching) I'm setting up the hook with:

hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KBHookProc, GetModuleHandle("telemreader.dll"), 0);

(telemreader.dll is the name of my DLL being injected).

This returns as successful (GetLastError == 0 and a non-null handle is assigned to hHook), however the function itself is not being called when I press a key in my app.

Can anyone please help me with this?

Cheers in advance, sorry if I'm missing something obvious - have tried all sorts before posting here :)

Thanks,
Chris

0
Comment
Question by:chrispauljarram
  • 10
  • 9
  • 3
  • +1
23 Comments
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 34185757
I've not met this WH_KEYBOARD_LL flag before so I looked it up in help.  The LPARAM is a pointer to a KBDLLHOOKSTRUCT which makes me really puzzled about your line:
if (lParam & (1 << 31))

I don't understand that line of code at all, is the if statement doing what you want?


The other point I noticed in the help is:
This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

Does it?
0
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 34185763
A final point from the help files.
Note that debug hooks cannot track this type of hook.

How do you determine that it is not working?  (As I understand that breakpoints won't work in this code section)
0
 

Author Comment

by:chrispauljarram
ID: 34186694
Hi Andy,

Please ignore the lParam line, it is residually pasted code from an earlier version of the function where I was using WH_KEYBOARD instead of WH_KEYBOARD_LL (and will no doubt be removed, sorry if it caused confusion).  I am not debugging into the function using breakpoints, I've previously determined it is definatley not being called by having the first statement within it set up to print a string to my own console (my AIT hooks also print this to verify they are called ok).

Thanks,
Chris
0
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 34186751
No other ideas.
I'll bow out and exit stage left
0
 
LVL 9

Expert Comment

by:Orcbighter
ID: 34187361
Maybe the problem is that your dll is not being loaded.
Maybe you have to explicitly load your dll before injecting it.
OR
If there is a problem in your dll and it fails to load, how can you tell? Trusting the SetWindowsHook may not be wise. It returns a sucess, but that could just mean a successful call was made, it may not reflect the fact that your dll may have failed on the first line of code.
Have you tested your dll outside the game, say in its own test harness?
0
 

Author Comment

by:chrispauljarram
ID: 34188839
Hi,

I know my DLL is loaded and injected correctly as all the other IAT hooks it performs work fine.

Thanks,
Chris
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34259179
Howabout replacing the WNDPROC for the game's main window, so you intercept all key presses yourself.  That avoids the need for a keyboard hook...
0
 

Author Comment

by:chrispauljarram
ID: 34278765
Hi mrwad99,

How do i go about doing that?  I've looked all over the place and can't see how this can be hooked.

Thanks,
Chirs
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34291397
You need to be able to find the main window for the game.  Once you have that, you can follow the procedure outlined at http://www.rohitab.com/discuss/topic/17456-window-subclassing-with-dll-injection/. This talks about adding a new menu item to Notepad, and handling it with a message box, but that is just the start of what you can do with this powerful technique.

I am playing with this whilst still working on your other problem about delay loaded DLLs, so if you have any issues, shout up.
0
 

Author Comment

by:chrispauljarram
ID: 34292241
Hi mrwad99,

Thanks again for your comments - I've just tried the above procedure (first on 2 games, then on notedpad itself) and it doesn't work at all, I'm doing things pretty much exactly as they are shown there, always getting valid window handles, however the remap just doesn't remap the WndProc function.  Any ideas?

Thanks,
Chris.
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34292891
I had some issues getting the code on that link to work too, so I had to change some of it.  Below you will find two source files.  Create a new VS solution, add to it a Win32 DLL project (call it "DLL")  and a Win32 EXE (call it "Loader").  Paste the source code in DLL.cpp over your DLL.cpp, and the code in Loader.cpp over your Loader.cpp.  Then, after starting notepad up, step through the code.  If it doesn't work, post back here stating at what point the code failed.
// DLL.cpp : Defines the entry point for the DLL application.

//



#include "stdafx.h"

#include "DLL.h"

#include <TCHAR.h>



#ifdef _MANAGED

#pragma managed(push, off)

#endif





LONG OldWndProc;



LRESULT CALLBACK NewWndProc(HWND Hwnd, UINT Message, WPARAM wParam, LPARAM lParam)

{

	 switch(Message)

	 {

		   case WM_COMMAND:

			   switch(wParam)

			   {

					case 2000:

						MessageBox(HWND_DESKTOP, _T("You pressed our new menu button!"), _T("Yay!"), MB_OK);

						break;

			   }

			   break;



	 }



	 return CallWindowProc((WNDPROC)OldWndProc, Hwnd, Message, wParam, lParam);

}



DWORD WINAPI Creation(LPVOID)

{

	 HWND hWnd = FindWindow(_T("Notepad"), _T("Untitled - Notepad"));

	 HMENU hCurrent = GetMenu(hWnd); //Get the CURRENT menu of the window

	 HMENU hNew = CreateMenu(); //Create a new one

	 AppendMenu(hCurrent, MF_STRING | MF_POPUP, (unsigned int)hNew, _T("Tutorial"));

	 AppendMenu(hNew, MF_STRING, 2000, _T("Button") ); //2000 is the ID of the button

	 DrawMenuBar(hWnd); //redraw the Menu!

	 OldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, (long)NewWndProc); //subclass the original window procedure

																							  //With our new one

	 //So now all messages sent to the notepad window, are sent to OUR window proc..See above!

	return TRUE;

}



BOOL APIENTRY DllMain( HMODULE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

					 )

{

	switch (ul_reason_for_call)

	{

	case DLL_PROCESS_ATTACH:

		{

			CreateThread(0, NULL, (LPTHREAD_START_ROUTINE)&Creation, NULL, NULL, NULL);

			break;

		}

	case DLL_THREAD_ATTACH:

	case DLL_THREAD_DETACH:

	case DLL_PROCESS_DETACH:

		break;

	}

	return TRUE;

}

Open in new window

//

// Loader.cpp

//

#include "stdafx.h"

#include <windows.h>

#include <tlhelp32.h>

#include <atlstr.h>



HANDLE GetProcessHandle(LPCTSTR szExeName)

{

	PROCESSENTRY32 Pc = { sizeof(PROCESSENTRY32) };

	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);

	HANDLE phandle = NULL;

	if(Process32First(hSnapshot, &Pc)){

		do{

			if(!_tcsicmp(Pc.szExeFile, szExeName)) {

				HANDLE token;

				if (OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token))

				{

					LUID luid;

					if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))

					{

						TOKEN_PRIVILEGES tp;

						tp.PrivilegeCount = 1;

						tp.Privileges[0].Luid = luid;

						tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

						if (AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))

						{

							phandle = OpenProcess(PROCESS_ALL_ACCESS, TRUE, Pc.th32ProcessID);

						}

					}

				}

				if (phandle == NULL)

				{

					DWORD err = GetLastError();

					err = 0;

				}

			}

		}while(Process32Next(hSnapshot, &Pc));

	}



	return phandle;

}



// NOTE: if we pass a UNICODE string as lpszDllPath, dwExitResult will always be 0 indicating failure!

BOOL DllInject(HANDLE hProcess, LPCTSTR lpszDllPath)

{

	HMODULE hmKernel = GetModuleHandle(_T("Kernel32"));//heres the DLL

	if(hmKernel == NULL || hProcess == NULL) return FALSE;

	int nBytes = ( _tcslen(lpszDllPath) + 1 ) * sizeof ( TCHAR );

	LPVOID lpvMem = VirtualAllocEx(hProcess, NULL, nBytes, MEM_COMMIT, PAGE_READWRITE);

	BOOL bRet = WriteProcessMemory(hProcess, lpvMem, lpszDllPath, nBytes, NULL);

	DWORD dwWaitResult, dwExitResult = 0;

#ifdef _UNICODE

	HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(hmKernel, "LoadLibraryW"), lpvMem, 0, NULL);

#else

	HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(hmKernel, "LoadLibraryA"), lpvMem, 0, NULL);

#endif

	if(hThread != NULL){

		dwWaitResult = WaitForSingleObject(hThread, 10000); // 10 seconds

		bRet = GetExitCodeThread(hThread, &dwExitResult);

		bRet = CloseHandle(hThread);

	}

	VirtualFreeEx(hProcess, lpvMem, 0, MEM_RELEASE);

	return ((dwWaitResult != WAIT_TIMEOUT) && (dwExitResult > 0));

}



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

{

	HANDLE hProcess;

	do

	{

		hProcess = GetProcessHandle(_T("NOTEPAD.exe"));

		Sleep(1);

	}while(hProcess == NULL);

	

	CString strFileName ( _T("") );

	GetModuleFileName ( NULL, strFileName.GetBuffer ( MAX_PATH ), MAX_PATH );

	strFileName.ReleaseBuffer();

	strFileName = strFileName.Mid ( 0, strFileName.ReverseFind ( '\\') + 1 ) + _T("DLL.dll");

	if(!DllInject(hProcess, strFileName) )

	{

		MessageBox(HWND_DESKTOP, _T("An error occurred injecting DLL!"), _T("Error!"), MB_OK | MB_ICONERROR);

	}

	Sleep ( 10000 );

	return 0;

}

Open in new window

0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 19

Expert Comment

by:mrwad99
ID: 34292910
Whoops, ignore

>> // NOTE: if we pass a UNICODE string as lpszDllPath, dwExitResult will always be 0 indicating failure!

Within Loader.cpp, that is not true as long as you remember to WriteProcessMemory the correct number of bytes (double the normal if using unicode!)
0
 

Author Comment

by:chrispauljarram
ID: 34295265
Thats fantastic mrwad99, thanks!

I can verify that it works,  also with the 2 games I tried when changing the FindWindow param.

However, this still requires I know the title of the parent window which I don't necessarily know (this can be injected into any one of over 100 games for instance, and sometimes titles include version numbers etc).  I've tried using the following code which I grabbed from somewhere else, but this never seems to give me the window handle I need...

static HWND  g_hwnd;
static int  g_nFound;

static BOOL CALLBACK FindHwndFromPID(HWND hwnd, LPARAM lParam);

static HWND GetHwndFromPID (DWORD dwProcessId)
{
      g_hwnd = NULL;
      g_nFound = 0;
      EnumWindows(FindHwndFromPID, (LPARAM) dwProcessId);
      if (g_hwnd)  // we found one...
            return (g_hwnd);
      return (NULL);
}

static BOOL CALLBACK FindHwndFromPID( HWND hwnd, LPARAM lParam)
{
    DWORD dwPID2Find = (DWORD) lParam;
    DWORD dwPID = 0;

    if(GetWindowThreadProcessId(hwnd, &dwPID))
    {
            if(dwPID == dwPID2Find)
        {
                  g_hwnd = hwnd;
            return(FALSE);
        }
    }

    return(TRUE);
}      


I've read you could also enumerate all the process windows and iterate through them calling GetParent to check for NULL or the desktop window for instance, and that this would 'likely' give you the top level window, but this is for a commercial software release so needs to be as reliable as using Windows Hooks (but without having to hook /every/ process which is silly for my needs).  I do have the process handle (in my loader) if this helps - is there any reliable way I can get the top level window from a process (as returned by FindWindow) with only the information I have?


Cheers again,
Chris.
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34299727
Good question.  The problematic point is

>> ...so needs to be as reliable...

In theory as you know, a process can have multiple top level windows (such as Word, or IE, for example).  I don't think a game will "suffer" from that problem however.  If you have read http://msdn.microsoft.com/en-us/magazine/cc301495.aspx and http://social.msdn.microsoft.com/Forums/en-US/windowssdk/thread/cdfa4c0c-eebc-49a6-8869-ecbd29c639fa (which are what I looked at when I first thought about this issue when playing with this idea a while back), and don't think it will be reliable, then we need to go a little deeper, so to speak.

All windows are created via the CreateWindow or CreateWindowEx functions, as far as I know.  If you can "hook" these two functions in your process, you can then intercept the HWND as it is created.  Once you have the HWND you can then hack its WNDPROC.  This may be a little overkill as you would be hacking the WNDPROC for all windows created by that process, but you could guarantee getting the one you wanted...

Have a think about this and let me know what you think
0
 

Author Comment

by:chrispauljarram
ID: 34300109
That's a good point, it sounds like the solution then is to 1) call EnumWindows and override WndProc (with my one version that captures key input) for  every single window belonging to the process at the point of injection, and 2) Hook CreateWindow/CreateWindowEx to override WndProc for subsequently created app windows to ensure none are missed by injecting too early.  It's not really a problem if I hook and catch keypresses for /every/ Window created by the app - I just want to be sure I've definately hooked the main one the game is visible in.

I'll try and give it a go today, in the meantime does this sound like it makes sense to you?  Also, is there any situation you can think of where overriding WndProc may not work, or is this pretty much guaranteed to be always used to catch keypresses at the lowest level by every window?

Cheers,
Chris.
0
 
LVL 19

Accepted Solution

by:
mrwad99 earned 500 total points
ID: 34300578
>> ...in the meantime does this sound like it makes sense to you?

Heh, if it would not have made sense to me, I would not have said it :)

Seriously though, in theory it is the way to go.  In practice it might not be so simple, but if you have already successfully tried replacing the window procedure of a game, and it has worked, then you should be ok.

>> Also, is there any situation you can think of where overriding WndProc may not work...

Hmm, not *currently*.  Every message a window receives has to go through the window procedure in order to be dispatched then processed, but you really never know with Windows: Uncle Bill and friends do so much under the hood that I would not be willing to bet extortionate amounts of money on this being the case.  I think the biggest place to fall foul is in not calling the original window procedure after you have handled the message yourself, but that is not tough to do.  You might run into some issues with some pieces of software that specifically watch out for DLL injection and actively prevent it, but I doubt games would fall into that category.  As long as you handle error cases appropriately (i.e. check the WNDPROC you get back to ensure it is valid/check that you get something back *period*) then you should have no other issues.

HTH
0
 

Author Comment

by:chrispauljarram
ID: 34302674
Well,  I've tried implementing it this way and it seems to work!  I know anti-cheat engines like PunkBuster prevent API hooking by periodically repatching (as I understand it), but that's just something I'll have to live with - some people use a brute force approach literally just having a thread constantly repatching the functions in a loop (ugly).

Anyway, looks like this is the answer for now - thanks again for all your help mrwad99 you are a legend! :-D

Cheers,
Chris
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34303084
Glad to help; maybe you will have another related problem sometime that I can possibly be of assistance with :)

Just so we are clear, are hooking the CreateWindow functions *and* enumerating all top level windows at startup time, or just one or the other?
0
 

Author Comment

by:chrispauljarram
ID: 34304197
Both, I'm storing a hash table (map) of existing HWND's original function pointers at startup, and remapping them all to my own WndProc, and adding to this whenever CreateWindowExA or CreateWindowExW is called.  When I eject, I flush the hashtable and restore all the pointers - seems to be working well :)

Thanks again!  Will be sure to touch base if I hit any more problems, very kind of you to offer your time helping with this...

Cheers,
Chris
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34304475
Gotcha.  As a sidenote, you might want to consider hooking the basic CreateWindowA/CreateWindowW too, not just the "Ex" counterpart...
0
 

Author Comment

by:chrispauljarram
ID: 34305147
Hiya,

I did consider that originally, but the MSDN docs state this:-

"CreateWindow is implemented as a call to the CreateWindowEx function.

#define CreateWindowA(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\
CreateWindowExA(0L, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)

#define CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\
CreateWindowExW(0L, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)

#ifdef UNICODE
#define CreateWindow  CreateWindowW
#else
#define CreateWindow  CreateWindowA
#endif
"

So presumably just handing the Ex versions should catch these calls?

Cheers,
Chris
0
 
LVL 19

Expert Comment

by:mrwad99
ID: 34308773
Bah, I knew I should have read the full documentation before posting that comment :)

In this case, hooking the Ex counterparts is all you need.
0
 

Author Comment

by:chrispauljarram
ID: 34309302
Cool, thanks so much once again :)
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

What my article will show is if you ever had to do processing to a listbox without being able to just select all the items in it. My software Visual Studio 2008 crystal report v11 My issue was I wanted to add crystal report to a form and show…
Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now