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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +136 -0
- data/LICENSE +21 -0
- data/README.md +217 -0
- data/exe/augure +181 -0
- data/exe/augure-benchmark +67 -0
- data/home/runner/work/augure/augure/docs/agent-snippet.md +31 -0
- data/home/runner/work/augure/augure/docs/auditability.md +160 -0
- data/home/runner/work/augure/augure/docs/build-format.md +119 -0
- data/home/runner/work/augure/augure/docs/explanation-architecture.md +74 -0
- data/home/runner/work/augure/augure/docs/how-to-swap-solver.md +73 -0
- data/home/runner/work/augure/augure/docs/how-to-write-rules.md +71 -0
- data/home/runner/work/augure/augure/docs/pack-format.md +137 -0
- data/home/runner/work/augure/augure/docs/reference.md +293 -0
- data/home/runner/work/augure/augure/docs/tutorial.md +108 -0
- data/home/runner/work/augure/augure/examples/ret2win.facts +11 -0
- data/home/runner/work/augure/augure/examples/split.facts +12 -0
- data/home/runner/work/augure/augure/exe/augure +181 -0
- data/home/runner/work/augure/augure/exe/augure-benchmark +67 -0
- data/home/runner/work/augure/augure/lib/augure/bandit.rb +118 -0
- data/home/runner/work/augure/augure/lib/augure/coverage.rb +56 -0
- data/home/runner/work/augure/augure/lib/augure/ctf_corpus.json +773 -0
- data/home/runner/work/augure/augure/lib/augure/engine.rb +128 -0
- data/home/runner/work/augure/augure/lib/augure/facts.rb +194 -0
- data/home/runner/work/augure/augure/lib/augure/kb_corpus.json +345 -0
- data/home/runner/work/augure/augure/lib/augure/knowledge_base.rb +109 -0
- data/home/runner/work/augure/augure/lib/augure/mcts.rb +178 -0
- data/home/runner/work/augure/augure/lib/augure/pack.rb +244 -0
- data/home/runner/work/augure/augure/lib/augure/pack_loader.rb +93 -0
- data/home/runner/work/augure/augure/lib/augure/parity.rb +69 -0
- data/home/runner/work/augure/augure/lib/augure/pipeline.rb +107 -0
- data/home/runner/work/augure/augure/lib/augure/priors.rb +32 -0
- data/home/runner/work/augure/augure/lib/augure/rules.rb +300 -0
- data/home/runner/work/augure/augure/lib/augure/smt_process.rb +48 -0
- data/home/runner/work/augure/augure/lib/augure/verifier.rb +40 -0
- data/home/runner/work/augure/augure/lib/augure/version.rb +5 -0
- data/home/runner/work/augure/augure/lib/augure.rb +25 -0
- data/home/runner/work/augure/augure/packs/banner_leak.yml +46 -0
- data/home/runner/work/augure/augure/packs/brop.yml +46 -0
- data/home/runner/work/augure/augure/packs/fastbin_hook.yml +45 -0
- data/home/runner/work/augure/augure/packs/got_partial_overwrite.yml +45 -0
- data/home/runner/work/augure/augure/packs/got_partial_overwrite_deref.yml +39 -0
- data/home/runner/work/augure/augure/packs/house_botcake.yml +38 -0
- data/home/runner/work/augure/augure/packs/house_force.yml +37 -0
- data/home/runner/work/augure/augure/packs/house_of_apple2.yml +45 -0
- data/home/runner/work/augure/augure/packs/house_orange.yml +38 -0
- data/home/runner/work/augure/augure/packs/jop.yml +39 -0
- data/home/runner/work/augure/augure/packs/large_bin_attack.yml +39 -0
- data/home/runner/work/augure/augure/packs/one_gadget.yml +38 -0
- data/home/runner/work/augure/augure/packs/orw.yml +55 -0
- data/home/runner/work/augure/augure/packs/ret2csu_v2.yml +55 -0
- data/home/runner/work/augure/augure/packs/ret2dlresolve_x86.yml +39 -0
- data/home/runner/work/augure/augure/packs/ret2partial_overwrite.yml +38 -0
- data/home/runner/work/augure/augure/packs/ret2vdso.yml +39 -0
- data/home/runner/work/augure/augure/packs/setcontext_srop.yml +46 -0
- data/home/runner/work/augure/augure/packs/uaf_tcache.yml +45 -0
- data/home/runner/work/augure/augure/packs/unsorted_bin_attack.yml +38 -0
- 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
|
+
]
|