move core/ and commands/ to kippo/

git-svn-id: https://kippo.googlecode.com/svn/trunk@50 951d7100-d841-11de-b865-b3884708a8e2
This commit is contained in:
desaster 2009-11-22 07:07:58 +00:00
parent 876e116b62
commit 88d377cfc6
15 changed files with 1321 additions and 2 deletions

View File

@ -13,8 +13,8 @@ from twisted.internet import reactor, defer
from twisted.application import internet, service
from twisted.cred import portal
from twisted.conch.ssh import factory, keys
from core import honeypot
from core.config import config
from kippo.core import honeypot
from kippo.core.config import config
factory = honeypot.HoneyPotSSHFactory()
factory.portal = portal.Portal(honeypot.HoneyPotRealm())

0
kippo/__init__.py Normal file
View File

View File

@ -0,0 +1,12 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
__all__ = [
'base',
'ls',
'ping',
'ssh',
'tar',
'wget',
'dice',
]

293
kippo/commands/base.py Normal file
View File

@ -0,0 +1,293 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
import os, time
from kippo.core.honeypot import HoneyPotCommand
from kippo.core.fs import *
from twisted.internet import reactor
import config
commands = {}
class command_whoami(HoneyPotCommand):
def call(self):
self.writeln(self.honeypot.user.username)
commands['/usr/bin/whoami'] = command_whoami
class command_cat(HoneyPotCommand):
def call(self):
for arg in self.args:
path = self.fs.resolve_path(arg, self.honeypot.cwd)
if not path or not self.fs.exists(path):
self.writeln('bash: cat: %s: No such file or directory' % arg)
return
f = self.fs.getfile(path)
realfile = self.fs.realfile(f,
'%s/%s' % (config.contents_path, path))
if realfile:
f = file(realfile, 'rb')
self.write(f.read())
f.close()
commands['/bin/cat'] = command_cat
class command_cd(HoneyPotCommand):
def call(self):
if not self.args:
path = '/root'
else:
path = self.args[0]
try:
newpath = self.fs.resolve_path(path, self.honeypot.cwd)
newdir = self.fs.get_path(newpath)
except IndexError:
newdir = None
if newdir is None:
self.writeln('bash: cd: %s: No such file or directory' % path)
return
if not self.fs.is_dir(newpath):
self.writeln('-bash: cd: %s: Not a directory' % path)
return
self.honeypot.cwd = newpath
commands['cd'] = command_cd
class command_rm(HoneyPotCommand):
def call(self):
for f in self.args:
path = self.fs.resolve_path(f, self.honeypot.cwd)
try:
dir = self.fs.get_path('/'.join(path.split('/')[:-1]))
except IndexError:
self.writeln(
'rm: cannot remove `%s\': No such file or directory' % f)
continue
basename = path.split('/')[-1]
contents = [x for x in dir]
for i in dir[:]:
if i[A_NAME] == basename:
if i[A_TYPE] == T_DIR:
self.writeln(
'rm: cannot remove `%s\': Is a directory' % \
i[A_NAME])
else:
dir.remove(i)
commands['/bin/rm'] = command_rm
class command_mkdir(HoneyPotCommand):
def call(self):
for f in self.args:
path = self.fs.resolve_path(f, self.honeypot.cwd)
if self.fs.exists(path):
self.writeln(
'mkdir: cannot create directory `%s\': File exists' % f)
return
ok = self.fs.mkdir(path, 0, 0, 4096, 16877)
if not ok:
self.writeln(
'mkdir: cannot create directory `%s\': ' % f + \
'No such file or directory')
return
commands['/bin/mkdir'] = command_mkdir
class command_rmdir(HoneyPotCommand):
def call(self):
for f in self.args:
path = self.fs.resolve_path(f, self.honeypot.cwd)
if len(self.fs.get_path(path)):
self.writeln(
'rmdir: failed to remove `%s\': Directory not empty' % f)
continue
try:
dir = self.fs.get_path('/'.join(path.split('/')[:-1]))
except IndexError:
dir = None
if not dir or f not in [x[A_NAME] for x in dir]:
self.writeln(
'rmdir: failed to remove `%s\': ' % f + \
'No such file or directory')
continue
for i in dir[:]:
if i[A_NAME] == f:
dir.remove(i)
commands['/bin/rmdir'] = command_rmdir
class command_uptime(HoneyPotCommand):
def call(self):
self.writeln(
' %s up 14 days, 3:53, 0 users, load average: 0.08, 0.02, 0.01' % \
time.strftime('%H:%M:%S'))
commands['/usr/bin/uptime'] = command_uptime
class command_w(HoneyPotCommand):
def call(self):
self.writeln(
' %s up 14 days, 3:53, 0 users, load average: 0.08, 0.02, 0.01' % \
time.strftime('%H:%M:%S'))
self.writeln('USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT')
commands['/usr/bin/w'] = command_w
commands['/usr/bin/who'] = command_w
class command_echo(HoneyPotCommand):
def call(self):
self.writeln(' '.join(self.args))
commands['/bin/echo'] = command_echo
class command_exit(HoneyPotCommand):
def call(self):
#self.honeypot.terminal.loseConnection()
self.honeypot.terminal.reset()
self.writeln('Connection to server closed.')
self.honeypot.hostname = 'localhost'
commands['exit'] = command_exit
class command_clear(HoneyPotCommand):
def call(self):
self.honeypot.terminal.reset()
commands['/usr/bin/clear'] = command_clear
class command_vi(HoneyPotCommand):
def call(self):
self.writeln('E558: Terminal entry not found in terminfo')
commands['/usr/bin/vi'] = command_vi
class command_hostname(HoneyPotCommand):
def call(self):
self.writeln(self.honeypot.hostname)
commands['/bin/hostname'] = command_hostname
class command_uname(HoneyPotCommand):
def call(self):
if len(self.args) and self.args[0].strip() == '-a':
self.writeln(
'Linux %s 2.6.26-2-686 #1 SMP Wed Nov 4 20:45:37 UTC 2009 i686 GNU/Linux' % \
self.honeypot.hostname)
else:
self.writeln('Linux')
commands['/bin/uname'] = command_uname
class command_ps(HoneyPotCommand):
def call(self):
if len(self.args) and self.args[0].strip().count('a'):
output = (
'USER PID %%CPU %%MEM VSZ RSS TTY STAT START TIME COMMAND',
'root 1 0.0 0.1 2100 688 ? Ss Nov06 0:07 init [2] ',
'root 2 0.0 0.0 0 0 ? S< Nov06 0:00 [kthreadd]',
'root 3 0.0 0.0 0 0 ? S< Nov06 0:00 [migration/0]',
'root 4 0.0 0.0 0 0 ? S< Nov06 0:00 [ksoftirqd/0]',
'root 5 0.0 0.0 0 0 ? S< Nov06 0:00 [watchdog/0]',
'root 6 0.0 0.0 0 0 ? S< Nov06 0:17 [events/0]',
'root 7 0.0 0.0 0 0 ? S< Nov06 0:00 [khelper]',
'root 39 0.0 0.0 0 0 ? S< Nov06 0:00 [kblockd/0]',
'root 41 0.0 0.0 0 0 ? S< Nov06 0:00 [kacpid]',
'root 42 0.0 0.0 0 0 ? S< Nov06 0:00 [kacpi_notify]',
'root 170 0.0 0.0 0 0 ? S< Nov06 0:00 [kseriod]',
'root 207 0.0 0.0 0 0 ? S Nov06 0:01 [pdflush]',
'root 208 0.0 0.0 0 0 ? S Nov06 0:00 [pdflush]',
'root 209 0.0 0.0 0 0 ? S< Nov06 0:00 [kswapd0]',
'root 210 0.0 0.0 0 0 ? S< Nov06 0:00 [aio/0]',
'root 748 0.0 0.0 0 0 ? S< Nov06 0:00 [ata/0]',
'root 749 0.0 0.0 0 0 ? S< Nov06 0:00 [ata_aux]',
'root 929 0.0 0.0 0 0 ? S< Nov06 0:00 [scsi_eh_0]',
'root 1014 0.0 0.0 0 0 ? D< Nov06 0:03 [kjournald]',
'root 1087 0.0 0.1 2288 772 ? S<s Nov06 0:00 udevd --daemon',
'root 1553 0.0 0.0 0 0 ? S< Nov06 0:00 [kpsmoused]',
'root 2054 0.0 0.2 28428 1508 ? Sl Nov06 0:01 /usr/sbin/rsyslogd -c3',
'root 2103 0.0 0.2 2628 1196 tty1 Ss Nov06 0:00 /bin/login -- ',
'root 2105 0.0 0.0 1764 504 tty2 Ss+ Nov06 0:00 /sbin/getty 38400 tty2',
'root 2107 0.0 0.0 1764 504 tty3 Ss+ Nov06 0:00 /sbin/getty 38400 tty3',
'root 2109 0.0 0.0 1764 504 tty4 Ss+ Nov06 0:00 /sbin/getty 38400 tty4',
'root 2110 0.0 0.0 1764 504 tty5 Ss+ Nov06 0:00 /sbin/getty 38400 tty5',
'root 2112 0.0 0.0 1764 508 tty6 Ss+ Nov06 0:00 /sbin/getty 38400 tty6',
'root 2133 0.0 0.1 2180 620 ? S<s Nov06 0:00 dhclient3 -pf /var/run/dhclient.eth0.pid -lf /var/lib/dhcp3/dhclien',
'root 4969 0.0 0.1 5416 1024 ? Ss Nov08 0:00 /usr/sbin/sshd',
'root 5673 0.0 0.2 2924 1540 pts/0 Ss 04:30 0:00 -bash',
'root 5679 0.0 0.1 2432 928 pts/0 R+ 04:32 0:00 ps %s' % ' '.join(self.args),
)
else:
output = (
' PID TTY TIME CMD',
' 5673 pts/0 00:00:00 bash',
' 5677 pts/0 00:00:00 ps %s' % ' '.join(self.args),
)
for l in output:
self.writeln(l)
commands['/bin/ps'] = command_ps
class command_id(HoneyPotCommand):
def call(self):
self.writeln('uid=0(root) gid=0(root) groups=0(root)')
commands['/usr/bin/id'] = command_id
class command_mount(HoneyPotCommand):
def call(self):
if self.args and len(self.args[0].strip()):
return
for i in [
'/dev/sda1 on / type ext3 (rw,errors=remount-ro)',
'tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)',
'proc on /proc type proc (rw,noexec,nosuid,nodev)',
'sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)',
'udev on /dev type tmpfs (rw,mode=0755)',
'tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)',
'devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620)',
]:
self.writeln(i)
commands['/usr/mount'] = command_mount
class command_pwd(HoneyPotCommand):
def call(self):
self.writeln(self.honeypot.cwd)
commands['/bin/pwd'] = command_pwd
class command_passwd(HoneyPotCommand):
def start(self):
self.write('Enter new UNIX password: ')
self.honeypot.password_input = True
self.callbacks = [self.ask_again, self.finish]
def ask_again(self):
self.write('Retype new UNIX password: ')
def finish(self):
self.honeypot.password_input = False
self.writeln('Sorry, passwords do not match')
self.writeln(
'passwd: Authentication information cannot be recovered')
self.writeln('passwd: password unchanged')
self.exit()
def lineReceived(self, line):
print 'INPUT (passwd):', line
self.callbacks.pop(0)()
commands['/usr/bin/passwd'] = command_passwd
class command_reboot(HoneyPotCommand):
def start(self):
self.nextLine()
self.writeln(
'Broadcast message from root@%s (pts/0) (%s):' % \
(self.honeypot.hostname, time.ctime()))
self.nextLine()
self.writeln('The system is going down for reboot NOW!')
reactor.callLater(3, self.finish)
def finish(self):
self.writeln('Connection to server closed.')
self.honeypot.hostname = 'localhost'
self.exit()
commands['/sbin/reboot'] = command_reboot
class command_nop(HoneyPotCommand):
def call(self):
pass
commands['/bin/chmod'] = command_nop
commands['set'] = command_nop
commands['unset'] = command_nop
commands['history'] = command_nop
commands['export'] = command_nop
commands['/bin/bash'] = command_nop
commands['/bin/sh'] = command_nop
commands['/bin/kill'] = command_nop
# vim: set sw=4 et:

