patchelf 0.0.0 → 0.1.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 +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
|