one_gadget 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d381a734bccb70a6ffaac1b6e2ff82518adf2719
4
- data.tar.gz: 406db08cea4fc116b4d81f61d585b7a25ec9ecd4
2
+ SHA256:
3
+ metadata.gz: ff8578d867b076e1c59b2b271879d5448fc2c985c94b67a5e3fc8febcabb01ef
4
+ data.tar.gz: 39c681c497cfb5bce05eb993fcb47aa0b6e0ef77aba6af2507d4184ccb23ef20
5
5
  SHA512:
6
- metadata.gz: 327cb7edce0f03ac5d43807d91c9850f63869e9041d59ed236b467fd6ca97aa4aaaa68361525daa1273259b7df96a00e69193ef1ddd3b4856126a61afb3148ad
7
- data.tar.gz: 59b561675611597e011b5012574986138e041421eaecd8819ca40cdecc8f68056a2f42077eb7ad2f25f71ce2fd884b53aba9ea823791d250e18703d8e7228a3b
6
+ metadata.gz: 6aa7f7a80a3d0a096a02d40802df7d8014896ef15cba222610a334d80bab9d0fddf5db95fd2f1de47b86327d160b38eee65048f7300d40f6c6d69f0e4021aca7
7
+ data.tar.gz: adf5eaf2d3a70a3c17d5daddc0aa6f773911aea65eb82b633b73ef060d35073944d92a0feb440573f68c5badf522aba8b86b67fa51e89afd56a3c86e63980c5d
data/lib/one_gadget.rb CHANGED
@@ -30,20 +30,29 @@ module OneGadget
30
30
  ret = if build_id
31
31
  OneGadget::Fetcher.from_build_id(build_id) || OneGadget::Logger.not_found(build_id)
32
32
  else
33
- file = OneGadget::Helper.abspath(file)
34
- gadgets = try_from_build(file) unless force_file
35
- gadgets || OneGadget::Fetcher.from_file(file)
33
+ from_file(OneGadget::Helper.abspath(file), force: force_file)
36
34
  end
37
35
  ret = refine_gadgets(ret, level)
38
36
  ret.map!(&:offset) unless details
39
37
  ret
38
+ rescue OneGadget::Error::Error => e
39
+ OneGadget::Logger.error("#{e.class.name.split('::').last}: #{e.message}")
40
+ []
40
41
  end
41
42
 
42
43
  private
43
44
 
45
+ # Try from build id first, then file
46
+ def from_file(path, force: false)
47
+ OneGadget::Helper.verify_elf_file!(path)
48
+ gadgets = try_from_build(path) unless force
49
+ gadgets || OneGadget::Fetcher.from_file(path)
50
+ end
51
+
44
52
  def try_from_build(file)
45
53
  build_id = OneGadget::Helper.build_id_of(file)
46
54
  return unless build_id
55
+
47
56
  OneGadget::Fetcher.from_build_id(build_id, remote: false)
48
57
  end
49
58
 
@@ -51,6 +60,7 @@ module OneGadget
51
60
  def refine_gadgets(gadgets, level)
52
61
  return [] if gadgets.empty?
53
62
  return gadgets if level > 0 # currently only supports level > 0 or not
63
+
54
64
  # remain gadgets with the fewest constraints
55
65
  best = gadgets.map { |g| g.constraints.size }.min
56
66
  gadgets.select { |g| g.constraints.size == best }
@@ -83,6 +93,7 @@ end
83
93
  require 'one_gadget/update'
84
94
  OneGadget::Update.check!
85
95
 
96
+ require 'one_gadget/error'
86
97
  require 'one_gadget/fetcher'
87
98
  require 'one_gadget/helper'
88
99
  require 'one_gadget/logger'
@@ -1,3 +1,5 @@
1
+ require 'one_gadget/error'
2
+
1
3
  module OneGadget
2
4
  module Emulators
3
5
  # Define instruction name and it's argument count.
@@ -21,7 +23,10 @@ module OneGadget
21
23
  idx = cmd.index(inst)
22
24
  cmd = cmd[0...cmd.rindex('#')] if cmd.rindex('#')
23
25
  args = cmd[idx + inst.size..-1].split(',')
24
- raise ArgumentError, "Incorrect argument number in #{cmd}, expect: #{argc}" if argc >= 0 && args.size != argc
26
+ if argc >= 0 && args.size != argc
27
+ raise Error::ArgumentError, "Incorrect argument number in #{cmd}, expect: #{argc}"
28
+ end
29
+
25
30
  args.map do |arg|
26
31
  arg.gsub(/XMMWORD|QWORD|DWORD|WORD|BYTE|PTR/, '').strip
27
32
  end
@@ -1,3 +1,4 @@
1
+ require 'one_gadget/error'
1
2
  require 'one_gadget/helper'
2
3
 
3
4
  module OneGadget
@@ -23,7 +24,8 @@ module OneGadget
23
24
  # @param [Numeric] other Value to add.
24
25
  # @return [Lambda] The result.
25
26
  def +(other)
26
- raise ArgumentError, 'Expect other to be Numeric.' unless other.is_a?(Numeric)
27
+ raise Error::ArgumentError, 'Expect other to be Numeric.' unless other.is_a?(Numeric)
28
+
27
29
  if deref_count > 0
28
30
  ret = Lambda.new(self)
29
31
  else
@@ -49,9 +51,10 @@ module OneGadget
49
51
 
50
52
  # Decrease dreference count with 1.
51
53
  # @return [void]
52
- # @raise [ArgumentError] When this object cannot be referenced anymore.
54
+ # @raise [Error::ArgumentError] When this object cannot be referenced anymore.
53
55
  def ref!
54
- raise ArgumentError, 'Cannot reference anymore!' if @deref_count <= 0
56
+ raise Error::ArgumentError, 'Cannot reference anymore!' if @deref_count <= 0
57
+
55
58
  @deref_count -= 1
56
59
  end
57
60
 
@@ -81,8 +84,9 @@ module OneGadget
81
84
  # The context.
82
85
  # @return [Integer] Result of evaluation.
83
86
  def evaluate(context)
84
- raise ArgumentError, "Can't eval #{self}" if deref_count > 0
85
- raise ArgumentError, "Can't eval #{self}" if obj && !context.key?(obj)
87
+ raise Error::ArgumentError, "Can't eval #{self}" if deref_count > 0
88
+ raise Error::ArgumentError, "Can't eval #{self}" if obj && !context.key?(obj)
89
+
86
90
  context[obj] + immi
87
91
  end
88
92
 
@@ -106,10 +110,12 @@ module OneGadget
106
110
  deref_count = 1
107
111
  end
108
112
  return Integer(arg) if OneGadget::Helper.integer?(arg)
113
+
109
114
  sign = arg =~ /[+-]/
110
115
  val = 0
111
116
  if sign
112
- raise ArgumentError, "Not support #{arg}" unless OneGadget::Helper.integer?(arg[sign..-1])
117
+ raise Error::ArgumentError, "Not support #{arg}" unless OneGadget::Helper.integer?(arg[sign..-1])
118
+
113
119
  val = Integer(arg.slice!(sign..-1))
114
120
  end
115
121
  obj = predefined[arg] || Lambda.new(arg)
@@ -1,4 +1,5 @@
1
1
  require 'one_gadget/emulators/lambda'
2
+ require 'one_gadget/error'
2
3
 
3
4
  module OneGadget
4
5
  # Instruction emulator to solve the constraint of gadgets.
@@ -20,7 +21,8 @@ module OneGadget
20
21
  # The parsing result.
21
22
  def parse(cmd)
22
23
  inst = instructions.find { |i| i.match?(cmd) }
23
- raise ArgumentError, "Not implemented instruction in #{cmd}" if inst.nil?
24
+ raise Error::ArgumentError, "Not implemented instruction in #{cmd}" if inst.nil?
25
+
24
26
  [inst, inst.fetch_args(cmd)]
25
27
  end
26
28
 
@@ -31,6 +31,7 @@ module OneGadget
31
31
  inst, args = parse(cmd)
32
32
  # return registers[pc] = args[0] if inst.inst == 'call'
33
33
  return true if inst.inst == 'jmp' # believe the fetcher has handled jmp.
34
+
34
35
  sym = "inst_#{inst.inst}".to_sym
35
36
  __send__(sym, *args) != :fail
36
37
  end
@@ -41,7 +42,7 @@ module OneGadget
41
42
  # @return [Boolean]
42
43
  def process(cmd)
43
44
  process!(cmd)
44
- rescue ArgumentError, OneGadget::Error::Error
45
+ rescue OneGadget::Error::Error
45
46
  false
46
47
  end
47
48
 
@@ -87,8 +88,10 @@ module OneGadget
87
88
  else
88
89
  # Just ignore strange case...
89
90
  return unless tar.include?(sp)
91
+
90
92
  tar = OneGadget::Emulators::Lambda.parse(tar, predefined: registers)
91
93
  return if tar.deref_count != 1 # should not happen
94
+
92
95
  tar.ref!
93
96
  stack[tar.evaluate(eval_dict)] = src
94
97
  end
@@ -128,9 +131,11 @@ module OneGadget
128
131
  # check if (tar, src) in form (xmm*, [sp+*])
129
132
  def check_xmm_sp(tar, src)
130
133
  return yield unless tar.start_with?('xmm') && register?(tar) && src.include?(sp)
134
+
131
135
  tar_lm = OneGadget::Emulators::Lambda.parse(tar, predefined: registers)
132
136
  src_lm = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
133
137
  return yield if src_lm.deref_count != 1
138
+
134
139
  src_lm.ref!
135
140
  [tar_lm, src_lm]
136
141
  end
@@ -145,13 +150,15 @@ module OneGadget
145
150
  val = OneGadget::Emulators::Lambda.parse(val, predefined: registers)
146
151
  registers[sp] -= size_t
147
152
  cur_top = registers[sp].evaluate(eval_dict)
148
- raise ArgumentError, "Corrupted stack pointer: #{cur_top}" unless cur_top.is_a?(Integer)
153
+ raise Error::ArgumentError, "Corrupted stack pointer: #{cur_top}" unless cur_top.is_a?(Integer)
154
+
149
155
  stack[cur_top] = val
150
156
  end
151
157
 
152
158
  def inst_xor(dst, src)
153
159
  # only supports dst == src
154
- raise ArgumentError, 'xor operator only supports dst = src' unless dst == src
160
+ raise Error::ArgumentError, 'xor operator only supports dst = src' unless dst == src
161
+
155
162
  dst[0] = 'r' if self.class.bits == 64 && dst.start_with?('e')
156
163
  registers[dst] = 0
157
164
  end
@@ -163,7 +170,8 @@ module OneGadget
163
170
 
164
171
  def inst_sub(tar, src)
165
172
  src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
166
- raise ArgumentError, "Can't handle -= of type #{src.class}" unless src.is_a?(Integer)
173
+ raise Error::ArgumentError, "Can't handle -= of type #{src.class}" unless src.is_a?(Integer)
174
+
167
175
  registers[tar] -= src
168
176
  end
169
177
 
@@ -183,6 +191,7 @@ module OneGadget
183
191
  def inst_call(addr)
184
192
  # This is the last call
185
193
  return registers[pc] = addr if %w[execve execl].any? { |n| addr.include?(n) }
194
+
186
195
  # TODO: handle some registers would be fucked after call
187
196
  checker = {
188
197
  'sigprocmask' => {},
@@ -192,6 +201,7 @@ module OneGadget
192
201
  }
193
202
  func = checker.keys.find { |n| addr.include?(n) }
194
203
  return if func && checker[func].all? { |idx, sym| check_argument(idx, sym) }
204
+
195
205
  # unhandled case or checker's condition fails
196
206
  :fail
197
207
  end
@@ -205,11 +215,12 @@ module OneGadget
205
215
  end
206
216
 
207
217
  def raise_unsupported(inst, *args)
208
- raise OneGadget::Error::UnsupportedInstructionArguments, "#{inst} #{args.join(', ')}"
218
+ raise OneGadget::Error::UnsupportedInstructionArgumentsError, "#{inst} #{args.join(', ')}"
209
219
  end
210
220
 
211
221
  def to_lambda(reg)
212
222
  return super unless reg =~ /^xmm\d+$/
223
+
213
224
  Array.new(128 / self.class.bits) do |i|
214
225
  OneGadget::Emulators::Lambda.new("#{reg}__#{i}")
215
226
  end
@@ -3,7 +3,13 @@ module OneGadget
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
- class UnsupportedInstructionArguments < Error
6
+ class UnsupportedInstructionArgumentsError < Error
7
+ end
8
+
9
+ class UnsupportedArchitectureError < Error
10
+ end
11
+
12
+ class ArgumentError < Error
7
13
  end
8
14
  end
9
15
  end
@@ -1,3 +1,4 @@
1
+ require 'one_gadget/error'
1
2
  require 'one_gadget/fetchers/amd64'
2
3
  require 'one_gadget/fetchers/i386'
3
4
  require 'one_gadget/gadget'
@@ -24,11 +25,13 @@ module OneGadget
24
25
  # @return [Array<OneGadget::Gadget::Gadget>]
25
26
  # Array of all found gadgets is returned.
26
27
  def from_file(file)
28
+ arch = OneGadget::Helper.architecture(file)
27
29
  klass = {
28
30
  amd64: OneGadget::Fetcher::Amd64,
29
31
  i386: OneGadget::Fetcher::I386
30
- }[OneGadget::Helper.architecture(file)]
31
- raise ArgumentError, 'Unsupported architecture!' if klass.nil?
32
+ }[arch]
33
+ raise Error::UnsupportedArchitectureError, arch if klass.nil?
34
+
32
35
  trim_gadgets(klass.new(file).find)
33
36
  end
34
37
 
@@ -16,6 +16,7 @@ module OneGadget
16
16
  cands = super do |candidate|
17
17
  next false unless candidate.include?(bin_sh_hex) # works in x86-64
18
18
  next false unless candidate.lines.last.include?('execve') # only care execve
19
+
19
20
  true
20
21
  end
21
22
  cands + jmp_case_candidates # + sigaction_case_candidates
@@ -34,6 +35,7 @@ module OneGadget
34
35
  cand = cand.lines.map(&:strip).reject(&:empty?)
35
36
  jmp_at = cand.index { |c| c.include?('jmp') }
36
37
  next nil if jmp_at.nil?
38
+
37
39
  cand = cand[0..jmp_at]
38
40
  jmp_addr = cand.last.scan(/jmp\s+([\da-f]+)\s/)[0][0].to_i(16)
39
41
  dump = `#{objdump_cmd(start: jmp_addr, stop: jmp_addr + 100)}|egrep '[0-9a-f]+:'`
@@ -24,6 +24,7 @@ module OneGadget
24
24
  processor = emulate(lines[i..-1])
25
25
  options = resolve(processor)
26
26
  next if options.nil? # impossible be a gadget
27
+
27
28
  offset = offset_of(lines[i])
28
29
  gadgets << OneGadget::Gadget::Gadget.new(offset, options)
29
30
  end
@@ -69,6 +70,7 @@ module OneGadget
69
70
  # since the logic is different between amd64 and i386,
70
71
  # invoke str_bin_sh? for checking
71
72
  return unless str_bin_sh?(processor.argument(0).to_s)
73
+
72
74
  if call.include?('execve')
73
75
  resolve_execve(processor)
74
76
  elsif call.include?('execl')
@@ -84,11 +86,13 @@ module OneGadget
84
86
  cons = []
85
87
  cons << check_execve_arg(processor, arg1)
86
88
  return nil unless cons.all?
89
+
87
90
  envp = 'environ'
88
91
  return nil unless check_envp(processor, arg2) do |c|
89
92
  cons << c
90
93
  envp = arg2
91
94
  end
