Python数字取证 利用电子邮件进行调查

Python数字取证 利用电子邮件进行调查

前几章讨论了网络取证的重要性和过程以及所涉及的概念。在本章中,让我们了解电子邮件在数字取证中的作用以及使用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() 函数,在该函数中,我们将使用电子邮件库中名为 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是微软Outlook和Exchange使用的一种格式。带有MSG扩展名的文件可能包含纯ASCII文本的标题和主要邮件正文,以及超链接和附件。

在本节中,我们将学习如何使用 Outlook API 从 MSG 文件中提取信息。请注意,下面的Python脚本只在Windows下工作。为此,我们需要安装名为 pywin32 的第三方Python库,如下所示

pip install pywin32

Now, import the following libraries using the commands shown −

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 Takeout有关。

在本节中,你将看到一个Python脚本,我们将对从Google Takeouts得到的MBOX文件进行结构化处理。但在此之前,我们必须知道我们如何通过使用谷歌账户或Gmail账户来生成这些MBOX文件。

获取谷歌账户邮箱为MBX格式

获取谷歌账户的邮箱意味着要对我们的Gmail账户进行备份。备份可以出于各种个人或专业原因。请注意,谷歌提供Gmail数据的备份。要将我们的谷歌账户邮箱获取为MBOX格式,你需要遵循下面的步骤:

  • 打开 我的账户 仪表板。

  • 进入个人信息和隐私部分,选择控制你的内容链接。

  • 你可以创建一个新的档案,也可以管理现有的档案。如果我们点击 ” 创建档案 “ 链接,那么我们将得到一些复选框,用于我们希望包括的每个谷歌产品。

  • 在选择产品后,我们将自由选择文件类型和存档的最大尺寸,以及从列表中选择交付方式。

  • 最后,我们将得到MBOX格式的备份。

Python代码

现在,上面讨论的MBOX文件可以用Python来结构化,如下图所示

首先,需要导入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文件的 邮箱 库之外,所有的库都已经在前面的脚本中使用并解释过了。

现在,为命令行处理程序提供一个参数。这里它将接受两个参数–一个是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( )函数并调用邮箱库中的 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)))

现在,为 邮箱 库定义一个读者方法,如下所示

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

现在,让我们定义一个函数,从 信息 中提取文件名,以准确表示这些文件的名称,如下所示

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报告和充满附件的目录。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程