Python Digital Forensics - Guia rápido

Este capítulo fornecerá uma introdução sobre o que é a ciência forense digital e sua revisão histórica. Você também entenderá onde pode aplicar a perícia digital na vida real e suas limitações.

O que é Digital Forensics?

A forense digital pode ser definida como o ramo da ciência forense que analisa, examina, identifica e recupera as evidências digitais residentes em dispositivos eletrônicos. É comumente usado para o direito penal e investigações privadas.

Por exemplo, você pode contar com evidências de extração forense digital no caso de alguém roubar alguns dados em um dispositivo eletrônico.

Breve revisão histórica da perícia digital

A história de crimes informáticos e a revisão histórica da perícia digital é explicada nesta seção conforme abaixo -

1970-1980: Primeiro crime de computador

Antes desta década, nenhum crime de computador foi reconhecido. No entanto, se é para acontecer, as leis então existentes tratam disso. Mais tarde, em 1978, o primeiro crime de computador foi reconhecido no Florida Computer Crime Act, que incluiu legislação contra modificação não autorizada ou exclusão de dados em um sistema de computador. Mas com o passar do tempo, devido ao avanço da tecnologia, a gama de crimes de computador cometidos também aumentou. Para lidar com crimes relacionados a direitos autorais, privacidade e pornografia infantil, várias outras leis foram aprovadas.

1980-1990: Década de Desenvolvimento

Esta década foi a década de desenvolvimento para a perícia digital, tudo por causa da primeira investigação (1986) em que Cliff Stoll rastreou o hacker chamado Markus Hess. Durante este período, dois tipos de disciplinas forenses digitais foram desenvolvidas - a primeira foi com a ajuda de ferramentas e técnicas ad-hoc desenvolvidas por profissionais que a consideravam um hobby, enquanto a segunda foi desenvolvida pela comunidade científica. Em 1992, o termo“Computer Forensics”foi usado na literatura acadêmica.

2000-2010: Década de Padronização

Após o desenvolvimento da forense digital até um certo nível, houve a necessidade de fazer alguns padrões específicos que podem ser seguidos durante a realização de investigações. Conseqüentemente, várias agências e órgãos científicos publicaram diretrizes para análise forense digital. Em 2002, o Grupo de Trabalho Científico sobre Evidências Digitais (SWGDE) publicou um artigo intitulado “Melhores práticas para Computação Forense”. Outra pena foi um tratado internacional liderado pela Europa, a saber“The Convention on Cybercrime”foi assinado por 43 nações e ratificado por 16 nações. Mesmo após tais normas, ainda há a necessidade de resolver alguns problemas identificados pelos pesquisadores.

Processo de Perícia Digital

Desde o primeiro crime de computador em 1978, há um grande incremento nas atividades criminosas digitais. Devido a esse incremento, há necessidade de uma forma estruturada para lidar com eles. Em 1984, um processo formalizado foi introduzido e, depois disso, um grande número de novos e aprimorados processos de investigação forense computacional foram desenvolvidos.

Um processo de investigação forense computacional envolve três fases principais, conforme explicado abaixo -

Fase 1: Aquisição ou Imagem das Exposições

A primeira fase da análise forense digital envolve salvar o estado do sistema digital para que possa ser analisado posteriormente. É muito semelhante a tirar fotografias, amostras de sangue, etc. de uma cena de crime. Por exemplo, envolve a captura de uma imagem de áreas alocadas e não alocadas de um disco rígido ou RAM.

Fase 2: Análise

A entrada desta fase são os dados adquiridos na fase de aquisição. Aqui, esses dados foram examinados para identificar evidências. Esta fase fornece três tipos de evidências como segue -

  • Inculpatory evidences - Essas evidências apóiam uma determinada história.

  • Exculpatory evidences - Essas evidências contradizem uma dada história.

  • Evidence of tampering- Essas evidências mostram que o sistema foi temperado para evitar a identificação. Inclui examinar os arquivos e o conteúdo do diretório para recuperar os arquivos excluídos.

Fase 3: Apresentação ou Relatório

Como o nome sugere, esta fase apresenta a conclusão e as evidências correspondentes da investigação.

Aplicações da perícia digital

A forense digital trata de reunir, analisar e preservar as evidências que estão contidas em qualquer dispositivo digital. O uso de análise forense digital depende da aplicação. Conforme mencionado anteriormente, ele é usado principalmente nos dois aplicativos a seguir -

Lei criminal

No direito penal, as provas são coletadas para apoiar ou contestar uma hipótese no tribunal. Os procedimentos forenses são muito semelhantes aos usados ​​em investigações criminais, mas com requisitos e limitações legais diferentes.

Investigação Privada

Principalmente o mundo corporativo usa a perícia digital para investigação privada. É usado quando as empresas suspeitam que os funcionários podem estar executando uma atividade ilegal em seus computadores, que é contra a política da empresa. A perícia digital fornece um dos melhores caminhos para uma empresa ou pessoa tomar ao investigar alguém por má conduta digital.

Ramos da perícia digital

O crime digital não se restringe apenas aos computadores, no entanto hackers e criminosos estão usando pequenos dispositivos digitais como tablets, smartphones etc. em uma escala muito grande. Alguns dos dispositivos possuem memória volátil, enquanto outros possuem memória não volátil. Portanto, dependendo do tipo de dispositivos, a análise forense digital tem os seguintes ramos -

Computação Forense

Este ramo da forense digital lida com computadores, sistemas embarcados e memórias estáticas, como drives USB. Uma ampla gama de informações, de logs a arquivos reais na unidade, pode ser investigada em computação forense.

Forense móvel

Trata-se da investigação de dados de dispositivos móveis. Este ramo é diferente da computação forense no sentido de que os dispositivos móveis possuem um sistema de comunicação embutido que é útil para fornecer informações úteis relacionadas à localização.

Análise forense de rede

Trata-se do monitoramento e análise do tráfego da rede de computadores, tanto local quanto WAN (rede de longa distância) para fins de coleta de informações, coleta de evidências ou detecção de intrusão.

Análise de banco de dados

Este ramo da forense digital lida com o estudo forense de bancos de dados e seus metadados.

Habilidades necessárias para investigação forense digital

Os examinadores forenses digitais ajudam a rastrear hackers, recuperar dados roubados, acompanhar os ataques de computador até sua origem e auxiliar em outros tipos de investigações envolvendo computadores. Algumas das principais habilidades necessárias para se tornar um examinador forense digital, conforme discutido abaixo -

Excelentes capacidades de pensamento

Um investigador forense digital deve ser um pensador excepcional e deve ser capaz de aplicar diferentes ferramentas e metodologias em uma tarefa específica para obter o resultado. Ele / ela deve ser capaz de encontrar padrões diferentes e fazer correlações entre eles.

Habilidades técnicas

Um examinador forense digital deve ter boas habilidades tecnológicas, pois este campo requer o conhecimento de rede, como o sistema digital interage.

Apaixonado por segurança cibernética

Como o campo da ciência forense digital trata de solucionar crimes cibernéticos e essa é uma tarefa tediosa, é preciso muita paixão para alguém se tornar um investigador forense digital ace.

Habilidades de comunicação

Boas habilidades de comunicação são essenciais para a coordenação com várias equipes e para extrair quaisquer dados ou informações ausentes.

Hábil na elaboração de relatórios

Após a implementação bem-sucedida da aquisição e análise, um examinador forense digital deve mencionar todos os achados no relatório final e na apresentação. Portanto, ele / ela deve ter boas habilidades de elaboração de relatórios e atenção aos detalhes.

Limitações

A investigação forense digital oferece certas limitações, conforme discutido aqui -

Necessidade de produzir evidências convincentes

Um dos maiores contratempos da investigação forense digital é que o examinador deve cumprir os padrões exigidos para a prova no tribunal, pois os dados podem ser facilmente adulterados. Por outro lado, o investigador forense de computador deve ter conhecimento completo dos requisitos legais, manuseio de evidências e procedimentos de documentação para apresentar evidências convincentes no tribunal.

Ferramentas de investigação

A eficácia da investigação digital reside inteiramente na experiência do examinador forense digital e na seleção da ferramenta de investigação adequada. Se a ferramenta usada não estiver de acordo com os padrões especificados, no tribunal, as evidências podem ser negadas pelo juiz.

Falta de conhecimento técnico do público

Outra limitação é que alguns indivíduos não estão completamente familiarizados com a computação forense; portanto, muitas pessoas não entendem esse campo. Os investigadores devem comunicar suas descobertas aos tribunais de maneira que todos possam compreender os resultados.

Custo

Produzir evidências digitais e preservá-las é muito caro. Portanto, esse processo pode não ser escolhido por muitas pessoas que não podem arcar com os custos.

No capítulo anterior, aprendemos os fundamentos da perícia digital, suas vantagens e limitações. Este capítulo o deixará confortável com o Python, a ferramenta essencial que estamos usando nesta investigação forense digital.

Por que Python for Digital Forensics?

Python é uma linguagem de programação popular e é usada como ferramenta para segurança cibernética, testes de penetração, bem como investigações forenses digitais. Quando você escolhe Python como sua ferramenta para análise forense digital, você não precisa de nenhum outro software de terceiros para completar a tarefa.

Alguns dos recursos exclusivos da linguagem de programação Python que a torna uma boa opção para projetos forenses digitais são fornecidos abaixo -

  • Simplicity of Syntax - A sintaxe do Python é simples em comparação com outras linguagens, o que torna mais fácil aprender e colocar em uso para análise forense digital.

  • Comprehensive inbuilt modules - Os módulos integrados abrangentes do Python são uma excelente ajuda para realizar uma investigação forense digital completa.

  • Help and Support - Por ser uma linguagem de programação de código aberto, Python conta com excelente suporte da comunidade de desenvolvedores e usuários.

Recursos do Python

Python, sendo uma linguagem de script interpretada, interativa e orientada a objetos de alto nível, fornece os seguintes recursos -

  • Easy to Learn - Python é uma linguagem amigável para o desenvolvedor e fácil de aprender, pois possui menos palavras-chave e estrutura mais simples.

  • Expressive and Easy to read- A linguagem Python é expressiva por natureza; portanto, seu código é mais compreensível e legível.

  • Cross-platform Compatible - Python é uma linguagem compatível com várias plataformas, o que significa que pode ser executado com eficiência em várias plataformas, como UNIX, Windows e Macintosh.

  • Interactive Mode Programming - Podemos fazer testes interativos e depuração de código porque Python oferece suporte a um modo interativo para programação.

  • Provides Various Modules and Functions - Python tem uma grande biblioteca padrão que nos permite usar um rico conjunto de módulos e funções para nosso script.

  • Supports Dynamic Type Checking - Python suporta verificação de tipo dinâmico e fornece tipos de dados dinâmicos de alto nível.

  • GUI Programming - Python suporta programação GUI para desenvolver interfaces gráficas de usuário.

  • Integration with other programming languages - Python pode ser facilmente integrado com outras linguagens de programação como C, C ++, JAVA etc.

Instalando Python

A distribuição Python está disponível para várias plataformas, como Windows, UNIX, Linux e Mac. Precisamos apenas baixar o código binário de acordo com nossa plataforma. Caso o código binário para alguma plataforma não esteja disponível, devemos ter um compilador C para que o código fonte possa ser compilado manualmente.

Esta seção irá familiarizá-lo com a instalação do Python em várias plataformas -

Instalação Python em Unix e Linux

Você pode seguir as etapas abaixo para instalar o Python em uma máquina Unix / Linux.

Step 1- Abra um navegador da web. Digite e insira www.python.org/downloads/

Step 2 - Baixe o código-fonte compactado disponível para Unix / Linux.

Step 3 - Extraia os arquivos compactados baixados.

Step 4 - Se desejar personalizar algumas opções, você pode editar o Modules/Setup file.

Step 5 - Use os seguintes comandos para completar a instalação -

run ./configure script
make
make install

Depois de concluir com êxito as etapas fornecidas acima, o Python será instalado em seu local padrão /usr/local/bin e suas bibliotecas em /usr/local/lib/pythonXX onde XX é a versão do Python.

Instalação Python no Windows

Podemos seguir os seguintes passos simples para instalar o Python na máquina Windows.

Step 1- Abra um navegador da web. Digite e insira www.python.org/downloads/

Step 2 - Baixe o instalador do Windows python-XYZ.msi arquivo, onde XYZ é a versão que precisamos instalar.

Step 3 - Agora execute o arquivo MSI após salvar o arquivo do instalador em sua máquina local.

Step 4 - Execute o arquivo baixado que abrirá o assistente de instalação do Python.

Instalação Python no Macintosh

Para instalar o Python 3 no Mac OS X, devemos usar um instalador de pacote chamado Homebrew.

Você pode usar o seguinte comando para instalar o Homebrew, caso não o tenha em seu sistema -

$ ruby -e "$(curl -fsSL
https://raw.githubusercontent.com/Homebrew/install/master/install)"

Se você precisar atualizar o gerenciador de pacotes, isso pode ser feito com a ajuda do seguinte comando -

$ brew update

Agora, use o seguinte comando para instalar o Python3 em seu sistema -

$ brew install python3

Configurando o PATH

Precisamos definir o caminho para a instalação do Python e isso difere em plataformas como UNIX, WINDOWS ou MAC.

Configuração de caminho em Unix / Linux

Você pode usar as seguintes opções para definir o caminho no Unix / Linux -

  • If using csh shell - Tipo setenv PATH "$PATH:/usr/local/bin/python" e pressione Enter.

  • If using bash shell (Linux) - Tipo export ATH="$PATH:/usr/local/bin/python" e pressione Enter.

  • If using sh or ksh shell - Tipo PATH="$PATH:/usr/local/bin/python" e pressione Enter.

Configuração de caminho no Windows

Tipo path %path%;C:\Python no prompt de comando e pressione Enter.

Executando Python

Você pode escolher qualquer um dos três métodos a seguir para iniciar o interpretador Python -

Método 1: usando intérprete interativo

Um sistema que fornece um interpretador de linha de comando ou shell pode ser facilmente usado para iniciar o Python. Por exemplo, Unix, DOS, etc. Você pode seguir os passos abaixo para começar a codificar no interpretador interativo -

Step 1 - Entre python na linha de comando.

Step 2 - Comece a codificar imediatamente no interpretador interativo usando os comandos mostrados abaixo -

$python # Unix/Linux
or
python% # Unix/Linux
or
C:> python # Windows/DOS

Método 2: usando o script da linha de comando

Também podemos executar um script Python na linha de comando invocando o interpretador em nosso aplicativo. Você pode usar os comandos mostrados abaixo -

$python script.py # Unix/Linux
or
python% script.py # Unix/Linux
or
C: >python script.py # Windows/DOS

Método 3: Ambiente de Desenvolvimento Integrado

Se um sistema tiver um aplicativo GUI compatível com Python, o Python poderá ser executado a partir desse ambiente GUI. Algumas das IDE para várias plataformas são fornecidas abaixo -

  • Unix IDE - UNIX tem IDLE IDE para Python.

  • Windows IDE - O Windows tem PythonWin, a primeira interface do Windows para Python junto com a GUI.

  • Macintosh IDE - O Macintosh possui IDLE IDE que está disponível no site principal, para download como arquivos MacBinary ou BinHex.

Agora que você está confortável com a instalação e execução de comandos Python em seu sistema local, vamos entrar nos conceitos de perícia em detalhes. Este capítulo explicará vários conceitos envolvidos no tratamento de artefatos na análise forense digital do Python.

Necessidade de Criação de Relatório

O processo de análise forense digital inclui relatórios como a terceira fase. Esta é uma das partes mais importantes do processo forense digital. A criação de relatórios é necessária devido aos seguintes motivos -

  • É o documento no qual o examinador forense digital descreve o processo de investigação e suas conclusões.

  • Um bom relatório forense digital pode ser consultado por outro examinador para obter o mesmo resultado com os mesmos repositórios.

  • É um documento técnico e científico que contém fatos encontrados entre 1s e 0s da evidência digital.

Diretrizes Gerais para Criação de Relatórios

Os relatórios são escritos para fornecer informações ao leitor e devem começar com uma base sólida. os investigadores podem enfrentar dificuldades para apresentar com eficiência seus achados se o relatório for preparado sem algumas diretrizes ou padrões gerais. Algumas diretrizes gerais que devem ser seguidas durante a criação de relatórios forenses digitais são fornecidas abaixo -

  • Summary - O relatório deve conter um breve resumo das informações para que o leitor possa verificar a finalidade do relatório.

  • Tools used - Devemos mencionar as ferramentas que têm sido utilizadas para realizar o processo de perícia digital, incluindo sua finalidade.

  • Repository - Suponha que investigamos o computador de alguém, o resumo das evidências e a análise do material relevante como e-mail, histórico de buscas internas etc., então eles devem ser incluídos no relatório para que o caso seja apresentado de forma clara.

  • Recommendations for counsel - O relatório deve ter as recomendações para o advogado continuar ou interromper a investigação com base nas conclusões do relatório.