45
kippo/commands/dice.py Normal file
View File

@ -0,0 +1,45 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
# Random commands when running new executables
from kippo.core.honeypot import HoneyPotCommand
commands = {}
clist = []
class command_orly(HoneyPotCommand):
def start(self):
self.orly()
def orly(self):
self.writeln(' ___ ')
self.writeln(' {o,o}')
self.writeln(' |)__)')
self.writeln(' -"-"-')
self.write('O RLY? ')
def lineReceived(self, data):
if data.strip().lower() in ('ya', 'yarly', 'ya rly', 'yes', 'y'):
self.writeln(' ___')
self.writeln(' {o,o}')
self.writeln(' (__(|')
self.writeln(' -"-"-')
self.writeln('NO WAI!')
self.exit()
return
self.orly()
clist.append(command_orly)
class command_wargames(HoneyPotCommand):
def start(self):
self.write('Shall we play a game? ')
def lineReceived(self, data):
self.writeln('A strange game. ' + \
'The only winning move is not to play. ' + \
'How about a nice game of chess?')
self.exit()
clist.append(command_wargames)
# vim: set sw=4 et:

121
kippo/commands/ls.py Normal file
View File

@ -0,0 +1,121 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from kippo.core.honeypot import HoneyPotCommand
from kippo.core.fs import *
import stat, time
commands = {}
class command_ls(HoneyPotCommand):
def uid2name(self, uid):
if uid == 0:
return 'root'
return uid
def gid2name(self, gid):
if gid == 0:
return 'root'
return gid
def call(self):
path = self.honeypot.cwd
paths = []
if len(self.args):
for arg in self.args:
if not arg.startswith('-'):
paths.append(self.honeypot.fs.resolve_path(arg,
self.honeypot.cwd))
self.show_hidden = False
func = self.do_ls_normal
for x in self.args:
if x.startswith('-') and x.count('l'):
func = self.do_ls_l
if x.startswith('-') and x.count('a'):
self.show_hidden = True
if not paths:
func(path)
else:
for path in paths:
func(path)
def do_ls_normal(self, path):
try:
files = self.honeypot.fs.list_files(path)
except:
self.honeypot.writeln(
'ls: cannot access %s: No such file or directory' % path)
return
if not len(files):
return
l = [x[A_NAME] for x in files \
if self.show_hidden or not x[A_NAME].startswith('.')]
if not l:
return
count = 0
maxlen = max([len(x) for x in l])
perline = int(self.honeypot.user.windowSize[1] / (maxlen + 1))
if self.show_hidden:
l.insert(0, '..')
l.insert(0, '.')
for f in l:
if count == perline:
count = 0
self.nextLine()
self.write(f.ljust(maxlen + 1))
count += 1
self.nextLine()
def do_ls_l(self, path):
try:
files = self.honeypot.fs.list_files(path)[:]
except:
self.honeypot.writeln(
'ls: cannot access %s: No such file or directory' % path)
return
largest = 0
if len(files):
largest = max([x[A_SIZE] for x in files])
# FIXME: should grab these off the parents instead
files.insert(0,
['..', T_DIR, 0, 0, 4096, 16877, time.time(), [], None])
files.insert(0,
['.', T_DIR, 0, 0, 4096, 16877, time.time(), [], None])
for file in files:
perms = ['-'] * 10
if file[A_MODE] & stat.S_IRUSR: perms[1] = 'r'
if file[A_MODE] & stat.S_IWUSR: perms[2] = 'w'
if file[A_MODE] & stat.S_IXUSR: perms[3] = 'x'
if file[A_MODE] & stat.S_IRGRP: perms[4] = 'r'
if file[A_MODE] & stat.S_IWGRP: perms[5] = 'w'
if file[A_MODE] & stat.S_IXGRP: perms[6] = 'x'
if file[A_MODE] & stat.S_IROTH: perms[7] = 'r'
if file[A_MODE] & stat.S_IWOTH: perms[8] = 'w'
if file[A_MODE] & stat.S_IXOTH: perms[9] = 'x'
if file[A_TYPE] == T_DIR:
perms[0] = 'd'
perms = ''.join(perms)
ctime = time.localtime(file[A_CTIME])
l = '%s 1 %s %s %s %s %s' % \
(perms,
self.uid2name(file[A_UID]),
self.gid2name(file[A_GID]),
str(file[A_SIZE]).rjust(len(str(largest))),
time.strftime('%Y-%m-%d %H:%M', ctime),
file[A_NAME])
self.honeypot.writeln(l)
commands['/bin/ls'] = command_ls
# vim: set sw=4 et:

52
kippo/commands/ping.py Normal file
View File

@ -0,0 +1,52 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from kippo.core.honeypot import HoneyPotCommand
from twisted.internet import reactor
import time, re, random, md5
commands = {}
class command_ping(HoneyPotCommand):
def start(self):
if not len(self.args):
for l in (
'Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadline]',
' [-p pattern] [-s packetsize] [-t ttl] [-I interface or address]',
' [-M mtu discovery hint] [-S sndbuf]',
' [ -T timestamp option ] [ -Q tos ] [hop1 ...] destination',
):
self.writeln(l)
self.exit()
return
self.host = self.args[0]
if re.match('^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$',
self.host):
self.ip = self.host
else:
s = md5.md5(self.host).hexdigest()
self.ip = '.'.join([str(int(x, 16)) for x in
(s[0:2], s[2:4], s[4:6], s[6:8])])
self.writeln('PING %s (%s) 56(84) bytes of data.' % \
(self.host, self.ip))
self.scheduled = reactor.callLater(0.2, self.showreply)
self.count = 0
def showreply(self):
ms = 40 + random.random() * 10
self.writeln(
'64 bytes from %s (%s): icmp_seq=%d ttl=50 time=%.1f ms' % \
(self.host, self.ip, self.count + 1, ms))
self.count += 1
self.scheduled = reactor.callLater(1, self.showreply)
def ctrl_c(self):
self.scheduled.cancel()
self.writeln('--- %s ping statistics ---' % self.host)
self.writeln('%d packets transmitted, %d received, 0%% packet loss, time 907ms' % \
(self.count, self.count))
self.writeln('rtt min/avg/max/mdev = 48.264/50.352/52.441/2.100 ms')
self.exit()
commands['/bin/ping'] = command_ping

