Badanie osadzonych metadanych

W tym rozdziale dowiemy się szczegółowo o badaniu osadzonych metadanych za pomocą cyfrowej analizy śledczej w języku Python.

Wprowadzenie

Osadzone metadane to informacje o danych przechowywanych w tym samym pliku, w którym znajduje się obiekt opisany przez te dane. Innymi słowy, jest to informacja o zasobie cyfrowym przechowywanym w samym pliku cyfrowym. Jest zawsze powiązany z plikiem i nigdy nie można go rozdzielić.

W przypadku kryminalistyki cyfrowej nie możemy wydobyć wszystkich informacji o konkretnym pliku. Z drugiej strony osadzone metadane mogą dostarczyć nam informacji krytycznych dla dochodzenia. Na przykład metadane pliku tekstowego mogą zawierać informacje o autorze, jego długości, dacie powstania, a nawet krótkie podsumowanie tego dokumentu. Obraz cyfrowy może zawierać metadane, takie jak długość obrazu, czas otwarcia migawki itp.

Artefakty zawierające atrybuty metadanych i ich wyodrębnianie

W tej sekcji dowiemy się o różnych artefaktach zawierających atrybuty metadanych i procesie ich wyodrębniania za pomocą Pythona.

Dźwięk i wideo

Są to dwa bardzo popularne artefakty, które mają osadzone metadane. Te metadane można pobrać w celu zbadania.

Możesz użyć następującego skryptu Python, aby wyodrębnić wspólne atrybuty lub metadane z pliku audio lub MP3 i pliku wideo lub MP4.

Zauważ, że w przypadku tego skryptu musimy zainstalować bibliotekę Pythona innej firmy o nazwie mutagen, która pozwala nam wyodrębniać metadane z plików audio i wideo. Można go zainstalować za pomocą następującego polecenia -

pip install mutagen

Niektóre z przydatnych bibliotek, które musimy zaimportować dla tego skryptu Pythona, są następujące -

from __future__ import print_function

import argparse
import json
import mutagen

Program obsługi wiersza poleceń przyjmuje jeden argument, który reprezentuje ścieżkę do plików MP3 lub MP4. Następnie użyjemymutagen.file() metoda otwierania dojścia do pliku w następujący sposób -

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)

Teraz musimy użyć dwóch uchwytów, jednego do wyodrębnienia danych z MP3, a drugiego do wyodrębnienia danych z pliku MP4. Możemy zdefiniować te uchwyty w następujący sposób -

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

Teraz musimy powtórzyć ten plik MP4 w następujący sposób -

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

Powyższy skrypt dostarczy nam dodatkowych informacji o plikach MP3 oraz MP4.

Obrazy

Obrazy mogą zawierać różnego rodzaju metadane w zależności od formatu pliku. Jednak większość obrazów zawiera informacje GPS. Możemy wyodrębnić te informacje GPS, korzystając z bibliotek Pythona innych firm. Możesz użyć następującego skryptu Python, aby zrobić to samo -

Najpierw pobierz bibliotekę Pythona innej firmy o nazwie Python Imaging Library (PIL) w następujący sposób -

pip install pillow

Pomoże nam to wyodrębnić metadane z obrazów.

Możemy również zapisać szczegóły GPS osadzone w obrazach do pliku KML, ale w tym celu musimy pobrać bibliotekę Pythona innej firmy o nazwie simplekml w następujący sposób -

pip install simplekml

W tym skrypcie najpierw musimy zaimportować następujące biblioteki -

from __future__ import print_function
import argparse

from PIL import Image
from PIL.ExifTags import TAGS

import simplekml
import sys

Teraz program obsługi wiersza poleceń zaakceptuje jeden argument pozycyjny, który zasadniczo reprezentuje ścieżkę pliku zdjęć.

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

Teraz musimy określić adresy URL, które będą wypełniać informacje o współrzędnych. Adresy URL togmaps i open_maps. Potrzebujemy również funkcji konwertującej współrzędną krotki stopniominut sekundy (DMS) dostarczoną przez bibliotekę PIL na dziesiętną. Można to zrobić w następujący sposób -

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

Teraz użyjemy image.open() funkcji, aby otworzyć plik jako obiekt 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

Po znalezieniu GPSInfo tag, zapiszemy odniesienie GPS i przetworzymy współrzędne z process_coords() metoda.

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

Teraz zainicjuj kml obiekt z simplekml biblioteka w następujący sposób -

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

Możemy teraz wydrukować współrzędne z przetworzonych informacji w następujący sposób -

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

Dokumenty PDF

Dokumenty PDF zawierają wiele różnych mediów, w tym obrazy, tekst, formularze itp. Kiedy wyodrębniamy osadzone metadane w dokumentach PDF, możemy uzyskać wynikowe dane w formacie zwanym Extensible Metadata Platform (XMP). Możemy wyodrębnić metadane za pomocą następującego kodu Pythona -