Criação de diferentes tipos de relatórios

Na seção acima, ficamos sabendo da importância do relatório na perícia digital juntamente com as diretrizes para sua criação. Alguns dos formatos em Python para a criação de diferentes tipos de relatórios são discutidos abaixo -

Relatórios CSV

Um dos formatos de saída mais comuns de relatórios é um relatório de planilha CSV. Você pode criar um CSV para criar um relatório de dados processados ​​usando o código Python, conforme mostrado abaixo -

Primeiro, importe bibliotecas úteis para escrever a planilha -

from __future__ import print_function
import csv
import os
import sys

Agora, chame o seguinte método -

Write_csv(TEST_DATA_LIST, ["Name", "Age", "City", "Job description"], os.getcwd())

Estamos usando a seguinte variável global para representar tipos de dados de amostra -

TEST_DATA_LIST = [["Ram", 32, Bhopal, Manager], 
   ["Raman", 42, Indore, Engg.],
   ["Mohan", 25, Chandigarh, HR], 
   ["Parkash", 45, Delhi, IT]]

A seguir, vamos definir o método para prosseguir para as operações futuras. Abrimos o arquivo no modo “w” e definimos o argumento da palavra-chave newline como uma string vazia.

def Write_csv(data, header, output_directory, name = None):
   if name is None:
      name = "report1.csv"
   print("[+] Writing {} to {}".format(name, output_directory))
   
   with open(os.path.join(output_directory, name), "w", newline = "") as \ csvfile:
      writer = csv.writer(csvfile)
      writer.writerow(header)
      writer.writerow(data)

Se você executar o script acima, obterá os seguintes detalhes armazenados no arquivo report1.csv.

Nome Era Cidade Designação
RAM 32 Bhopal Managerh
Raman 42 Indore Engg
Mohan 25 Chandigarh RH
Parkash 45 Délhi ISTO

Relatórios Excel

Outro formato de saída comum de relatórios é o relatório de planilha Excel (.xlsx). Podemos criar a tabela e também plotar o gráfico usando o Excel. Podemos criar um relatório de dados processados ​​no formato Excel usando o código Python como mostrado abaixo−

Primeiro, importe o módulo XlsxWriter para criar planilhas -

import xlsxwriter

Agora, crie um objeto de pasta de trabalho. Para isso, precisamos usar o construtor Workbook ().

workbook = xlsxwriter.Workbook('report2.xlsx')

Agora, crie uma nova planilha usando o módulo add_worksheet ().

worksheet = workbook.add_worksheet()

Em seguida, escreva os seguintes dados na planilha -

report2 = (['Ram', 32, ‘Bhopal’],['Mohan',25, ‘Chandigarh’] ,['Parkash',45, ‘Delhi’])

row = 0
col = 0

Você pode iterar esses dados e gravá-los da seguinte maneira -

for item, cost in (a):
   worksheet.write(row, col, item)
   worksheet.write(row, col+1, cost)
   row + = 1

Agora, vamos fechar este arquivo Excel usando o método close ().

workbook.close()

O script acima criará um arquivo Excel chamado report2.xlsx com os seguintes dados -

RAM 32 Bhopal
Mohan 25 Chandigarh
Parkash 45 Délhi

Mídia de aquisição de investigação

É importante para um investigador ter notas investigativas detalhadas para lembrar com precisão as descobertas ou reunir todas as partes da investigação. Uma captura de tela é muito útil para acompanhar as etapas executadas em uma investigação específica. Com a ajuda do seguinte código Python, podemos fazer a captura de tela e salvá-la no disco rígido para uso futuro.

Primeiro, instale o módulo Python chamado pyscreenshot usando o seguinte comando -

Pip install pyscreenshot

Agora, importe os módulos necessários conforme mostrado -

import pyscreenshot as ImageGrab

Use a seguinte linha de código para obter a captura de tela -

image = ImageGrab.grab()

Use a seguinte linha de código para salvar a captura de tela no local fornecido -

image.save('d:/image123.png')

Agora, se você quiser exibir a captura de tela como um gráfico, pode usar o seguinte código Python -

import numpy as np
import matplotlib.pyplot as plt
import pyscreenshot as ImageGrab
imageg = ImageGrab.grab()
plt.imshow(image, cmap='gray', interpolation='bilinear')
plt.show()

Este capítulo explicará a análise forense digital Python em dispositivos móveis e os conceitos envolvidos.

Introdução

A perícia forense de dispositivos móveis é o ramo da perícia digital que lida com a aquisição e análise de dispositivos móveis para recuperar evidências digitais de interesse investigativo. Este ramo é diferente da computação forense porque os dispositivos móveis têm um sistema de comunicação embutido que é útil para fornecer informações úteis relacionadas à localização.

Embora o uso de smartphones esteja aumentando no dia a dia da perícia digital, ainda é considerado fora do padrão devido à sua heterogeneidade. Por outro lado, o hardware do computador, como o disco rígido, é considerado padrão e também desenvolvido como uma disciplina estável. Na indústria forense digital, há muito debate sobre as técnicas utilizadas para dispositivos fora do padrão, havendo evidências transitórias, como os smartphones.

Artefatos extraíveis de dispositivos móveis

Os dispositivos móveis modernos possuem muitas informações digitais em comparação com os telefones mais antigos, tendo apenas um registro de chamadas ou mensagens SMS. Assim, os dispositivos móveis podem fornecer aos investigadores muitos insights sobre seu usuário. Alguns artefatos que podem ser extraídos de dispositivos móveis são mencionados abaixo -

  • Messages - Estes são os artefatos úteis que podem revelar o estado de espírito do proprietário e podem até mesmo fornecer algumas informações anteriores desconhecidas ao investigador.

  • Location History- Os dados do histórico de localização são um artefato útil que pode ser usado por investigadores para validar sobre a localização específica de uma pessoa.

  • Applications Installed - Acessando o tipo de aplicativo instalado, o investigador obtém algumas informações sobre os hábitos e pensamentos do usuário móvel.

Fontes de evidência e processamento em Python

Smartphones têm bancos de dados SQLite e arquivos PLIST como as principais fontes de evidências. Nesta seção, iremos processar as fontes de evidências em python.

Analisando arquivos PLIST

Uma PLIST (lista de propriedades) é um formato flexível e conveniente para armazenar dados de aplicativos, especialmente em dispositivos iPhone. Usa a extensão.plist. Esse tipo de arquivo usado para armazenar informações sobre pacotes e aplicativos. Pode ser em dois formatos:XML e binary. O seguinte código Python irá abrir e ler o arquivo PLIST. Observe que antes de prosseguirmos, devemos criar o nosso próprioInfo.plist Arquivo.

Primeiro, instale uma biblioteca de terceiros chamada biplist pelo seguinte comando -

Pip install biplist

Agora, importe algumas bibliotecas úteis para processar arquivos plist -

import biplist
import os
import sys

Agora, use o seguinte comando no método principal pode ser usado para ler o arquivo plist em uma variável -

def main(plist):
   try:
      data = biplist.readPlist(plist)
   except (biplist.InvalidPlistException,biplist.NotBinaryPlistException) as e:
print("[-] Invalid PLIST file - unable to be opened by biplist")
sys.exit(1)

Agora, podemos ler os dados no console ou imprimi-los diretamente, a partir desta variável.

Bancos de dados SQLite

SQLite atua como o repositório de dados primário em dispositivos móveis. SQLite uma biblioteca em processo que implementa um mecanismo de banco de dados SQL transacional independente, sem servidor e com configuração zero. É um banco de dados com configuração zero, você não precisa configurá-lo em seu sistema, ao contrário de outros bancos de dados.

Se você for um novato ou não estiver familiarizado com bancos de dados SQLite, pode seguir o link www.tutorialspoint.com/sqlite/index.htm Além disso, você pode seguir o link www.tutorialspoint.com/sqlite/sqlite_python.htm caso queira entrar em detalhes de SQLite com Python.

Durante a análise forense móvel, podemos interagir com o sms.db arquivo de um dispositivo móvel e pode extrair informações valiosas de messagemesa. Python tem uma biblioteca integrada chamadasqlite3para se conectar ao banco de dados SQLite. Você pode importar o mesmo com o seguinte comando -

import sqlite3

Agora, com a ajuda do seguinte comando, podemos nos conectar com o banco de dados, digamos sms.db no caso de dispositivos móveis -

Conn = sqlite3.connect(‘sms.db’)
C = conn.cursor()

Aqui, C é o objeto cursor com a ajuda do qual podemos interagir com o banco de dados.

Agora, suponha que se desejamos executar um comando específico, digamos, para obter os detalhes do abc table, isso pode ser feito com a ajuda do seguinte comando -

c.execute(“Select * from abc”)
c.close()

O resultado do comando acima seria armazenado no cursorobjeto. Da mesma forma, podemos usarfetchall() método para despejar o resultado em uma variável que possamos manipular.

Podemos usar o seguinte comando para obter os dados dos nomes das colunas da tabela de mensagens em sms.db -

c.execute(“pragma table_info(message)”)
table_data = c.fetchall()
columns = [x[1] for x in table_data

Observe que aqui estamos usando o comando SQLite PRAGMA, que é um comando especial a ser usado para controlar várias variáveis ​​ambientais e sinalizadores de estado dentro do ambiente SQLite. No comando acima, ofetchall()método retorna uma tupla de resultados. O nome de cada coluna é armazenado no primeiro índice de cada tupla.

Agora, com a ajuda do seguinte comando, podemos consultar a tabela para todos os seus dados e armazená-los na variável chamada data_msg -

c.execute(“Select * from message”)
data_msg = c.fetchall()

O comando acima irá armazenar os dados na variável e, além disso, também podemos escrever os dados acima no arquivo CSV usando csv.writer() método.

Backups do iTunes

A análise forense móvel do iPhone pode ser realizada nos backups feitos pelo iTunes. Os examinadores forenses confiam na análise dos backups lógicos do iPhone adquiridos por meio do iTunes. O protocolo AFC (conexão de arquivo Apple) é usado pelo iTunes para fazer o backup. Além disso, o processo de backup não modifica nada no iPhone, exceto os registros da chave de garantia.

Agora, surge a pergunta: por que é importante para um especialista forense digital compreender as técnicas de backups do iTunes? É importante caso tenhamos acesso ao computador do suspeito em vez do iPhone diretamente, porque quando um computador é usado para sincronizar com o iPhone, é provável que a maioria das informações do iPhone seja armazenada no computador.

Processo de backup e sua localização

Sempre que é feito o backup de um produto Apple no computador, ele é sincronizado com o iTunes e haverá uma pasta específica com a ID exclusiva do dispositivo. No formato de backup mais recente, os arquivos são armazenados em subpastas que contêm os dois primeiros caracteres hexadecimais do nome do arquivo. A partir desses arquivos de backup, existem alguns arquivos, como info.plist, que são úteis junto com o banco de dados denominado Manifest.db. A tabela a seguir mostra os locais de backup, que variam de acordo com os sistemas operacionais de backups do iTunes -

SO Localização de backup
Win7 C: \ Usuários \ [nome de usuário] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \
MAC OS X ~ / Library / Application Suport / MobileSync / Backup /

Para processar o backup do iTunes com Python, precisamos primeiro identificar todos os backups no local do backup de acordo com nosso sistema operacional. Em seguida, iremos iterar em cada backup e ler o banco de dados Manifest.db.

Agora, com a ajuda de seguir o código Python, podemos fazer o mesmo -

Primeiro, importe as bibliotecas necessárias da seguinte forma -

from __future__ import print_function
import argparse
import logging
import os

from shutil import copyfile
import sqlite3
import sys
logger = logging.getLogger(__name__)

Agora, forneça dois argumentos posicionais, a saber INPUT_DIR e OUTPUT_DIR, que representam o backup do iTunes e a pasta de saída desejada -

if __name__ == "__main__":
   parser.add_argument("INPUT_DIR",help = "Location of folder containing iOS backups, ""e.g. ~\Library\Application Support\MobileSync\Backup folder")
   parser.add_argument("OUTPUT_DIR", help = "Output Directory")
   parser.add_argument("-l", help = "Log file path",default = __file__[:-2] + "log")
   parser.add_argument("-v", help = "Increase verbosity",action = "store_true") args = parser.parse_args()

Agora, configure o log da seguinte maneira -

if args.v:
   logger.setLevel(logging.DEBUG)
else:
   logger.setLevel(logging.INFO)

Agora, configure o formato da mensagem para este log da seguinte maneira -

msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-13s""%(levelname)-8s %(message)s")
strhndl = logging.StreamHandler(sys.stderr)
strhndl.setFormatter(fmt = msg_fmt)

fhndl = logging.FileHandler(args.l, mode = 'a')
fhndl.setFormatter(fmt = msg_fmt)

logger.addHandler(strhndl)
logger.addHandler(fhndl)
logger.info("Starting iBackup Visualizer")
logger.debug("Supplied arguments: {}".format(" ".join(sys.argv[1:])))
logger.debug("System: " + sys.platform)
logger.debug("Python Version: " + sys.version)

A linha de código a seguir criará as pastas necessárias para o diretório de saída desejado usando os.makedirs() função -

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)

Agora, passe os diretórios de entrada e saída fornecidos para a função main () da seguinte maneira -

if os.path.exists(args.INPUT_DIR) and os.path.isdir(args.INPUT_DIR):
   main(args.INPUT_DIR, args.OUTPUT_DIR)
else:
   logger.error("Supplied input directory does not exist or is not ""a directory")
   sys.exit(1)

Agora escreva main() função que irá posteriormente chamar backup_summary() função para identificar todos os backups presentes na pasta de entrada -

def main(in_dir, out_dir):
   backups = backup_summary(in_dir)
def backup_summary(in_dir):
   logger.info("Identifying all iOS backups in {}".format(in_dir))
   root = os.listdir(in_dir)
   backups = {}
   
   for x in root:
      temp_dir = os.path.join(in_dir, x)
      if os.path.isdir(temp_dir) and len(x) == 40:
         num_files = 0
         size = 0
         
         for root, subdir, files in os.walk(temp_dir):
            num_files += len(files)
            size += sum(os.path.getsize(os.path.join(root, name))
               for name in files)
         backups[x] = [temp_dir, num_files, size]
   return backups

Agora, imprima o resumo de cada backup no console da seguinte maneira -

print("Backup Summary")
print("=" * 20)

if len(backups) > 0:
   for i, b in enumerate(backups):
      print("Backup No.: {} \n""Backup Dev. Name: {} \n""# Files: {} \n""Backup Size (Bytes): {}\n".format(i, b, backups[b][1], backups[b][2]))

Agora, despeje o conteúdo do arquivo Manifest.db na variável chamada db_items.

try:
   db_items = process_manifest(backups[b][0])
   except IOError:
      logger.warn("Non-iOS 10 backup encountered or " "invalid backup. Continuing to next backup.")
continue

Agora, vamos definir uma função que tomará o caminho do diretório do backup -

def process_manifest(backup):
   manifest = os.path.join(backup, "Manifest.db")
   
   if not os.path.exists(manifest):
      logger.error("Manifest DB not found in {}".format(manifest))
      raise IOError

Agora, usando SQLite3, iremos conectar ao banco de dados pelo cursor chamado c -

c = conn.cursor()
items = {}

for row in c.execute("SELECT * from Files;"):
   items[row[0]] = [row[2], row[1], row[3]]
return items

create_files(in_dir, out_dir, b, db_items)
   print("=" * 20)
