随笔-60  评论-262  文章-1  trackbacks-0
====================================================================
以下为转帖

操作被占用的文件-unlocker机理分析
*[标题]: 操作被占用的文件-unlocker机理分析
*[作者]: gz1X [gz1x(at)tom(dot)com]
            EagleNet [hklt594(at)163(dot)com]
*[来自]: 中国黑客联盟 [CHU]
*[原始链接]: http://blog.china-pub.com/more.asp?name=zzs0405&id=41832

[前言]
——————————————————————
之前给原作者发过邮件, 不过没能得到unlocker的源代码, 所以自己逆向了一份;
逆向的很匆忙, 因为近来事情太多, 而且unlocker本身也是版权所有, 所以不好做的太露骨;
本人也还有不少地方不是很明白, 有机会和我联系, 一起探讨 :-)
感谢EagleNet的讨论.


[关于unlocker]
——————————————————————
Unlocker是一个免费的工具, 原作者的网站是: http://ccollomb.free.fr/unlocker
当使用者发现有某个文件或目录无法删除时, 只要按下鼠标右键中的"Unlocker",
程序会显示出是哪一些程序占用了该目录或文件, 接着只要按下"Unlock"就能够为你的文件解锁.


==============================Here we start=================================

[大致流程]
——————————————————————
主程序Unlocker.exe通过ZwQuerySystemInformation查询当前系统的所有句柄信息, 然后调用OpenProcess获取目标进程句柄,
遍历当前所有进程, 根据进程ID, 得到此进程打开的所有句柄信息, 接下来用DuplicateHandle复制Handle到本地进程,
然后把文件句柄发给驱动UnlockerDriver5.sys进行名字的查询, sys将返回文件句柄对应的内核文件对象的完整名字.
确定文件名后, 如果要删除文件, 则调用OpenProcess与DuplicateHandle关闭句柄, 然后ZwDeleteFile删除文件.


[UnlockerAssistant.exe]
[UnlockerHook.dll]
——————————————————————
UnlockerAssistant.exe主要是实现系统托盘等辅助功能, 同时安装钩子:
.text:00403AC9           public start
.text:00403AC9 start:
//...
.text:00403AD5           call   sub_40391E
跟进, 能看到主要的实现代码:
.text:00403925           call   sub_402E83
//...
.text:00403944           push   offset LibFileName ; "UnlockerHook.dll"
.text:00403949           call   ds:LoadLibraryA
//...
.text:0040398A           push   offset ProcName ; "HookInstall"
.text:00403994           call   edi ; GetProcAddress
注册窗口消息, 初始化控件, 安装钩子;

.text:00403A1A           call   ds:Shell_NotifyIconA
.text:00403A20           call   sub_40359E   ;RegOpenKeyExA...
设置托盘图标, 写入注册表启动项;

.text:00403A69           push   offset s_Hookuninstall ; "HookUninstall"
卸载钩子.

当然也有绿化版本只写入右键, 这些大家自己看反汇编的代码, 详细的钩子过程反汇编UnlockerHook.dll, 也不再做纠缠.
我们只看UnlockerHook.dll里一处:
.text:10001102 sub_10001102   proc near           ;
//...
.text:10001181           call   ds:GetModuleFileNameW
.text:1000118E           call   ds:PathRemoveFileSpecW
//...
.text:100011A7           push   offset s_SUnlocker_exe ; "\"%s\\Unlocker.exe\""
//...
.text:100011E0           call   ds:ShellExecuteExW
呼出主程序, 进行文件处理.


[UnlockerDriver5.sys分析]
——————————————————————
反汇编驱动文件, 跳到入口点:
INIT:00402000 ; int __stdcall start(PDRIVER_OBJECT DriverObject,int)
INIT:00402000           public start
INIT:00402000 start       proc near
//...
INIT:0040203D           call   ds:IoCreateDevice
//...
INIT:004020E3           call   ds:IoCreateSymbolicLink
//...
上面就是DriverEntry了. 注意这一段:
INIT:004020AB           mov   dword ptr [ecx], offset loc_401000
INIT:004020B1           mov   dword ptr [esi+40h], offset loc_401000
INIT:004020B8           mov   dword ptr [esi+44h], offset sub_401090
INIT:004020BF           mov   dword ptr [esi+48h], offset loc_401020
INIT:004020C6           mov   dword ptr [esi+34h], offset sub_401240
经典的, 处理MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)]等等.

