BelphC2 - Jitter Logic and Chaos Theory#

Disclaimer#

Is this necessary? Probably not… Do I find this cool and do I want to blab about it? Yes!

Introduction to Jittering#

While developing BelphC2’s polling behavior, I had to decide how to generate seemingly random jittering values.

Most post exploitation frameworks implement some form of a jitter.

The term “jitter” refers to how a post exploitation framework’s beacon/implant manages its communication or ‘polling’ intervals. “Jittering” implies slight deviations or “jitter” added to polling intervals in order to introduce variability with respect to how often the beacon will phone home.

Instead of sleeping for a fixed and regular interval like so:

60s
60s
60s
60s

There is a slight variation introduced into the timing:

53s
67s
61s
48s
72s

The sole purpose of jitter is to obscure any predictable patterns of communication that can be leveraged for detection purposes.

But randomness generated from traditional pseudo random number generators still tends to exhibit recognizable statistical properties over long periods of observation… How can we introduce timing variability that evolves continuously and resists settling into periodic behavior?

Sounds like a perfect opportunity to utilize concepts found in one of my favorite branches of mathematics: Chaos Theory.

Enter Chaos Theory#

Chaos theory studies systems that have the following characteristics:

  • Sensitivity to initial conditions: Small and seemingly insignificant changes in the system’s starting state can produce drastic differences in the final outcome.
  • Nonlinear Dynamics: The systems do not behave in a linear fashion, meaning a small cause cannot be assumed to create a small effect.
  • Deterministic behavior: Chaotic systems are not random, they do follow precise deterministic rules.
  • Attractors: A behavior that the system naturally tends to evolve towards over time. Attractors describe the long-term behavior of the system.

The most classic example being the Lorenz System, originally developed by Meteorologist Edward Lorenz while modeling atmospheric convection.

$$ \frac{dx}{dt} = \sigma(y-x) $$ $$ \frac{dy}{dt} = x(\rho-z)-y $$ $$ \frac{dz}{dt} = xy-\beta z $$

Where the following constants control the behavior of the system:

  • σ (sigma)
  • ρ (rho)
  • β (beta)

and x, y, and z are the starting state values subject to the control of the above constants.

When observing this system of equations over time, the famous image resembling a butterfly emerges. As you can can see, the attractor moves throughout phase space clearly attracted and bound to two points, yet never following the same path twice.

https://en.wikipedia.org/wiki/Lorenz_system for further reading, as this is a fascinating model

Keep in mind, the behavior is not “random” - only highly unpredictable without precisely knowing the initial conditions. Causality is intact and at play here, but it’s impossible to know the precise initial “cause” in most chaotic systems:

You could in theory predict the weather… If you knew the exact location and velocity of every molecule in the atmosphere. Displace a couple atoms, you may get a hurricane instead of clear skies.

This model can be used to ensure persistent unpredictable behavior to an outside observer.

The Jitter Engine#

Instead of using a pseudo random numbers to determine timing variability, this implementation continuously evolves a Lorenz system:

type LorenzJitter struct {
	x, y, z float64
}

func NewLorenzJitter() *LorenzJitter {
	return &LorenzJitter{
		x: 0.1,
		y: 0.0,
		z: 0.0,
	}
}

Each step numerically integrates the system using Euler integration:

func (lj *LorenzJitter) Step() {
	dx := sigma * (lj.y - lj.x)
	dy := lj.x*(rho-lj.z) - lj.y
	dz := lj.x*lj.y - beta*lj.z

	lj.x += dx * dt
	lj.y += dy * dt
	lj.z += dz * dt
}

Euler integration is used here so we can give the computer a fixed concept of time.

This is effectively saying:

new position = old position + (current slope × tiny timestep)

Describing the whole system in terms of Euler integration: $$ \Delta x = \sigma(y_n - x_n)\Delta t $$ $$ \Delta y = \left(x_n(\rho - z_n) - y_n\right)\Delta t $$ $$ \Delta z = \left(x_n y_n - \beta z_n\right)\Delta t $$

