Click here to Skip to main content
15,884,099 members
Articles / Desktop Programming / MFC

API hooking revealed

Rate me:
Please Sign up or sign in to vote.
4.94/5 (322 votes)
2 Dec 2002CPOL38 min read 2.8M   64.1K   1.3K  
The article demonstrates how to build a user mode Win32 API spying system
//---------------------------------------------------------------------------
// ModuleInstance.cpp
//
// AUTHOR:		Ivo Ivanov (ivopi@hotmail.com)
// ver 1.02 
// 05 September 2001
//---------------------------------------------------------------------------
#include "Common.h"
#include "ModuleInstance.h"
#include "SysUtils.h"

//---------------------------------------------------------------------------
//
// class CElements
//
//---------------------------------------------------------------------------
CElements::CElements():
	CModuleList()
{
	
}

CElements::~CElements()
{
	
}
//---------------------------------------------------------------------------
//
// class CLibHandler
//
//---------------------------------------------------------------------------
CLibHandler::CLibHandler(CRunningProcesses* pProcesses):
	m_pProcesses(pProcesses)
{

}

CLibHandler::~CLibHandler()
{

}


//---------------------------------------------------------------------------
//
// class CTaskManager
//
//---------------------------------------------------------------------------
CTaskManager::CTaskManager():
	m_pLibHandler(NULL)
{
	m_pProcesses = new CRunningProcesses();

	if (IsPsapiSupported())
		m_pLibHandler = new CPsapiHandler(m_pProcesses);
	else
	if (IsToolHelpSupported())
		m_pLibHandler = new CToolhelpHandler(m_pProcesses);
}

CTaskManager::~CTaskManager()
{
	delete m_pLibHandler;
	delete m_pProcesses;
}

//---------------------------------------------------------------------------
// Populate
//
// Populate the process and modules list using PSAPI or ToolHlp32
//---------------------------------------------------------------------------
BOOL CTaskManager::Populate(BOOL bPopulateModules)
{
	m_pProcesses->ReleaseAll();
	return m_pLibHandler->PopulateProcesses(bPopulateModules); 
}


//---------------------------------------------------------------------------
// PopulateProcess
//
// Populate the module list using PSAPI or ToolHlp32
//---------------------------------------------------------------------------
BOOL CTaskManager::PopulateProcess(DWORD dwProcessId, BOOL bPopulateModules)
{
	m_pProcesses->ReleaseAll();
	return m_pLibHandler->PopulateProcess(dwProcessId, bPopulateModules); 
}


//---------------------------------------------------------------------------
// GetProcessCount
//
// Returns a number of currently loaded processes
//---------------------------------------------------------------------------
DWORD CTaskManager::GetProcessCount() const
{
	return m_pProcesses->GetCount();
}


//---------------------------------------------------------------------------
// GetProcessByIndex
//
// Returns a process from the container
//---------------------------------------------------------------------------
CExeModuleInstance* CTaskManager::GetProcessByIndex(DWORD dwIndex)
{
	return static_cast<CExeModuleInstance*>(m_pProcesses->GetModule(dwIndex));
}

//---------------------------------------------------------------------------
// GetProcessById
//
// Returns a process by its ID
//---------------------------------------------------------------------------
CExeModuleInstance* CTaskManager::GetProcessById(DWORD dwProcessId)
{
	return static_cast<CExeModuleInstance*>
		(m_pProcesses->GetProcessById(dwProcessId));
}

//---------------------------------------------------------------------------
//
// class CLoadedModules
//
//---------------------------------------------------------------------------
CLoadedModules::CLoadedModules(DWORD dwProcessId):
	CElements(),
	m_dwProcessId(dwProcessId)
{
	
}

CLoadedModules::~CLoadedModules()
{
	
}

//---------------------------------------------------------------------------
//
// class CRunningProcesses
//
//---------------------------------------------------------------------------
CRunningProcesses::CRunningProcesses():
	CElements()
{

}

