Windows-II 中的重要工件



本章討論了 Windows 中一些更重要的工件及其使用 Python 的提取方法。

使用者活動

Windows 使用NTUSER.DAT檔案儲存各種使用者活動。每個使用者配置檔案都具有類似NTUSER.DAT的 hive,其中儲存與該使用者特有的資訊和配置。因此,對於取證分析師的調查非常有用。

以下 Python 指令碼將解析NTUSER.DAT 的一些鍵,以探索使用者在系統上的操作。在繼續進行 Python 指令碼之前,我們需要安裝第三方模組,即Registry、pytsk3、pyewf 和Jinja2。我們可以使用 pip 來安裝它們。

我們可以按照以下步驟從NTUSER.DAT檔案中提取資訊:

  • 首先,搜尋系統中所有NTUSER.DAT檔案。

  • 然後,為每個NTUSER.DAT檔案解析WordWheelQuery、TypePath 和 RunMRU鍵。

  • 最後,我們將使用Jinja2模組將這些已處理的工件寫入 HTML 報告。

Python 程式碼

讓我們看看如何為此目的使用 Python 程式碼:

首先,我們需要匯入以下 Python 模組:

from __future__ import print_function
from argparse import ArgumentParser

import os
import StringIO
import struct

from utility.pytskutil import TSKUtil
from Registry import Registry
import jinja2

現在,為命令列處理器提供引數。這裡它將接受三個引數 - 第一個是證據檔案的路徑,第二個是證據檔案的型別,第三個是 HTML 報告的所需輸出路徑,如下所示:

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Information from user activities')
   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',help = "Path to report file")
   args = parser.parse_args()
   main(args.EVIDENCE_FILE, args.IMAGE_TYPE, args.REPORT)

現在,讓我們定義main()函式來搜尋所有NTUSER.DAT檔案,如下所示:

def main(evidence, image_type, report):
   tsk_util = TSKUtil(evidence, image_type)
   tsk_ntuser_hives = tsk_util.recurse_files('ntuser.dat','/Users', 'equals')
   
   nt_rec = {
      'wordwheel': {'data': [], 'title': 'WordWheel Query'},
      'typed_path': {'data': [], 'title': 'Typed Paths'},
      'run_mru': {'data': [], 'title': 'Run MRU'}
   }

現在,我們將嘗試在NTUSER.DAT檔案中查詢鍵,一旦找到,就定義使用者處理函式,如下所示:

for ntuser in tsk_ntuser_hives:
   uname = ntuser[1].split("/")

open_ntuser = open_file_as_reg(ntuser[2])
try:
   explorer_key = open_ntuser.root().find_key("Software").find_key("Microsoft")
      .find_key("Windows").find_key("CurrentVersion").find_key("Explorer")
   except Registry.RegistryKeyNotFoundException:
      continue
   nt_rec['wordwheel']['data'] += parse_wordwheel(explorer_key, uname)
   nt_rec['typed_path']['data'] += parse_typed_paths(explorer_key, uname)
   nt_rec['run_mru']['data'] += parse_run_mru(explorer_key, uname)
   nt_rec['wordwheel']['headers'] = \ nt_rec['wordwheel']['data'][0].keys()
   nt_rec['typed_path']['headers'] = \ nt_rec['typed_path']['data'][0].keys()
   nt_rec['run_mru']['headers'] = \ nt_rec['run_mru']['data'][0].keys()

現在,將字典物件及其路徑傳遞給write_html()方法,如下所示:

write_html(report, nt_rec)

現在,定義一個方法,該方法採用pytsk檔案控制代碼並透過StringIO類將其讀入 Registry 類。

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)

現在,我們將定義一個函式,該函式將解析和處理NTUSER.DAT檔案中的WordWheelQuery鍵,如下所示:

