elftools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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