elf-mithril 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,335 @@
1
+ require 'bindata'
2
+ #Nothing to see here. For background, see Dartmouth College CS
3
+ #TR2013-727 (ELFbac)
4
+ module Elf
5
+ module Policy
6
+ def self.section_symbol_name(file_name,section_name)
7
+ "_elfp_#{file_name}_#{section_name}"
8
+ end
9
+ R = ElfFlags::Relocation
10
+ ELFP = ElfFlags::ElfPData
11
+ class Transition
12
+ attr_accessor :from, :to
13
+ end
14
+ class Call < Transition
15
+ attr_accessor :symbol
16
+ attr_accessor :parambytes,:returnbytes
17
+ def initialize(from,to, symbol, parambytes, returnbytes)
18
+ @from, @to, @symbol, @parambytes, @returnbytes = from,to, symbol, parambytes, returnbytes
19
+ end
20
+ def allows_return?
21
+ @returnbytes >= 0
22
+ end
23
+ end
24
+ class Data < Transition
25
+ attr_accessor :tag
26
+ attr_accessor :read,:write, :exec
27
+ def initialize(from,to,tag , read=false ,write=false,exec=false)
28
+ @from, @to, @tag, @read,@write,@exec = from,to,tag,read,write,exec
29
+ end
30
+ end
31
+ class MemoryRange
32
+ attr_accessor :low, :high
33
+ def initialize(from,to)
34
+ @low,@high = from,to
35
+ end
36
+ end
37
+ class Policy
38
+ attr_accessor :data, :calls, :start, :tags
39
+ attr_accessor :imported_symbols
40
+ def states
41
+ t = data.keys + data.values.map(&:keys).flatten + calls.map(&:from) +
42
+ calls.map(&:to)
43
+ t.uniq
44
+ end
45
+ def <<(*transitions)
46
+ transitions.each do |t|
47
+ if t.is_a? Data
48
+ x= data
49
+ x[t.from] ||= {}
50
+ x[t.from][t.to] ||= {}
51
+ x[t.from][t.to][t.tag] ||= t
52
+ x[t.from][t.to][t.tag].read ||= t.read
53
+ x[t.from][t.to][t.tag].write ||= t.write
54
+ x[t.from][t.to][t.tag].exec ||= t.exec
55
+ elsif t.is_a? Call
56
+ calls << t
57
+ else
58
+ raise ArgumentError.new "#{t.class} is not a valid transition"
59
+ end
60
+ end
61
+ end
62
+ def initialize
63
+ @data={}
64
+ @calls=[]
65
+ @states ={}
66
+ @tags = {}
67
+ @imported_symbols = {}
68
+ end
69
+ def resolve_reference(elffile, relocations,offset, ref)
70
+ if(ref.is_a? Integer)
71
+ ref.to_i
72
+ elsif(ref == "_dl_runtime_resolve") #HACK:HACK:HACK: I couldn't hack ld.so to fix this, so
73
+ #here comes a nasty hack
74
+ #note that the address of _dl_runtime_resolve is 16 bytes into PLT.GOT
75
+ if !elffile.symbols.include? "_elfp_hidden_trampolineaddr"
76
+ pltgot = elffile.dynamic.pltgot or raise RuntimeError.new "No plt.got for _dl_runtime_resolve hack"
77
+ elffile.symbols << Elf::Symbol.new("_elfp_hidden_trampolineaddr", pltgot,STT::STT_OBJECT,16, STB::STB_LOCAL,8)
78
+ end
79
+ symb = elffile.symbols["_elfp_hidden_trampolineaddr"]
80
+ relocations << Elf::Relocation.new.tap{|x|
81
+ x.type = R::R_X86_64_COPY
82
+ x.offset = offset
83
+ x.symbol = symb
84
+ x.is_dynamic = true
85
+ x.addend = 0
86
+ }
87
+ 0xDEADBEEF
88
+ else
89
+ raise RuntimeError.new "Symbol #{ref} not found" unless elffile.symbols.include? ref
90
+ relocations << Elf::Relocation.new.tap{|x|
91
+ x.type = R::R_X86_64_64
92
+ x.offset = offset
93
+ x.symbol = elffile.symbols[ref]
94
+ x.is_dynamic = true
95
+ x.addend = 0
96
+ }
97
+ 2**64-1
98
+ end
99
+ end
100
+ def resolve_size(elffile,relocations, offset, ref)
101
+ if(ref.is_a? Integer)
102
+ ref.to_i
103
+ else
104
+ raise RuntimeError.new "Symbol #{ref} not found" unless elffile.symbols.include? ref
105
+ relocations << Elf::Relocation.new.tap{|x|
106
+ x.type = R::R_X86_64_SIZE64
107
+ x.offset = offset
108
+ x.symbol = elffile.symbols[ref]
109
+ x.is_dynamic = true
110
+ x.addend = 0
111
+ }
112
+ 2**64-1
113
+ end
114
+ end
115
+ def write_amd64(elffile)
116
+ factory = ElfStructFactory.instance(:little,64)
117
+ @imported_symbols.each_key {|symbol|
118
+ if elffile.symbols.include?(symbol)
119
+ elffile.symbols[symbol].is_dynamic = true
120
+ else
121
+ elffile.symbols << Elf::Symbol.new(symbol,nil,Elf::STT::STT_OBJECT, 0, Elf::STB::STB_GLOBAL, 0).tap {|x|
122
+ x.semantics = Elf::SHN::SHN_UNDEF
123
+ }
124
+ end
125
+ }
126
+ out = factory.elfp_header.new()
127
+ state_ids = {}
128
+ tag_ids = {}
129
+ relocations = []
130
+ states = states()
131
+ @start = states.first unless states.include? @start
132
+ #These have to be filled in the order in which they are written
133
+ #FIXME: Make these aware of double transitions to the same range/ state
134
+ states.each_with_index do |state,index|
135
+ id = index + 2
136
+ id = 1 if @start == state
137
+ out.states << factory.elfp_state.new.tap {|x|
138
+ x.id = id
139
+ x.stackid = 0
140
+ }
141
+ state_ids[state] = id
142
+ print "State #{state} #{id}\n"
143
+ end
144
+ tag_ids[:default] = 0
145
+ @tags.each_with_index do |(name,ranges),index|
146
+ tag_ids[name] = index+1
147
+ ranges.each do |data|
148
+ out.tags << factory.elfp_tag.new.tap {|x|
149
+ x.tag = index + 1
150
+ x.addr = 0
151
+ x.siz = 0
152
+ }
153
+ out.tags.last.tap {|x|
154
+ x.addr = resolve_reference(elffile,relocations,x.addr.offset,data.low)
155
+ if data.high.nil?
156
+ x.siz = resolve_size(elffile,relocations,x.siz.offset,data.low)
157
+ else
158
+ pp "Warning, emitting SIZE symbol with value #{ data.high.to_i rescue data.high.name}"
159
+ x.siz = resolve_reference(elffile,relocations,x.siz.offset,data.high)
160
+ end
161
+ }
162
+ end
163
+ print "Tag #{name} #{index + 1} \n"
164
+ end
165
+ self.calls.each do |call|
166
+ out.calls << factory.elfp_call.new.tap {|x|
167
+ x.from = state_ids[call.from]
168
+ x.to = state_ids[call.to]
169
+ x.parambytes = call.parambytes
170
+ x.returnbytes = call.parambytes
171
+ }
172
+ out.calls.last.off = resolve_reference(elffile,relocations,out.calls.last.off.offset, call.symbol)
173
+ end
174
+ self.data.values.map(&:values).flatten.map(&:values).flatten.each do |data|
175
+ out.data << factory.elfp_data.new.tap {|x|
176
+ x.from = state_ids[data.from]
177
+ x.to = state_ids[data.to]
178
+ x.type = 0
179
+ x.type |= ELFP::ELFP_RW_READ if data.read
180
+ x.type |= ELFP::ELFP_RW_WRITE if data.write
181
+ x.type |= ELFP::ELFP_RW_EXEC if data.exec
182
+ raise RuntimeError.new "Unknown tag #{data.tag}" unless tag_ids.include? data.tag
183
+ x.tag = tag_ids[data.tag]
184
+ }
185
+ end
186
+ out = Elf::ProgBits.new(".elfbac",nil,out.to_binary_s)
187
+ out.align = 8
188
+ out.flags = SHF::SHF_ALLOC | SHF::SHF_WRITE
189
+ out.sect_type = SHT::SHT_PROGBITS
190
+ out.phdr = ElfFlags::PhdrType::PT_ELFBAC
191
+ out.phdr_flags = ElfFlags::PhdrFlags::PF_R
192
+ relocations.each { |rel|
193
+ rel.section = out
194
+ elffile.relocations << rel
195
+ }
196
+ elffile.progbits << out
197
+
198
+ end
199
+ def inject(file)
200
+ case file.machine
201
+ when ElfFlags::Machine::EM_X86_64
202
+ write_amd64(file)
203
+ else
204
+ raise RuntimeError.new "Wrong architecture for ARM64"
205
+ end
206
+ end
207
+ end
208
+ module BuilderHelper
209
+ def section_start(name, file_name="")
210
+ Elf::Policy.section_symbol_name(file_name,name).tap{|x| @policy.imported_symbols[x] = true}
211
+ end
212
+ end
213
+ class TagBuilder
214
+ include BuilderHelper
215
+ attr_accessor :ranges
216
+ def initialize(pol)
217
+ @policy = pol
218
+ @ranges = []
219
+ end
220
+ def section(name,file_name="")
221
+ range(section_start(name,file_name))
222
+ end
223
+ def range(low,high=nil)
224
+ @ranges << MemoryRange.new(low,high)
225
+ end
226
+ def symbol(sym)
227
+ range(sym)
228
+ end
229
+ end
230
+ class DataBuilder
231
+ include BuilderHelper
232
+ def initialize(transition)
233
+ @transition = transition
234
+ end
235
+ def read(v=true) #TODO: Unify transitions? Intervaltree?
236
+ @transition.read = v
237
+ end
238
+ def write(v=true)
239
+ @transition.write = v
240
+ @transition.read ||= v
241
+ end
242
+ def exec(v=true)
243
+ @transition.exec = v
244
+ @transition.read ||= v
245
+ end
246
+ end
247
+ class StateBuilder
248
+ include BuilderHelper
249
+ def initialize(from,to,pol)
250
+ @from = from
251
+ @to = to
252
+ @policy = pol
253
+ end
254
+
255
+ {
256
+ text: ".text",
257
+ data: ".data",
258
+ bss:".bss"
259
+ }.each{|function,name|
260
+ define_method function, lambda{|library=''| section(name,library)}
261
+ }
262
+ def call(symbol, parambytes= 0, returnbytes=0)
263
+ raise RuntimeError.new "Call has to have a destination" if @from == @to
264
+ @policy << Call.new(@from,@to, symbol, parambytes, returnbytes)
265
+ end
266
+ def call_noreturn(symbol,parambytes=0)
267
+ call(symbol, parambytes,-1)
268
+ end
269
+ def mem(tag, &block)
270
+ d = Data.new(@from,@to,tag)
271
+ DataBuilder.new(d).instance_eval(&block)
272
+ @policy << d
273
+ end
274
+ def exec(tag)
275
+ mem(tag){
276
+ exec }
277
+ end
278
+ def read(tag)
279
+ mem(tag){
280
+ read
281
+ }
282
+ end
283
+ def write(tag)
284
+ mem(tag){
285
+ write
286
+ }
287
+ end
288
+ def readwrite(tag)
289
+ mem(tag){
290
+ read
291
+ write
292
+ }
293
+ end
294
+
295
+ def to(name,&block)
296
+ raise RuntimeError.new "Cannot nest to{} blocks" if @from != @to# or name == @from
297
+ StateBuilder.new(@from,name, @policy).instance_eval(&block)
298
+ end
299
+ def transition(trans)
300
+ @policy << trans
301
+ end
302
+ end
303
+ class PolicyBuilder
304
+ include BuilderHelper
305
+ attr_reader :policy
306
+ def initialize()
307
+ @policy = Policy.new()
308
+ end
309
+ def state(name, &block)
310
+ StateBuilder.new(name,name,@policy).instance_eval(&block)
311
+ end
312
+ def tag(name, &block)
313
+ policy.tags[name] ||= []
314
+ x =TagBuilder.new(@policy)
315
+ x.instance_eval(&block)
316
+ policy.tags[name] += x.ranges
317
+ end
318
+ def call_noreturn(from,to,symbol, parambytes=0)
319
+ @policy << Call.new(from,to, symbol, parambytes,-1)
320
+ end
321
+ def read
322
+ @policy << Data.new(from,to,from,to )
323
+ end
324
+ def start(name)
325
+ @policy.start = name
326
+ end
327
+ end
328
+ def self.build(&block)
329
+ x= PolicyBuilder.new()
330
+ x.instance_eval(&block)
331
+ x.policy
332
+ end
333
+ end
334
+
335
+ end
@@ -0,0 +1,3 @@
1
+ module Mithril
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,774 @@
1
+ require_relative 'elf'
2
+ module Elf
3
+ module Writer
4
+ def self.to_file(filename,elf)
5
+ Elf::Writer::Writer.to_file(filename,elf)
6
+ end
7
+ def self.elf_hash(value)
8
+ h=0
9
+ g=0
10
+ value.chars.map(&:ord).each {|char|
11
+ h = ((h << 4) + (char % 256) )
12
+ g = h & 0xf0000000
13
+ if g!=0
14
+ h = h ^ (g>> 24) # This simulates overflow; elf spec is clever
15
+ end
16
+ h &= ~g
17
+ }
18
+ h
19
+ end
20
+ def self.gnu_hash(value)
21
+ h = 5381
22
+ value.chars.map(&:ord).each {|char|
23
+ h = (h*33 + char) & 0xffffffff
24
+ }
25
+ pp "Gnu_hash #{value} #{h}"
26
+ h
27
+ end
28
+ class StringTable #Replace with compacting string table
29
+ attr_reader :buf
30
+ def initialize
31
+ @buf = StringIO.new("\0")
32
+ @buf.seek 1
33
+ @strings = {} #TODO: Do substring matching, compress the string
34
+ #table.
35
+ # Actually, make all string tables except dynstr one, might save
36
+ # a bit
37
+ end
38
+ def add_string(string)
39
+ unless @strings.include? string
40
+ @strings[string] = @buf.tell.tap {|x|
41
+ BinData::Stringz::new(string).write(@buf)
42
+ }
43
+ end
44
+ @strings[string]
45
+ end
46
+ def buf()
47
+ @buf
48
+ end
49
+ end
50
+ class OutputSection
51
+ attr_accessor :name, :type,:flags,:vaddr,:siz,:link,:info, :align, :entsize, :data, :shdr, :off
52
+ attr_accessor :index
53
+ # TODO: Use params array
54
+ def initialize(name,type,flags,vaddr,siz,link,info,align,entsize,data) #link and info are strings, offset is done by output stage
55
+ @name,@type,@flags, @vaddr, @siz, @link, @info, @align, @entsize, @data= name,type,flags,vaddr,siz,link,info,align,entsize, data
56
+ @off = nil
57
+ @index = nil
58
+ end
59
+ def end
60
+ @vaddr + @siz
61
+ end
62
+
63
+ end
64
+
65
+ class Layout
66
+ def initialize(factory)
67
+ @layout_by_flags = {}
68
+ @layout = RBTree.new()
69
+ @shstrtab = StringTable.new()
70
+ @factory = factory
71
+ @phdrs = [] #BinData::Array.new(:type => @factory.phdr)
72
+ @unallocated =[]
73
+ @sections = [OutputSection.new("", SHT::SHT_NULL, 0,0,0,0,0,0,0,"")]
74
+ @sections[0].index = 0
75
+
76
+ @pinned_sections = {} # Name to vaddr
77
+ end
78
+ def pin_section(name,size,vaddr)
79
+ x = OutputSection.new(".pin_dummy",SHT::SHT_NULL,0,vaddr,0,0,0,0,0,
80
+ BinData::Array.new(type: :int8, initial_length: size).to_binary_s)
81
+ raise RuntimeError.new "Invalid pin" unless range_available?(@layout,vaddr,vaddr+size)
82
+ @layout[vaddr] = x
83
+ @pinned_sections[name] = vaddr
84
+ end
85
+ def [](idx)
86
+ @sections[idx]
87
+ end
88
+ def add(*sections) #Ordering as follows: Fixed
89
+ #(non-nil vaddrs) go where they have to go
90
+ # Flexible sections are added to lowest hole after section of
91
+ # the same type
92
+ retval = []
93
+ return [] if sections.empty?
94
+ sections.each{|sect|
95
+ sect.index = @sections.size
96
+ expect_value "Correct size", sect.data.bytesize, sect.siz
97
+ @sections << sect
98
+ retval << sect.index
99
+ }
100
+ flags = sections.first.flags
101
+ @layout_by_flags[flags] ||= RBTree.new()
102
+ if sections.length > 1 || sections.first.vaddr.nil?
103
+ sections.each { |i|
104
+ expect_value "All adjacent sections have the same flags", i.flags,flags
105
+ expect_value "All sections must float", i.vaddr, nil
106
+ }
107
+ if sections.length == 1 && @pinned_sections.include?(sections.first.name)
108
+ sections.first.vaddr = @pinned_sections[sections.first.name]
109
+ @layout.delete sections.first.vaddr
110
+ @pinned_sections.delete @sections.first.name
111
+ else
112
+ allocate_sections(sections)
113
+ end
114
+ end
115
+ #TODO: Handle the damn nobits
116
+ if(flags & SHF::SHF_ALLOC == 0)
117
+ sections.each {|i| @unallocated << i}
118
+ else
119
+ sections.each {|i|
120
+
121
+ expect_value "Sections shouldn't overlap", range_available?(@layout,i.vaddr,i.end), true
122
+ @layout[i.vaddr] = i
123
+ @layout_by_flags[i.flags][i.vaddr] = i
124
+ }
125
+ end
126
+ retval
127
+ end
128
+ def add_with_phdr(sections,type,flags)
129
+ raise RuntimeError.new "need one section in phdr" if sections.empty?
130
+ x=self.add *sections
131
+ @phdrs << [sections,type,flags]
132
+ x
133
+ end
134
+ def shstrtab(buf) # Last section written, TODO: Move to layout
135
+ name = ".shstrtab"
136
+ idx = @shstrtab.add_string name
137
+ x=@factory.shdr.new
138
+ x.name= idx
139
+ x.type = SHT::SHT_STRTAB
140
+ x.off = buf.tell
141
+ x.siz = @shstrtab.buf.size
142
+ buf.write @shstrtab.buf.string
143
+ x
144
+ end
145
+
146
+ LoadMap = Struct.new(:off,:end,:vaddr,:memsz,:flags,:align)
147
+ def pagealign_loadmap(loadmaps)
148
+ #TODO: Be smarter about this..
149
+ loadmaps.sort_by(&:vaddr).each_cons(2) do |low,high|
150
+ if rounddown(low.vaddr + low.memsz,PAGESIZE) >= rounddown(high.vaddr,PAGESIZE)
151
+ low.flags |= high.flags
152
+ high.flags |= low.flags
153
+ end
154
+ end
155
+ loadmaps
156
+ end
157
+ attr_accessor :tbss_size
158
+ def write_phdrs(buf,filehdr,load)
159
+ phdrs = BinData::Array.new(:type => @factory.phdr)
160
+ expect_value "Too many PHDRS",true, @phdrs.size + load.size < RESERVED_PHDRS
161
+ @phdrs.each {|x|
162
+ sections, type,flags =*x
163
+ x = @factory.phdr.new
164
+ x.align = sections.map(&:align).max
165
+ x.vaddr = sections.map(&:vaddr).min
166
+ x.paddr = x.vaddr
167
+ x.filesz = sections.map(&:end).max - x.vaddr
168
+ if type == PT::PT_TLS and @tbss_size #HACK
169
+ x.memsz = x.filesz + @tbss_size
170
+ else
171
+ x.memsz = x.filesz
172
+ end
173
+ x.flags = flags
174
+ x.off = sections.map(&:off).min
175
+ x.type = type
176
+ phdrs.push x
177
+ }
178
+ #
179
+ load.first.andand.tap{|l|
180
+ l.vaddr -= l.off
181
+ l.memsz += l.off
182
+ l.off = 0
183
+ }
184
+ pagealign_loadmap(load)
185
+ load.each {|l|
186
+ phdrs.push @factory.phdr.new.tap {|x|
187
+ x.align = [PAGESIZE,l.align].max
188
+ x.vaddr = l.vaddr
189
+ x.paddr = x.vaddr
190
+ x.type = PT::PT_LOAD
191
+ x.filesz = l.end - l.off#TODO: handle nobits
192
+ x.memsz = x.filesz # l.memsz
193
+ x.off = l.off
194
+ x.flags = PF::PF_R
195
+ x.flags |= PF::PF_W if(l.flags & SHF::SHF_WRITE != 0)
196
+ x.flags |= PF::PF_X if(l.flags & SHF::SHF_EXECINSTR != 0)
197
+
198
+ }
199
+ }
200
+
201
+ #phdrs.unshift @factory.phdr.new.tap {|x|
202
+ # x.align = 4
203
+ # x.vaddr =
204
+ # x.type = PT::PT_PHDRS
205
+ #}
206
+ # pp load
207
+ filehdr.phoff = buf.tell
208
+ filehdr.phentsize = @factory.phdr.new.num_bytes
209
+ filehdr.phnum = phdrs.size
210
+ phdrs.write buf
211
+
212
+ end
213
+ RESERVED_PHDRS = 16
214
+ def write_sections(buf,filehdr)
215
+ first_shdr = @sections.first
216
+ first_shdr.index = 0
217
+
218
+ #Get more clever about mapping files
219
+ # We put actual program headers right at the beginning.
220
+ phdr_off = buf.tell
221
+ buf.seek phdr_off + RESERVED_PHDRS * @factory.phdr.new.num_bytes
222
+ offset = buf.tell
223
+ @sections[0].off = 0
224
+ (@layout.to_a.sort_by(&:first).map(&:last) + @unallocated).each {|s|
225
+ if s.flags & SHF::SHF_ALLOC != 0
226
+ offset = align_mod(offset,s.vaddr, PAGESIZE)
227
+ end
228
+ s.off = offset
229
+ offset += s.data.bytesize
230
+
231
+ }
232
+ idx = 0
233
+ @sections.each{|s| expect_value "Size field correct", s.siz, s.data.bytesize}
234
+ @sections.sort_by(&:off).each_cons(2){|low,high|
235
+ expect_value "Offsets should not overlap", true, low.off + low.data.bytesize <= high.off
236
+ }
237
+ shdrs = BinData::Array.new(:type=>@factory.shdr).push *@sections.map{ |s|
238
+ expect_value "aligned to vaddr", 0,s.vaddr % s.align if s.align != 0
239
+ expect_value "idx", idx,s.index
240
+ idx +=1
241
+ #expect_value "aligned to pagesize",0, PAGESIZE % s.align
242
+
243
+ buf.seek s.off
244
+ old_off = buf.tell
245
+ buf.write(s.data)
246
+ # pp "#{idx }#{s.name} written to offset #{old_off} to #{buf.tell}"
247
+
248
+
249
+ expect_value "size", s.data.bytesize, s.siz
250
+ link_value = lambda do |name|
251
+ if name.is_a? String
252
+ @sections.to_enum.with_index.select {|sect,idx| sect.name == name}.first.last rescue raise RuntimeError.new("Invalid Section reference #{name}") #Index of first match TODO: check unique
253
+ else
254
+ name || 0
255
+ end
256
+ end
257
+ x= @factory.shdr.new
258
+ x.name = @shstrtab.add_string(s.name)
259
+ x.type = s.type
260
+ x.flags = s.flags
261
+ x.vaddr = s.vaddr
262
+ x.off = s.off
263
+ x.siz = s.siz
264
+ x.link = link_value.call(s.link)
265
+ x.info = link_value.call(s.info)
266
+ x.addralign = s.align
267
+ x.entsize= s.entsize
268
+ # x.flags |= SHF::SHF_ALLOC if(@file.gnu_tls.tbss == s)
269
+ x
270
+ }
271
+ #remove
272
+ mapped = @sections.select{|x| x.flags & SHF::SHF_ALLOC != 0}.sort_by(&:vaddr)
273
+ mapped.each_cons(2){|low,high| expect_value "Mapped sections should not overlap", true,low.vaddr + low.siz<= high.vaddr}
274
+ load = mapped.group_by(&:flags).map{|flags,sections|
275
+ sections.group_by{|x| x.vaddr - x.off}.map do |align,sections|
276
+ memsize = sections.last.off + sections.last.siz - sections.first.off
277
+ # if sections.last.type == SHT::SHT_ end
278
+ # sections.select{|x| x.type == SHT::SHT_NOBITS}.tap {|nobits|
279
+ # if nobits.size > 0
280
+ # expect_value "At most one NOBITS", nobits.size,1
281
+ # expect_value "NOBITS is the last section",sections.last.type, SHT::SHT_NOBITS
282
+ # last_file_addr = nobits.first.vaddr
283
+ # memsize = sections.last.off - sections.first.off
284
+ # end
285
+ # }
286
+ LoadMap.new(sections.first.off, sections.last.off + sections.last.siz,
287
+ sections.first.vaddr, memsize ,flags,sections.map(&:align).max) #TODO: LCM?
288
+ end
289
+ }.flatten
290
+ buf.seek 0,IO::SEEK_END
291
+
292
+ shdrs << shstrtab(buf)
293
+ filehdr.shstrndx = shdrs.size - 1
294
+ filehdr.shoff = buf.tell
295
+ filehdr.shentsize = shdrs[0].num_bytes
296
+ filehdr.shnum = shdrs.size
297
+ shdrs.write buf
298
+
299
+ buf.seek roundup(buf.tell, PAGESIZE)-1
300
+ buf.write '\0' # pad to pagesize
301
+ buf.seek phdr_off
302
+ write_phdrs(buf,filehdr,load)
303
+ end
304
+ private
305
+ def max_address()
306
+ unless @layout.last.nil?
307
+ @layout.last[1].end
308
+ else
309
+ 0x40000 # TODO: Make start_address a parameter
310
+ end
311
+ end
312
+ def allocate_sections(chunk)
313
+ return unless chunk.first.flags & SHF::SHF_ALLOC != 0
314
+ align = chunk.max_by{ |x| x.align}.align #Should technically be the
315
+ #lcm
316
+
317
+ #TODO: check that they are powers of two
318
+ size = chunk.reduce(0) {|i,x| i+roundup(x.siz,align)}
319
+ addr = @layout_by_flags[chunk.first.flags].last.andand { |x| roundup(x[1].end,align)}
320
+ if(addr.nil? or !range_available?(@layout,addr,addr+size))
321
+ addr = roundup(max_address,align)
322
+ expect_value "Address space has room", range_available?(@layout,addr,addr+size),true #TODO: This can actually overflow! Properly do arithemetic mod 2**64
323
+ end
324
+ chunk.each do |section|
325
+ section.vaddr = roundup(addr,align)
326
+ addr = section.end
327
+ end
328
+ end
329
+ def rounddown(number,align)
330
+ return number if align == 0
331
+ case number % align
332
+ when 0
333
+ number
334
+ else
335
+ number - (number % align)
336
+ end
337
+ end
338
+ def roundup(number, align)
339
+ return number if align == 0
340
+ case number % align
341
+ when 0
342
+ number
343
+ else
344
+ number + align - (number % align)
345
+ end
346
+ end
347
+ def align_mod(number,align,mod)
348
+ align = align % mod
349
+ x = number + align - number % mod
350
+ if(x<number)
351
+ x+mod
352
+ else
353
+ x
354
+ end
355
+ end
356
+ def range_available?(tree,from,to)
357
+ if tree.empty?
358
+ true
359
+ else
360
+ ( (tree.upper_bound(from).andand.last.andand.end || -1) <= from ) &&
361
+ ( (tree.lower_bound(to).andand.last.andand.vaddr || +1.0/0.0) >= to)
362
+ end
363
+ end
364
+ end
365
+ PAGESIZE = 1 << 12 #KLUDGE: largest pagesize , align everything to
366
+
367
+ UINT64_MOD = 2**64
368
+ #TODO: Needs a unique class for 'allocatable' sections.
369
+ #Then just sort, and write them out
370
+ class Writer #TODO: Completely refactor this
371
+
372
+ #pagesizes
373
+ def initialize(file,factory)
374
+ @factory = factory
375
+ @file = file
376
+ @layout = Layout.new(@factory)
377
+ @shdrs= BinData::Array::new(type: @factory.shdr,initial_length: 0)
378
+ @phdrs= BinData::Array::new(type: @factory.phdr,initial_length: 0)
379
+ @buf = StringIO.new()
380
+ @progbit_indices = {}
381
+ @section_vaddrs = {}
382
+
383
+ @file.pinned_sections.andand.each {|name,pin| @layout.pin_section(name,pin[:size],pin[:vaddr])
384
+ }
385
+ write_to_buf
386
+ end
387
+ def self.to_file(filename,elf)
388
+ factory = ElfStructFactory.instance(elf.endian,elf.bits)
389
+ writer = Writer.new(elf,factory)
390
+ IO.write filename,writer.buf.string
391
+ end
392
+ attr_reader :buf
393
+ private
394
+ def progbits
395
+ @file.gnu_tls.andand(&:tbss).andand{|x|
396
+ x.flags &= ~SHF::SHF_ALLOC
397
+ @layout.tbss_size = x.size
398
+ }
399
+ bits = (@file.progbits + @file.nobits).sort {|a,b|( a.addr and b.addr ) ? a.addr <=> b.addr : ( a.addr ? -1 : 1 ) }
400
+ bits.each do |sect|
401
+
402
+ out = OutputSection.new(sect.name,sect.sect_type, sect.flags, sect.addr, sect.size,0,0,sect.align, sect.entsize, sect.data.string)
403
+ # binding.pry if sect.sect_type == SHT::SHT_INIT_ARRAY
404
+ if sect.phdr.nil?
405
+ @layout.add out
406
+ else
407
+ @layout.add_with_phdr [out], sect.phdr, sect.phdr_flags
408
+ end
409
+ @progbit_indices[sect] = out.index
410
+ @section_vaddrs[sect] = out.vaddr
411
+ end
412
+
413
+ end
414
+ def note
415
+ os= @file.notes.map {|name,note|
416
+ OutputSection.new(name, SHT::SHT_NOTE, NOTE_FLAGS, nil, note.num_bytes,0,0,NOTE_ALIGN, NOTE_ENTSIZE,note.to_binary_s)
417
+ }
418
+ @layout.add_with_phdr os, PT::PT_NOTE, PF::PF_R unless os.empty?
419
+ #TODO: add phdr
420
+ end
421
+ def interp
422
+ if(@file.interp)
423
+ interp = BinData::Stringz.new(@file.interp)
424
+ @interp_section = OutputSection.new(".interp",SHT::SHT_PROGBITS, SHF::SHF_ALLOC, nil, interp.num_bytes,0,0,1,0,interp.to_binary_s)
425
+ idx, _ =@layout.add_with_phdr [@interp_section], PT::PT_INTERP, PF::PF_R
426
+ @progbit_indices[".interp"]=idx
427
+ end
428
+ end
429
+ def hashtab(table)
430
+ hashtab = BinData::Array.new(:type => "uint32#{@file.endian == :big ? 'be' : 'le'}".to_sym)
431
+ nbuckets = 5
432
+ nchain = table.size
433
+ buckets = Array.new(nbuckets,0)
434
+ chain = Array.new(nchain,0)
435
+
436
+ table.each {|sym,idx|
437
+
438
+ expect_value "Valid symbol index", idx<table.size,true
439
+ i = Elf::Writer::elf_hash(sym.name) % nbuckets
440
+ if(buckets[i] == 0)
441
+ buckets[i] = idx
442
+ else
443
+ i = buckets[i]
444
+ while(chain[i] != 0)
445
+ i= chain[i]
446
+ end
447
+ chain[i] = idx
448
+ end
449
+ }
450
+ hashtab.assign [nbuckets,nchain] + buckets + chain
451
+
452
+ sect = OutputSection.new(".hash",SHT::SHT_HASH,SHF::SHF_ALLOC,nil, hashtab.num_bytes, ".dynsym", 0,8,@file.bits/8, hashtab.to_binary_s)
453
+ @layout.add sect
454
+ @dynamic << @factory.dyn.new(tag: DT::DT_HASH, val: sect.vaddr)
455
+ # pp hashtab.snapshot
456
+ end
457
+ def versions(dynsym,dynstrtab) #TODO: Use parser combinator
458
+ @versions = {}
459
+ gnu_versions = dynsym.map(&:gnu_version).uniq.select{|x|x.is_a? GnuVersion}
460
+
461
+ defined_versions = gnu_versions.select{|x| x.needed == false}
462
+ unless @file.dynamic.gnu_version_basename.nil?
463
+ defined_versions.unshift GnuVersion.new(@file.dynamic.soname,@file.dynamic.gnu_version_basename, ElfFlags::GnuVerFlags::VERFLAG_BASE, false)
464
+ end
465
+ buffer = StringIO.new()
466
+ defined_versions.each_with_index {|ver,definedidx|
467
+ expect_value "Defined SONAME",false, @file.dynamic.gnu_version_basename.nil?
468
+ expect_value "Defined version file name",ver.file, @file.dynamic.soname
469
+ verdef = @factory.verdef.new
470
+ verdef.version = 1
471
+ verdef.flags = ver.flags
472
+ if ver.flags & ElfFlags::GnuVerFlags::VERFLAG_BASE != 0
473
+ verdef.ndx = 1
474
+ else
475
+ verdef.ndx = @versions.size + 2
476
+ @versions[ver] = @versions.size + 2
477
+ end
478
+ verdef.hsh = Elf::Writer::elf_hash(ver.version)
479
+ verdaux = BinData::Array.new(type: @factory.verdaux)
480
+ verdaux << @factory.verdaux.new.tap{|x|
481
+ x.name = dynstrtab.add_string(ver.version)
482
+ x.nextoff = if(ver.parents.size == 0)
483
+ 0
484
+ else
485
+ x.num_bytes
486
+ end
487
+ }
488
+ ver.parents.each_with_index {|parent,idx|
489
+ aux = @factory.verdaux.new
490
+ aux.name = dynstrtab.add_string(parent.version)
491
+ aux.nextoff = if(idx == ver.parents.size - 1)
492
+ 0
493
+ else
494
+ aux.num_bytes
495
+ end
496
+ verdaux << aux
497
+ }
498
+ verdef.cnt = verdaux.size
499
+ verdef.aux = verdef.num_bytes
500
+ if(defined_versions.size - 1 == definedidx)
501
+ verdef.nextoff = 0
502
+ else
503
+ verdef.nextoff = verdaux.num_bytes + verdef.num_bytes
504
+ end
505
+ verdef.write(buffer)
506
+ verdaux.write(buffer)
507
+ }
508
+ unless defined_versions.empty?
509
+ sect = OutputSection.new(".gnu.version_d", SHT::SHT_GNU_VERDEF,SHF::SHF_ALLOC,nil,buffer.size,".dynstr",defined_versions.size,8,0,buffer.string)
510
+ @layout.add sect
511
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERDEF, val: sect.vaddr)
512
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERDEFNUM, val: defined_versions.size)
513
+ end
514
+ buffer = StringIO.new()
515
+ needed_versions = gnu_versions.select{|x| x.needed == true}.group_by(&:file)
516
+ needed_idx = 0
517
+ needed_versions.each {|file, versions|
518
+ needed_idx+=1
519
+ #Create VERNEED for this file
520
+ verneed = @factory.verneed.new
521
+ verneed.version = 1
522
+ verneed.cnt = versions.size
523
+ verneed.file = dynstrtab.add_string(file)
524
+ verneed.aux = verneed.num_bytes
525
+ vernauxs = BinData::Array.new(type: @factory.vernaux)
526
+ versions.each {|ver|
527
+ vernauxs.push @factory.vernaux.new.tap {|x|
528
+ x.hsh = Elf::Writer::elf_hash(ver.version)
529
+ x.flags = ver.flags
530
+ x.other = @versions.size + 2
531
+ x.name = dynstrtab.add_string(ver.version)
532
+ x.nextoff = if vernauxs.size == versions.size - 1
533
+ 0
534
+ else
535
+ x.num_bytes
536
+ end
537
+ @versions[ver] = x.other.to_i
538
+ }
539
+ }
540
+ if (needed_versions.size == needed_idx)
541
+ verneed.nextoff = 0 # 0 for last
542
+ else
543
+ verneed.nextoff = verneed.num_bytes + vernauxs.num_bytes
544
+ end
545
+ verneed.write(buffer)
546
+ vernauxs.write(buffer)
547
+ }
548
+ unless needed_versions.empty?
549
+ sect = OutputSection.new(".gnu.version_r", SHT::SHT_GNU_VERNEED,SHF::SHF_ALLOC,nil,buffer.size,".dynstr",needed_versions.size,8,0,buffer.string)
550
+ @layout.add sect
551
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERNEED, val: sect.vaddr)
552
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERNEEDNUM, val: needed_versions.size)
553
+ end
554
+
555
+ @versions
556
+ end
557
+ def versym(versions,symbols)
558
+ return if versions.empty?
559
+ data = BinData::Array.new(type: @factory.versym)
560
+ symbols.each{|sym|
561
+ veridx = case sym.gnu_version
562
+ when :local
563
+ 0
564
+ when :global
565
+ 1
566
+ else
567
+ versions[sym.gnu_version]
568
+ end
569
+ data.push @factory.versym.new(veridx: veridx)
570
+ }
571
+ sect = OutputSection.new(".gnu.version",SHT::SHT_GNU_VERSYM, SHF::SHF_ALLOC, nil, data.num_bytes, ".dynsym",0, 8,2, data.to_binary_s)
572
+ @layout.add sect
573
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERSYM, val: sect.vaddr)
574
+ end
575
+ def dynsym(dynstrtab)
576
+ symtab = BinData::Array.new(:type => @factory.sym) #TODO: use initial length here to save on
577
+ #some allocations
578
+ syms = [Elf::Symbol.new("",nil,STT::STT_NOTYPE,0,STB::STB_LOCAL,0).tap{
579
+ |x| x.gnu_version = :local; x.semantics = 0}] + @file.symbols.select(&:is_dynamic)
580
+ versions = versions(syms, dynstrtab)
581
+ @dynsym = {}
582
+ #symtab << @factory.sym.new
583
+ syms.each_with_index.each do |sym,idx|
584
+ s = @factory.sym.new
585
+ s.name = dynstrtab.add_string(sym.name)
586
+ s.type = sym.type
587
+ s.binding = sym.bind
588
+ if sym.semantics.nil?
589
+ s.shndx = @progbit_indices[sym.section] || nil
590
+ unless s.shndx
591
+ pp "warning, symbol ", s,"does not have a valid progbits index"
592
+ s.shndx = nil
593
+ end
594
+ else
595
+ s.shndx = sym.semantics
596
+ end
597
+ s.other = sym.visibility
598
+ unless sym.section.nil?
599
+ expect_value "valid symbol offset", sym.sectoffset <= sym.section.size,true #Symbol can point to end of section
600
+ end
601
+
602
+ s.val = (@layout[s.shndx.to_i].andand.vaddr || 0) + sym.sectoffset #TODO: find output section
603
+
604
+ s.siz = sym.size
605
+ @dynsym[sym] = symtab.size
606
+ symtab.push s
607
+ end
608
+ last_local_idx = syms.to_enum.with_index.select{|v,i| v.bind == STB::STB_LOCAL}.andand.last.andand.last || -1
609
+ dynsym = OutputSection.new(".dynsym",SHT::SHT_DYNSYM, SHF::SHF_ALLOC, nil,symtab.num_bytes,".dynstr", last_local_idx+1,1,@factory.sym.new.num_bytes, symtab.to_binary_s)
610
+ versym(versions,syms)
611
+ @layout.add dynsym
612
+ hashtab(@dynsym)
613
+ @dynamic << @factory.dyn.new(tag: DT::DT_SYMTAB,val: dynsym.vaddr)
614
+ @dynamic << @factory.dyn.new(tag: DT::DT_SYMENT,val: @factory.sym.new.num_bytes)
615
+
616
+ end # Produce a hash and a GNU_HASH as well
617
+ def dynamic
618
+ @dynamic = BinData::Array.new(:type => @factory.dyn)
619
+ dynstrtab = StringTable.new()
620
+ reladyn(dynstrtab)
621
+ #PHDR
622
+ @file.dynamic.needed.each{|lib|
623
+ @dynamic << @factory.dyn.new(tag: DT::DT_NEEDED, val: dynstrtab.add_string(lib))
624
+ }
625
+ section_tag = lambda {|tag,value|
626
+ unless(value.nil?)
627
+ @dynamic << @factory.dyn.new(tag: tag, val: value.addr)
628
+ true
629
+ else
630
+ nil
631
+ end
632
+ }
633
+ unless @file.dynamic.soname.nil?
634
+ @dynamic << @factory.dyn.new(tag: DT::DT_SONAME, val: dynstrtab.add_string(@file.dynamic.soname))
635
+ end
636
+ unless @file.dynamic.rpath.nil?
637
+ @dynamic << @factory.dyn.new(tag: DT::DT_RPATH, val: dynstrtab.add_string(@file.dynamic.rpath))
638
+ end
639
+ section_tag.call(DT::DT_PLTGOT,@file.dynamic.pltgot)
640
+ if section_tag.call(DT::DT_INIT_ARRAY,@file.dynamic.init_array)
641
+ @dynamic << @factory.dyn.new(tag: DT::DT_INIT_ARRAYSZ,val: @file.dynamic.init_array.size)
642
+ end
643
+ if section_tag.call(DT::DT_FINI_ARRAY,@file.dynamic.fini_array)
644
+ @dynamic << @factory.dyn.new(tag: DT::DT_FINI_ARRAYSZ,val: @file.dynamic.fini_array.size)
645
+ end
646
+ # section_tag.call(DT::DT_INIT,@file.dynamic.init)
647
+ # section_tag.call(DT::DT_FINI,@file.dynamic.fini)
648
+ @dynamic << @factory.dyn.new(tag: DT::DT_INIT, val: @file.dynamic.init) if @file.dynamic.init
649
+ @dynamic << @factory.dyn.new(tag: DT::DT_FINI, val: @file.dynamic.fini) if @file.dynamic.fini
650
+
651
+ #@file.dynamic.debug_val.each {|dbg|
652
+ # @dynamic << @factory.dyn.new(tag: DT::DT_DEBUG, val: dbg)
653
+ #}
654
+ #@file.dynamic.extra_dynamic.each{ |extra|
655
+ # @dynamic << @factory.dyn.new(tag: extra[:tag], val: extra[:val])
656
+ #}
657
+ if @file.dynamic.flags
658
+ @dynamic << @factory.dyn.new(tag: DT::DT_FLAGS, val: @file.dynamic.flags)
659
+ end
660
+ if @file.dynamic.flags1
661
+ @dynamic << @factory.dyn.new(tag: DT::DT_FLAGS_1, val: @file.dynamic.flags1)
662
+ end
663
+ #string table
664
+ dynstr = OutputSection.new(".dynstr", SHT::SHT_STRTAB, SHF::SHF_ALLOC,nil, dynstrtab.buf.size, 0,0,1,0,dynstrtab.buf.string)
665
+ @layout.add dynstr
666
+ @dynamic << @factory.dyn.new(tag: DT::DT_STRTAB, val: dynstr.vaddr)
667
+ @dynamic << @factory.dyn.new(tag: DT::DT_STRSZ, val: dynstr.siz)
668
+
669
+ @dynamic << @factory.dyn.new(tag: DT::DT_NULL, val: 0) # End marker
670
+ @layout.add_with_phdr [OutputSection.new(".dynamic", SHT::SHT_DYNAMIC, SHF::SHF_ALLOC | SHF::SHF_WRITE, nil, @dynamic.num_bytes,".dynstr",0,8,@factory.dyn.new.num_bytes,@dynamic.to_binary_s)], PT::PT_DYNAMIC, PF::PF_R
671
+ #dynstr -> STRTAB STRSZ
672
+ #dynsym -> DT_SYMENT, D
673
+ end # Write note, etc for the above
674
+ RELATIVE_RELOCS=[R::R_X86_64_RELATIVE]
675
+ def rela_buffer(relocations)
676
+ relative_rela_count = 0
677
+ buf = BinData::Array.new(:type =>@factory.rela).new.tap{|retval|
678
+ relocations.sort_by{|x| RELATIVE_RELOCS.include?(x.type) ? 0 : 1}.each_with_index {|rel,idx|
679
+ entry = @factory.rela.new
680
+ entry.off = @section_vaddrs[rel.section] + rel.offset
681
+ if rel.symbol.nil?
682
+ entry.sym = ElfFlags::SymbolName::STN_UNDEF
683
+ else
684
+ entry.sym = @dynsym[rel.symbol]
685
+ end
686
+ relative_rela_count +=1 if(RELATIVE_RELOCS.include? rel.type)
687
+ entry.type = rel.type
688
+ entry.addend = rel.addend
689
+ retval.push entry
690
+ }
691
+ }
692
+ [buf, relative_rela_count]
693
+ end
694
+
695
+ def reladyn(dynstrtab)
696
+ @file.relocations.each{ |r|
697
+ r.symbol.andand {|x| x.is_dynamic = true}
698
+ }
699
+ dynsym(dynstrtab)
700
+ relaentsize = @factory.rela.new.num_bytes
701
+
702
+ pltrel = @file.relocations.select(&:is_dynamic).select(&:is_lazy)
703
+ dynrel = @file.relocations.select(&:is_dynamic).select{|x| not x.is_lazy}
704
+ rela_sections = []
705
+ unless dynrel.empty?
706
+ #All other relocations go into .rela.dyn
707
+ relabuf,relacount = rela_buffer(dynrel)
708
+ reladyn = OutputSection.new(".rela.dyn", SHT::SHT_RELA, SHF::SHF_ALLOC,nil, relabuf.num_bytes,".dynsym",0,8, relaentsize, relabuf.to_binary_s)
709
+ rela_sections << reladyn
710
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELACOUNT, val: relacount)
711
+ end
712
+ unless(pltrel.empty?)
713
+ relabuf,_=rela_buffer(pltrel)
714
+ relaplt = OutputSection.new(".rela.plt", SHT::SHT_RELA, SHF::SHF_ALLOC, nil, relabuf.num_bytes,".dynsym",@file.dynamic.pltgot.name,8, relaentsize, relabuf.to_binary_s)
715
+ rela_sections << relaplt
716
+ end
717
+ @layout.add *rela_sections # They need to in this order to produce a correct ld.so
718
+ unless pltrel.empty?
719
+ @dynamic << @factory.dyn.new(tag: DT::DT_PLTRELSZ, val: relabuf.num_bytes)
720
+ @dynamic << @factory.dyn.new(tag: DT::DT_PLTREL, val: DT::DT_RELA)
721
+ @dynamic << @factory.dyn.new(tag: DT::DT_JMPREL, val: relaplt.vaddr)
722
+ end
723
+ unless dynrel.empty?
724
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELA, val: reladyn.vaddr)
725
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELASZ, val: reladyn.siz)
726
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELAENT, val: relaentsize)
727
+ end
728
+ end
729
+ def write_headers
730
+ hdr = @factory.hdr.new
731
+ case @file.endian
732
+ when :big
733
+ hdr.ident.id_data = ElfFlags::IdentData::ELFDATA2MSB
734
+ when :little
735
+ hdr.ident.id_data = ElfFlags::IdentData::ELFDATA2LSB
736
+ else
737
+ raise ArgumentError.new "Invalid endianness"
738
+ end
739
+ case @file.bits
740
+ when 64
741
+ hdr.ident.id_class = ElfFlags::IdentClass::ELFCLASS64
742
+ when 32
743
+ hdr.ident.id_class = ElfFlags::IdentClass::ELFCLASS32
744
+ else
745
+ raise ArgumentError.new "Invalid class"
746
+ end
747
+ hdr.ident.id_version = ElfFlags::Version::EV_CURRENT
748
+ hdr.ehsize = hdr.num_bytes
749
+ hdr.type = @file.filetype
750
+ hdr.machine = @file.machine
751
+ hdr.version = @file.version
752
+ hdr.entry = @file.entry
753
+ hdr.flags = @file.flags
754
+
755
+ @layout.write_sections(@buf,hdr)
756
+
757
+ @buf.seek 0
758
+ hdr.write @buf
759
+ end
760
+ #TODO: Fix things like INTERP
761
+
762
+
763
+ def write_to_buf #Make this memoized
764
+ @buf.seek @factory.hdr.new.num_bytes #Leave space for header.
765
+ #this is pretty bad
766
+ progbits
767
+ interp
768
+ note
769
+ dynamic
770
+ write_headers
771
+ end
772
+ end
773
+ end
774
+ end