Investigación mediante correos electrónicos

Los capítulos anteriores discutieron sobre la importancia y el proceso de análisis forense de redes y los conceptos involucrados. En este capítulo, aprendamos sobre el papel de los correos electrónicos en el análisis forense digital y su investigación con Python.

Papel del correo electrónico en la investigación

Los correos electrónicos juegan un papel muy importante en las comunicaciones comerciales y se han convertido en una de las aplicaciones más importantes de Internet. Son un modo conveniente para enviar mensajes y documentos, no solo desde computadoras sino también desde otros dispositivos electrónicos como teléfonos móviles y tabletas.

El lado negativo de los correos electrónicos es que los delincuentes pueden filtrar información importante sobre su empresa. Por lo tanto, el papel de los correos electrónicos en la ciencia forense digital se ha incrementado en los últimos años. En la ciencia forense digital, los correos electrónicos se consideran evidencias cruciales y el análisis de encabezado de correo electrónico se ha vuelto importante para recopilar evidencia durante el proceso forense.

Un investigador tiene los siguientes objetivos al realizar análisis forenses de correo electrónico:

  • Identificar al principal criminal
  • Recoger las evidencias necesarias
  • Presentar los hallazgos
  • Para construir el caso

Desafíos en el análisis forense del correo electrónico

El análisis forense del correo electrónico juega un papel muy importante en la investigación, ya que la mayor parte de la comunicación en la era actual se basa en los correos electrónicos. Sin embargo, un investigador forense de correo electrónico puede enfrentar los siguientes desafíos durante la investigación:

Correos electrónicos falsos

El mayor desafío en el análisis forense del correo electrónico es el uso de correos electrónicos falsos que se crean manipulando y escribiendo encabezados, etc. En esta categoría, los delincuentes también usan el correo electrónico temporal, que es un servicio que permite a un usuario registrado recibir correo electrónico en una dirección temporal que caduca. después de un cierto período de tiempo.

Spoofing

Otro desafío en el análisis forense del correo electrónico es la suplantación de identidad, en la que los delincuentes solían presentar un correo electrónico como el de otra persona. En este caso, la máquina recibirá una dirección IP tanto falsa como original.

Reenvío anónimo

Aquí, el servidor de correo electrónico elimina la información de identificación del mensaje de correo electrónico antes de reenviarlo. Esto conduce a otro gran desafío para las investigaciones de correo electrónico.

Técnicas utilizadas en la investigación forense de correo electrónico

El análisis forense del correo electrónico es el estudio de la fuente y el contenido del correo electrónico como prueba para identificar al remitente y al destinatario reales de un mensaje junto con otra información, como la fecha / hora de transmisión y la intención del remitente. Implica investigar metadatos, escaneo de puertos y búsqueda de palabras clave.

Algunas de las técnicas comunes que se pueden utilizar para la investigación forense del correo electrónico son

  • Análisis de encabezado
  • Investigación del servidor
  • Investigación de dispositivos de red
  • Huellas digitales del remitente
  • Identificadores integrados de software

En las siguientes secciones, aprenderemos cómo obtener información usando Python con el propósito de investigar el correo electrónico.

Extracción de información de archivos EML

Los archivos EML son básicamente correos electrónicos en formato de archivo que se utilizan ampliamente para almacenar mensajes de correo electrónico. Son archivos de texto estructurados que son compatibles con varios clientes de correo electrónico, como Microsoft Outlook, Outlook Express y Windows Live Mail.

Un archivo EML almacena encabezados de correo electrónico, contenido del cuerpo y datos adjuntos como texto sin formato. Utiliza base64 para codificar datos binarios y codificación Quoted-Printable (QP) para almacenar información de contenido. El script de Python que se puede utilizar para extraer información del archivo EML se proporciona a continuación:

Primero, importe las siguientes bibliotecas de Python como se muestra a continuación:

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

import os
import quopri
import base64

En las bibliotecas anteriores, quoprise utiliza para decodificar los valores codificados QP de archivos EML. Cualquier dato codificado en base64 se puede decodificar con la ayuda debase64 biblioteca.

A continuación, proporcionemos un argumento para el controlador de línea de comandos. Tenga en cuenta que aquí solo aceptará un argumento que sería la ruta al archivo EML como se muestra a continuación:

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)

Ahora, necesitamos definir main() función en la que usaremos el método llamado message_from_file()de la biblioteca de correo electrónico para leer el archivo como objeto. Aquí accederemos a los encabezados, el contenido del cuerpo, los archivos adjuntos y otra información de carga útil mediante el uso de la variable resultante denominadaemlfile como se muestra en el código que figura a continuación -

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

Ahora, necesitamos definir process_payload() método en el que extraeremos el contenido del cuerpo del mensaje usando get_payload()método. Decodificaremos datos codificados QP usandoquopri.decodestring()función. También comprobaremos el tipo de contenido MIME para que pueda manejar correctamente el almacenamiento del correo electrónico. Observe el código que se proporciona a continuación:

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)

Después de ejecutar el script anterior, obtendremos la información del encabezado junto con varias cargas útiles en la consola.

Analizando archivos MSG usando Python

Los mensajes de correo electrónico vienen en muchos formatos diferentes. MSG es uno de esos formatos utilizados por Microsoft Outlook y Exchange. Los archivos con la extensión MSG pueden contener texto ASCII sin formato para los encabezados y el cuerpo del mensaje principal, así como hipervínculos y archivos adjuntos.

