Skip to content

Commit 3b90147

Browse files
committed
Added generating TTL distribution and packets/source CDF graphs
1 parent 541e009 commit 3b90147

File tree

5 files changed

+288
-21
lines changed

5 files changed

+288
-21
lines changed

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ duckdb
22
pandas
33
pyarrow
44
pymisp
5+
matplotlib
6+
seaborn

src/attack.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from datetime import datetime, timedelta
1111

1212
from util import AMPLIFICATION_SERVICES, ETHERNET_TYPES, DNS_QUERY_TYPES, ICMP_TYPES, TCP_FLAG_NAMES, \
13-
get_outliers_single, get_outliers_mult, FileType
13+
get_outliers_single, get_outliers_mult, get_ttl_distribution, get_packet_cdf, FileType
1414
from logger import LOGGER
1515
from misp import MispInstance
1616

@@ -48,6 +48,12 @@ def filter_data_on_target(self, target: list[str]):
4848
df = self.db.execute(f"select count() as entries, sum(nr_packets) as total from '{self.view}'").fetchdf()
4949
LOGGER.debug(f"Attack object contains {int(df['entries'][0])} entries, with information on {int(df['total'][0])} packets")
5050

51+
def ttl_distribution(self):
52+
return get_ttl_distribution(self.db, self.view)
53+
54+
def packet_cdf(self):
55+
return get_packet_cdf(self.db, self.view)
56+
5157

5258
class AttackVector:
5359
def __init__(self, db: DuckDBPyConnection, view: str, source_port, protocol: str, filetype: FileType):

