rbelftools
Pure Ruby library for parsing and patching ELF files.
Introduction
An ELF parser implemented in pure Ruby. This work is inspired by pyelftools by Eli Bendersky.
The original motivation to create this gem is to be a dependency of pwntools-ruby. Since ELF parser is not an easy work, it should not be implemented directly in pwntools.
Now rbelftools is also used by the Homebrew project: https://github.com/Homebrew/brew/tree/master/Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/elftools-1.1.3/lib
rbelftools's target is to create a nice ELF parsing library in Ruby. More features remain a work in progress.
Install
Available on RubyGems.org!
gem install elftools
Features
- Supports both big and little endian
- ELF parser
- ELF headers patcher
See example usage for more details.
Example Usage
Start from a file object
require 'elftools'
elf = ELFTools::ELFFile.new(File.open('spec/files/amd64.elf'))
#=> #<ELFTools::ELFFile:0x00560b147f8328 @elf_class=64, @endian=:little, @stream=#<File:spec/files/amd64>>
elf.machine
#=> 'Advanced Micro Devices X86-64'
elf.build_id
#=> '73ab62cb7bc9959ce053c2b711322158708cdc07'
Sections
elf.section_by_name('.dynstr')
#=>
# #<ELFTools::Sections::StrTabSection:0x00560b148cef40
# @header=
# {:sh_name=>86,
# :sh_type=>3,
# :sh_flags=>2,
# :sh_addr=>4195224,
# :sh_offset=>920,
# :sh_size=>113,
# :sh_link=>0,
# :sh_info=>0,
# :sh_addralign=>1,
# :sh_entsize=>0},
# @name=".dynstr">
elf.sections.map(&:name).join(' ')
#=> " .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss .comment .shstrtab .symtab .strtab"
elf.section_by_name('.note.gnu.build-id').data
#=> "\x04\x00\x00\x00\x14\x00\x00\x00\x03\x00\x00\x00GNU\x00s\xABb\xCB{\xC9\x95\x9C\xE0S\xC2\xB7\x112!Xp\x8C\xDC\a"
Symbols
symtab_section = elf.section_by_name('.symtab')
symtab_section.num_symbols
#=> 75
symtab_section.symbol_by_name('puts@@GLIBC_2.2.5')
#=>
# #<ELFTools::Sections::Symbol:0x00560b14af67a0
# @header={:st_name=>348, :st_info=>18, :st_other=>0, :st_shndx=>0, :st_value=>0, :st_size=>0},
# @name="puts@@GLIBC_2.2.5">
symbols = symtab_section.symbols # Array of symbols
symbols.map(&:name).reject(&:empty?).first(5).join(' ')
#=> "crtstuff.c __JCR_LIST__ deregister_tm_clones register_tm_clones __do_global_dtors_aux"
Segments
elf.segment_by_type(:note)
#=>
# #<ELFTools::Segments::NoteSegment:0x00555beaafe218
# @header=
# {:p_type=>4,
# :p_flags=>4,
# :p_offset=>624,
# :p_vaddr=>624,
# :p_paddr=>624,
# :p_filesz=>68,
# :p_memsz=>68,
# :p_align=>4}>
elf.segment_by_type(:interp).interp_name
#=> "/lib64/ld-linux-x86-64.so.2"
Relocations
elf = ELFTools::ELFFile.new(File.open('spec/files/amd64.elf'))
# Use relocation to get plt names.
rela_section = elf.sections_by_type(:rela).last
rela_section.name
#=> ".rela.plt"
relocations = rela_section.relocations
relocations.map { |r| '%x' % r.header.r_info }
#=> ["100000007", "200000007", "300000007", "400000007", "500000007", "700000007"]
symtab = elf.section_at(rela_section.header.sh_link) # get the symbol table section
relocations.map { |r| symtab.symbol_at(r.symbol_index).name }
#=> ["puts", "__stack_chk_fail", "printf", "__libc_start_main", "fgets", "scanf"]
Patch
Patch ELF is so easy!
All kinds of headers (i.e. Ehdr
, Shdr
, Phdr
, etc.) can be patched.
Patched slots will not be applied on the opened file.
Invoke elf.save(filename)
to save the patched ELF into filename
.
elf = ELFTools::ELFFile.new(File.open('spec/files/amd64.elf'))
elf.machine
#=> "Advanced Micro Devices X86-64"
elf.header.e_machine = 40
elf.machine
#=> "ARM"
interp_segment = elf.segment_by_type(:interp)
interp_segment.interp_name
#=> "/lib64/ld-linux-x86-64.so.2"
interp_segment.header.p_filesz
#=> 28
interp_segment.header.p_filesz = 20
interp_segment.interp_name
#=> "/lib64/ld-linux-x86"
# save the patched ELF
elf.save('elf.patched')
# in bash
# $ file elf.patched
# elf.patched: ELF 64-bit LSB executable, ARM, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86, for GNU...
Why rbelftools
- Fully documented
Always important for an Open-Source project. Online document is here - Fully tested
Of course. - Lazy loading on everything
To use rbelftools, passing the stream object of an ELF file. rbelftools will read the stream object as least times as possible when parsing the file. Most information will not be fetched until you need it, which makes rbelftools efficient. - To be a library
rbelftools is designed to be a library for further usage. It will not add any too trivial features. For example, to check whether NX is disabled, rbelftools provides!elf.segment_by_type(:gnu_stack).executable?
but notelf.nx?
- Section and segment parser
Providing common sections and segments parser. For example,.symtab
,.shstrtab
.dynamic
sections andINTERP
,DYNAMIC
segments, etc.
Development
git clone https://github.com/david942j/rbelftools
cd rbelftools
bundle
bundle exec rake
Any comments or suggestions are welcome!
Cross Platform
rbelftools can be used and has been fully tested on all platforms include Linux, OSX, and Windows!
License
MIT License