Investigando Metadados Embutidos

Neste capítulo, aprenderemos em detalhes sobre a investigação de metadados embutidos usando Python digital forensics.

Introdução

Metadados incorporados são as informações sobre os dados armazenados no mesmo arquivo que contém o objeto descrito por esses dados. Em outras palavras, são as informações sobre um ativo digital armazenadas no próprio arquivo digital. Ele está sempre associado ao arquivo e nunca pode ser separado.

No caso da perícia digital, não podemos extrair todas as informações sobre um determinado arquivo. Por outro lado, os metadados incorporados podem nos fornecer informações críticas para a investigação. Por exemplo, os metadados de um arquivo de texto podem conter informações sobre o autor, sua extensão, data de escrita e até mesmo um breve resumo sobre aquele documento. Uma imagem digital pode incluir os metadados, como o comprimento da imagem, a velocidade do obturador, etc.

Artefatos que contêm atributos de metadados e sua extração

Nesta seção, aprenderemos sobre vários artefatos contendo atributos de metadados e seu processo de extração usando Python.

Áudio e vídeo

Esses são os dois artefatos muito comuns que possuem os metadados integrados. Esses metadados podem ser extraídos para fins de investigação.

Você pode usar o seguinte script Python para extrair atributos comuns ou metadados do arquivo de áudio ou MP3 e de um vídeo ou arquivo MP4.

Observe que, para este script, precisamos instalar uma biblioteca python de terceiros chamada mutagen, que nos permite extrair metadados de arquivos de áudio e vídeo. Ele pode ser instalado com a ajuda do seguinte comando -

pip install mutagen

Algumas das bibliotecas úteis que precisamos importar para este script Python são as seguintes -

from __future__ import print_function

import argparse
import json
import mutagen

O manipulador de linha de comando terá um argumento que representa o caminho para os arquivos MP3 ou MP4. Então, vamos usarmutagen.file() método para abrir um identificador para o arquivo da seguinte maneira -

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)

Agora, precisamos usar duas alças, uma para extrair os dados do MP3 e outra para extrair os dados do arquivo MP4. Podemos definir essas alças da seguinte maneira -

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

Agora, precisamos iterar por meio desse arquivo MP4 da seguinte maneira -

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

O script acima nos fornecerá informações adicionais sobre os arquivos MP3 e MP4.

Imagens

As imagens podem conter diferentes tipos de metadados, dependendo do formato do arquivo. No entanto, a maioria das imagens contém informações de GPS. Podemos extrair essas informações de GPS usando bibliotecas Python de terceiros. Você pode usar o seguinte script Python para fazer o mesmo -

Primeiro, baixe a biblioteca python de terceiros chamada Python Imaging Library (PIL) como segue -

pip install pillow

Isso nos ajudará a extrair metadados de imagens.

Também podemos gravar os detalhes do GPS incorporados nas imagens em um arquivo KML, mas para isso precisamos baixar a biblioteca Python de terceiros chamada simplekml como segue -

pip install simplekml

Neste script, primeiro precisamos importar as seguintes bibliotecas -

from __future__ import print_function
import argparse

from PIL import Image
from PIL.ExifTags import TAGS

import simplekml
import sys

Agora, o manipulador de linha de comando aceitará um argumento posicional que basicamente representa o caminho do arquivo das fotos.

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

Agora, precisamos especificar os URLs que preencherão as informações das coordenadas. Os URLs sãogmaps e open_maps. Também precisamos de uma função para converter a coordenada da tupla de graus, minuto e segundo (DMS), fornecida pela biblioteca PIL, em decimal. Isso pode ser feito da seguinte forma -

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

Agora, vamos usar image.open() função para abrir o arquivo 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

Depois de encontrar o GPSInfo tag, vamos armazenar a referência GPS e processar as coordenadas com o 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

Agora, inicie kml objeto de simplekml biblioteca da seguinte forma -

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

Agora podemos imprimir as coordenadas das informações processadas da seguinte forma -

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

Os documentos PDF têm uma grande variedade de mídias, incluindo imagens, texto, formulários, etc. Quando extraímos metadados incorporados em documentos PDF, podemos obter os dados resultantes no formato denominado Extensible Metadata Platform (XMP). Podemos extrair metadados com a ajuda do seguinte código Python -

