前面已经介绍过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头文件中声明,摘录如下。
- 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核心已完成。