Investigação de artefatos baseados em registro
Até agora, vimos como obter artefatos no Windows usando Python. Neste capítulo, vamos aprender sobre a investigação de artefatos baseados em log usando Python.
Introdução
Os artefatos baseados em log são o tesouro de informações que podem ser muito úteis para um especialista forense digital. Embora tenhamos vários softwares de monitoramento para coletar as informações, o principal problema para analisar informações úteis deles é que precisamos de muitos dados.
Vários artefatos baseados em log e investigação em Python
Nesta seção, vamos discutir vários artefatos baseados em log e sua investigação em Python -
Timestamps
O carimbo de data / hora transmite os dados e a hora da atividade no log. É um dos elementos importantes de qualquer arquivo de log. Observe que esses dados e valores de tempo podem vir em vários formatos.
O script Python mostrado abaixo pegará a data e hora bruta como entrada e fornecerá um carimbo de data / hora formatado como sua saída.
Para este script, precisamos seguir as seguintes etapas -
Primeiro, configure os argumentos que levarão o valor dos dados brutos junto com a fonte de dados e o tipo de dados.
Agora, forneça uma classe para fornecer interface comum para dados em diferentes formatos de data.
Código Python
Vamos ver como usar o código Python para esse propósito -
Primeiro, importe os seguintes módulos Python -
from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from datetime import datetime as dt
from datetime import timedelta
Agora, como de costume, precisamos fornecer um argumento para o manipulador de linha de comando. Aqui, ele aceitará três argumentos, o primeiro seria o valor da data a ser processado, o segundo seria a fonte desse valor de data e o terceiro seria o seu tipo -
if __name__ == '__main__':
parser = ArgumentParser('Timestamp Log-based artifact')
parser.add_argument("date_value", help="Raw date value to parse")
parser.add_argument(
"source", help = "Source format of date",choices = ParseDate.get_supported_formats())
parser.add_argument(
"type", help = "Data type of input value",choices = ('number', 'hex'), default = 'int')
args = parser.parse_args()
date_parser = ParseDate(args.date_value, args.source, args.type)
date_parser.run()
print(date_parser.timestamp)
Agora, precisamos definir uma classe que aceitará os argumentos para valor de data, fonte de data e o tipo de valor -
class ParseDate(object):
def __init__(self, date_value, source, data_type):
self.date_value = date_value
self.source = source
self.data_type = data_type
self.timestamp = None
Agora vamos definir um método que agirá como um controlador, assim como o método main () -
def run(self):
if self.source == 'unix-epoch':
self.parse_unix_epoch()
elif self.source == 'unix-epoch-ms':
self.parse_unix_epoch(True)
elif self.source == 'windows-filetime':
self.parse_windows_filetime()
@classmethod
def get_supported_formats(cls):
return ['unix-epoch', 'unix-epoch-ms', 'windows-filetime']
Agora, precisamos definir dois métodos que irão processar o Unix epoch time e FILETIME respectivamente -
def parse_unix_epoch(self, milliseconds=False):
if self.data_type == 'hex':
conv_value = int(self.date_value)
if milliseconds:
conv_value = conv_value / 1000.0
elif self.data_type == 'number':
conv_value = float(self.date_value)
if milliseconds:
conv_value = conv_value / 1000.0
else:
print("Unsupported data type '{}' provided".format(self.data_type))
sys.exit('1')
ts = dt.fromtimestamp(conv_value)
self.timestamp = ts.strftime('%Y-%m-%d %H:%M:%S.%f')
def parse_windows_filetime(self):
if self.data_type == 'hex':
microseconds = int(self.date_value, 16) / 10.0
elif self.data_type == 'number':
microseconds = float(self.date_value) / 10
else:
print("Unsupported data type '{}' provided".format(self.data_type))
sys.exit('1')
ts = dt(1601, 1, 1) + timedelta(microseconds=microseconds)
self.timestamp = ts.strftime('%Y-%m-%d %H:%M:%S.%f')
Depois de executar o script acima, fornecendo um carimbo de data / hora, podemos obter o valor convertido em um formato fácil de ler.
Logs do servidor web
Do ponto de vista do especialista forense digital, os logs do servidor da web são outro artefato importante porque podem obter estatísticas úteis do usuário, juntamente com informações sobre o usuário e localizações geográficas. A seguir está o script Python que irá criar uma planilha, após processar os logs do servidor web, para fácil análise das informações.
Em primeiro lugar, precisamos importar os seguintes módulos Python -
from __future__ import print_function
from argparse import ArgumentParser, FileType
import re
import shlex
import logging
import sys
import csv
logger = logging.getLogger(__file__)
Agora, precisamos definir os padrões que serão analisados a partir dos logs -
iis_log_format = [
("date", re.compile(r"\d{4}-\d{2}-\d{2}")),
("time", re.compile(r"\d\d:\d\d:\d\d")),
("s-ip", re.compile(
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}")),
("cs-method", re.compile(
r"(GET)|(POST)|(PUT)|(DELETE)|(OPTIONS)|(HEAD)|(CONNECT)")),
("cs-uri-stem", re.compile(r"([A-Za-z0-1/\.-]*)")),
("cs-uri-query", re.compile(r"([A-Za-z0-1/\.-]*)")),
("s-port", re.compile(r"\d*")),
("cs-username", re.compile(r"([A-Za-z0-1/\.-]*)")),
("c-ip", re.compile(
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}")),
("cs(User-Agent)", re.compile(r".*")),
("sc-status", re.compile(r"\d*")),
("sc-substatus", re.compile(r"\d*")),
("sc-win32-status", re.compile(r"\d*")),
("time-taken", re.compile(r"\d*"))]
Agora, forneça um argumento para o manipulador de linha de comando. Aqui, ele aceitará dois argumentos, o primeiro seria o log do IIS a ser processado, o segundo seria o caminho do arquivo CSV desejado.
if __name__ == '__main__':
parser = ArgumentParser('Parsing Server Based Logs')
parser.add_argument('iis_log', help = "Path to IIS Log",type = FileType('r'))
parser.add_argument('csv_report', help = "Path to CSV report")
parser.add_argument('-l', help = "Path to processing log",default=__name__ + '.log')
args = parser.parse_args()
logger.setLevel(logging.DEBUG)
msg_fmt = logging.Formatter(
"%(asctime)-15s %(funcName)-10s ""%(levelname)-8s %(message)s")
strhndl = logging.StreamHandler(sys.stdout)
strhndl.setFormatter(fmt = msg_fmt)
fhndl = logging.FileHandler(args.log, mode = 'a')
fhndl.setFormatter(fmt = msg_fmt)
logger.addHandler(strhndl)
logger.addHandler(fhndl)
logger.info("Starting IIS Parsing ")
logger.debug("Supplied arguments: {}".format(", ".join(sys.argv[1:])))
logger.debug("System " + sys.platform)
logger.debug("Version " + sys.version)
main(args.iis_log, args.csv_report, logger)
iologger.info("IIS Parsing Complete")
Agora precisamos definir o método main () que irá lidar com o script para informações de log em massa -
def main(iis_log, report_file, logger):
parsed_logs = []
for raw_line in iis_log:
line = raw_line.strip()
log_entry = {}
if line.startswith("#") or len(line) == 0:
continue
if '\"' in line:
line_iter = shlex.shlex(line_iter)
else:
line_iter = line.split(" ")
for count, split_entry in enumerate(line_iter):
col_name, col_pattern = iis_log_format[count]
if col_pattern.match(split_entry):
log_entry[col_name] = split_entry
else:
logger.error("Unknown column pattern discovered. "
"Line preserved in full below")
logger.error("Unparsed Line: {}".format(line))
parsed_logs.append(log_entry)
logger.info("Parsed {} lines".format(len(parsed_logs)))
cols = [x[0] for x in iis_log_format]
logger.info("Creating report file: {}".format(report_file))
write_csv(report_file, cols, parsed_logs)
logger.info("Report created")
Por último, precisamos definir um método que escreverá a saída na planilha -
def write_csv(outfile, fieldnames, data):
with open(outfile, 'w', newline="") as open_outfile:
csvfile = csv.DictWriter(open_outfile, fieldnames)
csvfile.writeheader()
csvfile.writerows(data)
Depois de executar o script acima, obteremos os logs do servidor da web em uma planilha.
Verificando arquivos importantes usando YARA
YARA (Yet Another Recursive Algorithm) é um utilitário de correspondência de padrões projetado para identificação de malware e resposta a incidentes. Usaremos YARA para escanear os arquivos. No seguinte script Python, usaremos YARA.
Podemos instalar o YARA com a ajuda do seguinte comando -
pip install YARA
Podemos seguir as etapas abaixo para usar as regras YARA para verificar arquivos -
Primeiro, configure e compile as regras YARA
Em seguida, examine um único arquivo e, em seguida, percorra os diretórios para processar arquivos individuais.
Por fim, exportaremos o resultado para CSV.
Código Python
Vamos ver como usar o código Python para esse propósito -
Primeiro, precisamos importar os seguintes módulos Python -
from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
import os
import csv
import yara
Em seguida, forneça o argumento para o manipulador de linha de comando. Observe que aqui ele aceitará dois argumentos - o primeiro é o caminho para as regras do YARA, o segundo é o arquivo a ser verificado.
if __name__ == '__main__':
parser = ArgumentParser('Scanning files by YARA')
parser.add_argument(
'yara_rules',help = "Path to Yara rule to scan with. May be file or folder path.")
parser.add_argument('path_to_scan',help = "Path to file or folder to scan")
parser.add_argument('--output',help = "Path to output a CSV report of scan results")
args = parser.parse_args()
main(args.yara_rules, args.path_to_scan, args.output)
Agora vamos definir a função main () que aceitará o caminho para as regras yara e o arquivo a ser verificado -
def main(yara_rules, path_to_scan, output):
if os.path.isdir(yara_rules):
yrules = yara.compile(yara_rules)
else:
yrules = yara.compile(filepath=yara_rules)
if os.path.isdir(path_to_scan):
match_info = process_directory(yrules, path_to_scan)
else:
match_info = process_file(yrules, path_to_scan)
columns = ['rule_name', 'hit_value', 'hit_offset', 'file_name',
'rule_string', 'rule_tag']
if output is None:
write_stdout(columns, match_info)
else:
write_csv(output, columns, match_info)
Agora, defina um método que irá iterar através do diretório e passar o resultado para outro método para processamento posterior -
def process_directory(yrules, folder_path):
match_info = []
for root, _, files in os.walk(folder_path):
for entry in files:
file_entry = os.path.join(root, entry)
match_info += process_file(yrules, file_entry)
return match_info
A seguir, defina duas funções. Observe que primeiro vamos usarmatch() método para yrulesobjeto e outro relatará as informações correspondentes ao console se o usuário não especificar nenhum arquivo de saída. Observe o código mostrado abaixo -
def process_file(yrules, file_path):
match = yrules.match(file_path)
match_info = []
for rule_set in match:
for hit in rule_set.strings:
match_info.append({
'file_name': file_path,
'rule_name': rule_set.rule,
'rule_tag': ",".join(rule_set.tags),
'hit_offset': hit[0],
'rule_string': hit[1],
'hit_value': hit[2]
})
return match_info
def write_stdout(columns, match_info):
for entry in match_info:
for col in columns:
print("{}: {}".format(col, entry[col]))
print("=" * 30)
Por último, definiremos um método que escreverá a saída no arquivo CSV, conforme mostrado abaixo -
def write_csv(outfile, fieldnames, data):
with open(outfile, 'w', newline="") as open_outfile:
csvfile = csv.DictWriter(open_outfile, fieldnames)
csvfile.writeheader()
csvfile.writerows(data)
Depois de executar o script acima com êxito, podemos fornecer os argumentos apropriados na linha de comando e gerar um relatório CSV.