Artefatos importantes no Windows-I

Este capítulo explicará vários conceitos envolvidos na análise forense do Microsoft Windows e os artefatos importantes que um investigador pode obter do processo de investigação.

Introdução

Artefatos são os objetos ou áreas em um sistema de computador que contêm informações importantes relacionadas às atividades realizadas pelo usuário do computador. O tipo e a localização dessas informações dependem do sistema operacional. Durante a análise forense, esses artefatos desempenham um papel muito importante na aprovação ou desaprovação da observação do investigador.

Importância dos artefatos do Windows para análise forense

Os artefatos do Windows assumem significância devido às seguintes razões -

  • Cerca de 90% do tráfego no mundo vem de computadores que usam o Windows como sistema operacional. É por isso que, para os examinadores forenses digitais, os artefatos do Windows são essenciais.

  • O sistema operacional Windows armazena diferentes tipos de evidências relacionadas à atividade do usuário no sistema de computador. Esse é outro motivo que mostra a importância dos artefatos do Windows para a perícia digital.

  • Muitas vezes, o investigador gira a investigação em torno de áreas antigas e tradicionais, como dados criados pelo usuário. Os artefatos do Windows podem levar a investigação a áreas não tradicionais, como dados criados pelo sistema ou os artefatos.

  • Grande abundância de artefatos é fornecida pelo Windows, que são úteis para investigadores, bem como para empresas e indivíduos que realizam investigações informais.

  • O aumento do crime cibernético nos últimos anos é outra razão pela qual os artefatos do Windows são importantes.

Artefatos do Windows e seus scripts Python

Nesta seção, vamos discutir sobre alguns artefatos do Windows e scripts Python para buscar informações deles.

Lixeira de reciclagem

É um dos artefatos importantes do Windows para investigação forense. A lixeira do Windows contém os arquivos que foram excluídos pelo usuário, mas ainda não removidos fisicamente pelo sistema. Mesmo que o usuário remova completamente o arquivo do sistema, ele serve como uma importante fonte de investigação. Isso ocorre porque o examinador pode extrair informações valiosas, como o caminho do arquivo original e a hora em que ele foi enviado para a Lixeira, dos arquivos excluídos.

Observe que o armazenamento de evidências da Lixeira depende da versão do Windows. No script Python a seguir, vamos lidar com o Windows 7, onde ele cria dois arquivos:$R arquivo que contém o conteúdo real do arquivo reciclado e $I arquivo que contém o nome do arquivo original, caminho, tamanho do arquivo quando o arquivo foi excluído.

Para o script Python, precisamos instalar módulos de terceiros, a saber pytsk3, pyewf e unicodecsv. Podemos usarpippara instalá-los. Podemos seguir as seguintes etapas para extrair informações da Lixeira -

  • Primeiro, precisamos usar o método recursivo para fazer a varredura através do $Recycle.bin pasta e selecione todos os arquivos começando com $I.

  • A seguir, leremos o conteúdo dos arquivos e analisaremos as estruturas de metadados disponíveis.

  • Agora, procuraremos o arquivo $ R associado.

  • Por fim, escreveremos os resultados em um arquivo CSV para revisão.

Vamos ver como usar o código Python para esse propósito -

Primeiro, precisamos importar as seguintes bibliotecas 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

Em seguida, precisamos fornecer um argumento para o manipulador de linha de comando. Observe que aqui ele aceitará três argumentos - o primeiro é o caminho para o arquivo de evidência, o segundo é o tipo de arquivo de evidência e o terceiro é o caminho de saída desejado para o relatório CSV, conforme mostrado abaixo -

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)

Agora, defina o main()função que fará todo o processamento. Vai procurar por$I arquivo da seguinte forma -

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

Agora, se encontrarmos $I arquivo, então ele deve ser enviado para process_dollar_i() função que aceitará o tsk_util objeto, bem como a lista de $I arquivos, como mostrado abaixo -

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:])

Agora, procure por arquivos $ R da seguinte maneira -

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

Agora, defina read_dollar_i() método para ler o $Iem outras palavras, os arquivos analisam os metadados. Nós vamos usarread_random()método para ler os primeiros oito bytes da assinatura. Isso não retornará nenhum se a assinatura não corresponder. Depois disso, teremos que ler e desempacotar os valores de$I arquivo se for um arquivo válido.

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)

Agora, depois de extrair esses arquivos, precisamos interpretar os inteiros em valores legíveis por humanos usando sizeof_fmt() função conforme mostrado abaixo -

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}

Agora, precisamos definir sizeof_fmt() funcionar da seguinte forma -

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)

Agora, defina uma função para inteiros interpretados em data e hora formatadas da seguinte maneira -

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

Agora vamos definir write_csv() método para gravar os resultados processados ​​em um arquivo CSV da seguinte maneira -

def write_csv(outfile, fieldnames, data):
   with open(outfile, 'wb') as open_outfile:
      csvfile = csv.DictWriter(open_outfile, fieldnames)
      csvfile.writeheader()
      csvfile.writerows(data)

