one_gadget 1.2.0 → 1.3.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 +27 -17
- data/lib/one_gadget/abi.rb +11 -2
- data/lib/one_gadget/builds/libc-2.23-60131540dadc6796cab33388349e6e4e68692053.rb +12 -4
- data/lib/one_gadget/builds/libc-2.23-926eb99d49cab2e5622af38ab07395f5b32035e9.rb +2 -0
- data/lib/one_gadget/emulators/amd64.rb +5 -35
- data/lib/one_gadget/emulators/i386.rb +20 -0
- data/lib/one_gadget/emulators/lambda.rb +18 -6
- data/lib/one_gadget/emulators/processor.rb +2 -0
- data/lib/one_gadget/emulators/x86.rb +111 -0
- data/lib/one_gadget/fetcher.rb +15 -0
- data/lib/one_gadget/fetchers/amd64.rb +36 -15
- data/lib/one_gadget/fetchers/base.rb +9 -14
- data/lib/one_gadget/fetchers/i386.rb +95 -27
- data/lib/one_gadget/gadget.rb +6 -2
- data/lib/one_gadget/helper.rb +2 -2
- data/lib/one_gadget/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cc62d276055fdb8dc5a968da82d417248f07d4b
|
4
|
+
data.tar.gz: 9b102faa2884dfb094e2c8b4a1df5f32fb7f5eaf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b86e5a6ad42af54af86358385aab99a43d3f04aeb5ce8303c68d0fcb15d3c06c3d916bb477cd067bb5fb504c0c4da1479f68aa56eea7742ff4996d0362c91c0b
|
7
|
+
data.tar.gz: 8a55837472e55ff34c52c74521f7c8358720055ed0aa6f7912563d4c1ad169aba3c6305c2480e43e576adb0df887f3b6fbdf9edba186b83d567b97e2fb9f8f50
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
[](https://travis-ci.org/david942j/one_gadget)
|
2
|
-

|
2
|
+
[](https://rubygems.org/gems/one_gadget)
|
3
3
|
[](https://codeclimate.com/github/david942j/one_gadget)
|
4
4
|
[](https://codeclimate.com/github/david942j/one_gadget)
|
5
5
|
[](https://codeclimate.com/github/david942j/one_gadget/coverage)
|
@@ -41,38 +41,43 @@ one_gadget
|
|
41
41
|
# The script will be run as 'exploit-script $offset'.
|
42
42
|
|
43
43
|
one_gadget -b 60131540dadc6796cab33388349e6e4e68692053
|
44
|
-
#
|
44
|
+
# 0x4526a execve("/bin/sh", rsp+0x30, environ)
|
45
45
|
# constraints:
|
46
46
|
# [rsp+0x30] == NULL
|
47
|
-
#
|
48
|
-
#
|
47
|
+
#
|
48
|
+
# 0xef6c4 execve("/bin/sh", rsp+0x50, environ)
|
49
49
|
# constraints:
|
50
50
|
# [rsp+0x50] == NULL
|
51
|
-
#
|
52
|
-
#
|
51
|
+
#
|
52
|
+
# 0xf0567 execve("/bin/sh", rsp+0x70, environ)
|
53
53
|
# constraints:
|
54
54
|
# [rsp+0x70] == NULL
|
55
|
-
#
|
56
|
-
#
|
55
|
+
#
|
56
|
+
# 0xcc543 execve("/bin/sh", rcx, r12)
|
57
57
|
# constraints:
|
58
|
-
# [
|
59
|
-
#
|
58
|
+
# rcx == NULL || [rcx] == NULL
|
59
|
+
# r12 == NULL || [r12] == NULL
|
60
|
+
#
|
61
|
+
# 0xf5b10 execve("/bin/sh", rcx, [rbp-0xf8])
|
62
|
+
# constraints:
|
63
|
+
# [rbp-0xf8] == NULL || [[rbp-0xf8]] == NULL
|
64
|
+
# rcx == NULL || [rcx] == NULL
|
60
65
|
|
61
66
|
one_gadget /lib/i386-linux-gnu/libc.so.6
|
62
|
-
#
|
67
|
+
# 0x3ac69 execve("/bin/sh", esp+0x34, environ)
|
63
68
|
# constraints:
|
64
69
|
# esi is the address of `rw-p` area of libc
|
65
70
|
# [esp+0x34] == NULL
|
66
|
-
#
|
67
|
-
#
|
71
|
+
#
|
72
|
+
# 0x5fbc5 execl("/bin/sh", eax)
|
68
73
|
# constraints:
|
69
74
|
# esi is the address of `rw-p` area of libc
|
70
75
|
# eax == NULL
|
71
|
-
#
|
72
|
-
#
|
76
|
+
#
|
77
|
+
# 0x5fbc6 execl("/bin/sh", [esp])
|
73
78
|
# constraints:
|
74
79
|
# esi is the address of `rw-p` area of libc
|
75
|
-
#
|
80
|
+
# [esp] == NULL
|
76
81
|
|
77
82
|
```
|
78
83
|
|
@@ -90,14 +95,19 @@ one_gadget ./spec/data/libc-2.19.so -s 'echo "offset ->"'
|
|
90
95
|
```ruby
|
91
96
|
require 'one_gadget'
|
92
97
|
OneGadget.gadgets(file: '/lib/x86_64-linux-gnu/libc.so.6')
|
93
|
-
# => [283242, 980676, 984423, 1006352]
|
98
|
+
# => [283242, 980676, 984423, 836931, 1006352]
|
94
99
|
```
|
95
100
|
|
96
101
|
## Screenshots
|
97
102
|
|
98
103
|
### Search gadgets from file
|
104
|
+
|
105
|
+
#### 64 bit
|
99
106
|

|
100
107
|
|
108
|
+
#### 32 bit
|
109
|
+

|
110
|
+
|
101
111
|
### Fetch gadgets from database
|
102
112
|

|
103
113
|
|
data/lib/one_gadget/abi.rb
CHANGED
@@ -5,10 +5,19 @@ module OneGadget
|
|
5
5
|
module ClassMethods
|
6
6
|
LINUX_X86_32 = %w(eax ebx ecx edx edi esi ebp esp).freeze
|
7
7
|
LINUX_X86_64 = LINUX_X86_32 + %w(rax rbx rcx rdx rdi rsi rbp rsp) + 7.upto(15).map { |i| "r#{i}" }
|
8
|
-
#
|
9
|
-
|
8
|
+
# Registers' name in amd64.
|
9
|
+
# @return [Array<String>] List of registers.
|
10
|
+
def amd64
|
10
11
|
LINUX_X86_64
|
11
12
|
end
|
13
|
+
|
14
|
+
# Registers' name in i386.
|
15
|
+
# @return [Array<String>] List of registers.
|
16
|
+
def i386
|
17
|
+
LINUX_X86_32
|
18
|
+
end
|
19
|
+
|
20
|
+
alias all amd64
|
12
21
|
end
|
13
22
|
extend ClassMethods
|
14
23
|
end
|
@@ -2,7 +2,15 @@ require 'one_gadget/gadget'
|
|
2
2
|
# Ubuntu GLIBC 2.23-0ubuntu5
|
3
3
|
# ELF 64-bit LSB shared object, x86-64
|
4
4
|
build_id = File.basename(__FILE__, '.rb').split('-').last
|
5
|
-
OneGadget::Gadget.add(build_id, 0x4526a, constraints: ['[rsp+0x30] == NULL']
|
6
|
-
|
7
|
-
OneGadget::Gadget.add(build_id,
|
8
|
-
|
5
|
+
OneGadget::Gadget.add(build_id, 0x4526a, constraints: ['[rsp+0x30] == NULL'],
|
6
|
+
effect: 'execve("/bin/sh", rsp+0x30, environ)')
|
7
|
+
OneGadget::Gadget.add(build_id, 0xef6c4, constraints: ['[rsp+0x50] == NULL'],
|
8
|
+
effect: 'execve("/bin/sh", rsp+0x50, environ)')
|
9
|
+
OneGadget::Gadget.add(build_id, 0xf0567, constraints: ['[rsp+0x70] == NULL'],
|
10
|
+
effect: 'execve("/bin/sh", rsp+0x70, environ)')
|
11
|
+
OneGadget::Gadget.add(build_id, 0xcc543, constraints: ['rcx == NULL || [rcx] == NULL',
|
12
|
+
'r12 == NULL || [r12] == NULL'],
|
13
|
+
effect: 'execve("/bin/sh", rcx, r12)')
|
14
|
+
OneGadget::Gadget.add(build_id, 0xf5b10, constraints: ['[rbp-0xf8] == NULL || [[rbp-0xf8]] == NULL',
|
15
|
+
'rcx == NULL || [rcx] == NULL'],
|
16
|
+
effect: 'execve("/bin/sh", rcx, [rbp-0xf8])')
|
@@ -5,4 +5,6 @@ build_id = File.basename(__FILE__, '.rb').split('-').last
|
|
5
5
|
rw_area_constraint = 'esi is the address of `rw-p` area of libc'
|
6
6
|
OneGadget::Gadget.add(build_id, 0x3ac69, constraints: [rw_area_constraint, '[esp+0x34] == NULL'])
|
7
7
|
OneGadget::Gadget.add(build_id, 0x5fbbe, constraints: [rw_area_constraint, 'eax == NULL'])
|
8
|
+
OneGadget::Gadget.add(build_id, 0x5fbbf, constraints: [rw_area_constraint, '[esp] == NULL'])
|
8
9
|
OneGadget::Gadget.add(build_id, 0x12036c, constraints: [rw_area_constraint, 'eax == NULL'])
|
10
|
+
OneGadget::Gadget.add(build_id, 0x12036d, constraints: [rw_area_constraint, '[esp] == NULL'])
|
@@ -1,43 +1,13 @@
|
|
1
|
-
require 'one_gadget/emulators/
|
2
|
-
require 'one_gadget/
|
1
|
+
require 'one_gadget/emulators/x86'
|
2
|
+
require 'one_gadget/abi'
|
3
3
|
|
4
4
|
module OneGadget
|
5
5
|
module Emulators
|
6
6
|
# Emulator of amd64 instruction set.
|
7
|
-
class Amd64 <
|
8
|
-
|
9
|
-
|
10
|
-
# Instantiate a {Amd64} object.
|
7
|
+
class Amd64 < X86
|
8
|
+
# Instantiate an {Amd64} object.
|
11
9
|
def initialize
|
12
|
-
super(
|
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
|
-
]
|
10
|
+
super(OneGadget::ABI.amd64, 'rsp', 'rip')
|
41
11
|
end
|
42
12
|
end
|
43
13
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'one_gadget/emulators/x86'
|
2
|
+
require 'one_gadget/abi'
|
3
|
+
|
4
|
+
module OneGadget
|
5
|
+
module Emulators
|
6
|
+
# Emulator of amd64 instruction set.
|
7
|
+
class I386 < X86
|
8
|
+
class << self
|
9
|
+
def bits
|
10
|
+
32
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Instantiate an {I386} object.
|
15
|
+
def initialize
|
16
|
+
super(OneGadget::ABI.i386, 'esp', 'eip')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -74,6 +74,17 @@ module OneGadget
|
|
74
74
|
str
|
75
75
|
end
|
76
76
|
|
77
|
+
# Eval the value of lambda.
|
78
|
+
# Only support those like +rsp+0x30+.
|
79
|
+
# @param [Hash{String => Integer}] context
|
80
|
+
# The context.
|
81
|
+
# @return [Integer] Result of evaluation.
|
82
|
+
def evaluate(context)
|
83
|
+
raise ArgumentError, "Can't eval #{self}" if deref_count > 0
|
84
|
+
raise ArgumentError, "Can't eval #{self}" if obj && !context.key?(obj)
|
85
|
+
context[obj] + immi
|
86
|
+
end
|
87
|
+
|
77
88
|
class << self
|
78
89
|
# Target: parse something like +[rsp+0x50]+ into a {Lambda} object.
|
79
90
|
# @param [String] arg
|
@@ -84,20 +95,21 @@ module OneGadget
|
|
84
95
|
# @example
|
85
96
|
# parse('[rsp+0x50]') #=> #<Lambda @obj='rsp', @immi=80, @deref_count=1>
|
86
97
|
def parse(arg, predefined: {})
|
87
|
-
|
98
|
+
deref_count = 0
|
88
99
|
if arg[0] == '[' # a little hack because there should nerver something like +[[rsp+1]+2]+ to parse.
|
89
100
|
arg = arg[1..-2]
|
90
|
-
|
101
|
+
deref_count = 1
|
91
102
|
end
|
92
103
|
return Integer(arg) if OneGadget::Helper.integer?(arg)
|
93
104
|
sign = arg =~ /[+-]/
|
94
|
-
|
105
|
+
val = 0
|
95
106
|
if sign
|
96
|
-
|
107
|
+
raise ArgumentError, "Not support #{arg}" unless OneGadget::Helper.integer?(arg[sign..-1])
|
108
|
+
val = Integer(arg[sign..-1])
|
97
109
|
arg = arg[0, sign]
|
98
110
|
end
|
99
|
-
|
100
|
-
|
111
|
+
obj = (predefined[arg] || Lambda.new(arg)) + val
|
112
|
+
deref_count.zero? ? obj : obj.deref
|
101
113
|
end
|
102
114
|
end
|
103
115
|
end
|
@@ -6,10 +6,12 @@ module OneGadget
|
|
6
6
|
# Base class of a processor.
|
7
7
|
class Processor
|
8
8
|
attr_reader :registers # @return [Hash{String => OneGadget::Emulators::Lambda}] The current registers' state.
|
9
|
+
attr_reader :stack # @return [Hash{Integer => OneGadget::Emulators::Lambda}] The content on stack.
|
9
10
|
# Instantiate a {Processor} object.
|
10
11
|
# @param [Array<String>] registers Registers that supported in the architecture.
|
11
12
|
def initialize(registers)
|
12
13
|
@registers = registers.map { |reg| [reg, OneGadget::Emulators::Lambda.new(reg)] }.to_h
|
14
|
+
@stack = {}
|
13
15
|
end
|
14
16
|
|
15
17
|
# Parse one command into instruction and arguments.
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'one_gadget/emulators/processor'
|
2
|
+
require 'one_gadget/emulators/instruction'
|
3
|
+
|
4
|
+
module OneGadget
|
5
|
+
module Emulators
|
6
|
+
# Super class for amd64 and i386 processor.
|
7
|
+
class X86 < Processor
|
8
|
+
attr_reader :sp # @return [String] Stack pointer.
|
9
|
+
attr_reader :pc # @return [String] Program counter.
|
10
|
+
# Constructor for a x86 processor.
|
11
|
+
def initialize(registers, sp, pc)
|
12
|
+
super(registers)
|
13
|
+
@sp = sp
|
14
|
+
@pc = pc
|
15
|
+
@stack = Hash.new do |h, k|
|
16
|
+
lmda = OneGadget::Emulators::Lambda.new(sp)
|
17
|
+
lmda.immi = k
|
18
|
+
lmda.deref!
|
19
|
+
h[k] = lmda
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Process one command.
|
24
|
+
# @param [String] cmd
|
25
|
+
# One line from result of objdump.
|
26
|
+
# @return [void]
|
27
|
+
def process(cmd)
|
28
|
+
inst, args = parse(cmd)
|
29
|
+
return registers[pc] = args[0] if inst.inst == 'call'
|
30
|
+
return if inst.inst == 'jmp' # believe the fetcher has handled jmp.
|
31
|
+
sym = "inst_#{inst.inst}".to_sym
|
32
|
+
send(sym, *args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Support instruction set.
|
36
|
+
# @return [Array<Instruction>] The support instructions.
|
37
|
+
def instructions
|
38
|
+
[
|
39
|
+
Instruction.new('mov', 2),
|
40
|
+
Instruction.new('lea', 2),
|
41
|
+
Instruction.new('add', 2),
|
42
|
+
Instruction.new('sub', 2),
|
43
|
+
Instruction.new('push', 1),
|
44
|
+
Instruction.new('call', 1),
|
45
|
+
Instruction.new('jmp', 1)
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
# 32 or 64.
|
51
|
+
# @return [Integer] 32 or 64.
|
52
|
+
def bits; raise NotImplementedError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def register?(reg)
|
59
|
+
registers.include?(reg)
|
60
|
+
end
|
61
|
+
|
62
|
+
def inst_mov(tar, src)
|
63
|
+
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
64
|
+
if register?(tar)
|
65
|
+
registers[tar] = src
|
66
|
+
else
|
67
|
+
# Just ignore strange case...
|
68
|
+
return unless tar.include?(sp)
|
69
|
+
tar = OneGadget::Emulators::Lambda.parse(tar, predefined: registers)
|
70
|
+
return if tar.deref_count != 1 # should not happened
|
71
|
+
tar.ref!
|
72
|
+
stack[tar.evaluate(eval_dict)] = src
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def inst_lea(tar, src)
|
77
|
+
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
78
|
+
src.ref!
|
79
|
+
registers[tar] = src
|
80
|
+
end
|
81
|
+
|
82
|
+
def inst_push(val)
|
83
|
+
val = OneGadget::Emulators::Lambda.parse(val, predefined: registers)
|
84
|
+
registers[sp] -= bytes
|
85
|
+
cur_top = registers[sp].evaluate(eval_dict)
|
86
|
+
raise ArgumentError, "Corrupted stack pointer: #{cur_top}" unless cur_top.is_a?(Integer)
|
87
|
+
stack[cur_top] = val
|
88
|
+
end
|
89
|
+
|
90
|
+
def inst_add(tar, src)
|
91
|
+
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
92
|
+
registers[tar] += src
|
93
|
+
end
|
94
|
+
|
95
|
+
def inst_sub(tar, src)
|
96
|
+
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
97
|
+
registers[tar] -= src
|
98
|
+
end
|
99
|
+
|
100
|
+
def bytes
|
101
|
+
self.class.bits / 8
|
102
|
+
end
|
103
|
+
|
104
|
+
def eval_dict
|
105
|
+
dict = {}
|
106
|
+
dict[sp] = 0
|
107
|
+
dict
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/one_gadget/fetcher.rb
CHANGED
@@ -39,9 +39,24 @@ module OneGadget
|
|
39
39
|
i386: OneGadget::Fetcher::I386,
|
40
40
|
unknown: OneGadget::Fetcher::Base
|
41
41
|
}[OneGadget::Helper.architecture(file)].new(file).find
|
42
|
+
gadgets = trim_gadgets(gadgets)
|
42
43
|
return gadgets if details
|
43
44
|
gadgets.map(&:offset)
|
44
45
|
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Unique, remove gadgets with harder constraints.
|
50
|
+
def trim_gadgets(gadgets)
|
51
|
+
gadgets = gadgets.uniq(&:constraints).sort_by { |g| g.constraints.size }
|
52
|
+
res = []
|
53
|
+
gadgets.each_with_index do |g, i|
|
54
|
+
res << g unless i.times.any? do |j|
|
55
|
+
(gadgets[j].constraints - g.constraints).empty?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
res.sort_by!(&:offset)
|
59
|
+
end
|
45
60
|
end
|
46
61
|
extend ClassMethods
|
47
62
|
end
|
@@ -7,32 +7,53 @@ module OneGadget
|
|
7
7
|
# Gadgets for amd64 glibc.
|
8
8
|
# @return [Array<OneGadget::Gadget::Gadget>] Gadgets found.
|
9
9
|
def find
|
10
|
-
|
11
|
-
cands = candidates do |candidate|
|
12
|
-
next false unless candidate.include?(bin_sh_hex) # works in x86-64
|
13
|
-
next false unless candidate.lines.last.include?('execve') # only care execve
|
14
|
-
true
|
15
|
-
end
|
16
|
-
cands.map do |candidate|
|
10
|
+
candidates.map do |candidate|
|
17
11
|
processor = OneGadget::Emulators::Amd64.new
|
18
12
|
candidate.lines.each { |l| processor.process(l) }
|
19
13
|
offset = offset_of(candidate)
|
20
|
-
|
21
|
-
next nil if
|
22
|
-
OneGadget::Gadget::Gadget.new(offset,
|
14
|
+
options = resolve(processor)
|
15
|
+
next nil if options.nil? # impossible be a gadget
|
16
|
+
OneGadget::Gadget::Gadget.new(offset, options)
|
23
17
|
end.compact
|
24
18
|
end
|
25
19
|
|
26
20
|
private
|
27
21
|
|
28
|
-
def
|
22
|
+
def candidates
|
23
|
+
bin_sh_hex = str_offset('/bin/sh').to_s(16)
|
24
|
+
cands = super do |candidate|
|
25
|
+
next false unless candidate.include?(bin_sh_hex) # works in x86-64
|
26
|
+
next false unless candidate.lines.last.include?('execve') # only care execve
|
27
|
+
true
|
28
|
+
end
|
29
|
+
# find gadgets in form:
|
30
|
+
# lea rdi, '/bin/sh'
|
31
|
+
# jmp xxx
|
32
|
+
# xxx:
|
33
|
+
# ...
|
34
|
+
# call execve
|
35
|
+
cands2 = `#{objdump_cmd}|egrep 'rdi.*# #{bin_sh_hex}' -A 1`.split('--').map do |cand|
|
36
|
+
cand = cand.lines.map(&:strip).reject(&:empty?)
|
37
|
+
next nil unless cand.last.include?('jmp')
|
38
|
+
jmp_addr = cand.last.scan(/jmp\s+([\da-f]+)\s/)[0][0].to_i(16)
|
39
|
+
dump = `#{objdump_cmd(start: jmp_addr, stop: jmp_addr + 100)}|egrep '[0-9a-f]+:'`
|
40
|
+
remain = dump.lines.map(&:strip).reject(&:empty?)
|
41
|
+
remain = remain[0..remain.index { |r| r.match(/call.*<execve[^+]*>/) }]
|
42
|
+
[cand + remain].join("\n")
|
43
|
+
end.compact
|
44
|
+
cands + cands2
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolve(processor)
|
29
48
|
# check rdi should always related to rip
|
30
49
|
return unless processor.registers['rdi'].to_s.include?('rip')
|
31
50
|
# rsi or [rsi] should be zero
|
32
|
-
[
|
33
|
-
|
34
|
-
|
35
|
-
|
51
|
+
rsi = processor.registers['rsi'].to_s
|
52
|
+
cons = [should_null(rsi)]
|
53
|
+
rdx = processor.registers['rdx'].to_s
|
54
|
+
env_cons = should_null(rdx, allow_global: true)
|
55
|
+
cons << env_cons if env_cons
|
56
|
+
{ constraints: cons, effect: %(execve("/bin/sh", #{rsi}, #{env_cons ? rdx : 'environ'})) }
|
36
57
|
end
|
37
58
|
|
38
59
|
def should_null(str, allow_global: false)
|
@@ -22,7 +22,7 @@ module OneGadget
|
|
22
22
|
# @return [Array<String>]
|
23
23
|
# Each +String+ returned is multi-lines of assembly code.
|
24
24
|
def candidates(&block)
|
25
|
-
cands =
|
25
|
+
cands = `#{objdump_cmd}|egrep 'call.*<exec[^+]*>$' -B 20`.split('--').map do |cand|
|
26
26
|
cand.lines.map(&:strip).reject(&:empty?).join("\n")
|
27
27
|
end
|
28
28
|
# remove all calls, jmps
|
@@ -33,6 +33,13 @@ module OneGadget
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
+
def objdump_cmd(start: nil, stop: nil)
|
37
|
+
cmd = %(objdump -w -d -M intel "#{file}")
|
38
|
+
cmd.concat(" --start-address #{start}") if start
|
39
|
+
cmd.concat(" --stop-address #{stop}") if stop
|
40
|
+
cmd
|
41
|
+
end
|
42
|
+
|
36
43
|
def slice_prefix(cands)
|
37
44
|
cands.map do |cand|
|
38
45
|
lines = cand.lines
|
@@ -55,19 +62,7 @@ module OneGadget
|
|
55
62
|
end
|
56
63
|
|
57
64
|
def offset_of(assembly)
|
58
|
-
|
59
|
-
lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
60
|
-
end
|
61
|
-
|
62
|
-
def convert_to_gadget(assembly, &block)
|
63
|
-
lines = assembly.lines
|
64
|
-
offset = lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
65
|
-
# fetch those might be constraints lines.
|
66
|
-
important_lines = lines.select(&block).map do |line|
|
67
|
-
ar = line.split("\t")
|
68
|
-
"#{ar.first} #{ar.last.gsub(/\s+/, ' ')}"
|
69
|
-
end
|
70
|
-
OneGadget::Gadget::Gadget.new(offset, constraints: important_lines)
|
65
|
+
assembly.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
71
66
|
end
|
72
67
|
end
|
73
68
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'one_gadget/fetchers/base'
|
2
|
+
require 'one_gadget/emulators/i386'
|
3
|
+
|
2
4
|
module OneGadget
|
3
5
|
module Fetcher
|
4
6
|
# Fetcher for i386.
|
@@ -8,49 +10,115 @@ module OneGadget
|
|
8
10
|
def find
|
9
11
|
rw_off = rw_offset
|
10
12
|
bin_sh = str_offset('/bin/sh')
|
11
|
-
minus_c = str_offset('-c')
|
12
13
|
rel_sh_hex = (rw_off - bin_sh).to_s(16)
|
13
|
-
rel_minus_c = (rw_off - minus_c).to_s(16)
|
14
14
|
cands = candidates do |candidate|
|
15
15
|
next false unless candidate.include?(rel_sh_hex)
|
16
16
|
true
|
17
17
|
end
|
18
|
-
|
19
|
-
cands = slice_prefix(cands) do |line|
|
20
|
-
line.include?(rel_minus_c)
|
21
|
-
end
|
22
|
-
# special handle for execl call
|
23
|
-
cands.map! do |cand|
|
18
|
+
cands.map do |cand|
|
24
19
|
lines = cand.lines
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
push_cnt += 1 if c.include?('push')
|
34
|
-
push_cnt >= 3
|
35
|
-
end
|
20
|
+
# use processor to find which can lead to a valid one-gadget call.
|
21
|
+
gadgets = []
|
22
|
+
(lines.size - 2).downto(0) do |i|
|
23
|
+
processor = emulate(lines[i..-1])
|
24
|
+
options = resolve(processor, bin_sh: rw_off - bin_sh)
|
25
|
+
next if options.nil?
|
26
|
+
offset = offset_of(lines[i])
|
27
|
+
gadgets << OneGadget::Gadget::Gadget.new(offset, options)
|
36
28
|
end
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
cands.map do |candidate|
|
41
|
-
convert_to_gadget(candidate) do |_|
|
42
|
-
true
|
43
|
-
end
|
44
|
-
end
|
29
|
+
gadgets
|
30
|
+
end.flatten.compact
|
45
31
|
end
|
46
32
|
|
47
33
|
private
|
48
34
|
|
35
|
+
def emulate(cmds)
|
36
|
+
cmds.each_with_object(OneGadget::Emulators::I386.new) { |cmd, obj| obj.process(cmd) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Generating constraints to be a valid gadget.
|
40
|
+
# @param [OneGadget::Emulators::I386] processor The processor after executing the gadget.
|
41
|
+
# @param [Integer] bin_sh The related offset refer to /bin/sh.
|
42
|
+
# @return [Hash{Symbol => Array<String>, String}, NilClass]
|
43
|
+
# The options to create a {OneGadget::Gadget::Gadget} object.
|
44
|
+
# Keys might be:
|
45
|
+
# 1. constraints: Array<String> List of constraints.
|
46
|
+
# 2. effect: String Result function call of this gadget.
|
47
|
+
# If the constraints can never be satisfied, +nil+ is returned.
|
48
|
+
def resolve(processor, bin_sh: 0)
|
49
|
+
call = processor.registers['eip'].to_s
|
50
|
+
cur_top = processor.registers['esp'].evaluate('esp' => 0)
|
51
|
+
arg = processor.stack[cur_top]
|
52
|
+
# arg0 must be /bin/sh
|
53
|
+
return nil unless arg.to_s.include?(bin_sh.to_s(16))
|
54
|
+
rw_base = arg.deref.obj.to_s # this should be esi or ebx..
|
55
|
+
arg1 = processor.stack[cur_top + 4]
|
56
|
+
arg2 = processor.stack[cur_top + 8]
|
57
|
+
options = if call.include?('execve')
|
58
|
+
resolve_execve(arg1, arg2, rw_base: rw_base)
|
59
|
+
elsif call.include?('execl')
|
60
|
+
resolve_execl(arg1, arg2, rw_base: rw_base, sh: bin_sh - 5)
|
61
|
+
end
|
62
|
+
return nil if options.nil?
|
63
|
+
options[:constraints].unshift("#{rw_base} is the address of `rw-p` area of libc")
|
64
|
+
options
|
65
|
+
end
|
66
|
+
|
67
|
+
# Resolve +call execl+ case.
|
68
|
+
# @param [OneGadget::Emulators::Lambda] arg1
|
69
|
+
# The second argument.
|
70
|
+
# @param [OneGadget::Emulators::Lambda] arg2
|
71
|
+
# The third argument.
|
72
|
+
# @param [String] rw_base Usually +ebx+ or +esi+.
|
73
|
+
# @param [Integer] sh The relative offset of string 'sh' appears.
|
74
|
+
# @return [Hash{Symbol => Array<String>, String}]
|
75
|
+
# Same format as {#resolve}.
|
76
|
+
def resolve_execl(arg1, arg2, rw_base: nil, sh: 0)
|
77
|
+
args = []
|
78
|
+
arg = arg1.to_s
|
79
|
+
if arg.include?(sh.to_s(16))
|
80
|
+
arg = arg2.to_s
|
81
|
+
args << '"sh"'
|
82
|
+
end
|
83
|
+
args << arg
|
84
|
+
return nil if arg.include?(rw_base) || arg.include?('eip') # we don't want base-related constraints
|
85
|
+
# now arg is the constraint.
|
86
|
+
{ constraints: ["#{arg} == NULL"], effect: %(execl("/bin/sh", #{args.join(', ')})) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Resolve +call execve+ case.
|
90
|
+
# @param [OneGadget::Emulators::Lambda] arg1
|
91
|
+
# The second argument.
|
92
|
+
# @param [OneGadget::Emulators::Lambda] arg2
|
93
|
+
# The third argument.
|
94
|
+
# @param [String] rw_base Usually +ebx+ or +esi+.
|
95
|
+
# @return [Hash{Symbol => Array<String>, String}]
|
96
|
+
# Same format as {#resolve}.
|
97
|
+
def resolve_execve(arg1, arg2, rw_base: nil)
|
98
|
+
# arg1 == NULL || [arg1] == NULL
|
99
|
+
# arg2 == NULL or arg2 is environ
|
100
|
+
cons = [should_null(arg1.to_s)]
|
101
|
+
envp = arg2.to_s
|
102
|
+
use_env = true
|
103
|
+
unless envp.include?('[[') && envp.include?(rw_base) # hope this is environ_ptr_0
|
104
|
+
return nil if envp.include?('[') # we don't like this :(
|
105
|
+
cons << should_null(envp)
|
106
|
+
use_env = false
|
107
|
+
end
|
108
|
+
{ constraints: cons, effect: %(execve("/bin/sh", #{arg1}, #{use_env ? 'environ' : envp})) }
|
109
|
+
end
|
110
|
+
|
49
111
|
def rw_offset
|
50
112
|
# How to find this offset correctly..?
|
51
113
|
line = `readelf -d #{file}|grep PLTGOT`
|
52
114
|
line.scan(/0x[\da-f]+/).last.to_i(16) & -0x1000
|
53
115
|
end
|
116
|
+
|
117
|
+
def should_null(str)
|
118
|
+
ret = "[#{str}] == NULL"
|
119
|
+
ret += " || #{str} == NULL" unless str.include?('esp')
|
120
|
+
ret
|
121
|
+
end
|
54
122
|
end
|
55
123
|
end
|
56
124
|
end
|
data/lib/one_gadget/gadget.rb
CHANGED
@@ -9,6 +9,8 @@ module OneGadget
|
|
9
9
|
attr_accessor :offset
|
10
10
|
# @return [Array<String>] The constraints need for this gadget.
|
11
11
|
attr_accessor :constraints
|
12
|
+
# @return [String] The final result of this gadget.
|
13
|
+
attr_accessor :effect
|
12
14
|
|
13
15
|
# Initialize method of {Gadget} instance.
|
14
16
|
# @param [Integer] offset The relative address offset of this gadget.
|
@@ -19,17 +21,19 @@ module OneGadget
|
|
19
21
|
def initialize(offset, **options)
|
20
22
|
@offset = offset
|
21
23
|
@constraints = options[:constraints] || []
|
24
|
+
@effect = options[:effect]
|
22
25
|
end
|
23
26
|
|
24
27
|
# Show gadget in a pretty way.
|
25
28
|
def inspect
|
26
|
-
str =
|
29
|
+
str = OneGadget::Helper.hex(offset)
|
30
|
+
str += effect ? "\t#{effect}\n" : "\n"
|
27
31
|
unless constraints.empty?
|
28
32
|
str += "#{OneGadget::Helper.colorize('constraints')}:\n "
|
29
33
|
str += constraints.join("\n ")
|
30
34
|
end
|
31
35
|
str.gsub!(/0x[\da-f]+/) { |s| OneGadget::Helper.colorize(s, sev: :integer) }
|
32
|
-
OneGadget::ABI.
|
36
|
+
OneGadget::ABI.all.each { |reg| str.gsub!(reg, OneGadget::Helper.colorize(reg, sev: :reg)) }
|
33
37
|
str + "\n"
|
34
38
|
end
|
35
39
|
end
|
data/lib/one_gadget/helper.rb
CHANGED
@@ -132,8 +132,8 @@ module OneGadget
|
|
132
132
|
# @return [String]
|
133
133
|
# Only supports :amd64, :i386 now.
|
134
134
|
def architecture(file)
|
135
|
-
str = `
|
136
|
-
return :amd64 if str.include?('
|
135
|
+
str = `readelf -h #{::Shellwords.escape(file)}`
|
136
|
+
return :amd64 if str.include?('X86-64')
|
137
137
|
return :i386 if str.include?('Intel 80386')
|
138
138
|
:unknown
|
139
139
|
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.3.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-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -102,9 +102,11 @@ files:
|
|
102
102
|
- lib/one_gadget/builds/libc-2.23-926eb99d49cab2e5622af38ab07395f5b32035e9.rb
|
103
103
|
- lib/one_gadget/builds/libc-2.23-edceed30099baad51871c5fc277daf9b74dc726a.rb
|
104
104
|
- lib/one_gadget/emulators/amd64.rb
|
105
|
+
- lib/one_gadget/emulators/i386.rb
|
105
106
|
- lib/one_gadget/emulators/instruction.rb
|
106
107
|
- lib/one_gadget/emulators/lambda.rb
|
107
108
|
- lib/one_gadget/emulators/processor.rb
|
109
|
+
- lib/one_gadget/emulators/x86.rb
|
108
110
|
- lib/one_gadget/fetcher.rb
|
109
111
|
- lib/one_gadget/fetchers/amd64.rb
|
110
112
|
- lib/one_gadget/fetchers/base.rb
|