Skip to content
Back to the Lab
Blue Team

Inside the GSocket Backdoor

Attackers turn a legitimate encrypted relay into a persistent Linux backdoor. Dissect GSRN, process masquerading, secret files, and host detection.

A dark security operations banner representing an encrypted relay connecting a hidden Linux backdoor.

An exposed Linux server can be backdoored without listening on a new inbound port. The operator does not need a stable address, the victim does not need a public IP, and the connection can remain encrypted while crossing a relay on an egress port that administrators normally permit.

Global Socket, usually shortened to GSocket, provides exactly those properties. It is an open-source networking toolkit, not a malware family. Two endpoints that know the same secret connect outward to the Global Socket Relay Network, or GSRN. The relay pairs them and forwards an end-to-end encrypted stream. The project legitimately supports interactive shells, file transfer, TCP forwarding, proxying, and optional Tor connectivity.

Attackers repeatedly turn that functionality into a backdoor by adding malicious deployment, persistence, deceptive process names, hidden secret files, and anti-forensic behavior. Elastic observed it in the REF6138 Linux intrusion in 2024. Sansec found it on compromised Adobe Commerce systems and later saw criminals scanning for other operators’ exposed GSocket keys. JPCERT/CC documented a renamed copy after React2Shell exploitation. SANS ISC analyzed a 2026 Bash deployer. Yandex Cloud then reported GSocket alongside Linux ransomware operations and warned that an implant can remain dormant for years.

The defensive problem is therefore larger than blocking one domain. A quiet implant may not resolve a relay while an investigation is running. A custom relay can remove the default domain indicator. A renamed static binary can avoid package-based inventory. The reliable strategy is to correlate what the executable is, how it starts, what its process claims to be, which files it reads, and where its sockets lead.

GSocket Is the Transport, Not the Initial Exploit#

GSocket does not exploit the target and should not be described as a vulnerability. Documented intrusions gained execution through another route, then staged GSocket for continued access. Sansec connected one deployment to Adobe Commerce exploitation. JPCERT/CC observed npm-cli after exploitation of CVE-2025-55182. Yandex Cloud described exposed application interfaces, stolen credentials, VPN sessions, and vulnerable web applications as initial access paths in its broader intrusion set.

This distinction changes incident response. Finding gs-netcat answers how an intruder maintained an access channel, but not how the intruder arrived. Removing the binary without reconstructing initial access leaves the original weakness, stolen credential, web shell, or second backdoor untouched. Sansec reported repeated reinfection on systems where visible malware was cleaned but GSocket persistence or the underlying compromise survived.

The upstream project openly calls gs-netcat an encrypted reverse backdoor in its feature documentation. That description refers to capability, not intent. An approved administrator may use the same binary for legitimate remote access. Malice is established by context: unauthorized installation, deceptive placement, an unapproved shell, process masquerading, encoded persistence, timestomping, or a relay relationship outside policy.

QuestionSafe conclusion
Is GSocket malware by origin?No. It is a dual-use open-source networking toolkit.
Does it create initial access?No. It is normally installed after code execution is already obtained.
Must the victim expose a port?No. Both endpoints can initiate outbound relay connections.
Does a relay expose the operator IP to the victim?Normally no. The victim connects to the relay, not directly to the operator.
Can the relay read the shell stream?The upstream design provides endpoint-to-endpoint encryption, so GSRN relays encrypted application traffic.
Does a YARA hit prove compromise?No. It proves tool identity or strong similarity. Behavior and authorization determine intent.

Inside the GSRN Rendezvous#

Traditional reverse shells embed or receive an operator-controlled host and port. GSocket replaces that direct addressing model with a shared-secret rendezvous. Each peer derives connection material locally, selects a relay host, and connects to GSRN. The listening endpoint registers an address and token. The connecting endpoint presents the corresponding address. When the relay finds both sides, it returns a start signal and becomes a byte forwarder between the encrypted endpoints.

The current upstream source defines separate LISTEN, CONNECT, PING, PONG, START, ACCEPT, and STATUS message types. LISTEN and CONNECT are assigned type values 0x01 and 0x02. The control structures contain a compact address derived from the shared secret and reserved space. After rendezvous, the peers negotiate the protected application channel using OpenSSL SRP and AES-256 according to the project documentation.

flowchart TD
    A["Initial access through another weakness"] --> B["Stage or rename gs-netcat"]
    B --> C["Store shared-secret material"]
    C --> D["Add cron, profile, rc.local, or systemd persistence"]
    D --> E["Launch with deceptive argv0 or volatile storage"]
    E --> F["Victim opens outbound connection to GSRN"]
    G["Operator derives the same rendezvous identity"] --> H["Operator opens a separate outbound connection"]
    F --> I{"Relay pairs matching identities"}
    H --> I
    I --> J["START response"]
    J --> K["End-to-end protected stream"]
    K --> L["Shell, forwarding, proxy, or file transfer"]
    E --> M["Host artifacts: procfs, secret file, persistence"]
    F --> N["Network artifacts: DNS, destination, timing, opening frame"]

The relay architecture hides a useful piece of attribution data. Socket telemetry on the victim normally reveals GSRN, not the operator’s real network address. Capturing only the victim’s flow records is therefore unlikely to identify who connected from the other side. The secret file becomes high-value evidence because possession of the same secret can grant another party access to the rendezvous.

Sansec demonstrated the consequence in April 2025. After detecting scans for defunct.dat and qfile, it checked 80,000 stores and found 45 with running GSocket backdoors. Those files contained connection keys. A key exposed through a web-accessible path could let a competing criminal reuse another operator’s backdoor. Treating the file as ordinary configuration understates its role. Operationally, it is closer to a bearer credential.

The protocol also creates a narrow network-detection opportunity. Application traffic is protected after the peers are paired, but the relay must process control information first. A sensor with reassembled TCP visibility may identify candidate LISTEN or CONNECT openings from the current message layout. This should never become a three-byte blocking rule. Segmentation, implementation changes, custom relays, Tor, and coincidental binary prefixes can all invalidate a simplistic signature.

Protocol propertyCurrent implementationDefensive meaning
Relay selectionLetter-prefixed host derived from the addressMatch the parent relay domain rather than one subdomain.
Default transportOutbound TCP, commonly port 443Port allowlisting does not establish that the flow is HTTPS.
Alternate transportGS_PORT or GSOCKET_PORT can change the portJPCERT/CC observed GSocket configured over TCP 53.
Rendezvous identityDerived locally from shared-secret materialThe human-readable secret is not sent to the relay as a password.
Application protectionEnd-to-end SRP/TLS design with AES-256Payload inspection loses value after session establishment.
Operator visibilityBoth endpoints communicate through a relayVictim flow logs usually do not reveal the operator endpoint.

How a Legitimate Client Becomes a Persistent Implant#

The most instructive deployments use ordinary Linux features rather than a kernel rootkit. A static or portable gs-netcat is copied into a writable path. The secret is placed next to it or disguised as another file type. A cron entry, shell profile, rc.local, or systemd unit restarts the process. The client attaches a shell to the encrypted relay and requires only outbound access.

Elastic’s REF6138 investigation recorded /dev/shm/.gs-1000, a hidden path under .config/htop, cron persistence, timestamp manipulation, and a process launched under a kernel-thread-like name using exec -a. Sansec later showed the pair .config/htop/defunct and .config/htop/defunct.dat, with the process presented as names such as [raid5wq], [kswapd0], or [slub_flushwq].

exec -a changes the first argument displayed to userspace tools. It does not turn an ELF executable into a kernel thread, change the inode referenced by /proc/<pid>/exe, erase the parent process, or remove its file descriptors. That gap between displayed identity and runtime truth is one of the strongest durable detection opportunities.

