使用電子郵件進行調查



前面的章節討論了網路取證的重要性、流程和相關概念。本章,我們將學習電子郵件在數字取證中的作用以及如何使用Python進行電子郵件調查。

電子郵件在調查中的作用

電子郵件在商務溝通中扮演著非常重要的角色,並且已成為網際網路上最重要的應用程式之一。它們是傳送訊息和文件的便捷方式,不僅可以透過計算機,還可以透過其他電子裝置,例如手機和平板電腦。

電子郵件的負面影響是犯罪分子可能會洩露有關其公司的重要資訊。因此,近年來,電子郵件在數字取證中的作用越來越重要。在數字取證中,電子郵件被視為關鍵證據,電子郵件標題分析已成為在取證過程中收集證據的重要手段。

調查人員在執行電子郵件取證時有以下目標:

  • 識別主要犯罪分子
  • 收集必要的證據
  • 呈現調查結果
  • 構建案件

電子郵件取證中的挑戰

電子郵件取證在調查中發揮著非常重要的作用,因為當今時代的大部分溝通都依賴於電子郵件。但是,電子郵件取證調查員在調查過程中可能會面臨以下挑戰:

虛假電子郵件

電子郵件取證中最大的挑戰是使用虛假電子郵件,這些電子郵件是透過操縱和編寫指令碼標頭等建立的。在此類別中,犯罪分子還使用臨時電子郵件,這是一種允許註冊使用者在特定時間段後失效的臨時地址接收電子郵件的服務。

欺騙

電子郵件取證中的另一個挑戰是欺騙,其中犯罪分子試圖將電子郵件偽裝成他人的電子郵件。在這種情況下,機器將同時接收虛假和原始IP地址。

匿名轉發電子郵件

在這裡,電子郵件伺服器會在轉發電子郵件之前剝離電子郵件訊息中的識別資訊。這給電子郵件調查帶來了另一個巨大挑戰。

電子郵件取證調查中使用的技術

電子郵件取證是對電子郵件的來源和內容的研究,作為證據來識別訊息的實際發件人和收件人,以及其他一些資訊,例如傳輸日期/時間和發件人的意圖。它涉及調查元資料、埠掃描以及關鍵字搜尋。

一些可用於電子郵件取證調查的常用技術包括:

  • 標題分析
  • 伺服器調查
  • 網路裝置調查
  • 發件人郵件指紋
  • 軟體嵌入式識別符號

在接下來的部分中,我們將學習如何使用Python獲取資訊以進行電子郵件調查。

從EML檔案中提取資訊

EML檔案基本上是檔案格式的電子郵件,廣泛用於儲存電子郵件訊息。它們是跨多個電子郵件客戶端(例如Microsoft Outlook、Outlook Express和Windows Live Mail)相容的結構化文字檔案。

EML檔案將電子郵件標題、正文內容、附件資料儲存為純文字。它使用base64編碼二進位制資料,並使用Quoted-Printable (QP)編碼儲存內容資訊。下面給出了可用於從EML檔案提取資訊的Python指令碼:

首先,匯入如下所示的Python庫:

from __future__ import print_function
from argparse import ArgumentParser, FileType
from email import message_from_file

import os
import quopri
import base64

在上面的庫中,quopri用於從EML檔案解碼QP編碼的值。任何base64編碼的資料都可以藉助base64庫進行解碼。

接下來,讓我們為命令列處理程式提供引數。請注意,這裡它只接受一個引數,該引數將是EML檔案的路徑,如下所示:

if __name__ == '__main__':
   parser = ArgumentParser('Extracting information from EML file')
   parser.add_argument("EML_FILE",help="Path to EML File", type=FileType('r'))
   args = parser.parse_args()
   main(args.EML_FILE)

現在,我們需要定義main()函式,在這個函式中,我們將使用email庫中名為message_from_file()的方法來讀取檔案物件。我們將使用名為emlfile的結果變數訪問標題、正文內容、附件和其他有效負載資訊,如下面的程式碼所示:

def main(input_file):
   emlfile = message_from_file(input_file)
   for key, value in emlfile._headers:
      print("{}: {}".format(key, value))
print("\nBody\n")

if emlfile.is_multipart():
   for part in emlfile.get_payload():
      process_payload(part)
else:
   process_payload(emlfile[1])

