深入分析Mimikatz:SSP

2024-03-20 12:40
文章标签 深入分析 ssp mimikatz

本文主要是介绍深入分析Mimikatz:SSP,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文出处:https://blog.xpnsec.com/exploring-mimikatz-part-2/

0x00 前言

在前一篇文章中,我们开始深入分析Mimikatz。我们的想法很简单,就是想澄清Mimikatz内部的工作原理,以便开发自定义和有针对性的payload。微软引入了一些安全控制机制(如Credential Guard),避免攻击者转储凭据信息。在本文中,我们将回顾一下绕过这种机制的巧妙方法,然后提取我们所需的凭据。这里我们想要分析的是Mimikatz所支持的SSP功能。

SSP(Security Support Provider)是一个DLL,允许开发者提供一些回调函数,以便在特定认证和授权事件期间调用。在前一篇文章中,我们可以了解到WDigest正是使用这个接口来缓存凭据。

Mimikatz为我们提供了利用SSP的其他一些不同技术。首先是“Mimilib”,这是具备各种功能的一个DLL,其中一个功能就是实现了SSP接口。其次是“memssp”,这是完成相同任务的另一种有趣方式,但这种方法需要patch内存,而不是单单加载DLL那么简单。

首先试一下以传统方式来加载SSP:Mimilib。

备注:与前一篇文章相同,本文大量用到了Mimikatz源代码,Mimikatz开发人员在这上面花了大量精力。感谢Mimikatz、Benjamin Delpy以及Vincent Le Toux的杰出工作。

 

0x01 Mimilib

Mimilib就像变色龙一样,支持利用ServerLevelPluginDll来通过RPC进行横向移动、DHCP Server Callout,甚至也可以作为WinDBG扩展。在本文中,我们主要关注的是这个库如何充当SSP角色,使攻击者能在受害者输入凭据时提取到目标信息。

系统在调用SSP时,会通过SSP接口传递明文凭据,这意味着我们可以提取到明文凭据,这也是Mimilib的理论基础。Mimilib SSP功能的入口点位于kssp.c中的kssp_SpLsaModeInitialize函数。DLL通过mimilib.def定义文件,将该函数导出为SpLsaModeInitializelsass会使用该函数来初始化包含多个回调的一个结构体。

Mimilib注册的回调函数包括:

  • SpInitialize:用来初始化SSP,提供一个函数指针列表。
  • SpShutDown:卸载SSP时就会被调用,以便释放资源。
  • SpGetInfoFn:提供SSP相关信息,包括版本、名称以及描述。
  • SpAcceptCredentials:接收LSA传递的明文凭据,以便SSP缓存。

如果大家看过上一篇文章,就知道WDigest会使用SpAcceptCredentials来缓存凭据,这也是多年来我们一直能成功提取凭据的切入点。

了解这些背景后,Mimilib所需要做的就是在SpAcceptCredentials被调用后保存传入的明文凭据,这正是kssp_SpAcceptCredentials的代码逻辑,如下所示:

NTSTATUS NTAPI kssp_SpAcceptCredentials(SECURITY_LOGON_TYPE LogonType, PUNICODE_STRING AccountName, PSECPKG_PRIMARY_CRED PrimaryCredentials, PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials)
{FILE *kssp_logfile;
#pragma warning(push)
#pragma warning(disable:4996)if(kssp_logfile = _wfopen(L"kiwissp.log", L"a"))
#pragma warning(pop){    klog(kssp_logfile, L"[%08x:%08x] [%08x] %wZ\%wZ (%wZ)t", PrimaryCredentials->LogonId.HighPart, PrimaryCredentials->LogonId.LowPart, LogonType, &PrimaryCredentials->DomainName, &PrimaryCredentials->DownlevelName, AccountName);klog_password(kssp_logfile, &PrimaryCredentials->Password);klog(kssp_logfile, L"n");fclose(kssp_logfile);}return STATUS_SUCCESS;
}

现在我不相信mimikatz.exe能够直接加载Mimilib,但根据微软的官方文档,我们可以添加注册表项、重启系统就能添加SSP。

然而经过一番搜索后,我找到了一则推文:

这里直接提到了AddSecurityPackage这个API,@mattifestation在Install-SSP.ps1脚本中利用这个API来加载SSP。这意味着实际上我们可以在不重启的情况下添加Mimilib。当添加成功后,我们发现每次进行身份认证时,凭据信息都会被写入kiwissp.log文件中。

现在在目标环境中使用SSP有一个缺点,那就是我们必须在lsass中注册SSP,这样我们就不得不留下一些踪迹,比如创建与SSP有关的注册表、或者在lsass进程中留下异常的DLL,防御方可以有针对性地跟踪我们的恶意行为。此外,SSP还会对外公开名称以及注释,可以使用EnumerateSecurityPackages来枚举这些信息,如下所示:

#define SECURITY_WIN32#include <stdio.h>
#include <Windows.h>
#include <Security.h>int main(int argc, char **argv) {ULONG packageCount = 0;PSecPkgInfoA packages;if (EnumerateSecurityPackagesA(&packageCount, &packages) == SEC_E_OK) {for (int i = 0; i < packageCount; i++) {printf("Name: %snComment: %snn", packages[i].Name, packages[i].Comment);}}
}

如下图所示,输出结果中包含已加载每个SSP的相关信息,其中大家可能会注意到有Mimilib的身影:

那么我们是否可以采取一些隐蔽措施呢?最明显的应该就是修改Mimilib中SpGetInfo回调函数所返回的描述信息,这些信息被硬编码在代码中,如下所示:

NTSTATUS NTAPI kssp_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       = L"KiwiSSP";PackageInfo->Comment    = L"Kiwi Security Support Provider";return STATUS_SUCCESS;
}

这里我们可以修改Name以及Comment字段,结果如下所示:

好吧,显然这还远远不够(即使我们修改了名称以及注释字段)。要注意一点,在没有充分剥离并重新编译之前,Mimilib中还包含大量功能,而不单单是充当SSP角色那么简单。

那么我们应该如何绕过这一点呢?这里要感谢Mimikatz还支持misc::memssp,这是我们可以使用的另一个较好的候选方案。

 

0x02 MemSSP

MemSSP回到了处理lsass内存的老路上,这一次MemSSP会识别并patch一些函数,重定向执行逻辑。

来看一下源头函数:kuhl_m_misc_memssp。这里我们可以看到代码会打开lsass进程,开始搜索msv1_0.dll,这个DLL是支持交互式身份认证的一个认证程序包:

NTSTATUS kuhl_m_misc_memssp(int argc, wchar_t * argv[])
{
...
if(kull_m_process_getProcessIdForName(L"lsass.exe", &processId)){if(hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, FALSE, processId)){if(kull_m_memory_open(KULL_M_MEMORY_TYPE_PROCESS, hProcess, &aLsass.hMemory)){            if(kull_m_process_getVeryBasicModuleInformationsForName(aLsass.hMemory, L"msv1_0.dll", &iMSV)){
...

接下来是在内存中搜索匹配模式,这类似于我们在WDigest中看到的处理逻辑:

...
sSearch.kull_m_memoryRange.kull_m_memoryAdress = iMSV.DllBase;
sSearch.kull_m_memoryRange.size = iMSV.SizeOfImage;
if(pGeneric = kull_m_patch_getGenericFromBuild(MSV1_0AcceptReferences, ARRAYSIZE(MSV1_0AcceptReferences), MIMIKATZ_NT_BUILD_NUMBER))
{aLocal.address = pGeneric->Search.Pattern;if(kull_m_memory_search(&aLocal, pGeneric->Search.Length, &sSearch, TRUE)){
...

如果我们暂停代码审计,投入Ghidra怀抱,就可以搜索代码正在使用的匹配模式,然后找到如下位置:

这里我们来看看代码实际上在执行哪些操作。memssp正被用来hook msv1_0.dllSpAcceptCredentials函数,以便恢复凭据信息。让我们使用调试器看一下添加后的hook长啥样子。

首先我们确认SpAcceptCredentials中包含一个hook:

接下来当我们逐步执行时,会进入一段代码逻辑,其中会在栈上创建一个文件名,将其传递给fopen,以便创建一个log文件:

一旦打开该文件,传递给SpAcceptCredentials的凭据就会被写入该文件中:

最后,执行流程会被重定向回msv1_0.dll

如果大家想查看负责这个hook的代码,可以在kuhl_m_misc.c源文件的misc_msv1_0_SpAcceptCredentials函数中找到相应代码。

那么我们使用这种技术的风险在哪呢?从前面分析中,我们可以看到hook会通过kull_m_memory_copy被拷贝到lsass中,该函数实际上使用的是WriteProcessMemory。根据目标具体环境,调用WriteProcessMemory插入另一个进程可能会被检测到,或者被标记为可疑行为,等等。特别当目标进程是lsass时,这种行为更加可疑。

现在对我们来说,深入分析Mimikatz使用的具体技术可以帮我们修改与lsass的交互行为,使蓝队更难发现我们的踪迹。接下来让我们看一下如何增加整个过程的复杂度。

 

0x03 不使用WriteProcessMemory重构memssp

回顾前面分析的技术后,我们能找到各自的优点以及缺点。

第一种方法(Mimilib)需要注册SSP,而这种行为可以通过EnumerateSecurityPackages枚举已注册的SSP列表来定位。此外,如果Mimilib库没有经过修改,那么DLL中还包含大量其他功能。另外一方面,当使用AddSecurityProvider来加载时,注册表键值会被修改,以便系统重启时还能保持SSP驻留。也就是说,这种方法最大的优点在于不需要调用有潜在风险的WriteProcessMemory API就能完成任务。

第二种方法(memssp)需要依赖容易被监控的API(如WriteProcessMemory),利用这些API来hook到lsass中。这种方法的最大优点就是不会存在于已注册的SSP列表中,也不会存在于已加载的DLL中。

那么我们可以做些什么呢?我们可以将这两种方法结合起来,使用AddSecurityProvider来加载我们的代码,同时避免自己出现在已注册的SSP列表中。我们需要找到方法避免直接调用AddSecurityProvider API,如果成功的话,这样就能绕过各种烦人的AV或者EDR(这些解决方法可能会hook这个函数)。

让我们先来看看AddSecurityPackage注册SSP的具体过程,这意味我们需要做一些逆向分析。我们先观察导出该API的DLL:Secur32.dll

在Ghidra中打开这个DLL,就可以看到这实际上是个封装库,会调用sspcli.dll

反汇编sspcli.dll中的AddSecurityPackage(特别是该函数所使用的外部API调用),我们可以找到NdrClientCall3,这意味着该函数正在使用RPC。这一点很正常,因为这个调用需要向lsass发送信号,通知lsass应当加载一个新的SSP:

跟踪NdrClientCall3,我们可以找到传入的如下参数:

其中nProcNum参数值为3,如果我们深入分析sspirpc_ProxyInfo结构,可以看到RPC接口的UUID值为4f32adc8-6052-4a04-8701-293ccf2096f0

现在我们已经掌握足够多的信息,可以通过RpcView来观察通过sspisrv.dll公开的SspirCallRpc RPC调用:

为了使用这个调用,我们需要知道传入的参数。我们可以通过RpcView来获取这些信息,如下所示:

long Proc3_SspirCallRpc([in][context_handle] void* arg_0,[in]long arg_1,[in][size_is(arg_1)]/*[range(0,0)]*/ char* arg_2,[out]long* arg_3,[out][ref][size_is(, *arg_3)]/*[range(0,0)]*/ char** arg_4,[out]struct Struct_144_t* arg_5);

然而在实现这个调用之前,我们需要知道arg_2参数传入的具体值(arg_1arg_2的大小,arg_3arg_4以及arg_5都标记为out)。我发现完成该任务最简单的方法就是启动调试器,然后在AddSecurityPackage调用NdrClientCall3之前插入断点:

暂停执行后,我们可以dump出传入的每个参数的值。我们可以使用dq rsp+0x20 L1来获取arg_1参数中传递的缓冲区大小值。

因此,我们知道在这种情况下,传入的缓冲区大小为0xEC字节。现在我们可以使用如下命令来dump出arg_2

经过一番挖掘后,我成功将大多数值关联起来。让我们以QWORD来重新格式化输出,这样能较为清晰地梳理我们正在处理的数据:

现在我们已经映射出传入的大部分数据,我们可以尝试在不直接使用AddSecurityPackage API的情况下发起RPC调用。大家可以访问Gist下载我构造的代码。

在不直接调用AddSecurityPackage下我们已经能够加载包,接下来我们看看能否进一步使这个过程更加隐蔽。

让我们使用Ghidra载入sspisrv.dll,观察服务端如何处理RPC调用。反汇编SspirCallRpc后,我们很快就发现执行流程会通过gLsapSspiExtension来传递:

这实际上使指向函数数组的一个指针,通过lsasrv.dll提供,会指向LsapSspiExtensionFunctions

我们对SspiExCallRpc比较感兴趣,这与我们在RPCView中观察到的非常相似。该函数会验证参数值,并将执行流程传递给LpcHandler

LpcHandler在将执行权交给DispatchApi之前,会进一步检查所提供的参数:

同样,这里会使用另一个函数数组指针来调度LpcDispatchTable所指向的函数调用:

现在我们应该对这个数组比较感兴趣,因为我们可能会根据函数名,找到其中的s_AddPackage,并且这个函数的索引值与我们在请求中找到的0xb “Function ID”索引值相匹配。

沿着线索进一步走下去,我们找到了WLsaAddPackage,该函数首先会检查我们是否具备足够的权限来调用RPC方法,具体操作就是模拟(impersonate)客户端,然后尝试以Read/Write权限打开HKLM\System\CurrentControlSet\Control\Lsa注册表项:

如果操作成功(请注意这是可用于权限提升的一个较新颖的后门技术),那么执行权就会继续交给SpmpLoadDll,后者会通过LoadLibraryExW将我们提供的SSP加载到lsass中:

如果SSP成功加载,那么DLL就会被添加到注册表中,以实现自动加载:

这里我们可能希望跳过这个操作,因为我们不希望使用这种方法实现本地驻留,并且如果没有必要,我们也不希望涉及到注册表操作。理想状态下,如果引起怀疑(比如防御方通过ProcessExplorer来分析时),我们还希望这个DLL不会出现在lsass载入列表中。因此我们可以使用RPC调用来传递我们的DLL,在SSP的DllMain中返回FALSE,强制SSP加载失败。这样就会跳过注册表修改操作,也意味着我们的DLL会从进程中卸载。

我以Mimikatz的memssp作为模板构造了一个DLL,可以通过我们的RPC调用来加载,使用Mimikatz所用的相同hook来patch SpAddCredentials。大家可以访问Gist下载源代码。

使用我们的AddSecurityPackage RPC调用来加载DLL的整个过程参考此处视频。

使用这种方法时,我们也不一定需要在本地系统中才能加载DLL,如果通过RPC调用,我们也可以使用UNC路径(但我们需要确保EDR并不会将这种操作标记为可疑行为)。

当然,我们也不一定要使用AddSecurityPackage来加载这个DLL。我们构造了一个独立版的DLL,可以实现memssp patch。我们可以使用前一篇文章中的SAMR RPC脚本,利用该脚本通过LoadLibrary来加载我们的DLL,获取使用SMB共享的登录操作信息,整个过程可以参考此处视频。

此外,还有很多方法能够改进这些方法的有效性。但与前一篇文章一样,我希望本文能给大家提供一个思路,让大家了解如何构造自己的SSP,以便在行动中更加得心应手。本文只提供了能够隐蔽将SSP载入lsass过程的一些参考方法,澄清Mimikatz实现该过程的具体原理。大家可以根据这些信息,在实际环境中定制自己的payload,以便绕过AV或者EDR,或者可以用来测试蓝队在Mimilib和memssp之外是否存在其他检测能力。

这篇关于深入分析Mimikatz:SSP的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/829484

相关文章

手工清理Linux后门:深入分析与实践指南

手工清理Linux后门:深入分析与实践指南 后门概述 后门程序允许未授权用户绕过正常的认证过程,获取对系统的访问权限。攻击者可能会通过修改计划任务、开机启动脚本,甚至植入Rootkit来维持后门。 分析操作系统被动手脚 在清理后门之前,首先需要分析系统可能被动了哪些手脚: 计划任务:检查/etc/cron.d/和/var/spool/cron/目录下的计划任务。开机启动脚本:检查/etc

深入分析并可视化城市轨道数据

介绍 中国城市化进程加速中,城市轨道交通的迅速扩张成为提升城市运行效率和居民生活品质的关键。这一网络从少数大城市延伸至众多大中型城市,映射了经济飞跃和城市管理现代化。深入分析并可视化城市轨道数据,对于揭示网络特性、评估效率、理解乘客行为及预测趋势至关重要,它不仅指导政府决策和城市规划,也通过简化复杂信息增进公众理解,助力形成共识。 数据概览 1.数据集表中各列含义说明如下: 最左边的是城市

深入分析 Android BroadcastReceiver (四)

文章目录 深入分析 Android BroadcastReceiver (四)1. 广播接收器的深入优化与应用1.1 实时性要求高的应用1.1.1 示例:音乐播放器中处理耳机插拔事件1.1.2 动态注册接收器 1.2 处理耗时操作1.2.1 示例:使用 `IntentService` 处理耗时操作 1.3 安全性管理1.3.1 示例:声明权限 1.4 应用内广播优化1.4.1 示例:聊天应用

深入分析 Android BroadcastReceiver (三)

文章目录 深入分析 Android BroadcastReceiver (三)1. 广播消息的优缺点及使用场景1.1 优点1.2 缺点 2. 广播的使用场景及代码示例2.1. 系统广播示例:监听网络状态变化 2.2. 自定义广播示例:发送自定义广播 2.3. 有序广播示例:有序广播 2.4. 本地广播示例:发送本地广播 3. 优化策略4. 总结 深入分析 Android Bro

cs与msf权限传递,以及mimikatz抓取明文密码

cs与msf权限传递,以及mimikatz抓取win10明文密码 1、环境准备2、Cobalt Strike ------> MSF2.1 Cobalt Strike拿权限2.2 将CS权限传递给msf 3、MSF ------> Cobalt Strike3.1 msf拿权限3.2 将msf权限传递给CS 4、使用mimikatz抓取明文密码 1、环境准备 攻击:【kali c

深入分析C#中的StringBuilder

C# 中的类是专为字符串操作而设计的命名空间的重要组成部分。与 C# 中的常规字符串不同,C# 中的常规字符串是不可变的,它提供了一个可变的字符串对象。这意味着可以修改它,而无需为每次修改创建新对象的开销,这在处理大型字符串或执行频繁的字符串操作时特别有用。StringBuilderSystem.TextStringBuilder StringBuilder 的核心概念 1. 可变字符串操作

深入分析 Flink SQL 工作机制

摘要:本文整理自 Flink Forward 2020 全球在线会议中文精华版,由 Apache Flink PMC 伍翀(云邪)分享,社区志愿者陈婧敏(清樾)整理。旨在帮助大家更好地理解 Flink SQL 引擎的工作原理。文章主要分为以下四部分: Flink SQL ArchitectureHow Flink SQL Works?Flink SQL OptimizationsSummary

AIDL使用学习(三):源码深入分析

前言 我们都已经学习并掌握了AIDl的用法,这一篇我们仔细的看看aidl文件生成的java文件以及具体的工作流程,来加深对AIDL的理解。 正文 首先我们就看看绑定服务的流程,MainActivity中绑定服务: private ITestInterface binder;private ServiceConnection connection = new ServiceConnectio

深入分析 Android BroadcastReceiver (一)

文章目录 深入分析 Android BroadcastReceiver (一)1. Android BroadcastReceiver 设计说明1.1 BroadcastReceiver 的主要用途 2. BroadcastReceiver 的工作机制2.1 注册 BroadcastReceiver2.1.1 静态注册2.1.2 动态注册 3. BroadcastReceiver 的生命周

深入分析 Android Service (完)

文章目录 深入分析 Android Service (完)1. Service 的生命周期管理2. Service 的生命周期方法2.1 onCreate()2.2 onStartCommand(Intent intent, int flags, int startId)2.3 onBind(Intent intent)2.4 onUnbind(Intent intent)2.5 onReb