src/graphs.py

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#! /usr/bin/env python3
2+
3+
###############################################################################
4+
import sys
5+
import os
6+
import logging
7+
import argparse
8+
from pathlib import Path
9+
from argparse import RawTextHelpFormatter
10+
import textwrap
11+
12+
import pandas as pd
13+
import pprint
14+
from math import pi
15+
import duckdb
16+
17+
import seaborn as sns
18+
import matplotlib
19+
import matplotlib.pyplot as plt
20+
21+
22+
# # ------------------------------------------------------------------------------
23+
# def createBarGraph_old(df, title=' ', y_label='score/percentage', label_suffix='', palette=paletteR, widegraph=False):
24+
# pp = pprint.PrettyPrinter(indent=4)
25+
#
26+
# sns.set_style('ticks')
27+
#
28+
# # Assume first column are the periods
29+
# df = df.set_index(df.columns[0])
30+
#
31+
# categories = list(df.columns)
32+
# # print("Categories ({}): {}".format(len(categories), categories))
33+
#
34+
# periods = list(df.index.values)
35+
# if isinstance(df.index, pd.DatetimeIndex):
36+
# periods = [str(prd)[:10] for prd in df.index.values]
37+
# # print("Periods ({}): {}".format(len(periods), periods))
38+
#
39+
# nr_of_bars = len(periods) * len(categories)
40+
#
41+
# # Create a linear color map from the palette given
42+
# # to avoid overrunning the palette
43+
# segments = len(periods)
44+
# my_cmap = LinearSegmentedColormap.from_list('Custom', palette, segments)
45+
#
46+
# # if (df.columns)
47+
# figwidth = 3 + (len(df.columns) * len(df)) / 5.5
48+
# barWidth = 1.0
49+
# # Number of bars as gap in between categories
50+
# cat_gap = 1
51+
#
52+
# if widegraph:
53+
# figwidth *= 3
54+
# barWidth = 0.5
55+
#
56+
# plt.figure(figsize=(figwidth, 8))
57+
# ax = plt.subplot()
58+
# ax.set_title(title, fontname=_graph_font, fontsize='large', y=1.05)
59+
# ax.set_ylabel('score/percentage', fontname=_graph_font, fontsize='medium', loc='center')
60+
# ax.set_xlabel('category', fontname=_graph_font, fontsize='medium', loc='center', labelpad=15.0)
61+
# ax.spines['bottom'].set_linewidth(0.5)
62+
# ax.spines['left'].set_linewidth(0.5)
63+
#
64+
# ax.set_ylim(0, 100)
65+
# loc = matplotlib.ticker.MultipleLocator(base=10)
66+
# ax.yaxis.set_major_locator(loc)
67+
# ax.yaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(2))
68+
# plt.tick_params(axis='y', which='minor', direction='out', length=3, width=0.5)
69+
# plt.tick_params(axis='y', which='major', width=0.5, labelsize='small')
70+
# plt.grid(which='major', axis='y', linestyle='dotted', linewidth=0.5, color='black', alpha=0.3)
71+
#
72+
# ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(len(periods) + 1))
73+
# ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(len(periods) + cat_gap))
74+
# plt.tick_params(axis='x', which='minor', direction='out', length=0, width=0.5, rotation=90, labelsize='x-small')
75+
# plt.tick_params(axis='x', which='major', direction='out', length=0, width=0.5, labelsize='small')
76+
#
77+
# plt.xticks(fontname=_graph_font)
78+
# plt.yticks(fontname=_graph_font)
79+
#
80+
# for i in range(0, len(periods)):
81+
# rbars = range(i + 1, nr_of_bars + cat_gap * len(categories) + 1, len(periods) + cat_gap)
82+
# plt.bar(rbars,
83+
# df.iloc[i, :].tolist(),
84+
# width=barWidth,
85+
# color=my_cmap(i),
86+
# edgecolor=(1, 1, 1, 1),
87+
# linewidth=1,
88+
# label=periods[i],
89+
# zorder=2,
90+
# )
91+
# # Plot the values on top
92+
# for j, r in enumerate(rbars):
93+
# x = r - 0.2
94+
# rotation = 'vertical'
95+
# if widegraph:
96+
# x = r - 0.05
97+
# rotation = 'horizontal'
98+
# y = df.iloc[i, j] + 1.5
99+
# s = str(int(df.iloc[i, j]))
100+
# plt.text(x=x, y=y, s=s, fontname=_graph_font, fontweight='normal', fontsize='small', rotation=rotation)
101+
#
102+
# barsx = []
103+
# for i in range(0, len(categories)):
104+
# barsx.append(i * (len(periods) + cat_gap) + len(periods) / 2 + 0.5)
105+
#
106+
# xticks = categories
107+
# if len(df) > 3:
108+
# plt.xticks(barsx, xticks, rotation='horizontal', fontname=_graph_font)
109+
# else:
110+
# plt.xticks(barsx, xticks, rotation='vertical', fontname=_graph_font)
111+
#
112+
# leg = plt.legend(prop={'family': _graph_font}, framealpha=0.5, edgecolor='grey')
113+
# for line in leg.get_lines():
114+
# line.set_linewidth(7)
115+
#
116+
# barslots = (len(periods) + cat_gap) * len(categories) - cat_gap
117+
# plt.margins(x=0.51 / barslots)
118+
# ax.set_xlim(0, barslots + 1)
119+
#
120+
# plt.tight_layout()
121+
# sns.despine()
122+
# # plt.show()
123+
#
124+
# return ax
125+
126+
127+
# ------------------------------------------------------------------------------
128+
def create_bar_graph(df, title=' ', max_x: float = None, max_y: float = None, filename: Path = None):
129+
pp = pprint.PrettyPrinter(indent=4)
130+
131+
sns.set()
132+
sns.set_style('ticks')
133+
134+
# Assume first column are the periods
135+
datax = df.columns[0]
136+
datay = df.columns[1]
137+
138+
categories = list(df.columns)
139+
140+
plt.figure(figsize=(16, 8))
141+
ax = plt.subplot()
142+
# ax.set_title(title, fontname=_graph_font, fontsize='large', y=1.05)
143+
# ax.set_ylabel(datay, fontname=_graph_font, fontsize='medium', loc='center')
144+
# ax.set_xlabel(datax, fontname=_graph_font, fontsize='medium', loc='center', labelpad=15.0)
145+
ax.set_title(title, fontsize='large', y=1.05)
146+
ax.set_ylabel(datay, fontsize='medium', loc='center')
147+
ax.set_xlabel(datax, fontsize='medium', loc='center', labelpad=15.0)
148+
149+
if max_y:
150+
ax.set_ylim(0, max_y)
151+
else:
152+
ax.set_ylim(0, df[datay].max())
153+
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
154+
plt.tick_params(axis='y', which='minor', direction='out', length=3, width=0.5)
155+
plt.tick_params(axis='y', which='major', width=0.5, labelsize='small')
156+
157+
if max_x:
158+
ax.set_xlim(0, max_x)
159+
else:
160+
ax.set_xlim(0, df[datax].max())
161+
loc = matplotlib.ticker.MultipleLocator(base=10)
162+
ax.xaxis.set_major_locator(loc)
163+
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(5))
164+
plt.tick_params(axis='x', which='minor', direction='out', length=3, width=0.5, rotation=90, labelsize='x-small')
165+
plt.tick_params(axis='x', which='major', length=5, width=0.5, rotation=0, labelsize='small')
166+
167+
plt.xticks()
168+
plt.yticks()
169+
170+
plt.grid(which='major', linestyle='dashed', color='black', linewidth=1)
171+
plt.grid(which='minor', linestyle='solid', color='grey', linewidth=0.5)
172+
173+
plt.bar(df[datax], df[datay], color='darkgreen', width=1)
174+
175+
plt.tight_layout()
176+
if filename:
177+
plt.savefig(str(filename) + '.svg', bbox_inches='tight')
178+
plt.savefig(str(filename) + '.png', bbox_inches='tight')
179+
plt.close()
180+
181+
182+
# ------------------------------------------------------------------------------
183+
def create_line_graph(df, title=' ', normalize_x: bool = False, normalize_y: bool = False, filename: Path = None):
184+
pp = pprint.PrettyPrinter(indent=4)
185+
186+
sns.set()
187+
sns.set_style('ticks')
188+
189+
datax = df.columns[0]
190+
datay = df.columns[1]
191+
192+
plt.figure(figsize=(16, 8))
193+
ax = plt.subplot()
194+
ax.set_title(title, fontsize='large', y=1.05)
195+
ax.set_ylabel(datay, fontsize='medium', loc='center')
196+
ax.set_xlabel(datax, fontsize='medium', loc='center', labelpad=15.0)
197+
198+
if normalize_y:
199+
df[datay] = df[datay] / df[datay].max()
200+
ax.set_ylim(0, df[datay].max())
201+
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
202+
plt.tick_params(axis='y', which='minor', direction='out', length=3, width=0.5)
203+
plt.tick_params(axis='y', which='major', width=0.5, labelsize='small')
204+
205+
if normalize_x:
206+
df[datax] = df[datax] / df[datax].max()
207+
ax.set_xlim(0, df[datax].max())
208+
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
209+
plt.tick_params(axis='x', which='minor', direction='out', length=3, width=0.5, rotation=90, labelsize='x-small')
210+
plt.tick_params(axis='x', which='major', length=5, width=0.5, rotation=0, labelsize='small')
211+
212+
plt.xticks()
213+
plt.yticks()
214+
215+
plt.grid(which='major', linestyle='dashed', color='black', linewidth=1)
216+
plt.grid(which='minor', linestyle='solid', color='grey', linewidth=0.5)
217+
218+
plt.plot(df[datax], df[datay], color='darkgreen')
219+
220+
plt.tight_layout()
221+
if filename:
222+
plt.savefig(str(filename) + '.svg', bbox_inches='tight')
223+
plt.savefig(str(filename) + '.png', bbox_inches='tight')
224+
plt.close()

