Investigação usando e-mails

Os capítulos anteriores discutiram sobre a importância e o processo da análise forense de redes e os conceitos envolvidos. Neste capítulo, vamos aprender sobre o papel dos e-mails na análise forense digital e sua investigação usando Python.

Papel do e-mail na investigação

Os emails desempenham um papel muito importante nas comunicações empresariais e surgiram como uma das aplicações mais importantes da Internet. Eles são um modo conveniente para enviar mensagens e documentos, não apenas de computadores, mas também de outros dispositivos eletrônicos, como telefones celulares e tablets.

O lado negativo dos e-mails é que os criminosos podem vazar informações importantes sobre sua empresa. Conseqüentemente, o papel dos e-mails na análise forense digital aumentou nos últimos anos. Na perícia digital, os e-mails são considerados evidências cruciais e a análise do cabeçalho do e-mail tornou-se importante para coletar evidências durante o processo forense.

Um investigador tem os seguintes objetivos ao realizar análises forenses de e-mail -

  • Para identificar o principal criminoso
  • Para coletar as evidências necessárias
  • Para apresentar as descobertas
  • Para construir o caso

Desafios em análise forense de e-mail

A análise forense de e-mail desempenha um papel muito importante na investigação, já que a maior parte da comunicação na era atual depende de e-mails. No entanto, um investigador forense por email pode enfrentar os seguintes desafios durante a investigação -

Emails falsos

O maior desafio na análise forense de e-mail é o uso de e-mails falsos que são criados pela manipulação e script de cabeçalhos etc. Nesta categoria, os criminosos também usam e-mail temporário, que é um serviço que permite que um usuário registrado receba e-mail em um endereço temporário que expira após um certo período de tempo.

Spoofing

Outro desafio na análise forense de email é o spoofing, no qual os criminosos costumavam apresentar um email como sendo de outra pessoa. Nesse caso, a máquina receberá tanto o endereço IP falso quanto o endereço IP original.

Reenvio de e-mail anônimo

Aqui, o servidor de e-mail remove as informações de identificação da mensagem de e-mail antes de encaminhá-la novamente. Isso leva a outro grande desafio para investigações de e-mail.

Técnicas usadas em investigação forense por email

A análise forense de email é o estudo da fonte e do conteúdo do email como evidência para identificar o remetente e o destinatário reais de uma mensagem, juntamente com algumas outras informações, como data / hora da transmissão e intenção do remetente. Envolve a investigação de metadados, varredura de portas, bem como pesquisa de palavras-chave.

Algumas das técnicas comuns que podem ser usadas para investigação forense de e-mail são

  • Análise de Cabeçalho
  • Investigação de servidor
  • Investigação de dispositivo de rede
  • Sender Mailer Fingerprints
  • Identificadores integrados de software

Nas seções a seguir, aprenderemos como obter informações usando Python para fins de investigação de e-mail.

Extração de informações de arquivos EML

Os arquivos EML são basicamente e-mails em formato de arquivo amplamente usados ​​para armazenar mensagens de e-mail. Eles são arquivos de texto estruturados compatíveis com vários clientes de e-mail, como Microsoft Outlook, Outlook Express e Windows Live Mail.

Um arquivo EML armazena cabeçalhos de e-mail, conteúdo do corpo e dados de anexos como texto simples. Ele usa base64 para codificar dados binários e codificação Quoted-Printable (QP) para armazenar informações de conteúdo. O script Python que pode ser usado para extrair informações do arquivo EML é fornecido abaixo -

Primeiro, importe as seguintes bibliotecas Python, conforme mostrado abaixo -

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

import os
import quopri
import base64

Nas bibliotecas acima, quoprié usado para decodificar os valores codificados QP dos arquivos EML. Quaisquer dados codificados em base64 podem ser decodificados com a ajuda debase64 biblioteca.

A seguir, vamos fornecer um argumento para o manipulador de linha de comando. Observe que aqui ele aceitará apenas um argumento que seria o caminho para o arquivo EML conforme mostrado abaixo -

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)

Agora, precisamos definir main() função na qual usaremos o método chamado message_from_file()da biblioteca de e-mail para ler o arquivo como um objeto. Aqui, acessaremos os cabeçalhos, conteúdo do corpo, anexos e outras informações de carga útil usando a variável resultante chamadaemlfile conforme mostrado no código fornecido abaixo -

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])

Agora, precisamos definir process_payload() método no qual extrairemos o conteúdo do corpo da mensagem usando get_payload()método. Vamos decodificar os dados codificados QP usandoquopri.decodestring()função. Também verificaremos o tipo de conteúdo MIME para que ele possa lidar com o armazenamento do e-mail de maneira adequada. Observe o código fornecido abaixo -

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)

Depois de executar o script acima, obteremos as informações do cabeçalho junto com várias cargas úteis no console.

Analisando arquivos MSG usando Python

As mensagens de e-mail vêm em muitos formatos diferentes. MSG é um tipo de formato usado pelo Microsoft Outlook e Exchange. Os arquivos com extensão MSG podem conter texto ASCII simples para os cabeçalhos e o corpo da mensagem principal, bem como hiperlinks e anexos.

