Investigación de metadatos incrustados

En este capítulo, aprenderemos en detalle sobre la investigación de metadatos incrustados usando análisis forense digital de Python.

Introducción

Los metadatos incrustados son la información sobre los datos almacenados en el mismo archivo que tiene el objeto descrito por esos datos. En otras palabras, es la información sobre un activo digital almacenada en el propio archivo digital. Siempre está asociado con el archivo y nunca se puede separar.

En el caso de la ciencia forense digital, no podemos extraer toda la información sobre un archivo en particular. Por otro lado, los metadatos incrustados pueden proporcionarnos información crítica para la investigación. Por ejemplo, los metadatos de un archivo de texto pueden contener información sobre el autor, su extensión, fecha de redacción e incluso un breve resumen sobre ese documento. Una imagen digital puede incluir metadatos como la longitud de la imagen, la velocidad del obturador, etc.

Artefactos que contienen atributos de metadatos y su extracción

En esta sección, aprenderemos sobre varios artefactos que contienen atributos de metadatos y su proceso de extracción usando Python.

Audio y video

Estos son los dos artefactos muy comunes que tienen metadatos incrustados. Estos metadatos se pueden extraer con fines de investigación.

Puede utilizar la siguiente secuencia de comandos de Python para extraer atributos o metadatos comunes de un archivo de audio o MP3 y un video o un archivo MP4.

Tenga en cuenta que para este script, necesitamos instalar una biblioteca de Python de terceros llamada mutagen que nos permite extraer metadatos de archivos de audio y video. Se puede instalar con la ayuda del siguiente comando:

pip install mutagen

Algunas de las bibliotecas útiles que necesitamos importar para este script de Python son las siguientes:

from __future__ import print_function

import argparse
import json
import mutagen

El controlador de línea de comando tomará un argumento que representa la ruta a los archivos MP3 o MP4. Entonces, usaremosmutagen.file() método para abrir un identificador para el archivo de la siguiente manera:

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Python Metadata Extractor')
   parser.add_argument("AV_FILE", help="File to extract metadata from")
   args = parser.parse_args()
   av_file = mutagen.File(args.AV_FILE)
   file_ext = args.AV_FILE.rsplit('.', 1)[-1]
   
   if file_ext.lower() == 'mp3':
      handle_id3(av_file)
   elif file_ext.lower() == 'mp4':
      handle_mp4(av_file)

Ahora, necesitamos usar dos identificadores, uno para extraer los datos de MP3 y otro para extraer datos del archivo MP4. Podemos definir estos identificadores de la siguiente manera:

def handle_id3(id3_file):
   id3_frames = {'TIT2': 'Title', 'TPE1': 'Artist', 'TALB': 'Album','TXXX':
      'Custom', 'TCON': 'Content Type', 'TDRL': 'Date released','COMM': 'Comments',
         'TDRC': 'Recording Date'}
   print("{:15} | {:15} | {:38} | {}".format("Frame", "Description","Text","Value"))
   print("-" * 85)
   
   for frames in id3_file.tags.values():
      frame_name = id3_frames.get(frames.FrameID, frames.FrameID)
      desc = getattr(frames, 'desc', "N/A")
      text = getattr(frames, 'text', ["N/A"])[0]
      value = getattr(frames, 'value', "N/A")
      
      if "date" in frame_name.lower():
         text = str(text)
      print("{:15} | {:15} | {:38} | {}".format(
         frame_name, desc, text, value))
def handle_mp4(mp4_file):
   cp_sym = u"\u00A9"
   qt_tag = {
      cp_sym + 'nam': 'Title', cp_sym + 'art': 'Artist',
      cp_sym + 'alb': 'Album', cp_sym + 'gen': 'Genre',
      'cpil': 'Compilation', cp_sym + 'day': 'Creation Date',
      'cnID': 'Apple Store Content ID', 'atID': 'Album Title ID',
      'plID': 'Playlist ID', 'geID': 'Genre ID', 'pcst': 'Podcast',
      'purl': 'Podcast URL', 'egid': 'Episode Global ID',
      'cmID': 'Camera ID', 'sfID': 'Apple Store Country',
      'desc': 'Description', 'ldes': 'Long Description'}
genre_ids = json.load(open('apple_genres.json'))

Ahora, debemos iterar a través de este archivo MP4 de la siguiente manera:

print("{:22} | {}".format('Name', 'Value'))
print("-" * 40)

for name, value in mp4_file.tags.items():
   tag_name = qt_tag.get(name, name)
   
   if isinstance(value, list):
      value = "; ".join([str(x) for x in value])
   if name == 'geID':
      value = "{}: {}".format(
      value, genre_ids[str(value)].replace("|", " - "))
   print("{:22} | {}".format(tag_name, value))

El script anterior nos dará información adicional sobre archivos MP3 y MP4.

Imagenes

Las imágenes pueden contener diferentes tipos de metadatos según su formato de archivo. Sin embargo, la mayoría de las imágenes incorporan información GPS. Podemos extraer esta información de GPS utilizando bibliotecas de Python de terceros. Puede utilizar la siguiente secuencia de comandos de Python para hacer lo mismo:

Primero, descargue la biblioteca de Python de terceros llamada Python Imaging Library (PIL) como sigue -

pip install pillow

Esto nos ayudará a extraer metadatos de imágenes.

También podemos escribir los detalles del GPS incrustados en imágenes en un archivo KML, pero para esto necesitamos descargar la biblioteca de Python de terceros llamada simplekml como sigue -

pip install simplekml

En este script, primero necesitamos importar las siguientes bibliotecas:

from __future__ import print_function
import argparse

from PIL import Image
from PIL.ExifTags import TAGS

import simplekml
import sys

Ahora, el controlador de la línea de comandos aceptará un argumento posicional que básicamente representa la ruta del archivo de las fotos.

parser = argparse.ArgumentParser('Metadata from images')
parser.add_argument('PICTURE_FILE', help = "Path to picture")
args = parser.parse_args()

Ahora, necesitamos especificar las URL que completarán la información de coordenadas. Las URL songmaps y open_maps. También necesitamos una función para convertir la coordenada de tupla de grado minuto segundo (DMS), proporcionada por la biblioteca PIL, en decimal. Se puede hacer de la siguiente manera:

gmaps = "https://www.google.com/maps?q={},{}"
open_maps = "http://www.openstreetmap.org/?mlat={}&mlon={}"

def process_coords(coord):
   coord_deg = 0
   
   for count, values in enumerate(coord):
      coord_deg += (float(values[0]) / values[1]) / 60**count
   return coord_deg

Ahora usaremos image.open() función para abrir el archivo como objeto PIL.

img_file = Image.open(args.PICTURE_FILE)
exif_data = img_file._getexif()

if exif_data is None:
   print("No EXIF data found")
   sys.exit()
for name, value in exif_data.items():
   gps_tag = TAGS.get(name, name)
   if gps_tag is not 'GPSInfo':
      continue

Después de encontrar el GPSInfo etiqueta, almacenaremos la referencia GPS y procesaremos las coordenadas con el process_coords() método.

lat_ref = value[1] == u'N'
lat = process_coords(value[2])

if not lat_ref:
   lat = lat * -1
lon_ref = value[3] == u'E'
lon = process_coords(value[4])

if not lon_ref:
   lon = lon * -1

Ahora, inicia kml objeto de simplekml biblioteca de la siguiente manera:

kml = simplekml.Kml()
kml.newpoint(name = args.PICTURE_FILE, coords = [(lon, lat)])
kml.save(args.PICTURE_FILE + ".kml")

Ahora podemos imprimir las coordenadas de la información procesada de la siguiente manera:

print("GPS Coordinates: {}, {}".format(lat, lon))
print("Google Maps URL: {}".format(gmaps.format(lat, lon)))
print("OpenStreetMap URL: {}".format(open_maps.format(lat, lon)))
print("KML File {} created".format(args.PICTURE_FILE + ".kml"))

Documentos PDF

Los documentos PDF tienen una amplia variedad de medios que incluyen imágenes, texto, formularios, etc. Cuando extraemos metadatos incrustados en documentos PDF, podemos obtener los datos resultantes en el formato llamado Plataforma extensible de metadatos (XMP). Podemos extraer metadatos con la ayuda del siguiente código Python:

Primero, instale una biblioteca Python de terceros llamada PyPDF2para leer metadatos almacenados en formato XMP. Se puede instalar de la siguiente manera:

pip install PyPDF2

Ahora, importe las siguientes bibliotecas para extraer los metadatos de archivos PDF:

from __future__ import print_function
from argparse import ArgumentParser, FileType

import datetime
from PyPDF2 import PdfFileReader
import sys

Ahora, el controlador de la línea de comandos aceptará un argumento posicional que básicamente representa la ruta del archivo PDF.

parser = argparse.ArgumentParser('Metadata from PDF')
parser.add_argument('PDF_FILE', help='Path to PDF file',type=FileType('rb'))
args = parser.parse_args()

Ahora podemos usar getXmpMetadata() método para proporcionar un objeto que contiene los metadatos disponibles de la siguiente manera:

pdf_file = PdfFileReader(args.PDF_FILE)
xmpm = pdf_file.getXmpMetadata()

if xmpm is None:
   print("No XMP metadata found in document.")
   sys.exit()