And

$$ x_{n+1} = x_n + \Delta x $$ $$ y_{n+1} = y_n + \Delta y $$ $$ z_{n+1} = z_n + \Delta z $$ Thousands of these tiny iterative updates cause the system to wander chaotically through phase space.

Consider the following:

  • Calculating Lorenz state transitions takes CPU time.
    • The more times we execute lj.Step(), the longer the delay becomes.
  • Each iteration slightly advances the chaotic system:
    • Updating the current:
      • $x$
      • $y$
      • $z$ state values.
  • The current chaotic state is then fed back into the timing engine:
    • The attractor determines how much work should happen during the next polling cycle.
  • More iterations:
    • Advance the attractor further.
    • Produce a completely different future chaotic state.
  • This creates a continuous feedback loop:
    • Current chaotic state → determines workload → workload advances the attractor → new attractor state influences future timing.

So lets make it happen!

The Main Loop#

The beacon loop itself looks like this:

func main() {
	j := butterfly.NewLorenzJitter()
	for {
		weitergehen, cmd := poll()
		
		if cmd != "" {
			Exec(cmd)
		}

		start := time.Now()         // For debug output
		j.JitterSeconds(10000)      // chaotic CPU "delay"
		elapsed := time.Since(start) // For debug output
		seconds := elapsed.Seconds() // For debug output
	
		fmt.Printf("Chaotic time: %.3f s\n", seconds) // Debug output
	
		if weitergehen == false {
			return
		}
	}
}
j.JitterSecondsRough(10000)

Instead of sleeping passively with:

time.Sleep()

the implant performs computational work driven by the Lorenz attractor. That work duration continuously changes, and is based on the current state of the attractor - j.x, j.y, j.z.


Chaotic Timing Variability#

This function JitterSeconds converts the current state of the Lorenz system into a dynamically changing amount of computational work.

First, a variability factor is calculated using the current chaotic state variables $x$ and $z$:

  • Current $z$ and $x$ locations are scaled.
  • Values are combined into the variation variable.
variation := 0.4 * (math.Abs(lj.x)/30 + math.Abs(lj.z)/40)

The magnitudes of $x$ and $z$ determine how strongly the timing should drift. As the attractor moves through phase space, these values continuously change, causing the amount of variability to evolve over time. The result is then capped at a max variability factor of 80% to prevent extreme or runaway timing deviations:

if variation > 0.8 {
	variation = 0.8
}

Running the experiment#

The actual timing perturbation happens here:

steps := baseSteps + int(
    float64(baseSteps) *
    variation *
    (2*math.Mod(lj.y, 2)-1),
)

Basically, this line takes a predefined normal amount of work (baseSteps) and continuously nudges it up or down using the current state of the Lorenz attractor.

final work =
normal work +
chaotic adjustment

The adjustment itself is computed from three parts:

$$ \text{baseSteps} \times \text{variation} \times \text{directionalFactor} $$ Where:

  • baseSteps

    • The normal amount of work we intended to perform.
    • Example: 220000 Lorenz iterations.
  • variation

    • Controls how strong the jitter effect is allowed to become.
    • Larger variation means larger timing drift.
  • (2*math.Mod(lj.y, 2)-1)

    • Converts the current chaotic $y$ state into a fluctuating positive or negative multiplier.
    • This determines whether the workload increases or decreases.
for i := 0; i < steps; i++ {
	lj.Step()
}

Every iteration slightly changes:

  • x
  • y
  • z

The system advances through the Lorenz equations repeatedly. Since each step changes the internal chaotic state and phase space position, future timing behavior continuously evolves as well!

Result#

Our debug output shows that we get seemingly random time intervals between polling requests!

Although the usefulness of this feature is questionable, this was a fun programming exercise and one of my favorite easter eggs within this project!

Happy hacking.