diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index e859004..fad0a02 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -3,6 +3,7 @@ require_relative "elf/header" require_relative "elf/program_header" require_relative "elf/section" +require_relative "elf/section/dynamic" require_relative "elf/section/rel" require_relative "elf/section/strtab" require_relative "elf/section/symtab" diff --git a/lib/caotral/binary/elf/section/dynamic.rb b/lib/caotral/binary/elf/section/dynamic.rb new file mode 100644 index 0000000..c531d20 --- /dev/null +++ b/lib/caotral/binary/elf/section/dynamic.rb @@ -0,0 +1,31 @@ +require "caotral/binary/elf/utils" +module Caotral + module Binary + class ELF + class Section + class Dynamic + include Caotral::Binary::ELF::Utils + TAG_TYPES = { NULL: 0 }.freeze + + def initialize + @tag = num2bytes(0, 8) + # d_un is a C union. Depending on d_tag, interpret this 8-byte field as either + # a pointer (d_ptr) or an integer value (d_val). + @un = num2bytes(0, 8) + end + + def set!(tag: nil, un: nil) + @tag = num2bytes(tag, 8) if check(tag, 8) + @un = num2bytes(un, 8) if check(un, 8) + self + end + + def tag = @tag.pack("C*").unpack1("Q<") + def null? = tag == TAG_TYPES[:NULL] + + private def bytes = [@tag, @un] + end + end + end + end +end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index d803912..e6017b0 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -11,6 +11,7 @@ class Builder BIND_BY_VALUE = SYMTAB_BIND.invert.freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze + GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic"].freeze attr_reader :symbols @@ -50,6 +51,7 @@ def build section_name: ".shstrtab", header: Caotral::Binary::ELF::SectionHeader.new ) + start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05] exec_text_offset = 0x1000 base_addr = 0x400000 @@ -150,6 +152,7 @@ def build entsize: 24 ) + sections += build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) if @shared sections << symtab_section rel_sections.each { |s| sections << s.dup } @@ -161,7 +164,7 @@ def build entsize: 0 ) - @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./]).each do |section| + @elf_objs.first.without_sections(GENERATED_SECTION_NAMES).each do |section| sections << section.dup end @@ -254,6 +257,35 @@ def resolve_symbols end private + def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) + sht = Caotral::Binary::ELF::SectionHeader::SHT + dynstr_section = Caotral::Binary::ELF::Section.new( + body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), + section_name: ".dynstr", + header: Caotral::Binary::ELF::SectionHeader.new + ) + + dynstr_section.header.set!(type: sht[:strtab], flags: 0, addralign: 1, entsize: 0) + + dynsym_section = Caotral::Binary::ELF::Section.new( + body: [Caotral::Binary::ELF::Section::Symtab.new], + section_name: ".dynsym", + header: Caotral::Binary::ELF::SectionHeader.new + ) + + dynsym_section.header.set!(type: sht[:dynsym], flags: 0, addralign: 8, entsize: 24) + + dynamic_section = Caotral::Binary::ELF::Section.new( + body: [Caotral::Binary::ELF::Section::Dynamic.new], + section_name: ".dynamic", + header: Caotral::Binary::ELF::SectionHeader.new + ) + + dynamic_section.header.set!(type: sht[:dynamic], flags: 0, addralign: 8, entsize: 16) + + [dynstr_section, dynsym_section, dynamic_section] + end + def ref_index(sections, section_name) raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil? ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 29cd6e4..c10a062 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -41,6 +41,7 @@ def write gap = [text_offset - f.pos, 0].max f.write("\0" * gap) f.write(text_section.body) + write_shared_dynamic_sections(file: f) if @shared symtab_offset = f.pos symtab_section.body.each { |sym| f.write(sym.build) } symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i @@ -67,7 +68,6 @@ def write f.write(shstrtab_section.body.names) shoffset = f.pos shstrndx = write_section_index(".shstrtab") - strtabndx = write_section_index(".strtab") symtabndx = write_section_index(".symtab") shnum = @write_sections.size @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) @@ -78,8 +78,8 @@ def write lookup_name = section.section_name name_offset = names.offset_of(lookup_name) name, info, entsize = (name_offset.nil? ? 0 : name_offset), header.info, header.entsize - link = header.link - link = strtabndx if section.section_name == ".symtab" + link = link_index(section.section_name) + link = header.link if link.nil? if [:rel, :rela].include?(header.type) link = symtabndx info = ref_index(section.section_name) @@ -100,6 +100,9 @@ def write_order_sections write_order = [] write_order << @elf_obj.sections.find { |s| s.section_name.nil? } write_order << @elf_obj.find_by_name(".text") + write_order << @elf_obj.find_by_name(".dynstr") + write_order << @elf_obj.find_by_name(".dynsym") + write_order << @elf_obj.find_by_name(".dynamic") write_order << @elf_obj.find_by_name(".symtab") write_order << @elf_obj.find_by_name(".strtab") write_order.concat(@elf_obj.select_by_names(RELOCATION_SECTION_NAMES)) @@ -107,6 +110,24 @@ def write_order_sections write_order.compact end def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } + + def write_shared_dynamic_sections(file:) + dynstr_offset = file.pos + file.write(dynstr_section.body.build) + size = file.pos - dynstr_offset + dynstr_section.header.set!(offset: dynstr_offset, size:) + + dynsym_offset = file.pos + dynsym_section.body.each { |dynsym| file.write(dynsym.build) } + size = file.pos - dynsym_offset + dynsym_section.header.set!(offset: dynsym_offset, size:) + + dynamic_offset = file.pos + dynamic_section.body.each { |dynamic| file.write(dynamic.build) } + size = file.pos - dynamic_offset + dynamic_section.header.set!(offset: dynamic_offset, size:) + end + def ref_index(section_name) ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" } ref_name = "." + ref_name.join(".") @@ -115,11 +136,25 @@ def ref_index(section_name) write_section_index(ref.section_name) end + def link_index(section_name) + case section_name + when ".symtab" + write_section_index(".strtab") + when ".dynsym", ".dynamic" + write_section_index(".dynstr") + else + nil + end + end + def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } def rel_sections = @rel_sections ||= @write_sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) } def symtab_section = @symtab_section ||= @write_sections.find { |s| ".symtab" === s.section_name.to_s } def strtab_section = @strtab_section ||= @write_sections.find { |s| ".strtab" === s.section_name.to_s } def shstrtab_section = @shstrtab_section ||= @write_sections.find { |s| ".shstrtab" === s.section_name.to_s } + def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" === s.section_name.to_s } + def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" === s.section_name.to_s } + def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } end end end diff --git a/test/caotral/linker/shared-object_test.rb b/test/caotral/linker/shared-object_test.rb index 5356dbf..390c215 100644 --- a/test/caotral/linker/shared-object_test.rb +++ b/test/caotral/linker/shared-object_test.rb @@ -17,6 +17,10 @@ def teardown def test_link_shared_object Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", shared: true, executable: false) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + section_names = elf.sections.map(&:section_name) + assert_include(section_names, ".dynstr") + assert_include(section_names, ".dynsym") + assert_include(section_names, ".dynamic") assert_equal(:DYN, elf.header.type) assert_equal(:AMD64, elf.header.arch) end