随机应变:深度研究COM接口函数的Hook技巧

在平常的事情中,应用hook操纵偶然可以或者大大进步咱们的事情效力。此中,典型的例子有:为了便于对法式停止调试,咱们可以或者对Windows API函数履行hook操纵或者为了停止歹意软件检测,咱们也可以或者对Windows API函数履行hook操纵。在这些场景中,每每是一些DLL被注入到目标过程中,而后对相干的功效函数履行hook操纵,有几种方法可以或者做到这一点,但这并非本文要评论辩论的重点,感兴趣的读者可以或者在网上搜刮与DLL相干的技巧文章。
在组件对象模子(COM)的天下中,想要对COM组件接口函数履行hook操纵并非一件轻易的事情。缘故原由是由于COM组件是基于对象的,是以平日情况下咱们是不可以或者得到COM组件接口函数地点的。而且,由于COM组件接口函数不直接导出,是以经由过程挪用GetProcAddress或类似的方法也是无奈找到这些函数地点的。别的,纵然COM组件中的某些函数的地点被找到,履行hook操纵也必要将一些代码注入到目标过程中,这类操纵在某些情况下(比方受掩护的过程)险些是不可以或者实现的。
但颠末咱们的研讨发明,COM组件供给了另一种“hooking”操纵或者相当于重定向的机制,此机制可以或者将一个CLSID重定向到另一个CLSID,MSDN文档中把这类机制称之为“仿真”功效,就像一个类可以或者模仿另一个类的功效似的。这类机制在一定程度上打开了将一个类重定向到另一个类的可以或者性,而不必要注入代码到目标过程中或者对某些函数履行hook操纵。
Hooking COM接口函数实例
上面让咱们一路来看一个详细的例子。Windows中的后盾智能传输办事(BITS)供给了异步下载/上传办事,该办事具备进度关照,收集主动规复等功效。歹意软件可以或者经由过程BITS办事来下载其有用载荷,而不必要其直接下载有用载荷,直接下载会使歹意软件更轻易裸露在反歹意软件检测对象中。经由过程BITS下载使得歹意软件与下载的有用载荷没有甚么直接的联系关系,进一步加大了对歹意软件的检测难度。
由于BITS是基于COM组件来实现的,是以可以或者应用上述所说的“仿真”机制对任何必要得到BITS操纵的函数履行hook操纵,以得到BITS模块的履行成果。实现这个设法主意的症结是COM组件中的CoTreatAsClass 这个API函数,该函数将一个TreatAs键添加到原始键中,并将其值指向备用的CLSID。尽管如此,可以或者咱们照样没有方法来对某些BITS操纵履行hook操纵,缘故原由是由于任何COM激活哀求(比方经由过程CoCreateInstance)都将重定向到TreatAs CLSID,是以咱们没有方法只重定向某些特定的哀求。
是以,对哪些CLSID应当履行hook操纵是咱们应当要好好斟酌和研讨的成绩。在BITS的情况下,咱们必要操纵的对象是BackgroundCopyManager,它是应用BITS操纵时肯定会创立的对象。或者你们会觉得一个更好的CLSID目标对象是由BITS创立的现实Job对象,然则如许做将起不到任何的感化,由于没有如许的CLSID对象。像很多基于COM组件的API同样,只有几个对象现实上可以或者应用COM大众激活API创立并具备CLSIDs。经由过程函数直接创立的一些对象,是不必要经由过程COM激活机制的。
如下是创立“替代类”以拦阻BITS哀求所需的代码:
HRESULT hr = ::CoTreatAsClass(
__uuidof(BackgroundCopyManager),
   __uuidof(FakeBitsManager));
上述代码中的FakeBitsManager是我创立的一个COM类,该类必要实现与原始对象(IBackgroundCopyManager)雷同的接口;不然,机械上的一切BITS操纵都将失败!由于拜访权限的缘故原由,上述CoTreatAsClass函数将挪用失败,纵然从高权限的过程中挪用也是无用的。缘故原由是HKCRCLSID {4991d34b-80a1-4291-83b6-3328366b9097}下的这个注册表键值由TrustedInstaller领有,它不允许被窜改,乃至不允许体系帐户去改动这个键值! 但是,超等管理员用户可以或者经由过程履行Take Ownership特权,成为该注册表键值的新领有者,是以咱们照样有权变动该键值权限的,如下图所示,咱们如今曾经领有对该键值的改动权限:

那末,咱们在哪里挪用CoTreatAsClass函数比拟适合呢?颠末一些试验咱们发明该挪用可以或者在一些装置法式中实现,装置法式可以或者是一个简略的批处置或PowerShell剧本,而且可以或者直接应用注册表函数停止准确的设置,详细如下图所示:

