Solved

GDI TextOut Hook

Posted on 2006-11-22
6
4,151 Views
Last Modified: 2008-09-26
Okay - I've already asked this question twice before, but never got an adequate response, so let me try again...

I need a simple C++ example that captures TextOut messages being sent to a particular Windows application (more specifically, a Window).  The notepad example that has been cited several times isn't exactly what I need since it intercepts and allows you to send the TextOut message yourself, but it never actually shows you what the incoming message is.  This is what I need...

Intercept the mesages to see what they are (peek at them).  I need something similar to what a keylogger would produce.  I need to look at the messages (not send them).  Assume an application is using TextOut to paint text as the user types into an edit control.  I need to capture this text (key hooks won't work for this because I need to look at data that the application paints as well).

Please help (and post an example if you have one).  Thanks!

0
Comment
Question by:scottmichael2
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 2
6 Comments
 
LVL 6

Accepted Solution

by:
SeanDurkin earned 500 total points
ID: 18010191
I just finally figured out how to use DLL injection combined with API hooking to solve this, using C++ with the WinAPI, and I'm happy to share my information with you. I'm assuming you're not using .NET or MFC or anything like that, because if you are, I don't think I can help you very much. I'll provide steps as well as code to try to explain:

1. In your main program, you need to inject your DLL (the one with the code inside to hook the TextOut function) into the target process. Here I'm just basically creating the process handle, allocating memory in the target process for the path of the DLL to inject, and then using LoadLibrary with that path as the parameter inside the target process so it will run the DLLMain of my DLL.

int injectDLL(DWORD idProcess)
{
      // get the process handle
      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, idProcess);
      if(hProcess == NULL)
            return 0;
      
      char szDLLPath[100] = [insert DLL name here];  // this is the name of your DLL to inject
      DWORD exitCode;
      
      // allocate memory for writing data within the process
      LPVOID lpDLLName = VirtualAllocEx(hProcess, NULL, sizeof(szDLLPath), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      if(lpDLLName == NULL)
            return -1;
      
      // write the library path to the allocated memory
      if(!WriteProcessMemory(hProcess, lpDLLName, (LPVOID) szDLLPath, sizeof(szDLLPath), NULL))
      {
            return -2;
      }
      
      // use CreateRemoteThread to call LoadLibrary within the remote process with the
      // pDLLName as its parameter, so the library is mapped into the remote process
      HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
            (LPTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"),
            lpDLLName, 0, NULL);
      
      if(hThread == NULL)
            return -3;
      
      WaitForSingleObject(hThread, INFINITE);
      GetExitCodeThread(hThread, &exitCode);
      CloseHandle(hThread);
      
      // free the memory
      VirtualFreeEx(hProcess, lpDLLName, sizeof(szDLLPath), MEM_RELEASE);

      return exitCode;
}


2. You create a thread inside DLLMain, which runs the main function to hook to your own function (I realize you don't want to replace it with your OWN function, but just you'll see what we're doing in a sec).

HMODULE g_hMod;

extern "C" BOOL WINAPI DllMain (HINSTANCE hInst     /* Library instance handle. */ ,
                       DWORD reason        /* Reason this function is being called. */ ,
                       LPVOID reserved     /* Not used. */ )
{
      if(reason == DLL_PROCESS_ATTACH)
      {
            g_hMod = (HMODULE) hInst;
            CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) setHooks, (LPVOID) MyTextOutA, 0, NULL);
      }
      
      return true;
}


3. Next you'll write your own TextOut function, and you'll call it MyTextOutA, as you did in the CreateThread function above. Inside this function you do whatever you want with the lpString, and then return with the original TextOutA function so the other program isn't disrupted.

DWORD WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString)
{
      // I think you can use PostMessage() to send the lpString in a WPARAM or LPARAM to your original program

      return TextOutA(hdc, nXStart, nYStart, lpString, cbString);
}


4. You write the setHooks() function and the function that swaps the original function pointer (inside GDI32.DLL) with your own function pointer. I'm assuming you don't need this code, as it seems as though you've been directed to code in other questions and know how this works, but if you need it, let me know and I'll see what I can do.



Basically, I'm thinking that all you need to do is the following: In your own function (MyTextOutA), you send a message to your main program from your injected DLL (I'm thinking PostMessage, but I haven't tried it) to get the information from all TextOut calls.
0
 
LVL 1

Author Comment

by:scottmichael2
ID: 18022084
At first glance this looks like exactly what I need.  Unfortunately, I don't have the time to try it since I've been placed on another (VB) project.  Once I get back to this project I'll start another thread if I have any additional questions.  Thanks very much for your effort on it!  (I can't wait to get back to it and try it!!)  
0
 
