elftools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,277 @@
1
+ require 'elftools/constants'
2
+ require 'elftools/exceptions'
3
+ require 'elftools/lazy_array'
4
+ require 'elftools/sections/sections'
5
+ require 'elftools/segments/segments'
6
+ require 'elftools/structures'
7
+
8
+ module ELFTools
9
+ # The main class for using elftools.
10
+ class ELFFile
11
+ attr_reader :stream # @return [File] The +File+ object.
12
+ attr_reader :elf_class # @return [Integer] 32 or 64.
13
+ attr_reader :endian # @return [Symbol] +:little+ or +:big+.
14
+
15
+ # Instantiate an {ELFFile} object.
16
+ #
17
+ # @param [File] stream
18
+ # The +File+ object to be fetch information from.
19
+ # @example
20
+ # ELFFile.new(File.open('/bin/cat'))
21
+ # #=> #<ELFTools::ELFFile:0x00564b106c32a0 @elf_class=64, @endian=:little, @stream=#<File:/bin/cat>>
22
+ def initialize(stream)
23
+ @stream = stream
24
+ identify # fetch the most basic information
25
+ end
26
+
27
+ # Return the file header.
28
+ #
29
+ # Lazy loading.
30
+ # @retrn [ELFTools::ELF_Ehdr] The header.
31
+ def header
32
+ return @header if @header
33
+ stream.pos = 0
34
+ @header = ELF_Ehdr.new(endian: endian)
35
+ @header.elf_class = elf_class
36
+ @header.read(stream)
37
+ end
38
+
39
+ # Return the BuildID of ELF.
40
+ # @return [String, NilClass]
41
+ # BuildID in hex form will be returned.
42
+ # +nil+ is returned if the .note.gnu.build-id section
43
+ # is not found.
44
+ # @example
45
+ # elf.build_id
46
+ # #=> '73ab62cb7bc9959ce053c2b711322158708cdc07'
47
+ def build_id
48
+ section = section_by_name('.note.gnu.build-id')
49
+ return nil if section.nil?
50
+ note = section.notes.first
51
+ return nil if note.nil?
52
+ note.desc.unpack('H*').first
53
+ end
54
+
55
+ # Get Machine architecture.
56
+ #
57
+ # Mappings of architecture can be found
58
+ # in {ELFTools::Constants::EM.mapping}.
59
+ # @return [String]
60
+ # Name of architecture.
61
+ # @example
62
+ # elf.machine
63
+ # #=> 'Advanced Micro Devices X86-64'
64
+ def machine
65
+ ELFTools::Constants::EM.mapping(header.e_machine)
66
+ end
67
+
68
+ #========= method about sections
69
+
70
+ # Number of sections in this file.
71
+ # @return [Integer] The desired number.
72
+ # @example
73
+ # elf.num_sections
74
+ # #=> 29
75
+ def num_sections
76
+ header.e_shnum
77
+ end
78
+
79
+ # Acquire the section named as +name+.
80
+ # @param [String] name The desired section name.
81
+ # @return [ELFTools::Sections::Section, NilClass] The target section.
82
+ # @example
83
+ # elf.section_by_name('.note.gnu.build-id')
84
+ # #=> #<ELFTools::Sections::Section:0x005647b1282428>
85
+ # elf.section_by_name('')
86
+ # #=> #<ELFTools::Sections::NullSection:0x005647b11da110>
87
+ # elf.section_by_name('no such section')
88
+ # #=> nil
89
+ def section_by_name(name)
90
+ @section_name_map ||= {}
91
+ return @section_name_map[name] if @section_name_map[name]
92
+ each_sections do |section|
93
+ @section_name_map[section.name] = section
94
+ return section if section.name == name
95
+ end
96
+ nil
97
+ end
98
+
99
+ # Iterate all sections.
100
+ #
101
+ # All sections are lazy loading, the section
102
+ # only be created whenever accessing it.
103
+ # This method is useful for {#section_by_name}
104
+ # since not all sections need to be created.
105
+ # @param [Block] block
106
+ # Just like +Array#each+, you can give a block.
107
+ # @return [ Array<ELFTools::Sections::Section>]
108
+ # The whole sections will be returned.
109
+ def each_sections
110
+ Array.new(num_sections) do |i|
111
+ sec = section_at(i)
112
+ block_given? ? yield(sec) : sec
113
+ end
114
+ end
115
+
116
+ # Simply use {#sections} without giving block to get all sections.
117
+ alias sections each_sections
118
+
119
+ # Acquire the +n+-th section, 0-based.
120
+ #
121
+ # Sections are lazy loaded.
122
+ # @param [Integer] n The index.
123
+ # @return [ELFTools::Sections::Section, NilClass]
124
+ # The target section.
125
+ # If +n+ is out of bound, +nil+ is returned.
126
+ def section_at(n)
127
+ @sections ||= LazyArray.new(num_sections, &method(:create_section))
128
+ @sections[n]
129
+ end
130
+
131
+ # Get the string table section.
132
+ #
133
+ # This section is acquired by using the +e_shstrndx+
134
+ # in ELF header.
135
+ # @return [ELFTools::Sections::StrTabSection] The desired section.
136
+ def strtab_section
137
+ section_at(header.e_shstrndx)
138
+ end
139
+
140
+ #========= method about segments
141
+
142
+ # Number of segments in this file.
143
+ # @return [Integer] The desited number.
144
+ def num_segments
145
+ header.e_phnum
146
+ end
147
+
148
+ # Iterate all segments.
149
+ #
150
+ # All segments are lazy loading, the segment
151
+ # only be created whenever accessing it.
152
+ # This method is useful for {#segment_by_type}
153
+ # since not all segments need to be created.
154
+ # @param [Block] block
155
+ # Just like +Array#each+, you can give a block.
156
+ # @return [Array<ELFTools::Segments::Segment>]
157
+ # Whole segments will be returned.
158
+ def each_segments
159
+ Array.new(num_segments) do |i|
160
+ seg = segment_at(i)
161
+ block_given? ? yield(seg) : seg
162
+ end
163
+ end
164
+
165
+ # Simply use {#segments} without giving block to get all segments.
166
+ alias segments each_segments
167
+
168
+ # Get the first segment with +p_type=type+.
169
+ # The available types are listed in {ELFTools::Constants},
170
+ # starts with +PT_+.
171
+ #
172
+ # Notice: this method will return the first segment found,
173
+ # to found all segments with specific type you can use {#segments_by_type}.
174
+ # @param [Integer, Symbol, String] type
175
+ # See examples for clear usage.
176
+ # @return [ELFTools::Segments::Segment] The target segment.
177
+ # @example
178
+ # # type as an integer
179
+ # elf.segment_by_type(ELFTools::Constants::PT_NOTE)
180
+ # #=> #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
181
+ #
182
+ # elf.segment_by_type(4) # PT_NOTE
183
+ # #=> #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
184
+ #
185
+ # # type as a symbol
186
+ # elf.segment_by_type(:PT_NOTE)
187
+ # #=> #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
188
+ #
189
+ # # you can do this
190
+ # elf.segment_by_type(:note) # will be transformed into `PT_NOTE`
191
+ # #=> #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
192
+ #
193
+ # # type as a string
194
+ # elf.segment_by_type('PT_NOTE')
195
+ # #=> #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
196
+ #
197
+ # # this is ok
198
+ # elf.segment_by_type('note') # will be tranformed into `PT_NOTE`
199
+ # #=> #<ELFTools::Segments::NoteSegment:0x005629dda1e4f8>
200
+ # @example
201
+ # elf.segment_by_type(1337)
202
+ # # ArgumentError: No constants in Constants::PT is 1337
203
+ #
204
+ # elf.segment_by_type('oao')
205
+ # # ArgumentError: No constants in Constants::PT named "PT_OAO"
206
+ # @example
207
+ # elf.segment_by_type(0)
208
+ # #=> nil # no such segment exists
209
+ def segment_by_type(type)
210
+ type = Util.to_constant(Constants::PT, type)
211
+ each_segments do |seg|
212
+ return seg if seg.header.p_type == type
213
+ end
214
+ nil
215
+ end
216
+
217
+ # Fetch all segments with specific type.
218
+ # If you want to find only one segment,
219
+ # use {#segment_by_type} instead.
220
+ # @param [Integer, Symbol, String] type
221
+ # The type needed, same format as {#segment_by_type}.
222
+ # @return [Array<ELFTools::Segments::Segment>] The target segments.
223
+ def segments_by_type(type)
224
+ type = Util.to_constant(Constants::PT, type)
225
+ segments.select { |segment| segment.header.p_type == type }
226
+ end
227
+
228
+ # Acquire the +n+-th segment, 0-based.
229
+ #
230
+ # Segments are lazy loaded.
231
+ # @param [Integer] n The index.
232
+ # @return [ELFTools::Segments::Segment, NilClass]
233
+ # The target segment.
234
+ # If +n+ is out of bound, +nil+ is returned.
235
+ def segment_at(n)
236
+ @segments ||= LazyArray.new(num_segments, &method(:create_segment))
237
+ @segments[n]
238
+ end
239
+
240
+ private
241
+
242
+ def identify
243
+ stream.pos = 0
244
+ magic = stream.read(4)
245
+ raise ELFError, "Invalid magic number #{magic.inspect}" unless magic == Constants::ELFMAG
246
+ ei_class = stream.read(1).ord
247
+ @elf_class = {
248
+ 1 => 32,
249
+ 2 => 64
250
+ }[ei_class]
251
+ raise ELFError, format('Invalid EI_CLASS "\x%02x"', ei_class) if elf_class.nil?
252
+ ei_data = stream.read(1).ord
253
+ @endian = {
254
+ 1 => :little,
255
+ 2 => :big
256
+ }[ei_data]
257
+ raise ELFError, format('Invalid EI_DATA "\x%02x"', ei_data) if endian.nil?
258
+ end
259
+
260
+ def create_section(n)
261
+ stream.pos = header.e_shoff + n * header.e_shentsize
262
+ shdr = ELF_Shdr.new(endian: endian)
263
+ shdr.elf_class = elf_class
264
+ shdr.read(stream)
265
+ Sections::Section.create(shdr, stream,
266
+ strtab: method(:strtab_section),
267
+ section_at: method(:section_at))
268
+ end
269
+
270
+ def create_segment(n)
271
+ stream.pos = header.e_phoff + n * header.e_phentsize
272
+ phdr = ELF_Phdr[elf_class].new(endian: endian)
273
+ phdr.elf_class = elf_class
274
+ Segments::Segment.create(phdr.read(stream), stream)
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,3 @@
1
+ module ELFTools
2
+ class ELFError < StandardError; end
3
+ end
@@ -0,0 +1,43 @@
1
+ module ELFTools
2
+ # A helper class for {ELFTools} easy to implement
3
+ # 'lazy loading' objects.
4
+ # Mainly used when loading sections, segments, and
5
+ # symbols.
6
+ class LazyArray
7
+ # Instantiate a {LazyArray} object.
8
+ # @param [Integer] size
9
+ # The size of array.
10
+ # @param [Block] block
11
+ # At first time accessing +i+-th element,
12
+ # block will be called as +block.call(i)+.
13
+ # @example
14
+ # arr = LazyArray.new(10) { |i| p i; i * i }
15
+ # p arr[2]
16
+ # # 2
17
+ # # 4
18
+ #
19
+ # p arr[3]
20
+ # # 3
21
+ # # 9
22
+ #
23
+ # p arr[3]
24
+ # # 9
25
+ def initialize(size, &block)
26
+ @internal = Array.new(size)
27
+ @block = block
28
+ end
29
+
30
+ # To access elements like a normal array.
31
+ #
32
+ # Elements are lazy loaded at the first time
33
+ # access it.
34
+ # @return [Object]
35
+ # The element, returned type is the
36
+ # return type of block given in {#initialize}.
37
+ def [](i)
38
+ # XXX: support negative index?
39
+ return nil if i < 0 || i >= @internal.size
40
+ @internal[i] ||= @block.call(i)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,112 @@
1
+ require 'elftools/structures'
2
+ require 'elftools/util'
3
+
4
+ module ELFTools
5
+ # Since both note sections and note segments
6
+ # refer to notes, this module defines common
7
+ # methods for {ELFTools::Sections::NoteSection} and {ELFTools::Segments::NoteSegment}.
8
+ #
9
+ # Notice: this module can only be included in {ELFTools::Sections::NoteSection} and
10
+ # {ELFTools::Segments::NoteSegment} since some methods assume some attributes already
11
+ # exist.
12
+ module Note
13
+ # Since size of {ELFTools::ELF_Nhdr} will not change no
14
+ # matter what endian and what arch, we can do this here.
15
+ # This value should equal to 12.
16
+ SIZE_OF_NHDR = ELF_Nhdr.new(endian: :little).num_bytes
17
+
18
+ # Iterate all notes in a note section or segment.
19
+ #
20
+ # Structure of notes are:
21
+ # +---------------+
22
+ # | Note 1 header |
23
+ # +---------------+
24
+ # | Note 1 name |
25
+ # +---------------+
26
+ # | Note 1 desc |
27
+ # +---------------+
28
+ # | Note 2 header |
29
+ # +---------------+
30
+ # | ... |
31
+ # +---------------+
32
+ #
33
+ # Notice: This method assume following methods exist:
34
+ # stream
35
+ # note_start
36
+ # note_total_size
37
+ # @return [Array<ELFTools::Note::Note>]
38
+ # The array of notes will be returned.
39
+ def each_notes
40
+ @notes_offset_map ||= {}
41
+ cur = note_start
42
+ notes = []
43
+ while cur < note_start + note_total_size
44
+ stream.pos = cur
45
+ @notes_offset_map[cur] ||= create_note(cur)
46
+ note = @notes_offset_map[cur]
47
+ # name and desc size needs to be 4-bytes align
48
+ name_size = Util.align(note.header.n_namesz, 2)
49
+ desc_size = Util.align(note.header.n_descsz, 2)
50
+ cur += SIZE_OF_NHDR + name_size + desc_size
51
+ notes << note
52
+ yield note if block_given?
53
+ end
54
+ notes
55
+ end
56
+
57
+ # Simply +#notes+ to get all notes.
58
+ alias notes each_notes
59
+
60
+ private
61
+
62
+ # Get the endian.
63
+ #
64
+ # Notice: This method assume method +header+ exists.
65
+ # @return [Symbol] +:little+ or +:big+.
66
+ def endian
67
+ header.class.self_endian
68
+ end
69
+
70
+ def create_note(cur)
71
+ nhdr = ELF_Nhdr.new(endian: endian).read(stream)
72
+ ELFTools::Note::Note.new(nhdr, stream, cur)
73
+ end
74
+
75
+ # Class of a note.
76
+ class Note
77
+ attr_reader :header # @return [ELFTools::ELF_Nhdr] Note header.
78
+ attr_reader :stream # @return [File] Streaming object.
79
+ attr_reader :offset # @return [Integer] Address of this note start, includes note header.
80
+
81
+ # Instantiate a {ELFTools::Note::Note} object.
82
+ # @param [ELF_Nhdr] header The note header.
83
+ # @param [File] stream Streaming object.
84
+ # @param [Integer] offset
85
+ # Start address of this note, includes the header.
86
+ def initialize(header, stream, offset)
87
+ @header = header
88
+ @stream = stream
89
+ @offset = offset
90
+ end
91
+
92
+ # Name of this note.
93
+ # @return [String] The name.
94
+ def name
95
+ return @name if @name
96
+ stream.pos = @offset + SIZE_OF_NHDR
97
+ @name = stream.read(header.n_namesz)[0..-2]
98
+ end
99
+
100
+ # Description of this note.
101
+ # @return [String] The description.
102
+ def desc
103
+ return @desc if @desc
104
+ stream.pos = @offset + SIZE_OF_NHDR + Util.align(header.n_namesz, 2)
105
+ @desc = stream.read(header.n_descsz)
106
+ end
107
+
108
+ # If someone likes to use full name.
109
+ alias description desc
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,20 @@
1
+ require 'elftools/dynamic'
2
+ require 'elftools/sections/section'
3
+
4
+ module ELFTools
5
+ module Sections
6
+ # Class for dynamic table section.
7
+ #
8
+ # This section should always be named .dynamic.
9
+ # This class knows how to get the list of dynamic tags.
10
+ class DynamicSection < Section
11
+ include ELFTools::Dynamic
12
+
13
+ # Get the start address of tags.
14
+ # @return [Integer] Start address of tags.
15
+ def tag_start
16
+ header.sh_offset
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ require 'elftools/note'
2
+ require 'elftools/sections/section'
3
+
4
+ module ELFTools
5
+ module Sections
6
+ # Class of note section.
7
+ # Note section records notes
8
+ class NoteSection < Section
9
+ # Load note related methods.
10
+ include ELFTools::Note
11
+
12
+ # Address offset of notes start.
13
+ # @return [Integer] The offset.
14
+ def note_start
15
+ header.sh_offset
16
+ end
17
+
18
+ # The total size of notes in this section.
19
+ # @return [Integer] The size.
20
+ def note_total_size
21
+ header.sh_size
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ require 'elftools/sections/section'
2
+
3
+ module ELFTools
4
+ module Sections
5
+ # Class of null section.
6
+ # Null section is for specific the end
7
+ # of linked list (+sh_link+) between sections.
8
+ class NullSection < Section
9
+ # Is this a null section?
10
+ # @return [Boolean] Yes it is.
11
+ def null?
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ require 'elftools/constants'
2
+ module ELFTools
3
+ module Sections
4
+ # Base class of sections.
5
+ class Section
6
+ attr_reader :header # @return [ELFTools::ELF_Shdr] Section header.
7
+ attr_reader :stream # @return [File] Streaming object.
8
+
9
+ # Instantiate a {Section} object.
10
+ # @param [ELFTools::ELF_Shdr] header
11
+ # The section header object.
12
+ # @param [File] stream
13
+ # The streaming object for further dump.
14
+ # @param [ELFTools::Sections::StrTabSection, Proc] strtab
15
+ # The string table object. For fetching section names.
16
+ # If +Proc+ if given, it will call at the first
17
+ # time access +#name+.
18
+ def initialize(header, stream, strtab: nil, **_kwagrs)
19
+ @header = header
20
+ @stream = stream
21
+ @strtab = strtab
22
+ end
23
+
24
+ # Get name of this section.
25
+ # @return [String] The name.
26
+ def name
27
+ @name ||= @strtab.call.name_at(header.sh_name)
28
+ end
29
+
30
+ # Fetch data of this section.
31
+ # @return [String] Data.
32
+ def data
33
+ stream.pos = header.sh_offset
34
+ stream.read(header.sh_size)
35
+ end
36
+
37
+ # Is this a null section?
38
+ # @return [Boolean] No it's not.
39
+ def null?
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # Require this file to load all sections classes.
2
+
3
+ require 'elftools/sections/section'
4
+
5
+ require 'elftools/sections/dynamic_section'
6
+ require 'elftools/sections/note_section'
7
+ require 'elftools/sections/null_section'
8
+ require 'elftools/sections/str_tab_section'
9
+ require 'elftools/sections/sym_tab_section'
10
+
11
+ module ELFTools
12
+ # Defines different types of sections in this module.
13
+ module Sections
14
+ # Class methods of {Sections::Section}.
15
+ class << Section
16
+ # Use different class according to +header.sh_type+.
17
+ # @param [ELFTools::ELF_Shdr] header Section header.
18
+ # @param [File] stream Streaming object.
19
+ # @return [ELFTools::Sections::Section]
20
+ # Return object dependes on +header.sh_type+.
21
+ def create(header, stream, *args)
22
+ klass = case header.sh_type
23
+ when Constants::SHT_DYNAMIC then DynamicSection
24
+ when Constants::SHT_NULL then NullSection
25
+ when Constants::SHT_NOTE then NoteSection
26
+ when Constants::SHT_STRTAB then StrTabSection
27
+ when Constants::SHT_SYMTAB, Constants::SHT_DYNSYM then SymTabSection
28
+ else Section
29
+ end
30
+ klass.new(header, stream, *args)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ require 'elftools/sections/section'
2
+
3
+ module ELFTools
4
+ module Sections
5
+ # Class of string table section.
6
+ # Usually for section .strtab and .dynstr,
7
+ # which record names.
8
+ class StrTabSection < Section
9
+ # Return the section or symbol name.
10
+ # @param [Integer] offset
11
+ # Usually from +shdr.sh_name+ or +sym.st_name+.
12
+ # @return [String] The name without null bytes.
13
+ def name_at(offset)
14
+ stream.pos = header.sh_offset + offset
15
+ # read until "\x00"
16
+ ret = ''
17
+ loop do
18
+ c = stream.read(1)
19
+ return nil if c.nil? # reach EOF
20
+ break if c == "\x00"
21
+ ret += c
22
+ end
23
+ ret
24
+ end
25
+ end
26
+ end
27
+ end