augure and lictor: deciding an exploitation and accounting for it
Every exploitation framework answers the same question, “how do I launch this technique”. The question that really matters is the other one, the one nobody automates, “which one to launch and why” and above all “why in a provable way”. I have circled it since a Perl prototype grafted onto gdb in 2006, which I enriched rather than replaced over the years, first with SWI-Prolog in 2009, then a genetic algorithm and the journey deserves its own telling. Today it goes by two names, augure and lictor and everything is in the split, one decides and accounts for it, the other goes from the decision down to the bytes and executes.
augure, the layer that decides and proves it
Ask a LLM and it will recommend a ret2libc against a target with no libc, with full confidence, because it generates text, not proofs.
augure generates proofs, not text, bearing in mind that those proofs are about a technique’s applicability and numerical feasibility, not about the final success of the exploitation, which only shows at execution and it does so in three stages you can read back one by one.
It proposes first.
Datalog rules over the target’s fact base select every applicable technique, decidably, terminating and readable and a rule can structurally forbid the absurd, so a ret2libc with no libc is not “discouraged”, it is impossible to propose.
It verifies next.
A fast arithmetic path, then an SMT-LIB emission to an external solver of your choice (z3, bitwuzla or cvc5), confirm that the chosen technique is numerically feasible and not merely logically applicable and a timeout returns unknown, never a crash.
It explains last. Every decision comes out with its provenance, which rule fired, which facts justified it, which prior and which seeded draw ranked it and the same input with the same seed always returns the same decision.
The ranking is no flat weight.
A Thompson bandit adapts the priors target by target, but it treats choices as independent and that is precisely its blind spot, since a leak is worth nothing in itself, it is worth what it unlocks next.
Above the bandit, a MCTS therefore plans multi-step trajectories over the techniques’ transition graph and it is the one that prefers to pull a fmtstr_leak first because the table says what it opens, where a flat weight would dumbly rank ret2libc on top.
On this ground augure does not settle for a toy corpus, it reproduces a kernel chain documented from the outside, the seven steps of PinTheft and the exercise is not to invent it but to check that the planner recovers its order.
The core of the idea I have carried a long time.
When an exploit dev rules, “this is a ret2plt case”, that choice is a heuristic and a heuristic is a local optimum, fast, often right, never tested systematically.
augure is the test bench for those local optima, you run your intuition against a frozen CTF corpus and look at the weight inversions, humans overrate shellcode and ret2libc, the data underrates them and ret2plt, the boring, reliable one, was massively undervalued.
Nothing is hardwired, not even the techniques. A new technique is taught as data, a YAML pack carrying its rules, its knowledge-base entries, its MCTS transition and its priors, with its author and source as provenance and a corpus guard rejects any pack that would reorder an existing technique or steal the top-1 of an already documented target, you extend the knowledge, you do not quietly rewrite history.
On the numbers I lay my cards on the table, which is the whole point of an auditable tool. augure reproduces the frozen reference corpus to the line, thirty-three classifications out of thirty-three and five multi-step plans out of five, kernel chain included and CI breaks the build below one hundred percent, the same corpus serving as a conformance test between the Ruby engine and a Datalog engine compiled to Soufflé that must agree to the line. But read the next sentence as carefully as the previous one, these are predictions against a frozen ground truth, not field results and the priors’ success probabilities are written, not measured. It is not autonomy I am selling, it is accountability and the audit starts with that very sentence.
From the decision to the bytes, never guessing a number
The old prototype ended on a payload, the padding, the return address, the shellcode.
lictor build inherits that, with one difference that changes everything, every number is now measured or sourced, never assumed.
The offset is measured.
The fast path is static, metasm decodes the ELF, resolves the PLT stubs of dangerous imports, disassembles .text from each function symbol, spots the call to the vulnerable sink and reads the buffer’s displacement relative to rbp to derive the offset, then validates against ROP Emporium’s canonical numbers.
When the static read finds no controlled return, the dynamic path takes over, a cyclic pattern crashes the binary under the debugger and the offset is read back from the pattern, with no brute force and no magic constant and an offset not found is a loud BuildError, never an invented number.
The return address comes from the facts with their provenance, the winning symbol or the classic jmp rsp named after the target’s stack register.
The gadget is not taken on trust, the hunt walks every offset of the executable sections, decodes a chain ending on ret and classifies it semantically, including the multi-instruction gadgets that byte-patterns never see, re-decodes the bytes at the announced address and a lying gadget is refused.
The shellcode, finally, is assembly source, not a hex blob nor an external generator, readable versioned stubs, position-independent by construction and the suite’s real-execution test runs our own bytes.
Obfuscation is a layer above, a keyed self-decoder whose get-pc avoids 0x00 by construction, xor and polymorphic flavors, seed-drawn keys, the shikata lineage but auditable, the same seed reproducing the exact bytes and a payload too large for a null-free channel is refused honestly rather than silently truncated.
I hold to the lineage, all published, metasm (Guillot, SSTIC 2007), semi-automatic binary deprotection (Guillot and Gazet, SSTIC 2008), automatic exploitation with metasm (esec-lab, 2010), miasm (Desclaux, SSTIC 2012) and ghost writing (2015) that our stubs echo in assembly source. The 2010 automatic exploitation loop, I do not claim to invent, I re-derive it, auditable this time.
Observe, not just model
The profiler does the bulk of the static work in pure Ruby with no subprocess, checksec, the gadgets, the crash identification by a control pattern found in the registers rather than by a 0x41414141 shape.
The dynamic side completes it and that is where the tool leaves the model to observe the real thing. A watchpoint on the return slot catches the instruction that triggers the overflow, taint is proven not inferred, a live execution reads back the libc and the stack and on the network side a harvest connects to the service, reads the banner it spits out and extracts addresses seen live, provenance “observed-live”, not modeled.
Between the two, fuzzing is the discovery layer for binaries with no write-up, a length sweep and mutations with a built-in dependency-free mutator, radamsa optional, gdb, rizin or lldb backends to observe the crash that comes back down as facts and no emulation in there, the real-execution tests are the truth.
lictor, execution that accounts for itself
augure decides, lictor executes and the name is no accident, the Roman lictor carried the fasces and applied the judgments, he did not judge. Most offensive automation is a black box that fires and whose reason you discover after the fact, or never. lictor flips that, every technique choice comes from a verified rule with its proofs and every action leaves an attributable, replayable JSONL line.
The chain runs from scan to report and two links deserve a pause.
The mirror first, because a remote target cannot always be profiled, so you stand the same service up locally, lictor profiles the mirror, builds on its facts and a differential verification proves the remote faults like the local model before anything goes out for real.
Automatic exploitation next, but in the exact sense, when discovery finds no exploit for the decided technique lictor writes one, a class carrying the measured constants and the loader for the built payload and the file lands in staging where the operator reviews it before committing.
The run loop closes the cycle, decide, execute, record the result, re-decide, with an attempt budget and the results feed the priors back in, the automation living there, in the chaining and the re-seeding, not in a genius that would write the exploit for you.
The real selling point is the trace
The two together are “generate-then-verify” made flesh, a LLM proposes hypotheses, augure disposes in the strict sense, lictor executes under the operator’s sole responsibility and it is the exact opposite of the theater I spend my time denouncing.
What you walk away with is not a tool that strikes faster, it is a tool you can answer for, move by move.
The handoff is the default, plan prints the recipe and executes nothing, the loop that really executes is opt-in and budgeted, the trace is append-only, decision then rule provenance then action then result, because an engagement you cannot replay is an engagement you cannot defend.
And when a LLM agent drives through MCP, the rigor does not move, the exposed tools validate server-side, the armor and the corpus guard run where the agent cannot weaken them, the agent proposes, augure disposes, lictor executes, the brakes are not a mode but the design.
On implementation, everything is native Ruby, not out of nostalgia but because the ecosystem finally allows it, ronin and its exploits as classes, the rex gems that natively carry msf’s internal primitives with gem install and no daemon, where Metasploit stays a monolith driven by a dead RPC, the profiler on metasm in pure Ruby and augure with no runtime dependency, each external capability entering through a subprocess or HTTP boundary that keeps the native-extension attack surface at zero.
A word on ethics, because it carries weight. augure is a decision layer, it contains no exploitation code, executes nothing and touches no target, it encodes technique metadata, the same class of knowledge as a public write-up. lictor is deliberately not shipped as a gem within anyone’s reach, it invents no exploitation technique, it assembles an artifact from measured constants and existing building blocks, dropped in staging for review and orchestrates the tooling the operator chooses, the knowledge living in the repositories and not here. The tool exists for authorized security work, signed scope, labs you own, CTF environments and you are responsible for the legality where you stand.
The omen and the axe
Some twenty years enriching the same reasoning, from the if/else grafted onto gdb to Prolog, to the genetic algorithm, the bandit, the planner, have landed on two tools whose aim is not to strike faster but to be able to answer for every blow. augure separates the decision from the execution, makes it provable and reproducible, names what is measured and what is only written and goes down to the bytes without ever guessing a number. lictor executes what was decided, under the operator’s sole responsibility and records everything in a replayable trace. The augur reads the signs and accounts for them, the lictor carries the fasces and executes the judgment, neither claims to decide for you and that is precisely why I use them.

