Imported msfmachscan from Darren Kemp
git-svn-id: file:///home/svn/framework3/trunk@5682 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
parent
b3c3b29ed1
commit
1310e0e94c
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
module Rex
|
||||
module MachParsey
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/machparsey/mach'
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
module Rex
|
||||
module MachParsey
|
||||
|
||||
class MachError < ::RuntimeError
|
||||
end
|
||||
|
||||
class MachParseError < MachError
|
||||
end
|
||||
|
||||
class MachHeaderError < MachParseError
|
||||
end
|
||||
|
||||
class ProgramHeaderError < MachParseError
|
||||
end
|
||||
|
||||
class BoundsError < MachError
|
||||
end
|
||||
|
||||
#class WtfError < MachError
|
||||
#end
|
||||
|
||||
class FatError < ::RuntimeError
|
||||
end
|
||||
|
||||
class FatParseError < FatError
|
||||
end
|
||||
|
||||
class FatHeaderError < FatParseError
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rex/machparsey/machbase'
|
||||
require 'rex/machparsey/exceptions'
|
||||
require 'rex/image_source'
|
||||
|
||||
module Rex
|
||||
module MachParsey
|
||||
|
||||
|
||||
class Mach < MachBase
|
||||
attr_accessor :mach_header, :segments, :isource, :bits, :endian, :arch, :fat_offset
|
||||
|
||||
def initialize(isource, offset = 0, fat = false)
|
||||
_parse_mach_header(isource, offset)
|
||||
if fat == true
|
||||
self.fat_offset = offset
|
||||
else
|
||||
self.fat_offset = 0
|
||||
end
|
||||
|
||||
self.isource = isource
|
||||
end
|
||||
|
||||
def _parse_mach_header(isource, offset)
|
||||
self.mach_header = MachHeader.new(isource.read(offset, MACH_HEADER_SIZE_64))
|
||||
bits = mach_header.bits
|
||||
endian = mach_header.endian
|
||||
ncmds = mach_header.ncmds
|
||||
|
||||
if bits == BITS_32
|
||||
offset += MACH_HEADER_SIZE
|
||||
else
|
||||
offset += MACH_HEADER_SIZE_64
|
||||
end
|
||||
|
||||
|
||||
segments = []
|
||||
ncmds.times do
|
||||
load_command = LoadCommand.new(isource.read(offset, LOAD_COMMAND_SIZE), endian)
|
||||
|
||||
case load_command.cmd
|
||||
when LC_SEGMENT
|
||||
segments << Segment.new(isource.read(offset, SEGMENT_COMMAND_SIZE), bits, endian)
|
||||
when LC_SEGMENT_64
|
||||
segments << Segment.new(isource.read(offset, SEGMENT_COMMAND_SIZE_64), bits, endian)
|
||||
end
|
||||
|
||||
offset += load_command.cmdsize
|
||||
end
|
||||
|
||||
self.mach_header = mach_header
|
||||
self.segments = segments
|
||||
self.isource = isource
|
||||
self.bits = bits
|
||||
self.endian = endian
|
||||
|
||||
return segments
|
||||
end
|
||||
|
||||
def self.new_from_file(filename, disk_backed = false)
|
||||
|
||||
file = ::File.open(filename, "rb")
|
||||
|
||||
if disk_backed
|
||||
return self.new(ImageSource::Disk.new(file))
|
||||
else
|
||||
obj = new_from_string(file.read)
|
||||
file.close
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
def self.new_from_string(data)
|
||||
return self.new(ImageSource::Memory.new(data))
|
||||
end
|
||||
|
||||
def ptr_64?
|
||||
mach_header.bits == BITS_64
|
||||
end
|
||||
|
||||
def ptr_32?
|
||||
ptr_64? == false
|
||||
end
|
||||
|
||||
def ptr_s(vaddr)
|
||||
(ptr_32?) ? ("0x%.8x" % vaddr) : ("0x%.16x" % vaddr)
|
||||
end
|
||||
|
||||
def read(offset, len)
|
||||
isource.read(offset, len)
|
||||
end
|
||||
|
||||
def index(*args)
|
||||
isource.index(*args)
|
||||
end
|
||||
|
||||
def close
|
||||
isource.close
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Fat < FatBase
|
||||
attr_accessor :fat_header, :fat_archs, :machos, :isource
|
||||
|
||||
def initialize(isource, offset = 0)
|
||||
self.fat_archs = []
|
||||
self.machos = []
|
||||
self.isource = isource
|
||||
self.fat_header = FatHeader.new(isource.read(offset, FAT_HEADER_SIZE))
|
||||
|
||||
if !self.fat_header
|
||||
raise FatHeaderError, "Could not parse FAT header"
|
||||
end
|
||||
|
||||
print "Detected " + self.fat_header.nfat_arch.to_s + " archs in binary.\n"
|
||||
|
||||
offset += FAT_HEADER_SIZE
|
||||
|
||||
self.fat_header.nfat_arch.times do
|
||||
fat_arch = FatArch.new(isource.read(offset, FAT_ARCH_SIZE), self.fat_header.endian)
|
||||
self.fat_archs << fat_arch
|
||||
self.machos << Mach.new(isource, fat_arch.offset, true)
|
||||
offset += FAT_ARCH_SIZE
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
#this is useful for debugging but we don't use it for anything.
|
||||
def _parse_fat_header(isource, offset)
|
||||
archs = []
|
||||
nfat_arch = self.fat_header.nfat_arch
|
||||
|
||||
print "Number of archs in binary: " + nfat_arch.to_s + "\n"
|
||||
|
||||
nfat_arch.times do
|
||||
arch = FatArch.new(isource.read(offset, FAT_ARCH_SIZE), self.endian)
|
||||
|
||||
case arch.cpu_type
|
||||
|
||||
when CPU_TYPE_I386
|
||||
print "i386\n"
|
||||
|
||||
when CPU_TYPE_X86_64
|
||||
print "x86_64\n"
|
||||
|
||||
when CPU_TYPE_ARM
|
||||
print "Arm\n"
|
||||
|
||||
when CPU_TYPE_POWERPC
|
||||
print "Power PC\n"
|
||||
|
||||
when CPU_TYPE_POWERPC64
|
||||
print "Power PC 64\n"
|
||||
end
|
||||
|
||||
offset += FAT_ARCH_SIZE
|
||||
end
|
||||
end
|
||||
|
||||
def self.new_from_file(filename, disk_backed = false)
|
||||
|
||||
file = ::File.open(filename, "rb")
|
||||
|
||||
if disk_backed
|
||||
return self.new(ImageSource::Disk.new(file))
|
||||
else
|
||||
obj = new_from_string(file.read)
|
||||
file.close
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.new_from_string(data)
|
||||
return self.new(ImageSource::Memory.new(data))
|
||||
end
|
||||
|
||||
def ptr_64?
|
||||
mach_header.bits == BITS_64
|
||||
end
|
||||
|
||||
def ptr_32?
|
||||
ptr_64? == false
|
||||
end
|
||||
|
||||
def ptr_s(vaddr)
|
||||
(ptr_32?) ? ("0x%.8x" % vaddr) : ("0x%.16x" % vaddr)
|
||||
end
|
||||
|
||||
def read(offset, len)
|
||||
isource.read(offset, len)
|
||||
end
|
||||
|
||||
def index(*args)
|
||||
isource.index(*args)
|
||||
end
|
||||
|
||||
def close
|
||||
isource.close
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,408 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rex/struct2'
|
||||
|
||||
module Rex
|
||||
module MachParsey
|
||||
|
||||
require 'rex/machparsey/exceptions'
|
||||
require 'rex/struct2'
|
||||
|
||||
class GenericStruct
|
||||
attr_accessor :struct
|
||||
def initialize(_struct)
|
||||
self.struct = _struct
|
||||
end
|
||||
|
||||
# Access a value
|
||||
def v
|
||||
struct.v
|
||||
end
|
||||
|
||||
# Access a value by array
|
||||
def [](*args)
|
||||
struct[*args]
|
||||
end
|
||||
|
||||
# Obtain an array of all fields
|
||||
def keys
|
||||
struct.keys
|
||||
end
|
||||
|
||||
def method_missing(meth, *args)
|
||||
v[meth.to_s] || (raise NoMethodError.new, meth)
|
||||
end
|
||||
end
|
||||
|
||||
class GenericHeader < GenericStruct
|
||||
end
|
||||
|
||||
BITS_32 = 0
|
||||
BITS_64 = 1
|
||||
ENDIAN_LSB = 0
|
||||
ENDIAN_MSB = 1
|
||||
|
||||
class MachBase
|
||||
|
||||
MH_MAGIC = 0xfeedface
|
||||
MH_MAGIC_64 = 0xfeedfacf
|
||||
MH_CIGAM = 0xcefaedfe
|
||||
MH_CIGAM_64 = 0xcffaedfe
|
||||
MACH_HEADER_SIZE = 28
|
||||
MACH_HEADER_SIZE_64 = 32
|
||||
|
||||
|
||||
MACH_HEADER_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v', 'magic', 0],
|
||||
['uint32v', 'cputype', 0],
|
||||
['uint32v', 'cpusubtype',0],
|
||||
['uint32v', 'filetype', 0],
|
||||
['uint32v', 'ncmds', 0],
|
||||
['uint32v', 'sizeofcmds',0],
|
||||
['uint32v', 'flags', 0]
|
||||
)
|
||||
|
||||
MACH_HEADER_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n', 'magic', 0],
|
||||
['uint32n', 'cputype', 0],
|
||||
['uint32n', 'cpusubtype',0],
|
||||
['uint32n', 'filetype', 0],
|
||||
['uint32n', 'ncmds', 0],
|
||||
['uint32n', 'sizeofcmds',0],
|
||||
['uint32n', 'flags', 0]
|
||||
)
|
||||
|
||||
|
||||
MACH_HEADER_64_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v', 'magic', 0],
|
||||
['uint32v', 'cputype', 0],
|
||||
['uint32v', 'cpusubtype',0],
|
||||
['uint32v', 'filetype', 0],
|
||||
['uint32v', 'ncmds', 0],
|
||||
['uint32v', 'sizeofcmds',0],
|
||||
['uint32v', 'flags', 0],
|
||||
['uint32v', 'reserved', 0]
|
||||
)
|
||||
|
||||
MACH_HEADER_64_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n', 'magic', 0],
|
||||
['uint32n', 'cputype', 0],
|
||||
['uint32n', 'cpusubtype',0],
|
||||
['uint32n', 'filetype', 0],
|
||||
['uint32n', 'ncmds', 0],
|
||||
['uint32n', 'sizeofcmds',0],
|
||||
['uint32n', 'flags', 0],
|
||||
['uint32n', 'reserved', 0]
|
||||
)
|
||||
|
||||
#cpu types for Mach-O binaries
|
||||
CPU_TYPE_I386 = 0x7
|
||||
CPU_TYPE_X86_64 = 0x01000007
|
||||
CPU_TYPE_ARM = 0xC
|
||||
CPU_TYPE_POWERPC = 0x12
|
||||
CPU_TYPE_POWERPC64 = 0x01000012
|
||||
|
||||
CPU_SUBTYPE_LITTLE_ENDIAN = 0
|
||||
CPU_SUBTYPE_BIG_ENDIAN = 1
|
||||
|
||||
LC_SEGMENT = 0x1 #/* segment of this file to be mapped */
|
||||
LC_SYMTAB = 0x2 #/* link-edit stab symbol table info */
|
||||
LC_SYMSEG = 0x3 #/* link-edit gdb symbol table info (obsolete) */
|
||||
LC_THREAD = 0x4 #/* thread */
|
||||
LC_UNIXTHREAD = 0x5 #/* unix thread (includes a stack) */
|
||||
LC_LOADFVMLIB = 0x6 #/* load a specified fixed VM shared library */
|
||||
LC_IDFVMLIB = 0x7 #/* fixed VM shared library identification */
|
||||
LC_IDENT = 0x8 #/* object identification info (obsolete) */
|
||||
LC_FVMFILE = 0x9 #/* fixed VM file inclusion (internal use) */
|
||||
LC_PREPAGE = 0xa #/* prepage command (internal use) */
|
||||
LC_DYSYMTAB = 0xb #/* dynamic link-edit symbol table info */
|
||||
LC_LOAD_DYLIB = 0xc #/* load a dynamicly linked shared library */
|
||||
LC_ID_DYLIB = 0xd #/* dynamicly linked shared lib identification */
|
||||
LC_LOAD_DYLINKER = 0xe #/* load a dynamic linker */
|
||||
LC_ID_DYLINKER = 0xf #/* dynamic linker identification */
|
||||
LC_PREBOUND_DYLIB = 0x10 #/* modules prebound for a dynamicly */
|
||||
LC_SEGMENT_64 = 0x19 #/* segment of this file to be mapped */
|
||||
|
||||
|
||||
|
||||
|
||||
class MachHeader < GenericHeader
|
||||
attr_accessor :bits, :endian
|
||||
|
||||
def initialize(rawdata)
|
||||
mach_header = MACH_HEADER_LSB.make_struct
|
||||
if !mach_header.from_s(rawdata)
|
||||
raise MachHeaderError, "Could't access Mach-O Magic", caller
|
||||
end
|
||||
|
||||
if mach_header.v['magic'] == MH_MAGIC
|
||||
endian = ENDIAN_LSB
|
||||
bits = BITS_32
|
||||
mach_header = MACH_HEADER_LSB.make_struct
|
||||
elsif mach_header.v['magic'] == MH_CIGAM
|
||||
bits = BITS_32
|
||||
endian = ENDIAN_MSB
|
||||
mach_header = MACH_HEADER_MSB.make_struct
|
||||
elsif mach_header.v['magic'] == MH_MAGIC_64
|
||||
endian = ENDIAN_LSB
|
||||
bits = BITS_64
|
||||
mach_header = MACH_HEADER_LSB.make_struct
|
||||
elsif mach_header.v['magic'] == MH_CIGAM_64
|
||||
endian = ENDIAN_MSB
|
||||
bits = BITS_64
|
||||
mach_header = MACH_HEADER_MSB.make_struct
|
||||
else
|
||||
raise MachHeaderError, "Couldn't find Mach Magic", caller
|
||||
end
|
||||
|
||||
if !mach_header.from_s(rawdata)
|
||||
raise MachHeaderError, "Could't process Mach-O Header", caller
|
||||
end
|
||||
|
||||
self.struct = mach_header
|
||||
self.endian = endian
|
||||
self.bits = bits
|
||||
end
|
||||
end
|
||||
|
||||
LOAD_COMMAND_SIZE = 8
|
||||
|
||||
LOAD_COMMAND_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v','cmd',0],
|
||||
['uint32v','cmdsize',0]
|
||||
)
|
||||
|
||||
LOAD_COMMAND_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n','cmd',0],
|
||||
['uint32n','cmdsize',0]
|
||||
)
|
||||
|
||||
class LoadCommand < GenericHeader
|
||||
def initialize(rawdata, endian)
|
||||
|
||||
if endian == ENDIAN_MSB
|
||||
load_command = LOAD_COMMAND_MSB.make_struct
|
||||
else
|
||||
load_command = LOAD_COMMAND_LSB.make_struct
|
||||
end
|
||||
|
||||
if !load_command.from_s(rawdata)
|
||||
raise MachParseError, "Couldn't parse load command"
|
||||
end
|
||||
|
||||
self.struct = load_command
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
SEGMENT_COMMAND_SIZE = 56
|
||||
|
||||
SEGMENT_COMMAND_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v', 'cmd', 0],
|
||||
['uint32v', 'cmdsize', 0],
|
||||
['string', 'segname', 16, ''],
|
||||
['uint32v', 'vmaddr', 0],
|
||||
['uint32v', 'vmsize', 0],
|
||||
['uint32v', 'fileoff', 0],
|
||||
['uint32v', 'filesize', 0],
|
||||
['uint32v', 'maxprot', 0],
|
||||
['uint32v', 'initprot', 0],
|
||||
['uint32v', 'nsects', 0],
|
||||
['uint32v', 'flags', 0]
|
||||
)
|
||||
|
||||
SEGMENT_COMMAND_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n', 'cmd', 0],
|
||||
['uint32n', 'cmdsize', 0],
|
||||
['string', 'segname', 16, ''],
|
||||
['uint32n', 'vmaddr', 0],
|
||||
['uint32n', 'vmsize', 0],
|
||||
['uint32n', 'fileoff', 0],
|
||||
['uint32n', 'filesize', 0],
|
||||
['uint32n', 'maxprot', 0],
|
||||
['uint32n', 'initprot', 0],
|
||||
['uint32n', 'nsects', 0],
|
||||
['uint32n', 'flags', 0]
|
||||
)
|
||||
|
||||
SEGMENT_COMMAND_SIZE_64 = 72
|
||||
|
||||
SEGMENT_COMMAND_64_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v', 'cmd', 0],
|
||||
['uint32v', 'cmdsize', 0],
|
||||
['string', 'segname', 16, ''],
|
||||
['uint64v', 'vmaddr', 0],
|
||||
['uint64v', 'vmsize', 0],
|
||||
['uint64v', 'fileoff', 0],
|
||||
['uint64v', 'filesize', 0],
|
||||
['uint32v', 'maxprot', 0],
|
||||
['uint32v', 'initprot', 0],
|
||||
['uint32v', 'nsects', 0],
|
||||
['uint32v', 'flags', 0]
|
||||
)
|
||||
|
||||
SEGMENT_COMMAND_64_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n', 'cmd', 0],
|
||||
['uint32n', 'cmdsize', 0],
|
||||
['string', 'segname', 16, ''],
|
||||
['uint64n', 'vmaddr', 0],
|
||||
['uint64n', 'vmsize', 0],
|
||||
['uint64n', 'fileoff', 0],
|
||||
['uint64n', 'filesize', 0],
|
||||
['uint32n', 'maxprot', 0],
|
||||
['uint32n', 'initprot', 0],
|
||||
['uint32n', 'nsects', 0],
|
||||
['uint32n', 'flags', 0]
|
||||
)
|
||||
|
||||
class Segment < GenericHeader
|
||||
attr_accessor :_bits, :_endian
|
||||
|
||||
def initialize(rawdata, bits, endian)
|
||||
self._bits = bits
|
||||
|
||||
if bits == BITS_64
|
||||
if endian == ENDIAN_MSB
|
||||
segment_command = SEGMENT_COMMAND_64_MSB.make_struct
|
||||
else
|
||||
segment_command = SEGMENT_COMMAND_64_LSB.make_struct
|
||||
end
|
||||
else
|
||||
if endian == ENDIAN_MSB
|
||||
segment_command = SEGMENT_COMMAND_MSB.make_struct
|
||||
else
|
||||
segment_command = SEGMENT_COMMAND_LSB.make_struct
|
||||
end
|
||||
end
|
||||
if !segment_command.from_s(rawdata)
|
||||
raise MachParseError, "Couldn't parse segment command"
|
||||
end
|
||||
|
||||
self.struct = segment_command
|
||||
end
|
||||
|
||||
def Segname
|
||||
v['segname']
|
||||
end
|
||||
|
||||
def Vmaddr
|
||||
v['vmaddr']
|
||||
end
|
||||
|
||||
def Vmsize
|
||||
v['vmsize']
|
||||
end
|
||||
|
||||
def FileOff
|
||||
v['fileoff']
|
||||
end
|
||||
|
||||
def FileSize
|
||||
v['filesize']
|
||||
end
|
||||
end
|
||||
|
||||
class Thread < GenericHeader
|
||||
def initialize(rawdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
FAT_MAGIC = 0xcafebabe
|
||||
FAT_CIGAM = 0xbebafeca
|
||||
FAT_HEADER_SIZE = 8
|
||||
|
||||
FAT_HEADER_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v', 'magic', 0],
|
||||
['uint32v', 'nfat_arch',0]
|
||||
)
|
||||
|
||||
FAT_HEADER_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n', 'magic', 0],
|
||||
['uint32n', 'nfat_arch',0]
|
||||
)
|
||||
|
||||
|
||||
FAT_ARCH_SIZE = 20
|
||||
|
||||
FAT_ARCH_LSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32v', 'cpu_type', 0],
|
||||
['uint32v', 'cpu_subtype',0],
|
||||
['uint32v', 'offset', 0],
|
||||
['uint32v', 'size', 0],
|
||||
['uint32v', 'align', 0]
|
||||
)
|
||||
|
||||
FAT_ARCH_MSB = Rex::Struct2::CStructTemplate.new(
|
||||
['uint32n', 'cpu_type', 0],
|
||||
['uint32n', 'cpu_subtype',0],
|
||||
['uint32n', 'offset', 0],
|
||||
['uint32n', 'size', 0],
|
||||
['uint32n', 'align', 0]
|
||||
)
|
||||
|
||||
|
||||
class FatBase
|
||||
|
||||
class FatHeader < GenericHeader
|
||||
attr_accessor :nfat_arch, :endian, :exists
|
||||
|
||||
def initialize(rawdata)
|
||||
fat_header = FAT_HEADER_LSB.make_struct
|
||||
if !fat_header.from_s(rawdata)
|
||||
#raise something
|
||||
end
|
||||
|
||||
magic = fat_header.v['magic']
|
||||
if magic == FAT_MAGIC
|
||||
endian = ENDIAN_LSB
|
||||
elsif magic == FAT_CIGAM
|
||||
endian = ENDIAN_MSB
|
||||
fat_header = FAT_HEADER_MSB.make_struct
|
||||
if !fat_header.from_s(rawdata)
|
||||
raise FatHeaderError, "Could not parse FAT header"
|
||||
end
|
||||
else
|
||||
self.exists = 0
|
||||
return
|
||||
end
|
||||
|
||||
self.nfat_arch = fat_header.v['nfat_arch']
|
||||
self.struct = fat_header
|
||||
self.endian = endian
|
||||
end
|
||||
end
|
||||
|
||||
class FatArch < GenericHeader
|
||||
attr_accessor :cpu_type, :cpu_subtype, :offset, :size
|
||||
|
||||
def initialize(rawdata, endian)
|
||||
if endian == ENDIAN_LSB
|
||||
fat_arch = FAT_ARCH_LSB.make_struct
|
||||
else
|
||||
fat_arch = FAT_ARCH_MSB.make_struct
|
||||
end
|
||||
|
||||
if !fat_arch.from_s(rawdata)
|
||||
raise FatHeaderError, "Could not parse arch from FAT header"
|
||||
end
|
||||
|
||||
self.cpu_type = fat_arch.v['cpu_type']
|
||||
self.cpu_subtype = fat_arch.v['cpu_subtype']
|
||||
self.offset = fat_arch.v['offset']
|
||||
self.size = fat_arch.v['size']
|
||||
self.struct = fat_arch
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Thread < GenericHeader
|
||||
def initialize(rawdata)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
module Rex
|
||||
module MachScan
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/machscan/scanner'
|
|
@ -0,0 +1,216 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
module Rex
|
||||
module MachScan
|
||||
module Scanner
|
||||
class Generic
|
||||
|
||||
attr_accessor :mach, :fat, :regex
|
||||
|
||||
def initialize(binary)
|
||||
if binary.class == Rex::MachParsey::Mach
|
||||
self.mach = binary
|
||||
else
|
||||
self.fat = binary
|
||||
end
|
||||
end
|
||||
|
||||
def config(param)
|
||||
end
|
||||
|
||||
def scan(param)
|
||||
config(param)
|
||||
|
||||
$stdout.puts "[#{param['file']}]"
|
||||
|
||||
if !self.mach
|
||||
for mach in fat.machos
|
||||
if mach.mach_header.cputype == 0x7 #since we only support intel for the time being its all we process
|
||||
self.mach = mach
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.mach.segments.each do |segment|
|
||||
if segment.segname.include? "__TEXT"
|
||||
scan_segment(segment, param).each do |hit|
|
||||
vaddr = hit[0]
|
||||
message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
|
||||
$stdout.puts self.mach.ptr_s(vaddr - self.mach.fat_offset) + " " + message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def scan_segment(segment, 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)
|
||||
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 mach.read(offset, 1)
|
||||
when "\xc3"
|
||||
return 1
|
||||
when "\xc2"
|
||||
return 3
|
||||
end
|
||||
$stderr.puts("Invalid return instruction")
|
||||
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(segment, param={})
|
||||
base_addr = segment.vmaddr
|
||||
segment_offset = segment.fileoff
|
||||
offset = segment_offset
|
||||
|
||||
hits = []
|
||||
|
||||
while (offset = mach.index(regex, offset)) != nil
|
||||
|
||||
vaddr = base_addr + (offset - segment_offset)
|
||||
message = ''
|
||||
|
||||
parse_ret = false
|
||||
|
||||
byte1 = mach.read(offset, 1)[0]
|
||||
|
||||
if byte1 == 0xff
|
||||
byte2 = mach.read(offset+1, 1)[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(mach.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(mach.read(offset+1, retsize))
|
||||
offset += 1 + retsize
|
||||
end
|
||||
|
||||
hits << [ vaddr, 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..)")
|
||||
end
|
||||
|
||||
def scan_segment(segment, param={})
|
||||
base_addr = segment.vmaddr
|
||||
segment_offset = segment.fileoff
|
||||
offset = segment_offset
|
||||
|
||||
hits = []
|
||||
|
||||
while offset < segment.fileoff + segment.filesize && (offset = mach.index(regex, offset)) != nil
|
||||
|
||||
vaddr = base_addr + (offset - segment_offset)
|
||||
message = ''
|
||||
|
||||
pops = mach.read(offset, 2)
|
||||
reg1 = Rex::Arch::X86.reg_name32(pops[0] & 0x7)
|
||||
reg2 = Rex::Arch::X86.reg_name32(pops[1] & 0x7)
|
||||
|
||||
message = "pop #{reg1}; pop #{reg2}; "
|
||||
|
||||
retsize = _ret_size(offset+2)
|
||||
message += _parse_ret(mach.read(offset+2, retsize))
|
||||
|
||||
offset += 2 + retsize
|
||||
|
||||
hits << [ vaddr, message ]
|
||||
end
|
||||
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
class RegexScanner < JmpRegScanner
|
||||
|
||||
def config(param)
|
||||
self.regex = Regexp.new(param['args'])
|
||||
end
|
||||
|
||||
def scan_segment(segment, param={})
|
||||
base_addr = segment.vmaddr
|
||||
segment_offset = segment.fileoff
|
||||
offset = segment_offset
|
||||
|
||||
hits = []
|
||||
|
||||
while offset < segment.fileoff + segment.filesize && (offset = mach.index(regex, offset)) != nil
|
||||
|
||||
idx = offset
|
||||
buf = ''
|
||||
mat = nil
|
||||
|
||||
while (! (mat = buf.match(regex)))
|
||||
buf << mach.read(idx, 1)
|
||||
idx += 1
|
||||
end
|
||||
|
||||
vaddr = base_addr + (offset - segment_offset)
|
||||
|
||||
hits << [ vaddr, buf.unpack("H*") ]
|
||||
offset += buf.length
|
||||
end
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
msfbase = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
||||
$:.unshift(File.join(File.dirname(msfbase), 'lib'))
|
||||
|
||||
require 'rex/machparsey'
|
||||
require 'rex/machscan'
|
||||
require 'rex/arch/x86'
|
||||
require 'optparse'
|
||||
|
||||
|
||||
def opt2i(o)
|
||||
o.index("0x")==0 ? o.hex : o.to_i
|
||||
end
|
||||
|
||||
opt = OptionParser.new
|
||||
|
||||
opt.banner = "Usage: #{$PROGRAM_NAME} [mode] <options> [targets]"
|
||||
opt.separator('')
|
||||
opt.separator('Modes:')
|
||||
|
||||
worker = nil
|
||||
param = {}
|
||||
|
||||
opt.on('-j', '--jump [regA,regB,regC]', 'Search for jump equivalent instructions') do |t|
|
||||
# take csv of register names (like eax,ebx) and convert
|
||||
# them to an array of register numbers
|
||||
regnums = t.split(',').collect { |o| Rex::Arch::X86.reg_number(o) }
|
||||
worker = Rex::MachScan::Scanner::JmpRegScanner
|
||||
param['args'] = regnums
|
||||
end
|
||||
|
||||
opt.on('-p', '--poppopret', 'Search for pop+pop+ret combinations') do |t|
|
||||
worker = Rex::MachScan::Scanner::PopPopRetScanner
|
||||
param['args'] = t
|
||||
end
|
||||
|
||||
opt.on('-r', '--regex [regex]', 'Search for regex match') do |t|
|
||||
worker = Rex::MachScan::Scanner::RegexScanner
|
||||
param['args'] = t
|
||||
end
|
||||
|
||||
opt.separator('')
|
||||
opt.separator('Options:')
|
||||
|
||||
opt.on('-A', '--after [bytes]', 'Number of bytes to show after match (-a/-b)') do |t|
|
||||
param['after'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.on('-B', '--before [bytes]', 'Number of bytes to show before match (-a/-b)') do |t|
|
||||
param['before'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.on('-I', '--image-base [address]', 'Specify an alternate ImageBase') do |t|
|
||||
param['imagebase'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.on_tail("-h", "--help", "Show this message") do
|
||||
puts opt
|
||||
exit(0)
|
||||
end
|
||||
|
||||
opt.parse!
|
||||
|
||||
if (! worker)
|
||||
puts opt
|
||||
exit(0)
|
||||
end
|
||||
|
||||
ARGV.each do |file|
|
||||
|
||||
param['file'] = file
|
||||
|
||||
begin
|
||||
mach = Rex::MachParsey::Mach.new_from_file(file, true)
|
||||
o = worker.new(mach)
|
||||
o.scan(param)
|
||||
mach.close
|
||||
rescue Rex::MachParsey::MachHeaderError
|
||||
$stderr.puts("File is not a Mach-O binary, trying Fat..\n")
|
||||
fat = Rex::MachParsey::Fat.new_from_file(file, true)
|
||||
o = worker.new(fat)
|
||||
o.scan(param)
|
||||
fat.close
|
||||
rescue Errno::ENOENT
|
||||
$stderr.puts("File does not exist: #{file}")
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
#end
|
||||
|
Loading…
Reference in New Issue