ConnectUnwise:威胁行为者滥用ConnectWise作为签名恶意软件构建器
自2025年3月以来,使用有效签名的ConnectWise样本的感染和虚假应用程序显著增加。我们揭示了不良签名实践如何允许威胁行为者滥用此合法软件来构建和分发他们自己的签名恶意软件,以及安全供应商可以如何检测它们。
作者:Lance Go和Karsten Hahn
ConnectWise滥用2024-2025
这不是ConnectWise第一次被威胁行为者使用。早在2024年2月,我们就看到与两个ConnectWise漏洞相关的勒索软件活动激增:CVE-2024-1708和CVE-2024-1709。大约在2025年3月,新一波ConnectWise滥用开始出现,现在以"EvilConwi"的名称进行跟踪。
当人们怀疑感染时,他们经常转向互联网寻求帮助。“UNITE against malware"论坛(如BleepingComputer.com)在这种情况下提供消毒援助。BleepingComputer论坛上的几个帖子(链接1,链接2)显示不需要的ConnectWise客户端是感染的罪魁祸首,通常以网络钓鱼电子邮件为起点。几个这样的帖子的存在表明安全程序未能防止威胁。即使在2025年5月,大多数防病毒产品也未将恶意使用的ConnectWise样本检测为恶意软件。
在一个BleepingComputer案例中,感染源是一封带有OneDrive链接的网络钓鱼电子邮件,承诺显示一个大文档。该链接重定向到一个Canva页面,带有一个"查看PDF"按钮,下载并运行ConnectWise安装程序。用户描述了"虚假的Windows更新屏幕"和他们的鼠标"随机自行移动”。除了这些指标外,没有其他可见的活动远程连接迹象(样本[1])。
Reddit用户也报告了类似事件,例如在一个案例中,恶意制作的ConnectWise样本[2]源自一个提供基于AI的图像转换器的网站。根据原帖作者,该网站在Facebook上做了广告。
样本比较
为了找出检测机会和设置位置,我们分析了两个ConnectWise样本之间的差异。
下图显示了PortexAnalyzer报告的两个ConnectWise样本[6][7],我们使用Meld进行比较。
图1:数据目录比较显示两个样本中证书表的大小不同
图2:此比较显示每个部分具有相同的哈希值,但整个文件具有不同的哈希值
除了覆盖层中的证书表外,节内容具有相同的哈希值。我们使用二进制差异工具确认,唯一的实质性差异存在于证书表中。
因此,我们可以用来区分ConnectWise安装程序的任何自定义必须驻留在证书表中。此时我们怀疑是Authenticode填充。
Authenticode填充
Authenticode填充是故意滥用证书结构,允许修改可执行文件而不使其签名失效。开发人员使用此技术来避免为微小更改重新签名其应用程序。这是一个相对常见的做法,像Dropbox这样的应用程序使用它。例如,一些安装程序[3]通过在文件下载前不久将用户代理、引荐来源、活动ID或来自浏览器cookie的类似数据保存在证书中来跟踪安装统计信息。在这种情况下,Authenticode填充是无害的,因为它不影响样本的行为。
有各种滥用Authenticode签名的方法。为了找出ConnectWise使用的方法,我们在两个样本上运行了Authenticode linter。
图3:AuthenticodeLint工具的输出
Linter输出显示两个样本的"无未知未签名属性"检查失败。这意味着ConnectWise有未认证的属性,这些属性不应该存在。
下图显示了签名Portable Executable文件的结构,以及未认证属性在证书表中的位置。原始图像来自Microsoft的官方Windows Authenticode PE签名格式文档。
图4:Windows Authenticode PE签名格式
为了验证Portable Executable文件的证书,Windows将证书中的认证哈希与文件的实际哈希进行比较。如果这些哈希不同,验证失败,文件不再有效签名。
Windows计算文件内容的认证哈希,除了图像左侧的灰色区域。这意味着可选头中的校验和、可选头中的证书表条目以及证书表本身被省略用于认证哈希计算。这包括未认证的属性。它们不会影响证书的有效性。
图像的右侧显示了证书表结构。未认证的属性通常保存时间戳,但也可以保存任意数据。
因为我们假设ConnectWise使用未认证的属性进行Authenticode填充,我们创建了一个Python脚本来从PE文件中提取未认证的属性。
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
|
import pefile
from asn1crypto import cms
import sys
import os
def dump_certificate_table(file_path, output_dir):
pe = pefile.PE(file_path, fast_load=True)
security_dir_entry = pe.OPTIONAL_HEADER.DATA_DIRECTORY[
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']
]
if security_dir_entry.VirtualAddress == 0:
print("No certificate table found.")
return
cert_table_offset = security_dir_entry.VirtualAddress
cert_table_size = security_dir_entry.Size
cert_data = pe.__data__[cert_table_offset: cert_table_offset + cert_table_size]
offset = 0
attr_counter = 0
os.makedirs(output_dir, exist_ok=True)
while offset < len(cert_data):
length = int.from_bytes(cert_data[offset:offset+4], 'little')
revision = int.from_bytes(cert_data[offset+4:offset+6], 'little')
cert_type = int.from_bytes(cert_data[offset+6:offset+8], 'little')
cert_blob = cert_data[offset+8: offset+length]
try:
content_info = cms.ContentInfo.load(cert_blob)
if content_info['content_type'].native != 'signed_data':
print("Not a SignedData structure, skipping.")
offset += ((length + 7) & ~7)
continue
signed_data = content_info['content']
for signer_info in signed_data['signer_infos']:
unsigned_attrs = signer_info['unsigned_attrs']
if unsigned_attrs is not None:
print("\nUnauthenticated Attributes")
for attr in unsigned_attrs:
print("\nNext Attribute")
print(f"Attribute Type OID: {attr['type'].dotted}")
print(f"Attribute Type Name: {attr['type'].native}")
for value in attr['values']:
attr_counter += 1
filename = os.path.join(output_dir, f"attr_{attr_counter}.bin")
with open(filename, "wb") as f:
# Dump raw bytes
f.write(value.dump())
print(f"Value dumped to: {filename}")
else:
print("No unauthenticated attributes found in this signer.")
except Exception as e:
print(f"Failed to parse certificate blob: {e}")
offset += ((length + 7) & ~7)
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <input_pe_file> <output_directory>")
sys.exit(1)
input_file = sys.argv[1]
output_directory = sys.argv[2]
dump_certificate_table(input_file, output_directory)
|
ConnectWise配置滥用
我们构建了一个配置转储器,从证书中提取设置和嵌入文件。虽然出于法律原因我们不共享脚本,但大多数对威胁检测有用的有意义数据以XML格式保存,并直接在转储的属性(使用上面的Python脚本)或样本的字符串列表中可见。
以下是一个恶意样本[4]的配置转储器输出示例:
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
|
Processing: cb8a1a1e90c29461b0503e2c5deac7b673617477128ee3baea4d8134676c8af4
========= Parsed Client Data =========
Is Silent: False
Components to Exclude: 0x0
--- Instance Identifier Blob ---
0602000000a40000525341310008000001000100051e42d081b84623245c42be884c3217fb43dd511451b1fbdb5019df867e7672a3e6c5dce4fd41025f2d4ed931d3978f52105bbe293b3be6f4ba476bcdaf1806d456ffa6cc64e005958582793330c3f4dc22385d8afff1e15a1437daa3c3b11c3ad5078b288072a7c42fa0383b5b8f17b4ddab832bc501698714cbfffa569636c46395d0d4c37171a33c710bdb353335d93222a1dad1a26ae65c55a6711bb95a3559826db8c1dacf36df5705002433b7e2967a2b5d1975eb5ada96e627b73b9b62fb915835897b03d61683c68c92e3c77b264990941a9504461549c4a3d07c9f70207525262f8d20c907802a3cfb5dc7fb6886dac164c1080a3425b87f1d10d2
--- Launch Parameters ---
Launch Parameters: ?e=Access&y=Guest&h=bookinginvoiceview.top&p=8041&k=BgIAAACkAABSU0ExAAgAAAEAAQAFHkLQgbhGIyRcQr6ITDIX%2b0PdURRRsfvbUBnfhn52cqPmxdzk%2fUECXy1O2THTl49SEFu%2bKTs75vS6R2vNrxgG1Fb%2fpsxk4AWVhYJ5MzDD9NwiOF2K%2f%2fHhWhQ32qPDsRw61QeLKIByp8QvoDg7W48XtN2rgyvFAWmHFMv%2f%2blaWNsRjldDUw3FxozxxC9s1MzXZMiKh2tGiauZcVaZxG7laNVmCbbjB2s8231cFACQzt%2bKWeitdGXXrWtqW5ie3O5ti%2b5FYNYl7A9YWg8aMkuPHeyZJkJQalQRGFUnEo9B8n3AgdSUmL40gyQeAKjz7Xcf7aIbawWTBCAo0Jbh%2fHRDS&c=Property%20S%20New&c=Working%20new&c=Property%20Working%20new&c=Working%20Property&c=&c=&c=&c=
Launch Constraints: ?h=bookinginvoiceview.top&p=8041&k=BgIAAACkAABSU0ExAAgAAAEAAQAFHkLQgbhGIyRcQr6ITDIX%2b0PdURRRsfvbUBnfhn52cqPmxdzk%2fUECXy1O2THTl49SEFu%2bKTs75vS6R2vNrxgG1Fb%2fpsxk4AWVhYJ5MzDD9NwiOF2K%2f%2fHhWhQ32qPDsRw61QeLKIByp8QvoDg7W48XtN2rgyvFAWmHFMv%2f%2blaWNsRjldDUw3FxozxxC9s1MzXZMiKh2tGiauZcVaZxG7laNVmCbbjB2s8231cFACQzt%2bKWeitdGXXrWtqW5ie3O5ti%2b5FYNYl7A9YWg8aMkuPHeyZJkJQalQRGFUnEo9B8n3AgdSUmL40gyQeAKjz7Xcf7aIbawWTBCAo0Jbh%2fHRDS
--- Additional Files ---
app.config 0x970 bytes
Client.en-US.resources 0xc3d0 bytes
Client.Override.en-US.resources 0x1f6 bytes
Client.Override.resources 0x1f12 bytes
Client.resources 0x73a8 bytes
system.config 0x3b6 bytes
--- PNG Icons by Size ---
16px : 0xed5 bytes
32px : 0xed5 bytes
48px : 0x1d8 bytes
|
第一个有趣的指标是连接URL和端口,它们是启动参数的一部分,以及此处设置为false的静默安装标志。但还有更多:嵌入的附加资源和配置文件。
此样本提取的附加文件之一是一个名为Client.Override.en-US.resources的.NET资源。在此样本[4]中,它修改了ApplicationTitle,以便ConnectWise伪造Windows更新并指示用户不要关闭系统,可能是为了确保远程连接保持活动一段时间。
图5:配置文件中的虚假Windows更新消息
另一个名为Client.Override.resources的资源包含Google Chrome图标PNG文件,这些文件覆盖了ApplicationIcon属性。类似的样本如[5]使用相同的文件用虚假的Windows更新屏幕JPEG覆盖BlankMonitorBackgroundImage属性。两个图像如下所示。
图6:设置Google Chrome图标的配置数据,样本[4]
图7:样本[5]的ConnectWise配置中的虚假Windows更新屏幕JPEG,压缩伪影如样本中所示
还有两个名为system.config和app.config的配置文件。这些是带有更多设置的XML文件。system.config通常包括另一个ClientLaunchParametersConstraint值—除了已经使用配置提取器提取的值之外—该值保存连接URL、端口和其他参数。
app.config XML对于威胁评估也很有趣。下图显示了恶意ConnectWise样本典型的app.config文件内容:
图6:app.config,远程连接指标设置为false
此app.config禁用了几个会提醒用户ConnectWise存在的指标,如托盘图标或活动连接期间的黑色壁纸。
总之,ConnectWise证书表中的设置显著影响ConnectWise安装程序和客户端的行为。除其他外,证书保存:
- 静默安装选项
- 启动参数,包括连接URL和端口
- 应用程序图标
- 向用户显示的消息和窗口标题
- 软件使用的图像,如背景图像
- 显示活动连接存在的指标
通过修改这些设置,威胁行为者创建自己的远程访问恶意软件,假装是像Google Chrome的AI到图像转换器这样的不同软件。他们通常还添加虚假的Windows更新图像和消息,以便用户在威胁行为者远程连接到他们时不会关闭系统。
威胁预防建议
我们建议同行防御者禁止任何具有以下app.config设置中多个设置为false的ConnectWise样本(使用正则表达式语法):
1
2
3
4
5
|
(Support|Access)?HideWallpaperOnConnect
(Support|Access)?ShowBalloonOnHide
(Support|Access)?ShowBalloonOnConnect
(Support|Access)?ShowSystemTrayIcon
(Support|Access)?ShowCloseDialogOnExit
|
Yara规则可能如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
rule SilentConwi : Riskware PUP
{
meta:
author = "Karsten Hahn @ G DATA CyberDefense"
description = "Settings from app.config that hide the connection of the client. These settings are potentially unwanted"
sha256 = "1fc7f1ef95f064b6c6f79fd1a3445902b7d592d4ff9989175b7caae66dd4aa50"
strings:
$rex_01 = /<setting name="(Access|Support)?ShowBalloonOnConnect"[^>]*>\s*<value>false<\/value>/
$rex_02 = /<setting name="(Access|Support)?HideWallpaperOnConnect"[^>]*>\s*<value>false<\/value>/
$rex_03 = /<setting name="(Access|Support)?ShowBalloonOnHide"[^>]*>\s*<value>false<\/value>/
$rex_04 = /<setting name="(Access|Support)?ShowSystemTrayIcon"[^>]*>\s*<value>false<\/value>/
condition:
all of them
}
|
我们还建议检测嵌入在证书中.NET资源中的虚假应用程序标题、虚假图标和虚假背景图像。
GDATA产品将恶意滥用的ConnectWise样本检测为Win32.Backdoor.EvilConwi.,并将具有可疑设置的样本检测为Win32.Riskware.SilentConwi.。
供应商实践和最终用户风险
尽管Authenticode填充是常见做法,但ConnectWise决定用未认证的属性影响关键行为及其用户界面显然是危险的。它引诱威胁行为者构建自己的具有自定义图标、背景图像和文本的远程访问恶意软件,这些软件由受信任的公司签名。
鉴于ConnectWise的ScreenConnect被广泛(滥)用,关注这些样本是个好主意。在ConnectWise改变其Authenticode填充实践之前,创建和分发签名恶意软件的可能性仍然是一个威胁。
6月12日,我们在本文发布前联系了ConnectWise,让他们意识到上述问题并给他们发表声明的机会。我们在2025年6月17日星期二注意到用于签署样本的签名已被撤销。在本文发布时,我们尚未收到声明。
2025年6月30日更新
本文发布后,Ken Pyle联系我们,表示EvilConwi可能与以下CVE相关:
https://www.cve.org/CVERecord?id=CVE-2023-25718
https://www.cve.org/CVERecord?id=CVE-2023-25719
新的ConnectWise安装程序不再将配置保存在证书中,而是保存在与安装程序一起提供的单独文件中。这些新的安装程序仍然被威胁行为者滥用。
本文引用的样本
[1] 来自BleepingComputer的ConnectWise
7287a53167db901c5b1221137b5a1727390579dffd7098b59e6636596b37bc27
[2] 来自Reddit的ConnectWise
7180238578817d3d62fd