63
kippo/commands/ssh.py Normal file
View File

@ -0,0 +1,63 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from kippo.core.honeypot import HoneyPotCommand
from twisted.internet import reactor
import time
commands = {}
class command_ssh(HoneyPotCommand):
def start(self):
if not self.args:
for l in (
'usage: ssh [-1246AaCfgKkMNnqsTtVvXxY] [-b bind_address] [-c cipher_spec]',
' [-D [bind_address:]port] [-e escape_char] [-F configfile]',
' [-i identity_file] [-L [bind_address:]port:host:hostport]',
' [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]',
' [-R [bind_address:]port:host:hostport] [-S ctl_path]',
' [-w local_tun[:remote_tun]] [user@]hostname [command]',
):
self.writeln(l)
self.exit()
return
self.host = self.args[0].strip()
self.writeln('The authenticity of host \'187.42.2.9 (187.42.2.9)\' can\'t be established.')
self.writeln('RSA key fingerprint is 9d:30:97:8a:9e:48:0d:de:04:8d:76:3a:7b:4b:30:f8.')
self.write('Are you sure you want to continue connecting (yes/no)? ')
self.callbacks = [self.yesno, self.wait]
def yesno(self, line):
host = line.strip()
self.writeln(
'Warning: Permanently added \'%s\' (RSA) to the list of known hosts.' % \
host)
self.write('%s\'s password: ' % self.host)
self.honeypot.password_input = True
def wait(self, line):
reactor.callLater(2, self.finish, line)
def finish(self, line):
self.pause = False
user, rest, host = 'root', self.host, 'localhost'
if self.host.count('@'):
user, rest = self.host.split('@', 1)
rest = rest.strip().split('.')
if len(rest) and rest[0].isalpha():
host = rest[0]
self.honeypot.hostname = host
self.honeypot.password_input = False
self.writeln(
'Linux %s 2.6.26-2-686 #1 SMP Wed Nov 4 20:45:37 UTC 2009 i686' % \
self.honeypot.hostname)
self.writeln('Last login: %s from 192.168.9.4' % \
time.ctime(time.time() - 123123))
self.exit()
def lineReceived(self, line):
if len(self.callbacks):
self.callbacks.pop(0)(line)
commands['/usr/bin/ssh'] = command_ssh
# vim: set sw=4 et:

66
kippo/commands/tar.py Normal file
View File