現在,我們需要定義process_payload()方法,在這個方法中,我們將使用get_payload()方法提取訊息正文內容。我們將使用quopri.decodestring()函式解碼QP編碼的資料。我們還將檢查內容MIME型別,以便它可以正確處理電子郵件的儲存。觀察下面的程式碼:

def process_payload(payload):
   print(payload.get_content_type() + "\n" + "=" * len(payload.get_content_type()))
   body = quopri.decodestring(payload.get_payload())
   
   if payload.get_charset():
      body = body.decode(payload.get_charset())
else:
   try:
      body = body.decode()
   except UnicodeDecodeError:
      body = body.decode('cp1252')

if payload.get_content_type() == "text/html":
   outfile = os.path.basename(args.EML_FILE.name) + ".html"
   open(outfile, 'w').write(body)
elif payload.get_content_type().startswith('application'):
   outfile = open(payload.get_filename(), 'wb')
   body = base64.b64decode(payload.get_payload())
   outfile.write(body)
   outfile.close()
   print("Exported: {}\n".format(outfile.name))
else:
   print(body)

執行上述指令碼後,我們將獲得控制檯上帶有各種有效負載的標題資訊。

使用Python分析MSG檔案

電子郵件有多種不同的格式。MSG是Microsoft Outlook和Exchange使用的此類格式之一。副檔名為MSG的檔案可能包含標題和主訊息正文的純ASCII文字,以及超連結和附件。

在本節中,我們將學習如何使用Outlook API從MSG檔案提取資訊。請注意,以下Python指令碼僅適用於Windows。為此,我們需要安裝名為pywin32的第三方Python庫,如下所示:

pip install pywin32

現在,使用顯示的命令匯入以下庫:

from __future__ import print_function
from argparse import ArgumentParser

import os
import win32com.client
import pywintypes

現在,讓我們為命令列處理程式提供一個引數。這裡它將接受兩個引數,一個是MSG檔案的路徑,另一個是所需的輸出資料夾,如下所示:

if __name__ == '__main__':
   parser = ArgumentParser(‘Extracting information from MSG file’)
   parser.add_argument("MSG_FILE", help="Path to MSG file")
   parser.add_argument("OUTPUT_DIR", help="Path to output folder")
   args = parser.parse_args()
   out_dir = args.OUTPUT_DIR
   
   if not os.path.exists(out_dir):
      os.makedirs(out_dir)
   main(args.MSG_FILE, args.OUTPUT_DIR)

現在,我們需要定義main()函式,在這個函式中,我們將呼叫win32com庫來設定Outlook API,這進一步允許訪問MAPI名稱空間。

def main(msg_file, output_dir):
   mapi = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
   msg = mapi.OpenSharedItem(os.path.abspath(args.MSG_FILE))
   
   display_msg_attribs(msg)
   display_msg_recipients(msg)
   
   extract_msg_body(msg, output_dir)
   extract_attachments(msg, output_dir)

現在,定義我們在本指令碼中使用的不同函式。下面的程式碼顯示了定義display_msg_attribs()函式,該函式允許我們顯示訊息的各種屬性,例如主題、收件人、密件抄送、抄送、大小、發件人姓名、傳送時間等。

def display_msg_attribs(msg):
   attribs = [
      'Application', 'AutoForwarded', 'BCC', 'CC', 'Class',
      'ConversationID', 'ConversationTopic', 'CreationTime',
      'ExpiryTime', 'Importance', 'InternetCodePage', 'IsMarkedAsTask',
      'LastModificationTime', 'Links','ReceivedTime', 'ReminderSet',
      'ReminderTime', 'ReplyRecipientNames', 'Saved', 'Sender',
      'SenderEmailAddress', 'SenderEmailType', 'SenderName', 'Sent',
      'SentOn', 'SentOnBehalfOfName', 'Size', 'Subject',
      'TaskCompletedDate', 'TaskDueDate', 'To', 'UnRead'
   ]
   print("\nMessage Attributes")
   for entry in attribs:
      print("{}: {}".format(entry, getattr(msg, entry, 'N/A')))

現在,定義display_msg_recipeints()函式,該函式迭代訊息並顯示收件人詳細資訊。

