module OneGadget::Helper
Define some helpful methods here.
Constants
- BUILD_ID_FORMAT
Format of build-id, 40 hex numbers.
- COLOR_CODE
Color codes for pretty print
Public Instance Methods
Get absolute path from relative path. Support symlink. @param [String] path Relative path. @return [String] Absolute path, with symlink resolved. @example
Helper.abspath('/lib/x86_64-linux-gnu/libc.so.6') #=> '/lib/x86_64-linux-gnu/libc-2.23.so'
# File lib/one_gadget/helper.rb, line 47 def abspath(path) Pathname.new(File.expand_path(path)).realpath.to_s end
Returns the binary name of objdump. @param [Symbol] arch @return [String]
# File lib/one_gadget/helper.rb, line 317 def arch_specific_objdump(arch) { aarch64: 'aarch64-linux-gnu-objdump', amd64: 'x86_64-linux-gnu-objdump', i386: 'i686-linux-gnu-objdump' }[arch] end
Fetch the ELF architecture of file
. @param [String] file The target ELF filename. @return [Symbol]
Currently supports amd64, i386, arm, aarch64, and mips.
@example
Helper.architecture('/bin/cat') #=> :amd64
# File lib/one_gadget/helper.rb, line 197 def architecture(file) return :invalid unless File.exist?(file) f = File.open(file) str = ELFTools::ELFFile.new(f).machine { 'Advanced Micro Devices X86-64' => :amd64, 'Intel 80386' => :i386, 'ARM' => :arm, 'AArch64' => :aarch64, 'MIPS R3000' => :mips }[str] || :unknown rescue ELFTools::ELFError # not a valid ELF :invalid ensure f&.close end
Get the Build ID of target ELF. @param [String] path Absolute file path. @return [String] Target build id. @example
Helper.build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so') #=> '60131540dadc6796cab33388349e6e4e68692053'
# File lib/one_gadget/helper.rb, line 88 def build_id_of(path) File.open(path) { |f| ELFTools::ELFFile.new(f).build_id } end
Is colorize output enabled? @return [Boolean]
True or false.
# File lib/one_gadget/helper.rb, line 101 def color_enabled? # if not set, use tty to check return $stdout.tty? unless instance_variable_defined?(:@disable_color) !@disable_color end
Disable colorize. @return [void]
# File lib/one_gadget/helper.rb, line 94 def color_off! @disable_color = true end
Returns the hexified and colorized integer. @param [Integer] val @return [String]
# File lib/one_gadget/helper.rb, line 133 def colored_hex(val) colorize(hex(val), sev: :integer) end
Wrap string with color codes for pretty inspect. @param [String] str Contents to colorize. @param [Symbol] sev Specify which kind of color to use, valid symbols are defined in {.COLOR_CODE}. @return [String] String wrapped with color codes.
# File lib/one_gadget/helper.rb, line 122 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
Fetch lines start with ‘#’. @param [String] file
Filename.
@return [Array<String>]
Lines of comments.
# File lib/one_gadget/helper.rb, line 37 def comments_of_file(file) File.readlines(file).map { |s| s[2..-1].rstrip if s.start_with?('# ') }.compact end
Download the latest version of file
in lib/one_gadget/builds/
from remote repo.
@param [String] file The filename desired. @return [Tempfile] The temp file be created.
# File lib/one_gadget/helper.rb, line 156 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 that supports architecture arch
. @param [String] arch @return [String?] @example
Helper.find_objdump(:amd64) #=> '/usr/bin/objdump' Helper.find_objdump(:aarch64) #=> '/usr/bin/aarch64-linux-gnu-objdump'
# File lib/one_gadget/helper.rb, line 278 def find_objdump(arch) [ which('objdump'), which(arch_specific_objdump(arch)) ].find { |bin| objdump_arch_supported?(bin, arch) } end
Returns a dictionary that maps functions to their offsets. @param [String] file @param [Array<String>] functions @return [Hash{String => Integer}]
# File lib/one_gadget/helper.rb, line 338 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
Returns the names of functions from the file’s global offset table. @param [String] file @return [Array<String>]
# File lib/one_gadget/helper.rb, line 328 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
Present number in hex format. @param [Integer] val
The number.
@param [Boolean] psign
If needs to show the plus sign when +val >= 0+.
@return [String]
String in hex format.
@example
Helper.hex(32) #=> '0x20' Helper.hex(32, psign: true) #=> '+0x20' Helper.hex(-40) #=> '-0x28' Helper.hex(0) #=> '0x0' Helper.hex(0, psign: true) #=> '+0x0'
# File lib/one_gadget/helper.rb, line 228 def hex(val, psign: false) return format("#{psign ? '+' : ''}0x%x", val) if val >= 0 format('-0x%x', -val) end
Checks if a string can be converted into an integer. @param [String] str
String to be checked.
@return [Boolean]
If +str+ can be converted into an integer.
@example
Helper.integer? '1234' #=> true Helper.integer? '0x1234' #=> true Helper.integer? '0xheapoverflow' #=> false
# File lib/one_gadget/helper.rb, line 246 def integer?(str) true if Integer(str) rescue ArgumentError, TypeError false end
Fetch the latest release version’s tag name. @return [String] The tag name, in form vX.X.X
.
# File lib/one_gadget/helper.rb, line 139 def latest_tag releases_url = 'https://github.com/david942j/one_gadget/releases/latest' @latest_tag ||= url_request(releases_url).split('/').last end
Converts to the architecture name shown in objdump’s --help
command. @param [Symbol] arch @return [String] @example
Helper.objdump_arch(:i386) #=> 'i386' Helper.objdump_arch(:amd64) #=> 'i386:x86-64'
# File lib/one_gadget/helper.rb, line 307 def objdump_arch(arch) case arch when :amd64 then 'i386:x86-64' else arch.to_s end end
Checks if the given objdump supports certain architecture. @param [String] bin @param [Symbol] arch @return [Boolean] @example
Helper.objdump_arch_supported?('/usr/bin/objdump', :i386) #=> true
# File lib/one_gadget/helper.rb, line 292 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
Get the latest builds list from repo. @return [Array<String>] List of build ids.
# File lib/one_gadget/helper.rb, line 164 def remote_builds @remote_builds ||= url_request(url_of_file('builds_list')).lines.map(&:strip) end
Get the url which can fetch filename
from remote repo. @param [String] filename @return [String] The url.
# File lib/one_gadget/helper.rb, line 147 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
Get request. @param [String] url The url. @return [String]
The request response body. If the response is +302 Found+, returns the location in header.
# File lib/one_gadget/helper.rb, line 173 def url_request(url) uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE 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.message) nil end
Checks if the file of given path is a valid ELF file.
@param [String] path Path to target file. @return [Boolean] If the file is an ELF or not. @example
Helper.valid_elf_file?('/etc/passwd') #=> false Helper.valid_elf_file?('/lib64/ld-linux-x86-64.so.2') #=> true
# File lib/one_gadget/helper.rb, line 60 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
Checks if build_id
is a valid SHA1 hex format. @param [String] build_id
BuildID.
@raise [Error::ArgumentError]
Raises error if invalid.
@return [void]
# File lib/one_gadget/helper.rb, line 26 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
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.
@param [String] path Path to target file. @return [void] @raise [Error::ArgumentError] Raise exception if not a valid ELF.
# File lib/one_gadget/helper.rb, line 76 def verify_elf_file!(path) return if valid_elf_file?(path) raise Error::ArgumentError, 'Not an ELF file, expected glibc as input' end
Cross-platform way of finding an executable in +$PATH+.
@param [String] cmd @return [String?] @example
Helper.which('ruby') #=> "/usr/bin/ruby"
# File lib/one_gadget/helper.rb, line 259 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