原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

作者:知道创宇404实验室
时间:2024年9月4日



 1 前言


参考资料

近几年随着 Windows 越来越重视系统安全,Windows Defender 已经从最开始的杀毒软件发展为如今的端点检测和响应软件(EDR),并成为 Windows 安全体系的一部分;从系统安全的角度来看,Windows 不建议用户关闭或停用 Defender;从安全研究的角度来看,由于 Defender 提供的默认关闭策略无法完全停止该软件运行,可以说的上是 Windows 「不允许」用户关闭 Defender,这常常就会阻碍和影响对恶意软件的调试分析。

2024年6月,安全研究员 es3n1n 在 Github 分享了开源工具 no-defender,其利用 Windows 安全防护的第三方杀毒软件接管机制,通过逆向分析 Avast 杀毒软件提取出关键组件,编写 hook 代码修改关键组件的执行逻辑,从而实现完全关闭 Defender 软件。

本文将结合 no-defender 源码和 Avast 杀毒软件,研究学习 no-defender 的源码和实现原理。

本文实验环境:

Windows10 专业版22H2 19045.2364
Visual Studio 2022
Avast 24.7.9311.0
no-defender master



 2 关闭Defender的方案


参考资料

随着 Defender 软件在 Windows 安全体系中发挥着越来越关键的作用,Windows 系统逐步加码停用 Defender 的难度,导致互联网上大量的关闭 Defender 的方案都存在局限性或直接失效了,我们这里对其进行简单梳理:

  1. 关闭实时保护:临时关闭「实时保护」功能,Defender 将自动择机恢复;

  2. 添加排除文件夹:在 Defender 中配置排除文件夹,扫描和实时保护将绕过该文件夹;

  3. 组策略关闭Defender:在 组策略-计算机配置-管理模板-Windows组件-Microsoft Defender 防病毒 选择 关闭 Microsoft Defender防病毒-已启动,目前已无效;

  4. 注册表关闭Defender:在 注册表-HKEY_LOCAL_MACHINE-SOFTWARE-Policies-Microsoft-Windows Defender 项添加名为 DWORD DisableAntiSpyware=1,目前已无效;

  5. 服务停止Defender:在 Services 中配置 Defender 进程为 禁用,目前已无效;

  6. 安全模式下损坏Defender二进制:在 Defender 关闭防篡改后进入安全模式,修改 Defender 的二进制程序(如C:ProgramDataMicrosoftWindows Defenderplatform4.18.2211.5-0MsMpEng.exe)的所有者,并重命名为 MsMpEng.exe.bak,可永久关闭 Defender;

  7. 第三方杀毒软件接管:安装第三方杀毒软件后,Defender 将自动退出

有些方案能够关闭 Defender,但随后会由 Windows Security Center 自动启动 Defender 进程,这种仍视为无效;
由于操作系统版本、Dedenfer版本均会影响其内部的策略,各方案的适用情况难以确定,本文以 
Windows10 专业版22H2 19045.2364 测试为准。

Windows Defender 正常工作示意图:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

使用「安全模式下损坏Defender二进制」的方案可以永久关闭 Defender,但这种方案具有一定的侵入性和破坏性,而使用「第三方杀毒软件接管」的方案则需要进一步确认该杀毒软件是否提供了完全关闭的功能。

实际上早在 2016 年就有安全研究者分享开源软件 YourAV 工具,该工具通过模仿杀毒软件行为,在安全中心注册了一个杀毒软件,随后 Defender 就会自动退出,达到了关闭 Defender 的目的;但由于近年 Windows 安全体系的层层加码,目前杀毒软件必须经由微软继续签名认证,同时还需使用未公开的 API 和安全中心进行消息同步,导致 YourAV 这种方案也失效了。



 3 no-defender概要


参考资料

no-defender 工具可以说是 YourAV 工具的一个接力,既然目前需要微软签名和未公开的 API,那就直接从第三方杀毒软件(Avast)中逆向分析并提取出来,通过 hook 修改杀毒软件的运行逻辑,调用在安全中心(Windows Security Center)注册杀毒软件的逻辑,同样 Defender 就会自动关闭退出。

no-defender 工具提供的程序如下:

win_x64
├── no-defender-loader.exe // no-defender服务配置工具
├── no-defender-loader.pdb
├── powrprof.dll // no-defender的hook实现
├── powrprof.pdb
├── wsc.dll // avast的wsc通信核心组件
└── wsc_proxy.exe // avast的wsc通信程序

按照 README 运行工具如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

在安全中心中可以看到 no-defender 已经注册成功,Defender 自动退出如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

由于 no-defender 在一定程度上影响了 Windows 的安全策略,Github 官方于 2024.06.08 对 no-defender 仓库发出 DMCA 下架政策,删除了仓库所有数据内容,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

通过 no-defender 工具的源码我们可以清晰的理解其执行流程,但要进一步理解其原理则需要从 Avast 逆向分析开始说起。




 4 Avast的wsc机制


参考资料

Avast是位于捷克布拉格的 AVAST Software a.s. 公司于1988年首次发行的杀毒软件,软件名取自「Anti-Vzzirus-Advanced-Set」,即「高级杀毒软件」;其提供家用用途的免费版本以及企业和专业用户的付费版本,被全球用户广泛使用。

在 Avast 官网下载程序并安装,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

安装完毕后可以在安全中心中看到 Avast 已经接管了杀毒软件功能,Defender 自动退出,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

Windows操作系统和安全防护软件之间的协同工作,得益于安全中心(Security Center)服务,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

安全防护软件通过 wscsvc 服务提供的 API 向 Windows 系统通知、报告自身的运行状态,随后由 Windows 系统调整安全策略,以保证持续的安全防护功能;wscsvc 服务除了能够注册防病毒软件,还可以注册防火墙、防间谍等软件。

在 Avast 的软件实现中,将与 wscsvc 服务通信的部分封装为一个单独的服务 AvastWscReporter,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

那么我们可以整理出 Avast 和 Windows 安全中心的通信关系如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

根据服务路径找到其关键文件 wsc_proxy.exe 和 wsc.dll 如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

也就是说如果我们可以复用 wsc_proxy.exe 和 wsc.dll 程序,那么就可以向 Windows 安全中心注册任意的杀毒软件软件,从而关闭 Defender 软件。




 5 wsc.dll


参考资料

首先分析 wsc_proxy.exe 程序,其逻辑非常简单,主函数中只有加载 wsc.dll 文件并调用 run() 函数的逻辑,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

随后跟入 wsc.dll 程序,在该动态链接库的 dllmain() 函数中仅完成一些初始化的工作,其导出函数如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

而核心逻辑主要在 run() 函数下,跟入函数 run() => sub_18004A2E0() => sub_180048000()/service_proc(),可以看到 wsc.dll 首先对一些文件对象(如:\wsc.log 和 WscReporter 等)进行初始化,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

随后在 sub_180194020() 函数对 ASW* 文件对象进行初始化,若文件打开失败则返回错误 sd is not loaded 并退出,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

在一切准备就绪后,调用 StartServiceCtrlDispatcherW 系统启动服务,绑定的服务主函数为 sub_180047800()/service_proc(),如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

跟入服务主函数 sub_180047800()/service_proc() 中,其主要逻辑为首先使用 CreateThread() 系统调用启动了 sub_18002CB20() 线程,随后使用 RpCServerRegisterIfEx 系统调用注册启动了 RPC 服务器,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

首先我们来分析 RPC 服务器,根据 _RPC_SERVER_INTERFACE_T 定义逆向分析 unk_1802BE3C0 结构体,可找到 RPC 处理函数 sub_18002A590()

typedef struct _RPC_SERVER_INTERFACE_T {
UINT Length;
RPC_IF_ID InterfaceId;
RPC_IF_ID TransferSyntax;
(RPC_DISPATCH_TABLE*) DispatchTable;
UINT RpcProtseqEndpointCount;
PRPC_PROTSEQ_ENDPOINT_T RpcProtseqEndpoint;
RPC_MGR_EPV PTR_T DefaultManagerEpv;
(MIDL_SERVER_INFO*) InterpreterInfo;
UINT Flags ;
} RPC_SERVER_INTERFACE_T, PTR_T PRPC_SERVER_INTERFACE_T;

当接收到 RPC 请求后将调用该函数 sub_18002A590()/s_wscrpc_update(),该函数读取请求字符串并将其转换为二进制格式,随后通过 sub_18002B0D0() 存入 qword_1803E3098 队列中,然后设置 hHandle 多线程信号量如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

而 sub_18002CB20() 线程则用于实际处理队列中的任务,跟入到 sub_18002CB20() => sub_18002A090()/queue_worker() 线程内部,while(1) 循环中首先等待 hHandle 多线程信号量,当接收到信号量后从 qword_1803E3098 队列中获取任务,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

获取到任务后,调用 sub_180029FB0()/process_item() 处理该任务,其内部调用 sub_180046D80() 函数根据 RPC 请求中的配置向 Windows 安全中心同步杀毒软件的自身状态,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

逆向分析到这里 wsc.dll 的功能比较清晰了,最后一个关键点是 RPC 服务器接收的数据是什么样子的?使用双机内核调试并在 sub_18002A590()/s_wscrpc_update() 函数处打下断点(使用双机内核调试绕过安全软件的 PPL 进程保护,否则无法进行动态调试),在 Avast 中尝试打开和关闭安全防护功能,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

随后在 WinDBG 中关注 sub_18002A590()/s_wscrpc_update() 函数断点,可以看到传入的 RPC 请求数据为 /svc /update /av_as /state: snoozed /signatures: up_to_date,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

想得到更为详细的数据说明,我们可以通过逆向分析 RPC 客户端来获取;从 _RPC_SERVER_INTERFACE_T 中找到 16 位的 RPC_IF_ID,使用 PowerShell 将其转换为 UUID,随后使用 RPCMon.exe 工具监控和过滤所有的 RPC 请求,可以找到发起 RPC 请求的客户端为 AvastSvc,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

随后再逆向分析 AvastSvc 即可,我们这里不再进行拓展。

我们整理一下 wsc.dll 的关键执行流程如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制




 6 代码实现


参考资料

有了以上逆向基础,我们就可以构建 no-defender 工具的代码了。

按照 Avast 的设计 wsc_proxy.exe 和 wsc.dll 应该作为服务注册运行,首先我们应考虑让服务正常的运行起来;在 wsc.dll 服务的初始化过程中会使用 CreateFileW 尝试访问 ASW* 文件对象,参考原作者采用 hook 的方式劫持 CreateFileW 返回魔数 <HANDLE>1337 进行修复,随后 wsc.dll 即可正常完成初始化代码,服务成功运行;

随后我们尝试去触发 process_item() 函数向 Windows 安全中心报告杀毒软件的状态;使用 hook 方式修改 queue_worker 函数,在该函数入口前调用 s_wscrpc_update(),传入 RPC 请求数据如下:

# 开启安全防护
/svc /update /av_as /state:on /signatures:up_to_date
# 关闭安全防护
/svc /update /av_as /state:off /signatures:up_to_date

随后 s_wscrpc_update() 函数会转化 RPC 请求数据转换为内部的 Task 并存入队列中,当执行原 queue_worker 函数时,将获取到该任务并进行处理。

我们这里使用 MinHook 框架,参考原作者劫持 powrprof.dll 实现代码注入,因此构建代码 dllmain.c 如下:

#include <Windows.h>
#include <stdint.h>

#include "MinHook.h"

#pragma warning(disable: 4996)

#define QUEUE_WORKER_ADDRESS 0x2A090
#define S_WSCRPC_UPDATE_ADDRESS 0x2A590

typedef NTSTATUS(*CALLNTPOWERINFORMATION) (POWER_INFORMATION_LEVEL, PVOID, ULONG, PVOID, ULONG);
typedef HANDLE(*CREATEFILEW) (LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
typedef RPC_STATUS(*I_RPCBINDINGINQLOCALCLIENTPID) (RPC_BINDING_HANDLE, unsigned long*);
typedef int64_t(*QUEUE_WORKER) ();
typedef void (*S_WSCRPC_UPDATE) (const wchar_t*, const bool);

CREATEFILEW OriginalCreateFileW = NULL;
I_RPCBINDINGINQLOCALCLIENTPID OriginalI_RpcBindingInqLocalClientPID = NULL;
QUEUE_WORKER Originalqueue_worker = NULL;
CALLNTPOWERINFORMATION OriginalCallNtPowerInformation = NULL;
int64_t wsc_base = 0x0;

// "CallNtPowerInformation()" function proxy
__declspec(dllexport) NTSTATUS WINAPI CallNtPowerInformation(POWER_INFORMATION_LEVEL InformationLevel, PVOID InputBuffer, ULONG InputBufferLength,
PVOID OutputBuffer, ULONG OutputBufferLength) {
return OriginalCallNtPowerInformation(InformationLevel, InputBuffer, InputBufferLength, OutputBuffer, OutputBufferLength);
}

// hook "CreateFileW", return magic number "1337" if lpFileName startwith "asw"
HANDLE WINAPI HookCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
char buffer[1024] = { 0 };
wcstombs(buffer, lpFileName, 1024);
if (strstr(strlwr(buffer), "asw") == NULL) {
return OriginalCreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
return (HANDLE)1337;
}

// hook "queue_worker", manually addr queue-task at the function entry
int64_t Hookqueue_worker() {
wchar_t payload[1024] = { 0 };
mbstowcs(payload, "/svc /update /av_as /state:on /signatures:up_to_date", 1024);

S_WSCRPC_UPDATE s_wscrpc_update = (S_WSCRPC_UPDATE)(wsc_base + S_WSCRPC_UPDATE_ADDRESS);
s_wscrpc_update(payload, 1);

return Originalqueue_worker();
}

// hook "I_RpcBindingInqLocalClientPID()", return Pid from "GetCurrentProcessId()"
RPC_STATUS RPC_ENTRY HookI_RpcBindingInqLocalClientPID(RPC_BINDING_HANDLE Binding, unsigned long* Pid) {
*Pid = GetCurrentProcessId();
return 0;
}

void* load_func_from_library(char* dllname, char* funcname) {
HMODULE dll = LoadLibrary(dllname);
if (dll == NULL) {
return 0;
}
void* func = GetProcAddress(dll, funcname);
if (func == NULL) {
return 0;
}
return func;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call != DLL_PROCESS_ATTACH) {
return TRUE;
}

// get original "CallNtPowerInformation" address
OriginalCallNtPowerInformation = load_func_from_library("C:\Windows\System32\powrprof.dll", "CallNtPowerInformation");

// get "wsc.dll" base address
wsc_base = (int64_t)LoadLibrary("wsc.dll");

// hook setup
if (MH_Initialize() != MH_OK) {
return 0;
}

void* CreateFileW_address = load_func_from_library("Kernel32.dll", "CreateFileW");
void* I_RpcBindingInqLocalClientPID_address = load_func_from_library("Rpcrt4.dll", "I_RpcBindingInqLocalClientPID");

MH_CreateHook(CreateFileW_address, HookCreateFileW, (LPVOID*)&OriginalCreateFileW);
MH_CreateHook(I_RpcBindingInqLocalClientPID_address, HookI_RpcBindingInqLocalClientPID, (LPVOID*)&OriginalI_RpcBindingInqLocalClientPID);
int64_t addr = wsc_base + QUEUE_WORKER_ADDRESS;
MH_CreateHook((void*)addr, Hookqueue_worker, (LPVOID*)&Originalqueue_worker);

if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) {
return 0;
}

