patchelf 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +70 -12
- data/bin/patchelf.rb +1 -0
- data/lib/patchelf.rb +2 -0
- data/lib/patchelf/cli.rb +52 -8
- data/lib/patchelf/helper.rb +8 -6
- data/lib/patchelf/logger.rb +2 -0
- data/lib/patchelf/mm.rb +95 -78
- data/lib/patchelf/patcher.rb +105 -138
- data/lib/patchelf/saver.rb +284 -0
- data/lib/patchelf/version.rb +3 -1
- metadata +4 -5
- data/lib/patchelf/interval.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0268e11ae1f743826ab85b81245876d13ec987f4bf9b530f399260447b939aa
|
4
|
+
data.tar.gz: f8fca7603e33bdea3d44c328076a64a37172d61c49d1fd7a7915fe5b4d9ddba3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 462df528984a4f51efb4f9b0f589b0378ff4b28a1008357c0f7b7b4cdaa4a41739402c3821cc6b06a1dccd7292e02c1f2e1d9513a7926e9708b56e0abc66ba84
|
7
|
+
data.tar.gz: acddd987c408cf26dc323a6873d5db81aba3635ffba5e9eb6a3d4ca76f2d0d5cfe395739073f0594307274be756e5f270b162cbc952ac5d4797672b321ddeb5f
|
data/README.md
CHANGED
@@ -12,19 +12,34 @@ Implements features of NixOS/patchelf in pure Ruby.
|
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
15
|
-
|
15
|
+
Available on RubyGems.org!
|
16
|
+
```
|
17
|
+
$ gem install patchelf
|
18
|
+
```
|
16
19
|
|
17
20
|
## Usage
|
18
21
|
|
19
22
|
```
|
20
23
|
$ patchelf.rb
|
21
24
|
# Usage: patchelf.rb <commands> FILENAME [OUTPUT_FILE]
|
22
|
-
# --
|
23
|
-
# --
|
24
|
-
# --
|
25
|
-
# --
|
25
|
+
# --print-interpreter, --pi Show interpreter's name.
|
26
|
+
# --print-needed, --pn Show needed libraries specified in DT_NEEDED.
|
27
|
+
# --print-runpath, --pr Show the path specified in DT_RUNPATH.
|
28
|
+
# --print-soname, --ps Show soname specified in DT_SONAME.
|
29
|
+
# --set-interpreter, --interp INTERP
|
26
30
|
# Set interpreter's name.
|
27
|
-
# --
|
31
|
+
# --set-needed, --needed LIB1,LIB2,LIB3
|
32
|
+
# Set needed libraries, this will remove all existent needed libraries.
|
33
|
+
# --add-needed LIB Append a new needed library.
|
34
|
+
# --remove-needed LIB Remove a needed library.
|
35
|
+
# --replace-needed LIB1,LIB2 Replace needed library LIB1 as LIB2.
|
36
|
+
# --set-runpath, --runpath PATH
|
37
|
+
# Set the path of runpath.
|
38
|
+
# --force-rpath According to the ld.so docs, DT_RPATH is obsolete,
|
39
|
+
# patchelf.rb will always try to get/set DT_RUNPATH first.
|
40
|
+
# Use this option to force every operations related to runpath (e.g. --runpath)
|
41
|
+
# to consider 'DT_RPATH' instead of 'DT_RUNPATH'.
|
42
|
+
# --set-soname, --so SONAME Set name of a shared library.
|
28
43
|
# --version Show current gem's version.
|
29
44
|
|
30
45
|
```
|
@@ -32,8 +47,8 @@ $ patchelf.rb
|
|
32
47
|
### Display information
|
33
48
|
```
|
34
49
|
$ patchelf.rb --print-interpreter --print-needed /bin/ls
|
35
|
-
#
|
36
|
-
#
|
50
|
+
# interpreter: /lib64/ld-linux-x86-64.so.2
|
51
|
+
# needed: libselinux.so.1 libc.so.6
|
37
52
|
|
38
53
|
```
|
39
54
|
|
@@ -47,11 +62,54 @@ $ file ls.patch
|
|
47
62
|
|
48
63
|
```
|
49
64
|
|
65
|
+
### Modify dependency libraries
|
66
|
+
|
67
|
+
#### Add
|
68
|
+
```
|
69
|
+
$ patchelf.rb --add-needed libnew.so /bin/ls ls.patch
|
70
|
+
```
|
71
|
+
|
72
|
+
#### Remove
|
73
|
+
```
|
74
|
+
$ patchelf.rb --remove-needed libc.so.6 /bin/ls ls.patch
|
75
|
+
```
|
76
|
+
|
77
|
+
#### Replace
|
78
|
+
```
|
79
|
+
$ patchelf.rb --replace-needed libc.so.6,libcnew.so.6 /bin/ls ls.patch
|
80
|
+
|
81
|
+
$ readelf -d ls.patch | grep NEEDED
|
82
|
+
# 0x0000000000000001 (NEEDED) Shared library: [libselinux.so.1]
|
83
|
+
# 0x0000000000000001 (NEEDED) Shared library: [libcnew.so.6]
|
84
|
+
|
85
|
+
```
|
86
|
+
|
87
|
+
#### Set directly
|
88
|
+
```
|
89
|
+
$ patchelf.rb --needed a.so,b.so,c.so /bin/ls ls.patch
|
90
|
+
|
91
|
+
$ readelf -d ls.patch | grep NEEDED
|
92
|
+
# 0x0000000000000001 (NEEDED) Shared library: [a.so]
|
93
|
+
# 0x0000000000000001 (NEEDED) Shared library: [b.so]
|
94
|
+
# 0x0000000000000001 (NEEDED) Shared library: [c.so]
|
95
|
+
|
96
|
+
```
|
97
|
+
|
98
|
+
### Set RUNPATH of an executable
|
99
|
+
```
|
100
|
+
$ patchelf.rb --runpath . /bin/ls ls.patch
|
101
|
+
|
102
|
+
$ readelf -d ls.patch | grep RUNPATH
|
103
|
+
# 0x000000000000001d (RUNPATH) Library runpath: [.]
|
104
|
+
|
105
|
+
```
|
106
|
+
|
50
107
|
### Change SONAME of a shared library
|
51
108
|
```
|
52
|
-
$ patchelf.rb --
|
109
|
+
$ patchelf.rb --so libc.so.217 /lib/x86_64-linux-gnu/libc.so.6 libc.patch
|
53
110
|
|
54
|
-
$ readelf -d libc.
|
111
|
+
$ readelf -d libc.patch | grep SONAME
|
112
|
+
# 0x000000000000000e (SONAME) Library soname: [libc.so.217]
|
55
113
|
|
56
114
|
```
|
57
115
|
|
@@ -60,11 +118,11 @@ $ readelf -d libc.patched | grep SONAME
|
|
60
118
|
require 'patchelf'
|
61
119
|
|
62
120
|
patcher = PatchELF::Patcher.new('/bin/ls')
|
63
|
-
patcher.
|
121
|
+
patcher.interpreter
|
64
122
|
#=> "/lib64/ld-linux-x86-64.so.2"
|
65
123
|
|
66
124
|
patcher.interpreter = '/lib/AAAA.so.2'
|
67
|
-
patcher.
|
125
|
+
patcher.interpreter
|
68
126
|
#=> "/lib/AAAA.so.2"
|
69
127
|
|
70
128
|
patcher.save('ls.patch')
|
data/bin/patchelf.rb
CHANGED
data/lib/patchelf.rb
CHANGED
data/lib/patchelf/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
|
3
5
|
require 'patchelf/patcher'
|
@@ -25,25 +27,32 @@ module PatchELF
|
|
25
27
|
def work(argv)
|
26
28
|
@options = {
|
27
29
|
set: {},
|
28
|
-
print: []
|
30
|
+
print: [],
|
31
|
+
needed: []
|
29
32
|
}
|
30
33
|
return $stdout.puts "PatchELF Version #{PatchELF::VERSION}" if argv.include?('--version')
|
31
34
|
return $stdout.puts option_parser unless parse(argv)
|
32
35
|
|
33
36
|
# Now the options are (hopefully) valid, let's process the ELF file.
|
34
37
|
patcher = PatchELF::Patcher.new(@options[:in_file])
|
38
|
+
patcher.use_rpath! if @options[:force_rpath]
|
35
39
|
# TODO: Handle ELFTools::ELFError
|
36
40
|
@options[:print].uniq.each do |s|
|
37
|
-
content = patcher.
|
41
|
+
content = patcher.__send__(s)
|
38
42
|
next if content.nil?
|
39
43
|
|
40
|
-
|
44
|
+
s = :rpath if @options[:force_rpath] && s == :runpath
|
45
|
+
$stdout.puts "#{s}: #{Array(content).join(' ')}"
|
41
46
|
end
|
42
47
|
|
43
48
|
@options[:set].each do |sym, val|
|
44
49
|
patcher.__send__("#{sym}=".to_sym, val)
|
45
50
|
end
|
46
51
|
|
52
|
+
@options[:needed].each do |type, val|
|
53
|
+
patcher.__send__("#{type}_needed".to_sym, *val)
|
54
|
+
end
|
55
|
+
|
47
56
|
patcher.save(@options[:out_file])
|
48
57
|
end
|
49
58
|
|
@@ -62,23 +71,58 @@ module PatchELF
|
|
62
71
|
@option_parser ||= OptionParser.new do |opts|
|
63
72
|
opts.banner = USAGE
|
64
73
|
|
65
|
-
opts.on('--
|
74
|
+
opts.on('--print-interpreter', '--pi', 'Show interpreter\'s name.') do
|
66
75
|
@options[:print] << :interpreter
|
67
76
|
end
|
68
77
|
|
69
|
-
opts.on('--
|
78
|
+
opts.on('--print-needed', '--pn', 'Show needed libraries specified in DT_NEEDED.') do
|
70
79
|
@options[:print] << :needed
|
71
80
|
end
|
72
81
|
|
73
|
-
opts.on('--
|
82
|
+
opts.on('--print-runpath', '--pr', 'Show the path specified in DT_RUNPATH.') do
|
83
|
+
@options[:print] << :runpath
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on('--print-soname', '--ps', 'Show soname specified in DT_SONAME.') do
|
74
87
|
@options[:print] << :soname
|
75
88
|
end
|
76
89
|
|
77
|
-
opts.on('--
|
90
|
+
opts.on('--set-interpreter INTERP', '--interp INTERP', 'Set interpreter\'s name.') do |interp|
|
78
91
|
@options[:set][:interpreter] = interp
|
79
92
|
end
|
80
93
|
|
81
|
-
opts.on('--
|
94
|
+
opts.on('--set-needed LIB1,LIB2,LIB3', '--needed LIB1,LIB2,LIB3', Array,
|
95
|
+
'Set needed libraries, this will remove all existent needed libraries.') do |needs|
|
96
|
+
@options[:set][:needed] = needs
|
97
|
+
end
|
98
|
+
|
99
|
+
opts.on('--add-needed LIB', 'Append a new needed library.') do |lib|
|
100
|
+
@options[:needed] << [:add, lib]
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on('--remove-needed LIB', 'Remove a needed library.') do |lib|
|
104
|
+
@options[:needed] << [:remove, lib]
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.on('--replace-needed LIB1,LIB2', Array, 'Replace needed library LIB1 as LIB2.') do |libs|
|
108
|
+
@options[:needed] << [:replace, libs]
|
109
|
+
end
|
110
|
+
|
111
|
+
opts.on('--set-runpath PATH', '--runpath PATH', 'Set the path of runpath.') do |path|
|
112
|
+
@options[:set][:runpath] = path
|
113
|
+
end
|
114
|
+
|
115
|
+
opts.on(
|
116
|
+
'--force-rpath',
|
117
|
+
'According to the ld.so docs, DT_RPATH is obsolete,',
|
118
|
+
"#{SCRIPT_NAME} will always try to get/set DT_RUNPATH first.",
|
119
|
+
'Use this option to force every operations related to runpath (e.g. --runpath)',
|
120
|
+
'to consider \'DT_RPATH\' instead of \'DT_RUNPATH\'.'
|
121
|
+
) do
|
122
|
+
@options[:force_rpath] = true
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.on('--set-soname SONAME', '--so SONAME', 'Set name of a shared library.') do |soname|
|
82
126
|
@options[:set][:soname] = soname
|
83
127
|
end
|
84
128
|
|
data/lib/patchelf/helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PatchELF
|
2
4
|
# Helper methods for internal usage.
|
3
5
|
module Helper
|
@@ -40,11 +42,11 @@ module PatchELF
|
|
40
42
|
# @return [Integer]
|
41
43
|
# Aligned result.
|
42
44
|
# @example
|
43
|
-
#
|
45
|
+
# aligndown(0x1234)
|
44
46
|
# #=> 4096
|
45
|
-
#
|
47
|
+
# aligndown(0x33, 0x20)
|
46
48
|
# #=> 32
|
47
|
-
#
|
49
|
+
# aligndown(0x10, 0x8)
|
48
50
|
# #=> 16
|
49
51
|
def aligndown(val, align = PAGE_SIZE)
|
50
52
|
val - (val & (align - 1))
|
@@ -55,11 +57,11 @@ module PatchELF
|
|
55
57
|
# @return [Integer]
|
56
58
|
# Aligned result.
|
57
59
|
# @example
|
58
|
-
#
|
60
|
+
# alignup(0x1234)
|
59
61
|
# #=> 8192
|
60
|
-
#
|
62
|
+
# alignup(0x33, 0x20)
|
61
63
|
# #=> 64
|
62
|
-
#
|
64
|
+
# alignup(0x10, 0x8)
|
63
65
|
# #=> 16
|
64
66
|
def alignup(val, align = PAGE_SIZE)
|
65
67
|
(val & (align - 1)).zero? ? val : (aligndown(val, align) + align)
|
data/lib/patchelf/logger.rb
CHANGED
data/lib/patchelf/mm.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'patchelf/helper'
|
2
|
-
require 'patchelf/interval'
|
3
4
|
|
4
5
|
module PatchELF
|
5
6
|
# Memory management, provides malloc/free to allocate LOAD segments.
|
7
|
+
# @private
|
6
8
|
class MM
|
7
|
-
attr_reader :extend_size # @return [Integer]
|
8
|
-
attr_reader :threshold # @return [Integer]
|
9
|
+
attr_reader :extend_size # @return [Integer] The size extended.
|
10
|
+
attr_reader :threshold # @return [Integer] Where the file start to be extended.
|
9
11
|
|
10
12
|
# Instantiate a {MM} object.
|
11
13
|
# @param [ELFTools::ELFFile] elf
|
@@ -21,7 +23,7 @@ module PatchELF
|
|
21
23
|
# @yieldreturn [void]
|
22
24
|
# One can only do the following things in the block:
|
23
25
|
# 1. Set ELF headers' attributes (with ELFTools)
|
24
|
-
# 2. Invoke {
|
26
|
+
# 2. Invoke {Saver#inline_patch}
|
25
27
|
def malloc(size, &block)
|
26
28
|
# TODO: check size > 0
|
27
29
|
@request << [size, block]
|
@@ -32,39 +34,21 @@ module PatchELF
|
|
32
34
|
def dispatch!
|
33
35
|
return if @request.empty?
|
34
36
|
|
35
|
-
request_size = @request.map(&:first).inject(0, :+)
|
36
|
-
#
|
37
|
-
|
38
|
-
# We'
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
37
|
+
@request_size = @request.map(&:first).inject(0, :+)
|
38
|
+
# The malloc-ed area must be 'rw-' since the dynamic table will be modified during runtime.
|
39
|
+
# Find all LOADs and calculate their f-gaps and m-gaps.
|
40
|
+
# We prefer f-gap since it doesn't need move the whole binaries.
|
41
|
+
# 1. Find if any f-gap has enough size, and one of the LOAD next to it is 'rw-'.
|
42
|
+
# - expand (forwardlly), only need to change the attribute of LOAD.
|
43
|
+
# 2. Do 1. again but consider m-gaps instead.
|
44
|
+
# - expand (forwardlly), need to modify all section headers.
|
45
|
+
# 3. We have to create a new LOAD, now we need to expand the first LOAD for putting new segment header.
|
46
|
+
|
47
|
+
# First of all we check if there're less than two LOADs.
|
48
|
+
abnormal_elf('No LOAD segment found, not an executable.') if load_segments.empty?
|
49
|
+
# TODO: Handle only one LOAD. (be careful if memsz > filesz)
|
50
|
+
|
51
|
+
fgap_method || mgap_method || new_load_method
|
68
52
|
end
|
69
53
|
|
70
54
|
# Query if extended.
|
@@ -73,7 +57,11 @@ module PatchELF
|
|
73
57
|
defined?(@threshold)
|
74
58
|
end
|
75
59
|
|
60
|
+
# Get correct offset after the extension.
|
61
|
+
#
|
62
|
+
# @param [Integer] off
|
76
63
|
# @return [Integer]
|
64
|
+
# Shifted offset.
|
77
65
|
def extended_offset(off)
|
78
66
|
return off unless defined?(@threshold)
|
79
67
|
return off if off < @threshold
|
@@ -83,37 +71,74 @@ module PatchELF
|
|
83
71
|
|
84
72
|
private
|
85
73
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
74
|
+
def fgap_method
|
75
|
+
idx = find_gap { |prv, nxt| nxt.file_head - prv.file_tail }
|
76
|
+
return false if idx.nil?
|
77
|
+
|
78
|
+
loads = load_segments
|
79
|
+
# prefer extend backwardly
|
80
|
+
return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
|
91
81
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
82
|
+
extend_forward(loads[idx])
|
83
|
+
end
|
84
|
+
|
85
|
+
def extend_backward(seg, size = @request_size)
|
86
|
+
invoke_callbacks(seg, seg.file_tail)
|
87
|
+
seg.header.p_filesz += size
|
88
|
+
seg.header.p_memsz += size
|
89
|
+
true
|
90
|
+
end
|
97
91
|
|
92
|
+
def extend_forward(seg, size = @request_size)
|
93
|
+
seg.header.p_offset -= size
|
94
|
+
seg.header.p_vaddr -= size
|
95
|
+
seg.header.p_filesz += size
|
96
|
+
seg.header.p_memsz += size
|
97
|
+
invoke_callbacks(seg, seg.file_head)
|
98
98
|
true
|
99
99
|
end
|
100
100
|
|
101
|
-
|
102
|
-
|
103
|
-
#
|
104
|
-
|
101
|
+
def mgap_method
|
102
|
+
# | 1 | | 2 |
|
103
|
+
# | 1 | | 2 |
|
104
|
+
#=>
|
105
|
+
# | 1 | | 2 |
|
106
|
+
# | 1 | | 2 |
|
107
|
+
idx = find_gap(check_sz: false) { |prv, nxt| PatchELF::Helper.aligndown(nxt.mem_head) - prv.mem_tail }
|
108
|
+
return false if idx.nil?
|
105
109
|
|
106
|
-
loads = load_segments
|
107
|
-
|
110
|
+
loads = load_segments
|
111
|
+
@threshold = loads[idx].file_head
|
112
|
+
@extend_size = PatchELF::Helper.alignup(@request_size)
|
113
|
+
shift_attributes
|
114
|
+
# prefer backward than forward
|
115
|
+
return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
|
116
|
+
|
117
|
+
# note: loads[idx].file_head has been changed in shift_attributes
|
118
|
+
extend_forward(loads[idx], @extend_size)
|
119
|
+
end
|
120
|
+
|
121
|
+
def find_gap(check_sz: true)
|
122
|
+
loads = load_segments
|
123
|
+
loads.each_with_index do |l, i|
|
124
|
+
next if i.zero?
|
125
|
+
next unless writable?(l) || writable?(loads[i - 1])
|
126
|
+
|
127
|
+
sz = yield(loads[i - 1], l)
|
128
|
+
abnormal_elf('LOAD segments are out of order.') if check_sz && sz.negative?
|
129
|
+
next unless sz >= @request_size
|
130
|
+
|
131
|
+
return i
|
108
132
|
end
|
109
|
-
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def new_load_method
|
137
|
+
raise NotImplementedError
|
138
|
+
end
|
110
139
|
|
111
|
-
|
112
|
-
|
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)
|
140
|
+
def writable?(seg)
|
141
|
+
seg.readable? && seg.writable?
|
117
142
|
end
|
118
143
|
|
119
144
|
# For all attributes >= threshold, += offset
|
@@ -123,14 +148,18 @@ module PatchELF
|
|
123
148
|
# all
|
124
149
|
# Segments:
|
125
150
|
# all
|
126
|
-
# XXX: will be buggy if
|
151
|
+
# XXX: will be buggy if someday the number of segments can be changed.
|
127
152
|
|
128
153
|
# Bottom-up
|
129
154
|
@elf.each_sections do |sec|
|
130
155
|
sec.header.sh_offset += extend_size if sec.header.sh_offset >= threshold
|
131
156
|
end
|
132
157
|
@elf.each_segments do |seg|
|
133
|
-
|
158
|
+
next unless seg.header.p_offset >= threshold
|
159
|
+
|
160
|
+
seg.header.p_offset += extend_size
|
161
|
+
# We have to change align of LOAD segment since ld.so checks it.
|
162
|
+
seg.header.p_align = Helper::PAGE_SIZE if seg.is_a?(ELFTools::Segments::LoadSegment)
|
134
163
|
end
|
135
164
|
|
136
165
|
@elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold
|
@@ -140,28 +169,16 @@ module PatchELF
|
|
140
169
|
@elf.segments_by_type(:load)
|
141
170
|
end
|
142
171
|
|
143
|
-
def
|
144
|
-
|
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
|
172
|
+
def invoke_callbacks(seg, start)
|
173
|
+
cur = start
|
155
174
|
@request.each do |sz, block|
|
156
175
|
block.call(cur, seg.offset_to_vma(cur))
|
157
176
|
cur += sz
|
158
177
|
end
|
159
178
|
end
|
160
179
|
|
161
|
-
def
|
162
|
-
|
163
|
-
seg.header.p_filesz += size
|
164
|
-
seg.header.p_memsz += size
|
180
|
+
def abnormal_elf(msg)
|
181
|
+
raise ArgumentError, msg
|
165
182
|
end
|
166
183
|
end
|
167
184
|
end
|
data/lib/patchelf/patcher.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'elftools/elf_file'
|
3
5
|
|
4
6
|
require 'patchelf/logger'
|
5
|
-
require 'patchelf/
|
7
|
+
require 'patchelf/saver'
|
6
8
|
|
7
9
|
module PatchELF
|
8
10
|
# Class to handle all patching things.
|
@@ -17,6 +19,16 @@ module PatchELF
|
|
17
19
|
@in_file = filename
|
18
20
|
@elf = ELFTools::ELFFile.new(File.open(filename))
|
19
21
|
@set = {}
|
22
|
+
@rpath_sym = :runpath
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String?]
|
26
|
+
# Get interpreter's name.
|
27
|
+
# @example
|
28
|
+
# PatchELF::Patcher.new('/bin/ls').interpreter
|
29
|
+
# #=> "/lib64/ld-linux-x86-64.so.2"
|
30
|
+
def interpreter
|
31
|
+
@set[:interpreter] || interpreter_
|
20
32
|
end
|
21
33
|
|
22
34
|
# Set interpreter's name.
|
@@ -26,11 +38,73 @@ module PatchELF
|
|
26
38
|
# @param [String] interp
|
27
39
|
# @macro note_apply
|
28
40
|
def interpreter=(interp)
|
29
|
-
return if
|
41
|
+
return if interpreter_.nil? # will also show warning if there's no interp segment.
|
30
42
|
|
31
43
|
@set[:interpreter] = interp
|
32
44
|
end
|
33
45
|
|
46
|
+
# Get needed libraries.
|
47
|
+
# @return [Array<String>]
|
48
|
+
# @example
|
49
|
+
# patcher = PatchELF::Patcher.new('/bin/ls')
|
50
|
+
# patcher.needed
|
51
|
+
# #=> ["libselinux.so.1", "libc.so.6"]
|
52
|
+
def needed
|
53
|
+
@set[:needed] || needed_
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set needed libraries.
|
57
|
+
# @param [Array<String>] needs
|
58
|
+
# @macro note_apply
|
59
|
+
def needed=(needs)
|
60
|
+
@set[:needed] = needs
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add the needed library.
|
64
|
+
# @param [String] need
|
65
|
+
# @return [void]
|
66
|
+
# @macro note_apply
|
67
|
+
def add_needed(need)
|
68
|
+
@set[:needed] ||= needed_
|
69
|
+
@set[:needed] << need
|
70
|
+
end
|
71
|
+
|
72
|
+
# Remove the needed library.
|
73
|
+
# @param [String] need
|
74
|
+
# @return [void]
|
75
|
+
# @macro note_apply
|
76
|
+
def remove_needed(need)
|
77
|
+
@set[:needed] ||= needed_
|
78
|
+
@set[:needed].delete(need)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Replace needed library +src+ with +tar+.
|
82
|
+
#
|
83
|
+
# @param [String] src
|
84
|
+
# Library to be replaced.
|
85
|
+
# @param [String] tar
|
86
|
+
# Library replace with.
|
87
|
+
# @return [void]
|
88
|
+
# @macro note_apply
|
89
|
+
def replace_needed(src, tar)
|
90
|
+
@set[:needed] ||= needed_
|
91
|
+
@set[:needed].map! { |v| v == src ? tar : v }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get the soname of a shared library.
|
95
|
+
# @return [String?] The name.
|
96
|
+
# @example
|
97
|
+
# patcher = PatchELF::Patcher.new('/bin/ls')
|
98
|
+
# patcher.soname
|
99
|
+
# # [WARN] Entry DT_SONAME not found, not a shared library?
|
100
|
+
# #=> nil
|
101
|
+
# @example
|
102
|
+
# PatchELF::Patcher.new('/lib/x86_64-linux-gnu/libc.so.6').soname
|
103
|
+
# #=> "libc.so.6"
|
104
|
+
def soname
|
105
|
+
@set[:soname] || soname_
|
106
|
+
end
|
107
|
+
|
34
108
|
# Set soname.
|
35
109
|
#
|
36
110
|
# If the input ELF is not a shared library with a soname,
|
@@ -38,19 +112,32 @@ module PatchELF
|
|
38
112
|
# @param [String] name
|
39
113
|
# @macro note_apply
|
40
114
|
def soname=(name)
|
41
|
-
return if
|
115
|
+
return if soname_.nil?
|
42
116
|
|
43
117
|
@set[:soname] = name
|
44
118
|
end
|
45
119
|
|
46
|
-
#
|
120
|
+
# Get runpath.
|
121
|
+
# @return [String?]
|
122
|
+
def runpath
|
123
|
+
@set[@rpath_sym] || runpath_
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set runpath.
|
47
127
|
#
|
48
|
-
# If
|
49
|
-
# a new
|
50
|
-
# @param [String]
|
128
|
+
# If DT_RUNPATH is not presented in the input ELF,
|
129
|
+
# a new DT_RUNPATH attribute will be inserted into the DYNAMIC segment.
|
130
|
+
# @param [String] runpath
|
51
131
|
# @macro note_apply
|
52
|
-
def
|
53
|
-
@set[
|
132
|
+
def runpath=(runpath)
|
133
|
+
@set[@rpath_sym] = runpath
|
134
|
+
end
|
135
|
+
|
136
|
+
# Set all operations related to DT_RUNPATH to use DT_RPATH.
|
137
|
+
# @return [self]
|
138
|
+
def use_rpath!
|
139
|
+
@rpath_sym = :rpath
|
140
|
+
self
|
54
141
|
end
|
55
142
|
|
56
143
|
# Save the patched ELF as +out_file+.
|
@@ -58,87 +145,18 @@ module PatchELF
|
|
58
145
|
# If +out_file+ is +nil+, the original input file will be modified.
|
59
146
|
# @return [void]
|
60
147
|
def save(out_file = nil)
|
61
|
-
# TODO: Test if we can save twice, and the output files are exactly same.
|
62
148
|
# If nothing is modified, return directly.
|
63
149
|
return if out_file.nil? && !dirty?
|
64
150
|
|
65
151
|
out_file ||= @in_file
|
66
|
-
|
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]
|
152
|
+
saver = PatchELF::Saver.new(@in_file, out_file, @set)
|
130
153
|
|
131
|
-
|
154
|
+
saver.save!
|
132
155
|
end
|
133
156
|
|
134
157
|
private
|
135
158
|
|
136
|
-
|
137
|
-
# Get interpreter's name.
|
138
|
-
# @example
|
139
|
-
# Patcher.new('/bin/ls').interpreter
|
140
|
-
# #=> "/lib64/ld-linux-x86-64.so.2"
|
141
|
-
def interpreter
|
159
|
+
def interpreter_
|
142
160
|
segment = @elf.segment_by_type(:interp)
|
143
161
|
return PatchELF::Logger.warn('No interpreter found.') if segment.nil?
|
144
162
|
|
@@ -146,7 +164,7 @@ module PatchELF
|
|
146
164
|
end
|
147
165
|
|
148
166
|
# @return [Array<String>]
|
149
|
-
def
|
167
|
+
def needed_
|
150
168
|
segment = dynamic_or_log
|
151
169
|
return if segment.nil?
|
152
170
|
|
@@ -154,64 +172,20 @@ module PatchELF
|
|
154
172
|
end
|
155
173
|
|
156
174
|
# @return [String?]
|
157
|
-
def
|
158
|
-
|
159
|
-
tag_name_or_log(:rpath, 'Entry DT_RPATH not found.')
|
175
|
+
def runpath_
|
176
|
+
tag_name_or_log(@rpath_sym, "Entry DT_#{@rpath_sym.to_s.upcase} not found.")
|
160
177
|
end
|
161
178
|
|
162
179
|
# @return [String?]
|
163
|
-
def
|
180
|
+
def soname_
|
164
181
|
tag_name_or_log(:soname, 'Entry DT_SONAME not found, not a shared library?')
|
165
182
|
end
|
166
183
|
|
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
184
|
# @return [Boolean]
|
203
185
|
def dirty?
|
204
186
|
@set.any?
|
205
187
|
end
|
206
188
|
|
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
189
|
def tag_name_or_log(type, log_msg)
|
216
190
|
segment = dynamic_or_log
|
217
191
|
return if segment.nil?
|
@@ -227,12 +201,5 @@ module PatchELF
|
|
227
201
|
PatchELF::Logger.warn('DYNAMIC segment not found, might be a statically-linked ELF?') if s.nil?
|
228
202
|
end
|
229
203
|
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
204
|
end
|
238
205
|
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elftools/constants'
|
4
|
+
require 'elftools/elf_file'
|
5
|
+
require 'elftools/structs'
|
6
|
+
require 'elftools/util'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
require 'patchelf/mm'
|
10
|
+
|
11
|
+
module PatchELF
|
12
|
+
# Internal use only.
|
13
|
+
#
|
14
|
+
# For {Patcher} to do patching things and save to file.
|
15
|
+
# @private
|
16
|
+
class Saver
|
17
|
+
attr_reader :in_file # @return [String] Input filename.
|
18
|
+
attr_reader :out_file # @return [String] Output filename.
|
19
|
+
|
20
|
+
# Instantiate a {Saver} object.
|
21
|
+
# @param [String] in_file
|
22
|
+
# @param [String] out_file
|
23
|
+
# @param [{Symbol => String, Array}] set
|
24
|
+
def initialize(in_file, out_file, set)
|
25
|
+
@in_file = in_file
|
26
|
+
@out_file = out_file
|
27
|
+
@set = set
|
28
|
+
# [{Integer => String}]
|
29
|
+
@inline_patch = {}
|
30
|
+
@elf = ELFTools::ELFFile.new(File.open(in_file))
|
31
|
+
@mm = PatchELF::MM.new(@elf)
|
32
|
+
@strtab_extend_requests = []
|
33
|
+
@append_dyn = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [void]
|
37
|
+
def save!
|
38
|
+
# In this method we assume all attributes that should exist do exist.
|
39
|
+
# e.g. DT_INTERP, DT_DYNAMIC. These should have been checked in the patcher.
|
40
|
+
patch_interpreter
|
41
|
+
patch_dynamic
|
42
|
+
|
43
|
+
@mm.dispatch!
|
44
|
+
|
45
|
+
FileUtils.cp(in_file, out_file) if out_file != in_file
|
46
|
+
patch_out(@out_file)
|
47
|
+
# Let output file have the same permission as input.
|
48
|
+
FileUtils.chmod(File.stat(in_file).mode, out_file)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def patch_interpreter
|
54
|
+
return if @set[:interpreter].nil?
|
55
|
+
|
56
|
+
new_interp = @set[:interpreter] + "\x00"
|
57
|
+
old_interp = @elf.segment_by_type(:interp).interp_name + "\x00"
|
58
|
+
return if old_interp == new_interp
|
59
|
+
|
60
|
+
# These headers must be found here but not in the proc.
|
61
|
+
seg_header = @elf.segment_by_type(:interp).header
|
62
|
+
sec_header = section_header('.interp')
|
63
|
+
|
64
|
+
patch = proc do |off, vaddr|
|
65
|
+
# Register an inline patching
|
66
|
+
inline_patch(off, new_interp)
|
67
|
+
|
68
|
+
# The patching feature of ELFTools
|
69
|
+
seg_header.p_offset = off
|
70
|
+
seg_header.p_vaddr = seg_header.p_paddr = vaddr
|
71
|
+
seg_header.p_filesz = seg_header.p_memsz = new_interp.size
|
72
|
+
|
73
|
+
if sec_header
|
74
|
+
sec_header.sh_offset = off
|
75
|
+
sec_header.sh_size = new_interp.size
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if new_interp.size <= old_interp.size
|
80
|
+
# easy case
|
81
|
+
patch.call(seg_header.p_offset.to_i, seg_header.p_vaddr.to_i)
|
82
|
+
else
|
83
|
+
# hard case, we have to request a new LOAD area
|
84
|
+
@mm.malloc(new_interp.size, &patch)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def patch_dynamic
|
89
|
+
# We never do inline patching on strtab's string.
|
90
|
+
# 1. Search if there's useful string exists
|
91
|
+
# - only need header patching
|
92
|
+
# 2. Append a new string to the strtab.
|
93
|
+
# - register strtab extension
|
94
|
+
dynamic.tags # HACK, force @tags to be defined
|
95
|
+
patch_soname if @set[:soname]
|
96
|
+
patch_runpath if @set[:runpath]
|
97
|
+
patch_runpath(:rpath) if @set[:rpath]
|
98
|
+
patch_needed if @set[:needed]
|
99
|
+
malloc_strtab!
|
100
|
+
expand_dynamic!
|
101
|
+
end
|
102
|
+
|
103
|
+
def patch_soname
|
104
|
+
# The tag must exist.
|
105
|
+
so_tag = dynamic.tag_by_type(:soname)
|
106
|
+
reg_str_table(@set[:soname]) do |idx|
|
107
|
+
so_tag.header.d_val = idx
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def patch_runpath(sym = :runpath)
|
112
|
+
tag = dynamic.tag_by_type(sym)
|
113
|
+
tag = tag.nil? ? lazy_dyn(sym) : tag.header
|
114
|
+
reg_str_table(@set[sym]) do |idx|
|
115
|
+
tag.d_val = idx
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# To mark a not-using tag
|
120
|
+
IGNORE = ELFTools::Constants::DT_LOOS
|
121
|
+
def patch_needed
|
122
|
+
original_needs = dynamic.tags_by_type(:needed)
|
123
|
+
@set[:needed].uniq!
|
124
|
+
# 3 sets:
|
125
|
+
# 1. in original and in needs - remain unchanged
|
126
|
+
# 2. in original but not in needs - remove
|
127
|
+
# 3. not in original and in needs - append
|
128
|
+
original_needs.each do |n|
|
129
|
+
next if @set[:needed].include?(n.name)
|
130
|
+
|
131
|
+
n.header.d_tag = IGNORE # temporarily mark
|
132
|
+
end
|
133
|
+
|
134
|
+
extra = @set[:needed] - original_needs.map(&:name)
|
135
|
+
original_needs.each do |n|
|
136
|
+
break if extra.empty?
|
137
|
+
next if n.header.d_tag != IGNORE
|
138
|
+
|
139
|
+
n.header.d_tag = ELFTools::Constants::DT_NEEDED
|
140
|
+
reg_str_table(extra.shift) { |idx| n.header.d_val = idx }
|
141
|
+
end
|
142
|
+
return if extra.empty?
|
143
|
+
|
144
|
+
# no spaces, need append
|
145
|
+
extra.each do |name|
|
146
|
+
tag = lazy_dyn(:needed)
|
147
|
+
reg_str_table(name) { |idx| tag.d_val = idx }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Create a temp tag header.
|
152
|
+
# @return [ELFTools::Structs::ELF_Dyn]
|
153
|
+
def lazy_dyn(sym)
|
154
|
+
ELFTools::Structs::ELF_Dyn.new(endian: @elf.endian).tap do |dyn|
|
155
|
+
@append_dyn << dyn
|
156
|
+
dyn.elf_class = @elf.elf_class
|
157
|
+
dyn.d_tag = ELFTools::Util.to_constant(ELFTools::Constants::DT, sym)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def expand_dynamic!
|
162
|
+
return if @append_dyn.empty?
|
163
|
+
|
164
|
+
dyn_sec = section_header('.dynamic')
|
165
|
+
total = dynamic.tags.map(&:header)
|
166
|
+
# the last must be a null-tag
|
167
|
+
total = total[0..-2] + @append_dyn + [total.last]
|
168
|
+
bytes = total.first.num_bytes * total.size
|
169
|
+
@mm.malloc(bytes) do |off, vaddr|
|
170
|
+
inline_patch(off, total.map(&:to_binary_s).join)
|
171
|
+
dynamic.header.p_offset = off
|
172
|
+
dynamic.header.p_vaddr = dynamic.header.p_paddr = vaddr
|
173
|
+
dynamic.header.p_filesz = dynamic.header.p_memsz = bytes
|
174
|
+
if dyn_sec
|
175
|
+
dyn_sec.sh_offset = off
|
176
|
+
dyn_sec.sh_addr = vaddr
|
177
|
+
dyn_sec.sh_size = bytes
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def malloc_strtab!
|
183
|
+
return if @strtab_extend_requests.empty?
|
184
|
+
|
185
|
+
strtab = dynamic.tag_by_type(:strtab)
|
186
|
+
# Process registered requests
|
187
|
+
need_size = strtab_string.size + @strtab_extend_requests.reduce(0) { |sum, (str, _)| sum + str.size + 1 }
|
188
|
+
dynstr = section_header('.dynstr')
|
189
|
+
@mm.malloc(need_size) do |off, vaddr|
|
190
|
+
new_str = strtab_string + @strtab_extend_requests.map(&:first).join("\x00") + "\x00"
|
191
|
+
inline_patch(off, new_str)
|
192
|
+
@strtab_extend_requests.each do |str, block|
|
193
|
+
# TODO: make here more efficient
|
194
|
+
block.call(new_str.index(str + "\x00"))
|
195
|
+
end
|
196
|
+
# Now patching strtab header
|
197
|
+
strtab.header.d_val = vaddr
|
198
|
+
# We also need to patch dynstr to let readelf have correct output.
|
199
|
+
if dynstr
|
200
|
+
dynstr.sh_size = new_str.size
|
201
|
+
dynstr.sh_offset = off
|
202
|
+
dynstr.sh_addr = vaddr
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# @param [String] str
|
208
|
+
# @yieldparam [Integer] idx
|
209
|
+
# @yieldreturn [void]
|
210
|
+
def reg_str_table(str, &block)
|
211
|
+
idx = strtab_string.index(str + "\x00")
|
212
|
+
# Request string is already exist
|
213
|
+
return yield idx if idx
|
214
|
+
|
215
|
+
# Record the request
|
216
|
+
@strtab_extend_requests << [str, block]
|
217
|
+
end
|
218
|
+
|
219
|
+
def strtab_string
|
220
|
+
return @strtab_string if defined?(@strtab_string)
|
221
|
+
|
222
|
+
# TODO: handle no strtab exists..
|
223
|
+
offset = @elf.offset_from_vma(dynamic.tag_by_type(:strtab).value)
|
224
|
+
# This is a little tricky since no length information is stored in the tag.
|
225
|
+
# We first get the file offset of the string then 'guess' where the end is.
|
226
|
+
@elf.stream.pos = offset
|
227
|
+
@strtab_string = +''
|
228
|
+
loop do
|
229
|
+
c = @elf.stream.read(1)
|
230
|
+
break unless c =~ /\x00|[[:print:]]/
|
231
|
+
|
232
|
+
@strtab_string << c
|
233
|
+
end
|
234
|
+
@strtab_string
|
235
|
+
end
|
236
|
+
|
237
|
+
# This can only be used for patching interpreter's name
|
238
|
+
# or set strings in a malloc-ed area.
|
239
|
+
# i.e. NEVER intend to change the string defined in strtab
|
240
|
+
def inline_patch(off, str)
|
241
|
+
@inline_patch[off] = str
|
242
|
+
end
|
243
|
+
|
244
|
+
# Modify the out_file according to registered patches.
|
245
|
+
def patch_out(out_file)
|
246
|
+
File.open(out_file, 'r+') do |f|
|
247
|
+
if @mm.extended?
|
248
|
+
original_head = @mm.threshold
|
249
|
+
extra = {}
|
250
|
+
# Copy all data after the second load
|
251
|
+
@elf.stream.pos = original_head
|
252
|
+
extra[original_head + @mm.extend_size] = @elf.stream.read # read to end
|
253
|
+
# zero out the 'gap' we created
|
254
|
+
extra[original_head] = "\x00" * @mm.extend_size
|
255
|
+
extra.each do |pos, str|
|
256
|
+
f.pos = pos
|
257
|
+
f.write(str)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
@elf.patches.each do |pos, str|
|
261
|
+
f.pos = @mm.extended_offset(pos)
|
262
|
+
f.write(str)
|
263
|
+
end
|
264
|
+
|
265
|
+
@inline_patch.each do |pos, str|
|
266
|
+
f.pos = pos
|
267
|
+
f.write(str)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# @return [ELFTools::Sections::Section?]
|
273
|
+
def section_header(name)
|
274
|
+
sec = @elf.section_by_name(name)
|
275
|
+
return if sec.nil?
|
276
|
+
|
277
|
+
sec.header
|
278
|
+
end
|
279
|
+
|
280
|
+
def dynamic
|
281
|
+
@dynamic ||= @elf.segment_by_type(:dynamic)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
data/lib/patchelf/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patchelf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- david942j
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01-
|
11
|
+
date: 2019-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elftools
|
@@ -123,10 +123,10 @@ files:
|
|
123
123
|
- lib/patchelf.rb
|
124
124
|
- lib/patchelf/cli.rb
|
125
125
|
- lib/patchelf/helper.rb
|
126
|
-
- lib/patchelf/interval.rb
|
127
126
|
- lib/patchelf/logger.rb
|
128
127
|
- lib/patchelf/mm.rb
|
129
128
|
- lib/patchelf/patcher.rb
|
129
|
+
- lib/patchelf/saver.rb
|
130
130
|
- lib/patchelf/version.rb
|
131
131
|
homepage: https://github.com/david942j/patchelf.rb
|
132
132
|
licenses:
|
@@ -147,8 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
147
|
- !ruby/object:Gem::Version
|
148
148
|
version: '0'
|
149
149
|
requirements: []
|
150
|
-
|
151
|
-
rubygems_version: 2.7.6
|
150
|
+
rubygems_version: 3.0.2
|
152
151
|
signing_key:
|
153
152
|
specification_version: 4
|
154
153
|
summary: patchelf
|
data/lib/patchelf/interval.rb
DELETED
@@ -1,30 +0,0 @@
|
|
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
|