操作被占用的文件-unlocker机理分析
2009-10-11 21:54
操作被占用的文件-unlocker机理分析
==================================================================== 以下为转帖 [前言] —————————————————————— 之前给原作者发过邮件, 不过没能得到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 的实现请看我的 另一篇博文 " 从文件句柄得到文件路径的函数 " 1.运行中的程序或DLL,任何运行中的程序或DLL都可以被删除,包括FileKill360本身 系统文件也可以删除,不过系统的文件检查会将其自动恢复,若关闭自动恢复或将dllcache中文件一起删除,也可以删除之 这点超越金山粉碎机、Unlocker、PowerRMv等使用MoveFileEx等不稳定或容易失败技巧的文件删除工具 被运行中的文件进程功能可能受到影响,但如果是来删除流氓软件或木马的DLL或文件,却是再好不过了 2.被独占打开的文件,被独占(拒绝读写共享)的文件,也可以删除,包括MY123系列驱动 这点目前金山粉碎机、Unlocker、PowerRMv都无法做到 独占打开的文件被删除后,不会消失,但任何软件都无法将其打开,这样可以防止恶意软件在后台重建该文件 等关机或独占该文件的进程退出后,文件会自动消失 3.被IFS文件过滤驱动保护的文件,比如百度搜霸的被保护文件、彩信通的被保护文件等 4.被Fsd Dispatch Hook保护的文件,如万能搜索保护的文件等 5.被fsd inline hook保护的文件,如cnnic保护的文件等 直接发IRP删除文件 然后Hook fastfat.sys和Ntfs.sys上IAT的MmFlushImageSection 在NewMmFlushImageSection中判断其&SectionObjectPointer->DataSectionObject->Fileobject是否为当前要删除的文件的FileObject,若是,返回SUCCESS 即可删除运行中的文件 删除句柄被锁文件可以用360IceBreaker里 No Handle的办法 删除增加自己Ref的办法:找到FileObject的Object_Header中PointCount,清0之 再结合360IceBreaker干净的fsd image和fsd dispatch routine,基本文件系统层面上没有删除不了的文件了 无论是正在运行的程序或文件,或者是独占打开的文件,包括其他任何FSD驱动保护的文件等 |