Python数字取证 Windows中的重要工件
本章将解释微软Windows取证所涉及的各种概念,以及调查员可以从调查过程中获得的重要人工制品。
简介
工件是计算机系统中的对象或区域,它们具有与计算机用户执行的活动有关的重要信息。这些信息的类型和位置取决于操作系统。在法医分析过程中,这些工件在批准或不批准调查员的观察中起着非常重要的作用。
Windows工件对取证的重要性
由于以下原因,Windows人工制品具有重要意义
- 世界上大约90%的流量来自于使用Windows作为操作系统的计算机。这就是为什么对于数字取证检查员来说,Windows人工制品是非常重要的。
-
Windows操作系统存储了与计算机系统上的用户活动有关的不同类型的证据。这是另一个原因,显示了Windows人工制品对数字取证的重要性。
-
很多时候,调查员围绕着旧的和传统的领域进行调查,如用户装箱的数据。Windows人工制品可以将调查引向非传统的领域,如系统创建的数据或人工制品。
-
Windows提供了大量的人工制品,这对调查员以及进行非正式调查的公司和个人都很有帮助。
-
近年来,网络犯罪的增加是Windows人工制品重要的另一个原因。
Windows工件和它们的Python脚本
在本节中,我们将讨论一些Windows工件和Python脚本来获取它们的信息。
回收站
这是取证调查中重要的Windows人工制品之一。Windows回收站包含了已经被用户删除的文件,但还没有被系统物理删除。即使用户从系统中完全删除了该文件,它也是一个重要的调查源。这是因为检查人员可以从被删除的文件中提取有价值的信息,如原始文件的路径以及被发送到回收站的时间。
请注意,回收站证据的存储方式取决于Windows的版本。在下面的Python脚本中,我们要处理的是Windows 7,它创建了两个文件: $R 文件,包含回收文件的实际内容; $I 文件,包含原始文件名、路径、文件被删除时的大小。
对于Python脚本,我们需要安装第三方模块,即 pytsk3、pyewf 和 unicodecsv。 我们可以使用 pip 来安装它们。我们可以按照以下步骤从回收站中提取信息—-。
- 首先,我们需要使用递归方法来扫描 $Recycle.bin 文件夹并选择所有以 $I 开头的文件 。
-
接下来,我们将读取这些文件的内容并解析可用的元数据结构。
-
现在,我们将搜索相关的$R文件。
-
最后,我们将把结果写到CSV文件中,以供查阅。
让我们看看如何使用Python代码来达到这个目的–
首先,我们需要导入以下Python库 –
from __future__ import print_function
from argparse import ArgumentParser
import datetime
import os
import struct
from utility.pytskutil import TSKUtil
import unicodecsv as csv
接下来,我们需要为命令行处理程序提供参数。注意,这里它将接受三个参数–第一个是证据文件的路径,第二个是证据文件的类型,第三个是CSV报告的期望输出路径,如下所示
if __name__ == '__main__':
parser = argparse.ArgumentParser('Recycle Bin evidences')
parser.add_argument('EVIDENCE_FILE', help = "Path to evidence file")
parser.add_argument('IMAGE_TYPE', help = "Evidence file format",
choices = ('ewf', 'raw'))
parser.add_argument('CSV_REPORT', help = "Path to CSV report")
args = parser.parse_args()
main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.CSV_REPORT)
现在,定义 main() 函数,它将处理所有程序。它将搜索 $I 文件,如下所示
def main(evidence, image_type, report_file):
tsk_util = TSKUtil(evidence, image_type)
dollar_i_files = tsk_util.recurse_files("I", path = '/Recycle.bin',logic = "startswith")
if dollar_i_files is not None:
processed_files = process_dollar_i(tsk_util, dollar_i_files)
write_csv(report_file,['file_path', 'file_size', 'deleted_time','dollar_i_file', 'dollar_r_file', 'is_directory'],processed_files)
else:
print("No $I files found")
现在,如果我们找到了 $I 文件,那么它必须被发送到 process_dollar_i( )函数,该函数将接受 tsk_util 对象以及 $I 文件的列表,如下所示。
def process_dollar_i(tsk_util, dollar_i_files):
processed_files = []
for dollar_i in dollar_i_files:
file_attribs = read_dollar_i(dollar_i[2])
if file_attribs is None:
continue
file_attribs['dollar_i_file'] = os.path.join('/$Recycle.bin', dollar_i[1][1:])
现在,搜索$R文件,如下所示 –
recycle_file_path = os.path.join('/Recycle.bin',dollar_i[1].rsplit("/", 1)[0][1:])
dollar_r_files = tsk_util.recurse_files(
"R" + dollar_i[0][2:],path = recycle_file_path, logic = "startswith")
if dollar_r_files is None:
dollar_r_dir = os.path.join(recycle_file_path,"$R" + dollar_i[0][2:])
dollar_r_dirs = tsk_util.query_directory(dollar_r_dir)
if dollar_r_dirs is None:
file_attribs['dollar_r_file'] = "Not Found"
file_attribs['is_directory'] = 'Unknown'
else:
file_attribs['dollar_r_file'] = dollar_r_dir
file_attribs['is_directory'] = True
else:
dollar_r = [os.path.join(recycle_file_path, r[1][1:])for r in dollar_r_files]
file_attribs['dollar_r_file'] = ";".join(dollar_r)
file_attribs['is_directory'] = False
processed_files.append(file_attribs)
return processed_files
现在,定义 read_dollar_i() 方法来读取 $I 文件,换句话说,解析元数据。我们将使用 read_random() 方法来读取签名的前八个字节。如果签名不匹配,这将返回无。之后,如果 $I 文件是一个有效的文件,我们将不得不从该文件中读取和解压值。
def read_dollar_i(file_obj):
if file_obj.read_random(0, 8) != '\x01\x00\x00\x00\x00\x00\x00\x00':
return None
raw_file_size = struct.unpack('<q', file_obj.read_random(8, 8))
raw_deleted_time = struct.unpack('<q', file_obj.read_random(16, 8))
raw_file_path = file_obj.read_random(24, 520)
现在,在提取这些文件后,我们需要通过使用 sizeof_fmt( )函数将整数解释为人类可读的数值,如下图所示。
file_size = sizeof_fmt(raw_file_size[0])
deleted_time = parse_windows_filetime(raw_deleted_time[0])
file_path = raw_file_path.decode("utf16").strip("\x00")
return {'file_size': file_size, 'file_path': file_path,'deleted_time': deleted_time}
现在,我们需要定义 sizeof_fmt( )函数,如下所示
def sizeof_fmt(num, suffix = 'B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
现在,定义一个将整数解释为格式化日期和时间的函数,如下所示
def parse_windows_filetime(date_value):
microseconds = float(date_value) / 10
ts = datetime.datetime(1601, 1, 1) + datetime.timedelta(
microseconds = microseconds)
return ts.strftime('%Y-%m-%d %H:%M:%S.%f')
现在,我们将定义 write_csv( )方法,将处理后的结果写入CSV文件中,如下所示
def write_csv(outfile, fieldnames, data):
with open(outfile, 'wb') as open_outfile:
csvfile = csv.DictWriter(open_outfile, fieldnames)
csvfile.writeheader()
csvfile.writerows(data)
当你运行上述脚本时,我们将从I和R文件中获得数据。
粘性笔记
Windows Sticky Notes取代了现实世界中用纸笔书写的习惯。这些笔记曾经漂浮在桌面上,有不同的颜色、字体等选项。在Windows 7中,Sticky Notes文件是以OLE文件的形式存储的,因此在下面的Python脚本中,我们将调查这个OLE文件,以便从Sticky Notes中提取元数据。
对于这个Python脚本,我们需要安装第三方模块,即 olefile、pytsk3、pyewf 和unicodecsv。我们可以使用 pip 命令来安装它们。
我们可以按照下面讨论的步骤从 Stickynote 文件即 StickyNote.sn 中提取信息。
- 首先,打开证据文件,找到所有StickyNote.snt文件。
-
然后,从OLE流中解析元数据和内容,并将RTF内容写入文件中。
-
最后,为这些元数据创建CSV报告。
Python代码
让我们看看如何使用Python代码来达到这个目的–
首先,导入以下Python库 –
from __future__ import print_function
from argparse import ArgumentParser
import unicodecsv as csv
import os
import StringIO
from utility.pytskutil import TSKUtil
import olefile
接下来,定义一个全局变量,该变量将在本脚本中使用 —
REPORT_COLS = ['note_id', 'created', 'modified', 'note_text', 'note_file']
接下来,我们需要为命令行处理程序提供参数。注意,这里它将接受三个参数–第一个是证据文件的路径,第二个是证据文件的类型,第三个是期望的输出路径,如下所示
if __name__ == '__main__':
parser = argparse.ArgumentParser('Evidence from Sticky Notes')
parser.add_argument('EVIDENCE_FILE', help="Path to evidence file")
parser.add_argument('IMAGE_TYPE', help="Evidence file format",choices=('ewf', 'raw'))
parser.add_argument('REPORT_FOLDER', help="Path to report folder")
args = parser.parse_args()
main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.REPORT_FOLDER)
现在,我们将定义 main( )函数,它将与之前的脚本类似,如下图所示
def main(evidence, image_type, report_folder):
tsk_util = TSKUtil(evidence, image_type)
note_files = tsk_util.recurse_files('StickyNotes.snt', '/Users','equals')
现在,让我们对得到的文件进行迭代。然后我们将调用 parse_snt_file() 函数来处理文件,然后我们将用 write_note_rtf() 方法写出RTF文件,如下所示
report_details = []
for note_file in note_files:
user_dir = note_file[1].split("/")[1]
file_like_obj = create_file_like_obj(note_file[2])
note_data = parse_snt_file(file_like_obj)
if note_data is None:
continue
write_note_rtf(note_data, os.path.join(report_folder, user_dir))
report_details += prep_note_report(note_data, REPORT_COLS,"/Users" + note_file[1])
write_csv(os.path.join(report_folder, 'sticky_notes.csv'), REPORT_COLS,report_details)
接下来,我们需要定义这个脚本中使用的各种函数。
首先,我们将定义 create_file_like_obj() 函数,通过获取 pytsk 文件对象来读取文件的大小。然后,我们将定义 parse_snt_file() 函数,该函数将接受类文件对象作为其输入,用于读取和解释便签文件。
def parse_snt_file(snt_file):
if not olefile.isOleFile(snt_file):
print("This is not an OLE file")
return None
ole = olefile.OleFileIO(snt_file)
note = {}
for stream in ole.listdir():
if stream[0].count("-") == 3:
if stream[0] not in note:
note[stream[0]] = {"created": ole.getctime(stream[0]),"modified": ole.getmtime(stream[0])}
content = None
if stream[1] == '0':
content = ole.openstream(stream).read()
elif stream[1] == '3':
content = ole.openstream(stream).read().decode("utf-16")
if content:
note[stream[0]][stream[1]] = content
return note
现在,通过定义 write_note_rtf( )函数创建一个RTF文件,如下所示
def write_note_rtf(note_data, report_folder):
if not os.path.exists(report_folder):
os.makedirs(report_folder)
for note_id, stream_data in note_data.items():
fname = os.path.join(report_folder, note_id + ".rtf")
with open(fname, 'w') as open_file:
open_file.write(stream_data['0'])
现在,我们将把嵌套的字典翻译成更适合于CSV电子表格的平面字典列表。这将通过定义 prep_note_report() 函数来完成。最后,我们将定义 write_csv() 函数。
def prep_note_report(note_data, report_cols, note_file):
report_details = []
for note_id, stream_data in note_data.items():
report_details.append({
"note_id": note_id,
"created": stream_data['created'],
"modified": stream_data['modified'],
"note_text": stream_data['3'].strip("\x00"),
"note_file": note_file
})
return report_details
def write_csv(outfile, fieldnames, data):
with open(outfile, 'wb') as open_outfile:
csvfile = csv.DictWriter(open_outfile, fieldnames)
csvfile.writeheader()
csvfile.writerows(data)
运行上述脚本后,我们将从Sticky Notes文件中获得元数据。
注册表文件
Windows注册表文件包含许多重要的细节,这些细节对于法医分析人员来说就像一个信息宝库。它是一个分层的数据库,包含与操作系统配置、用户活动、软件安装等相关的细节。在下面的Python脚本中,我们将从 SYSTEM 和 SOFTWARE 蜂巢中访问常见的基线信息。
对于这个Python脚本,我们需要安装第三方模块,即 pytsk3、pyewf 和 registry。 我们可以使用 pip 来安装它们。
我们可以按照下面的步骤从Windows注册表中提取信息。
- 首先,通过名称和路径找到要处理的注册表蜂巢。
-
然后,我们通过使用StringIO和注册表模块打开这些文件。
-
最后,我们需要处理每一个蜂巢,并将解析后的值打印到控制台进行解释。
Python代码
让我们看看如何使用Python代码来达到这个目的
首先,导入以下Python库 –
from __future__ import print_function
from argparse import ArgumentParser
import datetime
import StringIO
import struct
from utility.pytskutil import TSKUtil
from Registry import Registry
现在,为命令行处理程序提供参数。这里它将接受两个参数–第一个是证据文件的路径,第二个是证据文件的类型,如下所示
if __name__ == '__main__':
parser = argparse.ArgumentParser('Evidence from Windows Registry')
parser.add_argument('EVIDENCE_FILE', help = "Path to evidence file")
parser.add_argument('IMAGE_TYPE', help = "Evidence file format",
choices = ('ewf', 'raw'))
args = parser.parse_args()
main(args.EVIDENCE_FILE, args.IMAGE_TYPE)
现在我们将定义 main( )函数来搜索 /Windows/System32/config 文件夹下的 SYSTEM 和 SOFTWARE 文件,如下所示。
def main(evidence, image_type):
tsk_util = TSKUtil(evidence, image_type)
tsk_system_hive = tsk_util.recurse_files('system', '/Windows/system32/config', 'equals')
tsk_software_hive = tsk_util.recurse_files('software', '/Windows/system32/config', 'equals')
system_hive = open_file_as_reg(tsk_system_hive[0][2])
software_hive = open_file_as_reg(tsk_software_hive[0][2])
process_system_hive(system_hive)
process_software_hive(software_hive)
现在,定义打开注册表文件的函数。为此,我们需要从 pytsk 元数据中收集文件的大小,如下所示
def open_file_as_reg(reg_file):
file_size = reg_file.info.meta.size
file_content = reg_file.read_random(0, file_size)
file_like_obj = StringIO.StringIO(file_content)
return Registry.Registry(file_like_obj)
现在,在以下方法的帮助下,我们可以处理 **SYSTEM > **蜂巢—-。
def process_system_hive(hive):
root = hive.root()
current_control_set = root.find_key("Select").value("Current").value()
control_set = root.find_key("ControlSet{:03d}".format(current_control_set))
raw_shutdown_time = struct.unpack(
'<Q', control_set.find_key("Control").find_key("Windows").value("ShutdownTime").value())
shutdown_time = parse_windows_filetime(raw_shutdown_time[0])
print("Last Shutdown Time: {}".format(shutdown_time))
time_zone = control_set.find_key("Control").find_key("TimeZoneInformation")
.value("TimeZoneKeyName").value()
print("Machine Time Zone: {}".format(time_zone))
computer_name = control_set.find_key("Control").find_key("ComputerName").find_key("ComputerName")
.value("ComputerName").value()
print("Machine Name: {}".format(computer_name))
last_access = control_set.find_key("Control").find_key("FileSystem")
.value("NtfsDisableLastAccessUpdate").value()
last_access = "Disabled" if last_access == 1 else "enabled"
print("Last Access Updates: {}".format(last_access))
现在,我们需要定义一个函数,将整数解释为格式化的日期和时间,如下所示
def parse_windows_filetime(date_value):
microseconds = float(date_value) / 10
ts = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds = microseconds)
return ts.strftime('%Y-%m-%d %H:%M:%S.%f')
def parse_unix_epoch(date_value):
ts = datetime.datetime.fromtimestamp(date_value)
return ts.strftime('%Y-%m-%d %H:%M:%S.%f')
现在,在以下方法的帮助下,我们可以处理 SOFTWARE 蜂巢—-。
def process_software_hive(hive):
root = hive.root()
nt_curr_ver = root.find_key("Microsoft").find_key("Windows NT")
.find_key("CurrentVersion")
print("Product name: {}".format(nt_curr_ver.value("ProductName").value()))
print("CSD Version: {}".format(nt_curr_ver.value("CSDVersion").value()))
print("Current Build: {}".format(nt_curr_ver.value("CurrentBuild").value()))
print("Registered Owner: {}".format(nt_curr_ver.value("RegisteredOwner").value()))
print("Registered Org:
{}".format(nt_curr_ver.value("RegisteredOrganization").value()))
raw_install_date = nt_curr_ver.value("InstallDate").value()
install_date = parse_unix_epoch(raw_install_date)
print("Installation Date: {}".format(install_date))
运行上述脚本后,我们将得到存储在Windows注册表文件中的元数据。