该Windows文件监控系统旨在为Windows环境中的文件提供安全性。我需要设计一个应用程序来监视Windows上的文件打开、关闭和保存操作,并限制用户在安装此实用程序之前访问文件类型的子集。这是通过连接Windows文件相关api,然后根据需要在Windows中对文件进行打开、保存和关闭操作的预处理来实现的。预处理可能是对文件进行加密,破坏文件的标题部分等。如果在系统上安装了这个实用程序,那么文件(最初是加密的格式)在打开之前首先解密,然后在关闭或保存文件时再次加密。这将有助于防止在未安装此实用程序的任何其他地方访问该文件,从而为该文件提供安全性。
我发现一些很好的应用程序,如AvaFind、FileMon等,可以执行文件监控功能。但是,这些应用程序使用系统驱动程序来实现它们的目标。由于编写驱动程序涉及的复杂性,我试图通过Win32用户级编程实现同样的目标。我通过连接kernel32.dll的CreateProcess()、OpenProcess()、CreateFile()、CloseHandle()和WriteFile()函数实现了这一点。
在挂钩Windows api的各种可用方法中,我选择了“通过更改系统上运行的所有进程的导入地址表(Import Address Table, IAT)来挂钩”的方法。通过使用Windows API CreateRemoteThread()在目标进程的地址空间中创建远程线程,将包含更改IAT代码的DLL注入目标进程的地址空间(DLL将被注入的进程)。
Jeffrey Richter的文章“使用INJLIB将32位DLL加载到另一个进程的地址空间”很好地记录了远程线程注入DLL。到目前为止,注入DLL一直是使用最广泛的概念,一旦DLL位于其地址空间内,注入DLL的最大优点是可以控制进程。但是,这种方法有一个缺点,在kernel32的情况下DLL没有被注入到目标进程中。dll不会在其众所周知的首选加载地址加载。因此,注入DLL的方法是基于唯一的假设,即Kernel32的加载地址。dll对这两个进程保持相同。
回到最初的问题……
我执行的第一步是创建HookAPI。dll,其中包含挂钩Windows api的代码,然后将该dll注入到系统上所有运行的进程中。一旦注入目标进程,HookAPI。dll改变进程的IAT及其所有加载模块。HookAPI。dll包含一个名为GetIAList()的函数,该函数遍历注入它的进程的IAT。它使用EnumProcessModules()获取注入它的进程的所有模块的列表。然后,它检查要挂起哪个函数,并将其在IAT中的地址替换为为该API提供的包装器函数的地址。GetNewAddress()函数遍历指定要挂起的函数的列表,并返回为要挂起的函数提供的包装器函数的地址。
以下是替换周期的逻辑步骤:
从进程DLL模块加载的每个进程的IAT中找到import部分,以及进程本身。
找到导出该函数的DLL的IMAGE_IMPORT_DESCRIPTOR块。
找到保存导入函数的原始地址的IMAGE_THUNK_DATA。
将函数地址替换为包装器函数的地址。
void GetIAList()
{
HMODULE hMods[1024];
TCHAR szLibFile[MAX_PATH];
HANDLE hProcess = GetCurrentProcess();
HMODULE hModule = GetModuleHandle(TEXT("HookAPI.dll"));
GetModuleFileName(hModule,szLibFile,sizeof(szLibFile));
DWORD cbNeeded = 0;
multimap m_mapOld;
multimap :: iterator m_AcIter;
PROC pfnNewAddress = NULL;
unsigned int i = 0;
PROC* ppfn = NULL;
CString str;
ULONG ulSize = 0;
if( EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
{
for ( i = 0; i < (cbNeeded / sizeof(HMODULE)); i++ )
{
TCHAR szModName[MAX_PATH];
// Get the full path to the module's file.
if ( GetModuleFileNameEx( hProcess, hMods[i],
szModName, sizeof(szModName)))
{
// We must skip the IAT of HookAPI.dll
// from being modified as it contains
// the wrapper functions for Windows AOIs being hooked.
if(_tcscmp(szModName,szLibFile) == 0)
{
i++;
}
}
// 获取模块文件的完整路径。
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
ImageDirectoryEntryToData(hMods[i], TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
if(NULL != pImportDesc)
{
while (pImportDesc->Name)
{
PSTR pszModName = (PSTR)((PBYTE) hMods[i] + pImportDesc->Name);
CString strModName = pszModName;
// 调用者的IAT
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
( (PBYTE) hMods[i] + pImportDesc->FirstThunk );
while (pThunk->u1.AddressOfData)
{
// 获取函数地址的地址
ppfn = (PROC*) &pThunk->u1.AddressOfData;
str.Format(_T("%s:%x"),strModName,*ppfn);
// 从IAT存储函数的dll名称和地址
// into a map in the form ("KERNEL32.dll:",
// ).
// The map contains the entries in the form
// ("KERNEL32.dll:0x110023",0x707462)
// ("KERNEL32.dll:0x110045",0x707234)
// ("KERNEL32.dll:0x110074",0x402462)
// ...
m_mapOld.insert( func_Pair( str, ppfn ) );
pThunk++;
}
pImportDesc++;
}
}
}
}
// Traverse the map to hook the appropriate function.
for(m_AcIter = m_mapOld.begin() ; m_AcIter != m_mapOld.end() ; m_AcIter++)
{
// pfnNewAddress = GetNewAddress(m_AcIter -> first);
// m_AcIter -> first is a string that gives all the informatino
// about the Windows API to be hooked like the name of the DLL
// in which the function is actually present, its address in that DLL
// and its address in IAT.
// GetNewAddress should be implemented in such a way that it should return
// the address of the wrapper function for Windows API implemented in HookAPI.dll
if(pfnNewAddress != NULL)
{
PROC* pfnOldAddress = (PROC*)m_AcIter -> second;
MEMORY_BASIC_INFORMATION mbi = {0};
VirtualQuery( pfnOldAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION) );
VirtualProtect( mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,
&mbi.Protect);
// Replace the origional address of API with the address of corresponding
// wrapper function
*pfnOldAddress = *pfnNewAddress;
DWORD dwOldProtect = 0;
VirtualProtect( mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect );
}
}
}
设计好DLL之后,下一个任务是创建注入器。它注入挂钩DLL,挂钩api。dll,在所有运行的进程中,通过使用CreateRemoteThread(),如下所示。InjectIntoExistingProcesses()是在所有正在运行的进程中注入DLL的函数。它使用EnumProcesses()获取所有正在运行的进程的列表。这里,pszLibFile包含需要注入的DLL的路径,它只是钩子. DLL的路径。
// Get the path of DLL to be injected
TCHAR pszLibFile[MAX_PATH];
GetModuleFileName(NULL,pszLibFile,sizeof(szLibFile));
_tcscpy(_tcsrchr(pszLibFile,TEXT('\\')) + 1,TEXT("HookAPI.dll"));
VirtualAllocEx()用于在加载DLL的远程进程的地址空间中分配内存。WriteProcessMemory()用于在分配的内存空间中写入DLL路径。GetProcAddress()给出了LoadLibrary() API的地址(假设内核32的加载地址)。然后CreateRemoteThread()最终在远程进程中创建一个线程,并将dll加载到远程进程的地址空间中。
void InjectIntoExistingProcesses(PCWSTR pszLibFile)
{
BOOL fOk=FALSE;
PWSTR pszLibFileRemote = NULL;
int cch = 1+lstrlenW(pszLibFile);
int cb = (cch + sizeof(WCHAR))*sizeof(WCHAR);
DWORD aProcesses[1024], cbNeeded, cProcesses;
EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded);
cProcesses = cbNeeded / sizeof(DWORD);
// Get process handle for each running process.
for (int i = 0; i < cProcesses; i++)
{
HANDLE hRunningProcess =
OpenProcess( PROCESS_ALL_ACCESS,FALSE, aProcesses[i] );
pszLibFileRemote = (PWSTR)VirtualAllocEx(hCurrentProcess,NULL,cb,
MEM_COMMIT,PAGE_READWRITE);
SIZE_T nBytes = 0;
WriteProcessMemory(hCurrentProcess,
pszLibFileRemote,(PVOID) pszLibFile,cb,&nBytes );
LPTHREAD_START_ROUTINE pfnThreadRtn = ( LPTHREAD_START_ROUTINE )
GetProcAddress(GetModuleHandle(TEXT("Kernel32")),
"LoadLibraryW");
CreateRemoteThread(hCurrentProcess,NULL,0,
pfnThreadRtn,pszLibFileRemote,0,NULL);
if (pszLibFileRemote != NULL)
{
VirtualFreeEx(hCurrentProcess,pszLibFileRemote,0,MEM_RELEASE);
}
if (hRunningProcess != NULL)
{
CloseHandle(hRunningProcess );
}
}
}
当HookAPI.dll挂起所有正在运行的进程的CreateProcess()、OpenProcess()、CreateFile()、CloseHandle()和WriteFile()函数,我们在包装器函数中获得对在系统上完成的几乎所有文件操作的控制。CreateProcess()和OpenProcess()通过一个正在运行的进程来捕获任何新进程的创建,然后是HookAPI.dll再次通过分别为CreateProcess()和OpenProcess()提供的包装器函数注入到新创建的进程中。这样做是为了在任何新进程开始之前将DLL注入到每个新创建的进程中。将CreateFile()、close handle()和WriteFile()函数挂起,以便分别嗅出任何正在运行的进程和新创建的进程中的文件打开、关闭和写入操作。然后,可以根据需要修改为挂钩函数(如CreateFile()、CloseHandle()和WriteFile())提供的包装器函数,以便对文件操作进行预处理。
最初,系统上的所有文件既不是加密的格式,也不是解密的格式。该实用程序要求系统上所有相关文件(属于文件类型的特定子集的文件)在打开之前都应该以加密的形式存在。因此,这种加密应该在安装此实用程序时完成。一种方法是检查文件中是否存在这种噪声模式,以防定义了一种特定的噪声模式来损坏文件头。如有,应清除噪音,即,然后以上述方式解密该文件。如果没有,则表明这是第一次运行,不需要预处理。另一种方法是查找系统上属于实用程序要处理的文件类型的特定子集的所有文件,并在安装该实用程序时对它们进行加密。
转自:https://www.codeproject.com/Articles/30537/%2fArticles%2f30537%2fWindows-File-Monitoring-System-Using-Windows-API-H