def display_msg_recipients(msg):
   recipient_attrib = ['Address', 'AutoResponse', 'Name', 'Resolved', 'Sendable']
   i = 1
   
   while True:
   try:
      recipient = msg.Recipients(i)
   except pywintypes.com_error:
      break
   print("\nRecipient {}".format(i))
   print("=" * 15)
   
   for entry in recipient_attrib:
      print("{}: {}".format(entry, getattr(recipient, entry, 'N/A')))
   i += 1

接下來,我們定義extract_msg_body()函式,該函式從訊息中提取正文內容、HTML以及純文字。

def extract_msg_body(msg, out_dir):
   html_data = msg.HTMLBody.encode('cp1252')
   outfile = os.path.join(out_dir, os.path.basename(args.MSG_FILE))
   
   open(outfile + ".body.html", 'wb').write(html_data)
   print("Exported: {}".format(outfile + ".body.html"))
   body_data = msg.Body.encode('cp1252')
   
   open(outfile + ".body.txt", 'wb').write(body_data)
   print("Exported: {}".format(outfile + ".body.txt"))

接下來,我們將定義extract_attachments()函式,該函式將附件資料匯出到所需的輸出目錄。

def extract_attachments(msg, out_dir):
   attachment_attribs = ['DisplayName', 'FileName', 'PathName', 'Position', 'Size']
   i = 1 # Attachments start at 1
   
   while True:
      try:
         attachment = msg.Attachments(i)
   except pywintypes.com_error:
      break

定義所有函式後,我們將使用以下程式碼行將所有屬性列印到控制檯:

print("\nAttachment {}".format(i))
print("=" * 15)
   
for entry in attachment_attribs:
   print('{}: {}'.format(entry, getattr(attachment, entry,"N/A")))
outfile = os.path.join(os.path.abspath(out_dir),os.path.split(args.MSG_FILE)[-1])
   
if not os.path.exists(outfile):
os.makedirs(outfile)
outfile = os.path.join(outfile, attachment.FileName)
attachment.SaveAsFile(outfile)
   
print("Exported: {}".format(outfile))
i += 1

執行上述指令碼後,我們將獲得控制檯視窗中訊息及其附件的屬性以及輸出目錄中的多個檔案。

使用Python構建來自Google Takeout的MBOX檔案

MBOX檔案是具有特殊格式的文字檔案,用於分割其中儲存的訊息。它們通常與UNIX系統、Thunderbolt和Google Takeouts相關聯。

在本節中,您將看到一個Python指令碼,我們將使用該指令碼構建從Google Takeouts獲得的MBOX檔案。但在那之前,我們必須瞭解如何使用我們的Google帳戶或Gmail帳戶生成這些MBOX檔案。

將Google帳戶郵箱獲取為MBX格式

獲取Google帳戶郵箱意味著備份我們的Gmail帳戶。出於各種個人或專業原因,可以進行備份。請注意,Google提供Gmail資料的備份。要將我們的Google帳戶郵箱獲取為MBOX格式,您需要按照以下步驟操作:

  • 開啟我的帳戶資訊中心。

  • 轉到“個人資訊和隱私”部分,然後選擇“控制您的內容”連結。

  • 您可以建立一個新的存檔或管理現有的存檔。如果我們單擊建立存檔連結,我們將獲得一些複選框,用於我們希望包含的每個Google產品。

  • 選擇產品後,我們將可以自由選擇存檔的檔案型別和最大大小,以及從列表中選擇的交付方式。

  • 最後,我們將以MBOX格式獲得此備份。

Python程式碼

現在,可以使用Python構建上面討論的MBOX檔案,如下所示:

首先,需要匯入Python庫,如下所示:

from __future__ import print_function
from argparse import ArgumentParser

import mailbox
import os
import time
import csv
from tqdm import tqdm

import base64

除了用於解析MBOX檔案的mailbox庫外,所有庫都在之前的指令碼中使用並解釋過。

現在,為命令列處理程式提供一個引數。這裡它將接受兩個引數:一個是MBOX檔案的路徑,另一個是所需的輸出資料夾。

if __name__ == '__main__':
   parser = ArgumentParser('Parsing MBOX files')
   parser.add_argument("MBOX", help="Path to mbox file")
   parser.add_argument(
      "OUTPUT_DIR",help = "Path to output directory to write report ""and exported content")
   args = parser.parse_args()
   main(args.MBOX, args.OUTPUT_DIR)

現在,將定義main()函式並呼叫mailbox庫的mbox類,藉助該類,我們可以透過提供其路徑來解析MBOX檔案:

