chg: [uncommited] historical uncommited files added + paper and PhD thesis

This commit is contained in:
Alexandre Dulaunoy 2021-08-06 23:06:36 +02:00
parent f1039eec4d
commit 59e6fc3bc2
Signed by: adulau
GPG key ID: 09E2CD4944E6CBCD
34 changed files with 46988 additions and 92163 deletions

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# AHA - Adaptive Honeypot Alternative (historical repository)

View file

@ -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

File diff suppressed because it is too large Load diff

8199
aha/aha.log.recycled_pids Normal file

File diff suppressed because it is too large Load diff

View file

@ -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..."

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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;
}

View file

@ -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