CRunningProcesses::~CRunningProcesses()
{

}

CExeModuleInstance* CRunningProcesses::GetProcessById(DWORD dwProcessId)
{
	CExeModuleInstance* pResult = NULL;
	CExeModuleInstance* pProcess;

	for (long i = 0; i < GetCount(); i++)
	{
		pProcess = static_cast<CExeModuleInstance*>( GetModule(i) );
		if (pProcess->Get_ProcessId() == dwProcessId)
		{
			pResult = pProcess;
			break;
		} // if
	} // for
	return pResult;
}

//---------------------------------------------------------------------------
//
// class CPsapiHandler
//
//---------------------------------------------------------------------------
CPsapiHandler::CPsapiHandler(CRunningProcesses* pProcesses):
	CLibHandler(pProcesses),
	m_hModPSAPI(NULL),
    m_pfnEnumProcesses(NULL),
    m_pfnEnumProcessModules(NULL),
    m_pfnGetModuleFileNameExA(NULL)
{
	
}

CPsapiHandler::~CPsapiHandler()
{
	Finalize();
}

//---------------------------------------------------------------------------
// Initialize
//
//---------------------------------------------------------------------------
BOOL CPsapiHandler::Initialize()
{
	BOOL bResult = FALSE;
    //
    // Get to the 3 functions in PSAPI.DLL dynamically.  We can't
    // be sure that PSAPI.DLL has been installed
    //
    if (NULL == m_hModPSAPI)
        m_hModPSAPI = ::LoadLibraryA("PSAPI.DLL");

    if (NULL != m_hModPSAPI)
	{
		m_pfnEnumProcesses = (PFNENUMPROCESSES)
			::GetProcAddress(m_hModPSAPI,"EnumProcesses");

		m_pfnEnumProcessModules = (PFNENUMPROCESSMODULES)
			::GetProcAddress(m_hModPSAPI, "EnumProcessModules");

		m_pfnGetModuleFileNameExA = (PFNGETMODULEFILENAMEEXA)
			::GetProcAddress(m_hModPSAPI, "GetModuleFileNameExA");

	}
	bResult = 
		m_pfnEnumProcesses
		&&  m_pfnEnumProcessModules
		&&  m_pfnGetModuleFileNameExA;

	return bResult;
}

//---------------------------------------------------------------------------
// Finalize
//
//---------------------------------------------------------------------------
void CPsapiHandler::Finalize()
{
	if (NULL != m_hModPSAPI)
		::FreeLibrary(m_hModPSAPI);
}

//---------------------------------------------------------------------------
// PopulateModules
//
// Populate the module list using PSAPI
//---------------------------------------------------------------------------
BOOL CPsapiHandler::PopulateModules(CModuleInstance* pProcess)
{
	BOOL   bResult = TRUE;
	CModuleInstance  *pDllModuleInstance = NULL;

    if (TRUE == Initialize())
	{
		DWORD pidArray[1024];
		DWORD cbNeeded;
		DWORD nProcesses;
		// EnumProcesses returns an array with process IDs
		if (m_pfnEnumProcesses(pidArray, sizeof(pidArray), &cbNeeded))
		{
			// Determine number of processes
			nProcesses = cbNeeded / sizeof(DWORD);  
			// Release the container
			pProcess->ReleaseModules();

			for (DWORD i = 0; i < nProcesses; i++)
			{
				HMODULE hModuleArray[1024];
				HANDLE  hProcess;
				DWORD   pid = pidArray[i];
				DWORD   nModules;
				// Let's open the process
				hProcess = ::OpenProcess(
					PROCESS_QUERY_INFORMATION |	PROCESS_VM_READ,
					FALSE, pid);

				if (!hProcess)
					continue;

				if (static_cast<CExeModuleInstance*>(pProcess)->Get_ProcessId() != pid)
				{
					::CloseHandle(hProcess);
					continue;
				}

				// EnumProcessModules function retrieves a handle for 
				// each module in the specified process. 
				if (!m_pfnEnumProcessModules(
						hProcess, hModuleArray,
						sizeof(hModuleArray), &cbNeeded))
				{
					::CloseHandle(hProcess);
					continue;
				}

				// Calculate number of modules in the process                                   
				nModules = cbNeeded / sizeof(hModuleArray[0]);
				for (DWORD j = 0; j < nModules; j++)
				{
					HMODULE hModule = hModuleArray[j];
					char    szModuleName[MAX_PATH];
					m_pfnGetModuleFileNameExA(
						hProcess, 
						hModule,
						szModuleName, 
						sizeof(szModuleName)
						);

					if (0 == j)   // First module is the EXE.  
					{
						// Do nothing.
					} // if
					else    // Not the first module.  It's a DLL
					{
						pDllModuleInstance = new CModuleInstance(
							szModuleName, 
							hModule
							);
						pProcess->AddModule(pDllModuleInstance);
					} // else
				} // for
				::CloseHandle(hProcess);    // We're done with this process handle
			} // for
			bResult = TRUE;
		} // if
		else
		{
			bResult = FALSE;
		}
	} // if 
	else
	{
		bResult = FALSE;
	}
    return bResult;
}


