elftools 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []