class PatchELF::Saver
Internal use only.
For {Patcher} to do patching things and save to file. @private
Constants
- IGNORE
To mark a not-using tag
Attributes
in_file[R]
out_file[R]
Public Class Methods
new(in_file, out_file, set)
click to toggle source
Instantiate a {Saver} object. @param [String] in_file
@param [String] out_file
@param [{Symbol => String, Array}] set
# File lib/patchelf/saver.rb, line 24 def initialize(in_file, out_file, set) @in_file = in_file @out_file = out_file @set = set # [{Integer => String}] @inline_patch = {} @elf = ELFTools::ELFFile.new(File.open(in_file)) @mm = PatchELF::MM.new(@elf) @strtab_extend_requests = [] @append_dyn = [] end
Public Instance Methods
save!()
click to toggle source
@return [void]
# File lib/patchelf/saver.rb, line 37 def save! # In this method we assume all attributes that should exist do exist. # e.g. DT_INTERP, DT_DYNAMIC. These should have been checked in the patcher. patch_interpreter patch_dynamic @mm.dispatch! FileUtils.cp(in_file, out_file) if out_file != in_file patch_out(@out_file) # Let output file have the same permission as input. FileUtils.chmod(File.stat(in_file).mode, out_file) end
Private Instance Methods
dynamic()
click to toggle source
# File lib/patchelf/saver.rb, line 278 def dynamic @dynamic ||= @elf.segment_by_type(:dynamic) end
expand_dynamic!()
click to toggle source
# File lib/patchelf/saver.rb, line 158 def expand_dynamic! return if @append_dyn.empty? dyn_sec = section_header('.dynamic') total = dynamic.tags.map(&:header) # the last must be a null-tag total = total[0..-2] + @append_dyn + [total.last] bytes = total.first.num_bytes * total.size @mm.malloc(bytes) do |off, vaddr| inline_patch(off, total.map(&:to_binary_s).join) dynamic.header.p_offset = off dynamic.header.p_vaddr = dynamic.header.p_paddr = vaddr dynamic.header.p_filesz = dynamic.header.p_memsz = bytes if dyn_sec dyn_sec.sh_offset = off dyn_sec.sh_addr = vaddr dyn_sec.sh_size = bytes end end end
inline_patch(off, str)
click to toggle source
This can only be used for patching interpreter's name or set strings in a malloc-ed area. i.e. NEVER intend to change the string defined in strtab
# File lib/patchelf/saver.rb, line 238 def inline_patch(off, str) @inline_patch[off] = str end
lazy_dyn(sym)
click to toggle source
Create a temp tag header. @return [ELFTools::Structs::ELF_Dyn]
# File lib/patchelf/saver.rb, line 150 def lazy_dyn(sym) ELFTools::Structs::ELF_Dyn.new(endian: @elf.endian).tap do |dyn| @append_dyn << dyn dyn.elf_class = @elf.elf_class dyn.d_tag = ELFTools::Util.to_constant(ELFTools::Constants::DT, sym) end end
malloc_strtab!()
click to toggle source
# File lib/patchelf/saver.rb, line 179 def malloc_strtab! return if @strtab_extend_requests.empty? strtab = dynamic.tag_by_type(:strtab) # Process registered requests need_size = strtab_string.size + @strtab_extend_requests.reduce(0) { |sum, (str, _)| sum + str.size + 1 } dynstr = section_header('.dynstr') @mm.malloc(need_size) do |off, vaddr| new_str = strtab_string + @strtab_extend_requests.map(&:first).join("\x00") + "\x00" inline_patch(off, new_str) cur = strtab_string.size @strtab_extend_requests.each do |str, block| block.call(cur) cur += str.size + 1 end # Now patching strtab header strtab.header.d_val = vaddr # We also need to patch dynstr to let readelf have correct output. if dynstr dynstr.sh_size = new_str.size dynstr.sh_offset = off dynstr.sh_addr = vaddr end end end
patch_dynamic()
click to toggle source
# File lib/patchelf/saver.rb, line 88 def patch_dynamic # We never do inline patching on strtab's string. # 1. Search if there's useful string exists # - only need header patching # 2. Append a new string to the strtab. # - register strtab extension dynamic.tags # HACK, force @tags to be defined patch_soname if @set[:soname] patch_runpath if @set[:runpath] patch_runpath(:rpath) if @set[:rpath] patch_needed if @set[:needed] malloc_strtab! expand_dynamic! end
patch_interpreter()
click to toggle source
# File lib/patchelf/saver.rb, line 53 def patch_interpreter return if @set[:interpreter].nil? new_interp = @set[:interpreter] + "\x00" old_interp = @elf.segment_by_type(:interp).interp_name + "\x00" return if old_interp == new_interp # These headers must be found here but not in the proc. seg_header = @elf.segment_by_type(:interp).header sec_header = section_header('.interp') patch = proc do |off, vaddr| # Register an inline patching inline_patch(off, new_interp) # The patching feature of ELFTools seg_header.p_offset = off seg_header.p_vaddr = seg_header.p_paddr = vaddr seg_header.p_filesz = seg_header.p_memsz = new_interp.size if sec_header sec_header.sh_offset = off sec_header.sh_size = new_interp.size end end if new_interp.size <= old_interp.size # easy case patch.call(seg_header.p_offset.to_i, seg_header.p_vaddr.to_i) else # hard case, we have to request a new LOAD area @mm.malloc(new_interp.size, &patch) end end
patch_needed()
click to toggle source
# File lib/patchelf/saver.rb, line 121 def patch_needed original_needs = dynamic.tags_by_type(:needed) @set[:needed].uniq! original = original_needs.map(&:name) replace = @set[:needed] # 3 sets: # 1. in original and in needs - remain unchanged # 2. in original but not in needs - remove # 3. not in original and in needs - append append = replace - original remove = original - replace ignored_dyns = remove.each_with_object([]) do |name, ignored| dyn = original_needs.find { |n| n.name == name }.header dyn.d_tag = IGNORE ignored << dyn end append.zip(ignored_dyns) do |name, ignored_dyn| dyn = ignored_dyn || lazy_dyn(:needed) dyn.d_tag = ELFTools::Constants::DT_NEEDED reg_str_table(name) { |idx| dyn.d_val = idx } end end
patch_out(out_file)
click to toggle source
Modify the out_file
according to registered patches.
# File lib/patchelf/saver.rb, line 243 def patch_out(out_file) File.open(out_file, 'r+') do |f| if @mm.extended? original_head = @mm.threshold extra = {} # Copy all data after the second load @elf.stream.pos = original_head extra[original_head + @mm.extend_size] = @elf.stream.read # read to end # zero out the 'gap' we created extra[original_head] = "\x00" * @mm.extend_size extra.each do |pos, str| f.pos = pos f.write(str) end end @elf.patches.each do |pos, str| f.pos = @mm.extended_offset(pos) f.write(str) end @inline_patch.each do |pos, str| f.pos = pos f.write(str) end end end
patch_runpath(sym = :runpath)
click to toggle source
# File lib/patchelf/saver.rb, line 111 def patch_runpath(sym = :runpath) tag = dynamic.tag_by_type(sym) tag = tag.nil? ? lazy_dyn(sym) : tag.header reg_str_table(@set[sym]) do |idx| tag.d_val = idx end end
patch_soname()
click to toggle source
# File lib/patchelf/saver.rb, line 103 def patch_soname # The tag must exist. so_tag = dynamic.tag_by_type(:soname) reg_str_table(@set[:soname]) do |idx| so_tag.header.d_val = idx end end
reg_str_table(str) { |idx| ... }
click to toggle source
@param [String] str @yieldparam [Integer] idx @yieldreturn [void]
# File lib/patchelf/saver.rb, line 208 def reg_str_table(str, &block) idx = strtab_string.index(str + "\x00") # Request string is already exist return yield idx if idx # Record the request @strtab_extend_requests << [str, block] end
section_header(name)
click to toggle source
@return [ELFTools::Sections::Section?]
# File lib/patchelf/saver.rb, line 271 def section_header(name) sec = @elf.section_by_name(name) return if sec.nil? sec.header end
strtab_string()
click to toggle source
# File lib/patchelf/saver.rb, line 217 def strtab_string return @strtab_string if defined?(@strtab_string) # TODO: handle no strtab exists.. offset = @elf.offset_from_vma(dynamic.tag_by_type(:strtab).value) # This is a little tricky since no length information is stored in the tag. # We first get the file offset of the string then 'guess' where the end is. @elf.stream.pos = offset @strtab_string = +'' loop do c = @elf.stream.read(1) break unless c =~ /\x00|[[:print:]]/ @strtab_string << c end @strtab_string end