使用电子邮件进行调查


前几章讨论了网络取证的重要性和过程以及所涉及的概念。在本章中,让我们了解电子邮件在数字取证中的作用以及使用 Python 进行的调查。

电子邮件在调查中的作用


电子邮件在商业通信中发挥着非常重要的作用,并已成为互联网上最重要的应用之一。它们是发送消息和文档的便捷模式,不仅可以从计算机发送,还可以从其他电子设备发送,例如手机和平板电脑。

电子邮件的不利方面是犯罪分子可能会泄露有关其公司的重要信息。因此,近年来电子邮件在数字取证中的作用有所增加。在数字取证中,电子邮件被视为重要证据,电子邮件标题分析对于在取证过程中收集证据变得很重要。

调查员在进行电子邮件取证时有以下目标:

  • 确定主要犯罪分子
  • 收集必要的证据
  • 呈现调查结果
  • 建立案例

电子邮件取证的挑战


电子邮件取证在调查中起着非常重要的作用,因为当今时代的大多数通信都依赖于电子邮件。但是,电子邮件取证调查员在调查过程中可能会面临以下挑战:

假电子邮件

电子邮件取证的最大挑战是使用通过操纵和脚本标头等创建的虚假电子邮件。在此类别中,犯罪分子还使用临时电子邮件,这是一种允许注册用户在过期的临时地址接收电子邮件的服务一定时间后。

Spoofing

电子邮件取证的另一个挑战是欺骗,犯罪分子过去常常将电子邮件呈现为他人的电子邮件。在这种情况下,机器将收到假 IP 地址和原始 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 library.

接下来,让我们为命令行处理程序提供参数。请注意,这里它只接受一个参数,即 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() 方法。我们将使用解码 QP 编码数据 quopri.decodestring() 功能。我们还将检查内容 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。为此,我们需要安装名为的第三方 Python 库 pywin32 如下:

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 用于设置的库 展望 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"))

接下来,我们将定义 提取附件() 将附件数据导出到所需输出目录的功能。

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 Account Mailbox 获取为 MBX 格式


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

  • Open 我的帐户 仪表板。

  • 转到个人信息和隐私部分,然后选择控制你的内容链接。

  • 你可以创建新存档或管理现有存档。如果我们点击, 创建存档 链接,然后我们将为我们希望包含的每个 Google 产品获得一些复选框。

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

  • 最后,我们将得到这个 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

所有库都已在早期脚本中使用和解释,除了 mailbox 用于解析 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)))

现在,为 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)

现在,需要附加数据。然后我们将调用 创建报告() 方法如下:

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)

现在,我们可以通过定义 创建报告() 功能如下:

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