AdaptixC2 Extraction de configuration et TTPs

Reading time: 9 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

AdaptixC2 est un framework modulaire et open‑source de post‑exploitation/C2 avec des beacons Windows x86/x64 (EXE/DLL/service EXE/raw shellcode) et prise en charge de BOF. Cette page documente :

  • Comment sa configuration chiffrĂ©e RC4 est intĂ©grĂ©e et comment l'extraire des beacons
  • Indicateurs rĂ©seau/profil pour les listeners HTTP/SMB/TCP
  • TTPs courants de loader et de persistence observĂ©s dans la nature, avec des liens vers des pages de techniques Windows pertinentes

Profils et champs des beacons

AdaptixC2 prend en charge trois types principaux de beacons :

  • BEACON_HTTP: C2 web avec serveurs/ports/SSL configurables, mĂ©thode, URI, headers, user‑agent, et un nom de paramĂštre personnalisĂ©
  • BEACON_SMB: C2 peer‑to‑peer via named‑pipe (intranet)
  • BEACON_TCP: sockets directs, Ă©ventuellement avec un marqueur prĂ©fixĂ© pour obfusquer le dĂ©marrage du protocole

Champs de profil typiques observés dans les configs HTTP des beacons (aprÚs déchiffrement) :

  • agent_type (u32)
  • use_ssl (bool)
  • servers_count (u32), servers (array of strings), ports (array of u32)
  • http_method, uri, parameter, user_agent, http_headers (length‑prefixed strings)
  • ans_pre_size (u32), ans_size (u32) – used to parse response sizes
  • kill_date (u32), working_time (u32)
  • sleep_delay (u32), jitter_delay (u32)
  • listener_type (u32)
  • download_chunk_size (u32)

Example default HTTP profile (from a beacon build):

json
{
"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
}

Profil HTTP malveillant observé (attaque réelle) :

json
{
"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
}

Empaquetage de la configuration chiffrée et chemin de chargement

Lorsque l'opérateur clique sur Create dans le builder, AdaptixC2 intÚgre le profil chiffré en tant que tail blob dans le beacon. Le format est :

  • 4 bytes: taille de la configuration (uint32, little‑endian)
  • N bytes: donnĂ©es de configuration chiffrĂ©es par RC4
  • 16 bytes: RC4 key

Le beacon loader copie la key de 16 bytes depuis la fin et RC4‑decrypts le bloc de N bytes en place:

c
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);

Implications pratiques :

  • L'ensemble de la structure se trouve souvent dans la section .rdata du PE.
  • L'extraction est dĂ©terministe : lire la taille, lire le ciphertext de cette taille, lire la clĂ© de 16 octets placĂ©e immĂ©diatement aprĂšs, puis dĂ©chiffrer avec RC4.

Flux de travail d'extraction de configuration (défenseurs)

Écrire un extracteur qui imite la logique du beacon :

  1. Localisez le blob dans le PE (gĂ©nĂ©ralement .rdata). Une approche pragmatique consiste Ă  scanner .rdata Ă  la recherche d'une mise en page plausible [size|ciphertext|16‑byte key] et tenter RC4.
  2. Lire les 4 premiers octets → size (uint32 LE).
  3. Lire les N=size octets suivants → ciphertext.
  4. Lire les 16 derniers octets → clĂ© RC4.
  5. Déchiffrer le ciphertext avec RC4. Puis analyser le profil en clair comme suit :
  • scalaires u32/boolean comme indiquĂ© ci‑dessus
  • chaĂźnes prĂ©fixĂ©es par la longueur (u32 length suivi des octets ; un NUL terminal peut ĂȘtre prĂ©sent)
  • tableaux : servers_count suivi de autant de paires [string, u32 port]

Preuve de concept Python minimale (autonome, sans dĂ©pendances externes) fonctionnant avec un blob pré‑extrait :

python
import struct
from typing import List, Tuple

def rc4(key: bytes, data: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) & 0xFF
S[i], S[j] = S[j], S[i]
i = j = 0
out = bytearray()
for b in data:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) & 0xFF]
out.append(b ^ K)
return bytes(out)

class P:
def __init__(self, buf: bytes):
self.b = buf; self.o = 0
def u32(self) -> int:
v = struct.unpack_from('<I', self.b, self.o)[0]; self.o += 4; return v
def u8(self) -> int:
v = self.b[self.o]; self.o += 1; return v
def s(self) -> str:
L = self.u32(); s = self.b[self.o:self.o+L]; self.o += L
return s[:-1].decode('utf-8','replace') if L and s[-1] == 0 else s.decode('utf-8','replace')

