Nitrogen Dropping Cobalt Strike – A Combination of “Chemical Elements”

by Apr 29, 2025

First detected in September 2024 and initially targeting the United States and Canada, the Nitrogen ransomware group has since expanded its reach into parts of Africa and Europe. Many of their victims remain absent from Nitrogen’s public ransomware blog and likely never will be listed. At the time of writing, ransomware.live reports 21 known victims of Nitrogen. Notably, indicators of this malware family surfaced as early as 2023, suggesting links to other ransomware infections.

In this post, we’ll share details from a recent, non-published, Nitrogen ransomware case, including how the attackers gained initial access, their lateral movement across systems (confirmed through user access logs), and how they attempted to cover their tracks by clearing logs. By examining Windows Error Reporting (WER) and crash dump files, we uncovered a Cobalt Strike configuration, along with a Cobalt Strike C2 team server and the attacker’s use of a pivot system.

Malvertising to Gain Initial Access

In recent months, threat actors have leveraged targeted Nitrogen-themed malvertising, bundling malicious code within tools that appear legitimate. For instance, thedfirreport documented a Nitrogen campaign that distributed a fake “Advanced IP Scanner,” ultimately leading to a BlackCat ransomware infection. Similar malvertising tactics have been observed with disguised versions of FileZilla and WinRAR.

During one of our recent investigations, a user searching for “WinSCP download” via Microsoft Edge clicked on a suspicious ad served through Bing. The ad redirected them from ftp-winscp[.]org to a compromised WordPress site hosting a malicious WinSCP ZIP file — establishing the initial foothold (“beachhead”) in a broader attack chain.

WinSCP ZIP download detected in Microsoft Edge browser history on patient zero

Within the ZIP archive, WinSCP-6.3.6-Setup.zip (SHA-256: fa3eca4d53a1b7c4cfcd14f642ed5f8a8a864f56a8a47acbf5cf11a6c5d2afa2), several files were bundled: a malicious python312.dll, three legitimate DLLs, and a renamed python.exe labeled setup.exe. Once the user ran setup.exe, DLL sideloading occurred — WinSCP was installed in the foreground while the malicious DLL was loaded into the running process.

Malicious WinSCP ZIP bundled files

As indicated by the imports in setup.exe, python312.dll is invoked as a dependency at runtime, triggering the execution of the malicious DLL. Because the file path for the DLL is not defined with an absolute file path in setup.exe, Windows relies on its default DLL search order: it first checks the application’s directory, then the system directory, the Windows directory, and finally the PATH environment variable if the DLL is still not found.

setup.exe imports

Closer inspection of the malicious DLL, also referenced as the “NitrogenLoader,” shows that it mirrors the same exports and ordinals found in a genuine Python DLL. For example, it includes the Py_Main export mentioned in the setup.exe import table. However, whereas a legitimate python312.dll (for instance, 278f22e258688a2afc1b6ac9f3aba61be0131b0de743c74db1607a7b6b934043) features authentic logic, the malicious file uses a minimalist approach, returning null instructions instead.

Comparison of a legitimate and malicious python312.dll

Its primary malicious backdoor functionality resides in the DllMain export, in which the packed connect-back logic establishes a C2 connection. Various forensic artifacts — including Prefetch files on the compromised Windows client — confirmed that setup.exe and, consequently, python312.dll executed successfully, ultimately compromising Patient Zero.

Windows Host Triaging

Typically, when analyzing a system — unless you’re performing a scheduled compromise assessment — you have some lead pointing you toward the right direction for your forensic investigation. Doing forensics without a clear lead or well-defined questions is like setting off on vacation without deciding where you want to go. With that in mind, we rely on a battle-tested workflow to analyze systems and determine which tools to run, a process we refer to as “preparational forensics”. It’s partially automated, so we don’t have to deploy the same tools every time manually. As usual, we started off by analyzing “patient zero” with Velociraptor’s triage output.

After confirming infection, we took a full disk image. We won’t go into every detail of our standard deep-dive workflow here, but one key step we always take is to run THOR and look for recently created executables in the Master File Table. We focused on executables created that same day because we knew the exact timestamp of the WinSCP infection and suspected the threat actor might have used a C2 framework like Cobalt Strike. This approach led us to files named Intel64.exe, tcpp.exe, and IntelGup.exe.