else:
   logger.warning("No valid backups found. The input directory should be
      " "the parent-directory immediately above the SHA-1 hash " "iOS device backups")
      sys.exit(2)

Agora, defina o create_files() método da seguinte forma -

def create_files(in_dir, out_dir, b, db_items):
   msg = "Copying Files for backup {} to {}".format(b, os.path.join(out_dir, b))
   logger.info(msg)

Agora, itere por meio de cada chave no db_items dicionário -

for x, key in enumerate(db_items):
   if db_items[key][0] is None or db_items[key][0] == "":
      continue
   else:
      dirpath = os.path.join(out_dir, b,
os.path.dirname(db_items[key][0]))
   filepath = os.path.join(out_dir, b, db_items[key][0])
   
   if not os.path.exists(dirpath):
      os.makedirs(dirpath)
      original_dir = b + "/" + key[0:2] + "/" + key
   path = os.path.join(in_dir, original_dir)
   
   if os.path.exists(filepath):
      filepath = filepath + "_{}".format(x)

Agora usa shutil.copyfile() método para copiar o arquivo de backup da seguinte forma -

try:
   copyfile(path, filepath)
   except IOError:
      logger.debug("File not found in backup: {}".format(path))
         files_not_found += 1
   if files_not_found > 0:
      logger.warning("{} files listed in the Manifest.db not" "found in
backup".format(files_not_found))
   copyfile(os.path.join(in_dir, b, "Info.plist"), os.path.join(out_dir, b,
"Info.plist"))
   copyfile(os.path.join(in_dir, b, "Manifest.db"), os.path.join(out_dir, b,
"Manifest.db"))
   copyfile(os.path.join(in_dir, b, "Manifest.plist"), os.path.join(out_dir, b,
"Manifest.plist"))
   copyfile(os.path.join(in_dir, b, "Status.plist"),os.path.join(out_dir, b,
"Status.plist"))

Com o script Python acima, podemos obter a estrutura de arquivo de backup atualizada em nossa pasta de saída. Podemos usarpycrypto biblioteca python para descriptografar os backups.

Wi-fi

Os dispositivos móveis podem ser usados ​​para se conectar ao mundo exterior, conectando-se por meio de redes Wi-Fi que estão disponíveis em todos os lugares. Às vezes, o dispositivo se conecta a essas redes abertas automaticamente.

No caso do iPhone, a lista de conexões Wi-Fi abertas com as quais o dispositivo se conectou é armazenada em um arquivo PLIST chamado com.apple.wifi.plist. Este arquivo conterá o SSID Wi-Fi, BSSID e tempo de conexão.

Precisamos extrair os detalhes do Wi-Fi do relatório XML padrão da Cellebrite usando Python. Para isso, precisamos usar a API do Wireless Geographic Logging Engine (WIGLE), uma plataforma popular que pode ser usada para encontrar a localização de um dispositivo usando os nomes de redes wi-fi.

Podemos usar a biblioteca Python chamada requestspara acessar a API do WIGLE. Ele pode ser instalado da seguinte forma -

pip install requests

API do WIGLE

Precisamos nos registrar no site do WIGLE https://wigle.net/accountpara obter uma API gratuita do WIGLE. O script Python para obter as informações sobre o dispositivo do usuário e sua conexão por meio da API WIGEL é discutido abaixo -

Primeiro, importe as seguintes bibliotecas para lidar com coisas diferentes -

from __future__ import print_function

import argparse
import csv
import os
import sys
import xml.etree.ElementTree as ET
import requests

Agora, forneça dois argumentos posicionais, a saber INPUT_FILE e OUTPUT_CSV que representará o arquivo de entrada com o endereço MAC Wi-Fi e o arquivo CSV de saída desejado, respectivamente -

if __name__ == "__main__":
   parser.add_argument("INPUT_FILE", help = "INPUT FILE with MAC Addresses")
   parser.add_argument("OUTPUT_CSV", help = "Output CSV File")
   parser.add_argument("-t", help = "Input type: Cellebrite XML report or TXT
file",choices = ('xml', 'txt'), default = "xml")
   parser.add_argument('--api', help = "Path to API key
   file",default = os.path.expanduser("~/.wigle_api"),
   type = argparse.FileType('r'))
   args = parser.parse_args()

Agora, as seguintes linhas de código verificarão se o arquivo de entrada existe e é um arquivo. Caso contrário, sai do script -

if not os.path.exists(args.INPUT_FILE) or \ not os.path.isfile(args.INPUT_FILE):
   print("[-] {} does not exist or is not a
file".format(args.INPUT_FILE))
   sys.exit(1)
directory = os.path.dirname(args.OUTPUT_CSV)
if directory != '' and not os.path.exists(directory):
   os.makedirs(directory)
api_key = args.api.readline().strip().split(":")

Agora, passe o argumento para main da seguinte forma -

main(args.INPUT_FILE, args.OUTPUT_CSV, args.t, api_key)
def main(in_file, out_csv, type, api_key):
   if type == 'xml':
      wifi = parse_xml(in_file)
   else:
      wifi = parse_txt(in_file)
query_wigle(wifi, out_csv, api_key)

Agora, vamos analisar o arquivo XML da seguinte maneira -

def parse_xml(xml_file):
   wifi = {}
   xmlns = "{http://pa.cellebrite.com/report/2.0}"
   print("[+] Opening {} report".format(xml_file))
   
   xml_tree = ET.parse(xml_file)
   print("[+] Parsing report for all connected WiFi addresses")
   
   root = xml_tree.getroot()

Agora, itere através do elemento filho da raiz da seguinte maneira -

for child in root.iter():
   if child.tag == xmlns + "model":
      if child.get("type") == "Location":
         for field in child.findall(xmlns + "field"):
            if field.get("name") == "TimeStamp":
               ts_value = field.find(xmlns + "value")
               try:
               ts = ts_value.text
               except AttributeError:
continue

Agora, vamos verificar se a string 'ssid' está presente no texto do valor ou não -

if "SSID" in value.text:
   bssid, ssid = value.text.split("\t")
   bssid = bssid[7:]
   ssid = ssid[6:]

Agora, precisamos adicionar BSSID, SSID e timestamp ao dicionário wi-fi da seguinte maneira -

if bssid in wifi.keys():

wifi[bssid]["Timestamps"].append(ts)
   wifi[bssid]["SSID"].append(ssid)
else:
   wifi[bssid] = {"Timestamps": [ts], "SSID":
[ssid],"Wigle": {}}
return wifi

O analisador de texto, que é muito mais simples do que o analisador XML, é mostrado abaixo -

def parse_txt(txt_file):
   wifi = {}
   print("[+] Extracting MAC addresses from {}".format(txt_file))
   
   with open(txt_file) as mac_file:
      for line in mac_file:
         wifi[line.strip()] = {"Timestamps": ["N/A"], "SSID":
["N/A"],"Wigle": {}}
return wifi

Agora, vamos usar o módulo de solicitações para fazer WIGLE APIchamadas e precisa passar para o query_wigle() método -

def query_wigle(wifi_dictionary, out_csv, api_key):
   print("[+] Querying Wigle.net through Python API for {} "
"APs".format(len(wifi_dictionary)))
   for mac in wifi_dictionary:

   wigle_results = query_mac_addr(mac, api_key)
def query_mac_addr(mac_addr, api_key):

   query_url = "https://api.wigle.net/api/v2/network/search?" \
"onlymine = false&freenet = false&paynet = false" \ "&netid = {}".format(mac_addr)
   req = requests.get(query_url, auth = (api_key[0], api_key[1]))
   return req.json()

Na verdade, há um limite por dia para chamadas WIGLE API, se esse limite exceder, ele deve mostrar um erro da seguinte forma -

try:
   if wigle_results["resultCount"] == 0:
      wifi_dictionary[mac]["Wigle"]["results"] = []
         continue
   else:
      wifi_dictionary[mac]["Wigle"] = wigle_results
except KeyError:
   if wigle_results["error"] == "too many queries today":
      print("[-] Wigle daily query limit exceeded")
      wifi_dictionary[mac]["Wigle"]["results"] = []
      continue
   else:
      print("[-] Other error encountered for " "address {}: {}".format(mac,
wigle_results['error']))
   wifi_dictionary[mac]["Wigle"]["results"] = []
   continue
prep_output(out_csv, wifi_dictionary)

Agora, vamos usar prep_output() método para nivelar o dicionário em pedaços facilmente graváveis ​​-

def prep_output(output, data):
   csv_data = {}
   google_map = https://www.google.com/maps/search/

Agora, acesse todos os dados que coletamos até agora, da seguinte forma -

for x, mac in enumerate(data):
   for y, ts in enumerate(data[mac]["Timestamps"]):
      for z, result in enumerate(data[mac]["Wigle"]["results"]):
         shortres = data[mac]["Wigle"]["results"][z]
         g_map_url = "{}{},{}".format(google_map, shortres["trilat"],shortres["trilong"])

Agora, podemos escrever a saída em um arquivo CSV como fizemos em scripts anteriores neste capítulo, usando write_csv() função.

Neste capítulo, aprenderemos em detalhes sobre como investigar metadados embutidos usando Python digital forensics.

Introdução

Metadados incorporados são as informações sobre os dados armazenados no mesmo arquivo que contém o objeto descrito por esses dados. Em outras palavras, são as informações sobre um ativo digital armazenadas no próprio arquivo digital. Ele está sempre associado ao arquivo e nunca pode ser separado.

No caso da perícia digital, não podemos extrair todas as informações sobre um determinado arquivo. Por outro lado, os metadados incorporados podem nos fornecer informações críticas para a investigação. Por exemplo, os metadados de um arquivo de texto podem conter informações sobre o autor, sua extensão, data de escrita e até um breve resumo sobre aquele documento. Uma imagem digital pode incluir os metadados, como o comprimento da imagem, a velocidade do obturador, etc.

Artefatos que contêm atributos de metadados e sua extração

Nesta seção, aprenderemos sobre vários artefatos contendo atributos de metadados e seu processo de extração usando Python.

Áudio e vídeo

Esses são os dois artefatos muito comuns que possuem os metadados integrados. Esses metadados podem ser extraídos para fins de investigação.

Você pode usar o seguinte script Python para extrair atributos comuns ou metadados do arquivo de áudio ou MP3 e de um vídeo ou arquivo MP4.

Observe que, para este script, precisamos instalar uma biblioteca python de terceiros chamada mutagen, que nos permite extrair metadados de arquivos de áudio e vídeo. Ele pode ser instalado com a ajuda do seguinte comando -

pip install mutagen

Algumas das bibliotecas úteis que precisamos importar para este script Python são as seguintes -

from __future__ import print_function

import argparse
import json
import mutagen

O manipulador de linha de comando terá um argumento que representa o caminho para os arquivos MP3 ou MP4. Então, vamos usarmutagen.file() método para abrir um identificador para o arquivo da seguinte maneira -

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Python Metadata Extractor')
   parser.add_argument("AV_FILE", help="File to extract metadata from")
   args = parser.parse_args()
   av_file = mutagen.File(args.AV_FILE)
   file_ext = args.AV_FILE.rsplit('.', 1)[-1]
   
   if file_ext.lower() == 'mp3':
      handle_id3(av_file)
   elif file_ext.lower() == 'mp4':
      handle_mp4(av_file)

Agora, precisamos usar duas alças, uma para extrair os dados do MP3 e outra para extrair os dados do arquivo MP4. Podemos definir essas alças da seguinte maneira -

def handle_id3(id3_file):
   id3_frames = {'TIT2': 'Title', 'TPE1': 'Artist', 'TALB': 'Album','TXXX':
      'Custom', 'TCON': 'Content Type', 'TDRL': 'Date released','COMM': 'Comments',
         'TDRC': 'Recording Date'}
   print("{:15} | {:15} | {:38} | {}".format("Frame", "Description","Text","Value"))
   print("-" * 85)
   
   for frames in id3_file.tags.values():
      frame_name = id3_frames.get(frames.FrameID, frames.FrameID)
      desc = getattr(frames, 'desc', "N/A")
      text = getattr(frames, 'text', ["N/A"])[0]
      value = getattr(frames, 'value', "N/A")
      
      if "date" in frame_name.lower():
         text = str(text)
      print("{:15} | {:15} | {:38} | {}".format(
         frame_name, desc, text, value))
def handle_mp4(mp4_file):
   cp_sym = u"\u00A9"
   qt_tag = {
      cp_sym + 'nam': 'Title', cp_sym + 'art': 'Artist',
      cp_sym + 'alb': 'Album', cp_sym + 'gen': 'Genre',
      'cpil': 'Compilation', cp_sym + 'day': 'Creation Date',
      'cnID': 'Apple Store Content ID', 'atID': 'Album Title ID',
      'plID': 'Playlist ID', 'geID': 'Genre ID', 'pcst': 'Podcast',
      'purl': 'Podcast URL', 'egid': 'Episode Global ID',
      'cmID': 'Camera ID', 'sfID': 'Apple Store Country',
      'desc': 'Description', 'ldes': 'Long Description'}
genre_ids = json.load(open('apple_genres.json'))

Agora, precisamos iterar por meio desse arquivo MP4 da seguinte maneira -

print("{:22} | {}".format('Name', 'Value'))
print("-" * 40)

for name, value in mp4_file.tags.items():
   tag_name = qt_tag.get(name, name)
   
   if isinstance(value, list):
      value = "; ".join([str(x) for x in value])
   if name == 'geID':
      value = "{}: {}".format(
      value, genre_ids[str(value)].replace("|", " - "))
   print("{:22} | {}".format(tag_name, value))

O script acima nos fornecerá informações adicionais sobre os arquivos MP3 e MP4.

Imagens

As imagens podem conter diferentes tipos de metadados, dependendo do formato do arquivo. No entanto, a maioria das imagens contém informações de GPS. Podemos extrair essas informações de GPS usando bibliotecas Python de terceiros. Você pode usar o seguinte script Python para fazer o mesmo -

Primeiro, baixe a biblioteca python de terceiros chamada Python Imaging Library (PIL) como segue -

pip install pillow

Isso nos ajudará a extrair metadados de imagens.

Também podemos gravar os detalhes do GPS incorporados nas imagens em um arquivo KML, mas para isso precisamos baixar a biblioteca Python de terceiros chamada simplekml como segue -

pip install simplekml

Neste script, primeiro precisamos importar as seguintes bibliotecas -

from __future__ import print_function
import argparse

from PIL import Image
from PIL.ExifTags import TAGS

import simplekml
import sys

Agora, o manipulador de linha de comando aceitará um argumento posicional que basicamente representa o caminho do arquivo das fotos.

parser = argparse.ArgumentParser('Metadata from images')
parser.add_argument('PICTURE_FILE', help = "Path to picture")
args = parser.parse_args()

Agora, precisamos especificar os URLs que preencherão as informações das coordenadas. Os URLs sãogmaps e open_maps. Também precisamos de uma função para converter a coordenada da tupla de graus-minuto-segundos (DMS), fornecida pela biblioteca PIL, em decimal. Isso pode ser feito da seguinte forma -

gmaps = "https://www.google.com/maps?q={},{}"
open_maps = "http://www.openstreetmap.org/?mlat={}&mlon={}"

def process_coords(coord):
   coord_deg = 0
   
   for count, values in enumerate(coord):
      coord_deg += (float(values[0]) / values[1]) / 60**count
   return coord_deg

Agora, vamos usar image.open() função para abrir o arquivo como objeto PIL.

img_file = Image.open(args.PICTURE_FILE)
exif_data = img_file._getexif()

if exif_data is None:
   print("No EXIF data found")
   sys.exit()
for name, value in exif_data.items():
   gps_tag = TAGS.get(name, name)
   if gps_tag is not 'GPSInfo':
      continue

Depois de encontrar o GPSInfo tag, vamos armazenar a referência GPS e processar as coordenadas com o process_coords() método.

lat_ref = value[1] == u'N'
lat = process_coords(value[2])

if not lat_ref:
   lat = lat * -1
lon_ref = value[3] == u'E'
lon = process_coords(value[4])

if not lon_ref:
   lon = lon * -1

Agora, inicie kml objeto de simplekml biblioteca da seguinte forma -

kml = simplekml.Kml()
kml.newpoint(name = args.PICTURE_FILE, coords = [(lon, lat)])
kml.save(args.PICTURE_FILE + ".kml")

Agora podemos imprimir as coordenadas das informações processadas da seguinte forma -

print("GPS Coordinates: {}, {}".format(lat, lon))
print("Google Maps URL: {}".format(gmaps.format(lat, lon)))
print("OpenStreetMap URL: {}".format(open_maps.format(lat, lon)))
print("KML File {} created".format(args.PICTURE_FILE + ".kml"))

Documentos PDF

Os documentos PDF têm uma ampla variedade de mídias, incluindo imagens, texto, formulários, etc. Quando extraímos metadados incorporados em documentos PDF, podemos obter os dados resultantes no formato denominado Extensible Metadata Platform (XMP). Podemos extrair metadados com a ajuda do seguinte código Python -

Primeiro, instale uma biblioteca Python de terceiros chamada PyPDF2para ler metadados armazenados no formato XMP. Ele pode ser instalado da seguinte forma -

pip install PyPDF2

Agora, importe as seguintes bibliotecas para extrair os metadados de arquivos PDF -

from __future__ import print_function
from argparse import ArgumentParser, FileType

import datetime
from PyPDF2 import PdfFileReader
import sys

Agora, o manipulador de linha de comando aceitará um argumento posicional que basicamente representa o caminho do arquivo PDF.

parser = argparse.ArgumentParser('Metadata from PDF')
parser.add_argument('PDF_FILE', help='Path to PDF file',type=FileType('rb'))
args = parser.parse_args()

Agora podemos usar getXmpMetadata() método para fornecer um objeto contendo os metadados disponíveis da seguinte forma -

pdf_file = PdfFileReader(args.PDF_FILE)
xmpm = pdf_file.getXmpMetadata()

if xmpm is None:
   print("No XMP metadata found in document.")
   sys.exit()

Podemos usar custom_print() método para extrair e imprimir os valores relevantes como título, criador, contribuidor etc. da seguinte maneira -

custom_print("Title: {}", xmpm.dc_title)
custom_print("Creator(s): {}", xmpm.dc_creator)
custom_print("Contributors: {}", xmpm.dc_contributor)
custom_print("Subject: {}", xmpm.dc_subject)
custom_print("Description: {}", xmpm.dc_description)
custom_print("Created: {}", xmpm.xmp_createDate)
custom_print("Modified: {}", xmpm.xmp_modifyDate)
custom_print("Event Dates: {}", xmpm.dc_date)

Nós também podemos definir custom_print() método no caso de o PDF ser criado usando vários softwares, como segue -

def custom_print(fmt_str, value):
   if isinstance(value, list):
      print(fmt_str.format(", ".join(value)))
   elif isinstance(value, dict):
      fmt_value = [":".join((k, v)) for k, v in value.items()]
      print(fmt_str.format(", ".join(value)))
   elif isinstance(value, str) or isinstance(value, bool):
      print(fmt_str.format(value))
   elif isinstance(value, bytes):
      print(fmt_str.format(value.decode()))
   elif isinstance(value, datetime.datetime):
      print(fmt_str.format(value.isoformat()))
   elif value is None:
      print(fmt_str.format("N/A"))
   else:
      print("warn: unhandled type {} found".format(type(value)))

Também podemos extrair qualquer outra propriedade personalizada salva pelo software da seguinte forma -

if xmpm.custom_properties:
   print("Custom Properties:")
   
   for k, v in xmpm.custom_properties.items():
      print("\t{}: {}".format(k, v))

O script acima lerá o documento PDF e imprimirá os metadados armazenados no formato XMP, incluindo algumas propriedades personalizadas armazenadas pelo software com a ajuda do qual o PDF foi criado.

Arquivos executáveis ​​do Windows

Às vezes, podemos encontrar um arquivo executável suspeito ou não autorizado. Mas, para fins de investigação, pode ser útil por causa dos metadados incorporados. Podemos obter as informações como sua localização, sua finalidade e outros atributos, como o fabricante, data de compilação, etc. Com a ajuda do seguinte script Python, podemos obter a data de compilação, dados úteis de cabeçalhos e símbolos importados, bem como exportados.

Para isso, instale primeiro a biblioteca Python de terceiros pefile. Isso pode ser feito da seguinte forma -

pip install pefile

Depois de instalar com sucesso, importe as seguintes bibliotecas da seguinte forma -

from __future__ import print_function

import argparse
from datetime import datetime
from pefile import PE

Agora, o manipulador de linha de comando aceitará um argumento posicional que basicamente representa o caminho do arquivo executável. Você também pode escolher o estilo de saída, se você precisa dele de forma detalhada e detalhada ou de forma simplificada. Para isso, você precisa fornecer um argumento opcional conforme mostrado abaixo -

parser = argparse.ArgumentParser('Metadata from executable file')
parser.add_argument("EXE_FILE", help = "Path to exe file")
parser.add_argument("-v", "--verbose", help = "Increase verbosity of output",
action = 'store_true', default = False)
args = parser.parse_args()

Agora, vamos carregar o arquivo executável de entrada usando a classe PE. Também iremos despejar os dados executáveis ​​em um objeto de dicionário usandodump_dict() método.

pe = PE(args.EXE_FILE)
ped = pe.dump_dict()

Podemos extrair metadados de arquivo básicos, como autoria incorporada, versão e tempo de compilação usando o código mostrado abaixo -

file_info = {}
for structure in pe.FileInfo:
   if structure.Key == b'StringFileInfo':
      for s_table in structure.StringTable:
         for key, value in s_table.entries.items():
            if value is None or len(value) == 0:
               value = "Unknown"
            file_info[key] = value
print("File Information: ")
print("==================")

for k, v in file_info.items():
   if isinstance(k, bytes):
      k = k.decode()
   if isinstance(v, bytes):
      v = v.decode()
   print("{}: {}".format(k, v))
comp_time = ped['FILE_HEADER']['TimeDateStamp']['Value']
comp_time = comp_time.split("[")[-1].strip("]")
time_stamp, timezone = comp_time.rsplit(" ", 1)
comp_time = datetime.strptime(time_stamp, "%a %b %d %H:%M:%S %Y")
print("Compiled on {} {}".format(comp_time, timezone.strip()))

Podemos extrair os dados úteis dos cabeçalhos da seguinte maneira -

for section in ped['PE Sections']:
   print("Section '{}' at {}: {}/{} {}".format(
      section['Name']['Value'], hex(section['VirtualAddress']['Value']),
      section['Misc_VirtualSize']['Value'],
      section['SizeOfRawData']['Value'], section['MD5'])
   )

Agora, extraia a lista de importações e exportações de arquivos executáveis, conforme mostrado abaixo -

if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
   print("\nImports: ")
   print("=========")
   
   for dir_entry in pe.DIRECTORY_ENTRY_IMPORT:
      dll = dir_entry.dll
      
      if not args.verbose:
         print(dll.decode(), end=", ")
         continue
      name_list = []
      
      for impts in dir_entry.imports:
         if getattr(impts, "name", b"Unknown") is None:
            name = b"Unknown"
         else:
            name = getattr(impts, "name", b"Unknown")
			name_list.append([name.decode(), hex(impts.address)])
      name_fmt = ["{} ({})".format(x[0], x[1]) for x in name_list]
      print('- {}: {}'.format(dll.decode(), ", ".join(name_fmt)))
   if not args.verbose:
      print()

Agora, imprima exports, names e addresses usando o código conforme mostrado abaixo -

if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
   print("\nExports: ")
   print("=========")
   
   for sym in pe.DIRECTORY_ENTRY_EXPORT.symbols:
      print('- {}: {}'.format(sym.name.decode(), hex(sym.address)))

O script acima irá extrair os metadados básicos, informações dos cabeçalhos dos arquivos executáveis ​​do Windows.

Metadados de documentos de escritório

A maior parte do trabalho no computador é feita em três aplicativos do MS Office - Word, PowerPoint e Excel. Esses arquivos possuem metadados enormes, que podem expor informações interessantes sobre sua autoria e história.

Observe que os metadados do formato Word (.docx), Excel (.xlsx) e PowerPoint (.pptx) de 2007 são armazenados em um arquivo XML. Podemos processar esses arquivos XML em Python com a ajuda do seguinte script Python mostrado abaixo -

Primeiro, importe as bibliotecas necessárias conforme mostrado abaixo -

from __future__ import print_function
from argparse import ArgumentParser
from datetime import datetime as dt
from xml.etree import ElementTree as etree

import zipfile
parser = argparse.ArgumentParser('Office Document Metadata’)
parser.add_argument("Office_File", help="Path to office file to read")
args = parser.parse_args()

Agora, verifique se o arquivo é um arquivo ZIP. Caso contrário, gere um erro. Agora, abra o arquivo e extraia os elementos-chave para processamento usando o seguinte código -

zipfile.is_zipfile(args.Office_File)
zfile = zipfile.ZipFile(args.Office_File)
core_xml = etree.fromstring(zfile.read('docProps/core.xml'))
app_xml = etree.fromstring(zfile.read('docProps/app.xml'))

Agora, crie um dicionário para iniciar a extração dos metadados -

core_mapping = {
   'title': 'Title',
   'subject': 'Subject',
   'creator': 'Author(s)',
   'keywords': 'Keywords',
   'description': 'Description',
   'lastModifiedBy': 'Last Modified By',
   'modified': 'Modified Date',
   'created': 'Created Date',
   'category': 'Category',
   'contentStatus': 'Status',
   'revision': 'Revision'
}

Usar iterchildren() método para acessar cada uma das tags dentro do arquivo XML -

for element in core_xml.getchildren():
   for key, title in core_mapping.items():
      if key in element.tag:
         if 'date' in title.lower():
            text = dt.strptime(element.text, "%Y-%m-%dT%H:%M:%SZ")
         else:
            text = element.text
         print("{}: {}".format(title, text))

Da mesma forma, faça isso para o arquivo app.xml, que contém informações estatísticas sobre o conteúdo do documento -

app_mapping = {
   'TotalTime': 'Edit Time (minutes)',
   'Pages': 'Page Count',
   'Words': 'Word Count',
   'Characters': 'Character Count',
   'Lines': 'Line Count',
   'Paragraphs': 'Paragraph Count',
   'Company': 'Company',
   'HyperlinkBase': 'Hyperlink Base',
   'Slides': 'Slide count',
   'Notes': 'Note Count',
   'HiddenSlides': 'Hidden Slide Count',
}
for element in app_xml.getchildren():
   for key, title in app_mapping.items():
      if key in element.tag:
         if 'date' in title.lower():
            text = dt.strptime(element.text, "%Y-%m-%dT%H:%M:%SZ")
         else:
            text = element.text
         print("{}: {}".format(title, text))

Agora, depois de executar o script acima, podemos obter os diferentes detalhes sobre o documento específico. Observe que podemos aplicar este script em documentos do Office 2007 ou versão posterior apenas.

Este capítulo explicará os fundamentos envolvidos na execução de análise forense de rede usando Python.

Noções básicas sobre análise forense de rede

Network forensics é um ramo da forense digital que trata do monitoramento e análise do tráfego de redes de computadores, tanto locais quanto WAN (rede de longa distância), para fins de coleta de informações, coleta de evidências ou detecção de intrusão. A perícia de rede desempenha um papel fundamental na investigação de crimes digitais, como roubo de propriedade intelectual ou vazamento de informações. Uma imagem das comunicações de rede ajuda um investigador a resolver algumas questões cruciais como segue -

  • Quais sites foram acessados?

  • Que tipo de conteúdo foi carregado em nossa rede?

  • Que tipo de conteúdo foi baixado de nossa rede?

  • Quais servidores estão sendo acessados?

  • Alguém está enviando informações confidenciais fora dos firewalls da empresa?

Internet Evidence Finder (IEF)

O IEF é uma ferramenta digital forense para encontrar, analisar e apresentar evidências digitais encontradas em diferentes mídias digitais como computador, smartphones, tablets etc. É muito popular e utilizado por milhares de profissionais forenses.

Uso de IEF

Devido à sua popularidade, o IEF é amplamente utilizado por profissionais forenses. Alguns dos usos do IEF são os seguintes -

  • Devido aos seus poderosos recursos de pesquisa, ele é usado para pesquisar vários arquivos ou mídia de dados simultaneamente.

  • Ele também é usado para recuperar dados excluídos do espaço não alocado da RAM por meio de novas técnicas de escultura.

  • Se os investigadores quiserem reconstruir as páginas da web em seu formato original na data em que foram abertas, eles podem usar o IEF.

  • Ele também é usado para pesquisar volumes de disco lógico ou físico.

Relatórios de despejo de IEF para CSV usando Python

O IEF armazena dados em um banco de dados SQLite e o script Python seguinte identifica dinamicamente as tabelas de resultados no banco de dados IEF e as despeja nos respectivos arquivos CSV.

Este processo é feito nas etapas mostradas abaixo

  • Primeiro, gere o banco de dados de resultados do IEF que será um arquivo de banco de dados SQLite terminando com a extensão .db.

  • Em seguida, consulte esse banco de dados para identificar todas as tabelas.

  • Por último, grave essas tabelas de resultados em um arquivo CSV individual.

Código Python

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

Para o script Python, importe as bibliotecas necessárias da seguinte maneira -

from __future__ import print_function

import argparse
import csv
import os
import sqlite3
import sys

Agora, precisamos fornecer o caminho para o arquivo de banco de dados IEF -

if __name__ == '__main__':
   parser = argparse.ArgumentParser('IEF to CSV')
   parser.add_argument("IEF_DATABASE", help="Input IEF database")
   parser.add_argument("OUTPUT_DIR", help="Output DIR")
   args = parser.parse_args()

Agora, vamos confirmar a existência do banco de dados IEF da seguinte forma -

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)
if os.path.exists(args.IEF_DATABASE) and \ os.path.isfile(args.IEF_DATABASE):
   main(args.IEF_DATABASE, args.OUTPUT_DIR)
else:
   print("[-] Supplied input file {} does not exist or is not a " "file".format(args.IEF_DATABASE))
   sys.exit(1)

Agora, como fizemos em scripts anteriores, faça a conexão com o banco de dados SQLite da seguinte forma para executar as consultas por meio do cursor -

def main(database, out_directory):
   print("[+] Connecting to SQLite database")
   conn = sqlite3.connect(database)
   c = conn.cursor()

As seguintes linhas de código buscarão os nomes das tabelas no banco de dados -

print("List of all tables to extract")
c.execute("select * from sqlite_master where type = 'table'")
tables = [x[2] for x in c.fetchall() if not x[2].startswith('_') and not x[2].endswith('_DATA')]

Agora, vamos selecionar todos os dados da tabela e usando fetchall() método no objeto cursor, iremos armazenar a lista de tuplas contendo os dados da tabela em sua totalidade em uma variável -

print("Dumping {} tables to CSV files in {}".format(len(tables), out_directory))

for table in tables:
c.execute("pragma table_info('{}')".format(table))
table_columns = [x[1] for x in c.fetchall()]

c.execute("select * from '{}'".format(table))
table_data = c.fetchall()

Agora, usando CSV_Writer() método, vamos escrever o conteúdo em arquivo CSV -

csv_name = table + '.csv'
csv_path = os.path.join(out_directory, csv_name)
print('[+] Writing {} table to {} CSV file'.format(table,csv_name))

with open(csv_path, "w", newline = "") as csvfile:
   csv_writer = csv.writer(csvfile)
   csv_writer.writerow(table_columns)
   csv_writer.writerows(table_data)

O script acima irá buscar todos os dados das tabelas do banco de dados IEF e escrever o conteúdo no arquivo CSV de nossa escolha.

Trabalho com dados em cache

No banco de dados de resultados do IEF, podemos buscar mais informações que não são necessariamente suportadas pelo próprio IEF. Podemos buscar os dados em cache, um produto bi para informações, de um provedor de serviços de e-mail como Yahoo, Google etc. usando o banco de dados de resultados do IEF.

A seguir está o script Python para acessar as informações de dados em cache do correio do Yahoo, acessado no Google Chrome, usando o banco de dados IEF. Observe que as etapas seriam mais ou menos iguais às seguidas no último script Python.

Primeiro, importe as bibliotecas necessárias para Python da seguinte maneira -

from __future__ import print_function
import argparse
import csv
import os
import sqlite3
import sys
import json

Agora, forneça o caminho para o arquivo de banco de dados IEF junto com dois argumentos posicionais aceitos pelo manipulador de linha de comando como feito no último script -

if __name__ == '__main__':
   parser = argparse.ArgumentParser('IEF to CSV')
   parser.add_argument("IEF_DATABASE", help="Input IEF database")
   parser.add_argument("OUTPUT_DIR", help="Output DIR")
   args = parser.parse_args()

Agora, confirme a existência do banco de dados IEF da seguinte forma -

directory = os.path.dirname(args.OUTPUT_CSV)

if not os.path.exists(directory):os.makedirs(directory)
if os.path.exists(args.IEF_DATABASE) and \ os.path.isfile(args.IEF_DATABASE):
   main(args.IEF_DATABASE, args.OUTPUT_CSV)
   else: print("Supplied input file {} does not exist or is not a " "file".format(args.IEF_DATABASE))
sys.exit(1)

Agora, faça a conexão com o banco de dados SQLite da seguinte forma para executar as consultas por meio do cursor -

def main(database, out_csv):
   print("[+] Connecting to SQLite database")
   conn = sqlite3.connect(database)
   c = conn.cursor()

Você pode usar as seguintes linhas de código para buscar as instâncias do registro de cache de contato do Yahoo Mail -

print("Querying IEF database for Yahoo Contact Fragments from " "the Chrome Cache Records Table")
   try:
      c.execute("select * from 'Chrome Cache Records' where URL like " "'https://data.mail.yahoo.com" "/classicab/v2/contacts/?format=json%'")
   except sqlite3.OperationalError:
      print("Received an error querying the database --    database may be" "corrupt or not have a Chrome Cache Records table")
      sys.exit(2)

Agora, a lista de tuplas retornada da consulta acima para ser salva em uma variável da seguinte maneira -

contact_cache = c.fetchall()
contact_data = process_contacts(contact_cache)
write_csv(contact_data, out_csv)

Observe que aqui vamos usar dois métodos, a saber process_contacts() para configurar a lista de resultados, bem como iterar através de cada registro de cache de contato e json.loads() para armazenar os dados JSON extraídos da tabela em uma variável para posterior manipulação -

def process_contacts(contact_cache):
   print("[+] Processing {} cache files matching Yahoo contact cache " " data".format(len(contact_cache)))
   results = []
   
   for contact in contact_cache:
      url = contact[0]
      first_visit = contact[1]
      last_visit = contact[2]
      last_sync = contact[3]
      loc = contact[8]
	   contact_json = json.loads(contact[7].decode())
      total_contacts = contact_json["total"]
      total_count = contact_json["count"]
      
      if "contacts" not in contact_json:
         continue
      for c in contact_json["contacts"]:
         name, anni, bday, emails, phones, links = ("", "", "", "", "", "")
            if "name" in c:
            name = c["name"]["givenName"] + " " + \ c["name"]["middleName"] + " " + c["name"]["familyName"]
            
            if "anniversary" in c:
            anni = c["anniversary"]["month"] + \"/" + c["anniversary"]["day"] + "/" + \c["anniversary"]["year"]
            
            if "birthday" in c:
            bday = c["birthday"]["month"] + "/" + \c["birthday"]["day"] + "/" + c["birthday"]["year"]
            
            if "emails" in c:
               emails = ', '.join([x["ep"] for x in c["emails"]])
            
            if "phones" in c:
               phones = ', '.join([x["ep"] for x in c["phones"]])
            
            if "links" in c:
              links = ', '.join([x["ep"] for x in c["links"]])

Agora, para empresa, título e notas, o método get é usado conforme mostrado abaixo -

company = c.get("company", "")
title = c.get("jobTitle", "")
notes = c.get("notes", "")

Agora, vamos acrescentar a lista de metadados e elementos de dados extraídos à lista de resultados da seguinte forma -

results.append([url, first_visit, last_visit, last_sync, loc, name, bday,anni, emails, phones, links, company, title, notes,total_contacts, total_count])
return results

Agora, usando CSV_Writer() método, vamos escrever o conteúdo em arquivo CSV -

def write_csv(data, output):
   print("[+] Writing {} contacts to {}".format(len(data), output))
   with open(output, "w", newline="") as csvfile:
      csv_writer = csv.writer(csvfile)
      csv_writer.writerow([
         "URL", "First Visit (UTC)", "Last Visit (UTC)",
         "Last Sync (UTC)", "Location", "Contact Name", "Bday",
         "Anniversary", "Emails", "Phones", "Links", "Company", "Title",
         "Notes", "Total Contacts", "Count of Contacts in Cache"])
      csv_writer.writerows(data)

Com a ajuda do script acima, podemos processar os dados em cache do correio do Yahoo usando o banco de dados IEF.

O capítulo anterior tratou de alguns dos conceitos de análise forense de redes usando Python. Neste capítulo, vamos entender a análise forense de rede usando Python em um nível mais profundo.

Preservação de página da web com sopa bonita

A World Wide Web (WWW) é um recurso único de informação. No entanto, seu legado está em alto risco devido à perda de conteúdo a uma taxa alarmante. Diversas instituições de patrimônio cultural e acadêmicas, organizações sem fins lucrativos e empresas privadas exploraram as questões envolvidas e contribuíram para o desenvolvimento de soluções técnicas para arquivamento na web.

A preservação de páginas da web ou arquivamento da web é o processo de coleta de dados da World Wide Web, garantindo que os dados sejam preservados em um arquivo e disponibilizando-os para futuros pesquisadores, historiadores e o público. Antes de prosseguir com a preservação da página da web, vamos discutir algumas questões importantes relacionadas à preservação da página da web conforme fornecido abaixo -

  • Change in Web Resources - Os recursos da web mudam todos os dias, o que é um desafio para a preservação da página da web.

  • Large Quantity of Resources - Outra questão relacionada à preservação de páginas web é a grande quantidade de recursos que deve ser preservada.

  • Integrity - As páginas da Web devem ser protegidas contra alterações, exclusões ou remoções não autorizadas para proteger sua integridade.

  • Dealing with multimedia data - Ao mesmo tempo em que preservamos as páginas da web, também precisamos lidar com dados multimídia, e isso pode causar problemas ao fazer isso.

  • Providing access - Além de preservar, a questão de fornecer acesso a recursos da web e lidar com questões de propriedade também precisa ser resolvida.

Neste capítulo, vamos usar a biblioteca Python chamada Beautiful Soup para preservação de página da web.

O que é a bela sopa?

Beautiful Soup é uma biblioteca Python para extrair dados de arquivos HTML e XML. Pode ser usado comurlibporque ele precisa de uma entrada (documento ou url) para criar um objeto sopa, já que ele não pode buscar a página da web por si só. Você pode aprender em detalhes sobre isso em www.crummy.com/software/BeautifulSoup/bs4/doc/

Observe que antes de usá-lo, devemos instalar uma biblioteca de terceiros usando o seguinte comando -

pip install bs4

Em seguida, usando o gerenciador de pacotes Anaconda, podemos instalar Beautiful Soup da seguinte forma -

conda install -c anaconda beautifulsoup4

Script Python para preservação de páginas da web

O script Python para preservar páginas da web usando uma biblioteca de terceiros chamada Beautiful Soup é discutido aqui -

Primeiro, importe as bibliotecas necessárias da seguinte forma -

from __future__ import print_function
import argparse

from bs4 import BeautifulSoup, SoupStrainer
from datetime import datetime

import hashlib
import logging
import os
import ssl
import sys
from urllib.request import urlopen

import urllib.error
logger = logging.getLogger(__name__)

Observe que este script terá dois argumentos posicionais, um é o URL que deve ser preservado e o outro é o diretório de saída desejado, conforme mostrado abaixo -

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Web Page preservation')
   parser.add_argument("DOMAIN", help="Website Domain")
   parser.add_argument("OUTPUT_DIR", help="Preservation Output Directory")
   parser.add_argument("-l", help="Log file path",
   default=__file__[:-3] + ".log")
   args = parser.parse_args()

Agora, configure o registro para o script especificando um arquivo e um manipulador de fluxo para estar em loop e documente o processo de aquisição como mostrado -

logger.setLevel(logging.DEBUG)
msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-10s""%(levelname)-8s %(message)s")
strhndl = logging.StreamHandler(sys.stderr)
strhndl.setFormatter(fmt=msg_fmt)
fhndl = logging.FileHandler(args.l, mode='a')
fhndl.setFormatter(fmt=msg_fmt)

logger.addHandler(strhndl)
logger.addHandler(fhndl)
logger.info("Starting BS Preservation")
logger.debug("Supplied arguments: {}".format(sys.argv[1:]))
logger.debug("System " + sys.platform)
logger.debug("Version " + sys.version)

Agora, vamos fazer a validação de entrada no diretório de saída desejado da seguinte maneira -

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)
main(args.DOMAIN, args.OUTPUT_DIR)

Agora, vamos definir o main() função que extrairá o nome de base do site, removendo os elementos desnecessários antes do nome real, juntamente com a validação adicional no URL de entrada da seguinte forma -

def main(website, output_dir):
   base_name = website.replace("https://", "").replace("http://", "").replace("www.", "")
   link_queue = set()
   
   if "http://" not in website and "https://" not in website:
      logger.error("Exiting preservation - invalid user input: {}".format(website))
      sys.exit(1)
   logger.info("Accessing {} webpage".format(website))
   context = ssl._create_unverified_context()

Agora, precisamos abrir uma conexão com a URL usando o método urlopen (). Vamos usar o bloco try-except da seguinte forma -

try:
   index = urlopen(website, context=context).read().decode("utf-8")
except urllib.error.HTTPError as e:
   logger.error("Exiting preservation - unable to access page: {}".format(website))
   sys.exit(2)
logger.debug("Successfully accessed {}".format(website))

As próximas linhas de código incluem três funções, conforme explicado abaixo -

  • write_output() para escrever a primeira página da web no diretório de saída

  • find_links() função para identificar os links nesta página da web

  • recurse_pages() função para iterar e descobrir todos os links na página da web.

write_output(website, index, output_dir)
link_queue = find_links(base_name, index, link_queue)
logger.info("Found {} initial links on webpage".format(len(link_queue)))
recurse_pages(website, link_queue, context, output_dir)
logger.info("Completed preservation of {}".format(website))

Agora, vamos definir write_output() método da seguinte forma -

def write_output(name, data, output_dir, counter=0):
   name = name.replace("http://", "").replace("https://", "").rstrip("//")
   directory = os.path.join(output_dir, os.path.dirname(name))
   
   if not os.path.exists(directory) and os.path.dirname(name) != "":
      os.makedirs(directory)

Precisamos registrar alguns detalhes sobre a página da web e, em seguida, registrar o hash dos dados usando hash_data() método da seguinte forma -

logger.debug("Writing {} to {}".format(name, output_dir)) logger.debug("Data Hash: {}".format(hash_data(data)))
path = os.path.join(output_dir, name)
path = path + "_" + str(counter)
with open(path, "w") as outfile:
   outfile.write(data)
logger.debug("Output File Hash: {}".format(hash_file(path)))

Agora, defina hash_data() método com a ajuda do qual lemos o UTF-8 dados codificados e, em seguida, gerar o SHA-256 hash da seguinte forma -

def hash_data(data):
   sha256 = hashlib.sha256()
   sha256.update(data.encode("utf-8"))
   return sha256.hexdigest()
def hash_file(file):
   sha256 = hashlib.sha256()
   with open(file, "rb") as in_file:
      sha256.update(in_file.read())
return sha256.hexdigest()

Agora, vamos criar um Beautifulsoup objeto fora dos dados da página da web em find_links() método da seguinte forma -

def find_links(website, page, queue):
   for link in BeautifulSoup(page, "html.parser",parse_only = SoupStrainer("a", href = True)):
      if website in link.get("href"):
         if not os.path.basename(link.get("href")).startswith("#"):
            queue.add(link.get("href"))
   return queue

Agora, precisamos definir recurse_pages() método, fornecendo a ele as entradas do URL do site, a fila de links atual, o contexto SSL não verificado e o diretório de saída da seguinte forma -

def recurse_pages(website, queue, context, output_dir):
   processed = []
   counter = 0
   
   while True:
      counter += 1
      if len(processed) == len(queue):
         break
      for link in queue.copy(): if link in processed:
         continue
	   processed.append(link)
      try:
      page = urlopen(link,      context=context).read().decode("utf-8")
      except urllib.error.HTTPError as e:
         msg = "Error accessing webpage: {}".format(link)
         logger.error(msg)
         continue

Agora, escreva a saída de cada página da web acessada em um arquivo passando o nome do link, os dados da página, o diretório de saída e o contador da seguinte forma -

write_output(link, page, output_dir, counter)
queue = find_links(website, page, queue)
logger.info("Identified {} links throughout website".format(
   len(queue)))

Agora, quando executarmos esse script fornecendo a URL do site, o diretório de saída e um caminho para o arquivo de log, obteremos os detalhes sobre a página da web que pode ser usada para uso futuro.

Caça ao Vírus

Você já se perguntou como analistas forenses, pesquisadores de segurança e respondentes de incidentes podem entender a diferença entre software útil e malware? A resposta está na própria pergunta, pois sem estudar sobre o malware, gerado rapidamente por hackers, é quase impossível para pesquisadores e especialistas distinguir entre software útil e malware. Nesta seção, vamos discutir sobreVirusShare, uma ferramenta para realizar essa tarefa.

Compreendendo o VirusShare

O VirusShare é a maior coleção privada de amostras de malware para fornecer aos pesquisadores de segurança, responsáveis ​​pela resposta a incidentes e analistas forenses as amostras de código malicioso ativo. Ele contém mais de 30 milhões de amostras.

O benefício do VirusShare é a lista de hashes de malware que está disponível gratuitamente. Qualquer pessoa pode usar esses hashes para criar um conjunto de hash muito abrangente e usá-lo para identificar arquivos potencialmente maliciosos. Mas antes de usar o VirusShare, sugerimos que você visitehttps://virusshare.com para mais detalhes.

Criação de lista de hash delimitada por nova linha a partir do VirusShare usando Python

Uma lista hash do VirusShare pode ser usada por várias ferramentas forenses, como X-way e EnCase. No script discutido abaixo, iremos automatizar o download de listas de hashes do VirusShare para criar uma lista de hashes delimitada por nova linha.

Para este script, precisamos de uma biblioteca Python de terceiros tqdm que pode ser baixado da seguinte forma -

pip install tqdm

Observe que, neste script, primeiro leremos a página de hashes do VirusShare e identificaremos dinamicamente a lista de hashes mais recente. Em seguida, inicializaremos a barra de progresso e baixaremos a lista de hash no intervalo desejado.

Primeiro, importe as seguintes bibliotecas -

from __future__ import print_function

import argparse
import os
import ssl
import sys
import tqdm

from urllib.request import urlopen
import urllib.error

Este script terá um argumento posicional, que seria o caminho desejado para o conjunto hash -

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Hash set from VirusShare')
   parser.add_argument("OUTPUT_HASH", help = "Output Hashset")
   parser.add_argument("--start", type = int, help = "Optional starting location")
   args = parser.parse_args()

Agora, vamos realizar a validação de entrada padrão da seguinte forma -

directory = os.path.dirname(args.OUTPUT_HASH)
if not os.path.exists(directory):
   os.makedirs(directory)
if args.start:
   main(args.OUTPUT_HASH, start=args.start)
else:
   main(args.OUTPUT_HASH)

Agora precisamos definir main() funcionar com **kwargs como um argumento porque isso criará um dicionário que podemos consultar para apoiar os argumentos-chave fornecidos, conforme mostrado abaixo -

def main(hashset, **kwargs):
   url = "https://virusshare.com/hashes.4n6"
   print("[+] Identifying hash set range from {}".format(url))
   context = ssl._create_unverified_context()

Agora, precisamos abrir a página de hashes do VirusShare usando urlib.request.urlopen()método. Usaremos o bloco try-except da seguinte forma -

try:
   index = urlopen(url, context = context).read().decode("utf-8")
except urllib.error.HTTPError as e:
   print("[-] Error accessing webpage - exiting..")
   sys.exit(1)

Agora, identifique a lista de hash mais recente das páginas baixadas. Você pode fazer isso encontrando a última instância do HTMLhreftag para a lista de hash do VirusShare. Isso pode ser feito com as seguintes linhas de código -

tag = index.rfind(r'a href = "hashes/VirusShare_')
stop = int(index[tag + 27: tag + 27 + 5].lstrip("0"))

if "start" not in kwa<rgs:
   start = 0
else:
   start = kwargs["start"]

if start < 0 or start > stop:
   print("[-] Supplied start argument must be greater than or equal ""to zero but less than the latest hash list, ""currently: {}".format(stop))
sys.exit(2)
print("[+] Creating a hashset from hash lists {} to {}".format(start, stop))
hashes_downloaded = 0

Agora, vamos usar tqdm.trange() método para criar um loop e uma barra de progresso da seguinte maneira -

for x in tqdm.trange(start, stop + 1, unit_scale=True,desc="Progress"):
   url_hash = "https://virusshare.com/hashes/VirusShare_"\"{}.md5".format(str(x).zfill(5))
   try:
      hashes = urlopen(url_hash, context=context).read().decode("utf-8")
      hashes_list = hashes.split("\n")
   except urllib.error.HTTPError as e:
      print("[-] Error accessing webpage for hash list {}"" - continuing..".format(x))
   continue

Depois de realizar as etapas acima com êxito, abriremos o arquivo de texto do conjunto hash no modo a + para anexar ao final do arquivo de texto.

with open(hashset, "a+") as hashfile:
   for line in hashes_list:
   if not line.startswith("#") and line != "":
      hashes_downloaded += 1
      hashfile.write(line + '\n')
   print("[+] Finished downloading {} hashes into {}".format(
      hashes_downloaded, hashset))

Depois de executar o script acima, você obterá a lista de hash mais recente contendo valores de hash MD5 em formato de texto.

Os capítulos anteriores discutiram sobre a importância e o processo da análise forense de redes e os conceitos envolvidos. Neste capítulo, vamos aprender sobre a função dos e-mails na perícia digital e sua investigação usando Python.

Papel do e-mail na investigação

Os emails desempenham um papel muito importante nas comunicações empresariais e têm surgido como uma das aplicações mais importantes da Internet. Eles são um modo conveniente para enviar mensagens e documentos, não apenas de computadores, mas também de outros dispositivos eletrônicos, como telefones celulares e tablets.

O lado negativo dos e-mails é que os criminosos podem vazar informações importantes sobre sua empresa. Conseqüentemente, o papel dos e-mails na análise forense digital aumentou nos últimos anos. Na perícia digital, os e-mails são considerados evidências cruciais e a análise do cabeçalho do e-mail se tornou importante para coletar evidências durante o processo forense.

Um investigador tem os seguintes objetivos ao realizar análises forenses de e-mail -

  • Para identificar o principal criminoso
  • Para coletar as evidências necessárias
  • Para apresentar as descobertas
  • Para construir o caso

Desafios em análise forense de e-mail

A análise forense de e-mail desempenha um papel muito importante na investigação, pois a maior parte da comunicação na era atual depende de e-mails. No entanto, um investigador forense por email pode enfrentar os seguintes desafios durante a investigação -

Emails falsos

O maior desafio na análise forense de e-mail é o uso de e-mails falsos que são criados pela manipulação e script de cabeçalhos etc. Nesta categoria, os criminosos também usam e-mail temporário, que é um serviço que permite que um usuário registrado receba e-mail em um endereço temporário que expira após um certo período de tempo.

Spoofing

Outro desafio na análise forense de email é o spoofing, no qual os criminosos costumavam apresentar um email como sendo de outra pessoa. Nesse caso, a máquina receberá endereços IP falsos e originais.

Reenvio de e-mail anônimo

Aqui, o servidor de e-mail remove as informações de identificação da mensagem de e-mail antes de encaminhá-la novamente. Isso leva a outro grande desafio para investigações de e-mail.

Técnicas usadas em investigação forense por email

A análise forense de email é o estudo da fonte e do conteúdo do email como evidência para identificar o remetente e o destinatário reais de uma mensagem, juntamente com algumas outras informações, como data / hora da transmissão e intenção do remetente. Envolve a investigação de metadados, varredura de portas, bem como pesquisa de palavras-chave.

Algumas das técnicas comuns que podem ser usadas para investigação forense de e-mail são

  • Análise de Cabeçalho
  • Investigação de servidor
  • Investigação de dispositivo de rede
  • Sender Mailer Fingerprints
  • Identificadores integrados de software

Nas seções a seguir, aprenderemos como buscar informações usando Python para fins de investigação de e-mail.

Extração de informações de arquivos EML

Os arquivos EML são basicamente emails em formato de arquivo amplamente usados ​​para armazenar mensagens de email. Eles são arquivos de texto estruturados que são compatíveis com vários clientes de e-mail, como Microsoft Outlook, Outlook Express e Windows Live Mail.

Um arquivo EML armazena cabeçalhos de e-mail, conteúdo do corpo e dados de anexos como texto simples. Ele usa base64 para codificar dados binários e codificação Quoted-Printable (QP) para armazenar informações de conteúdo. O script Python que pode ser usado para extrair informações do arquivo EML é fornecido abaixo -

Primeiro, importe as seguintes bibliotecas Python, conforme mostrado abaixo -

from __future__ import print_function
from argparse import ArgumentParser, FileType
from email import message_from_file

import os
import quopri
import base64

Nas bibliotecas acima, quoprié usado para decodificar os valores codificados QP dos arquivos EML. Quaisquer dados codificados em base64 podem ser decodificados com a ajuda debase64 biblioteca.

A seguir, vamos fornecer um argumento para o manipulador de linha de comando. Observe que aqui ele aceitará apenas um argumento que seria o caminho para o arquivo EML conforme mostrado abaixo -

if __name__ == '__main__':
   parser = ArgumentParser('Extracting information from EML file')
   parser.add_argument("EML_FILE",help="Path to EML File", type=FileType('r'))
   args = parser.parse_args()
   main(args.EML_FILE)

Agora, precisamos definir main() função na qual usaremos o método chamado message_from_file()da biblioteca de e-mail para ler o arquivo como objeto. Aqui, acessaremos os cabeçalhos, conteúdo do corpo, anexos e outras informações de carga útil usando a variável resultante chamadaemlfile conforme mostrado no código fornecido abaixo -

def main(input_file):
   emlfile = message_from_file(input_file)
   for key, value in emlfile._headers:
      print("{}: {}".format(key, value))
print("\nBody\n")

if emlfile.is_multipart():
   for part in emlfile.get_payload():
      process_payload(part)
else:
   process_payload(emlfile[1])

Agora, precisamos definir process_payload() método no qual extrairemos o conteúdo do corpo da mensagem usando get_payload()método. Vamos decodificar os dados codificados QP usandoquopri.decodestring()função. Também verificaremos o tipo de conteúdo MIME para que ele possa lidar com o armazenamento do e-mail adequadamente. Observe o código fornecido abaixo -

def process_payload(payload):
   print(payload.get_content_type() + "\n" + "=" * len(payload.get_content_type()))
   body = quopri.decodestring(payload.get_payload())
   
   if payload.get_charset():
      body = body.decode(payload.get_charset())
else:
   try:
      body = body.decode()
   except UnicodeDecodeError:
      body = body.decode('cp1252')

if payload.get_content_type() == "text/html":
   outfile = os.path.basename(args.EML_FILE.name) + ".html"
   open(outfile, 'w').write(body)
elif payload.get_content_type().startswith('application'):
   outfile = open(payload.get_filename(), 'wb')
   body = base64.b64decode(payload.get_payload())
   outfile.write(body)
   outfile.close()
   print("Exported: {}\n".format(outfile.name))
else:
   print(body)

Depois de executar o script acima, obteremos as informações do cabeçalho junto com várias cargas úteis no console.

Analisando arquivos MSG usando Python

As mensagens de e-mail vêm em muitos formatos diferentes. MSG é um tipo de formato usado pelo Microsoft Outlook e Exchange. Os arquivos com extensão MSG podem conter texto ASCII simples para os cabeçalhos e o corpo da mensagem principal, bem como hiperlinks e anexos.

Nesta seção, aprenderemos como extrair informações do arquivo MSG usando a API do Outlook. Observe que o seguinte script Python funcionará apenas no Windows. Para isso, precisamos instalar uma biblioteca Python de terceiros chamadapywin32 como segue -

pip install pywin32

Agora, importe as seguintes bibliotecas usando os comandos mostrados -

from __future__ import print_function
from argparse import ArgumentParser

import os
import win32com.client
import pywintypes

Agora, vamos fornecer um argumento para o manipulador de linha de comando. Aqui ele aceitará dois argumentos, um seria o caminho para o arquivo MSG e o outro seria a pasta de saída desejada da seguinte forma -

if __name__ == '__main__':
   parser = ArgumentParser(‘Extracting information from MSG file’)
   parser.add_argument("MSG_FILE", help="Path to MSG file")
   parser.add_argument("OUTPUT_DIR", help="Path to output folder")
   args = parser.parse_args()
   out_dir = args.OUTPUT_DIR
   
   if not os.path.exists(out_dir):
      os.makedirs(out_dir)
   main(args.MSG_FILE, args.OUTPUT_DIR)

Agora, precisamos definir main() função na qual chamaremos win32com biblioteca para configurar Outlook API que ainda permite o acesso ao MAPI namespace.

def main(msg_file, output_dir):
   mapi = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
   msg = mapi.OpenSharedItem(os.path.abspath(args.MSG_FILE))
   
   display_msg_attribs(msg)
   display_msg_recipients(msg)
   
   extract_msg_body(msg, output_dir)
   extract_attachments(msg, output_dir)

Agora, defina as diferentes funções que estamos usando neste script. O código fornecido abaixo mostra a definição dodisplay_msg_attribs() função que nos permite exibir vários atributos de uma mensagem, como assunto, para, BCC, CC, tamanho, nome do remetente, enviado, etc.

def display_msg_attribs(msg):
   attribs = [
      'Application', 'AutoForwarded', 'BCC', 'CC', 'Class',
      'ConversationID', 'ConversationTopic', 'CreationTime',
      'ExpiryTime', 'Importance', 'InternetCodePage', 'IsMarkedAsTask',
      'LastModificationTime', 'Links','ReceivedTime', 'ReminderSet',
      'ReminderTime', 'ReplyRecipientNames', 'Saved', 'Sender',
      'SenderEmailAddress', 'SenderEmailType', 'SenderName', 'Sent',
      'SentOn', 'SentOnBehalfOfName', 'Size', 'Subject',
      'TaskCompletedDate', 'TaskDueDate', 'To', 'UnRead'
   ]
   print("\nMessage Attributes")
   for entry in attribs:
      print("{}: {}".format(entry, getattr(msg, entry, 'N/A')))

Agora, defina o display_msg_recipeints() função que itera através das mensagens e exibe os detalhes do destinatário.

def display_msg_recipients(msg):
   recipient_attrib = ['Address', 'AutoResponse', 'Name', 'Resolved', 'Sendable']
   i = 1
   
   while True:
   try:
      recipient = msg.Recipients(i)
   except pywintypes.com_error:
      break
   print("\nRecipient {}".format(i))
   print("=" * 15)
   
   for entry in recipient_attrib:
      print("{}: {}".format(entry, getattr(recipient, entry, 'N/A')))
   i += 1

Em seguida, definimos extract_msg_body() função que extrai o conteúdo do corpo, HTML e texto simples, da mensagem.

def extract_msg_body(msg, out_dir):
   html_data = msg.HTMLBody.encode('cp1252')
   outfile = os.path.join(out_dir, os.path.basename(args.MSG_FILE))
   
   open(outfile + ".body.html", 'wb').write(html_data)
   print("Exported: {}".format(outfile + ".body.html"))
   body_data = msg.Body.encode('cp1252')
   
   open(outfile + ".body.txt", 'wb').write(body_data)
   print("Exported: {}".format(outfile + ".body.txt"))

A seguir, vamos definir o extract_attachments() função que exporta dados de anexo para o diretório de saída desejado.

def extract_attachments(msg, out_dir):
   attachment_attribs = ['DisplayName', 'FileName', 'PathName', 'Position', 'Size']
   i = 1 # Attachments start at 1
   
   while True:
      try:
         attachment = msg.Attachments(i)
   except pywintypes.com_error:
      break

Assim que todas as funções forem definidas, imprimiremos todos os atributos no console com a seguinte linha de códigos -

print("\nAttachment {}".format(i))
print("=" * 15)
   
for entry in attachment_attribs:
   print('{}: {}'.format(entry, getattr(attachment, entry,"N/A")))
outfile = os.path.join(os.path.abspath(out_dir),os.path.split(args.MSG_FILE)[-1])
   
if not os.path.exists(outfile):
os.makedirs(outfile)
outfile = os.path.join(outfile, attachment.FileName)
attachment.SaveAsFile(outfile)
   
print("Exported: {}".format(outfile))
i += 1

Depois de executar o script acima, obteremos os atributos da mensagem e seus anexos na janela do console junto com vários arquivos no diretório de saída.

Estruturação de arquivos MBOX do Google Takeout usando Python

Os arquivos MBOX são arquivos de texto com formatação especial que dividem as mensagens armazenadas. Eles geralmente são encontrados em associação com sistemas UNIX, Thunderbolt e Google Takeouts.

Nesta seção, você verá um script Python, onde estruturaremos os arquivos MBOX obtidos do Google Takeouts. Mas antes disso devemos saber como podemos gerar esses arquivos MBOX usando nossa conta do Google ou conta do Gmail.

Adquirindo a caixa de correio da conta do Google no formato MBX

A aquisição da caixa de correio da conta do Google implica em fazer backup de nossa conta do Gmail. O backup pode ser feito por vários motivos pessoais ou profissionais. Observe que o Google fornece backup de dados do Gmail. Para adquirir nossa caixa de correio da conta do Google no formato MBOX, você precisa seguir as etapas abaixo -

  • Abrir My account painel de controle.

  • Vá para a seção Informações pessoais e privacidade e selecione o link Controlar seu conteúdo.

  • Você pode criar um novo arquivo ou pode gerenciar um existente. Se clicarmos,CREATE ARCHIVE link, obteremos algumas caixas de seleção para cada produto do Google que desejamos incluir.

  • Depois de selecionar os produtos, teremos a liberdade de escolher o tipo de arquivo e o tamanho máximo do nosso arquivo, juntamente com o método de entrega para selecionar na lista.

  • Por fim, obteremos este backup no formato MBOX.

Código Python

Agora, o arquivo MBOX discutido acima pode ser estruturado usando Python como mostrado abaixo -

Primeiro, precisa importar bibliotecas Python da seguinte forma -

from __future__ import print_function
from argparse import ArgumentParser

import mailbox
import os
import time
import csv
from tqdm import tqdm

import base64

Todas as bibliotecas foram usadas e explicadas em scripts anteriores, exceto o mailbox biblioteca que é usada para analisar arquivos MBOX.

Agora, forneça um argumento para o manipulador de linha de comando. Aqui, ele aceitará dois argumentos - um seria o caminho para o arquivo MBOX e o outro seria a pasta de saída desejada.

if __name__ == '__main__':
   parser = ArgumentParser('Parsing MBOX files')
   parser.add_argument("MBOX", help="Path to mbox file")
   parser.add_argument(
      "OUTPUT_DIR",help = "Path to output directory to write report ""and exported content")
   args = parser.parse_args()
   main(args.MBOX, args.OUTPUT_DIR)

Agora, vai definir main() função e chamada mbox classe de biblioteca de caixa de correio com a ajuda da qual podemos analisar um arquivo MBOX, fornecendo seu caminho -

def main(mbox_file, output_dir):
   print("Reading mbox file")
   mbox = mailbox.mbox(mbox_file, factory=custom_reader)
   print("{} messages to parse".format(len(mbox)))

Agora, defina um método de leitor para mailbox biblioteca da seguinte forma -

def custom_reader(data_stream):
   data = data_stream.read()
   try:
      content = data.decode("ascii")
   except (UnicodeDecodeError, UnicodeEncodeError) as e:
      content = data.decode("cp1252", errors="replace")
   return mailbox.mboxMessage(content)

Agora, crie algumas variáveis ​​para processamento posterior, como segue -

parsed_data = []
attachments_dir = os.path.join(output_dir, "attachments")

if not os.path.exists(attachments_dir):
   os.makedirs(attachments_dir)
columns = [
   "Date", "From", "To", "Subject", "X-Gmail-Labels", "Return-Path", "Received", 
   "Content-Type", "Message-ID","X-GM-THRID", "num_attachments_exported", "export_path"]

Em seguida, use tqdm para gerar uma barra de progresso e acompanhar o processo de iteração da seguinte forma -

for message in tqdm(mbox):
   msg_data = dict()
   header_data = dict(message._headers)
for hdr in columns:
   msg_data[hdr] = header_data.get(hdr, "N/A")

Agora, verifique se a mensagem meteorológica está tendo cargas úteis ou não. Se estiver havendo então vamos definirwrite_payload() método da seguinte forma -

if len(message.get_payload()):
   export_path = write_payload(message, attachments_dir)
   msg_data['num_attachments_exported'] = len(export_path)
   msg_data['export_path'] = ", ".join(export_path)

Agora, os dados precisam ser anexados. Então vamos ligarcreate_report() método da seguinte forma -

parsed_data.append(msg_data)
create_report(
   parsed_data, os.path.join(output_dir, "mbox_report.csv"), columns)
def write_payload(msg, out_dir):
   pyld = msg.get_payload()
   export_path = []
   
if msg.is_multipart():
   for entry in pyld:
      export_path += write_payload(entry, out_dir)
else:
   content_type = msg.get_content_type()
   if "application/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "image/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))

   elif "video/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "audio/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "text/csv" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "info/" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   elif "text/calendar" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   elif "text/rtf" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   else:
      if "name=" in msg.get('Content-Disposition', "N/A"):
         content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "name=" in msg.get('Content-Type', "N/A"):
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
return export_path

Observe que as instruções if-else acima são fáceis de entender. Agora, precisamos definir um método que irá extrair o nome do arquivo domsg objeto da seguinte forma -

def export_content(msg, out_dir, content_data):
   file_name = get_filename(msg)
   file_ext = "FILE"
   
   if "." in file_name: file_ext = file_name.rsplit(".", 1)[-1]
   file_name = "{}_{:.4f}.{}".format(file_name.rsplit(".", 1)[0], time.time(), file_ext)
   file_name = os.path.join(out_dir, file_name)

Agora, com a ajuda das seguintes linhas de código, você pode realmente exportar o arquivo -

if isinstance(content_data, str):
   open(file_name, 'w').write(content_data)
else:
   open(file_name, 'wb').write(content_data)
return file_name

Agora, vamos definir uma função para extrair nomes de arquivos do message para representar com precisão os nomes desses arquivos da seguinte forma -

def get_filename(msg):
   if 'name=' in msg.get("Content-Disposition", "N/A"):
      fname_data = msg["Content-Disposition"].replace("\r\n", " ")
      fname = [x for x in fname_data.split("; ") if 'name=' in x]
      file_name = fname[0].split("=", 1)[-1]
   elif 'name=' in msg.get("Content-Type", "N/A"):
      fname_data = msg["Content-Type"].replace("\r\n", " ")
      fname = [x for x in fname_data.split("; ") if 'name=' in x]
      file_name = fname[0].split("=", 1)[-1]
   else:
      file_name = "NO_FILENAME"
   fchars = [x for x in file_name if x.isalnum() or x.isspace() or x == "."]
   return "".join(fchars)

Agora, podemos escrever um arquivo CSV definindo o create_report() funcionar da seguinte forma -

def create_report(output_data, output_file, columns):
   with open(output_file, 'w', newline="") as outfile:
      csvfile = csv.DictWriter(outfile, columns)
      csvfile.writeheader()
      csvfile.writerows(output_data)

Depois de executar o script fornecido acima, obteremos o relatório CSV e o diretório cheio de anexos.

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 aos seguintes motivos -

  • Cerca de 90% do tráfego mundial 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 conduzir a investigação para á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 foram 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 examinar o $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 irá lidar com todo o processamento. Ele irá 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, 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 descompactar 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 da seguinte forma -

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)

