patchelf 0.0.0 → 1.2.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 +71 -13
- data/bin/patchelf.rb +1 -0
- data/lib/patchelf.rb +2 -0
- data/lib/patchelf/cli.rb +70 -13
- data/lib/patchelf/exceptions.rb +13 -0
- data/lib/patchelf/helper.rb +8 -6
- data/lib/patchelf/logger.rb +2 -0
- data/lib/patchelf/mm.rb +98 -79
- data/lib/patchelf/patcher.rb +143 -138
- data/lib/patchelf/saver.rb +285 -0
- data/lib/patchelf/version.rb +3 -1
- metadata +10 -10
- data/lib/patchelf/interval.rb +0 -30
data/lib/patchelf/patcher.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'elftools/elf_file'
|
5
|
+
|
6
|
+
require 'patchelf/exceptions'
|
4
7
|
require 'patchelf/logger'
|
5
|
-
require 'patchelf/
|
8
|
+
require 'patchelf/saver'
|
6
9
|
|
7
10
|
module PatchELF
|
8
11
|
# Class to handle all patching things.
|
@@ -10,13 +13,36 @@ module PatchELF
|
|
10
13
|
# @!macro [new] note_apply
|
11
14
|
# @note This setting will be saved after {#save} being invoked.
|
12
15
|
|
16
|
+
attr_reader :elf # @return [ELFTools::ELFFile] ELF parser object.
|
17
|
+
|
13
18
|
# Instantiate a {Patcher} object.
|
14
19
|
# @param [String] filename
|
15
20
|
# Filename of input ELF.
|
16
|
-
|
21
|
+
# @param [Boolean] logging
|
22
|
+
# *deprecated*: use +on_error+ instead
|
23
|
+
# @param [:log, :silent, :exception] on_error
|
24
|
+
# action when the desired segment/tag field isn't present
|
25
|
+
# :log = logs to stderr
|
26
|
+
# :exception = raise exception related to the error
|
27
|
+
# :silent = ignore the errors
|
28
|
+
def initialize(filename, on_error: :log, logging: true)
|
17
29
|
@in_file = filename
|
18
30
|
@elf = ELFTools::ELFFile.new(File.open(filename))
|
19
31
|
@set = {}
|
32
|
+
@rpath_sym = :runpath
|
33
|
+
@on_error = !logging ? :exception : on_error
|
34
|
+
|
35
|
+
on_error_syms = %i[exception log silent]
|
36
|
+
raise ArgumentError, "on_error must be one of #{on_error_syms}" unless on_error_syms.include?(@on_error)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String?]
|
40
|
+
# Get interpreter's name.
|
41
|
+
# @example
|
42
|
+
# PatchELF::Patcher.new('/bin/ls').interpreter
|
43
|
+
# #=> "/lib64/ld-linux-x86-64.so.2"
|
44
|
+
def interpreter
|
45
|
+
@set[:interpreter] || interpreter_
|
20
46
|
end
|
21
47
|
|
22
48
|
# Set interpreter's name.
|
@@ -26,11 +52,73 @@ module PatchELF
|
|
26
52
|
# @param [String] interp
|
27
53
|
# @macro note_apply
|
28
54
|
def interpreter=(interp)
|
29
|
-
return if
|
55
|
+
return if interpreter_.nil? # will also show warning if there's no interp segment.
|
30
56
|
|
31
57
|
@set[:interpreter] = interp
|
32
58
|
end
|
33
59
|
|
60
|
+
# Get needed libraries.
|
61
|
+
# @return [Array<String>]
|
62
|
+
# @example
|
63
|
+
# patcher = PatchELF::Patcher.new('/bin/ls')
|
64
|
+
# patcher.needed
|
65
|
+
# #=> ["libselinux.so.1", "libc.so.6"]
|
66
|
+
def needed
|
67
|
+
@set[:needed] || needed_
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set needed libraries.
|
71
|
+
# @param [Array<String>] needs
|
72
|
+
# @macro note_apply
|
73
|
+
def needed=(needs)
|
74
|
+
@set[:needed] = needs
|
75
|
+
end
|
76
|
+
|
77
|
+
# Add the needed library.
|
78
|
+
# @param [String] need
|
79
|
+
# @return [void]
|
80
|
+
# @macro note_apply
|
81
|
+
def add_needed(need)
|
82
|
+
@set[:needed] ||= needed_
|
83
|
+
@set[:needed] << need
|
84
|
+
end
|
85
|
+
|
86
|
+
# Remove the needed library.
|
87
|
+
# @param [String] need
|
88
|
+
# @return [void]
|
89
|
+
# @macro note_apply
|
90
|
+
def remove_needed(need)
|
91
|
+
@set[:needed] ||= needed_
|
92
|
+
@set[:needed].delete(need)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Replace needed library +src+ with +tar+.
|
96
|
+
#
|
97
|
+
# @param [String] src
|
98
|
+
# Library to be replaced.
|
99
|
+
# @param [String] tar
|
100
|
+
# Library replace with.
|
101
|
+
# @return [void]
|
102
|
+
# @macro note_apply
|
103
|
+
def replace_needed(src, tar)
|
104
|
+
@set[:needed] ||= needed_
|
105
|
+
@set[:needed].map! { |v| v == src ? tar : v }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get the soname of a shared library.
|
109
|
+
# @return [String?] The name.
|
110
|
+
# @example
|
111
|
+
# patcher = PatchELF::Patcher.new('/bin/ls')
|
112
|
+
# patcher.soname
|
113
|
+
# # [WARN] Entry DT_SONAME not found, not a shared library?
|
114
|
+
# #=> nil
|
115
|
+
# @example
|
116
|
+
# PatchELF::Patcher.new('/lib/x86_64-linux-gnu/libc.so.6').soname
|
117
|
+
# #=> "libc.so.6"
|
118
|
+
def soname
|
119
|
+
@set[:soname] || soname_
|
120
|
+
end
|
121
|
+
|
34
122
|
# Set soname.
|
35
123
|
#
|
36
124
|
# If the input ELF is not a shared library with a soname,
|
@@ -38,115 +126,81 @@ module PatchELF
|
|
38
126
|
# @param [String] name
|
39
127
|
# @macro note_apply
|
40
128
|
def soname=(name)
|
41
|
-
return if
|
129
|
+
return if soname_.nil?
|
42
130
|
|
43
131
|
@set[:soname] = name
|
44
132
|
end
|
45
133
|
|
46
|
-
#
|
134
|
+
# Get runpath.
|
135
|
+
# @return [String?]
|
136
|
+
def runpath
|
137
|
+
@set[@rpath_sym] || runpath_(@rpath_sym)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Get rpath
|
141
|
+
# return [String?]
|
142
|
+
def rpath
|
143
|
+
@set[:rpath] || runpath_(:rpath)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Set rpath
|
47
147
|
#
|
48
|
-
#
|
49
|
-
#
|
148
|
+
# Modify / set DT_RPATH of the given ELF.
|
149
|
+
# similar to runpath= except DT_RPATH is modifed/created in DYNAMIC segment.
|
50
150
|
# @param [String] rpath
|
51
151
|
# @macro note_apply
|
52
152
|
def rpath=(rpath)
|
53
153
|
@set[:rpath] = rpath
|
54
154
|
end
|
55
155
|
|
156
|
+
# Set runpath.
|
157
|
+
#
|
158
|
+
# If DT_RUNPATH is not presented in the input ELF,
|
159
|
+
# a new DT_RUNPATH attribute will be inserted into the DYNAMIC segment.
|
160
|
+
# @param [String] runpath
|
161
|
+
# @macro note_apply
|
162
|
+
def runpath=(runpath)
|
163
|
+
@set[@rpath_sym] = runpath
|
164
|
+
end
|
165
|
+
|
166
|
+
# Set all operations related to DT_RUNPATH to use DT_RPATH.
|
167
|
+
# @return [self]
|
168
|
+
def use_rpath!
|
169
|
+
@rpath_sym = :rpath
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
56
173
|
# Save the patched ELF as +out_file+.
|
57
174
|
# @param [String?] out_file
|
58
175
|
# If +out_file+ is +nil+, the original input file will be modified.
|
59
176
|
# @return [void]
|
60
177
|
def save(out_file = nil)
|
61
|
-
# TODO: Test if we can save twice, and the output files are exactly same.
|
62
178
|
# If nothing is modified, return directly.
|
63
179
|
return if out_file.nil? && !dirty?
|
64
180
|
|
65
181
|
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
|
182
|
+
saver = PatchELF::Saver.new(@in_file, out_file, @set)
|
98
183
|
|
99
|
-
|
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)
|
184
|
+
saver.save!
|
107
185
|
end
|
108
186
|
|
109
|
-
|
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]
|
187
|
+
private
|
130
188
|
|
131
|
-
|
132
|
-
|
189
|
+
def log_or_raise(msg, exception = PatchELF::PatchError)
|
190
|
+
raise exception, msg if @on_error == :exception
|
133
191
|
|
134
|
-
|
192
|
+
PatchELF::Logger.warn(msg) if @on_error == :log
|
193
|
+
end
|
135
194
|
|
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
|
195
|
+
def interpreter_
|
142
196
|
segment = @elf.segment_by_type(:interp)
|
143
|
-
return
|
197
|
+
return log_or_raise 'No interpreter found.', PatchELF::MissingSegmentError if segment.nil?
|
144
198
|
|
145
199
|
segment.interp_name
|
146
200
|
end
|
147
201
|
|
148
202
|
# @return [Array<String>]
|
149
|
-
def
|
203
|
+
def needed_
|
150
204
|
segment = dynamic_or_log
|
151
205
|
return if segment.nil?
|
152
206
|
|
@@ -154,85 +208,36 @@ module PatchELF
|
|
154
208
|
end
|
155
209
|
|
156
210
|
# @return [String?]
|
157
|
-
def
|
158
|
-
|
159
|
-
tag_name_or_log(:rpath, 'Entry DT_RPATH not found.')
|
211
|
+
def runpath_(rpath_sym = :runpath)
|
212
|
+
tag_name_or_log(rpath_sym, "Entry DT_#{rpath_sym.to_s.upcase} not found.")
|
160
213
|
end
|
161
214
|
|
162
215
|
# @return [String?]
|
163
|
-
def
|
216
|
+
def soname_
|
164
217
|
tag_name_or_log(:soname, 'Entry DT_SONAME not found, not a shared library?')
|
165
218
|
end
|
166
219
|
|
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
220
|
# @return [Boolean]
|
203
221
|
def dirty?
|
204
222
|
@set.any?
|
205
223
|
end
|
206
224
|
|
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
225
|
def tag_name_or_log(type, log_msg)
|
216
226
|
segment = dynamic_or_log
|
217
227
|
return if segment.nil?
|
218
228
|
|
219
229
|
tag = segment.tag_by_type(type)
|
220
|
-
return PatchELF::
|
230
|
+
return log_or_raise log_msg, PatchELF::MissingTagError if tag.nil?
|
221
231
|
|
222
232
|
tag.name
|
223
233
|
end
|
224
234
|
|
225
235
|
def dynamic_or_log
|
226
236
|
@elf.segment_by_type(:dynamic).tap do |s|
|
227
|
-
|
237
|
+
if s.nil?
|
238
|
+
log_or_raise 'DYNAMIC segment not found, might be a statically-linked ELF?', PatchELF::MissingSegmentError
|
239
|
+
end
|
228
240
|
end
|
229
241
|
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
242
|
end
|
238
243
|
end
|
@@ -0,0 +1,285 @@
|
|
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
|
+
cur = strtab_string.size
|
193
|
+
@strtab_extend_requests.each do |str, block|
|
194
|
+
block.call(cur)
|
195
|
+
cur += str.size + 1
|
196
|
+
end
|
197
|
+
# Now patching strtab header
|
198
|
+
strtab.header.d_val = vaddr
|
199
|
+
# We also need to patch dynstr to let readelf have correct output.
|
200
|
+
if dynstr
|
201
|
+
dynstr.sh_size = new_str.size
|
202
|
+
dynstr.sh_offset = off
|
203
|
+
dynstr.sh_addr = vaddr
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [String] str
|
209
|
+
# @yieldparam [Integer] idx
|
210
|
+
# @yieldreturn [void]
|
211
|
+
def reg_str_table(str, &block)
|
212
|
+
idx = strtab_string.index(str + "\x00")
|
213
|
+
# Request string is already exist
|
214
|
+
return yield idx if idx
|
215
|
+
|
216
|
+
# Record the request
|
217
|
+
@strtab_extend_requests << [str, block]
|
218
|
+
end
|
219
|
+
|
220
|
+
def strtab_string
|
221
|
+
return @strtab_string if defined?(@strtab_string)
|
222
|
+
|
223
|
+
# TODO: handle no strtab exists..
|
224
|
+
offset = @elf.offset_from_vma(dynamic.tag_by_type(:strtab).value)
|
225
|
+
# This is a little tricky since no length information is stored in the tag.
|
226
|
+
# We first get the file offset of the string then 'guess' where the end is.
|
227
|
+
@elf.stream.pos = offset
|
228
|
+
@strtab_string = +''
|
229
|
+
loop do
|
230
|
+
c = @elf.stream.read(1)
|
231
|
+
break unless c =~ /\x00|[[:print:]]/
|
232
|
+
|
233
|
+
@strtab_string << c
|
234
|
+
end
|
235
|
+
@strtab_string
|
236
|
+
end
|
237
|
+
|
238
|
+
# This can only be used for patching interpreter's name
|
239
|
+
# or set strings in a malloc-ed area.
|
240
|
+
# i.e. NEVER intend to change the string defined in strtab
|
241
|
+
def inline_patch(off, str)
|
242
|
+
@inline_patch[off] = str
|
243
|
+
end
|
244
|
+
|
245
|
+
# Modify the out_file according to registered patches.
|
246
|
+
def patch_out(out_file)
|
247
|
+
File.open(out_file, 'r+') do |f|
|
248
|
+
if @mm.extended?
|
249
|
+
original_head = @mm.threshold
|
250
|
+
extra = {}
|
251
|
+
# Copy all data after the second load
|
252
|
+
@elf.stream.pos = original_head
|
253
|
+
extra[original_head + @mm.extend_size] = @elf.stream.read # read to end
|
254
|
+
# zero out the 'gap' we created
|
255
|
+
extra[original_head] = "\x00" * @mm.extend_size
|
256
|
+
extra.each do |pos, str|
|
257
|
+
f.pos = pos
|
258
|
+
f.write(str)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
@elf.patches.each do |pos, str|
|
262
|
+
f.pos = @mm.extended_offset(pos)
|
263
|
+
f.write(str)
|
264
|
+
end
|
265
|
+
|
266
|
+
@inline_patch.each do |pos, str|
|
267
|
+
f.pos = pos
|
268
|
+
f.write(str)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# @return [ELFTools::Sections::Section?]
|
274
|
+
def section_header(name)
|
275
|
+
sec = @elf.section_by_name(name)
|
276
|
+
return if sec.nil?
|
277
|
+
|
278
|
+
sec.header
|
279
|
+
end
|
280
|
+
|
281
|
+
def dynamic
|
282
|
+
@dynamic ||= @elf.segment_by_type(:dynamic)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|