@ -0,0 +1,66 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from kippo.core.honeypot import HoneyPotCommand
from kippo.core.fs import *
from kippo.commands import dice
import time, random, tarfile, os
commands = {}
class command_tar(HoneyPotCommand):
def call(self):
if len(self.args) < 2:
self.writeln('tar: You must specify one of the `-Acdtrux\' options')
self.writeln('Try `tar --help\' or `tar --usage\' for more information.')
return
filename = self.args[1]
extract = False
if 'x' in self.args[0]:
extract = True
verbose = False
if 'v' in self.args[0]:
verbose = True
path = self.fs.resolve_path(filename, self.honeypot.cwd)
if not path or not self.honeypot.fs.exists(path):
self.writeln('tar: %s: Cannot open: No such file or directory' % \
filename)
self.writeln('tar: Error is not recoverable: exiting now')
self.writeln('tar: Child returned status 2')
self.writeln('tar: Error exit delayed from previous errors')
return
f = self.fs.getfile(path)
if not f[A_REALFILE]:
self.writeln('tar: this does not look like a tar archive')
self.writeln('tar: skipping to next header')
self.writeln('tar: error exit delayed from previous errors')
return
try:
t = tarfile.open(f[A_REALFILE])
except:
self.writeln('tar: this does not look like a tar archive')
self.writeln('tar: skipping to next header')
self.writeln('tar: error exit delayed from previous errors')
return
for f in t:
dest = self.fs.resolve_path(f.name.strip('/'), self.honeypot.cwd)
if verbose:
self.writeln(f.name)
if not extract or not len(dest):
continue
if f.isdir():
self.fs.mkdir(dest, 0, 0, 4096, f.mode, f.mtime)
elif f.isfile():
self.fs.mkfile(dest, 0, 0, f.size, f.mode, f.mtime)
self.honeypot.commands[dest] = random.choice(dice.clist)
else:
print 'tar: skipping [%s]' % f.name
commands['/bin/tar'] = command_tar
# vim: set sw=4 et:

194
kippo/commands/wget.py Normal file
View File

