# -*- coding: binary -*-

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