Newly created executables on patient zero

As mentioned before, it’s also possible to run THOR against a system image, or, as we did, a mounted disk image by running thor64.exe --lab -p F:\ --htmlfile A:\Artifacts\case\output.html, where the F:\ drive served as the mount point.

Another option is to aim THOR directly at specific files of interest created on the day of initial infection, which, in this case, flagged tcpp.exe as containing a potential Cobalt Strike configuration. A byte pattern in this specific file that stood out was the recurring value 0x2e, a default XOR key for encrypting configurations in many versions of Cobalt Strike. Whenever we see stretches of 0x2e or 0x69 in a file, it usually indicates XOR-encrypted null bytes.

THOR detection highlighting a potential Cobalt Strike byte pattern

Several methods can help reveal more details about this potential Cobalt Strike configuration. The one we typically use is to copy the suspicious byte section and decrypt it using CyberChef or Python. From there, we can export the decrypted data and feed it into a Cobalt Strike parser.

Potential encrypted Cobalt Strike configuration pattern

The first step is to copy the 0x2e pattern, paste it into CyberChef, and decrypt it using 0x2e. Straight away, you can see interesting strings appearing.

Cobalt Strike decryption using CyberChef

Next, we can download the decrypted blob and leverage Sentinel One’s CobaltStrikeParser, extracting and parsing even more information.

Cobalt Strike configuration detection using CobaltStrikeParser

A particularly noteworthy aspect of the detected Cobalt Strike configuration was its reference to the internal IP address 192.168.101.XXX on port 5000, which happened to match patient zero’s own IP. This detail strongly suggests that patient zero was being used as a pivot for a Cobalt Strike beacon — a conclusion that became even clearer later in our investigation. We also observed that gpupdate.exe was employed as a sacrificial process for Cobalt Strike, as post-compromise payloads are typically injected into dedicated processes.

Note: The manual process described above for extracting Cobalt Strike configurations using the 0x2e pattern will soon be obsolete. THOR v11 includes a built-in feature that automatically detects, decrypts, and parses Cobalt Strike Beacon configurations — directly during the scan, no manual steps required. This feature will be covered in more detail in an upcoming blog post.

Interjection – Cobalt Strike Detection and Threat Intel

From these strings — for example, @%windir%\syswow64\gpupdate.exe, @%windir%\sysnative\gpupdate.exe, and the watermark hash S+sMUHERQLpRZukekGExAw== — we can build a custom YARA rule. Encrypting each of these strings with all possible single-byte values makes it possible to detect additional XOR-encrypted Cobalt Strike configurations, not only on patient zero but also on other potentially compromised hosts.

#!/usr/bin/env python3

def main():
    results = []
    str_input = ["@%windir%\syswow64\gpupdate.exe", "@%windir%\sysnative\gpupdate.exe", "S+sMUHERQLpRZukekGExAw=="]
    for string in str_input:
        for key in range(256):  # 0x00 through 0xFF
            xored_bytes = [ord(ch) ^ key for ch in string]  # XOR each character
            xored_hex = "".join(f"{byte:02x}" for byte in xored_bytes)
            results.append((key, xored_hex))

    # Write results to file
    with open("output.txt", "w", encoding="utf-8") as f:
        i = 0
        for key, xored_str in results:
            f.write(f"$s{i} = \"{xored_str}\"\n")
            i += 1

    print("All XOR variations written to output.txt")

if __name__ == "__main__":
    main()

Using the script’s output, we can create a very simple YARA rule to be used during the engagement, potentially highlighting even more suspicious files like the one we already discovered.

YARA Cobalt Strike signature and rule creation

Notably, the identified Cobalt Strike watermark 678358251 has previously been listed on abuse.ch. This watermark has been associated with multiple threat actors, including the ransomware group Black Basta, further highlighting its reuse across malicious campaigns and threat actors. Cobalt Strike watermarks serve as unique identifiers, allowing to track and correlate activity across disparate Cobalt Strike C2 servers observed in the wild.

Cobalt Strike C2 team servers with watermark 678358251

Detecting Lateral Movement with User Access Logging

After identifying patient zero, we set out to locate further compromised hosts. Tracking lateral movement from patient zero proved challenging because artifacts on the source system are typically less thorough than those on the destination. Complicating matters even more, the threat actor had cleared critical Windows event logs — among them the Security, System, and PowerShell logs — on several machines, as shown in the following screenshot.

Log clearing detected using Hayabusa

Nevertheless, not all forensic data was lost. Even with extensive log clearing, we built a supertimeline on one of the client’s physical Windows servers, revealing User Access Logging (UAL) entries. These entries provided clear evidence of lateral movement to another Windows Server originating from the beachhead on the exact date of the initial compromise.

User access logging artifacts in a system supertimeline

Basic Crash Dump Triaging

When we reran THOR against the newly uncovered compromised server system, it yielded some additional leads. In this instance, we discovered a suspicious operating system svchost.exe file that presented telltale signs of Cobalt Strike activity.

THOR detection of Cobalt Strike artifacts in a user mode crash dump

We also found that a Windows Error Reporting (WER) log for this particular svchost.exe was saved in C:\ProgramData\Microsoft\Windows\WER. WER is a highly underrated artifact capable of capturing detailed debug information, such as the application name, loaded modules, and a heap dump that preserves the memory data active at the time of a crash. If configured to do so, WER also collects user-mode crash dumps and stores them locally whenever an application crashes — exactly the situation THOR detected here. Although crash dumps are disabled by default, administrators can enable them by configuring the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps.

Crash dump SOFTWARE registry hive configuration

In recent years, these crash dumps have improved considerably and can be analyzed in more depth using tools like WinDBG — a process we’ll explore in the next chapter. In this specific scenario, we verified the crash dump settings by reviewing the registry keys and confirmed that a full dump (dump type 2), which includes all virtual memory, was being saved to the %LOCALAPPDATA%\CrashDumps directory, with a maximum of ten dump files retained.

From the svchost.exe.17872.dmp crash dump we identified through THOR, several suspicious string artifacts pointed to a possible Cobalt Strike beacon configuration. THOR referenced a GitHub repository — “Detects specific keywords found in Malleable C2 profiles for Office 365 Calendar” — indicating that both client and server configuration details, including cookie header values from the client and custom headers from the server, had been embedded within the crash dump.

M365 Calendar Profile on GitHub

To confirm these findings, we used bstrings.exe to extract strings from the crash dump running bstrings.exe -f .\svchost.exe.17872.dmp > .\svchost.exe.17872.dmp.strings.txt. This process uncovered the precise configuration strings highlighted earlier, revealing what appeared to be an entire HTTP response. We even found a Server header that matched the system responding to the request.

Cobalt Strike HTTP Response in the svchost.exe crash dump

We repeated this methodology until no additional pertinent strings emerged, then ran bstrings.exe to focus specifically on URLs: bstrings.exe --lr url3986 -f .\svchost.exe.17872.dmp -q –sa. That step exposed the Cobalt Strike team server, confirming our suspicions regarding an active beacon configuration within the crash dump.

C2 Team Server detected in URL strings extracted from the detected crash dump

Crash Dump Analysis with WinDBG

In this scenario, the process crash dump was configured to capture a full user-mode dump that included all virtual memory. Having access to a full dump file allowed for a thorough examination of the process at the time it failed. By loading the crash dump directly into WinDBG, the debugger halted at the specific exception that caused the crash and displayed the associated thread — thread 0x5 with an ID of 0x4c78 — along with a reference to the full memory dump type. The debugger also showed the debug session time, which matched the timestamp of the crash dump’s creation.

Crash dump loaded into WinDBG

The available information showed that a failure occurred while the process executed the kernel32 function CreateFileA (0x4c78 0x5 kernel32!CreateFileA (00007ffd'2ac44960)). Running !analyze –v initiated the exception analysis, revealing details about the operating system version, build, CPU registers, and a stack trace, alongside an error code. Unfortunately, the error code did not yield any additional clues, only indicating that the exception must have happened before the error handling routine at 00007ffd'12c5ac52 mscorlib_ni!System.Environment.ResourceHelper.GetResourceStringCode+0x252.

WinDBG analyze extension output

To gather more insights, the MEX extension provided the command !mex.di (or simply !di when using built-in aliases). This command revealed information about the user under whose account the process was running, as well as the operating system version, system uptime, and the process ID.

Basic MEX triaging in WinDBG

Further investigation involved the !peb command, which examined the Process Environment Block (PEB) — a structure containing details on loaded modules, command-line arguments, the image file in use, and the window title for the process. In this instance, the PEB indicated that the process path was C:\StorageReport\tcpp.exe, a file previously identified as a Cobalt Strike pivot beacon that facilitated tunneling through the patient zero system. With a Cobalt Strike configuration discovered in memory (as supported by the string analysis), it was apparent that malicious activity had been running within this process.

Pivot beacon identified as windows title metadata in the analyzed crash dump

These same details could have been extracted manually by inspecting the PEB structure without relying on the !peb extension. Typically, one would locate the PEB address first by referencing the pseudoregister $peb (dt @$peb). In a kernel-mode dump, the command !process -0 0 would also yield the PEB location. With that address in hand — in this case, 0x000000b977fe1000 — the relevant data can be read by issuing a command such as dt _PEB 000000b977fe1000.

0:005> dt @$peb
Symbol not found at address 000000b977fe1000.
0:005> dt _PEB 000000b977fe1000
ole32!_PEB
   +0x000 Reserved1        : [2]  ""
   +0x002 BeingDebugged    : 0 ''
   +0x003 Reserved2        : [1]  "???"
   +0x008 Reserved3        : [2] 0xffffffff`ffffffff Void
   +0x018 Ldr              : 0x00007ffd`2af203c0 _PEB_LDR_DATA
   +0x020 ProcessParameters : 0x000001e4`ef132160 _RTL_USER_PROCESS_PARAMETERS
   +0x028 Reserved4        : [3] (null) 
   +0x040 AtlThunkSListPtr : (null) 
   +0x048 Reserved5        : (null) 
   +0x050 Reserved6        : 4
   +0x058 Reserved7        : 0x00007ffd`292bf000 Void
   +0x060 Reserved8        : 0
   +0x064 AtlThunkSListPtr32 : 0
   +0x068 Reserved9        : [45] 0x000001e4`eef20000 Void
   +0x1d0 Reserved10       : [96]  ""
   +0x230 PostProcessInitRoutine : (null) 
   +0x238 Reserved11       : [128]  "???"
   +0x2b8 Reserved12       : [1] (null) 
   +0x2c0 SessionId        : 0

It is in the _PEB_LDR_DATA member that key information regarding loaded modules resides, as documented by Microsoft. The InMemoryOrderModuleList field within the _PEB_LDR_DATA structure is a doubly linked list of loaded modules, so walking this list can provide details on every module.

0:005> dt _LIST_ENTRY
ole32!_LIST_ENTRY
   +0x000 Flink            : Ptr64 _LIST_ENTRY
   +0x008 Blink            : Ptr64 _LIST_ENTRY

This includes the primary image executable (in this instance, svchost.exe) and subsequent items referenced in its InMemoryOrderLinks or InLoadOrderLinks fields.

Manual PEB iteration

The first loaded module points to the next one via its InMemoryOrderLinks or InLoadOrderLinks member, which, in this instance, leads to the address 0x000001e4ef132950. Because that address is also of type _LIST_ENTRY, the command dt _LDR_DATA_TABLE_ENTRY 0x000001e4ef132950 can reveal details about the next link. This manual approach — iterating through the linked list entry by entry — proves especially useful when you need to investigate a specific module or structure in greater depth.

Second module in the PEB

Returning to the original purpose — gathering conclusive evidence of a Cobalt Strike beacon residing in memory — analysis continued by examining suspicious strings and testing them against a Cobalt Strike YARA rule by Elastic.

Cobalt Strike YARA rule by Elastic

Observed strings were traced to the corresponding memory address within the dump, revealing that all originated from a similar region. Searching for the MZ header indicated the presence of what looked like a loaded binary at that location.

WinDBG potential Cobalt Strike string search

By investigating the DOS header (ntdll!_IMAGE_DOS_HEADER at 000001e4'eef80000), one can identify the PE header offset (e_lfanew), determine the approximate size of the binary (SizeOfImage), and theoretically dump that data. However, it is important to note that paging can cause portions of memory to be absent from the dump file, so the extracted DLL may be incomplete or partially overwritten.

0:005> dt -r ntdll!_IMAGE_DOS_HEADER 000001e4`eef80000
   +0x000 e_magic          : 0x5a4d
   +0x002 e_cblp           : 0x90
   +0x004 e_cp             : 3
  […]
   +0x028 e_res2           : [10] 0
   +0x03c e_lfanew         : 0n184
0:005> ? 000001e4`eef80000 + 0n184
Evaluate expression: 2082773401784 = 000001e4`eef800b8
0:005> dt -r _IMAGE_NT_HEADERS 000001e4`eef800b8
ole32!_IMAGE_NT_HEADERS
   +0x000 Signature        : 0x4550
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
      +0x000 Machine          : 0x14c
      +0x002 NumberOfSections : 2
      +0x004 TimeDateStamp    : 0
      +0x008 PointerToSymbolTable : 0
      +0x00c NumberOfSymbols  : 0
      +0x010 SizeOfOptionalHeader : 0xe0
      +0x012 Characteristics  : 0x2102
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER
      +0x000 Magic            : 0x10b
  […]
      +0x038 SizeOfImage      : 0x3000
      +0x03c SizeOfHeaders    : 0x200
      +0x040 CheckSum         : 0xc085
      +0x044 Subsystem        : 2
      +0x046 DllCharacteristics : 0x540
      +0x048 SizeOfStackReserve : 0x100000
      +0x04c SizeOfStackCommit : 0x1000
      +0x050 SizeOfHeapReserve : 0x100000
      +0x054 SizeOfHeapCommit : 0x1000
      +0x058 LoaderFlags      : 0
      +0x05c NumberOfRvaAndSizes : 0x10
      +0x060 DataDirectory    : [16] _IMAGE_DATA_DIRECTORY
         +0x000 VirtualAddress   : 0
         +0x004 Size             : 0

Using .writemem in WinDBG with an appropriate address range (000001e4'eef80000 L3000) attempts to write this region to disk. In this case, portions of memory at 000001e4'eef81000 were unreadable, likely due to paging, and the range did not encompass the exact strings indicative of the beacon configuration.

Memory sections written to disk using .writemem WinDBG extension

Consequently, additional blocks of memory were dumped around the suspicious strings — for instance, those containing %02d/%02d/%02d %02d:%02d:%02d, %s (admin), or Content-Length: %d — in an effort to capture more complete data. Although this did not yield a fully parsable beacon configuration in this specific instance, the discovered indicators, combined with previous string analysis, further reinforced that a Cobalt Strike payload had indeed been running within the process at the time of the crash.

Dumped memory region containing potential Cobalt Strike strings

Summing Up

The Nitrogen ransomware group exemplifies a modern, multi-stage intrusion operation that blends social engineering, evasive malware, and post-exploitation frameworks. By abusing malvertising — often disguising payloads as legitimate tools like WinSCP, Advanced IP Scanner, or FileZilla — Nitrogen establishes initial access via DLL sideloading, with malicious loaders delivering backdoor functionality through NitrogenLoader.

Once inside the network, Cobalt Strike becomes their tool of choice for lateral movement, command and control, and post-compromise activity. In our case study, Nitrogen used a compromised host as a pivot system while simultaneously wiping critical Windows logs to hinder detection and response efforts.

Throughout this post, we highlighted various ways to detect and extract Cobalt Strike configurations, including pattern analysis, byte-level XOR decryption, and custom YARA rules. In particular, we emphasized the power of crash dump analysis — specifically using Windows Error Reporting (WER) artifacts and WinDBG — to uncover in-memory indicators of Cobalt Strike beacons, configuration strings, and HTTP response structures embedded in dump files.

With that being said—stay safe, make use of lesser-known artifacts like WER, crash dumps, and UAL — and always read the labels before you install something from an ad.

About the author:

Maurice Fielenbach

Maurice Fielenbach trains cybersecurity professionals in reverse engineering and malware analysis — his main area of focus — and digital forensics through his company, Hexastrike Cybersecurity. The company also develops tools for red and blue teams and publishes technical blog posts covering both offensive and defensive topics. He also serves as Head of CERT at r-tec, leading a team of forensic specialists, managing and investigating a wide range of security incidents.

Subscribe to our Newsletter

Monthly news, tips and insights.

Follow Us

Upgrade Your Cyber Defense with THOR

Detect hacker activity with the advanced APT scanner THOR. Utilize signature-based detection, YARA rules, anomaly detection, and fileless attack analysis to identify and respond to sophisticated intrusions.