Indagine su artefatti basati su log

Fino ad ora, abbiamo visto come ottenere artefatti in Windows utilizzando Python. In questo capitolo, impariamo a studiare gli artefatti basati sui log usando Python.

introduzione

Gli artefatti basati sui registri sono un tesoro di informazioni che possono essere molto utili per un esperto di medicina legale digitale. Sebbene disponiamo di vari software di monitoraggio per la raccolta delle informazioni, il problema principale per analizzare le informazioni utili da essi è che abbiamo bisogno di molti dati.

Vari artefatti basati su log e indagini in Python

In questa sezione, discutiamo di vari artefatti basati su log e della loro indagine in Python -

Timestamp

Timestamp trasmette i dati e l'ora dell'attività nel registro. È uno degli elementi importanti di qualsiasi file di registro. Notare che questi dati e valori di ora possono essere disponibili in vari formati.

Lo script Python mostrato di seguito prenderà la data-ora non elaborata come input e fornisce un timestamp formattato come output.

Per questo script, dobbiamo seguire i seguenti passaggi:

  • Innanzitutto, imposta gli argomenti che prenderanno il valore dei dati non elaborati insieme all'origine dei dati e al tipo di dati.

  • Fornisci ora una classe per fornire un'interfaccia comune per i dati in diversi formati di data.

Codice Python

Vediamo come utilizzare il codice Python per questo scopo -

Innanzitutto, importa i seguenti moduli Python:

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

Ora, come al solito, dobbiamo fornire un argomento per il gestore della riga di comando. Qui accetterà tre argomenti, il primo sarebbe il valore della data da elaborare, il secondo sarebbe l'origine di quel valore della data e il terzo sarebbe il suo 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)

Ora, dobbiamo definire una classe che accetti gli argomenti per il valore della data, l'origine della data e il tipo di valore -

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

Ora definiremo un metodo che agirà come un controller proprio come il metodo 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']

Ora, dobbiamo definire due metodi che elaboreranno rispettivamente l'ora dell'epoca di Unix e FILETIME -

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

Dopo aver eseguito lo script precedente, fornendo un timestamp possiamo ottenere il valore convertito in un formato di facile lettura.

Registri del server Web

Dal punto di vista dell'esperto di digital forensic, i log del server web sono un altro importante artefatto perché possono ottenere statistiche utili sugli utenti insieme a informazioni sull'utente e sulle posizioni geografiche. Di seguito è riportato lo script Python che creerà un foglio di calcolo, dopo aver elaborato i log del server web, per una facile analisi delle informazioni.

Prima di tutto dobbiamo importare i seguenti moduli 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__)

Ora, dobbiamo definire i modelli che verranno analizzati dai log -

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

Fornisci ora un argomento per il gestore della riga di comando. Qui accetterà due argomenti, il primo sarebbe il log IIS da elaborare, il secondo sarebbe il percorso del file CSV desiderato.

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

Ora dobbiamo definire il metodo main () che gestirà lo script per le informazioni di registro in blocco -

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

Infine, dobbiamo definire un metodo che scriverà l'output nel foglio di calcolo -

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)

Dopo aver eseguito lo script precedente, otterremo i log basati sul server Web in un foglio di calcolo.

Scansione di file importanti utilizzando YARA

YARA (Yet Another Recursive Algorithm) è un'utilità di pattern matching progettata per l'identificazione del malware e la risposta agli incidenti. Useremo YARA per la scansione dei file. Nel seguente script Python, useremo YARA.

Possiamo installare YARA con l'aiuto del seguente comando:

pip install YARA

Possiamo seguire i passaggi indicati di seguito per l'utilizzo delle regole YARA per la scansione dei file:

  • Innanzitutto, imposta e compila le regole YARA

  • Quindi, eseguire la scansione di un singolo file e quindi scorrere le directory per elaborare i singoli file.

  • Infine, esporteremo il risultato in CSV.

Codice Python

Vediamo come utilizzare il codice Python per questo scopo -

Innanzitutto, dobbiamo importare i seguenti moduli Python:

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

import os
import csv
import yara

Successivamente, fornire un argomento per il gestore della riga di comando. Nota che qui accetterà due argomenti: il primo è il percorso delle regole YARA, il secondo è il file da scansionare.

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)

Ora definiremo la funzione main () che accetterà il percorso delle regole yara e del file da scansionare -

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)

Ora, definisci un metodo che itererà attraverso la directory e passerà il risultato a un altro metodo per un'ulteriore elaborazione -

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

Successivamente, definisci due funzioni. Nota che prima useremomatch() metodo a yrulesoggetto e un altro riporterà le informazioni sulla corrispondenza alla console se l'utente non specifica alcun file di output. Rispettare il codice mostrato di seguito -

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)

Infine, definiremo un metodo che scriverà l'output nel file CSV, come mostrato di seguito -

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)

Dopo aver eseguito con successo lo script precedente, possiamo fornire gli argomenti appropriati sulla riga di comando e possiamo generare un rapporto CSV.