Windows-II 中的重要工件


本章将讨论 Windows 中一些更重要的工件以及使用 Python 提取它们的方法。

用户活动


窗户有 NTUSER.DAT 用于存储各种用户活动的文件。每个用户个人资料都有类似的蜂巢 NTUSER.DAT ,它专门存储与该用户相关的信息和配置。因此,它对于法医分析人员的调查非常有用。

以下 Python 脚本将解析 NTUSER.DAT 用于探索用户在系统上的操作。在继续之前,对于 Python 脚本,我们需要安装第三方模块,即 注册表,pytsk3 , pyewf 和 Jinja2 .我们可以使用 pip 来安装它们。

我们可以按照以下步骤从中提取信息 NTUSER.DAT file:

  • 首先,搜索所有 NTUSER.DAT 系统中的文件。

  • 然后解析 WordWheelQuery、TypePath 和 RunMRU 每个键 NTUSER.DAT file.

  • 最后,我们将通过使用将这些已处理的工件写入 HTML 报告 Jinja2 fmodule.

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 class.

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)

现在,我们将定义将解析和处理的函数 WordWheelQuery key from NTUSER.DAT 文件如下:

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

现在,我们将定义将解析和处理的函数 类型化路径 key from NTUSER.DAT 文件如下:

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

现在,我们将定义将解析和处理的函数 RunMRU key from NTUSER.DAT 文件如下:

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 文档来报告。运行上述脚本后,我们将从 NTUSER.DAT 文件中获取 HTML 文档格式的信息。

链接文件


当用户或操作系统为经常使用、双击或从系统驱动器(如附加存储)访问的文件创建快捷方式文件时,就会创建快捷方式文件。此类快捷方式文件称为链接文件。通过访问这些链接文件,调查人员可以找到窗口的活动,例如访问这些文件的时间和位置。

让我们讨论一下可用于从这些 Windows LINK 文件中获取信息的 Python 脚本。

对于 Python 脚本,安装第三方模块,即 pylnk, pytsk3, pyewf .我们可以按照以下步骤从中提取信息 lnk files

  • 首先,搜索 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)

运行上述脚本后,我们会从discovered中获取信息 lnk CSV 报告中的文件:

预取文件


每当应用程序首次从特定位置运行时,Windows 都会创建 预取文件 .这些用于加速应用程序启动过程。这些文件的扩展名是 .PF 这些存储在 “\根\Windows\预取” folder.

数字取证专家可以从指定位置揭示程序执行的证据以及用户的详细信息。预取文件对审查员来说是有用的工件,因为即使在程序被删除或卸载后,它们的条目仍然存在。

让我们讨论一下将从 Windows 预取文件中获取信息的 Python 脚本,如下所示:

对于 Python 脚本,安装第三方模块,即 pylnk, pytsk3 and unicodecsv .回想一下,我们已经在前面章节中讨论过的 Python 脚本中使用了这些库。

我们必须按照下面给出的步骤从 prefetch files:

  • 首先,扫描 .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 版本的预取文件中获取信息到电子表格中。