def parse_wordwheel(explorer_key, username):
   try:
      wwq = explorer_key.find_key("WordWheelQuery")
   except Registry.RegistryKeyNotFoundException:
      return []
   mru_list = wwq.value("MRUListEx").value()
   mru_order = []
   
   for i in xrange(0, len(mru_list), 2):
      order_val = struct.unpack('h', mru_list[i:i + 2])[0]
   if order_val in mru_order and order_val in (0, -1):
      break
   else:
      mru_order.append(order_val)
   search_list = []
   
   for count, val in enumerate(mru_order):
      ts = "N/A"
      if count == 0:
         ts = wwq.timestamp()
      search_list.append({
         'timestamp': ts,
         'username': username,
         'order': count,
         'value_name': str(val),
         'search': wwq.value(str(val)).value().decode("UTF-16").strip("\x00")
})
   return search_list  

現在,我們將定義一個函式,該函式將解析和處理NTUSER.DAT檔案中的TypedPaths鍵,如下所示:

def parse_typed_paths(explorer_key, username):
   try:
      typed_paths = explorer_key.find_key("TypedPaths")
   except Registry.RegistryKeyNotFoundException:
      return []
   typed_path_details = []
   
   for val in typed_paths.values():
      typed_path_details.append({
         "username": username,
         "value_name": val.name(),
         "path": val.value()
      })
   return typed_path_details

現在,我們將定義一個函式,該函式將解析和處理NTUSER.DAT檔案中的RunMRU鍵,如下所示:

def parse_run_mru(explorer_key, username):
   try:
      run_mru = explorer_key.find_key("RunMRU")
   except Registry.RegistryKeyNotFoundException:
      return []
   
   if len(run_mru.values()) == 0:
      return []
   mru_list = run_mru.value("MRUList").value()
   mru_order = []
   
   for i in mru_list:
      mru_order.append(i)
   mru_details = []
   
   for count, val in enumerate(mru_order):
      ts = "N/A"
      if count == 0:
         ts = run_mru.timestamp()
      mru_details.append({
         "username": username,
         "timestamp": ts,
         "order": count,
         "value_name": val,
         "run_statement": run_mru.value(val).value()
      })
   return mru_details

現在,以下函式將處理 HTML 報告的建立:

def write_html(outfile, data_dict):
   cwd = os.path.dirname(os.path.abspath(__file__))
   env = jinja2.Environment(loader=jinja2.FileSystemLoader(cwd))
   template = env.get_template("user_activity.html")
   rendering = template.render(nt_data=data_dict)
   
   with open(outfile, 'w') as open_outfile:
      open_outfile.write(rendering)

最後,我們可以為報告編寫 HTML 文件。執行上述指令碼後,我們將獲得 HTML 文件格式的 NTUSER.DAT 檔案資訊。

LINK 檔案

當用戶或作業系統為經常使用、雙擊或從系統驅動器(如附加儲存)訪問的檔案建立快捷方式檔案時,會建立快捷方式檔案。此類快捷方式檔案稱為連結檔案。透過訪問這些連結檔案,調查人員可以找到視窗的活動,例如訪問這些檔案的時間和位置。

讓我們討論一下我們可以用來從這些 Windows LINK 檔案中獲取資訊的 Python 指令碼。

對於 Python 指令碼,請安裝名為pylnk、pytsk3、pyewf的第三方模組。我們可以按照以下步驟從lnk檔案中提取資訊:

  • 首先,搜尋系統中的lnk檔案。

  • 然後,透過遍歷它們來提取檔案中的資訊。

  • 現在,最後我們需要將此資訊寫入 CSV 報告。

Python 程式碼

讓我們看看如何為此目的使用 Python 程式碼:

首先,匯入以下 Python 庫:

from __future__ import print_function
from argparse import ArgumentParser

import csv
import StringIO

from utility.pytskutil import TSKUtil
import pylnk

現在,為命令列處理器提供引數。這裡它將接受三個引數 - 第一個是證據檔案的路徑,第二個是證據檔案的型別,第三個是 CSV 報告的所需輸出路徑,如下所示:

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Parsing LNK files')
   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)

現在,透過建立TSKUtil物件並遍歷檔案系統以查詢以lnk結尾的檔案來解釋證據檔案。這可以透過定義main()函式來完成,如下所示:

def main(evidence, image_type, report):
   tsk_util = TSKUtil(evidence, image_type)
   lnk_files = tsk_util.recurse_files("lnk", path="/", logic="endswith")
   
   if lnk_files is None:
      print("No lnk files found")
      exit(0)
   columns = [
      'command_line_arguments', 'description', 'drive_serial_number',
      'drive_type', 'file_access_time', 'file_attribute_flags',
      'file_creation_time', 'file_modification_time', 'file_size',
      'environmental_variables_location', 'volume_label',
      'machine_identifier', 'local_path', 'network_path',
      'relative_path', 'working_directory'
   ]