src/main.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
from pathlib import Path
99
from argparse import ArgumentParser, Namespace
1010

11+
import matplotlib.pyplot as plt
12+
1113
from logger import LOGGER
1214
from misp import MispInstance
1315
from reader import read_files
1416
from attack import Attack, Fingerprint
1517
from analysis import infer_target, extract_attack_vectors, compute_summary
1618
from util import parquet_files_to_view, FileType, determine_filetype, determine_source_filetype, \
1719
print_logo, parse_config
20+
from graphs import create_line_graph, create_bar_graph
1821

1922
DOCKERIZED: bool = 'DISSECTOR_DOCKER' in os.environ
2023

@@ -36,6 +39,8 @@ def parse_arguments() -> Namespace:
3639
help='Optional: target IP address of this attack (subnet currently unsupported)')
3740
parser.add_argument('--ddosdb', action='store_true', help='Optional: directly upload fingerprint to DDoS-DB')
3841
parser.add_argument('--misp', action='store_true', help='Optional: directly upload fingerprint to MISP')
42+
parser.add_argument('--graph', action='store_true',
43+
help='Optional: Create graphs of the attack, stored alongside the fingerprint')
3944
parser.add_argument('--noverify', action='store_true', help="Optional: Don't verify TLS certificates")
4045
parser.add_argument('--debug', action='store_true', help='Optional: show debug messages')
4146
parser.add_argument('--show-target', action='store_true', help='Optional: Do NOT anonymize the target IP address '
@@ -77,14 +82,14 @@ def parse_arguments() -> Namespace:
7782
# Convert the file(s) to parquet
7883
dst_dir = tempfile.gettempdir() if DOCKERIZED else f"{os.getcwd()}/parquet"
7984
pqt_files = read_files(args.files, dst_dir=dst_dir, filetype=filetype, nr_processes=args.n)
80-
duration = time.time()-start
85+
duration = time.time() - start
8186
LOGGER.info(f"Conversion took {duration:.2f}s")
8287
LOGGER.debug(pqt_files)
8388

8489
if args.debug and not DOCKERIZED:
8590
# Store duckdb on disk in debug mode if not dockerized
8691
os.makedirs('duckdb', exist_ok=True)
87-
db_name = "duckdb/"+os.path.basename(args.files[0])+".duckdb"
92+
db_name = "duckdb/" + os.path.basename(args.files[0]) + ".duckdb"
8893
LOGGER.debug(f"Basename: {db_name}")
8994
if os.path.exists(db_name):
9095
os.remove(db_name)
@@ -125,6 +130,16 @@ def parse_arguments() -> Namespace:
125130
args.output.mkdir(parents=True, exist_ok=True)
126131
fingerprint.write_to_file(args.output / (fingerprint.checksum[:16] + '.json')) # write the fingerprint to disk
127132

133+
if args.graph:
134+
ttl = attack.ttl_distribution()
135+
create_bar_graph(ttl, 'TTL distribution', max_x=255,
136+
filename=args.output / (fingerprint.checksum[:16] + '_ttl'))
137+
138+
cdf = attack.packet_cdf()
139+
create_line_graph(cdf, "Cumulative distribution of packets per source", normalize_x=False,
140+
filename=args.output / (fingerprint.checksum[:16] + '_cdf'))
141+
142+
128143
if args.ddosdb: # Upload the fingerprint to a specified DDoS-DB instance
129144
fingerprint.upload_to_ddosdb(**parse_config(args.config), noverify=args.noverify)
130145
if args.misp: # Upload the fingerprint to a specified MISP instance

0 commit comments

Comments
 (0)