LVL 6

Expert Comment

by:SeanDurkin
ID: 18022110
No problem, and looking back on your question it looks as though you were having more trouble with interprocess communication, and I'm definitely going to look into PostMessage() to see if it will work in sending stuff such as character arrays.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 1

Expert Comment

by:nksaxena
ID: 25614131
Great Job SeanDurkin,

I would appreciate if you could provide the implementation of setHooks() to hook GDI textout API.
Thanks,
0
 
LVL 6

Expert Comment

by:SeanDurkin
ID: 25616114
It's your lucky day, I still have the old code that I was working on :) This was the code I used for a keylogger detector I was writing back then, and I hooked five different functions with it. It's not the best looking code and probably can't be used straight out of context, but you'll probably be able to figure out how to tweak it.

I haven't done anything related to hooking for a couple years now, so I figure that I can now release my code for hooking the import and export address tables. If you don't know what those are, I suggest you look into Matt Pietrek's articles on the Portable Executable format.

PROC setHooks()
{
	HMODULE psapi = LoadLibrary("psapi.dll");
	HMODULE user32 = LoadLibrary("user32.dll");
	
	/* initialize all the original functions to be hooked */
	origSWHE = (SWHEPROC) GetProcAddress(user32, "SetWindowsHookExA");
	origUWHE = (UWHEPROC) GetProcAddress(user32, "UnhookWindowsHookEx");
	origGKS = (GKSPROC) GetProcAddress(user32, "GetKeyState");
	origGAKS = (GAKSPROC) GetProcAddress(user32, "GetAsyncKeyState");
	origGKBS = (GKBSPROC) GetProcAddress(user32, "GetKeyboardState");
	
	HMODULE hMods[1024];
	DWORD cbNeeded;
	enumPROC = (ENUMPROCS) GetProcAddress(psapi, "EnumProcesses");
	epm = (PFNENUMPROCESSMODULES) GetProcAddress(psapi, "EnumProcessModules");
	hProcess = GetCurrentProcess();
	
	if(epm(hProcess, hMods, sizeof(hMods), &cbNeeded))
	{
		patchEAT(user32, (PROC) origSWHE, (PROC) MySetWindowsHookEx);
		patchEAT(user32, (PROC) origUWHE, (PROC) MyUnhookWindowsHookEx);
		patchEAT(user32, (PROC) origGKS, (PROC) MyGetKeyState);
		patchEAT(user32, (PROC) origGAKS, (PROC) MyGetAsyncKeyState);
		patchEAT(user32, (PROC) origGKBS, (PROC) MyGetKeyboardState);
		
		for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); ++i)
		{
			if(hMods[i] != g_hMod)
			{
				patchIAT(hMods[i], (PROC) origSWHE, (PROC) MySetWindowsHookEx);
				patchIAT(hMods[i], (PROC) origUWHE, (PROC) MyUnhookWindowsHookEx);
				patchIAT(hMods[i], (PROC) origGKS, (PROC) MyGetKeyState);
				patchIAT(hMods[i], (PROC) origGAKS, (PROC) MyGetAsyncKeyState);
				patchIAT(hMods[i], (PROC) origGKBS, (PROC) MyGetKeyboardState);
			}
		}
	}
}
 