//---------------------------------------------------------------------------
// PopulateProcesses
//
// Populate the process list using PSAPI
//---------------------------------------------------------------------------

BOOL CPsapiHandler::PopulateProcesses(BOOL bPopulateModules)
{
	BOOL   bResult = TRUE;
	CExeModuleInstance* pProcessInfo;
	char    szModuleName[MAX_PATH];
    
    if (TRUE == Initialize())
	{
		DWORD pidArray[1024];
		DWORD cbNeeded;
		DWORD nProcesses;
      
		if (m_pfnEnumProcesses(pidArray, sizeof(pidArray), &cbNeeded))
		{
			// Determine number of processes
			nProcesses = cbNeeded / sizeof(DWORD);  
			m_pProcesses->ReleaseAll();
			for (DWORD i = 0; i < nProcesses; i++)
			{
				HMODULE hModuleArray[1024];
				HANDLE hProcess;
				DWORD pid = pidArray[i];
				DWORD nModules;
				hProcess = ::OpenProcess(
					PROCESS_QUERY_INFORMATION |	PROCESS_VM_READ,
					FALSE, 
					pid
					);
				if (!hProcess)
					continue;

				if (!m_pfnEnumProcessModules(hProcess, hModuleArray,
											sizeof(hModuleArray), &cbNeeded))
				{
					::CloseHandle(hProcess);
					continue;
				}
				// Calculate number of modules in the process                                   
				nModules = cbNeeded / sizeof(hModuleArray[0]);

				for (DWORD j = 0; j < nModules; j++)
				{
					HMODULE hModule = hModuleArray[j];

					m_pfnGetModuleFileNameExA(
						hProcess, 
						hModule,
						szModuleName, 
						sizeof(szModuleName)
						);

					if (0 == j)   // First module is the EXE.  Just add it to the map
					{
						pProcessInfo = new CExeModuleInstance(
							this,
							szModuleName, 
							hModule, 
							pid
							);
						m_pProcesses->Add(*pProcessInfo);
						if (bPopulateModules)
							pProcessInfo->PopulateModules();
						break;
					} // if
				} // for
				::CloseHandle(hProcess);    
			} // for
   
			bResult = TRUE;
		} // if
		else
		{
			bResult = FALSE;
		}
	} // if 
	else
	{
		bResult = FALSE;
	}
   
    return bResult;
}	

