Module: OneGadget::Emulators::TrackedMemory

Included in:
Processor
Defined in:
lib/one_gadget/emulators/tracked_memory.rb

Overview

The memory a candidate reads and writes. Every store is filed under the base its address is an offset from -- the stack pointer, the frame pointer, or any register holding a pointer the caller supplies -- so a later load of the same slot reads back what this candidate put there, and a load of an untouched one is answered as what the caller left. Mixed into Processor.

Instance Method Summary collapse

Instance Method Details

#bp_based_stackHash{Integer => Lambda}?

Returns Memory written through Processor#bp, or nil when the arch has none.

Returns:

  • (Hash{Integer => Lambda}, nil)

    Memory written through Processor#bp, or nil when the arch has none.



17
# File 'lib/one_gadget/emulators/tracked_memory.rb', line 17

def bp_based_stack = bp && get_corresponding_stack(bp)

#get_corresponding_stack(base) ⇒ Hash{Integer => Lambda}?

The memory base addresses: what this candidate has written through it, keyed by offset. Every base gets one -- the stack pointer, the frame pointer, any other register, and a value no register names at all (a pointer the candidate derived and then built an array through).

Keyed by how the base renders, which is what makes one store enough: a register that gets reassigned addresses somewhere else and renders differently, so it lands on a different key without any invalidation to arrange. Only a store overwriting what the base itself reads from would break that, which a candidate short enough to be a gadget doesn't do.

Examples:

(amd64) After +mov QWORD PTR [rsp+0x10], rdi+, keyed by offset.

get_corresponding_stack('rsp') #=> { 0x10 => rdi }

Parameters:

  • base (String, Lambda)

    A base, as #resolve_address yields it -- not an offset expression, whose offset belongs in the key it indexes.

Returns:

  • (Hash{Integer => Lambda}, nil)

    nil when base names nothing this emulator tracks memory for.



60
61
62
63
64
# File 'lib/one_gadget/emulators/tracked_memory.rb', line 60

def get_corresponding_stack(base)
  return nil unless base.is_a?(OneGadget::Emulators::Lambda) || registers.key?(base.to_s)

  tracked_memory[base.to_s]
end

#resolve_address(address) ⇒ (Hash{Integer => Lambda}?, Integer)

Where address lands in the memory this emulator tracks: the stack it falls in and its offset within it. A load or store passes the address it dereferences, i.e. its operand with that dereference peeled off.

Examples:

an offset from a register

resolve_address(Lambda.parse('rsp+0x10')) #=> [sp_based_stack, 0x10]

an offset from a pointer no register names

resolve_address(Lambda.parse('[rbp-0x48]+0x8')) #=> [the "[rbp-0x48]" stack, 0x8]

Parameters:

  • address (Lambda, String)

    An address.

Returns:

  • ((Hash{Integer => Lambda}?, Integer))

    The stack, nil if none tracks this address, and the offset to index it at.



39
40
41
42
# File 'lib/one_gadget/emulators/tracked_memory.rb', line 39

def resolve_address(address)
  base, offset = address_base(address)
  [get_corresponding_stack(base), offset]
end

#setup_frame_pointer(bp) ⇒ void

This method returns an undefined value.

Enable frame-pointer stack tracking with bp as the frame register, so a gadget staging data at +[bp+imm]+ (e.g. an argv array off the frame pointer) is recovered instead of collapsing to a bare writable:. A nil bp leaves the arch +sp+-only. Call from the arch initializer after super.

Parameters:

  • bp (String, nil)

    The frame register's name, or nil for an arch with none.



25
26
27
# File 'lib/one_gadget/emulators/tracked_memory.rb', line 25

def setup_frame_pointer(bp)
  @bp = bp
end

#sp_based_stackHash{Integer => OneGadget::Emulators::Lambda}

Returns Memory written through sp.

Returns:



14
# File 'lib/one_gadget/emulators/tracked_memory.rb', line 14

def sp_based_stack = get_corresponding_stack(sp)

#writes_through(text) ⇒ Array<String>

The values this candidate wrote through an address built from text, which the array reported at text therefore holds -- wherever in it they landed. A reader told about that array has to be told about these too, or it is described as though the gadget had not written to it.

Examples:

(riscv64) a store of s8 through +((a5 << 0x3) + [sp+0xd0])+

writes_through('[sp+0xd0]') #=> ['s8']

Parameters:

  • text (String)

    How the address is named.

Returns:

  • (Array<String>)

    Each value written, rendered, without duplicates.



74
75
76
77
78
79
80
81
82
83
84
# File 'lib/one_gadget/emulators/tracked_memory.rb', line 74

def writes_through(text)
  return [] if text.empty?

  derived_writes.filter_map do |base, values|
    next unless operands_of(base).any? { |operand| operand.to_s == text }

    # A literal is not the caller's to arrange -- it is already what it is,
    # and the NULL such a loop writes to terminate the array is one.
    values.grep_v(Integer).map(&:to_s)
  end.flatten.uniq
end