Primeiro, instale uma biblioteca Python de terceiros chamada PyPDF2para ler metadados armazenados no formato XMP. Ele pode ser instalado da seguinte forma -

pip install PyPDF2

Agora, importe as seguintes bibliotecas para extrair os metadados de arquivos PDF -

from __future__ import print_function
from argparse import ArgumentParser, FileType

import datetime
from PyPDF2 import PdfFileReader
import sys

Agora, o manipulador de linha de comando aceitará um argumento posicional que basicamente representa o caminho do arquivo PDF.

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

Agora podemos usar getXmpMetadata() método para fornecer um objeto contendo os metadados disponíveis da seguinte forma -

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

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

Podemos usar custom_print() método para extrair e imprimir os valores relevantes, como título, criador, contribuidor, etc. da seguinte forma -

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)

Nós também podemos definir custom_print() método no caso de o PDF ser criado usando vários softwares, como segue -

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

Também podemos extrair qualquer outra propriedade personalizada salva pelo software da seguinte forma -

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

O script acima lerá o documento PDF e imprimirá os metadados armazenados no formato XMP, incluindo algumas propriedades personalizadas armazenadas pelo software com a ajuda do qual o PDF foi criado.

Arquivos executáveis ​​do Windows

Às vezes, podemos encontrar um arquivo executável suspeito ou não autorizado. Mas, para fins de investigação, pode ser útil por causa dos metadados incorporados. Podemos obter as informações como sua localização, sua finalidade e outros atributos, como o fabricante, data de compilação, etc. Com a ajuda do seguinte script Python, podemos obter a data de compilação, dados úteis de cabeçalhos e símbolos importados, bem como exportados.

Para isso, primeiro instale a biblioteca Python de terceiros pefile. Isso pode ser feito da seguinte forma -

pip install pefile

Depois de instalar com sucesso, importe as seguintes bibliotecas da seguinte forma -

from __future__ import print_function

import argparse
from datetime import datetime
from pefile import PE

Agora, o manipulador de linha de comando aceitará um argumento posicional que basicamente representa o caminho do arquivo executável. Você também pode escolher o estilo de saída, se você precisa dele de forma detalhada e detalhada ou de forma simplificada. Para isso, você precisa fornecer um argumento opcional conforme mostrado abaixo -

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

Agora, carregaremos o arquivo executável de entrada usando a classe PE. Também iremos despejar os dados executáveis ​​em um objeto de dicionário usandodump_dict() método.

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

Podemos extrair metadados de arquivo básicos, como autoria incorporada, versão e tempo de compilação usando o código mostrado abaixo -

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 extrair os dados úteis dos cabeçalhos da seguinte maneira -

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

Agora, extraia a lista de importações e exportações de arquivos executáveis, conforme mostrado abaixo -

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

Agora, imprima exports, names e addresses usando o código conforme mostrado abaixo -

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

O script acima irá extrair os metadados básicos, informações dos cabeçalhos dos arquivos executáveis ​​do Windows.

Metadados de documentos de escritório

A maior parte do trabalho no computador é feita em três aplicativos do MS Office - Word, PowerPoint e Excel. Esses arquivos possuem metadados enormes, que podem expor informações interessantes sobre sua autoria e história.

Observe que os metadados do formato de Word (.docx), Excel (.xlsx) e PowerPoint (.pptx) de 2007 são armazenados em um arquivo XML. Podemos processar esses arquivos XML em Python com a ajuda do seguinte script Python mostrado abaixo -

Primeiro, importe as bibliotecas necessárias conforme mostrado abaixo -

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

Agora, verifique se o arquivo é um arquivo ZIP. Caso contrário, gere um erro. Agora, abra o arquivo e extraia os elementos-chave para processamento usando o seguinte 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'))

Agora, crie um dicionário para iniciar a extração dos metadados -

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

Usar iterchildren() método para acessar cada uma das tags dentro do arquivo 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))

Da mesma forma, faça isso para o arquivo app.xml, que contém informações estatísticas sobre o conteúdo do 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))

Agora, depois de executar o script acima, podemos obter os diferentes detalhes sobre o documento específico. Observe que podemos aplicar este script em documentos do Office 2007 ou versões posteriores apenas.