[转] 打开被独占的文件方法 -- 寻找打开文件的句柄

打开被独占的文件方法
      被占用文件操作三法 
      无疑我们中的很多人都会遇到需要读写被其它进程占用的文件的情况,比如说在编写backup程序或是trojan的时候。能从系统中抽出SAM文件,或是读取其它某些用标准方法无法成功访问的文件显然是件不错的事情。比如说当用标志dwShareMode = 0打开文件时,其它进程就不能对它进行访问了。一个很好的例子就是网络寻呼机程序Miranda。这个程序在自己工作的时候不允许别人打开自己的数据库。假设我们需要写一个这样的木马,它在感染机器后从数据库中窃走密码,然后删除自身,这个时候就需要解决这个问题。所以我决定写下这篇文章。文章篇幅不大,但里面的内容可能会对某些人有益。那我们就开始吧。

寻找打开文件的句柄
      如果文件由某个进程打开,那么这个进程就拥有了它的句柄。在我第二篇关于API拦截的文章里我讲解了如何搜索需要的句柄并用它打开进程,要访问已打开的文件,我们也可以使用这种方法。我们需要使用ZwQuerySystemInformation函数来枚举句柄,将每一个句柄都用DuplicateHandle进行复制,确定句柄属于那个文件(ZwQueryInformationFile),如果是要找的文件,就将句柄拷贝。 
      这些在理论上都讲得通,但在实践中会遇到两处难点。第一,在对打开的named pipe(工作于block mode)的句柄调用ZwQueryInformationFile的时候,调用线程会等待pipe中的消息,而pipe中却可能没有消息,也就是说,调用ZwQueryInformationFile的线程实际上永久性地挂起了。所以命名文件的获取不用在挑选句柄的主线程中进行,可以启动独立的线程并设置一个timeout值来避免挂起。第二,在拷贝句柄后,两个句柄(我们进程的和打开文件进程的)将会指向同一个FileObject,从而当前的输入输出模式、在文件中的位置以及其它与文件相关的信息就会由两个进程来共享。这时,甚至只是读取文件都会引起读取位置的改变,从而破坏了打开文件程序的正常运行。为了避免这种情形,我们需要需要停止占用文件进程的线程、保存当前位置、拷贝文件、恢复当前位置以及重新启动占用文件的进程。这种方法不能用于许多情形,比如要在运行的系统中拷贝注册表文件,用这种方法就不会成功。 
      我们先来试着实现对系统中所有已打开文件的句柄的枚举。为枚举句柄,每个句柄都由以下结构体描述:

typedef struct _SYSTEM_HANDLE { ULONG uIdProcess; UCHAR ObjectType; UCHAR Flags; USHORT Handle; POBJECT pObject; ACCESS_MASK GrantedAccess; } SYSTEM_HANDLE, *PSYSTEM_HANDLE;

 

    这里的ObjectType域定义了句柄所属的对象类型。这里我们又遇到了问题――File类型的ObjectType在Windows 2000、XP和2003下的取值各不相同,所以我们不得不动态的定义这个值。为此我们用CreateFile来打开NUL设备,找到它的句柄并记下它的类型:

 

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 GetFileName(HANDLE hFile, PCHAR TheName) { HANDLE hThread; PNM_INFO Info = HeapAlloc(hHeap, 0, sizeof(NM_INFO)); Info->hFile = hFile; hThread = CreateThread(NULL, 0, GetFileNameThread, Info, 0, NULL); if (WaitForSingleObject(hThread, INFINITE) == WAIT_TIMEOUT) TerminateThread(hThread, 0); CloseHandle(hThread); memset(TheName, 0, MAX_PATH); WideCharToMultiByte(CP_ACP, 0, Info->Info.FileName, Info->Info.FileNameLength >> 1, TheName, MAX_PATH, NULL, NULL); HeapFree(hHeap, 0, Info); }

 

现在来枚举打开的文件:

void main() { PSYSTEM_HANDLE_INFORMATION Info; ULONG r; CHAR Name[MAX_PATH]; HANDLE hProcess, hFile; hHeap = GetProcessHeap(); ObFileType = GetFileHandleType(); Info = GetInfoTable(SystemHandleInformation); if (Info) { for (r = 0; r < Info->uCount; r++) { if (Info->aSH[r].ObjectType == ObFileType) { hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Info->aSH[r].uIdProcess); if (hProcess) { if (DuplicateHandle(hProcess, (HANDLE)Info->aSH[r].Handle, GetCurrentProcess(), &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS)) { GetFileName(hFile, Name); printf("%s/n", Name); CloseHandle(hFile); } CloseHandle(hProcess); } } } HeapFree(hHeap, 0, Info); } }

 

      现在对于文件的拷贝我们剩下的工作只是找到所需句柄后用ReadFile读取它。这里一定要使用前面提到的机制,不可疏忽。 
这种方法的优点是实现简单,但是其缺点更多,所以这个方法只适用于确定文件被那个进程占用。

你可能感兴趣的:([转] 打开被独占的文件方法 -- 寻找打开文件的句柄)