commit
8eba48ebc6
|
@ -7,6 +7,12 @@ Metrics/ClassLength:
|
|||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
|
||||
Rails:
|
||||
Enabled: false
|
||||
|
||||
|
|
1
Rakefile
1
Rakefile
|
@ -6,7 +6,6 @@ task default: %i(rubocop spec gen_builds_list)
|
|||
|
||||
RuboCop::RakeTask.new(:rubocop) do |task|
|
||||
task.patterns = ['lib/**/*.rb', 'spec/**/*.rb', 'bin/*']
|
||||
task.formatters = ['files']
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |task|
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
require 'one_gadget/emulators/processor'
|
||||
require 'one_gadget/emulators/instruction'
|
||||
|
||||
module OneGadget
|
||||
module Emulators
|
||||
# Emulator of amd64 instruction set.
|
||||
class Amd64 < Processor
|
||||
REGISTERS = %w(rax rbx rcx rdx rdi rsi rbp rsp rip) + 7.upto(15).map { |i| "r#{i}" }
|
||||
def initialize
|
||||
super(REGISTERS)
|
||||
end
|
||||
|
||||
def process(cmd)
|
||||
inst, args = parse(cmd)
|
||||
# where should this be defined..?
|
||||
return if inst.inst == 'call' # later
|
||||
case inst.inst
|
||||
when 'mov', 'lea'
|
||||
tar = args[0]
|
||||
src = OneGadget::Emulators::Lambda.parse(args[1], predefined: @registers)
|
||||
end
|
||||
src.ref! if inst.inst == 'lea'
|
||||
|
||||
@registers[tar] = src
|
||||
end
|
||||
|
||||
def instructions
|
||||
[
|
||||
Instruction.new('mov', 2),
|
||||
Instruction.new('lea', 2),
|
||||
Instruction.new('call', 1)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module OneGadget
|
||||
module Emulators
|
||||
# Define instruction name and it's argument count.
|
||||
class Instruction
|
||||
attr_reader :inst # @return [String]
|
||||
attr_reader :argc # @return [Integer]
|
||||
def initialize(inst, argc)
|
||||
@inst = inst
|
||||
@argc = argc
|
||||
end
|
||||
|
||||
def fetch_args(cmd)
|
||||
idx = cmd.index(inst)
|
||||
cmd = cmd[0...cmd.rindex('#')] if cmd.rindex('#')
|
||||
args = cmd[idx + inst.size..-1].split(',')
|
||||
raise ArgumentError, "Incorrect argument number in #{cmd}, expect: #{argc}" if args.size != argc
|
||||
args.map do |arg|
|
||||
arg.gsub(/QWORD|DWORD|WORD|BYTE|PTR/, '').strip
|
||||
end
|
||||
end
|
||||
|
||||
def match?(str)
|
||||
str.include?(inst)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,89 @@
|
|||
require 'one_gadget/helper'
|
||||
|
||||
module OneGadget
|
||||
module Emulators
|
||||
# A {Lambda} object can be:
|
||||
# 1. {String} # variable name
|
||||
# 2. {Numeric}
|
||||
# 3. {Lambda} + {Numeric}
|
||||
# 4. dereference {Lambda}
|
||||
class Lambda
|
||||
attr_accessor :obj # @return [String, Lambda]
|
||||
attr_accessor :immi # @return [Integer]
|
||||
attr_accessor :deref_count # @return [Integer] The times of dereference.
|
||||
def initialize(val)
|
||||
@immi = 0
|
||||
@obj = val
|
||||
@deref_count = 0
|
||||
end
|
||||
|
||||
def +(other)
|
||||
raise ArgumentError, 'Expect other to be Numeric.' unless other.is_a?(Numeric)
|
||||
if deref_count > 0
|
||||
ret = Lambda.new(self)
|
||||
else
|
||||
ret = Lambda.new(obj)
|
||||
ret.immi = immi
|
||||
end
|
||||
ret.immi += other
|
||||
ret
|
||||
end
|
||||
|
||||
def -(other)
|
||||
self.+(-other)
|
||||
end
|
||||
|
||||
def deref!
|
||||
@deref_count += 1
|
||||
end
|
||||
|
||||
def ref!
|
||||
raise ArgumentError, 'Cannot reference anymore!' if @deref_count <= 0
|
||||
@deref_count -= 1
|
||||
end
|
||||
|
||||
def deref
|
||||
ret = Lambda.new(obj)
|
||||
ret.immi = immi
|
||||
ret.deref_count = deref_count + 1
|
||||
ret
|
||||
end
|
||||
|
||||
def to_s
|
||||
str = ''
|
||||
str += '[' * deref_count
|
||||
str += obj.to_s unless obj.nil?
|
||||
str += OneGadget::Helper.hex(immi, psign: true) unless immi.zero?
|
||||
str += ']' * deref_count
|
||||
str
|
||||
end
|
||||
|
||||
class << self
|
||||
# Target: parse something like +[rsp+0x50]+ into a {Lambda} object.
|
||||
# @param [String] arg
|
||||
# @param [Hash{String => Lambda}] predefined
|
||||
# @return [OneGadget::Emulators::Lambda, Integer]
|
||||
# If +arg+ contains number only, return it.
|
||||
# Otherwise, return a {Lambda} object.
|
||||
# @example
|
||||
# parse('[rsp+0x50]') #=> #<Lambda @obj='rsp', @immi=80, @deref_count=1>
|
||||
def parse(arg, predefined: {})
|
||||
ret = Lambda.new('tmp')
|
||||
if arg[0] == '[' # a little hack because there should nerver something like +[[rsp+1]+2]+ to parse.
|
||||
arg = arg[1..-2]
|
||||
ret.deref_count += 1
|
||||
end
|
||||
return Integer(arg) if OneGadget::Helper.integer?(arg)
|
||||
sign = arg =~ /[+-]/
|
||||
raise ArgumentError, "Not support #{arg}" if sign && !OneGadget::Helper.integer?(arg[sign..-1])
|
||||
if sign
|
||||
ret.immi = Integer(arg[sign..-1])
|
||||
arg = arg[0, sign]
|
||||
end
|
||||
ret.obj = predefined[arg] || arg
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
require 'one_gadget/emulators/lambda'
|
||||
|
||||
module OneGadget
|
||||
# Instruction emulator to solve the constraint of gadgets.
|
||||
module Emulators
|
||||
# Base class of a processor.
|
||||
class Processor
|
||||
attr_reader :registers
|
||||
attr_accessor :stack
|
||||
def initialize(registers)
|
||||
@registers = registers.map { |reg| [reg, OneGadget::Emulators::Lambda.new(reg)] }.to_h
|
||||
@stack = []
|
||||
end
|
||||
|
||||
def parse(cmd)
|
||||
inst = instructions.find { |i| i.match?(cmd) }
|
||||
raise ArgumentError, "Not implemented instruction in #{cmd}" if inst.nil?
|
||||
[inst, inst.fetch_args(cmd)]
|
||||
end
|
||||
|
||||
def process(_cmd); raise NotImplementedError
|
||||
end
|
||||
|
||||
def instructions; raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
require 'one_gadget/fetchers/base'
|
||||
require 'one_gadget/emulators/amd64'
|
||||
module OneGadget
|
||||
module Fetcher
|
||||
# Fetcher for amd64.
|
||||
|
@ -13,10 +14,32 @@ module OneGadget
|
|||
true
|
||||
end
|
||||
cands.map do |candidate|
|
||||
convert_to_gadget(candidate) do |line|
|
||||
['rsi'].any? { |r| line.include?(r) }
|
||||
end
|
||||
end
|
||||
processor = OneGadget::Emulators::Amd64.new
|
||||
candidate.lines.each { |l| processor.process(l) }
|
||||
offset = offset_of(candidate)
|
||||
constraints = gen_constraints(processor)
|
||||
next nil if constraints.nil? # impossible be a gadget
|
||||
OneGadget::Gadget::Gadget.new(offset, constraints: constraints)
|
||||
end.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gen_constraints(processor)
|
||||
# check rdi should always related to rip
|
||||
return unless processor.registers['rdi'].to_s.include?('rip')
|
||||
# rsi or [rsi] should be zero
|
||||
[
|
||||
should_null(processor.registers['rsi'].to_s),
|
||||
should_null(processor.registers['rdx'].to_s, allow_global: true)
|
||||
].compact
|
||||
end
|
||||
|
||||
def should_null(str, allow_global: false)
|
||||
return nil if allow_global && str.include?('rip')
|
||||
ret = "[#{str}] == NULL"
|
||||
ret += " || #{str} == NULL" unless str.include?('rsp')
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,6 +54,11 @@ module OneGadget
|
|||
match.split.first.to_i(16)
|
||||
end
|
||||
|
||||
def offset_of(assembly)
|
||||
lines = assembly.lines
|
||||
lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
||||
end
|
||||
|
||||
def convert_to_gadget(assembly, &block)
|
||||
lines = assembly.lines
|
||||
offset = lines.first.scan(/^([\da-f]+):/)[0][0].to_i(16)
|
||||
|
|
|
@ -137,6 +137,37 @@ module OneGadget
|
|||
return :i386 if str.include?('Intel 80386')
|
||||
:unknown
|
||||
end
|
||||
|
||||
# Present number in hex format.
|
||||
# @param [Integer] val The number.
|
||||
# @param [Boolean] psign Need to show plus sign when +val >= 0+.
|
||||
# @return [String] string in hex format.
|
||||
# @example
|
||||
# hex(32) #=> 0x20
|
||||
# hex(32, psign: true) #=> +0x20
|
||||
# hex(-40) #=> -0x28
|
||||
# hex(0) #=> 0x0
|
||||
# hex(0, psign: true) #=> +0x0
|
||||
def hex(val, psign: false)
|
||||
return format("#{psign ? '+' : ''}0x%x", val) if val >= 0
|
||||
format('-0x%x', -val)
|
||||
end
|
||||
|
||||
# For checking a string is actually an integer.
|
||||
# @param [String] str String to be checked.
|
||||
# @return [Boolean] If +str+ can be converted into integer.
|
||||
# @example
|
||||
# Helper.integer? '1234'
|
||||
# # => true
|
||||
# Helper.integer? '0x1234'
|
||||
# # => true
|
||||
# Helper.integer? '0xheapoverflow'
|
||||
# # => false
|
||||
def integer?(str)
|
||||
true if Integer(str)
|
||||
rescue ArgumentError, TypeError
|
||||
false
|
||||
end
|
||||
end
|
||||
extend ClassMethods
|
||||
end
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
require 'one_gadget/emulators/amd64'
|
||||
|
||||
describe OneGadget::Emulators::Amd64 do
|
||||
before(:each) do
|
||||
@processor = OneGadget::Emulators::Amd64.new
|
||||
end
|
||||
|
||||
describe 'process' do
|
||||
it 'libc-2.24 gadget' do
|
||||
gadget = <<-EOS
|
||||
d67e5: 48 8b 05 b4 16 2c 00 mov rax,QWORD PTR [rip+0x2c16b4] # 397ea0 <_DYNAMIC+0x340>
|
||||
d67ec: 48 8d 74 24 70 lea rsi,[rsp+0x70]
|
||||
d67f1: 48 8d 3d 61 ab 08 00 lea rdi,[rip+0x8ab61] # 161359 <_nl_POSIX_name+0x154>
|
||||
d67f8: 48 8b 10 mov rdx,QWORD PTR [rax]
|
||||
d67fb: e8 70 1c fe ff call b8470 <execve>
|
||||
EOS
|
||||
gadget.lines.each { |s| @processor.process(s) }
|
||||
expect(@processor.registers['rsi'].to_s).to eq 'rsp+0x70'
|
||||
expect(@processor.registers['rdx'].to_s).to eq '[[rip+0x2c16b4]]'
|
||||
end
|
||||
|
||||
it 'mov' do
|
||||
gadget = <<-EOS
|
||||
mov rax, rdx
|
||||
mov rdx, [rax+0x10]
|
||||
mov rdi, [rdx]
|
||||
mov rdx, rax-0x30
|
||||
EOS
|
||||
gadget.lines.each { |s| @processor.process(s) }
|
||||
expect(@processor.registers['rdi'].to_s).to eq '[[rdx+0x10]]'
|
||||
expect(@processor.registers['rdx'].to_s).to eq 'rdx-0x30'
|
||||
expect(@processor.registers['rax'].to_s).to eq 'rdx'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
require 'one_gadget/emulators/instruction'
|
||||
describe OneGadget::Emulators::Instruction do
|
||||
before(:all) do
|
||||
@mov = OneGadget::Emulators::Instruction.new('mov', 2)
|
||||
@lea = OneGadget::Emulators::Instruction.new('lea', 2)
|
||||
@call = OneGadget::Emulators::Instruction.new('call', 1)
|
||||
end
|
||||
|
||||
it 'fetch_args' do
|
||||
expect(@mov.fetch_args(<<-'EOS')).to eq ['rax', '[rip+0x2c16b4]']
|
||||
d67e5: 48 8b 05 b4 16 2c 00 mov rax,QWORD PTR [rip+0x2c16b4] # 397ea0 <_DYNAMIC+0x340>
|
||||
EOS
|
||||
expect(@mov.fetch_args(<<-'EOS')).to eq ['rdx', '[rax]']
|
||||
d67f8: 48 8b 10 mov rdx,QWORD PTR [rax]
|
||||
EOS
|
||||
expect(@lea.fetch_args(<<-'EOS')).to eq ['rsi', '[rsp+0x70]']
|
||||
d67ec: 48 8d 74 24 70 lea rsi,[rsp+0x70]
|
||||
EOS
|
||||
expect(@lea.fetch_args(<<-'EOS')).to eq ['rdi', '[rip+0x8ab61]']
|
||||
d67f1: 48 8d 3d 61 ab 08 00 lea rdi,[rip+0x8ab61] # 161359 <_nl_POSIX_name+0x154>
|
||||
EOS
|
||||
expect(@call.fetch_args(<<-'EOS')).to eq ['b8470 <execve>']
|
||||
d67fb: e8 70 1c fe ff call b8470 <execve>
|
||||
EOS
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
require 'one_gadget/emulators/lambda'
|
||||
describe OneGadget::Emulators::Lambda do
|
||||
before(:each) do
|
||||
@rsp = OneGadget::Emulators::Lambda.new('rsp')
|
||||
end
|
||||
|
||||
describe '+' do
|
||||
it 'normal' do
|
||||
expect((@rsp + 0x50).to_s).to eq 'rsp+0x50'
|
||||
expect((@rsp - 0x50).to_s).to eq 'rsp-0x50'
|
||||
end
|
||||
|
||||
it 'mixed' do
|
||||
rax = OneGadget::Emulators::Lambda.new('rax')
|
||||
rax += 0x50
|
||||
expect(rax.to_s).to eq 'rax+0x50'
|
||||
rax += 0x50
|
||||
expect(rax.to_s).to eq 'rax+0xa0'
|
||||
rax -= 0xa0
|
||||
expect(rax.to_s).to eq 'rax'
|
||||
end
|
||||
end
|
||||
|
||||
it 'dereference' do
|
||||
drsp = @rsp.deref
|
||||
expect(@rsp.to_s).to eq 'rsp'
|
||||
expect(drsp.to_s).to eq '[rsp]'
|
||||
expect(drsp.deref.to_s).to eq '[[rsp]]'
|
||||
drsp.deref!
|
||||
expect(drsp.to_s).to eq '[[rsp]]'
|
||||
end
|
||||
|
||||
it 'mixed' do
|
||||
expect(((@rsp + 0x50).deref - 0x30).deref.to_s).to eq '[[rsp+0x50]-0x30]'
|
||||
end
|
||||
|
||||
it 'parse' do
|
||||
expect(OneGadget::Emulators::Lambda.parse('[rsp+0x50]').to_s).to eq '[rsp+0x50]'
|
||||
expect(OneGadget::Emulators::Lambda.parse('[rsp+80]').to_s).to eq '[rsp+0x50]'
|
||||
expect(OneGadget::Emulators::Lambda.parse('esp').to_s).to eq 'esp'
|
||||
expect(OneGadget::Emulators::Lambda.parse('esp-10').to_s).to eq 'esp-0xa'
|
||||
expect(OneGadget::Emulators::Lambda.parse('123')).to be 123
|
||||
expect(OneGadget::Emulators::Lambda.parse('0xabc123')).to be 0xabc123
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue