one_gadget 1.5.0 → 1.6.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 +5 -4
- data/bin/one_gadget +11 -0
- data/lib/one_gadget/abi.rb +5 -2
- data/lib/one_gadget/builds/libc-2.26-ddcc13122ddbfe5e5ef77d4ebe66d124ae5762c2.rb +49 -0
- data/lib/one_gadget/builds/libc-2.26-f65648a832414f2144ce795d75b6045a1ec2e252.rb +52 -0
- data/lib/one_gadget/builds/libc-2.27-63b3d43ad45e1b0f601848c65b067f9e9b40528b.rb +47 -0
- data/lib/one_gadget/builds/libc-2.27-b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0.rb +41 -0
- data/lib/one_gadget/emulators/instruction.rb +2 -2
- data/lib/one_gadget/emulators/lambda.rb +5 -5
- data/lib/one_gadget/emulators/processor.rb +8 -2
- data/lib/one_gadget/emulators/x86.rb +71 -16
- data/lib/one_gadget/error.rb +9 -0
- data/lib/one_gadget/fetcher.rb +1 -3
- data/lib/one_gadget/gadget.rb +27 -0
- data/lib/one_gadget/helper.rb +30 -10
- data/lib/one_gadget/logger.rb +2 -2
- data/lib/one_gadget/update.rb +2 -2
- data/lib/one_gadget/version.rb +1 -1
- metadata +16 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d381a734bccb70a6ffaac1b6e2ff82518adf2719
|
4
|
+
data.tar.gz: 406db08cea4fc116b4d81f61d585b7a25ec9ecd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 327cb7edce0f03ac5d43807d91c9850f63869e9041d59ed236b467fd6ca97aa4aaaa68361525daa1273259b7df96a00e69193ef1ddd3b4856126a61afb3148ad
|
7
|
+
data.tar.gz: 59b561675611597e011b5012574986138e041421eaecd8819ca40cdecc8f68056a2f42077eb7ad2f25f71ce2fd884b53aba9ea823791d250e18703d8e7228a3b
|
data/README.md
CHANGED
@@ -28,9 +28,9 @@ Note: require ruby version >= 2.1.0, you can use `ruby --version` to check.
|
|
28
28
|
|
29
29
|
## Implementation
|
30
30
|
|
31
|
-
OneGadget uses simple self-implement symbolic execution to find the constraints of gadgets to
|
31
|
+
OneGadget uses simple self-implement symbolic execution to find the constraints of gadgets to be successful.
|
32
32
|
|
33
|
-
The article introducing how I develop this tool can be found [
|
33
|
+
The article introducing how I develop this tool can be found [in my blog](https://david942j.blogspot.com/2017/02/project-one-gadget-in-glibc.html).
|
34
34
|
|
35
35
|
## Usage
|
36
36
|
|
@@ -39,7 +39,7 @@ much more one-gadgets have been found.
|
|
39
39
|
And gadgets become too many to show them all,
|
40
40
|
they would be selected automatically according to the difficulty of constraints.
|
41
41
|
Therefore, gadgets shown will be less than previous versions (before v1.5.0).
|
42
|
-
|
42
|
+
But you can use option `--level 1` to show all gadgets found.
|
43
43
|
|
44
44
|
### Command Line Interface
|
45
45
|
|
@@ -55,6 +55,7 @@ $ one_gadget
|
|
55
55
|
# -r, --[no-]raw Output gadgets offset only, split with one space.
|
56
56
|
# -s, --script exploit-script Run exploit script with all possible gadgets.
|
57
57
|
# The script will be run as 'exploit-script $offset'.
|
58
|
+
# --info BuildID Show version information given BuildID.
|
58
59
|
# --version Current gem version.
|
59
60
|
|
60
61
|
$ one_gadget -b 60131540dadc6796cab33388349e6e4e68692053
|
@@ -210,4 +211,4 @@ one_gadget('60131540dadc6796cab33388349e6e4e68692053')
|
|
210
211
|
Any suggestion or feature request is welcome! Feel free to send a pull request.
|
211
212
|
|
212
213
|
Please let me know if you find any libc that make OneGadget fail to find gadgets.
|
213
|
-
And, if you like this work, I'll be happy to be [
|
214
|
+
And, if you like this work, I'll be happy to be [starred](https://github.com/david942j/one_gadget/stargazers) :grimacing:
|
data/bin/one_gadget
CHANGED
@@ -31,6 +31,10 @@ parser = OptionParser.new do |opts|
|
|
31
31
|
options[:script] = script
|
32
32
|
end
|
33
33
|
|
34
|
+
opts.on('--info BuildID', 'Show version information given BuildID.') do |b|
|
35
|
+
options[:info] = b
|
36
|
+
end
|
37
|
+
|
34
38
|
opts.on('--version', 'Current gem version.') do |v|
|
35
39
|
options[:version] = v
|
36
40
|
end
|
@@ -49,6 +53,13 @@ def execute(script, offset)
|
|
49
53
|
Process.wait pid
|
50
54
|
end
|
51
55
|
|
56
|
+
if options[:info]
|
57
|
+
result = OneGadget::Gadget.builds_info(options[:info])
|
58
|
+
exit(1) if result.nil? # error happend
|
59
|
+
OneGadget::Logger.info("Information of #{options[:info]}:\n#{result.join("\n")}\n")
|
60
|
+
exit(0)
|
61
|
+
end
|
62
|
+
|
52
63
|
level = options[:level] || 0
|
53
64
|
if options[:build_id]
|
54
65
|
gadgets = OneGadget.gadgets(build_id: options[:build_id], details: true, level: level)
|
data/lib/one_gadget/abi.rb
CHANGED
@@ -4,9 +4,12 @@ module OneGadget
|
|
4
4
|
# Define class methods here.
|
5
5
|
module ClassMethods
|
6
6
|
# Registers in i386.
|
7
|
-
LINUX_X86_32 = %w(eax ebx ecx edx edi esi ebp esp).
|
7
|
+
LINUX_X86_32 = %w(eax ebx ecx edx edi esi ebp esp) + 0.upto(7).map { |i| "xmm#{i}" }
|
8
8
|
# Registers in x86_64/
|
9
|
-
LINUX_X86_64 = LINUX_X86_32 +
|
9
|
+
LINUX_X86_64 = LINUX_X86_32 +
|
10
|
+
%w(rax rbx rcx rdx rdi rsi rbp rsp) +
|
11
|
+
8.upto(15).map { |i| "r#{i}" } +
|
12
|
+
8.upto(15).map { |i| "xmm#{i}" }
|
10
13
|
# Registers' name in amd64.
|
11
14
|
# @return [Array<String>] List of registers.
|
12
15
|
def amd64
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'one_gadget/gadget'
|
2
|
+
# ubuntu17.10-libc-2.26.so
|
3
|
+
#
|
4
|
+
# Advanced Micro Devices X86-64
|
5
|
+
#
|
6
|
+
# GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2.1) stable release version 2.26, by Roland McGrath et al.
|
7
|
+
# Copyright (C) 2017 Free Software Foundation, Inc.
|
8
|
+
# This is free software; see the source for copying conditions.
|
9
|
+
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
10
|
+
# PARTICULAR PURPOSE.
|
11
|
+
# Compiled by GNU CC version 6.4.0 20171010.
|
12
|
+
# Available extensions:
|
13
|
+
# crypt add-on version 2.1 by Michael Glad and others
|
14
|
+
# GNU Libidn by Simon Josefsson
|
15
|
+
# Native POSIX Threads Library by Ulrich Drepper et al
|
16
|
+
# BIND-8.2.3-T5B
|
17
|
+
# libc ABIs: UNIQUE IFUNC
|
18
|
+
# For bug reporting instructions, please see:
|
19
|
+
# <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
|
20
|
+
|
21
|
+
build_id = File.basename(__FILE__, '.rb').split('-').last
|
22
|
+
OneGadget::Gadget.add(build_id, 293958,
|
23
|
+
constraints: ["rax == NULL"],
|
24
|
+
effect: "execve(\"/bin/sh\", rsp+0x30, environ)")
|
25
|
+
OneGadget::Gadget.add(build_id, 294042,
|
26
|
+
constraints: ["[rsp+0x30] == NULL"],
|
27
|
+
effect: "execve(\"/bin/sh\", rsp+0x30, environ)")
|
28
|
+
OneGadget::Gadget.add(build_id, 890723,
|
29
|
+
constraints: ["[r13] == NULL || r13 == NULL", "[rbx] == NULL || rbx == NULL"],
|
30
|
+
effect: "execve(\"/bin/sh\", r13, rbx)")
|
31
|
+
OneGadget::Gadget.add(build_id, 891441,
|
32
|
+
constraints: ["[[rbp-0xa0]] == NULL || [rbp-0xa0] == NULL", "[[rbp-0x70]] == NULL || [rbp-0x70] == NULL"],
|
33
|
+
effect: "execve(\"/bin/sh\", [rbp-0xa0], [rbp-0x70])")
|
34
|
+
OneGadget::Gadget.add(build_id, 891448,
|
35
|
+
constraints: ["[rcx] == NULL || rcx == NULL", "[[rbp-0x70]] == NULL || [rbp-0x70] == NULL"],
|
36
|
+
effect: "execve(\"/bin/sh\", rcx, [rbp-0x70])")
|
37
|
+
OneGadget::Gadget.add(build_id, 891452,
|
38
|
+
constraints: ["[rcx] == NULL || rcx == NULL", "[rdx] == NULL || rdx == NULL"],
|
39
|
+
effect: "execve(\"/bin/sh\", rcx, rdx)")
|
40
|
+
OneGadget::Gadget.add(build_id, 1035486,
|
41
|
+
constraints: ["[rsp+0x40] == NULL"],
|
42
|
+
effect: "execve(\"/bin/sh\", rsp+0x40, environ)")
|
43
|
+
OneGadget::Gadget.add(build_id, 1035498,
|
44
|
+
constraints: ["[rsi] == NULL || rsi == NULL", "[[rax]] == NULL || [rax] == NULL"],
|
45
|
+
effect: "execve(\"/bin/sh\", rsi, [rax])")
|
46
|
+
OneGadget::Gadget.add(build_id, 1039246,
|
47
|
+
constraints: ["[rsp+0x70] == NULL"],
|
48
|
+
effect: "execve(\"/bin/sh\", rsp+0x70, environ)")
|
49
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'one_gadget/gadget'
|
2
|
+
# ubuntu17.10-libc-2.26.so
|
3
|
+
#
|
4
|
+
# Intel 80386
|
5
|
+
#
|
6
|
+
# GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2.1) stable release version 2.26, by Roland McGrath et al.
|
7
|
+
# Copyright (C) 2017 Free Software Foundation, Inc.
|
8
|
+
# This is free software; see the source for copying conditions.
|
9
|
+
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
10
|
+
# PARTICULAR PURPOSE.
|
11
|
+
# Compiled by GNU CC version 6.4.0 20171010.
|
12
|
+
# Available extensions:
|
13
|
+
# crypt add-on version 2.1 by Michael Glad and others
|
14
|
+
# GNU Libidn by Simon Josefsson
|
15
|
+
# Native POSIX Threads Library by Ulrich Drepper et al
|
16
|
+
# BIND-8.2.3-T5B
|
17
|
+
# libc ABIs: UNIQUE IFUNC
|
18
|
+
# For bug reporting instructions, please see:
|
19
|
+
# <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
|
20
|
+
|
21
|
+
build_id = File.basename(__FILE__, '.rb').split('-').last
|
22
|
+
OneGadget::Gadget.add(build_id, 248879,
|
23
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x34] == NULL"],
|
24
|
+
effect: "execve(\"/bin/sh\", esp+0x34, environ)")
|
25
|
+
OneGadget::Gadget.add(build_id, 248881,
|
26
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x38] == NULL"],
|
27
|
+
effect: "execve(\"/bin/sh\", esp+0x38, environ)")
|
28
|
+
OneGadget::Gadget.add(build_id, 248885,
|
29
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x3c] == NULL"],
|
30
|
+
effect: "execve(\"/bin/sh\", esp+0x3c, environ)")
|
31
|
+
OneGadget::Gadget.add(build_id, 248892,
|
32
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x40] == NULL"],
|
33
|
+
effect: "execve(\"/bin/sh\", esp+0x40, environ)")
|
34
|
+
OneGadget::Gadget.add(build_id, 248927,
|
35
|
+
constraints: ["esi is the GOT address of libc", "[eax] == NULL || eax == NULL", "[[esp]] == NULL || [esp] == NULL"],
|
36
|
+
effect: "execve(\"/bin/sh\", eax, [esp])")
|
37
|
+
OneGadget::Gadget.add(build_id, 248928,
|
38
|
+
constraints: ["esi is the GOT address of libc", "[[esp]] == NULL || [esp] == NULL", "[[esp+0x4]] == NULL || [esp+0x4] == NULL"],
|
39
|
+
effect: "execve(\"/bin/sh\", [esp], [esp+0x4])")
|
40
|
+
OneGadget::Gadget.add(build_id, 421503,
|
41
|
+
constraints: ["edi is the GOT address of libc", "eax == NULL"],
|
42
|
+
effect: "execl(\"/bin/sh\", eax)")
|
43
|
+
OneGadget::Gadget.add(build_id, 421504,
|
44
|
+
constraints: ["edi is the GOT address of libc", "[esp] == NULL"],
|
45
|
+
effect: "execl(\"/bin/sh\", [esp])")
|
46
|
+
OneGadget::Gadget.add(build_id, 1257406,
|
47
|
+
constraints: ["esi is the GOT address of libc", "eax == NULL"],
|
48
|
+
effect: "execl(\"/bin/sh\", eax)")
|
49
|
+
OneGadget::Gadget.add(build_id, 1257407,
|
50
|
+
constraints: ["esi is the GOT address of libc", "[esp] == NULL"],
|
51
|
+
effect: "execl(\"/bin/sh\", [esp])")
|
52
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'one_gadget/gadget'
|
2
|
+
# spec/data/libc-2.27-63b3d43ad45e1b0f601848c65b067f9e9b40528b.so
|
3
|
+
#
|
4
|
+
# Intel 80386
|
5
|
+
#
|
6
|
+
# GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
|
7
|
+
# Copyright (C) 2018 Free Software Foundation, Inc.
|
8
|
+
# This is free software; see the source for copying conditions.
|
9
|
+
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
10
|
+
# PARTICULAR PURPOSE.
|
11
|
+
# Compiled by GNU CC version 7.3.0.
|
12
|
+
# libc ABIs: UNIQUE IFUNC
|
13
|
+
# For bug reporting instructions, please see:
|
14
|
+
# <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
|
15
|
+
|
16
|
+
build_id = File.basename(__FILE__, '.rb').split('-').last
|
17
|
+
OneGadget::Gadget.add(build_id, 248810,
|
18
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x34] == NULL"],
|
19
|
+
effect: "execve(\"/bin/sh\", esp+0x34, environ)")
|
20
|
+
OneGadget::Gadget.add(build_id, 248812,
|
21
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x38] == NULL"],
|
22
|
+
effect: "execve(\"/bin/sh\", esp+0x38, environ)")
|
23
|
+
OneGadget::Gadget.add(build_id, 248816,
|
24
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x3c] == NULL"],
|
25
|
+
effect: "execve(\"/bin/sh\", esp+0x3c, environ)")
|
26
|
+
OneGadget::Gadget.add(build_id, 248823,
|
27
|
+
constraints: ["esi is the GOT address of libc", "[esp+0x40] == NULL"],
|
28
|
+
effect: "execve(\"/bin/sh\", esp+0x40, environ)")
|
29
|
+
OneGadget::Gadget.add(build_id, 248858,
|
30
|
+
constraints: ["esi is the GOT address of libc", "[eax] == NULL || eax == NULL", "[[esp]] == NULL || [esp] == NULL"],
|
31
|
+
effect: "execve(\"/bin/sh\", eax, [esp])")
|
32
|
+
OneGadget::Gadget.add(build_id, 248859,
|
33
|
+
constraints: ["esi is the GOT address of libc", "[[esp]] == NULL || [esp] == NULL", "[[esp+0x4]] == NULL || [esp+0x4] == NULL"],
|
34
|
+
effect: "execve(\"/bin/sh\", [esp], [esp+0x4])")
|
35
|
+
OneGadget::Gadget.add(build_id, 422559,
|
36
|
+
constraints: ["esi is the GOT address of libc", "eax == NULL"],
|
37
|
+
effect: "execl(\"/bin/sh\", eax)")
|
38
|
+
OneGadget::Gadget.add(build_id, 422560,
|
39
|
+
constraints: ["esi is the GOT address of libc", "[esp] == NULL"],
|
40
|
+
effect: "execl(\"/bin/sh\", [esp])")
|
41
|
+
OneGadget::Gadget.add(build_id, 1267518,
|
42
|
+
constraints: ["ebx is the GOT address of libc", "eax == NULL"],
|
43
|
+
effect: "execl(\"/bin/sh\", eax)")
|
44
|
+
OneGadget::Gadget.add(build_id, 1267519,
|
45
|
+
constraints: ["ebx is the GOT address of libc", "[esp] == NULL"],
|
46
|
+
effect: "execl(\"/bin/sh\", [esp])")
|
47
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'one_gadget/gadget'
|
2
|
+
# spec/data/libc-2.27-b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0.so
|
3
|
+
#
|
4
|
+
# Advanced Micro Devices X86-64
|
5
|
+
#
|
6
|
+
# GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
|
7
|
+
# Copyright (C) 2018 Free Software Foundation, Inc.
|
8
|
+
# This is free software; see the source for copying conditions.
|
9
|
+
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
10
|
+
# PARTICULAR PURPOSE.
|
11
|
+
# Compiled by GNU CC version 7.3.0.
|
12
|
+
# libc ABIs: UNIQUE IFUNC
|
13
|
+
# For bug reporting instructions, please see:
|
14
|
+
# <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
|
15
|
+
|
16
|
+
build_id = File.basename(__FILE__, '.rb').split('-').last
|
17
|
+
OneGadget::Gadget.add(build_id, 324293,
|
18
|
+
constraints: ["rcx == NULL"],
|
19
|
+
effect: "execve(\"/bin/sh\", rsp+0x40, environ)")
|
20
|
+
OneGadget::Gadget.add(build_id, 324386,
|
21
|
+
constraints: ["[rsp+0x40] == NULL"],
|
22
|
+
effect: "execve(\"/bin/sh\", rsp+0x40, environ)")
|
23
|
+
OneGadget::Gadget.add(build_id, 939679,
|
24
|
+
constraints: ["[r14] == NULL || r14 == NULL", "[r12] == NULL || r12 == NULL"],
|
25
|
+
effect: "execve(\"/bin/sh\", r14, r12)")
|
26
|
+
OneGadget::Gadget.add(build_id, 940120,
|
27
|
+
constraints: ["[[rbp-0x88]] == NULL || [rbp-0x88] == NULL", "[[rbp-0x70]] == NULL || [rbp-0x70] == NULL"],
|
28
|
+
effect: "execve(\"/bin/sh\", [rbp-0x88], [rbp-0x70])")
|
29
|
+
OneGadget::Gadget.add(build_id, 940127,
|
30
|
+
constraints: ["[r10] == NULL || r10 == NULL", "[[rbp-0x70]] == NULL || [rbp-0x70] == NULL"],
|
31
|
+
effect: "execve(\"/bin/sh\", r10, [rbp-0x70])")
|
32
|
+
OneGadget::Gadget.add(build_id, 940131,
|
33
|
+
constraints: ["[r10] == NULL || r10 == NULL", "[rdx] == NULL || rdx == NULL"],
|
34
|
+
effect: "execve(\"/bin/sh\", r10, rdx)")
|
35
|
+
OneGadget::Gadget.add(build_id, 1090444,
|
36
|
+
constraints: ["[rsp+0x70] == NULL"],
|
37
|
+
effect: "execve(\"/bin/sh\", rsp+0x70, environ)")
|
38
|
+
OneGadget::Gadget.add(build_id, 1090456,
|
39
|
+
constraints: ["[rsi] == NULL || rsi == NULL", "[[rax]] == NULL || [rax] == NULL"],
|
40
|
+
effect: "execve(\"/bin/sh\", rsi, [rax])")
|
41
|
+
|
@@ -4,7 +4,7 @@ module OneGadget
|
|
4
4
|
class Instruction
|
5
5
|
attr_reader :inst # @return [String] The instruction name.
|
6
6
|
attr_reader :argc # @return [Integer] Count of arguments.
|
7
|
-
# Instantiate a
|
7
|
+
# Instantiate a {Instruction} object.
|
8
8
|
# @param [String] inst The instruction name.
|
9
9
|
# @param [Integer] argc
|
10
10
|
# Count of arguments.
|
@@ -23,7 +23,7 @@ module OneGadget
|
|
23
23
|
args = cmd[idx + inst.size..-1].split(',')
|
24
24
|
raise ArgumentError, "Incorrect argument number in #{cmd}, expect: #{argc}" if argc >= 0 && args.size != argc
|
25
25
|
args.map do |arg|
|
26
|
-
arg.gsub(/QWORD|DWORD|WORD|BYTE|PTR/, '').strip
|
26
|
+
arg.gsub(/XMMWORD|QWORD|DWORD|WORD|BYTE|PTR/, '').strip
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -95,9 +95,9 @@ module OneGadget
|
|
95
95
|
# If +arg+ contains number only, return it.
|
96
96
|
# Otherwise, return a {Lambda} object.
|
97
97
|
# @example
|
98
|
-
# obj = parse('[rsp+0x50]')
|
98
|
+
# obj = Lambda.parse('[rsp+0x50]')
|
99
99
|
# #=> #<Lambda @obj='rsp', @immi=80, @deref_count=1>
|
100
|
-
# parse('obj+0x30', predefined: { 'obj' => obj }).to_s
|
100
|
+
# Lambda.parse('obj+0x30', predefined: { 'obj' => obj }).to_s
|
101
101
|
# #=> '[rsp+0x50]+0x30'
|
102
102
|
def parse(arg, predefined: {})
|
103
103
|
deref_count = 0
|
@@ -110,10 +110,10 @@ module OneGadget
|
|
110
110
|
val = 0
|
111
111
|
if sign
|
112
112
|
raise ArgumentError, "Not support #{arg}" unless OneGadget::Helper.integer?(arg[sign..-1])
|
113
|
-
val = Integer(arg
|
114
|
-
arg = arg[0, sign]
|
113
|
+
val = Integer(arg.slice!(sign..-1))
|
115
114
|
end
|
116
|
-
obj =
|
115
|
+
obj = predefined[arg] || Lambda.new(arg)
|
116
|
+
obj += val unless val.zero?
|
117
117
|
deref_count.zero? ? obj : obj.deref
|
118
118
|
end
|
119
119
|
end
|
@@ -10,13 +10,13 @@ module OneGadget
|
|
10
10
|
# Instantiate a {Processor} object.
|
11
11
|
# @param [Array<String>] registers Registers that supported in the architecture.
|
12
12
|
def initialize(registers)
|
13
|
-
@registers = registers.map { |reg| [reg,
|
13
|
+
@registers = registers.map { |reg| [reg, to_lambda(reg)] }.to_h
|
14
14
|
@stack = {}
|
15
15
|
end
|
16
16
|
|
17
17
|
# Parse one command into instruction and arguments.
|
18
18
|
# @param [String] cmd One line of result of objdump.
|
19
|
-
# @return [
|
19
|
+
# @return [(Instruction, Array<String>)]
|
20
20
|
# The parsing result.
|
21
21
|
def parse(cmd)
|
22
22
|
inst = instructions.find { |i| i.match?(cmd) }
|
@@ -33,6 +33,12 @@ module OneGadget
|
|
33
33
|
# @return [Array<Instruction>] The support instructions.
|
34
34
|
def instructions; raise NotImplementedError
|
35
35
|
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def to_lambda(reg)
|
40
|
+
OneGadget::Emulators::Lambda.new(reg)
|
41
|
+
end
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
-
require 'one_gadget/emulators/processor'
|
2
1
|
require 'one_gadget/emulators/instruction'
|
2
|
+
require 'one_gadget/emulators/lambda'
|
3
|
+
require 'one_gadget/emulators/processor'
|
4
|
+
require 'one_gadget/error'
|
3
5
|
|
4
6
|
module OneGadget
|
5
7
|
module Emulators
|
@@ -13,10 +15,10 @@ module OneGadget
|
|
13
15
|
@sp = sp
|
14
16
|
@pc = pc
|
15
17
|
@stack = Hash.new do |h, k|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
h[k] = OneGadget::Emulators::Lambda.new(sp).tap do |lmda|
|
19
|
+
lmda.immi = k
|
20
|
+
lmda.deref!
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
@@ -24,22 +26,22 @@ module OneGadget
|
|
24
26
|
# Will raise exceptions when encounter unhandled instruction.
|
25
27
|
# @param [String] cmd
|
26
28
|
# One line from result of objdump.
|
27
|
-
# @return [
|
29
|
+
# @return [Boolean]
|
28
30
|
def process!(cmd)
|
29
31
|
inst, args = parse(cmd)
|
30
32
|
# return registers[pc] = args[0] if inst.inst == 'call'
|
31
33
|
return true if inst.inst == 'jmp' # believe the fetcher has handled jmp.
|
32
34
|
sym = "inst_#{inst.inst}".to_sym
|
33
|
-
|
35
|
+
__send__(sym, *args) != :fail
|
34
36
|
end
|
35
37
|
|
36
38
|
# Process one command, without raising any exceptions.
|
37
39
|
# @param [String] cmd
|
38
40
|
# See {#process!} for more information.
|
39
|
-
# @return [
|
41
|
+
# @return [Boolean]
|
40
42
|
def process(cmd)
|
41
43
|
process!(cmd)
|
42
|
-
rescue ArgumentError
|
44
|
+
rescue ArgumentError, OneGadget::Error::Error
|
43
45
|
false
|
44
46
|
end
|
45
47
|
|
@@ -55,7 +57,10 @@ module OneGadget
|
|
55
57
|
Instruction.new('nop', -1),
|
56
58
|
Instruction.new('push', 1),
|
57
59
|
Instruction.new('sub', 2),
|
58
|
-
Instruction.new('xor', 2)
|
60
|
+
Instruction.new('xor', 2),
|
61
|
+
Instruction.new('movq', 2),
|
62
|
+
Instruction.new('movaps', 2),
|
63
|
+
Instruction.new('movhps', 2)
|
59
64
|
]
|
60
65
|
end
|
61
66
|
|
@@ -83,12 +88,53 @@ module OneGadget
|
|
83
88
|
# Just ignore strange case...
|
84
89
|
return unless tar.include?(sp)
|
85
90
|
tar = OneGadget::Emulators::Lambda.parse(tar, predefined: registers)
|
86
|
-
return if tar.deref_count != 1 # should not
|
91
|
+
return if tar.deref_count != 1 # should not happen
|
87
92
|
tar.ref!
|
88
93
|
stack[tar.evaluate(eval_dict)] = src
|
89
94
|
end
|
90
95
|
end
|
91
96
|
|
97
|
+
# This instruction moves 128bits.
|
98
|
+
def inst_movaps(tar, src)
|
99
|
+
# XXX: here we only support `movaps [sp+*], xmm*`
|
100
|
+
# TODO: This need an extra constraint: sp & 0xf == 0
|
101
|
+
src, tar = check_xmm_sp(src, tar) { raise_unsupported('movaps', tar, src) }
|
102
|
+
off = tar.evaluate(eval_dict)
|
103
|
+
(128 / self.class.bits).times do |i|
|
104
|
+
stack[off + i * size_t] = src[i]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Mov *src to tar[:64]
|
109
|
+
def inst_movq(tar, src)
|
110
|
+
# XXX: here we only support `movq xmm*, [sp+*]`
|
111
|
+
tar, src = check_xmm_sp(tar, src) { raise_unsupported('movq', tar, src) }
|
112
|
+
off = src.evaluate(eval_dict)
|
113
|
+
(64 / self.class.bits).times do |i|
|
114
|
+
tar[i] = stack[off + i * size_t]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Move *src to tar[64:128]
|
119
|
+
def inst_movhps(tar, src)
|
120
|
+
# XXX: here we only support `movhps xmm*, [sp+*]`
|
121
|
+
tar, src = check_xmm_sp(tar, src) { raise_unsupported('movhps', tar, src) }
|
122
|
+
off = src.evaluate(eval_dict)
|
123
|
+
(64 / self.class.bits).times do |i|
|
124
|
+
tar[i + 64 / self.class.bits] = stack[off + i * size_t]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# check if (tar, src) in form (xmm*, [sp+*])
|
129
|
+
def check_xmm_sp(tar, src)
|
130
|
+
return yield unless tar.start_with?('xmm') && register?(tar) && src.include?(sp)
|
131
|
+
tar_lm = OneGadget::Emulators::Lambda.parse(tar, predefined: registers)
|
132
|
+
src_lm = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
133
|
+
return yield if src_lm.deref_count != 1
|
134
|
+
src_lm.ref!
|
135
|
+
[tar_lm, src_lm]
|
136
|
+
end
|
137
|
+
|
92
138
|
def inst_lea(tar, src)
|
93
139
|
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
94
140
|
src.ref!
|
@@ -97,7 +143,7 @@ module OneGadget
|
|
97
143
|
|
98
144
|
def inst_push(val)
|
99
145
|
val = OneGadget::Emulators::Lambda.parse(val, predefined: registers)
|
100
|
-
registers[sp] -=
|
146
|
+
registers[sp] -= size_t
|
101
147
|
cur_top = registers[sp].evaluate(eval_dict)
|
102
148
|
raise ArgumentError, "Corrupted stack pointer: #{cur_top}" unless cur_top.is_a?(Integer)
|
103
149
|
stack[cur_top] = val
|
@@ -150,14 +196,23 @@ module OneGadget
|
|
150
196
|
:fail
|
151
197
|
end
|
152
198
|
|
153
|
-
def
|
199
|
+
def size_t
|
154
200
|
self.class.bits / 8
|
155
201
|
end
|
156
202
|
|
157
203
|
def eval_dict
|
158
|
-
|
159
|
-
|
160
|
-
|
204
|
+
{ sp => 0 }
|
205
|
+
end
|
206
|
+
|
207
|
+
def raise_unsupported(inst, *args)
|
208
|
+
raise OneGadget::Error::UnsupportedInstructionArguments, "#{inst} #{args.join(', ')}"
|
209
|
+
end
|
210
|
+
|
211
|
+
def to_lambda(reg)
|
212
|
+
return super unless reg =~ /^xmm\d+$/
|
213
|
+
Array.new(128 / self.class.bits) do |i|
|
214
|
+
OneGadget::Emulators::Lambda.new("#{reg}__#{i}")
|
215
|
+
end
|
161
216
|
end
|
162
217
|
end
|
163
218
|
end
|
data/lib/one_gadget/fetcher.rb
CHANGED
@@ -15,9 +15,7 @@ module OneGadget
|
|
15
15
|
# @return [Array<OneGadget::Gadget::Gadget>?]
|
16
16
|
# +nil+ is returned if cannot find target id in database.
|
17
17
|
def from_build_id(build_id, remote: true)
|
18
|
-
|
19
|
-
raise ArgumentError, format('invalid BuildID format: %p', build_id)
|
20
|
-
end
|
18
|
+
OneGadget::Helper.verify_build_id!(build_id)
|
21
19
|
OneGadget::Gadget.builds(build_id, remote: remote)
|
22
20
|
end
|
23
21
|
|
data/lib/one_gadget/gadget.rb
CHANGED
@@ -64,6 +64,33 @@ module OneGadget
|
|
64
64
|
BUILDS[build_id]
|
65
65
|
end
|
66
66
|
|
67
|
+
# Returns the comments in builds/libc-*-<build_id>*.rb
|
68
|
+
# @param [String] build_id
|
69
|
+
# Supports give only few starting bytes, but a warning will be shown
|
70
|
+
# if multiple BulidIDs are matched.
|
71
|
+
# @return [String?]
|
72
|
+
# Lines of comments.
|
73
|
+
# @example
|
74
|
+
# puts OneGadget::Gadget.builds_info('3bbdc')
|
75
|
+
# # https://gitlab.com/libcdb/libcdb/blob/master/libc/libc6-amd64-2.19-18+deb8u4/lib64/libc-2.19.so
|
76
|
+
# #
|
77
|
+
# # Advanced Micro Devices X86-64
|
78
|
+
# # ...
|
79
|
+
def builds_info(build_id)
|
80
|
+
raise ArgumentError, "Invalid BuildID #{build_id.inspect}" if build_id =~ /[^0-9a-f]/
|
81
|
+
files = Dir.glob(File.join(BUILDS_PATH, "*-#{build_id}*.rb")).sort
|
82
|
+
return OneGadget::Logger.not_found(build_id) && nil if files.empty?
|
83
|
+
if files.size > 1
|
84
|
+
OneGadget::Logger.warn("Multiple BuildIDs match /^#{build_id}/\n")
|
85
|
+
show = files.map do |f|
|
86
|
+
File.basename(f, '.rb').reverse.split('-', 2).join(' ').reverse
|
87
|
+
end
|
88
|
+
OneGadget::Logger.warn("Candidates are:\n#{show * "\n"}\n")
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
OneGadget::Helper.comments_of_file(files.first)
|
92
|
+
end
|
93
|
+
|
67
94
|
# Add a gadget, for scripts in builds/ to use.
|
68
95
|
# @param [String] build_id The target's build id.
|
69
96
|
# @param [Integer] offset The relative address offset of this gadget.
|
data/lib/one_gadget/helper.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
require 'elftools'
|
2
1
|
require 'net/http'
|
3
2
|
require 'openssl'
|
4
3
|
require 'pathname'
|
5
4
|
require 'tempfile'
|
6
5
|
|
6
|
+
require 'elftools'
|
7
|
+
|
7
8
|
require 'one_gadget/logger'
|
8
9
|
|
9
10
|
module OneGadget
|
@@ -13,6 +14,26 @@ module OneGadget
|
|
13
14
|
BUILD_ID_FORMAT = /[0-9a-f]{40}/
|
14
15
|
# Define class methods here.
|
15
16
|
module ClassMethods
|
17
|
+
# Verify if `build_id` is a valid SHA1 hex format.
|
18
|
+
# @param [String] build_id
|
19
|
+
# BuildID.
|
20
|
+
# @raise [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
|
+
raise ArgumentError, format('invalid BuildID format: %p', build_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetch lines start with '#'.
|
29
|
+
# @param [String] file
|
30
|
+
# Filename.
|
31
|
+
# @return [Array<String>]
|
32
|
+
# Lines of comments.
|
33
|
+
def comments_of_file(file)
|
34
|
+
File.readlines(file).map { |s| s[2..-1].rstrip if s.start_with?('# ') }.compact
|
35
|
+
end
|
36
|
+
|
16
37
|
# Get absolute path from relative path. Support symlink.
|
17
38
|
# @param [String] path Relative path.
|
18
39
|
# @return [String] Absolute path, with symlink resolved.
|
@@ -30,7 +51,7 @@ module OneGadget
|
|
30
51
|
# build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so')
|
31
52
|
# #=> '60131540dadc6796cab33388349e6e4e68692053'
|
32
53
|
def build_id_of(path)
|
33
|
-
ELFTools::ELFFile.new(
|
54
|
+
File.open(path) { |f| ELFTools::ELFFile.new(f).build_id }
|
34
55
|
end
|
35
56
|
|
36
57
|
# Disable colorize.
|
@@ -48,7 +69,7 @@ module OneGadget
|
|
48
69
|
# Is colorify output enabled?
|
49
70
|
# @return [Boolean]
|
50
71
|
# True or false.
|
51
|
-
def
|
72
|
+
def color_enabled?
|
52
73
|
# if not set, use tty to check
|
53
74
|
return $stdout.tty? if @disable_color.nil?
|
54
75
|
!@disable_color
|
@@ -68,7 +89,7 @@ module OneGadget
|
|
68
89
|
# @param [Symbol] sev Specific which kind of color want to use, valid symbols are defined in +COLOR_CODE+.
|
69
90
|
# @return [String] Wrapper with color codes.
|
70
91
|
def colorize(str, sev: :normal_s)
|
71
|
-
return str unless
|
92
|
+
return str unless color_enabled?
|
72
93
|
cc = COLOR_CODE
|
73
94
|
color = cc.key?(sev) ? cc[sev] : ''
|
74
95
|
"#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
|
@@ -96,9 +117,8 @@ module OneGadget
|
|
96
117
|
def download_build(file)
|
97
118
|
temp = Tempfile.new(['gadgets', file + '.rb'])
|
98
119
|
url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb')))
|
99
|
-
temp.write
|
100
|
-
temp.close
|
101
|
-
temp
|
120
|
+
temp.write(url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))))
|
121
|
+
temp.tap(&:close)
|
102
122
|
end
|
103
123
|
|
104
124
|
# Get the latest builds list from repo.
|
@@ -132,14 +152,14 @@ module OneGadget
|
|
132
152
|
# @return [void]
|
133
153
|
def ask_update(msg: '')
|
134
154
|
name = 'one_gadget'
|
135
|
-
cmd = colorize("gem update #{name}")
|
155
|
+
cmd = colorize("gem update #{name} && gem cleanup #{name}")
|
136
156
|
OneGadget::Logger.info(msg + "\n" + "Update with: $ #{cmd}" + "\n")
|
137
157
|
end
|
138
158
|
|
139
159
|
# Fetch the file archiecture of +file+.
|
140
160
|
# @param [String] file The target ELF filename.
|
141
|
-
# @return [
|
142
|
-
# Only supports
|
161
|
+
# @return [Symbol]
|
162
|
+
# Only supports architecture amd64 and i386 now.
|
143
163
|
def architecture(file)
|
144
164
|
f = File.open(file)
|
145
165
|
str = ELFTools::ELFFile.new(f).machine
|
data/lib/one_gadget/logger.rb
CHANGED
@@ -25,13 +25,13 @@ module OneGadget
|
|
25
25
|
# @param [String] build_id
|
26
26
|
# Build ID.
|
27
27
|
def not_found(build_id)
|
28
|
-
warn("Cannot find BuildID [#{build_id}]")
|
28
|
+
warn("Cannot find BuildID [#{build_id}]\n")
|
29
29
|
[]
|
30
30
|
end
|
31
31
|
|
32
32
|
%i[info warn].each do |sym|
|
33
33
|
define_method(sym) do |msg|
|
34
|
-
@logger.
|
34
|
+
@logger.__send__(sym, msg)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
data/lib/one_gadget/update.rb
CHANGED
@@ -53,8 +53,8 @@ module OneGadget
|
|
53
53
|
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
54
54
|
IO.binwrite(CACHE_FILE, '') unless File.exist?(CACHE_FILE)
|
55
55
|
CACHE_FILE
|
56
|
-
rescue # prevent dir is not writable
|
57
|
-
|
56
|
+
rescue Errno::EACCES # prevent dir is not writable
|
57
|
+
nil
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
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.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- david942j
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elftools
|
@@ -24,76 +24,62 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: codeclimate-test-reporter
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.6'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.6'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rake
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '12.
|
33
|
+
version: '12.3'
|
48
34
|
type: :development
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '12.
|
40
|
+
version: '12.3'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rspec
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
47
|
+
version: '3.7'
|
62
48
|
type: :development
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3.
|
54
|
+
version: '3.7'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rubocop
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
59
|
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
61
|
+
version: '0.55'
|
76
62
|
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
68
|
+
version: '0.55'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: simplecov
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
75
|
+
version: 0.16.1
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
80
|
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
82
|
+
version: 0.16.1
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: yard
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -792,12 +778,17 @@ files:
|
|
792
778
|
- lib/one_gadget/builds/libc-2.25-912fc00c0da67045111928bd5c8a350e5be18c41.rb
|
793
779
|
- lib/one_gadget/builds/libc-2.25-eae5038c2b9ae67d9eda345aa9fbe0a7185ab436.rb
|
794
780
|
- lib/one_gadget/builds/libc-2.26-2104f3d4ad5cf68603afbe7ba1a17f5ac99c5988.rb
|
781
|
+
- lib/one_gadget/builds/libc-2.26-ddcc13122ddbfe5e5ef77d4ebe66d124ae5762c2.rb
|
782
|
+
- lib/one_gadget/builds/libc-2.26-f65648a832414f2144ce795d75b6045a1ec2e252.rb
|
783
|
+
- lib/one_gadget/builds/libc-2.27-63b3d43ad45e1b0f601848c65b067f9e9b40528b.rb
|
784
|
+
- lib/one_gadget/builds/libc-2.27-b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0.rb
|
795
785
|
- lib/one_gadget/emulators/amd64.rb
|
796
786
|
- lib/one_gadget/emulators/i386.rb
|
797
787
|
- lib/one_gadget/emulators/instruction.rb
|
798
788
|
- lib/one_gadget/emulators/lambda.rb
|
799
789
|
- lib/one_gadget/emulators/processor.rb
|
800
790
|
- lib/one_gadget/emulators/x86.rb
|
791
|
+
- lib/one_gadget/error.rb
|
801
792
|
- lib/one_gadget/fetcher.rb
|
802
793
|
- lib/one_gadget/fetchers/amd64.rb
|
803
794
|
- lib/one_gadget/fetchers/base.rb
|
@@ -827,7 +818,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
827
818
|
version: '0'
|
828
819
|
requirements: []
|
829
820
|
rubyforge_project:
|
830
|
-
rubygems_version: 2.
|
821
|
+
rubygems_version: 2.6.14
|
831
822
|
signing_key:
|
832
823
|
specification_version: 4
|
833
824
|
summary: one_gadget
|