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:



148
149
150
151
152
153
154
# File 'lib/elftools/elf_file.rb', line 148

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:



238
239
240
241
242
243
244
# File 'lib/elftools/elf_file.rb', line 238

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.

A file with more sections than the ELF header can count records a zero there and states the number in the first section header instead, which a file with no section headers at all records as well.

Examples:

elf.num_sections
#=> 29

Returns:

  • (Integer)

    The desired number.



116
117
118
119
120
121
# File 'lib/elftools/elf_file.rb', line 116

def num_sections
  count = header.e_shnum.to_i
  return count unless count.zero? && !header.e_shoff.to_i.zero?

  first_section_header.sh_size.to_i
end

#num_segmentsInteger

Number of segments in this file.

A file with more segments than the ELF header can count states the number in the first section header instead, as it does for the sections.

Returns:

  • (Integer)

    The desited number.



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

def num_segments
  count = header.e_phnum.to_i
  return count unless count == Constants::PN_XNUM

  first_section_header.sh_info.to_i
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.



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

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})


366
367
368
369
370
371
372
373
374
# File 'lib/elftools/elf_file.rb', line 366

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)


380
381
382
383
384
385
386
387
# File 'lib/elftools/elf_file.rb', line 380

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:



173
174
175
176
# File 'lib/elftools/elf_file.rb', line 173

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:



133
134
135
# File 'lib/elftools/elf_file.rb', line 133

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:



206
207
208
209
210
211
212
# File 'lib/elftools/elf_file.rb', line 206

def section_name_table
  index = header.e_shstrndx.to_i
  # An index too large for the ELF header is stated in the first section
  # header instead.
  index = first_section_header.sh_link.to_i if index == Constants::SHN_XINDEX
  section_at(index)
end

#sectionsArray<ELFTools::Sections::Section>

Simply use #sections to get all sections.

Returns:



162
163
164
# File 'lib/elftools/elf_file.rb', line 162

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.



192
193
194
195
# File 'lib/elftools/elf_file.rb', line 192

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:



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

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:



297
298
299
300
# File 'lib/elftools/elf_file.rb', line 297

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:



252
253
254
# File 'lib/elftools/elf_file.rb', line 252

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:



312
313
314
315
# File 'lib/elftools/elf_file.rb', line 312

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.



356
357
358
359
360
361
362
# File 'lib/elftools/elf_file.rb', line 356

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