A seguir, 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.

  • Então, 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 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')

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.

Este capítulo fala sobre alguns artefatos mais importantes no Windows e seu método de extração usando Python.

Atividades do usuário

Tendo Windows NTUSER.DATarquivo para armazenar várias atividades do usuário. Cada perfil de usuário tem uma colmeiaNTUSER.DAT, que armazena as informações e configurações relacionadas especificamente a esse usuário. Portanto, é altamente útil para fins de investigação por analistas forenses.

O seguinte script Python irá analisar algumas das chaves de NTUSER.DATpara explorar as ações de um usuário no sistema. Antes de prosseguir, para o script Python, precisamos instalar módulos de terceiros, a saberRegistry, pytsk3, pyewf e Jinja2. Podemos usar pip para instalá-los.

Podemos seguir as seguintes etapas para extrair informações de NTUSER.DAT arquivo -

  • Primeiro, pesquise por todos NTUSER.DAT arquivos no sistema.

  • Em seguida, analise o WordWheelQuery, TypePath and RunMRU chave para cada NTUSER.DAT Arquivo.

  • Por fim, escreveremos esses artefatos, já processados, em um relatório HTML usando Jinja2 fmodule.

Código Python

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

Em primeiro lugar, precisamos importar os seguintes módulos 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

Agora, forneça o argumento para o manipulador de linha de comando. 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 HTML, conforme mostrado abaixo -

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)

Agora, vamos definir main() função para pesquisar todos NTUSER.DAT arquivos, como mostrado -

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

Agora, vamos tentar encontrar a chave em NTUSER.DAT arquivo e depois de encontrá-lo, defina as funções de processamento do usuário conforme mostrado abaixo -

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

Agora, passe o objeto de dicionário e seu caminho para write_html() método da seguinte forma -

write_html(report, nt_rec)

Agora, defina um método, que leva pytsk identificador de arquivo e lê-lo na classe Registry através do StringIO classe.

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, vamos definir a função que irá analisar e tratar WordWheelQuery chave de NTUSER.DAT arquivo da seguinte forma -

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

Agora, vamos definir a função que irá analisar e tratar TypedPaths chave de NTUSER.DAT arquivo da seguinte forma -

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

Agora, vamos definir a função que irá analisar e tratar RunMRU chave de NTUSER.DAT arquivo da seguinte forma -

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

Agora, a seguinte função tratará da criação do relatório 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)

Por fim, podemos escrever um documento HTML para relatório. Após executar o script acima, obteremos as informações do arquivo NTUSER.DAT em formato de documento HTML.

Arquivos LINK

Os arquivos de atalho são criados quando um usuário ou o sistema operacional cria arquivos de atalho para os arquivos usados ​​com frequência, clicados duas vezes ou acessados ​​a partir de unidades do sistema, como armazenamento anexado. Esses tipos de arquivos de atalho são chamados de arquivos de link. Ao acessar esses arquivos de link, um investigador pode encontrar a atividade da janela, como a hora e o local de onde esses arquivos foram acessados.

