<gh_stars>0
import re
import glob
from pyxlsb import open_workbook as open_xlsb
from beautifultable import BeautifulTable
import zipfile
import xml.etree.ElementTree as ET
import maldoc_parser
#from XLSParser import XLSParser
from OLEParser import OLEParser
#from DocParser import DocParser
#from VBADecompress import VBADecompress
from helpers import *
from helpers import Helpers


class OOXMLParser:

    binary_ooxml = False
    doc_type = ""
    helpers = Helpers()

    def __init__(self, data):
        self.data = data

    def zip_extrcat(self, filename):
        extract_path = "unzipped"
        #print("\n- Extracting archive to: %s" % extract_path)
        zip = zipfile.ZipFile(filename)
        zip.extractall(extract_path)
        path = ".\\" + extract_path
        return path

    def list_archive_files(self, data, filename):

        path = self.zip_extrcat(filename)

        files = glob.glob(path + "**\\**\\**\\*.*", recursive=True)
        files = list(dict.fromkeys(files))
        return files

    def detect_emb_ole(self, data, filename):

        # ms_ole = OLE_Parser(data)
        # emb_ole_files = re.findall(self.OLE_FILE_MAGIC, data)

        files = self.list_archive_files(data, filename)

        archive_files = BeautifulTable(maxwidth=100)
        archive_files.headers = ["File"]

        print("\nFile contents:")
        for f in files:

            archive_files.rows.append([f])
            print(f)
            if "word" in f:
                self.doc_type = "word"
            elif"\\xl\\" in f:
                self.doc_type = "excel"
            else:
                continue
        archive_files.columns.alignment = BeautifulTable.ALIGN_LEFT

        print("\nAnalyzing files in archive")

        indicators = BeautifulTable()
        indicators.headers = ["Indication", "Description"]
        indicators.columns.width = [50, 70]

        for file in files:

            file_data = open(file, "rb").read()

            if ".bin" in file or self.helpers.OLE_FILE_MAGIC in file_data[:len(self.helpers.OLE_FILE_MAGIC)]:

                ms_ole = OLEParser(data)
                self.parse_ole_file(file_data, file)
                ms_ole.extract_embedded_ole(file, file)


            if ".rels" in file:

                xml_data = open(file, "r").read()
                reference = self.find_ext_references(xml_data, file)

                if reference:
                    print_string = "[!] Found external relationship in file: %s" % file
                    indicators.rows.append([print_string, reference])

            #self.decrypt_cdfv2(filename)

                #print(ext_references)

            if "sharedStrings.xml" in file:

                tree = ET.parse(file)
                root = tree.getroot()
                clean_sst_strings = []

                for child in root:
                    for attrib in child:
                        if attrib.text is not None:
                            clean_sst_strings.append(attrib.text)

                print_line = "Shared Strings Table Strings:"
                indicators.rows.append([print_line, ", ".join(clean_sst_strings)])

                self.helpers.summary_table.rows.append(["Found Shared Strings Table", ", ".join(clean_sst_strings)])

            if "document.xml" in file or "workbook.xml" in file:
                try:
                    xml_data = open(file, "r").read()
                    dde_command = self.detect_dde(xml_data, file)
                    if dde_command:
                        print_line = "Detected DDE usage in file: %s" % file
                        indicators.rows.append([print_line, dde_command])
                        self.helpers.summary_table.rows.append([print_line, dde_command])

                    if "&lt" in xml_data:
                        possible_payload = re.findall(r">&lt(.*)", xml_data)
                        print_line = "Possible payload in file: %s" % file
                        indicators.rows.append([print_line, possible_payload])
                        self.helpers.summary_table.rows.append([print_line, possible_payload[:100]])

                except UnicodeDecodeError as e:
                    print("[-] Error reading %s: %s" % (str(file), str(e)))
                    continue

            if "macrosheets" in file or "worksheets" in file:

                xml_data = open(file, "r", errors="ignore").read()
                emb_ole_tag_data = self.detect_emb_ole_tag(xml_data)
                if emb_ole_tag_data:
                    print_line = "reference to embedded OLE object in file: %s" % file
                    indicators.rows.append([print_line, emb_ole_tag_data])

                    self.helpers.summary_table.rows.append([print_line, emb_ole_tag_data])

        indicators.columns.alignment = BeautifulTable.ALIGN_LEFT
        print(indicators)


    def find_ext_references(self, data, filename):

        # Target=\"(.*)</Relationship.*TargetMode="External"
        # .*TargetMode=\"External\" Target=\"(.*)\".*
        mshtml = re.findall(r'oleObject\" Target=\"mhtml:.*TargetMode=\"External\"', data)
        ext_references = re.findall(r'attachedTemplate\" Target=\"http.*TargetMode=\"External\"', data)
        #ext_references = re.findall('.*Target=\"(.*).*\"\ TargetMode=\"External\".*', data)
        if mshtml:
            mshtml_string = str(", ".join(mshtml))
            print("\n[!] Found Possible MSHTML abuse in file: %s -- %s" % (filename, mshtml_string))
            summary_string = "Found Possible MSHTML abuse in file: %s" % filename.replace("\\unzipped", "")
            self.helpers.summary_table.rows.append([summary_string, mshtml[0]])
            return mshtml_string

        if ext_references:
            reference = str(", ".join(ext_references))
            print("\nFound external relationship in file: %s -- %s" % (filename, reference))
            summary_string = "Found Possible Template injection in file: %s" % filename.replace("\\unzipped", "")
            self.helpers.summary_table.rows.append([summary_string, ext_references[0]])
            return reference
            pass

    def extract_strings_sst(self, data, filename):


        # <t>(.*)</t>
        shared_strings = re.findall('<t>(.*)</t>', data)
        if shared_strings:
            print("\nShared strings table found in file: %s" % filename)
            for string in shared_strings:
                print(string)
        else:
            pass


    def parse_ole_file(self, data, filename):

        ms_ole = OLEParser(data)
        #ms_ole.extract_embedded_ole(filename, filename)

    def detect_eqedit32(self, data):

        #https://www.forcepoint.com/blog/x-labs/assessing-risk-office-documents-part-3-exploited-%E2%80%9Cweaponized%E2%80%9D-rtfs

        eqedit32_clsid = re.findall(self.helpers.EQ_EDIT_CLSID_RE, data)
        if eqedit32_clsid:
            return eqedit32_clsid
        else:
            return "    Did not detect an Equation Editor CLSID..."

    def detect_activex(self, filename):

        path = self.zip_extrcat(filename)
        activex_dir = path + "\\xl\\activeX"
        files = glob.glob(activex_dir + "\\*.*", recursive=False)
        if files:
            print("\nSearching for ActiveX OLE objects")
            activex_ole_files = []

            for file in files:
                if ".bin" in file:
                    file_data = open(file, "rb").read()
                    activex_ole_files.append(file)
                    self.parse_ole_file(file_data, file)

            if activex_ole_files:
                self.helpers.summary_table.rows.append(["ActiveX objects", ", ".join(activex_ole_files)])
            return activex_ole_files

    def detect_dde(self, data, filename):

        DDE_PATTERN = "DDEAUTO.*|INCLUDE.*"

        dde = re.findall(DDE_PATTERN, data)

        if dde:

            if ".xml" in filename:
                tree = ET.parse(filename)
                root = tree.getroot()

                for child in root:
                    dde_command = self.inline_xml(child)

                final_dde_command = "".join(dde_command)
                return final_dde_command

    def detect_emb_ole_tag(self, data):

        EMB_OLE_TAG_PATTERN = r"\<oleObjects\>.*\<\/oleObjects\>"
        emb_ole_tag = re.findall(EMB_OLE_TAG_PATTERN, data)

        if emb_ole_tag:
            return emb_ole_tag

    def inline_xml(self, child):

        clean_string = []

        for attrib in child:
            for val in attrib:
                y = filter(lambda v: v.text is not None, val)
                [clean_string.append(x.text) for x in list(y)]

        return clean_string


