metasploit-framework/lib/rex/elfscan/scanner.rb

227 lines
5.1 KiB
Ruby

# -*- coding: binary -*-
require 'metasm'
module Rex
module ElfScan
module Scanner
class Generic
attr_accessor :elf, :regex
def initialize(elf)
self.elf = elf
end
def config(param)
end
def scan(param)
config(param)
$stdout.puts "[#{param['file']}]"
elf.program_header.each do |program_header|
# Scan only loadable segment entries in the program header table
if program_header.p_type == Rex::ElfParsey::ElfBase::PT_LOAD
hits = scan_segment(program_header, param)
hits.each do |hit|
rva = hit[0]
message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
$stdout.puts elf.ptr_s(rva) + " " + message
if(param['disasm'])
message.gsub!("; ", "\n")
if message.include?("retn")
message.gsub!("retn", "ret")
end
begin
d2 = Metasm::Shellcode.assemble(Metasm::Ia32.new, message).disassemble
rescue Metasm::ParseError
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, [message].pack('H*'))
end
addr = 0
while ((di = d2.disassemble_instruction(addr)))
disasm = "0x%08x\t" % (rva + addr)
disasm << di.instruction.to_s
$stdout.puts disasm
addr = di.next_addr
end
end
end
end
end
end
def scan_segment(program_header, param={})
[]
end
end
class JmpRegScanner < Generic
def config(param)
regnums = param['args']
# build a list of the call bytes
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
jmps = _build_byte_list(0xe0, regnums)
pushs1 = _build_byte_list(0x50, regnums)
pushs2 = _build_byte_list(0xf0, regnums)
regexstr = '('
if !calls.empty?
regexstr += "\xff[#{calls}]|"
end
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
self.regex = Regexp.new(regexstr, nil, 'n')
end
# build a list for regex of the possible bytes, based on a base
# byte and a list of register numbers..
def _build_byte_list(base, regnums)
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
end
def _ret_size(offset)
case elf.read(offset, 1)
when "\xc3"
return 1
when "\xc2"
return 3
end
raise "wtf"
end
def _parse_ret(data)
if data.length == 1
return "ret"
else
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
end
end
def scan_segment(program_header, param={})
offset = program_header.p_offset
hits = []
while (offset = elf.index(regex, offset)) != nil
rva = elf.offset_to_rva(offset)
message = ''
parse_ret = false
byte1 = elf.read(offset, 1).unpack('C')[0]
if byte1 == 0xff
byte2 = elf.read(offset+1, 1).unpack('C')[0]
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
case byte2 & 0xf8
when 0xd0
message = "call #{regname}"
offset += 2
when 0xe0
message = "jmp #{regname}"
offset += 2
when 0xf0
retsize = _ret_size(offset+2)
message = "push #{regname}; " + _parse_ret(elf.read(offset+2, retsize))
offset += 2 + retsize
else
raise "wtf"
end
else
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
retsize = _ret_size(offset+1)
message = "push #{regname}; " + _parse_ret(elf.read(offset+1, retsize))
offset += 1 + retsize
end
hits << [ rva, message ]
end
return hits
end
end
class PopPopRetScanner < JmpRegScanner
def config(param)
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", nil, 'n')
end
def scan_segment(program_header, param={})
offset = program_header.p_offset
hits = []
while offset < program_header.p_offset + program_header.p_filesz &&
(offset = elf.index(regex, offset)) != nil
rva = elf.offset_to_rva(offset)
message = ''
pops = elf.read(offset, 2)
reg1 = Rex::Arch::X86.reg_name32(pops[0,1].unpack('C*')[0] & 0x7)
reg2 = Rex::Arch::X86.reg_name32(pops[1,1].unpack('C*')[0] & 0x7)
message = "pop #{reg1}; pop #{reg2}; "
retsize = _ret_size(offset+2)
message += _parse_ret(elf.read(offset+2, retsize))
offset += 2 + retsize
hits << [ rva, message ]
end
return hits
end
end
class RegexScanner < JmpRegScanner
def config(param)
self.regex = Regexp.new(param['args'], nil, 'n')
end
def scan_segment(program_header, param={})
offset = program_header.p_offset
hits = []
while offset < program_header.p_offset + program_header.p_filesz &&
(offset = elf.index(regex, offset)) != nil
idx = offset
buf = ''
mat = nil
while (! (mat = buf.match(regex)))
buf << elf.read(idx, 1)
idx += 1
end
rva = elf.offset_to_rva(offset)
hits << [ rva, buf.unpack("H*") ]
offset += buf.length
end
return hits
end
end
end
end
end