consider jmp case in amd64

This commit is contained in:
david942j 2017-02-20 23:26:46 +08:00
parent b87178a9ab
commit a42f01cfa1
8 changed files with 57 additions and 20 deletions

View File

@ -5,10 +5,14 @@ module OneGadget
module ClassMethods
LINUX_X86_32 = %w(eax ebx ecx edx edi esi ebp esp).freeze
LINUX_X86_64 = LINUX_X86_32 + %w(rax rbx rcx rdx rdi rsi rbp rsp) + 7.upto(15).map { |i| "r#{i}" }
# Registers' name in amd64.
# @return [Array<String>] List of registers.
def amd64
LINUX_X86_64
end
# Registers' name in i386.
# @return [Array<String>] List of registers.
def i386
LINUX_X86_32
end

View File

@ -2,7 +2,12 @@ require 'one_gadget/gadget'
# Ubuntu GLIBC 2.23-0ubuntu5
# ELF 64-bit LSB shared object, x86-64
build_id = File.basename(__FILE__, '.rb').split('-').last
OneGadget::Gadget.add(build_id, 0x4526a, constraints: ['[rsp+0x30] == NULL'])
OneGadget::Gadget.add(build_id, 0xef6c4, constraints: ['[rsp+0x50] == NULL'])
OneGadget::Gadget.add(build_id, 0xf0567, constraints: ['[rsp+0x70] == NULL'])
OneGadget::Gadget.add(build_id, 0xf5b10, constraints: ['[rbp-0xf8] == NULL', 'rcx == NULL'])
OneGadget::Gadget.add(build_id, 0x4526a, constraints: ['[rsp+0x30] == NULL'],
effect: 'execve("/bin/sh", rsp+0x30, environ)')
OneGadget::Gadget.add(build_id, 0xef6c4, constraints: ['[rsp+0x50] == NULL'],
effect: 'execve("/bin/sh", rsp+0x50, environ)')
OneGadget::Gadget.add(build_id, 0xf0567, constraints: ['[rsp+0x70] == NULL'],
effect: 'execve("/bin/sh", rsp+0x70, environ)')
OneGadget::Gadget.add(build_id, 0xf5b10, constraints: ['[rbp-0xf8] == NULL || [[rbp-0xf8]] == NULL',
'rcx == NULL || [rcx] == NULL'],
effect: 'execve("/bin/sh", rcx, [rbp-0xf8])')

View File

@ -27,6 +27,7 @@ module OneGadget
def process(cmd)
inst, args = parse(cmd)
return registers[pc] = args[0] if inst.inst == 'call'
return if inst.inst == 'jmp' # believe the fetcher has handled jmp.
sym = "inst_#{inst.inst}".to_sym
send(sym, *args)
end
@ -40,7 +41,8 @@ module OneGadget
Instruction.new('add', 2),
Instruction.new('sub', 2),
Instruction.new('push', 1),
Instruction.new('call', 1)
Instruction.new('call', 1),
Instruction.new('jmp', 1)
]
end

View File

@ -7,13 +7,7 @@ module OneGadget
# Gadgets for amd64 glibc.
# @return [Array<OneGadget::Gadget::Gadget>] Gadgets found.
def find
bin_sh_hex = str_offset('/bin/sh').to_s(16)
cands = candidates do |candidate|
next false unless candidate.include?(bin_sh_hex) # works in x86-64
next false unless candidate.lines.last.include?('execve') # only care execve
true
end
cands.map do |candidate|
candidates.map do |candidate|
processor = OneGadget::Emulators::Amd64.new
candidate.lines.each { |l| processor.process(l) }
offset = offset_of(candidate)
@ -25,6 +19,31 @@ module OneGadget
private
def candidates
bin_sh_hex = str_offset('/bin/sh').to_s(16)
cands = super do |candidate|
next false unless candidate.include?(bin_sh_hex) # works in x86-64
next false unless candidate.lines.last.include?('execve') # only care execve
true
end
# find gadgets in form:
# lea rdi, '/bin/sh'
# jmp xxx
# xxx:
# ...
# call execve
cands2 = `#{objdump_cmd}|egrep 'rdi.*# #{bin_sh_hex}' -A 1`.split('--').map do |cand|
cand = cand.lines.map(&:strip).reject(&:empty?)
next nil unless cand.last.include?('jmp')
jmp_addr = cand.last.scan(/jmp\s+([\da-f]+)\s/)[0][0].to_i(16)
dump = `#{objdump_cmd(start: jmp_addr, stop: jmp_addr + 100)}|egrep '[0-9a-f]+:'`
remain = dump.lines.map(&:strip).reject(&:empty?)
remain = remain[0..remain.index { |r| r.match(/call.*<execve[^+]*>/) }]
[cand + remain].join("\n")
end.compact
cands + cands2
end
def resolve(processor)
# check rdi should always related to rip
return unless processor.registers['rdi'].to_s.include?('rip')

