二.详细设计
2.1查找进程加模的动态库模块// 自定义结构,保存打开句柄的的信息 typedef struct _UNFILE_INFO { int nFileType; DWORD dwHandle; char *strFileName; } UNFILE_INFO, *PUNFILE_INFO; ////////////////////////////////////////////////////////////////////////// // 通过PID号取得PID打开的文件句柄信息 ////////////////////////////////////////////////////////////////////////// void GetModules(DWORD dwProcessID, CList<PUNFILE_INFO, PUNFILE_INFO> &plsUnFileInfo) { CToolhelp::EnableDebugPrivilege(TRUE); CToolhelp th(TH32CS_SNAPALL, dwProcessID); // 显示进程的详细资料 MODULEENTRY32 me = { sizeof(me) }; BOOL fOk = th.ModuleFirst(&me); for (; fOk; fOk = th.ModuleNext(&me)) { PVOID pvPreferredBaseAddr = NULL; pvPreferredBaseAddr =GetModulePreferredBaseAddr(dwProcessID, me.modBaseAddr); // 取得进程模块信息 PUNFILE_INFO pUnFileInfo = new UNFILE_INFO; // 模块地址 pUnFileInfo->dwHandle = (DWORD)me.modBaseAddr; // 模块类型 pUnFileInfo->nFileType = UNTYPE_DLL; // 模块名称 pUnFileInfo->strFileName = new char[strlen(me.szExePath)+1]; memset( pUnFileInfo->strFileName, 0, strlen(me.szExePath)+1); strcpy( pUnFileInfo->strFileName, me.szExePath); // 保存打开的模块信息 plsUnFileInfo.AddTail( pUnFileInfo); } }
上面功能完成了枚举进程加载的模块功能,我们把得到的枚举信息加入了链表中,以备后面使用。
NTSTATUS ZwQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL ); NTSTATUS ZwQueryInformationFile( IN HANDLE FileHandle, OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation, IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass );ZwQuerySystemInformation是个未公开函数,通过它的SYSTEM_INFORMATION_CLASS结构,我们能完成许多进程和线程的操作,下面是它的部分内容,在这时我们使用它的SystemHandleInformation(0x10),来完成文件句柄的操作。
typedef enum _SYSTEM_INFORMATION_CLASS { SystemBasicInformation, // 0 SystemProcessorInformation, // 1 SystemPerformanceInformation, // 2 … SystemHandleInformation, // 16 … SystemSessionProcessesInformation // 53 } SYSTEM_INFORMATION_CLASS;ZwQueryInformationFile可以根据参数FileInformationClass的不同值来返回不同的类型,在这里我们使用FileInformationClass=FileInformationClass来得到FILE_NAME_INFORMATION,从而得到文件句柄指向的文件名,下面是它的结构定义。
{ g_hNtDLL = LoadLibrary( "ntdll.dll" ); if ( !g_hNtDLL ) { return FALSE; } ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress( g_hNtDLL, "ZwQuerySystemInformation"); if( ZwQuerySystemInformation == NULL) { return FALSE; } ZwQueryInformationFile = (ZWQUERYINFORMATIONFILE)GetProcAddress( g_hNtDLL, "ZwQueryInformationFile"); if( ZwQueryInformationFile == NULL) { return FALSE; } }我们先来使用ZwQuerySystemInformation函数枚举系统中打开的所有文件的句柄,枚举出的信息将包含如下结构:
UCHAR GetFileHandleType() { HANDLE hFile; PSYSTEM_HANDLE_INFORMATION Info; ULONG r; UCHAR Result = 0; hFile = CreateFile("NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); if (hFile != INVALID_HANDLE_VALUE) { Info = GetInfoTable(SystemHandleInformation); if (Info) { for (r = 0; r < Info->uCount; r++) { if (Info->aSH[r].Handle == (USHORT)hFile && Info->aSH[r].uIdProcess == GetCurrentProcessId()) { Result = Info->aSH[r].ObjectType; break; } } HeapFree(hHeap, 0, Info); } CloseHandle(hFile); } return Result; }现在知道了句柄的类型就可以枚举系统中打开的文件了。首先用句柄获取打开的文件名:
typedef struct _NM_INFO { HANDLE hFile; FILE_NAME_INFORMATION Info; WCHAR Name[MAX_PATH]; } NM_INFO, *PNM_INFO; // 因为在在线程中取得文件名 DWORD WINAPI GetFileNameThread(PVOID lpParameter) { PNM_INFO NmInfo = lpParameter; IO_STATUS_BLOCK IoStatus; int r; NtQueryInformationFile(NmInfo->hFile, &IoStatus, &NmInfo->Info, sizeof(NM_INFO) - sizeof(HANDLE), FileNameInformation); return 0; } void CFileProcess::GetFileName(HANDLE hFile, PCHAR TheName) { HANDLE hThread; PNM_INFO Info = new NM_INFO; char namebuf[2000] = {0}; Info->Info = (PFILE_NAME_INFORMATION)namebuf; Info->hFile = hFile; Info->nRet = 0x00; // 在对打开的句柄调用ZwQueryInformationFile时,调用线程会等待PIPE中的消息 // 如果PIPE中没有消息时,线程可能会永久挂起,所以使用一个等待超时来打开句柄 hThread = CreateThread(NULL, 0, GetFileNameThread, Info, 0, NULL); if (WaitForSingleObject(hThread, 100) == WAIT_TIMEOUT) TerminateThread(hThread, 0); CloseHandle(hThread); // 调用句柄失败 if( Info->nRet == 0x00) { delete Info; return ; } // 加上盘符 wchar_t volume = GetVolumeName(hFile); if (volume != L'!') { wchar_t outstr[1000] = L"A:"; outstr[0] = volume; memcpy(&outstr[2], Info->Info->FileName, Info->Info->FileNameLength); outstr[2+Info->Info->FileNameLength] = 0; WideCharToMultiByte(CP_ACP, 0, outstr, 2+Info->Info->FileNameLength, TheName, MAX_PATH, NULL, NULL); } delete Info; return ; }下面代码片段用来枚举打开的文件:
{ // 取得操作系统文件句柄定义类型 UCHAR ObFileType = GetFileHandleType(); // 取得系统中打开的文件句柄列表 PULONG buf = GetHandleList(); if (buf == NULL) return ; Info = (PSYSTEM_HANDLE_INFORMATION)&buf[1]; if (Info) { for (r = 0; r < buf[0]; r++, Info++) { if (Info->ObjectTypeNumber == ObFileType && Info->ProcessId == dwProcessID) { hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Info->ProcessId); if (hProcess) { // 复制句柄到当前进程中,这样方便对当前进程句柄取出文件名 // 因为共享一个FileObject,这两个文件句柄对像将由两个进程共享 if ( DuplicateHandle( hProcess, (HANDLE)Info->Handle, GetCurrentProcess(), &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS)) { GetFileName(hFile, Name); // 取得文件信息到链表中 if( strlen( Name)>0) { AfxTrace("PID=%d FileHandle %d FileName=%s", Info->ProcessId, Info->Handle, Name); // 在这里加入你要保存的文件句柄信息 } CloseHandle(hFile); } CloseHandle(hProcess); } } } } }
Void CloseHandel( PVOID pHandle, BYTE nHandleType) { If(HandleType = HANDLE_FILE) FreeLibrary((HMODULE)pHandle); else CloseHandle((HANDLE)pHandle); }在DllMain中调用关闭句柄的函数:
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { // Remove this if you use lpReserved UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { DWORD nHandle=0; BYTE nType=0; TRACE0("MOPER.DLL Initializing!\n"); AfxMessageBox("MOPER.DLL Initializing!"); // 从碎甲程序所生成的文件句柄映射文件中取出要关闭的句柄信息 // 方法很多我在这里直接用共享文件实现的,就不写出代码了 GetHandle( nHandle, nType) // 关闭句柄 CloseHandel((PVOID)nHandle, nType); ... } ... }下面示例取出通过文件句柄链表中的PID,使用远程注入的方法向进程注入关闭句柄的DLL
{ HANDLE hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许远程创建线程 PROCESS_VM_OPERATION | //允许远程VM操作 PROCESS_VM_WRITE,//允许远程VM写 FALSE, dwProcessId); if( hRemoteProcess == NULL) return FALSE; PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA"); int nLen = (strlen( pszLibFile)+1)*sizeof(char); char * pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL, nLen, MEM_COMMIT, PAGE_READWRITE); if( pszLibFileRemote != NULL && pfnStartAddr !=NULL) { if( WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID) pszLibFile, nLen, NULL)) { HANDLE hThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL); if( !hThread) { WaitForSingleObject(hThread, INFINITE); bRet = TRUE; CloseHandle(hThread); } } VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE); } CloseHandle( hRemoteProcess); }
三.备注
对查找文件句柄的方法,参考了鄙人拙译( http://greatdong.blog.edu.cn )的“如何操作被占用文件”在此表示感谢。使用了Jeffrey在《Windows核心编程》一书中提供的CToolhelp类,用它可以完成进程加载信息的分析功能,感谢Jeffrey。