En esta sección, aprenderemos cómo extraer información del archivo MSG usando la API de Outlook. Tenga en cuenta que la siguiente secuencia de comandos de Python solo funcionará en Windows. Para esto, necesitamos instalar la biblioteca Python de terceros llamadapywin32 como sigue -

pip install pywin32

Ahora, importe las siguientes bibliotecas usando los comandos que se muestran:

from __future__ import print_function
from argparse import ArgumentParser

import os
import win32com.client
import pywintypes

Ahora, proporcionemos un argumento para el controlador de línea de comandos. Aquí aceptará dos argumentos, uno sería la ruta al archivo MSG y el otro sería la carpeta de salida deseada de la siguiente manera:

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)

Ahora, necesitamos definir main() función en la que llamaremos win32com biblioteca para configurar Outlook API que además permite el acceso a la MAPI espacio de nombres.

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)

Ahora, defina las diferentes funciones que estamos usando en este script. El código que se proporciona a continuación muestra la definición dedisplay_msg_attribs() función que nos permite mostrar varios atributos de un mensaje como asunto, a, BCC, CC, Tamaño, 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')))

Ahora, defina el display_msg_recipeints() función que recorre en iteración los mensajes y muestra los detalles del destinatario.

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

A continuación, definimos extract_msg_body() función que extrae el contenido del cuerpo, HTML y texto sin formato, del mensaje.

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

A continuación, definiremos el extract_attachments() función que exporta datos adjuntos al directorio de salida deseado.

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

Una vez definidas todas las funciones, imprimiremos todos los atributos a la consola con la siguiente línea 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

Después de ejecutar el script anterior, obtendremos los atributos del mensaje y sus archivos adjuntos en la ventana de la consola junto con varios archivos en el directorio de salida.

Estructurar archivos MBOX de Google Takeout usando Python

Los archivos MBOX son archivos de texto con un formato especial en el que se dividen los mensajes almacenados. A menudo se encuentran asociados con sistemas UNIX, Thunderbolt y Google Takeouts.

En esta sección, verá un script de Python, donde estructuraremos los archivos MBOX obtenidos de Google Takeouts. Pero antes de eso debemos saber cómo podemos generar estos archivos MBOX usando nuestra cuenta de Google o cuenta de Gmail.

Adquirir el buzón de la cuenta de Google en formato MBX

Adquirir el buzón de la cuenta de Google implica realizar una copia de seguridad de nuestra cuenta de Gmail. La copia de seguridad se puede realizar por diversas razones personales o profesionales. Tenga en cuenta que Google proporciona una copia de seguridad de los datos de Gmail. Para adquirir el buzón de nuestra cuenta de Google en formato MBOX, debe seguir los pasos que se indican a continuación:

  • Abierto My account tablero.

  • Vaya a la sección Información personal y privacidad y seleccione Controlar el enlace de su contenido.

  • Puede crear un nuevo archivo o administrar uno existente. Si hacemos clic,CREATE ARCHIVE enlace, luego obtendremos algunas casillas de verificación para cada producto de Google que deseamos incluir.

  • Después de seleccionar los productos, tendremos la libertad de elegir el tipo de archivo y el tamaño máximo para nuestro archivo junto con el método de entrega para seleccionar de la lista.

  • Finalmente, obtendremos esta copia de seguridad en formato MBOX.

Código Python

Ahora, el archivo MBOX discutido anteriormente se puede estructurar usando Python como se muestra a continuación:

Primero, es necesario importar las bibliotecas de Python de la siguiente manera:

from __future__ import print_function
from argparse import ArgumentParser

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

import base64

Todas las bibliotecas se han utilizado y explicado en scripts anteriores, excepto el mailbox biblioteca que se utiliza para analizar archivos MBOX.

Ahora, proporcione un argumento para el controlador de línea de comandos. Aquí aceptará dos argumentos: uno sería la ruta al archivo MBOX y el otro sería la carpeta de salida deseada.

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)

Ahora, definirá main() función y llamada mbox clase de biblioteca de buzones con la ayuda de la cual podemos analizar un archivo MBOX proporcionando su ruta -

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

Ahora, defina un método de lectura para mailbox biblioteca de la siguiente manera:

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)

Ahora, cree algunas variables para su posterior procesamiento de la siguiente manera:

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

A continuación, use tqdm para generar una barra de progreso y realizar un seguimiento del proceso de iteración de la siguiente manera:

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

Ahora, compruebe si el mensaje meteorológico tiene cargas útiles o no. Si es tener entonces definiremoswrite_payload() método de la siguiente manera -

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)

Ahora, se deben agregar datos. Entonces llamaremoscreate_report() método de la siguiente manera -

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 las declaraciones if-else anteriores son fáciles de entender. Ahora, necesitamos definir un método que extraerá el nombre del archivo de lamsg objeto de la siguiente manera:

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)

Ahora, con la ayuda de las siguientes líneas de código, puede exportar el archivo:

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

Ahora, definamos una función para extraer nombres de archivos de la message para representar con precisión los nombres de estos archivos de la siguiente manera:

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)

Ahora, podemos escribir un archivo CSV definiendo el create_report() funciona de la siguiente manera:

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)

Una vez que ejecute el script proporcionado anteriormente, obtendremos el informe CSV y el directorio lleno de archivos adjuntos.