95
+
92
96
  { constraints: cons, effect: %(execve("/bin/sh", #{arg1}, #{envp})) }
93
97
  end
94
98
 
@@ -99,6 +103,7 @@ module OneGadget
99
103
  num = Integer(arg[processor.sp.size..-1])
100
104
  slot = processor.stack[num].to_s
101
105
  return if global_var?(slot)
106
+
102
107
  "#{slot} == NULL"
103
108
  else
104
109
  "[#{arg}] == NULL || #{arg} == NULL"
@@ -110,9 +115,11 @@ module OneGadget
110
115
  # believe it is environ
111
116
  # if starts with [[ but not global, drop it.
112
117
  return global_var?(arg) if arg.start_with?('[[')
118
+
113
119
  # normal
114
120
  cons = check_execve_arg(processor, arg)
115
121
  return nil if cons.nil?
122
+
116
123
  yield cons
117
124
  end
118
125
 
@@ -125,6 +132,7 @@ module OneGadget
125
132
  args << '"sh"'
126
133
  end
127
134
  return nil if global_var?(arg) # we don't want base-related constraints
135
+
128
136
  args << arg
129
137
  # now arg is the constraint.
130
138
  { constraints: ["#{arg} == NULL"], effect: %(execl("/bin/sh", #{args.join(', ')})) }
@@ -168,7 +176,8 @@ module OneGadget
168
176
  end
169
177
 
170
178
  def str_offset(str)
171
- IO.binread(file).index(str + "\x00")
179
+ IO.binread(file).index(str + "\x00") ||
180
+ raise(Error::ArgumentError, "File #{file.inspect} doesn't contain string \"/bin/sh\", not glibc?")
172
181
  end
173
182
 
174
183
  def offset_of(assembly)
@@ -13,6 +13,7 @@ module OneGadget
13
13
  rel_sh_hex = rel_sh.to_s(16)
14
14
  super do |candidate|
15
15
  next false unless candidate.include?(rel_sh_hex)
16
+
16
17
  true
17
18
  end
18
19
  end
@@ -26,10 +27,12 @@ module OneGadget
26
27
  # first check if argument 0 is '/bin/sh' to prevent error
27
28
  arg0 = processor.argument(0)
28
29
  return nil unless str_bin_sh?(arg0.to_s)
30
+
29
31
  @base_reg = arg0.deref.obj.to_s # this should be esi or ebx..
30
32
  # now we can let parent to invoke global_var?
31
33
  res = super
32
34
  return if res.nil?
35
+
33
36
  # unshift got constraint into cons
34
37
  res[:constraints].unshift("#{@base_reg} is the GOT address of libc")
35
38
  res
@@ -1,4 +1,5 @@
1
1
  require 'one_gadget/abi'
2
+ require 'one_gadget/error'
2
3
 
3
4
  module OneGadget
4
5
  # Module for define gadgets.
@@ -53,9 +54,11 @@ module OneGadget
53
54
  require_all if BUILDS.empty?
54
55
  return BUILDS[build_id] if BUILDS.key?(build_id)
55
56
  return build_not_found unless remote
57
+
56
58
  # fetch remote builds
57
59
  table = OneGadget::Helper.remote_builds.find { |c| c.include?(build_id) }
58
60
  return build_not_found if table.nil? # remote doesn't have this one either.
61
+
59
62
  # builds found in remote! Ask update gem and download remote gadgets.
60
63
  OneGadget::Helper.ask_update(msg: 'The desired one-gadget can be found in lastest version!')
61
64
  tmp_file = OneGadget::Helper.download_build(table)
@@ -77,9 +80,11 @@ module OneGadget
77
80
  # # Advanced Micro Devices X86-64
78
81
  # # ...
79
82
  def builds_info(build_id)
80
- raise ArgumentError, "Invalid BuildID #{build_id.inspect}" if build_id =~ /[^0-9a-f]/
83
+ raise Error::ArgumentError, "Invalid BuildID #{build_id.inspect}" if build_id =~ /[^0-9a-f]/
84
+
81
85
  files = Dir.glob(File.join(BUILDS_PATH, "*-#{build_id}*.rb")).sort
82
86
  return OneGadget::Logger.not_found(build_id) && nil if files.empty?
87
+
83
88
  if files.size > 1
84
89
  OneGadget::Logger.warn("Multiple BuildIDs match /^#{build_id}/\n")
85
90
  show = files.map do |f|
@@ -5,6 +5,7 @@ require 'tempfile'
5
5
 
6
6
  require 'elftools'
7
7
 
8
+ require 'one_gadget/error'
8
9
  require 'one_gadget/logger'
9
10
 
10
11
  module OneGadget
@@ -17,12 +18,13 @@ module OneGadget
17
18
  # Verify if `build_id` is a valid SHA1 hex format.
18
19
  # @param [String] build_id
19
20
  # BuildID.
20
- # @raise [ArgumentError]
21
+ # @raise [Error::ArgumentError]
21
22
  # Raises error if invalid.
22
23
  # @return [void]
23
24
  def verify_build_id!(build_id)
24
25
  return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/
25
- raise ArgumentError, format('invalid BuildID format: %p', build_id)
26
+
27
+ raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id)
26
28
  end
27
29
 
28
30
  # Fetch lines start with '#'.
@@ -44,6 +46,36 @@ module OneGadget
44
46
  Pathname.new(File.expand_path(path)).realpath.to_s
45
47
  end
46
48
 
49
+ # Checks if the file of given path is a valid ELF file.
50
+ #
51
+ # @param [String] path Path to target file.
52
+ # @return [Boolean] If the file is an ELF or not.
53
+ # @example
54
+ # valid_elf_file?('/etc/passwd')
55
+ # => false
56
+ # valid_elf_file?('/lib64/ld-linux-x86-64.so.2')
57
+ # => true
58
+ def valid_elf_file?(path)
59
+ # A light-weight way to check if is a valid ELF file
60
+ # Checks at least one phdr should present.
61
+ File.open(path) { |f| ELFTools::ELFFile.new(f).each_segments.first }
62
+ true
63
+ rescue ELFTools::ELFError
64
+ false
65
+ end
66
+
67
+ # Checks if the file of given path is a valid ELF file
68
+ # An error message will be shown if given path is not a valid ELF.
69
+ #
70
+ # @param [String] path Path to target file.
71
+ # @return [void]
72
+ # @raise [Error::ArgumentError] Raise exception if not a valid ELF.
73
+ def verify_elf_file!(path)
74
+ return if valid_elf_file?(path)
75
+
76
+ raise Error::ArgumentError, 'Not an ELF file, expected glibc as input'
77
+ end
78
+
47
79
  # Get the Build ID of target ELF.
48
80
  # @param [String] path Absolute file path.
49
81
  # @return [String] Target build id.
@@ -72,6 +104,7 @@ module OneGadget
72
104
  def color_enabled?
73
105
  # if not set, use tty to check
74
106
  return $stdout.tty? if @disable_color.nil?
107
+
75
108
  !@disable_color
76
109
  end
77
110
 
@@ -81,7 +114,8 @@ module OneGadget
81
114
  normal_s: "\e[38;5;203m", # red
82
115
  integer: "\e[38;5;189m", # light purple
83
116
  reg: "\e[38;5;82m", # light green
84
- warn: "\e[38;5;230m" # light yellow
117
+ warn: "\e[38;5;230m", # light yellow
118
+ error: "\e[38;5;196m" # heavy red
85
119
  }.freeze
86
120
 
87
121
  # Wrapper color codes for pretty inspect.
@@ -90,6 +124,7 @@ module OneGadget
90
124
  # @return [String] Wrapper with color codes.
91
125
  def colorize(str, sev: :normal_s)
92
126
  return str unless color_enabled?
127
+
93
128
  cc = COLOR_CODE
94
129
  color = cc.key?(sev) ? cc[sev] : ''
95
130
  "#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
@@ -142,6 +177,7 @@ module OneGadget
142
177
 
143
178
  response = http.request(request)
144
179
  raise ArgumentError, "Fail to get response of #{url}" unless %w(200 302).include?(response.code)
180
+
145
181
  response.code == '302' ? response['location'] : response.body
146
182
  rescue NoMethodError, SocketError, ArgumentError => e
147
183
  p e
@@ -163,9 +199,13 @@ module OneGadget
163
199
  def architecture(file)
164
200
  f = File.open(file)
165
201
  str = ELFTools::ELFFile.new(f).machine
166
- return :amd64 if str.include?('X86-64')
167
- return :i386 if str.include?('Intel 80386')
168
- :unknown
202
+ {
203
+ 'Advanced Micro Devices X86-64' => :amd64,
204
+ 'Intel 80386' => :i386,
205
+ 'ARM' => :arm,
206
+ 'AArch64' => :aarch64,
207
+ 'MIPS R3000' => :mips
208
+ }[str] || :unknown
169
209
  rescue ELFTools::ELFError # not a valid ELF
170
210
  :invalid
171
211
  ensure
@@ -184,6 +224,7 @@ module OneGadget
184
224
  # hex(0, psign: true) #=> +0x0
185
225
  def hex(val, psign: false)
186
226
  return format("#{psign ? '+' : ''}0x%x", val) if val >= 0
227
+
187
228
  format('-0x%x', -val)
188
229
  end
189
230
 
@@ -10,11 +10,13 @@ module OneGadget
10
10
  prep = ' ' * 12
11
11
  message = msg.lines.map.with_index do |str, i|
12
12
  next str if i.zero?
13
+
13
14
  str.strip.empty? ? str : prep + str
14
15
  end
15
16
  color = case severity
16
17
  when 'WARN' then :warn
17
18
  when 'INFO' then :reg
19
+ when 'ERROR' then :error
18
20
  end
19
21
  "[#{OneGadget::Helper.colorize('OneGadget', sev: color)}] #{message.join}"
20
22
  end
@@ -29,7 +31,7 @@ module OneGadget
29
31
  []
30
32
  end
31
33
 
32
- %i[info warn].each do |sym|
34
+ %i[info warn error].each do |sym|
33
35
  define_method(sym) do |msg|
34
36
  @logger.__send__(sym, msg)
35
37
  end
@@ -7,8 +7,8 @@ require 'one_gadget/version'
7
7
  module OneGadget
8
8
  # For automatically check update.
9
9
  module Update
10
- # At least 7 days between check for new version.
11
- FREQUENCY = 7 * 24 * 60 * 60
10
+ # At least 30 days between check for new version.
11
+ FREQUENCY = 30 * 24 * 60 * 60
12
12
  # Path to cache file.
13
13
  CACHE_FILE = File.join(ENV['HOME'], '.cache', 'one_gadget', 'update').freeze
14
14
 
@@ -18,6 +18,7 @@ module OneGadget
18
18
  # @return [void]
19
19
  def check!
20
20
  return unless need_check?
21
+
21
22
  FileUtils.touch(cache_file)
22
23
  OneGadget::Logger.info("Checking for new versions of OneGadget\n" \
23
24
  "To disable this functionality, do\n$ echo never > #{CACHE_FILE}\n\n")
@@ -27,9 +28,8 @@ module OneGadget
27
28
  end
28
29
 
29
30
  # show update message
30
- msg = format("A newer version of OneGadget is available (%s --> %s).\n", OneGadget::VERSION, latest)
31
- msg << "Update with: $ gem update one_gadget\n\n"
32
- OneGadget::Logger.info(msg)
31
+ msg = format('A newer version of OneGadget is available (%s --> %s).', OneGadget::VERSION, latest)
32
+ OneGadget::Helper.ask_update(msg: msg)
33
33
  end
34
34
 
35
35
  private
@@ -37,14 +37,18 @@ module OneGadget
37
37
  # check ~/.cache/one_gadget/update
38
38
  def need_check?
39
39
  cache = cache_file
40
+ # don't check if not CLI
41
+ return false unless $stdout.tty?
40
42
  return false if cache.nil? # cache file fails, no update check.
41
43
  return false if IO.binread(cache).strip == 'never'
44
+
42
45
  Time.now >= last_check + FREQUENCY
43
46
  end
44
47
 
45
48
  def last_check
46
49
  cache = cache_file
47
50
  return Time.now if cache.nil?
51
+
48
52
  File.open(cache, &:mtime)
49
53
  end
50
54
 
@@ -1,4 +1,4 @@
1
1
  module OneGadget
2
2
  # Current gem version.
3
- VERSION = '1.6.0'.freeze
3
+ VERSION = '1.6.1'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: one_gadget
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-28 00:00:00.000000000 Z
11
+ date: 2018-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elftools
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.55'
61
+ version: '0.59'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.55'
68
+ version: '0.59'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -818,7 +818,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
818
818
  version: '0'
819
819
  requirements: []
820
820
  rubyforge_project:
821
- rubygems_version: 2.6.14
821
+ rubygems_version: 2.7.6
822
822
  signing_key:
823
823
  specification_version: 4
824
824
  summary: one_gadget