本文介绍了Hyper-V平台TPM组件服务进程vmsp.exe相关的通用TPM漏洞CVE-2025-2884的分析与复现过程,包括利用嵌套虚拟化模式进行调试和漏洞成因分析。
文章结合了笔者魔改的用户模式隔离进程调试工具和逆向代码及调试结果分析了通用TPM漏洞CVE-2025-2884的利用过程和漏洞成因。
Windows 11 24h2 canary preview 启用嵌套虚拟化模式
Hyper-V 的 虚拟信任级别(VTL)是Windows的Hyper-V 虚拟化技术中用于隔离不同安全域的安全机制.VTL1 是其中较高的特权级别,其特性如下:
VTL1 的特权高于 VTL0(最低权限级别),在虚拟安全模式(Virtual Secure Mode)中,VTL1 用于运行安全内核和隔离用户模式(IUM isolated user mode process)的代码,这些程序通过系统调用与 VTL0 中的内核交互.常见IUM进程有LsaIso.exe,vmsp.exe等。
开启虚拟安全模式可在 Windows 安全中心>设备安全>核心隔离详细信息>中找到内存完整性启用后重启计算机就能开启。
在VTL0 域中即使获取了内核级的范围权限 ,也无法对VTL1中内存进行操作.这种设计可抵御内核级攻击,保护用户密码哈希、BitLocker加密密钥等机密信息.VTL1 的安全内核模式由securekernel.exe承载用于调度用户IUM模式系统调用。
笔者使用最新版的LiveCloudKd工具找到了一种可以嵌套虚拟化模式下调试虚拟机内用户模式隔离进程方法,原理在于区别虚拟机分区中虚拟安全模式隔离了跨VTL内存的访问权限。
对于虚拟机的父分区仍然可以调用内核模式hypercall或winhv.sys驱动api直接操作虚拟机线性物理内存,LiveCloudKd工具为我提供了一个签名的驱动hvmm.sys通过应用层api就能实现这些操作,即使在虚拟机中这片保护的物理内存被划分给VTL1级别,仍然可以在父分区使用物理地址对这片虚拟机内的内存进行访问.如果要调试用户IUM模式进程就需要patch一下。
securekernel!SkpsIsProcessDebuggingEnabled函数,在新版中这个函数被内联在IumInvokeSecureService系统调用实现中,简单描述下特征。
intSkpsIsProcessDebuggingEnabled(){ int editto1=0;int Policy = SkpspFindPolicy((__int64)v560, 2, 7, (__int64)&BugCheckParameter2_4, (__int64)v554);if ( Policy < 0 || BugCheckParameter2_4 >= 4 ) {DbgPrint("%hs: SkpspFindPolicy returned 0x%x and DebugEnable is %d.n","SkpsIsProcessDebuggingEnabled", Policy, BugCheckParameter2_4); }else if ( BugCheckParameter2_4 ) {if ( BugCheckParameter2_4 == 1 ) { editto1 = 1; }else if ( BugCheckParameter2_4 == 2 ) { editto1 = (unsignedint)SkIsSecureBootEnabled() == 0x80430006; }else { editto1 = BYTE1(*v560) & 1; } } if ( editto1 ) {//editto1改成1开启调试 SkpsEnableDebugging((unsigned __int64)v560, a1[16]); }
securekernel默认的调试策略被保存在其镜像策略配置中,直接修改配置或patch文件二进制会导致securekernel签名验证失败,重启虚拟机蓝屏,所以这种方式是不可行.但是对于运行时的代码只需要匹配包含特征的代码patch掉SkpsEnableDebugging条件始终为true就可以。
在这里我们需要得到3个关键的数据:
1.搜索到这片代码所在的物理页面地址的映射
2.找到一个立即触发的securekernel函数地址HvGetVpRegisters通过这个hypercall得到虚拟地址RIP和cr3
3.找到securekernel进程虚拟机地址和物理页面地址的映射
一个最常见的可以稳定触发的securekernel函数笔者找到的是IumInvokeSecureService这个函数,通过SharpDisasm这个工具反编译目标得到函数返回指令ret地址,然后不断搜索匹配的个函数所在页面的二进制数据,直到匹配到一个合适的物理页面,然后把指令ret改成如下无限循环汇编代码。
spin_loop: pause ; <-- 关键指令 jmp spin_loop ret
修改内存后并不会引起虚拟机蓝屏,查询虚拟机所有vcpu的RIP通过HvGetVpRegisters这个hypercall,如果rip的低位与这个指令的低16位匹配,这样我们得到了一个符合条件的虚拟地址gva和匹配的物理地址gpa,就可以计算出页表页目录的基址cr3(也是通过hypercall)等.也同样通过SharpDisasm汇编指令匹配SkpsIsProcessDebuggingEnabled字符串。
得到要patch代码的地址根据securekernel计算得出的基址,加上偏移量转为物理地址后patch目标汇编代码,这样我们就成功绕过并启用调试用户IUM模式进程。
1.下载最新版的LiveCloudKd
2.复制所有文件到:"C:Program FilesWindows Kits10Debuggersx64"
3.修改"C:Program FilesWindows Kits10Debuggersx64cfgRegParam.reg" 其中WinDbgPath改成"C:Program FilesWindows Kits10Debuggersx64"
4.安装vc运行库x64版
5.regsvr32 "C:Program FilesWindows Kits10Debuggersx64ExdiHvSrv.dll"
6."C:WindowsSystem32WindowsPowerShellv1.0powershell.exe" -exec bypass -Command "Set-VMProcessor -VMName YourVMName -ExposeVirtualizationExtensions $true"其中YourVMName是你的虚拟机名字,启用嵌套虚拟化模式
7.复制虚拟机内C:WindowsSystem32securekernel.exe文件到:"C:Program FilesWindows Kits10Debuggersx64"和笔者工具同目录
8.虚拟机运行后运行笔者工具,看到打印出securekernel.exe基址即为成功
笔者工具使采用.net程序实现,笔者工具无需禁用安全启动或者修改bcdedit配置,下图是笔者在的Win11 24h2物理机上嵌套虚拟化模式下调试用户模式隔离进程vmsp.exe效果。

TPM模块实现2.0(Trusted Platform Module)是可信平台模块的第二代版本,是一种安全密码处理器的国际标准,TPM模块实现可以是cpu上的加密芯片固件程序或者Hyper-V等虚拟机平台TPM组件模拟服务进程如vmsp.exe.笔者poc采用uefi程序实现,初始化TPM客户端通过与Hyper-V服务的共享物理页面 PcdTpmBaseAddress = 0xFED40000获取适配的TPM版本模式。
使用字段的结构地址进行所有的TPM通信数据包交互,有固定的输入输出缓冲区大小设置和操作返回等读写功能。
通用TPM漏洞CVE-2025-2884补丁发布与今年6月所影响包含所有共享tpm组件代码的固件及服务端应用程序,基于笔者的IUM模式进程调试工具我们得到了一种可以调试vmsp.exe进程的方法,下面我我们就通过代码结合调试分析下这个漏洞。
该漏洞存在于CryptHmacSign函数中,补丁前后的代码分析如下:
static UINT64 PcdTpmBaseAddress = 0xFED40000;//入口TPM2_Sign TPM_RCCryptSign(OBJECT* signKey, // IN: signing key TPMT_SIG_SCHEME* signScheme, // IN: sign scheme. TPM2B_DIGEST* digest, // IN: The digest being signed TPMT_SIGNATURE* signature // OUT: signature){ signature->sigAlg = signScheme->scheme; signature->signature.any.hashAlg = signScheme->details.any.hashAlg;if(signKey->publicArea.type==TPM_ALG_KEYEDHASH) {static TPM_RC CryptHmacSign(TPMT_SIGNATURE* signature, // OUT: signature OBJECT* signKey, // IN: HMAC key sign the hash TPM2B_DIGEST* hashData // IN: hash to be signed){ HMAC_STATE hmacState; UINT32 digestSize;//这行是补丁代码if (signature->sigAlg == TPM_ALG_HMAC) { digestSize = CryptHmacStart2B(&hmacState, signature->signature.any.hashAlg, &signKey->sensitive.sensitive.bits.b);CryptDigestUpdate2B(&hmacState.hashState, &hashData->b);CryptHmacEnd(&hmacState, digestSize, (BYTE*)&signature->signature.hmac.digest);return TPM_RC_SUCCESS; }return TPM_RC_SCHEME;}}
到达目标的漏洞代码需要调用Tpm2_CreatePrimary函数创建一个signKey,具体方法需要预定义一个key参数模板publicArea。
成功后在返回结果可以得到它的句柄,根据tpm文档构造参数传入调用TPM2_Sign,预先验证参数的signScheme和signKey的相关字段匹配成功后。
该函数对于传入的signKey->publicArea.type如果hash类型为TPM_ALG_KEYEDHASH会调用CryptHmacSign进行对传入的数据进行hash也就是签名。
根据其中的signature->signature.any.hashAlg算法可以是TPM_ALG_SHA1到TPM_ALG_SHA512等,hash数据的签名结果大小在0x10-0x30之间,而且可以通过传入的hashData控制。
这个函数默认是给HMAC算法使用,但实际类型可以由用户控制,导致签名的结果数据反序列化时存在对signature->sigAlg类型的混肴,进行另外一种类型对其反序列化,可能允许攻击者读取该签名的结果缓冲区末尾后最多65535个字节。
UINT16BYTE_Array_Marshal(BYTE* source, BYTE** buffer, INT32* size, INT32 count){if(buffer != 0) {if((size == 0) || ((*size -= count) >= 0)) { memcpy(*buffer, source, count); *buffer += count; } pAssert((size == 0) || (*size >= 0)); }if(count >= INT16_MAX){ {//poc位置 DebugBreak(); }}return ((UINT16)count);}UINT16TPM2B_ECC_PARAMETER_Marshal(TPM2B_ECC_PARAMETER* source, BYTE** buffer, INT32* size){UINT16 result = (UINT16)(result + UINT16_Marshal((UINT16*)&(source->t.size), buffer, size));if(source->t.size == 0)return result;return (UINT16)(result + BYTE_Array_Marshal((BYTE*)&(source->t.buffer), buffer, size, (INT32)source->t.size));}UINT16TPMS_SIGNATURE_ECC_Marshal(TPMS_SIGNATURE_ECC* source, BYTE** buffer, INT32* size){UINT16 result = (UINT16)(result + TPMI_ALG_HASH_Marshal( (TPMI_ALG_HASH*)&(source->hash), buffer, size));return = (UINT16)(result + TPM2B_ECC_PARAMETER_Marshal( (TPM2B_ECC_PARAMETER*)&(source->signatureR), buffer, size)); ...}UINT16TPMU_SIGNATURE_Marshal( TPMU_SIGNATURE* source, BYTE** buffer, INT32* size, UINT32 selector){sswitch(selector=signature->sigAlg) {//HMAC算法使用类型case TPM_ALG_HMAC:return TPMT_HA_Marshal((TPMT_HA*)&(source->hmac), buffer, size); ...//对signature->sigAlg类型的混肴case TPM_ALG_EDDSA:...return TPMS_SIGNATURE_ECC_Marshal( (TPMS_SIGNATURE_EDDSA*)&(source->eddsa), buffer, size); case .. }
使用正常TPM_ALG_HMAC类型反序列化长度是对称的最多0x30,导致这个缓冲区之后的数据被泄露字节是没有问题的不存在泄露,但如果比如使用一种不匹配的反序列化类型比如TPM_ALG_EDDSA,由于输出缓冲区是共用的,会将CryptHmacSign结果signature->signature.hmac.digest前4个字节后的2个字节(UINT16可以由hash结果控制)作为反序列作为下个输出反序列化字节流的长度,这个长度可以是UINT16最大值65535,所以就导致了可能的越界读取信息泄露或者内存破坏等拒绝服务情况。
但实际上在输出缓冲区反序列化时候会对输出字节流的最大长度判断即输出最大数据大小Sign_Out减去当前长度,如果这个长度很大比如65535,会导致结果是负数,进入不可预知的分支崩溃断言,导致vmsp.exe进程崩溃(int 3),如下显示是崩溃时的复现结果.但如果这个值未超过输出缓冲区的最大大小,比如果小于65535会根据这个值累加返回缓冲区大小长度,当然这个值可能导致最后的缓冲区UINT16大小溢出,则可能导致越过TPM_ALG_HMAC哈希结果的最大0x30大小导致这个缓冲区之后的数据被泄露,但是实际上这些数据都是0,只有溢出的数据才是泄露的部分。
这个结果证明信息泄露的可能性和客户端可以读取实际上的越界信息泄露.CVE-2025-2884通过bindiff分析可以找到相同的补丁修复位置,手动在windbg把验证的代码绕过就可以触发到达漏洞的复现位置,复现效果为和未打补丁前相同,其他tpm通用平台漏洞复现也可以使用类似poc代码实现。
> !analyze -v******************************************************************************** ** Exception Analysis ** ********************************************************************************KEY_VALUES_STRING: 1 Key : Analysis.CPU.Sec Value: 0 Key : Analysis.DebugAnalysisProvider.CPP Value: Create: 8007007e on DESKTOP-9IQIGSF Key : Analysis.DebugData Value: CreateObject Key : Analysis.DebugModel Value: CreateObject Key : Analysis.Elapsed.Sec Value: 38 Key : Analysis.Memory.CommitPeak.Mb Value: 67 Key : Analysis.System Value: CreateObject Key : Timeline.OS.Boot.DeltaSec Value: 4128 Key : Timeline.Process.Start.DeltaSec Value: 1698NTGLOBALFLAG: 0APPLICATION_VERIFIER_FLAGS: 0EXCEPTION_RECORD: (.exr -1)ExceptionAddress: 00007ffec98ff2a2 (TpmEngUM!BYTE_Array_Marshal+0x00000000000000ce) ExceptionCode: 80000003 (Break instruction exception) ExceptionFlags: 00000000NumberParameters: 1 Parameter[0]: 0000000000000000FAULTING_THREAD: 0000280cPROCESS_NAME: vmsp.exeERROR_CODE: (NTSTATUS) 0x80000003 - { }EXCEPTION_CODE_STR: 80000003EXCEPTION_PARAMETER1: 0000000000000000STACK_TEXT: 00000182`5440e270 00000000`0000003e : 00007ffe`c995018b 00007ffe`c994cfa0 00007ffe`c990efb7 00000182`5440e414 : TpmEngUM!BYTE_Array_Marshal+0xce00000182`5440e278 00007ffe`c995018b : 00007ffe`c994cfa0 00007ffe`c990efb7 00000182`5440e414 00007ffe`c98ff337 : 0x3e00000182`5440e280 00007ffe`c994cfa0 : 00007ffe`c990efb7 00000182`5440e414 00007ffe`c98ff337 00000000`00000002 : TpmEngUM!s_requestBuffer+0x2b00000182`5440e288 00007ffe`c990efb7 : 00000182`5440e414 00007ffe`c98ff337 00000000`00000002 00000182`5440e350 : TpmEngUM!s_actionOutputBuffer00000182`5440e290 00007ffe`c99072cb : 00000182`5440e410 00007ffe`c994cfa0 00000182`5440e410 00007ffe`c98f0000 : TpmEngUM!TPM2_Sign+0xfb00000182`5440e330 00007ffe`c98f5d7b : 00000000`0000015d 00000000`ffff0101 00007ffe`c995018b 00007ffe`c99501c9 : TpmEngUM!CommandDispatcher+0x1a3f00000182`5440e3b0 00007ffe`c98f5135 : 00000000`00001000 00007ffe`d88d6605 00007ffe`d7ea5e00 00007ffe`d7ea5cbd : TpmEngUM!ExecuteCommand+0x34b00000182`5440e490 00007ff6`b0c48fff : 00000000`00000069 00000182`5440eb20 00007ff6`b0c5a950 00007ffe`d886b4c6 : TpmEngUM!VTpmExecuteCommand+0xd500000182`5440e4f0 00000000`00000069 : 00000182`5440eb20 00007ff6`b0c5a950 00007ffe`d886b4c6 00000000`00001000 : 0x00007ff6`b0c48fff00000182`5440e4f8 00000182`5440eb20 : 00007ff6`b0c5a950 00007ffe`d886b4c6 00000000`00001000 00000182`5440e880 : 0x6900000182`5440e500 00007ff6`b0c5a950 : 00007ffe`d886b4c6 00000000`00001000 00000182`5440e880 00007ff6`00001000 : 0x00000182`5440eb2000000182`5440e508 00007ffe`d886b4c6 : 00000000`00001000 00000182`5440e880 00007ff6`00001000 00008ce1`373b1774 : 0x00007ff6`b0c5a95000000182`5440e510 00000182`5423e098 : 00000000`00001000 00000182`5440eb20 00000182`54264b80 00007ffe`d89218c6 : RPCRT4!Ndr64OutInit+0x1da00000182`5440e580 00000000`00001000 : 00000182`5440eb20 00000182`54264b80 00007ffe`d89218c6 00000000`00000000 : 0x00000182`5423e09800000182`5440e588 00000182`5440eb20 : 00000182`54264b80 00007ffe`d89218c6 00000000`00000000 00000182`5440e9a0 : 0x100000000182`5440e590 00000182`54264b80 : 00007ffe`d89218c6 00000000`00000000 00000182`5440e9a0 00000000`00000000 : 0x00000182`5440eb2000000182`5440e598 00007ffe`d89218c6 : 00000000`00000000 00000182`5440e9a0 00000000`00000000 00000000`0000000e : 0x00000182`54264b8000000182`5440e5a0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : RPCRT4!Invoke+0x26
笔者漏洞poc采用uefi程序复现,出于安全原因笔者不能提供完整的poc代码,下图是笔者在的Win11 24h2物理机上使用嵌套虚拟化模式成功复现了CVE的利用效果。

CVE-2025-2884
https://www.cve.org/CVERecord?id=CVE-2025-2884
TPM源码
https://github.com/microsoft/ms-tpm-20-ref/tree/main
补丁
https://github.com/stefanberger/libtpms/commit/04b2d8e9afc0a9b6bffe562a23e58c0de11532d1
笔者工具
https://github.com/cbwang505/SecurekernelIUMDebug

看雪ID:王cb
https://bbs.kanxue.com/user-home-609565.htm
#
原文始发于微信公众号(看雪学苑):Hyper-V平台IUM进程调试工具及通用TPM漏洞CVE-2025-2884分析与复现