├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── kippo_extra ├── __init__.py ├── commands │ ├── __init__.py │ ├── env.py │ ├── gcc.py │ ├── iptables.py │ ├── netstat.py │ ├── sleep.py │ ├── uname.py │ └── which.py ├── loader.py ├── patches │ ├── kippo_core_honeypot.patch │ └── kippo_core_protocol.patch └── utils.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Python related 2 | *.pyc 3 | 4 | # Setuptools related 5 | build/ 6 | dist/ 7 | kippo_extra.egg-info/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | recursive-include kippo_extra/patches * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kippo-extra 2 | Set of extra commands for the kippo SSH honeypot daemon (https://github.com/desaster/kippo/). 3 | 4 | ## Provided commands 5 | * `/usr/bin/env` - current environment variables 6 | * `/usr/bin/gcc` - fake compiler with file output 7 | * `/sbin/iptables` - fake firewall management, supports flush and list for different tables/chains 8 | * `/bin/which` - path of binary 9 | * `/bin/netstat` - work in progress 10 | * `/bin/sleep` - sleep command 11 | * `/bin/uname` - kernel and OS identification 12 | 13 | The commands are based on the x64 build of Debian 5. 14 | 15 | ## Installation 16 | Please read the full installation part. 17 | 18 | Python 2.7 or later is required (Python 3.x isn't). Install kippo-extra via one of the methods below. 19 | 20 | * `pip install kippo-extra` 21 | * `pip install git+git://github.com/basilfx/kippo-extra.git`. 22 | 23 | To check if everything works, you can run `python -c 'import kippo_extra'` which should not produce any errors. 24 | 25 | Kippo doesn't come with a plugin system. Therefore, the kippo source should be modified. Open the file `KIPPO_ROOT/kippo/__init__.py`. By default, it is an empty file. Insert the line `from kippo_extra import loader` and save the file. 26 | 27 | ### Note 1 28 | The commands require you have a fake filesystem ready with fake links to the commands. Therefore, make sure the `/path/to/command` is in your fake filesystem. If a command does not work in a session, try to `touch /path/to/command` and then try again. If it works now, then you do not have the fake links in your fake filesystem. 29 | 30 | ### Note 2 31 | A small portion of the kippo source code is modified at runtime by intercepting imports (See PEP302). However, these patches are dependend on the version of kippo. At this moment, the patches are based on [revision 4999618](https://github.com/desaster/kippo/commit/4999618f476ffa3be5e2be88ea11519ab4a86d87). In case a new version is available and kippo fails to start, you can try to manually apply the patches to the kippo source (see the directory `kippo_extra/patches`). Insert `PATCH_SOURCE = False` in `KIPPO_ROOT/kippo/__init__.py` to disable runtime patching. 32 | 33 | ## Known issues 34 | Probably a lot. Of course, not all commands are complete and are functional. I have implemented the basic options to make an honeypot session more realistic. 35 | 36 | Feel free to fork or submit issues! 37 | 38 | ## License 39 | See the `LICENSE` file (MIT license). 40 | 41 | This work uses of some code of [Merge-in-Memory](https://github.com/danielmoniz/merge_in_memory) by Daniel Moniz. 42 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | kippo-extra 2 | =========== 3 | 4 | Set of extra commands for the kippo SSH honeypot daemon 5 | (https://github.com/desaster/kippo/). 6 | 7 | Provided commands 8 | ----------------- 9 | 10 | - ``/usr/bin/env`` - current environment variables 11 | - ``/usr/bin/gcc`` - fake compiler with file output 12 | - ``/sbin/iptables`` - fake firewall management, supports flush and 13 | list for different tables/chains 14 | - ``/bin/which`` - path of binary 15 | - ``/bin/netstat`` - work in progress 16 | - ``/bin/sleep`` - sleep command 17 | - ``/bin/uname`` - kernel and OS identification 18 | 19 | The commands are based on the x64 build of Debian 5. 20 | 21 | Installation 22 | ------------ 23 | 24 | Please read the full installation part. 25 | 26 | Python 2.7 or later is required (Python 3.x isn't). Install kippo-extra 27 | via one of the methods below. 28 | 29 | - ``pip install kippo-extra`` 30 | - ``pip install git+git://github.com/basilfx/kippo-extra.git``. 31 | 32 | To check if everything works, you can run 33 | ``python -c 'import kippo_extra'`` which should not produce any errors. 34 | 35 | Kippo doesn't come with a plugin system. Therefore, the kippo source 36 | should be modified. Open the file ``KIPPO_ROOT/kippo/__init__.py``. By 37 | default, it is an empty file. Insert the line 38 | ``from kippo_extra import loader`` and save the file. 39 | 40 | Note 1 41 | ~~~~~~ 42 | 43 | The commands require you have a fake filesystem ready with fake links to 44 | the commands. Therefore, make sure the ``/path/to/command`` is in your 45 | fake filesystem. If a command does not work in a session, try to 46 | ``touch /path/to/command`` and then try again. If it works now, then you 47 | do not have the fake links in your fake filesystem. 48 | 49 | Note 2 50 | ~~~~~~ 51 | 52 | A small portion of the kippo source code is modified at runtime by 53 | intercepting imports (See PEP302). However, these patches are dependend 54 | on the version of kippo. At this moment, the patches are based on 55 | `revision 56 | 4999618 `__. 57 | In case a new version is available and kippo fails to start, you can try 58 | to manually apply the patches to the kippo source (see the directory 59 | ``kippo_extra/patches``). Insert ``PATCH_SOURCE = False`` in 60 | ``KIPPO_ROOT/kippo/__init__.py`` to disable runtime patching. 61 | 62 | Known issues 63 | ------------ 64 | 65 | Probably a lot. Of course, not all commands are complete and are 66 | functional. I have implemented the basic options to make an honeypot 67 | session more realistic. 68 | 69 | Feel free to fork or submit issues! 70 | 71 | License 72 | ------- 73 | 74 | See the ``LICENSE`` file (MIT license). 75 | 76 | This work uses of some code of 77 | `Merge-in-Memory `__ by 78 | Daniel Moniz. 79 | -------------------------------------------------------------------------------- /kippo_extra/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basilfx/kippo-extra/a03f8b94877efcb7fbf458226b73ed06ce732b4a/kippo_extra/__init__.py -------------------------------------------------------------------------------- /kippo_extra/commands/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | __all__ = [ 4 | "env", 5 | "gcc", 6 | "iptables", 7 | "netstat", 8 | "sleep", 9 | "uname", 10 | "which" 11 | ] 12 | -------------------------------------------------------------------------------- /kippo_extra/commands/env.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo_extra.utils import ExtendedHoneyPotCommand 4 | 5 | commands = {} 6 | 7 | 8 | class command_env(ExtendedHoneyPotCommand): 9 | def call(self): 10 | """ Print the current environment variables """ 11 | 12 | if self.env and len(self.env) > 0: 13 | for key, value in self.env.iteritems(): 14 | self.writeln("%s=%s" % (key, value)) 15 | 16 | # Definition 17 | commands['/usr/bin/env'] = command_env 18 | -------------------------------------------------------------------------------- /kippo_extra/commands/gcc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo_extra.utils import ExtendedHoneyPotCommand 4 | from twisted.internet import reactor 5 | 6 | import getopt 7 | import random 8 | import time 9 | import re 10 | 11 | commands = {} 12 | 13 | 14 | class command_gcc(ExtendedHoneyPotCommand): 15 | # Name of program. Under OSX, you might consider another version such as 16 | # i686-apple-darwin11-llvm-gcc-X.X 17 | APP_NAME = "gcc" 18 | 19 | # GCC verson, used in help, version and the commandline name gcc-X.X 20 | APP_VERSION = (4, 4, 5) 21 | 22 | # Random binary data, which looks awesome. You could change this to 23 | # whatever you want, but this data will be put in the actual file and thus 24 | # exposed to our hacker when he\she cats the file. 25 | RANDOM_DATA = "\x6a\x00\x48\x89\xe5\x48\x83\xe4\xf0\x48\x8b\x7d\x08\x48\x8d\x75\x10\x89\xfa" \ 26 | "\x83\xc2\x01\xc1\xe2\x03\x48\x01\xf2\x48\x89\xd1\xeb\x04\x48\x83\xc1\x08\x48" \ 27 | "\x83\x39\x00\x75\xf6\x48\x83\xc1\x08\xe8\x0c\x00\x00\x00\x89\xc7\xe8\xb9\x00" \ 28 | "\x00\x00\xf4\x90\x90\x90\x90\x55\x48\x89\xe5\x48\x83\xec\x40\x89\x7d\xfc\x48" \ 29 | "\x89\x75\xf0\x48\x8b\x45\xf0\x48\x8b\x00\x48\x83\xf8\x00\x75\x0c\xb8\x00\x00" \ 30 | "\x00\x00\x89\xc7\xe8\x8c\x00\x00\x00\x48\x8b\x45\xf0\x48\x8b\x40\x08\x30\xc9" \ 31 | "\x48\x89\xc7\x88\xc8\xe8\x7e\x00\x00\x00\x89\xc1\x89\x4d\xdc\x48\x8d\x0d\xd8" \ 32 | "\x01\x00\x00\x48\x89\xcf\x48\x89\x4d\xd0\xe8\x72\x00\x00\x00\x8b\x4d\xdc\x30" \ 33 | "\xd2\x48\x8d\x3d\xa4\x00\x00\x00\x89\xce\x88\x55\xcf\x48\x89\xc2\x8a\x45\xcf" \ 34 | "\xe8\x53\x00\x00\x00\x8b\x45\xdc\x88\x05\xc3\x01\x00\x00\x8b\x45\xdc\xc1\xe8" \ 35 | "\x08\x88\x05\xb8\x01\x00\x00\x8b\x45\xdc\xc1\xe8\x10\x88\x05\xad\x01\x00\x00" \ 36 | "\x8b\x45\xdc\xc1\xe8\x18\x88\x05\xa2\x01\x00\x00\x48\x8b\x45\xd0\x48\x89\x45" \ 37 | "\xe0\x48\x8b\x45\xe0\xff\xd0\x8b\x45\xec\x48\x83\xc4\x40\x5d\xc3\xff\x25\x3e" \ 38 | "\x01\x00\x00\xff\x25\x40\x01\x00\x00\xff\x25\x42\x01\x00\x00\xff\x25\x44\x01" \ 39 | "\x00\x00\x4c\x8d\x1d\x1d\x01\x00\x00\x41\x53\xff\x25\x0d\x01\x00\x00\x90\x68" \ 40 | "\x00\x00\x00\x00\xe9\xe6\xff\xff\xff\x68\x0c\x00\x00\x00\xe9\xdc\xff\xff\xff" \ 41 | "\x68\x1d\x00\x00\x00\xe9\xd2\xff\xff\xff\x68\x2b\x00\x00\x00\xe9\xc8\xff\xff" \ 42 | "\xff\x01\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00" \ 43 | "\x00\x00\x1c\x00\x00\x00\x02\x00\x00\x00\x00\x0e\x00\x00\x34\x00\x00\x00\x34" \ 44 | "\x00\x00\x00\xf5\x0e\x00\x00\x00\x00\x00\x00\x34\x00\x00\x00\x03\x00\x00\x00" \ 45 | "\x0c\x00\x02\x00\x14\x00\x02\x00\x00\x00\x00\x01\x40\x00\x00\x00\x00\x00\x00" \ 46 | "\x01\x00\x00\x00" 47 | 48 | def start(self): 49 | """ 50 | Parse as much as possible from a GCC syntax and generate the output 51 | that is requested. The file that is generated can be read (and will) 52 | output garbage from an actual file, but when executed, it will generate 53 | a segmentation fault. 54 | 55 | The input files are expected to exists, but can be empty. 56 | 57 | Verified syntaxes, including non-existing files: 58 | * gcc test.c 59 | * gcc test.c -o program 60 | * gcc test1.c test2.c 61 | * gcc test1.c test2.c -o program 62 | * gcc test.c -o program -lm 63 | * gcc -g test.c -o program -lm 64 | * gcc test.c -DF_CPU=16000000 -I../etc -o program 65 | * gcc test.c -O2 -o optimized_program 66 | * gcc test.c -Wstrict-overflow=n -o overflowable_program 67 | 68 | Others: 69 | * gcc 70 | * gcc -h 71 | * gcc -v 72 | * gcc --help 73 | * gcc --version 74 | """ 75 | 76 | output_file = None 77 | input_files = 0 78 | complete = True 79 | 80 | # Parse options or display no files 81 | try: 82 | opts, args = getopt.gnu_getopt(self.args, 'ESchvgo:x:l:I:W:D:X:O:', ['help', 'version', 'param']) 83 | except getopt.GetoptError: 84 | self.no_files() 85 | return 86 | 87 | # Parse options 88 | for o, a in opts: 89 | if o in ("-v"): 90 | self.version(short=False) 91 | return 92 | elif o in ("--version"): 93 | self.version(short=True) 94 | return 95 | elif o in ("-h"): 96 | self.arg_missing("-h") 97 | return 98 | elif o in ("--help"): 99 | self.help() 100 | return 101 | elif o in ("-o"): 102 | if len(a) == 0: 103 | self.arg_missing("-o") 104 | else: 105 | output_file = a 106 | 107 | # Check for *.c or *.cpp files 108 | for value in args: 109 | if '.c' in value.lower(): 110 | sourcefile = self.fs.resolve_path(value, self.honeypot.cwd) 111 | 112 | if self.fs.exists(sourcefile): 113 | input_files = input_files + 1 114 | else: 115 | self.writeln("%s: %s: No such file or directory" % (command_gcc.APP_NAME, value)) 116 | complete = False 117 | 118 | # To generate, or not 119 | if input_files > 0 and complete: 120 | func = lambda: self.generate_file(output_file if output_file else 'a.out') 121 | timeout = 0.1 + random.random() 122 | 123 | # Schedule call to make it more time consuming and real 124 | self.scheduled = reactor.callLater(timeout, func) 125 | else: 126 | self.no_files() 127 | 128 | def ctrl_c(self): 129 | """ Make sure the scheduled call will be canceled """ 130 | 131 | if getattr(self, 'scheduled', False): 132 | self.scheduled.cancel() 133 | 134 | def no_files(self): 135 | """ Notify user there are no input files, and exit """ 136 | self.writeln_and_exit('%s: no input files' % command_gcc.APP_NAME) 137 | 138 | def version(self, short): 139 | """ Print long or short version, and exit """ 140 | 141 | # Generate version number 142 | version = '.'.join([str(v) for v in command_gcc.APP_VERSION[:3]]) 143 | version_short = '.'.join([str(v) for v in command_gcc.APP_VERSION[:2]]) 144 | 145 | if short: 146 | data = ( 147 | '%s (Debian %s-8) %s' % (command_gcc.APP_NAME, version, version), 148 | 'Copyright (C) 2010 Free Software Foundation, Inc.', 149 | 'This is free software; see the source for copying conditions. There is NO', 150 | 'warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.', 151 | '' 152 | ) 153 | else: 154 | data = ( 155 | 'Using built-in specs.', 156 | 'Target: x86_64-linux-gnu', 157 | 'Configured with: ../src/configure -v --with-pkgversion=\'Debian %s-8\' --with-bugurl=file:///usr/share/doc/gcc-%s/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-%s --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/%s --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu' % (version, version_short, version_short, version_short), 158 | 'Thread model: posix', 159 | 'gcc version %s (Debian %s-8)' % (version, version) 160 | ) 161 | 162 | # Write 163 | self.writeln_and_exit(data) 164 | 165 | def generate_file(self, outfile): 166 | data = "" 167 | # TODO: make sure it is written to temp file, not downloads 168 | safeoutfile = '%s/%s_%s' % ( 169 | self.honeypot.env.cfg.get('honeypot', 'download_path'), 170 | time.strftime('%Y%m%d%H%M%S'), 171 | re.sub('[^A-Za-z0-9]', '_', outfile)) 172 | 173 | # Data contains random garbage from an actual file, so when 174 | # catting the file, you'll see some 'real' compiled data 175 | for i in range(random.randint(3, 15)): 176 | if random.randint(1, 3) == 1: 177 | data = data + command_gcc.RANDOM_DATA[::-1] 178 | else: 179 | data = data + command_gcc.RANDOM_DATA 180 | 181 | # Write random data 182 | with open(safeoutfile, 'wb') as f: 183 | f.write(data) 184 | 185 | # Output file 186 | outfile = self.fs.resolve_path(outfile, self.honeypot.cwd) 187 | 188 | # Create file for the honeypot 189 | self.fs.mkfile(outfile, 0, 0, len(data), 33188) 190 | self.fs.update_realfile(self.fs.getfile(outfile), safeoutfile) 191 | 192 | # Segfault command 193 | class segfault_command(ExtendedHoneyPotCommand): 194 | def call(self): 195 | self.write("Segmentation fault\n") 196 | 197 | # Trick the 'new compiled file' as an segfault 198 | self.honeypot.commands[outfile] = segfault_command 199 | 200 | # Done 201 | self.exit() 202 | 203 | def arg_missing(self, arg): 204 | """ Print missing argument message, and exit """ 205 | self.writeln_and_exit("%s: argument to '%s' is missing" % (command_gcc.APP_NAME, arg)) 206 | 207 | def help(self): 208 | """ Print help info, and exit """ 209 | 210 | version = '.'.join([ str(v) for v in command_gcc.APP_VERSION[:2] ]) 211 | 212 | self.writeln_and_exit([ 213 | 'Usage: %s [options] file...' % command_gcc.APP_NAME, 214 | 'Options:', 215 | ' -pass-exit-codes Exit with highest error code from a phase', 216 | ' --help Display this information', 217 | ' --target-help Display target specific command line options', 218 | ' --help={target|optimizers|warnings|params|[^]{joined|separate|undocumented}}[,...]', 219 | ' Display specific types of command line options', 220 | ' (Use \'-v --help\' to display command line options of sub-processes)', 221 | ' --version Display compiler version information', 222 | ' -dumpspecs Display all of the built in spec strings', 223 | ' -dumpversion Display the version of the compiler', 224 | ' -dumpmachine Display the compiler\'s target processor', 225 | ' -print-search-dirs Display the directories in the compiler\'s search path', 226 | ' -print-libgcc-file-name Display the name of the compiler\'s companion library', 227 | ' -print-file-name= Display the full path to library ', 228 | ' -print-prog-name= Display the full path to compiler component ', 229 | ' -print-multi-directory Display the root directory for versions of libgcc', 230 | ' -print-multi-lib Display the mapping between command line options and', 231 | ' multiple library search directories', 232 | ' -print-multi-os-directory Display the relative path to OS libraries', 233 | ' -print-sysroot Display the target libraries directory', 234 | ' -print-sysroot-headers-suffix Display the sysroot suffix used to find headers', 235 | ' -Wa, Pass comma-separated on to the assembler', 236 | ' -Wp, Pass comma-separated on to the preprocessor', 237 | ' -Wl, Pass comma-separated on to the linker', 238 | ' -Xassembler Pass on to the assembler', 239 | ' -Xpreprocessor Pass on to the preprocessor', 240 | ' -Xlinker Pass on to the linker', 241 | ' -combine Pass multiple source files to compiler at once', 242 | ' -save-temps Do not delete intermediate files', 243 | ' -pipe Use pipes rather than intermediate files', 244 | ' -time Time the execution of each subprocess', 245 | ' -specs= Override built-in specs with the contents of ', 246 | ' -std= Assume that the input sources are for ', 247 | ' --sysroot= Use as the root directory for headers', 248 | ' and libraries', 249 | ' -B Add to the compiler\'s search paths', 250 | ' -b Run gcc for target , if installed', 251 | ' -V Run gcc version number , if installed', 252 | ' -v Display the programs invoked by the compiler', 253 | ' -### Like -v but options quoted and commands not executed', 254 | ' -E Preprocess only; do not compile, assemble or link', 255 | ' -S Compile only; do not assemble or link', 256 | ' -c Compile and assemble, but do not link', 257 | ' -o Place the output into ', 258 | ' -x Specify the language of the following input files', 259 | ' Permissible languages include: c c++ assembler none', 260 | ' \'none\' means revert to the default behavior of', 261 | ' guessing the language based on the file\'s extension', 262 | '', 263 | 'Options starting with -g, -f, -m, -O, -W, or --param are automatically', 264 | ' passed on to the various sub-processes invoked by gcc. In order to pass', 265 | ' other options on to these processes the -W options must be used.', 266 | '', 267 | 'For bug reporting instructions, please see:', 268 | '.' % version, 269 | ]) 270 | 271 | # Definitions 272 | commands['/usr/bin/gcc'] = command_gcc 273 | commands['/usr/bin/gcc-%s' % ( 274 | '.'.join([str(v) for v in command_gcc.APP_VERSION[:2]]))] = command_gcc 275 | -------------------------------------------------------------------------------- /kippo_extra/commands/iptables.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo_extra.utils import ExtendedHoneyPotCommand, ModifiedOptionParser, \ 4 | OptionParsingError, OptionParsingExit 5 | 6 | commands = {} 7 | 8 | 9 | class command_iptables(ExtendedHoneyPotCommand): 10 | # Do not resolve args 11 | resolve_args = False 12 | 13 | # iptables app name 14 | APP_NAME = "iptables" 15 | 16 | # iptables app version, used in help messages etc. 17 | APP_VERSION = "v1.4.14" 18 | 19 | # Default iptable table 20 | DEFAULT_TABLE = "filter" 21 | 22 | def start(self): 23 | """ 24 | Emulate iptables commands, including permission checking. 25 | 26 | Verified examples: 27 | * iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT 28 | * iptables -A INPUT -i eth0 -p tcp -s "127.0.0.1" -j DROP 29 | 30 | Others: 31 | * iptables 32 | * iptables [[-t | --table] ] [-h | --help] 33 | * iptables [[-t | --table] ] [-v | --version] 34 | * iptables [[-t | --table] ] [-F | --flush] 35 | * iptables [[-t | --table] ] [-L | --list] 36 | * iptables [[-t | --table] ] [-S | --list-rules] 37 | * iptables --this-is-invalid 38 | """ 39 | 40 | # In case of no arguments 41 | if len(self.args) == 0: 42 | self.no_command() 43 | return 44 | 45 | # Utils 46 | def optional_arg(arg_default): 47 | def func(option,opt_str,value,parser): 48 | if parser.rargs and not parser.rargs[0].startswith('-'): 49 | val=parser.rargs[0] 50 | parser.rargs.pop(0) 51 | else: 52 | val=arg_default 53 | setattr(parser.values,option.dest,val) 54 | return func 55 | 56 | # Initialize options 57 | parser = ModifiedOptionParser(add_help_option=False) 58 | parser.add_option("-h", "--help", dest="help", action="store_true") 59 | parser.add_option("-V", "--version", dest="version", action="store_true") 60 | parser.add_option("-v", "--verbose", dest="verbose", action="store_true") 61 | parser.add_option("-x", "--exact", dest="exact", action="store_true") 62 | parser.add_option("--line-numbers", dest="line_numbers", action="store_true") 63 | parser.add_option("-n", "--numeric", dest="numeric", action="store_true") 64 | parser.add_option("--modprobe", dest="modprobe", action="store") 65 | 66 | parser.add_option("-t", "--table", dest="table", action="store", default=command_iptables.DEFAULT_TABLE) 67 | parser.add_option("-F", "--flush", dest="flush", action="callback", callback=optional_arg(True)) 68 | parser.add_option("-Z", "--zero", dest="zero", action="callback", callback=optional_arg(True)) 69 | parser.add_option("-S", "--list-rules", dest="list_rules", action="callback", callback=optional_arg(True)) 70 | parser.add_option("-L", "--list", dest="list", action="callback", callback=optional_arg(True)) 71 | parser.add_option("-A", "--append", dest="append", action="store") 72 | parser.add_option("-D", "--delete", dest="delete", action="store") 73 | parser.add_option("-I", "--insert", dest="insert", action="store") 74 | parser.add_option("-R", "--replace", dest="replace", action="store") 75 | parser.add_option("-N", "--new-chain", dest="new_chain", action="store") 76 | parser.add_option("-X", "--delete-chain", dest="delete_chain", action="store") 77 | parser.add_option("-P", "--policy", dest="policy", action="store") 78 | parser.add_option("-E", "--rename-chain", dest="rename_chain", action="store") 79 | 80 | parser.add_option("-p", "--protocol", dest="protocol", action="store") 81 | parser.add_option("-s", "--source", dest="source", action="store") 82 | parser.add_option("-d", "--destination", dest="destination", action="store") 83 | parser.add_option("-j", "--jump", dest="jump", action="store") 84 | parser.add_option("-g", "--goto", dest="goto", action="store") 85 | parser.add_option("-i", "--in-interface", dest="in_interface", action="store") 86 | parser.add_option("-o", "--out-interface", dest="out_interface", action="store") 87 | parser.add_option("-f", "--fragment", dest="fragment", action="store_true") 88 | parser.add_option("-c", "--set-counters", dest="set_counters", action="store") 89 | parser.add_option("-m", "--match", dest="match", action="store") 90 | 91 | parser.add_option("--sport", "--source-ports", dest="source_ports", action="store") 92 | parser.add_option("--dport", "--destination-ports", dest="dest_ports", action="store") 93 | parser.add_option("--ports", dest="ports", action="store") 94 | parser.add_option("--state", dest="state", action="store") 95 | 96 | 97 | # Parse options or display no files 98 | try: 99 | (opts, args) = parser.parse_args(list(self.args)) 100 | except OptionParsingError, e: 101 | self.bad_argument(self.args[0]) 102 | return 103 | except OptionParsingExit, e: 104 | self.unknown_option(e) 105 | return 106 | 107 | # Initialize table 108 | if not self.setup_table(opts.table): 109 | return 110 | 111 | # Parse options 112 | if opts.help: 113 | self.show_help() 114 | return 115 | elif opts.version: 116 | self.show_version() 117 | return 118 | elif opts.flush: 119 | self.flush("" if opts.flush == True else opts.flush) 120 | return 121 | elif opts.list: 122 | self.list("" if opts.list == True else opts.list) 123 | return 124 | elif opts.list_rules: 125 | self.list_rules("" if opts.list_rules == True else opts.list_rules) 126 | return 127 | 128 | # Done 129 | self.exit() 130 | 131 | def setup_table(self, table): 132 | """ 133 | Called during startup to make sure the current environment has some 134 | fake rules in memory. 135 | """ 136 | 137 | # Create fresh tables on start 138 | if not hasattr(self.honeypot.env, 'iptables'): 139 | setattr(self.honeypot.env, 'iptables', { 140 | "raw": { 141 | "PREROUTING": [], 142 | "OUTPUT": [] 143 | }, 144 | "filter": { 145 | "INPUT": [ 146 | ('ACCEPT', 'tcp', '--', 'anywhere', 'anywhere', 'tcp', 'dpt:ssh'), 147 | ('DROP', 'all', '--', 'anywhere', 'anywhere', '', '') 148 | ], 149 | "FORWARD": [], 150 | "OUTPUT": [] 151 | }, 152 | "mangle": { 153 | "PREROUTING": [], 154 | "INPUT": [], 155 | "FORWARD": [], 156 | "OUTPUT": [], 157 | "POSTROUTING": [] 158 | }, 159 | "nat": { 160 | "PREROUTING": [], 161 | "OUTPUT": [] 162 | } 163 | }) 164 | 165 | # Get the tables 166 | self.tables = getattr(self.honeypot.env, 'iptables') 167 | 168 | # Verify selected table 169 | if not self.is_valid_table(table): 170 | return False 171 | 172 | # Set table 173 | self.current_table = self.tables[table] 174 | 175 | # Done 176 | return True 177 | 178 | def is_valid_table(self, table): 179 | if self.user_is_root(): 180 | # Verify table existence 181 | if not table in self.tables.iterkeys(): 182 | self.writeln( """%s: can\'t initialize iptables table \'%s\': Table does not exist (do you need to insmod?) 183 | Perhaps iptables or your kernel needs to be upgraded.""" % (command_iptables.APP_NAME, table) ) 184 | self.exit() 185 | else: 186 | # Exists 187 | return True 188 | else: 189 | self.no_permission() 190 | 191 | # Failed 192 | return False 193 | 194 | def is_valid_chain(self, chain): 195 | # Verify chain existence. Requires valid table first 196 | if not chain in self.current_table.iterkeys(): 197 | self.writeln("%s: No chain/target/match by that name." % command_iptables.APP_NAME) 198 | self.exit() 199 | return False 200 | 201 | # Exists 202 | return True 203 | 204 | def show_version(self): 205 | """ Show version and exit """ 206 | self.writeln_and_exit('%s %s' % (command_iptables.APP_NAME, command_iptables.APP_VERSION)) 207 | 208 | def show_help(self): 209 | """ Show help and exit """ 210 | 211 | self.writeln_and_exit("""%s %s' 212 | 213 | Usage: iptables -[AD] chain rule-specification [options] 214 | iptables -I chain [rulenum] rule-specification [options] 215 | iptables -R chain rulenum rule-specification [options] 216 | iptables -D chain rulenum [options] 217 | iptables -[LS] [chain [rulenum]] [options] 218 | iptables -[FZ] [chain] [options] 219 | iptables -[NX] chain 220 | iptables -E old-chain-name new-chain-name 221 | iptables -P chain target [options] 222 | iptables -h (print this help information) 223 | 224 | Commands: 225 | Either long or short options are allowed. 226 | --append -A chain Append to chain 227 | --delete -D chain Delete matching rule from chain 228 | --delete -D chain rulenum 229 | Delete rule rulenum (1 = first) from chain 230 | --insert -I chain [rulenum] 231 | Insert in chain as rulenum (default 1=first) 232 | --replace -R chain rulenum 233 | Replace rule rulenum (1 = first) in chain 234 | --list -L [chain [rulenum]] 235 | List the rules in a chain or all chains 236 | --list-rules -S [chain [rulenum]] 237 | Print the rules in a chain or all chains 238 | --flush -F [chain] Delete all rules in chain or all chains 239 | --zero -Z [chain [rulenum]] 240 | Zero counters in chain or all chains 241 | --new -N chain Create a new user-defined chain 242 | --delete-chain 243 | -X [chain] Delete a user-defined chain 244 | --policy -P chain target 245 | Change policy on chain to target 246 | --rename-chain 247 | -E old-chain new-chain 248 | Change chain name, (moving any references) 249 | Options: 250 | [!] --proto -p proto protocol: by number or name, eg. \'tcp\' 251 | [!] --source -s address[/mask][...] 252 | source specification 253 | [!] --destination -d address[/mask][...] 254 | destination specification 255 | [!] --in-interface -i input name[+] 256 | network interface name ([+] for wildcard) 257 | --jump -j target 258 | target for rule (may load target extension) 259 | --goto -g chain 260 | jump to chain with no return 261 | --match -m match 262 | extended match (may load extension) 263 | --numeric -n numeric output of addresses and ports 264 | [!] --out-interface -o output name[+] 265 | network interface name ([+] for wildcard) 266 | --table -t table table to manipulate (default: \'filter\') 267 | --verbose -v verbose mode 268 | --line-numbers print line numbers when listing 269 | --exact -x expand numbers (display exact values) 270 | [!] --fragment -f match second or further fragments only 271 | --modprobe= try to insert modules using this command 272 | --set-counters PKTS BYTES set the counter during insert/append 273 | [!] --version -V print package version.""" % ( 274 | command_iptables.APP_NAME, command_iptables.APP_VERSION)) 275 | 276 | def list_rules(self, chain): 277 | """ List current rules as commands""" 278 | 279 | if self.user_is_root(): 280 | if len(chain) > 0: 281 | print chain 282 | # Check chain 283 | if not self.is_valid_chain(chain): 284 | return 285 | 286 | chains = [chain] 287 | else: 288 | chains = self.current_table.iterkeys() 289 | 290 | # Output buffer 291 | output = [] 292 | 293 | for chain in chains: 294 | output.append("-P %s ACCEPT" % chain) 295 | 296 | # Done 297 | self.writeln_and_exit(output) 298 | else: 299 | self.no_permission() 300 | 301 | def list(self, chain): 302 | """ List current rules """ 303 | 304 | if self.user_is_root(): 305 | if len(chain) > 0: 306 | print chain 307 | # Check chain 308 | if not self.is_valid_chain(chain): 309 | return 310 | 311 | chains = [chain] 312 | else: 313 | chains = self.current_table.iterkeys() 314 | 315 | # Output buffer 316 | output = [] 317 | 318 | for chain in chains: 319 | # Chain table header 320 | chain_output = [ 321 | "Chain %s (policy ACCEPT)" % chain, 322 | "target prot opt source destination", 323 | ] 324 | 325 | # Format the rules 326 | for rule in self.current_table[chain]: 327 | chain_output.append( 328 | "%-10s %-4s %-3s %-20s %-20s %s %s" % rule, 329 | ) 330 | 331 | # Create one string 332 | output.append("\n".join(chain_output)) 333 | 334 | # Done 335 | self.writeln_and_exit("\n\n".join(output)) 336 | else: 337 | self.no_permission() 338 | 339 | def flush(self, chain): 340 | """ Mark rules as flushed """ 341 | 342 | if self.user_is_root(): 343 | if len(chain) > 0: 344 | # Check chain 345 | if not self.is_valid_chain(chain): 346 | return 347 | 348 | chains = [chain] 349 | else: 350 | chains = self.current_table.iterkeys() 351 | 352 | # Flush 353 | for chain in chains: 354 | self.current_table[chain] = [] 355 | 356 | self.exit() 357 | else: 358 | self.no_permission() 359 | 360 | def no_permission(self): 361 | self.writeln_and_exit("""%s %s: can\'t initialize iptables table \'filter\': Permission denied (you must be root) 362 | Perhaps iptables or your kernel needs to be upgraded.""" % ( 363 | command_iptables.APP_NAME, command_iptables.APP_VERSION)) 364 | 365 | def no_command(self): 366 | """ Print no command message and exit """ 367 | 368 | self.writeln_and_exit("""%s %s: no command specified' 369 | Try `iptables -h\' or \'iptables --help\' for more information.""" % ( 370 | command_iptables.APP_NAME, command_iptables.APP_VERSION) ) 371 | 372 | def unknown_option(self, option): 373 | """ Print unknown option message and exit """ 374 | 375 | self.writeln_and_exit("""%s %s: unknown option \'%s\'' 376 | Try `iptables -h\' or \'iptables --help\' for more information.""" % ( 377 | command_iptables.APP_NAME, command_iptables.APP_VERSION, option) ) 378 | 379 | def bad_argument(self, argument): 380 | """ Print bad argument and exit """ 381 | 382 | self.writeln_and_exit("""Bad argument \'%s\' 383 | Try `iptables -h\' or \'iptables --help\' for more information.""" % argument) 384 | 385 | # Definition 386 | commands['/sbin/iptables'] = command_iptables 387 | -------------------------------------------------------------------------------- /kippo_extra/commands/netstat.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo_extra.utils import ExtendedHoneyPotCommand 4 | 5 | commands = {} 6 | 7 | 8 | class command_netstat(ExtendedHoneyPotCommand): 9 | def call(self): 10 | pass 11 | 12 | def show_version(self): 13 | self.writeln_and_exit([ 14 | 'netstat 1.42 (2001-04-15)', 15 | 'Fred Baumgarten, Alan Cox, Bernd Eckenfels, Phil Blundell, Tuan Hoang and others', 16 | '+NEW_ADDRT +RTF_IRTT +RTF_REJECT +FW_MASQUERADE +I18N', 17 | 'AF: (inet) +UNIX +INET +INET6 +IPX +AX25 +NETROM +X25 +ATALK +ECONET +ROSE', 18 | 'HW: +ETHER +ARC +SLIP +PPP +TUNNEL +TR +AX25 +NETROM +X25 +FR +ROSE +ASH +SIT +FDDI +HIPPI +HDLC/LAPB +EUI64' 19 | ]) 20 | 21 | def show_help(self): 22 | self.writeln_and_exit([ 23 | 'usage: netstat [-vWeenNcCF] [] -r netstat {-V|--version|-h|--help}', 24 | ' netstat [-vWnNcaeol] [ ...]', 25 | ' netstat { [-vWeenNac] -i | [-cWnNe] -M | -s }', 26 | '', 27 | ' -r, --route display routing table', 28 | ' -i, --interfaces display interface table', 29 | ' -g, --groups display multicast group memberships', 30 | ' -s, --statistics display networking statistics (like SNMP)', 31 | ' -M, --masquerade display masqueraded connections', 32 | '', 33 | ' -v, --verbose be verbose', 34 | ' -W, --wide don\'t truncate IP addresses', 35 | ' -n, --numeric don\'t resolve names', 36 | ' --numeric-hosts don\'t resolve host names', 37 | ' --numeric-ports don\'t resolve port names', 38 | ' --numeric-users don\'t resolve user names', 39 | ' -N, --symbolic resolve hardware names', 40 | ' -e, --extend display other/more information', 41 | ' -p, --programs display PID/Program name for sockets', 42 | ' -c, --continuous continuous listing', 43 | '', 44 | ' -l, --listening display listening server sockets', 45 | ' -a, --all, --listening display all sockets (default: connected)', 46 | ' -o, --timers display timers', 47 | ' -F, --fib display Forwarding Information Base (default)', 48 | ' -C, --cache display routing cache instead of FIB', 49 | '', 50 | ' ={-t|--tcp} {-u|--udp} {-w|--raw} {-x|--unix} --ax25 --ipx --netrom', 51 | ' =Use \'-6|-4\' or \'-A \' or \'--\'; default: inet', 52 | ' List of possible address families (which support routing):', 53 | ' inet (DARPA Internet) inet6 (IPv6) ax25 (AMPR AX.25) ', 54 | ' netrom (AMPR NET/ROM) ipx (Novell IPX) ddp (Appletalk DDP) ', 55 | ' x25 (CCITT X.25) ', 56 | ]) 57 | 58 | # Definitions 59 | commands['/bin/netstat'] = command_netstat 60 | -------------------------------------------------------------------------------- /kippo_extra/commands/sleep.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo_extra.utils import ExtendedHoneyPotCommand 4 | from twisted.internet import reactor 5 | 6 | commands = {} 7 | 8 | 9 | class command_sleep(ExtendedHoneyPotCommand): 10 | def start(self): 11 | """ Sleep for n seconds, or display help """ 12 | 13 | if len(self.args) == 1: 14 | _time = int(self.args[0]) 15 | self.scheduled = reactor.callLater(_time, self.exit) 16 | else: 17 | self.writeln_and_exit('usage: sleep seconds') 18 | 19 | commands['/bin/sleep'] = command_sleep 20 | -------------------------------------------------------------------------------- /kippo_extra/commands/uname.py: -------------------------------------------------------------------------------- 1 | from kippo_extra.utils import ExtendedHoneyPotCommand, ModifiedOptionParser, \ 2 | OptionParsingError, OptionParsingExit 3 | 4 | commands = {} 5 | 6 | 7 | class command_uname(ExtendedHoneyPotCommand): 8 | def call(self): 9 | """ Add uname command to identify server """ 10 | 11 | # Initialize options 12 | parser = ModifiedOptionParser(add_help_option=False) 13 | parser.add_option("--help", dest="help", action="store_true") 14 | parser.add_option("--version", dest="version", action="store_true") 15 | parser.add_option("-a", "--all", dest="all", action="store_true") 16 | parser.add_option( 17 | "-s", "--kenel-name", dest="name", action="store_true") 18 | parser.add_option( 19 | "-r", "--kernel-release", dest="release", action="store_true") 20 | parser.add_option( 21 | "-v", "--kernel-version", dest="kernel", action="store_true") 22 | parser.add_option( 23 | "-m", "--machine", dest="machine", action="store_true") 24 | parser.add_option( 25 | "-p", "--processor", dest="processor", action="store_true") 26 | parser.add_option( 27 | "-i", "--hardware-platform", dest="hardware", action="store_true") 28 | parser.add_option( 29 | "-o", "--operating-system", dest="os", action="store_true") 30 | 31 | try: 32 | (opts, args) = parser.parse_args(list(self.args)) 33 | except OptionParsingError, e: 34 | self.bad_argument(self.args[0]) 35 | return 36 | except OptionParsingExit, e: 37 | self.bad_argument(e) 38 | return 39 | 40 | if opts.help: 41 | self.help() 42 | elif opts.version: 43 | self.writeln("#1 SMP Wed Nov 4 23:40:10 UTC 2009") 44 | elif opts.all: 45 | self.writeln( 46 | "Linux %s 2.6.26-2-686 #1 SMP Wed Nov 4 20:45:37 " 47 | "UTC 2009 i686 GNU/Linux" % self.honeypot.hostname) 48 | else: 49 | parts = [] 50 | 51 | # Actually, the order matters, e.g. '-s -r -v' gives another output 52 | # as '-r -v -s'. 53 | if opts.name: 54 | parts.append("Linux") 55 | if opts.release: 56 | parts.append("2.6.26-2-686") 57 | if opts.kernel: 58 | parts.append("#1 SMP Wed Nov 4 20:45:37 UTC 2009") 59 | if opts.machine: 60 | parts.append("i686") 61 | if opts.processor: 62 | parts.append("i686") 63 | if opts.hardware: 64 | parts.append("i686") 65 | if opts.os: 66 | parts.append("GNU/Linux") 67 | 68 | # No command given, disply the name only. 69 | if parts: 70 | self.writeln(" ".join(parts)) 71 | else: 72 | self.writeln("Linux") 73 | 74 | def bad_argument(self, argument): 75 | self.writeln("""uname: invalid option -- '%s' 76 | Try 'uname --help' for more information.""" % argument) 77 | 78 | def help(self): 79 | self.writeln("""Usage: uname [OPTION]... 80 | Print certain system information. With no OPTION, same as -s. 81 | 82 | -a, --all print all information, in the following order, 83 | except omit -p and -i if unknown: 84 | -s, --kernel-name print the kernel name 85 | -n, --nodename print the network node hostname 86 | -r, --kernel-release print the kernel release 87 | -v, --kernel-version print the kernel version 88 | -m, --machine print the machine hardware name 89 | -p, --processor print the processor type or "unknown" 90 | -i, --hardware-platform print the hardware platform or "unknown" 91 | -o, --operating-system print the operating system 92 | --help display this help and exit 93 | --version output version information and exit 94 | 95 | GNU coreutils online help: 96 | Full documentation at: 97 | or available locally via: info '(coreutils) uname invocation'""") 98 | 99 | commands['/bin/uname'] = command_uname 100 | -------------------------------------------------------------------------------- /kippo_extra/commands/which.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo_extra.utils import ExtendedHoneyPotCommand 4 | 5 | commands = {} 6 | 7 | 8 | class command_which(ExtendedHoneyPotCommand): 9 | # Do not resolve args 10 | resolve_args = False 11 | 12 | def call(self): 13 | """ Look up all the arguments on PATH and print each (first) result """ 14 | 15 | # No arguments, just exit 16 | if not len(self.args) or 'PATH' not in self.env: 17 | return 18 | 19 | # Look up each file 20 | for f in self.args: 21 | for path in self.env['PATH'].split(':'): 22 | resolved = self.fs.resolve_path(f, path) 23 | 24 | if self.fs.exists(resolved): 25 | self.writeln("%s/%s" % (path, f)) 26 | continue 27 | 28 | # Definition 29 | commands['/bin/which'] = command_which 30 | -------------------------------------------------------------------------------- /kippo_extra/loader.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | import importlib 4 | import sys 5 | import imp 6 | import os 7 | 8 | # Set to False to disable source patching 9 | PATCH_SOURCE = True 10 | 11 | # List of patches to apply. Format is 'module: (kippo_file, patch_file)'. 12 | PATCHES = { 13 | "kippo.core.honeypot": ( 14 | "core/honeypot.py", "patches/kippo_core_honeypot.patch"), 15 | "kippo.core.protocol": ( 16 | "core/protocol.py", "patches/kippo_core_protocol.patch"), 17 | } 18 | 19 | 20 | class KippoExtraLoader(object): 21 | def find_module(self, fullname, path=None): 22 | # Intercept command imports 23 | if "kippo.commands.kippo_extra" in fullname: 24 | return KippoExtraCommandsLoader() 25 | 26 | # Intercept core import 27 | if PATCH_SOURCE: 28 | if fullname in PATCHES: 29 | return KippoPatchingLoader() 30 | 31 | return None 32 | 33 | 34 | class KippoPatchingLoader(object): 35 | def load_module(self, name): 36 | # Check if module is already loaded. 37 | if name in sys.modules: 38 | return sys.modules[name] 39 | 40 | # Create a new module. 41 | module = imp.new_module(name) 42 | 43 | # Check if a patch has to be applied. 44 | if name in PATCHES: 45 | source, source_file = self.patch_source(*PATCHES[name]) 46 | bytecode = compile(source, source_file, "exec") 47 | 48 | # Run the bytecode inside the new module. 49 | exec bytecode in module.__dict__ 50 | 51 | # Add it as loaded. 52 | sys.modules[name] = module 53 | 54 | return module 55 | 56 | def get_kippo_root(self): 57 | _, path, _ = imp.find_module("kippo") 58 | return path 59 | 60 | def read_file(self, filename): 61 | with open(filename, "r") as fp: 62 | result = fp.read() 63 | 64 | return result 65 | 66 | def patch_source(self, source_file, patch_file): 67 | source_file = os.path.join(self.get_kippo_root(), source_file) 68 | patch_file = os.path.join(os.path.dirname(__file__), patch_file) 69 | 70 | source = self.read_file(source_file) 71 | patch = self.read_file(patch_file) 72 | 73 | # Modified from https://github.com/danielmoniz/merge_in_memory/ 74 | diff = patch.split("\n") 75 | source = source.split("\n") 76 | hunk = 0 77 | i = 0 78 | 79 | # Iterate through diff sections 80 | for line in diff: 81 | i += 1 82 | 83 | if line.startswith("@"): 84 | hunk = hunk + 1 85 | 86 | line = line.replace("-", "") 87 | line = line.replace("+", "") 88 | line = line.strip("@") 89 | line = line.strip() 90 | 91 | old_info, new_info = line.split(" ") 92 | old_info = old_info.split(",") 93 | new_info = new_info.split(",") 94 | 95 | i = int(new_info[0]) - 1 96 | elif line.startswith("---") or line.startswith("+++"): 97 | continue 98 | elif line.startswith("-"): 99 | # Delete the line. 100 | del source[i - 1] 101 | i = i - 1 102 | elif line.startswith("+"): 103 | # Add in a new line. 104 | line = line[1:] 105 | source.insert(i - 1, line) 106 | elif line.startswith(" "): 107 | # Verify if the line matches, so we know if we are in sync. 108 | if source[i - 1] != line[1:]: 109 | raise Exception("Hunk #%d does not match source." % hunk) 110 | 111 | # Done 112 | return "\n".join(source), source_file 113 | 114 | 115 | class KippoExtraCommandsLoader(object): 116 | def load_module(self, name): 117 | # Strip namespace 118 | name = name.replace("kippo.commands.", "") 119 | 120 | # Check loaded modules first 121 | if name in sys.modules: 122 | return sys.modules[name] 123 | 124 | # Not loaded, so load it 125 | return importlib.import_module(name) 126 | 127 | 128 | def install_hook(): 129 | # Extend kippo's commands 130 | import kippo.commands 131 | import kippo_extra.commands 132 | 133 | for command in kippo_extra.commands.__all__: 134 | module = "%s.%s" % (kippo_extra.commands.__name__, command) 135 | kippo.commands.__all__.append(module) 136 | 137 | # Add import interceptor 138 | sys.meta_path.append(KippoExtraLoader()) 139 | 140 | install_hook() 141 | -------------------------------------------------------------------------------- /kippo_extra/patches/kippo_core_honeypot.patch: -------------------------------------------------------------------------------- 1 | --- 2 | +++ 3 | @@ -14,9 +14,10 @@ 4 | import pickle 5 | 6 | class HoneyPotCommand(object): 7 | - def __init__(self, honeypot, *args): 8 | + def __init__(self, honeypot, *args, **kwargs): 9 | self.honeypot = honeypot 10 | self.args = args 11 | + self.env = kwargs 12 | self.writeln = self.honeypot.writeln 13 | self.write = self.honeypot.terminal.write 14 | self.nextLine = self.honeypot.terminal.nextLine 15 | @@ -122,7 +123,11 @@ 16 | if cmdclass: 17 | print 'Command found: %s' % (line,) 18 | self.honeypot.logDispatch('Command found: %s' % (line,)) 19 | - self.honeypot.call_command(cmdclass, *rargs) 20 | + 21 | + if hasattr(cmdclass, 'resolve_args'): 22 | + self.honeypot.call_command(cmdclass, *rargs, **envvars) 23 | + else: 24 | + self.honeypot.call_command(cmdclass, *args, **envvars) 25 | else: 26 | self.honeypot.logDispatch('Command not found: %s' % (line,)) 27 | print 'Command not found: %s' % (line,) 28 | -------------------------------------------------------------------------------- /kippo_extra/patches/kippo_core_protocol.patch: -------------------------------------------------------------------------------- 1 | --- 2 | +++ 3 | @@ -111,8 +111,8 @@ 4 | self.terminal.write(data) 5 | self.terminal.nextLine() 6 | 7 | - def call_command(self, cmd, *args): 8 | - obj = cmd(self, *args) 9 | + def call_command(self, cmd, *args, **kwargs): 10 | + obj = cmd(self, *args, **kwargs) 11 | self.cmdstack.append(obj) 12 | obj.start() 13 | 14 | @@ -177,9 +177,9 @@ 15 | def initializeScreen(self): 16 | self.setInsertMode() 17 | 18 | - def call_command(self, cmd, *args): 19 | + def call_command(self, cmd, *args, **kwargs): 20 | self.setTypeoverMode() 21 | - HoneyPotBaseProtocol.call_command(self, cmd, *args) 22 | + HoneyPotBaseProtocol.call_command(self, cmd, *args, **kwargs) 23 | 24 | def keystrokeReceived(self, keyID, modifier): 25 | transport = self.terminal.transport.session.conn.transport 26 | -------------------------------------------------------------------------------- /kippo_extra/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | from kippo.core.honeypot import HoneyPotCommand 4 | 5 | import optparse 6 | 7 | 8 | class ExtendedHoneyPotCommand(HoneyPotCommand): 9 | """ 10 | Extend the HoneyPotCommand class with some commonly used methods. 11 | """ 12 | 13 | def __init__(self, *args, **kwargs): 14 | super(ExtendedHoneyPotCommand, self).__init__(*args, **kwargs) 15 | 16 | # Override default writeln 17 | def _writeln(data): 18 | """ 19 | Write data to the honeypot terminal. Data can be a string, but also 20 | an iterable 21 | """ 22 | 23 | # If data is iterable, then write line for line 24 | if type(data) in (type([]), type(())): 25 | for line in data: 26 | self.honeypot.writeln(line) 27 | else: 28 | self.honeypot.writeln(data) 29 | self.writeln = _writeln 30 | 31 | def writeln_and_exit(self, data): 32 | """ 33 | Write data and exit. Useful for more complicated commands that need to 34 | display help, version e.g. 35 | """ 36 | 37 | # Write data and quit 38 | self.writeln(data) 39 | self.exit() 40 | 41 | def user_is_root(self): 42 | return self.honeypot.user.username == 'root' 43 | 44 | 45 | class OptionParsingError(RuntimeError): 46 | def __init__(self, msg): 47 | self.msg = msg 48 | 49 | 50 | class OptionParsingExit(Exception): 51 | def __init__(self, status, msg): 52 | self.msg = msg 53 | self.status = status 54 | 55 | 56 | class ModifiedOptionParser(optparse.OptionParser): 57 | def error(self, msg): 58 | raise OptionParsingError(msg) 59 | 60 | def exit(self, status=0, msg=None): 61 | raise OptionParsingExit(status, msg) 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # Check python version 4 | if sys.version_info < (2, 7, 0): 5 | sys.stderr.write("kippo-extra requires Python 2.7 or newer.\n") 6 | sys.exit(1) 7 | 8 | try: 9 | from setuptools import setup 10 | except ImportError: 11 | from distutils.core import setup 12 | 13 | # Run setup 14 | setup( 15 | name="kippo-extra", 16 | version="1.1.0", 17 | author="Bas Stottelaar", 18 | author_email="basstottelaar@gmail.com", 19 | url="https://github.com/basilfx/kippo-extra", 20 | description="Set of extra commands for the kippo SSH honeypot daemon", 21 | long_description=open("README.rst").read(), 22 | license=open("LICENSE").read(), 23 | packages=["kippo_extra", "kippo_extra.commands"], 24 | package_data={ 25 | "": ["LICENSE", "README.rst"], 26 | "kippo_extra/patches": ["*.patches"] 27 | }, 28 | include_package_data=True, 29 | classifiers=( 30 | "Development Status :: 3 - Alpha", 31 | "Intended Audience :: Developers", 32 | "Intended Audience :: Science/Research", 33 | "Intended Audience :: System Administrators", 34 | "License :: OSI Approved :: MIT License", 35 | "Programming Language :: Python", 36 | "Programming Language :: Python :: 2.7", 37 | "Topic :: Security" 38 | ), 39 | ) 40 | --------------------------------------------------------------------------------