11#!/usr/bin/env python3
22
33"""
4- Usage: script.py [options] <os>
4+ Usage:
5+ script.py --list
6+ script.py [options] <os_regex>
57
68Options:
79 -h --help Display this message
10+ -l --list List available OS in the database
811 -d --debug Enable debug output
912"""
1013
1114
1215import sys
1316import logging
17+ from datetime import datetime
1418from collections import Counter
1519
20+ import matplotlib
21+ import matplotlib .pyplot as plt
22+ import seaborn as sns
23+ import pandas as pd
1624from docopt import docopt
1725from py2neo import Graph
1826
1927from oswatcher .model import OS
2028
2129DB_PASSWORD = "admin"
22-
30+ PROTECTIONS = ['relro' , 'canary' , 'nx' , 'fortify_source' , 'rpath' , 'runpath' , 'symtables' ]
31+ OS_CHECKSEC_QUERY = """
32+ MATCH (os:OS)-[:OWNS_FILESYSTEM]->(root:Inode)-[:HAS_CHILD*]->(i:Inode)
33+ WHERE os.name = '{}' AND i.checksec = True
34+ RETURN i
35+ """
2336
2437def init_logger (debug = False ):
2538 logging_level = logging .INFO
@@ -37,41 +50,75 @@ def main(args):
3750 logging .info ('connect to Neo4j DB' )
3851 graph = Graph (password = DB_PASSWORD )
3952
40- os_name = args ['<os>' ]
41- os = OS .match (graph ).where ("_.name = '{}'" .format (os_name )).first ()
42- if os is None :
43- logging .info ('unable to find OS %s in the database' , os_name )
53+ # list ?
54+ if args ['--list' ]:
4455 logging .info ('available operating systems:' )
4556 for os in OS .match (graph ):
46- logging .info ('⭢ %s' , os .name )
57+ logging .info ('\t %s' , os .name )
58+ return
59+
60+ os_regex = args ['<os_regex>' ]
61+ os_match = OS .match (graph ).where ("_.name =~ '{}'" .format (os_regex ))
62+ if os_match is None :
63+ logging .info ('unable to find OS that matches \' %s\' regex in the database' , os_regex )
4764 return 1
4865
49- # TODO translate to py2neo API
50- checksec_inodes = graph .run ("MATCH (os:OS)-[:OWNS_FILESYSTEM]->(root:Inode)-[:HAS_CHILD*]->(i:Inode) WHERE os.name = 'ubuntu16.04' AND i.checksec = True return i" )
51- c = Counter ()
52- for node in checksec_inodes :
53- inode = node ['i' ]
54- logging .debug ('%s: %s' , inode ['name' ], inode ['mime_type' ])
55- c ['total' ] += 1
56- if inode ['relro' ]:
57- c ['relro' ] += 1
58- if inode ['canary' ]:
59- c ['canary' ] += 1
60- if inode ['nx' ]:
61- c ['nx' ] += 1
62- if inode ['rpath' ]:
63- c ['rpath' ] += 1
64- if inode ['runpath' ]:
65- c ['runpath' ] += 1
66- if inode ['symtables' ]:
67- c ['symtables' ] += 1
68- if inode ['fortify_source' ]:
69- c ['fortify_source' ] += 1
70-
71- logging .info ('Results for %s' , os .name )
72- logging .info ('Total binaries: %d' , c ['total' ])
73- for feature in ['relro' , 'canary' , 'nx' , 'rpath' , 'runpath' , 'symtables' , 'fortify_source' ]:
74- logging .info ('%s: %.1f%%' , feature , c [feature ] * 100 / c ['total' ])
66+ os_df_list = []
67+ # iterate over OS list, sorted by release date, converted from string to date object
68+ for os in sorted (os_match , key = lambda x : datetime .strptime (x .release_date , '%Y-%m-%d' )):
69+ # TODO translate to py2neo API
70+ checksec_inodes = graph .run (OS_CHECKSEC_QUERY .format (os .name ))
71+ c = Counter ()
72+ for node in checksec_inodes :
73+ inode = node ['i' ]
74+ logging .debug ('%s: %s' , inode ['name' ], inode ['mime_type' ])
75+ c ['total' ] += 1
76+ if inode ['relro' ]:
77+ c ['relro' ] += 1
78+ if inode ['canary' ]:
79+ c ['canary' ] += 1
80+ if inode ['nx' ]:
81+ c ['nx' ] += 1
82+ if inode ['rpath' ]:
83+ c ['rpath' ] += 1
84+ if inode ['runpath' ]:
85+ c ['runpath' ] += 1
86+ if inode ['symtables' ]:
87+ c ['symtables' ] += 1
88+ if inode ['fortify_source' ]:
89+ c ['fortify_source' ] += 1
90+
91+ logging .info ('Results for %s' , os .name )
92+ logging .info ('Total binaries: %d' , c ['total' ])
93+ for feature in PROTECTIONS :
94+ logging .info ('%s: %.1f%%' , feature , c [feature ] * 100 / c ['total' ])
95+
96+ # fix matplotlib, uses agg by default, non-gui backend
97+ matplotlib .use ('tkagg' )
98+ sns .set_style ('whitegrid' )
99+
100+ per_data = []
101+ for feature in PROTECTIONS :
102+ value = c [feature ] * 100 / c ['total' ]
103+ per_data .append (value )
104+ # initialize OS Panda DataFrame
105+ df = pd .DataFrame ({'Protections' : PROTECTIONS , 'Percentage' : per_data , 'OS' : os .name })
106+ os_df_list .append (df )
107+
108+ # concatenate all the individual DataFrames
109+ main_df = pd .concat (os_df_list , ignore_index = True )
110+
111+ logging .info ('Displaying results...' )
112+ if len (os_df_list ) == 1 :
113+ ax = sns .barplot (x = "Protections" , y = "Percentage" , data = main_df )
114+ ax .set_title ('{} binary security overview' .format (os_regex ))
115+ else :
116+ ax = sns .barplot (x = "Protections" , y = "Percentage" , hue = "OS" , data = main_df )
117+ ax .set_title ('binary security overview for regex "{}"' .format (os_regex ))
118+ # show plot
119+ plt .legend (loc = 'upper right' )
120+ plt .show ()
121+
75122
76123if __name__ == '__main__' :
77124 args = docopt (__doc__ )
0 commit comments