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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 25904f1508d192a00e1702d7adb48d52af589ae0
4
- data.tar.gz: abe1ec3e6b3f57420326aad5100a3a6d4a260b6b
3
+ metadata.gz: 3cc62d276055fdb8dc5a968da82d417248f07d4b
4
+ data.tar.gz: 9b102faa2884dfb094e2c8b4a1df5f32fb7f5eaf
5
5
  SHA512:
6
- metadata.gz: 0d8593c144d504c00ea3f1e3b80397391012a3414054bdc586c52340830b22b7bb7ec57b054c20d6198ded00b25443dbe42225f2804da504474d531777ba922e
7
- data.tar.gz: a06dce9ee9693fe06422000c6d7105ab55655fbdd79b4edcf69dd9f2a618b9b5f4e5c041941f809fa251296825e63e7ae2f4c90a401ac3d20d05dbe37b8138ac
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
- # offset: 0x4526a
44
+ # 0x4526a execve("/bin/sh", rsp+0x30, environ)
45
45
  # constraints:
46
46
  # [rsp+0x30] == NULL
47
- #
48
- # offset: 0xef6c4
47
+ #
48
+ # 0xef6c4 execve("/bin/sh", rsp+0x50, environ)
49
49
  # constraints:
50
50
  # [rsp+0x50] == NULL
51
- #
52
- # offset: 0xf0567
51
+ #
52
+ # 0xf0567 execve("/bin/sh", rsp+0x70, environ)
53
53
  # constraints:
54
54
  # [rsp+0x70] == NULL
55
- #
56
- # offset: 0xf5b10
55
+ #
56
+ # 0xcc543 execve("/bin/sh", rcx, r12)
57
57
  # constraints:
58
- # [rbp-0xf8] == NULL
59
- # rcx == NULL
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
- # offset: 0x3ac69
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
- # offset: 0x5fbbe
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
- # offset: 0x12036c
76
+ #
77
+ # 0x5fbc6 execl("/bin/sh", [esp])
73
78
  # constraints:
74
79
  # esi is the address of `rw-p` area of libc
75
- # eax == NULL
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
 
@@ -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
- # Only support x86-64 now.
9
- def registers
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
- 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'])
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/processor'
2
- require 'one_gadget/emulators/instruction'
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 < 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.
7
+ class Amd64 < X86
8
+ # Instantiate an {Amd64} object.
11
9
  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
- ]
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
- ret = Lambda.new('tmp')
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
- ret.deref_count += 1
101
+ deref_count = 1
91
102
  end
92
103
  return Integer(arg) if OneGadget::Helper.integer?(arg)
93
104
  sign = arg =~ /[+-]/
94
- raise ArgumentError, "Not support #{arg}" if sign && !OneGadget::Helper.integer?(arg[sign..-1])
105
+ val = 0
95
106
  if sign
96
- ret.immi = Integer(arg[sign..-1])
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
- ret.obj = predefined[arg] || arg
100
- ret
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
@@ -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
- bin_sh_hex = str_offset('/bin/sh').to_s(16)
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
- constraints = gen_constraints(processor)
21
- next nil if constraints.nil? # impossible be a gadget
22
- OneGadget::Gadget::Gadget.new(offset, constraints: constraints)
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 gen_constraints(processor)
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
- should_null(processor.registers['rsi'].to_s),
34
- should_null(processor.registers['rdx'].to_s, allow_global: true)
35
- ].compact
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 = `objdump -w -d -M intel #{file}|egrep 'call.*<exec[^+]*>$' -B 20`.split('--').map do |cand|
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
- lines = assembly.lines
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
- # remove lines before and with -c appears
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
- next cand unless lines.last.include?('execl')
26
- # Find the last three +push+, or mov [esp+0x8], .*
27
- # Make it call +execl("/bin/sh", "sh", NULL)+.
28
- if cand.include?('esp+0x8')
29
- to_rm = lines.index { |c| c.include?('esp+0x8') }
30
- else
31
- push_cnt = 0
32
- to_rm = lines.rindex do |c|
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
- lines = lines[to_rm..-1] unless to_rm.nil?
38
- lines.join
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
@@ -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 = format("#{OneGadget::Helper.colorize('offset', sev: :sym)}: 0x%x\n", offset)
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.registers.each { |reg| str.gsub!(reg, OneGadget::Helper.colorize(reg, sev: :reg)) }
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
@@ -132,8 +132,8 @@ module OneGadget
132
132
  # @return [String]
133
133
  # Only supports :amd64, :i386 now.
134
134
  def architecture(file)
135
- str = `file #{::Shellwords.escape(file)}`
136
- return :amd64 if str.include?('x86-64')
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
@@ -1,3 +1,3 @@
1
1
  module OneGadget
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.3.0'.freeze
3
3
  end
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.2.0
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-17 00:00:00.000000000 Z
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