Cyber Security Technical Blog

MiniDumpDotNet: Pure CLR Alt – Part 2

Written by Jerry Odegaard | Sep 19, 2024 5:00:00 AM

MiniDumpDotNet – Part 1 & 2

In MiniDumpDoNet – Part 1, we took a brief look at the MiniDumpWriteDump() Win32 API and considered options for reimplementation for the purposes of avoiding AV and EDR prevention, as well as ReactOS’s reimplementation (as a reference point) without digging too deeply into the details.

In this Part 2, We continues the story and we will walk through the two projects currently available as Cobalt Strike Beacon Object Files. Then, we will wrap the story up by discussing how we adapted one of these projects to be loadable with .NET CLR injection methods (i.e. Assembly.Load() and MemoryStream via DotNetToJScript). 

First Beacon Object File Implementation

While being all cryptic about this “secret project” I was working on and chatting with Michael Rand during the past months of occasionally searching on this topic, I unfortunately never once came across rookuu’s BOFs project (which had been released approximately 8 months prior). This project contains a reimplementation of MiniDumpWriteDump() and is organized as a Beacon Object File (BOF) to be used with Cobalt Strike. Michael took maybe a few minutes and found it:

After feeling sorry for myself for about 15 minutes; I went and pulled down the project, scrubbed out all the BOF-specific and SysWhispers code, added the necessary libraries for the linker, and built a functional standalone adaptation of the project in Visual Studio. Although this was half-way toward what I wanted (i.e. a tool that could be used in Cobalt Strike OR Covenant), I wasn’t able to complete the migration of the code to C++ .NET before my next string of client projects.

Regardless, this was an awesome find by Michael and outstanding work by rookuu. In fact, when I was reading through this source code, he had actually mentioned that he started his project by reviewing ReactOS’s codebase as well!

Second Beacon Object File Implementation

On yet another random day, I was chatting with Wes Harden when he sent me NanoDump, created by a few security folks at HelpSystems:

On its surface, this project shared many similarities with the previously mentioned BOFs project by rookuu. It could be used as a BOF and it could dump LSASS without utilizing the Win32 version of MiniDumpWriteDump(). 

It also has some nice features that weren’t available in the other project: standalone binary support, automatic LSASS process discovery, and several opsec features such as obfuscated (corrupted) MiniDump header, stub code for implementing MiniDump encryption, and automatic download without touching disk. 

The major drawback is that the NanoDump implementation is designed to dump only LSASS. Realistically, that’s probably the most important process to dump during threat emulation exercises, however at times, you may want to dump other processes (i.e. Outlook, which will cache domain creds under certain circumstances). Yet, the codebase is smaller and may be a bit easier to convert to C++ .NET than the code from the BOFs project.

Converting Native C++ to CLR-Enabled C++

I haven’t had many opportunities to work with CLR enabled C++, but as it turns out it can be remarkably easy to convert non-CLR C++ code to CLR enabled C++ with some copying and pasting. P/Invoke methods to access native API calls are handled automatically by the compiler, and most code is converted to CLR compatible counterparts. When building a C++ CLR application you have a few options for CLR support, however to ensure that you’re targeting your project so that it can be injected via Assembly.Load() (or alternatives), the project MUST compile successfully with the /clr:pure option:

In general, we used the following approach to attempt converting both the BOFs and NanoDump projects to CLR-Enabled C++:

  1. Create a new .NET C++ project in Visual Studio
  2. Copy/paste source code (either directly, or by copying the source code files, and adding them to the project)
  3. Remove code deemed unnecessary (e.g. SysWhispers, Cobalt Strike specific BOF code)
  4. Update the code to call native API calls previously handled by SysWhispers, and/or include linker inputs (i.e. adding ntdll.lib)
  5. Troubleshoot compiler errors (this is a potentially lengthy process of including Windows header files and/or directly including structs/constants in another header file)
  6. Enable /clr:pure and troubleshoot any remaining compiler errors

By default when you create a CLR-Enabled C++ project, the CLR support is initially set to simply /clr – which will potentially produce a binary with mixed CLR code and native code that can be called from CLR code. Although the bulk of the conversion effort occurs up to and including step (5) from above, there are some specific caveats to completing step (6) which we’ll cover in a moment.

Making MiniDumpDotNet

We had initially attempted to convert the BOFs project to support Pure CLR code, however eventually pivoted to NanoDump. It’s worth noting an issue we ran into when attempting to convert the BOFs project. Once we finished Step (5) from above, we switched the CLR Support option to /clr:pure, which caused a compiler error. Some function calls in C++ (in particular Win32 API calls) may require pointers to native functions. An example of this can be found in the BOFs project in its use of EnumerateLoadedModulesW64(), a Win32 API call that takes as a parameter a native function pointer (i.e. fetch_pe_module_info_cb):