我们跟踪这两个函数, 先跟sub_401240, 如下:
PAGE:0040124D           call   ds:RtlInitUnicodeString
//...
PAGE:00401257           call   ds:IoDeleteSymbolicLink
//...
PAGE:00401265           call   ds:IoDeleteDevice
很明显的DriverUnload函数;

接着跟sub_401090, 这个函数就是类似于DispatchControl(IRP_MJ_DEVICE_CONTROL)了;
我们挑关键的看, RtlInitUnicodeString函数填充UNICODE_STRING结构就不多做纠缠:
PAGE:004010FA           call   ds:ObReferenceObjectByHandle
获取由句柄描述的对象的指针, 也就是获取FILE_OBJECT对象;
这里, 想想内核级文件的Read和Write, 通过HANDLE执行就要先用ObReferenceObjectByHandle函数来获得Handle对应的FileObject,
然后我们再给FileObject发送IRP进行实质操作.

略过分配内存, 接着往下走:
PAGE:00401158           call   ds:ObQueryNameString
这个函数将获取设备名, 然后和FILE_OBJECT的FileName构成完整的名字返回(应用层在注册表中保存设备名+目录名);

PAGE:00401214           call   ds:ObfDereferenceObject
再一次调用ObfDereferenceObject, 将对象的引用计数器恢复到先前的值, 防止泄漏;

PAGE:0040122D           call   ds:IofCompleteRequest
MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)], 完成操作.

回头看看这个驱动, 实际上是很经典也很简单的驱动程序, 实现的功能也很简单:
返回文件句柄对应的内核文件对象的完整名字, 传给主程序进行文件"删除""移动"等操作.


[Unlocker.exe分析]
——————————————————————
直接跳到入口点:
.text:004135FB           public start
.text:004135FB start       proc near
//...
.text:00413607           call   sub_412EDC
跟进去, 挑重点:

.text:00412EEF           call   sub_40D1FF     ; 命令行用法说明
.text:00412EFC           call   sub_40D78C     ; 查询注册表
.text:00412F0C           push   offset Caption ; "Unlocker 1.8.5"
.text:00412F11           call   sub_413DEF     ; 创建线程, 网络升级, 在线辅助
//...
.text:00412F1C           mov   ecx, eax
.text:00412F1E           call   sub_40FA79     ; 这里开始!

=============================================================================
我把sub_40FA79列出来(有省略), 如下:
.text:0040FA79 sub_40FA79     proc near           ; CODE XREF: sub_412EDC+42 p
.text:0040FA79           push   ebx
.text:0040FA7A           push   esi
.text:0040FA7B           push   offset LibFileName ; "ntdll.dll"
.text:0040FA82           call   ds:LoadLibraryA
//...
.text:0040FA8E           push   edi
.text:0040FA8F           mov   edi, ds:GetProcAddress
.text:0040FA95           push   offset ProcName ; "ZwQuerySystemInformation"
.text:0040FA9D           push   offset s_Zwqueryobject ; "ZwQueryObject"
.text:0040FAA7           push   offset s_Zwdeletefile ; "ZwDeleteFile"
.text:0040FAB2           push   offset s_Rtlinitunicod ; "RtlInitUnicodeString"
.text:0040FABD           push   offset s_Rtladjustpriv ; "RtlAdjustPrivilege"
.text:0040FAC8           push   offset s_Ntloaddriver ; "NtLoadDriver"
.text:0040FAD3           push   offset s_Ntunloaddrive ; "NtUnloadDriver"
.text:0040FAD8           push   ebx         ; hModule
.text:0040FAD9           mov   [esi+14h], eax
.text:0040FADC           call   edi ; GetProcAddress

这一段的代码是unlocker的重点部分, 获取ntdll.dll地址后, 调用其中的函数:
1)ZwQuerySystemInformation函数获得系统当前所以进程的所建立的句柄及其相关信息;
2)ZwQueryObject获取句柄所代表对象信息, 查出目标文件(设备名+目录名);
3)ZwDeleteFile删除目标文件;
4)NtLoadDriver加载驱动, 当然还需要后面的注册表修改;
===========================================================================

再往下:
.text:00412F9E           call   ds:PathRemoveFileSpecW
.text:00412FAB           push   offset s_SUnlocker_cfg ; "%s\\Unlocker.cfg"
.text:00412FB5           call   ds:wsprintfW
//...
.text:00412FC2           call   sub_40D3F4
跟进sub_40D3F4函数, 发现作用是将驱动信息写入注册表;

