研究嵌入式元数据

在本章中,我们将详细了解使用Python数字取证调查嵌入式元数据.

简介

嵌入式元数据是关于存储在同一文件中的数据,该文件具有该数据描述的对象.换句话说,它是关于存储在数字文件本身中的数字资产的信息.它始终与文件相关联,永远不会分开.

如果是数字取证,我们无法提取有关特定文件的所有信息.另一方面,嵌入式元数据可以为我们提供对调查至关重要的信息.例如,文本文件的元数据可能包含有关作者,其长度,书面日期的信息,甚至包含有关该文档的简短摘要.数字图像可以包括元数据,例如图像的长度,快门速度等.

包含元数据属性及其提取的工件

In在本节中,我们将了解包含元数据属性的各种工件及其使用Python的提取过程.

音频和视频

这两个是非常常见的具有嵌入元数据的工件.可以提取此元数据用于调查.

您可以使用以下Python脚本从音频或MP3文件以及视频或MP4文件中提取公共属性或元数据.

请注意,对于此脚本,我们需要安装名为mutagen的第三方python库,它允许我们从音频和视频文件中提取元数据.它可以在以下命令的帮助下安装 :

 
 pip install mutagen


我们需要为这个Python脚本导入的一些有用的库如下:&/;

from __future__ import print_function

import argparse
import json
import mutagen


命令行处理程序将采用一个表示路径的参数到MP3或MP4文件.然后,我们将使用 mutagen.file()方法打开文件的句柄,如下所示 :

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)


现在,我们需要使用两个句柄,一个用于从MP3中提取数据,另一个用于从MP4文件中提取数据.我们可以将这些句柄定义如下 :

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


现在,我们需要按照以下方式迭代这个MP4文件;

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


以上脚本将为我们提供有关MP3和MP4文件的更多信息.

图像

图像可能根据文件格式包含不同类型的元数据.但是,大多数图像都嵌入了GPS信息.我们可以使用第三方Python库提取这些GPS信息.你可以使用下面的Python脚本来做同样的事情.

首先,下载名为 Python Imaging Library(PIL)的第三方python库,如下所示 :

 
 pip install pillow


这将有助于我们从图像中提取元数据.

我们也可以将嵌入图像的GPS细节写入KML文件,但为此我们需要下载第三个派对Python库名为 simplekml ,如下 :

 
 pip install simplekml


在这个脚本中,首先我们需要导入以下库 :

from __future__ import print_function
import argparse

from PIL import Image
from PIL.ExifTags import TAGS

import simplekml
import sys


现在,命令行处理程序将接受一个位置参数,该参数基本上代表照片的文件路径.

 
 parser = argparse.ArgumentParser('来自图像的元数据')
 parser.add_argument('PICTURE_FILE',help ="图片路径")
 args = parser.parse_args()


现在,我们需要指定将填充坐标信息的URL.网址为 gmaps open_maps .我们还需要一个函数来将PIL库提供的度数分秒(DMS)元组坐标转换为十进制.它可以按照以下方式完成;

gmaps = "https://www.google.com/maps?q={},{}"
open_maps = "https://img01.yuandaxia.cn/Content/img/tutorials/python_digital_forensics/mlat=%7B%7D&mlon=%7B%7D"

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


现在,我们将使用 image.open()函数将文件作为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


找到 GPSInfo 标签后,我们将存储GPS参考并处理使用 process_coords()方法进行坐标.

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


现在,从 simplekml 库中启动 kml 对象,如下所示 :

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


我们现在可以打印处理过的信息中的坐标,如下所示;

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


PDF文档

PDF文档种类繁多媒体包括图像,文本,表格等.当我们在PDF文档中提取嵌入的元数据时,我们可以以称为可扩展元数据平台(XMP)的格式获得结果数据.我们可以借助以下Python代码提取元数据 :

首先,安装名为 PyPDF2 的第三方Python库,以读取以XMP格式存储的元数据.它可以安装如下 :

 
 pip install PyPDF2


现在,导入以下库以从PDF文件中提取元数据 :

from __future__ import print_function
from argparse import ArgumentParser, FileType

import datetime
from PyPDF2 import PdfFileReader
import sys


现在,命令行处理程序将接受一个位置参数,基本上代表PDF文件的文件路径.

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


现在我们可以使用 getXmpMetadata()方法提供包含可用元数据的对象,如下所示;

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

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


我们可以使用 custom_print()方法来提取和打印标题,创建者,贡献者等相关值如下&&;

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)


我们还可以定义 custom_print() 如果PDF是使用多个软件创建的方法,如下所示 :

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


我们还可以提取软件保存的任何其他自定义属性,如下所示;

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


以上脚本将读取PDF文档,并将打印以XMP格式存储的元数据,包括软件存储的一些自定义属性,以及PDF的帮助.

Windows可执行文件

有时我们可能会遇到可疑或未经授权的可执行文件.但是出于调查的目的,由于嵌入的元数据,它可能是有用的.我们可以获得诸如其位置,目的和其他属性(如制造商,编译日期等)的信息.借助以下Python脚本,我们可以获得编译日期,来自标题以及导入和导出符号的有用数据.

为此,首先安装第三方Python库 pefile .它可以按照以下方式完成 :

pip install pefile

成功安装后,导入以下库,如下所示:

现在,命令行处理程序将接受一个基本代表文件路径的位置参数可执行文件.您也可以选择输出样式,无论是详细而冗长还是简单的方式.为此,您需要提供一个可选参数,如下所示 :

from __future__ import print_function

import argparse
from datetime import datetime
from pefile import PE
现在,我们将加载输入的可执行文件使用PE类.我们还将使用 dump_dict()方法将可执行数据转储到字典对象.
pe = PE(args.EXE_FILE)
ped = pe.dump_dict()
我们可以使用以下方法提取基本文件元数据,例如嵌入式作者,版本和编译时间下面显示的代码 :
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()))
我们可以从标题中提取有用数据,如下所示;
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'])
   )
现在,从可执行文件中提取导入和导出列表,如下所示 :
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()
现在,打印使用如下所示的代码导出,名称和地址 :
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)))
以上脚本将提取基本元数据,来自Windows可执行文件的标题信息.

Office文档元数据

计算机中的大部分工作都是在三个MS应用程序中完成的办公室 -  Word,PowerPoint和Excel.这些文件拥有巨大的元数据,可以显示有关其作者和历史的有趣信息.

请注意2007年格式的单词(.docx),excel(.xlsx)和powerpoint(. pptx)存储在XML文件中.我们可以使用下面显示的Python脚本帮助处理Python中的这些XML文件 :

首先,导入所需的库,如下所示 :

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


现在,检查文件是否是ZIP文件.否则,引发错误.现在,打开文件并使用以下代码提取要处理的关键元素;

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


现在,创建一个字典来启动元数据的提取 :

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


使用 iterchildren()方法访问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))


同样,对app.xml文件执行此操作,其中包含有关文档内容的统计信息 :

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


现在运行上面的脚本后,我们可以获得有关特定文档的不同详细信息.请注意,我们只能在Office 2007或更高版本的文档中应用此脚本.