View File

@ -22,7 +22,7 @@ module OneGadget
# @return [Array<String>]
# Each +String+ returned is multi-lines of assembly code.
def candidates(&block)
cands = `objdump -w -d -M intel "#{file}"|egrep 'call.*<exec[^+]*>$' -B 20`.split('--').map do |cand|
cands = `#{objdump_cmd}|egrep 'call.*<exec[^+]*>$' -B 20`.split('--').map do |cand|
cand.lines.map(&:strip).reject(&:empty?).join("\n")
end
# remove all calls, jmps
@ -33,6 +33,13 @@ module OneGadget
private
def objdump_cmd(start: nil, stop: nil)
cmd = %(objdump -w -d -M intel "#{file}")
cmd.concat(" --start-address #{start}") if start
cmd.concat(" --stop-address #{stop}") if stop
cmd
end
def slice_prefix(cands)
cands.map do |cand|
lines = cand.lines

View File

@ -55,16 +55,16 @@ module OneGadget
arg1 = processor.stack[cur_top + 4]
arg2 = processor.stack[cur_top + 8]
options = if call.include?('execve')
valid_execve(arg1, arg2, rw_base: rw_base)
resolve_execve(arg1, arg2, rw_base: rw_base)
elsif call.include?('execl')
valid_execl(arg1, arg2, rw_base: rw_base, sh: bin_sh - 5)
resolve_execl(arg1, arg2, rw_base: rw_base, sh: bin_sh - 5)
end
return nil if options.nil?
options[:constraints].unshift("#{rw_base} is the address of `rw-p` area of libc")
options
end
def valid_execl(arg1, arg2, rw_base: nil, sh: 0)
def resolve_execl(arg1, arg2, rw_base: nil, sh: 0)
args = []
arg = arg1.to_s
if arg.include?(sh.to_s(16))
@ -77,7 +77,7 @@ module OneGadget
{ constraints: ["#{arg} == NULL"], effect: %(execl("/bin/sh", #{args.join(', ')})) }
end
def valid_execve(arg1, arg2, rw_base: nil)
def resolve_execve(arg1, arg2, rw_base: nil)
# arg1 == NULL || [arg1] == NULL
# arg2 == NULL or arg2 is environ
cons = [should_null(arg1.to_s)]

View File

@ -132,8 +132,8 @@ module OneGadget
# @return [String]
# Only supports :amd64, :i386 now.
def architecture(file)
str = `file #{::Shellwords.escape(file)}`
return :amd64 if str.include?('x86-64')
str = `readelf -h #{::Shellwords.escape(file)}`
return :amd64 if str.include?('X86-64')
return :i386 if str.include?('Intel 80386')
:unknown
end

View File

@ -9,7 +9,7 @@ describe 'one_gadget' do
describe 'from file' do
it 'libc-2.19' do
path = @data_path['libc-2.19-cf699a15caae64f50311fc4655b86dc39a479789.so']
expect(OneGadget.gadgets(file: path)).to eq [0x4647c, 0xe5765, 0xe66bd]
expect(OneGadget.gadgets(file: path)).to eq [0x4647c, 0xc1ba3, 0xe4968, 0xe5765, 0xe66bd]
end
it 'libc-2.24' do