Solved

GDI TextOut Hook

Posted on 2006-11-22
6
3,979 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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

 
LVL 1

Expert Comment

by:nksaxena
Comment Utility
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
Comment Utility
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
Comment Utility
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

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

Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, wh…
IntroductionThis article is the second in a three part article series on the Visual Studio 2008 Debugger.  It provides tips in setting and using breakpoints. If not familiar with this debugger, you can find a basic introduction in the EE article loc…
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
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…

772 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

10 Experts available now in Live!

Get 1:1 Help Now