Class: OneGadget::Fetchers::Mips

Inherits:
Base
  • Object
show all
Defined in:
lib/one_gadget/fetchers/mips.rb

Overview

Fetcher for MIPS (32-bit, o32).

Two things about this architecture are unlike every other one supported, and both are answered here so that nothing about them reaches the engine:

  • a call states no target -- it goes through a register loaded from the GOT, so the callee's name has to be resolved and written where every other arch has one already (#name_got_calls);
  • a branch or call has a delay slot: the instruction after it runs before it takes effect. Emulators::Mips#process! holds the transfer back so both run in that order, which leaves the disassembly in the order objdump wrote it and every address meaning what it says. Two seams follow from it, and they are the only ones: the instruction after a call belongs to the window that ends at the call (#emulate), and the edge into a branch's target leaves from the delay slot rather than the branch (#branch_pred_map).

Constant Summary

Constants included from DynamicSymbols

DynamicSymbols::CONTROL_TARGET, DynamicSymbols::TRAILING_ADDRESS

Constants included from Disassembly

Disassembly::TERMINAL_PREFIXES, Disassembly::TERMINAL_SPAWN, Disassembly::WINDOW_BACK, Disassembly::WINDOW_FWD

Constants included from CandidateWalk

CandidateWalk::MAX_FORKS, CandidateWalk::PATH_BUDGET

Instance Attribute Summary

Attributes inherited from Base

#file

Instance Method Summary collapse

Methods inherited from Base

cached, #find, #initialize, #refused_before_call?, #resolve_suffix, #terminal_call_line?

Methods included from CandidateWalk

#candidates

Constructor Details

This class inherits a constructor from OneGadget::Fetchers::Base

Instance Method Details

#environ?(str) ⇒ Boolean

This arch reads a global through the GOT and resolves the slot to the address it holds, so what reaches a call is one dereference of the variable rather than two of the slot naming it. Which variable that is comes from the symbols already read for the table.

Parameters:

  • str (String)

    A rendered value.

Returns:

  • (Boolean)


52
53
54
55
56
57
# File 'lib/one_gadget/fetchers/mips.rb', line 52

def environ?(str)
  got = mips_got or return false
  offset = string_file_offset(str.delete('[]')) or return false

  ENVIRON.match?(got[:names][offset].to_s)
end

#executed_windows(lines) {|window| ... } ⇒ void

This method returns an undefined value.

A candidate may begin at a delay slot -- entering there runs it and falls past the transfer it belongs to -- but it may not then follow that transfer, which never executed. Such a window shows it by its second line not being the next instruction along; entering one instruction earlier, at the transfer itself, is the separate and valid window that does follow it.

Parameters:

  • lines (Array<String>)

    One candidate, as a line list.

Yield Parameters:

  • window (Array<String>)


86
87
88
89
90
91
92
93
# File 'lib/one_gadget/fetchers/mips.rb', line 86

def executed_windows(lines)
  super do |window|
    next if follows_a_transfer_it_skipped?(window) || enters_at_an_unset_call?(window)
    next if calls_without_the_callee_in_t9?(window)

    yield(window)
  end
end

#got_preconditions(processor, got) ⇒ Array<String>

What the caller must arrange for this window to reach the GOT. Normally just the register itself -- but o32 has the caller restore it after every call, because the callee establishes its own, so a window that runs past a call reads the table through whatever it restored from. Every call it makes after that point was named on the assumption that this is the GOT, so say so rather than leaving it unsaid.

Examples:

a window that restores gp from its frame

got_preconditions(processor, 'gp is the GOT address of libc')
#=> ['gp is the GOT address of libc', '[sp+0x18] is the GOT address of libc']

Parameters:

Returns:

  • (Array<String>)


71
72
73
74
75
76
# File 'lib/one_gadget/fetchers/mips.rb', line 71

def got_preconditions(processor, got)
  held = processor.registers[GOT_BASE].to_s
  return [got] if held == GOT_BASE

  [got, "#{held} is the GOT address of libc"]
end

#resolve(processor) ⇒ Hash?

Everything this arch reaches -- its calls and its globals alike -- goes through the GOT base in gp, which is a precondition the caller arranges by setting that register. Say so, and drop the read/write requirements rooted there: the GOT is a fixed, mapped libc address, so reaching through it asks nothing further of the caller (as i386 does for its own GOT register).

Parameters:

Returns:

  • (Hash, nil)


35
36
37
38
39
40
41
42
43
44
# File 'lib/one_gadget/fetchers/mips.rb', line 35

def resolve(processor)
  res = super
  return if res.nil?

  got = got_base_constraint(processor, GOT_BASE) or return nil

  res[:constraints].unshift(*got_preconditions(processor, got))
  res[:constraints].reject! { |con| con.match?(/\A(?:writable|readable): \[*#{GOT_BASE}\b/) }
  res
end