JPCERT/CC observed the same pattern under different names in December 2025. An hourly cron job started a Global Socket binary called npm-cli; npm-cli.dat held the secret; Bash was selected as the shell; and GS_PORT='53' moved the relay connection to a port normally associated with DNS. The traffic was not automatically DNS simply because its destination port was 53.

SANS ISC’s March 2026 sample added another anti-forensic layer. Its Bash deployer copied gs-netcat beneath .ssh/putty, stored the shared secret in a fake SSH-key file, added cron and .profile persistence, and tracked file timestamps so they could be restored after modification. SANS did not determine the script’s delivery method, so the sample proves deployment mechanics but does not justify a campaign attribution or initial-access claim.

Yandex Cloud’s June 2026 bulletin connected GSocket use with targeted Linux ransomware activity. It listed systemd and cron persistence, exec -a masquerading, execution from memfd, and default relay domains among useful artifacts. It also warned that the implant may remain dormant for years. A clean five-minute packet capture is not proof that the host is clean.

Hunt the Lie in /proc#

Linux exposes several independent descriptions of a process. /proc/<pid>/cmdline contains the arguments a process presents. /proc/<pid>/comm contains a short task name. /proc/<pid>/exe links to the executable mapping. /proc/<pid>/status exposes the parent and user IDs. /proc/<pid>/maps can reveal deleted or anonymous executable mappings. /proc/<pid>/fd provides socket and file references.

A kernel-thread-style argument enclosed in brackets is only one weak signal. It becomes stronger when the process has a normal executable path, a non-kernel parent, an executable marked deleted, environment variables associated with GSocket, or an outbound socket. The following collector inventories those facts without executing the discovered file or using any recovered secret.

#!/usr/bin/env python3
import argparse
import hashlib
import json
import os
import re
from pathlib import Path


BRACKETED = re.compile(r"^\[[^\]]+\]$")
GS_ENV_NAMES = {
    "GS_ARGS",
    "GS_HOST",
    "GS_PORT",
    "GSOCKET_ARGS",
    "GSOCKET_DOMAIN",
    "GSOCKET_HOST",
    "GSOCKET_PORT",
    "GSOCKET_SECRET",
}


def read_bytes(path: Path, limit: int = 1_048_576) -> bytes:
    try:
        with path.open("rb") as handle:
            return handle.read(limit)
    except (FileNotFoundError, PermissionError, ProcessLookupError, OSError):
        return b""


def read_text(path: Path, limit: int = 1_048_576) -> str:
    return read_bytes(path, limit).decode("utf-8", errors="replace")


def parse_nul(data: bytes) -> list[str]:
    return [part.decode("utf-8", errors="replace") for part in data.split(b"\0") if part]


def parse_status(text: str) -> dict[str, str]:
    result: dict[str, str] = {}
    for line in text.splitlines():
        key, separator, value = line.partition(":")
        if separator:
            result[key] = value.strip()
    return result


def safe_readlink(path: Path) -> str:
    try:
        return os.readlink(path)
    except (FileNotFoundError, PermissionError, ProcessLookupError, OSError):
        return ""


def sha256_path(path: Path) -> str:
    digest = hashlib.sha256()
    try:
        with path.open("rb") as handle:
            while chunk := handle.read(1_048_576):
                digest.update(chunk)
    except (FileNotFoundError, PermissionError, ProcessLookupError, OSError):
        return ""
    return digest.hexdigest()


def socket_inodes(fd_dir: Path) -> list[str]:
    inodes: list[str] = []
    try:
        entries = list(fd_dir.iterdir())
    except (FileNotFoundError, PermissionError, ProcessLookupError, OSError):
        return inodes
    for entry in entries:
        target = safe_readlink(entry)
        if target.startswith("socket:[") and target.endswith("]"):
            inodes.append(target[8:-1])
    return sorted(set(inodes))


def inspect_process(proc_dir: Path) -> dict | None:
    pid = proc_dir.name
    cmdline = parse_nul(read_bytes(proc_dir / "cmdline"))
    status = parse_status(read_text(proc_dir / "status"))
    environ = parse_nul(read_bytes(proc_dir / "environ"))
    exe = safe_readlink(proc_dir / "exe")
    comm = read_text(proc_dir / "comm", 4096).strip()
    maps = read_text(proc_dir / "maps")
    env_names = sorted(
        item.partition("=")[0]
        for item in environ
        if item.partition("=")[0] in GS_ENV_NAMES
    )
    bracketed_argv0 = bool(cmdline and BRACKETED.fullmatch(cmdline[0]))
    deleted_or_memfd = "(deleted)" in exe or exe.startswith("/memfd:") or "memfd:" in maps
    suspicious = bracketed_argv0 or deleted_or_memfd or bool(env_names)
    if not suspicious:
        return None
    executable_hash = ""
    if exe and not deleted_or_memfd:
        executable_hash = sha256_path(proc_dir / "exe")
    return {
        "pid": int(pid),
        "ppid": int(status.get("PPid", "0")),
        "uid": status.get("Uid", "").split("\t", 1)[0],
        "comm": comm,
        "argv": cmdline,
        "exe": exe,
        "sha256": executable_hash,
        "gs_environment_names": env_names,
        "kernel_style_argv0": bracketed_argv0,
        "deleted_or_memfd": deleted_or_memfd,
        "socket_inodes": socket_inodes(proc_dir / "fd"),
    }


def collect(proc_root: Path) -> list[dict]:
    findings: list[dict] = []
    for entry in sorted(proc_root.iterdir(), key=lambda path: path.name):
        if not entry.name.isdigit():
            continue
        finding = inspect_process(entry)
        if finding is not None:
            findings.append(finding)
    return findings


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--proc-root", type=Path, default=Path("/proc"))
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    payload = json.dumps(collect(args.proc_root), indent=2, sort_keys=True)
    if args.output:
        args.output.write_text(payload + "\n", encoding="utf-8")
    else:
        print(payload)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Run the collector with sufficient read permissions and send output to an evidence location on a trusted mounted volume:

sudo python3 proc_truth.py --output /mnt/evidence/proc-truth.json
python3 -m json.tool /mnt/evidence/proc-truth.json >/dev/null
sha256sum /mnt/evidence/proc-truth.json

The script reports environment variable names, never their values. That prevents a GSocket shared secret from being copied into routine logs. Hashing /proc/<pid>/exe is skipped for an obviously deleted or anonymous mapping because the acquisition workflow should copy that executable into controlled evidence storage first and record provenance.

The collector is a triage aid, not a verdict engine. Browsers, language runtimes, legitimate updaters, and security tools may execute deleted or anonymous mappings. Containers also create unfamiliar ancestry and namespaces. Escalation should depend on combined evidence rather than one boolean field.

Detect the Binary on Disk and in Memory#

Renaming gs-netcat to defunct, npm-cli, id_rsa, or a monitoring-like name does not remove compiled strings. Elastic maintains Multi_Hacktool_Gsocket_761d3a0f, a cross-platform YARA rule for file and memory scanning. It matches combinations of GSocket-specific strings such as GSOCKET_SECRET, GS_HIJACK_PORTS, GSOCKET_NO_GREETINGS, GS-NETCAT(1), and related tool names.

Use the maintained rule from Elastic’s repository instead of freezing a copied version in an article. This keeps metadata, architecture coverage, and tuning aligned with the publisher. An offline workflow can scan staged evidence as follows:

git clone --depth 1 https://github.com/elastic/protections-artifacts.git
yara -r \
  protections-artifacts/yara/rules/Multi_Hacktool_Gsocket.yar \
  /mnt/evidence/acquired-files

