In early May 2025, Unit 42 researchers observed that AdaptixC2 was used to infect several systems.
AdaptixC2 is a recently identified, open-source post-exploitation and adversarial emulation framework made for penetration testers that threat actors are using in campaigns. Unlike many well-known C2 frameworks, AdaptixC2 has remained largely under the radar. There is limited public documentation available demonstrating its use in real-world attacks. Our research looks at what AdaptixC2 can do, helping security teams to defend against it.
AdaptixC2 is a versatile post-exploitation framework. Threat actors use it to execute commands, transfer files and perform data exfiltration on compromised systems. Because it’s open-source, threat actors can easily customize and adapt it for their specific objectives. This makes it a highly flexible and dangerous tool.
The emergence of AdaptixC2 as a tool used in the wild by threat actors highlights a growing trend of attackers using customizable frameworks to evade detection.
Palo Alto Networks customers are better protected from the threats described in this article through the following products:
If you think you might have been compromised or have an urgent matter, contact the Unit 42 Incident Response team.
Related Unit 42 Topics | Pentesting Tools, C2 |
AdaptixC2 is an open-source C2 framework that we recently saw being used in several real-world attacks.
We identified two AdaptixC2 infections. One case leveraged social engineering techniques. We assess with high confidence that the other used AI-based code generation tools.
AdaptixC2 is a red teaming tool that can be used to perform adversarial actions, which can be expanded for customization. If this were used by a threat actor, they could comprehensively control impacted machines, to execute a wide range of actions. These include:
Threat actors use these capabilities to establish and maintain a foothold in an environment, further explore the compromised system and move laterally within the network.
To facilitate covert communication and bypass network restrictions, the framework supports sophisticated tunneling capabilities, including SOCKS4/5 proxy functionality and port forwarding. This enables attackers to maintain communication channels even if the network is heavily protected.
AdaptixC2 is designed to be modular, using “extenders” that act like plugins for both listeners and agents. This lets hackers create custom payloads and ways to avoid detection that are specific to the system they're attacking. AdaptixC2 also supports Beacon Object Files (BOFs), which let attackers run small, custom programs written in C directly within the agent's process to evade detection.
AdaptixC2’s beacon agents are equipped with dedicated commands for transferring data quickly and secretly. These agents support both x86 and x64 architectures, and can be generated in various formats, including:
Attackers can use the AdaptixC2 framework to steal data from the compromised network. This data exfiltration functionality allows configurable chunk sizes for file downloads and uploads, as network-based detection is likely to see smaller segments as less suspicious.
The AdaptixC2 interface shows linked agents and sessions in a graphical view. Figure 1 shows an attacker’s view of how multi-stage attacks are progressing and what paths are available for moving around a targeted network.
AdaptixC2 also has features to help the attacker maintain operational security (OpSec). These include parameters that help them blend in with normal network traffic:
Additionally, threat actors can modify and enhance the agent using custom obfuscation, anti-analysis and evasion techniques, making it a continuously evolving threat.
AdaptixC2’s configuration is encrypted, and supports three primary beacon types through specialized profile structures:
The HTTP profile is the most common beacon variant and contains typical web communication parameters such as:
The SMB profile uses Windows named pipes when HTTP might be blocked or monitored. The TCP profile is used to create direct socket connections with the option to prepend data for basic protocol obfuscation.
AdaptixC2 includes a built-in default configuration that demonstrates typical deployment parameters. The default HTTP profile targets 172.16.196.1:4443 using HTTPS communication, with a POST method to the /uri.php endpoint and the X-Beacon-Id parameter for beacon identification.
Figure 2 shows how to configure the beacon.
After clicking “Create,” the beacon builder encrypts the configuration with RC4 and then embeds it in the compiled beacon. The encrypted configuration is stored as follows:
The following code is the key extraction logic, taken from AgentConfig.cpp:
ULONG profileSize = packer->Unpack32(); this->encrypt_key = (PBYTE) MemAllocLocal(16); memcpy(this->encrypt_key, packer->data() + 4 + profileSize, 16); DecryptRC4(packer->data()+4, profileSize, this->encrypt_key, 16); |
Because the encryption is simple and predictable, defenders can develop an extractor that will extract configurations from samples automatically. This extraction tool should work in the same way that the beacon loads its own configurations.
The extractor locates the configuration in the PE file’s .rdata section. It then extracts the size (first four bytes), encrypted data block and RC4 key (last 16 bytes). After using the embedded RC4 key to decrypt the data, it parses the plaintext configuration by unpacking the following fields:
Using this method, we created a tool that can process AdaptixC2 samples and get their embedded configurations. The complete extractor code supports the BEACON_HTTP variant. This tool is provided in the Configuration Extractor Example section. Researchers can use this extractor to analyze AdaptixC2 samples or adapt the code for other variants.
Following is the built-in default configuration of the beacon.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
{ "agent_type": 3192652105, "use_ssl": true, "servers_count": 1, "servers": ["172.16.196.1"], "ports": [4443], "http_method": "POST", "uri": "/uri.php", "parameter": "X-Beacon-Id", "user_agent": "Mozilla/5.0 (Windows NT 6.2; rv:20.0) Gecko/20121202 Firefox/20.0", "http_headers": "\r\n", "ans_pre_size": 26, "ans_size": 47, "kill_date": 0, "working_time": 0, "sleep_delay": 2, "jitter_delay": 0, "listener_type": 0, "download_chunk_size": 102400 } |
In May 2025, we investigated multiple incidents where threat actors installed AdaptixC2 beacons. In some cases, we observed threat actors using the same attack vector, shown in Figure 3.
The threat actors leveraged trust in Microsoft Teams to trick people into giving them access to company systems. In one case, attackers used phishing attacks to impersonate IT support personnel (using subject lines like “Help Desk (External) | Microsoft Teams”). This convinced employees to initiate legitimate remote assistance sessions using tools like the Quick Assist Remote Monitoring and Management (RMM) tool.
Threat actors often misuse legitimate products for malicious purposes. This does not necessarily imply a flaw or malicious quality to the legitimate product being misused.
The 2025 Unit 42 Global Incident Response Report: Social Engineering Edition noted that social engineering techniques like this are the most prevalent initial access vector for compromises we observe. This initial access provides the attackers with a foothold within the targeted system, without having to bypass perimeter defenses such as firewalls and intrusion detection systems.
The attackers deployed the AdaptixC2 beacon using a multi-stage PowerShell loader that downloads an encoded and encrypted payload from a link to a legitimate service,
Once downloaded, the PowerShell script decrypts the payload using a simple XOR key. Instead of writing the decrypted payload to disk, which would make it easier to detect, the script leverages .NET capabilities to allocate memory within the PowerShell process itself. The script then copies the decrypted payload, which is actually shellcode, into this allocated memory region. This fileless approach significantly reduces the attacker’s footprint on the system.
The script uses a technique called “dynamic invocation” to execute the shellcode directly from memory. It does this using the GetDelegateForFunctionPointer method, which dynamically creates a delegate (a type-safe function pointer) that points to the beginning of the shellcode in memory. The script then calls this delegate as if it were a normal function, effectively executing the shellcode without writing an executable file to disk. To guarantee the malicious process automatically starts after reboot, the script creates a shortcut in the startup folder. Figure 4 shows the PowerShell script.
The beacon variant loaded in this attack had the following configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
{ "agent_type": 3192652105, "use_ssl": true, "servers_count": 1, "servers": [ "tech-system[.]online" ], "ports": [ 443 ], "http_method": "POST", "uri": "/endpoint/api", "parameter": "X-App-Id", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36", "http_headers": "\r\n", "ans_pre_size": 26, "ans_size": 47, "kill_date": 0, "working_time": 0, "sleep_delay": 4, "jitter_delay": 0, "listener_type": 0, "download_chunk_size": 102400 } |
Following the successful deployment of AdaptixC2, the attackers initiated reconnaissance activities, using command-line tools to gather information about the compromised systems and network. This included discovery commands such as nltest.exe, whoami.exe and ipconfig.exe.
The beacon then established communication with a remote server, enabling the threat actors to obtain C2 on the infected machine.
In another case, threat actors deployed a PowerShell script that was designed to deploy AdaptixC2 beacons. We assess with high confidence that this script was AI-generated. This deployment was done both through in-memory shellcode injection and using a file-based DLL hijacking persistence mechanism. The script, shown in Figure 5, focuses on staying hidden on the impacted system to give the hackers a strong foothold.
The structure and composition of this PowerShell script strongly suggests that the attacker used AI-assisted generation. The following stylistic elements are commonly observed in code generated by AI tools:
We assess with high confidence that the code was generated with the assistance of AI. This is based on the factors above, as well as evidence gathered from the attacker’s server and results extracted from two separate AI detectors.
AI tools without sufficient guardrails can let attackers rapidly develop malicious code, making it easier to execute operations in infected networks.
A consistent pattern emerged across both of these incidents:
Our telemetry and threat intelligence show that AdaptixC2 is becoming more common. We continue to identify new AdaptixC2 servers, suggesting that more threat actors are adopting this framework as part of their attack toolkit.
This trend extends beyond typical post-exploitation scenarios. For example, attackers deployed Fog ransomware alongside AdaptixC2 in a recent attack on a financial institution in Asia. This shows that AdaptixC2 is versatile and can be used with other malicious tools, like ransomware, to achieve broader objectives.
AdaptixC2 is an adaptable threat, which is shown by its increasing popularity with threat actors and the complexity of its deployment techniques. The framework’s modularity, combined with the potential for AI-assisted code generation, could allow threat actors to rapidly evolve their tactics. Security teams must remain aware of AdaptixC2’s capabilities and proactively adapt their defenses to counter this threat.
Palo Alto Networks customers are better protected from the threats discussed above through the following products and services:
If you think you may have been compromised or have an urgent matter, get in touch with the Unit 42 Incident Response team or call:
Palo Alto Networks has shared these findings with our fellow Cyber Threat Alliance (CTA) members. CTA members use this intelligence to rapidly deploy protections to their customers and to systematically disrupt malicious cyber actors. Learn more about the Cyber Threat Alliance.
Value | Type | Description |
bdb1b9e37f6467b5f98d151a43f280f319bacf18198b22f55722292a832933ab | SHA256 | PowerShell script that installs an AdaptixC2 beacon |
83AC38FB389A56A6BD5EB39ABF2AD81FAB84A7382DA296A855F62F3CDD9D629D | SHA256 | PowerShell script that installs an AdaptixC2 beacon |
19c174f74b9de744502cdf47512ff10bba58248aa79a872ad64c23398e19580b | SHA256 | PowerShell script that installs an AdaptixC2 beacon |
750b29ca6d52a55d0ba8f13e297244ee8d1b96066a9944f4aac88598ae000f41 | SHA256 | PowerShell script that installs an AdaptixC2 beacon |
b81aa37867f0ec772951ac30a5616db4d23ea49f7fd1a07bb1f1f45e304fc625 | SHA256 | AdaptixC2 beacon as DLL |
df0d4ba2e0799f337daac2b0ad7a64d80b7bcd68b7b57d2a26e47b2f520cc260 | SHA256 | AdaptixC2 beacon as EXE |
AD96A3DAB7F201DD7C9938DCF70D6921849F92C1A20A84A28B28D11F40F0FB06 | SHA256 | Shellcode that installs AdaptixC2 beacon |
tech-system[.]online | Domain | AdaptixC2 domain |
protoflint[.]com | Domain | AdaptixC2 domain |
novelumbsasa[.]art | Domain | AdaptixC2 domain |
picasosoftai[.]shop | Domain | AdaptixC2 domain |
dtt.alux[.]cc | Domain | AdaptixC2 domain |
moldostonesupplies[.]pro | Domain | AdaptixC2 domain |
x6iye[.]site | Domain | AdaptixC2 domain |
buenohuy[.]live | Domain | AdaptixC2 domain |
firetrue[.]live | Domain | AdaptixC2 domain |
lokipoki[.]live | Domain | AdaptixC2 domain |
veryspec[.]live | Domain | AdaptixC2 domain |
mautau[.]live | Domain | AdaptixC2 domain |
muatay[.]live | Domain | AdaptixC2 domain |
nicepliced[.]live | Domain | AdaptixC2 domain |
nissi[.]bg | Domain | AdaptixC2 domain |
express1solutions[.]com | Domain | AdaptixC2 domain |
iorestore[.]com | Domain | AdaptixC2 domain |
doamin[.]cc | Domain | AdaptixC2 domain |
regonalone[.]com | Domain | AdaptixC2 domain |
Defenders can use these Yara rules to check for the presence of AdaptixC2 beacons on machines.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
rule u42_hacktool_beacon_adaptixC2 { meta: description = "Detects AdaptixC2 beacon via basic functions" reference = "https://github.com/Adaptix-Framework/AdaptixC2" strings: $FileTimeToUnixTimestamp = {D1 65 F8 83 7D F4 1F 7E 17 8B 55 E4} $Proxyfire_RecvProxy = {B9 FC FF 0F 00 E8 6A 04 00 00} $timeCalc1 = {8D 82 A0 05 00 00 89 44 24 3C EB 07} $timeCalc2 = {FF D2 0F B7 44 24 28 66 3B} $b64_encoded_size = {83 C0 01 39 45 18 7E 22 8B 45 E4 C1 E0 08 89 C1} $manage = {C6 44 24 5F 00 48 8B 45 10 48 8B 00} condition: any of them } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
rule u42_hacktool_beaconGo_adaptixC2 { meta: description = "Detects AdaptixC2 beacon in GO via basic functions" reference = "https://github.com/Adaptix-Framework/AdaptixC2/tree/a7401fa3fdbc7ae6b632c40570292f844e40ff40/Extenders/agent_gopher" strings: $GetProcesses = {E8 96 4D E1 FF E8 96 4D E1 FF E8 96 4D E1 FF} $ConnRead = {0F 8E BD 00 00 00 4C 89 44 24 30 4C 89 54 24 40} $normalizedPath = {48 85 C9 74 0A 31 C0 31 DB 48 83 C4 38 5D C3 90 0F 1F 40 00} $Linux_GetOsVersion = {48 8D 05 51 D6 10 00 BB 0F 00 00 00} $Mac_GetOsVersion = {48 8D 05 AE 5A 0A 00 BB 30 00 00 00} condition: any of them } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
rule u42_hacktool_adaptixC2_loader { meta: description = "Detects AdaptixC2 shellcode loader via API Hashing" reference = "https://github.com/Adaptix-Framework/AdaptixC2/blob/main/Extenders/agent_beacon/src_beacon/beacon/ApiDefines.h" strings: $hash_NtFlushInstructionCache = { 9E 65 A1 91 } $hash_VirtualAlloc = { 76 63 CE 63 } $hash_GetProcAddress = { DE 2A 4F 18 } $hash_LoadLibraryA = { FA D0 59 11} $Calc_Func_resolve_ApiFuncs = {06 00 00 0F B6 11 48 FF C1 85 D2 74 14 44 8D 42} condition: ( $hash_NtFlushInstructionCache and $hash_VirtualAlloc and $hash_GetProcAddress and $hash_LoadLibraryA ) or ( $Calc_Func_resolve_ApiFuncs ) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
config case_sensitive = false | dataset=xdr_data | fields _time as TeamsTime ,event_type,agent_hostname,actor_effective_username,event_sub_type, title, actor_process_image_name as teams_image_name, actor_process_image_sha256 , actor_process_image_command_line, agent_hostname, _time, action_process_image_name, agent_os_type, agent_id | filter agent_os_type = ENUM.AGENT_OS_WINDOWS and event_type = ENUM.USER_SESSION and teams_image_name in ("ms-teams.exe","updater.exe") and ((title contains "(external)" and title not contains "Chat |" ) and (title contains "help" )) | join type = inner ( dataset=xdr_data | fields _time as RmmStartTime ,agent_os_type , action_file_extension , event_type,agent_hostname,actor_effective_username,event_sub_type, actor_process_image_name , action_process_image_path, agent_hostname, action_process_image_name, agent_id, event_id | filter agent_os_type = ENUM.AGENT_OS_WINDOWS and (event_type=ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START and action_process_image_name in ("*quickassist.exe","*anydesk.exe","*screenconnect.*.exe","*logmein.exe")) ) as rmm rmm.agent_id = agent_id and rmm.actor_effective_username = actor_effective_username and (timestamp_diff(rmm.RmmStartTime,TeamsTime , "MINUTE") < 10 and timestamp_diff(rmm.RmmStartTime,TeamsTime , "MINUTE") >= 0) | comp values(TeamsTime) as _time ,values(RmmStartTime) as RmmStartTime, values(teams_image_name) as teams_image_name, values(action_process_image_path) as action_process_image_name, values(actor_process_image_name) as ActorProcess, count(Title) as CountOfTitle by title,actor_effective_username,agent_hostname , agent_id, event_id | filter (array_length(action_process_image_name)>0) |
The following code is an example of a configuration extractor that extracts configurations from HTTP beacon files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
import struct import json import sys from typing import Dict, Any from malduck import procmempe, rc4, int32, enhex class ConfigParser: def __init__(self, data: bytes): self.data = data self.offset = 0 def unpack32(self) -> int: value = struct.unpack('<I', self.data[self.offset:self.offset + 4])[0] self.offset += 4 return value def unpack16(self) -> int: """Unpack a 16-bit unsigned integer (little-endian)""" value = struct.unpack('<H', self.data[self.offset:self.offset + 2])[0] self.offset += 2 return value def unpack8(self) -> int: """Unpack an 8-bit unsigned integer""" value = self.data[self.offset] self.offset += 1 return value def unpack_string(self) -> str: """Unpack a length-prefixed string""" length = self.unpack32() string_data = self.data[self.offset:self.offset + length] self.offset += length if string_data and string_data[-1] == 0: string_data = string_data[:-1] return string_data.decode('utf-8', errors='replace') def unpack_bytes(self, length: int) -> bytes: """Unpack a fixed number of bytes""" data = self.data[self.offset:self.offset + length] self.offset += length return data def parse_beacon_http_config(data: bytes) -> Dict[str, Any]: """Parse BEACON_HTTP configuration from raw bytes""" parser = ConfigParser(data) config = {} try: # Parse agent type config['agent_type'] = parser.unpack32() # Parse HTTP profile config['use_ssl'] = bool(parser.unpack8()) config['servers_count'] = parser.unpack32() # Parse servers and ports config['servers'] = [] config['ports'] = [] for i in range(config['servers_count']): server = parser.unpack_string() port = parser.unpack32() config['servers'].append(server) config['ports'].append(port) # Parse HTTP settings config['http_method'] = parser.unpack_string() config['uri'] = parser.unpack_string() config['parameter'] = parser.unpack_string() config['user_agent'] = parser.unpack_string() config['http_headers'] = parser.unpack_string() # Parse answer sizes config['ans_pre_size'] = parser.unpack32() ans_size_raw = parser.unpack32() config['ans_size'] = ans_size_raw + config['ans_pre_size'] # Parse timing settings config['kill_date'] = parser.unpack32() config['working_time'] = parser.unpack32() config['sleep_delay'] = parser.unpack32() config['jitter_delay'] = parser.unpack32() # Default values from constructor config['listener_type'] = 0 config['download_chunk_size'] = 0x19000 return config except Exception as e: print(f"Failed to parse configuration: {e}") raise def parse_config(data: bytes, beacon_type: str = "BEACON_HTTP") -> Dict[str, Any]: """Main entry point for parsing beacon configurations""" if beacon_type == "BEACON_HTTP": return parse_beacon_http_config(data) else: raise NotImplementedError(f"Parser for {beacon_type} not implemented") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python extractor.py <path_to_config_file>") sys.exit(1) passed_arg = sys.argv[1] try: sample = procmempe.from_file(passed_arg) rdata_section = sample.pe.section(".rdata") config_structure = sample.readp(rdata_section.PointerToRawData, rdata_section.SizeOfRawData) config_size = int32(config_structure) encrypted_config = config_structure[4:config_size+4] rc4_key = config_structure[config_size + 4 : config_size + 4 + 16] except Exception as e: print(f"Error reading file or extracting configuration: {e}") print("Using provided encrypted configuration bytes directly.") try: config_structure = bytes.fromhex(passed_arg) config_size = int32(config_structure) encrypted_config = config_structure[4:config_size+4] rc4_key = config_structure[config_size + 4 : config_size + 4 + 16] except Exception as e: print(f"Failed to process provided argument as configuration bytes: {e}") sys.exit(1) try: decrypted_config = rc4(rc4_key, encrypted_config) print(f"Decrypted configuration size: {len(decrypted_config)} bytes") print(f"Decrypted configuration content: {decrypted_config}") print("Decrypted configuration (hex): %s", enhex(decrypted_config)) config = parse_config(decrypted_config) print("Parsed configuration:") print(json.dumps(config, indent=2)) except Exception as e: print(f"Error parsing configuration: {e}") |