-
Notifications
You must be signed in to change notification settings - Fork 0
/
bard.py
executable file
·347 lines (308 loc) · 11.6 KB
/
bard.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# The BARD overlay
## System modules
import sys, socket, threading, getopt, os
## Local modules
import p_engine, rulesUpdate, rule_generator
## default values for globals
host, port = "localhost", 9999
p_ratio = 0.5
max_atoms = 10000
## 10hr work week. 1 day, 2 days. 1 week = 168hrs. 1 month = 720hrs.
frames = [10, 24, 48]#, 168, 720]
## The list of persistant atoms that await user decision and white filtering
p_atoms = []
## Filenames
white_file = "../lists/whitelist.txt"
black_file = "../lists/blacklist_bard.rules"
et_rules = "../lists/blacklist_et.rules"
dump_path = "../dump"
## Locks
list_lock = threading.Lock()
white_lock = threading.Lock()
## Implements basic client functions for connecting to server
# and recieving data in the form of a string
class Client:
## The constructor
# @param host The host to connect to
# @param port The port to connect on
def __init__(self):
self.host = host
self.port = port
self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## Recieves data in the form of a string
# @return data string
def get_data(self):
data = []
while 1:
d = self.conn.recv(1024)
data.extend(d)
if len(d) < 1024: break
return "".join(data)
## Starts a connection to the server to see if it is up
# @return errorcode 0 if everything is ok, 1 otherwise
def connect_to_server(self):
try:
self.conn.connect((self.host, self.port))
return 1
except socket.error, e:
print >>sys.stderr, "Can't connect to remote host", str(e)
return 0
## Class that runs Pengine as a thread
class PengineThread(threading.Thread):
## The constructor
# @param pengine The class isntance that will be running under
# this thread
def __init__(self):
threading.Thread.__init__(self)
self.pengine = p_engine.Pengine(frames, max_atoms, dump_path, p_ratio)
## Runs pengine indefinitely
def run(self):
while True:
list_lock.acquire()
p_atoms.append(self.pengine.run())
print "p_atoms length: ",
print len(p_atoms)
list_lock.release()
## Performs updates to local files based on server response
class FileUpdateThread(threading.Thread, Client):
## The constructor
# @param host The host to connect to
# @param port The port to connect to
def __init__(self):
threading.Thread.__init__(self)
Client.__init__(self)
## Updates the local blacklist and whitelist files
# @param serv_white The text version of the new file
# @param serv_black The text version of the new file
def update_files(self, serv_white, serv_black):
white_lock.acquire()
white_fp = file(white_file, 'w')
black_fp = file(black_file, 'w')
print >>white_fp, serv_white
print >>black_fp, serv_black
white_fp.close()
black_fp.close()
white_lock.release()
## Runs the thread indefinitely
# @return err The error code
def run(self):
if not self.connect_to_server():
return 1
err = 0
while True:
try:
self.conn.send('3') # Check for whitelist update
serv_white = self.get_data()
self.conn.send('4') # Check for blacklist update
serv_black = self.get_data()
except socket.error, e:
print >>sys.stderr, "Got a socket error in update files thread: ", str(e)
err = 1
break
except (EOFError, KeyboardInterrupt):
err = 1
break
self.update_files(serv_white, serv_black)
# Update ET rules
rulesUpdate.update()
self.conn.close()
return err
## Thread that handles user input, and updates the server with said input
class UserInputServerPushThread(threading.Thread, Client):
## The constructor
# @param p_atoms The list of persistant atoms being stored by Bard at the moment
def __init__(self):
threading.Thread.__init__(self)
Client.__init__(self)
self.p_atoms = p_atoms
self.sid = 1000001
self.rg = rule_generator.rule_generator("../lists", self.sid)
## Removes whitelisted atoms from the list of persistent atoms gotten from
# Pengine.
def filterWhites(self):
white_lock.acquire()
whiteList = open(white_file, "r")
wll = []
for line in whiteList:
if(line != "\n"):
wll.append(self.stringToAtom(line))
for index, atom in enumerate(self.p_atoms):
for j in wll:
if(p_engine.compareAtoms(atom,j)):
self.p_atoms.pop(index)
break
white_lock.release()
## Converts a line from the white_file into an atom object
# @param line A line from the file that corresponds to a whitelisted atom
# @return The atom object created for comparison with new p_atoms
def stringToAtom(self, line):
print "stringtoAtom ", line
temp = line.split()
print "stringtoAtom ", temp
a = p_engine.Pengine.Atom(temp[0],temp[1],temp[2], -1)
return a
## Prompts user for input on each atom for addition to white or black list
def getDecisions(self):
self.b_list = []
self.w_list = []
print("Assign each persistent atom to the blacklist (b) or whitelist (w) after each prompt\n")
for index, atom in enumerate(self.p_atoms):
print "here!"
i = raw_input("Atom: " + str(atom.dest_ip) + ", " + str(atom.dest_port) + ", " + str(atom.protocol) + "\n")
print "here2!"
while( i != 'b' and i != 'w'):
i = raw_input("Input b or w\n")
if(i == 'b'):
self.b_list.append(atom)
elif(i == 'w'):
self.w_list.append(atom)
self.p_atoms.pop(index)
## Updates the server's blacklist and whitelist
# @return err The error code
def updateServer(self):
if not self.connect_to_server():
return 1
err = 0
# Create snort rules
b_list_rules = ""
for b_atom in self.b_list:
b_list_rules = b_list_rules + rg.block(b_atom) + "\n"
w_list_str = ""
for w_atom in self.w_list:
w_list_str = a_list_str + w_atom + "\n"
while True:
try:
self.conn.send('1') # Prompt to update whitelist
data = self.get_data()
print data
if data != "whitelist":
err = 1
break
print "sending white list: ", '1' + w_list_str
self.conn.send('1'+ w_list_str)
if self.get_data() != 'ok':
err = 1
break
self.conn.send('2') # Prompt to update blacklist
if self.get_data() != 'blacklist':
err = 1
break
self.conn.send(b_list_rules)
if self.get_data() != 'ok':
err = 1
break
break
except socket.error, e:
print >>sys.stderr, "Got a socket error in updateServer thread: ", str(e)
err = 1
break
except (EOFError, KeyboardInterrupt):
err = 1
break
self.conn.close()
return err
## Runs the thread indefinitely
def run(self):
while True:
x = raw_input("Press Enter to label atoms")
self.filterWhites()
self.getDecisions()
print "Got Decisions"
if self.updateServer() != 0:
print "There was an error in Server update"
return 1
## The overarching Bard Class. Handles all operations from a threaded
# standpoint.
# Calls: p_engine for packet analysis
# snort for packet sniffing and blocking
# parser for snort output parsing
# etc..
# @param host The host ip to host the program
# @param port The port to use for socket connections
class Bard(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
## Initialize all components
self.host = host
self.port = port
self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## Begins by checking if the server is up
def start(self):
# start snort
#os.system("sudo /usr/local/snort/bin/snort -c /usr/local/snort/etc/snort_inline.conf -Q --daq afpacket -i eth0:vmnet1::eth0:vmnet8 -b -l ../dump &")
# start UserInputServerPushThread
uisp = UserInputServerPushThread();
uisp.start()
# start PEngine thread
pen = PengineThread();
pen.start()
# start FileUpdate thread
fup = FileUpdateThread();
fup.start()
while 1:
user_input = "" #sys.stdin.readlines()
if user_input == 'exit':
#shut down threads
sys.exit()
help_msg = """ Welcome to BARD. For help type --help.
Window size is hardcoded to 1hr. Please set frame to be a number of windows.
List of options:
-p --port to specify a port number
-h --host to specify a host ip
-t --threshold to specify a threshold persistence ration (between 0 and 1)
-f --frames to specify custom frame size. Must be comma delimited, no spaces.
"""
## Usage() Exception is caught at the end of main().
# Provides main with a single exit point for graceful error handling
class Usage(BaseException):
def __init__(self, msg):
self.msg = msg
## The main method called on runtime
def main(argv=None):
global port, host, p_ratio, frames, help_msg
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "p:h:t:f:d:",
["help", "port", "host", "threshold",
"frame", "debug"])
for o, a in opts:
print 'checking o,a'
print o, a
if o == '-p' or o == '--port':# ('-p', '--port'):
print 'got here'
a = int(a)
assert a > 0 and a <=65536, "Invalid Port"
port = a
print "Reassigned port"
if o in ('-h', '--host'):
# could place ip check here
host = a
if o in ('-t', '--threshold'):
assert a <= 1 and a > 0, "Invalid ratio, must be between 0 and 1"
p_ratio = a
if o in ('-f', '--frame'):
a_parsed = a.split(',')
for x in a_parsed:
assert type(int(x)) == int, "Incorrect format of frame, must be int"
frames.append(int(x))
if o in ('-d', '--debug'):
a_parsed = a.split(',')
"You picked atom list ", a_parsed
if o == '--help':
print help_msg
return 0
except AssertionError, msg:
raise Usage(msg)
except Usage, msg:
print msg
return 1
# Start Program
x = Bard()
x.start()
# End Program
print "Thank you for using BARD!"
return 0
if __name__ == "__main__":
sys.exit(main())