one_gadget 1.6.2 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +141 -94
- data/bin/one_gadget +4 -3
- data/lib/one_gadget/abi.rb +42 -22
- data/lib/one_gadget/builds/libc-2.19-397c84e78c14cbffba39a48184db482211df9fb3.rb +38 -0
- data/lib/one_gadget/builds/libc-2.19-4eda8ff01be3fba1c7bdd442a8690c3dc7397b6a.rb +44 -0
- data/lib/one_gadget/builds/libc-2.19-509ee0c9616c4c3ed81951501a8950e1f529bbff.rb +38 -0
- data/lib/one_gadget/builds/libc-2.19-6aff6d091954955fe931bb720a17708513aabda7.rb +41 -0
- data/lib/one_gadget/builds/libc-2.19-8d935a42f2f2a1149aa52d3098b32b1d5012cb67.rb +38 -0
- data/lib/one_gadget/builds/libc-2.19-a820f849dda0b99ed06dd59bb88404969b3a5f88.rb +41 -0
- data/lib/one_gadget/builds/libc-2.19-d9a10b8ef90300628dd0a3a535106967714d7328.rb +47 -0
- data/lib/one_gadget/builds/libc-2.21-169a143e9c40cfd9d09695333e45fd67743cd2d6.rb +37 -0
- data/lib/one_gadget/builds/libc-2.21-2e9718e58257bda1dc0d751665a3ee233bf606f2.rb +37 -0
- data/lib/one_gadget/builds/libc-2.23-29e38445a740bba5a77b86691e3c51a7e48dc79b.rb +46 -0
- data/lib/one_gadget/builds/libc-2.23-679ad41a6bc9e718a11a36cf9879cac97197e565.rb +37 -0
- data/lib/one_gadget/builds/libc-2.23-b5381a457906d279073822a5ceb24c4bfef94ddb.rb +37 -0
- data/lib/one_gadget/builds/libc-2.23-d10fbfd9328f5ffaca50aa93562cb3bfb618fbcc.rb +43 -0
- data/lib/one_gadget/builds/libc-2.23-dd5192a769e33ed6ca68a6ab5740ff9e8ec678a7.rb +46 -0
- data/lib/one_gadget/builds/libc-2.24-1f7bdfb9a24714835cee6e6597ea7aa782821371.rb +46 -0
- data/lib/one_gadget/builds/libc-2.24-206b2bb216b6cdb6b1be565a6fcd29f3862db060.rb +49 -0
- data/lib/one_gadget/builds/libc-2.24-26e84118fee5788eb5d8dda66b7e7f029d2c7800.rb +43 -0
- data/lib/one_gadget/builds/libc-2.24-43adbb1e7368c94fba1ba9020d8ef0808bff5bc4.rb +37 -0
- data/lib/one_gadget/builds/libc-2.24-497931f8d2346a6d0e300a65d8fc6106c6c88c15.rb +37 -0
- data/lib/one_gadget/builds/libc-2.24-4fa7401566d6b3e2c7ee5df3b4d85a01f85b595c.rb +37 -0
- data/lib/one_gadget/builds/libc-2.24-568d20b7e0d08bc282fb42ae405c7054e4209ede.rb +37 -0
- data/lib/one_gadget/builds/libc-2.24-5b72576ff331e93852355123afecdec70fd247b5.rb +49 -0
- data/lib/one_gadget/builds/libc-2.24-a4c01d397b6584f7040ef266b16a5d4da0b7a087.rb +43 -0
- data/lib/one_gadget/builds/libc-2.24-b81a06f0ac241c4aa8860602d9abcc903adbb675.rb +46 -0
- data/lib/one_gadget/builds/libc-2.24-be6d412ecc4816c46eb49e750b02f714a9131c4e.rb +46 -0
- data/lib/one_gadget/builds/libc-2.24-d2a8a8ac188a6c3bafa4813a3d2789240ee49489.rb +46 -0
- data/lib/one_gadget/builds/libc-2.24-dff06414a29b97b865ef938e06a7751fe8b1b2d0.rb +46 -0
- data/lib/one_gadget/builds/libc-2.24-e5dc6c0caa39828fa10ed37e642723a581acdb6d.rb +37 -0
- data/lib/one_gadget/builds/libc-2.24-fd0655c4d2073eda4235084e1d0e558f0251be8a.rb +37 -0
- data/lib/one_gadget/builds/libc-2.25-e5eb6347f0629b37bf698200022a683b7efb10ed.rb +37 -0
- data/lib/one_gadget/builds/libc-2.26-1c39b3b3faa2a2cbb0fa0b6845b29332562262d3.rb +37 -0
- data/lib/one_gadget/builds/libc-2.26-499b381aaf00ce85ee5d4a12770ea369b30d2a41.rb +52 -0
- data/lib/one_gadget/builds/libc-2.26-4cc84abfe1fd26a485fc2b1b954c281ce9d358fd.rb +52 -0
- data/lib/one_gadget/builds/libc-2.26-4ea852c9d6a5084b8b58509b3b3d37d3d8cddb90.rb +52 -0
- data/lib/one_gadget/builds/libc-2.26-6d2b609f0c8e7b338f767b08c5ac712fac809d31.rb +49 -0
- data/lib/one_gadget/builds/libc-2.26-fb587bc4429e7d1b0de31a3b9ee8ae78ee797eb0.rb +37 -0
- data/lib/one_gadget/builds/libc-2.27-0e188ec5f09c187a7a92784d4b97aa251b15a93c.rb +47 -0
- data/lib/one_gadget/builds/libc-2.27-53f40c1d2f3739ae017dcdcef1a17314786e3709.rb +38 -0
- data/lib/one_gadget/builds/libc-2.27-9dd0bb57f81671704475d1e5163405f7b4d4b454.rb +32 -0
- data/lib/one_gadget/builds/libc-2.28-44f5a3efb0e5733fa9d97e690cb36cd4c682bcdb.rb +41 -0
- data/lib/one_gadget/builds/libc-2.28-5784a31a1c26f6d2157e585205ebb63dd19ff90f.rb +41 -0
- data/lib/one_gadget/builds/libc-2.28-5b157f49586a3ca84d55837f97ff466767dd3445.rb +38 -0
- data/lib/one_gadget/builds/libc-2.28-6ee9454b96efa9e343f9e8105f2fa4529265ea05.rb +38 -0
- data/lib/one_gadget/emulators/aarch64.rb +176 -0
- data/lib/one_gadget/emulators/amd64.rb +1 -1
- data/lib/one_gadget/emulators/i386.rb +1 -1
- data/lib/one_gadget/emulators/instruction.rb +36 -7
- data/lib/one_gadget/emulators/lambda.rb +36 -25
- data/lib/one_gadget/emulators/processor.rb +94 -6
- data/lib/one_gadget/emulators/x86.rb +43 -95
- data/lib/one_gadget/error.rb +15 -3
- data/lib/one_gadget/fetcher.rb +3 -1
- data/lib/one_gadget/fetchers/aarch64.rb +41 -0
- data/lib/one_gadget/fetchers/amd64.rb +4 -2
- data/lib/one_gadget/fetchers/base.rb +35 -11
- data/lib/one_gadget/fetchers/i386.rb +2 -2
- data/lib/one_gadget/fetchers/x86.rb +23 -0
- data/lib/one_gadget/gadget.rb +63 -11
- data/lib/one_gadget/helper.rb +282 -203
- data/lib/one_gadget/one_gadget.rb +12 -4
- data/lib/one_gadget/version.rb +1 -1
- metadata +57 -6
data/lib/one_gadget/error.rb
CHANGED
@@ -5,12 +5,24 @@ module OneGadget
|
|
5
5
|
class Error < StandardError
|
6
6
|
end
|
7
7
|
|
8
|
-
#
|
9
|
-
class
|
8
|
+
# Super class of unsupported errors.
|
9
|
+
class UnsupportedError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
# Unsupported instruction.
|
13
|
+
class UnsupportedInstructionError < UnsupportedError
|
14
|
+
end
|
15
|
+
|
16
|
+
# Raises when arguments form of an instrution is invalid.
|
17
|
+
class InstructionArgumentError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
# Raises when form of arguments is valid but not supported.
|
21
|
+
class UnsupportedInstructionArgumentError < UnsupportedError
|
10
22
|
end
|
11
23
|
|
12
24
|
# Unsupported architecture.
|
13
|
-
class UnsupportedArchitectureError <
|
25
|
+
class UnsupportedArchitectureError < UnsupportedError
|
14
26
|
end
|
15
27
|
|
16
28
|
# Argument error of ruby methods.
|
data/lib/one_gadget/fetcher.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'one_gadget/error'
|
2
|
+
require 'one_gadget/fetchers/aarch64'
|
2
3
|
require 'one_gadget/fetchers/amd64'
|
3
4
|
require 'one_gadget/fetchers/i386'
|
4
5
|
require 'one_gadget/gadget'
|
@@ -27,6 +28,7 @@ module OneGadget
|
|
27
28
|
def from_file(file)
|
28
29
|
arch = OneGadget::Helper.architecture(file)
|
29
30
|
klass = {
|
31
|
+
aarch64: OneGadget::Fetcher::AArch64,
|
30
32
|
amd64: OneGadget::Fetcher::Amd64,
|
31
33
|
i386: OneGadget::Fetcher::I386
|
32
34
|
}[arch]
|
@@ -46,7 +48,7 @@ module OneGadget
|
|
46
48
|
(gadgets[j].constraints - g.constraints).empty?
|
47
49
|
end
|
48
50
|
end
|
49
|
-
res.sort_by
|
51
|
+
res.sort_by(&:offset)
|
50
52
|
end
|
51
53
|
end
|
52
54
|
extend ClassMethods
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'one_gadget/emulators/aarch64'
|
2
|
+
require 'one_gadget/fetchers/base'
|
3
|
+
|
4
|
+
module OneGadget
|
5
|
+
module Fetcher
|
6
|
+
# Define common methods for gadget fetchers.
|
7
|
+
class AArch64 < Base
|
8
|
+
private
|
9
|
+
|
10
|
+
def emulator
|
11
|
+
OneGadget::Emulators::AArch64.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# If str contains a branch instruction.
|
15
|
+
def branch?(str)
|
16
|
+
%w[b b.hi b.gt b.eq b.le b.ls b.lt b.ne b.cs].any? { |f| str.include?(' ' + f + ' ') }
|
17
|
+
end
|
18
|
+
|
19
|
+
def call_str
|
20
|
+
'bl'
|
21
|
+
end
|
22
|
+
|
23
|
+
def bin_sh_offset
|
24
|
+
@bin_sh_offset ||= str_offset('/bin/sh')
|
25
|
+
end
|
26
|
+
|
27
|
+
def str_bin_sh?(str)
|
28
|
+
str.include?('$base') && str.include?(bin_sh_offset.to_s(16))
|
29
|
+
end
|
30
|
+
|
31
|
+
def str_sh?(str)
|
32
|
+
# XXX: hardcode -0x10 is bad
|
33
|
+
str.include?('$base') && str.include?((bin_sh_offset - 0x10).to_s(16))
|
34
|
+
end
|
35
|
+
|
36
|
+
def global_var?(str)
|
37
|
+
str.include?('$base')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'one_gadget/emulators/amd64'
|
2
|
-
require 'one_gadget/fetchers/
|
2
|
+
require 'one_gadget/fetchers/x86'
|
3
3
|
|
4
4
|
module OneGadget
|
5
5
|
module Fetcher
|
6
6
|
# Fetcher for amd64.
|
7
|
-
class Amd64 < OneGadget::Fetcher::
|
7
|
+
class Amd64 < OneGadget::Fetcher::X86
|
8
8
|
private
|
9
9
|
|
10
10
|
def emulator
|
@@ -36,6 +36,8 @@ module OneGadget
|
|
36
36
|
next nil if jmp_at.nil?
|
37
37
|
|
38
38
|
cand = cand[0..jmp_at]
|
39
|
+
next if cand.any? { |c| c.include?(call_str) }
|
40
|
+
|
39
41
|
jmp_addr = cand.last.scan(/jmp\s+([\da-f]+)\s/)[0][0].to_i(16)
|
40
42
|
dump = `#{objdump_cmd(start: jmp_addr, stop: jmp_addr + 100)}|egrep '[0-9a-f]+:'`
|
41
43
|
remain = dump.lines.map(&:strip).reject(&:empty?)
|
@@ -11,6 +11,7 @@ module OneGadget
|
|
11
11
|
# @param [String] file Absolute path of target libc.
|
12
12
|
def initialize(file)
|
13
13
|
@file = file
|
14
|
+
@arch = self.class.name.split('::').last.downcase.to_sym
|
14
15
|
end
|
15
16
|
|
16
17
|
# Do find gadgets in glibc.
|
@@ -29,7 +30,7 @@ module OneGadget
|
|
29
30
|
gadgets << OneGadget::Gadget::Gadget.new(offset, options)
|
30
31
|
end
|
31
32
|
gadgets
|
32
|
-
end.flatten
|
33
|
+
end.flatten
|
33
34
|
end
|
34
35
|
|
35
36
|
# Fetch candidates that end with call exec*.
|
@@ -42,7 +43,7 @@ module OneGadget
|
|
42
43
|
# @return [Array<String>]
|
43
44
|
# Each +String+ returned is multi-lines of assembly code.
|
44
45
|
def candidates(&block)
|
45
|
-
cands = `#{objdump_cmd}|egrep '
|
46
|
+
cands = `#{objdump_cmd}|egrep '#{call_str}.*<exec[^+]*>$' -B 30`.split('--').map do |cand|
|
46
47
|
cand.lines.map(&:strip).reject(&:empty?).join("\n")
|
47
48
|
end
|
48
49
|
# remove all jmps
|
@@ -83,7 +84,7 @@ module OneGadget
|
|
83
84
|
# arg[2] == NULL || [arg[2]] == NULL || arg[2] == envp
|
84
85
|
arg1 = processor.argument(1).to_s
|
85
86
|
arg2 = processor.argument(2).to_s
|
86
|
-
cons =
|
87
|
+
cons = processor.constraints
|
87
88
|
cons << check_execve_arg(processor, arg1)
|
88
89
|
return nil unless cons.all?
|
89
90
|
|
@@ -134,8 +135,9 @@ module OneGadget
|
|
134
135
|
return nil if global_var?(arg) # we don't want base-related constraints
|
135
136
|
|
136
137
|
args << arg
|
138
|
+
cons = processor.constraints + ["#{arg} == NULL"]
|
137
139
|
# now arg is the constraint.
|
138
|
-
{ constraints:
|
140
|
+
{ constraints: cons, effect: %(execl("/bin/sh", #{args.join(', ')})) }
|
139
141
|
end
|
140
142
|
|
141
143
|
def global_var?(_str); raise NotImplementedError
|
@@ -147,6 +149,9 @@ module OneGadget
|
|
147
149
|
def str_sh?(_str); raise NotImplementedError
|
148
150
|
end
|
149
151
|
|
152
|
+
def call_str; raise NotImplementedError
|
153
|
+
end
|
154
|
+
|
150
155
|
def emulate(cmds)
|
151
156
|
cmds.each_with_object(emulator) { |cmd, obj| break obj unless obj.process(cmd) }
|
152
157
|
end
|
@@ -155,10 +160,20 @@ module OneGadget
|
|
155
160
|
end
|
156
161
|
|
157
162
|
def objdump_cmd(start: nil, stop: nil)
|
158
|
-
cmd =
|
159
|
-
cmd.
|
160
|
-
cmd.
|
161
|
-
cmd
|
163
|
+
cmd = [objdump_bin, '--no-show-raw-insn', '-w', '-d', *objdump_options, file]
|
164
|
+
cmd.push('--start-address', start) if start
|
165
|
+
cmd.push('--stop-address', stop) if stop
|
166
|
+
::Shellwords.join(cmd)
|
167
|
+
end
|
168
|
+
|
169
|
+
def objdump_bin
|
170
|
+
OneGadget::Helper.find_objdump(@arch).tap do |bin|
|
171
|
+
install_objdump_guide! if bin.nil?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def objdump_options
|
176
|
+
[]
|
162
177
|
end
|
163
178
|
|
164
179
|
def slice_prefix(cands)
|
@@ -171,18 +186,27 @@ module OneGadget
|
|
171
186
|
end
|
172
187
|
|
173
188
|
# If str contains a branch instruction.
|
174
|
-
def branch?(
|
175
|
-
%w(jmp je jne jl jb ja jg).any? { |f| str.include?(f) }
|
189
|
+
def branch?(_str); raise NotImplementedError
|
176
190
|
end
|
177
191
|
|
178
192
|
def str_offset(str)
|
179
193
|
IO.binread(file).index(str + "\x00") ||
|
180
|
-
raise(Error::ArgumentError, "File #{file.inspect} doesn't contain string
|
194
|
+
raise(Error::ArgumentError, "File #{file.inspect} doesn't contain string #{str.inspect}, not glibc?")
|
181
195
|
end
|
182
196
|
|
183
197
|
def offset_of(assembly)
|
184
198
|
assembly.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
185
199
|
end
|
200
|
+
|
201
|
+
def install_objdump_guide!
|
202
|
+
raise Error::UnsupportedArchitectureError, <<-EOS
|
203
|
+
Objdump that supports architecture #{@arch.to_s.inspect} is not found!
|
204
|
+
Please install the package 'binutils-multiarch' and try one_gadget again!
|
205
|
+
|
206
|
+
For Ubuntu users:
|
207
|
+
$ [sudo] apt install binutils-multiarch
|
208
|
+
EOS
|
209
|
+
end
|
186
210
|
end
|
187
211
|
end
|
188
212
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'elftools'
|
2
2
|
|
3
3
|
require 'one_gadget/emulators/i386'
|
4
|
-
require 'one_gadget/fetchers/
|
4
|
+
require 'one_gadget/fetchers/x86'
|
5
5
|
|
6
6
|
module OneGadget
|
7
7
|
module Fetcher
|
8
8
|
# Fetcher for i386.
|
9
|
-
class I386 < OneGadget::Fetcher::
|
9
|
+
class I386 < OneGadget::Fetcher::X86
|
10
10
|
private
|
11
11
|
|
12
12
|
def candidates
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'one_gadget/fetchers/base'
|
2
|
+
|
3
|
+
module OneGadget
|
4
|
+
module Fetcher
|
5
|
+
# Define common methods for gadget fetchers.
|
6
|
+
class X86 < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
# If str contains a branch instruction.
|
10
|
+
def branch?(str)
|
11
|
+
%w[jmp je jne jl jb ja jg].any? { |f| str.include?(f) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def objdump_options
|
15
|
+
%w[-M intel]
|
16
|
+
end
|
17
|
+
|
18
|
+
def call_str
|
19
|
+
'call'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/one_gadget/gadget.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'one_gadget/abi'
|
2
|
+
require 'one_gadget/emulators/lambda'
|
2
3
|
require 'one_gadget/error'
|
3
4
|
|
4
5
|
module OneGadget
|
@@ -7,11 +8,11 @@ module OneGadget
|
|
7
8
|
# Information of a gadget.
|
8
9
|
class Gadget
|
9
10
|
# @return [Integer] The gadget's address offset.
|
10
|
-
|
11
|
+
attr_reader :offset
|
11
12
|
# @return [Array<String>] The constraints need for this gadget.
|
12
|
-
|
13
|
+
attr_reader :constraints
|
13
14
|
# @return [String] The final result of this gadget.
|
14
|
-
|
15
|
+
attr_reader :effect
|
15
16
|
|
16
17
|
# Initialize method of {Gadget} instance.
|
17
18
|
# @param [Integer] offset The relative address offset of this gadget.
|
@@ -28,22 +29,70 @@ module OneGadget
|
|
28
29
|
# Show gadget in a pretty way.
|
29
30
|
def inspect
|
30
31
|
str = OneGadget::Helper.hex(offset)
|
31
|
-
str += effect ? "
|
32
|
+
str += effect ? " #{effect}\n" : "\n"
|
32
33
|
unless constraints.empty?
|
33
34
|
str += "#{OneGadget::Helper.colorize('constraints')}:\n "
|
34
|
-
str +=
|
35
|
+
str += merge_constraints.join("\n ")
|
35
36
|
end
|
36
37
|
str.gsub!(/0x[\da-f]+/) { |s| OneGadget::Helper.colorize(s, sev: :integer) }
|
37
|
-
OneGadget::ABI.all.each
|
38
|
+
OneGadget::ABI.all.each do |reg|
|
39
|
+
str.gsub!(/([^\w])(#{reg})([^\w])/, '\1' + OneGadget::Helper.colorize('\2', sev: :reg) + '\3')
|
40
|
+
end
|
38
41
|
str + "\n"
|
39
42
|
end
|
43
|
+
|
44
|
+
# @return [Float]
|
45
|
+
# The success probability of the constraints.
|
46
|
+
def score
|
47
|
+
@score ||= constraints.reduce(1.0) { |s, c| s * calculate_score(c) }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# REG: OneGadget::ABI.all
|
53
|
+
# IMM: [+-]0x[\da-f]+
|
54
|
+
# Identity: <REG><IMM>?
|
55
|
+
# Identity: [<Identity>]
|
56
|
+
# Expr: <REG> is the GOT address of libc
|
57
|
+
# Expr: writable: <Identity>
|
58
|
+
# Expr: <Identity> == NULL
|
59
|
+
# Expr: <Expr> || <Expr>
|
60
|
+
def calculate_score(cons)
|
61
|
+
return cons.split(' || ').map(&method(:calculate_score)).max if cons.include?(' || ')
|
62
|
+
return 0.9 if cons.include?('GOT address')
|
63
|
+
return 0.81 if cons.include?('writable')
|
64
|
+
|
65
|
+
expr = cons.gsub(' == NULL', ' == 0')
|
66
|
+
# raise Error::ArgumentError, cons unless expr.end_with?(' == 0')
|
67
|
+
|
68
|
+
identity = expr.slice(0...expr.rindex(' == 0'))
|
69
|
+
# Thank God we are already able to parse this
|
70
|
+
lmda = OneGadget::Emulators::Lambda.parse(identity)
|
71
|
+
# raise Error::ArgumentError, cons unless OneGadget::ABI.all.include?(lmda.obj)
|
72
|
+
# rax == 0 is easy; rax + 0x10 == 0 is damn hard.
|
73
|
+
return lmda.immi.zero? ? 0.9 : 0.1 if lmda.deref_count.zero?
|
74
|
+
|
75
|
+
# [sp+xx] == NULL is easy.
|
76
|
+
base = OneGadget::ABI.stack_register?(lmda.obj) ? 0 : 1
|
77
|
+
0.9**(lmda.deref_count + base)
|
78
|
+
end
|
79
|
+
|
80
|
+
def merge_constraints
|
81
|
+
key = 'writable: '
|
82
|
+
w_cons, normal = constraints.partition { |c| c.start_with?(key) }
|
83
|
+
return normal if w_cons.empty?
|
84
|
+
|
85
|
+
w_cons.map! { |c| c[key.size..-1] }
|
86
|
+
["address#{w_cons.size > 1 ? 'es' : ''} #{w_cons.join(', ')} #{w_cons.size > 1 ? 'are' : 'is'} writable"] +
|
87
|
+
normal
|
88
|
+
end
|
40
89
|
end
|
41
90
|
|
42
91
|
# Define class methods here.
|
43
92
|
module ClassMethods
|
44
93
|
# Path to the pre-build files.
|
45
94
|
BUILDS_PATH = File.join(__dir__, 'builds').freeze
|
46
|
-
#
|
95
|
+
# Record.
|
47
96
|
BUILDS = Hash.new { |h, k| h[k] = [] }
|
48
97
|
# Get gadgets from pre-defined corpus.
|
49
98
|
# @param [String] build_id Desired build id.
|
@@ -51,8 +100,8 @@ module OneGadget
|
|
51
100
|
# When local not found, try search in latest version?
|
52
101
|
# @return [Array<Gadget::Gadget>?] Gadgets.
|
53
102
|
def builds(build_id, remote: true)
|
54
|
-
|
55
|
-
return
|
103
|
+
ret = find_build(build_id)
|
104
|
+
return ret unless ret.nil?
|
56
105
|
return build_not_found unless remote
|
57
106
|
|
58
107
|
# fetch remote builds
|
@@ -107,10 +156,13 @@ module OneGadget
|
|
107
156
|
|
108
157
|
private
|
109
158
|
|
110
|
-
def
|
111
|
-
|
159
|
+
def find_build(id)
|
160
|
+
return BUILDS[id] if BUILDS.key?(id)
|
161
|
+
|
162
|
+
Dir.glob(File.join(BUILDS_PATH, "*-#{id}.rb")).each do |dic|
|
112
163
|
require dic
|
113
164
|
end
|
165
|
+
BUILDS[id] if BUILDS.key?(id)
|
114
166
|
end
|
115
167
|
|
116
168
|
def build_not_found
|
data/lib/one_gadget/helper.rb
CHANGED
@@ -11,230 +11,309 @@ module OneGadget
|
|
11
11
|
# Define some helpful methods here.
|
12
12
|
module Helper
|
13
13
|
# Format of build-id, 40 hex numbers.
|
14
|
-
BUILD_ID_FORMAT = /[0-9a-f]{40}
|
15
|
-
# Define class methods here.
|
16
|
-
module ClassMethods
|
17
|
-
# Verify if `build_id` is a valid SHA1 hex format.
|
18
|
-
# @param [String] build_id
|
19
|
-
# BuildID.
|
20
|
-
# @raise [Error::ArgumentError]
|
21
|
-
# Raises error if invalid.
|
22
|
-
# @return [void]
|
23
|
-
def verify_build_id!(build_id)
|
24
|
-
return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/
|
25
|
-
|
26
|
-
raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id)
|
27
|
-
end
|
14
|
+
BUILD_ID_FORMAT = /[0-9a-f]{40}/.freeze
|
28
15
|
|
29
|
-
|
30
|
-
# @param [String] file
|
31
|
-
# Filename.
|
32
|
-
# @return [Array<String>]
|
33
|
-
# Lines of comments.
|
34
|
-
def comments_of_file(file)
|
35
|
-
File.readlines(file).map { |s| s[2..-1].rstrip if s.start_with?('# ') }.compact
|
36
|
-
end
|
16
|
+
module_function
|
37
17
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
18
|
+
# Checks if +build_id+ is a valid SHA1 hex format.
|
19
|
+
# @param [String] build_id
|
20
|
+
# BuildID.
|
21
|
+
# @raise [Error::ArgumentError]
|
22
|
+
# Raises error if invalid.
|
23
|
+
# @return [void]
|
24
|
+
def verify_build_id!(build_id)
|
25
|
+
return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/
|
47
26
|
|
48
|
-
|
49
|
-
|
50
|
-
# @param [String] path Path to target file.
|
51
|
-
# @return [Boolean] If the file is an ELF or not.
|
52
|
-
# @example
|
53
|
-
# valid_elf_file?('/etc/passwd')
|
54
|
-
# => false
|
55
|
-
# valid_elf_file?('/lib64/ld-linux-x86-64.so.2')
|
56
|
-
# => true
|
57
|
-
def valid_elf_file?(path)
|
58
|
-
# A light-weight way to check if is a valid ELF file
|
59
|
-
# Checks at least one phdr should present.
|
60
|
-
File.open(path) { |f| ELFTools::ELFFile.new(f).each_segments.first }
|
61
|
-
true
|
62
|
-
rescue ELFTools::ELFError
|
63
|
-
false
|
64
|
-
end
|
27
|
+
raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id)
|
28
|
+
end
|
65
29
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
30
|
+
# Fetch lines start with '#'.
|
31
|
+
# @param [String] file
|
32
|
+
# Filename.
|
33
|
+
# @return [Array<String>]
|
34
|
+
# Lines of comments.
|
35
|
+
def comments_of_file(file)
|
36
|
+
File.readlines(file).map { |s| s[2..-1].rstrip if s.start_with?('# ') }.compact
|
37
|
+
end
|
74
38
|
|
75
|
-
|
76
|
-
|
39
|
+
# Get absolute path from relative path. Support symlink.
|
40
|
+
# @param [String] path Relative path.
|
41
|
+
# @return [String] Absolute path, with symlink resolved.
|
42
|
+
# @example
|
43
|
+
# Helper.abspath('/lib/x86_64-linux-gnu/libc.so.6')
|
44
|
+
# #=> '/lib/x86_64-linux-gnu/libc-2.23.so'
|
45
|
+
def abspath(path)
|
46
|
+
Pathname.new(File.expand_path(path)).realpath.to_s
|
47
|
+
end
|
77
48
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
+
# Helper.valid_elf_file?('/etc/passwd')
|
55
|
+
# #=> false
|
56
|
+
# Helper.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
|
87
66
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
67
|
+
# Checks if the file of given path is a valid ELF file.
|
68
|
+
#
|
69
|
+
# An error message will be shown if given path is not a valid ELF.
|
70
|
+
#
|
71
|
+
# @param [String] path Path to target file.
|
72
|
+
# @return [void]
|
73
|
+
# @raise [Error::ArgumentError] Raise exception if not a valid ELF.
|
74
|
+
def verify_elf_file!(path)
|
75
|
+
return if valid_elf_file?(path)
|
93
76
|
|
94
|
-
|
95
|
-
|
96
|
-
def color_on!
|
97
|
-
@disable_color = false
|
98
|
-
end
|
77
|
+
raise Error::ArgumentError, 'Not an ELF file, expected glibc as input'
|
78
|
+
end
|
99
79
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
80
|
+
# Get the Build ID of target ELF.
|
81
|
+
# @param [String] path Absolute file path.
|
82
|
+
# @return [String] Target build id.
|
83
|
+
# @example
|
84
|
+
# Helper.build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so')
|
85
|
+
# #=> '60131540dadc6796cab33388349e6e4e68692053'
|
86
|
+
def build_id_of(path)
|
87
|
+
File.open(path) { |f| ELFTools::ELFFile.new(f).build_id }
|
88
|
+
end
|
106
89
|
|
107
|
-
|
108
|
-
|
90
|
+
# Disable colorize.
|
91
|
+
# @return [void]
|
92
|
+
def color_off!
|
93
|
+
@disable_color = true
|
94
|
+
end
|
109
95
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
warn: "\e[38;5;230m", # light yellow
|
117
|
-
error: "\e[38;5;196m" # heavy red
|
118
|
-
}.freeze
|
119
|
-
|
120
|
-
# Wrapper color codes for pretty inspect.
|
121
|
-
# @param [String] str Contents to colorize.
|
122
|
-
# @param [Symbol] sev Specific which kind of color want to use, valid symbols are defined in +COLOR_CODE+.
|
123
|
-
# @return [String] Wrapper with color codes.
|
124
|
-
def colorize(str, sev: :normal_s)
|
125
|
-
return str unless color_enabled?
|
126
|
-
|
127
|
-
cc = COLOR_CODE
|
128
|
-
color = cc.key?(sev) ? cc[sev] : ''
|
129
|
-
"#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
|
130
|
-
end
|
96
|
+
# Is colorize output enabled?
|
97
|
+
# @return [Boolean]
|
98
|
+
# True or false.
|
99
|
+
def color_enabled?
|
100
|
+
# if not set, use tty to check
|
101
|
+
return $stdout.tty? unless instance_variable_defined?(:@disable_color)
|
131
102
|
|
132
|
-
|
133
|
-
|
134
|
-
def latest_tag
|
135
|
-
releases_url = 'https://github.com/david942j/one_gadget/releases/latest'
|
136
|
-
@latest_tag ||= url_request(releases_url).split('/').last
|
137
|
-
end
|
103
|
+
!@disable_color
|
104
|
+
end
|
138
105
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
106
|
+
# Color codes for pretty print
|
107
|
+
COLOR_CODE = {
|
108
|
+
esc_m: "\e[0m",
|
109
|
+
normal_s: "\e[38;5;203m", # red
|
110
|
+
integer: "\e[38;5;189m", # light purple
|
111
|
+
reg: "\e[38;5;82m", # light green
|
112
|
+
warn: "\e[38;5;230m", # light yellow
|
113
|
+
error: "\e[38;5;196m" # heavy red
|
114
|
+
}.freeze
|
146
115
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb')))
|
154
|
-
temp.write(url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))))
|
155
|
-
temp.tap(&:close)
|
156
|
-
end
|
116
|
+
# Wrap string with color codes for pretty inspect.
|
117
|
+
# @param [String] str Contents to colorize.
|
118
|
+
# @param [Symbol] sev Specify which kind of color to use, valid symbols are defined in {.COLOR_CODE}.
|
119
|
+
# @return [String] String wrapped with color codes.
|
120
|
+
def colorize(str, sev: :normal_s)
|
121
|
+
return str unless color_enabled?
|
157
122
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
123
|
+
cc = COLOR_CODE
|
124
|
+
color = cc.key?(sev) ? cc[sev] : ''
|
125
|
+
"#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
|
126
|
+
end
|
163
127
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
uri = URI.parse(url)
|
171
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
172
|
-
http.use_ssl = true
|
173
|
-
http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
|
174
|
-
|
175
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
176
|
-
|
177
|
-
response = http.request(request)
|
178
|
-
raise ArgumentError, "Fail to get response of #{url}" unless %w(200 302).include?(response.code)
|
179
|
-
|
180
|
-
response.code == '302' ? response['location'] : response.body
|
181
|
-
rescue NoMethodError, SocketError, ArgumentError => e
|
182
|
-
p e
|
183
|
-
nil
|
184
|
-
end
|
128
|
+
# Fetch the latest release version's tag name.
|
129
|
+
# @return [String] The tag name, in form +vX.X.X+.
|
130
|
+
def latest_tag
|
131
|
+
releases_url = 'https://github.com/david942j/one_gadget/releases/latest'
|
132
|
+
@latest_tag ||= url_request(releases_url).split('/').last
|
133
|
+
end
|
185
134
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
{
|
194
|
-
'Advanced Micro Devices X86-64' => :amd64,
|
195
|
-
'Intel 80386' => :i386,
|
196
|
-
'ARM' => :arm,
|
197
|
-
'AArch64' => :aarch64,
|
198
|
-
'MIPS R3000' => :mips
|
199
|
-
}[str] || :unknown
|
200
|
-
rescue ELFTools::ELFError # not a valid ELF
|
201
|
-
:invalid
|
202
|
-
ensure
|
203
|
-
f.close
|
204
|
-
end
|
135
|
+
# Get the url which can fetch +filename+ from remote repo.
|
136
|
+
# @param [String] filename
|
137
|
+
# @return [String] The url.
|
138
|
+
def url_of_file(filename)
|
139
|
+
raw_file_url = 'https://raw.githubusercontent.com/david942j/one_gadget/@tag/@file'
|
140
|
+
raw_file_url.sub('@tag', latest_tag).sub('@file', filename)
|
141
|
+
end
|
205
142
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
143
|
+
# Download the latest version of +file+ in +lib/one_gadget/builds/+ from remote repo.
|
144
|
+
#
|
145
|
+
# @param [String] file The filename desired.
|
146
|
+
# @return [Tempfile] The temp file be created.
|
147
|
+
def download_build(file)
|
148
|
+
temp = Tempfile.new(['gadgets', file + '.rb'])
|
149
|
+
temp.write(url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))))
|
150
|
+
temp.tap(&:close)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get the latest builds list from repo.
|
154
|
+
# @return [Array<String>] List of build ids.
|
155
|
+
def remote_builds
|
156
|
+
@remote_builds ||= url_request(url_of_file('builds_list')).lines.map(&:strip)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get request.
|
160
|
+
# @param [String] url The url.
|
161
|
+
# @return [String]
|
162
|
+
# The request response body.
|
163
|
+
# If the response is +302 Found+, returns the location in header.
|
164
|
+
def url_request(url)
|
165
|
+
uri = URI.parse(url)
|
166
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
167
|
+
http.use_ssl = true
|
168
|
+
http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
|
169
|
+
|
170
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
171
|
+
|
172
|
+
response = http.request(request)
|
173
|
+
raise ArgumentError, "Fail to get response of #{url}" unless %w[200 302].include?(response.code)
|
174
|
+
|
175
|
+
response.code == '302' ? response['location'] : response.body
|
176
|
+
rescue NoMethodError, SocketError, ArgumentError => e
|
177
|
+
OneGadget::Logger.error(e.message)
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
# Fetch the ELF archiecture of +file+.
|
182
|
+
# @param [String] file The target ELF filename.
|
183
|
+
# @return [Symbol]
|
184
|
+
# Currently supports amd64, i386, arm, aarch64, and mips.
|
185
|
+
# @example
|
186
|
+
# Helper.architecture('/bin/cat')
|
187
|
+
# #=> :amd64
|
188
|
+
def architecture(file)
|
189
|
+
return :invalid unless File.exist?(file)
|
190
|
+
|
191
|
+
f = File.open(file)
|
192
|
+
str = ELFTools::ELFFile.new(f).machine
|
193
|
+
{
|
194
|
+
'Advanced Micro Devices X86-64' => :amd64,
|
195
|
+
'Intel 80386' => :i386,
|
196
|
+
'ARM' => :arm,
|
197
|
+
'AArch64' => :aarch64,
|
198
|
+
'MIPS R3000' => :mips
|
199
|
+
}[str] || :unknown
|
200
|
+
rescue ELFTools::ELFError # not a valid ELF
|
201
|
+
:invalid
|
202
|
+
ensure
|
203
|
+
f.close if f
|
204
|
+
end
|
205
|
+
|
206
|
+
# Present number in hex format.
|
207
|
+
# @param [Integer] val
|
208
|
+
# The number.
|
209
|
+
# @param [Boolean] psign
|
210
|
+
# If needs to show the plus sign when +val >= 0+.
|
211
|
+
# @return [String]
|
212
|
+
# String in hex format.
|
213
|
+
# @example
|
214
|
+
# Helper.hex(32) #=> '0x20'
|
215
|
+
# Helper.hex(32, psign: true) #=> '+0x20'
|
216
|
+
# Helper.hex(-40) #=> '-0x28'
|
217
|
+
# Helper.hex(0) #=> '0x0'
|
218
|
+
# Helper.hex(0, psign: true) #=> '+0x0'
|
219
|
+
def hex(val, psign: false)
|
220
|
+
return format("#{psign ? '+' : ''}0x%x", val) if val >= 0
|
221
|
+
|
222
|
+
format('-0x%x', -val)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Checks if a string can be converted into an integer.
|
226
|
+
# @param [String] str
|
227
|
+
# String to be checked.
|
228
|
+
# @return [Boolean]
|
229
|
+
# If +str+ can be converted into an integer.
|
230
|
+
# @example
|
231
|
+
# Helper.integer? '1234'
|
232
|
+
# #=> true
|
233
|
+
# Helper.integer? '0x1234'
|
234
|
+
# #=> true
|
235
|
+
# Helper.integer? '0xheapoverflow'
|
236
|
+
# #=> false
|
237
|
+
def integer?(str)
|
238
|
+
true if Integer(str)
|
239
|
+
rescue ArgumentError, TypeError
|
240
|
+
false
|
241
|
+
end
|
242
|
+
|
243
|
+
# Cross-platform way of finding an executable in +$PATH+.
|
244
|
+
#
|
245
|
+
# @param [String] cmd
|
246
|
+
# @return [String?]
|
247
|
+
# @example
|
248
|
+
# Helper.which('ruby')
|
249
|
+
# #=> "/usr/bin/ruby"
|
250
|
+
def which(cmd)
|
251
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
252
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
253
|
+
exts.each do |ext|
|
254
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
255
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
256
|
+
end
|
220
257
|
end
|
258
|
+
nil
|
259
|
+
end
|
260
|
+
|
261
|
+
# Find objdump that supports architecture +arch+.
|
262
|
+
# @param [String] arch
|
263
|
+
# @return [String?]
|
264
|
+
# @example
|
265
|
+
# Helper.find_objdump(:amd64)
|
266
|
+
# #=> '/usr/bin/objdump'
|
267
|
+
# Helper.find_objdump(:aarch64)
|
268
|
+
# #=> '/usr/bin/aarch64-linux-gnu-objdump'
|
269
|
+
def find_objdump(arch)
|
270
|
+
[
|
271
|
+
which('objdump'),
|
272
|
+
which(arch_specific_objdump(arch))
|
273
|
+
].find { |bin| objdump_arch_supported?(bin, arch) }
|
274
|
+
end
|
221
275
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
276
|
+
# Checks if the given objdump supports certain architecture.
|
277
|
+
# @param [String] bin
|
278
|
+
# @param [Symbol] arch
|
279
|
+
# @return [Boolean]
|
280
|
+
# @example
|
281
|
+
# Helper.objdump_arch_supported?('/usr/bin/objdump', :i386)
|
282
|
+
# #=> true
|
283
|
+
def objdump_arch_supported?(bin, arch)
|
284
|
+
return false if bin.nil?
|
285
|
+
|
286
|
+
arch = objdump_arch(arch)
|
287
|
+
archs = `#{::Shellwords.join([bin, '--help'])}`.lines.find { |c| c.include?('supported architectures') }
|
288
|
+
return false if archs.nil?
|
289
|
+
|
290
|
+
archs.split.include?(arch)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Converts to the architecture name shown in objdump's +--help+ command.
|
294
|
+
# @param [Symbol] arch
|
295
|
+
# @return [String]
|
296
|
+
# @example
|
297
|
+
# Helper.objdump_arch(:i386)
|
298
|
+
# #=> 'i386'
|
299
|
+
# Helper.objdump_arch(:amd64)
|
300
|
+
# #=> 'i386:x86-64'
|
301
|
+
def objdump_arch(arch)
|
302
|
+
case arch
|
303
|
+
when :amd64 then 'i386:x86-64'
|
304
|
+
else arch.to_s
|
236
305
|
end
|
237
306
|
end
|
238
|
-
|
307
|
+
|
308
|
+
# Returns the binary name of objdump.
|
309
|
+
# @param [Symbol] arch
|
310
|
+
# @return [String]
|
311
|
+
def arch_specific_objdump(arch)
|
312
|
+
{
|
313
|
+
aarch64: 'aarch64-linux-gnu-objdump',
|
314
|
+
amd64: 'x86_64-linux-gnu-objdump',
|
315
|
+
i386: 'i686-linux-gnu-objdump'
|
316
|
+
}[arch]
|
317
|
+
end
|
239
318
|
end
|
240
319
|
end
|