WIN7 下 explorer 进行的文件移动COPY HOOK

在 WinXP 下通过HOOK explorer来截获用户的复制,剪切,粘贴是很容易的。只需要HOOK以下三个API:


CopyFileExW, MoveFileWithProgressW, ReplaceFileW,


至于 MoveFileA等函数,最终也是调用这三个函数,这点可以通过用IDA反kernel32.dll分析得来。


但在WIN7下却不是这个回事了,我使用上述技术做的程序在XP下能正常使用,到了WIN7下根本不行。


经调试WIN7的EXPLORER发现,对文件的移动等操作竟然似乎直接跳过了API层。


上网一搜,原来WIN7的explorer使用了COM来代替旧有的文件API,这个COM接口就是 IFileOperation。


知道了是哪个家伙,想黑它就比较容易了。


在网上找到的这个接口的定义:


Interface IUnknown


QueryInterface


AddRef


Release


EndInterface


 


Interface IFileOperation Extends IUnknown


Advise(pfops, pdwCookie)


Unadvise(in_dwCookie.l)


SetOperationFlags(in_dwOperationFlags.l)


SetProgressMessage(in_string_LPCWSTR_pszMessage)


SetProgressDialog(popd_in)


SetProperties(pproparray_in)


SetOwnerWindow(in_hwndParent.l)


ApplyPropertiesToItem(psiItem_in)


ApplyPropertiesToItems(punkItems_in)


RenameItem(psiItem_in, in_string_LPCWSTR_pszNewName, pfopsItem_in_unique)


RenameItems(pUnkItems_in, in_string_LPCWSTR_pszNewName)


MoveItem(psiItem_in, psiDestinationFolder_in, in_unique_string_LPCWSTR_pszNewName, pfopsItem_in_unique)


MoveItems(punkItems_in, psiDestinationFolder_in)


CopyItem(psiItem_in, psiDestinationFolder_in, in_unique_string_LPCWSTR_pszCopyName, pfopsItem_in_unique)


CopyItems(punkItems_in, psiDestinationFolder_in)


DeleteItem(psiItem_in, pfopsItem_in_unique)


DeleteItems(punkItems_in)


NewItem(psiDestinationFolder_in, in_dwFileAttributes.l, in_unique_string_LPCWSTR_pszName, in_unique_string_LPCWSTR_pszTemplateName, pfopsItem_in_unique)


PerformOperations()


GetAnyOperationsAborted(pfAnyOperationsAborted_out_BOOL)


EndInterface


根据这个定义,我们可以知道 IFileOperation的虚函数表的样子,这样就为我们HOOK虚函数表提供了可能。


再根据虚函数表的静态特性,我们可以通过新建一个对象,修改这个对象的虚函数表,然后该类的其它对象也会受到影响。


虚函数表的细节不多说,只说下具体的HOOK相关代码。


写一个函数用来HOOK对象虛函数表,如下:


int HookVtbl(void* pObject, unsigned int classIdx, unsigned int methodIdx, int newMethod)


{


    int** vtbl = (int**)pObject;


    DWORD oldProtect = 0;


    int oldMethod = vtbl[classIdx][methodIdx];


    VirtualProtect(vtbl[classIdx] + sizeof(int*) * methodIdx, sizeof(int*), PAGE_READWRITE, &oldProtect);


    vtbl[classIdx][methodIdx] = newMethod;


    VirtualProtect(vtbl[classIdx] + sizeof(int*) * methodIdx, sizeof(int*), oldProtect, &oldProtect);


    return oldMethod;


}


如果了解对象模型,上面代码应该很好理解。参数1指对象指针,参数二指类索引,即要HOOK自己继承的第几个类的虚函数表,


如果没有父或只有一个父,则设置成0即可。参数三指成员函数的索引,参数四指要替换成的函数地址。


如想HOOK对象a的第二个父的第三个函数,则应传参 HookVtbl(a, 1, 2, xxx);


如果对虚函数表比较熟,则很容易利用上面这个函数HOOK对象。


 


下面我们看要HOOK的 IFileOperation 的函数索引,根据上面列出的接口定义,做出如下定义:


#define QueryInterface_Index 0


#define AddRef_Index (QueryInterface_Index + 1)


#define Release_Index (AddRef_Index + 1)


#define Advice_Index (Release_Index + 1)


#define Unadvise_Index (Advice_Index + 1)


#define SetOperationFlags_Index (Unadvise_Index + 1)


#define SetProgressMessage_Index (SetOperationFlags_Index + 1)


#define SetProgressDialog_Index (SetProgressMessage_Index + 1)


#define SetProperties_Index (SetProgressDialog_Index + 1)


#define SetOwnerWindow_Index (SetProperties_Index + 1)


