one_gadget 1.2.0 → 1.3.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 +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
|
[![Build Status](https://travis-ci.org/david942j/one_gadget.svg?branch=master)](https://travis-ci.org/david942j/one_gadget)
|
2
|
-
![](http://ruby-gem-downloads-badge.herokuapp.com/one_gadget?type=total&color=orange)
|
2
|
+
[![Downloads](http://ruby-gem-downloads-badge.herokuapp.com/one_gadget?type=total&color=orange)](https://rubygems.org/gems/one_gadget)
|
3
3
|
[![Code Climate](https://codeclimate.com/github/david942j/one_gadget/badges/gpa.svg)](https://codeclimate.com/github/david942j/one_gadget)
|
4
4
|
[![Issue Count](https://codeclimate.com/github/david942j/one_gadget/badges/issue_count.svg)](https://codeclimate.com/github/david942j/one_gadget)
|
5
5
|
[![Test Coverage](https://codeclimate.com/github/david942j/one_gadget/badges/coverage.svg)](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
|
![from file](https://github.com/david942j/one_gadget/blob/master/examples/from_file.png?raw=true)
|
100
107
|
|
108
|
+
#### 32 bit
|
109
|
+
![from file](https://github.com/david942j/one_gadget/blob/master/examples/from_file_32bit.png?raw=true)
|
110
|
+
|
101
111
|
### Fetch gadgets from database
|
102
112
|
![build id](https://github.com/david942j/one_gadget/blob/master/examples/from_build_id.png?raw=true)
|
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
|