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頭文件中聲明,摘錄如下。
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;

    };

實現自定義的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;
}

註冊全局鉤子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;
}

安裝全局鉤子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);
            }
        }   
    }
}

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章