elftools 0.2.2 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c37e680363ef30fc5b86b00e983ccbc45125b852
4
- data.tar.gz: 705aaeba44b9dd230d21afa5647d8b41833f1f3a
3
+ metadata.gz: a9ca4e5e90a3710cbc74557520cfd6efcb442e3b
4
+ data.tar.gz: 9f745e3609cdaa67103ccf718db973ee8c4b3c74
5
5
  SHA512:
6
- metadata.gz: 0aa96a9e111680f44fc23c0309a181d2968675ddfb5b3ab86fdd76f7923900702543803028f9d66ba5a7e5d3b4d3924ac18d5115c9dceba54c8602e33bd545bc
7
- data.tar.gz: a723dfa7b1bcc39a687b488ff522a7ded8439e72e2275be16c903979d671d6ca8e737fe233d991fa2541f10e80189dc6d8c3243d31467703f1e131fc0cf019e6
6
+ metadata.gz: 9edec0296929aa3c6dbc90475d8c0281d6f9a559d681945b4badc1778ad293f340a9c49a0526c4c92cfd03aa546774f3a411d98d66a04390233a29c2780a4151
7
+ data.tar.gz: cc1df33b85fdaebd9dadf64250fdd1f633fe29f9501e2c68fc41cb711e736564a42c307f4c61a06ce1db24348998b2d6652bb72e15c1ae7375ab9d9e22a40bc2
data/README.md CHANGED
@@ -6,6 +6,9 @@
6
6
  [![Inline docs](https://inch-ci.org/github/david942j/rbelftools.svg?branch=master)](https://inch-ci.org/github/david942j/rbelftools)
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
8
8
 
9
+ # rbelftools
10
+ Pure ruby library for parsing and patching ELF files.
11
+
9
12
  # Introduction
10
13
 
11
14
  ELF parser in pure ruby implementation. This work is inspired by [pyelftools](https://github.com/eliben/pyelftools) by [Eli Bendersky](https://github.com/eliben).
@@ -21,6 +24,14 @@ Available on RubyGems.org!
21
24
  gem install elftools
22
25
  ```
23
26
 
27
+ # Features
28
+
29
+ - [x] Supports both big and little endian
30
+ - [x] ELF parser
31
+ - [x] ELF headers patcher
32
+
33
+ See example usage for more details.
34
+
24
35
  # Example Usage
25
36
 
26
37
  ## Start from file object
@@ -114,6 +125,39 @@ relocations.map { |r| symtab.symbol_at(r.symbol_index).name }
114
125
  #=> ["puts", "__stack_chk_fail", "printf", "__libc_start_main", "fgets", "scanf"]
115
126
  ```
116
127
 
128
+ ## Patch
129
+
130
+ Patch ELF is so easy!
131
+
132
+ All kinds of headers (i.e. `Ehdr`, `Shdr`, `Phdr`, etc.) can be patched.
133
+ Patched slots will not be applied on the opened file.
134
+ Invoke `elf.save(filename)` to save the patched ELF into `filename`.
135
+
136
+ ```ruby
137
+ elf = ELFTools::ELFFile.new(File.open('spec/files/amd64.elf'))
138
+ elf.machine
139
+ #=> "Advanced Micro Devices X86-64"
140
+ elf.header.e_machine = 40
141
+ elf.machine
142
+ #=> "ARM"
143
+
144
+ interp_segment = elf.segment_by_type(:interp)
145
+ interp_segment.interp_name
146
+ #=> "/lib64/ld-linux-x86-64.so.2"
147
+ interp_segment.header.p_filesz
148
+ #=> 28
149
+ interp_segment.header.p_filesz = 20
150
+ interp_segment.interp_name
151
+ #=> "/lib64/ld-linux-x86"
152
+
153
+ # save the patched ELF
154
+ elf.save('elf.patched')
155
+
156
+ # in bash
157
+ # $ file elf.patched
158
+ # elf.patched: ELF 64-bit LSB executable, ARM, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86, for GNU...
159
+ ```
160
+
117
161
  # Why rbelftools
118
162
 
119
163
  1. Fully documented
@@ -82,6 +82,7 @@ module ELFTools
82
82
  dyn = Structs::ELF_Dyn.new(endian: endian)
83
83
  dyn.elf_class = header.elf_class
84
84
  stream.pos = tag_start + n * dyn.num_bytes
85
+ dyn.offset = stream.pos
85
86
  @tag_at_map[n] = Tag.new(dyn.read(stream), stream, method(:str_offset))
86
87
  end
87
88
 
@@ -21,6 +21,8 @@ module ELFTools
21
21
  # #=> #<ELFTools::ELFFile:0x00564b106c32a0 @elf_class=64, @endian=:little, @stream=#<File:/bin/cat>>
22
22
  def initialize(stream)
23
23
  @stream = stream
24
+ # always set binmode if stream is an IO object.
25
+ @stream.binmode if @stream.respond_to?(:binmode)
24
26
  identify # fetch the most basic information
25
27
  end
26
28
 
@@ -31,7 +33,7 @@ module ELFTools
31
33
  def header
32
34
  return @header if defined?(@header)
33
35
  stream.pos = 0
34
- @header = Structs::ELF_Ehdr.new(endian: endian)
36
+ @header = Structs::ELF_Ehdr.new(endian: endian, offset: stream.pos)
35
37
  @header.elf_class = elf_class
36
38
  @header.read(stream)
37
39
  end
@@ -290,8 +292,45 @@ module ELFTools
290
292
  end
291
293
  end
292
294
 
295
+ # The patch status.
296
+ # @return [Hash{Integer => String}]
297
+ def patches
298
+ patch = {}
299
+ loaded_headers.each do |header|
300
+ header.patches.each do |key, val|
301
+ patch[key + header.offset] = val
302
+ end
303
+ end
304
+ patch
305
+ end
306
+
307
+ # Apply patches and save as +filename+.
308
+ #
309
+ # @param [String] filename
310
+ # @return [void]
311
+ def save(filename)
312
+ stream.pos = 0
313
+ all = stream.read.force_encoding('ascii-8bit')
314
+ patches.each do |pos, val|
315
+ all[pos, val.size] = val
316
+ end
317
+ IO.binwrite(filename, all)
318
+ end
319
+
293
320
  private
294
321
 
322
+ # bad idea..
323
+ def loaded_headers
324
+ explore = lambda do |obj|
325
+ return obj if obj.is_a?(::ELFTools::Structs::ELFStruct)
326
+ return obj.map(&explore) if obj.is_a?(Array)
327
+ obj.instance_variables.map do |s|
328
+ explore.call(obj.instance_variable_get(s))
329
+ end
330
+ end
331
+ explore.call(self).flatten
332
+ end
333
+
295
334
  def identify
296
335
  stream.pos = 0
297
336
  magic = stream.read(4)
@@ -312,7 +351,7 @@ module ELFTools
312
351
 
313
352
  def create_section(n)
314
353
  stream.pos = header.e_shoff + n * header.e_shentsize
315
- shdr = Structs::ELF_Shdr.new(endian: endian)
354
+ shdr = Structs::ELF_Shdr.new(endian: endian, offset: stream.pos)
316
355
  shdr.elf_class = elf_class
317
356
  shdr.read(stream)
318
357
  Sections::Section.create(shdr, stream,
@@ -323,7 +362,7 @@ module ELFTools
323
362
 
324
363
  def create_segment(n)
325
364
  stream.pos = header.e_phoff + n * header.e_phentsize
326
- phdr = Structs::ELF_Phdr[elf_class].new(endian: endian)
365
+ phdr = Structs::ELF_Phdr[elf_class].new(endian: endian, offset: stream.pos)
327
366
  phdr.elf_class = elf_class
328
367
  Segments::Segment.create(phdr.read(stream), stream, offset_from_vma: method(:offset_from_vma))
329
368
  end
@@ -8,17 +8,17 @@ module ELFTools
8
8
  # @param [Integer] size
9
9
  # The size of array.
10
10
  # @yieldparam [Integer] i
11
- # Needs +i+-th element.
11
+ # Needs the +i+-th element.
12
12
  # @yieldreturn [Object]
13
13
  # Value of the +i+-th element.
14
14
  # @example
15
- # arr = LazyArray.new(10) { |i| p i; i * i }
15
+ # arr = LazyArray.new(10) { |i| p "calc #{i}"; i * i }
16
16
  # p arr[2]
17
- # # 2
17
+ # # "calc 2"
18
18
  # # 4
19
19
  #
20
20
  # p arr[3]
21
- # # 3
21
+ # # "calc 3"
22
22
  # # 9
23
23
  #
24
24
  # p arr[3]
data/lib/elftools/note.rb CHANGED
@@ -76,7 +76,7 @@ module ELFTools
76
76
  end
77
77
 
78
78
  def create_note(cur)
79
- nhdr = Structs::ELF_Nhdr.new(endian: endian).read(stream)
79
+ nhdr = Structs::ELF_Nhdr.new(endian: endian, offset: stream.pos).read(stream)
80
80
  ELFTools::Note::Note.new(nhdr, stream, cur)
81
81
  end
82
82
 
@@ -59,7 +59,7 @@ module ELFTools
59
59
  def create_relocation(n)
60
60
  stream.pos = header.sh_offset + n * header.sh_entsize
61
61
  klass = rela? ? Structs::ELF_Rela : Structs::ELF_Rel
62
- rel = klass.new(endian: header.class.self_endian)
62
+ rel = klass.new(endian: header.class.self_endian, offset: stream.pos)
63
63
  rel.elf_class = header.elf_class
64
64
  rel.read(stream)
65
65
  Relocation.new(rel, stream)
@@ -28,7 +28,7 @@ module ELFTools
28
28
  # @return [Integer]
29
29
  # The type, meaning of types are defined in {Constants::SHT}.
30
30
  def type
31
- header.sh_type
31
+ header.sh_type.to_i
32
32
  end
33
33
 
34
34
  # Get name of this section.
@@ -87,7 +87,7 @@ module ELFTools
87
87
 
88
88
  def create_symbol(n)
89
89
  stream.pos = header.sh_offset + n * header.sh_entsize
90
- sym = Structs::ELF_sym[header.elf_class].new(endian: header.class.self_endian)
90
+ sym = Structs::ELF_sym[header.elf_class].new(endian: header.class.self_endian, offset: stream.pos)
91
91
  sym.read(stream)
92
92
  Symbol.new(sym, stream, symstr: method(:symstr))
93
93
  end
@@ -2,7 +2,7 @@ require 'bindata'
2
2
  module ELFTools
3
3
  # Define ELF related structures in this module.
4
4
  #
5
- # Structures are fetch from https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h.
5
+ # Structures are fetched from https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h.
6
6
  # Using the bindata gem to make these structures support 32/64 bits and
7
7
  # little/big endian simultaneously.
8
8
  module Structs
@@ -14,11 +14,54 @@ module ELFTools
14
14
  }.freeze
15
15
 
16
16
  attr_accessor :elf_class # @return [Integer] 32 or 64.
17
+ attr_accessor :offset # @return [Integer] The file offset of this header.
17
18
 
18
- # Hacking to get endian of current class
19
- # @return [Symbol, nil] +:little+ or +:big+.
20
- def self.self_endian
21
- bindata_name[-2..-1] == 'ge' ? :big : :little
19
+ # Records which fields have been patched.
20
+ # @return [Hash{Integer => Integer}] Patches.
21
+ def patches
22
+ @patches ||= {}
23
+ end
24
+
25
+ class << self
26
+ # Hook constructor, while +BinData::Record+ doesn't allow us to override +#initialize+,
27
+ # so we hack +new+ here.
28
+ def new(**kwargs)
29
+ offset = kwargs.delete(:offset)
30
+ super.tap do |obj|
31
+ obj.offset = offset
32
+ obj.field_names.each do |f|
33
+ m = "#{f}=".to_sym
34
+ old_method = obj.singleton_method(m)
35
+ obj.define_singleton_method(m) do |val|
36
+ org = obj.send(f)
37
+ obj.patches[org.abs_offset] = ELFStruct.pack(val, org.num_bytes)
38
+ old_method.call(val)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # Hacking to get endian of current class
45
+ # @return [Symbol, nil] +:little+ or +:big+.
46
+ def self_endian
47
+ bindata_name[-2..-1] == 'ge' ? :big : :little
48
+ end
49
+
50
+ # Pack integer into string.
51
+ # @param [Integer] val
52
+ # @param [Integer] bytes
53
+ # @return [String]
54
+ def pack(val, bytes)
55
+ raise ArgumentError, "Not supported assign type #{val.class}" unless val.is_a?(Integer)
56
+ number = val & ((1 << (8 * bytes)) - 1)
57
+ out = []
58
+ bytes.times do
59
+ out << (number & 0xff)
60
+ number >>= 8
61
+ end
62
+ out = out.pack('C*')
63
+ self_endian == :little ? out : out.reverse
64
+ end
22
65
  end
23
66
  end
24
67
 
@@ -1,4 +1,4 @@
1
1
  module ELFTools
2
2
  # Current gem version
3
- VERSION = '0.2.2'.freeze
3
+ VERSION = '1.0.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elftools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-26 00:00:00.000000000 Z
11
+ date: 2017-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bindata
@@ -25,33 +25,33 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rspec
28
+ name: codeclimate-test-reporter
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '3.5'
33
+ version: '0.6'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '3.5'
40
+ version: '0.6'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop
42
+ name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.47'
47
+ version: '0.10'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.47'
54
+ version: '0.10'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -67,33 +67,47 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '12.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: simplecov
70
+ name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.13.0
75
+ version: '3.5'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.13.0
82
+ version: '3.5'
83
83
  - !ruby/object:Gem::Dependency
84
- name: codeclimate-test-reporter
84
+ name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.6'
89
+ version: '0.47'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.6'
96
+ version: '0.47'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.13.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.13.0
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: yard
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -164,5 +178,5 @@ rubyforge_project:
164
178
  rubygems_version: 2.5.2
165
179
  signing_key:
166
180
  specification_version: 4
167
- summary: ELFTools - Pure ruby library for parsing ELF files
181
+ summary: ELFTools - Pure ruby library for parsing and patching ELF files
168
182
  test_files: []