.text:00413008           push   dword ptr [eax+8] ; lpFileName
.text:0041300B           call   sub_410E28
//...
.text:004133E8           call   ds:QueryDosDeviceA
//...
.text:004134B7           push   offset s_DeviceLanmanr ; "\\Device\\LanmanRedirector"
.text:004134BC           push   eax         ; LPWSTR
.text:004134BD           mov   [ebp+lpSrch], eax
.text:004134C0           call   ds:wsprintfW
遍历查询DOS设备, 进行重定向;

.text:00413504           mov   esi, ds:DialogBoxParamA     
.text:0041350A           mov   edi, ds:GetModuleHandleA
//...
.text:00413529           call   sub_411A59   //--->getfullname
创建一个对话框窗口, 显示所有枚举出的相关进程;
sub_411A59函数将获取对象文件的完整名, 并返回, 我们跟进去:

.text:00411F63           push   dword ptr [esi] ; dwProcessId
.text:00411F65           mov   edi, ds:OpenProcess
.text:00411F6B           push   ebx         ; bInheritHandle
.text:00411F6C           push   450h         ; dwDesiredAccess
.text:00411F71           call   edi ; OpenProcess
之前是进程和模块遍历, 找到相关联的所有进程和模块, OpenProcess打开需要操作的文件;

.text:00411F93           call   ds:GetCurrentProcess
//...
.text:00411FA2           call   ds:DuplicateHandle
获取本地进程, 将对象进程的句柄复制到本地进程(句柄进程相关);

.text:00411FBB           push   offset s_?Unlockerdriv ; "\\\\?\\UnlockerDriver5"
.text:00411FC0           call   ds:CreateFileW
//...
将句柄发送给驱动程序, 驱动将返回文件句柄对应的内核文件对象的完整名字;


[文件操作选择]
——————————————————————
现在退出sub_411A59函数返回主线, 我们走到这里:
.text:00413536           cmp   byte ptr [eax+3], 0
.text:0041353A           jz     short loc_41357B
.text:0041353A
.text:0041353C           mov   eax, cInitial
.text:00413541           test   eax, eax
.text:00413543           jz     short loc_413571   ; "移动"或者"重命名"操作;
.text:00413543
.text:00413545           xor   ebx, ebx
.text:00413547           test   eax, eax
.text:00413549           jbe   short loc_41355F   ; "删除"操作
这里将进行文件处理的选择, 是无动作? 删除? 还是移动? 重命名?

.text:0041353A           jz     short loc_41357B
//...
.text:0041357B           xor   ebx, ebx
.text:0041357D           cmp   cInitial, ebx
.text:00413583           jz     short loc_4135AE
cInitial存放的值代表当前的窗口是否为初始窗口;

a)
我们先看是派生窗口时的处理, loc_4135AE:
.text:004135B5           push   offset sub_412D62 ; lpDialogFunc
//...
.text:00412DA4           push   [esp+800h+hDlg] ; hDlg
.text:00412DAB           call   sub_412371   //-->toMoveFile
生成一个浏览对话框供选择路径保存文件:
.text:004123AB           call   sub_410064
//--->
.text:00410082           call   ds:CoInitialize
.text:004100D6           call   ds:SHBrowseForFolderW
.text:004100E4           call   ds:SHGetPathFromIDListW
.text:004100F4           call   ds:CoUninitialize
//<---
.text:004123DE           call   ds:PathIsDirectoryW
.text:0041242C           call   ds:GetSaveFileNameW
.text:00412451           call   sub_4115AE     //-->inject

实际上查看sub_412371这个函数的交叉引用(实际上你不用看引用也很容易就会发现), 会发现:
.text:00412478 sub_412478     proc near     //-->GuiControlDeal
这里是一个对话框, 也就是我们右键unlocker时产生的界面, 它将生成unlocker的主界面,
也将处理各种用户操作的消息, 发给各个子程序去处理;

回到:
.text:004135C7           call   esi ; DialogBoxParamA
.text:004135CF           call   sub_410F86   //-->MoveFile
由于cInitial值为0, 所以此时必定是用户选择了"移动"或"重命名";

