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