Nesta seção, aprenderemos como extrair informações do arquivo MSG usando a API do Outlook. Observe que o seguinte script Python funcionará apenas no Windows. Para isso, precisamos instalar uma biblioteca Python de terceiros chamadapywin32 como segue -

pip install pywin32

Agora, importe as seguintes bibliotecas usando os comandos mostrados -

from __future__ import print_function
from argparse import ArgumentParser

import os
import win32com.client
import pywintypes

Agora, vamos fornecer um argumento para o manipulador de linha de comando. Aqui ele aceitará dois argumentos, um seria o caminho para o arquivo MSG e o outro seria a pasta de saída desejada da seguinte forma -

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)

Agora, precisamos definir main() função na qual chamaremos win32com biblioteca para configurar Outlook API que ainda permite o acesso ao MAPI namespace.

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)

Agora, defina as diferentes funções que estamos usando neste script. O código fornecido abaixo mostra a definição dodisplay_msg_attribs() função que nos permite exibir vários atributos de uma mensagem como assunto, para, BCC, CC, Tamanho, SenderName, enviado, etc.

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')))

Agora, defina o display_msg_recipeints() função que itera através das mensagens e exibe os detalhes do destinatário.

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

Em seguida, definimos extract_msg_body() função que extrai o conteúdo do corpo, HTML e texto simples, da mensagem.

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"))

Em seguida, devemos definir o extract_attachments() função que exporta dados de anexo para o diretório de saída desejado.

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

Assim que todas as funções forem definidas, imprimiremos todos os atributos no console com a seguinte linha de códigos -

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

Depois de executar o script acima, obteremos os atributos da mensagem e seus anexos na janela do console, juntamente com vários arquivos no diretório de saída.

Estruturação de arquivos MBOX do Google Takeout usando Python

Os arquivos MBOX são arquivos de texto com formatação especial que dividem as mensagens armazenadas. Eles são freqüentemente encontrados em associação com sistemas UNIX, Thunderbolt e Google Takeouts.

Nesta seção, você verá um script Python, onde estruturaremos os arquivos MBOX obtidos do Google Takeouts. Mas antes disso devemos saber como podemos gerar esses arquivos MBOX usando nossa conta do Google ou conta do Gmail.

Adquirindo a caixa de correio da conta do Google no formato MBX

A aquisição da caixa de correio da conta do Google implica em fazer backup de nossa conta do Gmail. O backup pode ser feito por vários motivos pessoais ou profissionais. Observe que o Google fornece backup de dados do Gmail. Para adquirir nossa caixa de correio da conta do Google no formato MBOX, você precisa seguir as etapas abaixo -

  • Abrir My account painel de controle.

  • Vá para a seção Informações pessoais e privacidade e selecione o link Controlar seu conteúdo.

  • Você pode criar um novo arquivo ou pode gerenciar um existente. Se clicarmos,CREATE ARCHIVE link, obteremos algumas caixas de seleção para cada produto do Google que desejamos incluir.

  • Depois de selecionar os produtos, teremos a liberdade de escolher o tipo de arquivo e o tamanho máximo do nosso arquivo, juntamente com o método de entrega para selecionar na lista.

  • Por fim, obteremos este backup no formato MBOX.

Código Python

Agora, o arquivo MBOX discutido acima pode ser estruturado usando Python como mostrado abaixo -

Primeiro, precisa importar bibliotecas Python da seguinte maneira -

from __future__ import print_function
from argparse import ArgumentParser

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

import base64

Todas as bibliotecas foram usadas e explicadas em scripts anteriores, exceto o mailbox biblioteca que é usada para analisar arquivos MBOX.

Agora, forneça um argumento para o manipulador de linha de comando. Aqui, ele aceitará dois argumentos - um seria o caminho para o arquivo MBOX e o outro seria a pasta de saída desejada.

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)

Agora, vai definir main() função e chamada mbox classe de biblioteca de caixa de correio com a ajuda da qual podemos analisar um arquivo MBOX, fornecendo seu caminho -

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)))

Agora, defina um método de leitor para mailbox biblioteca da seguinte forma -

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)

Agora, crie algumas variáveis ​​para processamento posterior, como segue -

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"]

Em seguida, use tqdm para gerar uma barra de progresso e acompanhar o processo de iteração da seguinte forma -

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")

Agora, verifique se a mensagem do tempo está tendo cargas úteis ou não. Se estiver havendo então vamos definirwrite_payload() método da seguinte forma -

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)

Agora, os dados precisam ser anexados. Então vamos ligarcreate_report() método da seguinte forma -

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

Observe que as instruções if-else acima são fáceis de entender. Agora, precisamos definir um método que irá extrair o nome do arquivo domsg objeto da seguinte forma -

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)

Agora, com a ajuda das seguintes linhas de código, você pode realmente exportar o arquivo -

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

Agora, vamos definir uma função para extrair nomes de arquivos do message para representar com precisão os nomes desses arquivos da seguinte forma -

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)

Agora, podemos escrever um arquivo CSV definindo o create_report() funcionar da seguinte forma -

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)

Depois de executar o script fornecido acima, obteremos o relatório CSV e o diretório cheio de anexos.