def main(mbox_file, output_dir):
   print("Reading mbox file")
   mbox = mailbox.mbox(mbox_file, factory=custom_reader)
   print("{} messages to parse".format(len(mbox)))

現在,為mailbox庫定義一個讀取器方法,如下所示:

def custom_reader(data_stream):
   data = data_stream.read()
   try:
      content = data.decode("ascii")
   except (UnicodeDecodeError, UnicodeEncodeError) as e:
      content = data.decode("cp1252", errors="replace")
   return mailbox.mboxMessage(content)

現在,建立一些變數以進行進一步處理,如下所示:

parsed_data = []
attachments_dir = os.path.join(output_dir, "attachments")

if not os.path.exists(attachments_dir):
   os.makedirs(attachments_dir)
columns = [
   "Date", "From", "To", "Subject", "X-Gmail-Labels", "Return-Path", "Received", 
   "Content-Type", "Message-ID","X-GM-THRID", "num_attachments_exported", "export_path"]

接下來,使用tqdm生成進度條並跟蹤迭代過程,如下所示:

for message in tqdm(mbox):
   msg_data = dict()
   header_data = dict(message._headers)
for hdr in columns:
   msg_data[hdr] = header_data.get(hdr, "N/A")

現在,檢查訊息是否具有有效負載。如果有,我們將定義write_payload()方法,如下所示:

if len(message.get_payload()):
   export_path = write_payload(message, attachments_dir)
   msg_data['num_attachments_exported'] = len(export_path)
   msg_data['export_path'] = ", ".join(export_path)

現在,需要追加資料。然後我們將呼叫create_report()方法,如下所示:

parsed_data.append(msg_data)
create_report(
   parsed_data, os.path.join(output_dir, "mbox_report.csv"), columns)
def write_payload(msg, out_dir):
   pyld = msg.get_payload()
   export_path = []
   
if msg.is_multipart():
   for entry in pyld:
      export_path += write_payload(entry, out_dir)
else:
   content_type = msg.get_content_type()
   if "application/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "image/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))

   elif "video/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "audio/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "text/csv" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "info/" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   elif "text/calendar" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   elif "text/rtf" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   else:
      if "name=" in msg.get('Content-Disposition', "N/A"):
         content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "name=" in msg.get('Content-Type', "N/A"):
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
return export_path

請注意,上述if-else語句易於理解。現在,我們需要定義一個方法,該方法將從msg物件中提取檔名,如下所示:

def export_content(msg, out_dir, content_data):
   file_name = get_filename(msg)
   file_ext = "FILE"
   
   if "." in file_name: file_ext = file_name.rsplit(".", 1)[-1]
   file_name = "{}_{:.4f}.{}".format(file_name.rsplit(".", 1)[0], time.time(), file_ext)
   file_name = os.path.join(out_dir, file_name)

現在,藉助以下幾行程式碼,您可以實際匯出檔案:

if isinstance(content_data, str):
   open(file_name, 'w').write(content_data)
else:
   open(file_name, 'wb').write(content_data)
return file_name

現在,讓我們定義一個函式,從message中提取檔名,以準確表示這些檔案的名稱,如下所示:

def get_filename(msg):
   if 'name=' in msg.get("Content-Disposition", "N/A"):
      fname_data = msg["Content-Disposition"].replace("\r\n", " ")
      fname = [x for x in fname_data.split("; ") if 'name=' in x]
      file_name = fname[0].split("=", 1)[-1]
   elif 'name=' in msg.get("Content-Type", "N/A"):
      fname_data = msg["Content-Type"].replace("\r\n", " ")
      fname = [x for x in fname_data.split("; ") if 'name=' in x]
      file_name = fname[0].split("=", 1)[-1]
   else:
      file_name = "NO_FILENAME"
   fchars = [x for x in file_name if x.isalnum() or x.isspace() or x == "."]
   return "".join(fchars)

現在,我們可以透過定義如下所示的 **create_report()** 函式來編寫 CSV 檔案:

def create_report(output_data, output_file, columns):
   with open(output_file, 'w', newline="") as outfile:
      csvfile = csv.DictWriter(outfile, columns)
      csvfile.writeheader()
      csvfile.writerows(output_data)

執行上述指令碼後,我們將獲得 CSV 報告和一個包含附件的目錄。

廣告