前面已經介紹過API的hook方法及具體實踐,本文則講述COM組件的Hook方式。COM組件可簡單理解爲一個二進制可執行程序或DLL,內部包含一系列的接口和函數。Hook COM本質上也是進行函數地址切換,讓它跳到我們自定義的函數執行我們想要的功能。但這一切同樣是發生在系統架構下,自定義函數的執行時機不由我們觸發。
一次完整的Hook流程包括以下三步:
- 定位COM,確定自己需要hook的目標。可以從msdn和vs sdk上尋求幫助,獲取目標COM的接口細節,比如組件的CLSID、接口的IID等信息,可關注shellapi.h、shobjidl.h等頭文件。
- 參考目標COM的接口信息,實現自定義的COM功能。
- 將自定義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核心已完成。