Điều tra siêu dữ liệu được nhúng
Trong chương này, chúng ta sẽ tìm hiểu chi tiết về cách điều tra siêu dữ liệu được nhúng bằng pháp y kỹ thuật số Python.
Giới thiệu
Siêu dữ liệu được nhúng là thông tin về dữ liệu được lưu trữ trong cùng một tệp có đối tượng được mô tả bởi dữ liệu đó. Nói cách khác, đó là thông tin về tài sản kỹ thuật số được lưu trữ trong chính tệp kỹ thuật số. Nó luôn được liên kết với tệp và không bao giờ có thể tách rời.
Trong trường hợp pháp y kỹ thuật số, chúng tôi không thể trích xuất tất cả thông tin về một tệp cụ thể. Mặt khác, siêu dữ liệu được nhúng có thể cung cấp cho chúng tôi thông tin quan trọng đối với cuộc điều tra. Ví dụ: siêu dữ liệu của tệp văn bản có thể chứa thông tin về tác giả, độ dài, ngày viết và thậm chí là một bản tóm tắt ngắn về tài liệu đó. Hình ảnh kỹ thuật số có thể bao gồm siêu dữ liệu như độ dài của hình ảnh, tốc độ cửa trập, v.v.
Phần mềm có chứa thuộc tính siêu dữ liệu và phần trích xuất của chúng
Trong phần này, chúng ta sẽ tìm hiểu về các tạo tác khác nhau chứa các thuộc tính siêu dữ liệu và quy trình trích xuất của chúng bằng Python.
Âm thanh và video
Đây là hai tạo tác rất phổ biến có siêu dữ liệu được nhúng. Siêu dữ liệu này có thể được trích xuất cho mục đích điều tra.
Bạn có thể sử dụng tập lệnh Python sau để trích xuất các thuộc tính hoặc siêu dữ liệu phổ biến từ tệp âm thanh hoặc MP3 và video hoặc tệp MP4.
Lưu ý rằng đối với tập lệnh này, chúng tôi cần cài đặt thư viện python của bên thứ ba có tên là mutagen cho phép chúng tôi trích xuất siêu dữ liệu từ các tệp âm thanh và video. Nó có thể được cài đặt với sự trợ giúp của lệnh sau:
pip install mutagen
Một số thư viện hữu ích mà chúng ta cần nhập cho tập lệnh Python này như sau:
from __future__ import print_function
import argparse
import json
import mutagen
Trình xử lý dòng lệnh sẽ nhận một đối số đại diện cho đường dẫn đến tệp MP3 hoặc MP4. Sau đó, chúng tôi sẽ sử dụngmutagen.file() phương thức để mở một xử lý tệp như sau:
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)
Bây giờ, chúng ta cần sử dụng hai tay cầm, một để trích xuất dữ liệu từ MP3 và một để trích xuất dữ liệu từ tệp MP4. Chúng ta có thể xác định các chốt này như sau:
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'))
Bây giờ, chúng ta cần lặp qua tệp MP4 này như sau:
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))
Tập lệnh trên sẽ cung cấp cho chúng ta thông tin bổ sung về tệp MP3 cũng như MP4.
Hình ảnh
Hình ảnh có thể chứa các loại siêu dữ liệu khác nhau tùy thuộc vào định dạng tệp của nó. Tuy nhiên, hầu hết các hình ảnh đều nhúng thông tin GPS. Chúng tôi có thể trích xuất thông tin GPS này bằng cách sử dụng các thư viện Python của bên thứ ba. Bạn có thể sử dụng tập lệnh Python sau có thể được sử dụng để làm tương tự:
Đầu tiên, tải xuống thư viện python của bên thứ ba có tên Python Imaging Library (PIL) như sau -
pip install pillow
Điều này sẽ giúp chúng tôi trích xuất siêu dữ liệu từ hình ảnh.
Chúng tôi cũng có thể ghi chi tiết GPS được nhúng trong hình ảnh vào tệp KML, nhưng để làm điều này, chúng tôi cần tải xuống thư viện Python của bên thứ ba có tên simplekml như sau -
pip install simplekml
Trong tập lệnh này, trước tiên chúng ta cần nhập các thư viện sau:
from __future__ import print_function
import argparse
from PIL import Image
from PIL.ExifTags import TAGS
import simplekml
import sys
Bây giờ, trình xử lý dòng lệnh sẽ chấp nhận một đối số vị trí về cơ bản đại diện cho đường dẫn tệp của ảnh.
parser = argparse.ArgumentParser('Metadata from images')
parser.add_argument('PICTURE_FILE', help = "Path to picture")
args = parser.parse_args()
Bây giờ, chúng ta cần chỉ định các URL sẽ điền thông tin tọa độ. Các URL làgmaps và open_maps. Chúng ta cũng cần một hàm để chuyển đổi tọa độ tuple độ phút giây (DMS), được cung cấp bởi thư viện PIL, thành số thập phân. Nó có thể được thực hiện như sau:
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
Bây giờ, chúng ta sẽ sử dụng image.open() để mở tệp dưới dạng đối tượng 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
Sau khi tìm thấy GPSInfo , chúng tôi sẽ lưu trữ tham chiếu GPS và xử lý các tọa độ với process_coords() phương pháp.
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
Bây giờ, bắt đầu kml đối tượng từ simplekml thư viện như sau -
kml = simplekml.Kml()
kml.newpoint(name = args.PICTURE_FILE, coords = [(lon, lat)])
kml.save(args.PICTURE_FILE + ".kml")
Bây giờ chúng ta có thể in tọa độ từ thông tin đã xử lý như sau:
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"))
Tài liệu PDF
Tài liệu PDF có nhiều loại phương tiện bao gồm hình ảnh, văn bản, biểu mẫu, v.v. Khi chúng tôi trích xuất siêu dữ liệu được nhúng trong tài liệu PDF, chúng tôi có thể nhận được dữ liệu kết quả ở định dạng được gọi là Nền tảng siêu dữ liệu có thể mở rộng (XMP). Chúng tôi có thể trích xuất siêu dữ liệu với sự trợ giúp của mã Python sau:
Đầu tiên, hãy cài đặt thư viện Python của bên thứ ba có tên PyPDF2để đọc siêu dữ liệu được lưu trữ ở định dạng XMP. Nó có thể được cài đặt như sau:
pip install PyPDF2
Bây giờ, hãy nhập các thư viện sau để trích xuất siêu dữ liệu từ tệp PDF -
from __future__ import print_function
from argparse import ArgumentParser, FileType
import datetime
from PyPDF2 import PdfFileReader
import sys
Bây giờ, trình xử lý dòng lệnh sẽ chấp nhận một đối số vị trí về cơ bản đại diện cho đường dẫn tệp của tệp PDF.
parser = argparse.ArgumentParser('Metadata from PDF')
parser.add_argument('PDF_FILE', help='Path to PDF file',type=FileType('rb'))
args = parser.parse_args()
Bây giờ chúng ta có thể sử dụng getXmpMetadata() để cung cấp một đối tượng chứa siêu dữ liệu có sẵn như sau:
pdf_file = PdfFileReader(args.PDF_FILE)
xmpm = pdf_file.getXmpMetadata()
if xmpm is None:
print("No XMP metadata found in document.")
sys.exit()
Chúng ta có thể sử dụng custom_print() để trích xuất và in các giá trị liên quan như tiêu đề, người tạo, người đóng góp, v.v. như sau:
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)
Chúng tôi cũng có thể xác định custom_print() trong trường hợp nếu PDF được tạo bằng nhiều phần mềm như sau:
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)))
Chúng tôi cũng có thể trích xuất bất kỳ thuộc tính tùy chỉnh nào khác được phần mềm lưu như sau:
if xmpm.custom_properties:
print("Custom Properties:")
for k, v in xmpm.custom_properties.items():
print("\t{}: {}".format(k, v))
Tập lệnh trên sẽ đọc tài liệu PDF và sẽ in siêu dữ liệu được lưu trữ ở định dạng XMP bao gồm một số thuộc tính tùy chỉnh được phần mềm lưu trữ với sự trợ giúp của tệp PDF đó.
Tệp Windows Executables
Đôi khi chúng tôi có thể gặp phải một tệp thực thi đáng ngờ hoặc trái phép. Nhưng đối với mục đích điều tra, nó có thể hữu ích vì siêu dữ liệu được nhúng. Chúng ta có thể lấy thông tin như vị trí, mục đích của nó và các thuộc tính khác như nhà sản xuất, ngày biên dịch, v.v. Với sự trợ giúp của tập lệnh Python sau, chúng ta có thể lấy ngày biên dịch, dữ liệu hữu ích từ tiêu đề và các ký hiệu được nhập cũng như xuất.
Với mục đích này, trước tiên hãy cài đặt thư viện Python của bên thứ ba pefile. Nó có thể được thực hiện như sau:
pip install pefile
Khi bạn cài đặt thành công, hãy nhập các thư viện sau như sau:
from __future__ import print_function
import argparse
from datetime import datetime
from pefile import PE
Bây giờ, trình xử lý dòng lệnh sẽ chấp nhận một đối số vị trí về cơ bản đại diện cho đường dẫn tệp của tệp thực thi. Bạn cũng có thể chọn kiểu đầu ra, cho dù bạn cần nó một cách chi tiết và dài dòng hay đơn giản hóa. Đối với điều này, bạn cần đưa ra một đối số tùy chọn như hình dưới đây:
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()
Bây giờ, chúng ta sẽ tải tệp thực thi đầu vào bằng cách sử dụng lớp PE. Chúng tôi cũng sẽ kết xuất dữ liệu thực thi vào một đối tượng từ điển bằng cách sử dụngdump_dict() phương pháp.
pe = PE(args.EXE_FILE)
ped = pe.dump_dict()
Chúng tôi có thể trích xuất siêu dữ liệu tệp cơ bản như quyền tác giả được nhúng, phiên bản và thời gian biên dịch bằng cách sử dụng mã hiển thị bên dưới:
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()))
Chúng tôi có thể trích xuất dữ liệu hữu ích từ các tiêu đề như sau:
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'])
)
Bây giờ, hãy trích xuất danh sách các lần nhập và xuất từ các tệp thực thi như hình dưới đây -
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()
Bây giờ, in exports, names và addresses bằng cách sử dụng mã như hình dưới đây -
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)))
Tập lệnh trên sẽ trích xuất siêu dữ liệu cơ bản, thông tin từ tiêu đề từ các tệp thực thi của windows.
Siêu dữ liệu tài liệu Office
Hầu hết các công việc trong máy tính được thực hiện trong ba ứng dụng của MS Office - Word, PowerPoint và Excel. Các tệp này sở hữu siêu dữ liệu khổng lồ, có thể tiết lộ thông tin thú vị về quyền tác giả và lịch sử của chúng.
Lưu ý rằng siêu dữ liệu từ năm 2007 có định dạng word (.docx), excel (.xlsx) và powerpoint (.pptx) được lưu trữ trong một tệp XML. Chúng tôi có thể xử lý các tệp XML này bằng Python với sự trợ giúp của tập lệnh Python sau được hiển thị bên dưới:
Đầu tiên, nhập các thư viện bắt buộc như hình dưới đây -
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()
Bây giờ, hãy kiểm tra xem tệp có phải là tệp ZIP hay không. Khác, nêu ra một lỗi. Bây giờ, hãy mở tệp và trích xuất các phần tử chính để xử lý bằng đoạn mã sau:
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'))
Bây giờ, hãy tạo một từ điển để bắt đầu trích xuất siêu dữ liệu -
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'
}
Sử dụng iterchildren() để truy cập từng thẻ trong tệp 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))
Tương tự, hãy làm điều này cho tệp app.xml chứa thông tin thống kê về nội dung của tài liệu -
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))
Bây giờ sau khi chạy tập lệnh trên, chúng ta có thể nhận được các chi tiết khác nhau về tài liệu cụ thể. Lưu ý rằng chúng tôi chỉ có thể áp dụng tập lệnh này trên các tài liệu phiên bản Office 2007 trở lên.