Solved

GDI TextOut Hook

Posted on 2006-11-22
6
4,012 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
  • 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
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 
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

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

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

Suggested Solutions

Written by John Humphreys C++ Threading and the POSIX Library This article will cover the basic information that you need to know in order to make use of the POSIX threading library available for C and C++ on UNIX and most Linux systems.   [s…
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
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.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

920 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

16 Experts available now in Live!

Get 1:1 Help Now