最近项目组须要实现一个对windows用户文件操做进行监控的功能,(也就是使用explorer资源管理器的操做),因而乎我就想到了使用Hook的方法进行拦截,查找一番资料后发现XP调用的是最简单的CopyFileEx, MoveFileWithProgressW, ReplaceFileW之类的API,因此XP是最好解决的,可是到了Vista及之后的系统中,微软采用了一种新的方法——com组件里的IID_IFileOperation接口类。这样的话就有点麻烦了,可是资料中也有人实现了对com组件的HOOK,是经过修改从com组件的虚表从而实现挂钩。所以,我也尝试了一下这种方法,就是Hook IFileOperation中的CopyItems/MoveItems/DeleteItem等接口从而实现对文件操做的监控。具体方法就下面给出。使用这种方法的过程当中,我发现了一些问题,以致于我没法彻底套用这种方法。问题以下:html
因为以上几种缘由,我没法经过上述方法实现我想要的功能,因此我开始另辟蹊径,我认为windows应该还有更详细更底层一点的接口能够供我使用,通过多天的查找和尝试,我终于找到了一个符合我需求的接口IFileOperationProgressSink。windows
对于这个接口的描述我给出微软的说明:api
Exposes methods that provide a rich notification system used by callers of IFileOperation to monitor the details of the operations they are performing through that interface.app
意思应该是说这个接口是用来响应IFileOperation的各类操做和消息的,还能获得更为详细的操做信息。而在这个接口类中,有着许多我寻找了许久的API,好比PreCopyItem/PreMoveItem/PreDeleteItem以及PostCopyItem/PostMoveItem等等系列API,而且根据微软官方的说明,Pre前缀系列的API是指在操做进行前的消息处理,Post前缀的系列API是指在操做完成后所调用的处理函数。下面以CopyItem为例,给出微软的说明。ide
Performs caller-implemented actions before the copy process for each item begins.函数
Performs caller-implemented actions after the copy process for each item is complete.post
再看看这些接口能给咱们一些什么信息。测试
如下是接口的定义和参数说明。this
HRESULT PostCopyItem( DWORD dwFlags, IShellItem *psiItem, IShellItem *psiDestinationFolder, LPCWSTR pszNewName, HRESULT hrCopy, IShellItem *psiNewlyCreated );
Parameters.net
dwFlags
bitwise value that contains flags that were used during the copy operation. Some values can be set or changed during the copy operation. See TRANSFER_SOURCE_FLAGS for flag descriptions.
psiItem
Pointer to an IShellItem that specifies the source item.
psiDestinationFolder
Pointer to an IShellItem that specifies the destination folder to which the item was copied.
pszNewName
Pointer to the new name that was given to the item after it was copied. This is a null-terminated Unicode string. Note that this might not be the name that you asked for, given collisions and other naming rules.
hrCopy
The return value of the copy operation. Note that this is not the HRESULT returned by CopyItem, which simply queues the copy operation. Instead, this is the result of the actual copy.
psiNewlyCreated
Pointer to an IShellItem that represents the new copy of the item.
从这里咱们就能看到,以上的问题都可以解决了。从API的命名能够看出这个接口是文件级的(由于是单数。。已测试),从参数能够直接获取到操做后的新文件名,甚至直接获得新文件的路径,都不用你去拼接,还有操做的结果也有,你可根据是否操做成功来进行操做。这样的话,你是想监控(Post)仍是想拦截(Pre)都也能够了。
找到这里我觉得我已经完成任务了,这么完美的接口,直接Hook不就完事了吗?然而事情并无这么简单。
我尝试使用跟上面同样的方法去HOOK这个接口,结果卡住了,由于Hook com组件的时候须要调用CoCreateInstance API来获取对应com组件的接口地址,从而Hook接口中对应的API,而调用这个API的最重要的两个参数(第一个和第四个),分别是这个com组件的方法类ID(CLSID),以及对应的接口类ID(IID),而我翻遍了全部的相关头文件和微软的相关资料都只有我须要的接口类IID_IFileOperationProgressSink{04b0f1a7-9490-44bc-96e1-4296a31252e2},没法找到对应的CLSID,我甚至去暴力遍历了对应注册表全部的CLSID挨个试,都没能找到。。。挣扎了2,3的天的我终于意识到微软压根就没给这个类的CLSID。因而我放弃了经过正道去Hook这个接口,而后在IFileOperation接口里找到了一个神奇的API——Advise。
PS1:在通过对不一样系统的测试时,我发现了使用Advise函数进行挂钩的行为不是很稳定,有时容易致使explorer崩溃,因此我在项目中放弃了这个方法,改为了使用下面这个方法。
PS2:在以后的尝试中,我不放弃的又去轮了一遍注册表,结果发现当时漏掉了很多注册表项,而后我就在win7 64位上找到了好几个(通常是3个)CLSID可以与上面的IID进行匹配,这三个CLSID分别对应不一样的操做行为,想要研究一下的人能够试试这个方法。
我所遍历到的CLSID以下:
//B能挂复制,删除,剪切,重命名,Win7有效
static const IID CLSID_IFileOperationProgressSinkB = {0x7abbbcca, 0xe01b, 0x4560, {0x82, 0x28, 0x92, 0xe1, 0xee, 0xdb, 0xc9, 0x08}};
//C能挂新建
static const IID CLSID_IFileOperationProgressSinkC = {0xD969A300, 0xE7FF, 0x11d0, {0xA9, 0x3B, 0x00, 0xA0, 0xC9, 0x0F, 0x27, 0x19}};
//Win10的复制,删除,剪切,重命名,功能与B相同
static const IID CLSID_IFileOperationProgressSinkD = {0xD105A4D4, 0x344C, 0x48EB, {0x98, 0x66, 0xEE, 0x37, 0x8D, 0x90, 0x65, 0x8B}};
IFileOperation::Advise method
Enables a handler to provide status and error information for all operations.
HRESULT Advise( IFileOperationProgressSink *pfops, DWORD *pdwCookie );
Pointer to an IFileOperationProgressSink object to be used for progress status and error notifications.
我发现这个函数的第一个参数就是我要的IFileOperationProgressSink的接口指针,而后我再看这个函数的说明,他说在全部的操做进行时都会调用它,再看一下第一个参数的说明,说会把全部的错误和状态都发送给IFileOperationProgressSink对象去处理,那就是说这两个接口是相互依存的了,那我可不能够经过Hook这个接口来获取接口地址再去Hook IFileOperationProgressSink类的相关方法呢?那就来试试吧。
使用Hook CopyItems同样的方法来Hook 一下Advise。具体实现以下:
static HRESULT __stdcall MyAdvise(IFileOperation* pThis,IFileOperationProgressSink *pfops,DWORD *pdwCookie) { OutputDebugString(_T("--------------Advise happened! ----------------")); //判断跳过回收站操做,挂上回收站会崩溃,回收站为0 if (pfops != NULL && *pdwCookie != 0) { if ((PostCopyItem_old == NULL/* || gs_bCopyFlag == FALSE*/) ) { PostCopyItem_old = (PPostCopyItem)HookVtbl((LPVOID)pfops, 0, PostCopyItem_Index, (size_t)MyPostCopyItem); if (PostCopyItem_old == NULL) OutputDebugString(_T("PostCopyItem Hook ERR!\n")); } if ((PostMoveItem_old == NULL/* || gs_bMoveFlag == FALSE*/)) { PostMoveItem_old = (PPostMoveItem)HookVtbl((LPVOID)pfops, 0, PostMoveItem_Index, (size_t)MyPostMoveItem); if (PostMoveItem_old == NULL) OutputDebugString(_T("PostMoveItem Hook ERR!\n")); } if ((PostDeleteItem_old == NULL || gs_bDelFlag == FALSE) && g_nSysVerCode <= SYS_VER_7) { //若是挂错了,从新挂回去 if (PostDeleteItem_old != NULL && gs_bDelFlag == FALSE) HookVtbl(pfops, 0, PostDeleteItem_Index, (size_t)PostDeleteItem_old); PostDeleteItem_old = (PPostDeleteItem)HookVtbl((LPVOID)pfops, 0, PostDeleteItem_Index, (size_t)MyPostDeleteItem); if (PostMoveItem_old == NULL) OutputDebugString(_T("PostDeleteItem Hook ERR!\n")); } } return Advise_old(pThis, pfops, pdwCookie); }
static HRESULT __stdcall MyPostCopyItem(IFileOperationProgressSink * This, DWORD dwFlags, IShellItem *psiItem, IShellItem *psiDestinationFolder,LPCWSTR pszNewName,HRESULT hrCopy,IShellItem *psiNewlyCreated) { OutputDebugString(_T("---------------PostCopyItem happened------------------\n\n")); if (pszNewName != NULL && wcslen(pszNewName) > 0 && SUCCEEDED(hrCopy)) { LPWSTR lpTmp = NULL; CString TmpSrc,TmpDst; psiItem->GetDisplayName(SIGDN_FILESYSPATH, &lpTmp); TmpSrc.Format(_T("Src: %s\n"), lpTmp); OutputDebugString(TmpSrc); CoTaskMemFree(lpTmp); psiNewlyCreated->GetDisplayName(SIGDN_FILESYSPATH, &lpTmp); TmpDst.Format(_T("Dst: %s\n"), lpTmp); OutputDebugString(TmpDst); CoTaskMemFree(lpTmp); } return PostCopyItem_old(This, dwFlags, psiItem, psiDestinationFolder, pszNewName, hrCopy, psiNewlyCreated); }
通过测试,完美实现了文件级监控的功能。
说明几点问题:
参考资料:
http://www.freebuf.com/column/134192.html
https://blog.csdn.net/zcl2770/article/details/52885634?locationNum=15&fps=1