- 介紹
該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 <CString,void*> m_mapOld;
multimap <CString,void*> :: 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:<address of CreateFile>",
// <address that contains the address of CreateFile in IAT>).
// 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())提供的包裝器函數,以便對文件操作進行預處理。
- 此實用程序的第一次運行出現問題
最初,系統上的所有文件既不是加密的格式,也不是解密的格式。該實用程序要求系統上所有相關文件(屬於文件類型的特定子集的文件)在打開之前都應該以加密的形式存在。因此,這種加密應該在安裝此實用程序時完成。一種方法是檢查文件中是否存在這種噪聲模式,以防定義了一種特定的噪聲模式來損壞文件頭。如有,應清除噪音,即,然後以上述方式解密該文件。如果沒有,則表明這是第一次運行,不需要預處理。另一種方法是查找系統上屬於實用程序要處理的文件類型的特定子集的所有文件,並在安裝該實用程序時對它們進行加密。