Zero2Automated - January 2023 Challenge
We've been seeing a flow of first stagers flying around on Twitter in the form of a OneNote document, so why don't we all have a look..
Today’s hunt was inspired from this awesome blog by Dray Agha! Also a special thanks to Robsware for taking the time out his day to proofread this post! Bug, bother, nudge - show those two horrendously cool blokes all the love you can muster and ask them all about their expert knowledge on lsass.dll
!
I’ve been absolutely brimming with excitement to release this blog post! In this hunt I covered a thorough analysis of a custom NPPSPY specimen, from loading it inside IDA Home and reproducing the attack to writing detection rules, although what was most important to me was taking on some challenges and learning a few new things along the way on and off-stream. Throughout the time you’re reading, be sure to tap or click the images to zoom in!
As described within the investigation post from Huntress, the original NPPSPY tool was capable of abusing the relationship between WinLogon and Local Security Authority Subsystem Service (LSASS) - when a user logins into a workstation, whether it’s domain-joined or within a workgroup, WinLogon retrieves the user’s credentials to pass on to LSASS. An attacker can write their own DLL to hand over to WinLogon using special exports and intercept these plaintext credentials to write to a file of their choice. This was once proven by Grzegorz Tworek and later adopted by Red Canary’s Atomic Red Team framework, before eventually finding its way into a threat actor’s hands.
Exchange was definitely a smart place to drop the malicious DLL, which also brings the question to how catastrophic NPPSPY could be when it comes to stealing credentials from other Active Directory services. Although I’m pretty sure they can do more than writing to files, I’ll be sticking to the topic and explaining later on the adversary’s implementation from the Huntress showcase.
I also saw some detection methods Dray had used and thought I’d tap into my past experience within the SOC, then sprinkle in some other helpful hunting methods to the mix!
I may or may not have been (too) thorough, but the below would be technically most layers of detections and hunting methods to utilise if you ever handle a specimen that resembles or attempts to simulate the activity of NPPSPY. Of course, these detection methods may not be invincible as red teamers on Twitter very often demonstrate ways of evading and obfuscating their ways around them, but it would still catch your average script kiddie and lazy malware authors! Honestly, I’d say the Windows registry would be the easiest detection method, as there should be no reason it should change. If there’s not a ticket, then it is a crime! But without further ado, I will now take a deep dive into the detections! A fair amount of credit also goes to SnapAttackHQ for their video also covering detection methods for NPPSPY, go check them out!
MITRE Techniques: T1003 - OS Credential Dumping and T1556.002 - Modify Authentication Process: Password Filter DLL.
PowerShell Script Block Logging (event code 4104
, practically my favourite one to hunt)
Process creation: copy.exe
, powershell
or reg.exe
. These would be less likely to be written as detection rules, and more for hunting purposes to build a timeline of how the attacker carried out an attack to achieve their objectives.
File creation at C:\Windows\System32\
by an administrator account without any proof of legitimacy. There is a possibility that this can be easily slipped under the hood by impersonating the SYSTEM
account. The source file was a PE, but seeing from the ways malware authors have been wilding with DLL names, the file extension can be trivial. Naming it wrongly can still make it stand out like a sore thumb and make a lot easier to detect the bad doing in your environment!
A real and true executable
12
, 13
and 4657
):
HLKM\SYSTEM\CurrentControlSet\Control\NetworkProvider\Order
HLKM\SYSTEM\CurrentControlSet\Services\<Arbitrary Value>
HLKM\SYSTEM\CurrentControlSet\Services\<Arbitrary Value>\NetworkProvider
HLKM\SYSTEM\CurrentControlSet\Services\<Arbitrary Value>\NetworkProvider\Class
HLKM\SYSTEM\CurrentControlSet\Services\<Arbitrary Value>\NetworkProvider\Name
HLKM\SYSTEM\CurrentControlSet\Services\<Arbitrary Value>\NetworkProvider\ProviderPath
CreateFile
and WriteFile
for file operationsRegOpenKey
or RegCreateKey
followed by RegSetValue
or RegQueryValue
.mpnotify.exe
image (event ID 11
) - within the next section, I will explain why!We will focus on two important functions: NPGetCaps
and NPLogonNotify
.
NPGetCaps
returns values that are relevant to information on which services are supported on the network. This is mostly to resemble somewhat “legitimate” behaviour from a service DLL, and therefore loses its spotlight in the analysis!
Contents within the NPGetCaps export
NPLogonNotify
calls SavePassword
, therefore the meat of the malware is at 0x180001000
.
As seen in the below images, the adversary tried to obfuscate the malicious function with stack strings, which stored an encrypted file path that was later on decrypted for malicious DLL to write the stolen credentials to. While having the original NPPSpy code from GitHub made this obvious, this trick can still be useful in obfuscating the file in ways we do not expect and temporarily evading endpoint protection!
Stack strings - very small compared to malware campaigns like Emotet!
When I initially loaded and decompiled the function within IDA, the psuedocode was heavily optimised so I needed to reconstruct the string at stack level. In all honesty, I spent the longest time of the analysis on this because I wasn’t aware that stack strings can appear differently than intended because of the way a decompiler translates from Assembly to psuedocode, but it was still a great learning experience and a powerful reminder that no tool is perfect and will almost always require intervention from its user.
Before vs after cleaning up the psuedocode with labelling
After reconstructing the stack string and using Python to replicate the decryption routine, it was revealed that the stack string eventually resolved to C:\\ProgramData\\Package Cache\\Windows10.0-KB5009543-x64.msu
. This demonstrates that the attacker had tried to masquerade the dropped file as a standalone Windows Update file, which can be very tricky for a defender to spot!
With this theory in mind, I performed some dynamic analysis to prove the suspected behaviour.
PowerShell commands written across as a messy one-liner to replicate the attack scenario
The world’s strongest password revealed and written over to
Windows10.0-KB5009543-x64.msu
Since the file was dropped on disk, I believed it would be impossible to not be able to detect it through Sysmon, so I decided test some custom rules. Adding the below detection rule to the Sysmon config file from SwiftOnSecurity, it was revealed that mpconfig.exe
would be responsible for the file creation activity:
<TargetFilename name="OSCredentialDumping" condition="end with">Windows10.0-KB5009543-x64.msu</TargetFilename> <!--File created from lsass.dll/NPPSpy-->
mpnotify.exe shown to create files from the detection rules
mpnotify.exe
has not been known to create or write any files as shown by this post. I suspect that attackers may find more use for it than just theft of passwords though. For example, they can force an implant to run on another thread or simply open a HTTP connection to send credentials back to a C2 server - the possibilities are endless! Again, this requires administrator privileges and will be quite noisy.
After performing the static and dynamic analysis on the DLL, I thought it was about time I demonstrated how I would use Florian Roth’s excellent tool, YARA!
In my eCTHPv2 review post, I talked about how YARA and other forms of detection rules can only be as great as the people that wrote them. For anyone else who’s learning, allow me to walk you through a few steps I’d go!
When creating YARA rules, you’d target the most critical malicious functions (TTPs), followed by hardcoded strings that appear to be written by a human. If possible, you would also conduct a comparative analysis to search for reused code, which the BinDiff
tool can be very helpful for. In order to achieve a goal, an adversary tends to exhibit certain behaviours regardless of their tools, though some differ a lot more than others when being heavily OPSEC-focused. However, by targeting the TTPs, you’re essentially ruling based on the behaviour of a malware specimen, and causing a threat actor to put more effort into obfuscating their activity or entirely switch tools altogether. It then becomes a cyclic arms race - with the threat actor rewriting or hiding their tools and you revising and evolving your detection rules! Although it may not be as easy as it sounds, once you get used to analysing malware across different campaigns, you would then begin to notice certain patterns, profile threat actors and even spot when they introduce some new quirks and ‘updates’ into their bad doing!
In the case of malware research, this meant retrieving opcodes for certain operations such as encryption algorithms, file & registry operations - basically anything that would be require a lot of effort to obfuscate. The main things I focused on were the stack strings as well as the common exports and algorithms and across both samples, plus any extras like decrypted strings to make them adapted for certain hunts whether on VirusTotal or through Volatility’s in-memory yarascan
module. There may be a chance you’d capture payloads and useful strings while they are decrypted in memory! The below YARA rule draft reflects the observations I made on both the original and custom NPPSPY DLLs:
import "pe"
rule nppspy_or_variants
{
meta:
name = "NPPSpy and Variants"
description = "Malware Hunt - The Curious Case of MPNotify & NPPSPY"
techniques = "T1003, T1556.002"
weaponisation = "Masquarades as lsass.dll, and then extracts the credentials from WinLogon by getting sideloaded within MpNotify to dump into a file."
reference = "https://www.huntress.com/blog/cleartext-shenanigans-gifting-user-passwords-to-adversaries-with-nppspy"
report = "https://www.malwareguy.tech/Hunts/nppspy.html"
author = "Malware Guy"
version = "1.1"
hash1 = "e30f8596f1beda8254cbe1ac7a75839f5fe6c332f45ebabff88aadbce3938a19"
hash2 = "b283415c9df06f0e53b7d452d3e5c840c5bd7a6ce734a30bae4a869a57974a0e"
strings:
$s1 = "C:\\NPPSpy.txt" wide
$s2 = "NPPSPY.dll" fullword ascii
$s3 = "C:\\ProgramData\\Package Cache\\Windows10.0-KB5009543-x64.msu" // Added for redundancy
$opcodes1 = { C6 84 24 ?? ?? 00 00 ?? } // Stack Strings
$opcodes2 = { C6 44 24 ?? ?? } // Stack Strings
$opcodes3 = { 48 63 44 24 40 48 83 F8 ?? } // Set up the path to decrypt - beginning
$opcodes4 = { 48 63 44 24 40 0F BE 44 04 78 89 44 24 50 48 63 4C 24 40 33 D2 48 8B C1 B9 20 00 00 00 48 F7 F1 48 8B C2 0F BE 44 04 58 8B 4C 24 50 33 C8 8B C1 48 63 4C 24 40 88 84 0C 58 01 00 00 E9 46 FC FF FF } // String Decryption Routine
$opcodes5 = { 8B 44 24 40 FF C0 89 44 24 40 48 63 44 24 40 } // Increment index and repeat loop
condition:
((uint16(0) == 0x5A4D) and (uint32(uint32(0x3C)) == 0x00004550)) and (pe.exports("NpGetCaps") and pe.exports("NpLogonNotify")) and ((pe.imports("kernel32.dll", "CreateFileA") or pe.imports("kernel32.dll", "CreateFileW")) and (pe.imports("kernel32.dll", "WriteFile") and pe.imports("kernel32.dll", "SetFilePointer"))) and (((2 of ($s*)) and pe.pdb_path == "C:\\Users\\GrzegorzTworek\\source\\repos\\NPPSpy\\x64\\Release\\NPPSPY.pdb") or (2 of ($opcodes*)))
}
After a great amount of trial and error, the YARA rules eventually alerted on both specimen:
EDIT: On Twitter, Grzegorz Tworek himself offered a mitigation method which prevents the attack from being successful:
If you are responsible for Win11 security baseline, please use the new (I mean fixed after 20+ years) configuration option "Enable MPR notifications" under Windows Components\Windows Logon Options.
— Grzegorz Tworek (@0gtweet) January 5, 2023
Defaults allow to read cleartext credentials from Winlogon with a simple DLL. pic.twitter.com/pUBWQCNkeI
Thank you for reading my post! I’ve learned quite a bit from hunting this specimen, and as usual, only ever plan on doing even more. Because of a few select individuals within the Infosec community, I am now slightly more knowledgeable about how Windows environments (dangerously) handle authentication, and have my own input to give you guys within the community something to read.
I consider the YARA rule I’ve written in this post to be a draft, and there may be another version of it depending on if criminal hackers adopted this technique a lot more (very unlikely, hopefully) and I looked at this malware family again. Either way, having stronger and layered detection rules increases your confidence that you (or your AI) will have a better chance of catching certain malware once it makes its way into your environment! I also had a Sigma hunting and IOC rule related to the malicious activity published on SOC Prime, if you would like to give it a test run, check it out here. You will need an account to access it!
If you would like to learn a few helpful malware research techniques and have some experience with IDA already, I’d definitely recommend the Zero2Automated course! If you also happen to have any feedback about my post or have suggestions on what I could work on, you’re more than welcome to reach out to me on any of my handles! I also stream, so if you’re down for a chat whenever I’m live then just drop by anytime.
lsass.dll:
SHA256: E30F8596F1BEDA8254CBE1AC7A75839F5FE6C332F45EBABFF88AADBCE3938A19
https://www.virustotal.com/gui/file/e30f8596f1beda8254cbe1ac7a75839f5fe6c332f45ebabff88aadbce3938a19/ https://analyze.intezer.com/analyses/ea11eeb9-d345-46e3-a1cb-2ce47f933ee0
Atomic Red Team: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1003/T1003.md#atomic-test-2—credential-dumping-with-nppspy
Original PoC:
SHA256: B283415C9DF06F0E53B7D452D3E5C840C5BD7A6CE734A30BAE4A869A57974A0E
https://github.com/gtworek/PSBits/blob/master/PasswordStealing/NPPSpy/NPPSPY.dll
https://analyze.intezer.com/analyses/7d8a7434-258e-4e87-a1c7-e832485a51b4