Python Digital Network Forensics-II

O capítulo anterior tratou de alguns dos conceitos de análise forense de redes usando Python. Neste capítulo, vamos entender a análise forense de rede usando Python em um nível mais profundo.

Preservação de página da web com sopa bonita

A World Wide Web (WWW) é um recurso único de informação. No entanto, seu legado está em alto risco devido à perda de conteúdo em uma taxa alarmante. Diversas instituições de patrimônio cultural e acadêmicas, organizações sem fins lucrativos e empresas privadas exploraram as questões envolvidas e contribuíram para o desenvolvimento de soluções técnicas para arquivamento na web.

A preservação de páginas da web ou arquivamento da web é o processo de coleta de dados da World Wide Web, garantindo que os dados sejam preservados em um arquivo e disponibilizando-os para futuros pesquisadores, historiadores e o público. Antes de prosseguir na preservação da página da web, vamos discutir algumas questões importantes relacionadas à preservação da página da web, conforme fornecido abaixo -

  • Change in Web Resources - Os recursos da web mudam todos os dias, o que é um desafio para a preservação da página da web.

  • Large Quantity of Resources - Outra questão relacionada à preservação de páginas web é a grande quantidade de recursos a serem preservados.

  • Integrity - As páginas da Web devem ser protegidas contra alterações, exclusões ou remoções não autorizadas para proteger sua integridade.

  • Dealing with multimedia data - Ao mesmo tempo em que preservamos as páginas da web, precisamos lidar com dados de multimídia também, e isso pode causar problemas ao fazer isso.

  • Providing access - Além de preservar, a questão de fornecer acesso a recursos da web e lidar com questões de propriedade também precisa ser resolvida.

Neste capítulo, vamos usar a biblioteca Python chamada Beautiful Soup para preservação de página da web.

O que é a bela sopa?

Beautiful Soup é uma biblioteca Python para extrair dados de arquivos HTML e XML. Pode ser usado comurlibporque ele precisa de uma entrada (documento ou url) para criar um objeto sopa, já que ele não pode buscar a página da web por si só. Você pode aprender em detalhes sobre isso em www.crummy.com/software/BeautifulSoup/bs4/doc/

Observe que antes de usá-lo, devemos instalar uma biblioteca de terceiros usando o seguinte comando -

pip install bs4

Em seguida, usando o gerenciador de pacotes Anaconda, podemos instalar Beautiful Soup da seguinte forma -

conda install -c anaconda beautifulsoup4

Script Python para preservação de páginas da web

O script Python para preservar páginas da web usando uma biblioteca de terceiros chamada Beautiful Soup é discutido aqui -

Primeiro, importe as bibliotecas necessárias da seguinte forma -

from __future__ import print_function
import argparse

from bs4 import BeautifulSoup, SoupStrainer
from datetime import datetime

import hashlib
import logging
import os
import ssl
import sys
from urllib.request import urlopen

import urllib.error
logger = logging.getLogger(__name__)

Observe que este script terá dois argumentos posicionais, um é o URL que deve ser preservado e o outro é o diretório de saída desejado, conforme mostrado abaixo -

if __name__ == "__main__":
   parser = argparse.ArgumentParser('Web Page preservation')
   parser.add_argument("DOMAIN", help="Website Domain")
   parser.add_argument("OUTPUT_DIR", help="Preservation Output Directory")
   parser.add_argument("-l", help="Log file path",
   default=__file__[:-3] + ".log")
   args = parser.parse_args()

Agora, configure o registro para o script especificando um arquivo e um manipulador de fluxo para estar em loop e documente o processo de aquisição como mostrado -

logger.setLevel(logging.DEBUG)
msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-10s""%(levelname)-8s %(message)s")
strhndl = logging.StreamHandler(sys.stderr)
strhndl.setFormatter(fmt=msg_fmt)
fhndl = logging.FileHandler(args.l, mode='a')
fhndl.setFormatter(fmt=msg_fmt)

logger.addHandler(strhndl)
logger.addHandler(fhndl)
logger.info("Starting BS Preservation")
logger.debug("Supplied arguments: {}".format(sys.argv[1:]))
logger.debug("System " + sys.platform)
logger.debug("Version " + sys.version)

Agora, vamos fazer a validação de entrada no diretório de saída desejado da seguinte maneira -

if not os.path.exists(args.OUTPUT_DIR):
   os.makedirs(args.OUTPUT_DIR)
main(args.DOMAIN, args.OUTPUT_DIR)

Agora, vamos definir o main() função que extrairá o nome de base do site, removendo os elementos desnecessários antes do nome real, juntamente com a validação adicional no URL de entrada da seguinte forma -

def main(website, output_dir):
   base_name = website.replace("https://", "").replace("http://", "").replace("www.", "")
   link_queue = set()
   
   if "http://" not in website and "https://" not in website:
      logger.error("Exiting preservation - invalid user input: {}".format(website))
      sys.exit(1)
   logger.info("Accessing {} webpage".format(website))
   context = ssl._create_unverified_context()

Agora, precisamos abrir uma conexão com a URL usando o método urlopen (). Vamos usar o bloco try-except da seguinte forma -

try:
   index = urlopen(website, context=context).read().decode("utf-8")
except urllib.error.HTTPError as e:
   logger.error("Exiting preservation - unable to access page: {}".format(website))
   sys.exit(2)
logger.debug("Successfully accessed {}".format(website))

As próximas linhas de código incluem três funções, conforme explicado abaixo -

  • write_output() para escrever a primeira página da web no diretório de saída

  • find_links() função para identificar os links nesta página da web

  • recurse_pages() função para iterar e descobrir todos os links na página da web.

write_output(website, index, output_dir)
link_queue = find_links(base_name, index, link_queue)
logger.info("Found {} initial links on webpage".format(len(link_queue)))
recurse_pages(website, link_queue, context, output_dir)
logger.info("Completed preservation of {}".format(website))

Agora, vamos definir write_output() método da seguinte forma -

def write_output(name, data, output_dir, counter=0):
   name = name.replace("http://", "").replace("https://", "").rstrip("//")
   directory = os.path.join(output_dir, os.path.dirname(name))
   
   if not os.path.exists(directory) and os.path.dirname(name) != "":
      os.makedirs(directory)

Precisamos registrar alguns detalhes sobre a página da web e, em seguida, registrar o hash dos dados usando hash_data() método da seguinte forma -

logger.debug("Writing {} to {}".format(name, output_dir)) logger.debug("Data Hash: {}".format(hash_data(data)))
path = os.path.join(output_dir, name)
path = path + "_" + str(counter)
with open(path, "w") as outfile:
   outfile.write(data)
logger.debug("Output File Hash: {}".format(hash_file(path)))

Agora, defina hash_data() método com a ajuda do qual lemos o UTF-8 dados codificados e, em seguida, gerar o SHA-256 hash da seguinte forma -

def hash_data(data):
   sha256 = hashlib.sha256()
   sha256.update(data.encode("utf-8"))
   return sha256.hexdigest()
def hash_file(file):
   sha256 = hashlib.sha256()
   with open(file, "rb") as in_file:
      sha256.update(in_file.read())
return sha256.hexdigest()

Agora, vamos criar um Beautifulsoup objeto fora dos dados da página da web em find_links() método da seguinte forma -

def find_links(website, page, queue):
   for link in BeautifulSoup(page, "html.parser",parse_only = SoupStrainer("a", href = True)):
      if website in link.get("href"):
         if not os.path.basename(link.get("href")).startswith("#"):
            queue.add(link.get("href"))
   return queue

Agora, precisamos definir recurse_pages() método, fornecendo a ele as entradas do URL do site, a fila de links atual, o contexto SSL não verificado e o diretório de saída da seguinte forma -

def recurse_pages(website, queue, context, output_dir):
   processed = []
   counter = 0
   
   while True:
      counter += 1
      if len(processed) == len(queue):
         break
      for link in queue.copy(): if link in processed:
         continue
	   processed.append(link)
      try:
      page = urlopen(link,      context=context).read().decode("utf-8")
      except urllib.error.HTTPError as e:
         msg = "Error accessing webpage: {}".format(link)
         logger.error(msg)
         continue

Agora, escreva a saída de cada página da web acessada em um arquivo, passando o nome do link, os dados da página, o diretório de saída e o contador da seguinte maneira -

write_output(link, page, output_dir, counter)
queue = find_links(website, page, queue)
logger.info("Identified {} links throughout website".format(
   len(queue)))

Agora, quando executarmos esse script fornecendo a URL do site, o diretório de saída e um caminho para o arquivo de log, obteremos os detalhes sobre essa página da web que pode ser usada para uso futuro.

Caça ao Vírus

Você já se perguntou como analistas forenses, pesquisadores de segurança e respondentes de incidentes podem entender a diferença entre software útil e malware? A resposta está na própria pergunta, pois sem estudar sobre o malware, gerado rapidamente por hackers, é quase impossível para pesquisadores e especialistas distinguir entre software útil e malware. Nesta seção, vamos discutir sobreVirusShare, uma ferramenta para realizar essa tarefa.

Compreendendo o VirusShare

O VirusShare é a maior coleção privada de amostras de malware para fornecer aos pesquisadores de segurança, responsáveis ​​pela resposta a incidentes e analistas forenses as amostras de código malicioso ativo. Ele contém mais de 30 milhões de amostras.

O benefício do VirusShare é a lista de hashes de malware que está disponível gratuitamente. Qualquer pessoa pode usar esses hashes para criar um conjunto de hash muito abrangente e usá-lo para identificar arquivos potencialmente maliciosos. Mas antes de usar o VirusShare, sugerimos que você visitehttps://virusshare.com para mais detalhes.

Criação de lista de hash delimitada por nova linha a partir do VirusShare usando Python

Uma lista hash do VirusShare pode ser usada por várias ferramentas forenses, como X-way e EnCase. No script discutido abaixo, iremos automatizar o download de listas de hashes do VirusShare para criar uma lista de hashes delimitada por nova linha.

Para este script, precisamos de uma biblioteca Python de terceiros tqdm que pode ser baixado da seguinte forma -

pip install tqdm

Observe que, neste script, primeiro leremos a página de hashes do VirusShare e identificaremos dinamicamente a lista de hashes mais recente. Em seguida, inicializaremos a barra de progresso e baixaremos a lista hash no intervalo desejado.

Primeiro, importe as seguintes bibliotecas -

from __future__ import print_function

import argparse
import os
import ssl
import sys
import tqdm

from urllib.request import urlopen
import urllib.error

Este script terá um argumento posicional, que seria o caminho desejado para o conjunto hash -

if __name__ == '__main__':
   parser = argparse.ArgumentParser('Hash set from VirusShare')
   parser.add_argument("OUTPUT_HASH", help = "Output Hashset")
   parser.add_argument("--start", type = int, help = "Optional starting location")
   args = parser.parse_args()

Agora, vamos realizar a validação de entrada padrão da seguinte forma -

directory = os.path.dirname(args.OUTPUT_HASH)
if not os.path.exists(directory):
   os.makedirs(directory)
if args.start:
   main(args.OUTPUT_HASH, start=args.start)
else:
   main(args.OUTPUT_HASH)

Agora precisamos definir main() funcionar com **kwargs como um argumento porque isso criará um dicionário que podemos consultar para apoiar os argumentos-chave fornecidos, conforme mostrado abaixo -

def main(hashset, **kwargs):
   url = "https://virusshare.com/hashes.4n6"
   print("[+] Identifying hash set range from {}".format(url))
   context = ssl._create_unverified_context()

Agora, precisamos abrir a página de hashes do VirusShare usando urlib.request.urlopen()método. Usaremos o bloco try-except da seguinte forma -

try:
   index = urlopen(url, context = context).read().decode("utf-8")
except urllib.error.HTTPError as e:
   print("[-] Error accessing webpage - exiting..")
   sys.exit(1)

Agora, identifique a lista de hash mais recente das páginas baixadas. Você pode fazer isso encontrando a última instância do HTMLhreftag para a lista de hash do VirusShare. Isso pode ser feito com as seguintes linhas de código -

tag = index.rfind(r'a href = "hashes/VirusShare_')
stop = int(index[tag + 27: tag + 27 + 5].lstrip("0"))

if "start" not in kwa<rgs:
   start = 0
else:
   start = kwargs["start"]

if start < 0 or start > stop:
   print("[-] Supplied start argument must be greater than or equal ""to zero but less than the latest hash list, ""currently: {}".format(stop))
sys.exit(2)
print("[+] Creating a hashset from hash lists {} to {}".format(start, stop))
hashes_downloaded = 0

Agora, vamos usar tqdm.trange() método para criar um loop e uma barra de progresso da seguinte maneira -

for x in tqdm.trange(start, stop + 1, unit_scale=True,desc="Progress"):
   url_hash = "https://virusshare.com/hashes/VirusShare_"\"{}.md5".format(str(x).zfill(5))
   try:
      hashes = urlopen(url_hash, context=context).read().decode("utf-8")
      hashes_list = hashes.split("\n")
   except urllib.error.HTTPError as e:
      print("[-] Error accessing webpage for hash list {}"" - continuing..".format(x))
   continue

Depois de realizar as etapas acima com êxito, abriremos o arquivo de texto do conjunto hash no modo a + para anexar ao final do arquivo de texto.

with open(hashset, "a+") as hashfile:
   for line in hashes_list:
   if not line.startswith("#") and line != "":
      hashes_downloaded += 1
      hashfile.write(line + '\n')
   print("[+] Finished downloading {} hashes into {}".format(
      hashes_downloaded, hashset))

Depois de executar o script acima, você obterá a lista de hash mais recente contendo valores de hash MD5 em formato de texto.