相信大家在Windows系统上都遇到过想删除一个文件时却被提示“无法删除”的情况。
我在初学电脑时遇到这种情况只能自认倒霉,重启之后再删除文件。学习了Windows后知道了在正常情况下(即不算文件被文件过滤驱动或者各种APIHOOK保护的情况),遇到这个提示只有两种可能性:你没有删除这个文件的权限;有句柄在某个进程里被打开了。
解决第一种情况不需要编程,只要把以下代码保存成*.reg文件并添加到注册表即可。
WindowsRegistryEditorVersion5.00 [HKEY_CLASSES_ROOT\*\shell\takeownership] @=Takeownership HasLUAShield= NoWorkingDirectory= [HKEY_CLASSES_ROOT\*\shell\takeownership\command] @=cmd.exe/ctakeown/f\%1\icacls\%1\/grantadministrators:F IsolatedCommand=cmd.exe/ctakeown/f\%1\icacls\%1\/grant administrators:F [HKEY_CLASSES_ROOT\exefile\shell\takeownership] @=Takeownership HasLUAShield= NoWorkingDirectory= [HKEY_CLASSES_ROOT\exefile\shell\takeownership\command] @=cmd.exe/ctakeown/f\%1\icacls\%1\/grantadministrators:F IsolatedCommand=cmd.exe/ctakeown/f\%1\icacls\%1\/grant administrators:F [HKEY_CLASSES_ROOT\dllfile\shell\takeownership] @=Takeownership HasLUAShield= NoWorkingDirectory= [HKEY_CLASSES_ROOT\dllfile\shell\takeownership\command] @=cmd.exe/ctakeown/f\%1\icacls\%1\/grantadministrators:F IsolatedCommand=cmd.exe/ctakeown/f\%1\icacls\%1\/grant administrators:F [HKEY_CLASSES_ROOT\Directory\shell\takeownership] @=Takeownership HasLUAShield= NoWorkingDirectory= [HKEY_CLASSES_ROOT\Directory\shell\takeownership\command] @=cmd.exe/ctakeown/f\%1\/r/dyicacls\%1\/grantadministrators:F /t IsolatedCommand=cmd.exe/ctakeown/f\%1\/r/dyicacls\%1\/grant administrators:F/t
当要删除一个文件而遇到“无法删除需要权限”的提示时,只要对着文件按下右键,选择“Takeownership”再删除文件即可。
第二种情况就要通过编程解决了,这也就是本文的核心内容。要删除被打开的文件,比较好的方法是关闭此文件在其它进程里的句柄(直接解析文件系统也可以,不过这个难度太大,而且不通用)。总体来说,步骤分为以下两步:枚举系统句柄表;获得所有和此文件有关的句柄并关闭。具体到代码级的思想,又可以分为以下几步:
1)调用ZwQuerySystemInformation的16功能号来枚举系统里的句柄;
2)打开拥有此句柄的进程,并把此句柄复制到自己的进程;
3)用ZwQueryObject查询句柄的类型和名称;
4)如果发现此句柄的类型是文件句柄,名称和被锁定的文件一致,就关闭此句柄。重复2、3、4步,直到遍历完系统里所有的句柄。
实现代码如下:
VOIDCloseFileHandle(char*szFileName) { PVOIDBuffer; ULONGBufferSize=0x20000,rtl=0; NTSTATUSStatus,qost=0; NTSTATUSns=STATUS_SUCCESS; ULONG64i=0; ULONG64qwHandleCount; SYSTEM_HANDLE_TABLE_ENTRY_INFO*p; OBJECT_BASIC_INFORMATIONBasicInfo; POBJECT_NAME_INFORMATIONpNameInfo; ULONGulProcessID; HANDLEhProcess; HANDLEhHandle; HANDLEhDupObj; CLIENT_IDcid; OBJECT_ATTRIBUTESoa; CHARszFile[260]={0}; Buffer=kmalloc(BufferSize); memset(Buffer,0,BufferSize); Status = ZwQuerySystemInformation(16, Buffer, BufferSize, 0); //SystemHandleInformation while(Status==0xC0000004)//STATUS_INFO_LENGTH_MISMATCH { kfree(Buffer); BufferSize=BufferSize*2; Buffer=kmalloc(BufferSize); memset(Buffer,0,BufferSize); Status=ZwQuerySystemInformation(16,Buffer,BufferSize,0); } if(!NT_SUCCESS(Status))return; qwHandleCount=((SYSTEM_HANDLE_INFORMATION*)Buffer)-NumberOfHandles; p=(SYSTEM_HANDLE_TABLE_ENTRY_INFO *)Buffer)-Handles; *)((SYSTEM_HANDLE_INFORMATION //cleararray memset(HandleInfo,0,1024*sizeof(HANDLE_INFO)); //ENUMHANDLEPROC for(i=0;i<qwHandleCount;i++) { ulProcessID=(ULONG)p[i].UniqueProcessId; cid.UniqueProcess=(HANDLE)ulProcessID; cid.UniqueThread=(HANDLE)0; hHandle=(HANDLE)p[i].HandleValue; InitializeObjectAttributes(oa,NULL,0,NULL,NULL); ns=ZwOpenProcess(hProcess,PROCESS_DUP_HANDLE,oa,cid); if(!NT_SUCCESS(ns)) { KdPrint((ZwOpenProcess:Fail)); continue; } ns=ZwDuplicateObject(hProcess,hHandle,NtCurrentProcess(),hDupObj, PROCESS_ALL_ACCESS,0,DUPLICATE_SAME_ACCESS); if(!NT_SUCCESS(ns)) { KdPrint((ZwDuplicateObject:Fail)); continue; } //getbasicinformation ZwQueryObject( hDupObj ,ObjectBasicInformation ,BasicInfo , sizeof(OBJECT_BASIC_INFORMATION),NULL); //getnameinformation pNameInfo=ExAllocatePoolWithTag(PagedPool,1024,'ONON'); RtlZeroMemory(pNameInfo,1024); qost=ZwQueryObject(hDupObj,ObjectNameInformation,pNameInfo,1024, rtl); //getinformationandclosehandle UnicodeStringToCharArray((pNameInfo-Name),szFile); ExFreePool(pNameInfo); ZwClose(hDupObj); ZwClose(hProcess); if(!_stricmp(szFile,szFileName)) { PEPROCESSep=LookupProcess((HANDLE)(p[i].UniqueProcessId)); ForceCloseHandle(ep,p[i].HandleValue); ObDereferenceObject(ep); } } }
接下来说说如何关闭其它进程里的句柄。
1)用KeStackAttachProcess“依附”到目标进程;
2)用ObSetHandleAttributes设置句柄为“可以关闭”;
3)用ZwClose关闭句柄;
4)用KeUnstackDetachProcess脱离“依附”的目标进程。
实现代码如下:
VOIDForceCloseHandle(PEPROCESSProcess,ULONG64HandleValue) { HANDLEh; KAPC_STATEks; OBJECT_HANDLE_FLAG_INFORMATIONohfi; if(Process==NULL) return; if(!MmIsAddressValid(Process)) return; KeStackAttachProcess(Process,ks); h=(HANDLE)HandleValue; ohfi.Inherit=0; ohfi.ProtectFromClose=0; ObSetHandleAttributes(h,ohfi,KernelMode); ZwClose(h); KeUnstackDetachProcess(ks); }
要注意的是,要解锁的文件的路径不能写成常见的DOS格式,而要写成NT格式,比如“c:\lockfile.txt”的NT格式路径可能是“\\Device\\HarddiskVolume2\\LockFile.txt”。
为什么说“可能是”呢?因为前半段“\\Device\\HarddiskVolumeX”中的X并不能确定是什么,要通过转换才知道。转换方法很简单,用QueryDosDevice就行了。以下是封装好的函数:
char*DosPathToNtPath(char*szFileName) { charszDosDrive[3]={0}; char*szNtDrive=NULL,*szFilePart=NULL,*szNtPath=NULL; szNtDrive=(char*)malloc(260); memset(szNtDrive,0,260); memcpy(szDosDrive,szFileName,2); QueryDosDeviceA(szDosDrive,szNtDrive,260); szFilePart=Mid(szFileName,3,0); szNtPath=cs(szNtDrive,szFilePart); free(szFilePart); free(szNtDrive); returnszNtPath; }
可能的输出结果如图1所示。
接下来说说测试步骤:
1)新建文件c:\lockfile.txt。
2)用lockfile.exe锁定文件c:\lockfile.txt.
3)双击c:\lockfile.txt,会提示无法打开(如果此时用Unlocker查看,会发现系统里每个进程都有c:\lockfile.txt』的句柄,如图3所示)。
4)加载UnlockFile.sys,再次打开c:\lockfile.txt,发现文件又可以打开了。
本文到此结束,代码在Win7X64和Win8X64上测试通过(运行任何程序时,都要以管理员权限运行)。在Win32上可以用相同的方法,但是结构体的定义并不相同,对应的结构体需要自己去寻找。