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}/.freeze
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

Class Method Summary collapse

Class Method Details

.abspath(path) ⇒ String

Get absolute path from relative path. Support symlink.

Examples:

Helper.abspath('/lib/x86_64-linux-gnu/libc.so.6')
#=> '/lib/x86_64-linux-gnu/libc-2.23.so'

Parameters:

  • path (String)

    Relative path.

Returns:

  • (String)

    Absolute path, with symlink resolved.



47
48
49
# File 'lib/one_gadget/helper.rb', line 47

def abspath(path)
  Pathname.new(File.expand_path(path)).realpath.to_s
end

.arch_specific_objdump(arch) ⇒ String

Returns the binary name of objdump.

Parameters:

  • arch (Symbol)

Returns:

  • (String)


317
318
319
320
321
322
323
# 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

.architecture(file) ⇒ Symbol

Fetch the ELF architecture of file.

Examples:

Helper.architecture('/bin/cat')
#=> :amd64

Parameters:

  • file (String)

    The target ELF filename.

Returns:

  • (Symbol)

    Currently supports amd64, i386, arm, aarch64, and mips.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# 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

.build_id_of(path) ⇒ String

Get the Build ID of target ELF.

Examples:

Helper.build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so')
#=> '60131540dadc6796cab33388349e6e4e68692053'

Parameters:

  • path (String)

    Absolute file path.

Returns:

  • (String)

    Target build id.



88
89
90
# File 'lib/one_gadget/helper.rb', line 88

def build_id_of(path)
  File.open(path) { |f| ELFTools::ELFFile.new(f).build_id }
end

.color_enabled?Boolean

Is colorize output enabled?

Returns:

  • (Boolean)

    True or false.



101
102
103
104
105
106
# 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

.color_off!void

This method returns an undefined value.

Disable colorize.



94
95
96
# File 'lib/one_gadget/helper.rb', line 94

def color_off!
  @disable_color = true
end

.colored_hex(val) ⇒ String

Returns the hexified and colorized integer.

Parameters:

  • val (Integer)

Returns:

  • (String)


133
134
135
# File 'lib/one_gadget/helper.rb', line 133

def colored_hex(val)
  colorize(hex(val), sev: :integer)
end

.colorize(str, sev: :normal_s) ⇒ String

Wrap string with color codes for pretty inspect.

Parameters:

  • str (String)

    Contents to colorize.

  • sev (Symbol) (defaults to: :normal_s)

    Specify which kind of color to use, valid symbols are defined in COLOR_CODE.

Returns:

  • (String)

    String wrapped with color codes.



122
123
124
125
126
127
128
# 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

.comments_of_file(file) ⇒ Array<String>

Fetch lines start with ‘#’.

Parameters:

  • file (String)

    Filename.

Returns:

  • (Array<String>)

    Lines of comments.



37
38
39
# 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_build(file) ⇒ Tempfile

Download the latest version of file in lib/one_gadget/builds/ from remote repo.

Parameters:

  • file (String)

    The filename desired.

Returns:

  • (Tempfile)

    The temp file be created.



156
157
158
159
160
# 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(arch) ⇒ String?

Find objdump that supports architecture arch.

Examples:

Helper.find_objdump(:amd64)
#=> '/usr/bin/objdump'
Helper.find_objdump(:aarch64)
#=> '/usr/bin/aarch64-linux-gnu-objdump'

Parameters:

  • arch (String)

Returns:

  • (String?)


278
279
280
281
282
283
# 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

.function_offsets(file, functions) ⇒ Hash{String => Integer}

Returns a dictionary that maps functions to their offsets.

Parameters:

  • file (String)
  • functions (Array<String>)

Returns:

  • (Hash{String => Integer})


338
339
340
341
342
343
344
345
346
347
348
349
# 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

.got_functions(file) ⇒ Array<String>

Returns the names of functions from the file’s global offset table.

Parameters:

  • file (String)

Returns:

  • (Array<String>)


328
329
330
331
332
# 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

.hex(val, psign: false) ⇒ String

Present number in hex format.

Examples:

Helper.hex(32) #=> '0x20'
Helper.hex(32, psign: true) #=> '+0x20'
Helper.hex(-40) #=> '-0x28'
Helper.hex(0) #=> '0x0'
Helper.hex(0, psign: true) #=> '+0x0'

Parameters:

  • val (Integer)

    The number.

  • psign (Boolean) (defaults to: false)

    If needs to show the plus sign when val >= 0.

Returns:

  • (String)

    String in hex format.



228
229
230
231
232
# 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

.integer?(str) ⇒ Boolean

Checks if a string can be converted into an integer.

Examples:

Helper.integer? '1234'
#=> true
Helper.integer? '0x1234'
#=> true
Helper.integer? '0xheapoverflow'
#=> false

Parameters:

  • str (String)

    String to be checked.

Returns:

  • (Boolean)

    If str can be converted into an integer.



246
247
248
249
250
# File 'lib/one_gadget/helper.rb', line 246

def integer?(str)
  true if Integer(str)
rescue ArgumentError, TypeError
  false
end

.latest_tagString

Fetch the latest release version’s tag name.

Returns:

  • (String)

    The tag name, in form vX.X.X.



139
140
141
142
# 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

.objdump_arch(arch) ⇒ String

Converts to the architecture name shown in objdump’s --help command.

Examples:

Helper.objdump_arch(:i386)
#=> 'i386'
Helper.objdump_arch(:amd64)
#=> 'i386:x86-64'

Parameters:

  • arch (Symbol)

Returns:

  • (String)


307
308
309
310
311
312
# 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

.objdump_arch_supported?(bin, arch) ⇒ Boolean

Checks if the given objdump supports certain architecture.

Examples:

Helper.objdump_arch_supported?('/usr/bin/objdump', :i386)
#=> true

Parameters:

  • bin (String)
  • arch (Symbol)

Returns:

  • (Boolean)


292
293
294
295
296
297
# 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

.remote_buildsArray<String>

Get the latest builds list from repo.

Returns:

  • (Array<String>)

    List of build ids.



164
165
166
# File 'lib/one_gadget/helper.rb', line 164

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.

Parameters:

  • filename (String)

Returns:

  • (String)

    The url.



147
148
149
150
# 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

.url_request(url) ⇒ String

Get request.

Parameters:

  • url (String)

    The url.

Returns:

  • (String)

    The request response body. If the response is 302 Found, returns the location in header.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# 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

.valid_elf_file?(path) ⇒ Boolean

Checks if the file of given path is a valid ELF file.

Examples:

Helper.valid_elf_file?('/etc/passwd')
#=> false
Helper.valid_elf_file?('/lib64/ld-linux-x86-64.so.2')
#=> true

Parameters:

  • path (String)

    Path to target file.

Returns:

  • (Boolean)

    If the file is an ELF or not.



60
61
62
63
64
65
66
67
# 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

.verify_build_id!(build_id) ⇒ void

This method returns an undefined value.

Checks if build_id is a valid SHA1 hex format.

Parameters:

  • build_id (String)

    BuildID.

Raises:



26
27
28
29
30
# 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

.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.

Parameters:

  • path (String)

    Path to target file.

Raises:



76
77
78
79
80
# 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

.which(cmd) ⇒ String?

Cross-platform way of finding an executable in $PATH.

Examples:

Helper.which('ruby')
#=> "/usr/bin/ruby"

Parameters:

  • cmd (String)

Returns:

  • (String?)


259
260
261
262
263
264
265
266
267
268
# 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