Nosotros podemos usar custom_print() método para extraer e imprimir los valores relevantes como título, creador, colaborador, etc. de la siguiente manera:

custom_print("Title: {}", xmpm.dc_title)
custom_print("Creator(s): {}", xmpm.dc_creator)
custom_print("Contributors: {}", xmpm.dc_contributor)
custom_print("Subject: {}", xmpm.dc_subject)
custom_print("Description: {}", xmpm.dc_description)
custom_print("Created: {}", xmpm.xmp_createDate)
custom_print("Modified: {}", xmpm.xmp_modifyDate)
custom_print("Event Dates: {}", xmpm.dc_date)

También podemos definir custom_print() método en caso de que el PDF se cree utilizando varios programas de la siguiente manera:

def custom_print(fmt_str, value):
   if isinstance(value, list):
      print(fmt_str.format(", ".join(value)))
   elif isinstance(value, dict):
      fmt_value = [":".join((k, v)) for k, v in value.items()]
      print(fmt_str.format(", ".join(value)))
   elif isinstance(value, str) or isinstance(value, bool):
      print(fmt_str.format(value))
   elif isinstance(value, bytes):
      print(fmt_str.format(value.decode()))
   elif isinstance(value, datetime.datetime):
      print(fmt_str.format(value.isoformat()))
   elif value is None:
      print(fmt_str.format("N/A"))
   else:
      print("warn: unhandled type {} found".format(type(value)))

También podemos extraer cualquier otra propiedad personalizada guardada por el software de la siguiente manera:

if xmpm.custom_properties:
   print("Custom Properties:")
   
   for k, v in xmpm.custom_properties.items():
      print("\t{}: {}".format(k, v))

La secuencia de comandos anterior leerá el documento PDF e imprimirá los metadatos almacenados en formato XMP, incluidas algunas propiedades personalizadas almacenadas por el software con la ayuda de la cual se creó ese PDF.

Archivos ejecutables de Windows

A veces podemos encontrar un archivo ejecutable sospechoso o no autorizado. Pero para fines de investigación, puede ser útil debido a los metadatos incrustados. Podemos obtener la información como su ubicación, su propósito y otros atributos como el fabricante, la fecha de compilación, etc. Con la ayuda del siguiente script de Python podemos obtener la fecha de compilación, datos útiles de los encabezados y símbolos importados y exportados.

Para este propósito, primero instale la biblioteca Python de terceros pefile. Se puede hacer de la siguiente manera:

pip install pefile

Una vez que lo haya instalado correctamente, importe las siguientes bibliotecas de la siguiente manera:

from __future__ import print_function

import argparse
from datetime import datetime
from pefile import PE

Ahora, el controlador de la línea de comandos aceptará un argumento posicional que básicamente representa la ruta del archivo ejecutable. También puede elegir el estilo de salida, ya sea que lo necesite de forma detallada y detallada o de forma simplificada. Para esto, debe proporcionar un argumento opcional como se muestra a continuación:

parser = argparse.ArgumentParser('Metadata from executable file')
parser.add_argument("EXE_FILE", help = "Path to exe file")
parser.add_argument("-v", "--verbose", help = "Increase verbosity of output",
action = 'store_true', default = False)
args = parser.parse_args()

Ahora, cargaremos el archivo ejecutable de entrada usando la clase PE. También volcaremos los datos ejecutables en un objeto de diccionario usandodump_dict() método.

pe = PE(args.EXE_FILE)
ped = pe.dump_dict()

Podemos extraer metadatos de archivos básicos, como la autoría incrustada, la versión y el tiempo de compilación utilizando el código que se muestra a continuación:

file_info = {}
for structure in pe.FileInfo:
   if structure.Key == b'StringFileInfo':
      for s_table in structure.StringTable:
         for key, value in s_table.entries.items():
            if value is None or len(value) == 0:
               value = "Unknown"
            file_info[key] = value
print("File Information: ")
print("==================")

for k, v in file_info.items():
   if isinstance(k, bytes):
      k = k.decode()
   if isinstance(v, bytes):
      v = v.decode()
   print("{}: {}".format(k, v))
comp_time = ped['FILE_HEADER']['TimeDateStamp']['Value']
comp_time = comp_time.split("[")[-1].strip("]")
time_stamp, timezone = comp_time.rsplit(" ", 1)
comp_time = datetime.strptime(time_stamp, "%a %b %d %H:%M:%S %Y")
print("Compiled on {} {}".format(comp_time, timezone.strip()))

Podemos extraer los datos útiles de los encabezados de la siguiente manera:

for section in ped['PE Sections']:
   print("Section '{}' at {}: {}/{} {}".format(
      section['Name']['Value'], hex(section['VirtualAddress']['Value']),
      section['Misc_VirtualSize']['Value'],
      section['SizeOfRawData']['Value'], section['MD5'])
   )

Ahora, extraiga la lista de importaciones y exportaciones de archivos ejecutables como se muestra a continuación:

if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
   print("\nImports: ")
   print("=========")
   
   for dir_entry in pe.DIRECTORY_ENTRY_IMPORT:
      dll = dir_entry.dll
      
      if not args.verbose:
         print(dll.decode(), end=", ")
         continue
      name_list = []
      
      for impts in dir_entry.imports:
         if getattr(impts, "name", b"Unknown") is None:
            name = b"Unknown"
         else:
            name = getattr(impts, "name", b"Unknown")
			name_list.append([name.decode(), hex(impts.address)])
      name_fmt = ["{} ({})".format(x[0], x[1]) for x in name_list]
      print('- {}: {}'.format(dll.decode(), ", ".join(name_fmt)))
   if not args.verbose:
      print()

Ahora, imprime exports, names y addresses usando el código como se muestra a continuación -

if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
   print("\nExports: ")
   print("=========")
   
   for sym in pe.DIRECTORY_ENTRY_EXPORT.symbols:
      print('- {}: {}'.format(sym.name.decode(), hex(sym.address)))

El script anterior extraerá los metadatos básicos, la información de los encabezados de los archivos ejecutables de Windows.

Metadatos de documentos de Office

La mayor parte del trabajo en computadora se realiza en tres aplicaciones de MS Office: Word, PowerPoint y Excel. Estos archivos poseen enormes metadatos, que pueden exponer información interesante sobre su autoría e historial.

Tenga en cuenta que los metadatos del formato 2007 de Word (.docx), excel (.xlsx) y powerpoint (.pptx) se almacenan en un archivo XML. Podemos procesar estos archivos XML en Python con la ayuda del siguiente script de Python que se muestra a continuación:

Primero, importe las bibliotecas necesarias como se muestra a continuación:

from __future__ import print_function
from argparse import ArgumentParser
from datetime import datetime as dt
from xml.etree import ElementTree as etree

import zipfile
parser = argparse.ArgumentParser('Office Document Metadata’)
parser.add_argument("Office_File", help="Path to office file to read")
args = parser.parse_args()

Ahora, compruebe si el archivo es un archivo ZIP. De lo contrario, genere un error. Ahora, abra el archivo y extraiga los elementos clave para procesarlos usando el siguiente código:

zipfile.is_zipfile(args.Office_File)
zfile = zipfile.ZipFile(args.Office_File)
core_xml = etree.fromstring(zfile.read('docProps/core.xml'))
app_xml = etree.fromstring(zfile.read('docProps/app.xml'))

Ahora, cree un diccionario para iniciar la extracción de los metadatos:

core_mapping = {
   'title': 'Title',
   'subject': 'Subject',
   'creator': 'Author(s)',
   'keywords': 'Keywords',
   'description': 'Description',
   'lastModifiedBy': 'Last Modified By',
   'modified': 'Modified Date',
   'created': 'Created Date',
   'category': 'Category',
   'contentStatus': 'Status',
   'revision': 'Revision'
}

Utilizar iterchildren() método para acceder a cada una de las etiquetas dentro del archivo XML -

for element in core_xml.getchildren():
   for key, title in core_mapping.items():
      if key in element.tag:
         if 'date' in title.lower():
            text = dt.strptime(element.text, "%Y-%m-%dT%H:%M:%SZ")
         else:
            text = element.text
         print("{}: {}".format(title, text))

Del mismo modo, haga esto para el archivo app.xml que contiene información estadística sobre el contenido del documento:

app_mapping = {
   'TotalTime': 'Edit Time (minutes)',
   'Pages': 'Page Count',
   'Words': 'Word Count',
   'Characters': 'Character Count',
   'Lines': 'Line Count',
   'Paragraphs': 'Paragraph Count',
   'Company': 'Company',
   'HyperlinkBase': 'Hyperlink Base',
   'Slides': 'Slide count',
   'Notes': 'Note Count',
   'HiddenSlides': 'Hidden Slide Count',
}
for element in app_xml.getchildren():
   for key, title in app_mapping.items():
      if key in element.tag:
         if 'date' in title.lower():
            text = dt.strptime(element.text, "%Y-%m-%dT%H:%M:%SZ")
         else:
            text = element.text
         print("{}: {}".format(title, text))

Ahora, después de ejecutar el script anterior, podemos obtener los diferentes detalles sobre el documento en particular. Tenga en cuenta que podemos aplicar este script en documentos de Office 2007 o versiones posteriores únicamente.