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 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