Since the corresponding function for fetch_pe_module_info_cb was compiled as a .NET Managed function, it can’t be passed as a pointer to a native Win32 API call. This restriction can be overcome by replacing EnumerateLoadedModulesW64(), but we didn’t have the opportunity to do that before NanoDump had been released.

Once NanoDump was released, we returned to the same process to convert the codebase to CLR-Enabled C++. We didn’t need to worry about the method to enumerate remote modules, since NanoDump makes use of PEB Walking to accomplish this. Once we finished Step (5) we ran into a different compiler error when switching to /clr:pure, and in this case it was due to the original project’s use of compiler intrinsic functions. A compiler intrinsic is essentially a function implemented in assembly that’s inserted directly into a native binary. 

Instead of working our way backwards from the compiler error, we’ll take a look at an example function call responsible for calling the compiler intrinsic that needs to be removed. We’ll start below at an example call to READ_MEMLOCK(). For context, READ_MEMLOCK() is trying to get a pointer to the PEB (Process Environment Block) of the current process and subsequent function calls extract the OS Major/Minor and Build Numbers.

In this case, the READ_MEMLOC() call is implemented via a preprocessor define statement, pointing to either __readfsdword or __readgsqword depending on the select architecture (x86/x64):

Reading through the NanoDump code, there are protections so that it won’t fully execute if compiled as an x86 module, that means that the line 20 definition using __readgsqword will be used when calling READ_MEMLOC(). As it turns out __readgsqword is a compiler intrinsic that reads memory from an offset relative to the beginning GS segment and needed to be replaced.

As we removed a large amount of unnecessary code, we found that the only lingering usage of READ_MEMLOC () was in pulling OS version information via reading the PEB. So we replaced the call with RtlGetNtVersionNumbers()to get the OS Major/Minor and Build Numbers.

Otherwise, NanoDump was a fairly straight-forward candidate codebase to adapt!

MiniDumpDotNet Usage

If you weren’t entertained by everything leading up to this point, hopefully you’ll enjoy the next part. 

We’re releasing the tool at MiniDumpDotNet for the community to enjoy. We’ve had fairly good luck using this tool against all the common EDRs and AVs, with the exception of one (which unfortunately we are unable to name).

After downloading the project source, the first thing you’ll need to do is build it with Visual Studio. We had originally implemented it in VS 2015 on Windows 7 for legacy testing purposes (nobody still has Win7, of course!) and validated that it builds just fine in VS 2019 on Windows 10. This is as simple as: Build -> Build Solution. Once built, you’ll have two binaries (both DLL and EXE) located in .\minidumpdotnet\build\Release

We implemented MiniDumpDotNet as a COM-Exposed Class, which allows for flexible usage right out of the box. We’ve provided sample usage scenarios on our GitHub page, so instead of showing every way we can use this tool – let’s just take a quick look at using MiniDumpDotNet in PowerShell and then offline extraction with Mimikatz!

MiniDumpDotNet In PowerShell

First you’ll want to open a cmd.exe prompt (or just go directly to PowerShell) as Administrator. Then you’ll navigate to the build folder (our example below is simplified) and load PowerShell. Once PowerShell is loaded, you’ll run the following commands (assuming the PID for LSASS is 620):

[string]$assemblyPath = ".\minidumpdotnet.dll"
Add-Type -Path $assemblyPath
[MiniDump.MiniDump]$a = New-Object -TypeName 'MiniDump.MiniDump'
$a.DumpPid(620, "dump.dmp")

Provided you don’t have any strange LSASS protection on your system, this should have produced a MiniDump file dump.dmp:

Once you’ve validated that you’ve produced a MiniDump, load up another command prompt and start up Mimikatz. 

MiniDumpDotNet In Mimikatz

In our case (below), we start Mimikatz from within a directory containing our MiniDump. You’ll want to follow the normal process of extracting credentials from a MiniDump:

sekurlsa::minidump dump.dmp
sekurlsa::logonPasswords

And just like that we have hashes to crack or pass!!

MiniDumpDotNet Conclusion & Credits

Please take a moment to check out our public repository for MiniDumpDotNet for all of the other validated usage scenarios. If you’d like to see features added (including anything that was stripped during the conversion process) – feel free to submit Issues or Pull Requests if you’d like us to incorporate any of your own changes!

We’d also like to give credit to the developers at HelpSystems and rookuu for producing the first reimplementations of MiniDumpWriteDump() for offensive purposes!

MiniDumpDotNet Part 1 in case you missed it.

MORE FROM OUR TECHNICAL BLOG

Cyber Advisors specializes in providing fully customizable cyber security solutions & services. Our knowledgeable, highly skilled, talented security experts are here to help design, deliver, implement, manage, monitor, put your defenses to the test, & strengthen your systems - so you don’t have to.

Read more from our technical experts...