Investigación de artefactos basados en registros
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 una gran cantidad de 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 comparación 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 utilizar 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 posterior procesamiento:
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.