Vamos discutir o script Python que podemos usar para obter as informações desses arquivos LINK do Windows.

Para o script Python, instale módulos de terceiros, nomeadamente pylnk, pytsk3, pyewf. Podemos seguir as seguintes etapas para extrair informações delnk arquivos

  • Primeiro, pesquise lnk arquivos dentro do sistema.

  • Em seguida, extraia as informações desse arquivo iterando por meio deles.

  • Agora, finalmente, precisamos dessas informações para um relatório CSV.

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 csv
import StringIO

from utility.pytskutil import TSKUtil
import pylnk

Agora, forneça o argumento para o manipulador de linha de comando. 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('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)

Agora, interprete o arquivo de evidência criando um objeto de TSKUtil e iterar através do sistema de arquivos para encontrar arquivos que terminem com lnk. Isso pode ser feito definindomain() funcionar da seguinte forma -

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

Agora, com a ajuda do código a seguir, iremos iterar através lnk arquivos criando uma função da seguinte maneira -

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)

Agora precisamos definir duas funções, uma abrirá o pytsk objeto de arquivo e outro serão usados ​​para escrever o relatório CSV conforme mostrado abaixo -

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)

Depois de executar o script acima, obteremos as informações do lnk arquivos em um relatório CSV -

Pré-busca de arquivos

Sempre que um aplicativo é executado pela primeira vez em um local específico, o Windows cria prefetch files. Eles são usados ​​para acelerar o processo de inicialização do aplicativo. A extensão para esses arquivos é.PF e estes são armazenados no ”\Root\Windows\Prefetch” pasta.

Os especialistas forenses digitais podem revelar a evidência da execução do programa a partir de um local especificado, juntamente com os detalhes do usuário. Arquivos de pré-busca são artefatos úteis para o examinador porque sua entrada permanece mesmo após o programa ter sido excluído ou desinstalado.

Vamos discutir o script Python que buscará informações dos arquivos de pré-busca do Windows conforme fornecido abaixo -

Para o script Python, instale módulos de terceiros, nomeadamente pylnk, pytsk3 e unicodecsv. Lembre-se de que já trabalhamos com essas bibliotecas nos scripts Python que discutimos nos capítulos anteriores.

Temos que seguir as etapas fornecidas abaixo para extrair informações de prefetch arquivos -

  • Primeiro, procure .pf arquivos de extensão ou os arquivos de pré-busca.

  • Agora, execute a verificação da assinatura para eliminar falsos positivos.

  • Em seguida, analise o formato de arquivo de pré-busca do Windows. Isso difere da versão do Windows. Por exemplo, para Windows XP é 17, para Windows Vista e Windows 7 é 23, 26 para Windows 8.1 e 30 para Windows 10.

  • Por fim, escreveremos o resultado analisado em um arquivo CSV.

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

Agora, forneça um argumento para o manipulador de linha de comando. Aqui, ele aceitará dois argumentos, o primeiro seria o caminho para o arquivo de evidência e o segundo seria o tipo de arquivo de evidência. Ele também aceita um argumento opcional para especificar o caminho para verificar os arquivos de pré-busca -

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)

Agora, interprete o arquivo de evidência criando um objeto de TSKUtil e iterar através do sistema de arquivos para encontrar arquivos que terminem com .pf. Isso pode ser feito definindomain() funcionar da seguinte forma -

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)

Agora, defina um método que fará a validação das assinaturas conforme mostrado abaixo -

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)

Agora, comece a processar os arquivos de pré-busca do Windows. Aqui estamos tomando o exemplo de arquivos de pré-busca do 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)

Agora, extraia os dados incorporados aos arquivos pré-buscados usando struct da seguinte maneira -

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 ]

Como fornecemos a versão de pré-busca para o Windows XP, mas e se ele encontrar versões de pré-busca para outros Windows. Em seguida, ele deve exibir uma mensagem de erro da seguinte forma -

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)

Agora, defina o método para escrever o resultado no relatório CSV da seguinte maneira -

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)

Após executar o script acima, obteremos as informações dos arquivos de pré-busca da versão do Windows XP em uma planilha.

Este capítulo explicará sobre outros artefatos que um investigador pode obter durante a análise forense no Windows.

Logs de eventos

Os arquivos de log de eventos do Windows, como sugestões de nomes, são arquivos especiais que armazenam eventos significativos, como quando o usuário faz logon no computador, quando o programa encontra um erro, sobre alterações do sistema, acesso RDP, eventos específicos do aplicativo, etc. Investigadores cibernéticos estão sempre interessados ​​no evento informações de registro porque fornece muitas informações históricas úteis sobre o acesso do sistema. No script Python a seguir, processaremos os formatos de log de eventos do Windows herdados e atuais.

Para o script Python, precisamos instalar módulos de terceiros, a saber pytsk3, pyewf, unicodecsv, pyevt and pyevtx. Podemos seguir as etapas abaixo para extrair informações dos logs de eventos -

  • Primeiro, pesquise todos os logs de eventos que correspondem ao argumento de entrada.

  • Em seguida, execute a verificação da assinatura do arquivo.

  • Agora, processe cada log de eventos encontrado com a biblioteca apropriada.

  • Por último, escreva a saída na planilha.

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
import argparse
import unicodecsv as csv
import os
import pytsk3
import pyewf
import pyevt
import pyevtx
import sys
from utility.pytskutil import TSKUtil

Agora, forneça os argumentos 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 nome do log de eventos a ser processado.

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Information from Event Logs')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("TYPE", help = "Type of Evidence",choices = ("raw", "ewf"))
   parser.add_argument(
      "LOG_NAME",help = "Event Log Name (SecEvent.Evt, SysEvent.Evt, ""etc.)")
   
   parser.add_argument(
      "-d", help = "Event log directory to scan",default = "/WINDOWS/SYSTEM32/WINEVT")
   
   parser.add_argument(
      "-f", help = "Enable fuzzy search for either evt or"" evtx extension", action = "store_true")
   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.LOG_NAME, args.d, args.f)
   else:
      print("[-] Supplied input file {} does not exist or is not a ""file".format(args.EVIDENCE_FILE))
   sys.exit(1)

Agora, interaja com os logs de eventos para consultar a existência do caminho fornecido pelo usuário, criando nosso TSKUtilobjeto. Isso pode ser feito com a ajuda demain() método da seguinte forma -

def main(evidence, image_type, log, win_event, fuzzy):
   tsk_util = TSKUtil(evidence, image_type)
   event_dir = tsk_util.query_directory(win_event)
   
   if event_dir is not None:
      if fuzzy is True:
         event_log = tsk_util.recurse_files(log, path=win_event)
   else:
      event_log = tsk_util.recurse_files(log, path=win_event, logic="equal")
   
   if event_log is not None:
      event_data = []
      for hit in event_log:
         event_file = hit[2]
         temp_evt = write_file(event_file)

Agora, precisamos realizar a verificação da assinatura seguida pela definição de um método que gravará todo o conteúdo no diretório atual -

def write_file(event_file):
   with open(event_file.info.name.name, "w") as outfile:
      outfile.write(event_file.read_random(0, event_file.info.meta.size))
   return event_file.info.name.name
      if pyevt.check_file_signature(temp_evt):
         evt_log = pyevt.open(temp_evt)
         print("[+] Identified {} records in {}".format(
            evt_log.number_of_records, temp_evt))
         
         for i, record in enumerate(evt_log.records):
            strings = ""
            for s in record.strings:
               if s is not None:
                  strings += s + "\n"
            event_data.append([
               i, hit[0], record.computer_name,
               record.user_security_identifier,
               record.creation_time, record.written_time,
               record.event_category, record.source_name,
               record.event_identifier, record.event_type,
               strings, "",
               os.path.join(win_event, hit[1].lstrip("//"))
            ])
      elif pyevtx.check_file_signature(temp_evt):
         evtx_log = pyevtx.open(temp_evt)
         print("[+] Identified {} records in {}".format(
            evtx_log.number_of_records, temp_evt))
         for i, record in enumerate(evtx_log.records):
            strings = ""
            for s in record.strings:
			   if s is not None:
               strings += s + "\n"
         event_data.append([
            i, hit[0], record.computer_name,
            record.user_security_identifier, "",
            record.written_time, record.event_level,
            record.source_name, record.event_identifier,
            "", strings, record.xml_string,
            os.path.join(win_event, hit[1].lstrip("//"))
      ])
      else:
         print("[-] {} not a valid event log. Removing temp" file...".format(temp_evt))
         os.remove(temp_evt)
      continue
      write_output(event_data)
   else:
      print("[-] {} Event log not found in {} directory".format(log, win_event))
      sys.exit(3)
else:
   print("[-] Win XP Event Log Directory {} not found".format(win_event))
   sys.exit(2

Por último, defina um método para gravar a saída em uma planilha da seguinte maneira -

def write_output(data):
   output_name = "parsed_event_logs.csv"
   print("[+] Writing {} to current working directory: {}".format(
      output_name, os.getcwd()))
   
   with open(output_name, "wb") as outfile:
      writer = csv.writer(outfile)
      writer.writerow([
         "Index", "File name", "Computer Name", "SID",
         "Event Create Date", "Event Written Date",
         "Event Category/Level", "Event Source", "Event ID",
         "Event Type", "Data", "XML Data", "File Path"
      ])
      writer.writerows(data)

Depois de executar com sucesso o script acima, obteremos as informações de log de eventos na planilha.

História da Internet

O histórico da Internet é muito útil para analistas forenses; já que a maioria dos crimes cibernéticos ocorre apenas na Internet. Vamos ver como extrair o histórico da Internet do Internet Explorer, conforme discutimos sobre a análise forense do Windows, e o Internet Explorer vem por padrão com o Windows.

No Internet Explorer, o histórico da Internet é salvo em index.datArquivo. Vejamos um script Python, que irá extrair as informações deindex.dat Arquivo.

Podemos seguir as etapas abaixo para extrair informações de index.dat arquivos -

  • Primeiro, pesquise index.dat arquivos dentro do sistema.

  • Em seguida, extraia as informações desse arquivo iterando por meio deles.

  • Agora, escreva todas essas informações em um relatório CSV.

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
import argparse

from datetime import datetime, timedelta
import os
import pytsk3
import pyewf
import pymsiecf
import sys
import unicodecsv as csv

from utility.pytskutil import TSKUtil

Agora, forneça argumentos para o manipulador de linha de comando. Observe que aqui ele aceitará dois argumentos - o primeiro seria o caminho para o arquivo de evidência e o segundo seria o tipo de arquivo de evidência -

if __name__ == "__main__":
parser = argparse.ArgumentParser('getting information from internet history')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("TYPE", help = "Type of Evidence",choices = ("raw", "ewf"))
   parser.add_argument("-d", help = "Index.dat directory to scan",default = "/USERS")
   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.d)
   else:
      print("[-] Supplied input file {} does not exist or is not a ""file".format(args.EVIDENCE_FILE))
      sys.exit(1)

Agora, interprete o arquivo de evidência criando um objeto de TSKUtile percorrer o sistema de arquivos para localizar arquivos index.dat. Isso pode ser feito definindo omain() funcionar da seguinte forma -

def main(evidence, image_type, path):
   tsk_util = TSKUtil(evidence, image_type)
   index_dir = tsk_util.query_directory(path)
   
   if index_dir is not None:
      index_files = tsk_util.recurse_files("index.dat", path = path,logic = "equal")
      
      if index_files is not None:
         print("[+] Identified {} potential index.dat files".format(len(index_files)))
         index_data = []
         
         for hit in index_files:
            index_file = hit[2]
            temp_index = write_file(index_file)

Agora, defina uma função com a ajuda da qual podemos copiar as informações do arquivo index.dat para o diretório de trabalho atual e, posteriormente, eles podem ser processados ​​por um módulo de terceiros -

def write_file(index_file):
   with open(index_file.info.name.name, "w") as outfile:
   outfile.write(index_file.read_random(0, index_file.info.meta.size))
return index_file.info.name.name

Agora, use o seguinte código para executar a validação da assinatura com a ajuda da função integrada, a saber check_file_signature() -

if pymsiecf.check_file_signature(temp_index):
   index_dat = pymsiecf.open(temp_index)
   print("[+] Identified {} records in {}".format(
   index_dat.number_of_items, temp_index))

   for i, record in enumerate(index_dat.items):
   try:
      data = record.data
   if data is not None:
      data = data.rstrip("\x00")
   except AttributeError:
   
   if isinstance(record, pymsiecf.redirected):
      index_data.append([
         i, temp_index, "", "", "", "", "",record.location, "", "", record.offset,os.path.join(path, hit[1].lstrip("//"))])
   
   elif isinstance(record, pymsiecf.leak):
      index_data.append([
         i, temp_index, record.filename, "","", "", "", "", "", "", record.offset,os.path.join(path, hit[1].lstrip("//"))])
   continue
   
   index_data.append([
      i, temp_index, record.filename,
      record.type, record.primary_time,
      record.secondary_time,
      record.last_checked_time, record.location,
      record.number_of_hits, data, record.offset,
      os.path.join(path, hit[1].lstrip("//"))
   ])
   else:
      print("[-] {} not a valid index.dat file. Removing "
      "temp file..".format(temp_index))
      os.remove("index.dat")
      continue
      os.remove("index.dat")
      write_output(index_data)
   else:
      print("[-] Index.dat files not found in {} directory".format(path))
   sys.exit(3)
   else:
      print("[-] Directory {} not found".format(win_event))
   sys.exit(2)

Agora, defina um método que irá imprimir a saída em arquivo CSV, conforme mostrado abaixo -

def write_output(data):
   output_name = "Internet_Indexdat_Summary_Report.csv"
   print("[+] Writing {} with {} parsed index.dat files to current "
   "working directory: {}".format(output_name, len(data),os.getcwd()))
   
   with open(output_name, "wb") as outfile:
      writer = csv.writer(outfile)
      writer.writerow(["Index", "File Name", "Record Name",
      "Record Type", "Primary Date", "Secondary Date",
      "Last Checked Date", "Location", "No. of Hits",
      "Record Data", "Record Offset", "File Path"])
      writer.writerows(data)

Após executar o script acima, obteremos as informações do arquivo index.dat no arquivo CSV.

Cópias de sombra de volume

Uma cópia de sombra é a tecnologia incluída no Windows para fazer cópias de backup ou instantâneos de arquivos de computador manualmente ou automaticamente. Também é chamado de serviço de instantâneo de volume ou serviço de sombra de volume (VSS).

Com a ajuda desses arquivos VSS, os especialistas forenses podem obter algumas informações históricas sobre como o sistema mudou ao longo do tempo e quais arquivos existiam no computador. A tecnologia de cópia de sombra requer que o sistema de arquivos seja NTFS para criar e armazenar cópias de sombra.

Nesta seção, veremos um script Python, que ajuda a acessar qualquer volume de cópias de sombra presentes na imagem forense.

Para o script Python, precisamos instalar módulos de terceiros, a saber pytsk3, pyewf, unicodecsv, pyvshadow e vss. Podemos seguir as etapas abaixo para extrair informações dos arquivos VSS

  • Primeiro, acesse o volume da imagem bruta e identifique todas as partições NTFS.

  • Em seguida, extraia as informações dessas cópias de sombra iterando por meio delas.

  • Agora, finalmente, precisamos criar uma lista de arquivos de dados dentro dos instantâneos.

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
import argparse
from datetime import datetime, timedelta

import os
import pytsk3
import pyewf
import pyvshadow
import sys
import unicodecsv as csv

from utility import vss
from utility.pytskutil import TSKUtil
from utility import pytskutil

Agora, forneça argumentos para o manipulador de linha de comando. Aqui, ele aceitará dois argumentos - o primeiro é o caminho para o arquivo de evidência e o segundo é o arquivo de saída.

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Parsing Shadow Copies')
   parser.add_argument("EVIDENCE_FILE", help = "Evidence file path")
   parser.add_argument("OUTPUT_CSV", help = "Output CSV with VSS file listing")
   args = parser.parse_args()

Agora, valide a existência do caminho do arquivo de entrada e também separe o diretório do arquivo de saída.

directory = os.path.dirname(args.OUTPUT_CSV)
if not os.path.exists(directory) and directory != "":
   os.makedirs(directory)
if os.path.exists(args.EVIDENCE_FILE) and \ os.path.isfile(args.EVIDENCE_FILE):
   main(args.EVIDENCE_FILE, args.OUTPUT_CSV)
else:
   print("[-] Supplied input file {} does not exist or is not a "
   "file".format(args.EVIDENCE_FILE))
   
   sys.exit(1)

Agora, interaja com o volume do arquivo de evidência criando o TSKUtilobjeto. Isso pode ser feito com a ajuda demain() método da seguinte forma -

def main(evidence, output):
   tsk_util = TSKUtil(evidence, "raw")
   img_vol = tsk_util.return_vol()

if img_vol is not None:
   for part in img_vol:
      if tsk_util.detect_ntfs(img_vol, part):
         print("Exploring NTFS Partition for VSS")
         explore_vss(evidence, part.start * img_vol.info.block_size,output)
      else:
         print("[-] Must be a physical preservation to be compatible ""with this script")
         sys.exit(2)

Agora, defina um método para explorar o arquivo de sombra de volume analisado da seguinte maneira -

def explore_vss(evidence, part_offset, output):
   vss_volume = pyvshadow.volume()
   vss_handle = vss.VShadowVolume(evidence, part_offset)
   vss_count = vss.GetVssStoreCount(evidence, part_offset)
   
   if vss_count > 0:
      vss_volume.open_file_object(vss_handle)
      vss_data = []
      
      for x in range(vss_count):
         print("Gathering data for VSC {} of {}".format(x, vss_count))
         vss_store = vss_volume.get_store(x)
         image = vss.VShadowImgInfo(vss_store)
         vss_data.append(pytskutil.openVSSFS(image, x))
write_csv(vss_data, output)

Por último, defina o método para escrever o resultado na planilha da seguinte maneira -

def write_csv(data, output):
   if data == []:
      print("[-] No output results to write")
      sys.exit(3)
   print("[+] Writing output to {}".format(output))
   if os.path.exists(output):
      append = True
with open(output, "ab") as csvfile:
      csv_writer = csv.writer(csvfile)
      headers = ["VSS", "File", "File Ext", "File Type", "Create Date",
         "Modify Date", "Change Date", "Size", "File Path"]
      if not append:
         csv_writer.writerow(headers)
      for result_list in data:
         csv_writer.writerows(result_list)

Depois de executar com êxito este script Python, obteremos as informações residentes no VSS em uma planilha.

Até agora, vimos como obter artefatos no Windows usando Python. Neste capítulo, vamos aprender sobre a investigação de artefatos baseados em log usando Python.

Introdução

Os artefatos baseados em log são o tesouro de informações que pode ser muito útil para um especialista forense digital. Embora tenhamos vários softwares de monitoramento para coletar as informações, o principal problema para analisar informações úteis deles é que precisamos de muitos dados.

Vários artefatos baseados em log e investigação em Python

Nesta seção, vamos discutir vários artefatos baseados em log e sua investigação em Python -

Timestamps

O carimbo de data / hora transmite os dados e a hora da atividade no log. É um dos elementos importantes de qualquer arquivo de log. Observe que esses dados e valores de tempo podem vir em vários formatos.

O script Python mostrado a seguir tomará a data e hora bruta como entrada e fornecerá um carimbo de data / hora formatado como sua saída.

Para este script, precisamos seguir as seguintes etapas -

  • Primeiro, configure os argumentos que receberão o valor dos dados brutos junto com a fonte de dados e o tipo de dados.

  • Agora, forneça uma classe para fornecer interface comum para dados em diferentes formatos de data.

Código Python

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

Primeiro, importe os seguintes módulos Python -

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from datetime import datetime as dt
from datetime import timedelta

Agora, como de costume, precisamos fornecer um argumento para o manipulador de linha de comando. Aqui, ele aceitará três argumentos, o primeiro seria o valor da data a ser processado, o segundo seria a fonte desse valor de data e o terceiro seria seu tipo -

if __name__ == '__main__':
   parser = ArgumentParser('Timestamp Log-based artifact')
   parser.add_argument("date_value", help="Raw date value to parse")
   parser.add_argument(
      "source", help = "Source format of date",choices = ParseDate.get_supported_formats())
   parser.add_argument(
      "type", help = "Data type of input value",choices = ('number', 'hex'), default = 'int')
   
   args = parser.parse_args()
   date_parser = ParseDate(args.date_value, args.source, args.type)
   date_parser.run()
   print(date_parser.timestamp)

Agora, precisamos definir uma classe que aceitará os argumentos para valor de data, fonte de data e o tipo de valor -

class ParseDate(object):
   def __init__(self, date_value, source, data_type):
      self.date_value = date_value
      self.source = source
      self.data_type = data_type
      self.timestamp = None

Agora vamos definir um método que agirá como um controlador, assim como o método main () -

def run(self):
   if self.source == 'unix-epoch':
      self.parse_unix_epoch()
   elif self.source == 'unix-epoch-ms':
      self.parse_unix_epoch(True)
   elif self.source == 'windows-filetime':
      self.parse_windows_filetime()
@classmethod
def get_supported_formats(cls):
   return ['unix-epoch', 'unix-epoch-ms', 'windows-filetime']

Agora, precisamos definir dois métodos que irão processar o tempo de época do Unix e o FILETIME respectivamente -

def parse_unix_epoch(self, milliseconds=False):
   if self.data_type == 'hex':
      conv_value = int(self.date_value)
      if milliseconds:
         conv_value = conv_value / 1000.0
   elif self.data_type == 'number':
      conv_value = float(self.date_value)
      if milliseconds:
         conv_value = conv_value / 1000.0
   else:
      print("Unsupported data type '{}' provided".format(self.data_type))
      sys.exit('1')
   ts = dt.fromtimestamp(conv_value)
   self.timestamp = ts.strftime('%Y-%m-%d %H:%M:%S.%f')
def parse_windows_filetime(self):
   if self.data_type == 'hex':
      microseconds = int(self.date_value, 16) / 10.0
   elif self.data_type == 'number':
      microseconds = float(self.date_value) / 10
   else:
      print("Unsupported data type '{}'   provided".format(self.data_type))
      sys.exit('1')
   ts = dt(1601, 1, 1) + timedelta(microseconds=microseconds)
   self.timestamp = ts.strftime('%Y-%m-%d %H:%M:%S.%f')

Depois de executar o script acima, fornecendo um carimbo de data / hora, podemos obter o valor convertido em um formato fácil de ler.

Logs do servidor web

Do ponto de vista do especialista forense digital, os logs do servidor da web são outro artefato importante porque podem obter estatísticas úteis do usuário, juntamente com informações sobre o usuário e localizações geográficas. A seguir está o script Python que irá criar uma planilha, após processar os logs do servidor web, para fácil análise das informações.

Em primeiro lugar, precisamos importar os seguintes módulos Python -

from __future__ import print_function
from argparse import ArgumentParser, FileType

import re
import shlex
import logging
import sys
import csv

logger = logging.getLogger(__file__)

Agora, precisamos definir os padrões que serão analisados ​​a partir dos logs -

iis_log_format = [
   ("date", re.compile(r"\d{4}-\d{2}-\d{2}")),
   ("time", re.compile(r"\d\d:\d\d:\d\d")),
   ("s-ip", re.compile(
      r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}")),
   ("cs-method", re.compile(
      r"(GET)|(POST)|(PUT)|(DELETE)|(OPTIONS)|(HEAD)|(CONNECT)")),
   ("cs-uri-stem", re.compile(r"([A-Za-z0-1/\.-]*)")),
   ("cs-uri-query", re.compile(r"([A-Za-z0-1/\.-]*)")),
   ("s-port", re.compile(r"\d*")),
   ("cs-username", re.compile(r"([A-Za-z0-1/\.-]*)")),
   ("c-ip", re.compile(
      r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}")),
   ("cs(User-Agent)", re.compile(r".*")),
   ("sc-status", re.compile(r"\d*")),
   ("sc-substatus", re.compile(r"\d*")),
   ("sc-win32-status", re.compile(r"\d*")),
   ("time-taken", re.compile(r"\d*"))]

Agora, forneça um argumento para o manipulador de linha de comando. Aqui, ele aceitará dois argumentos, o primeiro seria o log do IIS a ser processado, o segundo seria o caminho do arquivo CSV desejado.

if __name__ == '__main__':
   parser = ArgumentParser('Parsing Server Based Logs')
   parser.add_argument('iis_log', help = "Path to IIS Log",type = FileType('r'))
   parser.add_argument('csv_report', help = "Path to CSV report")
   parser.add_argument('-l', help = "Path to processing log",default=__name__ + '.log')
   args = parser.parse_args()
   logger.setLevel(logging.DEBUG)
   msg_fmt = logging.Formatter(
      "%(asctime)-15s %(funcName)-10s ""%(levelname)-8s %(message)s")
   
   strhndl = logging.StreamHandler(sys.stdout)
   strhndl.setFormatter(fmt = msg_fmt)
   fhndl = logging.FileHandler(args.log, mode = 'a')
   fhndl.setFormatter(fmt = msg_fmt)
   
   logger.addHandler(strhndl)
   logger.addHandler(fhndl)
   logger.info("Starting IIS Parsing ")
   logger.debug("Supplied arguments: {}".format(", ".join(sys.argv[1:])))
   logger.debug("System " + sys.platform)
   logger.debug("Version " + sys.version)
   main(args.iis_log, args.csv_report, logger)
   iologger.info("IIS Parsing Complete")

Agora precisamos definir o método main () que irá lidar com o script para informações de log em massa -

def main(iis_log, report_file, logger):
   parsed_logs = []

for raw_line in iis_log:
   line = raw_line.strip()
   log_entry = {}

if line.startswith("#") or len(line) == 0:
   continue

if '\"' in line:
   line_iter = shlex.shlex(line_iter)
else:
   line_iter = line.split(" ")
   for count, split_entry in enumerate(line_iter):
      col_name, col_pattern = iis_log_format[count]

      if col_pattern.match(split_entry):
         log_entry[col_name] = split_entry
else:
   logger.error("Unknown column pattern discovered. "
      "Line preserved in full below")
      logger.error("Unparsed Line: {}".format(line))
      parsed_logs.append(log_entry)
      
      logger.info("Parsed {} lines".format(len(parsed_logs)))
      cols = [x[0] for x in iis_log_format]
      
      logger.info("Creating report file: {}".format(report_file))
      write_csv(report_file, cols, parsed_logs)
      logger.info("Report created")

Por último, precisamos definir um método que escreverá a saída na planilha -

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

Depois de executar o script acima, obteremos os logs do servidor da web em uma planilha.

Verificando arquivos importantes usando YARA

YARA (Yet Another Recursive Algorithm) é um utilitário de correspondência de padrões projetado para identificação de malware e resposta a incidentes. Usaremos YARA para escanear os arquivos. No script Python a seguir, usaremos YARA.

Podemos instalar o YARA com a ajuda do seguinte comando -

pip install YARA

Podemos seguir as etapas abaixo para usar as regras YARA para verificar arquivos -

  • Primeiro, configure e compile as regras YARA

  • Em seguida, examine um único arquivo e, em seguida, percorra os diretórios para processar arquivos individuais.

  • Por fim, exportaremos o resultado para CSV.

Código Python

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

Primeiro, precisamos importar os seguintes módulos Python -

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

import os
import csv
import yara

Em seguida, forneça o argumento para o manipulador de linha de comando. Observe que aqui ele aceitará dois argumentos - o primeiro é o caminho para as regras YARA, o segundo é o arquivo a ser verificado.

if __name__ == '__main__':
   parser = ArgumentParser('Scanning files by YARA')
   parser.add_argument(
      'yara_rules',help = "Path to Yara rule to scan with. May be file or folder path.")
   parser.add_argument('path_to_scan',help = "Path to file or folder to scan")
   parser.add_argument('--output',help = "Path to output a CSV report of scan results")
   args = parser.parse_args()
   main(args.yara_rules, args.path_to_scan, args.output)

Agora vamos definir a função main () que aceitará o caminho para as regras yara e o arquivo a ser verificado -

def main(yara_rules, path_to_scan, output):
   if os.path.isdir(yara_rules):
      yrules = yara.compile(yara_rules)
   else:
      yrules = yara.compile(filepath=yara_rules)
   if os.path.isdir(path_to_scan):
      match_info = process_directory(yrules, path_to_scan)
   else:
      match_info = process_file(yrules, path_to_scan)
   columns = ['rule_name', 'hit_value', 'hit_offset', 'file_name',
   'rule_string', 'rule_tag']
   
   if output is None:
      write_stdout(columns, match_info)
   else:
      write_csv(output, columns, match_info)

Agora, defina um método que irá iterar através do diretório e passar o resultado para outro método para processamento posterior -

def process_directory(yrules, folder_path):
   match_info = []
   for root, _, files in os.walk(folder_path):
      for entry in files:
         file_entry = os.path.join(root, entry)
         match_info += process_file(yrules, file_entry)
   return match_info

A seguir, defina duas funções. Observe que primeiro vamos usarmatch() método para yrulesobjeto e outro reportará as informações correspondentes ao console se o usuário não especificar nenhum arquivo de saída. Observe o código mostrado abaixo -

def process_file(yrules, file_path):
   match = yrules.match(file_path)
   match_info = []
   
   for rule_set in match:
      for hit in rule_set.strings:
         match_info.append({
            'file_name': file_path,
            'rule_name': rule_set.rule,
            'rule_tag': ",".join(rule_set.tags),
            'hit_offset': hit[0],
            'rule_string': hit[1],
            'hit_value': hit[2]
         })
   return match_info
def write_stdout(columns, match_info):
   for entry in match_info:
      for col in columns:
         print("{}: {}".format(col, entry[col]))
   print("=" * 30)

Por fim, definiremos um método que escreverá a saída no arquivo CSV, conforme mostrado abaixo -

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

Depois de executar o script acima com êxito, podemos fornecer os argumentos apropriados na linha de comando e gerar um relatório CSV.