Quando você executar o script acima, obteremos os dados do arquivo $ I e $ R.

Lembretes

O Windows Sticky Notes substitui o hábito do mundo real de escrever com caneta e papel. Essas notas costumavam flutuar na área de trabalho com diferentes opções de cores, fontes, etc. No Windows 7, o arquivo Sticky Notes é armazenado como um arquivo OLE, portanto, no script Python a seguir, investigaremos esse arquivo OLE para extrair metadados das Sticky Notes.

Para este script Python, precisamos instalar módulos de terceiros, a saber olefile, pytsk3, pyewfe unicodecsv. Podemos usar o comandopip para instalá-los.

Podemos seguir as etapas discutidas abaixo para extrair as informações do arquivo Sticky note, a saber StickyNote.sn -

  • Em primeiro lugar, abra o arquivo de evidência e encontre todos os arquivos StickyNote.snt.

  • Em seguida, analise os metadados e o conteúdo do fluxo OLE e grave o conteúdo RTF nos arquivos.

  • Por último, crie um relatório CSV desses metadados.

Código Python

Vamos ver como usar o código Python para esse propósito -

Primeiro, importe as seguintes bibliotecas 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

Em seguida, defina uma variável global que será usada neste script -

REPORT_COLS = ['note_id', 'created', 'modified', 'note_text', 'note_file']

Em seguida, precisamos fornecer um argumento para o manipulador de linha de comando. Observe que aqui ele aceitará três argumentos - o primeiro é o caminho para o arquivo de evidência, o segundo é o tipo de arquivo de evidência e o terceiro é o caminho de saída desejado conforme segue -

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)

Agora vamos definir main() função que será semelhante ao script anterior, conforme mostrado abaixo -

def main(evidence, image_type, report_folder):
   tsk_util = TSKUtil(evidence, image_type)
   note_files = tsk_util.recurse_files('StickyNotes.snt', '/Users','equals')

Agora, vamos iterar pelos arquivos resultantes. Então vamos ligarparse_snt_file() função para processar o arquivo e, em seguida, escreveremos o arquivo RTF com o write_note_rtf() método da seguinte forma -

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)

Em seguida, precisamos definir várias funções usadas neste script.

Em primeiro lugar, vamos definir create_file_like_obj() função para ler o tamanho do arquivo, tomando pytskobjeto de arquivo. Então vamos definirparse_snt_file() função que aceitará o objeto semelhante a um arquivo como sua entrada e é usada para ler e interpretar o arquivo de nota adesiva.

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

Agora, crie um arquivo RTF definindo write_note_rtf() funcionar como segue

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

Agora, traduziremos o dicionário aninhado em uma lista simples de dicionários que são mais apropriados para uma planilha CSV. Será feito definindoprep_note_report()função. Por fim, vamos definirwrite_csv() função.

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)

Depois de executar o script acima, obteremos os metadados do arquivo Sticky Notes.

Arquivos de registro

Os arquivos de registro do Windows contêm muitos detalhes importantes que são como um tesouro de informações para um analista forense. É um banco de dados hierárquico que contém detalhes relacionados à configuração do sistema operacional, atividade do usuário, instalação de software, etc. No script Python a seguir, acessaremos informações de linha de base comuns doSYSTEM e SOFTWARE urticária.

Para este script Python, precisamos instalar módulos de terceiros, a saber pytsk3, pyewf e registry. Podemos usarpip para instalá-los.

Podemos seguir as etapas abaixo para extrair as informações do registro do Windows -

  • Primeiro, encontre as seções do registro para processar por nome e também por caminho.

  • Em seguida, devemos abrir esses arquivos usando os módulos StringIO e Registry.

  • Por fim, precisamos processar cada colmeia e imprimir os valores analisados ​​no console para interpretação.

Código Python

Vamos ver como usar o código Python para esse propósito -

Primeiro, importe as seguintes bibliotecas 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

Agora, forneça o argumento para o manipulador de linha de comando. Aqui ele aceitará dois argumentos - o primeiro é o caminho para o arquivo de evidência, o segundo é o tipo de arquivo de evidência, conforme mostrado abaixo -

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)

Agora vamos definir main() função para pesquisar SYSTEM e SOFTWARE urticária dentro /Windows/System32/config pasta da seguinte forma -

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)

Agora, defina a função para abrir o arquivo de registro. Para isso, precisamos coletar o tamanho do arquivo depytsk metadados da seguinte forma -

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)

Agora, com a ajuda do método a seguir, podemos processar SYSTEM> colmeia -

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

Agora, precisamos definir uma função para inteiros interpretados em data e hora formatadas da seguinte forma -

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

Agora, com a ajuda do seguinte método, podemos processar SOFTWARE colmeia -

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

Após executar o script acima, obteremos os metadados armazenados nos arquivos do Registro do Windows.