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 =
Fetch the ELF architecture of
file. 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 }.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
One of
:amd64,:i386,:armor:aarch64,:unknownfor a valid ELF this tool cannot search, or:invalidwhenfiledoes not exist or is not a valid ELF. -
.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.
-
.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.
329 330 331 332 333 334 335 336 |
# File 'lib/one_gadget/helper.rb', line 329 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' }[arch] end |
.architecture(file) ⇒ Symbol
Returns One of :amd64, :i386, :arm or :aarch64, :unknown for a valid ELF
this tool cannot search, or :invalid when file does not exist or is
not a valid ELF.
213 214 215 216 217 218 219 220 221 222 |
# File 'lib/one_gadget/helper.rb', line 213 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).
288 289 290 291 292 293 |
# File 'lib/one_gadget/helper.rb', line 288 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.
352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/one_gadget/helper.rb', line 352 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.
341 342 343 344 345 |
# File 'lib/one_gadget/helper.rb', line 341 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.
237 238 239 240 241 |
# File 'lib/one_gadget/helper.rb', line 237 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.
255 256 257 |
# File 'lib/one_gadget/helper.rb', line 255 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.
317 318 319 320 321 322 |
# File 'lib/one_gadget/helper.rb', line 317 def objdump_arch(arch) case arch when :amd64 then 'i386:x86-64' else arch.to_s end end |
.objdump_arch_supported?(bin, arch) ⇒ Boolean
Checks if the given objdump supports certain architecture.
302 303 304 305 306 307 |
# File 'lib/one_gadget/helper.rb', line 302 def objdump_arch_supported?(bin, arch) return false if bin.nil? arch = objdump_arch(arch) `#{::Shellwords.join([bin, '--help'])}`.lines.any? { |c| c.split.include?(arch) } 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.
266 267 268 269 270 271 272 273 274 275 |
# File 'lib/one_gadget/helper.rb', line 266 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 |