## -*- coding: utf-8 -*-
##
##  Jonathan Salwan - 2014-05-12 - ROPgadget tool
##
##  http://twitter.com/JonathanSalwan
##  http://shell-storm.org/project/ROPgadget/
##

from capstone   import *
from ctypes     import *

class MACH_HEADER(Structure):
    _fields_ = [
                ("magic",           c_uint),
                ("cputype",         c_uint),
                ("cpusubtype",      c_uint),
                ("filetype",        c_uint),
                ("ncmds",           c_uint),
                ("sizeofcmds",      c_uint),
                ("flags",           c_uint)
               ]

class LOAD_COMMAND(Structure):
    _fields_ = [
                ("cmd",             c_uint),
                ("cmdsize",         c_uint)
               ]

class SEGMENT_COMMAND(Structure):
    _fields_ = [
                ("cmd",             c_uint),
                ("cmdsize",         c_uint),
                ("segname",         c_ubyte * 16),
                ("vmaddr",          c_uint),
                ("vmsize",          c_uint),
                ("fileoff",         c_uint),
                ("filesize",        c_uint),
                ("maxprot",         c_uint),
                ("initprot",        c_uint),
                ("nsects",          c_uint),
                ("flags",           c_uint)
               ]

class SEGMENT_COMMAND64(Structure):
    _fields_ = [
                ("cmd",             c_uint),
                ("cmdsize",         c_uint),
                ("segname",         c_ubyte * 16),
                ("vmaddr",          c_ulonglong),
                ("vmsize",          c_ulonglong),
                ("fileoff",         c_ulonglong),
                ("filesize",        c_ulonglong),
                ("maxprot",         c_uint),
                ("initprot",        c_uint),
                ("nsects",          c_uint),
                ("flags",           c_uint)
               ]

class SECTION(Structure):
    _fields_ = [
                ("sectname",        c_ubyte * 16),
                ("segname",         c_ubyte * 16),
                ("addr",            c_uint),
                ("size",            c_uint),
                ("offset",          c_uint),
                ("align",           c_uint),
                ("reloff",          c_uint),
                ("nreloc",          c_uint),
                ("flags",           c_uint),
                ("reserved1",       c_uint),
                ("reserved2",       c_uint)
               ]

class SECTION64(Structure):
    _fields_ = [
                ("sectname",        c_ubyte * 16),
                ("segname",         c_ubyte * 16),
                ("addr",            c_ulonglong),
                ("size",            c_ulonglong),
                ("offset",          c_uint),
                ("align",           c_uint),
                ("reloff",          c_uint),
                ("nreloc",          c_uint),
                ("flags",           c_uint),
                ("reserved1",       c_uint),
                ("reserved2",       c_uint)
               ]

class MACHOFlags(object):
    CPU_TYPE_I386               = 0x7
    CPU_TYPE_X86_64             = (CPU_TYPE_I386 | 0x1000000)
    CPU_TYPE_MIPS               = 0x8
    CPU_TYPE_ARM                = 12
    CPU_TYPE_ARM64              = (CPU_TYPE_ARM | 0x1000000)
    CPU_TYPE_SPARC              = 14
    CPU_TYPE_POWERPC            = 18
    CPU_TYPE_POWERPC64          = (CPU_TYPE_POWERPC | 0x1000000)
    LC_SEGMENT                  = 0x1
    LC_SEGMENT_64               = 0x19
    S_ATTR_SOME_INSTRUCTIONS    = 0x00000400
    S_ATTR_PURE_INSTRUCTIONS    = 0x80000000

""" This class parses the Mach-O """
class MACHO(object):
    def __init__(self, binary):
        self.__binary = bytearray(binary)

        self.__machHeader   = None
        self.__rawLoadCmd   = None
        self.__sections_l   = []

        self.__setHeader()
        self.__setLoadCmd()

    def __setHeader(self):
        self.__machHeader = MACH_HEADER.from_buffer_copy(self.__binary)

        if self.getArchMode() == CS_MODE_32:
            self.__rawLoadCmd   = self.__binary[28:28+self.__machHeader.sizeofcmds]

        elif self.getArchMode() == CS_MODE_64:
            self.__rawLoadCmd   = self.__binary[32:32+self.__machHeader.sizeofcmds]

    def __setLoadCmd(self):
        base = self.__rawLoadCmd
        for i in range(self.__machHeader.ncmds):
            command = LOAD_COMMAND.from_buffer_copy(base)

            if command.cmd == MACHOFlags.LC_SEGMENT:
                segment = SEGMENT_COMMAND.from_buffer_copy(base)
                self.__setSections(segment, base[56:], 32)

            elif command.cmd == MACHOFlags.LC_SEGMENT_64:
                segment = SEGMENT_COMMAND64.from_buffer_copy(base)
                self.__setSections(segment, base[72:], 64)

            base = base[command.cmdsize:]

    def __setSections(self, segment, base, sizeHeader):
        for i in range(segment.nsects):
            if sizeHeader == 32:
                section = SECTION.from_buffer_copy(base)
                section.offset = segment.fileoff + section.addr - segment.vmaddr
                base = base[68:]
                self.__sections_l += [section]
            elif sizeHeader == 64:
                section = SECTION64.from_buffer_copy(base)
                section.offset = segment.fileoff + section.addr - segment.vmaddr
                base = base[80:]
                self.__sections_l += [section]

    def getEntryPoint(self):
        for section in self.__sections_l:
            if section.sectname[0:6] == "__text":
                return section.addr

    def getExecSections(self):
        ret = []
        for section in self.__sections_l:
            if section.flags & MACHOFlags.S_ATTR_SOME_INSTRUCTIONS or section.flags & MACHOFlags.S_ATTR_PURE_INSTRUCTIONS:
                ret +=  [{
                            "name"    : section.sectname,
                            "offset"  : section.offset,
                            "size"    : section.size,
                            "vaddr"   : section.addr,
                            "opcodes" : bytes(self.__binary[section.offset:section.offset+section.size])
                        }]
        return ret

    def getDataSections(self):
        ret = []
        for section in self.__sections_l:
            if not section.flags & MACHOFlags.S_ATTR_SOME_INSTRUCTIONS and not section.flags & MACHOFlags.S_ATTR_PURE_INSTRUCTIONS:
                ret +=  [{
                            "name"    : section.sectname,
                            "offset"  : section.offset,
                            "size"    : section.size,
                            "vaddr"   : section.addr,
                            "opcodes" : bytes(self.__binary[section.offset:section.offset+section.size])
                        }]
        return ret

    def getArch(self):
        if self.__machHeader.cputype == MACHOFlags.CPU_TYPE_I386 or self.__machHeader.cputype == MACHOFlags.CPU_TYPE_X86_64:
            return CS_ARCH_X86
        if self.__machHeader.cputype == MACHOFlags.CPU_TYPE_ARM:
            return CS_ARCH_ARM
        if self.__machHeader.cputype == MACHOFlags.CPU_TYPE_ARM64:
            return CS_ARCH_ARM64
        if self.__machHeader.cputype == MACHOFlags.CPU_TYPE_MIPS:
            return CS_ARCH_MIPS
        else:
            print("[Error] MACHO.getArch() - Architecture not supported")
            return None

    def getArchMode(self):
        if self.__machHeader.magic == 0xfeedface:
            return CS_MODE_32
        elif self.__machHeader.magic == 0xfeedfacf:
            return CS_MODE_64
        else:
            print("[Error] MACHO.getArchMode() - Bad Arch size")
            return None
        pass

    def getEndian(self):
        # TODO: Support other endianness
        return 0

    def getFormat(self):
        return "Mach-O"