現在,藉助以下程式碼,我們將透過建立如下所示的函式來遍歷lnk檔案:

parsed_lnks = []

for entry in lnk_files:
   lnk = open_file_as_lnk(entry[2])
   lnk_data = {'lnk_path': entry[1], 'lnk_name': entry[0]}
   
   for col in columns:
      lnk_data[col] = getattr(lnk, col, "N/A")
   lnk.close()
   parsed_lnks.append(lnk_data)
write_csv(report, columns + ['lnk_path', 'lnk_name'], parsed_lnks)

現在我們需要定義兩個函式,一個將開啟pytsk檔案物件,另一個將用於寫入 CSV 報告,如下所示:

def open_file_as_lnk(lnk_file):
   file_size = lnk_file.info.meta.size
   file_content = lnk_file.read_random(0, file_size)
   file_like_obj = StringIO.StringIO(file_content)
   lnk = pylnk.file()
   lnk.open_file_object(file_like_obj)
   return lnk
def write_csv(outfile, fieldnames, data):
   with open(outfile, 'wb') as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

執行上述指令碼後,我們將獲得已發現的lnk檔案的 CSV 報告資訊:

預取檔案

每當從特定位置第一次執行應用程式時,Windows 都會建立預取檔案。這些用於加快應用程式啟動過程。這些檔案的副檔名為.PF,並存儲在“\Root\Windows\Prefetch”資料夾中。

數字取證專家可以揭示程式從特定位置執行的證據以及使用者的詳細資訊。預取檔案對於檢查員來說是有用的工件,因為即使程式已被刪除或解除安裝,它們的條目仍然存在。

讓我們討論一下將獲取 Windows 預取檔案資訊的 Python 指令碼,如下所示:

對於 Python 指令碼,請安裝名為pylnk、pytsk3unicodecsv的第三方模組。回想一下,我們已經在前面章節中討論過的 Python 指令碼中使用過這些庫。

我們必須按照以下步驟從prefetch檔案中提取資訊:

  • 首先,掃描.pf副檔名檔案或預取檔案。

  • 現在,執行簽名驗證以消除誤報。

  • 接下來,解析 Windows 預取檔案格式。這與 Windows 版本不同。例如,對於 Windows XP,它是 17;對於 Windows Vista 和 Windows 7,它是 23;對於 Windows 8.1,它是 26;對於 Windows 10,它是 30。

  • 最後,我們將解析的結果寫入 CSV 檔案。

Python 程式碼

讓我們看看如何為此目的使用 Python 程式碼:

首先,匯入以下 Python 庫:

from __future__ import print_function
import argparse
from datetime import datetime, timedelta

import os
import pytsk3
import pyewf
import struct
import sys
import unicodecsv as csv
from utility.pytskutil import TSKUtil

現在,為命令列處理器提供引數。這裡它將接受兩個引數,第一個是證據檔案的路徑,第二個是證據檔案的型別。它還接受一個可選引數,用於指定掃描預取檔案的路徑:

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Parsing Prefetch files')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("TYPE", help = "Type of Evidence",choices = ("raw", "ewf"))
   parser.add_argument("OUTPUT_CSV", help = "Path to write output csv")
   parser.add_argument("-d", help = "Prefetch directory to scan",default = "/WINDOWS/PREFETCH")
   args = parser.parse_args()
   
   if os.path.exists(args.EVIDENCE_FILE) and \
      os.path.isfile(args.EVIDENCE_FILE):
   main(args.EVIDENCE_FILE, args.TYPE, args.OUTPUT_CSV, args.d)
else:
   print("[-] Supplied input file {} does not exist or is not a ""file".format(args.EVIDENCE_FILE))
   sys.exit(1)

現在,透過建立TSKUtil物件並遍歷檔案系統以查詢以.pf結尾的檔案來解釋證據檔案。這可以透過定義main()函式來完成,如下所示:

def main(evidence, image_type, output_csv, path):
   tsk_util = TSKUtil(evidence, image_type)
   prefetch_dir = tsk_util.query_directory(path)
   prefetch_files = None
   
   if prefetch_dir is not None:
      prefetch_files = tsk_util.recurse_files(".pf", path=path, logic="endswith")
   
   if prefetch_files is None:
      print("[-] No .pf files found")
      sys.exit(2)
   print("[+] Identified {} potential prefetch files".format(len(prefetch_files)))
   prefetch_data = []
   
   for hit in prefetch_files:
      prefetch_file = hit[2]
      pf_version = check_signature(prefetch_file)

現在,定義一個方法來執行簽名的驗證,如下所示:

def check_signature(prefetch_file):
   version, signature = struct.unpack("^<2i", prefetch_file.read_random(0, 8))
   
   if signature == 1094927187:
      return version
   else:
      return None
   
   if pf_version is None:
      continue
   pf_name = hit[0]
   
   if pf_version == 17:
      parsed_data = parse_pf_17(prefetch_file, pf_name)
      parsed_data.append(os.path.join(path, hit[1].lstrip("//")))
      prefetch_data.append(parsed_data)

現在,開始處理 Windows 預取檔案。這裡我們以 Windows XP 預取檔案為例:

def parse_pf_17(prefetch_file, pf_name):
   create = convert_unix(prefetch_file.info.meta.crtime)
   modify = convert_unix(prefetch_file.info.meta.mtime)
def convert_unix(ts):
   if int(ts) == 0:
      return ""
   return datetime.utcfromtimestamp(ts)
def convert_filetime(ts):
   if int(ts) == 0:
      return ""
   return datetime(1601, 1, 1) + timedelta(microseconds=ts / 10)

現在,使用 struct 提取嵌入在預取檔案中的資料,如下所示:

pf_size, name, vol_info, vol_entries, vol_size, filetime, \
   count = struct.unpack("<i60s32x3iq16xi",prefetch_file.read_random(12, 136))
name = name.decode("utf-16", "ignore").strip("/x00").split("/x00")[0]

vol_name_offset, vol_name_length, vol_create, \
   vol_serial = struct.unpack("<2iqi",prefetch_file.read_random(vol_info, 20))
   vol_serial = hex(vol_serial).lstrip("0x")
   vol_serial = vol_serial[:4] + "-" + vol_serial[4:]
   vol_name = struct.unpack(
      "<{}s".format(2 * vol_name_length),
      prefetch_file.read_random(vol_info + vol_name_offset,vol_name_length * 2))[0]

vol_name = vol_name.decode("utf-16", "ignore").strip("/x00").split("/x00")[0]
return [
   pf_name, name, pf_size, create,
   modify, convert_filetime(filetime), count, vol_name,
   convert_filetime(vol_create), vol_serial ]

我們提供了 Windows XP 的預取版本,但如果遇到其他 Windows 的預取版本怎麼辦?那麼它必須顯示一條錯誤訊息,如下所示:

elif pf_version == 23:
   print("[-] Windows Vista / 7 PF file {} -- unsupported".format(pf_name))
   continue
elif pf_version == 26:
   print("[-] Windows 8 PF file {} -- unsupported".format(pf_name))
   continue
elif pf_version == 30:
   print("[-] Windows 10 PF file {} -- unsupported".format(pf_name))
continue

else:
   print("[-] Signature mismatch - Name: {}\nPath: {}".format(hit[0], hit[1]))
continue
write_output(prefetch_data, output_csv)

現在,定義一個方法將結果寫入 CSV 報告,如下所示:

def write_output(data, output_csv):
   print("[+] Writing csv report")
   with open(output_csv, "wb") as outfile:
      writer = csv.writer(outfile)
      writer.writerow([
         "File Name", "Prefetch Name", "File Size (bytes)",
         "File Create Date (UTC)", "File Modify Date (UTC)",
         "Prefetch Last Execution Date (UTC)",
         "Prefetch Execution Count", "Volume", "Volume Create Date",
         "Volume Serial", "File Path" ])
      writer.writerows(data)

執行上述指令碼後,我們將獲得 Windows XP 版本預取檔案的電子表格資訊。

廣告