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 +5 -5
- data/lib/one_gadget.rb +14 -3
- data/lib/one_gadget/emulators/instruction.rb +6 -1
- data/lib/one_gadget/emulators/lambda.rb +12 -6
- data/lib/one_gadget/emulators/processor.rb +3 -1
- data/lib/one_gadget/emulators/x86.rb +16 -5
- data/lib/one_gadget/error.rb +7 -1
- data/lib/one_gadget/fetcher.rb +5 -2
- data/lib/one_gadget/fetchers/amd64.rb +2 -0
- data/lib/one_gadget/fetchers/base.rb +10 -1
- data/lib/one_gadget/fetchers/i386.rb +3 -0
- data/lib/one_gadget/gadget.rb +6 -1
- data/lib/one_gadget/helper.rb +47 -6
- data/lib/one_gadget/logger.rb +3 -1
- data/lib/one_gadget/update.rb +9 -5
- data/lib/one_gadget/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ff8578d867b076e1c59b2b271879d5448fc2c985c94b67a5e3fc8febcabb01ef
|
4
|
+
data.tar.gz: 39c681c497cfb5bce05eb993fcb47aa0b6e0ef77aba6af2507d4184ccb23ef20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
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::
|
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
|
data/lib/one_gadget/error.rb
CHANGED
@@ -3,7 +3,13 @@ module OneGadget
|
|
3
3
|
class Error < StandardError
|
4
4
|
end
|
5
5
|
|
6
|
-
class
|
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
|
data/lib/one_gadget/fetcher.rb
CHANGED
@@ -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
|
-
}[
|
31
|
-
raise
|
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
|
data/lib/one_gadget/gadget.rb
CHANGED
@@ -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|
|
data/lib/one_gadget/helper.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
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
|
|
data/lib/one_gadget/logger.rb
CHANGED
@@ -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
|
data/lib/one_gadget/update.rb
CHANGED
@@ -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
|
11
|
-
FREQUENCY =
|
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(
|
31
|
-
msg
|
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
|
|
data/lib/one_gadget/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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
|
821
|
+
rubygems_version: 2.7.6
|
822
822
|
signing_key:
|
823
823
|
specification_version: 4
|
824
824
|
summary: one_gadget
|