探索黑客技术攻防,实战研究与安全创新

导航菜单

在Win64上实现驱动级解锁文件

相信大家在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所示。

017.png

接下来说说测试步骤:

1)新建文件c:\lockfile.txt。

2)用lockfile.exe锁定文件c:\lockfile.txt.

3)双击c:\lockfile.txt,会提示无法打开(如果此时用Unlocker查看,会发现系统里每个进程都有c:\lockfile.txt』的句柄,如图3所示)。

018.png

4)加载UnlockFile.sys,再次打开c:\lockfile.txt,发现文件又可以打开了。

本文到此结束,代码在Win7X64和Win8X64上测试通过(运行任何程序时,都要以管理员权限运行)。在Win32上可以用相同的方法,但是结构体的定义并不相同,对应的结构体需要自己去寻找。