上面是一个应用ATL实现的COM类,经由过程实现IBackgroundCopyManager和IUnknown以用来对BITS管理器类履行拦阻操纵,代码如下所示:
class ATL_NO_VTABLE CFakeBitsManager :
public CComObjectRootEx,
public CComCoClass,
public IBackgroundCopyManager
{
//...
    BEGIN_COM_MAP(CFakeBitsManager)
        COM_INTERFACE_ENTRY(IBackgroundCopyManager)
    END_COM_MAP()
    HRESULT FinalConstruct();
    CComPtr m_spRealBits;
public:
    STDMETHOD(CreateJob)(
        LPCWSTR DisplayName,
        BG_JOB_TYPE Type,
        __RPC__out GUID *pJobId,
        IBackgroundCopyJob **ppJob);
    STDMETHOD(GetJob)(
        REFGUID jobID,
        IBackgroundCopyJob **ppJob);
    STDMETHOD(EnumJobs)(
        DWORD dwFlags,

        IEnumBackgroundCopyJobs **ppEnum);
    STDMETHOD(GetErrorDescription)(
        HRESULT hResult,
        DWORD LanguageId,
        LPWSTR *pErrorDescription);
};
OBJECT_ENTRY_AUTO(__uuidof(FakeBitsManager), CFakeBitsManager)
上述代码只是IBackgroundCopyManager类实现中的部门代码,必要咱们重点存眷的是IBackgroundCopyManager接口映照函数的申明和重新文件复制的四个方法。另一个必要咱们重点存眷的是IBackgroundCopyManager(m_spRealBits)智能指针的另一个实现,这个成员应当会被初始化为真实的BITS管理器,由于咱们不想让一切的BITS操纵都失败,是以咱们可以或者将哀求转发给它。然则若何创立真实的BITS管理器呢?实在谜底很简略,咱们只必要封闭TreatAs,以便疾速挪用创立真实的BITS,而后从新启动,代码如下所示:
HRESULT CFakeBitsManager::FinalConstruct() {
    auto hr = ::CoTreatAsClass(__uuidof(BackgroundCopyManager), CLSID_NULL);
    ATLASSERT(hr);
    hr = m_spRealBits.CoCreateInstance(__uuidof(BackgroundCopyManager));
    ATLASSERT(SUCCEEDED(hr));
    hr = ::CoTreatAsClass(__uuidof(BackgroundCopyManager), __uuidof(FakeBitsManager));
    ATLASSERT(SUCCEEDED(hr));
    return hr;
}
应用CLSID_NULL的第二个参数挪用CoTreatAsClass会封闭“仿真”,机械上的其余客户端法式会有很小的机遇可以或者得到“真实的”BITS。然则这个光阴距离很小,并无一种简略的方法可以或者得到该BITS(基于注册表的回折衷ETW变乱会捕捉它)。但纵然如许,用户形式下的注册表回调可以或者还不敷快,是以它可以或者在规复以前留意不到该变动。固然,高权限的过程可以或者手动复原它,但这不是一个典型的客户端法式应当要斟酌的事情,由于它可以或者没有充足的权限去如许做。
如今咱们可以或者继承实现这些函数方法。任何“不友善”的函数方法都可以或者被简略地重定向到真实的BITS管理器。示例代码如下所示:
STDMETHODIMP CFakeBitsManager::GetJob(
    REFGUID jobID,
    IBackgroundCopyJob **ppJob) {
    return m_spRealBits->GetJob(jobID, ppJob);
}
STDMETHODIMP CFakeBitsManager::EnumJobs(
    DWORD dwFlags,
    IEnumBackgroundCopyJobs **ppEnum) {
    return m_spRealBits->EnumJobs(dwFlags, ppEnum);
}
此中,比拟风趣的函数是CreateJob函数,由于咱们可以或者应用该函数收集信息,注册变乱关照或其余信息。示例代码如下所示:
STDMETHODIMP CFakeBitsManager::CreateJob(
    LPCWSTR DisplayName, BG_JOB_TYPE Type,
    GUID *pJobId, IBackgroundCopyJob **ppJob) {
    if(Type != BG_JOB_TYPE_DOWNLOAD)
        return m_spRealBits->CreateJob(DisplayName, Type, pJobId, ppJob);
    auto hr = m_spRealBits->CreateJob(DisplayName, Type, pJobId, ppJob);
    if (FAILED(hr))
        return hr;
    // handle the successful job creation
    // register for notifications, log the download, ...
    return hr;
}
该示例表现,对付非下载功课义务,咱们只需将哀求传递给真实的BITS管理器处置便可。在下载功课义务创立时,咱们可以或者对该下载哀求履行任何的监督操纵,如许咱们就实现为了对COM接口函数的Hooking操纵。
总结
理论上讲,对COM接口函数履行hook操纵要比对Win32 API履行hook操纵可以或者会简略一些。由于hook Win32 API还必要咱们去理解一些汇编语言相干的常识,和机械码等底层常识。它们的配合的地方都是改动目标法式的履行流程,以到达对数据流停止监控的目标。对COM接口履行hook操纵是一个风趣的技巧,是以还必要进一步研讨和阐发。