Enquête sur les artefacts basés sur des journaux

Jusqu'à présent, nous avons vu comment obtenir des artefacts dans Windows en utilisant Python. Dans ce chapitre, apprenons à étudier les artefacts basés sur les journaux à l'aide de Python.

introduction

Les artefacts basés sur des journaux sont le trésor d'informations qui peuvent être très utiles pour un expert en criminalistique numérique. Bien que nous ayons divers logiciels de surveillance pour collecter les informations, le principal problème pour analyser les informations utiles est que nous avons besoin de beaucoup de données.

Divers artefacts basés sur des journaux et enquête en Python

Dans cette section, discutons de divers artefacts basés sur les journaux et de leur investigation en Python -

Horodatages

L'horodatage transmet les données et l'heure de l'activité dans le journal. C'est l'un des éléments importants de tout fichier journal. Notez que ces données et valeurs d'heure peuvent se présenter sous différents formats.

Le script Python montré ci-dessous prendra la date-heure brute comme entrée et fournit un horodatage formaté comme sortie.

Pour ce script, nous devons suivre les étapes suivantes -

  • Tout d'abord, configurez les arguments qui prendront la valeur des données brutes avec la source des données et le type de données.

  • Maintenant, fournissez une classe pour fournir une interface commune pour les données dans différents formats de date.

Code Python

Voyons comment utiliser le code Python à cette fin -

Tout d'abord, importez les modules Python suivants -

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

Maintenant, comme d'habitude, nous devons fournir un argument pour le gestionnaire de ligne de commande. Ici, il acceptera trois arguments, le premier serait la valeur de date à traiter, le second serait la source de cette valeur de date et le troisième serait son type -

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)

Maintenant, nous devons définir une classe qui acceptera les arguments pour la valeur de la date, la source de la date et le type de valeur -

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

Nous allons maintenant définir une méthode qui agira comme un contrôleur tout comme la méthode 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']

Maintenant, nous devons définir deux méthodes qui traiteront respectivement l'heure de l'époque Unix et 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')

Après avoir exécuté le script ci-dessus, en fournissant un horodatage, nous pouvons obtenir la valeur convertie dans un format facile à lire.

Journaux du serveur Web

Du point de vue de l'expert en criminalistique numérique, les journaux de serveur Web sont un autre artefact important car ils peuvent obtenir des statistiques utiles sur l'utilisateur ainsi que des informations sur l'utilisateur et les emplacements géographiques. Voici le script Python qui créera une feuille de calcul, après le traitement des journaux du serveur Web, pour une analyse facile des informations.

Tout d'abord, nous devons importer les modules Python suivants -

from __future__ import print_function
from argparse import ArgumentParser, FileType

import re
import shlex
import logging
import sys
import csv

logger = logging.getLogger(__file__)

Maintenant, nous devons définir les modèles qui seront analysés à partir des journaux -

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

Maintenant, fournissez un argument pour le gestionnaire de ligne de commande. Ici, il acceptera deux arguments, le premier serait le journal IIS à traiter, le second serait le chemin du fichier CSV souhaité.

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

Nous devons maintenant définir la méthode main () qui gérera le script pour les informations du journal en bloc -

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

Enfin, nous devons définir une méthode qui écrira la sortie dans une feuille de calcul -

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)

Après avoir exécuté le script ci-dessus, nous obtiendrons les journaux basés sur le serveur Web dans une feuille de calcul.

Analyse des fichiers importants à l'aide de YARA

YARA (Yet Another Recursive Algorithm) est un utilitaire de correspondance de modèles conçu pour l'identification des logiciels malveillants et la réponse aux incidents. Nous utiliserons YARA pour scanner les fichiers. Dans le script Python suivant, nous utiliserons YARA.

Nous pouvons installer YARA à l'aide de la commande suivante -

pip install YARA

Nous pouvons suivre les étapes ci-dessous pour utiliser les règles YARA pour analyser les fichiers -

  • Tout d'abord, configurez et compilez les règles YARA

  • Ensuite, scannez un seul fichier, puis parcourez les répertoires pour traiter les fichiers individuels.

  • Enfin, nous exporterons le résultat au format CSV.

Code Python

Voyons comment utiliser le code Python à cette fin -

Tout d'abord, nous devons importer les modules Python suivants -

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

import os
import csv
import yara

Ensuite, fournissez un argument pour le gestionnaire de ligne de commande. Notez qu'ici, il acceptera deux arguments - le premier est le chemin d'accès aux règles YARA, le second est le fichier à analyser.

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)

Nous allons maintenant définir la fonction main () qui acceptera le chemin vers les règles yara et le fichier à analyser -

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)

Maintenant, définissez une méthode qui itérera dans le répertoire et transmettra le résultat à une autre méthode pour un traitement ultérieur -

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

Ensuite, définissez deux fonctions. Notez que nous allons d'abord utilisermatch() méthode pour yrulesobjet et un autre rapportera les informations correspondantes à la console si l'utilisateur ne spécifie aucun fichier de sortie. Observez le code ci-dessous -

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)

Enfin, nous définirons une méthode qui écrira la sortie dans un fichier CSV, comme indiqué ci-dessous -

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)

Une fois que vous avez exécuté le script ci-dessus avec succès, nous pouvons fournir des arguments appropriés sur la ligne de commande et générer un rapport CSV.