Class: EmergeCLI::MachOParser

Inherits:
Object
  • Object
show all
Defined in:
lib/utils/macho_parser.rb

Constant Summary collapse

TYPE_METADATA_KIND_MASK =
0x7 << 3
TYPE_METADATA_KIND_SHIFT =
3
BIND_OPCODE_MASK =

Bind Codes

0xF0
BIND_IMMEDIATE_MASK =
0x0F
BIND_OPCODE_DONE =
0x00
BIND_OPCODE_SET_DYLIB_ORDINAL_IMM =
0x10
BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB =
0x20
BIND_OPCODE_SET_DYLIB_SPECIAL_IMM =
0x30
BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM =
0x40
BIND_OPCODE_SET_TYPE_IMM =
0x50
BIND_OPCODE_SET_ADDEND_SLEB =
0x60
BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB =
0x70
BIND_OPCODE_ADD_ADDR_ULEB =
0x80
BIND_OPCODE_DO_BIND =
0x90
BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB =
0xA0
BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED =
0xB0
BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB =
0xC0
UINT64_SIZE =
8
UINT64_MAX_VALUE =
0xFFFFFFFFFFFFFFFF

Instance Method Summary collapse

Instance Method Details

#find_protocols_in_swift_proto(use_chained_fixups, imported_symbols, bound_symbols, search_symbols) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/utils/macho_parser.rb', line 118

def find_protocols_in_swift_proto(use_chained_fixups, imported_symbols, bound_symbols, search_symbols)
  found_section = nil
  @macho_file.segments.each do |segment|
    segment.sections.each do |section|
      if section.segname.strip == '__TEXT' && section.sectname.strip == '__swift5_proto'
        found_section = section
        break
      end
    end
  end

  unless found_section
    Logger.error 'The __swift5_proto section was not found.'
    return false
  end

  start = found_section.offset
  size = found_section.size
  offsets_list = parse_list(@binary_data, start, size)

  offsets_list.each do |relative_offset, offset_start|
    type_file_address = offset_start + relative_offset
    if type_file_address <= 0 || type_file_address >= @binary_data.size
      Logger.error 'Invalid protocol conformance offset'
      next
    end

    # ProtocolConformanceDescriptor -> ProtocolDescriptor
    protocol_descriptor = read_little_endian_signed_integer(@binary_data, type_file_address)

    # # ProtocolConformanceDescriptor -> ConformanceFlags
    conformance_flags = read_little_endian_signed_integer(@binary_data, type_file_address + 12)
    kind = (conformance_flags & TYPE_METADATA_KIND_MASK) >> TYPE_METADATA_KIND_SHIFT

    next unless kind == 0

    indirect_relative_offset = get_indirect_relative_offset(type_file_address, protocol_descriptor)

    bound_symbol = bound_symbols.find { |symbol| symbol.address == indirect_relative_offset }
    if bound_symbol
      return true if search_symbols.include?(bound_symbol.symbol)
    elsif use_chained_fixups
      descriptor_offset = protocol_descriptor & ~1
      jump_ptr = type_file_address + descriptor_offset

      address = @binary_data[jump_ptr, 4].unpack1('I<')
      symbol_name = imported_symbols[address]
      return true if search_symbols.include?(symbol_name)
    end
  end
  false
end

#load_binary(binary_path) ⇒ Object



28
29
30
31
# File 'lib/utils/macho_parser.rb', line 28

def load_binary(binary_path)
  @macho_file = MachO::MachOFile.new(binary_path)
  @binary_data = File.binread(binary_path)
end

#read_dyld_info_only_commandObject



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/utils/macho_parser.rb', line 79

def read_dyld_info_only_command
  dyld_info_only_command = nil
  @macho_file.load_commands.each do |lc|
    dyld_info_only_command = lc if lc.type == :LC_DYLD_INFO_ONLY
  end

  if dyld_info_only_command.nil?
    Logger.debug 'No LC_DYLD_INFO_ONLY found'
    return []
  end

  bound_symbols = []
  start_address = dyld_info_only_command.bind_off
  end_address = dyld_info_only_command.bind_off + dyld_info_only_command.bind_size
  current_address = start_address

  current_symbol = BoundSymbol.new(segment_offset: 0, library: nil, offset: 0, symbol: '')
  while current_address < end_address
    results, current_address, current_symbol = read_next_symbol(@binary_data, current_address, end_address,
                                                                current_symbol)

    # Dup items to avoid pointer issues
    results.each do |res|
      bound_symbols << res.dup
    end
  end

  # Filter only swift symbols starting with _$s
  swift_symbols = bound_symbols.select { |bound_symbol| bound_symbol.symbol.start_with?('_$s') }

  load_commands = @macho_file.load_commands.select { |lc| lc.type == :LC_SEGMENT_64 || lc.type == :LC_SEGMENT } # rubocop:disable Naming/VariableNumber

  swift_symbols.each do |swift_symbol|
    swift_symbol.address = load_commands[swift_symbol.segment_offset].vmaddr + swift_symbol.offset
  end

  swift_symbols
end

#read_linkedit_data_commandObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/utils/macho_parser.rb', line 33

def read_linkedit_data_command
  chained_fixups_command = nil
  @macho_file.load_commands.each do |lc|
    chained_fixups_command = lc if lc.type == :LC_DYLD_CHAINED_FIXUPS
  end

  if chained_fixups_command.nil?
    Logger.debug 'No LC_DYLD_CHAINED_FIXUPS found'
    return false, []
  end

  # linkedit_data_command
  _, _, dataoff, datasize = @binary_data[chained_fixups_command.offset, 16].unpack('L<L<L<L<')

  header = @binary_data[dataoff, datasize].unpack('L<L<L<L<L<L<L<')
  # dyld_chained_fixups_header
  _, _, imports_offset, symbols_offset, imports_count,
    imports_format, = header

  imports_start = dataoff + imports_offset
  symbols_start = dataoff + symbols_offset

  imported_symbols = []

  import_size, name_offset_proc =
    case imports_format
    when 1, nil # DYLD_CHAINED_IMPORT
      [4, ->(ptr) { ptr.unpack1('L<') >> 9 }]
    when 2 # DYLD_CHAINED_IMPORT_ADDEND
      [8, ->(ptr) { ptr.unpack1('L<') >> 9 }]
    when 3 # DYLD_CHAINED_IMPORT_ADDEND64
      [16, ->(ptr) { ptr.unpack1('Q<') >> 32 }]
    end

  # Extract imported symbol names
  imports_count.times do |i|
    import_offset = imports_start + (i * import_size)
    name_offset = name_offset_proc.call(@binary_data[import_offset, import_size])
    name_start = symbols_start + name_offset
    name = read_null_terminated_string(@binary_data[name_start..])
    imported_symbols << name
  end

  [true, imported_symbols]
end