def parse_http_cfg(plain: bytes) -> dict:
p = P(plain)
cfg = {}
cfg['agent_type']    = p.u32()
cfg['use_ssl']       = bool(p.u8())
n                    = p.u32()
cfg['servers']       = []
cfg['ports']         = []
for _ in range(n):
cfg['servers'].append(p.s())
cfg['ports'].append(p.u32())
cfg['http_method']   = p.s()
cfg['uri']           = p.s()
cfg['parameter']     = p.s()
cfg['user_agent']    = p.s()
cfg['http_headers']  = p.s()
cfg['ans_pre_size']  = p.u32()
cfg['ans_size']      = p.u32() + cfg['ans_pre_size']
cfg['kill_date']     = p.u32()
cfg['working_time']  = p.u32()
cfg['sleep_delay']   = p.u32()
cfg['jitter_delay']  = p.u32()
cfg['listener_type'] = 0
cfg['download_chunk_size'] = 0x19000
return cfg

# Usage (when you have [size|ciphertext|key] bytes):
# blob = open('blob.bin','rb').read()
# size = struct.unpack_from('<I', blob, 0)[0]
# ct   = blob[4:4+size]
# key  = blob[4+size:4+size+16]
# pt   = rc4(key, ct)
# cfg  = parse_http_cfg(pt)

Tips:

  • Lors de l'automatisation, utilisez un parseur PE pour lire .rdata puis appliquez une fenĂȘtre glissante : pour chaque offset o, essayez size = u32(.rdata[o:o+4]), ct = .rdata[o+4:o+4+size], candidate key = next 16 bytes; RC4‑decrypt et vĂ©rifiez que les champs string se dĂ©codent en UTF‑8 et que les longueurs sont cohĂ©rentes.
  • Analysez les profils SMB/TCP en suivant les mĂȘmes conventions prĂ©fixant la longueur.

Empreintes réseau et chasse

HTTP

  • FrĂ©quent : POST vers des URI choisies par l'opĂ©rateur (par ex., /uri.php, /endpoint/api)
  • ParamĂštre d'en‑tĂȘte personnalisĂ© utilisĂ© pour l'ID du beacon (par ex., X‑Beacon‑Id, X‑App‑Id)
  • User‑agents imitant Firefox 20 ou des builds Chrome contemporains
  • Cadence de sondage visible via sleep_delay/jitter_delay

SMB/TCP

  • Listeners de named‑pipe SMB pour C2 intranet lorsque la sortie web est limitĂ©e
  • Les beacons TCP peuvent prĂ©fixer quelques octets avant le trafic pour obscurcir le dĂ©but du protocole

TTPs de loader et de persistance observés dans des incidents

Chargeurs PowerShell en mémoire

  • TĂ©lĂ©chargent des payloads Base64/XOR (Invoke‑RestMethod / WebClient)
  • Allouent de la mĂ©moire non gĂ©rĂ©e, copient le shellcode, changent la protection en 0x40 (PAGE_EXECUTE_READWRITE) via VirtualProtect
  • ExĂ©cutent via invocation dynamique .NET : Marshal.GetDelegateForFunctionPointer + delegate.Invoke()

Consultez ces pages pour l'exécution en mémoire et les considérations AMSI/ETW :

Antivirus (AV) Bypass

Mécanismes de persistance observés

  • Raccourci dans le dossier Startup (.lnk) pour relancer un loader au logon
  • ClĂ©s Run du registre (HKCU/HKLM ...\CurrentVersion\Run), souvent avec des noms Ă  consonance bĂ©nigne comme "Updater" pour dĂ©marrer loader.ps1
  • DLL search‑order hijack en dĂ©posant msimg32.dll sous %APPDATA%\Microsoft\Windows\Templates pour des processus susceptibles

Approfondissements et vérifications techniques :

Privilege Escalation with Autoruns

Dll Hijacking

Idées de chasse

  • Transitions RW→RX initiĂ©es par PowerShell : VirtualProtect vers PAGE_EXECUTE_READWRITE dans powershell.exe
  • SchĂ©mas d'invocation dynamique (GetDelegateForFunctionPointer)
  • .lnk dans les dossiers Startup utilisateur ou communs
  • ClĂ©s Run suspectes (ex., "Updater"), et noms de loader comme update.ps1/loader.ps1
  • Chemins DLL inscriptibles par l'utilisateur sous %APPDATA%\Microsoft\Windows\Templates contenant msimg32.dll

Notes sur les champs OpSec

  • KillDate : horodatage aprĂšs lequel l'agent s'auto‑expire
  • WorkingTime : heures pendant lesquelles l'agent doit ĂȘtre actif pour se fondre dans l'activitĂ© professionnelle

Ces champs peuvent ĂȘtre utilisĂ©s pour le clustering et pour expliquer des pĂ©riodes d'inactivitĂ© observĂ©es.

YARA et pistes statiques

Unit 42 a publiĂ© des YARA basiques pour les beacons (C/C++ et Go) et des constantes de hachage d'API du loader. Envisagez de complĂ©ter avec des rĂšgles cherchant la mise en page [size|ciphertext|16‑byte‑key] prĂšs de la fin de .rdata du PE et les chaĂźnes du profil HTTP par dĂ©faut.

References

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks