Учебники

Исследование логарифмических артефактов

До сих пор мы видели, как получить артефакты в Windows, используя Python. В этой главе мы узнаем об исследовании артефактов на основе журналов с использованием Python.

Вступление

Основанные на логе артефакты являются сокровищницей информации, которая может быть очень полезна для цифрового судебного эксперта. Несмотря на то, что у нас есть различное программное обеспечение для мониторинга для сбора информации, основная проблема для извлечения полезной информации из них заключается в том, что нам нужно много данных.

Различные основанные на логе артефакты и исследования в Python

В этом разделе давайте обсудим различные артефакты на основе журналов и их исследование в Python —

Timestamps

Временная метка передает данные и время активности в журнале. Это один из важных элементов любого файла журнала. Обратите внимание, что эти данные и значения времени могут быть в разных форматах.

Сценарий Python, показанный ниже, примет необработанную дату и время в качестве входных данных и предоставит отформатированную метку времени в качестве выходных данных.

Для этого скрипта нам нужно выполнить следующие шаги —

  • Сначала настройте аргументы, которые будут принимать значение необработанных данных вместе с источником данных и типом данных.

  • Теперь предоставьте класс для предоставления общего интерфейса для данных в разных форматах даты.

Сначала настройте аргументы, которые будут принимать значение необработанных данных вместе с источником данных и типом данных.

Теперь предоставьте класс для предоставления общего интерфейса для данных в разных форматах даты.

Код Python

Давайте посмотрим, как использовать код Python для этой цели —

Сначала импортируйте следующие модули Python —

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

Теперь, как обычно, нам нужно предоставить аргумент для обработчика командной строки. Здесь он будет принимать три аргумента: первый будет значением даты для обработки, второй будет источником этого значения даты, а третий будет его типом —

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)

Теперь нам нужно определить класс, который будет принимать аргументы для значения даты, источника даты и типа значения —

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

Теперь мы определим метод, который будет действовать как контроллер точно так же, как метод 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']

Теперь нам нужно определить два метода, которые будут обрабатывать время эпохи Unix и 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')

После запуска вышеуказанного скрипта, предоставив метку времени, мы можем получить преобразованное значение в удобном для чтения формате.

Логи веб-сервера

С точки зрения эксперта по цифровым данным, журналы веб-сервера являются еще одним важным артефактом, поскольку они могут получать полезную статистику пользователя, а также информацию о пользователе и географическом местоположении. Ниже приведен сценарий Python, который после обработки логов веб-сервера создаст электронную таблицу для простого анализа информации.

Прежде всего нам нужно импортировать следующие модули 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__)

Теперь нам нужно определить шаблоны, которые будут анализироваться из журналов —

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

Теперь предоставьте аргумент для обработчика командной строки. Здесь он будет принимать два аргумента: первый — это обрабатываемый журнал IIS, второй — требуемый путь к CSV-файлу.

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

Теперь нам нужно определить метод main (), который будет обрабатывать скрипт для массовой информации журнала —

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

Наконец, нам нужно определить метод, который будет записывать вывод в электронную таблицу —

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)

После запуска вышеуказанного сценария мы получим журналы на основе веб-сервера в электронной таблице.

Сканирование важных файлов с использованием YARA

YARA (еще один рекурсивный алгоритм) — это утилита сопоставления с образцом, предназначенная для идентификации вредоносных программ и реагирования на инциденты. Мы будем использовать YARA для сканирования файлов. В следующем скрипте Python мы будем использовать YARA.

Мы можем установить YARA с помощью следующей команды —

pip install YARA

Мы можем следовать приведенным ниже инструкциям для использования правил YARA для сканирования файлов —

  • Сначала настройте и скомпилируйте правила YARA

  • Затем отсканируйте один файл, а затем выполните итерацию по каталогам для обработки отдельных файлов.

  • Наконец, мы экспортируем результат в CSV.

Сначала настройте и скомпилируйте правила YARA

Затем отсканируйте один файл, а затем выполните итерацию по каталогам для обработки отдельных файлов.

Наконец, мы экспортируем результат в CSV.

Код Python

Давайте посмотрим, как использовать код Python для этой цели —

Во-первых, нам нужно импортировать следующие модули Python —

from __future__ import print_function
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

import os
import csv
import yara

Далее укажите аргумент для обработчика командной строки. Обратите внимание, что здесь он будет принимать два аргумента: первый — это путь к правилам YARA, второй — файл, который нужно сканировать.

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)

Теперь мы определим функцию main (), которая будет принимать путь к правилам yara и проверяемому файлу —

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)

Теперь определите метод, который будет выполнять итерацию по каталогу и передавать результат другому методу для дальнейшей обработки —

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

Затем определите две функции. Обратите внимание, что сначала мы будем использовать метод match () для объекта yrules, а другой сообщит, что информация о совпадении выводится на консоль, если пользователь не указал какой-либо выходной файл. Соблюдайте код, показанный ниже —

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)

Наконец, мы определим метод, который будет записывать вывод в файл CSV, как показано ниже —

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)

После успешного запуска вышеуказанного сценария мы можем предоставить соответствующие аргументы в командной строке и сгенерировать отчет CSV.