@ -0,0 +1,194 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from kippo.core.honeypot import HoneyPotCommand
from kippo.core.fs import *
from twisted.web import client
from twisted.internet import reactor
import stat, time, urlparse, random, re
commands = {}
def tdiff(seconds):
t = seconds
days = int(t / (24 * 60 * 60))
t -= (days * 24 * 60 * 60)
hours = int(t / (60 * 60))
t -= (hours * 60 * 60)
minutes = int(t / 60)
t -= (minutes * 60)
s = '%ds' % int(t)
if minutes >= 1: s = '%dm %s' % (minutes, s)
if hours >= 1: s = '%dh %s' % (hours, s)
if days >= 1: s = '%dd %s' % (days, s)
return s
def sizeof_fmt(num):
for x in ['bytes','K','M','G','T']:
if num < 1024.0:
return "%d%s" % (num, x)
num /= 1024.0
# Luciano Ramalho @ http://code.activestate.com/recipes/498181/
def splitthousands( s, sep=','):
if len(s) <= 3: return s
return splitthousands(s[:-3], sep) + sep + s[-3:]
class command_wget(HoneyPotCommand):
def start(self):
url = None
for arg in self.args:
if arg.startswith('-'):
continue
url = arg.strip()
break
if not url:
self.writeln('wget: missing URL')
self.writeln('Usage: wget [OPTION]... [URL]...')
self.nextLine()
self.writeln('Try `wget --help\' for more options.')
self.exit()
return
if url and not url.startswith('http://'):
url = 'http://%s' % url
urldata = urlparse.urlparse(url)
outfile = urldata.path.split('/')[-1]
if not len(outfile.strip()) or not urldata.path.count('/'):
outfile = 'index.html'
self.safeoutfile = '%s/%s_%s' % \
(self.honeypot.env.cfg.get('honeypot', 'download_path'),
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', url))
self.deferred = self.download(url, outfile,
file(self.safeoutfile, 'wb'))
if self.deferred:
self.deferred.addCallback(self.success)
self.deferred.addErrback(self.error, url)
def download(self, url, fakeoutfile, outputfile, *args, **kwargs):
scheme, host, port, path = client._parse(url)
if scheme == 'https':
self.writeln('Sorry, SSL not supported in this release')
return None
self.writeln('--%s-- %s' % (time.strftime('%Y-%m-%d %H:%M:%S'), url))
self.writeln('Connecting to %s:%d... connected.' % (host, port))
self.write('HTTP request sent, awaiting response... ')
factory = HTTPProgressDownloader(
self, fakeoutfile, url, outputfile, *args, **kwargs)
self.connection = reactor.connectTCP(host, port, factory)
return factory.deferred
def ctrl_c(self):
self.writeln('^C')
self.connection.transport.loseConnection()
def success(self, data):
self.exit()
def error(self, error, url):
if hasattr(error, 'getErrorMessage'): # exceptions
error = error.getErrorMessage()
self.writeln(error)
# Real wget also adds this:
#self.writeln('%s ERROR 404: Not Found.' % \
# time.strftime('%Y-%m-%d %T'))
self.exit()
commands['/usr/bin/wget'] = command_wget
# from http://code.activestate.com/recipes/525493/
class HTTPProgressDownloader(client.HTTPDownloader):
def __init__(self, wget, fakeoutfile, url, outfile, headers=None):
client.HTTPDownloader.__init__(self, url, outfile, headers=headers)
self.status = None
self.wget = wget
self.fakeoutfile = fakeoutfile
self.lastupdate = 0
self.started = time.time()
self.proglen = 0
def noPage(self, reason): # called for non-200 responses
if self.status == '304':
client.HTTPDownloader.page(self, '')
else:
client.HTTPDownloader.noPage(self, reason)
def gotHeaders(self, headers):
if self.status == '200':
self.wget.writeln('200 OK')
if headers.has_key('content-length'):
self.totallength = int(headers['content-length'][0])
else:
self.totallength = 0
if headers.has_key('content-type'):
self.contenttype = headers['content-type'][0]
else:
self.contenttype = 'text/whatever'
self.currentlength = 0.0
if self.totallength > 0:
self.wget.writeln('Length: %d (%s) [%s]' % \
(self.totallength,
sizeof_fmt(self.totallength),
self.contenttype))
else:
self.wget.writeln('Length: unspecified [%s]' % \
(self.contenttype))
self.wget.writeln('Saving to: `%s' % self.fakeoutfile)
self.wget.honeypot.terminal.nextLine()
return client.HTTPDownloader.gotHeaders(self, headers)
def pagePart(self, data):
if self.status == '200':
self.currentlength += len(data)
if (time.time() - self.lastupdate) < 0.5:
return client.HTTPDownloader.pagePart(self, data)
if self.totallength:
percent = (self.currentlength/self.totallength)*100
spercent = "%i%%" % percent
else:
spercent = '%dK' % (self.currentlength/1000)
percent = 0
self.speed = self.currentlength / (time.time() - self.started)
eta = (self.totallength - self.currentlength) / self.speed
s = '\r%s [%s] %s %dK/s eta %s' % \
(spercent.rjust(3),
('%s>' % (int(39.0 / 100.0 * percent) * '=')).ljust(39),
splitthousands(str(int(self.currentlength))).ljust(12),
self.speed / 1000,
tdiff(eta))
self.wget.write(s.ljust(self.proglen))
self.proglen = len(s)
self.lastupdate = time.time()
return client.HTTPDownloader.pagePart(self, data)
def pageEnd(self):
if self.totallength != 0 and self.currentlength != self.totallength:
return client.HTTPDownloader.pageEnd(self)
self.wget.write('\r100%%[%s] %s %dK/s' % \
('%s>' % (38 * '='),
splitthousands(str(int(self.totallength))).ljust(12),
self.speed / 1000))
self.wget.honeypot.terminal.nextLine()
self.wget.honeypot.terminal.nextLine()
self.wget.writeln(
'%s (%d KB/s) - `%s\' saved [%d/%d]' % \
(time.strftime('%Y-%m-%d %H:%M:%S'),
self.speed / 1000,
self.fakeoutfile, self.currentlength, self.totallength))
outfile = '%s/%s' % (self.wget.honeypot.cwd, self.fakeoutfile)
self.wget.fs.mkfile(outfile, 0, 0, self.totallength, 33188)
self.wget.fs.update_realfile(
self.wget.fs.getfile(outfile),
self.wget.safeoutfile)
return client.HTTPDownloader.pageEnd(self)
# vim: set sw=4 et:

0
kippo/core/__init__.py Normal file
View File

14
kippo/core/config.py Normal file
View File

@ -0,0 +1,14 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
import ConfigParser, os
def config():
cfg = ConfigParser.ConfigParser()
for f in ('kippo.cfg', '/etc/kippo/kippo.cfg', '/etc/kippo.cfg'):
if os.path.exists(f):
cfg.read('kippo.cfg')
return cfg
return None
# vim: set sw=4 et:

126
kippo/core/fs.py Normal file
View File

@ -0,0 +1,126 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
import os, time
A_NAME, \
A_TYPE, \
A_UID, \
A_GID, \
A_SIZE, \
A_MODE, \
A_CTIME, \
A_CONTENTS, \
A_TARGET, \
A_REALFILE = range(0, 10)
T_LINK, \
T_DIR, \
T_FILE, \
T_BLK, \
T_CHR, \
T_SOCK, \
T_FIFO = range(0, 7)
class HoneyPotFilesystem(object):
def __init__(self, fs):
self.fs = fs
def resolve_path(self, path, cwd):
pieces = path.rstrip('/').split('/')
if path[0] == '/':
cwd = []
else:
cwd = [x for x in cwd.split('/') if len(x) and x is not None]
while 1:
if not len(pieces):
break
piece = pieces.pop(0)
if piece == '..':
if len(cwd): cwd.pop()
continue
if piece in ('.', ''):
continue
cwd.append(piece)
return '/%s' % '/'.join(cwd)
def get_path(self, path):
p = self.fs
for i in path.split('/'):
if not i:
continue
p = [x for x in p[A_CONTENTS] if x[A_NAME] == i][0]
return p[A_CONTENTS]
def list_files(self, path):
return self.get_path(path)
def exists(self, path):
f = self.getfile(path)
if f is not False:
return True
def update_realfile(self, f, realfile):
if not f[A_REALFILE] and os.path.exists(realfile) and \
not os.path.islink(realfile) and os.path.isfile(realfile) and \
f[A_SIZE] < 25000000:
print 'Updating realfile to %s' % realfile
f[A_REALFILE] = realfile
def realfile(self, f, path):
self.update_realfile(f, path)
if f[A_REALFILE]:
return f[A_REALFILE]
return None
def getfile(self, path):
pieces = path.strip('/').split('/')
p = self.fs
while 1:
if not len(pieces):
break
piece = pieces.pop(0)
if piece not in [x[A_NAME] for x in p[A_CONTENTS]]:
return False
p = [x for x in p[A_CONTENTS] \
if x[A_NAME] == piece][0]
return p
def mkfile(self, path, uid, gid, size, mode, ctime = None):
if ctime is None:
ctime = time.time()
dir = self.get_path(os.path.dirname(path))
outfile = os.path.basename(path)
if outfile in [x[A_NAME] for x in dir]:
dir.remove([x for x in dir if x[A_NAME] == outfile][0])
dir.append([outfile, T_FILE, uid, gid, size, mode, ctime, [],
None, None])
return True
def mkdir(self, path, uid, gid, size, mode, ctime = None):
if ctime is None:
ctime = time.time()
if not len(path.strip('/')):
return False
try:
dir = self.get_path(os.path.dirname(path.strip('/')))
except IndexError:
return False
dir.append([os.path.basename(path), T_DIR, uid, gid, size, mode,
ctime, [], None, None])
return True
def is_dir(self, path):
if path == '/':
return True
dir = self.get_path(os.path.dirname(path))
l = [x for x in dir
if x[A_NAME] == os.path.basename(path) and
x[A_TYPE] == T_DIR]
if l:
return True
return False
# vim: set sw=4 et:

303
kippo/core/honeypot.py Normal file
View File

@ -0,0 +1,303 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from twisted.cred import portal, checkers, credentials, error
from twisted.conch import avatar, recvline, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, common, transport
from twisted.conch.insults import insults
from twisted.application import service, internet
from twisted.protocols.policies import TrafficLoggingFactory
from twisted.internet import reactor, protocol, defer
from twisted.python import failure, log
from zope.interface import implements
from copy import deepcopy, copy
import sys, os, random, pickle, time, stat, shlex
from kippo.core import ttylog, fs
from kippo.core.config import config
import commands
class HoneyPotCommand(object):
def __init__(self, honeypot, *args):
self.honeypot = honeypot
self.args = args
self.writeln = self.honeypot.writeln
self.write = self.honeypot.terminal.write
self.nextLine = self.honeypot.terminal.nextLine
self.fs = self.honeypot.fs
def start(self):
self.call()
self.exit()
def call(self):
self.honeypot.writeln('Hello World! [%s]' % repr(self.args))
def exit(self):
self.honeypot.cmdstack.pop()
self.honeypot.cmdstack[-1].resume()
def ctrl_c(self):
print 'Received CTRL-C, exiting..'
self.writeln('^C')
self.exit()
def lineReceived(self, line):
print 'INPUT: %s' % line
def resume(self):
pass
class HoneyPotShell(object):
def __init__(self, honeypot):
self.honeypot = honeypot
self.showPrompt()
def lineReceived(self, line):
print 'CMD: %s' % line
if not len(line.strip()):
self.showPrompt()
return
try:
cmdAndArgs = shlex.split(line.strip())
except:
self.honeypot.writeln(
'-bash: syntax error: unexpected end of file')
self.showPrompt()
return
cmd, args = cmdAndArgs[0], []
if len(cmdAndArgs) > 1:
args = cmdAndArgs[1:]
cmdclass = self.honeypot.getCommand(cmd)
if cmdclass:
obj = cmdclass(self.honeypot, *args)
self.honeypot.cmdstack.append(obj)
self.honeypot.setTypeoverMode()
obj.start()
else:
if len(line.strip()):
self.honeypot.writeln('bash: %s: command not found' % cmd)
self.showPrompt()
def resume(self):
self.honeypot.setInsertMode()
self.showPrompt()
def showPrompt(self):
prompt = '%s:%%(path)s# ' % self.honeypot.hostname
path = self.honeypot.cwd
if path == '/root':
path = '~'
attrs = {'path': path}
self.honeypot.terminal.write(prompt % attrs)
def ctrl_c(self):
self.honeypot.terminal.nextLine()
self.showPrompt()
class HoneyPotProtocol(recvline.HistoricRecvLine):
def __init__(self, user, env):
self.user = user
self.env = env
self.cwd = '/root'
self.hostname = self.env.cfg.get('honeypot', 'hostname')
self.fs = fs.HoneyPotFilesystem(deepcopy(self.env.fs))
# commands is also a copy so we can add stuff on the fly
self.commands = copy(self.env.commands)
self.password_input = False
self.cmdstack = []
def connectionMade(self):
recvline.HistoricRecvLine.connectionMade(self)
self.cmdstack = [HoneyPotShell(self)]
def connectionLost(self, reason):
recvline.HistoricRecvLine.connectionLost(self, reason)
# not sure why i need to do this:
del self.fs
del self.commands
# Overriding to prevent terminal.reset()
def initializeScreen(self):
self.setInsertMode()
def getCommand(self, cmd):
if not len(cmd.strip()):
return None
path = None
if cmd in self.commands:
return self.commands[cmd]
if cmd[0] in ('.', '/'):
path = self.fs.resolve_path(cmd, self.cwd)
if not self.fs.exists(path):
return None
else:
for i in ['%s/%s' % (x, cmd) for x in \
'/bin', '/usr/bin', '/sbin', '/usr/sbin']:
if self.fs.exists(i):
path = i
break
if path in self.commands:
return self.commands[path]
return None
def lineReceived(self, line):
if len(self.cmdstack):
self.cmdstack[-1].lineReceived(line)
def keystrokeReceived(self, keyID, modifier):
if type(keyID) == type(''):
ttylog.ttylog_write(self.terminal.ttylog_file, len(keyID),
ttylog.DIR_READ, time.time(), keyID)
if keyID == '\x03':
self.cmdstack[-1].ctrl_c()
recvline.HistoricRecvLine.keystrokeReceived(self, keyID, modifier)
# Easier way to implement password input?
def characterReceived(self, ch, moreCharactersComing):
if self.mode == 'insert':
self.lineBuffer.insert(self.lineBufferIndex, ch)
else:
self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
self.lineBufferIndex += 1
if not self.password_input:
self.terminal.write(ch)
def writeln(self, data):
self.terminal.write(data)
self.terminal.nextLine()
class LoggingServerProtocol(insults.ServerProtocol):
def connectionMade(self):
self.ttylog_file = '%s/tty/%s-%s.log' % \
(config().get('honeypot', 'log_path'),
time.strftime('%Y%m%d-%H%M%S'),
int(random.random() * 10000))
print 'Opening TTY log: %s' % self.ttylog_file
ttylog.ttylog_open(self.ttylog_file, time.time())
self.ttylog_open = True
insults.ServerProtocol.connectionMade(self)
def write(self, bytes):
if self.ttylog_open:
ttylog.ttylog_write(self.ttylog_file, len(bytes),
ttylog.DIR_WRITE, time.time(), bytes)
insults.ServerProtocol.write(self, bytes)
def connectionLost(self, reason):
if self.ttylog_open:
ttylog.ttylog_close(self.ttylog_file, time.time())
self.ttylog_open = False
insults.ServerProtocol.connectionLost(self, reason)
class HoneyPotAvatar(avatar.ConchUser):
implements(conchinterfaces.ISession)
def __init__(self, username, env):
avatar.ConchUser.__init__(self)
self.username = username
self.env = env
self.channelLookup.update({'session':session.SSHSession})
def openShell(self, protocol):
serverProtocol = LoggingServerProtocol(HoneyPotProtocol, self, self.env)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
def getPty(self, terminal, windowSize, attrs):
self.windowSize = windowSize
return None
def execCommand(self, protocol, cmd):
raise NotImplementedError
def closed(self):
pass
def windowChanged(self, windowSize):
self.windowSize = windowSize
class HoneyPotEnvironment(object):
def __init__(self):
self.cfg = config()
self.commands = {}
import kippo.commands
for c in kippo.commands.__all__:
module = __import__('kippo.commands.%s' % c,
globals(), locals(), ['commands'])
self.commands.update(module.commands)
self.fs = pickle.load(file(
self.cfg.get('honeypot', 'filesystem_file')))
class HoneyPotRealm:
implements(portal.IRealm)
def __init__(self):
# I don't know if i'm supposed to keep static stuff here
self.env = HoneyPotEnvironment()
def requestAvatar(self, avatarId, mind, *interfaces):
if conchinterfaces.IConchUser in interfaces:
return interfaces[0], \
HoneyPotAvatar(avatarId, self.env), lambda: None
else:
raise Exception, "No supported interfaces found."
# As implemented by Kojoney
class HoneyPotSSHFactory(factory.SSHFactory):
#publicKeys = {'ssh-rsa': keys.getPublicKeyString(data=publicKey)}
#privateKeys = {'ssh-rsa': keys.getPrivateKeyObject(data=privateKey)}
services = {
'ssh-userauth': userauth.SSHUserAuthServer,
'ssh-connection': connection.SSHConnection,
}
def buildProtocol(self, addr):
# FIXME: try to mimic something real 100%
t = transport.SSHServerTransport()
t.ourVersionString = 'SSH-2.0-OpenSSH_5.1p1 Debian-5'
t.supportedPublicKeys = self.privateKeys.keys()
if not self.primes:
ske = t.supportedKeyExchanges[:]
ske.remove('diffie-hellman-group-exchange-sha1')
t.supportedKeyExchanges = ske
t.factory = self
return t
class HoneypotPasswordChecker:
implements(checkers.ICredentialsChecker)
credentialInterfaces = (credentials.IUsernamePassword,)
def __init__(self, users):
self.users = users
def requestAvatarId(self, credentials):
if (credentials.username, credentials.password) in self.users:
print 'login attempt [%s/%s] succeeded' % \
(credentials.username, credentials.password)
return defer.succeed(credentials.username)
else:
print 'login attempt [%s/%s] failed' % \
(credentials.username, credentials.password)
return defer.fail(error.UnauthorizedLogin())
def getRSAKeys():
if not (os.path.exists('public.key') and os.path.exists('private.key')):
# generate a RSA keypair
print "Generating RSA keypair..."
from Crypto.PublicKey import RSA
KEY_LENGTH = 1024
rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
publicKeyString = keys.makePublicKeyString(rsaKey)
privateKeyString = keys.makePrivateKeyString(rsaKey)
# save keys for next time
file('public.key', 'w+b').write(publicKeyString)
file('private.key', 'w+b').write(privateKeyString)
print "done."
else:
publicKeyString = file('public.key').read()
privateKeyString = file('private.key').read()
return publicKeyString, privateKeyString
# vim: set sw=4 et:

30
kippo/core/ttylog.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
# Should be compatible with user mode linux
import struct, sys
OP_OPEN, OP_CLOSE, OP_WRITE, OP_EXEC = 1, 2, 3, 4
DIR_READ, DIR_WRITE = 1, 2
def ttylog_write(logfile, len, direction, stamp, data = None):
f = file(logfile, 'a')
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('iLiiLL', 3, 0, len, direction, sec, usec))
f.write(data)
f.close()
def ttylog_open(logfile, stamp):
f = file(logfile, 'a')
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('iLiiLL', 1, 0, 0, 0, sec, usec))
f.close()
def ttylog_close(logfile, stamp):
f = file(logfile, 'a')
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('iLiiLL', 2, 0, 0, 0, sec, usec))
f.close()
# vim: set sw=4 et: