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.


45
46
47
# File 'lib/one_gadget/helper.rb', line 45

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)

308
309
310
311
312
313
314
# File 'lib/one_gadget/helper.rb', line 308

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


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/one_gadget/helper.rb', line 188

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


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?

Returns:

  • (Boolean)

    True or false.


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

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


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

Parameters:

  • file (String)

    Filename.

Returns:

  • (Array<String>)

    Lines of comments.


35
36
37
# File 'lib/one_gadget/helper.rb', line 35

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.


147
148
149
150
151
# File 'lib/one_gadget/helper.rb', line 147

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?)

269
270
271
272
273
274
# File 'lib/one_gadget/helper.rb', line 269

def find_objdump(arch)
  [
    which('objdump'),
    which(arch_specific_objdump(arch))
  ].find { |bin| objdump_arch_supported?(bin, arch) }
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.


219
220
221
222
223
# File 'lib/one_gadget/helper.rb', line 219

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.


237
238
239
240
241
# File 'lib/one_gadget/helper.rb', line 237

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.


130
131
132
133
# File 'lib/one_gadget/helper.rb', line 130

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)

298
299
300
301
302
303
# File 'lib/one_gadget/helper.rb', line 298

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)

283
284
285
286
287
288
# File 'lib/one_gadget/helper.rb', line 283

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.


155
156
157
# File 'lib/one_gadget/helper.rb', line 155

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.


138
139
140
141
# File 'lib/one_gadget/helper.rb', line 138

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.


164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/one_gadget/helper.rb', line 164

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.


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.

Parameters:

  • build_id (String)

    BuildID.

Raises:


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.

Parameters:

  • path (String)

    Path to target file.

Raises:


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.

Examples:

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

Parameters:

  • cmd (String)

Returns:

  • (String?)

250
251
252
253
254
255
256
257
258
259
# File 'lib/one_gadget/helper.rb', line 250

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