Python Digital Forensics - Guía rápida
Este capítulo le dará una introducción a lo que es la ciencia forense digital y su revisión histórica. También comprenderá dónde puede aplicar la ciencia forense digital en la vida real y sus limitaciones.
¿Qué es el análisis forense digital?
La ciencia forense digital puede definirse como la rama de la ciencia forense que analiza, examina, identifica y recupera las evidencias digitales que residen en dispositivos electrónicos. Se utiliza comúnmente para el derecho penal y las investigaciones privadas.
Por ejemplo, puede confiar en las evidencias de extracción forense digital en caso de que alguien robe algunos datos en un dispositivo electrónico.
Breve reseña histórica de la ciencia forense digital
La historia de los delitos informáticos y la revisión histórica de la ciencia forense digital se explica en esta sección como se indica a continuación:
Década de 1970-1980: primer delito informático
Antes de esta década, no se reconocía ningún delito informático. Sin embargo, si se supone que suceda, las leyes existentes se ocuparon de ellos. Más tarde, en 1978, se reconoció el primer delito informático en la Ley de delitos informáticos de Florida, que incluía legislación contra la modificación o eliminación no autorizada de datos en un sistema informático. Pero con el tiempo, debido al avance de la tecnología, también aumentó la gama de delitos informáticos que se cometían. Para hacer frente a los delitos relacionados con los derechos de autor, la privacidad y la pornografía infantil, se aprobaron varias otras leyes.
Década de 1980 a 1990: Década del desarrollo
Esta década fue la década del desarrollo de la ciencia forense digital, todo debido a la primera investigación (1986) en la que Cliff Stoll rastreó al hacker llamado Markus Hess. Durante este período, se desarrollaron dos tipos de disciplinas forenses digitales: la primera fue con la ayuda de herramientas y técnicas ad-hoc desarrolladas por profesionales que lo tomaron como un pasatiempo, mientras que la segunda fue desarrollada por la comunidad científica. En 1992, el término“Computer Forensics”se utilizó en la literatura académica.
Década de 2000-2010: década de estandarización
Después del desarrollo de la ciencia forense digital hasta cierto nivel, existía la necesidad de elaborar algunos estándares específicos que se puedan seguir al realizar investigaciones. En consecuencia, varias agencias y organismos científicos han publicado pautas para el análisis forense digital. En 2002, el Grupo de Trabajo Científico sobre Evidencia Digital (SWGDE) publicó un documento titulado "Mejores prácticas para la informática forense". Otra pluma en el límite fue un tratado internacional liderado por Europa, a saber“The Convention on Cybercrime”fue firmado por 43 naciones y ratificado por 16 naciones. Incluso después de tales estándares, todavía existe la necesidad de resolver algunos problemas que han sido identificados por los investigadores.
Proceso de análisis forense digital
Desde el primer delito informático en 1978, ha habido un enorme incremento en las actividades delictivas digitales. Debido a este incremento, existe la necesidad de una forma estructurada para tratar con ellos. En 1984, se introdujo un proceso formalizado y posteriormente se desarrollaron un gran número de procesos de investigación forense informática nuevos y mejorados.
Un proceso de investigación de informática forense implica tres fases principales, como se explica a continuación:
Fase 1: Adquisición o generación de imágenes de exhibiciones
La primera fase del análisis forense digital consiste en guardar el estado del sistema digital para poder analizarlo posteriormente. Es muy similar a tomar fotografías, muestras de sangre, etc. de la escena del crimen. Por ejemplo, implica capturar una imagen de áreas asignadas y no asignadas de un disco duro o RAM.
Fase 2: Análisis
La entrada de esta fase son los datos adquiridos en la fase de adquisición. Aquí, estos datos fueron examinados para identificar evidencias. Esta fase da tres tipos de evidencias de la siguiente manera:
Inculpatory evidences - Estas evidencias apoyan una historia determinada.
Exculpatory evidences - Estas evidencias contradicen una historia determinada.
Evidence of tampering- Estas evidencias muestran que el sistema fue templado para evitar la identificación. Incluye examinar los archivos y el contenido del directorio para recuperar los archivos eliminados.
Fase 3: Presentación o informe
Como sugiere el nombre, esta fase presenta la conclusión y las evidencias correspondientes de la investigación.
Aplicaciones de la ciencia forense digital
La ciencia forense digital se ocupa de recopilar, analizar y preservar las evidencias contenidas en cualquier dispositivo digital. El uso de análisis forense digital depende de la aplicación. Como se mencionó anteriormente, se utiliza principalmente en las siguientes dos aplicaciones:
Derecho penal
En derecho penal, la evidencia se recopila para apoyar u oponerse a una hipótesis en el tribunal. Los procedimientos forenses son muy similares a los que se utilizan en las investigaciones penales, pero con diferentes requisitos y limitaciones legales.
Investigación privada
El mundo empresarial principalmente utiliza la ciencia forense digital para la investigación privada. Se utiliza cuando las empresas sospechan que los empleados pueden estar realizando una actividad ilegal en sus computadoras que va en contra de la política de la empresa. El análisis forense digital proporciona una de las mejores rutas que puede tomar una empresa o una persona al investigar a alguien por mala conducta digital.
Ramas de la ciencia forense digital
El delito digital no se limita solo a las computadoras, sin embargo, los piratas informáticos y los delincuentes están utilizando pequeños dispositivos digitales como tabletas, teléfonos inteligentes, etc., también a gran escala. Algunos de los dispositivos tienen memoria volátil, mientras que otros tienen memoria no volátil. Por lo tanto, dependiendo del tipo de dispositivos, la ciencia forense digital tiene las siguientes ramas:
Informática forense
Esta rama de la ciencia forense digital se ocupa de las computadoras, los sistemas integrados y las memorias estáticas, como las unidades USB. La informática forense puede investigar una amplia gama de información, desde registros hasta archivos reales en la unidad.
Forense móvil
Se trata de la investigación de datos de dispositivos móviles. Esta rama es diferente de la informática forense en el sentido de que los dispositivos móviles tienen un sistema de comunicación incorporado que es útil para proporcionar información útil relacionada con la ubicación.
Análisis forense de redes
Se trata de la supervisión y el análisis del tráfico de la red informática, tanto local como WAN (red de área amplia) con el fin de recopilar información, recopilar pruebas o detectar intrusiones.
Análisis forense de bases de datos
Esta rama de la ciencia forense digital se ocupa del estudio forense de bases de datos y sus metadatos.
Habilidades necesarias para la investigación forense digital
Los examinadores forenses digitales ayudan a rastrear a los piratas informáticos, recuperar datos robados, seguir los ataques informáticos hasta su origen y ayudar en otros tipos de investigaciones que involucran computadoras. Algunas de las habilidades clave necesarias para convertirse en examinador forense digital, como se explica a continuación:
Excelentes capacidades de pensamiento
Un investigador forense digital debe ser un pensador sobresaliente y debe ser capaz de aplicar diferentes herramientas y metodologías en una tarea particular para obtener el resultado. Debe poder encontrar diferentes patrones y hacer correlaciones entre ellos.
Habilidades técnicas
Un examinador forense digital debe tener buenas habilidades tecnológicas porque este campo requiere el conocimiento de la red, cómo interactúa el sistema digital.
Apasionado de la seguridad cibernética
Debido a que el campo de la ciencia forense digital tiene que ver con la resolución de delitos cibernéticos y esta es una tarea tediosa, se necesita mucha pasión para que alguien se convierta en un investigador forense digital experto.
Habilidades de comunicación
Las buenas habilidades de comunicación son imprescindibles para coordinarse con varios equipos y extraer cualquier dato o información que falte.
Hábil en la elaboración de informes
Después de la implementación exitosa de la adquisición y el análisis, un examinador forense digital debe mencionar todos los hallazgos en el informe final y la presentación. Por lo tanto, debe tener buenas habilidades para la elaboración de informes y atención a los detalles.
Limitaciones
La investigación forense digital ofrece ciertas limitaciones, como se analiza aquí:
Necesidad de producir evidencias convincentes
Uno de los principales contratiempos de la investigación forense digital es que el examinador debe cumplir con los estándares que se requieren para la evidencia en el tribunal de justicia, ya que los datos pueden manipularse fácilmente. Por otro lado, el investigador forense informático debe tener un conocimiento completo de los requisitos legales, el manejo de pruebas y los procedimientos de documentación para presentar pruebas convincentes en el tribunal de justicia.
Investigar herramientas
La efectividad de la investigación digital radica completamente en la experiencia del examinador forense digital y la selección de la herramienta de investigación adecuada. Si la herramienta utilizada no cumple con los estándares especificados, entonces en el tribunal de justicia, el juez puede negar las pruebas.
Falta de conocimiento técnico entre la audiencia
Otra limitación es que algunas personas no están completamente familiarizadas con la informática forense; por tanto, mucha gente no comprende este campo. Los investigadores deben asegurarse de comunicar sus hallazgos a los tribunales de tal manera que todos puedan comprender los resultados.
Costo
Producir evidencias digitales y preservarlas es muy costoso. Por lo tanto, es posible que muchas personas que no pueden pagar el costo no elijan este proceso.
En el capítulo anterior, aprendimos los conceptos básicos de la ciencia forense digital, sus ventajas y limitaciones. Este capítulo lo hará sentir cómodo con Python, la herramienta esencial que estamos utilizando en esta investigación forense digital.
¿Por qué Python para análisis forense digital?
Python es un lenguaje de programación popular y se utiliza como herramienta para la seguridad cibernética, las pruebas de penetración y las investigaciones forenses digitales. Cuando elige Python como su herramienta de análisis forense digital, no necesita ningún otro software de terceros para completar la tarea.
Algunas de las características únicas del lenguaje de programación Python que lo hacen una buena opción para proyectos de análisis forense digital se detallan a continuación:
Simplicity of Syntax - La sintaxis de Python es simple en comparación con otros lenguajes, lo que hace que sea más fácil de aprender y poner en uso para la ciencia forense digital.
Comprehensive inbuilt modules - Los completos módulos incorporados de Python son una excelente ayuda para realizar una investigación forense digital completa.
Help and Support - Al ser un lenguaje de programación de código abierto, Python disfruta de un excelente soporte de la comunidad de desarrolladores y usuarios.
Características de Python
Python, al ser un lenguaje de scripting de alto nivel, interpretado, interactivo y orientado a objetos, proporciona las siguientes características:
Easy to Learn - Python es un lenguaje amigable para los desarrolladores y fácil de aprender, porque tiene menos palabras clave y la estructura más simple.
Expressive and Easy to read- El lenguaje Python es de naturaleza expresiva; de ahí que su código sea más comprensible y legible.
Cross-platform Compatible - Python es un lenguaje compatible multiplataforma, lo que significa que puede ejecutarse de manera eficiente en varias plataformas como UNIX, Windows y Macintosh.
Interactive Mode Programming - Podemos hacer pruebas interactivas y depuración de código porque Python admite un modo interactivo para la programación.
Provides Various Modules and Functions - Python tiene una gran biblioteca estándar que nos permite usar un rico conjunto de módulos y funciones para nuestro script.
Supports Dynamic Type Checking - Python admite la verificación dinámica de tipos y proporciona tipos de datos dinámicos de muy alto nivel.
GUI Programming - Python admite la programación GUI para desarrollar interfaces gráficas de usuario.
Integration with other programming languages - Python se puede integrar fácilmente con otros lenguajes de programación como C, C ++, JAVA, etc.
Instalación de Python
La distribución de Python está disponible para varias plataformas como Windows, UNIX, Linux y Mac. Solo necesitamos descargar el código binario según nuestra plataforma. En caso de que el código binario de alguna plataforma no esté disponible, debemos tener un compilador C para que el código fuente se pueda compilar manualmente.
Esta sección lo familiarizará con la instalación de Python en varias plataformas.
Instalación de Python en Unix y Linux
Puede seguir los pasos que se muestran a continuación para instalar Python en una máquina Unix / Linux.
Step 1- Abra un navegador web. Escriba e ingrese www.python.org/downloads/
Step 2 - Descargue el código fuente comprimido disponible para Unix / Linux.
Step 3 - Extraiga los archivos comprimidos descargados.
Step 4 - Si desea personalizar algunas opciones, puede editar el Modules/Setup file.
Step 5 - Utilice los siguientes comandos para completar la instalación -
run ./configure script
make
make install
Una vez que haya completado con éxito los pasos dados anteriormente, Python se instalará en su ubicación estándar /usr/local/bin y sus bibliotecas en /usr/local/lib/pythonXX donde XX es la versión de Python.
Instalación de Python en Windows
Podemos seguir los siguientes pasos simples para instalar Python en una máquina con Windows.
Step 1- Abra un navegador web. Escriba e ingrese www.python.org/downloads/
Step 2 - Descarga el instalador de Windows python-XYZ.msi archivo, donde XYZ es la versión que necesitamos instalar.
Step 3 - Ahora ejecute ese archivo MSI después de guardar el archivo de instalación en su máquina local.
Step 4 - Ejecute el archivo descargado que mostrará el asistente de instalación de Python.
Instalación de Python en Macintosh
Para instalar Python 3 en Mac OS X, debemos usar un instalador de paquetes llamado Homebrew.
Puede usar el siguiente comando para instalar Homebrew, en caso de que no lo tenga en su sistema:
$ ruby -e "$(curl -fsSL
https://raw.githubusercontent.com/Homebrew/install/master/install)"
Si necesita actualizar el administrador de paquetes, puede hacerlo con la ayuda del siguiente comando:
$ brew update
Ahora, use el siguiente comando para instalar Python3 en su sistema:
$ brew install python3
Establecer la RUTA
Necesitamos establecer la ruta para la instalación de Python y esto difiere con plataformas como UNIX, WINDOWS o MAC.
Configuración de ruta en Unix / Linux
Puede utilizar las siguientes opciones para establecer la ruta en Unix / Linux:
If using csh shell - Tipo setenv PATH "$PATH:/usr/local/bin/python" y luego presione Enter.
If using bash shell (Linux) - Tipo export ATH="$PATH:/usr/local/bin/python" y luego presione Enter.
If using sh or ksh shell - Tipo PATH="$PATH:/usr/local/bin/python" y luego presione Enter.
Configuración de ruta en Windows
Tipo path %path%;C:\Python en el símbolo del sistema y luego presione Entrar.
Ejecutando Python
Puede elegir cualquiera de los siguientes tres métodos para iniciar el intérprete de Python:
Método 1: uso de intérprete interactivo
Un sistema que proporciona un intérprete de línea de comandos o un shell se puede usar fácilmente para iniciar Python. Por ejemplo, Unix, DOS, etc. Puede seguir los pasos que se indican a continuación para comenzar a codificar en un intérprete interactivo:
Step 1 - Entrar python en la línea de comando.
Step 2 - Comience a codificar de inmediato en el intérprete interactivo usando los comandos que se muestran a continuación -
$python # Unix/Linux
or
python% # Unix/Linux
or
C:> python # Windows/DOS
Método 2: usar un script desde la línea de comandos
También podemos ejecutar un script de Python en la línea de comando invocando al intérprete en nuestra aplicación. Puede usar los comandos que se muestran a continuación:
$python script.py # Unix/Linux
or
python% script.py # Unix/Linux
or
C: >python script.py # Windows/DOS
Método 3: entorno de desarrollo integrado
Si un sistema tiene una aplicación GUI que admite Python, entonces Python se puede ejecutar desde ese entorno GUI. Algunos de los IDE para varias plataformas se dan a continuación:
Unix IDE - UNIX tiene IDE IDLE para Python.
Windows IDE - Windows tiene PythonWin, la primera interfaz de Windows para Python junto con GUI.
Macintosh IDE - Macintosh tiene IDLE IDE que está disponible en el sitio web principal, descargable como archivos MacBinary o BinHex'd.
Ahora que se siente cómodo con la instalación y ejecución de los comandos de Python en su sistema local, pasemos a los conceptos forenses en detalle. Este capítulo explicará varios conceptos relacionados con el manejo de artefactos en el análisis forense digital de Python.
Necesidad de creación de informes
El proceso de análisis forense digital incluye la presentación de informes como tercera fase. Esta es una de las partes más importantes del proceso forense digital. La creación de informes es necesaria debido a las siguientes razones:
Es el documento en el que el examinador forense digital describe el proceso de investigación y sus hallazgos.
Otro examinador puede hacer referencia a un buen informe forense digital para lograr el mismo resultado con los mismos repositorios.
Es un documento técnico y científico que contiene hechos encontrados dentro de los 1 y 0 de la evidencia digital.
Directrices generales para la creación de informes
Los informes están escritos para proporcionar información al lector y deben comenzar con una base sólida. Los investigadores pueden enfrentar dificultades para presentar sus hallazgos de manera eficiente si el informe se prepara sin algunas pautas o estándares generales. A continuación se dan algunas pautas generales que se deben seguir al crear informes forenses digitales:
Summary - El informe debe contener el breve resumen de la información para que el lector pueda conocer el propósito del informe.
Tools used - Debemos mencionar las herramientas que se han utilizado para llevar a cabo el proceso de la ciencia forense digital, incluida su finalidad.
Repository - Supongamos que investigamos la computadora de alguien, luego el resumen de la evidencia y el análisis de material relevante como correo electrónico, historial de búsqueda interna, etc., luego deben incluirse en el informe para que el caso pueda presentarse claramente.
Recommendations for counsel - El informe debe tener las recomendaciones para que el abogado continúe o cese la investigación en función de los hallazgos del informe.
Creación de diferentes tipos de informes
En la sección anterior, llegamos a conocer la importancia del informe en el análisis forense digital junto con las pautas para crear el mismo. Algunos de los formatos en Python para crear diferentes tipos de informes se analizan a continuación:
Informes CSV
Uno de los formatos de salida más comunes de los informes es un informe de hoja de cálculo CSV. Puede crear un CSV para crear un informe de datos procesados utilizando el código Python como se muestra a continuación:
Primero, importe bibliotecas útiles para escribir la hoja de cálculo:
from __future__ import print_function
import csv
import os
import sys
Ahora, llame al siguiente método:
Write_csv(TEST_DATA_LIST, ["Name", "Age", "City", "Job description"], os.getcwd())
Estamos utilizando la siguiente variable global para representar tipos de datos de muestra:
TEST_DATA_LIST = [["Ram", 32, Bhopal, Manager],
["Raman", 42, Indore, Engg.],
["Mohan", 25, Chandigarh, HR],
["Parkash", 45, Delhi, IT]]
A continuación, definamos el método para continuar con las operaciones posteriores. Abrimos el archivo en el modo "w" y establecemos el argumento de palabra clave de nueva línea en una cadena vacía.
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)
Si ejecuta el script anterior, obtendrá los siguientes detalles almacenados en el archivo report1.csv.
Nombre | Años | Ciudad | Designacion |
---|---|---|---|
RAM | 32 | Bhopal | Managerh |
Raman | 42 | Indore | Engg |
Mohan | 25 | Chandigarh | HORA |
Parkash | 45 | Delhi | ESO |
Informes de Excel
Otro formato de salida común de los informes es el informe de hoja de cálculo de Excel (.xlsx). Podemos crear una tabla y también trazar el gráfico usando Excel. Podemos crear un informe de datos procesados en formato Excel usando código Python como se muestra a continuación
Primero, importe el módulo XlsxWriter para crear una hoja de cálculo -
import xlsxwriter
Ahora, cree un objeto de libro de trabajo. Para esto, necesitamos usar el constructor Workbook ().
workbook = xlsxwriter.Workbook('report2.xlsx')
Ahora, cree una nueva hoja de trabajo usando el módulo add_worksheet ().
worksheet = workbook.add_worksheet()
A continuación, escriba los siguientes datos en la hoja de trabajo:
report2 = (['Ram', 32, ‘Bhopal’],['Mohan',25, ‘Chandigarh’] ,['Parkash',45, ‘Delhi’])
row = 0
col = 0
Puede iterar sobre estos datos y escribirlos de la siguiente manera:
for item, cost in (a):
worksheet.write(row, col, item)
worksheet.write(row, col+1, cost)
row + = 1
Ahora, cerremos este archivo de Excel usando el método close ().
workbook.close()
El script anterior creará un archivo de Excel llamado report2.xlsx con los siguientes datos:
RAM | 32 | Bhopal |
Mohan | 25 | Chandigarh |
Parkash | 45 | Delhi |
Medios de adquisición de investigación
Es importante que un investigador tenga las notas de investigación detalladas para recordar con precisión los hallazgos o reunir todas las piezas de la investigación. Una captura de pantalla es muy útil para realizar un seguimiento de los pasos dados para una investigación en particular. Con la ayuda del siguiente código Python, podemos tomar la captura de pantalla y guardarla en el disco duro para uso futuro.
Primero, instale el módulo de Python llamado pyscreenshot usando el siguiente comando:
Pip install pyscreenshot
Ahora, importe los módulos necesarios como se muestra:
import pyscreenshot as ImageGrab
Utilice la siguiente línea de código para obtener la captura de pantalla:
image = ImageGrab.grab()
Use la siguiente línea de código para guardar la captura de pantalla en la ubicación dada:
image.save('d:/image123.png')
Ahora, si desea mostrar la captura de pantalla como un gráfico, puede usar el siguiente código de 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á la ciencia forense digital de Python en dispositivos móviles y los conceptos involucrados.
Introducción
La ciencia forense de dispositivos móviles es la rama de la ciencia forense digital que se ocupa de la adquisición y análisis de dispositivos móviles para recuperar evidencias digitales de interés investigativo. Esta rama es diferente de la informática forense porque los dispositivos móviles tienen un sistema de comunicación incorporado que es útil para proporcionar información útil relacionada con la ubicación.
Aunque el uso de teléfonos inteligentes está aumentando día a día en la ciencia forense digital, aún se considera que no es estándar debido a su heterogeneidad. Por otro lado, el hardware informático, como el disco duro, se considera estándar y también se ha desarrollado como una disciplina estable. En la industria forense digital, existe mucho debate sobre las técnicas utilizadas para dispositivos no estándares, que tienen evidencias transitorias, como los teléfonos inteligentes.
Artefactos extraíbles de dispositivos móviles
Los dispositivos móviles modernos poseen mucha información digital en comparación con los teléfonos más antiguos que solo tienen un registro de llamadas o mensajes SMS. Por lo tanto, los dispositivos móviles pueden proporcionar a los investigadores una gran cantidad de información sobre su usuario. Algunos artefactos que se pueden extraer de dispositivos móviles son los que se mencionan a continuación:
Messages - Estos son los artefactos útiles que pueden revelar el estado de ánimo del propietario e incluso pueden dar alguna información desconocida previa al investigador.
Location History- Los datos del historial de ubicaciones son un artefacto útil que los investigadores pueden utilizar para validar la ubicación particular de una persona.
Applications Installed - Al acceder al tipo de aplicaciones instaladas, el investigador obtiene una idea de los hábitos y el pensamiento del usuario móvil.
Fuentes de evidencia y procesamiento en Python
Los teléfonos inteligentes tienen bases de datos SQLite y archivos PLIST como las principales fuentes de evidencias. En esta sección vamos a procesar las fuentes de evidencias en Python.
Analizando archivos PLIST
PLIST (lista de propiedades) es un formato flexible y conveniente para almacenar datos de aplicaciones, especialmente en dispositivos iPhone. Utiliza la extensión.plist. Este tipo de archivos se utilizan para almacenar información sobre paquetes y aplicaciones. Puede estar en dos formatos:XML y binary. El siguiente código de Python se abrirá y leerá el archivo PLIST. Tenga en cuenta que antes de continuar con esto, debemos crear nuestro propioInfo.plist archivo.
Primero, instale una biblioteca de terceros llamada biplist con el siguiente comando:
Pip install biplist
Ahora, importe algunas bibliotecas útiles para procesar archivos plist:
import biplist
import os
import sys
Ahora, use el siguiente comando en el método principal que se puede usar para leer el archivo plist en una variable:
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)
Ahora, podemos leer los datos en la consola o imprimirlos directamente, desde esta variable.
Bases de datos SQLite
SQLite sirve como repositorio de datos principal en dispositivos móviles. SQLite una biblioteca en proceso que implementa un motor de base de datos transaccional SQL autónomo, sin servidor, sin configuración. Es una base de datos, que no está configurada, no es necesario que la configure en su sistema, a diferencia de otras bases de datos.
Si es un novato o no está familiarizado con las bases de datos SQLite, puede seguir el enlace www.tutorialspoint.com/sqlite/index.htm Además, puede seguir el enlace www.tutorialspoint.com/sqlite/sqlite_python.htm en caso de que desee entrar en detalles de SQLite con Python.
Durante el análisis forense móvil, podemos interactuar con el sms.db archivo de un dispositivo móvil y puede extraer información valiosa de messagemesa. Python tiene una biblioteca incorporada llamadasqlite3para conectarse con la base de datos SQLite. Puede importar lo mismo con el siguiente comando:
import sqlite3
Ahora, con la ayuda del siguiente comando, podemos conectarnos con la base de datos, digamos sms.db en el caso de dispositivos móviles -
Conn = sqlite3.connect(‘sms.db’)
C = conn.cursor()
Aquí, C es el objeto cursor con la ayuda del cual podemos interactuar con la base de datos.
Ahora, suponga que si queremos ejecutar un comando en particular, digamos para obtener los detalles del abc table, se puede hacer con la ayuda del siguiente comando:
c.execute(“Select * from abc”)
c.close()
El resultado del comando anterior se almacenaría en el cursorobjeto. Del mismo modo, podemos usarfetchall() método para volcar el resultado en una variable que podamos manipular.
Podemos usar el siguiente comando para obtener los datos de los nombres de columna de la tabla de mensajes en sms.db -
c.execute(“pragma table_info(message)”)
table_data = c.fetchall()
columns = [x[1] for x in table_data
Observe que aquí estamos usando el comando SQLite PRAGMA, que es un comando especial que se usará para controlar varias variables ambientales y banderas de estado dentro del entorno SQLite. En el comando anterior, elfetchall()El método devuelve una tupla de resultados. El nombre de cada columna se almacena en el primer índice de cada tupla.
Ahora, con la ayuda del siguiente comando, podemos consultar la tabla para todos sus datos y almacenarlos en la variable llamada data_msg -
c.execute(“Select * from message”)
data_msg = c.fetchall()
El comando anterior almacenará los datos en la variable y, además, también podemos escribir los datos anteriores en un archivo CSV usando csv.writer() método.
Copias de seguridad de iTunes
Los análisis forenses móviles de iPhone se pueden realizar en las copias de seguridad realizadas por iTunes. Los examinadores forenses confían en analizar las copias de seguridad lógicas del iPhone adquiridas a través de iTunes. ITunes utiliza el protocolo AFC (conexión de archivos de Apple) para realizar la copia de seguridad. Además, el proceso de copia de seguridad no modifica nada en el iPhone, excepto los registros de la clave de custodia.
Ahora, surge la pregunta de por qué es importante que un experto forense digital comprenda las técnicas de las copias de seguridad de iTunes. Es importante en caso de que tengamos acceso a la computadora del sospechoso en lugar del iPhone directamente porque cuando se usa una computadora para sincronizar con el iPhone, es probable que la mayor parte de la información en el iPhone esté respaldada en la computadora.
Proceso de copia de seguridad y su ubicación
Siempre que se realiza una copia de seguridad de un producto de Apple en la computadora, está sincronizado con iTunes y habrá una carpeta específica con la identificación única del dispositivo. En el último formato de copia de seguridad, los archivos se almacenan en subcarpetas que contienen los dos primeros caracteres hexadecimales del nombre del archivo. De estos archivos de respaldo, hay algunos archivos como info.plist que son útiles junto con la base de datos llamada Manifest.db. La siguiente tabla muestra las ubicaciones de las copias de seguridad, que varían según los sistemas operativos de las copias de seguridad de iTunes:
SO | Ubicación de respaldo |
---|---|
Win7 | C: \ Users \ [nombre de usuario] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \ |
MAC OS X | ~ / Biblioteca / Soporte de aplicaciones / MobileSync / Backup / |
Para procesar la copia de seguridad de iTunes con Python, primero debemos identificar todas las copias de seguridad en la ubicación de la copia de seguridad según nuestro sistema operativo. Luego, iteraremos a través de cada copia de seguridad y leeremos la base de datos Manifest.db.
Ahora, con la ayuda de seguir el código de Python, podemos hacer lo mismo:
Primero, importe las bibliotecas necesarias de la siguiente manera:
from __future__ import print_function
import argparse
import logging
import os
from shutil import copyfile
import sqlite3
import sys
logger = logging.getLogger(__name__)
Ahora, proporcione dos argumentos posicionales, a saber, INPUT_DIR y OUTPUT_DIR, que representan la copia de seguridad de iTunes y la carpeta de salida deseada:
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()
Ahora, configure el registro de la siguiente manera:
if args.v:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
Ahora, configure el formato de mensaje para este registro de la siguiente manera:
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)
La siguiente línea de código creará las carpetas necesarias para el directorio de salida deseado usando os.makedirs() función -
if not os.path.exists(args.OUTPUT_DIR):
os.makedirs(args.OUTPUT_DIR)
Ahora, pase los directorios de entrada y salida proporcionados a la función main () de la siguiente manera:
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)
Ahora escribe main() función que llamará más adelante backup_summary() función para identificar todas las copias de seguridad presentes en la carpeta 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
Ahora, imprima el resumen de cada copia de seguridad en la consola de la siguiente manera:
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]))
Ahora, vuelque el contenido del archivo Manifest.db a la variable denominada 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
Ahora, definamos una función que tomará la ruta del directorio de la copia de seguridad:
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
Ahora, usando SQLite3 nos conectaremos a la base de datos mediante el cursor llamado 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)
Ahora, defina el create_files() método de la siguiente manera -
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)
Ahora, repita cada clave en el db_items diccionario -
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)
Ahora usa shutil.copyfile() método para copiar el archivo respaldado de la siguiente manera:
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"))
Con el script de Python anterior, podemos obtener la estructura de archivo de respaldo actualizada en nuestra carpeta de salida. Nosotros podemos usarpycrypto biblioteca de Python para descifrar las copias de seguridad.
Wifi
Los dispositivos móviles se pueden utilizar para conectarse al mundo exterior mediante la conexión a través de redes Wi-Fi que están disponibles en todas partes. A veces, el dispositivo se conecta a estas redes abiertas automáticamente.
En el caso de iPhone, la lista de conexiones Wi-Fi abiertas con las que se ha conectado el dispositivo se almacena en un archivo PLIST llamado com.apple.wifi.plist. Este archivo contendrá el SSID de Wi-Fi, BSSID y el tiempo de conexión.
Necesitamos extraer detalles de Wi-Fi del informe XML estándar de Cellebrite usando Python. Para esto, necesitamos usar API de Wireless Geographic Logging Engine (WIGLE), una plataforma popular que se puede usar para encontrar la ubicación de un dispositivo usando los nombres de redes Wi-Fi.
Podemos usar la biblioteca de Python llamada requestspara acceder a la API de WIGLE. Se puede instalar de la siguiente manera:
pip install requests
API de WIGLE
Necesitamos registrarnos en el sitio web de WIGLE https://wigle.net/accountpara obtener una API gratuita de WIGLE. El script de Python para obtener la información sobre el dispositivo del usuario y su conexión a través de la API de WIGEL se analiza a continuación:
Primero, importe las siguientes bibliotecas para manejar diferentes cosas:
from __future__ import print_function
import argparse
import csv
import os
import sys
import xml.etree.ElementTree as ET
import requests
Ahora, proporcione dos argumentos posicionales a saber INPUT_FILE y OUTPUT_CSV que representará el archivo de entrada con la dirección MAC de Wi-Fi y el archivo CSV de salida deseado 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()
Ahora, las siguientes líneas de código comprobarán si el archivo de entrada existe y es un archivo. Si no es así, sale del guión -
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(":")
Ahora, pase el argumento a main de la siguiente manera:
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)
Ahora, analizaremos el archivo XML de la siguiente manera:
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()
Ahora, itere a través del elemento hijo de la raíz de la siguiente manera:
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
Ahora, comprobaremos que la cadena 'ssid' esté presente en el texto del valor o no -
if "SSID" in value.text:
bssid, ssid = value.text.split("\t")
bssid = bssid[7:]
ssid = ssid[6:]
Ahora, necesitamos agregar BSSID, SSID y marca de tiempo al diccionario wifi de la siguiente manera:
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
El analizador de texto, que es mucho más simple que el analizador XML, se muestra a continuación:
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
Ahora, usemos el módulo de solicitudes para hacer WIGLE APIllamadas y necesita pasar a la 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()
En realidad, hay un límite por día para las llamadas a la API de WIGLE, si ese límite excede, debe mostrar un error de la siguiente manera:
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)
Ahora usaremos prep_output() método para aplanar el diccionario en fragmentos fáciles de escribir -
def prep_output(output, data):
csv_data = {}
google_map = https://www.google.com/maps/search/
Ahora, acceda a todos los datos que hemos recopilado hasta ahora de la siguiente manera:
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"])
Ahora, podemos escribir la salida en un archivo CSV como lo hemos hecho en scripts anteriores en este capítulo usando write_csv() función.
En este capítulo, aprenderemos en detalle sobre la investigación de metadatos incrustados usando análisis forense digital de Python.
Introducción
Los metadatos incrustados son la información sobre los datos almacenados en el mismo archivo que tiene el objeto descrito por esos datos. En otras palabras, es la información sobre un activo digital almacenada en el propio archivo digital. Siempre está asociado con el archivo y nunca se puede separar.
En el caso de la ciencia forense digital, no podemos extraer toda la información sobre un archivo en particular. Por otro lado, los metadatos incrustados pueden proporcionarnos información crítica para la investigación. Por ejemplo, los metadatos de un archivo de texto pueden contener información sobre el autor, su extensión, fecha de escritura e incluso un breve resumen sobre ese documento. Una imagen digital puede incluir metadatos como la longitud de la imagen, la velocidad de obturación, etc.
Artefactos que contienen atributos de metadatos y su extracción
En esta sección, aprenderemos sobre varios artefactos que contienen atributos de metadatos y su proceso de extracción usando Python.
Audio y video
Estos son los dos artefactos muy comunes que tienen metadatos incrustados. Estos metadatos se pueden extraer con fines de investigación.
Puede utilizar la siguiente secuencia de comandos de Python para extraer atributos o metadatos comunes de un archivo de audio o MP3 y un video o un archivo MP4.
Tenga en cuenta que para este script, necesitamos instalar una biblioteca de Python de terceros llamada mutagen que nos permite extraer metadatos de archivos de audio y video. Se puede instalar con la ayuda del siguiente comando:
pip install mutagen
Algunas de las bibliotecas útiles que necesitamos importar para este script de Python son las siguientes:
from __future__ import print_function
import argparse
import json
import mutagen
El controlador de línea de comando tomará un argumento que representa la ruta a los archivos MP3 o MP4. Entonces, usaremosmutagen.file() método para abrir un identificador para el archivo de la siguiente manera:
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)
Ahora, necesitamos usar dos identificadores, uno para extraer los datos de MP3 y otro para extraer datos del archivo MP4. Podemos definir estos identificadores de la siguiente manera:
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'))
Ahora, debemos iterar a través de este archivo MP4 de la siguiente manera:
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))
El script anterior nos dará información adicional sobre archivos MP3 y MP4.
Imagenes
Las imágenes pueden contener diferentes tipos de metadatos según su formato de archivo. Sin embargo, la mayoría de las imágenes incorporan información GPS. Podemos extraer esta información de GPS utilizando bibliotecas de Python de terceros. Puede usar la siguiente secuencia de comandos de Python para hacer lo mismo:
Primero, descargue la biblioteca de Python de terceros llamada Python Imaging Library (PIL) como sigue -
pip install pillow
Esto nos ayudará a extraer metadatos de imágenes.
También podemos escribir los detalles del GPS incrustados en imágenes en un archivo KML, pero para esto necesitamos descargar la biblioteca de Python de terceros llamada simplekml como sigue -
pip install simplekml
En este script, primero necesitamos importar las siguientes bibliotecas:
from __future__ import print_function
import argparse
from PIL import Image
from PIL.ExifTags import TAGS
import simplekml
import sys
Ahora, el controlador de la línea de comandos aceptará un argumento posicional que básicamente representa la ruta del archivo de las fotos.
parser = argparse.ArgumentParser('Metadata from images')
parser.add_argument('PICTURE_FILE', help = "Path to picture")
args = parser.parse_args()
Ahora, necesitamos especificar las URL que completarán la información de coordenadas. Las URL songmaps y open_maps. También necesitamos una función para convertir la coordenada de tupla de grados, minutos y segundos (DMS), proporcionada por la biblioteca PIL, en decimales. Se puede hacer de la siguiente manera:
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
Ahora usaremos image.open() función para abrir el archivo 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
Después de encontrar el GPSInfo etiqueta, almacenaremos la referencia GPS y procesaremos las coordenadas con el 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
Ahora, inicia kml objeto de simplekml biblioteca de la siguiente manera:
kml = simplekml.Kml()
kml.newpoint(name = args.PICTURE_FILE, coords = [(lon, lat)])
kml.save(args.PICTURE_FILE + ".kml")
Ahora podemos imprimir las coordenadas de la información procesada de la siguiente manera:
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
Los documentos PDF tienen una amplia variedad de medios que incluyen imágenes, texto, formularios, etc. Cuando extraemos metadatos incrustados en documentos PDF, es posible que obtengamos los datos resultantes en el formato llamado Plataforma de metadatos extensible (XMP). Podemos extraer metadatos con la ayuda del siguiente código Python:
Primero, instale una biblioteca Python de terceros llamada PyPDF2para leer metadatos almacenados en formato XMP. Se puede instalar de la siguiente manera:
pip install PyPDF2
Ahora, importe las siguientes bibliotecas para extraer los metadatos de archivos PDF:
from __future__ import print_function
from argparse import ArgumentParser, FileType
import datetime
from PyPDF2 import PdfFileReader
import sys
Ahora, el controlador de la línea de comandos aceptará un argumento posicional que básicamente representa la ruta del archivo PDF.
parser = argparse.ArgumentParser('Metadata from PDF')
parser.add_argument('PDF_FILE', help='Path to PDF file',type=FileType('rb'))
args = parser.parse_args()
Ahora podemos usar getXmpMetadata() método para proporcionar un objeto que contiene los metadatos disponibles de la siguiente manera:
pdf_file = PdfFileReader(args.PDF_FILE)
xmpm = pdf_file.getXmpMetadata()
if xmpm is None:
print("No XMP metadata found in document.")
sys.exit()
Nosotros podemos usar custom_print() método para extraer e imprimir los valores relevantes como título, creador, colaborador, etc. de la siguiente manera:
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)
También podemos definir custom_print() método en caso de que el PDF se cree utilizando varios programas de la siguiente manera:
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)))
También podemos extraer cualquier otra propiedad personalizada guardada por el software de la siguiente manera:
if xmpm.custom_properties:
print("Custom Properties:")
for k, v in xmpm.custom_properties.items():
print("\t{}: {}".format(k, v))
El script anterior leerá el documento PDF e imprimirá los metadatos almacenados en formato XMP, incluidas algunas propiedades personalizadas almacenadas por el software con la ayuda de la cual se creó ese PDF.
Archivos ejecutables de Windows
A veces podemos encontrar un archivo ejecutable sospechoso o no autorizado. Pero para fines de investigación, puede ser útil debido a los metadatos incrustados. Podemos obtener la información como su ubicación, su propósito y otros atributos como el fabricante, la fecha de compilación, etc. Con la ayuda del siguiente script de Python podemos obtener la fecha de compilación, datos útiles de los encabezados y símbolos importados y exportados.
Para este propósito, primero instale la biblioteca Python de terceros pefile. Se puede hacer de la siguiente manera:
pip install pefile
Una vez que lo haya instalado correctamente, importe las siguientes bibliotecas de la siguiente manera:
from __future__ import print_function
import argparse
from datetime import datetime
from pefile import PE
Ahora, el controlador de la línea de comandos aceptará un argumento posicional que básicamente representa la ruta del archivo del archivo ejecutable. También puede elegir el estilo de salida, ya sea que lo necesite de forma detallada y detallada o de forma simplificada. Para esto, debe proporcionar un argumento opcional como se muestra a continuación:
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()
Ahora, cargaremos el archivo ejecutable de entrada usando la clase PE. También volcaremos los datos ejecutables en un objeto de diccionario usandodump_dict() método.
pe = PE(args.EXE_FILE)
ped = pe.dump_dict()
Podemos extraer metadatos de archivos básicos, como autoría incorporada, versión y tiempo de compilación utilizando el código que se muestra a continuación:
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 extraer los datos útiles de los encabezados de la siguiente manera:
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'])
)
Ahora, extraiga la lista de importaciones y exportaciones de archivos ejecutables como se muestra a continuación:
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()
Ahora, imprime exports, names y addresses usando el código como se muestra a continuación -
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)))
El script anterior extraerá los metadatos básicos, la información de los encabezados de los archivos ejecutables de Windows.
Metadatos de documentos de Office
La mayor parte del trabajo en computadora se realiza en tres aplicaciones de MS Office: Word, PowerPoint y Excel. Estos archivos poseen enormes metadatos, que pueden exponer información interesante sobre su autoría e historial.
Tenga en cuenta que los metadatos del formato 2007 de Word (.docx), excel (.xlsx) y powerpoint (.pptx) se almacenan en un archivo XML. Podemos procesar estos archivos XML en Python con la ayuda del siguiente script de Python que se muestra a continuación:
Primero, importe las bibliotecas necesarias como se muestra a continuación:
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()
Ahora, compruebe si el archivo es un archivo ZIP. De lo contrario, genere un error. Ahora, abra el archivo y extraiga los elementos clave para procesarlos usando el siguiente 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'))
Ahora, cree un diccionario para iniciar la extracción de los metadatos:
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'
}
Utilizar iterchildren() método para acceder a cada una de las etiquetas dentro del archivo 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))
Del mismo modo, haga esto para el archivo app.xml que contiene información estadística sobre el contenido del 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))
Ahora, después de ejecutar el script anterior, podemos obtener los diferentes detalles sobre el documento en particular. Tenga en cuenta que podemos aplicar este script en documentos de Office 2007 o versiones posteriores únicamente.
Este capítulo explicará los fundamentos involucrados en la realización de análisis forense de redes usando Python.
Comprensión del análisis forense de redes
El análisis forense de redes es una rama del análisis forense digital que se ocupa del seguimiento y análisis del tráfico de redes informáticas, tanto locales como WAN (red de área amplia), con el fin de recopilar información, recopilar pruebas o detectar intrusiones. El análisis forense de redes juega un papel fundamental en la investigación de delitos digitales, como el robo de propiedad intelectual o la filtración de información. Una imagen de las comunicaciones de red ayuda a un investigador a resolver algunas preguntas cruciales de la siguiente manera:
¿A qué sitios web se ha accedido?
¿Qué tipo de contenido se ha subido a nuestra red?
¿Qué tipo de contenido se ha descargado de nuestra red?
¿A qué servidores se accede?
¿Alguien envía información confidencial fuera de los firewalls de la empresa?
Buscador de pruebas de Internet (IEF)
IEF es una herramienta forense digital para encontrar, analizar y presentar evidencia digital encontrada en diferentes medios digitales como computadoras, teléfonos inteligentes, tabletas, etc. Es muy popular y utilizada por miles de profesionales forenses.
Uso de IEF
Debido a su popularidad, IEF es utilizado en gran medida por profesionales forenses. Algunos de los usos de IEF son los siguientes:
Debido a sus poderosas capacidades de búsqueda, se utiliza para buscar múltiples archivos o medios de datos simultáneamente.
También se utiliza para recuperar datos eliminados del espacio no asignado de RAM a través de nuevas técnicas de talla.
Si los investigadores desean reconstruir las páginas web en su formato original en la fecha en que se abrieron, pueden usar IEF.
También se utiliza para buscar volúmenes de discos físicos o lógicos.
Volcado de informes de IEF a CSV usando Python
IEF almacena datos en una base de datos SQLite y el siguiente script de Python identificará dinámicamente las tablas de resultados dentro de la base de datos IEF y las volcará a los archivos CSV respectivos.
Este proceso se realiza en los pasos que se muestran a continuación
Primero, genere la base de datos de resultados IEF que será un archivo de base de datos SQLite que terminará con la extensión .db.
Luego, consulta esa base de datos para identificar todas las tablas.
Por último, escriba estas tablas de resultados en un archivo CSV individual.
Código Python
Veamos cómo usar el código Python para este propósito:
Para la secuencia de comandos de Python, importe las bibliotecas necesarias de la siguiente manera:
from __future__ import print_function
import argparse
import csv
import os
import sqlite3
import sys
Ahora, debemos proporcionar la ruta al archivo de base de datos 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()
Ahora, confirmaremos la existencia de la base de datos IEF de la siguiente manera:
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)
Ahora, como hicimos en scripts anteriores, realice la conexión con la base de datos SQLite de la siguiente manera para ejecutar las consultas a través del cursor:
def main(database, out_directory):
print("[+] Connecting to SQLite database")
conn = sqlite3.connect(database)
c = conn.cursor()
Las siguientes líneas de código obtendrán los nombres de las tablas de la base de datos:
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')]
Ahora, seleccionaremos todos los datos de la tabla y usando fetchall() en el objeto cursor almacenaremos la lista de tuplas que contienen los datos de la tabla en su totalidad en una variable -
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()
Ahora, usando CSV_Writer() método escribiremos el contenido en un archivo 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)
El script anterior obtendrá todos los datos de las tablas de la base de datos IEF y escribirá el contenido en el archivo CSV de nuestra elección.
Trabajar con datos en caché
De la base de datos de resultados de IEF, podemos obtener más información que no es necesariamente compatible con IEF. Podemos obtener los datos almacenados en caché, un producto bi para obtener información, de proveedores de servicios de correo electrónico como Yahoo, Google, etc., utilizando la base de datos de resultados IEF.
El siguiente es el script de Python para acceder a la información de datos en caché del correo de Yahoo, al que se accede en Google Chrome, mediante la base de datos IEF. Tenga en cuenta que los pasos serían más o menos los mismos que se siguieron en el último script de Python.
Primero, importe las bibliotecas necesarias para Python de la siguiente manera:
from __future__ import print_function
import argparse
import csv
import os
import sqlite3
import sys
import json
Ahora, proporcione la ruta al archivo de base de datos IEF junto con dos argumentos posicionales que acepta el controlador de línea de comandos como se hizo en el ú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()
Ahora, confirme la existencia de la base de datos IEF de la siguiente manera:
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)
Ahora, realice la conexión con la base de datos SQLite de la siguiente manera para ejecutar las consultas a través del cursor:
def main(database, out_csv):
print("[+] Connecting to SQLite database")
conn = sqlite3.connect(database)
c = conn.cursor()
Puede utilizar las siguientes líneas de código para obtener las instancias del registro de caché de contactos de 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)
Ahora, la lista de tuplas devuelta por la consulta anterior se guardará en una variable de la siguiente manera:
contact_cache = c.fetchall()
contact_data = process_contacts(contact_cache)
write_csv(contact_data, out_csv)
Tenga en cuenta que aquí utilizaremos dos métodos a saber process_contacts() para configurar la lista de resultados, así como para iterar a través de cada registro de caché de contacto y json.loads() para almacenar los datos JSON extraídos de la tabla en una variable para su posterior manipulación -
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"]])
Ahora, para la empresa, el título y las notas, el método de obtención se utiliza como se muestra a continuación:
company = c.get("company", "")
title = c.get("jobTitle", "")
notes = c.get("notes", "")
Ahora, agreguemos la lista de metadatos y elementos de datos extraídos a la lista de resultados de la siguiente manera:
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
Ahora, usando CSV_Writer() método, escribiremos el contenido en un archivo 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)
Con la ayuda del script anterior, podemos procesar los datos almacenados en caché del correo de Yahoo utilizando la base de datos IEF.
El capítulo anterior abordó algunos de los conceptos de análisis forense de redes utilizando Python. En este capítulo, comprendamos el análisis forense de redes usando Python a un nivel más profundo.
Preservación de la página web con Beautiful Soup
La World Wide Web (WWW) es un recurso de información único. Sin embargo, su legado corre un alto riesgo debido a la pérdida de contenido a un ritmo alarmante. Varias instituciones académicas y de patrimonio cultural, organizaciones sin fines de lucro y empresas privadas han explorado los problemas involucrados y han contribuido al desarrollo de soluciones técnicas para el archivo web.
La preservación de la página web o el archivo web es el proceso de recopilar los datos de la World Wide Web, garantizar que los datos se conserven en un archivo y ponerlos a disposición de futuros investigadores, historiadores y el público. Antes de continuar con la preservación de la página web, analicemos algunos temas importantes relacionados con la preservación de la página web como se indica a continuación:
Change in Web Resources - Los recursos web cambian todos los días, lo que supone un desafío para la preservación de la página web.
Large Quantity of Resources - Otro tema relacionado con la preservación de la página web es la gran cantidad de recursos que se desea preservar.
Integrity - Las páginas web deben estar protegidas de modificaciones, supresión o eliminación no autorizadas para proteger su integridad.
Dealing with multimedia data - Al preservar las páginas web, también debemos tratar con datos multimedia, y estos pueden causar problemas al hacerlo.
Providing access - Además de preservar, también debe resolverse el problema de proporcionar acceso a los recursos web y tratar los problemas de propiedad.
En este capítulo, usaremos la biblioteca de Python llamada Beautiful Soup para la conservación de la página web.
¿Qué es Beautiful Soup?
Beautiful Soup es una biblioteca de Python para extraer datos de archivos HTML y XML. Se puede utilizar conurlibporque necesita una entrada (documento o URL) para crear un objeto de sopa, ya que no puede recuperar la página web en sí. Puede obtener información detallada sobre esto en www.crummy.com/software/BeautifulSoup/bs4/doc/
Tenga en cuenta que antes de usarlo, debemos instalar una biblioteca de terceros usando el siguiente comando:
pip install bs4
A continuación, utilizando el administrador de paquetes Anaconda, podemos instalar Beautiful Soup de la siguiente manera:
conda install -c anaconda beautifulsoup4
Secuencia de comandos de Python para conservar páginas web
El script de Python para preservar páginas web mediante el uso de una biblioteca de terceros llamada Beautiful Soup se analiza aquí:
Primero, importe las bibliotecas necesarias de la siguiente manera:
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__)
Tenga en cuenta que este script tomará dos argumentos posicionales, uno es la URL que se debe conservar y el otro es el directorio de salida deseado, como se muestra a continuación:
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()
Ahora, configure el registro para el script especificando un archivo y un controlador de flujo para estar en bucle y documente el proceso de adquisición como se muestra:
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)
Ahora, hagamos la validación de entrada en el directorio de salida deseado de la siguiente manera:
if not os.path.exists(args.OUTPUT_DIR):
os.makedirs(args.OUTPUT_DIR)
main(args.DOMAIN, args.OUTPUT_DIR)
Ahora, definiremos el main() función que extraerá el nombre base del sitio web eliminando los elementos innecesarios antes del nombre real junto con una validación adicional en la URL de entrada de la siguiente manera:
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()
Ahora, necesitamos abrir una conexión con la URL usando el método urlopen (). Usemos el bloque try-except de la siguiente manera:
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))
Las siguientes líneas de código incluyen tres funciones como se explica a continuación:
write_output() para escribir la primera página web en el directorio de salida
find_links() función para identificar los enlaces en esta página web
recurse_pages() función para recorrer y descubrir todos los enlaces en la página 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))
Ahora, definamos write_output() método de la siguiente manera -
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)
Necesitamos registrar algunos detalles sobre la página web y luego registramos el hash de los datos usando hash_data() método de la siguiente manera -
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)))
Ahora, define hash_data() método con la ayuda del cual leemos el UTF-8 datos codificados y luego generar el SHA-256 hash de la siguiente manera:
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()
Ahora, creemos un Beautifulsoup objeto fuera de los datos de la página web bajo find_links() método de la siguiente manera -
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
Ahora, necesitamos definir recurse_pages() método proporcionándole las entradas de la URL del sitio web, la cola de enlaces actual, el contexto SSL no verificado y el directorio de salida de la siguiente manera:
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
Ahora, escriba la salida de cada página web a la que se accede en un archivo pasando el nombre del enlace, los datos de la página, el directorio de salida y el contador de la siguiente manera:
write_output(link, page, output_dir, counter)
queue = find_links(website, page, queue)
logger.info("Identified {} links throughout website".format(
len(queue)))
Ahora, cuando ejecutamos este script proporcionando la URL del sitio web, el directorio de salida y una ruta al archivo de registro, obtendremos los detalles sobre esa página web que se puede utilizar para uso futuro.
Caza de virus
¿Alguna vez se ha preguntado cómo los analistas forenses, los investigadores de seguridad y los encuestados de incidentes pueden comprender la diferencia entre software útil y malware? La respuesta está en la pregunta en sí, porque sin estudiar el malware, generado rápidamente por los piratas informáticos, es bastante imposible para los investigadores y especialistas distinguir entre software útil y malware. En esta sección, hablemos sobreVirusShare, una herramienta para realizar esta tarea.
Comprensión de VirusShare
VirusShare es la colección privada más grande de muestras de malware para proporcionar a los investigadores de seguridad, personal de respuesta a incidentes y analistas forenses las muestras de código malicioso activo. Contiene más de 30 millones de muestras.
El beneficio de VirusShare es la lista de hashes de malware que está disponible gratuitamente. Cualquiera puede usar estos hash para crear un conjunto de hash muy completo y usarlo para identificar archivos potencialmente maliciosos. Pero antes de usar VirusShare, le sugerimos que visitehttps://virusshare.com para más detalles.
Creación de una lista hash delimitada por líneas nuevas desde VirusShare con Python
Varias herramientas forenses como X-way y EnCase pueden utilizar una lista hash de VirusShare. En el script que se describe a continuación, automatizaremos la descarga de listas de hash de VirusShare para crear una lista de hash delimitada por líneas nuevas.
Para este script, necesitamos una biblioteca Python de terceros. tqdm que se puede descargar de la siguiente manera:
pip install tqdm
Tenga en cuenta que en este script, primero leeremos la página de hash de VirusShare e identificaremos dinámicamente la lista de hash más reciente. Luego inicializaremos la barra de progreso y descargaremos la lista hash en el rango deseado.
Primero, importe las siguientes 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 tomará un argumento posicional, que sería la ruta deseada para el conjunto de 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()
Ahora, realizaremos la validación de entrada estándar de la siguiente manera:
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)
Ahora necesitamos definir main() funcionar con **kwargs como argumento porque esto creará un diccionario al que podemos hacer referencia para admitir los argumentos clave suministrados como se muestra a continuación:
def main(hashset, **kwargs):
url = "https://virusshare.com/hashes.4n6"
print("[+] Identifying hash set range from {}".format(url))
context = ssl._create_unverified_context()
Ahora, debemos abrir la página de hashes de VirusShare usando urlib.request.urlopen()método. Usaremos el bloque try-except de la siguiente manera:
try:
index = urlopen(url, context = context).read().decode("utf-8")
except urllib.error.HTTPError as e:
print("[-] Error accessing webpage - exiting..")
sys.exit(1)
Ahora, identifique la última lista hash de las páginas descargadas. Puede hacer esto buscando la última instancia del HTMLhrefetiqueta a la lista hash de VirusShare. Se puede hacer con las siguientes líneas 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
Ahora usaremos tqdm.trange() método para crear un bucle y una barra de progreso de la siguiente manera:
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
Después de realizar los pasos anteriores con éxito, abriremos el archivo de texto del conjunto de hash en un modo + para agregarlo al final del archivo 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))
Después de ejecutar el script anterior, obtendrá la última lista de hash que contiene los valores de hash MD5 en formato de texto.
Los capítulos anteriores discutieron sobre la importancia y el proceso de análisis forense de redes y los conceptos involucrados. En este capítulo, aprendamos sobre el papel de los correos electrónicos en el análisis forense digital y su investigación con Python.
Papel del correo electrónico en la investigación
Los correos electrónicos juegan un papel muy importante en las comunicaciones comerciales y se han convertido en una de las aplicaciones más importantes de Internet. Son un modo conveniente para enviar mensajes y documentos, no solo desde computadoras sino también desde otros dispositivos electrónicos como teléfonos móviles y tabletas.
El lado negativo de los correos electrónicos es que los delincuentes pueden filtrar información importante sobre su empresa. Por lo tanto, el papel de los correos electrónicos en la ciencia forense digital se ha incrementado en los últimos años. En la ciencia forense digital, los correos electrónicos se consideran evidencias cruciales y el análisis de encabezado de correo electrónico se ha vuelto importante para recopilar evidencia durante el proceso forense.
Un investigador tiene los siguientes objetivos al realizar análisis forenses de correo electrónico:
- Identificar al principal criminal
- Recoger las evidencias necesarias
- Presentar los hallazgos
- Para construir el caso
Desafíos en el análisis forense del correo electrónico
El análisis forense del correo electrónico juega un papel muy importante en la investigación, ya que la mayor parte de la comunicación en la era actual se basa en los correos electrónicos. Sin embargo, un investigador forense de correo electrónico puede enfrentar los siguientes desafíos durante la investigación:
Correos electrónicos falsos
El mayor desafío en el análisis forense del correo electrónico es el uso de correos electrónicos falsos que se crean manipulando y escribiendo encabezados, etc. En esta categoría, los delincuentes también usan el correo electrónico temporal, que es un servicio que permite a un usuario registrado recibir correo electrónico en una dirección temporal que caduca. después de un cierto período de tiempo.
Spoofing
Otro desafío en el análisis forense del correo electrónico es la suplantación de identidad, en la que los delincuentes solían presentar un correo electrónico como el de otra persona. En este caso, la máquina recibirá una dirección IP tanto falsa como original.
Reenvío anónimo
Aquí, el servidor de correo electrónico elimina la información de identificación del mensaje de correo electrónico antes de reenviarlo. Esto conduce a otro gran desafío para las investigaciones de correo electrónico.
Técnicas utilizadas en la investigación forense de correo electrónico
El análisis forense del correo electrónico es el estudio de la fuente y el contenido del correo electrónico como prueba para identificar al remitente y al destinatario reales de un mensaje junto con otra información, como la fecha / hora de transmisión y la intención del remitente. Implica investigar metadatos, escaneo de puertos y búsqueda de palabras clave.
Algunas de las técnicas comunes que se pueden utilizar para la investigación forense del correo electrónico son
- Análisis de encabezado
- Investigación del servidor
- Investigación de dispositivos de red
- Huellas digitales del remitente
- Identificadores integrados de software
En las siguientes secciones, aprenderemos cómo obtener información usando Python con el propósito de investigar el correo electrónico.
Extracción de información de archivos EML
Los archivos EML son básicamente correos electrónicos en formato de archivo que se utilizan ampliamente para almacenar mensajes de correo electrónico. Son archivos de texto estructurados que son compatibles con varios clientes de correo electrónico, como Microsoft Outlook, Outlook Express y Windows Live Mail.
Un archivo EML almacena encabezados de correo electrónico, contenido del cuerpo y datos adjuntos como texto sin formato. Utiliza base64 para codificar datos binarios y codificación Quoted-Printable (QP) para almacenar información de contenido. El script de Python que se puede utilizar para extraer información del archivo EML se proporciona a continuación:
Primero, importe las siguientes bibliotecas de Python como se muestra a continuación:
from __future__ import print_function
from argparse import ArgumentParser, FileType
from email import message_from_file
import os
import quopri
import base64
En las bibliotecas anteriores, quoprise utiliza para decodificar los valores codificados QP de archivos EML. Cualquier dato codificado en base64 se puede decodificar con la ayuda debase64 biblioteca.
A continuación, proporcionemos un argumento para el controlador de línea de comandos. Tenga en cuenta que aquí solo aceptará un argumento que sería la ruta al archivo EML como se muestra a continuación:
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)
Ahora, necesitamos definir main() función en la que usaremos el método llamado message_from_file()de la biblioteca de correo electrónico para leer el archivo como objeto. Aquí accederemos a los encabezados, el contenido del cuerpo, los archivos adjuntos y otra información de carga útil mediante el uso de la variable resultante denominadaemlfile como se muestra en el código que figura a continuación -
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])
Ahora, necesitamos definir process_payload() método en el que extraeremos el contenido del cuerpo del mensaje usando get_payload()método. Decodificaremos datos codificados QP usandoquopri.decodestring()función. También comprobaremos el tipo de contenido MIME para que pueda manejar correctamente el almacenamiento del correo electrónico. Observe el código que se proporciona a continuación:
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)
Después de ejecutar el script anterior, obtendremos la información del encabezado junto con varias cargas útiles en la consola.
Analizando archivos MSG usando Python
Los mensajes de correo electrónico vienen en muchos formatos diferentes. MSG es uno de esos formatos utilizados por Microsoft Outlook y Exchange. Los archivos con la extensión MSG pueden contener texto ASCII sin formato para los encabezados y el cuerpo del mensaje principal, así como hipervínculos y archivos adjuntos.
En esta sección, aprenderemos cómo extraer información del archivo MSG usando la API de Outlook. Tenga en cuenta que la siguiente secuencia de comandos de Python solo funcionará en Windows. Para esto, necesitamos instalar la biblioteca Python de terceros llamadapywin32 como sigue -
pip install pywin32
Ahora, importe las siguientes bibliotecas usando los comandos que se muestran:
from __future__ import print_function
from argparse import ArgumentParser
import os
import win32com.client
import pywintypes
Ahora, proporcionemos un argumento para el controlador de línea de comandos. Aquí aceptará dos argumentos, uno sería la ruta al archivo MSG y el otro sería la carpeta de salida deseada de la siguiente manera:
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)
Ahora, necesitamos definir main() función en la que llamaremos win32com biblioteca para configurar Outlook API que además permite el acceso a la MAPI espacio de nombres.
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)
Ahora, defina las diferentes funciones que estamos usando en este script. El código que se proporciona a continuación muestra la definición dedisplay_msg_attribs() función que nos permite mostrar varios atributos de un mensaje como asunto, a, BCC, CC, Tamaño, SenderName, 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')))
Ahora, defina el display_msg_recipeints() función que recorre en iteración los mensajes y muestra los detalles del destinatario.
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
A continuación, definimos extract_msg_body() función que extrae el contenido del cuerpo, HTML y texto sin formato, del mensaje.
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 continuación, definiremos el extract_attachments() función que exporta datos adjuntos al directorio de salida deseado.
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
Una vez definidas todas las funciones, imprimiremos todos los atributos a la consola con la siguiente línea 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
Después de ejecutar el script anterior, obtendremos los atributos del mensaje y sus archivos adjuntos en la ventana de la consola junto con varios archivos en el directorio de salida.
Estructurar archivos MBOX de Google Takeout usando Python
Los archivos MBOX son archivos de texto con un formato especial en el que se dividen los mensajes almacenados. A menudo se encuentran asociados con sistemas UNIX, Thunderbolt y Google Takeouts.
En esta sección, verá un script de Python, donde estructuraremos los archivos MBOX obtenidos de Google Takeouts. Pero antes de eso debemos saber cómo podemos generar estos archivos MBOX usando nuestra cuenta de Google o cuenta de Gmail.
Adquirir el buzón de la cuenta de Google en formato MBX
Adquirir el buzón de la cuenta de Google implica realizar una copia de seguridad de nuestra cuenta de Gmail. La copia de seguridad se puede realizar por diversas razones personales o profesionales. Tenga en cuenta que Google proporciona una copia de seguridad de los datos de Gmail. Para adquirir el buzón de nuestra cuenta de Google en formato MBOX, debe seguir los pasos que se indican a continuación:
Abierto My account tablero.
Vaya a la sección Información personal y privacidad y seleccione Controlar el enlace de su contenido.
Puede crear un nuevo archivo o administrar uno existente. Si hacemos clic,CREATE ARCHIVE enlace, luego obtendremos algunas casillas de verificación para cada producto de Google que deseamos incluir.
Después de seleccionar los productos, tendremos la libertad de elegir el tipo de archivo y el tamaño máximo para nuestro archivo junto con el método de entrega para seleccionar de la lista.
Finalmente, obtendremos esta copia de seguridad en formato MBOX.
Código Python
Ahora, el archivo MBOX discutido anteriormente se puede estructurar usando Python como se muestra a continuación:
Primero, es necesario importar las bibliotecas de Python de la siguiente manera:
from __future__ import print_function
from argparse import ArgumentParser
import mailbox
import os
import time
import csv
from tqdm import tqdm
import base64
Todas las bibliotecas se han utilizado y explicado en scripts anteriores, excepto el mailbox biblioteca que se utiliza para analizar archivos MBOX.
Ahora, proporcione un argumento para el controlador de línea de comandos. Aquí aceptará dos argumentos: uno sería la ruta al archivo MBOX y el otro sería la carpeta de salida deseada.
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)
Ahora, definirá main() función y llamada mbox clase de biblioteca de buzones con la ayuda de la cual podemos analizar un archivo MBOX proporcionando su ruta -
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)))
Ahora, defina un método de lectura para mailbox biblioteca de la siguiente manera:
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)
Ahora, cree algunas variables para su posterior procesamiento de la siguiente manera:
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"]
A continuación, use tqdm para generar una barra de progreso y realizar un seguimiento del proceso de iteración de la siguiente manera:
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")
Ahora, compruebe si el mensaje meteorológico tiene cargas útiles o no. Si es tener entonces definiremoswrite_payload() método de la siguiente manera -
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)
Ahora, se deben agregar datos. Entonces llamaremoscreate_report() método de la siguiente manera -
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 las declaraciones if-else anteriores son fáciles de entender. Ahora, necesitamos definir un método que extraerá el nombre del archivo de lamsg objeto de la siguiente manera:
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)
Ahora, con la ayuda de las siguientes líneas de código, puede exportar el archivo:
if isinstance(content_data, str):
open(file_name, 'w').write(content_data)
else:
open(file_name, 'wb').write(content_data)
return file_name
Ahora, definamos una función para extraer nombres de archivos de la message para representar con precisión los nombres de estos archivos de la siguiente manera:
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)
Ahora, podemos escribir un archivo CSV definiendo el create_report() funciona de la siguiente manera:
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)
Una vez que ejecute el script proporcionado anteriormente, obtendremos el informe CSV y el directorio lleno de archivos adjuntos.
Este capítulo explicará varios conceptos relacionados con la ciencia forense de Microsoft Windows y los artefactos importantes que un investigador puede obtener del proceso de investigación.
Introducción
Los artefactos son los objetos o áreas dentro de un sistema informático que tienen información importante relacionada con las actividades realizadas por el usuario de la computadora. El tipo y la ubicación de esta información dependen del sistema operativo. Durante el análisis forense, estos artefactos juegan un papel muy importante en aprobar o desaprobar la observación del investigador.
Importancia de los artefactos de Windows para la ciencia forense
Los artefactos de Windows adquieren importancia debido a las siguientes razones:
Alrededor del 90% del tráfico mundial proviene de las computadoras que utilizan Windows como sistema operativo. Es por eso que para los examinadores forenses digitales, los artefactos de Windows son muy esenciales.
El sistema operativo Windows almacena diferentes tipos de evidencias relacionadas con la actividad del usuario en el sistema informático. Esta es otra razón que muestra la importancia de los artefactos de Windows para el análisis forense digital.
Muchas veces, el investigador gira la investigación en torno a áreas antiguas y tradicionales como los datos almacenados por el usuario. Los artefactos de Windows pueden llevar la investigación hacia áreas no tradicionales como los datos creados por el sistema o los artefactos.
Windows proporciona una gran cantidad de artefactos que son útiles para los investigadores, así como para las empresas y las personas que realizan investigaciones informales.
El aumento de los delitos cibernéticos en los últimos años es otra razón por la que los artefactos de Windows son importantes.
Artefactos de Windows y sus scripts de Python
En esta sección, analizaremos algunos artefactos de Windows y scripts de Python para obtener información de ellos.
Papelera de reciclaje
Es uno de los artefactos importantes de Windows para la investigación forense. La papelera de reciclaje de Windows contiene los archivos que el usuario ha eliminado, pero que el sistema aún no los ha eliminado físicamente. Incluso si el usuario elimina completamente el archivo del sistema, sirve como una fuente importante de investigación. Esto se debe a que el examinador puede extraer información valiosa, como la ruta del archivo original, así como la hora en que se envió a la Papelera de reciclaje, de los archivos eliminados.
Tenga en cuenta que el almacenamiento de la evidencia de la Papelera de reciclaje depende de la versión de Windows. En el siguiente script de Python, vamos a tratar con Windows 7 donde crea dos archivos:$R archivo que contiene el contenido real del archivo reciclado y $I archivo que contiene el nombre del archivo original, la ruta y el tamaño del archivo cuando se eliminó el archivo.
Para el script de Python, necesitamos instalar módulos de terceros, a saber pytsk3, pyewf y unicodecsv. Nosotros podemos usarpippara instalarlos. Podemos seguir los siguientes pasos para extraer información de la Papelera de reciclaje:
Primero, necesitamos usar un método recursivo para escanear a través del $Recycle.bin carpeta y seleccione todos los archivos que comienzan con $I.
A continuación, leeremos el contenido de los archivos y analizaremos las estructuras de metadatos disponibles.
Ahora, buscaremos el archivo $ R asociado.
Por último, escribiremos los resultados en un archivo CSV para su revisión.
Veamos cómo usar el código Python para este propósito:
Primero, necesitamos importar las siguientes bibliotecas de 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
A continuación, debemos proporcionar un argumento para el controlador de línea de comandos. Tenga en cuenta que aquí aceptará tres argumentos: primero es la ruta al archivo de evidencia, segundo es el tipo de archivo de evidencia y tercero es la ruta de salida deseada para el informe CSV, como se muestra a continuación:
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)
Ahora, defina el main()función que se encargará de todo el procesamiento. Buscará$I archivo de la siguiente manera:
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")
Ahora, si encontramos $I archivo, luego debe enviarse a process_dollar_i() función que aceptará el tsk_util objeto, así como la lista de $I archivos, como se muestra a continuación -
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:])
Ahora, busque archivos $ R de la siguiente manera:
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
Ahora, define read_dollar_i() método para leer el $Iarchivos, en otras palabras, analizar los metadatos. Usaremosread_random()método para leer los primeros ocho bytes de la firma. Esto devolverá ninguno si la firma no coincide. Después de eso, tendremos que leer y descomprimir los valores de$I archivo si es un archivo 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)
Ahora, después de extraer estos archivos, necesitamos interpretar los números enteros en valores legibles por humanos usando sizeof_fmt() funciona como se muestra a continuación -
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}
Ahora, necesitamos definir sizeof_fmt() funciona de la siguiente manera:
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)
Ahora, defina una función para enteros interpretados en fecha y hora formateadas de la siguiente manera:
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')
Ahora, definiremos write_csv() método para escribir los resultados procesados en un archivo CSV de la siguiente manera:
def write_csv(outfile, fieldnames, data):
with open(outfile, 'wb') as open_outfile:
csvfile = csv.DictWriter(open_outfile, fieldnames)
csvfile.writeheader()
csvfile.writerows(data)
Cuando ejecute el script anterior, obtendremos los datos del archivo $ I y $ R.
Notas adhesivas
Windows Sticky Notes reemplaza el hábito real de escribir con lápiz y papel. Estas notas solían flotar en el escritorio con diferentes opciones de colores, fuentes, etc. En Windows 7, el archivo Sticky Notes se almacena como un archivo OLE, por lo que en el siguiente script de Python investigaremos este archivo OLE para extraer metadatos de Sticky Notes.
Para este script de Python, necesitamos instalar módulos de terceros, a saber olefile, pytsk3, pyewfy unicodecsv. Podemos usar el comandopip para instalarlos.
Podemos seguir los pasos que se describen a continuación para extraer la información del archivo de notas adhesivas, a saber: StickyNote.sn -
En primer lugar, abra el archivo de pruebas y busque todos los archivos StickyNote.snt.
Luego, analice los metadatos y el contenido de la secuencia OLE y escriba el contenido RTF en los archivos.
Por último, cree un informe CSV de estos metadatos.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de 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
A continuación, defina una variable global que se utilizará en este script:
REPORT_COLS = ['note_id', 'created', 'modified', 'note_text', 'note_file']
A continuación, debemos proporcionar un argumento para el controlador de línea de comandos. Tenga en cuenta que aquí aceptará tres argumentos: primero es la ruta al archivo de evidencia, segundo es el tipo de archivo de evidencia y tercero es la ruta de salida deseada de la siguiente manera:
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)
Ahora, definiremos main() función que será similar a la secuencia de comandos anterior como se muestra a continuación -
def main(evidence, image_type, report_folder):
tsk_util = TSKUtil(evidence, image_type)
note_files = tsk_util.recurse_files('StickyNotes.snt', '/Users','equals')
Ahora, iteremos por los archivos resultantes. Entonces llamaremosparse_snt_file() función para procesar el archivo y luego escribiremos el archivo RTF con el write_note_rtf() método de la siguiente manera -
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 continuación, necesitamos definir varias funciones utilizadas en este script.
Primero que nada definiremos create_file_like_obj() función para leer el tamaño del archivo tomando pytskobjeto de archivo. Entonces definiremosparse_snt_file() función que aceptará el objeto similar a un archivo como su entrada y se utiliza para leer e interpretar el archivo de notas adhesivas.
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
Ahora, cree un archivo RTF definiendo write_note_rtf() funciona de la siguiente manera
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'])
Ahora, traduciremos el diccionario anidado en una lista plana de diccionarios que son más apropiados para una hoja de cálculo CSV. Se hará definiendoprep_note_report()función. Por último, definiremoswrite_csv() función.
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)
Después de ejecutar el script anterior, obtendremos los metadatos del archivo Sticky Notes.
Archivos de registro
Los archivos de registro de Windows contienen muchos detalles importantes que son como un tesoro de información para un analista forense. Es una base de datos jerárquica que contiene detalles relacionados con la configuración del sistema operativo, la actividad del usuario, la instalación de software, etc. En el siguiente script de Python vamos a acceder a información de línea de base común delSYSTEM y SOFTWARE urticaria.
Para este script de Python, necesitamos instalar módulos de terceros, a saber pytsk3, pyewf y registry. Nosotros podemos usarpip para instalarlos.
Podemos seguir los pasos que se indican a continuación para extraer la información del registro de Windows:
Primero, busque las secciones del registro para procesarlas por su nombre y por su ruta.
Luego, abrimos estos archivos usando los módulos StringIO y Registry.
Por último, necesitamos procesar todas y cada una de las colmenas e imprimir los valores analizados en la consola para su interpretación.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de 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
Ahora, proporcione un argumento para el controlador de la línea de comandos. Aquí aceptará dos argumentos - primero es la ruta al archivo de evidencia, segundo es el tipo de archivo de evidencia, como se muestra a continuación -
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)
Ahora definiremos main() función para buscar SYSTEM y SOFTWARE urticaria dentro /Windows/System32/config carpeta de la siguiente manera:
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)
Ahora, defina la función para abrir el archivo de registro. Para este propósito, necesitamos recopilar el tamaño del archivo depytsk metadatos de la siguiente manera:
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)
Ahora, con la ayuda del siguiente método, podemos procesar SYSTEM> colmena -
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))
Ahora, necesitamos definir una función para enteros interpretados en fecha y hora formateadas de la siguiente manera:
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')
Ahora, con la ayuda del siguiente método, podemos procesar SOFTWARE colmena -
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))
Después de ejecutar el script anterior, obtendremos los metadatos almacenados en los archivos del Registro de Windows.
Este capítulo habla sobre algunos artefactos más importantes en Windows y su método de extracción usando Python.
Actividades del usuario
Windows que tiene NTUSER.DATarchivo para almacenar diversas actividades del usuario. Cada perfil de usuario tiene colmena comoNTUSER.DAT, que almacena la información y configuraciones relacionadas específicamente con ese usuario. Por lo tanto, es muy útil para fines de investigación por parte de analistas forenses.
La siguiente secuencia de comandos de Python analizará algunas de las claves de NTUSER.DATpara explorar las acciones de un usuario en el sistema. Antes de continuar, para el script Python, necesitamos instalar módulos de terceros, a saberRegistry, pytsk3, pyewf y Jinja2. Podemos usar pip para instalarlos.
Podemos seguir los siguientes pasos para extraer información de NTUSER.DAT archivo -
Primero, busque todo NTUSER.DAT archivos en el sistema.
Luego analice el WordWheelQuery, TypePath and RunMRU clave para cada NTUSER.DAT archivo.
Por último, escribiremos estos artefactos, ya procesados, en un informe HTML utilizando Jinja2 fmodule.
Código Python
Veamos cómo usar el código Python para este propósito:
En primer lugar, necesitamos importar los siguientes módulos de 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
Ahora, proporcione un argumento para el controlador de línea de comandos. Aquí aceptará tres argumentos: el primero es la ruta al archivo de evidencia, el segundo es el tipo de archivo de evidencia y el tercero es la ruta de salida deseada para el informe HTML, como se muestra a continuación.
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)
Ahora, definamos main() función para buscar todo NTUSER.DAT archivos, como se muestra -
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'}
}
Ahora, intentaremos encontrar la clave en NTUSER.DAT archivo y una vez que lo encuentre, defina las funciones de procesamiento del usuario como se muestra a continuación:
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()
Ahora, pase el objeto de diccionario y su ruta a write_html() método de la siguiente manera -
write_html(report, nt_rec)
Ahora, defina un método que requiera pytsk identificador de archivo y léalo en la clase Registry a través del StringIO clase.
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)
Ahora, definiremos la función que analizará y manejará WordWheelQuery clave de NTUSER.DAT archivo de la siguiente manera:
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
Ahora, definiremos la función que analizará y manejará TypedPaths clave de NTUSER.DAT archivo de la siguiente manera:
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
Ahora, definiremos la función que analizará y manejará RunMRU clave de NTUSER.DAT archivo de la siguiente manera:
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
Ahora, la siguiente función manejará la creación del informe 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 fin podemos escribir un documento HTML para un informe. Después de ejecutar el script anterior, obtendremos la información del archivo NTUSER.DAT en formato de documento HTML.
LINK archivos
Los archivos de accesos directos se crean cuando un usuario o el sistema operativo crea archivos de acceso directo para los archivos que se utilizan con frecuencia, se hace doble clic o se accede a ellos desde las unidades del sistema, como el almacenamiento adjunto. Estos tipos de archivos de acceso directo se denominan archivos de enlace. Al acceder a estos archivos de enlace, un investigador puede encontrar la actividad de la ventana, como la hora y la ubicación desde donde se ha accedido a estos archivos.
Analicemos el script de Python que podemos usar para obtener la información de estos archivos LINK de Windows.
Para el script Python, instale módulos de terceros, a saber pylnk, pytsk3, pyewf. Podemos seguir los siguientes pasos para extraer información delnk archivos
Primero, busque lnk archivos dentro del sistema.
Luego, extraiga la información de ese archivo iterando a través de ellos.
Ahora, por fin necesitamos esta información para un informe CSV.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de Python:
from __future__ import print_function
from argparse import ArgumentParser
import csv
import StringIO
from utility.pytskutil import TSKUtil
import pylnk
Ahora, proporcione el argumento para el controlador de línea de comandos. Aquí aceptará tres argumentos: el primero es la ruta al archivo de evidencia, el segundo es el tipo de archivo de evidencia y el tercero es la ruta de salida deseada para el informe CSV, como se muestra a continuación.
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)
Ahora, interprete el archivo de evidencia creando un objeto de TSKUtil e iterar a través del sistema de archivos para encontrar archivos que terminen con lnk. Se puede hacer definiendomain() funciona de la siguiente manera:
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'
]
Ahora, con la ayuda del siguiente código, iteraremos a través de lnk archivos creando una función de la siguiente manera:
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)
Ahora necesitamos definir dos funciones, una abrirá el pytsk El objeto de archivo y otros se utilizarán para escribir el informe CSV como se muestra a continuación:
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)
Después de ejecutar el script anterior, obtendremos la información del descubrimiento lnk archivos en un informe CSV -
Precargar archivos
Siempre que una aplicación se ejecuta por primera vez desde una ubicación específica, Windows crea prefetch files. Se utilizan para acelerar el proceso de inicio de la aplicación. La extensión de estos archivos es.PF y estos se almacenan en el ”\Root\Windows\Prefetch” carpeta.
Los expertos forenses digitales pueden revelar la evidencia de la ejecución del programa desde una ubicación específica junto con los detalles del usuario. Los archivos de captación previa son artefactos útiles para el examinador porque su entrada permanece incluso después de que el programa se haya eliminado o desinstalado.
Analicemos el script de Python que obtendrá información de los archivos de recuperación previa de Windows como se indica a continuación:
Para el script Python, instale módulos de terceros, a saber pylnk, pytsk3 y unicodecsv. Recuerde que ya hemos trabajado con estas bibliotecas en los scripts de Python que hemos comentado en los capítulos anteriores.
Tenemos que seguir los pasos que se indican a continuación para extraer información de prefetch archivos -
Primero, busque .pf archivos de extensión o los archivos de captación previa.
Ahora, realice la verificación de la firma para eliminar falsos positivos.
A continuación, analice el formato de archivo de captación previa de Windows. Esto difiere con la versión de Windows. Por ejemplo, para Windows XP es 17, para Windows Vista y Windows 7 es 23, 26 para Windows 8.1 y 30 para Windows 10.
Por último, escribiremos el resultado analizado en un archivo CSV.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de 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
Ahora, proporcione un argumento para el controlador de línea de comandos. Aquí aceptará dos argumentos, el primero sería la ruta al archivo de pruebas y el segundo sería el tipo de archivo de pruebas. También acepta un argumento opcional para especificar la ruta para buscar archivos de captación previa:
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)
Ahora, interprete el archivo de evidencia creando un objeto de TSKUtil e iterar a través del sistema de archivos para encontrar archivos que terminen con .pf. Se puede hacer definiendomain() funciona de la siguiente manera:
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)
Ahora, defina un método que hará la validación de firmas como se muestra a continuación:
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)
Ahora, comience a procesar archivos de recuperación previa de Windows. Aquí tomamos el ejemplo de los archivos de captación previa de 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)
Ahora, extraiga los datos incrustados dentro de los archivos precargados usando la estructura de la siguiente manera:
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 hemos proporcionado la versión de búsqueda previa para Windows XP, pero ¿qué pasa si encuentra versiones de recuperación previa para otros Windows? Entonces debe mostrar un mensaje de error de la siguiente manera:
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)
Ahora, defina el método para escribir el resultado en el informe CSV de la siguiente manera:
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)
Después de ejecutar el script anterior, obtendremos la información de los archivos de recuperación previa de la versión de Windows XP en una hoja de cálculo.
Este capítulo explicará otros artefactos que un investigador puede obtener durante el análisis forense en Windows.
Registros de eventos
Los archivos de registro de eventos de Windows, como sugerencias de nombre, son archivos especiales que almacenan eventos importantes como cuando el usuario inicia sesión en la computadora, cuando un programa encuentra un error, sobre cambios en el sistema, acceso RDP, eventos específicos de la aplicación, etc. Los investigadores cibernéticos siempre están interesados en eventos información de registro porque proporciona mucha información histórica útil sobre el acceso al sistema. En el siguiente script de Python vamos a procesar los formatos de registro de eventos de Windows actuales y heredados.
Para el script de Python, necesitamos instalar módulos de terceros, a saber pytsk3, pyewf, unicodecsv, pyevt and pyevtX. Podemos seguir los pasos que se indican a continuación para extraer información de los registros de eventos:
Primero, busque todos los registros de eventos que coincidan con el argumento de entrada.
Luego, realice la verificación de la firma del archivo.
Ahora, procese cada registro de eventos que encuentre con la biblioteca adecuada.
Por último, escriba el resultado en una hoja de cálculo.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de 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
Ahora, proporcione los argumentos para el controlador de línea de comandos. Tenga en cuenta que aquí aceptará tres argumentos: el primero es la ruta al archivo de evidencia, el segundo es el tipo de archivo de evidencia y el tercero es el nombre del registro de eventos a procesar.
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)
Ahora, interactúe con los registros de eventos para consultar la existencia de la ruta proporcionada por el usuario creando nuestro TSKUtilobjeto. Se puede hacer con la ayuda demain() método de la siguiente manera -
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)
Ahora, necesitamos realizar la verificación de la firma y luego definir un método que escribirá todo el contenido en el directorio actual:
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 un método para escribir la salida en una hoja de cálculo de la siguiente manera:
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)
Una vez que ejecute con éxito el script anterior, obtendremos la información del registro de eventos en la hoja de cálculo.
Historia de Internet
El historial de Internet es muy útil para los analistas forenses; ya que la mayoría de los delitos cibernéticos ocurren únicamente a través de Internet. Veamos cómo extraer el historial de Internet de Internet Explorer, ya que discutimos sobre el análisis forense de Windows, e Internet Explorer viene por defecto con Windows.
En Internet Explorer, el historial de Internet se guarda en index.datarchivo. Veamos un script de Python, que extraerá la información deindex.dat archivo.
Podemos seguir los pasos que se indican a continuación para extraer información de index.dat archivos -
Primero, busque index.dat archivos dentro del sistema.
Luego, extraiga la información de ese archivo iterando a través de ellos.
Ahora, escriba toda esta información en un informe CSV.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de 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
Ahora, proporcione argumentos para el controlador de línea de comandos. Tenga en cuenta que aquí aceptará dos argumentos: el primero sería la ruta al archivo de pruebas y el segundo sería el tipo de archivo de pruebas.
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)
Ahora, interprete el archivo de evidencia creando un objeto de TSKUtile iterar a través del sistema de archivos para encontrar archivos index.dat. Se puede hacer definiendo elmain() funciona de la siguiente manera:
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)
Ahora, defina una función con la ayuda de la cual podemos copiar la información del archivo index.dat al directorio de trabajo actual y luego pueden ser procesados por un módulo de terceros:
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
Ahora, use el siguiente código para realizar la validación de la firma con la ayuda de la función incorporada 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)
Ahora, defina un método que imprima la salida en un archivo CSV, como se muestra a continuación:
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)
Después de ejecutar el script anterior, obtendremos la información del archivo index.dat en el archivo CSV.
Copias de sombra de volumen
Una instantánea es la tecnología incluida en Windows para realizar copias de seguridad o instantáneas de archivos de computadora de forma manual o automática. También se denomina servicio de instantáneas de volumen o servicio de sombra de volumen (VSS).
Con la ayuda de estos archivos VSS, los expertos forenses pueden tener información histórica sobre cómo cambió el sistema con el tiempo y qué archivos existían en la computadora. La tecnología de instantáneas requiere que el sistema de archivos sea NTFS para crear y almacenar instantáneas.
En esta sección, veremos un script de Python, que ayuda a acceder a cualquier volumen de instantáneas presentes en la imagen forense.
Para el script de Python, necesitamos instalar módulos de terceros, a saber pytsk3, pyewf, unicodecsv, pyvshadow y vss. Podemos seguir los pasos que se indican a continuación para extraer información de los archivos VSS
Primero, acceda al volumen de la imagen sin procesar e identifique todas las particiones NTFS.
Luego, extraiga la información de esas instantáneas repitiéndolas.
Ahora, por fin necesitamos crear una lista de archivos de datos dentro de las instantáneas.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe las siguientes bibliotecas de 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
Ahora, proporcione argumentos para el controlador de línea de comandos. Aquí aceptará dos argumentos: el primero es la ruta al archivo de evidencia y el segundo es el archivo de salida.
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()
Ahora, valide la existencia de la ruta del archivo de entrada y también separe el directorio del archivo de salida.
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)
Ahora, interactúe con el volumen del archivo de evidencia creando el TSKUtilobjeto. Se puede hacer con la ayuda demain() método de la siguiente manera -
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)
Ahora, defina un método para explorar el archivo de sombra de volumen analizado de la siguiente manera:
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 el método para escribir el resultado en una hoja de cálculo de la siguiente manera:
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)
Una vez que ejecute con éxito este script de Python, obtendremos la información que reside en VSS en una hoja de cálculo.
Hasta ahora, hemos visto cómo obtener artefactos en Windows usando Python. En este capítulo, aprendamos sobre la investigación de artefactos basados en registros usando Python.
Introducción
Los artefactos basados en registros son el tesoro de información que puede ser muy útil para un experto forense digital. Aunque tenemos varios software de monitoreo para recopilar la información, el problema principal para analizar información útil de ellos es que necesitamos muchos datos.
Varios artefactos basados en registros e investigación en Python
En esta sección, analicemos varios artefactos basados en registros y su investigación en Python:
Marcas de tiempo
La marca de tiempo transmite los datos y la hora de la actividad en el registro. Es uno de los elementos importantes de cualquier archivo de registro. Tenga en cuenta que estos datos y valores de tiempo pueden tener varios formatos.
La secuencia de comandos de Python que se muestra a continuación tomará la fecha y hora sin procesar como entrada y proporcionará una marca de tiempo formateada como salida.
Para este script, debemos seguir los siguientes pasos:
Primero, configure los argumentos que tomarán el valor de los datos brutos junto con la fuente de datos y el tipo de datos.
Ahora, proporcione una clase para proporcionar una interfaz común para datos en diferentes formatos de fecha.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, importe los siguientes módulos de Python:
from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from datetime import datetime as dt
from datetime import timedelta
Ahora, como de costumbre, necesitamos proporcionar un argumento para el controlador de línea de comandos. Aquí aceptará tres argumentos, el primero sería el valor de fecha a procesar, el segundo sería la fuente de ese valor de fecha y el tercero sería su 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)
Ahora, necesitamos definir una clase que acepte los argumentos para el valor de la fecha, la fuente de la fecha y el 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
Ahora definiremos un método que actuará como un controlador como el 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']
Ahora, necesitamos definir dos métodos que procesarán el tiempo de época de Unix y 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')
Después de ejecutar el script anterior, al proporcionar una marca de tiempo, podemos obtener el valor convertido en un formato fácil de leer.
Registros del servidor web
Desde el punto de vista del experto forense digital, los registros del servidor web son otro artefacto importante porque pueden obtener estadísticas de usuario útiles junto con información sobre el usuario y las ubicaciones geográficas. A continuación, se muestra el script de Python que creará una hoja de cálculo, después de procesar los registros del servidor web, para facilitar el análisis de la información.
En primer lugar, debemos importar los siguientes módulos de 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__)
Ahora, necesitamos definir los patrones que se analizarán a partir de los registros:
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*"))]
Ahora, proporcione un argumento para el controlador de línea de comandos. Aquí aceptará dos argumentos, el primero sería el registro de IIS que se procesará, el segundo sería la ruta del archivo CSV deseada.
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")
Ahora necesitamos definir el método main () que manejará el script para información de registro masivo -
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, necesitamos definir un método que escribirá el resultado en una hoja de cálculo:
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)
Después de ejecutar el script anterior, obtendremos los registros basados en el servidor web en una hoja de cálculo.
Escaneo de archivos importantes con YARA
YARA (Yet Another Recursive Algorithm) es una utilidad de coincidencia de patrones diseñada para la identificación de malware y la respuesta a incidentes. Usaremos YARA para escanear los archivos. En el siguiente script de Python, usaremos YARA.
Podemos instalar YARA con la ayuda del siguiente comando:
pip install YARA
Podemos seguir los pasos que se indican a continuación para usar las reglas de YARA para escanear archivos:
Primero, configure y compile las reglas de YARA
Luego, escanee un solo archivo y luego recorra los directorios para procesar archivos individuales.
Por último, exportaremos el resultado a CSV.
Código Python
Veamos cómo usar el código Python para este propósito:
Primero, necesitamos importar los siguientes módulos de Python:
from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
import os
import csv
import yara
A continuación, proporcione un argumento para el controlador de la línea de comandos. Tenga en cuenta que aquí aceptará dos argumentos: el primero es la ruta a las reglas YARA, el segundo es el archivo que se va a escanear.
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)
Ahora definiremos la función main () que aceptará la ruta a las reglas de yara y el archivo a escanear -
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)
Ahora, defina un método que iterará a través del directorio y pasará el resultado a otro método para su procesamiento 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 continuación, defina dos funciones. Tenga en cuenta que primero usaremosmatch() método para yrulesobjeto y otro informará esa información coincidente a la consola si el usuario no especifica ningún archivo de salida. Observe el código que se muestra a continuación:
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 último, definiremos un método que escribirá la salida en un archivo CSV, como se muestra a continuación:
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)
Una vez que ejecute correctamente el script anterior, podemos proporcionar los argumentos adecuados en la línea de comandos y generar un informe CSV.