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,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Augure
4
+ # Forward-chaining evaluator over a rule table. Interprets the Rules data;
5
+ # keeps, per derived head, the rule id and the evidence tuples that fired
6
+ # it. Naive fixpoint: the rule set is tiny, clarity beats semi-naive here.
7
+ class Engine
8
+ Result = Struct.new(:derived_rels, :provenance_by_head) do
9
+ def derived(rel)
10
+ derived_rels.fetch(rel, [])
11
+ end
12
+
13
+ def applicable
14
+ provenance_by_head.filter_map do |head, provs|
15
+ head[1] if head[0] == Rules::APPLICABLE && !provs.empty?
16
+ end
17
+ end
18
+
19
+ def provenance(technique)
20
+ provenance_by_head.fetch([Rules::APPLICABLE, technique], [])
21
+ end
22
+ end
23
+
24
+ OP = {lt: :<, le: :<=, gt: :>, ge: :>=, eq: :==}.freeze
25
+
26
+ def initialize(facts, rules: nil)
27
+ @facts = facts
28
+ @rules = rules || Rules.all
29
+ end
30
+
31
+ def run
32
+ rels = seeded_relations
33
+
34
+ loop do
35
+ fired = false
36
+ @rules.each do |rule|
37
+ head_key = [rule.head[0], rule.head[1..]]
38
+ evidence = satisfy(rule.conditions, rels)
39
+ next if evidence.empty?
40
+
41
+ # sibling rules sharing a head ALL contribute: the provenance
42
+ # carries every reason that fired, each attributed to its own
43
+ # rule (the docs promise "which reason fired" - all of them).
44
+ known = rels[head_key] || []
45
+ fresh = evidence.reject { |combo| known.any? { |(c, _)| c == combo } }
46
+ next if fresh.empty?
47
+
48
+ rels[head_key] = known + fresh.map { |combo| [combo, rule] }
49
+ fired = true
50
+ end
51
+ break unless fired
52
+ end
53
+
54
+ build_result(rels)
55
+ end
56
+
57
+ private
58
+
59
+ # key: [rel_name, tuple]; value: [[combo, rule], ...] - every firing
60
+ # (dedup by combo) with its own rule attribution. Input facts carry
61
+ # an empty evidence; defaults are seeded as inputs.
62
+ def seeded_relations
63
+ rels = {}
64
+ @facts.each { |(name, tuple)| rels[[name, tuple]] = [] }
65
+ Rules::DEFAULTS.each do |name, default|
66
+ rels[[name, [default]]] ||= [] if @facts.rel(name).nil?
67
+ end
68
+ rels
69
+ end
70
+
71
+ def tuples_for(rels, rel)
72
+ rels.filter_map { |(r, t), _| t if r == rel }
73
+ end
74
+
75
+ # Returns combos of evidence items ([rel, atom, ...] pairs) when
76
+ # satisfiable, [] else. A negated condition contributes an empty item.
77
+ def satisfy(conditions, rels)
78
+ combos = [[]]
79
+ conditions.each do |cond|
80
+ hits = match_condition(cond, rels)
81
+ return [] if hits.empty?
82
+
83
+ combos = combos.flat_map { |c| hits.map { |h| c + [h] } }
84
+ end
85
+ combos
86
+ end
87
+
88
+ def match_condition(cond, rels)
89
+ case cond[0]
90
+ when :fact
91
+ tuples_for(rels, cond[1])
92
+ .select { |t| t == [cond[2]] }
93
+ .map { |t| [cond[1], t[0]] }
94
+ when :not_fact
95
+ present = tuples_for(rels, cond[1]).any? { |t| t == [cond[2]] }
96
+ present ? [] : [[]]
97
+ when :match
98
+ pattern = /#{cond[3]}/
99
+ tuples_for(rels, cond[1])
100
+ .select { |t| t[cond[2]].is_a?(String) && t[cond[2]] =~ pattern }
101
+ .map { |t| [cond[1], *t] }
102
+ when :cmp
103
+ tuples_for(rels, cond[1])
104
+ .select { |t| t[0].is_a?(Integer) && t[0].public_send(OP.fetch(cond[2].to_sym), cond[3]) }
105
+ .map { |t| [cond[1], *t] }
106
+ else
107
+ raise EngineError, "unknown condition #{cond[0].inspect}"
108
+ end
109
+ end
110
+
111
+ def build_result(rels)
112
+ derived = {}
113
+ provs = {}
114
+ rels.each do |(r, t), firings|
115
+ # Pure input facts (empty evidence) never surface as derived outputs.
116
+ next if firings.empty?
117
+
118
+ (derived[r] ||= []) << t
119
+ key = [r, t[0]]
120
+ entries = provs[key] ||= []
121
+ firings.each do |(combo, rule)|
122
+ entries << {rule: rule.id, origin: rule.origin, evidence: combo.reject(&:empty?)}
123
+ end
124
+ end
125
+ Result.new(derived, provs)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Augure
4
+ # Strict, schema-checked parser for Augure fact files.
5
+ #
6
+ # A fact is one line: predicate("string"). or predicate("atom", 42).
7
+ # The schema is the single source of truth for which predicates exist and
8
+ # their arity. Anything else is rejected with an explicit error: silent
9
+ # acceptance of malformed input is what broke the Python prototype.
10
+ class Facts
11
+ # name => arity. Order is the emission order of #to_s.
12
+ SCHEMA = {
13
+ "nx" => 1, "pie" => 1, "canary" => 1, "relro" => 1,
14
+ "plt" => 1, "gadget" => 2, "vuln_hint" => 1, "vuln" => 1,
15
+ "target_function" => 1, "libc_present" => 1, "allocator" => 1,
16
+ "glibc_minor" => 1, "return_addr_filtered" => 1,
17
+ "got_overwrite_target" => 1, "function_pointer_on_heap" => 1,
18
+ "seccomp" => 1, "shellcode_input" => 1, "sigreturn_frame" => 1,
19
+ "fmtstr_read" => 1, "reloc_writable" => 1, "dt_lazy" => 1,
20
+ "limited_stack" => 1, "service" => 1, "software_version" => 1,
21
+ "remote" => 1, "verified" => 1, "win_symbol" => 2,
22
+ "crash_site" => 1, "vuln_function" => 1, "sink" => 1, "leaked_address" => 2,
23
+ "plt_addr" => 2, "got_addr" => 2, "cve" => 1
24
+ }.freeze
25
+
26
+ STRING_TYPES = {
27
+ "gadget" => %w[string integer],
28
+ "win_symbol" => %w[string integer],
29
+ "crash_site" => %w[string],
30
+ "vuln_function" => %w[string],
31
+ "sink" => %w[string],
32
+ "leaked_address" => %w[string integer],
33
+ "plt_addr" => %w[string integer],
34
+ "got_addr" => %w[string integer],
35
+ "glibc_minor" => %w[integer],
36
+ "cve" => %w[string]
37
+ }.freeze
38
+
39
+ # Closed value domains: a typo'd atom ("ture", "maybe") would parse,
40
+ # disable the closed-world default, and every rule of the predicate
41
+ # would silently never fire. The loud-failure philosophy stops at
42
+ # the grammar AND the vocabulary.
43
+ VALUE_DOMAINS = {
44
+ "nx" => %w[true false], "pie" => %w[true false], "canary" => %w[true false],
45
+ "relro" => %w[none partial full], "target_function" => %w[true false],
46
+ "libc_present" => %w[true false], "function_pointer_on_heap" => %w[true false],
47
+ "got_overwrite_target" => %w[true false], "seccomp" => %w[true false],
48
+ "shellcode_input" => %w[true false], "sigreturn_frame" => %w[true false],
49
+ "fmtstr_read" => %w[true false], "reloc_writable" => %w[true false],
50
+ "dt_lazy" => %w[true false], "limited_stack" => %w[true false],
51
+ "remote" => %w[true false], "verified" => %w[remote],
52
+ "return_addr_filtered" => %w[true false]
53
+ }.freeze
54
+
55
+ include Enumerable
56
+
57
+ def self.parse(text)
58
+ new.parse(text)
59
+ end
60
+
61
+ def self.from_file(path)
62
+ parse(File.read(path))
63
+ end
64
+
65
+ def initialize
66
+ @rels = {}
67
+ end
68
+
69
+ def parse(text)
70
+ text.each_line.with_index(1) do |line, lineno|
71
+ line = line.rstrip
72
+ next if line.empty? || line.start_with?("//")
73
+
74
+ consume(line, lineno)
75
+ end
76
+ self
77
+ end
78
+
79
+ def rel(name)
80
+ @rels[name]&.dup
81
+ end
82
+ alias_method :[], :rel
83
+
84
+ def each
85
+ SCHEMA.each_key do |name|
86
+ @rels.fetch(name, []).each { |tuple| yield([name, tuple]) }
87
+ end
88
+ end
89
+
90
+ def size
91
+ @rels.values.sum(&:size)
92
+ end
93
+
94
+ def merge(other)
95
+ merged = self.class.new
96
+ rels = merged.instance_variable_get(:@rels)
97
+ other_r = other.instance_variable_get(:@rels)
98
+ SCHEMA.each_key do |name|
99
+ combined = Array(@rels[name]) + Array(other_r[name])
100
+ rels[name] = combined unless combined.empty?
101
+ end
102
+ merged
103
+ end
104
+
105
+ # Builder API for programmatic construction (the profiler's contract):
106
+ # identical validation as parsing - same schema, same loud failures.
107
+ def add(name, *args)
108
+ raise UnknownPredicate, "unknown predicate #{name.inspect}" unless SCHEMA.key?(name)
109
+
110
+ arity = SCHEMA[name]
111
+ raise MalformedFact, "#{name} expects arity #{arity}, got #{args.size}" unless args.size == arity
112
+
113
+ check_types(name, args, 0)
114
+ domain = VALUE_DOMAINS[name]
115
+ if domain && args.size == 1 && args.first.is_a?(String) && !domain.include?(args.first)
116
+ raise MalformedFact, "#{name}: value #{args.first.inspect} is outside the value domain #{domain.inspect}"
117
+ end
118
+
119
+ # the atom grammar is the load-bearing wall: a symbol name from an
120
+ # untrusted binary (win_symbol) or a version string from a hostile
121
+ # banner (software_version) must not carry quotes, backslashes,
122
+ # parens or newlines into a re-parsed facts file
123
+ args.each do |arg|
124
+ next unless arg.is_a?(String) && arg.match?(/["\\()\n\r]/)
125
+
126
+ raise MalformedFact, "#{name}: fact atoms may not contain quotes, backslashes, parens or newlines"
127
+ end
128
+ (@rels[name] ||= []) << args
129
+ self
130
+ end
131
+
132
+ def to_s
133
+ SCHEMA.filter_map do |name, _arity|
134
+ @rels.fetch(name, []).map { |tuple| emit_fact(name, tuple) }
135
+ end.join
136
+ end
137
+
138
+ private
139
+
140
+ def consume(line, lineno)
141
+ raise MalformedFact, "line #{lineno}: rules are not facts, only facts belong here" if line.include?(":-")
142
+
143
+ match = line.match(/\A([a-z][a-z0-9_]*)\((.*)\)\.\z/)
144
+ raise MalformedFact, "line #{lineno}: malformed fact #{line.inspect}" unless match
145
+
146
+ name = match[1]
147
+ raw_args = match[2]
148
+ raise UnknownPredicate, "line #{lineno}: unknown predicate #{name.inspect}" unless SCHEMA.key?(name)
149
+
150
+ args = parse_args(raw_args, name, lineno)
151
+ begin
152
+ add(name, *args)
153
+ rescue UnknownPredicate, MalformedFact => e
154
+ raise e.class, "line #{lineno}: #{e.message}"
155
+ end
156
+ end
157
+
158
+ def parse_args(raw, name, lineno)
159
+ return [] if raw.strip.empty?
160
+
161
+ raw.split(",", -1).map.with_index do |arg, i|
162
+ arg = arg.strip
163
+ if arg.match?(/\A-?\d+\z/)
164
+ arg.to_i
165
+ elsif arg.match?(/\A"[^"\\()]*"\z/)
166
+ arg[1..-2]
167
+ else
168
+ raise MalformedFact,
169
+ "line #{lineno}: #{name} arg #{i + 1} is not a quoted string atom or integer"
170
+ end
171
+ end
172
+ end
173
+
174
+ def check_types(name, args, lineno)
175
+ types = STRING_TYPES[name] || (["string"] * SCHEMA[name])
176
+
177
+ args.each_with_index do |arg, i|
178
+ expected = types[i]
179
+ actual = arg.is_a?(Integer) ? "integer" : "string"
180
+ next if actual == expected
181
+
182
+ raise MalformedFact, "line #{lineno}: #{name} arg #{i + 1} must be a #{expected} atom, got #{actual}"
183
+ end
184
+ end
185
+
186
+ def emit_fact(name, tuple)
187
+ types = STRING_TYPES[name]
188
+ args = tuple.each_with_index.map do |arg, i|
189
+ (types && types[i] == "integer") ? arg.to_s : %("#{arg}")
190
+ end
191
+ "#{name}(#{args.join(", ")}).\n"
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,345 @@
1
+ [
2
+ {
3
+ "id": "ret2plt_nopie_001",
4
+ "technique": "ret2plt",
5
+ "target_class": "nx_no_pie",
6
+ "source": "ropemporium_ret2win",
7
+ "description": "ret2plt on no-PIE binary with system@plt. Requires pop rdi gadget binsh string NX on PIE off. Common mistake forgetting ret alignment x86_64 ABI movaps.",
8
+ "chain": [
9
+ "pop_rdi_ret",
10
+ "binsh_addr",
11
+ "ret_align",
12
+ "system_plt"
13
+ ],
14
+ "unlocks": [],
15
+ "success_rate": 0.85
16
+ },
17
+ {
18
+ "id": "ret2plt_partial_relro_001",
19
+ "technique": "ret2plt",
20
+ "target_class": "nx_no_pie",
21
+ "source": "protostar_stack6",
22
+ "description": "ret2plt with partial RELRO. GOT writable but not needed. Technique does not require GOT overwrite. system plt direct.",
23
+ "chain": [
24
+ "pop_rdi_ret",
25
+ "binsh_addr",
26
+ "system_plt"
27
+ ],
28
+ "unlocks": [],
29
+ "success_rate": 0.82
30
+ },
31
+ {
32
+ "id": "ret2libc_leak_001",
33
+ "technique": "ret2libc",
34
+ "target_class": "nx_pie",
35
+ "source": "phoenix_stack5",
36
+ "description": "ret2libc via puts leak defeat ASLR. Chain leak libc via puts plt calc system offset pop rdi. Requires pop rdi puts PLT known libc version.",
37
+ "chain": [
38
+ "pop_rdi_ret",
39
+ "got_puts",
40
+ "puts_plt",
41
+ "ret_align",
42
+ "pop_rdi_ret",
43
+ "binsh_offset",
44
+ "system_libc"
45
+ ],
46
+ "unlocks": [],
47
+ "success_rate": 0.65
48
+ },
49
+ {
50
+ "id": "fmtstr_arbitrary_write_001",
51
+ "technique": "fmtstr_write",
52
+ "target_class": "fmtstr",
53
+ "source": "protostar_format4",
54
+ "description": "Format string arbitrary write via %n overwrite GOT. Calibration find offset with %p chain first. FORTIFY_SOURCE breaks %n in glibc 2.17 and above.",
55
+ "chain": [
56
+ "leak_stack_canary",
57
+ "leak_pie_base",
58
+ "got_overwrite"
59
+ ],
60
+ "unlocks": [],
61
+ "success_rate": 0.55
62
+ },
63
+ {
64
+ "id": "fmtstr_stack_leak_001",
65
+ "technique": "fmtstr_write",
66
+ "target_class": "fmtstr",
67
+ "source": "ctf_generic",
68
+ "description": "Format string stack leak defeat canary PIE base address. Useful as first stage before ret2libc or ROP chain.",
69
+ "chain": [
70
+ "fmtstr_leak_canary",
71
+ "fmtstr_leak_pie"
72
+ ],
73
+ "unlocks": [
74
+ "ret2libc",
75
+ "rop"
76
+ ],
77
+ "success_rate": 0.72
78
+ },
79
+ {
80
+ "id": "rop_execve_001",
81
+ "technique": "rop",
82
+ "target_class": "nx_pie_full_prot",
83
+ "source": "ropemporium_write4",
84
+ "description": "ROP chain execve binsh NULL NULL syscall. Requires pop rdi rsi rdx gadgets write primitive binsh string syscall gadget ROPgadget ropper discovery.",
85
+ "chain": [
86
+ "write_binsh",
87
+ "pop_rdi",
88
+ "binsh_addr",
89
+ "pop_rsi",
90
+ "null",
91
+ "pop_rdx",
92
+ "null",
93
+ "syscall"
94
+ ],
95
+ "unlocks": [],
96
+ "success_rate": 0.45
97
+ },
98
+ {
99
+ "id": "fastbin_dup_001",
100
+ "technique": "heap_fastbin",
101
+ "target_class": "heap_ptmalloc2",
102
+ "source": "how2heap_fastbin_dup",
103
+ "description": "Fastbin dup double-free arbitrary write ptmalloc2. Only works glibc before 2.32 no safe linking. Requires controlled heap double-free primitive.",
104
+ "chain": [
105
+ "double_free",
106
+ "fastbin_overwrite",
107
+ "arbitrary_write"
108
+ ],
109
+ "unlocks": [],
110
+ "success_rate": 0.4
111
+ },
112
+ {
113
+ "id": "srop_001",
114
+ "technique": "srop",
115
+ "target_class": "nx_minimal_gadgets",
116
+ "source": "ctf_generic",
117
+ "description": "SROP sigreturn ROP minimal gadgets. Requires syscall gadget sigreturn frame control. Single gadget controls all registers unlike ROP chain.",
118
+ "chain": [
119
+ "sigreturn_frame",
120
+ "syscall"
121
+ ],
122
+ "unlocks": [],
123
+ "success_rate": 0.5
124
+ },
125
+ {
126
+ "id": "chain_fmtstr_ret2libc_001",
127
+ "technique": "fmtstr_write",
128
+ "target_class": "nx_pie",
129
+ "source": "ctf_generic",
130
+ "description": "Two stage fmtstr leak canary libc base then ret2libc. Classic chain PIE NX canary targets.",
131
+ "chain": [
132
+ "fmtstr_leak_canary",
133
+ "fmtstr_leak_libc",
134
+ "ret2libc"
135
+ ],
136
+ "unlocks": [
137
+ "ret2libc"
138
+ ],
139
+ "success_rate": 0.6
140
+ },
141
+ {
142
+ "id": "chain_puts_leak_rop_001",
143
+ "technique": "ret2libc",
144
+ "target_class": "nx_pie",
145
+ "source": "phoenix_stack7",
146
+ "description": "puts leak then ROP chain. Stage 1 leak libc base puts plt. Stage 2 construct ROP known libc offsets.",
147
+ "chain": [
148
+ "ret2plt_puts_leak",
149
+ "calc_libc_base",
150
+ "full_rop"
151
+ ],
152
+ "unlocks": [
153
+ "rop",
154
+ "ret2libc"
155
+ ],
156
+ "success_rate": 0.58
157
+ },
158
+ {
159
+ "id": "unlink_dlmalloc_001",
160
+ "technique": "heap_overwrite",
161
+ "target_class": "heap_dlmalloc",
162
+ "source": "pwnable.kr/unlink",
163
+ "description": "Unlink macro corruption old dlmalloc arbitrary write. FD BK pointer manipulation overwrite return address shell. Requires heap and stack leak to defeat ASLR.",
164
+ "chain": [
165
+ "heap_leak",
166
+ "stack_leak",
167
+ "unlink_corrupt",
168
+ "overwrite_ret"
169
+ ],
170
+ "unlocks": [],
171
+ "success_rate": 0.78
172
+ },
173
+ {
174
+ "id": "tcache_tear_001",
175
+ "technique": "heap_tcache",
176
+ "target_class": "heap_tcache",
177
+ "source": "pwnable.tw/tcache_tear",
178
+ "description": "Tcache tear double-free poisoning modern glibc. Overwrite free_hook with one_gadget. Unsorted bin attack for libc leak first.",
179
+ "chain": [
180
+ "double_free",
181
+ "tcache_poison",
182
+ "leak_libc",
183
+ "overwrite_free_hook",
184
+ "one_gadget"
185
+ ],
186
+ "unlocks": [],
187
+ "success_rate": 0.62
188
+ },
189
+ {
190
+ "id": "tcache_realloc_fmtstr_001",
191
+ "technique": "heap_tcache",
192
+ "target_class": "heap_tcache",
193
+ "source": "pwnable.tw/realloc",
194
+ "description": "Tcache dup overwrite atoll GOT with printf. Turns input into format string then leak libc. Overwrite atoll with system for shell.",
195
+ "chain": [
196
+ "tcache_dup",
197
+ "got_overwrite_printf",
198
+ "fmtstr_leak",
199
+ "got_overwrite_system"
200
+ ],
201
+ "unlocks": [
202
+ "fmtstr_write"
203
+ ],
204
+ "success_rate": 0.55
205
+ },
206
+ {
207
+ "id": "srop_smallest_001",
208
+ "technique": "srop",
209
+ "target_class": "minimal_static_nx",
210
+ "source": "360ctf/smallest",
211
+ "description": "SROP x86_64 canonical 17 byte binary read rsp ret. read retval sets rax 15 rt_sigreturn send 15 bytes. fake sigframe execve rax 59 rdi binsh syscall gadget.",
212
+ "chain": [
213
+ "read_sets_rax_15",
214
+ "syscall_sigreturn",
215
+ "fake_frame_execve"
216
+ ],
217
+ "unlocks": [],
218
+ "success_rate": 0.55
219
+ },
220
+ {
221
+ "id": "srop_sickrop_001",
222
+ "technique": "srop",
223
+ "target_class": "minimal_static_nx",
224
+ "source": "hackthebox/sick_rop",
225
+ "description": "SROP x86_64 mprotect variant static binary read write. rax 15 rt_sigreturn via read write byte count. fake sigframe mprotect rax 10 region rwx then shellcode.",
226
+ "chain": [
227
+ "set_rax_15",
228
+ "syscall_sigreturn",
229
+ "fake_frame_mprotect",
230
+ "region_rwx",
231
+ "shellcode"
232
+ ],
233
+ "unlocks": [],
234
+ "success_rate": 0.55
235
+ },
236
+ {
237
+ "id": "orw_seccomp_001",
238
+ "technique": "shellcode",
239
+ "target_class": "seccomp_nx_off",
240
+ "source": "pwnable.tw/orw",
241
+ "description": "Shellcode under seccomp open read write only. No execve allowed so shellcode reads flag file directly. Binary reads shellcode into executable buffer.",
242
+ "chain": [
243
+ "inject_shellcode",
244
+ "open_flag",
245
+ "read_flag",
246
+ "write_stdout"
247
+ ],
248
+ "unlocks": [],
249
+ "success_rate": 0.55
250
+ },
251
+ {
252
+ "id": "rop_calc_int80_001",
253
+ "technique": "rop",
254
+ "target_class": "nx_canary_static",
255
+ "source": "pwnable.tw/calc",
256
+ "description": "ROP chain static binary NX canary int 0x80 execve. Arbitrary relative write builds chain on stack. pop edx pop eax int80 gadgets execve binsh.",
257
+ "chain": [
258
+ "arbitrary_write",
259
+ "build_rop",
260
+ "int80_execve"
261
+ ],
262
+ "unlocks": [],
263
+ "success_rate": 0.5
264
+ },
265
+ {
266
+ "id": "fmtstr_leak_stack_001",
267
+ "technique": "fmtstr_leak",
268
+ "target_class": "nx_pie",
269
+ "source": "protostar_format1",
270
+ "description": "format string leak of stack data via %x %s. Reads defeat PIE by leaking code pointers and defeat canaries by leaking the stack cookie. Common mistake forgetting the read primitive needs no write at all.",
271
+ "chain": [
272
+ "printf"
273
+ ],
274
+ "unlocks": [
275
+ "ret2libc",
276
+ "rop"
277
+ ],
278
+ "success_rate": 0.72
279
+ },
280
+ {
281
+ "id": "ret2dlresolve_ropemporium_001",
282
+ "technique": "dlresolve",
283
+ "target_class": "nx_no_pie",
284
+ "source": "ropemporium_ret2dlresolve",
285
+ "description": "deferred symbol resolution abuse with lazy binding. Fake DT_SYMTAB entry resolves system without any libc leak. Requires writable relocation and lazy binding partial RELRO. Sparse gadget binary.",
286
+ "chain": [
287
+ "fake_reloc",
288
+ "dl_fixup",
289
+ "system"
290
+ ],
291
+ "unlocks": [
292
+ "shell"
293
+ ],
294
+ "success_rate": 0.55
295
+ },
296
+ {
297
+ "id": "ret2csu_sparse_001",
298
+ "technique": "ret2csu",
299
+ "target_class": "nx_no_pie",
300
+ "source": "ropemporium_ret2csu",
301
+ "description": "__libc_csu_init universal gadget populates rdx rsi rdi in gadget-sparse binaries. The popper and mov gadgets enable controlled multi-argument calls.",
302
+ "chain": [
303
+ "csu_popper",
304
+ "csu_mov",
305
+ "target_fn"
306
+ ],
307
+ "unlocks": [
308
+ "libc_base"
309
+ ],
310
+ "success_rate": 0.75
311
+ },
312
+ {
313
+ "id": "stack_pivot_bss_001",
314
+ "technique": "stack_pivot",
315
+ "target_class": "nx_no_pie",
316
+ "source": "ropemporium_pivot",
317
+ "description": "stack pivot via xchg rsp rax onto a larger buffer in bss. Required when the full chain does not fit the available stack space. Two-stage chain discipline.",
318
+ "chain": [
319
+ "pop_rax_ret",
320
+ "xchg_rsp_rax",
321
+ "bss_chain"
322
+ ],
323
+ "unlocks": [
324
+ "ret2libc",
325
+ "rop"
326
+ ],
327
+ "success_rate": 0.7
328
+ },
329
+ {
330
+ "id": "got_overwrite_fmtstr_001",
331
+ "technique": "got_overwrite",
332
+ "target_class": "nx_pie",
333
+ "source": "phoenix_format4",
334
+ "description": "overwrite a GOT entry to redirect a called function. Partial RELRO keeps the GOT writable. Redirect printf or exit to the target. Classic documented format4 path.",
335
+ "chain": [
336
+ "fmtstr_n_write",
337
+ "got_entry",
338
+ "redirect"
339
+ ],
340
+ "unlocks": [
341
+ "shell"
342
+ ],
343
+ "success_rate": 0.65
344
+ }
345
+ ]