Windows Hook经验总结之四:COM组件Hook原理及实践

前面已经介绍过API的hook方法及具体实践,本文则讲述COM组件的Hook方式。COM组件可简单理解为一个二进制可执行程序或DLL,内部包含一系列的接口和函数。Hook COM本质上也是进行函数地址切换,让它跳到我们自定义的函数执行我们想要的功能。但这一切同样是发生在系统架构下,自定义函数的执行时机不由我们触发。 
一次完整的Hook流程包括以下三步:

  1. 定位COM,确定自己需要hook的目标。可以从msdn和vs sdk上寻求帮助,获取目标COM的接口细节,比如组件的CLSID、接口的IID等信息,可关注shellapi.h、shobjidl.h等头文件。
  2. 参考目标COM的接口信息,实现自定义的COM功能。
  3. 将自定义COM注册生效。

本文以IFileOperation的文件拷贝接口为例进行阐述。

定位COM

XP时代,文件目录操作以一个个独立的API函数形式存在于kernel32.dll中,如CopyFile、CreateDirectory、DeleteFile等。WIN7之后,微软改了这一层的实现,改用IFileOperation囊括原有的API集。当然,这一信息来自baidu、msdn。然后就是确定IFileOperation的接口虚函数表,这个已在vs2008的shobjidl.h头文件中声明,摘录如下。
  • 1
  • 2
IFileOperation : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE Advise( 
    /* [in] */ __RPC__in_opt IFileOperationProgressSink *pfops,
    /* [out] */ __RPC__out DWORD *pdwCookie) = 0;

virtual HRESULT STDMETHODCALLTYPE Unadvise( 
    /* [in] */ DWORD dwCookie) = 0;

virtual HRESULT STDMETHODCALLTYPE SetOperationFlags( 
    /* [in] */ DWORD dwOperationFlags) = 0;

virtual HRESULT STDMETHODCALLTYPE SetProgressMessage( 
    /* [string][in] */ __RPC__in LPCWSTR pszMessage) = 0;

virtual HRESULT STDMETHODCALLTYPE SetProgressDialog( 
    /* [in] */ __RPC__in_opt IOperationsProgressDialog *popd) = 0;

virtual HRESULT STDMETHODCALLTYPE SetProperties( 
    /* [in] */ __RPC__in_opt IPropertyChangeArray *pproparray) = 0;

virtual HRESULT STDMETHODCALLTYPE SetOwnerWindow( 
    /* [in] */ __RPC__in HWND hwndParent) = 0;

virtual HRESULT STDMETHODCALLTYPE ApplyPropertiesToItem( 
    /* [in] */ __RPC__in_opt IShellItem *psiItem) = 0;

virtual HRESULT STDMETHODCALLTYPE ApplyPropertiesToItems( 
    /* [in] */ __RPC__in_opt IUnknown *punkItems) = 0;

