Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

GDI TextOut Hook

Posted on 2006-11-22
6
Medium Priority
?
4,278 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 2000 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
Industry Leaders: 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

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

Question has a verified solution.

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

In days of old, returning something by value from a function in C++ was necessarily avoided because it would, invariably, involve one or even two copies of the object being created and potentially costly calls to a copy-constructor and destructor. A…
  Included as part of the C++ Standard Template Library (STL) is a collection of generic containers. Each of these containers serves a different purpose and has different pros and cons. It is often difficult to decide which container to use and …
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 clear a vector as well as how to detect empty vectors in C++.

688 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