augure 0.1.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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +136 -0
  3. data/LICENSE +21 -0
  4. data/README.md +217 -0
  5. data/exe/augure +181 -0
  6. data/exe/augure-benchmark +67 -0
  7. data/home/runner/work/augure/augure/docs/agent-snippet.md +31 -0
  8. data/home/runner/work/augure/augure/docs/auditability.md +160 -0
  9. data/home/runner/work/augure/augure/docs/build-format.md +119 -0
  10. data/home/runner/work/augure/augure/docs/explanation-architecture.md +74 -0
  11. data/home/runner/work/augure/augure/docs/how-to-swap-solver.md +73 -0
  12. data/home/runner/work/augure/augure/docs/how-to-write-rules.md +71 -0
  13. data/home/runner/work/augure/augure/docs/pack-format.md +137 -0
  14. data/home/runner/work/augure/augure/docs/reference.md +293 -0
  15. data/home/runner/work/augure/augure/docs/tutorial.md +108 -0
  16. data/home/runner/work/augure/augure/examples/ret2win.facts +11 -0
  17. data/home/runner/work/augure/augure/examples/split.facts +12 -0
  18. data/home/runner/work/augure/augure/exe/augure +181 -0
  19. data/home/runner/work/augure/augure/exe/augure-benchmark +67 -0
  20. data/home/runner/work/augure/augure/lib/augure/bandit.rb +118 -0
  21. data/home/runner/work/augure/augure/lib/augure/coverage.rb +56 -0
  22. data/home/runner/work/augure/augure/lib/augure/ctf_corpus.json +773 -0
  23. data/home/runner/work/augure/augure/lib/augure/engine.rb +128 -0
  24. data/home/runner/work/augure/augure/lib/augure/facts.rb +194 -0
  25. data/home/runner/work/augure/augure/lib/augure/kb_corpus.json +345 -0
  26. data/home/runner/work/augure/augure/lib/augure/knowledge_base.rb +109 -0
  27. data/home/runner/work/augure/augure/lib/augure/mcts.rb +178 -0
  28. data/home/runner/work/augure/augure/lib/augure/pack.rb +244 -0
  29. data/home/runner/work/augure/augure/lib/augure/pack_loader.rb +93 -0
  30. data/home/runner/work/augure/augure/lib/augure/parity.rb +69 -0
  31. data/home/runner/work/augure/augure/lib/augure/pipeline.rb +107 -0
  32. data/home/runner/work/augure/augure/lib/augure/priors.rb +32 -0
  33. data/home/runner/work/augure/augure/lib/augure/rules.rb +300 -0
  34. data/home/runner/work/augure/augure/lib/augure/smt_process.rb +48 -0
  35. data/home/runner/work/augure/augure/lib/augure/verifier.rb +40 -0
  36. data/home/runner/work/augure/augure/lib/augure/version.rb +5 -0
  37. data/home/runner/work/augure/augure/lib/augure.rb +25 -0
  38. data/home/runner/work/augure/augure/packs/banner_leak.yml +46 -0
  39. data/home/runner/work/augure/augure/packs/brop.yml +46 -0
  40. data/home/runner/work/augure/augure/packs/fastbin_hook.yml +45 -0
  41. data/home/runner/work/augure/augure/packs/got_partial_overwrite.yml +45 -0
  42. data/home/runner/work/augure/augure/packs/got_partial_overwrite_deref.yml +39 -0
  43. data/home/runner/work/augure/augure/packs/house_botcake.yml +38 -0
  44. data/home/runner/work/augure/augure/packs/house_force.yml +37 -0
  45. data/home/runner/work/augure/augure/packs/house_of_apple2.yml +45 -0
  46. data/home/runner/work/augure/augure/packs/house_orange.yml +38 -0
  47. data/home/runner/work/augure/augure/packs/jop.yml +39 -0
  48. data/home/runner/work/augure/augure/packs/large_bin_attack.yml +39 -0
  49. data/home/runner/work/augure/augure/packs/one_gadget.yml +38 -0
  50. data/home/runner/work/augure/augure/packs/orw.yml +55 -0
  51. data/home/runner/work/augure/augure/packs/ret2csu_v2.yml +55 -0
  52. data/home/runner/work/augure/augure/packs/ret2dlresolve_x86.yml +39 -0
  53. data/home/runner/work/augure/augure/packs/ret2partial_overwrite.yml +38 -0
  54. data/home/runner/work/augure/augure/packs/ret2vdso.yml +39 -0
  55. data/home/runner/work/augure/augure/packs/setcontext_srop.yml +46 -0
  56. data/home/runner/work/augure/augure/packs/uaf_tcache.yml +45 -0
  57. data/home/runner/work/augure/augure/packs/unsorted_bin_attack.yml +38 -0
  58. metadata +103 -0
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Augure
4
+ # Thompson-sampling bandit over Beta posteriors. The prior table encodes the
5
+ # AG weight inversions ("humans overweight X, the data says underweight"):
6
+ # ret2plt (15, 5) vs the human prior that anchored on ROP chains.
7
+ class Bandit
8
+ # Beta(alpha, beta) arm. Sampling uses Marsaglia-Tsang gamma draws: if
9
+ # X ~ Gamma(a) and Y ~ Gamma(b) then X / (X + Y) ~ Beta(a, b). Ruby's
10
+ # stdlib has no Beta sampler; this is the standard construction.
11
+ class Arm
12
+ attr_reader :alpha, :beta
13
+
14
+ def initialize(alpha, beta, rng: Random.new)
15
+ @alpha = alpha.to_f
16
+ @beta = beta.to_f
17
+ @rng = rng
18
+ end
19
+
20
+ def mean
21
+ @alpha / (@alpha + @beta)
22
+ end
23
+
24
+ def sample
25
+ x = gamma(@alpha)
26
+ y = gamma(@beta)
27
+ x / (x + y)
28
+ end
29
+
30
+ def record(success)
31
+ success ? @alpha += 1 : @beta += 1
32
+ end
33
+
34
+ private
35
+
36
+ # Marsaglia-Tsang, shape >= 1 handled directly; shape < 1 via the
37
+ # boost transform.
38
+ def gamma(shape)
39
+ if shape < 1.0
40
+ u = non_zero
41
+ gamma(shape + 1.0) * u**(1.0 / shape)
42
+ else
43
+ d = shape - 1.0 / 3.0
44
+ c = 1.0 / Math.sqrt(9.0 * d)
45
+ loop do
46
+ x = gauss
47
+ v = (1.0 + c * x)**3
48
+ next if v <= 0
49
+
50
+ u = non_zero
51
+ return d * v if Math.log(u) < 0.5 * x * x + d * (1.0 - v + Math.log(v))
52
+ end
53
+ end
54
+ end
55
+
56
+ def non_zero
57
+ u = @rng.rand until u && u > 0.0
58
+ u
59
+ end
60
+
61
+ # Box-Muller transform.
62
+ def gauss
63
+ loop do
64
+ u1 = non_zero
65
+ u2 = @rng.rand
66
+ r = Math.sqrt(-2.0 * Math.log(u1))
67
+ theta = 2.0 * Math::PI * u2
68
+ return r * Math.sin(theta)
69
+ end
70
+ end
71
+ end
72
+
73
+ attr_reader :arms, :rng, :history
74
+
75
+ def initialize(priors: {}, rng: Random.new)
76
+ @rng = rng
77
+ @arms = {}
78
+ priors.each { |tech, (a, b)| @arms[tech] = Arm.new(a, b, rng: @rng) }
79
+ @history = []
80
+ end
81
+
82
+ def arm(technique)
83
+ @arms[technique] ||= Arm.new(1, 1, rng: @rng)
84
+ end
85
+
86
+ def mean(technique)
87
+ arm(technique).mean
88
+ end
89
+
90
+ # Thompson sample, restricted to the verified techniques. Deterministic
91
+ # for a fixed rng seed.
92
+ def select(verified)
93
+ return nil if verified.empty?
94
+
95
+ verified.max_by { |t| arm(t).sample }
96
+ end
97
+
98
+ def rankings
99
+ @arms.keys.sort_by { |t| [-arm(t).mean, t] }
100
+ end
101
+
102
+ # Prior-mean ranking restricted to the given techniques, stable order.
103
+ # This is the deterministic contract the corpus freezes; Thompson draws
104
+ # (select) are the online behavior on top of it.
105
+ def ranking_of(techniques)
106
+ techniques.each_with_index
107
+ .map { |t, i| [t, arm(t).mean, i] }
108
+ .sort_by { |(_, m, i)| [-m, i] }
109
+ .map { |(t, m, _)| [t, m] }
110
+ end
111
+
112
+ def feedback(technique, success)
113
+ arm(technique).record(success)
114
+ @history << {technique: technique, success: success}
115
+ self
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Augure
4
+ # The corpus read backwards (the defender side): re-decide every
5
+ # entry against a HARDENED fact set and report which techniques die,
6
+ # which survive. The auditability doc's "the model read backwards"
7
+ # as a command: flip nx/relro/canary/pie, watch the rules die, and
8
+ # the remaining applicable techniques are the attacker's next moves.
9
+ module Coverage
10
+ module_function
11
+
12
+ # entries: corpus-shaped ({suite:, name:, facts:}). flips: {predicate
13
+ # => value-or-nil} - a value REPLACES every existing fact of that
14
+ # predicate (hardening is an override); nil REMOVES them (the
15
+ # speculative turn-off).
16
+ def report(entries, flips:, priors: nil)
17
+ priors ||= Pipeline.default_priors
18
+ out = {entries: [], died: {}, gained: {}, survived_count: 0}
19
+ Array(entries).each do |entry|
20
+ facts = Facts.parse(entry["facts"])
21
+ before = Pipeline.analyze(facts: facts, priors: priors, plan: false)[:applicable]
22
+ hardened = with_flips(facts, flips)
23
+ after = Pipeline.analyze(facts: hardened, priors: priors, plan: false)[:applicable]
24
+ lost = before - after
25
+ found = after - before
26
+ lost.each { |t| out[:died][t] = out[:died].fetch(t, 0) + 1 }
27
+ found.each { |t| out[:gained][t] = out[:gained].fetch(t, 0) + 1 }
28
+ out[:survived_count] += 1 if before == after
29
+ out[:entries] << {suite: entry["suite"], name: entry["name"], before: before,
30
+ after: after, lost: lost, gained: found,
31
+ hardened_facts: hardened.to_s}
32
+ end
33
+ out
34
+ end
35
+
36
+ # Replace-or-remove every fact of a flipped predicate. The flip is
37
+ # the HARDENING decision: one value per predicate, decided by the
38
+ # operator, not appended noise.
39
+ def with_flips(facts, flips)
40
+ return facts if flips.nil? || flips.empty?
41
+
42
+ dropped = Facts.new
43
+ kept = Facts.new
44
+ facts.each do |(name, tuple)|
45
+ (flips.key?(name) ? dropped : kept).add(name, *tuple)
46
+ end
47
+ flips.each do |name, value|
48
+ next if value.nil?
49
+
50
+ arity = Facts::SCHEMA.fetch(name)
51
+ kept.add(name, *([value] * arity))
52
+ end
53
+ kept
54
+ end
55
+ end
56
+ end