mirror of
https://github.com/adulau/aha.git
synced 2024-12-05 00:17:23 +00:00
chg: [uncommited] historical uncommited files added + paper and PhD thesis
This commit is contained in:
parent
f1039eec4d
commit
59e6fc3bc2
34 changed files with 46988 additions and 92163 deletions
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# AHA - Adaptive Honeypot Alternative (historical repository)
|
||||
|
11
aha/aha.cfg
11
aha/aha.cfg
|
@ -4,18 +4,19 @@ sleeptime=3
|
|||
#The treshold that is taken into account to decide whether the file should be
|
||||
#removed or not
|
||||
timeout=3
|
||||
logfile=/tmp/aha.log
|
||||
logfile=/home/gerard/kernel/adaptive-honeypot/linux-2.6/aha/aha.log
|
||||
#Directory where UML information is periodically stored
|
||||
exportdir=/tmp/ahaworker
|
||||
|
||||
[common]
|
||||
#Directory where the kernel writes data
|
||||
outqueue=/home/gerard/dev/linux-2.6/out
|
||||
outqueue=/home/gerard/kernel/adaptive-honeypot/linux-2.6/out
|
||||
#Directory where the aha decision process writes data for the kernel
|
||||
inqueue=/home/gerard/dev/linux-2.6/in
|
||||
inqueue=/home/gerard/kernel/adaptive-honeypot/linux-2.6/in
|
||||
|
||||
[insults]
|
||||
maxidx = 3
|
||||
|
||||
[gui]
|
||||
database=gui.db
|
||||
[game]
|
||||
cases=0.54
|
||||
block=0.1
|
||||
|
|
2237
aha/aha.log
Normal file
2237
aha/aha.log
Normal file
File diff suppressed because it is too large
Load diff
8199
aha/aha.log.recycled_pids
Normal file
8199
aha/aha.log.recycled_pids
Normal file
File diff suppressed because it is too large
Load diff
108
aha/aha.py
108
aha/aha.py
|
@ -6,58 +6,40 @@ import os,sys,random,getopt,ConfigParser
|
|||
from pyinotify import *
|
||||
from ctypes import *
|
||||
from ahalib import *
|
||||
import sys
|
||||
import os
|
||||
import sqlite3,os.path
|
||||
|
||||
class KernelEvents(ProcessEvent):
|
||||
|
||||
def __init__(self,inqueue,outqueue,insultmaxidx, guidb):
|
||||
def __init__(self,inqueue,outqueue,insultmaxidx,cases,block):
|
||||
self.ahaa = AHAActions(inqueue,outqueue)
|
||||
self.database = guidb
|
||||
self.cases = cases
|
||||
self.block = block
|
||||
self.processtrees = ProcessTrees()
|
||||
if os.path.exists(self.database):
|
||||
self.con = sqlite3.connect(self.database)
|
||||
#Do it here to win time
|
||||
self.cur = self.con.cursor()
|
||||
|
||||
#Blocks the sys_execve calls according the game
|
||||
def play(self):
|
||||
#By default allow the system call
|
||||
print "PLAY: mixed cases ",cases
|
||||
print "PLAY: blockpr", blockpr
|
||||
b = 0
|
||||
x = random.random()
|
||||
|
||||
if x < self.cases:
|
||||
print "PLAY: Cases choice: ",x
|
||||
#i.e. in 0.54 blocking probability of 0.1 should be used
|
||||
y = random.random()
|
||||
print "PLAY: Blocking choice",y
|
||||
if y < self.block:
|
||||
b = 1
|
||||
else:
|
||||
os.system('pwd')
|
||||
print "[ERROR] Database file not found ",self.database
|
||||
sys.exit(1)
|
||||
|
||||
def askgui(self, filekey,msg):
|
||||
ret = False
|
||||
program = os.path.basename(msg['file'][0])
|
||||
args = ','.join(msg['argument'][1:])
|
||||
#Lets see what the user has defined
|
||||
action = 0
|
||||
for row in self.cur.execute('SELECT action FROM perms WHERE cmd=?',[program]):
|
||||
action = int(row[0])
|
||||
if action == 0:
|
||||
#Message is allowed
|
||||
self.ahaa.create_message(filekey,block=0,exitcode=0, insult=0,
|
||||
substitue=0)
|
||||
ret = True
|
||||
if action == 1:
|
||||
#Message is blocked
|
||||
self.ahaa.create_message(filekey, block=1,
|
||||
exitcode=KERNEL_ERRORS.EACESS, insult=0,
|
||||
substitue=0)
|
||||
ret = True
|
||||
if action == 2:
|
||||
#User is insulted
|
||||
self.ahaa.create_message(filekey, block=0, exitcode=0, insult=2,
|
||||
substitue=0)
|
||||
ret = True
|
||||
|
||||
#Update the gui shell this takes time but the message had already
|
||||
#been transmitted to the kernel
|
||||
outstr = program + "(" + args + ")"
|
||||
self.cur.execute('INSERT INTO shell (cmd) VALUES (?)',[outstr])
|
||||
self.con.commit()
|
||||
#FIXME If fallback of decision to allow it is anyhow too late
|
||||
#Therefore allows the kernel by it self the execution
|
||||
return ret
|
||||
#Exception handling is done in decision method
|
||||
# in the other cases another blocking probability should be used
|
||||
y = random.random()
|
||||
q = 1-self.block
|
||||
print "PLAY: Other blocking probability should be used ",q
|
||||
print "PLAY: Other blocking choice: ",y
|
||||
if y < q:
|
||||
b = 1
|
||||
|
||||
return b
|
||||
|
||||
def decision(self,filekey,msg):
|
||||
try:
|
||||
|
@ -71,28 +53,37 @@ class KernelEvents(ProcessEvent):
|
|||
if type == 1:
|
||||
# Got sys_execve
|
||||
command = msg['file'][0]
|
||||
print "Got command: ",command, "in ",filekey
|
||||
#Is there a new SSH connection?
|
||||
if msg['file'][0] == '/usr/sbin/sshd':
|
||||
print "New user found pid=",pid,",ppid=",ppid
|
||||
self.processtrees.addUser(pid)
|
||||
self.ahaa.create_message(filekey,block=0, exitcode=0,
|
||||
insult=0, substitue=0)
|
||||
#print "New user found pid=",pid,",ppid=",ppid
|
||||
return
|
||||
|
||||
#is this process induced by clone or sys_execve related to a user?
|
||||
if self.processtrees.searchTree(pid,ppid) == False:
|
||||
print "Process belongs to the system, allow it"
|
||||
#Note the process could also belong to a local
|
||||
#connected user
|
||||
self.ahaa.create_message(filekey,block=0, exitcode=0,
|
||||
insult=0, substitue=0)
|
||||
#print "Process belongs to the system, allow it"
|
||||
return
|
||||
else:
|
||||
if msg.has_key('file'):
|
||||
r = self.askgui(filekey,msg)
|
||||
if r:
|
||||
return
|
||||
|
||||
print "Process belongs to a user, play"
|
||||
shouldBlock = self.play()
|
||||
if shouldBlock:
|
||||
print "User process is artifically blocked ..."
|
||||
self.ahaa.create_message(filekey,block=1,
|
||||
exitcode=KERNEL_ERRORS.EACESS,insult=0,
|
||||
substitue=0)
|
||||
return
|
||||
else:
|
||||
print "User process is allowed ..."
|
||||
self.ahaa.create_message(filekey,block=0,exitcode=0,insult=0,
|
||||
substitue=0)
|
||||
return
|
||||
except KeyError,e:
|
||||
print "EXCEPTION: KeyError"
|
||||
except IndexError,w:
|
||||
|
@ -151,14 +142,15 @@ if __name__ == '__main__':
|
|||
inqueue = c.get('common','inqueue')
|
||||
outqueue = c.get('common','outqueue')
|
||||
insultmaxidx = int(c.get('insults','maxidx'))
|
||||
guidb = c.get('gui','database')
|
||||
cases = float(c.get('game','cases'))
|
||||
blockpr = float(c.get('game','block'))
|
||||
|
||||
print "Setting up listeners..."
|
||||
wm = WatchManager()
|
||||
mask = IN_CLOSE_WRITE # watched events
|
||||
|
||||
k = KernelEvents(inqueue, outqueue,insultmaxidx,guidb)
|
||||
#If database is not valid exit here
|
||||
notifier = Notifier(wm,k)
|
||||
notifier = Notifier(wm, KernelEvents(inqueue,outqueue,insultmaxidx,
|
||||
cases,blockpr))
|
||||
wdd = wm.add_watch(outqueue, mask, rec=True)
|
||||
|
||||
print "Waiting for events..."
|
||||
|
|
168
aha/ahagui.py
168
aha/ahagui.py
|
@ -1,168 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
#Adaptive Honeypot Demo
|
||||
#Gerard Wagener
|
||||
#License GPL
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
import getopt,sys,sqlite3
|
||||
|
||||
#Default values
|
||||
commandList = ['uname', 'id','cat','wget','rm','ls','tar', 'vim']
|
||||
database='gui.db'
|
||||
shouldCreate = False
|
||||
timerInterval = 500
|
||||
|
||||
def usage():
|
||||
print """
|
||||
Adaptive Honeypot Alternative - Demo
|
||||
|
||||
ahagui [-hdc]
|
||||
|
||||
OPTIONS
|
||||
-h --help Shows this screen
|
||||
-d --database Specify the message exchange database; default value = gui.db
|
||||
-c --create Create a new database
|
||||
"""
|
||||
|
||||
def createDatabase():
|
||||
try:
|
||||
|
||||
con = sqlite3.connect(database)
|
||||
cur = con.cursor()
|
||||
cur.execute('CREATE TABLE perms (cmd VARCHAR(100), action INTEGER)')
|
||||
#Go through the command list and allow everything
|
||||
for command in commandList:
|
||||
cur.execute('INSERT INTO perms (cmd,action) VALUES (?,?)',
|
||||
[command,0])
|
||||
cur.execute('CREATE TABLE shell (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
|
||||
cmd VARCHAR(255))')
|
||||
con.commit()
|
||||
print "Database successfully created"
|
||||
|
||||
except sqlite3.OperationalError,e:
|
||||
print e
|
||||
sys.stderr.write('Failed to create database '+database +'\n' )
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def resetDatabase(con):
|
||||
cur=con.cursor()
|
||||
cur.execute('UPDATE perms SET action=0')
|
||||
cur.execute('DELETE FROM shell')
|
||||
con.commit()
|
||||
|
||||
class ActionCombo(QtGui.QComboBox):
|
||||
|
||||
def __init__(self,name,con):
|
||||
QtGui.QComboBox.__init__(self)
|
||||
|
||||
self.con = con
|
||||
self.actionList = ['Allow','Block','Insult']
|
||||
self.addActions()
|
||||
self.name = name
|
||||
self.connect(self, QtCore.SIGNAL('currentIndexChanged (int)'),
|
||||
QtCore.SLOT('handler(int)'))
|
||||
|
||||
def addActions(self):
|
||||
for action in self.actionList:
|
||||
self.addItem(action)
|
||||
|
||||
|
||||
@QtCore.pyqtSlot('int')
|
||||
def handler(self,value):
|
||||
cur = self.con
|
||||
cur.execute('UPDATE perms SET action=? WHERE cmd=?',[value,self.name])
|
||||
con.commit()
|
||||
|
||||
class Example(QtGui.QWidget):
|
||||
|
||||
|
||||
def __init__(self, con):
|
||||
self.con = con
|
||||
QtGui.QWidget.__init__(self,None)
|
||||
|
||||
self.initUI()
|
||||
self.lastId = 0
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def updateShell(self):
|
||||
try:
|
||||
cur = self.con.cursor()
|
||||
for row in cur.execute('SELECT cmd,id FROM shell WHERE id>?',
|
||||
[self.lastId]):
|
||||
self.topright.appendPlainText(row[0])
|
||||
self.lastId = int(row[1])
|
||||
except sqlite3.OperationalError,e:
|
||||
self.topright.appendPlainText('Warning! System calls are not available')
|
||||
|
||||
def initUI(self):
|
||||
|
||||
hbox = QtGui.QHBoxLayout(self)
|
||||
|
||||
topleft = QtGui.QWidget()
|
||||
topleftScroll = QtGui.QScrollArea()
|
||||
topleftgrid = QtGui.QGridLayout()
|
||||
topleft.setLayout(topleftgrid)
|
||||
|
||||
self.topright = QtGui.QPlainTextEdit()
|
||||
|
||||
#Scroll test
|
||||
for i in xrange(0,len(commandList)):
|
||||
name = commandList[i]
|
||||
topleftgrid.addWidget(QtGui.QLabel(name),i,0)
|
||||
topleftgrid.addWidget(ActionCombo(name,self.con),i,1)
|
||||
|
||||
self.timer=QtCore.QTimer()
|
||||
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"),
|
||||
self.updateShell)
|
||||
#FIXME Slot does work here?
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
self.timer.start(timerInterval)
|
||||
|
||||
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
|
||||
topleftScroll.setWidget(topleft)
|
||||
splitter1.addWidget(topleftScroll)
|
||||
splitter1.addWidget(self.topright)
|
||||
|
||||
|
||||
hbox.addWidget(splitter1)
|
||||
self.setLayout(hbox)
|
||||
|
||||
self.setGeometry(250, 200, 450, 350)
|
||||
self.setWindowTitle('Adaptive Honeypot Alternative - Demo')
|
||||
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hcd:", ["help", "create",
|
||||
"database="])
|
||||
for o,a in opts:
|
||||
if o in ('-h','--help'):
|
||||
usage()
|
||||
if o in ('-d','--database'):
|
||||
database = a
|
||||
if o in ('-c','--create'):
|
||||
shouldCreate = True
|
||||
|
||||
except getopt.GetoptError, err:
|
||||
print str(err)
|
||||
usage()
|
||||
|
||||
|
||||
if (shouldCreate):
|
||||
createDatabase()
|
||||
|
||||
con=None
|
||||
try:
|
||||
con = sqlite3.connect(database)
|
||||
resetDatabase(con)
|
||||
|
||||
except sqlite3.OperationalError,e:
|
||||
sys.stderr.write('Cannot connect to message exchange database '
|
||||
+database +'\n')
|
||||
sys.exit(1)
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
exm = Example(con)
|
||||
exm.show()
|
||||
app.exec_()
|
||||
|
115
aha/ahakern01/aha-eye.py
Normal file
115
aha/ahakern01/aha-eye.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/python
|
||||
#Analyse log files generated from aha-worker and generate reports
|
||||
#FIXME If a PID is reused during an SSH sessions aha might be confused
|
||||
#a better solution than shadowing the process list is to contruct the
|
||||
#process vectors on the fly
|
||||
|
||||
from ahalib import *
|
||||
import getopt
|
||||
#logfile='aha.log'
|
||||
aha = AHAActions(None,None)
|
||||
ptress = ProcessTrees()
|
||||
|
||||
def usage(exitcode):
|
||||
print """
|
||||
Analyze log files of aha-worker and recovers process trees
|
||||
|
||||
OPTIONS
|
||||
|
||||
-h Shows this screen
|
||||
-e Specifies an export file (i.e. accessible through apache)
|
||||
-l Specifies the log file generated by aha-worker
|
||||
|
||||
AUTHOR
|
||||
Gerard Wagener
|
||||
|
||||
LICENSE
|
||||
|
||||
GPL
|
||||
"""
|
||||
sys.exit(exitcode)
|
||||
|
||||
def extract_object(obj,exportdir):
|
||||
try:
|
||||
#FIXME Until now discard decisions from aha
|
||||
if obj.has_key('block') and obj.has_key('insult'):
|
||||
return
|
||||
tp = int(obj['type'][0])
|
||||
pid = int(obj['pid'][0])
|
||||
ppid = int(obj['ppid'][0])
|
||||
ts = obj['timestamp']
|
||||
#handle sys_clone messages
|
||||
if (tp == 2):
|
||||
ptress.searchTree(pid,ppid)
|
||||
|
||||
|
||||
#handle sys_execve
|
||||
if (tp == 1):
|
||||
file = obj['file'][0]
|
||||
if file == '/usr/sbin/sshd':
|
||||
print "Potential new user found: pid=",pid,"ppid=",ppid
|
||||
ptress.addUser(pid)
|
||||
ptress.annotateProcessList(obj)
|
||||
return
|
||||
if ptress.searchTree(pid,ppid):
|
||||
print "User related command: ",file,"pid=",pid," ppid=",ppid
|
||||
#Annotation info is only available in sys_execve messages
|
||||
print "annotate process ",pid
|
||||
ptress.annotateProcessList(obj)
|
||||
#But need to record ppid for pid reusage .grrr
|
||||
if (tp == 2):
|
||||
if (ptress.searchTree(pid,ppid)):
|
||||
ptress.annotateProcessList(obj)
|
||||
# Thread exited
|
||||
if (tp == 3):
|
||||
if ptress.does_user_disconnects(pid):
|
||||
#User disconnected generate a report, to avoid that other
|
||||
#information is droped
|
||||
print "List export is triggered for root ",pid
|
||||
ptress.exportUserListTxt(exportdir)
|
||||
ptress.silent_remove_pid(pid)
|
||||
#Cleanup annotated list
|
||||
print "Clean annotated list"
|
||||
ptress.clean_aplist(pid)
|
||||
except ValueError,e:
|
||||
sys.stderr.write("Failed to parse "+str(obj) + '\n')
|
||||
except KeyError,e:
|
||||
sys.stderr.write("Incomplete message\n")
|
||||
|
||||
try:
|
||||
line = None
|
||||
logfile = None
|
||||
exportdir = None
|
||||
opts,args = getopt.getopt(sys.argv[1:],"hl:e:",["help","logfile=", "export="])
|
||||
for o,a in opts:
|
||||
if o in ('--help','-h'):
|
||||
usage(0)
|
||||
if o in ('--logfile','-l'):
|
||||
logfile = a
|
||||
if o in ('--export','-e'):
|
||||
exportdir = a
|
||||
if logfile == None:
|
||||
sys.stderr.write('A log file from aha-worker needs to be specified\n')
|
||||
sys.exit(1)
|
||||
#Load config file and get opts
|
||||
if exportdir == None:
|
||||
sys.stderr.write('An export file needs to be specified\n')
|
||||
sys.exit(1)
|
||||
f = open(logfile,'r')
|
||||
for line in f:
|
||||
(timestamp,key,serobj) = line.split('|',2)
|
||||
obj = aha.unserializeMessage(serobj)
|
||||
extract_object(obj,exportdir)
|
||||
f.close()
|
||||
#Dump process trees
|
||||
ptress.exportUserListTxt(exportdir)
|
||||
sys.exit(0)
|
||||
except ValueError,e:
|
||||
#File may be incomplete
|
||||
sys.stderr.write("Value error, file may be incomplete\n")
|
||||
sys.stderr.write(str(e) + '\n')
|
||||
sys.stderr.write(line+'\n')
|
||||
|
||||
except getopt.GetoptError,e:
|
||||
sys.stderr.write(str(e)+'\n')
|
||||
sys.exit(1)
|
145
aha/ahakern01/aha-worker.py
Normal file
145
aha/ahakern01/aha-worker.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
#Cleans up messages laying around from the kernel / aha framework
|
||||
#Copyright (c) 2010 Gerard Wagener
|
||||
#LICENSE GPL
|
||||
#
|
||||
#
|
||||
#We assume that after the timeout the message must be consummed and then
|
||||
#it is removed
|
||||
#Do this as seperated process aiming to speed up at maximum time
|
||||
#for the aha tak to take the decisions
|
||||
#The aha framework can be launched then in screen
|
||||
#
|
||||
|
||||
|
||||
import dircache,os.path,time,sys,ConfigParser,getopt
|
||||
from ahalib import *
|
||||
|
||||
class PeriodTaks():
|
||||
#Define message types
|
||||
FROM_KERNEL = 1
|
||||
TO_KERNEL = 2
|
||||
|
||||
def __init__(self,outqueue,inqueue, timeout,sleeptime, logfile):
|
||||
self.outqueue= outqueue
|
||||
self.inqueue = inqueue
|
||||
self.timeout = timeout
|
||||
self.sleeptime = sleeptime
|
||||
self.logfile = logfile
|
||||
#Log file descriptor
|
||||
self.lfd = open(logfile,'a')
|
||||
self.aha = AHAActions(inqueue,outqueue)
|
||||
|
||||
#Make close action externally available
|
||||
def closeLogFile(self):
|
||||
self.lfd.close()
|
||||
|
||||
def remove_old_msg(self,queue):
|
||||
#Get current date if the files are older than the timeout remove them
|
||||
t0 = int(time.strftime("%s"))
|
||||
#FIXME os.walkdir, os.listdir could also be used
|
||||
dircache.reset()
|
||||
files = dircache.listdir(queue)
|
||||
for file in files:
|
||||
af = queue + os.sep + file
|
||||
s = os.stat(af)
|
||||
t1 = int(s[os.path.stat.ST_CTIME])
|
||||
delta = t0 - t1
|
||||
if (delta > self.timeout):
|
||||
#Old file was found record it
|
||||
if queue == self.outqueue:
|
||||
self.record_message(af,t1,PeriodTaks.FROM_KERNEL)
|
||||
if queue == self.inqueue:
|
||||
self.record_message(af,t1,PeriodTaks.TO_KERNEL)
|
||||
#Remove it
|
||||
self.aha.silent_clean(af)
|
||||
|
||||
def clean_input_queue(self):
|
||||
try:
|
||||
self.remove_old_msg(self.inqueue)
|
||||
except OSError,e:
|
||||
sys.stderr.write(str(e))
|
||||
|
||||
|
||||
def clean_output_queue(self):
|
||||
try:
|
||||
self.remove_old_msg(self.outqueue)
|
||||
except OSError,e:
|
||||
sys.stderr.write(str(e))
|
||||
|
||||
#Parse the file an put the information in a log file for later processing
|
||||
#One log file is handier than for each message a file
|
||||
#Take timestamps when the kernel created the file
|
||||
def record_message(self,filename, ctime,type):
|
||||
try:
|
||||
if type == PeriodTaks.FROM_KERNEL:
|
||||
msg = self.aha.load_file(filename)
|
||||
logEntry = self.aha.serializeKernelMessage(msg,filename,ctime)
|
||||
self.lfd.write(logEntry)
|
||||
|
||||
if type == PeriodTaks.TO_KERNEL:
|
||||
msg = self.aha.get_kernel_reply(filename)
|
||||
logEntry=self.aha.serializeAhaReply(msg,filename,ctime)
|
||||
self.lfd.write(logEntry)
|
||||
except IOError,e:
|
||||
sys.stderr.write('Failed to record message: %s\n'%filename)
|
||||
|
||||
def usage(exitcode):
|
||||
print """
|
||||
Do periodic tasks, like cleanups from the AHA framework
|
||||
|
||||
-h Shows this screen
|
||||
-c Specifies the config file
|
||||
|
||||
AUTHOR
|
||||
|
||||
Gerard Wagener
|
||||
|
||||
LICENSE
|
||||
|
||||
GPL
|
||||
|
||||
"""
|
||||
return exitcode
|
||||
|
||||
|
||||
configfile = None
|
||||
isHelp = 0
|
||||
p = None
|
||||
try:
|
||||
opts,args = getopt.getopt(sys.argv[1:],"hc:",["help","config="])
|
||||
for o,a in opts:
|
||||
if o in ('--help','-h'):
|
||||
usage(0)
|
||||
if o in ('--config','-c'):
|
||||
configfile = a
|
||||
if configfile == None:
|
||||
sys.stderr.write('A configuration file needs to be specified\n')
|
||||
sys.exit(1)
|
||||
#Load config file and get opts
|
||||
c=ConfigParser.ConfigParser()
|
||||
c.read(configfile)
|
||||
timeout = int(c.get('worker','timeout'))
|
||||
sleeptime = int(c.get('worker','sleeptime'))
|
||||
inqueue = c.get('common','inqueue')
|
||||
outqueue= c.get('common','outqueue')
|
||||
logfile = c.get('worker','logfile')
|
||||
p = PeriodTaks(outqueue, inqueue, timeout,sleeptime,logfile)
|
||||
print "Start working ..."
|
||||
while True:
|
||||
p.clean_input_queue()
|
||||
p.clean_output_queue()
|
||||
time.sleep(sleeptime)
|
||||
print "Resume ..."
|
||||
|
||||
sys.exit(0)
|
||||
except getopt.GetoptError,e:
|
||||
usage(1)
|
||||
except ConfigParser.NoOptionError,e:
|
||||
sys.stderr.write('Configuration error. (%s)\n'%(str(e)))
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt,e:
|
||||
if p !=None:
|
||||
p.closeLogFile()
|
||||
sys.exit(0)
|
||||
#Should not be reached
|
||||
sys.exit(0)
|
22
aha/ahakern01/aha.cfg
Normal file
22
aha/ahakern01/aha.cfg
Normal file
|
@ -0,0 +1,22 @@
|
|||
[worker]
|
||||
#The worker is periodically polling for work
|
||||
sleeptime=3
|
||||
#The treshold that is taken into account to decide whether the file should be
|
||||
#removed or not
|
||||
timeout=3
|
||||
logfile=/home/gerard/kernel/adaptive-honeypot/linux-2.6/aha/aha.log
|
||||
#Directory where UML information is periodically stored
|
||||
exportdir=/tmp/ahaworker
|
||||
|
||||
[common]
|
||||
#Directory where the kernel writes data
|
||||
outqueue=/home/gerard/kernel/adaptive-honeypot/linux-2.6/out
|
||||
#Directory where the aha decision process writes data for the kernel
|
||||
inqueue=/home/gerard/kernel/adaptive-honeypot/linux-2.6/in
|
||||
|
||||
[insults]
|
||||
maxidx = 3
|
||||
|
||||
[game]
|
||||
cases=0.54
|
||||
block=0.1
|
171
aha/ahakern01/aha.py
Normal file
171
aha/ahakern01/aha.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
#!/usr/bin/python
|
||||
#Core of the adaptive honeypot alternative
|
||||
# (c) Gerard Wagener
|
||||
#License GPL
|
||||
import os,sys,random,getopt,ConfigParser
|
||||
from pyinotify import *
|
||||
from ctypes import *
|
||||
from ahalib import *
|
||||
|
||||
class KernelEvents(ProcessEvent):
|
||||
|
||||
def __init__(self,inqueue,outqueue,insultmaxidx,cases,block):
|
||||
self.ahaa = AHAActions(inqueue,outqueue)
|
||||
self.cases = cases
|
||||
self.block = block
|
||||
self.processtrees = ProcessTrees()
|
||||
|
||||
#Blocks the sys_execve calls according the game
|
||||
def play(self):
|
||||
#By default allow the system call
|
||||
print "PLAY: mixed cases ",cases
|
||||
print "PLAY: blockpr", blockpr
|
||||
b = 0
|
||||
x = random.random()
|
||||
|
||||
if x < self.cases:
|
||||
print "PLAY: Cases choice: ",x
|
||||
#i.e. in 0.54 blocking probability of 0.1 should be used
|
||||
y = random.random()
|
||||
print "PLAY: Blocking choice",y
|
||||
if y < self.block:
|
||||
b = 1
|
||||
else:
|
||||
# in the other cases another blocking probability should be used
|
||||
y = random.random()
|
||||
q = 1-self.block
|
||||
print "PLAY: Other blocking probability should be used ",q
|
||||
print "PLAY: Other blocking choice: ",y
|
||||
if y < q:
|
||||
b = 1
|
||||
|
||||
return b
|
||||
|
||||
def decision(self,filekey,msg):
|
||||
try:
|
||||
pid = int(msg['pid'][0])
|
||||
ppid = int(msg['ppid'][0])
|
||||
type = int(msg['type'][0])
|
||||
#Was a process closed?
|
||||
if type == 3:
|
||||
self.processtrees.silent_remove_pid(pid)
|
||||
return
|
||||
if type == 1:
|
||||
# Got sys_execve
|
||||
command = msg['file'][0]
|
||||
print "Got command: ",command, "in ",filekey
|
||||
#Is there a new SSH connection?
|
||||
if msg['file'][0] == '/usr/sbin/sshd':
|
||||
print "New user found pid=",pid,",ppid=",ppid
|
||||
self.processtrees.addUser(pid)
|
||||
self.ahaa.create_message(filekey,block=0, exitcode=0,
|
||||
insult=0, substitue=0)
|
||||
return
|
||||
|
||||
#is this process induced by clone or sys_execve related to a user?
|
||||
if self.processtrees.searchTree(pid,ppid) == False:
|
||||
print "Process belongs to the system, allow it"
|
||||
#Note the process could also belong to a local
|
||||
#connected user
|
||||
self.ahaa.create_message(filekey,block=0, exitcode=0,
|
||||
insult=0, substitue=0)
|
||||
return
|
||||
else:
|
||||
print "Process belongs to a user, play"
|
||||
shouldBlock = self.play()
|
||||
if shouldBlock:
|
||||
print "User process is artifically blocked ..."
|
||||
self.ahaa.create_message(filekey,block=1,
|
||||
exitcode=KERNEL_ERRORS.EACESS,insult=0,
|
||||
substitue=0)
|
||||
return
|
||||
else:
|
||||
print "User process is allowed ..."
|
||||
self.ahaa.create_message(filekey,block=0,exitcode=0,insult=0,
|
||||
substitue=0)
|
||||
return
|
||||
except KeyError,e:
|
||||
print "EXCEPTION: KeyError"
|
||||
except IndexError,w:
|
||||
print "EXCEPTION: IndexError"
|
||||
except ValueError,s:
|
||||
print "EXCEPTION: ValueError"
|
||||
#Default action; allow-> out of memory
|
||||
self.ahaa.create_message(filekey,block=0,exitcode=0,insult=0,
|
||||
substitue=0)
|
||||
|
||||
def process_IN_CLOSE_WRITE(self, event):
|
||||
try:
|
||||
filename = os.path.join(event.path,event.name)
|
||||
msg = self.ahaa.load_file(filename)
|
||||
#Send back a message
|
||||
self.decision(event.name,msg)
|
||||
except IOError,e:
|
||||
sys.stderr.write("Kernel message (%s) could not be loaded or \
|
||||
decison failed\n"%event.name)
|
||||
|
||||
def usage(exitcode):
|
||||
print """
|
||||
Setup listener for kernel events of the user mode linux
|
||||
-h Shows this screen
|
||||
-c Specifies the config file
|
||||
|
||||
AUTHOR
|
||||
Gerard Wagener
|
||||
|
||||
LICENSE
|
||||
GPL
|
||||
"""
|
||||
sys.exit(exitcode)
|
||||
def shutdown(notifier):
|
||||
if notifier != None:
|
||||
print "Stop listening..."
|
||||
notifier.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
notifier = None
|
||||
configfile = None
|
||||
try:
|
||||
opts,args = getopt.getopt(sys.argv[1:],"hc:",["help","config="])
|
||||
for o,a in opts:
|
||||
if o in ('--help','-h'):
|
||||
usage(0)
|
||||
if o in ('--config','-c'):
|
||||
configfile = a
|
||||
|
||||
if configfile == None:
|
||||
sys.stderr.write('A configuration file needs to be specified\n')
|
||||
sys.exit(1)
|
||||
#Load config file and get opts
|
||||
c=ConfigParser.ConfigParser()
|
||||
c.read(configfile)
|
||||
inqueue = c.get('common','inqueue')
|
||||
outqueue = c.get('common','outqueue')
|
||||
insultmaxidx = int(c.get('insults','maxidx'))
|
||||
cases = float(c.get('game','cases'))
|
||||
blockpr = float(c.get('game','block'))
|
||||
|
||||
print "Setting up listeners..."
|
||||
wm = WatchManager()
|
||||
mask = IN_CLOSE_WRITE # watched events
|
||||
|
||||
notifier = Notifier(wm, KernelEvents(inqueue,outqueue,insultmaxidx,
|
||||
cases,blockpr))
|
||||
wdd = wm.add_watch(outqueue, mask, rec=True)
|
||||
|
||||
print "Waiting for events..."
|
||||
while True:
|
||||
# process the queue of events as explained above
|
||||
notifier.process_events()
|
||||
if notifier.check_events():
|
||||
# read notified events and enqeue them
|
||||
notifier.read_events()
|
||||
except KeyboardInterrupt:
|
||||
# destroy the inotify's instance on this interrupt (stop monitoring)
|
||||
shutdown(notifier)
|
||||
except getopt.GetoptError,e:
|
||||
usage(1)
|
||||
except ConfigParser.NoOptionError,e:
|
||||
sys.stderr.write('Configuration error. (%s)\n'%(str(e)))
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
459
aha/ahakern01/ahalib.py
Normal file
459
aha/ahakern01/ahalib.py
Normal file
|
@ -0,0 +1,459 @@
|
|||
#Common functions shared between aha and aha-worker
|
||||
#FIXME Memory leak in process trees -> need to clean up them
|
||||
#triggered by the kernel
|
||||
from ctypes import *
|
||||
import os,sys,random,datetime,json,time, unittest
|
||||
|
||||
class AHAActions:
|
||||
|
||||
def __init__(self,inqueue,outqueue):
|
||||
self.inqueue = inqueue
|
||||
self.outqueue = outqueue
|
||||
|
||||
#Can trow IOError
|
||||
def load_file(self,filename):
|
||||
msg = {}
|
||||
s = os.stat(filename)
|
||||
ts = int(s[os.path.stat.ST_CTIME])
|
||||
msg['timestamp'] = ts
|
||||
fp = open(filename,'r')
|
||||
for i in fp.read().split('\n'):
|
||||
try:
|
||||
(key,value) = i.split('=',1)
|
||||
if msg.has_key(key) == False:
|
||||
msg[key]=[]
|
||||
msg[key].append(value)
|
||||
except ValueError,e:
|
||||
pass
|
||||
fp.close()
|
||||
return msg
|
||||
|
||||
def silent_clean(self,filename):
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except OSError,e:
|
||||
pass
|
||||
|
||||
|
||||
#Can trow IOError
|
||||
def create_message(self,filename,block,exitcode,substitue,insult):
|
||||
print "CREATE_MESSAGE ",filename,"block=",block
|
||||
try:
|
||||
reply = ReplyMessage(block=block,exitcode=exitcode,substitue=substitue,
|
||||
insult = insult)
|
||||
fn = self.inqueue + os.sep + filename
|
||||
f = open (fn,'wb')
|
||||
f.write(reply)
|
||||
f.close()
|
||||
reply="(key=%s, block=%d,exitcode=%d,substitue=%d,insult=%d)"\
|
||||
%(filename,block,exitcode, substitue,insult)
|
||||
return reply
|
||||
except IOError,e:
|
||||
sys.stderr.write('Could not create reply file=(%s)\n'%filename)
|
||||
#Propagate Error to higher level. Here it is only logged
|
||||
raise IOError(e)
|
||||
|
||||
#Takes a parses kernel message as input and returns a serialized string
|
||||
#that can be put in a log file
|
||||
def serializeKernelMessage(self,msg,filename,ctime):
|
||||
data = json.dumps(msg)
|
||||
obj=datetime.datetime.fromtimestamp(ctime)
|
||||
fn = os.path.basename(filename)
|
||||
#FIXME aaargg timestamps are a mess in python
|
||||
#Use str() which is not portable, but I do not want to spend hours
|
||||
#of this shit
|
||||
sd = str(obj)
|
||||
return "%s|%s|%s\n"%(sd,fn,data);
|
||||
|
||||
def unserializeMessage(self,serobj):
|
||||
return json.loads(serobj)
|
||||
#Can throw IOError
|
||||
#FIXME not tested
|
||||
def get_kernel_reply(self,filename):
|
||||
fp = open(filename,'rb')
|
||||
buf = fp.read()
|
||||
fp.close()
|
||||
cstring = create_string_buffer(buf)
|
||||
rmsg = cast(pointer(cstring), POINTER(ReplyMessage)).contents
|
||||
return rmsg
|
||||
|
||||
#FIXME not tested
|
||||
#Take a message read from get_kernel_reply function and return a string representation
|
||||
def serializeAhaReply(self,m,filename,ctime):
|
||||
#Create generic hash. Structure may change
|
||||
msg= {'block':m.block,'exitcode':m.exitcode,'substitue':m.substitue,'insult':m.insult};
|
||||
#kernel message is also a generic hash table; reuse it
|
||||
return self.serializeKernelMessage(msg,filename,ctime)
|
||||
|
||||
class KERNEL_ERRORS():
|
||||
EPERM = -1
|
||||
ENOENT = -2
|
||||
EIO = -5
|
||||
ENOMEM = -12
|
||||
EACESS = -13
|
||||
EFAULT = -14
|
||||
EPIPE = -32
|
||||
ETXTBSY = -26
|
||||
|
||||
def __init__(self):
|
||||
self.evec = (EPERM,ENOENT,EIO,ENOMEM,EACESS,EFAULT,EPIPE,ETXTBSY)
|
||||
class ReplyMessage(Structure):
|
||||
_fields_ = [ ("block" , c_int), ("exitcode" , c_int),
|
||||
("substitue" ,c_int),("insult" , c_int) ]
|
||||
|
||||
class ProcessTrees:
|
||||
def __init__(self):
|
||||
self.userList = {}
|
||||
self.processList = {}
|
||||
self.foundUser = 0
|
||||
self.aplist = {}
|
||||
#This first clone of /usr/sbin/sshd does not has the
|
||||
#SSH specific environment variables. Therefore ask all the
|
||||
#children
|
||||
def search_ssh_info(self,pid):
|
||||
#print "Searching info for ",pid
|
||||
children = self.get_children(pid)
|
||||
#print "Children of pid",children
|
||||
for child in children:
|
||||
if self.aplist.has_key(child):
|
||||
#print "Found annotations for child %d"%child
|
||||
if self.aplist[child].has_key('ssh_client'):
|
||||
#print "Found ssh info for child %d"%child
|
||||
return self.aplist[child]['ssh_client']
|
||||
# Retuns None if ssh related information was not found
|
||||
sys.stderr.write('ERROR: No child provided SSH information\n')
|
||||
return None
|
||||
|
||||
# Record additional information about processes like SSH parameters
|
||||
# and timestamps etc
|
||||
#TODO annotate SSH_LOGNAME
|
||||
#TODO annotate used terminal
|
||||
def annotateProcessList(self,msg):
|
||||
try:
|
||||
pid = int(msg['pid'][0])
|
||||
ppid = int(msg['ppid'][0])
|
||||
if self.aplist.has_key(pid) == False:
|
||||
#Got a new process, so create a new dictionary for meta data
|
||||
self.aplist[pid] = dict()
|
||||
#Store the parent
|
||||
self.aplist[pid]['parent'] = ppid
|
||||
#Does the message has a file name ?
|
||||
if msg.has_key('file'):
|
||||
self.aplist[pid]['file'] = msg['file'][0]
|
||||
#print "Annotated pid=",pid, "file=",msg['file'][0]
|
||||
#Does the message has SSH related information?
|
||||
if msg.has_key('env'):
|
||||
# Go through the environment list
|
||||
for ev in msg['env']:
|
||||
if ev.startswith('SSH_CLIENT='):
|
||||
ev = ev.replace('SSH_CLIENT=','')
|
||||
self.aplist[pid]['ssh_client'] = ev
|
||||
print "Annotated with ssh info pid=", pid," ev",ev
|
||||
# Is there a timestamp?
|
||||
if msg.has_key('timestamp'):
|
||||
self.aplist[pid]['timestamp'] = msg['timestamp']
|
||||
|
||||
except ValueError,e:
|
||||
print e
|
||||
pass
|
||||
except IndexError,e:
|
||||
print e
|
||||
pass
|
||||
|
||||
def addUser(self,pid):
|
||||
self.userList[pid] = 1 #Shortcut to init
|
||||
|
||||
def __searchTree(self, pid, ppid):
|
||||
#Always add it pid and ppid the list
|
||||
self.processList[pid] = ppid
|
||||
if self.userList.has_key(ppid):
|
||||
#print "DEBUG: user related command"
|
||||
self.foundUser = 1
|
||||
return
|
||||
#print "DEBUG: Searching ppid ",ppid, "in ",self.processList
|
||||
if self.processList.has_key(ppid):
|
||||
#print "DEBUG: found parent of ",pid, "which is ",ppid
|
||||
self.searchTree(ppid,self.processList[ppid])
|
||||
else:
|
||||
#print "DEBUG: Cannot find parent of ",ppid
|
||||
pass
|
||||
|
||||
def searchTree(self,pid,ppid):
|
||||
if pid == ppid:
|
||||
# Avoid recursion error
|
||||
return 0
|
||||
self.foundUser = 0
|
||||
self.__searchTree(pid,ppid)
|
||||
#If the process belongs to the system remove it, to free up memory
|
||||
if self.foundUser == False:
|
||||
self.processList.pop(pid)
|
||||
return self.foundUser
|
||||
|
||||
#Recursively get the children of a process
|
||||
#Internal function
|
||||
def __get_children(self,pid):
|
||||
#Establish a list of children for a process
|
||||
children = []
|
||||
#FIXME not efficient; Go through all the processes
|
||||
for p in self.processList.keys():
|
||||
if self.processList[p] == pid:
|
||||
children.append(p)
|
||||
#Record them in a global list too
|
||||
self.children[p]=1
|
||||
if len(children) == 0:
|
||||
return
|
||||
#Go through the children list and do a recursion
|
||||
for p in children:
|
||||
self.__get_children(p)
|
||||
|
||||
def get_children(self,pid):
|
||||
#Empty the list; do not want duplicates
|
||||
self.children = dict()
|
||||
self.__get_children(pid)
|
||||
return self.children.keys()
|
||||
|
||||
def silent_remove_pid(self,pid):
|
||||
try:
|
||||
if self.processList.has_key(pid):
|
||||
self.processList.pop(pid)
|
||||
if self.userList.has_key(pid):
|
||||
self.userList.pop(pid)
|
||||
print "User in process ",pid," pid disconnected"
|
||||
except KeyError,e:
|
||||
pass
|
||||
|
||||
def does_user_disconnects(self,pid):
|
||||
if self.userList.has_key(pid):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
# Describe the root process
|
||||
# f is file object
|
||||
# pid is the root process
|
||||
def desc_root_process(self,f,pid):
|
||||
vec = self.recover_process_vector(pid)
|
||||
#Sometimes SSHD clones processes that are not related
|
||||
#to users, small trees about a length of 2
|
||||
if (len(vec) == 0):
|
||||
return
|
||||
|
||||
f.write("** user root process %d **\n"%pid)
|
||||
sshinfo = self.search_ssh_info(pid)
|
||||
if sshinfo:
|
||||
f.write("SSH_client: %s\n"%sshinfo)
|
||||
ts = self.get_timestamp_from_pid(pid)
|
||||
print "11111",ts
|
||||
if ts >0:
|
||||
obj=datetime.datetime.fromtimestamp(float(ts))
|
||||
f.write("Connection date: %s\n"%str(obj))
|
||||
#Add process vector
|
||||
f.write("Process vector: %s\n"%','.join(vec))
|
||||
f.write('\n')
|
||||
def exportUserListTxt(self,filename):
|
||||
try:
|
||||
#Opens the file in append mode aiming to keep the history
|
||||
f = open(filename, 'a')
|
||||
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
f.write("*** UserList created on %s ***\n"%(str(ts)))
|
||||
for pid in self.userList.keys():
|
||||
#Each sshd clone is not necessarly related to a user
|
||||
if (len(self.get_children(pid)) == 0):
|
||||
#Discard empty subtrees
|
||||
continue
|
||||
self.desc_root_process(f,pid)
|
||||
f.close()
|
||||
except IOError,e:
|
||||
#TODO implement logging of internal errors
|
||||
#User should notice that there is something wrong when
|
||||
#user lists are outdated or corrupted
|
||||
pass
|
||||
|
||||
def get_command_from_pid(self,pid):
|
||||
if self.aplist.has_key(pid):
|
||||
if self.aplist[pid].has_key('file'):
|
||||
return self.aplist[pid]['file']
|
||||
else:
|
||||
sys.stderr.write('No file information for pid=%d\n'%pid)
|
||||
else:
|
||||
sys.stderr.write('pid %d was not annotated\n'%pid)
|
||||
return None
|
||||
|
||||
def get_timestamp_from_pid(self,pid):
|
||||
if self.aplist.has_key(pid):
|
||||
if self.aplist[pid].has_key('timestamp'):
|
||||
return self.aplist[pid]['timestamp']
|
||||
else:
|
||||
sys.stderr.write('No timestamp information for pid: %d\n'%pid)
|
||||
else:
|
||||
sys.stderr.write('pid %d was not annotated\n'%pid)
|
||||
return -1
|
||||
|
||||
def recover_process_vector(self,pid):
|
||||
vector = dict() # FIXME use timestamps as key for a dictionary
|
||||
print "Children of ",pid," ",self.get_children(pid)
|
||||
for pid in self.get_children(pid):
|
||||
ts = self.get_timestamp_from_pid(pid)
|
||||
its = int(ts)
|
||||
file = self.get_command_from_pid(pid)
|
||||
if ts != -1 and file != None:
|
||||
if vector.has_key(its) == False:
|
||||
vector[its] = []
|
||||
vector[its].append(file)
|
||||
#Now sort the vector
|
||||
tab = vector.keys()
|
||||
tab.sort()
|
||||
ret = []
|
||||
for ts in tab:
|
||||
for c in vector[ts]:
|
||||
ret.append(c)
|
||||
return ret
|
||||
|
||||
#Recursively get the children of a process. This time from the annotated
|
||||
#list.
|
||||
#Internal function
|
||||
def __get_aplist_children(self,pid):
|
||||
#Establish a list of children for a process
|
||||
children = []
|
||||
#FIXME not efficient; Go through all the processes
|
||||
for p in self.aplist.keys():
|
||||
if self.aplist[p]['parent'] == pid:
|
||||
children.append(p)
|
||||
#Record them in a global list too
|
||||
self.children[p]=1
|
||||
if len(children) == 0:
|
||||
return
|
||||
#Go through the children list and do a recursion
|
||||
for p in children:
|
||||
self.__get_aplist_children(p)
|
||||
|
||||
def get__aplist_children(self,pid):
|
||||
#Empty the list; do not want duplicates
|
||||
self.children = dict()
|
||||
self.__get_aplist_children(pid)
|
||||
return self.children.keys()
|
||||
|
||||
#Pid is the root; remove this pid and all chidren
|
||||
def clean_aplist(self,pid):
|
||||
children = self.get__aplist_children(pid)
|
||||
print "Removal candidates"
|
||||
for pid in children:
|
||||
self.aplist.pop(pid)
|
||||
|
||||
class TestProcessTree(unittest.TestCase):
|
||||
def testSearchRegular0(self):
|
||||
x = ProcessTrees()
|
||||
x.addUser(1079)
|
||||
#self.assertDictEqual(x.userList, {1079:1})
|
||||
#FIXME python version is too old
|
||||
self.assertEqual(x.userList[1079],1)
|
||||
print "TEST: SSH clones a process 1081"
|
||||
ret = x.searchTree(1081,1079)
|
||||
self.assertEqual(ret,1)
|
||||
print "TEST: System itself adds a porcess 555"
|
||||
ret = x.searchTree(555,333)
|
||||
self.assertEqual(ret,0)
|
||||
print "TEST: User process 1081 creates a process 1082"
|
||||
ret = x.searchTree(1082,1081)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
print "TEST: The clone clones again"
|
||||
ret = x.searchTree(1082,1081)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
print "TEST: The system process 555 creates a process 888"
|
||||
ret = x.searchTree(888,555)
|
||||
self.assertEqual(ret,0)
|
||||
|
||||
print "TEST: Second user arrives"
|
||||
x.addUser(2001)
|
||||
print "TEST: SSH clones a process"
|
||||
ret = x.searchTree(2002,2001)
|
||||
self.assertEqual(ret,1)
|
||||
print "TEST: Second user process create process 2007"
|
||||
ret=x.searchTree(2007,2002)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
print "TEST: First user process 1081 executes uname ppid 1082"
|
||||
ret = x.searchTree(1082,1081)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
print "TEST: Second user process 2007 creates process 2008"
|
||||
ret = x.searchTree(2008,2007)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
def testCleanUp(self):
|
||||
x = ProcessTrees()
|
||||
#Init is executed
|
||||
ret = x.searchTree(1,0)
|
||||
self.assertEqual(ret,0)
|
||||
print x.processList
|
||||
self.assertEqual(len(x.processList.keys()),0)
|
||||
|
||||
def testMixCleanUp(self):
|
||||
x = ProcessTrees()
|
||||
x.addUser(123)
|
||||
ret = x.searchTree(444,123)
|
||||
self.assertEqual(ret,1)
|
||||
self.assertEqual(len(x.processList.keys()),1)
|
||||
#System adds a process the process vector should not grow
|
||||
#Process 555 does not exits
|
||||
ret = x.searchTree(333,555)
|
||||
self.assertEqual(ret,0)
|
||||
self.assertEqual(len(x.processList.keys()),1)
|
||||
|
||||
def testRecurionErrorBreak(self):
|
||||
#FIXME can an attacker create a process having its own parent?
|
||||
x = ProcessTrees()
|
||||
x.addUser(123)
|
||||
x.searchTree(123,222)
|
||||
ret = x.searchTree(222,222)
|
||||
self.assertEqual(ret,0)
|
||||
|
||||
def testAnnotate(self):
|
||||
msg = {'env': ['SHELL=/bin/sh', 'TERM=screen', 'SSH_CLIENT=192.168.1.23 49826 22', 'SSH_TTY=/dev/pts/0', 'USER=gabriela', 'MAIL=/var/mail/gabriela', 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games', 'PWD=/home/gabriela', 'LANG=en_US.UTF-8', 'HISTCONTROL=ignoreboth', 'SHLVL=1', 'HOME=/home/gabriela', 'LOGNAME=gabriela', 'SSH_CONNECTION=192.168.1.23 49826 192.168.1.1 22', '_=/usr/bin/lesspipe'], 'rppid': ['1138'], 'pid': ['1139'], 'argument': ['lesspipe'], 'DONE': ['1'], 'file': ['/usr/bin/lesspipe'], 'ppid': ['1138'], 'type': ['1'], 'timestamp':'1263846206'}
|
||||
x = ProcessTrees()
|
||||
x.annotateProcessList(msg)
|
||||
# Check if information is there
|
||||
self.assertEqual(x.aplist[1139]['timestamp'],'1263846206')
|
||||
s = "192.168.1.23 49826 22"
|
||||
self.assertEqual(x.aplist[1139]['ssh_client'],s)
|
||||
self.assertEqual(x.aplist[1139]['file'], '/usr/bin/lesspipe')
|
||||
x.addUser(1139)
|
||||
#Test export
|
||||
x.exportUserListTxt('/tmp/userlist.txt')
|
||||
|
||||
def testChildrenList(self):
|
||||
x = ProcessTrees()
|
||||
x.addUser(123) # Has two children
|
||||
ret = x.searchTree(333,123)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
ret = x.searchTree(334,123)
|
||||
self.assertEqual(ret,1)
|
||||
|
||||
#First child has onother child
|
||||
ret = x.searchTree(555,333)
|
||||
self.assertEqual(ret,1)
|
||||
#Second child has another child
|
||||
ret = x.searchTree(666,334)
|
||||
self.assertEqual(ret,1)
|
||||
#Add concurrent user that has one child
|
||||
x.addUser(1000)
|
||||
ret = x.searchTree(1001,1000)
|
||||
self.assertEqual(ret,1)
|
||||
children = x.get_children(123)
|
||||
#[666, 555, 333, 334]
|
||||
self.assertEqual(len(children), 4)
|
||||
self.assertEqual(children[0],666)
|
||||
self.assertEqual(children[1],555)
|
||||
self.assertEqual(children[2],333)
|
||||
self.assertEqual(children[3],334)
|
||||
#Query children for an invalid process
|
||||
x= ProcessTrees()
|
||||
children = x.get_children(999)
|
||||
self.assertEqual(len(children),0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
33
aha/ahakern01/insult.c
Normal file
33
aha/ahakern01/insult.c
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Insulting program that should be installed on the honeypot.
|
||||
* The kernel then swaps the filename for do_execve with this one
|
||||
*
|
||||
* (c) 2010 Gerard Wagener
|
||||
* LICENSE GPL
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#define N 5
|
||||
|
||||
/* The element 0 is reserved for that insult is not set and if the program is
|
||||
* started from the shell with no arguments nothing should happen
|
||||
* atoi(sss) -> 0
|
||||
*/
|
||||
char* list[] = {"",
|
||||
"Fuck you",
|
||||
"Is that all? I want to do more ...",
|
||||
"Go away",
|
||||
"I love you"};
|
||||
|
||||
int main(int argc, char* argv[]){
|
||||
int idx;
|
||||
/* If another argv is used, then maybe only argv[0] is allocated when
|
||||
* no command lines are delivered. Therefore the kernel overwrites this
|
||||
* to avoid to allocate / smash the stack
|
||||
*/
|
||||
idx=atoi(argv[0]);
|
||||
if ((idx>=0) && (idx<N))
|
||||
printf("%s\n",list[idx]);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -34,9 +34,10 @@ class AHAActions:
|
|||
except OSError,e:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#Can trow IOError
|
||||
def create_message(self,filename,block,exitcode,substitue,insult):
|
||||
print "CREATE_MESSAGE ",filename,"block=",block
|
||||
try:
|
||||
reply = ReplyMessage(block=block,exitcode=exitcode,substitue=substitue,
|
||||
insult = insult)
|
||||
|
@ -46,8 +47,6 @@ class AHAActions:
|
|||
f.close()
|
||||
reply="(key=%s, block=%d,exitcode=%d,substitue=%d,insult=%d)"\
|
||||
%(filename,block,exitcode, substitue,insult)
|
||||
#print "CREATE_MESSAGE ",filename,"block=",block, "insult=",insult,\
|
||||
#"substitue=",substitue
|
||||
return reply
|
||||
except IOError,e:
|
||||
sys.stderr.write('Could not create reply file=(%s)\n'%filename)
|
||||
|
@ -124,7 +123,7 @@ class ProcessTrees:
|
|||
# Retuns None if ssh related information was not found
|
||||
sys.stderr.write('ERROR: No child provided SSH information\n')
|
||||
return None
|
||||
|
||||
|
||||
# Record additional information about processes like SSH parameters
|
||||
# and timestamps etc
|
||||
#TODO annotate SSH_LOGNAME
|
||||
|
@ -153,7 +152,7 @@ class ProcessTrees:
|
|||
# Is there a timestamp?
|
||||
if msg.has_key('timestamp'):
|
||||
self.aplist[pid]['timestamp'] = msg['timestamp']
|
||||
|
||||
|
||||
except ValueError,e:
|
||||
print e
|
||||
pass
|
||||
|
@ -205,8 +204,8 @@ class ProcessTrees:
|
|||
return
|
||||
#Go through the children list and do a recursion
|
||||
for p in children:
|
||||
self.__get_children(p)
|
||||
|
||||
self.__get_children(p)
|
||||
|
||||
def get_children(self,pid):
|
||||
#Empty the list; do not want duplicates
|
||||
self.children = dict()
|
||||
|
@ -219,21 +218,21 @@ class ProcessTrees:
|
|||
self.processList.pop(pid)
|
||||
if self.userList.has_key(pid):
|
||||
self.userList.pop(pid)
|
||||
#print "User in process ",pid," pid disconnected"
|
||||
print "User in process ",pid," pid disconnected"
|
||||
except KeyError,e:
|
||||
pass
|
||||
|
||||
def does_user_disconnects(self,pid):
|
||||
if self.userList.has_key(pid):
|
||||
return True
|
||||
else:
|
||||
else:
|
||||
return False
|
||||
# Describe the root process
|
||||
# Describe the root process
|
||||
# f is file object
|
||||
# pid is the root process
|
||||
def desc_root_process(self,f,pid):
|
||||
vec = self.recover_process_vector(pid)
|
||||
#Sometimes SSHD clones processes that are not related
|
||||
#Sometimes SSHD clones processes that are not related
|
||||
#to users, small trees about a length of 2
|
||||
if (len(vec) == 0):
|
||||
return
|
||||
|
@ -252,9 +251,9 @@ class ProcessTrees:
|
|||
f.write('\n')
|
||||
def exportUserListTxt(self,filename):
|
||||
try:
|
||||
#Opens the file in append mode aiming to keep the history
|
||||
#Opens the file in append mode aiming to keep the history
|
||||
f = open(filename, 'a')
|
||||
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
f.write("*** UserList created on %s ***\n"%(str(ts)))
|
||||
for pid in self.userList.keys():
|
||||
#Each sshd clone is not necessarly related to a user
|
||||
|
@ -265,7 +264,7 @@ class ProcessTrees:
|
|||
f.close()
|
||||
except IOError,e:
|
||||
#TODO implement logging of internal errors
|
||||
#User shoul |