#define ApplyPropertiesToItem_Index (SetOwnerWindow_Index + 1)


#define ApplyPropertiesToItems_Index (ApplyPropertiesToItem_Index + 1)


#define RenameItem_Index (ApplyPropertiesToItems_Index + 1)


#define RenameItems_Index (RenameItem_Index + 1)


#define MoveItem_Index (RenameItems_Index + 1)


#define MoveItems_Index (MoveItem_Index + 1)


#define CopyItem_Index (MoveItems_Index + 1)


#define CopyItems_Index (CopyItem_Index + 1)


#define DeleteItem_Index (CopyItems_Index + 1)


#define DeleteItems_Index (DeleteItem_Index + 1)


#define NewItem_Index (DeleteItems_Index + 1)


#define PerformOperations_Index (NewItem_Index + 1)


#define GetAnyOperationAborted_Index (PerformOperations_Index + 1)






须注意的是我们需要提供一个同样原型的函数来HOOK旧有函数,如果胡乱来,很容易造成栈失衡,崩溃去吧。


如我们想HOOK CopyItems,则应定义:


typedef HRESULT (__stdcall* PCopyItems)(IFileOperation*, IUnknown*, IShellItem*);


static PCopyItems CopyItems_old = NULL;


第一个参数是this指针,后面是常规参数。


下面那个函数指针变量是为了存放旧有的函数,然后提供一个新函数用来代替旧函数:


HRESULT __stdcall CopyItems_new(IFileOperation *pThis,


                                IUnknown *punkItems,


                                IShellItem *psiDestinationFolder)


{


    HRESULT hr = CopyItems_old(pThis, punkItems, psiDestinationFolder);


    OutputDebugStringA("调用了 CopyItems ");


    return hr;


}


这样一来,就可以使用 CopyItems_new 来代替旧有对象的 CopyItems 函数了。


为了方便,再定义宏如下:


#define HOOK(a, b) b##_old = (P##b)HookVtbl(a, 0, b##_Index, (PBYTE)b##_new) 


然后在我们的DLL加载时,我们就可以调用如下函数来完成HOOK了:


 static const IID CLSID_FileOperation = {0x3ad05575, 0x8857, 0x4850, {0x92, 0x77, 0x11, 0xb8, 0x5b, 0xdb, 0x8e, 0x09}};


static const IID IID_IFileOperation = {0x947aab5f, 0x0a5c, 0x4c13, {0xb4, 0xd6, 0x4b, 0xf7, 0x83, 0x6f, 0xc9, 0xf8}};


BOOL WINAPI StartHook()


{


    PVOID pInterface = NULL;


    CoInitialize(NULL);


    HRESULT hr = CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_SERVER, IID_IFileOperation, &pInterface);


    if(FAILED(hr))


    {


        OutputDebugStringA("CoCreateInstance 失败");


        return FALSE;


    }


    HOOK(pInterface, CopyItems);


    return TRUE;


}


注意CoCreateInstance之前应初始化一下。这个函数就利用了虚函数表的静态特性。


至于CLSID和IID,也是查到的。HOOK其他函数也是同样的做法,或者说HOOK所有的C++对象都是类似的做法。


这下就轻松搞定 WIN7 的 explorer了。
上面红色的代码就是关键代码了,这些代码是从我前几天刚做的一个模块中摘取的部分,


这个模块是用来完成USB设备保护的,使用户不能COPY文件到移动设备。


代码稍有改动,并未完全测试它能否正常工作。由于某些原因,我的代码不便贴上来。


但是只要理解了整个过程,则上面的代码就很清晰了。


处理文件的关键在于获取要COPY的所有文件名和COPY后的所有文件名。


COPY后所有文件名可以通过要COPY的文件名与COPY目标的目录合成得到,如要把 D:\a.txt COPY 到 C:\test\ 目录下,


则可以轻松知道COPY后文件名为 C:\test\a.txt ,当然,这是一般情况,还有其他情况如目标文件夹下已有同名文件,


需要保存为 a (1).txt 等情况,这些问题处理不是什么麻烦事,在此略过。


现在以 CopyItems 函数的HOOK为例说明获取源文件名和目标文件名的方法。


typedef WCHAR WPATH[MAX_PATH];


 


typedef struct _FileOperationItem


{


    UINT srcCounts;


    WPATH* srcList;


    WCHAR destFolder[MAX_PATH];


} FileOperationItem, *PFileOperationItem;


 


UINT GetFilesFromDataObject(IUnknown *iUnknown, WPATH **ppPath)