return TRUE;
}

成功构建 powrprof.dll hook 项目后,我们拷贝 wsc_proxy.exe / wsc.dll 至同目录下,使用管理员权限打开 PowerShell,注册服务如下:

# 创建 test 服务,并设置 wsc_name=test
> sc.exe create test type= own start= demand binpath= '"C:UsersjohnDesktopworkspacepowrprofx64Debugwsc_proxy.exe" /runassvc /rpcserver /wsc_name:"test"'
# 查询服务信息
> sc.exe queryex test

执行如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

使用 sc.exe start test 启动服务,查看 Windows 安全中心可以看到「test」防护软件已经运行,如下:

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

需要注意的是 wsc.dll 服务内部没有提供关闭服务的逻辑,我们这里可以通过重启系统来停止「test」服务,也可以像原作者一样在 hook 代码中选择合适的位置主动退出。

除此之外,在 hook 代码中我们对于 CreateFileW 无法访问的 ASW* 文件对象直接返回 <HANDLE>1337,理论访问到该 HANDLE 就会出错;进一步逆向分析 wsc.dll 可以找到有多处 DeviceIoControl 系统调用访问该 HANDLE,在目前版本下访问出错也不会影响服务的正常运行,所以我们的代码未对其进行处理,这里也可以参考原作者对 DeviceIoControl 函数进行 hook 修补。




 7 参考链接


参考资料

[1] https://github.com/es3n1n/no-defender

[2] https://github.com/Tlaster/YourAV

[3] https://www.avast.com/zh-cn/index

[4] https://www.avast.com/zh-cn/installation-files

[5] https://learn.microsoft.com/en-us/windows/win32/services/the-complete-service-sample

[6] https://serverfault.com/questions/974902/how-to-add-a-service-which-executable-file-has-a-space-and-requires-parameters

[7] https://github.com/TsudaKageyu/minhook

[8] https://learn.microsoft.com/en-us/windows/win32/services/debugging-a-service

[9] https://www.sysadmins.lv/retired-msft-blogs/alejacma/how-to-debug-windows-services-with-windbg.aspx

[10] https://www.debuginfo.com/articles/debugstartup.html

[11] https://stackoverflow.com/questions/74814844/outputdebugstring-with-both-windbg-and-dbgview

[12] https://jsecurity101.medium.com/wmi-internals-part-3-38e5dad016be



原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制
往 期 热 门
(点击图片跳转)

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

“阅读原文”更多精彩内容!

原文始发于微信公众号(知道创宇404实验室):原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制

版权声明:admin 发表于 2024年9月4日 下午5:26。
转载请注明:原创 Paper | 通过杀软 avast 及 no-defender 工具分析 Windows 防护机制 | CTF导航

相关文章