one_gadget 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +72 -0
- data/bin/one_gadget +54 -0
- data/lib/one_gadget.rb +35 -0
- data/lib/one_gadget/abi.rb +15 -0
- data/lib/one_gadget/builds/libc-2.23-60131540dadc6796cab33388349e6e4e68692053.rb +8 -0
- data/lib/one_gadget/fetcher.rb +75 -0
- data/lib/one_gadget/gadget.rb +77 -0
- data/lib/one_gadget/helper.rb +128 -0
- data/lib/one_gadget/version.rb +3 -0
- data/spec/data/libc-2.19.so +0 -0
- data/spec/data/libc-2.23.so +0 -0
- data/spec/gadget_spec.rb +22 -0
- data/spec/helper_spec.rb +19 -0
- data/spec/one_gadget_spec.rb +31 -0
- data/spec/spec_helper.rb +8 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 469c960b795a87ce7f7e02b91c46b2f8e75f4a7d
|
4
|
+
data.tar.gz: c05c6387725951eaee40a2733f33909aa7d81daa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fa08575754ee8a545db7cc1f60ba307670eadcb2df17750d0504b4a6ac8eb55897bdb2b77aeff6e0ceb207fbe6e15a58b975bee6aed0411b0bfd10beabe00b63
|
7
|
+
data.tar.gz: 92e091b662abf384bd4cc791a27d9478e3bd17c8b9c6fe7f75aa314479b8e50738ba40da8ec40c554fbb166a05e071147999911bd63f6cc21b3445b4a902fb76
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
[](https://travis-ci.org/david942j/one_gadget)
|
2
|
+
[](https://codeclimate.com/github/david942j/one_gadget)
|
3
|
+
[](https://codeclimate.com/github/david942j/one_gadget)
|
4
|
+
[](https://codeclimate.com/github/david942j/one_gadget/coverage)
|
5
|
+
[](https://inch-ci.org/github/david942j/one_gadget)
|
6
|
+
[](http://choosealicense.com/licenses/mit/)
|
7
|
+
|
8
|
+
## One Gadget
|
9
|
+
|
10
|
+
When playing ctf pwn challenges we usually needs the one-gadget of `execve('/bin/sh', NULL, NULL)`.
|
11
|
+
|
12
|
+
This gem provides such gadget finder, no need to use IDA-pro every time like a fool.
|
13
|
+
|
14
|
+
Also provides the command-line tool `one_gadget` for easy usage.
|
15
|
+
|
16
|
+
Note: only supports x86-64 now.
|
17
|
+
|
18
|
+
## Install
|
19
|
+
|
20
|
+
I'll push to rubygems.org..
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Command Line Tool
|
25
|
+
|
26
|
+
```bash
|
27
|
+
one_gadget
|
28
|
+
# Usage: one_gadget [file] [options]
|
29
|
+
# -b, --build-id BuildID BuildID[sha1] of libc
|
30
|
+
# -r, --[no-]raw Output gadgets offset only, split with one space
|
31
|
+
# -s, --script exploit-script Run exploit script with all possible gadgets
|
32
|
+
# The script will be run as 'exploit-script $offset'.
|
33
|
+
|
34
|
+
one_gadget -b 60131540dadc6796cab33388349e6e4e68692053
|
35
|
+
# offset: 0x4526a
|
36
|
+
# constraints:
|
37
|
+
# [rsp+0x30] == NULL
|
38
|
+
#
|
39
|
+
# offset: 0xef6c4
|
40
|
+
# constraints:
|
41
|
+
# [rsp+0x50] == NULL
|
42
|
+
#
|
43
|
+
# offset: 0xf0567
|
44
|
+
# constraints:
|
45
|
+
# [rsp+0x70] == NULL
|
46
|
+
```
|
47
|
+
|
48
|
+
#### Combine with exploit script
|
49
|
+
Pass your exploit script as `one_gadget`'s arguments, it can
|
50
|
+
try all gadgets one by one, so you don't need to try every possible gadgets manually.
|
51
|
+
|
52
|
+
```bash
|
53
|
+
one_gadget ./spec/data/libc-2.19.so -s 'echo "offset ->"'
|
54
|
+
```
|
55
|
+
|
56
|
+

|
57
|
+
|
58
|
+
### Directly use in script
|
59
|
+
```ruby
|
60
|
+
require 'one_gadget'
|
61
|
+
OneGadget.gadgets(file: '/lib/x86_64-linux-gnu/libc.so.6')
|
62
|
+
# => [283242, 980676, 984423]
|
63
|
+
```
|
64
|
+
|
65
|
+
## Screenshots
|
66
|
+
|
67
|
+
### Search gadgets from file
|
68
|
+

|
69
|
+
|
70
|
+
### Fetch gadgets from database
|
71
|
+

|
72
|
+
|
data/bin/one_gadget
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bun/env ruby
|
2
|
+
require 'one_gadget'
|
3
|
+
require 'optionparser'
|
4
|
+
|
5
|
+
options = { raw: false }
|
6
|
+
usage = 'Usage: one_gadget [file] [options]'
|
7
|
+
parser = OptionParser.new do |opts|
|
8
|
+
opts.banner = usage
|
9
|
+
|
10
|
+
opts.on('-b', '--build-id BuildID', 'BuildID[sha1] of libc') do |b|
|
11
|
+
options[:build_id] = b
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on('-r', '--[no-]raw', 'Output gadgets offset only, split with one space') do |v|
|
15
|
+
options[:raw] = v
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('-s', '--script exploit-script', 'Run exploit script with all possible gadgets.',
|
19
|
+
'The script will be run as \'exploit-script $offset\'.') do |script|
|
20
|
+
options[:script] = script
|
21
|
+
end
|
22
|
+
end
|
23
|
+
parser.parse!
|
24
|
+
|
25
|
+
def execute(script, offset)
|
26
|
+
pid = fork do
|
27
|
+
exec([script, offset.to_s].join(' '))
|
28
|
+
end
|
29
|
+
Process.wait pid
|
30
|
+
end
|
31
|
+
|
32
|
+
if options[:build_id]
|
33
|
+
gadgets = OneGadget.gadgets(build_id: options[:build_id], details: true)
|
34
|
+
elsif ARGV[0]
|
35
|
+
gadgets = OneGadget.gadgets(file: ARGV[0], details: true)
|
36
|
+
else
|
37
|
+
puts parser.help
|
38
|
+
exit(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
extend OneGadget::Helper::ClassMethods
|
42
|
+
if options[:script]
|
43
|
+
gadgets.map(&:offset).each do |offset|
|
44
|
+
puts "[#{colorize('OneGadget', sev: :reg)}] Trying #{colorize(format('0x%x', offset), sev: :integer)}..."
|
45
|
+
execute(options[:script], offset)
|
46
|
+
end
|
47
|
+
exit(0)
|
48
|
+
end
|
49
|
+
|
50
|
+
if options[:raw]
|
51
|
+
puts gadgets.map(&:offset).join(' ')
|
52
|
+
else
|
53
|
+
puts gadgets.map(&:inspect).join("\n")
|
54
|
+
end
|
data/lib/one_gadget.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# OneGadget - To find the execve(/bin/sh, 0, 0) in glibc.
|
2
|
+
#
|
3
|
+
# @author david942j
|
4
|
+
module OneGadget
|
5
|
+
class << self
|
6
|
+
# The man entry of gem +one_gadget+.
|
7
|
+
# If want to find gadgets from file, it will search gadgets by its
|
8
|
+
# build id first.
|
9
|
+
#
|
10
|
+
# @param [String] file
|
11
|
+
# The relative path of libc.so.6.
|
12
|
+
# @param [String] build_id
|
13
|
+
# The BuildID of target libc.so.6.
|
14
|
+
# @return [Array<OneGadget::Gadget::Gadget>, Array<Integer>]
|
15
|
+
# The gadgets found.
|
16
|
+
# @example
|
17
|
+
# OneGadget.gadgets(file: './libc.so.6')
|
18
|
+
# OneGadget.gadgets(build_id: '60131540dadc6796cab33388349e6e4e68692053')
|
19
|
+
def gadgets(file: nil, build_id: nil, details: false)
|
20
|
+
if build_id
|
21
|
+
OneGadget::Fetcher.from_build_id(build_id, details: details)
|
22
|
+
elsif file
|
23
|
+
file = OneGadget::Helper.abspath(file)
|
24
|
+
build_id = OneGadget::Helper.build_id_of(file)
|
25
|
+
gadgets = OneGadget::Fetcher.from_build_id(build_id, details: details)
|
26
|
+
return gadgets unless gadgets.empty?
|
27
|
+
OneGadget::Fetcher.from_file(file, details: details)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'one_gadget/fetcher'
|
34
|
+
require 'one_gadget/helper'
|
35
|
+
require 'one_gadget/version'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module OneGadget
|
2
|
+
# define the abi of different architecture.
|
3
|
+
module ABI
|
4
|
+
# Define class methods here.
|
5
|
+
module ClassMethods
|
6
|
+
LINUX_X86_32 = %w(eax ebx ecx edx edi esi ebp esp).freeze
|
7
|
+
LINUX_X86_64 = LINUX_X86_32 + %w(rax rbx rcx rdx rdi rsi rbp rsp) + 7.upto(15).map { |i| "r#{i}" }
|
8
|
+
# Only support x86-64 now.
|
9
|
+
def registers
|
10
|
+
LINUX_X86_64
|
11
|
+
end
|
12
|
+
end
|
13
|
+
extend ClassMethods
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'one_gadget/gadget'
|
2
|
+
# Ubuntu GLIBC 2.23-0ubuntu5
|
3
|
+
# ELF 64-bit LSB shared object, x86-64
|
4
|
+
build_id = File.basename(__FILE__, '.rb').split('-').last
|
5
|
+
OneGadget::Gadget.add(build_id, 0x4526a, constraints: ['[rsp+0x30] == NULL'])
|
6
|
+
OneGadget::Gadget.add(build_id, 0xef6c4, constraints: ['[rsp+0x50] == NULL'])
|
7
|
+
OneGadget::Gadget.add(build_id, 0xf0567, constraints: ['[rsp+0x70] == NULL'])
|
8
|
+
OneGadget::Gadget.add(build_id, 0xf5b10, constraints: ['[rbp-0xf8] == NULL', 'rcx == NULL'])
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'one_gadget/helper'
|
2
|
+
require 'one_gadget/gadget'
|
3
|
+
|
4
|
+
module OneGadget
|
5
|
+
# To find gadgets.
|
6
|
+
module Fetcher
|
7
|
+
# Define class methods here.
|
8
|
+
module ClassMethods
|
9
|
+
# Fetch one-gadget offsets of this build id.
|
10
|
+
# @param [String] build_id The targets' BuildID.
|
11
|
+
# @param [Boolean] details
|
12
|
+
# If needs to return the gadgets' constraints.
|
13
|
+
# @return [Array<Integer>, Array<OneGadget::Gadget::Gadget>]
|
14
|
+
# If +details+ is +false+, +Array<Integer>+ is returned, which
|
15
|
+
# contains offset only.
|
16
|
+
# Otherwise, array of gadgets is returned.
|
17
|
+
def from_build_id(build_id, details: false)
|
18
|
+
if (build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/).nil?
|
19
|
+
raise ArgumentError, format('invalid BuildID format: %p', build_id)
|
20
|
+
end
|
21
|
+
gadgets = OneGadget::Gadget.builds(build_id)
|
22
|
+
return gadgets if details
|
23
|
+
gadgets.map(&:offset)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Fetch one-gadget offsets from file.
|
27
|
+
# @param [String] file The absolute path of libc file.
|
28
|
+
# @param [Boolean] details
|
29
|
+
# If needs to return the gadgets' constraints.
|
30
|
+
# @return [Array<Integer>, Array<OneGadget::Gadget::Gadget>]
|
31
|
+
# If +details+ is +false+, +Array<Integer>+ is returned, which
|
32
|
+
# contains offset only.
|
33
|
+
# Otherwise, array of gadgets is returned.
|
34
|
+
def from_file(file, details: false)
|
35
|
+
bin_sh_hex = str_offset(file, '/bin/sh').to_s(16)
|
36
|
+
candidates = `objdump -d -M intel #{file}|egrep '<execve[^+]*>$' -B 10`.split('--').select do |candidate|
|
37
|
+
next false unless candidate.include?(bin_sh_hex)
|
38
|
+
next false unless candidate.lines.last.include?('call') # last line must be +call execve+
|
39
|
+
true
|
40
|
+
end
|
41
|
+
candidates.map! do |candidate|
|
42
|
+
# remove other calls
|
43
|
+
lines = candidate.lines
|
44
|
+
to_rm = lines[0...-1].rindex { |c| c.include?('call') }
|
45
|
+
lines = lines[to_rm + 1..-1] unless to_rm.nil?
|
46
|
+
lines.map! { |s| s.gsub(/#\s+#{bin_sh_hex}\s+<.*>$/, "# #{bin_sh_hex} \"/bin/sh\"") }
|
47
|
+
lines.join
|
48
|
+
end
|
49
|
+
gadgets = candidates.map { |c| convert_to_gadget(c) }
|
50
|
+
return gadgets if details
|
51
|
+
gadgets.map(&:offset)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def str_offset(file, str)
|
57
|
+
match = `strings -tx #{file} | grep '#{str}'`.lines.map(&:strip).first
|
58
|
+
return nil if match.nil?
|
59
|
+
# 17c8c3 /bin/sh
|
60
|
+
match.split.first.to_i(16)
|
61
|
+
end
|
62
|
+
|
63
|
+
def convert_to_gadget(assembly)
|
64
|
+
lines = assembly.lines.map(&:strip)
|
65
|
+
offset = lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
66
|
+
# fetch those might be constraints lines.
|
67
|
+
important_lines = lines.select { |line| ['rsi'].any? { |r| line.include?(r) } }.map do |line|
|
68
|
+
line.split("\t").last.gsub(/\s+/, ' ')
|
69
|
+
end
|
70
|
+
OneGadget::Gadget::Gadget.new(offset, constraints: important_lines)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
extend ClassMethods
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'one_gadget/abi'
|
2
|
+
|
3
|
+
module OneGadget
|
4
|
+
# Module for define gadgets.
|
5
|
+
module Gadget
|
6
|
+
# Information of a gadget.
|
7
|
+
class Gadget
|
8
|
+
# @return [Integer] The gadget's address offset.
|
9
|
+
attr_accessor :offset
|
10
|
+
# @return [Array<String>] The constraints need for this gadget.
|
11
|
+
attr_accessor :constraints
|
12
|
+
|
13
|
+
# Initialize method of {Gadget} instance.
|
14
|
+
# @param [Integer] offset The relative address offset of this gadget.
|
15
|
+
# @option options [Array<String>] :constraints
|
16
|
+
# The constraints need for this gadget. Defaults to +[]+.
|
17
|
+
# @example
|
18
|
+
# OneGadget::Gadget::Gadget.new(0x12345, constraints: ['rax == 0'])
|
19
|
+
def initialize(offset, **options)
|
20
|
+
@offset = offset
|
21
|
+
@constraints = options[:constraints] || []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Show gadget in a pretty way.
|
25
|
+
def inspect
|
26
|
+
str = format("#{OneGadget::Helper.colorize('offset', sev: :sym)}: 0x%x\n", offset)
|
27
|
+
unless constraints.empty?
|
28
|
+
str += "#{OneGadget::Helper.colorize('constraints')}:\n "
|
29
|
+
str += constraints.join("\n ")
|
30
|
+
end
|
31
|
+
str.gsub!(/0x[\da-f]+/) { |s| OneGadget::Helper.colorize(s, sev: :integer) }
|
32
|
+
OneGadget::ABI.registers.each { |reg| str.gsub!(reg, OneGadget::Helper.colorize(reg, sev: :reg)) }
|
33
|
+
str + "\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define class methods here.
|
38
|
+
module ClassMethods
|
39
|
+
BUILDS_PATH = File.join(File.dirname(__FILE__), 'builds').freeze
|
40
|
+
BUILDS = Hash.new { |h, k| h[k] = [] }
|
41
|
+
# Get gadgets from pre-defined corpus.
|
42
|
+
# @param [String] build_id Desired build id.
|
43
|
+
# @return [Array<Gadget::Gadget>] Gadgets.
|
44
|
+
def builds(build_id)
|
45
|
+
require_all if BUILDS.empty?
|
46
|
+
return BUILDS[build_id] if BUILDS.key?(build_id)
|
47
|
+
# fetch remote builds
|
48
|
+
table = OneGadget::Helper.remote_builds.find { |c| c.include?(build_id) }
|
49
|
+
return [] if table.nil? # remote doesn't have this one either.
|
50
|
+
# builds found in remote! Ask update gem and download remote gadgets.
|
51
|
+
OneGadget::Helper.ask_update(msg: 'The desired one-gadget can be found in lastest version!')
|
52
|
+
tmp_file = OneGadget::Helper.download_build(table)
|
53
|
+
require tmp_file.path
|
54
|
+
tmp_file.unlink
|
55
|
+
BUILDS[build_id]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add a gadget, for scripts in builds/ to use.
|
59
|
+
# @param [String] build_id The target's build id.
|
60
|
+
# @param [Integer] offset The relative address offset of this gadget.
|
61
|
+
# @param [Hash] options See {Gadget::Gadget#initialize} for more information.
|
62
|
+
# @return [void]
|
63
|
+
def add(build_id, offset, **options)
|
64
|
+
BUILDS[build_id] << OneGadget::Gadget::Gadget.new(offset, **options)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def require_all
|
70
|
+
Dir.glob(File.join(BUILDS_PATH, '**', '*.rb')).each do |dic|
|
71
|
+
require dic
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
extend ClassMethods
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
module OneGadget
|
8
|
+
# Define some helpful methods here.
|
9
|
+
module Helper
|
10
|
+
BUILD_ID_FORMAT = /[0-9a-f]{40}/
|
11
|
+
# Define class methods here.
|
12
|
+
module ClassMethods
|
13
|
+
# Get absolute path from relative path. Support symlink.
|
14
|
+
# @param [String] path Relative path.
|
15
|
+
# @return [String] Absolute path, with symlink resolved.
|
16
|
+
# @example
|
17
|
+
# abspath('/lib/x86_64-linux-gnu/libc.so.6')
|
18
|
+
# #=> '/lib/x86_64-linux-gnu/libc-2.23.so'
|
19
|
+
def abspath(path)
|
20
|
+
Pathname.new(File.expand_path(path)).realpath.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the Build ID of target ELF.
|
24
|
+
# @param [String] path Absolute file path.
|
25
|
+
# @return [String] Target build id.
|
26
|
+
# @example
|
27
|
+
# build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so')
|
28
|
+
# #=> '60131540dadc6796cab33388349e6e4e68692053'
|
29
|
+
def build_id_of(path)
|
30
|
+
cmd = 'readelf -n ' + ::Shellwords.escape(path)
|
31
|
+
bid = `#{cmd}`.scan(/Build ID: (#{BUILD_ID_FORMAT})$/).first
|
32
|
+
return nil if bid.nil?
|
33
|
+
bid.first
|
34
|
+
end
|
35
|
+
|
36
|
+
# Disable colorize
|
37
|
+
def color_off!
|
38
|
+
@disable_color = true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Enable colorize
|
42
|
+
def color_on!
|
43
|
+
@disable_color = false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Color codes for pretty print
|
47
|
+
COLOR_CODE = {
|
48
|
+
esc_m: "\e[0m",
|
49
|
+
normal_s: "\e[38;5;1m", # red
|
50
|
+
integer: "\e[38;5;12m", # light blue
|
51
|
+
fatal: "\e[38;5;197m", # dark red
|
52
|
+
reg: "\e[38;5;120m", # light green
|
53
|
+
sym: "\e[38;5;229m", # pry like
|
54
|
+
}.freeze
|
55
|
+
|
56
|
+
# Wrapper color codes for pretty inspect.
|
57
|
+
# @param [String] str Contents to colorize.
|
58
|
+
# @option [Symbol] sev Specific which kind of color want to use, valid symbols are defined in +COLOR_CODE+.
|
59
|
+
# @return [String] Wrapper with color codes.
|
60
|
+
def colorize(str, sev: :normal_s)
|
61
|
+
return str if @disable_color
|
62
|
+
cc = COLOR_CODE
|
63
|
+
color = cc.key?(sev) ? cc[sev] : ''
|
64
|
+
"#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Fetch the latest release version's tag name.
|
68
|
+
# @return [String] The tag name, in form +vx.x.x+.
|
69
|
+
def latest_tag
|
70
|
+
latest = url_request('https://github.com/david942j/one_gadget/releases').scan(%r{/tree/v([\d.]+)"}).map do |tag|
|
71
|
+
Gem::Version.new(tag.first)
|
72
|
+
end.max.to_s
|
73
|
+
'v' + latest
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the url which can fetch +filename+ from remote repo.
|
77
|
+
# @param [String] filename
|
78
|
+
# @return [String] The url.
|
79
|
+
def url_of_file(filename)
|
80
|
+
raw_file_url = 'https://raw.githubusercontent.com/david942j/one_gadget/@tag/@file'
|
81
|
+
raw_file_url.gsub('@tag', latest_tag).gsub('@file', filename)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Download the latest version of +file+ in +lib/one_gadget/builds/+ from remote repo.
|
85
|
+
#
|
86
|
+
# @param [String] file The filename desired.
|
87
|
+
# @return [Tempfile] The temp file be created.
|
88
|
+
def download_build(file)
|
89
|
+
temp = Tempfile.new(['gadgets', file + '.rb'])
|
90
|
+
url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb')))
|
91
|
+
temp.write url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb')))
|
92
|
+
temp.close
|
93
|
+
temp
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get the latest builds list from repo.
|
97
|
+
# @return [Array<String>] List of build ids.
|
98
|
+
def remote_builds
|
99
|
+
url_request(url_of_file('builds_list')).lines.map(&:strip)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get request.
|
103
|
+
# @param [String] url The url.
|
104
|
+
# @return [String] The request response body.
|
105
|
+
def url_request(url)
|
106
|
+
# TODO: add timeout to handle github crashed or in no network environment.
|
107
|
+
uri = URI.parse(url)
|
108
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
109
|
+
http.use_ssl = true
|
110
|
+
http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
|
111
|
+
|
112
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
113
|
+
|
114
|
+
response = http.request(request)
|
115
|
+
response.body
|
116
|
+
end
|
117
|
+
|
118
|
+
# Show the message of ask user to update gem.
|
119
|
+
# @return [void]
|
120
|
+
def ask_update(msg: '')
|
121
|
+
name = 'one_gadget'
|
122
|
+
cmd = colorize("gem update #{name}")
|
123
|
+
STDERR.puts msg + "\n" + "Update with: $ #{cmd}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
extend ClassMethods
|
127
|
+
end
|
128
|
+
end
|
Binary file
|
Binary file
|
data/spec/gadget_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'one_gadget/gadget'
|
2
|
+
require 'one_gadget/helper'
|
3
|
+
describe OneGadget::Gadget do
|
4
|
+
before(:all) do
|
5
|
+
@build_id = 'fake_id'
|
6
|
+
OneGadget::Helper.color_off! # disable colorize for easy testing.
|
7
|
+
OneGadget::Gadget.add(@build_id, 0x1234, constraints: ['[rsp+0x30] == NULL', 'rax == 0'])
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
OneGadget::Gadget::ClassMethods::BUILDS.delete @build_id
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'inspect' do
|
15
|
+
expect(OneGadget::Gadget.builds(@build_id).map(&:inspect).join).to eq <<-EOS
|
16
|
+
offset: 0x1234
|
17
|
+
constraints:
|
18
|
+
[rsp+0x30] == NULL
|
19
|
+
rax == 0
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
end
|
data/spec/helper_spec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'one_gadget/helper'
|
2
|
+
|
3
|
+
describe OneGadget::Helper do
|
4
|
+
before(:all) do
|
5
|
+
OneGadget::Helper.color_on!
|
6
|
+
@libcpath = File.join(File.dirname(__FILE__), 'data', 'libc-2.23.so')
|
7
|
+
end
|
8
|
+
it 'abspath' do
|
9
|
+
expect(OneGadget::Helper.abspath('./spec/data/libc-2.23.so')).to eq @libcpath
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'build_id_of' do
|
13
|
+
expect(OneGadget::Helper.build_id_of(@libcpath)).to eq '60131540dadc6796cab33388349e6e4e68692053'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'colorize' do
|
17
|
+
expect(OneGadget::Helper.colorize('123', sev: :integer)).to eq "\e[38;5;12m123\e[0m"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'one_gadget'
|
2
|
+
|
3
|
+
describe 'one_gadget' do
|
4
|
+
before(:each) do
|
5
|
+
@build_id = '60131540dadc6796cab33388349e6e4e68692053'
|
6
|
+
@libcpath = File.join(File.dirname(__FILE__), 'data', 'libc-2.19.so')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'from file' do
|
10
|
+
expect(OneGadget.gadgets(file: @libcpath)).to eq [0x4647c, 0xe5765, 0xe66bd]
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'from build id' do
|
14
|
+
it 'normal' do
|
15
|
+
# only check not empty because the gadgets might add frequently.
|
16
|
+
expect(OneGadget.gadgets(build_id: @build_id)).not_to be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'invalid' do
|
20
|
+
expect { OneGadget.gadgets(build_id: '^_^') }.to raise_error(ArgumentError, 'invalid BuildID format: "^_^"')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'fetch from remote' do
|
24
|
+
entry = OneGadget::Gadget::ClassMethods::BUILDS.delete(@build_id)
|
25
|
+
OneGadget::Gadget::ClassMethods::BUILDS[:a] = 1
|
26
|
+
expect(OneGadget.gadgets(build_id: @build_id)).not_to be_empty
|
27
|
+
OneGadget::Gadget::ClassMethods::BUILDS.delete(:a)
|
28
|
+
OneGadget::Gadget::ClassMethods::BUILDS[@build_id] = entry unless entry.nil?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: one_gadget
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- david942j
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.46'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.46'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.13.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.13.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: codeclimate-test-reporter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.6'
|
83
|
+
description: |2
|
84
|
+
When playing ctf pwn challenges we usually needs the one-gadget of `execve('/bin/sh', NULL, NULL)`.
|
85
|
+
This gem provides such gadget finder, no need to use IDA-pro every time like a fool.
|
86
|
+
Also provides the command-line tool `one_gadget` for easy usage.
|
87
|
+
email:
|
88
|
+
- david942j@gmail.com
|
89
|
+
executables:
|
90
|
+
- one_gadget
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- README.md
|
95
|
+
- bin/one_gadget
|
96
|
+
- lib/one_gadget.rb
|
97
|
+
- lib/one_gadget/abi.rb
|
98
|
+
- lib/one_gadget/builds/libc-2.23-60131540dadc6796cab33388349e6e4e68692053.rb
|
99
|
+
- lib/one_gadget/fetcher.rb
|
100
|
+
- lib/one_gadget/gadget.rb
|
101
|
+
- lib/one_gadget/helper.rb
|
102
|
+
- lib/one_gadget/version.rb
|
103
|
+
- spec/data/libc-2.19.so
|
104
|
+
- spec/data/libc-2.23.so
|
105
|
+
- spec/gadget_spec.rb
|
106
|
+
- spec/helper_spec.rb
|
107
|
+
- spec/one_gadget_spec.rb
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
homepage: https://github.com/david942j/one_gadget
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: 2.1.0
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.5.2
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: one_gadget
|
133
|
+
test_files:
|
134
|
+
- spec/data/libc-2.19.so
|
135
|
+
- spec/data/libc-2.23.so
|
136
|
+
- spec/spec_helper.rb
|
137
|
+
- spec/helper_spec.rb
|
138
|
+
- spec/one_gadget_spec.rb
|
139
|
+
- spec/gadget_spec.rb
|