Class: ELFTools::ELFFile

Inherits:
Object
  • Object
show all
Defined in:
lib/elftools/elf_file.rb

Overview

The main class for using elftools.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stream) ⇒ ELFFile

Instantiate an ELFTools::ELFFile object.

Examples:

ELFFile.new(File.open('/bin/cat'))
#=> #<ELFTools::ELFFile:0x00564b106c32a0 @elf_class=64, @endian=:little, @stream=#<File:/bin/cat>>

Parameters:

  • stream (#pos=, #read)

    The File object to be fetch information from.



24
25
26
27
28
29
# File 'lib/elftools/elf_file.rb', line 24

def initialize(stream)
  @stream = stream
  # always set binmode if stream is an IO object.
  @stream.binmode if @stream.respond_to?(:binmode)
  identify # fetch the most basic information
end

Instance Attribute Details

#elf_classInteger (readonly)

Returns 32 or 64.

Returns:

  • (Integer)

    32 or 64.



14
15
16
# File 'lib/elftools/elf_file.rb', line 14

def elf_class
  @elf_class
end

#endianSymbol (readonly)

Returns :little or :big.

Returns:

  • (Symbol)

    :little or :big.



15
16
17
# File 'lib/elftools/elf_file.rb', line 15

def endian
  @endian
end

#stream#pos=, #read (readonly)

Returns The File object.

Returns:

  • (#pos=, #read)

    The File object.



13
14
15
# File 'lib/elftools/elf_file.rb', line 13

def stream
  @stream
end

Instance Method Details

#build_idString?

Return the BuildID of ELF.

Examples:

elf.build_id
#=> '73ab62cb7bc9959ce053c2b711322158708cdc07'

Returns:

  • (String, nil)

    BuildID in hex form will be returned. nil is returned if the .note.gnu.build-id section is not found.



52
53
54
55
56
57
58
59
60
# File 'lib/elftools/elf_file.rb', line 52

def build_id
  section = section_by_name('.note.gnu.build-id')
  return nil if section.nil?

  note = section.notes.first
  return nil if note.nil?

  note.desc.unpack1('H*')
end

#dynamicELFTools::Segments::DynamicSegment, ...

The dynamic tags of this file, read from the view that its type makes authoritative.

A relocatable file is linked by its sections, so its sections answer and any segment it carries is disregarded, being something no linker reads. An executable or a shared object is loaded by its segments alone, so the segment answers and the section recording the same tags is metadata a tool may have stripped or rewritten.

Examples:

elf.dynamic.tag_by_type(:needed).name
#=> 'libc.so.6'

Returns:



75
76
77
78
79
# File 'lib/elftools/elf_file.rb', line 75

def dynamic
  return sections_by_type(:dynamic).first if header.e_type.to_i == Constants::ET_REL

  segment_by_type(:dynamic) || sections_by_type(:dynamic).first
end

#each_section {|section| ... } ⇒ Enumerator<ELFTools::Sections::Section>, Array<ELFTools::Sections::Section> Also known as: each_sections

Iterate all sections.

All sections are lazy loading, the section only be created whenever accessing it. This method is useful for #section_by_name since not all sections need to be created.

Yield Parameters:

Yield Returns:

  • (void)

Returns:



141
142
143
144
145
146
147
# File 'lib/elftools/elf_file.rb', line 141

def each_section(&block)
  return enum_for(:each_section) unless block_given?

  Array.new(num_sections) do |i|
    section_at(i).tap(&block)
  end
end

#each_segment {|segment| ... } ⇒ Array<ELFTools::Segments::Segment> Also known as: each_segments

Iterate all segments.

All segments are lazy loading, the segment only be created whenever accessing it. This method is useful for #segment_by_type since not all segments need to be created.

Yield Parameters:

Yield Returns:

  • (void)

Returns:



221
222
223
224
225
226
227
# File 'lib/elftools/elf_file.rb', line 221

def each_segment(&block)
  return enum_for(:each_segment) unless block_given?

  Array.new(num_segments) do |i|
    segment_at(i).tap(&block)
  end
end

#elf_typeString

Return the ELF type according to e_type.

Examples:

ELFFile.new(File.open('spec/files/libc.so.6')).elf_type
#=> 'DYN'
ELFFile.new(File.open('spec/files/amd64.elf')).elf_type
#=> 'EXEC'

Returns:

  • (String)

    Type in string format.



101
102
103
# File 'lib/elftools/elf_file.rb', line 101

def elf_type
  ELFTools::Constants::ET.mapping(header.e_type)
end

#headerELFTools::Structs::ELF_Ehdr

Return the file header.

Lazy loading.

Returns:



35
36
37
38
39
40
41
42
# File 'lib/elftools/elf_file.rb', line 35

def header
  return @header if defined?(@header)

  stream.pos = 0
  @header = Structs::ELF_Ehdr.new(endian:, offset: stream.pos)
  @header.elf_class = elf_class
  @header.read(stream)
end

#machineString

Get machine architecture.

Mappings of architecture can be found in Constants::EM.mapping.

Examples:

elf.machine
#=> 'Advanced Micro Devices X86-64 processor'

Returns:

  • (String)

    Name of architecture.



90
91
92
# File 'lib/elftools/elf_file.rb', line 90

def machine
  ELFTools::Constants::EM.mapping(header.e_machine)
end

#num_sectionsInteger

Number of sections in this file.

Examples:

elf.num_sections
#=> 29

Returns:

  • (Integer)

    The desired number.



112
113
114
# File 'lib/elftools/elf_file.rb', line 112

def num_sections
  header.e_shnum
end

#num_segmentsInteger

Number of segments in this file.

Returns:

  • (Integer)

    The desited number.



207
208
209
# File 'lib/elftools/elf_file.rb', line 207

def num_segments
  header.e_phnum
end

#offset_from_vma(vma, size = 1) ⇒ Integer?

Get the offset related to file, given virtual memory address.

This method should work no matter ELF is a PIE or not. This method refers from (actually equals to) binutils/readelf.c#offset_from_vma.

Examples:

elf = ELFTools::ELFFile.new(File.open('/bin/cat'))
elf.offset_from_vma(0x401337)
#=> 4919 # 0x1337

Parameters:

  • vma (Integer)

    The virtual address to be queried.

Returns:

  • (Integer?)

    Related file offset. nil if the queried region has no content in file.



322
323
324
325
326
327
328
# File 'lib/elftools/elf_file.rb', line 322

def offset_from_vma(vma, size = 1)
  segments_by_type(:load) do |seg|
    return seg.vma_to_offset(vma) if seg.vma_in?(vma, size)
  end

  nil
end

#patchesHash{Integer => String}

The patch status.

Returns:

  • (Hash{Integer => String})


349
350
351
352
353
354
355
356
357
# File 'lib/elftools/elf_file.rb', line 349

def patches
  patch = {}
  loaded_headers.each do |header|
    header.patches.each do |key, val|
      patch[key + header.offset] = val
    end
  end
  patch
end

#save(filename) ⇒ void

This method returns an undefined value.

Apply patches and save as filename.

Parameters:

  • filename (String)


363
364
365
366
367
368
369
370
# File 'lib/elftools/elf_file.rb', line 363

def save(filename)
  stream.pos = 0
  all = stream.read.force_encoding('ascii-8bit')
  patches.each do |pos, val|
    all[pos, val.size] = val
  end
  File.binwrite(filename, all)
end

#section_at(n) ⇒ ELFTools::Sections::Section?

Acquire the +n+-th section, 0-based.

Sections are lazy loaded.

Parameters:

  • n (Integer)

    The index.

Returns:



166
167
168
169
# File 'lib/elftools/elf_file.rb', line 166

def section_at(n)
  @sections ||= LazyArray.new(num_sections, &method(:create_section))
  @sections[n]
end

#section_by_name(name) ⇒ ELFTools::Sections::Section?

Acquire the section named as name.

Examples:

elf.section_by_name('.note.gnu.build-id')
#=> #<ELFTools::Sections::Section:0x005647b1282428>
elf.section_by_name('')
#=> #<ELFTools::Sections::NullSection:0x005647b11da110>
elf.section_by_name('no such section')
#=> nil

Parameters:

  • name (String)

    The desired section name.

Returns:



126
127
128
# File 'lib/elftools/elf_file.rb', line 126

def section_by_name(name)
  each_section.find { |sec| sec.name == name }
end

#section_name_tableELFTools::Sections::StrTabSection

The section the names of the sections are recorded in, which the ELF header names by index.

It is not the section the names of the symbols are recorded in, which Sections::SymTabSection#symstr answers.

Examples:

elf.section_name_table.name
#=> '.shstrtab'

Returns:



199
200
201
# File 'lib/elftools/elf_file.rb', line 199

def section_name_table
  section_at(header.e_shstrndx)
end

#sectionsArray<ELFTools::Sections::Section>

Simply use #sections to get all sections.

Returns:



155
156
157
# File 'lib/elftools/elf_file.rb', line 155

def sections
  each_section.to_a
end

#sections_by_type(type) {|section| ... } ⇒ Array<ELFTools::Sections::section>

Fetch all sections with specific type.

The available types are listed in Constants::PT. This method accept giving block.

Examples:

elf = ELFTools::ELFFile.new(File.open('spec/files/amd64.elf'))
elf.sections_by_type(:rela)
#=> [#<ELFTools::Sections::RelocationSection:0x00563cd3219970>,
#    #<ELFTools::Sections::RelocationSection:0x00563cd3b89d70>]

Parameters:

  • type (Integer, Symbol, String)

    The type needed, similar format as #segment_by_type.

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (Array<ELFTools::Sections::section>)

    The target sections.



185
186
187
188
# File 'lib/elftools/elf_file.rb', line 185

def sections_by_type(type, &)
  type = Util.to_constant(Constants::SHT, type)
  Util.select_by_type(each_section, type, &)
end

#segment_at(n) ⇒ ELFTools::Segments::Segment?

Acquire the +n+-th segment, 0-based.

Segments are lazy loaded.

Parameters:

  • n (Integer)

    The index.

Returns:



307
308
309
310
# File 'lib/elftools/elf_file.rb', line 307

def segment_at(n)
  @segments ||= LazyArray.new(num_segments, &method(:create_segment))
  @segments[n]
end

#segment_by_type(type) ⇒ ELFTools::Segments::Segment

Note:

This method will return the first segment found, to found all segments with specific type you can use #segments_by_type.

Get the first segment with p_type=type. The available types are listed in Constants::PT.

Examples:

# type as an integer
elf.segment_by_type(ELFTools::Constants::PT_NOTE)
#=>  #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>

elf.segment_by_type(4) # PT_NOTE
#=>  #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>

# type as a symbol
elf.segment_by_type(:PT_NOTE)
#=>  #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>

# you can do this
elf.segment_by_type(:note) # will be transformed into `PT_NOTE`
#=>  #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>

# type as a string
elf.segment_by_type('PT_NOTE')
#=>  #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>

# this is ok
elf.segment_by_type('note') # will be transformed into `PT_NOTE`
#=>  #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
elf.segment_by_type(1337)
# ArgumentError: No constants in Constants::PT is 1337

elf.segment_by_type('oao')
# ArgumentError: No constants in Constants::PT named "PT_OAO"
elf.segment_by_type(0)
#=> nil # no such segment exists

Parameters:

  • type (Integer, Symbol, String)

    See examples for clear usage.

Returns:



280
281
282
283
# File 'lib/elftools/elf_file.rb', line 280

def segment_by_type(type)
  type = Util.to_constant(Constants::PT, type)
  each_segment.find { |seg| seg.header.p_type == type }
end

#segmentsArray<ELFTools::Segments::Segment>

Simply use #segments to get all segments.

Returns:



235
236
237
# File 'lib/elftools/elf_file.rb', line 235

def segments
  each_segment.to_a
end

#segments_by_type(type) {|segment| ... } ⇒ Array<ELFTools::Segments::Segment>

Fetch all segments with specific type.

If you want to find only one segment, use #segment_by_type instead. This method accept giving block.

Parameters:

  • type (Integer, Symbol, String)

    The type needed, same format as #segment_by_type.

Yield Parameters:

Yield Returns:

  • (void)

Returns:



295
296
297
298
# File 'lib/elftools/elf_file.rb', line 295

def segments_by_type(type, &)
  type = Util.to_constant(Constants::PT, type)
  Util.select_by_type(each_segment, type, &)
end

#vma_from_offset(offset, size = 1) ⇒ Integer?

Get virtual address given offset in file

Examples:

elf = ELFTools::ELFFile.new(File.open('/bin/cat'))
elf.vma_from_offset(0x1337)
#=> 0x401337

Parameters:

  • offset (Integer)

    The file offset to be queried.

Returns:

  • (Integer?)

    Related virtual address. Note this is raw address, not always adjusted for base address. nil if offset is invalid.



339
340
341
342
343
344
345
# File 'lib/elftools/elf_file.rb', line 339

def vma_from_offset(offset, size = 1)
  segments_by_type(:load) do |seg|
    return seg.offset_to_vma(offset) if seg.offset_in?(offset, size)
  end

  nil
end