com全称是**Component Object Model,即组件对象模型。**按照微软官方的说法,COM 是一个平台无关的、分布式的、面向对象的系统,用于创建可交互的二进制软件组件。COM 是微软 OLE(复合文档)和 ActiveX(互联网组件)技术的基础技术。
Windows COM(Component Object Model,组件对象模型)的历史可以追溯到1993年,当时它被引入以支持对象链接和嵌入(OLE)2.0。COM最初是为了实现不同应用程序间的对象重用和交互而设计的,它定义了一套二进制接口标准,允许不同编程语言编写的软件组件在运行时进行交互。COM 是 Windows 系统中的核心技术,被广泛应用于操作系统、应用程序、脚本引擎、系统服务等多个方面。
com组件在整个windows系统的应用非常广泛,包括但不限于如下:
Microsoft Office 系列
Office 应用(如 Word、Excel、PowerPoint)提供了丰富的 COM 接口,允许第三方程序进行自动化操作,例如通过 VBA(Visual Basic for Applications)脚本控制 Office 应用
Internet Explorer (IE) 和 ActiveX 控件
ActiveX 控件 是一种基于 COM 的组件技术,主要用于浏览器插件、嵌入网页内容等,例如,IE 浏览器中用于视频播放、数据可视化的插件往往是基于 ActiveX 实现的。
多媒体应用 DirectX(Microsoft 的多媒体图形 API)是基于 COM 设计的,特别是 DirectShow 和 Direct3D。DirectShow 通过 COM 接口处理音频、视频流,例如视频解码和播放。
com组件 提供了多种激活模型以适应不同的应用场景。根据 COM 组件的部署和运行位置,激活方式分为以下三种类型
local local 激活是指 com组件作为独立的可执行文件exe运行。客户端与组件通过本地进程间通信即IPC的方式进行交互。比如office上的公式编辑器组件就是local方式激活的。
remote
Remote 激活是指 COM 组件在远程计算机的进程中运行,客户端通过网络与远程组件进行交互,Remote COM 使用 DCOM(Distributed COM)实现跨网络的对象激活和通信。
in-process In-Process 激活是指 COM 组件作为动态链接库(DLL)加载到调用方客户端进程的地址空间中运行,客户端进程和 COM 组件共享相同的内存空间,调用时可以直接通过内存指针访问对象方法。比如浏览器加载的 ActiveX 控件通常是 In-Process COM 组件。
当前主流的com模糊测试工具有ComRaider、axman、dranzer等,由于ComRaider以及axman都需要人工操作,为了方便进行自动化测试,这里使用dranzer作为com模糊测试工具。
为了确认dranzer这款工具对普通的com应用程序是否有效,这里笔者自己编写了一个简单的存在漏洞的com应用,利用vs studio的ATL模板可以较快的开发com程序。代码目录如下:
在temp.h头文件种实现了Ctemp类
class ATL_NO_VTABLE Ctemp :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Ctemp, &CLSID_temp>,
public IDispatchImpl<Itemp, &IID_Itemp, &LIBID_atl1Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
STDMETHODIMP Number(LONG num, LONG* result);
Ctemp()
{
}
DECLARE_REGISTRY_RESOURCEID(106)
BEGIN_COM_MAP(Ctemp)
COM_INTERFACE_ENTRY(Itemp)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
};
其中创建了一个Number成员函数,该成员函数一旦被调用即可触发程序崩溃,函数实现如下:
#include "pch.h"
#include "temp.h"
STDMETHODIMP Ctemp::Number(LONG num, LONG* result)
{
*(result-0x3000) = num + 1;
return S_OK;
}
同时配置好对应的idl和rgs文件,即可将目标项目编译为一个dll。
HKCR
{
atl1.temp.1 = s 'temp class'
{
CLSID = s '{f2207b67-b76a-4a7a-842a-e86bce5f8802}'
}
atl1.temp = s 'temp class'
{
CurVer = s 'atl1.temp.1'
}
NoRemove CLSID
{
ForceRemove {f2207b67-b76a-4a7a-842a-e86bce5f8802} = s 'com object test'
{
ProgID = s 'atl1.temp.1'
VersionIndependentProgID = s 'atl1.temp'
ForceRemove Programmable
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
TypeLib = s '{6fa318af-3697-456f-8fef-c5018359865b}'
Version = s '1.0'
}
}
}
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(f1fa2254-8654-4746-8afd-544b71000e26),
dual,
nonextensible,
pointer_default(unique)
]
interface Itemp : IDispatch
{
[id(1)] HRESULT Number([in] LONG num, [out, retval] LONG* result);
};
[
uuid(6fa318af-3697-456f-8fef-c5018359865b),
version(1.0),
]
library atl1Lib
{
importlib("stdole2.tlb");
[
uuid(f2207b67-b76a-4a7a-842a-e86bce5f8802)
]
coclass temp
{
[default] interface Itemp;
};
};
import "shobjidl.idl";
编译成功后还需要对该dll进行注册,在管理员权限下,使用regsvr32.exe atl1.dll
即可进行注册
注册成功后可以在注册表下观察到对应的CLSID项:
这样就可以通过CLSID来对该com应用进行加载,利用dranzer的遍历当前可测试的com组件的功能,也可以找到该com应用。
查看下dranzer的参数如下:
-o <outputfile> - Output Filename
-i <inputfile> - Use input file CLSID list
-d <notestfile> - Use don't test CLSID List
-g - Generate base COM list
-k - Generate Kill Bit COM list
-l - Generate Interface Listings
-b - Load In Browser (IE)
-t - Test Interfaces Properties and Methods
-p - Test PARAMS (PropertyBag) in Internet Explorer
-s - Test PARAMS (Binary Scan) in Internet Explorer
-n - Print COM object information
-v - Print out version information
-r - Generate Kill Bit registry files
这里我们主要用到-t参数,该参数意味着对目标com应用测试其属性和方法。将可测试的CLSID列表保存为一个文件,利用dranzer -t -i clsids.txt
命令让dranzer对该文件内的com组件进行模糊测试。为了能够尽快发现atl1.dll中的漏洞,这里准备了下面的文件作为测试目标:
但是经过测试发现此时的dranzer并不能对笔者开发的com应用直接进行fuzz,原因未知,接下来需要对dranzer的代码进行分析来寻找fuzz失败的原因。
对dranzer的代码进行分析,经过初步的参数解析以及一些初始化工作后,创建了一个线程用于后续fuzz,
当前我们传递了待fuzzing的com组件文件,COM_TestThreadProcInputFile
函数会读取每行的CLSID值,并根据该值来获取对应com组件的信息,
后续会根据ExecutionMode
这个变量的值来决定执行后续的代码分支,由于我们传递的参数为-t,因此此时ExecutionMode的值为TEST_INTERFACES,如下:
此时会进入到TestCOMObject
这个函数中,该函数主要调用了另一个项目TestAndReport中的二进制程序,如下:
分析到这里可以发现dranzer起到一个转发的作用,真正对目标com组件进行测试的功能位于TestAndReport
项目中,在t_main函数中,对com库进行了初始化,如下:
CoInitialize(NULL);
sprintf(TempTextBuffer,"*******************************************************************************\r\n");
WriteText(hLogFile,TempTextBuffer);
if (ExecutionMode==TEST_CONTROL)
{
sprintf(TempTextBuffer,"Testing COM Object - %s %s\r\n",CLSID_String,CLSID_Description);
}
else if (ExecutionMode==IE_LOAD)
{
sprintf(TempTextBuffer,"Loading COM Object in to IE - %s %s\r\n",CLSID_String,CLSID_Description);
}
在TestAndReport程序中,当前的ExecutionMode为TEST_CONTROL,此时会调用TestCOM_Object
函数,发现dranzer会检测当前com组件的IObjectSafety接口,如果该com组件没有实现对应的IObjectSafety接口,即跳过对当前com组件的fuzzing。
IObjectSafety
接口是微软定义的一个标准接口,用于控制和查询 COM 组件的安全性设置。它主要用于 ActiveX 控件和其他 COM 组件,以确保这些组件在不同的环境中如 Web 浏览器能够安全运行。如果pIObjectSafety为空,则认为该组件不是activex组件,因此会跳过对该组件的fuzzing过程。为了让dranzer能对普通的com组件也可以fuzz,修改这部分逻辑即可。修改后发现可以针对普通的com组件进行fuzzing,fuzzing时的输出如下:
在对atl1.dll进行简单的测试后就发现了问题,输出如下:
从上图可以看出出现问题的目标文件以及具体的调用函数,可以尽快帮我们定位漏洞。
经过初步的测试,目前dranzer作为一个简单的com组件fuzzing工具是合格的,但是相对目前更加智能化的模糊测试工具还是存在较多问题,对目前的windows中的核心com组件进行fuzzing时会稍显不足。当前仅测试了对in-process类型的com组件,后续会对local以及dcom类型的应用进行深入测试。笔者也是刚涉足这个方向,有什么问题欢迎大家一起讨论。
2 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!