Class: OneGadget::Emulators::Arm

Inherits:
ArmFamily show all
Defined in:
lib/one_gadget/emulators/arm.rb

Overview

Emulator of 32-bit ARM (both A32 and Thumb-2 encodings).

Constant Summary collapse

FLAG_SETTING =

The flag-setting spelling of an instruction we model, which differs from the base mnemonic only by a trailing s (+movs+, ands, ...). The flags it sets are not modelled, so a branch reading them aborts the path anyway; what matters here is the value it also writes. Conditional variants (+moveq+, addne, ...) are deliberately absent and stay unsupported.

/\A(mov|add|sub|and|orr|eor|bic|mvn|lsl|lsr)s\z/

Constants inherited from ArmFamily

OneGadget::Emulators::ArmFamily::COMPARES, OneGadget::Emulators::ArmFamily::COND

Constants inherited from Processor

Processor::TERMINAL_CALL_RE

Constants included from Constraints

Constraints::ADDRESS_TYPES, Constraints::CLOBBERED, Constraints::NULLABLE_REQUIREMENTS, Constraints::POINTER_REQUIREMENTS

Constants included from Conditional

Conditional::COMPARE_OPS, Conditional::NEGATE, Conditional::RELATION, Conditional::ZERO

Instance Attribute Summary

Attributes inherited from Processor

#bp, #pc, #refused_line, #registers, #sp

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Processor

instruction_table, line_memo, #parse, #process, #reach_terminal_call, #terminal_call?

Methods included from TrackedMemory

#bp_based_stack, #get_corresponding_stack, #resolve_address, #setup_frame_pointer, #sp_based_stack, #writes_through

Methods included from Constraints

#address_deref0?, #closed_fds, #constraint_key, #constraints, #drop_implied_nonzero, #drop_restated_null, #render_constraint

Methods included from Conditional

#branch_on_bit, #branch_on_compare, #branch_on_zero, #comparisons_on, #handle_compare, #mnemonic, #operand_str, #record_compare, #resolve_pending_branch, #satisfiable?, #value_str

Constructor Details

#initialize(file = nil) ⇒ Arm

Instantiate an OneGadget::Emulators::Arm object.

Parameters:

  • file (String, nil) (defaults to: nil)

    Path to the target libc. Used to read words from the literal pool when resolving PC-relative ldr loads. May be nil in unit tests that don't exercise literal loads.



19
20
21
22
23
24
25
26
27
28
# File 'lib/one_gadget/emulators/arm.rb', line 19

def initialize(file = nil)
  super(OneGadget::ABI.arm, 'sp')
  @pc = 'pc'
  # find() builds a fresh emulator per candidate; cache the file's bytes so
  # the literal pool isn't re-read from disk thousands of times.
  @data = file && self.class.file_data(file)
  @prev_addr = nil
  # A32 until proven Thumb by a +.w+/+.n+ suffix or a 2-byte instruction stride.
  @thumb = false
end

Class Method Details

.bitsInteger

ARM (32-bit) is 32-bit.

Returns:

  • (Integer)


309
310
311
# File 'lib/one_gadget/emulators/arm.rb', line 309

def bits
  32
end

.file_data(file) ⇒ String

Memoized bytes of file (the target libc), shared across emulator instances.

Parameters:

  • file (String)

    Path to the target libc.

Returns:

  • (String)

    Its bytes.



33
34
35
# File 'lib/one_gadget/emulators/arm.rb', line 33

def self.file_data(file)
  (@file_data ||= {})[file] ||= File.binread(file)
end

Instance Method Details

#argument(idx) ⇒ Lambda, Integer

Return the argument value of calling a function.

Parameters:

  • idx (Integer)

    The 0-based index of the argument.

Returns:

  • (Lambda, Integer)

    AAPCS passes the first four arguments in +r0+-+r3+; any further arguments are on the stack at [sp], +[sp+4]+, ... (needed for 6-argument calls such as posix_spawn).



101
102
103
104
105
# File 'lib/one_gadget/emulators/arm.rb', line 101

def argument(idx)
  return registers["r#{idx}"] if idx < 4

  sp_based_stack[(idx - 4) * size_t]
end

#instructionsArray<Instruction>

Supported instruction set. Any instruction not listed here aborts the current gadget candidate (mirrors the conservative aarch64 emulator).

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/one_gadget/emulators/arm.rb', line 65

def instructions
  [
    Instruction.new('push', 1),
    Instruction.new('pop', 1),
    Instruction.new('add', 2..3),
    Instruction.new('sub', 2..3),
    Instruction.new('mov', 2),
    Instruction.new('ldr', 2..3),
    Instruction.new('ldrb', 2..3),
    Instruction.new('str', 2..3),
    Instruction.new('bl', 1),
    Instruction.new('blx', 1),
    Instruction.new('nop', 0..1),
    Instruction.new('dmb', 0..1),
    Instruction.new('dsb', 0..1),
    Instruction.new('isb', 0..1),
    Instruction.new('and', 2..3),
    Instruction.new('orr', 2..3),
    Instruction.new('eor', 2..3),
    Instruction.new('bic', 2..3),
    Instruction.new('mvn', 2),
    Instruction.new('lsl', 2..3),
    Instruction.new('lsr', 2..3),
    Instruction.new('cmp', 2..3),
    Instruction.new('cmn', 2..3),
    Instruction.new('tst', 2..3),
    Instruction.new('svc', 1)
  ]
end

#note_instruction_set(lines) ⇒ void

This method returns an undefined value.

Settle Thumb vs A32 from a whole candidate, before any of it is emulated. #track_mode learns only from lines already seen, so the first instruction would otherwise be judged on no evidence -- and when it reads pc, that decides an address the constraints go on to name.

Examples:

A 2-byte stride settles it as Thumb, where pc reads four ahead

of the instruction rather than eight.
note_instruction_set(['4a1c0: ldr r0, [pc, #8]', '4a1c2: add r0, pc'])
process('4a1c2: mov r0, pc')
registers['r0'] #=> $base+0x4a1c6

Parameters:

  • lines (Array<String>)

    The candidate's objdump lines.



118
119
120
121
122
123
124
# File 'lib/one_gadget/emulators/arm.rb', line 118

def note_instruction_set(lines)
  return if @thumb

  addrs = lines.filter_map { |l| l[/\A\s*([0-9a-f]+):/, 1]&.to_i(16) }
  @thumb = lines.any? { |l| l.match?(/\.[wn]\b/) } ||
           addrs.each_cons(2).any? { |a, b| b - a == 2 }
end

#process!(cmd) ⇒ Boolean

Returns If successfully processed.

Parameters:

  • cmd (String)

    One line from result of objdump.

Returns:

  • (Boolean)

    If successfully processed.

See Also:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/one_gadget/emulators/arm.rb', line 40

def process!(cmd)
  resolve_pending_branch(cmd)
  line = cmd.strip
  track_mode(line)
  body, @literal = decompose(line)
  mnem, rest = body.split(/\s+/, 2)
  return handle_compare(COMPARES[mnem], rest) if COMPARES.key?(mnem)
  return handle_branch(mnem, rest) != :fail if branch_mnem?(mnem)
  # push/pop take a {reg-list} whose commas would confuse the generic parser.
  return __send__(:"inst_#{mnem}", rest) != :fail if %w[push pop].include?(mnem)

  inst, args = parse(body)
  __send__(inst.handler, *args) != :fail
end