首页
社区
课程
招聘
[翻译]Hook COM 类
2020-9-22 12:40 25767

[翻译]Hook COM 类

2020-9-22 12:40
25767

有些场景下,我们会得益于hook操作。经典的例子就是hook Windows API函数用于调试目的,或者恶意检测。在这种情形下,一些DLL会被注入到目标进程然后hook相关函数。有很多方法可以做到这点,但这不是本篇博客的焦点。有兴趣的读者可以在网上搜索更多的信息。在组件对象模型的世界里,事情变得没有那么简单。因为COM是基于对象的,一般不可能获取COM 接口的地址,因为他不是直接导出的,不能通过类似调用GetProcAddress来获取到。

 

此外,即使以某种方式获取到了函数地址,hook操作仍然需要注入一些代码到目标进程。这通常不是那么容易,甚至在某些情况下是不可行的。比如说被保护的进程。

 

COM 提供了其他的hook方式,或者说重定向机制。这个机制提供了一种方式来重定向一个CLSID到另一个CLSID。MSDN文档里称这种能力为”仿真“,一个类可有模拟另外一个类。这使得重定向一个类到另外一个类而不需要任何的注入或者函数挂钩。

 

让我们来看一个具体的例子。Windows的后台智能传输服务(BITS)提供了异步下载/上传服务,而且具有进度通知,网络不稳定时自动恢复等功能。一个恶意软件可以通过BITS来下载他的payload。如果直接下载会使得恶意行为直接暴露给反恶意软件工具。通过BITS使恶意软件与下载的payload保持了一定距离,这使得反恶意工具更难识别发生了什么错误事件。

 

由于BITS是基于COM的。”仿真“技术可以用来"hook"任何BITS的操作和操作的结果。

 

这个技术的关键是通过CoTreatAsClassAPI为原始的CLISID key添加了一个TreatAs key,让其值指向代理CLSID。但这是一种极端的全盘接管,我们无法直接重定向具体的请求,因为任何COM的启动请求(比如说CoCreateInstance)都会被重定向到TreatAs指定的 CLSID。

 

具体哪个CLSIDs应该被hook呢? 对于BITS来说,比较简单。因为只有一个——BackgroundCopyManager,这是使用BITS时会创建的根类工厂对象。你可能会认为一个更好的CLSID是由BITS创建一个实际工作对象的CLSID,但这是行不通的,因为不存在这样的CLSID。和许多基于COM的API一样,只有少量拥有CLSID的对象可以被COM public activation API函数来创建。其他的则是间接创建的,不需要通过COM启动机制。(因此也不需要通过注册表)。

 

这样做是目的是为了达到控制。如果没有CLSID,那么在BITS管理不知情的情况下,就不能突然创建一个BITS Job。

 

下面的代码是注册一个用于拦截BITS请求的代理类:

1
2
3
HRESULT hr = ::CoTreatAsClass(
    __uuidof(BackgroundCopyManager),
    __uuidof(FakeBitsManager));

这个COM类——FakeBitManager需要实现原始对象(IBackgroundCopyManager)的所有接口,否则的话,所有的机器上的BITS操作都将失败。

 

请注意,特别是对于BITS来说,即使是使用提权后的进程来调用CoTreatAsClass,也会因拒绝访问而失败。原因是因为这个键下的所有者是TrustedInstaller,他不允许被篡改,即是是系统账号也不行。然而管理员可以行使获取所有权的特权,成为该键的所有者,从而改变他的权限。在这个实验中,我就是通过注册表权限编辑器修改的。

 

 

我们在哪里调用 CoTreatAsClass呢?应该在一些安装程序中完成,可以是简单的批处理程序或者PowerShell脚本使用注册表函数进行正确的设置。

 

最终结果是这样的:

 

 

下面是通过ATL实现的COM 类,他用于拦截BITS Manager的实现类 IBackgroundCopyManager和IUnknown。

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
class ATL_NO_VTABLE CFakeBitsManager:
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CFakeBitsManager,&CLSID_FakeBitsManager>,
    public IBackgroundCopyManager
{
//...
       BEGIN_COM_MAP(CFakeBitsManager)
        COM_INTERFACE_ENTRY(IBackgroundCopyManager)
    END_COM_MAP()
 
    HRESULT FinalConstruct();
 
    CComPtr<IBackgroundCopyManager> 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和4个拷贝自<bits.h>头文件中的方法。

 

此外,注意到一个智能指针m_spRealBits。这个数据成员应该被真正的BITS管理器初始化,这样我们可以把不关心的请求转发给他。

 

但是现在我们要如何创建一个真正的BITS管理器呢?任何尝试创建的操作都会跑到我们的类里。答案非常简单,我们需要临时关掉TreatAs,然后快速调用真正的BITS,接着再次打开TreatAs:

1
2
3
4
5
6
7
8
9
10
11
12
HRESULT CFakeBitsManager::FinalConstruct(){
    auto hr = ::CoTreatAsClass(__uuidof(BackgroundCopyManager),CLSID_NULL);
    ATLASSERT(hr);
 
    hr = m_spRealBits.CoCreateInsateInstance(__uuidof(BackgroundCopyManager));
    ATLASSERT(SUCCEEDED(hr));
 
    hr = ::CoTreatAsClass(__uuidof(BackgroundCopyManager),__uuidof(FakeBitsManager));
    ATLASSERT(SUCCEEDED(hr));
 
    return hr;
}

CoTreatAsClass的第二个参数如果为CLSID_NULL表明关闭仿真。这个切换过程中会存在那么一个窗口期使得机器上的其他客户程序能调用到实际的BITS。但是这个间隔十分短,不容易被察觉到。(不过基于注册表回调和ETW的事件记录可以捕获到这些改变),但是即使是这样,用户模式的注册表回调在状态反转之后,可能也来不及察觉到这点细微的改变。当然,提权的进程可以手动还原代理。但通常不会考虑客户端的这种情况,因为他们应该没有足够的权限来做这件事。

 

接下来,我们可以继续实现这些方法。任何不感兴趣的方法都可以简单的重定向到实际的BITS Manager。比方说这样:

1
2
3
4
5
6
7
8
9
10
11
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,在这个函数中我们可以收集一些信息,注册进度通知什么的。可以写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 manager。对于成功的创建下载工作请求,我们可以对这个下载请求做我们想做的监测。

 

总结

 

Hook COM 类是一个有趣的技术问题,值得我们进一步的研究。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 3446
活跃值: (3708)
能力值: ( LV8,RANK:131 )
在线值:
发帖
回帖
粉丝
coneco 2 2022-3-5 19:24
2
0
感谢分享。
游客
登录 | 注册 方可回帖
返回