=========================================================================
跟进sub_410F86, 看到:
.text:00410F96           jnz   loc_4110C2     ; 重命名
//...
.text:00411145           call   esi ; wsprintfW
//...
.text:00411173           call   edi ; SHFileOperationW
//...
.text:00411166           mov   [ebp+FileOp.wFunc], 4   ;ReName
text:00411217             call   edi ; SHFileOperationW
调用wsprintfW格式化路径后, 填充SHFILEOPSTRUCT结构, 由SHFileOperationW来重命名, 完成后调用MessageBoxA通知完成;

否则就是"移动"操作:
.text:00411048           call   sub_410BB2   //-->getdirfile
//--->
.text:00410C20           call   ds:FindFirstFileW
.text:00410C95           call   ds:FindNextFileW
//<---
.text:00411064           call   ds:MoveFileExW
.text:00411076           jnz   short loc_41105F ; 循环移动目录下的所有文件
.text:00411093           jb     loc_410FBF     //-->deleteDir
//...
.text:004113E4           push   eax         ; int
.text:004113E5           mov   eax, lpSrch
.text:004113EA           push   [ebp+var_8]   ; int
.text:004113ED           add   eax, esi
.text:004113EF           push   eax         ; lpExistingFileName
.text:004113F0           call   sub_410462   //-->movefile
到这里就完成了这两项功能, 不过注意函数里压栈时的参数:

.text:00411281           call   getdirfile
//...
.text:00411302           push   4           ; MOVEFILE_DELAY_UNTIL_REBOOT
.text:00411304           lea   eax, [ebp+NewFileName]
.text:0041130A           push   eax         ; lpNewFileName
.text:0041130B           push   dword ptr [edi] ; lpExistingFileName
.text:0041130D           call   ds:MoveFileExW
这段代码的意思是, 如果暂时处理不了用户请求的文件操作, 那就在系统重新启动时实行操作;
========================================================================

接着:
.text:004135DE           cmp   [ebp+var_9], 0
.text:004135E2           jnz   loc_413514
返回, 重新获取文件的完整名, 重绘主窗口;

.text:004135E8           call   sub_40D51D
.text:004135ED           call   sub_413B8F
ds:GlobalFree释放内存后, 调用了sub_40D51D, 它删除了注册表里的sys服务, 然后sub_413B8F休眠, 继续等待操作触发;

b)
现在我们回到a)处, 考虑另一种情况, cInitial==1:

.text:00413586           push   offset GuiControlDeal ; lpDialogFunc
.text:00413596           call   esi ; DialogBoxParamA
//...
.text:00413568           push   [ebp+hMem]
.text:0041356B           call   sub_41178F   //--->CloseFileHandle
此时的文件操作是"删除"(或者"无动作");

sub_41178F函数的作用就是实现删除文件, 我们看:

.text:004117FB           call   ds:OpenProcess
//...
.text:0041180D           call   ds:TerminateProcess
//...
.text:00411819           cmp   eax, 0FFFFFFFFh
.text:0041181C           jnz   loc_4119F8
判断目标文件是哪种类型文件, exe还是dll? 是exe则跳到loc_4119F8处;

.text:0041182E           call   ds:OpenProcess
//...
.text:00411868           call   GetModuleFileNameExW
//...
.text:00411873           push   offset s__dll   ; ".DLL"
.text:00411878           push   eax         ; pszPath
.text:00411879           call   sub_40FC27
文件是dll类型, 获取其路径;

.text:0041189F           push   offset s_SUS   ; "/s /u \"%s\""
//...
.text:004118B3           mov   [ebp+ExecInfo.lpVerb], offset s_Open ; "open"
.text:004118BA           mov   [ebp+ExecInfo.lpFile], offset s_Regsvr32_exe ; "regsvr32.exe"
//...
.text:004118DA           call   ds:ShellExecuteExW
先将模块注销掉, 使用regsvr32.exe /s /u实现;

.text:00411944           push   offset s_Freelibrary ; "FreeLibrary"
//...
.text:00411966           push   offset s_Closehandle ; "CloseHandle"
然后通过FreeLibrary来释放, 调用GetModuleHandleW查询句柄后, 用CloseHandle将其关闭;

注意这一段代码:
.text:00411888           push   0Eh
.text:0041188A           pop   ecx
也就是说循环将进行0EH(14)次, FreeLibrary也将执行14次直到dll被释放;

.text:00411921           call   esi ; WriteProcessMemory
.text:00411933           call   edi ; VirtualAllocEx
.text:004119A8           call   esi ; WriteProcessMemory
.text:004119B5           call   ds:CreateRemoteThread
想必您已经注意到了这些代码, 对, 思路就是用线程注入到目标进程去调用FreeLibrary来释放dll;

现在我们来到loc_4119F8处, 也就是上文提到的如果文件是exe文件时的处理方法:
.text:00411A07           call   ds:OpenProcess
.text:00411A1B           call   ds:GetCurrentProcess
.text:00411A28           call   ds:DuplicateHandle
.text:00411A3C           call   ds:CloseHandle
还是先打开文件, 然后调用DuplicateHandle, 但是这里传递的参数是DUPLICATE_CLOSE_SOURCE标志, 将强制关闭句柄;

===========================EOF===But to be modified========================


[后语]
——————————————————————
突然发现没结尾, 被老大姐骂了, 补充一个吧.
unlocker最主要的功能实现都在unlocker.exe和一个.sys驱动里, 值得关注的是它怎么实现的关闭句柄和删除文件.
关键的函数 (部分也是未公开的API) , 比如:
ZwQuerySystemInformation、 ZwQueryObject、ZwDeleteFile、ObReferenceObjectByHandle、ObQueryNameString、 SHFileOperationW、MoveFileExW、DuplicateHandle.
这里尤其是DuplicateHandle和MoveFileExW比较有意思.
详细的界面构造没有去研究, 就是这句:
.text:00412478 sub_412478     proc near     //-->GuiControlDeal
跟进去分析就是界面的相关操作了, 太烦, 本人时间有限, 如果有人愿意继续, 记得把结果分享一份给我, 谢谢.



*[参考资料]:
——————————————————————
1. Windows平台内核级文件访问   baiyuanfan
2. unlocker1.8.5       http://ccollomb.free.fr/unlocker
3. Kmd教程-全功能的驱动程序分析   罗云彬

4. http://forum.sysinternals.com/forum_posts.asp?TID=7974

==========================================================================

另一篇: http://windknown.spaces.live.com/blog/cns!627D8DB6EC5BD4A7!435.entry

April 07
UnLocker带来的思考
    这几个礼拜已经是忙得焦头烂额了, 这2天还偏偏和unlocker这个软件耗上了, 我的睡眠时间啊~~
    UnLocker是一款可以用来删除已被占用文件的软件, http://ccollomb.free.fr/unlocker/
    通常的删除文件是通过调用win32 api的DeleteFile来执行的, 该函数会检查是否有引用该文件的句柄, 因此若有进程打开了该文件是无法删除的. 另外说一下该api的执行过程, 打开文件-〉设置为删除-〉关闭文件, 此时系统会自动删除该文件. 设置是通过主控制号为IRP_MJ_SET_INFORMATION的IRP进行的, IO 栈的Parameters.SetFile.FileInformationClass值为 FileDispositionInformation, AssociatedIrp.SystemBuffer指向结构 FILE_DISPOSITION_INFORMATION, 其成员DeleteFile设置为TRUE. 于是乎, 如果在自己的文件过滤驱动里过滤该 IRP, 只需要改为FASLE就可以防止文件被删除了.
    说一下另一种删除文件的方法, 也就是unlocker使用的, ntdll导出的ZwDeleteFile, 该函数的原理没有细究, 但是同样也是会检查句柄的.
    因此, 要删除文件的关键是需要关闭打开的文件句柄, 而句柄是与进程相关的. 今天仔细逆向了一下unlocker, 包括主程序和一个sys文件.
    sys文件的作用是返回文件句柄对应的内核文件对象的完整名字, 例如: \Device\HarddiskVolum1\mytest.doc,  \Device\HarddiskVolum1就是C:这个卷的设备名. 大致流程就是调用ObReferenceObjectByHandle通过对象获取FILE_OBJECT对象, 然后通过ObQuerNameString查询FILE_OBJECT中的DeviceObject指针获取设备名, 再与 FILE_OBJECT的FileName构成完整的名字返回.
    主程序是通过 ZwQuerySystemInformation 查询 handle 信息, 类型为 SystemHandleInformation, 然后调用 OpenProcess 获取目标进程句柄, 再用 DuplicateHandle 复制 Handle 为本进程, 再通过驱动查询名字, 确定文件名. 一定记住句柄是进程相关的, MSDN里描述, 例如在DriverEntry中打开的句柄是在system进程下, 因此在驱动DispatchFunction中不可用, 因为DispatchFunction的进程上下文为与驱动通信的进程. 当要删除文件时, 对于打开的文件, unlocker仍然是通过OpenProcess与DuplicateHandle, 只是指定了 DUPLICATE_CLOSE_SOURCE标志, 真是大开眼界阿, 如此便关闭了句柄, 高. 而对于dll就没有那么方便了, unlocker会用线程注入到目标进程去调用FreeLibrary来释放dll, 因此可能不够稳定, 而且逆向时发现注入的代码只尝试0x10次Free, 呵呵我load20次自己就可以搞定捞.
    然后看MSDN过程中发现了另一个nb函数, SetHandleInformation, 设置这个标志 HANDLE_FLAG_PROTECT_FROM_CLOSE , 那么 unlocker 调用 DuplicateHandle 也关不掉你的句柄了, 试验了一下, 果然说删不了捞, hoho
    整这些鸟东西弄了我一天时间, 唉, 许久不逆向, 生疏了不少~~而且因为在虚拟机里调试, 老破机器这个卡啊~~睡觉去!
    顺便一提, 今天发现国内搞安全的兄弟们共享精神不如国外阿, 唉, 环境使然, 人吗首先还是要自己生存的. 想想自己也快毕业了, 有点迷惘, 毕业了干啥工作去捏……生存阿生存