DWORD WINAPI patchEAT(HMODULE hMod, PROC origFunc, PROC newFunc)
{
	PIMAGE_DOS_HEADER pDosH;
	PIMAGE_NT_HEADERS pNTH;
	PIMAGE_EXPORT_DIRECTORY pExportDir;
	
	pDosH = (PIMAGE_DOS_HEADER) hMod;
	pNTH = (PIMAGE_NT_HEADERS) ((DWORD) pDosH + (DWORD) pDosH->e_lfanew);
	pExportDir = (PIMAGE_EXPORT_DIRECTORY) ((DWORD) pDosH + 
		(DWORD) (pNTH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
	
	ULONG *arrSymbols = (ULONG *) ((DWORD) pDosH + pExportDir->AddressOfFunctions);
	for(int i = 0; i < pExportDir->NumberOfFunctions; ++i)
	{
		DWORD *pFuncRVA = (DWORD *) &arrSymbols[i];
		DWORD origRVA = (DWORD) origFunc - (DWORD) pDosH;
		if(*pFuncRVA == origRVA)
		{
			MEMORY_BASIC_INFORMATION mbi;
			DWORD oldProt;
			VirtualQuery(pFuncRVA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
			VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &oldProt);
			*pFuncRVA = (DWORD) newFunc - (DWORD) pDosH;
			VirtualProtect(mbi.BaseAddress, mbi.RegionSize, oldProt, &oldProt);
			
			break;
		}
	}
	
	return 0;
}
 
DWORD WINAPI patchIAT(HMODULE hMod, PROC origFunc, PROC newFunc)
{
	PIMAGE_DOS_HEADER pDosH;
	PIMAGE_NT_HEADERS pNTH;
	PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
	PIMAGE_EXPORT_DIRECTORY pExportDir;
	PIMAGE_THUNK_DATA pThunk;
	
	if(!newFunc || !hMod || hMod == g_hMod)
		return 0;
	
	// Verify that the newFunc is valid
	if(IsBadCodePtr(newFunc))
		return 0;
	
	// Get DOS Header
	pDosH = (PIMAGE_DOS_HEADER) hMod;
	
	// Verify that the PE is valid by checking e_magic's value and DOS Header size
	if(IsBadReadPtr(pDosH, sizeof(IMAGE_DOS_HEADER)))
		return 0;
	
	if(pDosH->e_magic != IMAGE_DOS_SIGNATURE)
		return 0;
	
	// Find the NT Header by using the offset of e_lfanew value from hMod
	pNTH = (PIMAGE_NT_HEADERS) ((DWORD) pDosH + (DWORD) pDosH->e_lfanew);
	
	// Verify that the NT Header is correct
	if(IsBadReadPtr(pNTH, sizeof(IMAGE_NT_HEADERS)))
		return 0;
	
	if(pNTH->Signature != IMAGE_NT_SIGNATURE)
		return 0;
	
	
	// iat patching
	pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD) pDosH + 
		(DWORD) (pNTH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
	
	if(pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR) pNTH)
		return 0;
	
	while(pImportDesc->Name)
	{
		// pImportDesc->Name gives the name of the module, so we can find "user32.dll"
		char *name = (char *) ((DWORD) pDosH + (DWORD) (pImportDesc->Name));
		// stricmp returns 0 if strings are equal, case insensitive
		if(_stricmp(name, "user32.dll") == 0)
		{
			pThunk = (PIMAGE_THUNK_DATA)((DWORD) pDosH + (DWORD) pImportDesc->FirstThunk);
			while(pThunk->u1.Function)
			{
				// get the pointer of the imported function and see if it matches up with the original 
				if((DWORD) pThunk->u1.Function == (DWORD) origFunc)
				{
					MEMORY_BASIC_INFORMATION mbi;
					DWORD oldProt;
					VirtualQuery(&pThunk->u1.Function, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
					VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &oldProt);
					pThunk->u1.Function = (DWORD) newFunc;
					VirtualProtect(mbi.BaseAddress, mbi.RegionSize, oldProt, &oldProt);
					break;
				}
				else
				{
					++pThunk;
				}	
			}
		}
		
		++pImportDesc;
	}
	
	return 0;	
}

Open in new window

0
 
LVL 1

Expert Comment

by:nksaxena
ID: 25631542
Dear SeanDurkin,

Thanks for sharing this code, I am able to use this to hook TextOutW API and It works great.
However, It was crashing so I had to put Sleep while enumerating to avoid crash in the dll. (please see the code snippet) I don't understand why some delay here is needed. Even if I remove one of sleep, it start crashing randomly. If i don't use Sleep at all, target application crashes in the DLL as soon as it is injected.

Now the side effect, it would affect the target application performance. even it is running as a thread.
Do you have an idea why it need some time delay?

Secondly, I noticed that hooking operation (patchIAT) takes place several times, even after minutes, why does it happen, i need to know. I hope you can help.


// get the pointer of the imported function and see if it matches up with the original 
if((DWORD) pThunk->u1.Function == (DWORD) origFunc)
{
	Sleep(25); // <<<<<<<<<<<<<< HERE
	MEMORY_BASIC_INFORMATION mbi;
	DWORD oldProt;
	VirtualQuery(&pThunk->u1.Function, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
	VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &oldProt);
	pThunk->u1.Function = (DWORD) newFunc;
	VirtualProtect(mbi.BaseAddress, mbi.RegionSize, oldProt, &oldProt);
	break;
}
else
{
	Sleep(25); // <<<<<<<<<<<<<< and HERE
	++pThunk;
}	

Open in new window

0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Introduction This article is a continuation of the C/C++ Visual Studio Express debugger series. Part 1 provided a quick start guide in using the debugger. Part 2 focused on additional topics in breakpoints. As your assignments become a little more …
C++ Properties One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property (http://www.experts-exchange.com/Programming/Languages/CPP/A_3912-Object-Properties-in-C.ht…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

752 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