Najpierw zainstaluj bibliotekę Pythona innej firmy o nazwie PyPDF2czytać metadane zapisane w formacie XMP. Można go zainstalować w następujący sposób -

pip install PyPDF2

Teraz zaimportuj następujące biblioteki, aby wyodrębnić metadane z plików PDF -

from __future__ import print_function
from argparse import ArgumentParser, FileType

import datetime
from PyPDF2 import PdfFileReader
import sys

Teraz program obsługi wiersza poleceń zaakceptuje jeden argument pozycyjny, który zasadniczo reprezentuje ścieżkę pliku PDF.

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

Teraz możemy użyć getXmpMetadata() metoda dostarczania obiektu zawierającego dostępne metadane w następujący sposób -

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

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

Możemy użyć custom_print() metoda wyodrębniania i drukowania odpowiednich wartości, takich jak tytuł, twórca, współpracownik itp. w następujący sposób -

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)

Możemy też zdefiniować custom_print() w przypadku, gdy plik PDF został utworzony przy użyciu wielu programów w następujący sposób -

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

Możemy również wyodrębnić dowolną inną niestandardową właściwość zapisaną przez oprogramowanie w następujący sposób -

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

Powyższy skrypt odczyta dokument PDF i wydrukuje metadane zapisane w formacie XMP, w tym niektóre niestandardowe właściwości przechowywane przez oprogramowanie, za pomocą którego utworzono ten plik PDF.

Pliki wykonywalne systemu Windows

Czasami możemy napotkać podejrzany lub nieautoryzowany plik wykonywalny. Jednak dla celów badania może być przydatne ze względu na osadzone metadane. Możemy uzyskać takie informacje jak jego lokalizacja, przeznaczenie i inne atrybuty, takie jak producent, data kompilacji itp. Za pomocą następującego skryptu w Pythonie możemy uzyskać datę kompilacji, przydatne dane z nagłówków oraz importowane i eksportowane symbole.

W tym celu najpierw zainstaluj bibliotekę Pythona innej firmy pefile. Można to zrobić w następujący sposób -

pip install pefile

Po pomyślnym zainstalowaniu zaimportuj następujące biblioteki w następujący sposób -

from __future__ import print_function

import argparse
from datetime import datetime
from pefile import PE

Teraz program obsługi wiersza poleceń przyjmie jeden argument pozycyjny, który w zasadzie reprezentuje ścieżkę do pliku wykonywalnego. Możesz także wybrać styl wyjścia, niezależnie od tego, czy potrzebujesz go w sposób szczegółowy i szczegółowy, czy w sposób uproszczony. W tym celu musisz podać opcjonalny argument, jak pokazano poniżej -

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

Teraz załadujemy wejściowy plik wykonywalny za pomocą klasy PE. Zrzucimy również dane wykonywalne do obiektu słownika przy użyciudump_dict() metoda.

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

Możemy wyodrębnić podstawowe metadane plików, takie jak osadzone autorstwo, wersja i czas kompilacji, używając kodu pokazanego poniżej -

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

Możemy wyodrębnić przydatne dane z nagłówków w następujący sposób -

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

Teraz wypakuj listę importów i eksportów z plików wykonywalnych, jak pokazano poniżej -

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

Teraz drukuj exports, names i addresses używając kodu, jak pokazano poniżej -

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

Powyższy skrypt wyodrębni podstawowe metadane, informacje z nagłówków z plików wykonywalnych systemu Windows.

Metadane dokumentów pakietu Office

Większość pracy na komputerze wykonywana jest w trzech aplikacjach MS Office - Word, PowerPoint i Excel. Pliki te mają ogromne metadane, które mogą ujawniać interesujące informacje o ich autorstwie i historii.

Zwróć uwagę, że metadane z formatu Word (.docx), Excel (.xlsx) i PowerPoint (.pptx) z 2007 roku są przechowywane w pliku XML. Możemy przetwarzać te pliki XML w Pythonie za pomocą następującego skryptu Pythona pokazanego poniżej -

Najpierw zaimportuj wymagane biblioteki, jak pokazano poniżej -

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

Teraz sprawdź, czy plik jest plikiem ZIP. W przeciwnym razie zgłoś błąd. Teraz otwórz plik i wyodrębnij kluczowe elementy do przetworzenia, używając następującego kodu -

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

Teraz utwórz słownik, aby zainicjować wyodrębnianie metadanych -

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

Posługiwać się iterchildren() metoda dostępu do każdego z tagów w pliku 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))

Podobnie zrób to dla pliku app.xml, który zawiera informacje statystyczne o zawartości dokumentu -

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

Teraz po uruchomieniu powyższego skryptu możemy uzyskać różne szczegóły dotyczące konkretnego dokumentu. Należy pamiętać, że możemy zastosować ten skrypt tylko do dokumentów pakietu Office 2007 lub nowszych wersji.