我通过逆向工程 WSC 毁掉了我的假期
es3n1n's Blog
逆向工程、编程以及其他一些无聊的东西。
我通过逆向工程 WSC 毁掉了我的假期
发布于 — 2025年5月8日
在这篇文章中,我将简要描述我在实现 defendnot 时所经历的旅程。
即使这很可能不是你期望在这里看到的,但我不会详细介绍一切如何运作的技术细节,而是描述我经历了哪些兔子洞,以及由于我✨特殊的✨环境,一切有多么痛苦。
请注意,这篇文章很可能过于随意,不像我之前的文章,我非常确定所有带有 irl
标签的其他文章都将以这种风格编写。如果您正在寻找更详细的技术描述,稍后其他人会发布类似的文章,我将在此处链接它。
一年前
差不多整整一年前,我发布了一个工具 no-defender,该项目使用特殊的 windows api 禁用 windows defender,该api专门用于杀毒软件,以便让系统知道还有另一个杀毒软件,因此无需运行 defender 扫描。 管理所有这些混乱的部分系统称为 Windows Security Center - 简称 WSC。我的项目的工作方式是,它使用了来自一些现有杀毒软件的第三方代码,并强制该 AV 在 WSC 中注册该杀毒软件。 然后,在发布几周后,该项目突然爆火,获得了约 1.5k 个 star,之后我使用的杀毒软件的开发人员提交了 DMCA 删除请求,我真的不想对此做任何事情,所以只是删除了所有内容并结束了。
如何开始的
目前,即使在撰写本文时,我也坐在我们在首尔租的 airbnb 中。在多次前往地球其他地方参加 CTF 之类的活动后,我和我的一个朋友决定访问首尔,并在几个月后抵达。 我目前用于非 ctf 事物的主要机器是一台 M4Pro MacBook,通常,当我去参加 CTF 时,我会带另一台 x86 笔记本电脑,以进行一些广泛的逆向工程/pwn 工作,因为它通常是为 x86 cpu 构建的。模拟对于这项任务来说有点用,但非常痛苦,所以我只是用另一台笔记本电脑来处理所有 x86 的东西。 而且,正如你可能猜到的那样,这次旅行我没有带那台 x86 笔记本电脑,但我确实带了我的 macbook,以便在我的空闲时间做一些其他的开发工作。所以,我没有任何 x86 机器来做 x86 逆向工程。 而且,在 5 月 4 日,在韩国度过了几天与我最喜欢的韩国 CTF 朋友见面并与他们喝酒的日子后,我收到了 MrBruh 的一条消息,他们说他们正在看 no-defender,并正在研究是否有可能创建一个“干净”的实现,而无需使用任何 AV。
初步研究 (第一天)
我的睡眠时间安排有点问题,我醒得比我的朋友们早一些,所以我决定在等待我的朋友们醒来时看看这个。
MrBruh 向我提供了 wsc 的最新二进制文件,因为我懒得启动我的 parallels vm 来获取二进制文件,我开始研究我们得到的东西。
作为参考实现,我采用了与一年前我使用的相同的 AV 所做的 WSC 注册实现。我有点熟悉他们的东西的内部结构,这是一个很棒的调用。
本质上,WSC 有一个 COM API,所有杀毒软件都在使用它,所以我快速重建了 AV 使用 COM API 所做的一切,大约花了 1 个小时,在 parallels 中启动了一个 arm64 windows 并测试了它。我收到了一个访问被拒绝错误。
但根据我去年所知的,我知道 WSC 正在以某种方式验证调用这些 API 的进程,我的猜测是他们正在验证签名,这确实是一个正确的猜测,但我还不能确定。
然后我的做法是将我的代码注入到与 AV 一样执行所有 WSC 操作的同一进程中,并从那里注册我的 AV,当我这样做时,结果如下:
然后,我重新创建了另一个 COM 调用来更新我注册的全新杀毒软件的状态,一切也都像魅力一样有效!
你可能已经猜到了,这正是我在 twitter 上发布的图片,目的是让我心爱的粉丝知道我可能正在准备一些东西:
尝试摆脱 AV 的二进制文件(第一天)
在我的初步研究之后,我花了很多时间享受生活,并在深夜回到 airbnb 并再次开始摆弄它。
我的第一个想法是创建一个合法签名的进程,将我的模块注入到其中,并从那里执行我的恶作剧,与我正在做的完全相同,只是我将使用系统提供的二进制文件,而不是 AV 的二进制文件 (因为我不希望我的新项目被该 AV 从 github 中删除)。
作为第一个受害者进程,我选择了 cmd.exe
,没有任何特别的原因,只是我脑海中浮现的第一件事。然而,令我惊讶的是,api 拒绝了我的调用,我不得不真正深入研究该实现,以找出导致该问题的原因。
快速浏览 wscsvc.dll
后,我发现该二进制文件正在进行一些调用以检查调用方进程的 PPL,但是,我运行的二进制文件仅使用简单的 CreateProcessA
调用创建,它不可能受到 PPL 保护 (而且确实没有)。
当时已经是清晨了,所以我去睡觉了。
设置环境(第二天)
当我醒来时,我尝试了一堆其他系统进程,但没有任何效果,所以我决定现在实际上是时候正确地逆向工程这个服务,调试它并找出所有这些背后的原因 - 这是我真正试图避免的事情,因为我没有 x86 机器。
正如你可能还记得的那样,我正在 arm64 macbook 上工作,并且目前没有合理的解决方案如何在 arm macbook 上模拟 x86 windows。我不想处理 arm64 的东西并且无法使用我最喜欢的 x64dbg,所以我要求我的一个好朋友在他们睡觉的时候把他们的电脑借给我,这样我就可以在虚拟机中调试 wsc 服务,该虚拟机将在他们的电脑上运行。
幸运的是,我的朋友 pindos 几乎立刻同意了这一点,通过 parsec 共享了对其电脑的访问权限并去睡觉了。关于所有这些,一个相当值得注意的事情是,当我在韩国时,他们在美国,所以从我的电脑到他们的电脑的平均延迟约为 210 毫秒。不是很方便,但可以忍受。
所以,当时,我的设置是这样的:
- 使用 MSVC 在 parallels 中运行的 windows arm64 中构建模块
- 使用共享文件夹与我的主机共享构建工件
- 使用 anydesk 将构建工件复制到在 pindos 的电脑上运行的虚拟机
- 使用具有 210 毫秒延迟的 parsec 调试服务
正如你可能猜到的那样,这 非常 难以前进,因此开发/研究的速度非常慢。
调试 WSC 服务(第二天)
本质上,WSC 服务只是一个由 svchost 运行的 dll,阻止我们直接将调试器附加到它的唯一事情是 PPL 保护,可以使用内核模式下的几行代码非常方便地将其删除。所以我启用了 vm 上的测试模式,启动了一个从进程中删除 PPL 的驱动程序,我们就可以开始了。
在查看了 cmd.exe
请求在 WSC 中注册杀毒软件后发生的事情之后,我跟踪到它失败的函数是一个所谓的 WscServiceUtils::CreateExternalBaseFromCaller
。
这是该函数的快速预览:
v10 = RpcImpersonateClient(ClientBinding);
if ( ConvertStringSidToSidW(L"S-1-5-80-1913148863-3492339771-4165695881-2087618961-4109116736", Sid) )
{
if ( !CheckTokenMembership(0, Sid[0], &IsMember) )
{
IsMember = 0;
}
LocalFree(Sid[0]);
}
if ( IsMember )
{
v8 = (*(__int64 (__fastcall **)(void *, struct CExternalBase **))(*(_QWORD *)a2 + 8LL))(a2, a4);
}
else
{
// a bunch of some other stuff
}
所以它正在做的是检查调用 RPC 方法的进程的令牌上是否具有 WinDefend
SID。
当时,我不知道任何与令牌相关的东西是如何工作的,所以我花了一些时间来掌握它的确切工作方式,并很快得出了结论:如果我们模拟 WinDefend
,我们将通过所有这些检查,我们应该可以开始了。
当时已经是傍晚了,我想看看在我最初测试我的代码的合法 av 二进制文件中该函数中发生了什么,并且 由于某种原因,可能是由于睡眠不足 我得出了结论,对于该二进制文件,IsMember 等于 1。现在回想起来,我真的不知道我为什么这么认为,但我认为当你试图加速完成任务时,这种事情确实会发生。
模拟 WinDefend(第二天)
在花了三个多小时学习 windows 中的令牌如何工作之后,我想出了这个理论:
我花了一些时间做 IRL 的事情,然后提出了所描述的算法的实现:
很快,在与我的朋友聊天后,我想测试一下,如果我在令牌上具有 WinDefend sid 的 cmd 中运行我的代码,我的代码是否有效。
令人惊讶的是,虽然所有 COM 调用都返回
STATUS_SUCCESS
,但实际上什么也没发生。它没有注册任何新的 AV,它什么也没做。
重建验证算法(第三天)
当我睡觉时,我正在使用的 PC 的所有者告诉我他们要去睡觉了,但他们没有关闭 PC,所以我可以随时连接到它。我继续做一些愚蠢的 IRL 事情,一旦我厌倦了这些,就回去分析实际发生的事情。
我做的第一件事是验证我以为通过的那些二进制文件的检查是否确实通过了 - 主要是因为我不信任自己,尤其是在我睡眠不足时。伙计,我真的倾向于忽视事情并误解事物。
事实证明,实际上,SID 检查并没有像我想的那样通过合法的 AV 二进制文件。而且,事实证明,如果你通过了该检查,你将操作 windows defender 的 WSC 对象,但这没什么用,因为你不能像这样使用 WSC 调用来禁用它。
所以我删除了我拥有的所有 WinDefend 模拟的东西,并开始研究代码的第二个分支。
它向我显示的是,如果 windefend 检查未通过,该服务将检查调用二进制文件是否已提升,然后在 CSecurityVerificationManager::CreateExternalBaseFromPESettings
中检查签名和特定的 dllcharacteristics 标志,该函数稍后在分支中被调用:
/// Check DllCharacteristics flag
FileW = CreateFileW(a2, 0x80000000, 1u, 0, 3u, 0x8000000u, 0);
FileMappingW = CreateFileMappingW(FileW, 0, 2u, 0, 0, 0);
v12 = MapViewOfFile(FileMappingW, 4u, 0, 0, 0, 0);
v14 = ImageNtHeader(v12);
v6 = SLOBYTE(v14->OptionalHeader.DllCharacteristics) < 0;
UnmapViewOfFile(v13);
/// Check the signature
CertContext = GetCertContext(a2, &pCertContext);
if ( CryptHashPublicKeyInfo(
0,
0x8003u,
0,
1u,
&pCertContext->pCertInfo->SubjectPublicKeyInfo,
(BYTE *)&SystemTime,
&pcbComputedHash) )
{
/// Store signature info
}
/// ...
在查看了 DllCharacteristics
的结构之后,我意识到它检查的标志是 ForceIntegrity
。
在调试器中,我看到的失败检查是 DllCharacteristics,所以为了获得一个新的受害者进程,我重新创建了 wsc 对二进制文件执行的检查(defendnot 存储库中的 wsc-binary-check
文件夹),并针对它测试了所有 System32 二进制文件。
使用 Taskmgr 作为受害者进程(第三天)
当时,我的朋友已经醒了,必须在他们的电脑上做一些大学的东西,所以我使用 Parsec 直接连接到 vm,我的设置立即变得更糟,因为除了延迟问题之外,现在编码是在软件中完成的,这非常慢。
我尝试在我的代码中只用 Taskmgr.exe
替换 cmd.exe
,但不幸的是,仍然缺少一些东西,我仍然收到来自 RPC 的错误。
在尝试在同一个 vm 上调试它之后,我意识到实际上不可能调试任何东西,因为一切都非常滞后,有些键没有被按下,有时当我按下一个键时,该键在机器上被按下了两次 - 这是非常难以忍受的。
所以我做的是,在朋友推荐我这项服务之后,我在 shadow.tech 订阅上花了 30 美元,因为它让你裸机访问机器,这是我需要的。
等待了一个小时后,我的 VM 被创建了,我跳进去并注意到,由于他们使用的 windows 版本比最新版本旧得多,所以我感兴趣的代码 wscsvc 没有内联到单个函数中,并且反编译代码的总体“质量”要好得多。太糟糕了,我已经弄清楚了一切,但是如果你正在查看 wsc 并且由于一切都内联到单个函数中而感到困惑,你可以查看旧版本。
为了切入正题,VM 上发生的错误是由于我作为 AV 名称传递的名称无效。
你看,defendnot 将数据从 defendnot-loader 传输到 defendnot.dll 的方式是创建一个带有序列化参数的 ctx.bin
文件。虽然我在项目中添加了适当的 IPC 机制来跟踪状态,但我懒得移植这个 context thingy 以使用相同的 IPC 缓冲区,并将 config 仍然使用这个 ctx.bin
thing。毕竟,这是最初的 no-defender 代码中剩下的。
所以发生的事情是,我的代码正在读取无效的 ctx.bin
文件,因为推断此文件路径的函数已损坏(它使用 Taskmgr.exe
模块的基本文件夹,而不是 nodefend.dll
的基本文件夹),它读取了一堆空字节并将该字节作为 AV 的名称传递。当然,wsc 拒绝了这个缓冲区并返回了一个错误。
在弄清楚它之后,我修复了这个问题,经过测试,结果如下:
清理代码(第三天)
我想在这一天完成一切,所以我熬夜到早上 8 点来清理代码并实现一些其他功能,例如将其添加到自动运行中。 早上 8 点,我完成了所有工作,除了自动运行部分,因为它就是不起作用。我测试了无数种方法来做到这一点,但没有任何效果,所以我只是去睡觉了。
实现自动运行(第四天)
当我醒来时,我立即开始研究自动运行,并意识到当我创建一个任务时,我的自动运行代码不起作用的原因是这两个复选框:
这正是发生的事情。我的笔记本电脑未接通交流电源,并且该任务根本没有执行。在取消设置这两个标志后,它开始工作了。
我花了更多的时间来清理代码,就是这样了。
结论
虽然这是一次有趣的经历,但我认为我不想重复过去几天经历的一切。仅考虑我所处的那个邪恶的环境,就足以让我失去理智。 感谢您的阅读,有关 wsc 的更技术性的文档稍后将由其他人发布。
致谢
-
Pindos 因为他的电脑晚上运行来加热他们的房间,以便我可以调试 WSC 服务
-
MrBruh 因为鼓励我研究这个问题并在我工作时倾听我的疯狂想法
-
在这几天发短信给我的每个人
-
我爱你泡菜
-
破坏我们墙壁的涂鸦艺术家
Copyright © Arsenii es3n1n 2024 | Ezhil theme | Built with Hugo