class OOXML_Excel(OOXMLParser):

    sheet_cells = BeautifulTable(maxwidth=100)
    sheet_cells.headers = ["Cell #", "Cell Content"]

    def read_sheets(self, filename, read_macros=True):

        print("\nReading Excel sheets:")
        path = self.zip_extrcat(filename)
        sheets_dir = path + "\\xl\\worksheets"
        sheet_types = ["worksheet"]

        if read_macros:
            sheet_types.append("macrosheets")
            macros_dir = path + "\\xl\\macrosheets"
            macros_sheets = glob.glob(macros_dir + "\\*.*", recursive=False)

        sheets = glob.glob(sheets_dir + "\\*.*", recursive=False)

        for type in sheet_types:

            if type == "macrosheets":
                self.sheet_cells.columns.alignment = BeautifulTable.ALIGN_LEFT
                print(self.sheet_cells)
                self.sheet_cells.clear()

                sheets = macros_sheets
                self.print_cells(sheets)
                self.sheet_cells.columns.alignments = BeautifulTable.ALIGN_LEFT
                print(self.sheet_cells)
                break

            self.print_cells(sheets)

    def print_cells(self, sheets):

        for sheet in sheets:
            if ".xml" in sheet:
                tree = ET.parse(sheet)
                root = tree.getroot()
                print("    Sheet: %s" % sheet)
                for child in root:
                    m = filter(self.inline_cell(child, self.sheet_cells), root)

    def inline_cell(self, child, sheet_cells):

        if "sheetData" in child.tag:
            for attrib in child:
                for val in attrib:
                    y = filter(lambda v: v.text is not "0", val)
                    for x in list(y):
                        sheet_cells.rows.append([val.attrib.get("r"), x.text])

                        self.helpers.search_indicators_in_string(x.text)

    def inline_cell_ole(self, child, sheet_cells):

        if "oleObjects" in child.tag:
            for attrib in child:
                for val in attrib:
                    y = filter(lambda v: v.text is not "0", val)
                    [sheet_cells.rows.append([val.attrib.get("r"), x.text]) for x in list(y)]

    def list_contents(self, filename):

        indicators = BeautifulTable(maxwidth=100)
        path = self.zip_extrcat(filename)
        files = glob.glob(path + "**\\**\\**\\*.*", recursive=True)
        if files:
            for file in files:

                file_data = open(file, "rb").read()
                eqedit32 = self.detect_eqedit32(file_data)

                if self.helpers.OLE_FILE_MAGIC in file_data[:len(self.helpers.OLE_FILE_MAGIC)]:

                    ms_ole = OLEParser(file_data)
                    ms_ole.extract_embedded_ole(file)

                    if eqedit32:
                        print_string = "[!] Detected Equation Editor CLSID"
                        indicators.rows.append([print_string, file])

                if "vbaProject.bin" in file:
                    self.parse_ole_file(file_data, file)
                    continue

            indicators.columns.alignment = BeautifulTable.ALIGN_LEFT
            print(indicators)

    def read_binary_excel_sheets(self, file):

        sheet_binary_table = BeautifulTable(maxwidth=200)
        sst_table = BeautifulTable(maxwidth=100)
        content = []

        with open_xlsb(file) as wb:
            sheets = wb._sheets

            for sheet_name, sheet_path in sheets:
                #if "macrosheets" in sheet_path:
                if sheet_path:
                    with wb.get_sheet(sheet_name) as s:
                        for row in s.rows():
                            for item in row:
                                if item.v is not None and item.v is not False and type(item.v) is str and len(item.v) > 1:
                                    content.append(item.v)
                                    sheet_binary_table.rows.append(["Cell at row:" + str(item.r) + ", col:" + str(item.c), item.v.replace("\x00", "")])

                                    self.helpers.search_indicators_in_string(item.v)
                else:
                    continue

                sheet_binary_table.columns.alignment = BeautifulTable.ALIGN_LEFT
                print("\nSheet name: %s" % sheet_name)
                print(sheet_binary_table)

            self.print_binary_sst(file, sst_table)

    def print_binary_sst(self, file, sst_table):
        print("Shared Strings Table (SST):")
        with open_xlsb(file) as wb:
            #print(wb.stringtable._strings)
            sst_table.rows.append(["Shared Strings Table", " , ".join(wb.stringtable._strings)])
            i = 0
            #for string in wb.stringtable._strings:
                #sst_table.rows.append([str(i), string])
                #i += 1
            sst_table.columns.alignment = BeautifulTable.ALIGN_LEFT
            print(sst_table)


class OOXML_Word(OOXMLParser):

    def __init__(self, data, filename):
        self.data = data
        self.filename = filename
