Module: OneGadget::Emulators::Conditional
- Included in:
- Processor
- Defined in:
- lib/one_gadget/emulators/conditional.rb
Overview
Shared modelling of compare instructions and conditional branches.
A gadget candidate may cross a conditional branch: the fetcher stitches the actual taken/not-taken path (see Fetchers::Base#candidates), and the emulator turns the branch decision into a gadget constraint.
The including class (an Processor subclass) must
provide registers and register? (operand lookup), operands(cmd) (the
arch's operand splitter), self.class.bits (32/64, for the signedness cast),
and the +@flags+/+@pending+/+@constraints+ state Processor#initialize sets up.
Constant Summary collapse
- RELATION =
Taken-semantics of each supported branch condition, keyed by a predicate named after the comparison it encodes (the LLVM
icmpnames): a leadingu= unsigned,s= signed. { eq: ['==', nil], ne: ['!=', nil], ult: ['<', :u], ule: ['<=', :u], ugt: ['>', :u], uge: ['>=', :u], slt: ['<', :s], sle: ['<=', :s], sgt: ['>', :s], sge: ['>=', :s] }.freeze
- NEGATE =
Relation under the not-taken branch.
{ '==' => '!=', '!=' => '==', '>=' => '<', '<' => '>=', '>' => '<=', '<=' => '>' }.freeze
- COMPARE_OPS =
The flag-setting compares we model, keyed by the ALU operation the compare performs. An arch maps its own mnemonics onto these (its
COMPARES), so adding one needs no change here. { sub: { ordered: true, render: :render_sub }, # subtraction: flags from lhs - rhs add: { ordered: true, render: :render_add }, # addition: flags from lhs + rhs and: { ordered: false, render: :render_and } # bitwise AND: flags from lhs & rhs (zero flag) }.freeze
- ZERO =
A value comparison's zero literal, always hex. Pointer operands render
NULL(chosen by the arg resolvers); every value-context zero here is0x0, so a bare0(and objdump's arch-specific#0vs0x0) never leaks into a constraint. Recorded here and read back by OneGadget::Emulators::Constraints, whose drop rules recognise a compare against zero by it. '0x0'
Instance Method Summary collapse
-
#branch_on_bit(target, operand, bit, negate:) ⇒ true
Register a self-contained branch that tests a single bit of a register: also carries its own test, so no preceding compare is needed.
-
#branch_on_compare(cond, target) ⇒ true, :fail
Register a branch on the last recorded compare's flags, resolved on the next line.
-
#branch_on_zero(target, operand, negate:) ⇒ true
Register a self-contained branch that tests a register against zero.
-
#comparisons_on(expr) ⇒ Array<(String, String, String)>
Every comparison recorded so far on
expr, the left side as rendered. -
#handle_compare(op, cmd) ⇒ true
Model a compare line: record its two operands' current values under the compare's ALU op, so a following conditional branch can be rendered.
-
#mnemonic(cmd) ⇒ String
The mnemonic of an objdump line.
-
#operand_str(operand) ⇒ String
Render an operand for a constraint: a register becomes its current value, an immediate becomes hex, anything else (a memory operand) stays as-is.
-
#record_compare(op, lhs, rhs) ⇒ true
Record a compare so a following conditional branch can be rendered.
-
#resolve_pending_branch(cmd) ⇒ void
Resolve the pending branch using +cmd+'s address: if it equals the branch target the stitched path took the branch, else it fell through.
-
#satisfiable?(triples) ⇒ Boolean
Whether some value satisfies every comparison in
triplesat once, by intersecting the range each one allows. -
#value_str(val) ⇒ String
A value as a constraint reads it: a concrete one in hex, whichever side of a compare it came from, and anything else as it renders itself.
Instance Method Details
#branch_on_bit(target, operand, bit, negate:) ⇒ true
Register a self-contained branch that tests a single bit of a register: also carries its own test, so no preceding compare is needed. Renders a bitmask test.
210 211 212 213 214 215 216 217 |
# File 'lib/one_gadget/emulators/conditional.rb', line 210 def branch_on_bit(target, operand, bit, negate:) reg = operand_str(operand) mask = OneGadget::Helper.hex(1 << bit) hit = negate ? '!=' : '==' miss = negate ? '==' : '!=' @pending = { target:, compare: ->(taken) { ["(#{reg} & #{mask})", taken ? hit : miss, ZERO] } } true end |
#branch_on_compare(cond, target) ⇒ true, :fail
Register a branch on the last recorded compare's flags, resolved on the next
line. Call it from handle_branch; cond is the comparison predicate and
target is the branch's direct destination address.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/one_gadget/emulators/conditional.rb', line 163 def branch_on_compare(cond, target) return :fail if @flags.nil? rel = RELATION[cond] return :fail if rel.nil? # A magnitude condition needs a compare whose flags reflect a full ordering # (+:sub+/+:add+); an equality-only compare (+:and+) supports just eq/ne. return :fail unless COMPARE_OPS.fetch(@flags[:op])[:ordered] || %i[eq ne].include?(cond) op = @flags[:op] lhs = @flags[:lhs] rhs = @flags[:rhs] @pending = { target:, compare: ->(taken) { compare_triple(op, lhs, rhs, rel, taken) } } true end |
#branch_on_zero(target, operand, negate:) ⇒ true
Register a self-contained branch that tests a register against zero. It carries
its own compare, so no preceding compare is needed. negate: selects the sense:
false branches when the register is zero, true when it isn't.
192 193 194 195 196 197 198 |
# File 'lib/one_gadget/emulators/conditional.rb', line 192 def branch_on_zero(target, operand, negate:) reg = operand_str(operand) hit = negate ? '!=' : '==' # taken (not negated) => reg == 0 miss = negate ? '==' : '!=' @pending = { target:, compare: ->(taken) { [reg, taken ? hit : miss, ZERO] } } true end |
#comparisons_on(expr) ⇒ Array<(String, String, String)>
Every comparison recorded so far on expr, the left side as rendered.
Matching on that text is what makes this sound: the renderer substitutes
each register's current value, so two constraints printing the same left
side really are about the same tracked value (and a differing signedness
cast is part of that text, keeping incomparable ones apart).
254 255 256 |
# File 'lib/one_gadget/emulators/conditional.rb', line 254 def comparisons_on(expr) @constraints.filter_map { |type, obj| obj if type == :cmp && obj.first == expr } end |
#handle_compare(op, cmd) ⇒ true
Model a compare line: record its two operands' current values under the compare's ALU op, so a following conditional branch can be rendered.
Call this from process! when the mnemonic is one of the arch's compares
(its COMPARES maps the mnemonic to the op), before dispatching to the
inst_* handlers.
98 99 100 101 |
# File 'lib/one_gadget/emulators/conditional.rb', line 98 def handle_compare(op, cmd) lhs, rhs = operands(cmd) record_compare(op, operand_str(lhs), operand_str(rhs)) end |
#mnemonic(cmd) ⇒ String
The mnemonic of an objdump line. Use it at the top of process! to decide
whether a line is a compare or a branch.
112 113 114 |
# File 'lib/one_gadget/emulators/conditional.rb', line 112 def mnemonic(cmd) cmd[/\A[0-9a-f]+:\s*(\S+)/, 1] || '' end |
#operand_str(operand) ⇒ String
Render an operand for a constraint: a register becomes its current value,
an immediate becomes hex, anything else (a memory operand) stays as-is.
#handle_compare uses it on each compare operand; call it yourself only
when writing a bespoke branch_on_* helper.
127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/one_gadget/emulators/conditional.rb', line 127 def operand_str(operand) if register?(operand) raise Error::ClobberedRegisterError, operand if clobbered?(registers[operand]) return value_str(registers[operand]) end OneGadget::Helper.hex(Integer(operand)) rescue ArgumentError operand end |
#record_compare(op, lhs, rhs) ⇒ true
Record a compare so a following conditional branch can be rendered. Normally reached through #handle_compare; call it directly only when an arch models a flag-setting instruction that #handle_compare doesn't cover.
77 78 79 80 |
# File 'lib/one_gadget/emulators/conditional.rb', line 77 def record_compare(op, lhs, rhs) @flags = { op:, lhs:, rhs: } true end |
#resolve_pending_branch(cmd) ⇒ void
This method returns an undefined value.
Resolve the pending branch using +cmd+'s address: if it equals the branch
target the stitched path took the branch, else it fell through. On
resolution the rendered relation is appended to the gadget's constraints.
Must be called at the top of process! for every line (a no-op when nothing
is pending), so the branch registered on the previous line sees this line's address.
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/one_gadget/emulators/conditional.rb', line 235 def resolve_pending_branch(cmd) return if @pending.nil? taken = branch_addr(cmd) == @pending[:target] triple = @pending[:compare].call(taken) @pending = nil @constraints << [:cmp, triple] return if satisfiable?(comparisons_on(triple.first)) raise Error::InfeasiblePathError, "cannot hold together: #{triple.first}" end |
#satisfiable?(triples) ⇒ Boolean
Whether some value satisfies every comparison in triples at once, by
intersecting the range each one allows. Comparisons against anything but
an integer are ignored rather than guessed at, so an undecidable one never
makes a path look impossible.
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/one_gadget/emulators/conditional.rb', line 265 def satisfiable?(triples) low = nil high = nil excluded = [] triples.each do |_expr, op, rhs| next unless OneGadget::Helper.integer?(rhs) value = Integer(rhs) case op when '==' low = [low, value].compact.max high = [high, value].compact.min when '!=' then excluded << value when '<' then high = [high, value - 1].compact.min when '<=' then high = [high, value].compact.min when '>' then low = [low, value + 1].compact.max when '>=' then low = [low, value].compact.max end end return false if low && high && (low > high || (low == high && excluded.include?(low))) true end |
#value_str(val) ⇒ String
A value as a constraint reads it: a concrete one in hex, whichever side of a compare it came from, and anything else as it renders itself.
144 145 146 |
# File 'lib/one_gadget/emulators/conditional.rb', line 144 def value_str(val) val.is_a?(Integer) ? OneGadget::Helper.hex(val) : val.to_s end |