Module: OneGadget::Helper
- Defined in:
- lib/one_gadget/helper.rb
Overview
Define some helpful methods here.
Constant Summary collapse
- BUILD_ID_FORMAT =
Format of build-id, 40 hex numbers.
/[0-9a-f]{40}/- COLOR_CODE =
Color codes for pretty print
{ esc_m: "\e[0m", normal_s: "\e[38;5;203m", # red integer: "\e[38;5;189m", # light purple reg: "\e[38;5;82m", # light green warn: "\e[38;5;230m", # light yellow error: "\e[38;5;196m" # heavy red }.freeze
- MACHINES =
What this tool calls each architecture it can search. Keyed by the header's own value, which is fixed by the ELF ABI, rather than by the name a reader library prints for it -- that is the library's wording and does change, as elftools 2.0.0 renamed "AArch64" to "ARM 64-bit architecture".
{ ELFTools::Constants::EM::EM_386 => :i386, ELFTools::Constants::EM::EM_ARM => :arm, ELFTools::Constants::EM::EM_X86_64 => :amd64, ELFTools::Constants::EM::EM_AARCH64 => :aarch64, ELFTools::Constants::EM::EM_RISCV => :riscv64, ELFTools::Constants::EM::EM_MIPS => :mips }.freeze
Class Method Summary collapse
-
.abspath(path) ⇒ String
Get absolute path from relative path.
-
.arch_specific_objdump(arch) ⇒ String?
Returns the architecture-specific objdump binary name for
arch. -
.architecture(file) ⇒ Symbol
Fetch the ELF architecture of
file. -
.build_id_of(path) ⇒ String
Get the Build ID of target ELF.
-
.color_enabled? ⇒ Boolean
Is colorize output enabled?.
-
.color_off! ⇒ void
Disable colorize.
-
.colored_hex(val) ⇒ String
Returns the hexified and colorized integer.
-
.colorize(str, sev: :normal_s) ⇒ String
Wrap string with color codes for pretty inspect.
-
.comments_of_file(file) ⇒ Array<String>
Fetch lines start with '#'.
-
.download_build(file) ⇒ Tempfile
Download the latest version of
fileinlib/one_gadget/builds/from remote repo. -
.find_objdump(arch) ⇒ String?
Find objdump that supports architecture
arch. -
.function_offsets(file, functions) ⇒ Hash{String => Integer}
Returns a dictionary that maps functions to their offsets.
-
.got_functions(file) ⇒ Array<String>
Returns the names of functions from the file's global offset table.
-
.hex(val, psign: false) ⇒ String
Present number in hex format.
-
.integer?(str) ⇒ Boolean
Checks if a string can be converted into an integer.
-
.latest_tag ⇒ String
Fetch the latest release version's tag name.
-
.objdump_arch(arch) ⇒ String
Converts to the architecture name shown in objdump's
--helpcommand. -
.objdump_arch_supported?(bin, arch) ⇒ Boolean
Checks if the given objdump supports certain architecture.
-
.objdump_targets(bin) ⇒ Array<String>
The target names an objdump binary lists as supported.
-
.remote_builds ⇒ Array<String>
Get the latest builds list from repo.
-
.url_of_file(filename) ⇒ String
Get the url which can fetch
filenamefrom remote repo. -
.url_request(url) ⇒ String
Get request.
-
.valid_elf_file?(path) ⇒ Boolean
Checks if the file of given path is a valid ELF file.
-
.verify_build_id!(build_id) ⇒ void
Checks if
build_idis a valid SHA1 hex format. -
.verify_elf_file!(path) ⇒ void
Checks if the file of given path is a valid ELF file.
-
.which(cmd) ⇒ String?
Cross-platform way of finding an executable in
$PATH.
Class Method Details
.abspath(path) ⇒ String
Get absolute path from relative path. Support symlink.
45 46 47 |
# File 'lib/one_gadget/helper.rb', line 45 def abspath(path) Pathname.new(File.(path)).realpath.to_s end |
.arch_specific_objdump(arch) ⇒ String?
Returns the architecture-specific objdump binary name for arch.
342 343 344 345 346 347 348 349 350 |
# File 'lib/one_gadget/helper.rb', line 342 def arch_specific_objdump(arch) { aarch64: 'aarch64-linux-gnu-objdump', amd64: 'x86_64-linux-gnu-objdump', arm: 'arm-linux-gnueabihf-objdump', i386: 'i686-linux-gnu-objdump', riscv64: 'riscv64-linux-gnu-objdump' }[arch] end |
.architecture(file) ⇒ Symbol
Fetch the ELF architecture of file.
215 216 217 218 219 220 221 222 223 224 |
# File 'lib/one_gadget/helper.rb', line 215 def architecture(file) return :invalid unless File.exist?(file) f = File.open(file) # rubocop:disable Style/FileOpen MACHINES[ELFTools::ELFFile.new(f).header.e_machine.to_i] || :unknown rescue ELFTools::ELFError # not a valid ELF :invalid ensure f&.close end |
.build_id_of(path) ⇒ String
Get the Build ID of target ELF.
86 87 88 |
# File 'lib/one_gadget/helper.rb', line 86 def build_id_of(path) File.open(path) { |f| ELFTools::ELFFile.new(f).build_id } end |
.color_enabled? ⇒ Boolean
Is colorize output enabled?
99 100 101 102 103 104 |
# File 'lib/one_gadget/helper.rb', line 99 def color_enabled? # if not set, use tty to check return $stdout.tty? unless instance_variable_defined?(:@disable_color) !@disable_color end |
.color_off! ⇒ void
This method returns an undefined value.
Disable colorize.
92 93 94 |
# File 'lib/one_gadget/helper.rb', line 92 def color_off! @disable_color = true end |
.colored_hex(val) ⇒ String
Returns the hexified and colorized integer.
131 132 133 |
# File 'lib/one_gadget/helper.rb', line 131 def colored_hex(val) colorize(hex(val), sev: :integer) end |
.colorize(str, sev: :normal_s) ⇒ String
Wrap string with color codes for pretty inspect.
120 121 122 123 124 125 126 |
# File 'lib/one_gadget/helper.rb', line 120 def colorize(str, sev: :normal_s) return str unless color_enabled? cc = COLOR_CODE color = cc.key?(sev) ? cc[sev] : '' "#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}" end |
.comments_of_file(file) ⇒ Array<String>
Fetch lines start with '#'.
35 36 37 |
# File 'lib/one_gadget/helper.rb', line 35 def comments_of_file(file) File.readlines(file).map { |s| s[2..].rstrip if s.start_with?('# ') }.compact end |
.download_build(file) ⇒ Tempfile
Download the latest version of file in lib/one_gadget/builds/ from remote repo.
154 155 156 157 158 |
# File 'lib/one_gadget/helper.rb', line 154 def download_build(file) temp = Tempfile.new(['gadgets', "#{file}.rb"]) temp.write(url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', "#{file}.rb")))) temp.tap(&:close) end |
.find_objdump(arch) ⇒ String?
Find objdump that supports architecture arch.
Prefers the generic objdump if it supports arch, otherwise falls back to
the architecture-specific binary (see arch_specific_objdump).
290 291 292 293 294 295 |
# File 'lib/one_gadget/helper.rb', line 290 def find_objdump(arch) [ which('objdump'), which(arch_specific_objdump(arch)) ].find { |bin| objdump_arch_supported?(bin, arch) } end |
.function_offsets(file, functions) ⇒ Hash{String => Integer}
Returns a dictionary that maps functions to their offsets.
366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/one_gadget/helper.rb', line 366 def function_offsets(file, functions) arch = architecture(file) objdump_bin = find_objdump(arch) objdump_cmd = ::Shellwords.join([objdump_bin, '-T', file]) functions.map! { |f| "\\b#{f}\\b" } ret = {} `#{objdump_cmd} | grep -iP '(#{functions.join('|')})'`.lines.map(&:chomp).each do |line| tokens = line.split ret[tokens.last] = tokens.first.to_i(16) end ret end |
.got_functions(file) ⇒ Array<String>
Returns the names of functions from the file's global offset table.
355 356 357 358 359 |
# File 'lib/one_gadget/helper.rb', line 355 def got_functions(file) arch = architecture(file) objdump_bin = find_objdump(arch) `#{::Shellwords.join([objdump_bin, '-T', file])} | grep -iPo 'GLIBC_.+?\\s+\\K.*'`.split end |
.hex(val, psign: false) ⇒ String
Present number in hex format.
239 240 241 242 243 |
# File 'lib/one_gadget/helper.rb', line 239 def hex(val, psign: false) return format("#{'+' if psign}0x%x", val) if val >= 0 format('-0x%x', -val) end |
.integer?(str) ⇒ Boolean
Checks if a string can be converted into an integer.
257 258 259 |
# File 'lib/one_gadget/helper.rb', line 257 def integer?(str) !Integer(str, exception: false).nil? end |
.latest_tag ⇒ String
Fetch the latest release version's tag name.
137 138 139 140 |
# File 'lib/one_gadget/helper.rb', line 137 def latest_tag releases_url = 'https://github.com/david942j/one_gadget/releases/latest' @latest_tag ||= url_request(releases_url).split('/').last end |
.objdump_arch(arch) ⇒ String
Converts to the architecture name shown in objdump's --help command.
329 330 331 332 333 334 335 |
# File 'lib/one_gadget/helper.rb', line 329 def objdump_arch(arch) case arch when :amd64 then 'i386:x86-64' when :riscv64 then 'riscv:rv64' else arch.to_s end end |
.objdump_arch_supported?(bin, arch) ⇒ Boolean
Checks if the given objdump supports certain architecture.
304 305 306 307 308 |
# File 'lib/one_gadget/helper.rb', line 304 def objdump_arch_supported?(bin, arch) return false if bin.nil? objdump_targets(bin).include?(objdump_arch(arch)) end |
.objdump_targets(bin) ⇒ Array<String>
The target names an objdump binary lists as supported. Asked of the binary once: the answer is a property of that build, while the question is asked again for every command a search assembles.
317 318 319 |
# File 'lib/one_gadget/helper.rb', line 317 def objdump_targets(bin) (@objdump_targets ||= {})[bin] ||= `#{::Shellwords.join([bin, '--help'])}`.split end |
.remote_builds ⇒ Array<String>
Get the latest builds list from repo.
162 163 164 |
# File 'lib/one_gadget/helper.rb', line 162 def remote_builds @remote_builds ||= url_request(url_of_file('builds_list')).lines.map(&:strip) end |
.url_of_file(filename) ⇒ String
Get the url which can fetch filename from remote repo.
145 146 147 148 |
# File 'lib/one_gadget/helper.rb', line 145 def url_of_file(filename) raw_file_url = 'https://raw.githubusercontent.com/david942j/one_gadget/@tag/@file' raw_file_url.sub('@tag', latest_tag).sub('@file', filename) end |
.url_request(url) ⇒ String
Get request.
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/one_gadget/helper.rb', line 171 def url_request(url) # Asked for here rather than with the file: only reaching the build database # over the network needs them, and together they are a quarter of what it # takes to load one_gadget at all. require 'net/http' require 'openssl' uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri.request_uri) response = http.request(request) raise ArgumentError, "Fail to get response of #{url}" unless %w[200 302].include?(response.code) response.code == '302' ? response['location'] : response.body rescue NoMethodError, SocketError, ArgumentError => e OneGadget::Logger.error(e.) nil end |
.valid_elf_file?(path) ⇒ Boolean
Checks if the file of given path is a valid ELF file.
58 59 60 61 62 63 64 65 |
# File 'lib/one_gadget/helper.rb', line 58 def valid_elf_file?(path) # A light-weight way to check if is a valid ELF file # Checks at least one phdr should present. File.open(path) { |f| ELFTools::ELFFile.new(f).each_segments.first } true rescue ELFTools::ELFError false end |
.verify_build_id!(build_id) ⇒ void
This method returns an undefined value.
Checks if build_id is a valid SHA1 hex format.
24 25 26 27 28 |
# File 'lib/one_gadget/helper.rb', line 24 def verify_build_id!(build_id) return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/ raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id) end |
.verify_elf_file!(path) ⇒ void
This method returns an undefined value.
Checks if the file of given path is a valid ELF file.
An error message will be shown if given path is not a valid ELF.
74 75 76 77 78 |
# File 'lib/one_gadget/helper.rb', line 74 def verify_elf_file!(path) return if valid_elf_file?(path) raise Error::ArgumentError, 'Not an ELF file, expected glibc as input' end |
.which(cmd) ⇒ String?
Cross-platform way of finding an executable in $PATH.
268 269 270 271 272 273 274 275 276 277 |
# File 'lib/one_gadget/helper.rb', line 268 def which(cmd) exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| exts.each do |ext| exe = File.join(path, "#{cmd}#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) end end nil end |