patchelf 0.0.0

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