Memory scanning is especially important when /proc/<pid>/exe ends in (deleted), when the executable came from memfd, or when the original staging directory has been cleaned. A YARA hit should initially be classified as dual-use/hacktool. Raise confidence when the same host also has encoded cron content, an unauthorized relay connection, a fake secret file, deceptive argv[0], or unexplained shell access.

Hunt Persistence by Structure#

Filenames are fragile indicators. Attackers can replace defunct and npm-cli without changing how the implant survives. Detection should inspect cron, systemd units, shell profiles, and rc.local for structural combinations: GS_ARGS, GSOCKET_SECRET, exec -a, pkill -0, gs-netcat, a hidden executable path, or Base64 content decoded into a shell.

The following Sigma-style rule is deliberately behavioral. It is a starting point that must be translated to the organization’s Linux process schema and validated against approved administration:

title: Suspicious GSocket-Style Linux Persistence
id: 5704145e-e72c-4ae5-a856-ecdf386004e9
status: experimental
description: Detects shell execution patterns associated with persistent GSocket deployments
author: Egnworks
date: 2026-09-09
logsource:
  product: linux
  category: process_creation
detection:
  shell:
    Image|endswith:
      - /bash
      - /dash
      - /sh
      - /zsh
  gsocket_markers:
    CommandLine|contains:
      - 'GS_ARGS='
      - 'GSOCKET_SECRET='
      - 'gs-netcat'
      - 'GS_HIDDEN_NAME='
      - '.gs-'
  masquerade:
    CommandLine|contains|all:
      - 'exec -a'
      - '['
  encoded_shell:
    CommandLine|contains|all:
      - 'base64'
      - 'bash'
  condition: shell and (2 of gsocket_markers or masquerade or encoded_shell)
falsepositives:
  - Authorized GSocket administration or security testing
level: high
tags:
  - attack.persistence
  - attack.t1053.003
  - attack.defense-evasion
  - attack.t1036.011

File-integrity monitoring should include system and user crontabs, systemd unit directories, /etc/rc.local, .profile, .bashrc, .zshrc, .ssh, .config/htop, .config/dbus, /dev/shm, and /tmp. A web server writing an executable and a small adjacent secret file beneath its own home directory deserves immediate investigation even before a relay connection appears.

Network Detection After Encryption#

The default relay domain remains a strong policy signal. Alert on unauthorized queries and connections involving gs.thc.org and its subdomains, then preserve historical DNS because the implant may be inactive during triage. Do not hard-code only current IP addresses. Relay infrastructure can change, and GSocket supports alternate hosts, direct IP configuration, proxies, and Tor.

Process-aware egress is more durable than a port rule. Web servers, database daemons, application-service accounts, and hypervisor management processes rarely need long-lived external TCP sessions to an unapproved relay. Port 443 should be evaluated against protocol and process identity. Port 53 should be verified as DNS rather than trusted by number.

A candidate opening-frame detector can be developed from the current upstream structures, but it must be validated in an authorized lab before production. Capture both LISTEN and CONNECT paths against a controlled relay, enable stream reassembly, test deliberately segmented frames, compare several GSocket versions, and measure collisions against normal non-TLS traffic. Publish a Suricata signature only after the PCAP and regression set prove the offset and reserved-byte assumptions.

This restraint matters. The first type byte alone is not distinctive, and a short pattern such as 01 01 03 can occur in unrelated protocols. An untested rule creates false confidence and may block legitimate traffic. Domain, endpoint process, binary identity, persistence, and protocol evidence should contribute to one scored decision.

SignalSuggested score
Elastic GSocket YARA hit in file or memory40
Kernel-thread-style argv[0] with userspace executable35
Unauthorized default GSRN domain or relay connection30
Validated GSRN opening-frame match30
Persistence references hidden binary and secret file30
Executable mapping is deleted or backed by memfd25
GSocket environment variable name in process25
Timestamp inconsistency around persistence creation15
Known filename such as defunct or npm-cli10

A practical starting threshold is 50 for investigation and 80 for probable unauthorized GSocket access. These weights are an engineering proposal, not vendor-tested values. Tune them with local package inventory, sanctioned penetration-testing tools, container workloads, and known remote-administration paths.

Preserve Volatile Evidence Before Cleanup#

Do not begin with kill, deletion, or reboot. Isolate the host while preserving the ability to collect memory and runtime evidence. Record the process tree, namespaces, cgroups, /proc links, maps, environment variable names, file descriptors, socket ownership, DNS cache, and flow telemetry. If an executable is deleted or anonymous, acquire it from /proc/<pid>/exe before stopping the process.

Preserve suspicious .dat files and fake SSH keys with ownership, permissions, extended attributes, and filesystem timestamps. Never try a recovered secret against public GSRN. Doing so can contact a third party, alert an operator, contaminate evidence, or create unauthorized access.

After acquisition, export every system and user crontab, timer, systemd unit, rc.local, and shell profile. Decode suspicious Base64 only from a copied evidence file in an offline environment. Search fleet-wide for the same binary hash, YARA signature, secret-file hash, persistence fragment, process name, relay domain, service account, and deployment timestamp.

Then return to initial access. Review web access and error logs, application audit records, authentication history, VPN sessions, container events, cloud control-plane activity, vulnerable packages, and recently created credentials. GSocket is evidence that access was maintained, not an explanation of the first compromise.

Assume the attached shell could read everything available to its account. Rotate application secrets, SSH keys, database credentials, cloud tokens, backup credentials, and deployment keys according to exposure. Root-level persistence, timestomping, unknown initial access, or several unrelated malware families are strong reasons to rebuild from a known-good image instead of trusting file-by-file cleanup.

Detection Must Outlive the IOC#

GSocket is attractive because it solves difficult operator problems with legitimate code. It traverses NAT through outbound connections, avoids an inbound listener, hides the operator behind a relay, protects the application stream, and supports shells and forwarding across multiple platforms. Attackers do not need to invent a new C2 protocol to obtain those properties.

The same reuse gives defenders stable seams. A renamed process still maps an executable. A fake kernel thread still has userspace ancestry and sockets. A dormant implant still needs persistence and secret material. A custom relay removes a domain indicator but not process provenance. Encrypted content hides commands, not the existence and ownership of the process that carries them.

The durable Blue Team lesson is to compare independent truths. Command lines can lie, filenames can lie, timestamps can be restored, and a quiet packet capture can be incomplete. Procfs, executable identity, memory strings, persistence content, filesystem metadata, DNS history, and process-aware flow records are much harder to falsify together.

References#

  1. The Hacker’s Choice. Global Socket repository. Accessed September 9, 2026.
  2. The Hacker’s Choice. gsocket.h protocol definitions. Accessed September 9, 2026.
  3. The Hacker’s Choice. gsocket-engine.c and deploy.sh. Accessed September 9, 2026.
  4. Elastic Security Labs. Betting on Bots: Investigating Linux malware, crypto mining, and gambling API abuse. September 27, 2024.
  5. Sansec Forensics Team. Persistent backdoors injected on Adobe Commerce via new CosmicSting attack. August 27, 2024.
  6. Sansec Forensics Team. Found defunct.dat on your site? You’ve got a problem. April 3, 2025.
  7. JPCERT/CC. Multiple Threat Actors Rapidly Exploit React2Shell: A Case Study of Active Compromise. February 13, 2026.
  8. Xavier Mertens, SANS Internet Storm Center. GSocket Backdoor Delivered Through Bash Script. March 20, 2026.
  9. Yandex Cloud. Targeted data encryption attacks in the Linux infrastructure of Russian organizations. June 23, 2026.
  10. Elastic Security. Multi_Hacktool_Gsocket_761d3a0f YARA rule. Accessed September 9, 2026.