Python数字网络取证-II
上一章讨论了使用Python进行网络取证的一些概念。在本章中,让我们在更深的层次上理解使用Python的网络取证。
用Beautiful Soup保存网页
万维网(WWW)是一种独特的信息资源。然而,由于内容以惊人的速度流失,它的遗产正处于高度危险之中。一些文化遗产和学术机构、非营利组织和私营企业已经探讨了相关问题,并为开发网络存档的技术解决方案做出了贡献。
网页保存或网络归档是从万维网中收集数据的过程,确保数据被保存在档案中,并为未来的研究人员、历史学家和公众提供。在进一步了解网页保存之前,让我们讨论一下与网页保存有关的一些重要问题,如下所示。
- 网络资源的变化 – 网络资源每天都在变化,这对网页保存是一个挑战。
-
大量的资源 – 另一个与网页保存有关的问题是需要保存的大量的资源。
-
完整性 – 为了保护网页的完整性,必须防止未经授权的修改、删除或删除。
-
处理多媒体数据 – 在保存网页的同时,我们还需要处理多媒体数据,而这些数据在处理时可能会引起问题。
-
提供访问 – 除了保存,还需要解决提供对网络资源的访问和处理所有权的问题。
在本章中,我们将使用名为 Beautiful Soup 的Python库来保存网页。
什么是Beautiful Soup
Beautiful Soup是一个用于从HTML和XML文件中提取数据的Python库。它可以和 urlib 一起使用,因为它需要一个输入(文档或url)来创建一个汤对象,因为它不能自己获取网页。你可以在www.crummy.com/software/BeautifulSoup/bs4/doc/ 上详细了解这个问题。
注意,在使用它之前,我们必须使用以下命令安装第三方库 −
pip install bs4
接下来,使用Anaconda软件包管理器,我们可以安装Beautiful Soup,如下所示 —
conda install -c anaconda beautifulsoup4
保存网页的Python脚本
这里讨论的是通过使用第三方库Beautiful Soup来保存网页的Python脚本。
首先,导入所需的库,如下所示
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__)
请注意,这个脚本将接受两个位置参数,一个是要保留的URL,另一个是想要的输出目录,如下图所示。
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()
现在,通过指定一个文件和流处理程序来设置脚本的日志,使其处于循环状态,并记录采集过程,如图所示。
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)
现在,让我们对所需的输出目录进行输入验证,如下所示
if not os.path.exists(args.OUTPUT_DIR):
os.makedirs(args.OUTPUT_DIR)
main(args.DOMAIN, args.OUTPUT_DIR)
现在,我们将定义 main( )函数,该函数将通过删除实际名称前不必要的元素来提取网站的基本名称,同时对输入的URL进行额外验证,如下所示
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()
现在,我们需要使用urlopen()方法来打开一个与URL的连接。让我们使用try-except块,如下所示
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))
接下来的几行代码包括三个函数,解释如下
- write_output() 函数将第一个网页写到输出目录中。
-
find_links() 函数用于识别该网页上的链接
-
recurse_pages() 函数用于遍历和发现网页上的所有链接。
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))
现在,让我们定义 write_output( )方法,如下所示
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)
我们需要记录一些关于网页的细节,然后通过使用 hash_data() 方法记录数据的哈希值,如下所示
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)))
现在,定义 hash_data( )方法,在该方法的帮助下,我们读取 UTF-8 编码的数据,然后生成 SHA-256 散列值,如下所示
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()
现在,让我们在 find_links( )方法下从网页数据中创建一个 Beautifulsoup 对象,如下所示
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
现在,我们需要定义 recurse_pages( )方法,向它提供网站URL、当前链接队列、未经验证的SSL上下文和输出目录等输入,如下所示
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
现在,通过传递链接名称、页面数据、输出目录和计数器,将访问的每个网页的输出写入一个文件中,如下所示
write_output(link, page, output_dir, counter)
queue = find_links(website, page, queue)
logger.info("Identified {} links throughout website".format(
len(queue)))
现在,当我们通过提供网站的URL、输出目录和日志文件的路径来运行这个脚本时,我们将得到有关该网页的详细信息,可供今后使用。
猎取病毒
你有没有想过,法医分析师、安全研究人员和事件调查人员如何理解有用的软件和恶意软件之间的区别?答案就在问题本身,因为如果不研究黑客迅速产生的恶意软件,研究人员和专家就很难区分有用的软件和恶意软件。在这一节中,让我们讨论一下 VirusShare ,一个完成这一任务的工具。
了解VirusShare
VirusShare是最大的私有恶意软件样本集,为安全研究人员、事件响应者和法医分析人员提供实时恶意代码的样本。它包含超过3000万个样本。
VirusShare的好处是免费提供恶意软件的哈希值列表。任何人都可以使用这些哈希值来创建一个非常全面的哈希值集,并利用它来识别潜在的恶意文件。但在使用VirusShare之前,我们建议你访问https://virusshare.com ,以了解更多细节。
使用Python从VirusShare创建以新行分隔的哈希列表
VirusShare的哈希列表可以被各种取证工具使用,如X-ways和EnCase。在下面讨论的脚本中,我们将自动从VirusShare下载哈希列表,以创建一个以新行分隔的哈希列表。
对于这个脚本,我们需要一个第三方的Python库 tqdm ,可以下载如下
pip install tqdm
请注意,在这个脚本中,首先我们将读取VirusShare的哈希值页面,并动态地识别最新的哈希值列表。然后我们将初始化进度条并下载所需范围内的哈希列表。
首先,导入下面的库 –
from __future__ import print_function
import argparse
import os
import ssl
import sys
import tqdm
from urllib.request import urlopen
import urllib.error
这个脚本接受一个位置参数,即散列集的所需路径 −
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()
现在,我们将执行标准输入验证,如下所示
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)
现在我们需要用 ****kwargs** 作为参数来定义 main( )函数,因为这将创建一个字典,我们可以参考支持提供的关键参数,如下图所示。
def main(hashset, **kwargs):
url = "https://virusshare.com/hashes.4n6"
print("[+] Identifying hash set range from {}".format(url))
context = ssl._create_unverified_context()
现在,我们需要使用 urlib.request.urlopen() 方法来打开VirusShare的hash页面。我们将使用try-except块,如下所示
try:
index = urlopen(url, context = context).read().decode("utf-8")
except urllib.error.HTTPError as e:
print("[-] Error accessing webpage - exiting..")
sys.exit(1)
现在,从下载的网页中找出最新的哈希列表。你可以通过找到HTML href 标签的最后一个实例来实现VirusShare哈希列表。它可以通过以下几行代码来完成:
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
现在,我们将使用 tqdm.trange( )方法来创建一个循环和进度条,如下所示
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
成功执行上述步骤后,我们将以a+模式打开哈希集文本文件,将其追加到文本文件的底部。
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))
运行上述脚本后,你将得到包含MD5哈希值的最新文本格式的哈希列表。