{


    UINT uFileCount = 0;


    IDataObject *iDataObject = NULL;


    HRESULT hr = iUnknown->QueryInterface(IID_IDataObject, (void **)&iDataObject);


 


    do


    {


        if(!SUCCEEDED(hr))


        {


            break;


        }


        FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };


        STGMEDIUM stg = { TYMED_HGLOBAL };


 


        if(!SUCCEEDED(iDataObject->GetData(&fmt, &stg)))


        {


            break;


        }


        HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal);


        if(hDrop == NULL)


        {


            break;


        }


        uFileCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);


        if(uFileCount <= 0)


        {


            break;


        }


        *ppPath = new WPATH[uFileCount];


        if(*ppPath != NULL)


        {


            for(UINT uIndex = 0; uIndex < uFileCount; uIndex++)


            {


                DragQueryFile(hDrop, uIndex, (*ppPath)[uIndex], MAX_PATH);


            }


        }


        else


        {


            uFileCount = 0;


        }


 


        GlobalUnlock(stg.hGlobal);


        ReleaseStgMedium(&stg);


    } while (FALSE);


 


    return uFileCount;


}


 


PFileOperationItem GetOperationItem()


{


    if(!TlsGetValue(g_opSlot))


    {


        PFileOperationItem foi = new FileOperationItem();


        memset(foi->destFolder, 0, sizeof(WCHAR) * MAX_PATH);


        foi->srcCounts = 0;


        foi->srcList = NULL;


        TlsSetValue(g_opSlot, foi);


    }


    return (PFileOperationItem)TlsGetValue(g_opSlot);


}


 


void WINAPI SetOperationState(OperationType type)


{


    if(TLS_OUT_OF_INDEXES != g_tlsSlot)


    {


        TlsSetValue(g_tlsSlot, (void*)type);


        return;


    }


    g_opType = type;


}


 


HRESULT __stdcall CopyItems_new(IFileOperation *pThis,


                                IUnknown *punkItems,


                                IShellItem *psiDestinationFolder)


{


    HRESULT hr = CopyItems_old(pThis, punkItems, psiDestinationFolder);


    if(SUCCEEDED(hr))


    {


        SetOperationState(COPY_FILE);


        LPWSTR lpDst = NULL;


        psiDestinationFolder->GetDisplayName(SIGDN_FILESYSPATH, &lpDst);


        PFileOperationItem foi = GetOperationItem();


        wcscpy(foi->destFolder, lpDst);


        foi->srcCounts =  GetFilesFromDataObject(punkItems, &(foi->srcList));


 


        CoTaskMemFree(lpDst);


    }


    return hr;


}






其中 GetFilesFromDataObject 函数是个关键所在,这个函数从 CopyItems_new 中的 IUnknow* 


参数反查要操作的文件名。COPY目标直接可以从 IShellItem* 参数,调用 GetDisplayName 来获取目标文件夹。


简单结构 FileOperationItem 的 srcCounts 表示本次操作的文件数目,srcList 表示源文件列表,destFolder 表示目标目录。


另外两个TLS 辅助函数是因为 explorer 可以有多个线程同时COPY操作。


这样,在 CopyItems_new 函数中就可以获取完整的文件列表,目标目录等信息。


MoveItems 和这个是一个思路,废话不再多说。


 


但是这还没有完,可以注意到 CopyItems_new 中只是记录了要操作的文件信息,并没有做别的处理。


这是因为 IFileOperation 还有一个函数: PerformOperations 。


此函数用来提交已排队的 COPY/MOVE 等操作。


比如COPY的目标已经有同名文件,则会询问是否覆盖云云,这时候才是 PerformOperations 起作用的时候。


即是说,只有调用完 PerformOperations 之后,才知道本次COPY/MOVE是否成功。


则应HOOK PerformOperations 函数,并做相应处理。只有在成功的时候,才对文件进行处理。


然后无论如何,都要清理之前文件操作申请的内存。


大致如下:


HRESULT __stdcall PerformOperations_new(IFileOperation* pThis)


{


    HRESULT hr = PerformOperations_old(pThis);


    PFileOperationItem foi = GetOperationItem();


    do


    {


        if(!SUCCEEDED(hr))


        {


            break;


        }


        if(!foi)


        {


            break;


        }






        for(int i = 0; i < foi->srcCounts; ++i)


        {


            ......


        }


        .....


    } while (FALSE);


 


    if(foi && foi->srcList)


    {


        delete[] foi->srcList;


        foi->srcList = NULL;


    }


    if(foi)


    {


        foi->srcCounts = 0;


    }


 


    return hr;


}






还一个细节在于,无论移动文件,删除文件,复制文件,都会调用 PerformOperations ,


所以如果需要的话,需要自己记录一下当前文件操作的类型,当然,这个类型信息也需要是 TLS 的。


以上代码为我项目实际代码中的摘除部分,表意为先,不一定可以直接使用。


转载来自: http://gobismoon.blog.163.com/blog/static/524428022011102111559162/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值