one_gadget 1.1.1 → 1.2.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 +4 -4
- data/README.md +5 -0
- data/lib/one_gadget/emulators/amd64.rb +44 -0
- data/lib/one_gadget/emulators/instruction.rb +36 -0
- data/lib/one_gadget/emulators/lambda.rb +105 -0
- data/lib/one_gadget/emulators/processor.rb +36 -0
- data/lib/one_gadget/fetchers/amd64.rb +27 -4
- data/lib/one_gadget/fetchers/base.rb +5 -0
- data/lib/one_gadget/helper.rb +31 -0
- data/lib/one_gadget/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25904f1508d192a00e1702d7adb48d52af589ae0
|
4
|
+
data.tar.gz: abe1ec3e6b3f57420326aad5100a3a6d4a260b6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d8593c144d504c00ea3f1e3b80397391012a3414054bdc586c52340830b22b7bb7ec57b054c20d6198ded00b25443dbe42225f2804da504474d531777ba922e
|
7
|
+
data.tar.gz: a06dce9ee9693fe06422000c6d7105ab55655fbdd79b4edcf69dd9f2a618b9b5f4e5c041941f809fa251296825e63e7ae2f4c90a401ac3d20d05dbe37b8138ac
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
[](https://travis-ci.org/david942j/one_gadget)
|
2
|
+

|
2
3
|
[](https://codeclimate.com/github/david942j/one_gadget)
|
3
4
|
[](https://codeclimate.com/github/david942j/one_gadget)
|
4
5
|
[](https://codeclimate.com/github/david942j/one_gadget/coverage)
|
@@ -22,6 +23,10 @@ Available on RubyGems.org!
|
|
22
23
|
gem install one_gadget
|
23
24
|
```
|
24
25
|
|
26
|
+
## Implementation
|
27
|
+
|
28
|
+
OneGadget use simple self-implement symbolic execution to find the constraints of gadgets.
|
29
|
+
|
25
30
|
## Usage
|
26
31
|
|
27
32
|
### Command Line Tool
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'one_gadget/emulators/processor'
|
2
|
+
require 'one_gadget/emulators/instruction'
|
3
|
+
|
4
|
+
module OneGadget
|
5
|
+
module Emulators
|
6
|
+
# Emulator of amd64 instruction set.
|
7
|
+
class Amd64 < Processor
|
8
|
+
REGISTERS = %w(rax rbx rcx rdx rdi rsi rbp rsp rip) + 7.upto(15).map { |i| "r#{i}" }
|
9
|
+
|
10
|
+
# Instantiate a {Amd64} object.
|
11
|
+
def initialize
|
12
|
+
super(REGISTERS)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Process one command.
|
16
|
+
# @param [String] cmd
|
17
|
+
# One line from result of objdump.
|
18
|
+
# @return [void]
|
19
|
+
def process(cmd)
|
20
|
+
inst, args = parse(cmd)
|
21
|
+
# where should this be defined..?
|
22
|
+
return if inst.inst == 'call' # later
|
23
|
+
case inst.inst
|
24
|
+
when 'mov', 'lea'
|
25
|
+
tar = args[0]
|
26
|
+
src = OneGadget::Emulators::Lambda.parse(args[1], predefined: @registers)
|
27
|
+
end
|
28
|
+
src.ref! if inst.inst == 'lea'
|
29
|
+
|
30
|
+
@registers[tar] = src
|
31
|
+
end
|
32
|
+
|
33
|
+
# Support instruction set.
|
34
|
+
# @return [Array<Instruction>] The support instructions.
|
35
|
+
def instructions
|
36
|
+
[
|
37
|
+
Instruction.new('mov', 2),
|
38
|
+
Instruction.new('lea', 2),
|
39
|
+
Instruction.new('call', 1)
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module OneGadget
|
2
|
+
module Emulators
|
3
|
+
# Define instruction name and it's argument count.
|
4
|
+
class Instruction
|
5
|
+
attr_reader :inst # @return [String] The instruction name.
|
6
|
+
attr_reader :argc # @return [Integer] Count of arguments.
|
7
|
+
# Instantiate a n{Instruction} object.
|
8
|
+
# @param [String] inst The instruction name.
|
9
|
+
# @param [Integer] argc Count of arguments.
|
10
|
+
def initialize(inst, argc)
|
11
|
+
@inst = inst
|
12
|
+
@argc = argc
|
13
|
+
end
|
14
|
+
|
15
|
+
# Extract arguments from command.
|
16
|
+
# @param [String] cmd
|
17
|
+
# @return [Array<String>] Arguments.
|
18
|
+
def fetch_args(cmd)
|
19
|
+
idx = cmd.index(inst)
|
20
|
+
cmd = cmd[0...cmd.rindex('#')] if cmd.rindex('#')
|
21
|
+
args = cmd[idx + inst.size..-1].split(',')
|
22
|
+
raise ArgumentError, "Incorrect argument number in #{cmd}, expect: #{argc}" if args.size != argc
|
23
|
+
args.map do |arg|
|
24
|
+
arg.gsub(/QWORD|DWORD|WORD|BYTE|PTR/, '').strip
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# If the command contains this instruction.
|
29
|
+
# @param [String] cmd
|
30
|
+
# @return [Boolean]
|
31
|
+
def match?(cmd)
|
32
|
+
cmd.include?(inst)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'one_gadget/helper'
|
2
|
+
|
3
|
+
module OneGadget
|
4
|
+
module Emulators
|
5
|
+
# A {Lambda} object can be:
|
6
|
+
# 1. {String} # variable name
|
7
|
+
# 2. {Numeric}
|
8
|
+
# 3. {Lambda} + {Numeric}
|
9
|
+
# 4. dereference {Lambda}
|
10
|
+
class Lambda
|
11
|
+
attr_accessor :obj # @return [String, Lambda] The object currently related to.
|
12
|
+
attr_accessor :immi # @return [Integer] The immidiate value currently added.
|
13
|
+
attr_accessor :deref_count # @return [Integer] The times of dereference.
|
14
|
+
# Instantiate a {Lambda} object.
|
15
|
+
# @param [Lambda, String] obj
|
16
|
+
def initialize(obj)
|
17
|
+
@immi = 0
|
18
|
+
@obj = obj
|
19
|
+
@deref_count = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Implement addition with +Numeric+.
|
23
|
+
# @param [Numeric] other Value to add.
|
24
|
+
# @return [Lambda] The result.
|
25
|
+
def +(other)
|
26
|
+
raise ArgumentError, 'Expect other to be Numeric.' unless other.is_a?(Numeric)
|
27
|
+
if deref_count > 0
|
28
|
+
ret = Lambda.new(self)
|
29
|
+
else
|
30
|
+
ret = Lambda.new(obj)
|
31
|
+
ret.immi = immi
|
32
|
+
end
|
33
|
+
ret.immi += other
|
34
|
+
ret
|
35
|
+
end
|
36
|
+
|
37
|
+
# Implement substract with +Numeric+.
|
38
|
+
# @param [Numeric] other Value to substract.
|
39
|
+
# @return [Lambda] The result.
|
40
|
+
def -(other)
|
41
|
+
self.+(-other)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Increase dreference count with 1.
|
45
|
+
# @return [void]
|
46
|
+
def deref!
|
47
|
+
@deref_count += 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# Decrease dreference count with 1.
|
51
|
+
# @return [void]
|
52
|
+
def ref!
|
53
|
+
raise ArgumentError, 'Cannot reference anymore!' if @deref_count <= 0
|
54
|
+
@deref_count -= 1
|
55
|
+
end
|
56
|
+
|
57
|
+
# A new {Lambda} object with dreference count increase 1.
|
58
|
+
# @return [Lambda]
|
59
|
+
def deref
|
60
|
+
ret = Lambda.new(obj)
|
61
|
+
ret.immi = immi
|
62
|
+
ret.deref_count = deref_count + 1
|
63
|
+
ret
|
64
|
+
end
|
65
|
+
|
66
|
+
# Expand the lambda presentation.
|
67
|
+
# @return [String] The expand result.
|
68
|
+
def to_s
|
69
|
+
str = ''
|
70
|
+
str += '[' * deref_count
|
71
|
+
str += obj.to_s unless obj.nil?
|
72
|
+
str += OneGadget::Helper.hex(immi, psign: true) unless immi.zero?
|
73
|
+
str += ']' * deref_count
|
74
|
+
str
|
75
|
+
end
|
76
|
+
|
77
|
+
class << self
|
78
|
+
# Target: parse something like +[rsp+0x50]+ into a {Lambda} object.
|
79
|
+
# @param [String] arg
|
80
|
+
# @param [Hash{String => Lambda}] predefined
|
81
|
+
# @return [OneGadget::Emulators::Lambda, Integer]
|
82
|
+
# If +arg+ contains number only, return it.
|
83
|
+
# Otherwise, return a {Lambda} object.
|
84
|
+
# @example
|
85
|
+
# parse('[rsp+0x50]') #=> #<Lambda @obj='rsp', @immi=80, @deref_count=1>
|
86
|
+
def parse(arg, predefined: {})
|
87
|
+
ret = Lambda.new('tmp')
|
88
|
+
if arg[0] == '[' # a little hack because there should nerver something like +[[rsp+1]+2]+ to parse.
|
89
|
+
arg = arg[1..-2]
|
90
|
+
ret.deref_count += 1
|
91
|
+
end
|
92
|
+
return Integer(arg) if OneGadget::Helper.integer?(arg)
|
93
|
+
sign = arg =~ /[+-]/
|
94
|
+
raise ArgumentError, "Not support #{arg}" if sign && !OneGadget::Helper.integer?(arg[sign..-1])
|
95
|
+
if sign
|
96
|
+
ret.immi = Integer(arg[sign..-1])
|
97
|
+
arg = arg[0, sign]
|
98
|
+
end
|
99
|
+
ret.obj = predefined[arg] || arg
|
100
|
+
ret
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'one_gadget/emulators/lambda'
|
2
|
+
|
3
|
+
module OneGadget
|
4
|
+
# Instruction emulator to solve the constraint of gadgets.
|
5
|
+
module Emulators
|
6
|
+
# Base class of a processor.
|
7
|
+
class Processor
|
8
|
+
attr_reader :registers # @return [Hash{String => OneGadget::Emulators::Lambda}] The current registers' state.
|
9
|
+
# Instantiate a {Processor} object.
|
10
|
+
# @param [Array<String>] registers Registers that supported in the architecture.
|
11
|
+
def initialize(registers)
|
12
|
+
@registers = registers.map { |reg| [reg, OneGadget::Emulators::Lambda.new(reg)] }.to_h
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parse one command into instruction and arguments.
|
16
|
+
# @param [String] cmd One line of result of objdump.
|
17
|
+
# @return [Array(Instruction, Array<String>)]
|
18
|
+
# The parsing result.
|
19
|
+
def parse(cmd)
|
20
|
+
inst = instructions.find { |i| i.match?(cmd) }
|
21
|
+
raise ArgumentError, "Not implemented instruction in #{cmd}" if inst.nil?
|
22
|
+
[inst, inst.fetch_args(cmd)]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Method need to be implemented in inheritors.
|
26
|
+
# @return [void]
|
27
|
+
def process(_cmd); raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# Method need to be implemented in inheritors.
|
31
|
+
# @return [Array<Instruction>] The support instructions.
|
32
|
+
def instructions; raise NotImplementedError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'one_gadget/fetchers/base'
|
2
|
+
require 'one_gadget/emulators/amd64'
|
2
3
|
module OneGadget
|
3
4
|
module Fetcher
|
4
5
|
# Fetcher for amd64.
|
@@ -13,10 +14,32 @@ module OneGadget
|
|
13
14
|
true
|
14
15
|
end
|
15
16
|
cands.map do |candidate|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
processor = OneGadget::Emulators::Amd64.new
|
18
|
+
candidate.lines.each { |l| processor.process(l) }
|
19
|
+
offset = offset_of(candidate)
|
20
|
+
constraints = gen_constraints(processor)
|
21
|
+
next nil if constraints.nil? # impossible be a gadget
|
22
|
+
OneGadget::Gadget::Gadget.new(offset, constraints: constraints)
|
23
|
+
end.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def gen_constraints(processor)
|
29
|
+
# check rdi should always related to rip
|
30
|
+
return unless processor.registers['rdi'].to_s.include?('rip')
|
31
|
+
# rsi or [rsi] should be zero
|
32
|
+
[
|
33
|
+
should_null(processor.registers['rsi'].to_s),
|
34
|
+
should_null(processor.registers['rdx'].to_s, allow_global: true)
|
35
|
+
].compact
|
36
|
+
end
|
37
|
+
|
38
|
+
def should_null(str, allow_global: false)
|
39
|
+
return nil if allow_global && str.include?('rip')
|
40
|
+
ret = "[#{str}] == NULL"
|
41
|
+
ret += " || #{str} == NULL" unless str.include?('rsp')
|
42
|
+
ret
|
20
43
|
end
|
21
44
|
end
|
22
45
|
end
|
@@ -54,6 +54,11 @@ module OneGadget
|
|
54
54
|
match.split.first.to_i(16)
|
55
55
|
end
|
56
56
|
|
57
|
+
def offset_of(assembly)
|
58
|
+
lines = assembly.lines
|
59
|
+
lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
60
|
+
end
|
61
|
+
|
57
62
|
def convert_to_gadget(assembly, &block)
|
58
63
|
lines = assembly.lines
|
59
64
|
offset = lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
data/lib/one_gadget/helper.rb
CHANGED
@@ -137,6 +137,37 @@ module OneGadget
|
|
137
137
|
return :i386 if str.include?('Intel 80386')
|
138
138
|
:unknown
|
139
139
|
end
|
140
|
+
|
141
|
+
# Present number in hex format.
|
142
|
+
# @param [Integer] val The number.
|
143
|
+
# @param [Boolean] psign Need to show plus sign when +val >= 0+.
|
144
|
+
# @return [String] string in hex format.
|
145
|
+
# @example
|
146
|
+
# hex(32) #=> 0x20
|
147
|
+
# hex(32, psign: true) #=> +0x20
|
148
|
+
# hex(-40) #=> -0x28
|
149
|
+
# hex(0) #=> 0x0
|
150
|
+
# hex(0, psign: true) #=> +0x0
|
151
|
+
def hex(val, psign: false)
|
152
|
+
return format("#{psign ? '+' : ''}0x%x", val) if val >= 0
|
153
|
+
format('-0x%x', -val)
|
154
|
+
end
|
155
|
+
|
156
|
+
# For checking a string is actually an integer.
|
157
|
+
# @param [String] str String to be checked.
|
158
|
+
# @return [Boolean] If +str+ can be converted into integer.
|
159
|
+
# @example
|
160
|
+
# Helper.integer? '1234'
|
161
|
+
# # => true
|
162
|
+
# Helper.integer? '0x1234'
|
163
|
+
# # => true
|
164
|
+
# Helper.integer? '0xheapoverflow'
|
165
|
+
# # => false
|
166
|
+
def integer?(str)
|
167
|
+
true if Integer(str)
|
168
|
+
rescue ArgumentError, TypeError
|
169
|
+
false
|
170
|
+
end
|
140
171
|
end
|
141
172
|
extend ClassMethods
|
142
173
|
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.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- david942j
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -101,6 +101,10 @@ files:
|
|
101
101
|
- lib/one_gadget/builds/libc-2.23-60131540dadc6796cab33388349e6e4e68692053.rb
|
102
102
|
- lib/one_gadget/builds/libc-2.23-926eb99d49cab2e5622af38ab07395f5b32035e9.rb
|
103
103
|
- lib/one_gadget/builds/libc-2.23-edceed30099baad51871c5fc277daf9b74dc726a.rb
|
104
|
+
- lib/one_gadget/emulators/amd64.rb
|
105
|
+
- lib/one_gadget/emulators/instruction.rb
|
106
|
+
- lib/one_gadget/emulators/lambda.rb
|
107
|
+
- lib/one_gadget/emulators/processor.rb
|
104
108
|
- lib/one_gadget/fetcher.rb
|
105
109
|
- lib/one_gadget/fetchers/amd64.rb
|
106
110
|
- lib/one_gadget/fetchers/base.rb
|
@@ -129,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
133
|
version: '0'
|
130
134
|
requirements: []
|
131
135
|
rubyforge_project:
|
132
|
-
rubygems_version: 2.
|
136
|
+
rubygems_version: 2.6.10
|
133
137
|
signing_key:
|
134
138
|
specification_version: 4
|
135
139
|
summary: one_gadget
|