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 的。

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

你可能感兴趣的:(WIN7 下 explorer 进行的文件移动COPY HOOK)