0x00 前言
Dumping Credentials from Lsass Process Memory在内网渗透流程中起到不可忽视的作用。本文将从源码以及对抗杀软的角度对几种仍然有效的方法进行分析。
前阵子在github发了一个Dump Lsass进程的工具seventeenman/CallBackDump,该项目内利用了回调的方式Dump Lsass进程,暂时满足了过国内杀软的需求(核晶还需要加以小修改),在本文中也会提到如何对该项目加以改造。
0x01 Dump Hash痛点
Dump Hash主要分为两步,第一步是把目标机器上的Lsass进程转储下来,第二步便是解密Lsass进程的转储文件。因为第二步可以在本地机器上处理,所以我们将重点放在第一个步骤上。
-
首先的是如何获取lsass进程句柄,当你写完代码在国内环境下完美地dump hash,放到国外或者核晶的环境下就会发现连lsass进程句柄都拿不到,根本到不了后面dump的操作的免杀。
-
其次,便是如何将lsass进程转储下来,这涉及到两个动作,一个是dump,另外一个是保存到文件。比如defender对保存出来的文件检测比较严格,只要检测到你dump出来的是lsass进程,一般都会直接处置。
在实际测试中发现,不同杀软对这些操作的敏感度不同,所以我目前最佳的方案是针对不同杀软制定不同的dump方案。
当然,本文并不涉及到开启PPL以及国外EDR挂钩加密Lsass进程这两种情况。
0x02 获取Lsass进程句柄方案
dump lsass进程最常见的一种方法是先通过OpenProcess的PROCESS_QUERY_LIMITED_INFORMATION|PROCESS_VM_READ权限来获取lsass进程句柄。但这种方法与直接调用MiniDumpWriteDump的行为一组合就变得很敏感。
那么先来看一下OpenProcess这个api,这里需要关注第一个参数为对进程访问的权限,第三个参数为进程PID。
这里重点来回顾一下第一个参数Process Security and Access Rights
- PROCESS_ALL_ACCESS: 进程的所有可能访问权限。
- PROCESS_CREATE_PROCESS: 需要创建一个进程。
- PROCESS_CREATE_THREAD: 需要创建一个线程。
- PROCESS_DUP_HANDLE: 需要使用 DuplicateHandle 复制句柄。
- PROCESS_QUERY_INFORMATION: 需要检索有关进程的一般信息,例如其令牌、退出代码和优先级。
- PROCESS_QUERY_LIMITED_INFORMATION: 需要检索有关进程的某些有限信息。
- PROCESS_SET_INFORMATION: 需要设置有关进程的某些信息,例如其优先级。
- PROCESS_SET_QUOTA: 需要使用 SetProcessWorkingSetSize 设置内存限制。
- PROCESS_SUSPEND_RESUME: 需要暂停或恢复进程。
- PROCESS_TERMINATE: 需要使用 TerminateProcess 终止进程。
- PROCESS_VM_OPERATION: 需要对进程的地址空间执行操作(VirtualProtectEx、WriteProcessMemory)。
- PROCESS_VM_READ: 需要在使用 ReadProcessMemory 的进程中读取内存。
- PROCESS_VM_WRITE: 需要在使用 WriteProcessMemory 的进程中写入内存。
在下面的方法中将会对上述权限利用方式以及效果进行分析。
NtDuplicateObject间接获取句柄
大概的一个过程如下:
-
先通过NtQuerySystemInformation来获取所有进程打开的句柄。
-
使用具有PROCESS_DUP_HANDLE权限的OpenProcess打开进程。
-
使用NtDuplicateObject来获取远程进程副本。
-
再通过NtQueryObject来获取句柄的信息。
-
最后通过QueryFullProcessImageName显示进程完整路径加以判断。
首先是通过NtQuerySystemInformation检索SystemHandleInformation(16)获取所有进程的句柄。
这里用PSYSTEM_HANDLE_INFORMATION结构体存放所有进程句柄的信息,所以我们遍历这个结构体即可。
接着,遍历刚刚获取到的进程句柄,通过NtDuplicateObject来Duplicate句柄。
最后通过NtQueryObject获取进程的信息,使用QueryFullProcessImageName获取进程的完整路径判断是否为lsass进程的句柄,如果是则返回,否则继续遍历句柄。
这个思路很不错,间接性地获取lsass的进程,避免了直接获取lsass进程的句柄,让我们来测试一下实际免杀效果怎么样。
普通的defender处理好还是随便过的,defender主要的问题还是转储出来的文件一定要经过加密,不能就单纯的dump出来。(具体处理方法见0x03)
360企业安全云也是没啥问题。
来试一下黑化了的核晶360,可以看到核晶是懂拦截的。(这个已允许不是我点的哈,自己可以测一下,它自己允许的不关我事情。从这里看得出来360也是考虑了一下要不要拦截)
可惜PROCESS_DUP_HANDLE权限有点招摇。像核晶这种杀软在OpenProcess检测比较严格,会阻止我们对lsass进程做一些操作,在api中请求的权限会直接影响到能否获取到lsass进程句柄,但是否OpenProcess我们就彻底无法利用来获取句柄呢?
CreateProcessEx获取进程句柄
相比于PROCESS_VM_READ和PROCESS_DUP_HANDLE这类权限,实际上PROCESS_CREATE_PROCESS权限并不是很敏感。
那PROCESS_CREATE_PROCESS权限有什么用呢?实际上我们拿到这个权限的句柄就可以代表此进程创建子进程,PPID Spoofing也是应用了此技术来创建子进程。
在ForkPlayground/ForkLib.cpp的项目中利用该技术,首先是调用OpenProcess以PROCESS_CREATE_PROCESS权限打开lsass进程句柄。
接着在NtCreateProcessEx中的参数SectionHandle中传递NULL(内存区对象直接继承自父系统进程),在ParentProcess参数中传递目标进程的PROCESS_CREATE_PROCESS句柄,就会得到远程进程lsass的handle。
让我们来测试一下这种获取句柄的效果如何?我们可以看到这位懂哥又不懂了,lsass进程直接被dump下来了。
这里顺带说一下,PssCaptureSnapshot这个API实际上也是在做这个操作。
再者,RtlCloneUserProcess这个API同样也能实现相同的功能。
因为考虑到免杀性的原因,所以在我的CallBackDump/main.cpp项目里并没有对获取进程句柄做过处理的代码,只放了第一版朴实无华的PROCESS_ALL_ACCESS代码给师傅们改造。但是在国内的对抗环境下,其实除了核晶也没有什么问题。
0x03 检测lsass转储文件
CallBack加密转储文件
这里利用的是MiniDumpWriteDump的回调API,该API主要用来扩展MiniDumpWriteDump的功能。
CallbackType用来判断何时回调执行我们自定义的代码段,以下是微软文档的说明。
在IoStartCallback中将Status成员设置为S_FALSE,其目的是让每次涉及IO操作都经过回调。在IoWriteAllCallback中,我们将每一部分的lsass进程都写入到申请的堆内存中,以便我们加密后再存储到文件中。
相比于重写MiniDumpWriteDump,这种方法会简单很多,但也因为回调的方式一些利用无法扩展。
到此为止,国内的已经处理差不多了,接下来看看稳重的卡巴斯基。
0x04 直接利用系统白操作
在我的测试中,对于卡巴斯基这种分配受限组的杀软,dump lsass进程最好是用白操作。
SilentProcessExit机制dump内存
通过RtlReportSilentProcessExit与Windows错误报告服务(WerSvcGroup下的WerSvc)通信,由WerFault.exe来转储lsass进程。
值得注意的是该方法获取lsass进程句柄方法不能随意更改,更改完权限后会发现dump不出lsass进程。
代码可以参考此处RedTeamTools/windows/LsassSilentProcessExit,具体代码其实就只有改注册表以及调用RtlReportSilentProcessExit,其余的就还是一些细节的处理,这里就没什么好讲的了。
需要动注册表,实际测试中对卡巴斯基个人版有效,360会直接报毒镜像劫持。
有一个非常有意思的点,如果你在defender环境下利用该方法去dump lsass进程。你会发现第一次成功了,并且defender反复的来回报毒和加白,这种方法会让defender犹豫不决,过个几秒就正常了,并且也没有被杀。但在第二次dump的时候,defender就会直接杀。
此外还有一种通过ALPC模拟正常lsass异常信息的方法,但需要模拟system权限并且也是需要动注册表。不过相比之下优点就是windows日志记录不会记录模拟的exe,只会记录lsass自己的操作。(但这种方法在卡巴环境测试之中dump不出lsass,在defender环境下直接转储原文件落地反而没什么问题,个人觉得实战效果不会很理想)
这种方法具体的原理利用可以看这个DEF CON 30 presentations/Asaf Gilboa - LSASS Shtinkering Abusing Windows Error Reporting to Dump LSASS.pdf
Lsass SSP自加载
这种方法的好处主要是让lsass自己加载一个dll,让我们dump lsass进程的操作变成lsass自己的操作。
关键代码如下,通过AddSecurityPackage加载一个dll,需要注意的是dll的路径要是绝对路径。之前xpn通过rpc调用AddSecurityPackage的项目师傅们感兴趣可以改改。
#define SECURITY_WIN32
#include <stdio.h>
#include <Windows.h>
#include <Security.h>
#pragma comment(lib,"Secur32.lib")
bool loadersth(IN LPSTR ssp_path) {
wchar_t ssp_path_w[MAX_PATH];
mbstowcs(ssp_path_w, ssp_path, MAX_PATH);
SECURITY_PACKAGE_OPTIONS spo = { 0 };
NTSTATUS status = AddSecurityPackageW(ssp_path_w, &spo);
if (status == SEC_E_SECPKG_NOT_FOUND)
{
return true;
}
else
{
return false;
}
}
可以看到这里调用了AddSecurityPackage,那么能不能通过EnumerateSecurityPackages枚举呢?在我本地测试中,发现不管在dllmain中return true还是false,通过EnumerateSecurityPackages都没有查询到我们的ssp。
#define SECURITY_WIN32
#include <stdio.h>
#include <Windows.h>
#include <Security.h>
#pragma comment(lib,"Secur32.lib")
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: %s\nComment: %s\n\n", packages[i].Name, packages[i].Comment);
}
}
}
这里也可以监控一下dump dll是怎么被lsass加载的。
在我的测试中发现这里并没有RegSetValue的行为,所以xpn文章里指的应该是注册mimilib.dll为ssp的过程才会去注册表注册。
这里其实做一些免杀上的处理也是可以直接过卡巴斯基的。
在上面提到CallBackDump一些操作会有问题,其实就是我在利用ssp自加载CallBackDump的时候出现了问题。我不确定问题是否由回调引起的,但目前我可以十分肯定的是电脑重启是因为ssp加载了CallBackDump,就算使用了debugview也没有收到异常是什么。
0x05 Protected Prcoess Light
这里顺带提一下这个PPL,上述的免杀方案针对于没有开启PPL的情况下,那么我们如何检测目标机器是否开启PPL呢?
开启PPL
首先将HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL的值改为1,重新启动。
开启与关闭的方法参考Configuring Additional LSA Protection | Microsoft Learn
(谨慎开启,我的卡巴环境开了不知道为什么关不了)
检测开启PPL
检测开启PPL过程主要是:
-
使用OpenProcess以PROCESS_QUERY_LIMITED_INFORMATION权限打开lsass进程。
-
再通过GetProcessInformation获取ProcessProtectionLevelInfo信息。
具体的代码可以参考PPLdump/utils.cpp
检测开启PPL这个操作一般杀软不会拦截,如果要保险一点也可以做一些细节处理。
0x06 Reference
GitHub - skelsec/pypykatz: Mimikatz implementation in pure Python
GitHub - D4stiny/ForkPlayground: An implementation and proof-of-concept of Process Forking.
Abusing Windows’ Implementation of Fork() for Stealthy Memory Operations
Dumping Lsass without Mimikatz with MiniDumpWriteDump - Red Teaming Experiments
RedTeamTools/windows/LsassSilentProcessExit at master · lengjibo/RedTeamTools · GitHub
Exploring Mimikatz - Part 2 - SSP - XPN InfoSec Blog
GitHub - itm4n/PPLdump: Dump the memory of a PPL with a userland exploit