one_gadget 1.6.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
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