Python-digital-forensics-important-artifacts-in-ms-windows

提供:Dev Guides
移動先:案内検索

Windows-IIの重要なアーティファクト

この章では、Windowsの重要なアーティファクトとPythonを使用した抽出方法について説明します。

ユーザー活動

さまざまなユーザーアクティビティを保存するための NTUSER.DAT ファイルを持つWindows。 すべてのユーザープロファイルには、 NTUSER.DAT のようなハイブがあります。このハイブには、そのユーザーに特に関連する情報と構成が保存されます。 したがって、法医学アナリストによる調査の目的には非常に有用です。

次のPythonスクリプトは、システム上のユーザーのアクションを調べるために NTUSER.DAT のキーの一部を解析します。 先に進む前に、Pythonスクリプトの場合、サードパーティのモジュール、つまり Registry、pytsk3 、pyewfおよび Jinja2 をインストールする必要があります。 pipを使用してインストールできます。

次の手順に従って、 NTUSER.DAT ファイルから情報を抽出できます-

  • まず、システム内のすべての NTUSER.DAT ファイルを検索します。
  • 次に、各 NTUSER.DAT ファイルの WordWheelQuery、TypePathおよびRunMRU キーを解析します。
  • 最後に、 Jinja2 fmoduleを使用して、既に処理されたこれらのアーティファクトを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

次に、コマンドラインハンドラーの引数を指定します。 ここでは、3つの引数を受け入れます-最初は証拠ファイルへのパス、2番目は証拠ファイルのタイプ、3番目は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)

次に、示されているように、すべての NTUSER.DAT ファイルを検索するための* main()*関数を定義しましょう-

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_activityl")
   rendering = template.render(nt_data=data_dict)

   with open(outfile, 'w') as open_outfile:
      open_outfile.write(rendering)

最後に、レポート用のHTMLドキュメントを作成できます。 上記のスクリプトを実行した後、NTUSER.DATファイルからHTMLドキュメント形式の情報を取得します。

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

次に、コマンドラインハンドラーの引数を指定します。 ここでは、3つの引数を受け入れます。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプ、3つ目は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)

次に、2つの関数を定義する必要があります。1つは pytsk ファイルオブジェクトを開き、もう1つは以下に示すように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)

上記のスクリプトを実行した後、CSVレポートで検出された lnk ファイルから情報を取得します-

ファイルのプリフェッチ

アプリケーションが特定の場所から初めて実行されるたびに、Windowsは prefetch files を作成します。 これらは、アプリケーションの起動プロセスを高速化するために使用されます。 これらのファイルの拡張子は .PF で、*” \ Root \ Windows \ Prefetch” *フォルダーに保存されます。

デジタルフォレンジックの専門家は、ユーザーの詳細とともに、指定された場所からのプログラム実行の証拠を明らかにすることができます。 プリフェッチファイルは、プログラムが削除またはアンインストールされた後でもエントリが残るため、審査官にとって有用なアーティファクトです。

以下に示すように、Windowsプリフェッチファイルから情報を取得するPythonスクリプトについて説明しましょう-

Pythonスクリプトの場合、サードパーティのモジュール、つまり pylnk、pytsk3 および unicodecsv をインストールします。 前の章で説明した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

次に、コマンドラインハンドラーの引数を指定します。 ここでは、2つの引数を受け入れます。1つ目は証拠ファイルへのパス、2つ目は証拠ファイルのタイプです。 また、プリフェッチファイルをスキャンするパスを指定するためのオプションの引数も受け入れます-

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)

今、次のように構造体を使用して、プリフェッチされたファイル内に埋め込まれたデータを抽出します-

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バージョンのプリフェッチファイルからスプレッドシートに情報を取得します。