patchelf 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e432d8ac7a3e75e8c9e80d433bc756e635d6b4d057b7e02ed952be113c7edbf5
4
+ data.tar.gz: 463cee3d6e780346706752f2a537e67fde649db82b206a8d79f15e60dd4cb4ae
5
+ SHA512:
6
+ metadata.gz: bebe4cca89632d3660045d7582c19cf356562d2cbd389cc910377e88ea293c4fb812bb8917e89670cad05224254497c361f608e7429dcfe2c09ed94d31834a9f
7
+ data.tar.gz: 849a0a0b5b8c5bc1c1e52c4f65c9452917f58cf881bca89de4a4986df0556fd677724fe1b17466ff469eed8bcada2320d69169951d9ea0ac4aed1d9be6e4d5aa
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ [![Build Status](https://travis-ci.org/david942j/patchelf.rb.svg?branch=master)](https://travis-ci.org/david942j/patchelf.rb)
2
+ [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=david942j/patchelf.rb)](https://dependabot.com)
3
+ [![Code Climate](https://codeclimate.com/github/david942j/patchelf.rb/badges/gpa.svg)](https://codeclimate.com/github/david942j/patchelf.rb)
4
+ [![Issue Count](https://codeclimate.com/github/david942j/patchelf.rb/badges/issue_count.svg)](https://codeclimate.com/github/david942j/patchelf.rb)
5
+ [![Test Coverage](https://codeclimate.com/github/david942j/patchelf.rb/badges/coverage.svg)](https://codeclimate.com/github/david942j/patchelf.rb/coverage)
6
+ [![Inline docs](https://inch-ci.org/github/david942j/patchelf.rb.svg?branch=master)](https://inch-ci.org/github/david942j/patchelf.rb)
7
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
8
+
9
+ # patchelf.rb
10
+
11
+ Implements features of NixOS/patchelf in pure Ruby.
12
+
13
+ ## Installation
14
+
15
+ WIP.
16
+
17
+ ## Usage
18
+
19
+ ```
20
+ $ patchelf.rb
21
+ # Usage: patchelf.rb <commands> FILENAME [OUTPUT_FILE]
22
+ # --pi, --print-interpreter Show interpreter's name.
23
+ # --pn, --print-needed Show needed libraries specified in DT_NEEDED.
24
+ # --ps, --print-soname Show soname specified in DT_SONAME.
25
+ # --interp, --set-interpreter INTERP
26
+ # Set interpreter's name.
27
+ # --so, --set-soname SONAME Set name of a shared library.
28
+ # --version Show current gem's version.
29
+
30
+ ```
31
+
32
+ ### Display information
33
+ ```
34
+ $ patchelf.rb --print-interpreter --print-needed /bin/ls
35
+ # Interpreter: /lib64/ld-linux-x86-64.so.2
36
+ # Needed: libselinux.so.1 libc.so.6
37
+
38
+ ```
39
+
40
+ ### Change the dynamic loader (interpreter)
41
+ ```
42
+ # $ patchelf.rb --interp NEW_INTERP input.elf output.elf
43
+ $ patchelf.rb --interp /lib/AAAA.so /bin/ls ls.patch
44
+
45
+ $ file ls.patch
46
+ # ls.patch: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/AAAA.so, for GNU/Linux 3.2.0, BuildID[sha1]=9567f9a28e66f4d7ec4baf31cfbf68d0410f0ae6, stripped
47
+
48
+ ```
49
+
50
+ ### Change SONAME of a shared library
51
+ ```
52
+ $ patchelf.rb --soname libnewname.so.217 /lib/x86_64-linux-gnu/libc.so.6 ./libc.patched
53
+
54
+ $ readelf -d libc.patched | grep SONAME
55
+
56
+ ```
57
+
58
+ ### As Ruby library
59
+ ```rb
60
+ require 'patchelf'
61
+
62
+ patcher = PatchELF::Patcher.new('/bin/ls')
63
+ patcher.get(:interpreter)
64
+ #=> "/lib64/ld-linux-x86-64.so.2"
65
+
66
+ patcher.interpreter = '/lib/AAAA.so.2'
67
+ patcher.get(:interpreter)
68
+ #=> "/lib/AAAA.so.2"
69
+
70
+ patcher.save('ls.patch')
71
+
72
+ # $ file ls.patch
73
+ # ls.patch: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/AAAA.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9567f9a28e66f4d7ec4baf31cfbf68d0410f0ae6, stripped
74
+
75
+ ```
76
+
77
+ ## Environment
78
+
79
+ patchelf.rb is implemented in pure Ruby, so it should work in all environments include Linux, maxOS, and Windows!
data/bin/patchelf.rb ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'patchelf/cli'
4
+
5
+ PatchELF::CLI.work(ARGV)
data/lib/patchelf.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Main module of patchelf.
2
+ #
3
+ # @author david942j
4
+ module PatchELF
5
+ end
6
+
7
+ require 'patchelf/patcher'
8
+ require 'patchelf/version'
@@ -0,0 +1,91 @@
1
+ require 'optparse'
2
+
3
+ require 'patchelf/patcher'
4
+ require 'patchelf/version'
5
+
6
+ module PatchELF
7
+ # For command line interface to parsing arguments.
8
+ module CLI
9
+ # Name of binary.
10
+ SCRIPT_NAME = 'patchelf.rb'.freeze
11
+ # CLI usage string.
12
+ USAGE = format('Usage: %s <commands> FILENAME [OUTPUT_FILE]', SCRIPT_NAME).freeze
13
+
14
+ module_function
15
+
16
+ # Main method of CLI.
17
+ # @param [Array<String>] argv
18
+ # Command line arguments.
19
+ # @return [void]
20
+ # @example
21
+ # PatchELF::CLI.work(%w[--help])
22
+ # # usage message to stdout
23
+ # PatchELF::CLI.work(%w[--version])
24
+ # # version message to stdout
25
+ def work(argv)
26
+ @options = {
27
+ set: {},
28
+ print: []
29
+ }
30
+ return $stdout.puts "PatchELF Version #{PatchELF::VERSION}" if argv.include?('--version')
31
+ return $stdout.puts option_parser unless parse(argv)
32
+
33
+ # Now the options are (hopefully) valid, let's process the ELF file.
34
+ patcher = PatchELF::Patcher.new(@options[:in_file])
35
+ # TODO: Handle ELFTools::ELFError
36
+ @options[:print].uniq.each do |s|
37
+ content = patcher.get(s)
38
+ next if content.nil?
39
+
40
+ $stdout.puts "#{s.to_s.capitalize}: #{Array(content).join(' ')}"
41
+ end
42
+
43
+ @options[:set].each do |sym, val|
44
+ patcher.__send__("#{sym}=".to_sym, val)
45
+ end
46
+
47
+ patcher.save(@options[:out_file])
48
+ end
49
+
50
+ private
51
+
52
+ def parse(argv)
53
+ remain = option_parser.permute(argv)
54
+ return false if remain.first.nil?
55
+
56
+ @options[:in_file] = remain.first
57
+ @options[:out_file] = remain[1] # can be nil
58
+ true
59
+ end
60
+
61
+ def option_parser
62
+ @option_parser ||= OptionParser.new do |opts|
63
+ opts.banner = USAGE
64
+
65
+ opts.on('--pi', '--print-interpreter', 'Show interpreter\'s name.') do
66
+ @options[:print] << :interpreter
67
+ end
68
+
69
+ opts.on('--pn', '--print-needed', 'Show needed libraries specified in DT_NEEDED.') do
70
+ @options[:print] << :needed
71
+ end
72
+
73
+ opts.on('--ps', '--print-soname', 'Show soname specified in DT_SONAME.') do
74
+ @options[:print] << :soname
75
+ end
76
+
77
+ opts.on('--interp INTERP', '--set-interpreter INTERP', 'Set interpreter\'s name.') do |interp|
78
+ @options[:set][:interpreter] = interp
79
+ end
80
+
81
+ opts.on('--so SONAME', '--set-soname SONAME', 'Set name of a shared library.') do |soname|
82
+ @options[:set][:soname] = soname
83
+ end
84
+
85
+ opts.on('--version', 'Show current gem\'s version.') {}
86
+ end
87
+ end
88
+
89
+ extend self
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ module PatchELF
2
+ # Helper methods for internal usage.
3
+ module Helper
4
+ # The size of one page.
5
+ PAGE_SIZE = 0x1000
6
+
7
+ module_function
8
+
9
+ # Color codes for pretty print.
10
+ COLOR_CODE = {
11
+ esc_m: "\e[0m",
12
+ info: "\e[38;5;82m", # light green
13
+ warn: "\e[38;5;230m", # light yellow
14
+ error: "\e[38;5;196m" # heavy red
15
+ }.freeze
16
+
17
+ # For wrapping string with color codes for prettier inspect.
18
+ # @param [String] str
19
+ # Content to colorize.
20
+ # @param [Symbol] type
21
+ # Specify which kind of color to use, valid symbols are defined in {.COLOR_CODE}.
22
+ # @return [String]
23
+ # String that wrapped with color codes.
24
+ def colorize(str, type)
25
+ return str unless color_enabled?
26
+
27
+ cc = COLOR_CODE
28
+ color = cc.key?(type) ? cc[type] : ''
29
+ "#{color}#{str.sub(COLOR_CODE[:esc_m], color)}#{cc[:esc_m]}"
30
+ end
31
+
32
+ # For {#colorize} to decide if need add color codes.
33
+ # @return [Boolean]
34
+ def color_enabled?
35
+ $stderr.tty?
36
+ end
37
+
38
+ # @param [Integer] val
39
+ # @param [Integer] align
40
+ # @return [Integer]
41
+ # Aligned result.
42
+ # @example
43
+ # Helper.aligndown(0x1234)
44
+ # #=> 4096
45
+ # Helper.aligndown(0x33, 0x20)
46
+ # #=> 32
47
+ # Helper.aligndown(0x10, 0x8)
48
+ # #=> 16
49
+ def aligndown(val, align = PAGE_SIZE)
50
+ val - (val & (align - 1))
51
+ end
52
+
53
+ # @param [Integer] val
54
+ # @param [Integer] align
55
+ # @return [Integer]
56
+ # Aligned result.
57
+ # @example
58
+ # Helper.alignup(0x1234)
59
+ # #=> 8192
60
+ # Helper.alignup(0x33, 0x20)
61
+ # #=> 64
62
+ # Helper.alignup(0x10, 0x8)
63
+ # #=> 16
64
+ def alignup(val, align = PAGE_SIZE)
65
+ (val & (align - 1)).zero? ? val : (aligndown(val, align) + align)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ module PatchELF
2
+ # Provides easier-to-use methods for manipulating LOAD segment.
3
+ #
4
+ # Internal use only.
5
+ class Interval
6
+ include Comparable
7
+
8
+ attr_reader :head # @return [Integer] Head.
9
+ attr_reader :size # @return [Integer] Length.
10
+
11
+ # @param [Integer] head
12
+ # @param [Integer] size
13
+ def initialize(head, size)
14
+ @head = head
15
+ @size = size
16
+ end
17
+
18
+ # Comparator.
19
+ # @param [Interval] other
20
+ def <=>(other)
21
+ head <=> other.head
22
+ end
23
+
24
+ # @return [Integer]
25
+ # The end of this interval.
26
+ def tail
27
+ head + size
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ require 'logger'
2
+
3
+ require 'patchelf/helper'
4
+
5
+ module PatchELF
6
+ # A logger for internal usage.
7
+ module Logger
8
+ module_function
9
+
10
+ @logger = ::Logger.new($stderr).tap do |log|
11
+ log.formatter = proc do |severity, _datetime, _progname, msg|
12
+ "[#{PatchELF::Helper.colorize(severity, severity.downcase.to_sym)}] #{msg}\n"
13
+ end
14
+ end
15
+
16
+ %i[info warn error].each do |sym|
17
+ define_method(sym) do |msg|
18
+ @logger.__send__(sym, msg)
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,167 @@
1
+ require 'patchelf/helper'
2
+ require 'patchelf/interval'
3
+
4
+ module PatchELF
5
+ # Memory management, provides malloc/free to allocate LOAD segments.
6
+ class MM
7
+ attr_reader :extend_size # @return [Integer]
8
+ attr_reader :threshold # @return [Integer]
9
+
10
+ # Instantiate a {MM} object.
11
+ # @param [ELFTools::ELFFile] elf
12
+ def initialize(elf)
13
+ @elf = elf
14
+ @request = []
15
+ end
16
+
17
+ # @param [Integer] size
18
+ # @return [void]
19
+ # @yieldparam [Integer] off
20
+ # @yieldparam [Integer] vaddr
21
+ # @yieldreturn [void]
22
+ # One can only do the following things in the block:
23
+ # 1. Set ELF headers' attributes (with ELFTools)
24
+ # 2. Invoke {Patcher#inline_patch}
25
+ def malloc(size, &block)
26
+ # TODO: check size > 0
27
+ @request << [size, block]
28
+ end
29
+
30
+ # Let the malloc / free requests be effective.
31
+ # @return [void]
32
+ def dispatch!
33
+ return if @request.empty?
34
+
35
+ request_size = @request.map(&:first).inject(0, :+)
36
+ # TODO: raise exception if no LOAD exists.
37
+
38
+ # We're going to expand the first LOAD segment.
39
+ # Sometimes there's a 'gap' between the first and the second LOAD segment,
40
+ # in this case we only need to expand the first LOAD segment and remain all other things unchanged.
41
+ if gap_useful?(request_size)
42
+ invoke_callbacks
43
+ grow_first_load(request_size)
44
+ elsif extendable?(request_size)
45
+ # After extended we should have large enough 'gap'.
46
+
47
+ # | 1 | | 2 |
48
+ # | 1 | | 2 |
49
+ #=>
50
+ # | 1 | | 2 |
51
+ # | 1 | | 2 |
52
+ # This is really dangerous..
53
+ # We have to check all p_offset / sh_offset
54
+ # 1. Use ELFTools to patch all headers
55
+ # 2. Mark the extended size, inline_patch will behave different after this.
56
+ # 3. Invoke block.call, which might copy tables and (not-allow-to-patch) strings into the gap
57
+
58
+ @threshold = load_segments[1].file_head
59
+ # 1.file_tail + request_size <= 2.file_head + 0x1000x
60
+ @extend_size = PatchELF::Helper.alignup(request_size - gap_between_load.size)
61
+ shift_attributes
62
+
63
+ invoke_callbacks
64
+ grow_first_load(request_size)
65
+ # else
66
+ # This can happen in 32bit
67
+ end
68
+ end
69
+
70
+ # Query if extended.
71
+ # @return [Boolean]
72
+ def extended?
73
+ defined?(@threshold)
74
+ end
75
+
76
+ # @return [Integer]
77
+ def extended_offset(off)
78
+ return off unless defined?(@threshold)
79
+ return off if off < @threshold
80
+
81
+ off + @extend_size
82
+ end
83
+
84
+ private
85
+
86
+ def gap_useful?(need_size)
87
+ # Two conditions:
88
+ # 1. gap is large enough
89
+ gap = gap_between_load
90
+ return false if gap.size < need_size
91
+
92
+ # XXX: Do we really need this..?
93
+ # If gap is enough but not all zeros, we will fail on extension..
94
+ # 2. gap is all zeroes.
95
+ # @elf.stream.pos = gap.head
96
+ # return false unless @elf.stream.read(gap.size).bytes.inject(0, :+).zero?
97
+
98
+ true
99
+ end
100
+
101
+ # @return [PatchELF::Interval]
102
+ def gap_between_load
103
+ # We need this cache since the second LOAD might be changed
104
+ return @gap_between_load if defined?(@gap_between_load)
105
+
106
+ loads = load_segments.map do |seg|
107
+ PatchELF::Interval.new(seg.file_head, seg.size)
108
+ end
109
+ # TODO: raise if loads.min != loads.first
110
+
111
+ loads.sort!
112
+ # Only one LOAD, the gap has infinity size!
113
+ size = if loads.size == 1 then Float::INFINITY
114
+ else loads[1].head - loads.first.tail
115
+ end
116
+ @gap_between_load = PatchELF::Interval.new(loads.first.tail, size)
117
+ end
118
+
119
+ # For all attributes >= threshold, += offset
120
+ def shift_attributes
121
+ # ELFHeader->section_header
122
+ # Sections:
123
+ # all
124
+ # Segments:
125
+ # all
126
+ # XXX: will be buggy if one day the number of segments might be changed.
127
+
128
+ # Bottom-up
129
+ @elf.each_sections do |sec|
130
+ sec.header.sh_offset += extend_size if sec.header.sh_offset >= threshold
131
+ end
132
+ @elf.each_segments do |seg|
133
+ seg.header.p_offset += extend_size if seg.header.p_offset >= threshold
134
+ end
135
+
136
+ @elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold
137
+ end
138
+
139
+ def load_segments
140
+ @elf.segments_by_type(:load)
141
+ end
142
+
143
+ def extendable?(request_size)
144
+ loads = load_segments
145
+ # We can assume loads.size >= 2 because
146
+ # 0: has raised an exception before
147
+ # 1: the gap must be used, nobody cares extendable size.
148
+ # Calcluate the max size of the first LOAD segment can be.
149
+ PatchELF::Helper.aligndown(loads[1].mem_head) - loads.first.mem_tail >= request_size
150
+ end
151
+
152
+ def invoke_callbacks
153
+ seg = load_segments.first
154
+ cur = gap_between_load.head
155
+ @request.each do |sz, block|
156
+ block.call(cur, seg.offset_to_vma(cur))
157
+ cur += sz
158
+ end
159
+ end
160
+
161
+ def grow_first_load(size)
162
+ seg = load_segments.first
163
+ seg.header.p_filesz += size
164
+ seg.header.p_memsz += size
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,238 @@
1
+ require 'elftools'
2
+ require 'fileutils'
3
+
4
+ require 'patchelf/logger'
5
+ require 'patchelf/mm'
6
+
7
+ module PatchELF
8
+ # Class to handle all patching things.
9
+ class Patcher
10
+ # @!macro [new] note_apply
11
+ # @note This setting will be saved after {#save} being invoked.
12
+
13
+ # Instantiate a {Patcher} object.
14
+ # @param [String] filename
15
+ # Filename of input ELF.
16
+ def initialize(filename)
17
+ @in_file = filename
18
+ @elf = ELFTools::ELFFile.new(File.open(filename))
19
+ @set = {}
20
+ end
21
+
22
+ # Set interpreter's name.
23
+ #
24
+ # If the input ELF has no existent interpreter,
25
+ # this method will show a warning and has no effect.
26
+ # @param [String] interp
27
+ # @macro note_apply
28
+ def interpreter=(interp)
29
+ return if interpreter.nil? # will also show warning if there's no interp segment.
30
+
31
+ @set[:interpreter] = interp
32
+ end
33
+
34
+ # Set soname.
35
+ #
36
+ # If the input ELF is not a shared library with a soname,
37
+ # this method will show a warning and has no effect.
38
+ # @param [String] name
39
+ # @macro note_apply
40
+ def soname=(name)
41
+ return if soname.nil?
42
+
43
+ @set[:soname] = name
44
+ end
45
+
46
+ # Set rpath.
47
+ #
48
+ # If DT_RPATH is not presented in the input ELF,
49
+ # a new DT_RPATH attribute will be inserted into the DYNAMIC segment.
50
+ # @param [String] rpath
51
+ # @macro note_apply
52
+ def rpath=(rpath)
53
+ @set[:rpath] = rpath
54
+ end
55
+
56
+ # Save the patched ELF as +out_file+.
57
+ # @param [String?] out_file
58
+ # If +out_file+ is +nil+, the original input file will be modified.
59
+ # @return [void]
60
+ def save(out_file = nil)
61
+ # TODO: Test if we can save twice, and the output files are exactly same.
62
+ # If nothing is modified, return directly.
63
+ return if out_file.nil? && !dirty?
64
+
65
+ out_file ||= @in_file
66
+ # [{Integer => String}]
67
+ @inline_patch = {}
68
+ @mm = PatchELF::MM.new(@elf)
69
+ # Patching interpreter is the easiest.
70
+ patch_interpreter(@set[:interpreter])
71
+
72
+ @mm.dispatch!
73
+
74
+ FileUtils.cp(@in_file, out_file) if out_file != @in_file
75
+ # if @mm.extend_size != 0:
76
+ # 1. Remember all data after the original second LOAD
77
+ # 2. Apply patches before the second LOAD.
78
+ # 3. Apply patches located after the second LOAD.
79
+
80
+ File.open(out_file, 'r+') do |f|
81
+ if @mm.extended?
82
+ original_head = @mm.threshold
83
+ extra = {}
84
+ # Copy all data after the second load
85
+ @elf.stream.pos = original_head
86
+ extra[original_head + @mm.extend_size] = @elf.stream.read # read to end
87
+ # zero out the 'gap' we created
88
+ extra[original_head] = "\x00" * @mm.extend_size
89
+ extra.each do |pos, str|
90
+ f.pos = pos
91
+ f.write(str)
92
+ end
93
+ end
94
+ @elf.patches.each do |pos, str|
95
+ f.pos = @mm.extended_offset(pos)
96
+ f.write(str)
97
+ end
98
+
99
+ @inline_patch.each do |pos, str|
100
+ f.pos = pos
101
+ f.write(str)
102
+ end
103
+ end
104
+
105
+ # Let output file have the same permission as input.
106
+ FileUtils.chmod(File.stat(@in_file).mode, out_file)
107
+ end
108
+
109
+ # Get name(s) of interpreter, needed libraries, rpath, or soname.
110
+ #
111
+ # @param [:interpreter, :needed, :rpath, :soname] name
112
+ # @return [String, Array<String>, nil]
113
+ # Returns name(s) fetched from ELF.
114
+ # @example
115
+ # patcher = Patcher.new('/bin/ls')
116
+ # patcher.get(:interpreter)
117
+ # #=> "/lib64/ld-linux-x86-64.so.2"
118
+ # patcher.get(:needed)
119
+ # #=> ["libselinux.so.1", "libc.so.6"]
120
+ #
121
+ # patcher.get(:soname)
122
+ # # [WARN] Entry DT_SONAME not found, not a shared library?
123
+ # #=> nil
124
+ # @example
125
+ # Patcher.new('/lib/x86_64-linux-gnu/libc.so.6').get(:soname)
126
+ # #=> "libc.so.6"
127
+ def get(name)
128
+ return unless %i[interpreter needed rpath soname].include?(name)
129
+ return @set[name] if @set[name]
130
+
131
+ __send__(name)
132
+ end
133
+
134
+ private
135
+
136
+ # @return [String?]
137
+ # Get interpreter's name.
138
+ # @example
139
+ # Patcher.new('/bin/ls').interpreter
140
+ # #=> "/lib64/ld-linux-x86-64.so.2"
141
+ def interpreter
142
+ segment = @elf.segment_by_type(:interp)
143
+ return PatchELF::Logger.warn('No interpreter found.') if segment.nil?
144
+
145
+ segment.interp_name
146
+ end
147
+
148
+ # @return [Array<String>]
149
+ def needed
150
+ segment = dynamic_or_log
151
+ return if segment.nil?
152
+
153
+ segment.tags_by_type(:needed).map(&:name)
154
+ end
155
+
156
+ # @return [String?]
157
+ def rpath
158
+ # TODO: consider both rpath and runpath
159
+ tag_name_or_log(:rpath, 'Entry DT_RPATH not found.')
160
+ end
161
+
162
+ # @return [String?]
163
+ def soname
164
+ tag_name_or_log(:soname, 'Entry DT_SONAME not found, not a shared library?')
165
+ end
166
+
167
+ def patch_interpreter(new_interp)
168
+ return if new_interp.nil?
169
+
170
+ new_interp += "\x00"
171
+ old_interp = interpreter + "\x00"
172
+ return if old_interp == new_interp
173
+
174
+ # These headers must be found here but not in the proc.
175
+ seg_header = @elf.segment_by_type(:interp).header
176
+ sec_header = section_header('.interp')
177
+
178
+ patch = proc do |off, vaddr|
179
+ # Register an inline patching
180
+ inline_patch(off, new_interp)
181
+
182
+ # The patching feature of ELFTools
183
+ seg_header.p_offset = off
184
+ seg_header.p_vaddr = seg_header.p_paddr = vaddr
185
+ seg_header.p_filesz = seg_header.p_memsz = new_interp.size
186
+
187
+ if sec_header
188
+ sec_header.sh_offset = off
189
+ sec_header.sh_size = new_interp.size
190
+ end
191
+ end
192
+
193
+ if new_interp.size <= old_interp.size
194
+ # easy case
195
+ patch.call(seg_header.p_offset.to_i, seg_header.p_vaddr.to_i)
196
+ else
197
+ # hard case, we have to request a new LOAD area
198
+ @mm.malloc(new_interp.size, &patch)
199
+ end
200
+ end
201
+
202
+ # @return [Boolean]
203
+ def dirty?
204
+ @set.any?
205
+ end
206
+
207
+ # @return [ELFTools::Sections::Section?]
208
+ def section_header(name)
209
+ sec = @elf.section_by_name(name)
210
+ return if sec.nil?
211
+
212
+ sec.header
213
+ end
214
+
215
+ def tag_name_or_log(type, log_msg)
216
+ segment = dynamic_or_log
217
+ return if segment.nil?
218
+
219
+ tag = segment.tag_by_type(type)
220
+ return PatchELF::Logger.warn(log_msg) if tag.nil?
221
+
222
+ tag.name
223
+ end
224
+
225
+ def dynamic_or_log
226
+ @elf.segment_by_type(:dynamic).tap do |s|
227
+ PatchELF::Logger.warn('DYNAMIC segment not found, might be a statically-linked ELF?') if s.nil?
228
+ end
229
+ end
230
+
231
+ # This can only be used for patching interpreter's name
232
+ # or set strings in a malloc-ed area.
233
+ # i.e. NEVER intend to change the string defined in strtab
234
+ def inline_patch(off, str)
235
+ @inline_patch[@mm.extended_offset(off)] = str
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,4 @@
1
+ module PatchELF
2
+ # Current gem version.
3
+ VERSION = '0.0.0'.freeze
4
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: patchelf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - david942j
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: elftools
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.62'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.62'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.16.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.16.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-platform
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ description: |2
112
+ A simple utility for modifying existing ELF executables and
113
+ libraries.
114
+ email:
115
+ - david942j@gmail.com
116
+ executables:
117
+ - patchelf.rb
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - README.md
122
+ - bin/patchelf.rb
123
+ - lib/patchelf.rb
124
+ - lib/patchelf/cli.rb
125
+ - lib/patchelf/helper.rb
126
+ - lib/patchelf/interval.rb
127
+ - lib/patchelf/logger.rb
128
+ - lib/patchelf/mm.rb
129
+ - lib/patchelf/patcher.rb
130
+ - lib/patchelf/version.rb
131
+ homepage: https://github.com/david942j/patchelf.rb
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: 2.1.0
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.7.6
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: patchelf
155
+ test_files: []