Python数字取证 调查嵌入式元数据
在本章中,我们将详细了解使用Python数字取证技术调查嵌入式元数据的情况。
简介
嵌入元数据是存储在同一文件中的数据信息,该文件有该数据所描述的对象。换句话说,它是存储在数字文件本身的数字资产的信息。它总是与文件相关联,而且永远无法分离。
在数字取证的情况下,我们无法提取关于一个特定文件的所有信息。另一方面,嵌入式元数据可以为我们提供对调查至关重要的信息。例如,一个文本文件的元数据可能包含关于作者的信息,它的长度,写作日期,甚至关于该文件的简短摘要。一张数字图像可能包括元数据,如图像的长度、快门速度等。
包含元数据属性的工件和它们的提取
在本节中,我们将学习各种包含元数据属性的人工制品以及使用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文件,但为此我们需要下载名为 simplekml 的第三方Python库,如下所示
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('Metadata from images')
parser.add_argument('PICTURE_FILE', help = "Path to picture")
args = parser.parse_args()
现在,我们需要指定将填充坐标信息的URLs。这些URL是 gmaps 和 open_maps。 我们还需要一个函数将PIL库提供的度分秒(DMS)元组坐标转换为十进制。它可以按以下方式完成
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
现在,我们将使用 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代码的帮助下提取元数据– 1.
首先,安装一个名为 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)
如果使用多个软件创建PDF,我们也可以定义 custom_print( )方法,如下所示。
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
现在,命令行处理程序将接受一个位置参数,基本上代表可执行文件的文件路径。你也可以选择输出的风格,你是需要详细的、粗略的方式还是简化的方式。为此,你需要给出一个可选的参数,如下所示
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()
现在,我们将通过使用PE类加载输入的可执行文件。我们还将通过使用 dump_dict() 方法将可执行数据转储为一个字典对象。
pe = PE(args.EXE_FILE)
ped = pe.dump_dict()
We can extract basic file metadata such as embedded authorship, version and compilation time using the code shown below −
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可执行文件的头文件中提取基本元数据、信息。
办公室文件元数据
计算机中的大部分工作是在MS Office的三个应用程序中完成的–Word、PowerPoint和Excel。这些文件拥有巨大的元数据,它可以暴露出关于其作者和历史的有趣信息。
请注意,2007年格式的word(.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文件。否则,引发一个错误。Now, open the file and extract the key elements for processing using the following code −
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或更高版本的文档上应用这个脚本。