Skip to content
All systems nominalMITRE ATT&CK · PTES · TIBER-EU · TLPT · DORA
All field notes
Tradecraft

Writing a loader that survives one specific EDR

Evasion is not a product. But emulation means living in memory — a teardown of the techniques, and exactly where each one breaks.

Pablo Ruiz
· 6 min read
  • #malware-dev
  • #evasion

Evasion is not a capability you buy or a technique you learn once. It is a perishable good with an unadvertised expiry date, and the entire discipline consists of knowing roughly when yours went off. Adversary emulation requires operating in memory against a defended endpoint, which means understanding loaders. It does not require pretending that any of it lasts.

This note is about where the techniques break. That is the more useful half for both sides of the exercise: an operator who knows where a method fails stops relying on it before it embarrasses them, and a defender who knows the same thing knows what to instrument.

Everything here is a trade

A loader has one job — get code running in memory without the artifact ever being interesting to the thing watching. Every technique that advances that goal pays for it somewhere else, and the payment is always the same currency: a behaviour that is rarer than the behaviour it replaced.

That is the shape of the whole field. You are not becoming invisible. You are choosing which of your observable properties is least likely to be the one under observation. Rarity is the risk, and the techniques that sound most impressive are usually the rarest.

Static properties: solved, and not where the fight is

Signature avoidance is the part everyone learns first and the part that matters least. A file's bytes are trivially changed, so a defence keyed on a file's bytes is trivially defeated, and everyone involved has known this for fifteen years. Modern detection barely looks there.

What replaced it is not "better signatures" but a shift in where the evaluation happens. Content is inspected at the point it becomes meaningful rather than at the point it arrives on disk, which is what interfaces like AMSI exist to do. A script that is unreadable on disk becomes readable at the moment the runtime has to interpret it, and that is where the check sits.

The consequence is that obfuscation buys you nothing against a modern endpoint on its own, while costing you the entropy profile of a normal file. A packed binary with high-entropy sections and a nearly empty import table is not evading anything — it is announcing that it is worth examining. The technique that was supposed to hide you has made you the least ordinary file in the directory.

Behavioural detection is the actual opponent

Once content inspection is not the limiting factor, the fight moves to what the process does, and this is where each technique's failure mode lives.

Process injection. Writing into another process and executing there is the oldest move in the category, and its problem is that the sequence has no benign explanation at scale. The specific API set has been the subject of dedicated instrumentation for a decade. Every variant developed since — writing to a section object instead of allocating, hijacking a thread instead of creating one, queueing work to an existing thread — moves the same behaviour to a slightly less-watched interface. That works exactly until the interface is watched, and the trend is one-directional. Failure mode: the cross-process operation itself is the signal, and the signal does not depend on which function performed it.

Unbacked executable memory. Code that runs from a region with no file behind it is anomalous in a way that is cheap to scan for. A defender can enumerate every executable region in every process and ask which ones correspond to a mapped image on disk. The answer is short and the false positives are a known, enumerable set — some runtimes legitimately generate code, and that list can be baselined once. Failure mode: a periodic memory scan finds you without needing to have watched you arrive.

Direct system calls. Bypassing a monitored user-mode function by invoking the kernel interface directly removes one observation point and creates a different one. A process issuing system calls from a region that is not the system library is itself unusual, and the kernel-side telemetry that a modern EDR receives does not care which user-mode path preceded it. Failure mode: you evaded the hook and kept the event.

Living off the land. Using signed, expected system utilities to do the work avoids introducing any foreign code at all, which is genuinely the most durable approach here. Its limit is arithmetic: the set of such utilities is finite, well catalogued publicly, and the unusual invocations of each are increasingly baselined. Failure mode: the technique is durable, but the specific binary you chose is on a list, and so is the argument pattern.

Every evasion technique works right up until the moment enough people use it. Publication is the countdown, and you are rarely the one who started the clock.

What actually keeps working

Reliability in this space does not come from novelty. It comes from resembling the environment.

A process that runs from a plausible location, is signed, spawns the children its real counterpart spawns, contacts hosts the organisation already contacts, and does its work at a rate that fits the surrounding traffic is difficult to isolate — not because any single technique defeated a control, but because no individual property of it is rare. That is a research problem about the target environment, not a coding problem, and it is where the time should go.

The corollary is the useful one for defenders: the constraint on an intruder is knowledge of what is normal in your estate, and everything you do that makes normal harder to characterise from the inside is worth more than another product. Consistent build paths, a short and enforced list of signed software, egress that goes through a proxy that logs, and workstations that resemble each other are all boring and all raise the cost of blending in.

The defender's version of this note

If you take one thing from the section above, take the ordering. These are roughly in descending value per unit of effort:

  • Scan process memory for executable regions with no backing file, on a schedule. It catches a broad class of techniques at once and does not require having observed the arrival.
  • Alert on cross-process memory operations by lineage, not by API. The specific function will change; the parent-child relationship is more stable.
  • Baseline which processes on your estate legitimately generate executable memory. The list is shorter than expected and everything outside it is a question worth asking.
  • Watch for system calls originating outside the system libraries. It is a narrow signal with very little noise.
  • Constrain and log egress. A loader that cannot reach its infrastructure is an inert file.

Why this is written down at all

The techniques above are documented publicly, taught in commercial courses, and implemented in open tooling that any defender can run against their own estate this afternoon. Nothing here shortens anyone's path to anything.

What is worth stating plainly is the economics. An operator's evasion research decays continuously and requires constant reinvestment to stay current. A defender's structural work — knowing the estate, constraining what is normal, instrumenting behaviour rather than content — appreciates. The side that has to keep running to stay in place is not the one people assume.

// was this note useful?

Pablo Ruiz

Offensive security since 2018 — Madrid, Oslo, Amsterdam. Runs the intrusion, then writes the report you can act on.

Back to the Labs