//---------------------------------------------------------------------------
// PopulateProcess
//
// Populate all modules of a single process
//
//---------------------------------------------------------------------------
BOOL CPsapiHandler::PopulateProcess(DWORD dwProcessId, BOOL bPopulateModules)
{
	BOOL   bResult = TRUE;
	CExeModuleInstance* pProcessInfo;
    
    if (TRUE == Initialize())
	{
		m_pProcesses->ReleaseAll();
		HMODULE hModuleArray[1024];
		HANDLE  hProcess;
		DWORD   nModules;
		DWORD   cbNeeded;
		hProcess = ::OpenProcess(
			PROCESS_QUERY_INFORMATION |	PROCESS_VM_READ,
			FALSE, 
			dwProcessId
			);
		if (hProcess) 
		{
			if (!m_pfnEnumProcessModules(
				hProcess, 
				hModuleArray,
				sizeof(hModuleArray), 
				&cbNeeded
				))
				::CloseHandle(hProcess);
			else
			{
				// Calculate number of modules in the process                                   
				nModules = cbNeeded / sizeof(hModuleArray[0]);

				for (DWORD j = 0; j < nModules; j++)
				{
					HMODULE hModule = hModuleArray[j];
					char    szModuleName[MAX_PATH];

					m_pfnGetModuleFileNameExA(
						hProcess, 
						hModule,
						szModuleName, 
						sizeof(szModuleName)
						);

					if (0 == j)   // First module is the EXE.  Just add it to the map
					{
						pProcessInfo = new CExeModuleInstance(
							this,
							szModuleName, 
							hModule, 
							dwProcessId
							);
						m_pProcesses->Add(*pProcessInfo);
						if (bPopulateModules)
							pProcessInfo->PopulateModules();
						break;
					} // if
				} // for
				::CloseHandle(hProcess);    
			} // if
		} // if
	} // if 
	else
	{
		bResult = FALSE;
	}
    return bResult;
}

//---------------------------------------------------------------------------
//
// class CToolhelpHandler
//
//---------------------------------------------------------------------------
CToolhelpHandler::CToolhelpHandler(CRunningProcesses* pProcesses):
	CLibHandler(pProcesses)
{
	
}

CToolhelpHandler::~CToolhelpHandler()
{
	
}


//---------------------------------------------------------------------------
// Initialize
//
//---------------------------------------------------------------------------
BOOL CToolhelpHandler::Initialize()
{
	BOOL           bResult = FALSE;
	HINSTANCE      hInstLib;

	hInstLib = ::LoadLibraryA("Kernel32.DLL");
	if (NULL != hInstLib)
	{
		//
		// We must link to these functions of Kernel32.DLL explicitly. Otherwise 
		// a module using this code would fail to load under Windows NT, which does not 
		// have the Toolhelp32 functions in the Kernel32.
		//
		m_pfnCreateToolhelp32Snapshot = (PFNCREATETOOLHELP32SNAPSHOT) 
			::GetProcAddress(hInstLib, "CreateToolhelp32Snapshot");
		m_pfnProcess32First = (PFNPROCESS32FIRST)
			::GetProcAddress(hInstLib, "Process32First");
		m_pfnProcess32Next = (PFNPROCESS32NEXT)
			::GetProcAddress(hInstLib, "Process32Next");
		m_pfnModule32First = (PFNMODULE32FIRST)
			::GetProcAddress(hInstLib, "Module32First");
		m_pfnModule32Next = (PFNMODULE32NEXT)
			::GetProcAddress(hInstLib, "Module32Next");

		::FreeLibrary( hInstLib );

		bResult = m_pfnCreateToolhelp32Snapshot &&
			      m_pfnProcess32First &&
				  m_pfnProcess32Next &&
				  m_pfnModule32First &&
				  m_pfnModule32Next;
	} // if


	return bResult;
}

//---------------------------------------------------------------------------
// PopulateModules
//
// Populate the module list using ToolHelp32
//---------------------------------------------------------------------------
BOOL CToolhelpHandler::PopulateModules(CModuleInstance* pProcess)
{
	BOOL   bResult = TRUE;
	CModuleInstance  *pDllModuleInstance = NULL;
	HANDLE hSnapshot = INVALID_HANDLE_VALUE;

	hSnapshot = m_pfnCreateToolhelp32Snapshot(
		TH32CS_SNAPMODULE, 
		static_cast<CExeModuleInstance*>(pProcess)->Get_ProcessId());

	MODULEENTRY32 me = { sizeof(me) };

	for (BOOL bOk = ModuleFirst(hSnapshot, &me); bOk; bOk = ModuleNext(hSnapshot, &me)) 
	{	
		// We don't need to add to the list the process itself.
		// The module list should keep references to DLLs only
		if (0 != stricmp(pProcess->GetBaseName(), me.szModule))  
		{
			pDllModuleInstance = new CModuleInstance(me.szExePath, me.hModule);
			pProcess->AddModule(pDllModuleInstance);
		}
		else
		//
		// However, we should fix up the module of the EXE, because
		// th32ModuleID member has meaning only to the tool help functions
		// and it is not usable by Win32 API elements. 
		//
		{
			pProcess->Set_Module( me.hModule );
		}
	} // for

	if (hSnapshot != INVALID_HANDLE_VALUE)
		::CloseHandle(hSnapshot);

	return bResult;
}

BOOL CToolhelpHandler::ModuleFirst(HANDLE hSnapshot, PMODULEENTRY32 pme) const
{
	return m_pfnModule32First(hSnapshot, pme);
}

BOOL CToolhelpHandler::ModuleNext(HANDLE hSnapshot, PMODULEENTRY32 pme) const
{
	return m_pfnModule32Next(hSnapshot, pme);
}


BOOL CToolhelpHandler::ProcessFirst(HANDLE hSnapshot, PROCESSENTRY32* pe32) const
{
	return m_pfnProcess32First(hSnapshot, pe32);
}

BOOL CToolhelpHandler::ProcessNext(HANDLE hSnapshot, PROCESSENTRY32* pe32) const
{
	return m_pfnProcess32Next(hSnapshot, pe32);
}

//---------------------------------------------------------------------------
// PopulateProcesses
//
// Populate the process list using Toolhelp
//---------------------------------------------------------------------------

BOOL CToolhelpHandler::PopulateProcesses(BOOL bPopulateModules)
{
	return PopulateProcess(NULL, bPopulateModules);
}	

//---------------------------------------------------------------------------
// PopulateProcess
//
// Populate all modules of a single process
//
//---------------------------------------------------------------------------
BOOL CToolhelpHandler::PopulateProcess(DWORD dwProcessId, BOOL bPopulateModules)
{
	BOOL   bResult    = FALSE;
	CExeModuleInstance* pProcessInfo;
	HANDLE hSnapshot  = INVALID_HANDLE_VALUE;

	if (TRUE == Initialize())
	{
		hSnapshot = m_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, dwProcessId);

		PROCESSENTRY32 pe32 = { sizeof(pe32) };

		for (BOOL bOk = ProcessFirst(hSnapshot, &pe32); bOk; bOk = ProcessNext(hSnapshot, &pe32)) 
		{
			if ( (dwProcessId != NULL) && (dwProcessId != pe32.th32ProcessID) )
				continue;

			pProcessInfo = new CExeModuleInstance(
				this,
				pe32.szExeFile, 
				NULL,                // We will fix up later this value
				pe32.th32ProcessID
				);
			m_pProcesses->Add(*pProcessInfo);
			if (bPopulateModules)
				pProcessInfo->PopulateModules();

			if (dwProcessId != NULL)
				break;
		} // for

		if (hSnapshot != INVALID_HANDLE_VALUE)
			::CloseHandle(hSnapshot);

		bResult = TRUE;
	}

	return bResult;
}


//---------------------------------------------------------------------------
//
// class CModuleList
//
//---------------------------------------------------------------------------

CModuleList::CModuleList()
{

}

CModuleList::~CModuleList()
{
	ReleaseAll();
}

//---------------------------------------------------------------------------
// Add
//
// Add new object to the container
//---------------------------------------------------------------------------
void CModuleList::Add(CModuleInstance &moduleInstance)
{
	push_back(&moduleInstance);
}

//---------------------------------------------------------------------------
// ReleaseAll
//
// Release all objects and clear the list
//---------------------------------------------------------------------------
void CModuleList::ReleaseAll()
{
	CModuleList::iterator itr;
	CModuleInstance *pModuleInstance;

	for (itr = begin(); itr != end(); ++itr)
	{
		pModuleInstance = *itr;
		delete pModuleInstance;
	} // for
	clear();
}

//---------------------------------------------------------------------------
// GetModule
//
// Return a module object by its index (Pascal-like style)
//---------------------------------------------------------------------------
CModuleInstance* CModuleList::GetModule(DWORD dwIndex) const
{
	return at(dwIndex);	
}

//---------------------------------------------------------------------------
// GetCount
//
// Return number of items in the container
//---------------------------------------------------------------------------
DWORD CModuleList::GetCount() const
{
	return size();
}

//---------------------------------------------------------------------------
//
// class CModuleInstance
//
//---------------------------------------------------------------------------
CModuleInstance::CModuleInstance(char *pszName, HMODULE hModule):
	m_pszName(NULL),
    m_hModule(NULL),
	m_pInternalList(NULL)
{
	Set_Name(pszName);
	Set_Module(hModule);
	
}

CModuleInstance::~CModuleInstance()
{
	delete m_pInternalList;

	if (NULL != m_pszName)
		delete [] m_pszName;	
}


void CModuleInstance::AddModule(CModuleInstance* pModuleInstance)
{
	if (NULL == m_pInternalList)
		m_pInternalList = new CModuleList();
	m_pInternalList->Add(*pModuleInstance);
}

void CModuleInstance::ReleaseModules()
{
	if (NULL != m_pInternalList)
		m_pInternalList->ReleaseAll();
}

char* CModuleInstance::Get_Name() const
{
	return m_pszName;
}

char* CModuleInstance::GetBaseName() const
{
	char *pdest;
	int  ch = '\\';
	// Search backward 
	pdest = strrchr(m_pszName, ch);
	if(pdest != NULL)
		return &pdest[1];
	else
		return m_pszName;
}

void CModuleInstance::Set_Name(char *pszName)
{
	if (NULL != m_pszName)
		delete [] m_pszName;	
	if ((NULL != pszName) && (strlen(pszName)))
	{
		m_pszName = new char[strlen(pszName) + 1];
		strcpy(m_pszName, pszName);
	}
	else
	{
		m_pszName = new char[strlen("\0") + 1];
		strcpy(m_pszName, "\0");
	}

}

HMODULE CModuleInstance::Get_Module() const
{
	return m_hModule;
}

void CModuleInstance::Set_Module(HMODULE module)
{
	m_hModule = module;
}


//---------------------------------------------------------------------------
//
// class CExeModuleInstance
//
//---------------------------------------------------------------------------

CExeModuleInstance::CExeModuleInstance(
	CLibHandler* pLibHandler,
	char*        pszName, 
	HMODULE      hModule, 
	DWORD        dwProcessId
	):
	CModuleInstance(pszName, hModule),
	m_pLibHandler(pLibHandler),
	m_dwProcessId(dwProcessId)
{
	
}

CExeModuleInstance::~CExeModuleInstance()
{
	
}

DWORD CExeModuleInstance::Get_ProcessId() const
{
	return m_dwProcessId;
}

BOOL CExeModuleInstance::PopulateModules()
{
	return m_pLibHandler->PopulateModules(this);
}


DWORD CExeModuleInstance::GetModuleCount()
{
	return m_pInternalList ? m_pInternalList->GetCount() : 0;
}

CModuleInstance* CExeModuleInstance::GetModuleByIndex(DWORD dwIndex)
{
	return m_pInternalList ? m_pInternalList->GetModule(dwIndex) : NULL;
}

//----------------------------End of the file -------------------------------

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I specialize in OS Internals and Reverse Engineering.
Before joining my current employer I used to run a security blog for malware analysis: http://vinsula.com/security-blog

Comments and Discussions