virtual HRESULT STDMETHODCALLTYPE RenameItem( 
    /* [in] */ __RPC__in_opt IShellItem *psiItem,
    /* [string][in] */ __RPC__in LPCWSTR pszNewName,
    /* [unique][in] */ __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;

virtual HRESULT STDMETHODCALLTYPE RenameItems( 
    /* [in] */ __RPC__in_opt IUnknown *pUnkItems,
    /* [string][in] */ __RPC__in LPCWSTR pszNewName) = 0;

virtual HRESULT STDMETHODCALLTYPE MoveItem( 
    /* [in] */ __RPC__in_opt IShellItem *psiItem,
    /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder,
    /* [string][unique][in] */ __RPC__in_opt LPCWSTR pszNewName,
    /* [unique][in] */ __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;

virtual HRESULT STDMETHODCALLTYPE MoveItems( 
    /* [in] */ __RPC__in_opt IUnknown *punkItems,
    /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder) = 0;

virtual HRESULT STDMETHODCALLTYPE CopyItem( 
    /* [in] */ __RPC__in_opt IShellItem *psiItem,
    /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder,
    /* [string][unique][in] */ __RPC__in_opt LPCWSTR pszCopyName,
    /* [unique][in] */ __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;

virtual HRESULT STDMETHODCALLTYPE CopyItems( 
    /* [in] */ __RPC__in_opt IUnknown *punkItems,
    /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder) = 0;

virtual HRESULT STDMETHODCALLTYPE DeleteItem( 
    /* [in] */ __RPC__in_opt IShellItem *psiItem,
    /* [unique][in] */ __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;

virtual HRESULT STDMETHODCALLTYPE DeleteItems( 
    /* [in] */ __RPC__in_opt IUnknown *punkItems) = 0;

virtual HRESULT STDMETHODCALLTYPE NewItem( 
    /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder,
    /* [in] */ DWORD dwFileAttributes,
    /* [string][unique][in] */ __RPC__in_opt LPCWSTR pszName,
    /* [string][unique][in] */ __RPC__in_opt LPCWSTR pszTemplateName,
    /* [unique][in] */ __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;

virtual HRESULT STDMETHODCALLTYPE PerformOperations( void) = 0;

virtual HRESULT STDMETHODCALLTYPE GetAnyOperationsAborted( 
    /* [out] */ __RPC__out BOOL *pfAnyOperationsAborted) = 0;

    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

实现自定义的COM功能

CopyFile在IFileOperation中对应的是CopyItems。我们只需要实现它即可。不多说了,上代码:

/*操作类型*/
enum OPTYPE { UNDEF = 0, OT_MOVE, OT_COPY, OT_DELETE, OT_NEW};
/*函数类型声明*/
typedef HRESULT (WINAPI *PFO_COPYITEMS)(IFileOperation*, IUnknown*, IShellItem*);
/*自定义结构体*/
typedef WCHAR FILEPATH[MAX_PATH];
typedef struct _FILE_OPERATION_ITEM_
{
    OPTYPE      opType;
    UINT        nCount;
    FILEPATH*   szSource;
    FILEPATH    szDest;
} FileOperationItem, *LPFileOperationItem;

/*线程局部变量,初始化*/
DWORD m_dwTLS = TlsAlloc();

/*如果留意到上面的IFileOperation定义,可以看到CopyItems第一个参数的不同,是为了以C样式调用*/
HRESULT WINAPI  CopyItems(IFileOperation *pThis, IUnknown *punkItems, IShellItem *psiDestinationFolder)
{
    /*获取目标目录*/
    LPWSTR lpDst = NULL;
    psiDestinationFolder->GetDisplayName(SIGDN_FILESYSPATH, &lpDst);

    /*获取文件项*/
    LPFileOperationItem pfoi = GetOperationItem();  
    pfoi->nCount = GetFilesFromDataObject(punkItems, &(pfoi->szSource));
    for (UINT i = 0; i < pfoi->nCount; i++)
    {
        /*pfoi->szSource[i]);*/
        /*可以在这里添加自定义代码*/
    }

    pfoi->opType = OT_COPY;
    wcscpy_s(pfoi->szDest, MAX_PATH, lpDst);

    /*从虚函数表中取函数指针执行,会调用PerformOperations接口执行真正的Copy动作*/
    HRESULT hr = ((PFO_COPYITEMS)pfn)(pThis, punkItems, psiDestinationFolder);
    if(!SUCCEEDED(hr))
        FreeFileOperationItem();

    /*释放*/
    CoTaskMemFree(lpDst);
    return hr;
}

/*获取文件列表*/
UINT    GetFilesFromDataObject(IUnknown *iUnknown, FILEPATH **ppPath)
{
    UINT uFileCount = 0;
    IDataObject *iDataObject = NULL;
    HRESULT hr = iUnknown->QueryInterface(IID_IDataObject, (void **)&iDataObject);
    if(!SUCCEEDED(hr))
        return uFileCount;

    FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stg = { TYMED_HGLOBAL };  
    if(!SUCCEEDED(iDataObject->GetData(&fmt, &stg)))
        return uFileCount;

    HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal);
    if(hDrop == NULL)
        return uFileCount;

    /*获取文件个数*/
    uFileCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
    if(uFileCount <= 0)
        return uFileCount;

    *ppPath = new FILEPATH[uFileCount];
    if(*ppPath != NULL)
    {
        /*获取文件路径*/
        for(UINT uIndex = 0; uIndex < uFileCount; uIndex++)
            DragQueryFileW(hDrop, uIndex, (*ppPath)[uIndex], MAX_PATH);
    }
    else
    {
        uFileCount = 0;
    }

    GlobalUnlock(stg.hGlobal);
    ReleaseStgMedium(&stg);   

    return uFileCount;
}

/*获取线程TLS数据*/
LPFileOperationItem GetOperationItem()
{
    if(!TlsGetValue(m_dwTLS))
    {
        LPFileOperationItem pfoi = new FileOperationItem();
        memset(pfoi->szDest, 0, MAX_PATH * sizeof(TCHAR));
        pfoi->nCount    = 0;
        pfoi->szSource  = NULL;
        pfoi->opType    = UNDEF;
        TlsSetValue(m_dwTLS, pfoi);
    }

    return (LPFileOperationItem)TlsGetValue(m_dwTLS);
}

/*释放线程TLS数据*/
void    FreeFileOperationItem()
{
    LPFileOperationItem pfoi = GetOperationItem();
    if(pfoi->szSource)
    {
        delete[] pfoi->szSource;
        pfoi->szSource = NULL;
    }
    pfoi->opType = UNDEF;
    pfoi->nCount = 0;
    memset(pfoi->szDest, 0, MAX_PATH * sizeof(TCHAR));
    delete pfoi;
}

/*这里才是真正的文件操作函数接口*/
HRESULT WINAPI  PerformOperations(IFileOperation *pThis)
{
    /*执行原定行为前,可以添加自定义的pre-event-code*/

    /*这里可以调用CreateFile/ReadFile/WriteFile/CloseHandle API */

    /*获取接口虚函数表中的PerformOperations函数地址执行*/
    HRESULT hr = ((PFO_PERFORMOPERATIONS)pfn)(pThis);
    if(!SUCCEEDED(hr))
        return hr;

    /*这里已执行完原始的行为,可以追加自定义的 post-event-code */
    LPFileOperationItem pfoi = GetOperationItem();
    OPTYPE opType = pfoi->opType;
    if (OT_COPY != opType && OT_MOVE != opType)
        return hr;  
    for(unsigned int i = 0; i < pfoi->nCount; ++i)
    {
        FILEPATH szDest;
        wcscpy_s(szDest, MAX_PATH, pfoi->szDest);
        PathAddBackslashW(szDest);
        LPCWSTR szTemp = (LPCWSTR)wcsrchr(pfoi->szSource[i], TEXT('\\'));
        wcscat_s(szDest, MAX_PATH, ++szTemp);

        DWORD dwAttributes = GetFileAttributesW(szDest);
        if(INVALID_FILE_ATTRIBUTES == dwAttributes)
            continue;
    }

    /*释放线程局部数据*/
    FreeFileOperationItem();

    return hr;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153

注册全局钩子WH_CBT

创建实例,改写虚函数表

/*IUnknown的虚函数不做处理,定义虚函数表中的函数index*/
#define IFO_UNADVISE                    4
#define IFO_MAX                         23 /*虚函数表中有23项*/
/* 虚函数表*/
struct _IFO_HOOK_{
        size_t  Index; /*函数序号*/
        size_t  Original;/*原函数指针*/
        size_t  HookFn;/*hook所用函数指针*/
}IFO[IFO_MAX];

LPVOID  m_piFileOperation = NULL;

/*初始化,创建COM对象实例,改写虚函数表*/
BOOL    HookInit()
{    
    /*COM实例化*/
    CoInitialize(NULL);
    m_piFileOperation = NULL;
    HRESULT hr = CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_SERVER, IID_IFileOperation, &m_piFileOperation);

    /*申请TLS数据*/
    m_dwTLS = TlsAlloc();
    assert(TLS_OUT_OF_INDEXES > m_dwTLS);

    /*获取IFileOperation虚函数表项*/
    for (int i = IFO_UNADVISE + 1; i < IFO_MAX; i++)
        IFO[i].Original = GetOriginalFunc(m_piFileOperation, 0, i);

    HookVtbl(m_piFileOperation, 0, IFO_SET_OPERATION_FLAGS,         (size_t)SetOperationFlags);
    HookVtbl(m_piFileOperation, 0, IFO_SET_PROGRESS_MESSAGE,        (size_t)SetProgressMessage);
    HookVtbl(m_piFileOperation, 0, IFO_SET_PROGRESS_DIALOG,         (size_t)SetProgressDialog);
    HookVtbl(m_piFileOperation, 0, IFO_SET_PROPERTIES,              (size_t)SetProperties);
    HookVtbl(m_piFileOperation, 0, IFO_SET_OWNER_WINDOW,            (size_t)SetOwnerWindow);
    HookVtbl(m_piFileOperation, 0, IFO_APPLY_PROPERTIES_TO_ITEM,    (size_t)ApplyPropertiesToItem);
    HookVtbl(m_piFileOperation, 0, IFO_APPLY_PROPERTIES_TO_ITEMS,   (size_t)ApplyPropertiesToItems);
    HookVtbl(m_piFileOperation, 0, IFO_RENAME_ITEM,                 (size_t)RenameItem);
    HookVtbl(m_piFileOperation, 0, IFO_RENAME_ITEMS,                (size_t)RenameItems);
    HookVtbl(m_piFileOperation, 0, IFO_MOVE_ITEM,                   (size_t)MoveItem);
    HookVtbl(m_piFileOperation, 0, IFO_MOVE_ITEMS,                  (size_t)MoveItems);
    HookVtbl(m_piFileOperation, 0, IFO_COPY_ITEM,                   (size_t)CopyItem);
    HookVtbl(m_piFileOperation, 0, IFO_COPY_ITEMS,                  (size_t)CopyItems);
    HookVtbl(m_piFileOperation, 0, IFO_DELETE_ITEM,                 (size_t)DeleteItem);
    HookVtbl(m_piFileOperation, 0, IFO_DELETE_ITEMS,                (size_t)DeleteItems);
    HookVtbl(m_piFileOperation, 0, IFO_NEW_ITEM,                    (size_t)NewItem);
    HookVtbl(m_piFileOperation, 0, IFO_PERFORM_OPERATIONS,          (size_t)PerformOperations);
    HookVtbl(m_piFileOperation, 0, IFO_GET_ANY_OPERATION_ABORTED,   (size_t)GetAnyOperationsAborted);

    return TRUE;
}

/*虚函数表中函数地址*/
size_t  GetOriginalFunc(void* pObject, size_t classIdx, size_t methodIdx)
{
    size_t** vtbl = (size_t**)pObject;
    return vtbl[classIdx][methodIdx];
}

/*hook的核心代码,改写虚函数表*/
size_t  HookVtbl(void* pObject, size_t classIdx, size_t methodIdx, size_t newMethod)
{
    size_t** vtbl = (size_t**)pObject;
    DWORD oldProtect = 0;
    size_t oldMethod = vtbl[classIdx][methodIdx];
    VirtualProtect(vtbl[classIdx] + sizeof(size_t*) * methodIdx, sizeof(size_t*), PAGE_READWRITE, &oldProtect);

    vtbl[classIdx][methodIdx] = newMethod;
    VirtualProtect(vtbl[classIdx] + sizeof(size_t*) * methodIdx, sizeof(size_t*), oldProtect, &oldProtect);

    // HOOK
    IFO[methodIdx].Original = oldMethod;
    IFO[methodIdx].HookFn   = newMethod;    
    return oldMethod;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

安装全局钩子WH_CBT

HOOK m_hHook;
HHOOK   InstallHook()
{
    m_hHook = SetWindowsHookEx(WH_CBT, &HOOKCOMPROC, m_hModule, 0);
    if (m_hHook)
    {
        if (IsProcess(TEXT("RUNDLL32.EXE")))
        {
            EnterMessageLoop();
        }
    }

    return m_hHook;
}

LRESULT CALLBACK HOOKCOMWNDPROC(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
    if (WM_DESTROY == nMsg)
        PostQuitMessage(0);
    return DefWindowProc(hWnd, nMsg, wParam, lParam);
}

/*创建Wnd以执行消息循环*/
void    EnterMessageLoop()
{
    LPCTSTR lpszClassName = TEXT("HOOKCOMWND");
    WNDCLASSEX wcex     = {sizeof(wcex)};       
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = (WNDPROC)HOOKCOMWNDPROC;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = m_hModule;
    wcex.hIcon          = LoadIcon(NULL, IDI_INFORMATION);
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wcex.lpszClassName  = lpszClassName;

    if(RegisterClassEx(&wcex))
    {
        HWND hWnd = CreateWindowEx(0, lpszClassName, NULL, WS_OVERLAPPEDWINDOW, 0, 0, 50, 50, NULL, NULL, m_hModule, NULL);
        if(IsWindow(hWnd))
        {
            UpdateWindow(hWnd);

            MSG msg;            
            while(GetMessage(&msg, hWnd, 0, 0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }   
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

到此,COM的hook核心已完成。

猜你喜欢

转载自blog.csdn.net/bruce135lee/article/details/80951361