Windows-I 中的重要工件


本章将解释 Microsoft 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、pyewfunicodecsv。我们可以使用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 粘滞便笺取代了现实世界中用笔和纸书写的习惯。这些便笺用于浮动在桌面上,具有不同的颜色、字体等选项。在 Windows 7 中,便笺文件存储为 OLE 文件,因此在下面的 Python 脚本中,我们将研究此 OLE 文件以从便笺中提取元数据。

对于这个Python脚本,我们需要安装第三方模块,即olefile、pytsk3、pyewf和unicodecsv。我们可以使用命令pip来安装它们。

我们可以按照下面讨论的步骤从便签文件(即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)

运行上述脚本后,我们将从粘滞便笺文件中获取元数据。

注册表文件

Windows 注册表文件包含许多重要的详细信息,对于法医分析师而言,它们就像一个信息宝库。它是一个分层数据库,包含与操作系统配置、用户活动、软件安装等相关的详细信息。在下面的 Python 脚本中,我们将从系统软件配置单元访问公共基线信息。

对于这个Python脚本,我们需要安装第三方模块,即pytsk3、pyewfregistry。我们可以使用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文件夹中搜索系统软件配置单元,如下所示 -

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

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

现在借助以下方法,我们可以处理软件配置单元 -

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 注册表文件中的元数据。