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