The Curious Case of MPNotify & NPPSPY

The Curious Case of MPNotify & NPPSPY

in

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!

Detection and Hunting Methods

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

  • Registry modifications and creation of the following (event IDs 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
  • In terms of malware research and EDRs, I’d assume hooking the following APIs:
    • CreateFile and WriteFile for file operations
    • RegOpenKey or RegCreateKey followed by RegSetValue or RegQueryValue.
  • File creation from the mpnotify.exe image (event ID 11) - within the next section, I will explain why!

‘lsass.dll’ Static & Dynamic Analysis

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

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.

YARA Time

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:

Mitigations

EDIT: On Twitter, Grzegorz Tworek himself offered a mitigation method which prevents the attack from being successful:

Final Thoughts

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.

References

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