============================================================================
第三篇: http://blog.vckbase.com/windowssky/archive/2007/04/18/25565.html

小议文件保护和锁定技术

  近1年来互连网上的木马越来越多, 有的还删除不掉, 要切换到dos才可行, 如: CNNC, 3721等, 实现技术也五花八门, 但就文件不可删除的实现技术可分三类:

  1 Attach file system; 这种技术和Filemon/sfilter查不多, 就是挂一个 filter 驱动到 fs 上, 其他函数都是 passthru 下去, 只处理 IRP_MJ_SET_INFORMATION, 当发现有删除需保护文件的 IRP, 就
Irp->IoStatus.Status = STATUS_SUCCESS;            
Irp->IoStatus.Information = 0;            
IoCompleteRequest(Irp, IO_NO_INCREMENT);
根本不让 Fs 去处理, 从而达到文件不可删除的作用!

有什么方法可以删除呢? 自己写个驱动自己填充 irp 包 (见 OSR 文档 Rolling Your Own) , 直接发送 IRP 到 File System Device 上去就 ok 啦!


  2 修改 file system 的 dispatch 函数表; 首先得到 Fs 的 DriverObject (根据驱动名得到驱动设备对象 (ObReferenceObjectByName(IoDriverObjectType))), pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = MySetInformation, 然后在 MySetInformation 中再调用原来的调度函数, 类似于 HookApi; 发现有删除需保护文件的 IRP, 就直接 IoCompleteRequest, 根本不让原来的 FsSetInformation 处理!

有什么方法可以删除呢? 自己写个驱动来修复 Fs 的 dispatch 函数表, 读 Fs 的原始文件, 根据 PE 文件得到 Fs 的 Entrypoint, dispatch 函数表的填充都在 EntryPoint 后面, 我们可以根据 Opcode 查找, XX XX XX XX 就是我们要找的 dispatch 的原始地址; 找到后 pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = XX XX XX XX, 然后就能删除文件啦!

FunOpc=MajorFunction*4+0x38
C7 46 FunOpc[<80]  XX XX XX XX  mov     dword ptr [esi+50h], offset _NtfsFsdSetInformation
C7 86 FunOpc[>=80] XX XX XX XX
C7 43 FunOpc[<80]  XX XX XX XX  mov     dword ptr [ebx+50h], offset _NtfsFsdSetInformation
C7 83 FunOpc[>=80] XX XX XX XX


  3 通过 ZwCreateFile 把文件锁定; 删除时报告 "文件正在使用, 禁止删除", 具体原理自己摸索吧, 反正是通过 ZwCreateFile 实现的!

有什么方法可以删除呢?
  step1: 通过 QuerySystemInformation(SystemHandleInformation) 得到当前系统的所有句柄信息
  step2: 遍历当前所有进程, 根据进程 ID, 得到此进程打开的所有句柄信息
  Step3: 把句柄发送给我们的驱动程序, 驱动程序根据 ObQueryNameString 得到句柄的路径信息, 然后再传给我们的应用程序
  Step4: 如果是我们要删除文件的路径, 应用程序调用 DuplicateHandle(DUPLICATE_CLOSE_SOURCE), 句柄被关闭了, 现在可以删除文件了!
  注: QuerySystemInformation 的使用说明见 The Undocumented Functions, 或者 http://undocumented.ntinternals.net, 第三种解除文件锁定的方法是我反汇编 Unlocker 软件学习到的


  最后感谢 7cat 的帮助!

