elf-mithril 0.0.1

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.
File without changes
@@ -0,0 +1,335 @@
1
+
2
+ require 'bindata'
3
+ require_relative 'elf_enums'
4
+ module ElfStructs
5
+ class ElfIdentification < BinData::Record
6
+ endian :little
7
+ string :id_magic, :length=>4 , :initial_value => "\x7FELF" #TODO: validate?
8
+ uint8 :id_class
9
+ uint8 :id_data
10
+ uint8 :id_version
11
+ skip :length => 9
12
+ end
13
+
14
+ def alias_recordtype(from,to)
15
+ self.class.class_eval do
16
+ define_method from do |*args|
17
+ send(to,*args)
18
+ end
19
+ end
20
+ end
21
+ def hdr
22
+ elf_identification :ident
23
+ #end of identification
24
+ half :type #File type.
25
+ half :machine #Machine architecture.
26
+ word :version #ELF format version.
27
+ addr :entry #Entry point.
28
+ off :phoff #Program header file offset.
29
+ off :shoff #Section header file offset.
30
+ word :flags #Architecture-specific flags.
31
+ half :ehsize #Size of ELF header in bytes.
32
+ half :phentsize #Size of program header entry.
33
+ half :phnum #Number of program header entries.
34
+ half :shentsize #Size of section header entry.
35
+ half :shnum #Number of section header entries.
36
+ half :shstrndx #Section name strings section.
37
+ end
38
+
39
+ def note
40
+ uint32 :namesz, :value => lambda{name.num_bytes} #Length of name.
41
+ uint32 :descsz, :value => lambda{desc.num_bytes} # Length of descriptor.
42
+ uint32 :type # Type of this note.
43
+ string :name, :read_length => lambda{ (namesz.to_i * 4 + 3)/4 } # Round up
44
+ string :desc, :read_length => lambda{ (descsz.to_i * 4 + 3)/4 }
45
+ attr_accessor :section_name
46
+ end
47
+
48
+ def shdr
49
+ word :name #Section name (index into section header string table
50
+ word :type #Section type.
51
+ xword :flags #Section flags.
52
+ addr :vaddr #Address in memory image.
53
+ off :off #Offset in file.
54
+ xword :siz#Size in bytes. Patch: Had to change to siz
55
+ word :link #Index of a related section.
56
+ word :info #Depends on section type.
57
+ xword :addralign #Alignment in bytes.
58
+ xword :entsize #Size of each entry in section.
59
+
60
+
61
+ attr_accessor :index # So we retain the index after parsing
62
+ end
63
+ def phdr32 # FAIL different layout with 32 and 64
64
+ word :type #Entry type.
65
+ off :off #File offset of contents.
66
+ addr :vaddr #Virtual address in memory image.
67
+ addr :paddr #Physical address (not used).
68
+ xword :filesz #Size of contents in file.
69
+ xword :memsz #Size of contents in memory.
70
+ word :flags #Access permission flags.
71
+ xword :align #Alignment in memory and file.
72
+ end
73
+ def phdr64
74
+ word :type
75
+ word :flags
76
+ off :off
77
+ addr :vaddr
78
+ addr :paddr
79
+ xword :filesz
80
+ xword :memsz
81
+ xword :align
82
+ end
83
+ # Dynamic structure. The ".dynamic" section contains an array of them.
84
+ def dyn
85
+ sxword :tag #Entry type.
86
+ addr :val #Address value or raw value
87
+ end
88
+ # * Relocation entries
89
+ # Relocations that don't need an addend field. */
90
+ def rel_common
91
+ case @bits
92
+ when 32
93
+ define_method :sym do
94
+ info.to_i >> 8
95
+ end
96
+ define_method :type do
97
+ info.to_i & 0xff
98
+ end
99
+ define_method :sym= do |val|
100
+ self.info = type | (val << 8)
101
+ end
102
+ define_method :type= do |val|
103
+ self.info = (val &0xff) | (sym << 8)
104
+ end
105
+ when 64
106
+ define_method :sym do
107
+ info.to_i >> 32
108
+ end
109
+ define_method :type do
110
+ info.to_i & 0xffffffff
111
+ end
112
+ define_method :sym= do |val|
113
+ self.info = type | (val << 32)
114
+ end
115
+ define_method :type= do |val|
116
+ self.info = (val &0xffffffff) | (sym << 32)
117
+ end
118
+ end
119
+ end
120
+ def rel
121
+ addr :off #Location to be relocated.
122
+ xword :info #Relocation type and symbol index.
123
+ define_method :addend do
124
+ nil
125
+ end
126
+ rel_common
127
+ end
128
+
129
+ # Relocations that need an addend field. */
130
+ def rela
131
+ addr :off #Location to be relocated.
132
+ xword :info #Relocation type and symbol index.
133
+ sxword :addend #Addend.
134
+ rel_common
135
+ end
136
+
137
+ #Elf Symbol
138
+ def sym_common
139
+ define_method :type do
140
+ info & 0xf
141
+ end
142
+ define_method :binding do
143
+ info >> 4
144
+ end
145
+ define_method :type= do |val|
146
+ raise RuntimeError.new "Invalid param" unless val & 0xf == val
147
+ self.info = (info&0xf0) | val
148
+ end
149
+ define_method :binding= do |val|
150
+ raise RuntimeError.new "Invalid param" unless val & 0xf == val
151
+ self.info = (info&0xf) | (val << 4)
152
+ end
153
+ end
154
+ def sym32
155
+ word :name #String table index of name.
156
+ addr :val #Symbol value. PATCH: change to val so as to avoid name conflict
157
+ word :siz #Size of associated object. PATCH: Change to val
158
+ char :info #Type and binding information.
159
+ char :other #Reserved (not used).
160
+ half :shndx #Section index of symbol.
161
+ sym_common
162
+ end
163
+ def sym64
164
+ word :name
165
+ uint8 :info
166
+ uint8 :other
167
+ half :shndx
168
+ addr :val
169
+ xword :siz
170
+ sym_common
171
+ end
172
+ def versym
173
+ half :veridx
174
+ end
175
+ def verdaux
176
+ word :name
177
+ word :nextoff
178
+ end
179
+ def verdef
180
+ half :version
181
+ half :flags
182
+ half :ndx
183
+ half :cnt
184
+ word :hsh
185
+ word :aux
186
+ word :nextoff
187
+ end
188
+ def verneed
189
+ half :version
190
+ half :cnt
191
+ word :file
192
+ word :aux
193
+ word :nextoff
194
+ end
195
+ def vernaux
196
+ word :hsh
197
+ half :flags
198
+ half :other
199
+ word :name
200
+ word :nextoff
201
+ end
202
+ def stringtable
203
+ array :strings, :type => :stringz, :initial_length=> 0
204
+ end
205
+ #ELFP redesign - in progress
206
+ =begin
207
+ def elfp_chunk_desc # Each chunk of ELFbac metadata is also a section
208
+ uint32 :type
209
+ uint32 :offset
210
+ uint32 :size
211
+ end
212
+ def elfp_header
213
+ uint32 :sectioncount, value: lambda { sectionheaders.length }
214
+ array :sectionheaders, type: @@factory.elfp_chunk_desc, read_length: initia
215
+ end
216
+ =end
217
+ #ELFP old
218
+ def elfp_header
219
+ uint32 :chunkcount, value: lambda {states.size + tags.size+ calls.size + data.size}
220
+ array :states, type: @factory.elfp_state
221
+ array :tags, type: @factory.elfp_tag
222
+ array :calls, type: @factory.elfp_call
223
+ array :data, type: @factory.elfp_data
224
+ end
225
+ def elfp_state
226
+ uint32 :chunkid , value: 1
227
+ uint32 :id
228
+ uint32 :stackid, default_value: 0
229
+ end
230
+ def elfp_call
231
+ uint32 :chunktype, value: 2
232
+ uint32 :from
233
+ uint32 :to
234
+ uint64 :off
235
+ uint16 :parambytes
236
+ uint16 :returnbytes
237
+ end
238
+ def elfp_data
239
+ uint32 :chunktype, value: 3
240
+ uint32 :from
241
+ uint32 :to
242
+ uint64 :tag
243
+ uint32 :type
244
+ end
245
+ def elfp_tag
246
+ uint32 :chunktype, value:4
247
+ uint64 :tag
248
+ uint64 :addr
249
+ uint64 :siz
250
+ end
251
+ ELF_OBJECTS = [:sym, :rela, :rel, :dyn, :phdr, :shdr, :hdr, :note, :vernaux, :verneed, :verdef, :verdaux, :versym, :elfp_state, :elfp_call, :elfp_data, :elfp_tag, :elfp_header]
252
+ Split = {
253
+ phdr: {
254
+ 32 => :phdr32,
255
+ 64 => :phdr64
256
+ },
257
+ sym: {
258
+ 32 => :sym32,
259
+ 64 => :sym64
260
+ }
261
+ }
262
+ def bitness(bits)
263
+ @bits = bits
264
+ alias_recordtype :char, :uint8
265
+ case bits
266
+ when 32
267
+ alias_recordtype :word, :uint32
268
+ alias_recordtype :sword, :int32
269
+ alias_recordtype :half, :uint16
270
+ alias_recordtype :off, :uint32
271
+ alias_recordtype :addr, :uint32
272
+ alias_recordtype :xword, :uint32
273
+ alias_recordtype :sxword, :uint32
274
+ when 64
275
+ alias_recordtype :addr, :uint64
276
+ alias_recordtype :off, :uint64
277
+ alias_recordtype :half, :uint16
278
+ alias_recordtype :word, :uint32
279
+ alias_recordtype :sword, :int32
280
+ alias_recordtype :xword, :uint64
281
+ alias_recordtype :sxword, :int64
282
+ else
283
+ raise RuntimeError.new "We only know about 32-bit or 64-bit ELF formats, not about #{bits}"
284
+ end
285
+ end
286
+ end
287
+ class ElfStructFactory
288
+ attr_reader *ElfStructs::ELF_OBJECTS
289
+ def self.instance(endian, bits)
290
+ @@instances[endian][bits]
291
+ end
292
+ def rel_info_sym(info)
293
+ if @width == 32
294
+ info.to_i >> 8
295
+ else
296
+ info.to_i >> 32
297
+ end
298
+ end
299
+ def rel_info_type(info)
300
+ if @width == 32
301
+ info.to_i & 0xff
302
+ else
303
+ info.to_i & 0xffffffff
304
+ end
305
+ end
306
+ def rel_build_info(sym,type)
307
+ if @width == 32
308
+ (sym.to_i << 8) | (type.to_i &0xff)
309
+ else
310
+ (sym.to_i << 32) | (type.to_i &0xffffffff)
311
+ end
312
+ end
313
+
314
+ def initialize(endian,width)
315
+ @endian = endian
316
+ @width = width
317
+ factory = self
318
+ ElfStructs::ELF_OBJECTS.each do |object|
319
+ klass = Class.new BinData::Record do
320
+ extend ElfStructs
321
+ endian endian
322
+ bitness width
323
+ @factory = factory
324
+ if(ElfStructs::Split.include? object)
325
+ self.send(ElfStructs::Split[object][width])
326
+ else
327
+ self.send(object)
328
+ end
329
+ end
330
+ instance_variable_set("@#{object.to_s}",klass)
331
+ end
332
+ end
333
+ @@instances = {:little => {32 => ElfStructFactory.new(:little,32), 64=>ElfStructFactory.new(:little,64) },
334
+ :big => {32 => ElfStructFactory.new(:big,32) , 64 => ElfStructFactory.new(:big,64)} }
335
+ end
@@ -0,0 +1,22 @@
1
+ module Elf::Policy
2
+ def self.inject_symbols(file)
3
+ case file.filetype
4
+ when ElfFlags::Type::ET_EXEC
5
+ filename = ""
6
+ when ElfFlags::Type::ET_DYN
7
+ filename = file.dynamic.soname
8
+ else
9
+ raise RuntimeError.new "section_symbols works only for ET_DYN and ET_EXEC files"
10
+ end
11
+ (file.progbits + file.nobits).each{|section|
12
+ name = Elf::Policy::section_symbol_name(filename,section.name)
13
+ next if file.symbols.include? name
14
+ sym = Elf::Symbol.new(name,section,ElfFlags::SymbolType::STT_OBJECT,0,ElfFlags::SymbolBinding::STB_GLOBAL,section.size) # STT_SECTION is ignored for lookup!
15
+ # print "Injecting #{name}"
16
+ sym.gnu_version = :global
17
+ sym.hidden = false
18
+ sym.is_dynamic = true
19
+ file.symbols << sym
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,706 @@
1
+ require_relative 'elf'
2
+
3
+ $UNSAFE_PARSER = ! ENV['HOWDY_NEIGHBOURS_BX'].nil? # shoutout to one
4
+ # of the best ELF hackers on the block.
5
+
6
+ def expect_value(desc,is,should)
7
+ unless $UNSAFE_PARSER
8
+ raise RuntimeError.new "Invalid #{desc}, expected #{should} instead of #{is}" if is != should
9
+ end
10
+ end
11
+
12
+ module Elf
13
+ module Parser
14
+ class StringTable
15
+ def initialize(data)
16
+ expect_value "First byte of string table", data.bytes.first, 0
17
+ @data = StringIO.new(data)
18
+ end
19
+ def [](offset)
20
+ @data.seek offset
21
+ obj = BinData::Stringz.new
22
+ obj.read(@data)
23
+ obj.snapshot
24
+ end
25
+ end
26
+ class NilStringTable
27
+ def [](offset)
28
+ return ""
29
+ end
30
+ end
31
+ class Parser
32
+ attr_reader :file
33
+ def initialize(string)
34
+ @data = StringIO.new(string)
35
+ @file = ElfFile.new
36
+ ident = ElfStructs::ElfIdentification.read(@data)
37
+ print ident.snapshot.inspect
38
+ raise RuntimeError.new "Invalid ELF version #{ident.id_version}" if ident.id_version != ElfFlags::Version::EV_CURRENT
39
+ case ident.id_class
40
+ when ElfFlags::IdentClass::ELFCLASS64
41
+ @file.bits = 64
42
+ when ElfFlags::IdentClass::ELFCLASS32
43
+ @file.bits = 32
44
+ else
45
+ RuntimeError.new "Invalid ELF class #{ident.id_class}"
46
+ end
47
+ case ident.id_data
48
+ when ElfFlags::IdentData::ELFDATA2LSB
49
+ @file.endian = :little
50
+ when ElfFlags::IdentData::ELFDATA2MSB
51
+ @file.endian = :big
52
+ else
53
+ RuntimeError.new "Invalid ELF endianness #{ident.id_data}"
54
+ end
55
+ @versions = {}
56
+ @reladyn_indices = []
57
+ @factory = ElfStructFactory.instance(@file.endian,@file.bits)
58
+ parse_with_factory()
59
+ end
60
+
61
+ private
62
+ def unique_section(sects, type)
63
+ if sects.include? type
64
+ expect_value "Number of #{type} sections", sects[type].size, 1
65
+ return sects[type].first
66
+ else
67
+ return nil
68
+ end
69
+ end
70
+ def safe_strtab(index)
71
+ if(index ==0)
72
+ NilStringTable.new()
73
+ else
74
+ hdr = @shdrs[index]
75
+ expect_value "STRTAB type", hdr.type, ElfFlags::SectionType::SHT_STRTAB
76
+ @data.seek hdr.off
77
+ @unparsed_sections.delete index
78
+ StringTable.new(@data.read(hdr.siz))
79
+ end
80
+ end
81
+ def parse_symtable(sect,strtab)
82
+ return [] if sect.nil?
83
+ expect_value "Size of symbol table entry", @factory.sym.new.num_bytes, sect.entsize
84
+ @data.seek sect.off
85
+ @unparsed_sections.delete sect.index
86
+ BinData::Array.new( :type=> @factory.sym, :initial_length => sect.siz / sect.entsize).read(@data).map do |sym|
87
+ #TODO: find appropriate section
88
+ unless @bits_by_index.include? sym.shndx.to_i
89
+ section = nil
90
+ value = sym.val.to_i
91
+ else
92
+ section = @bits_by_index[sym.shndx.to_i]
93
+ if([ET::ET_EXEC, ET::ET_DYN].include? @file.filetype)
94
+ value = sym.val.to_i - section.addr #TODO: Only if absolute.
95
+ else
96
+ value = sym.val.to_i
97
+ end
98
+ section = ".interp" if section.name == ".interp" #HACK: this should not be the case
99
+ # expect_value "Section index #{sym.shndx.to_i} in symbol should be in progbits", false, section.nil?
100
+ end
101
+ x= Symbol.new(strtab[sym.name],section, sym.type.to_i, value, sym.binding.to_i, sym.siz.to_i)
102
+ if(x.name.empty? and x.type == STT::STT_SECTION)
103
+ x.name = @shstrtab[@shdrs[sym.shndx.to_i].name]
104
+ end
105
+ x.visibility = sym.other.to_i & 0x3
106
+ if [SHN::SHN_ABS, SHN::SHN_COMMON, SHN::SHN_UNDEF, SHN::SHN_XINDEX].include? sym.shndx.to_i
107
+ x.semantics = sym.shndx.to_i
108
+ else
109
+ x.semantics = nil
110
+ end
111
+ x
112
+ end
113
+ end
114
+ def parse_progbits(shdr)
115
+ @data.seek shdr.off
116
+ @unparsed_sections.delete shdr.index
117
+ expect_value "PROGBITS link",shdr.link,0
118
+ if shdr.type.to_i == SHT::SHT_NOBITS
119
+ NoBits.new(@shstrtab[shdr.name],shdr.snapshot)
120
+ else
121
+ ProgBits.new(@shstrtab[shdr.name], shdr.snapshot, @data.read(shdr.siz))
122
+ end
123
+ end
124
+ def parse_verdef(shdr)
125
+ @unparsed_sections.delete shdr.index
126
+ strtab = safe_strtab(shdr.link)
127
+ verdefoff = shdr.off
128
+ file = @file.dynamic.soname
129
+ versions = {}
130
+ parents = {}
131
+ shdr.info.to_i.times {
132
+ @data.seek verdefoff
133
+ verdef = @factory.verdef.read(@data)
134
+ expect_value "VERDEF version", verdef.version, 1
135
+ verdauxoff = verdefoff + verdef.aux
136
+ aux = []
137
+ verdef.cnt.to_i.times {
138
+ @data.seek verdauxoff
139
+ verdaux = @factory.verdaux.read(@data)
140
+ aux << strtab[verdaux.name]
141
+ verdauxoff += verdaux.nextoff.to_i
142
+ # expect_value "Nonzero verdaux.nextoff #{verdaux.nextoff}", true,verdaux.nextoff != 0 unless
143
+ }
144
+ name = aux.first
145
+ par = aux[1..-1] || []
146
+ expect_value "Name present", false, name.nil?
147
+ parents[name] = par
148
+ # expect_value "Version #{verdef.idx} unique", false, @versions.include? verdef.idx.to_i
149
+ @versions[verdef.ndx.to_i] = GnuVersion.new(file,name,verdef.flags.to_i,false)
150
+ versions[name] = @versions[verdef.ndx.to_i]
151
+ # expect_value "Nonzero verdef.nextoff #{verdef.nextoff}", true,verdef.nextoff != 0
152
+ verdefoff += verdef.nextoff.to_i
153
+ }
154
+ parents.each{|version,p|
155
+ p.each{|x|
156
+ # expect_value "Valid parent version #{x}", true, versions.include? x
157
+ versions[version].parents << versions[x]
158
+ }
159
+ }
160
+ if @versions.include? 1
161
+ @file.dynamic.gnu_version_basename = @versions[1].version
162
+ end
163
+ end
164
+ def parse_verneed(shdr)
165
+ @unparsed_sections.delete shdr.index
166
+ strtab = safe_strtab(shdr.link)
167
+ @data.seek shdr.off
168
+ data = StringIO.new(@data.read(shdr.siz.to_i))
169
+ # This is the weird, screwed up 'array', that actually is a
170
+ # linked list
171
+ verneedoff = 0
172
+ shdr.info.to_i.times{ #SHDR.info has number of entries
173
+ data.seek verneedoff
174
+ verneed = @factory.verneed.read(data)
175
+ expect_value "VERNEED version", verneed.version, 1
176
+ file = strtab[verneed.file]
177
+ vernauxoff = verneedoff + verneed.aux
178
+
179
+ verneed.cnt.times {
180
+ data.seek vernauxoff
181
+
182
+ vernaux = @factory.vernaux.read(data)
183
+ versionname = strtab[vernaux.name]
184
+ flags = vernaux.flags.to_i
185
+ version = vernaux.other.to_i
186
+ # expect_value "Nonzero vernaux.nextoff #{vernaux.nextoff}", true,vernaux.nextoff != 0
187
+
188
+ @versions[version] = GnuVersion.new( file, versionname, flags,true)
189
+ vernauxoff += vernaux.nextoff
190
+ }
191
+ # expect_value "Nonzero verneedoff ",true, verneed.nextoff != 0
192
+ verneedoff += verneed.nextoff
193
+ }
194
+ end
195
+ VERSYM_HIDDEN = 0x8000
196
+ VERSYM_IDX_MASK = 0xffff & ~VERSYM_HIDDEN
197
+ def parse_versym(shdr,dynsym)
198
+ @data.seek shdr.off
199
+ data = StringIO.new(@data.read(shdr.siz.to_i))
200
+ BinData::Array.new(type: @factory.versym, initial_length: shdr.siz / @factory.versym.new.num_bytes).read(data).to_enum.with_index {|versym,index|
201
+ veridx = versym.veridx & VERSYM_IDX_MASK
202
+ dynsym[index].gnu_version =case veridx
203
+ when 0
204
+ :local
205
+ when 1
206
+ :global
207
+ else
208
+ unless @versions.include? veridx
209
+ raise RuntimeError.new "Invalid veridx #{versym.veridx} in dynamic symbol #{index}"
210
+ end
211
+ @versions[veridx]
212
+ end
213
+ dynsym[index].hidden = (versym.veridx & VERSYM_HIDDEN != 0)
214
+ }
215
+ end
216
+ DYNAMIC_FLAGS = {
217
+ DT::DT_TEXTREL=>:@textrel,
218
+ DT::DT_BIND_NOW => :@bind_now,
219
+ DT::DT_SYMBOLIC => :@symbolic
220
+ }
221
+ def parse_rel_common(relocations,sect_idx,symtab_idx, uses_addresses,is_jmprel)
222
+ case @shdrs[symtab_idx].type.to_i
223
+ when SHT::SHT_DYNSYM
224
+ symtab= @dynsym
225
+ when SHT::SHT_SYMTAB
226
+ symtab= @symtab
227
+ else
228
+ raise ArgumentError.new "Invalid link field #{symtab_idx} in relocation section"
229
+ end
230
+
231
+ if sect_idx == 0 and uses_addresses
232
+ applies_to = nil
233
+ else
234
+ applies_to = @bits_by_index[sect_idx]
235
+ raise ArgumentError.new "Section index #{sect_idx} not referring to PROGBITS for relocation table" if applies_to.nil?
236
+ end
237
+
238
+ relocations.map {|rel_entry|
239
+ Relocation.new.tap { |rel|
240
+ if uses_addresses
241
+ rel.section = @relocatable_sections.find(rel_entry.off.to_i).andand(&:value)
242
+ print "Warning: Invalid relocation address 0x#{rel_entry.off.snapshot.to_s(16)}\n" unless rel.section
243
+ rel.offset = rel_entry.off.to_i - rel.section.addr
244
+ else
245
+ rel.section = applies_to
246
+ rel.offset = rel_entry.off.to_i
247
+ end
248
+ rel.type = rel_entry.type
249
+ if rel_entry.sym == 0
250
+ rel.symbol = nil
251
+ else
252
+ rel.symbol = @canonical_symbol[symtab[ rel_entry.sym]] or raise(RuntimeError.new("Symbol could not be canonicalized"))
253
+ end
254
+ rel.addend = rel_entry.addend.to_i
255
+ rel.is_lazy = is_jmprel
256
+ }
257
+ }
258
+ end
259
+ def is_jmprel(shdr)
260
+ @jmprel_addr == shdr.vaddr
261
+ end
262
+ def parse_rela(shdr,has_addrs)
263
+ @unparsed_sections.delete shdr.index
264
+ @data.seek shdr.off
265
+ expect_value "RELA entsize", shdr.entsize, @factory.rela.new.num_bytes
266
+ rela = BinData::Array.new(:type => @factory.rela, :initial_length => shdr.siz/shdr.entsize).read(@data)
267
+ parse_rel_common(rela,shdr.info, shdr.link,has_addrs,is_jmprel(shdr))
268
+ end
269
+ def parse_rel(shdr,has_addrs)
270
+ @unparsed_sections.delete shdr.index
271
+ @data.seek shdr.off
272
+ expect_value "REL entsize", shdr.entsize, @factory.rel.new.num_bytes
273
+ rela = BinData::Array.new(:type => @factory.rel, :initial_length => shdr.siz/shdr.entsize).read(@data)
274
+ parse_rel_common(rela,shdr.info, shdr.link,has_addrs,is_jmprel(shdr))
275
+ end
276
+ def parse_dynamic(shdr)
277
+ retval = Dynamic.new
278
+
279
+ @data.seek shdr.off
280
+ #TODO: find unused dynamic entries
281
+ expect_value "Size of dynamic entry", @factory.dyn.new.num_bytes, shdr.entsize
282
+ dynamic = BinData::Array.new(:type=> @factory.dyn, :initial_length => shdr.siz/ shdr.entsize).read(@data)
283
+ @unparsed_sections.delete shdr.index
284
+ by_type = dynamic.group_by {|x| x.tag.to_i}
285
+ expect_unique = lambda do |sym,optional| # Validates that either one
286
+ # or zero entries of this type exist, returning the one entry
287
+ # if it exists
288
+ if(by_type.include? sym)
289
+ expect_value "Dynamic entry #{sym} count", by_type[sym].size,1
290
+ by_type[sym].first
291
+ else
292
+ if(optional)
293
+ nil
294
+ else
295
+ raise ArgumentError.new "Missing mandatory dynamic entry #{sym}"
296
+ end
297
+ end
298
+ end
299
+
300
+ expect_value "DT_NULL", dynamic.last, @factory.dyn.new()
301
+
302
+ by_type.delete DT::DT_NULL
303
+
304
+ expect_unique.call DT::DT_STRTAB,false
305
+ expect_unique.call DT::DT_STRSZ,false #TODO: check that this a strtab and get a strtab
306
+ strtab_hdr= @sect_types[SHT::SHT_STRTAB].group_by(&:vaddr)[by_type[DT::DT_STRTAB].first.val].andand(&:first)
307
+
308
+ expect_value "Some STRTAB section should be mapped at DT_STRTAB", strtab_hdr.nil?,false
309
+ by_type.delete DT::DT_STRTAB
310
+ expect_value "STRSZ", by_type[DT::DT_STRSZ].first.val, strtab_hdr.siz
311
+ by_type.delete DT::DT_STRSZ
312
+ @dynstr = safe_strtab(strtab_hdr.index)
313
+
314
+ expect_unique.call DT::DT_SYMENT, false
315
+ expect_value "Dynamic SYMENT",by_type[DT::DT_SYMENT].first.val, @factory.sym.new.num_bytes
316
+ by_type.delete DT::DT_SYMENT
317
+ expect_unique.call DT::DT_SYMTAB, false
318
+ expect_value "Dynamic symbol table needs to be mapped", by_type[DT::DT_SYMTAB].first.val,@sect_types[SHT::SHT_DYNSYM].first.vaddr
319
+ by_type.delete DT::DT_SYMTAB
320
+ expect_unique.call DT::DT_HASH, true# We totally ignore the hash
321
+ by_type.delete DT::DT_HASH
322
+ expect_unique.call DT::DT_GNU_HASH,true
323
+ by_type.delete DT::DT_GNU_HASH
324
+
325
+ retval.needed = []
326
+ by_type[DT::DT_NEEDED].andand.each do |needed|
327
+ retval.needed << @dynstr[needed.val]
328
+ end
329
+ by_type.delete DT::DT_NEEDED
330
+
331
+ DYNAMIC_FLAGS.each do |tag, var|
332
+ val = false
333
+ expect_unique.call(tag,true) { |x| val = true}
334
+ instance_variable_set(var,val)
335
+ by_type.delete tag
336
+ end
337
+
338
+
339
+ progbits_by_addr = (@progbits+@nobits).group_by(&:addr) #TODO: check
340
+ #that vaddrs don't overlap
341
+
342
+ expect_unique.call(DT::DT_INIT,true).andand { |init|
343
+ # expect_value "DT_INIT should point to a valid progbits section",
344
+ # progbits_by_addr.include?(init.val), true
345
+
346
+ retval.init = init.val # progbits_by_addr[init.val].first
347
+ }
348
+ by_type.delete DT::DT_INIT
349
+ expect_unique.call(DT::DT_FINI,true).andand { |init|
350
+ # expect_value "DT_FINI should point to a valid progbits section",
351
+ # progbits_by_addr.include?(init.val), true
352
+
353
+ retval.fini = init.val #progbits_by_addr[init.val].first
354
+ }
355
+ by_type.delete DT::DT_FINI
356
+
357
+ expect_unique.call(DT::DT_INIT_ARRAY,true).andand{|initarray|
358
+ expect_value "DT_INITARRAY needs to point to a section", true, progbits_by_addr.include?(initarray.val)
359
+ sect = progbits_by_addr[initarray.val].first
360
+ expect_value "DT_INITARRAY section type", SHT::SHT_INIT_ARRAY,sect.sect_type
361
+ retval.init_array = sect
362
+ by_type.delete DT::DT_INIT_ARRAYSZ
363
+ }
364
+ by_type.delete DT::DT_INIT_ARRAY
365
+ expect_unique.call(DT::DT_FINI_ARRAY,true).andand{|finiarray|
366
+ expect_value "DT_FINIARRAY needs to point to a section", true, progbits_by_addr.include?(finiarray.val)
367
+ sect = progbits_by_addr[finiarray.val].first
368
+ expect_value "DT_FINIARRAY section type", SHT::SHT_FINI_ARRAY,sect.sect_type
369
+ retval.fini_array = sect
370
+ by_type.delete DT::DT_FINI_ARRAYSZ
371
+ }
372
+ by_type.delete DT::DT_FINI_ARRAY
373
+ expect_unique.call(DT::DT_PLTGOT,true).andand { |init|
374
+ expect_value "DT_PLTGOT should point to a valid progbits section",
375
+ progbits_by_addr.include?(init.val), true
376
+
377
+ retval.pltgot = progbits_by_addr[init.val].first
378
+ }#TODO: check processor supplements
379
+ by_type.delete DT::DT_PLTGOT
380
+ expect_unique.call(DT::DT_SONAME,true).andand {|soname|
381
+ retval.soname = @dynstr[soname.val]
382
+ }
383
+ by_type.delete DT::DT_SONAME
384
+ expect_unique.call(DT::DT_RPATH,true).andand{|rpath|
385
+ retval.rpath = @dynstr[rpath.val]
386
+ }
387
+ by_type.delete DT::DT_RPATH
388
+ #TODO: write 'expect_group'
389
+ expect_unique.call(DT::DT_RELA,true).andand{ |rela|
390
+ x= @sect_types[SHT::SHT_RELA].group_by{|x| x.vaddr.to_i}
391
+ expect_value "DT_RELA should point to a valid relocation section", x.include?(rela.val), true
392
+ #assert that no overlap?
393
+ reladyn_hdr = x[rela.val].first #TODO: Use parsed relocations!
394
+ expect_unique.call(DT::DT_RELAENT,false).andand {|relaent|
395
+ expect_value "DT_RELAENT size", relaent.val, @factory.rela.new.num_bytes
396
+ }
397
+ expect_unique.call(DT::DT_RELASZ,false).andand {|relasz|
398
+ expect_value "DT_RELASZ", relasz.val, reladyn_hdr.siz
399
+ }
400
+ # expect_unique.call(DT::DT_RELACOUNT,false).andand{|relacount|
401
+ # expect_value "DT_RELACOUNT", reladyn_hdr.siz / @factory.rela.new.num_bytes, relacount.val
402
+ # }
403
+ #RELACOUNT is a different beast
404
+ @reladyn_indices << reladyn_hdr.index
405
+ }
406
+ #TODO: maybe use eval to delete duplication?
407
+ expect_unique.call(DT::DT_REL,true).andand{ |rela|
408
+ x= @sect_types[SHT::SHT_REL].group_by{|x| x.vaddr.to_i}
409
+ expect_value "DT_REL should point to a valid relocation section", x.include?(rela.val), true
410
+ reladyn_hdr = x[rela.val] #TODO: Use parsed relocations!
411
+ expect_unique.call(DT::DT_RELENT,false).andand {|relaent|
412
+ expect_value "DT_RELENT size", relaent.val, @factory.rela.new.num_bytes
413
+ }
414
+ expect_unique.call(DT::DT_RELSZ,false).andand {|relasz|
415
+ expect_value "DT_RELSZ", relasz.val, reladyn_hdr.siz
416
+ }
417
+ @reladyn_indices << reladyn_hdr.index
418
+ }
419
+ [DT::DT_RELA, DT::DT_RELAENT, DT::DT_RELASZ, DT::DT_REL, DT::DT_RELENT, DT::DT_RELSZ, DT::DT_RELACOUNT].each {|x| by_type.delete x}
420
+ expect_unique.call(DT::DT_VERSYM,true).andand{|versym|
421
+ x = @sect_types[SHT::SHT_GNU_VERSYM].first
422
+ expect_value "One versym", @sect_types[SHT::SHT_GNU_VERSYM].size, 1
423
+ expect_value "SHT_GNU_VERSYM points to versym", x.vaddr == versym.val.to_i, true
424
+ }
425
+ by_type.delete DT::DT_VERSYM
426
+ expect_unique.call(DT::DT_VERDEF,true).andand{|verdef|
427
+ expect_value "Only one verdef", @sect_types[SHT::SHT_GNU_VERDEF].size, 1
428
+ x= @sect_types[SHT::SHT_GNU_VERDEF].first
429
+ expect_value "DT_VERDEF points to a SHT_GNU_VERDEF", x.vaddr.to_i, verdef.val.to_i
430
+ expect_unique.call(DT::DT_VERDEFNUM,false).andand{|verdefnum|
431
+ expect_value "DT_VERDEFNUM",x.info,verdefnum.val.to_i
432
+ }
433
+ }
434
+ expect_unique.call(DT::DT_VERNEED,true).andand{|verdef|
435
+ expect_value "Only one verdef", @sect_types[SHT::SHT_GNU_VERNEED].size, 1
436
+ x= @sect_types[SHT::SHT_GNU_VERNEED].first
437
+ expect_value "DT_VERNEED points to a SHT_GNU_VERNEED", x.vaddr.to_i, verdef.val.to_i
438
+ expect_unique.call(DT::DT_VERNEEDNUM,true).andand{|verdefnum|
439
+ expect_value "DT_VERNEEDNUM",x.info,verdefnum.val.to_i
440
+ }
441
+ }
442
+ [DT::DT_VERDEFNUM,DT::DT_VERNEEDNUM,DT::DT_VERNEED,DT::DT_VERDEF].each{|x| by_type.delete x}
443
+ #Parse RELA.plt or REL.plt
444
+ expect_unique.call(DT::DT_FLAGS,true).andand{|flags|
445
+ retval.flags = flags.val.to_i
446
+ }
447
+ expect_unique.call(DT::DT_FLAGS_1,true).andand{|flags1|
448
+ retval.flags1 = flags1.val.to_i
449
+ }
450
+ by_type.delete DT::DT_FLAGS
451
+ by_type.delete DT::DT_FLAGS_1
452
+ expect_unique.call(DT::DT_JMPREL,true).andand{ |rela| #TODO:Make
453
+ #this better too!!!
454
+ @jmprel_addr = rela.val
455
+ expect_unique.call(DT::DT_PLTREL,false).andand {|pltrel|
456
+ if pltrel.val == DT::DT_RELA
457
+ type = SHT::SHT_RELA
458
+ elsif pltrel.val == DT::DT_REL
459
+ type = SHT::SHT_REL
460
+ else
461
+ raise ArgumentError.new "Invalid DT_PLTREL"
462
+ end
463
+ x= @sect_types[type].group_by{|x| x.vaddr.to_i}
464
+ expect_value "DT_PLREL should point to a valid relocation section", x.include?(rela.val), true
465
+ reladyn_hdr = x[rela.val].first
466
+ #TODO: Use parsed #relocations!
467
+ expect_unique.call(DT::DT_PLTRELSZ,false).andand {|relasz|
468
+ expect_value "DT_PLTRELSZ", relasz.val, reladyn_hdr.siz
469
+ }
470
+ @reladyn_indices << reladyn_hdr.index
471
+ by_type.delete DT::DT_PLTRELSZ
472
+ }
473
+ by_type.delete DT::DT_PLTREL
474
+ }
475
+ by_type.delete DT::DT_JMPREL
476
+
477
+ retval.debug_val = []
478
+ (by_type[DT::DT_DEBUG] || []).each {|x| retval.debug_val << x.val}
479
+ by_type.delete DT::DT_DEBUG
480
+
481
+ #TODO: gnu extensions
482
+ retval.extra_dynamic = by_type.values.flatten.map(&:snapshot)
483
+ unless by_type.empty?
484
+ print "Warning, unparsed dynamic entries \n"
485
+ pp by_type
486
+ end
487
+ retval
488
+ end
489
+ def parse_note(note)
490
+ note_name = @shstrtab[note.name] || ".note.unk.#{note.off}"
491
+ @data.seek note.off
492
+ expect_value "Note alignment", note.addralign, NOTE_ALIGN
493
+ expect_value "Note flags", note.flags, NOTE_FLAGS
494
+ expect_value "Note entsize", note.entsize, NOTE_ENTSIZE
495
+ @unparsed_sections.delete @data
496
+ [note_name, @factory.note.read(@data).tap {|n|
497
+ expect_value "Note size",n.num_bytes, note.siz
498
+ } ]
499
+ end
500
+
501
+ def parse_phdrs()
502
+ #TODO: validate flags
503
+ by_type = @phdrs.group_by{|x| x.type.to_i}
504
+ by_type.delete PT::PT_NULL
505
+ process_unique = lambda do |sym| # Validates that either one
506
+ # or zero entries of this type exist, returning the one entry
507
+ # if it exists
508
+ if(by_type.include? sym)
509
+ expect_value "PHDR #{sym} count", by_type[sym].size,1
510
+ by_type[sym].first.tap { by_type.delete sym }
511
+ else
512
+ nil
513
+ end
514
+ end
515
+
516
+ process_unique.call(PT::PT_PHDR).andand do |pt_phdr|
517
+ expect_value "PHD offset",pt_phdr.off, @hdr.phoff
518
+ expect_value "PHDR size",pt_phdr.filesz, @hdr.phnum * @hdr.phentsize
519
+ end
520
+
521
+ by_type.delete PT::PT_LOAD # TODO:: validate range and that
522
+ # section vaddr is correct!
523
+ =begin all notes go into one or multiple program headers.
524
+ by_type[PT::PT_NOTE].each {|note|
525
+ expect_value "SHT_NOTE at this address",
526
+ @sect_types[SHT::SHT_NOTE].find{|n| note.vaddr.to_i == n.vaddr.to_i}.andand {|n|
527
+ [n.off.to_i,n.siz.to_i]
528
+ }, [note.off.to_i,note.filesz.to_i] }
529
+ =end
530
+ by_type.delete PT::PT_NOTE
531
+
532
+ process_unique.call(PT::PT_INTERP).andand do |pt_interp| #Technically
533
+ #not needed according to spec, INTERP doesn't need to have its
534
+ #own section. Instead just check what is at that vaddr
535
+ interp_section = @progbits.select {|x| x.addr == pt_interp.vaddr.to_i}.first
536
+ expect_value ".interp section", interp_section.nil?, false
537
+ @file.interp = BinData::Stringz.read(interp_section.data.read).snapshot
538
+ @progbits.delete interp_section
539
+ end
540
+ process_unique.call(PT::PT_DYNAMIC).andand do |pt_dynamic|
541
+ dynamic_section = @sect_types[SHT::SHT_DYNAMIC].first
542
+ expect_value "PT_dynamic address", pt_dynamic.vaddr, dynamic_section.vaddr
543
+ expect_value "PT_dynamic offset" , pt_dynamic.off, dynamic_section.off
544
+ expect_value "PT_dynamic size", pt_dynamic.filesz, dynamic_section.siz
545
+ end
546
+ @file.extra_phdrs = by_type.values.flatten
547
+ unless(@file.extra_phdrs.empty?)
548
+ print "Unparsed PHDR\n"
549
+ pp @file.extra_phdrs
550
+ end
551
+ end
552
+
553
+ def parse_with_factory()
554
+ @data.rewind
555
+ @hdr = @factory.hdr.read(@data)
556
+ @file.filetype = @hdr.type
557
+ @file.machine = @hdr.machine
558
+ @file.version = @hdr.version # Shouldn't this always be the current one
559
+ @file.flags = @hdr.flags
560
+ @file.entry = @hdr.entry
561
+ expect_value "ELF version",@file.version, ElfFlags::Version::EV_CURRENT
562
+ #pp hdr.snapshot
563
+ if @hdr.phnum != 0
564
+ expect_value "PHT size", @factory.phdr.new.num_bytes, @hdr.phentsize
565
+ @data.seek @hdr.phoff
566
+ @phdrs = BinData::Array.new(:type => @factory.phdr, :initial_length => @hdr.phnum)
567
+ @phdrs.read(@data)
568
+ else
569
+ @phdrs =[]
570
+ end
571
+
572
+ @data.seek @hdr.shoff
573
+ @shdrs = BinData::Array.new(:type => @factory.shdr, :initial_length => @hdr.shnum)
574
+ @shdrs.read(@data)
575
+ @unparsed_sections = Set.new []
576
+ expect_value "SHT size", @shdrs[0].num_bytes, @hdr.shentsize
577
+ @shstrtab = safe_strtab(@hdr.shstrndx)
578
+
579
+ @shdrs.to_enum.with_index.each do |elem, i|
580
+ elem.index = i
581
+ @unparsed_sections.add i
582
+ unless elem.flags & SHF::SHF_ALLOC
583
+ expect_value "Unallocated section address", elem.vaddr, 0
584
+ end
585
+ end
586
+
587
+
588
+ #Keep a hash of sections by type
589
+ @sect_types = @shdrs.group_by {|x| x.type.to_i}
590
+ #TODO: keep track which #sections we have already parsed to find unparsed sections
591
+ @bits_by_index = Hash.new.tap{|h| (@sect_types.values_at SHT::SHT_PROGBITS,SHT::SHT_NOBITS,SHT::SHT_INIT_ARRAY,SHT::SHT_FINI_ARRAY).reject(&:nil?).flatten.each { |s| h[s.index] = parse_progbits(s)} }
592
+ @progbits = @bits_by_index.values.select {|x| x.sect_type != SHT::SHT_NOBITS}
593
+ #TODO: just make NOBITS progbits for now
594
+ @file.progbits = @progbits
595
+
596
+ @progbits.select{|x| x.flags & SHF::SHF_TLS != 0}.each {|tdata|
597
+ @file.gnu_tls ||= TLS.new
598
+ expect_value "Only one .tdata per file",@file.gnu_tls.tdata.nil?,true
599
+ @file.gnu_tls.tdata = tdata
600
+ tdata.phdr = PT::PT_TLS
601
+ tdata.phdr_flags = PF::PF_R
602
+ }
603
+ @nobits = @bits_by_index.values.select {|x| x.sect_type == SHT::SHT_NOBITS}
604
+ @nobits.each{|nobit|
605
+ if nobit.flags & SHF::SHF_TLS != 0
606
+ @file.gnu_tls ||= TLS.new
607
+ @file.gnu_tls.tbss = nobit
608
+ else
609
+ pagesize = 4096
610
+ pagemask = 4095
611
+ nobit.size += pagesize - ( (nobit.size + nobit.addr.to_i) % pagesize)#HACK: This makes the BSS
612
+ expect_value "Nobit end alignment", (nobit.addr + nobit.size) % pagesize, 0
613
+ #section span a page, which it implicitly does due to the semantics of the mmap call
614
+ #HACK: TODO: Hardcoded pagesize
615
+ end
616
+ }
617
+ @file.nobits = @nobits
618
+
619
+ @relocatable_sections = SegmentTree.new(Hash.new.tap{|h|
620
+ (@progbits + @nobits).each{ |pb|
621
+ h[(pb.addr)..(pb.addr + pb.size)]=pb
622
+ }
623
+ })
624
+
625
+ parse_phdrs()
626
+
627
+ @file.dynamic = unique_section(@sect_types, ElfFlags::SectionType::SHT_DYNAMIC).andand{|dynamic| parse_dynamic dynamic} || Elf::Dynamic.new
628
+
629
+ @symtab = unique_section(@sect_types, ElfFlags::SectionType::SHT_SYMTAB).andand {|symtab| parse_symtable symtab, safe_strtab(symtab.link) } || []
630
+ @dynsym = unique_section(@sect_types, ElfFlags::SectionType::SHT_DYNSYM).andand {|symtab| parse_symtable symtab, safe_strtab(symtab.link) } || []
631
+
632
+ unique_section(@sect_types,ElfFlags::SectionType::SHT_GNU_VERNEED).andand{|verneed| parse_verneed verneed}
633
+ unique_section(@sect_types,ElfFlags::SectionType::SHT_GNU_VERDEF).andand{|verdef| parse_verdef verdef}
634
+ unique_section(@sect_types,ElfFlags::SectionType::SHT_GNU_VERSYM).andand{|versym|
635
+ expect_value "Need a dynsym when we have versym", @dynsym.nil?, false
636
+ parse_versym versym,@dynsym
637
+ }
638
+ #TODO: Parse versions in static symbols
639
+ @file.symbols = SymbolTable.new.tap{|h| (@symtab || []).each{|sym|
640
+ h<< sym if sym.name != "" #TODO: Represent nameless symbols - why did we drop these?
641
+ }}
642
+ if(@file.symbols.include? "_DYNAMIC" or @file.dynamic.soname =~ /^ld/) #HACK: This is how we detect ld.so and friends
643
+ @file.pinned_sections ||= {}
644
+ dyn_section = unique_section(@sect_types,Elf::SHT::SHT_DYNAMIC)
645
+ #expect_value "_DYNAMIC symbol points to dynamic section", @file.symbols["_DYNAMIC"].section, nil
646
+ #expect_value "_DYNAMIC symbol points to dynamic section", @file.symbols["_DYNAMIC"].sectoffset, dyn_section.vaddr.to_i
647
+ @file.pinned_sections[".dynamic"] = {vaddr: dyn_section.vaddr.to_i, size: dyn_section.siz.to_i}
648
+ end
649
+ dyn_by_name = {}
650
+ @dynsym.each{|d|
651
+ d.is_dynamic= true
652
+ dyn_by_name[d.name]||=[];
653
+ dyn_by_name[d.name] << d
654
+ }
655
+ @canonical_symbol = {}
656
+ @file.symbols.each {|staticsym|
657
+ @canonical_symbol[staticsym] = staticsym
658
+ next if staticsym.name == ""
659
+ next if staticsym.visibility == STB::STB_LOCAL
660
+ dyn_by_name[staticsym.name].andand.each {|sym|
661
+ next if sym.sectoffset != dynsym.sectoffset
662
+ next if sym.section != dynsym.section
663
+ @canonical_symbol[dynsym] = sym
664
+ staticsym.is_dynamic = true
665
+ staticsym.gnu_version = sym.gnu_version
666
+ expect_value "Dynamic #{sym.name} size", sym.size, staticsym.size
667
+ }
668
+ }
669
+ @dynsym.each {|sym|
670
+ unless @canonical_symbol.include? sym
671
+ @canonical_symbol[sym]= sym
672
+ @file.symbols << sym
673
+ end
674
+ }
675
+ # symtab_array = @file.symbols.to_a
676
+ # @canonical_symbol.each_value{|x| expect_value "x",symtab_array.include?(x), true}
677
+
678
+ rels_addrs = [ET::ET_EXEC, ET::ET_DYN].include? @hdr.type
679
+ rel = (@sect_types[SHT::SHT_RELA] || []).map {|rela| [rela.index, parse_rela(rela,rels_addrs)] }+ (@sect_types[SHT::SHT_REL] || []).map{|rel| [rela.index,parse_rel(rela,rels_addrs)]}
680
+
681
+ rels_by_index = Hash[*rel.flatten(1)]
682
+ @reladyn_indices.each {|reladyn|
683
+ rels_by_index[reladyn].each{|r|
684
+ r.is_dynamic = true
685
+ }
686
+ }
687
+
688
+
689
+
690
+
691
+
692
+ @file.notes = Hash[(@sect_types[SHT::SHT_NOTE] || []).map{|note| parse_note note}]
693
+ #TODO: expect non-nil dynamic for some types
694
+ @file.relocations = rels_by_index.values.flatten
695
+
696
+ #TODO: Validate flags
697
+ #TODO: Validate header?
698
+
699
+ end
700
+ end
701
+ def self.from_file(filename)
702
+ contents = IO.read(filename)
703
+ Parser.new(contents).file
704
+ end
705
+ end
706
+ end