====================================================================
以下为原创

free2000fly 按:
unlocker 软件真是搞的百转千回, 还用到了驱动, 其实不必要, 在ring3是可以通过文件句柄得到文件名的, 以下是删除已被锁定文件的代码:
BOOL DeleteLockedFile(DWORD dwProcessID, HANDLE hFile)
{
    TCHAR szTargetName[MAX_PATH] 
= { 0 };
    HANDLE hTargeFile 
= INVALID_HANDLE_VALUE;
    HANDLE hProcess 
= NULL;
    BOOL bResult 
= FALSE;
    
    
do 
    {
        hProcess 
= OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
        
if (NULL == hProcess) {
            
break;
        }
        
        
if (FALSE == DuplicateHandle(hProcess, hFile, 
            GetCurrentProcess(), 
&hTargeFile, 
            
0, FALSE, DUPLICATE_SAME_ACCESS)) 
        {
            
break;
        }
        
        
if (INVALID_HANDLE_VALUE==hTargeFile || NULL==hTargeFile) {
            
break;
        }

        
if (FALSE == GetFilePathFromHandle(hTargeFile, 
            szTargetName, _countof(szTargetName))) 
        {
            
break;
        }

        CloseHandle(hTargeFile);
        hTargeFile 
= INVALID_HANDLE_VALUE;
        
        
if (0 == lstrlen(szTargetName)) {
            
break;
        }
        
        
if (FALSE == DuplicateHandle(hProcess, hFile, 
            GetCurrentProcess(), 
&hTargeFile, 
            
0, FALSE, 
            DUPLICATE_SAME_ACCESS
|DUPLICATE_CLOSE_SOURCE))  // 关键标志
        {
            
break;
        }

        
if (INVALID_HANDLE_VALUE==hTargeFile || NULL==hTargeFile) {
            
break;
        }
        
        CloseHandle(hTargeFile);
        hTargeFile 
= INVALID_HANDLE_VALUE;

        bResult 
= DeleteFile(szTargetName);
    } 
while (FALSE);

    
if (INVALID_HANDLE_VALUE != hTargeFile && NULL != hTargeFile) {
        CloseHandle(hTargeFile);
    }
    
    
if (hProcess) {
        CloseHandle(hProcess);
    }
    
return bResult;
}
其中函数 GetFilePathFromHandle 的实现请看我的 另一篇博文 " 从文件句柄得到文件路径的函数 "

PS. 最近几天试图实现枚举已打开所有文件的功能, 我却一而再,再而三的在函数 NtQueryObject 和 ZwQueryInformationFile 上遭遇挂起的现象, 这两个函数在被调用后有可能不再返回, 软件失去响应, 这是相当要命的事, 后来的解决方法是, 用一个独立的线程来调用这两个函数, 如果超时则立即返回, 但在需要讲求效率的场合这个解决方法行不通. 查遍网络才发现这可能算是 Windows 系列操作系统的一个 BUG.  因此, 最后, 还是回到了必须使用驱动的老路上去了.  2009.12.05

参考:
http://www.codeproject.com/KB/shell/OpenedFileFinder.aspx
http://www.codeproject.com/Articles/35202/GetFinalPathNameByHandle-API-Hangs.aspx
http://www.codeguru.com/forum/showthread.php?t=359606
http://bbs.pediy.com/showthread.php?t=60190

posted on 2009-07-14 02:45 free2000fly 阅读(6300) 评论(2)  编辑 收藏 引用

评论:
# re: 操作被占用的文件-unlocker机理分析 2009-12-22 14:05 | marihees
大哥您好,小弟有一点不明白 DeleteLockedFile(DWORD dwProcessID, HANDLE hFile) dwProcessID这个锁定文件的进程ID如何获得? hFile传入的是?我想改成DeleteLockedFile(CString strFilePath) 该如何做呢?  回复  更多评论
  
# re: 操作被占用的文件-unlocker机理分析 2012-05-07 11:33 | 断了的猫
强大 谢谢分享   回复  更多评论
  

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理