mirror of https://github.com/n1nj4sec/pupy.git
653 lines
18 KiB
Python
Executable File
653 lines
18 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
import argparse
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import errno
|
|
import hashlib
|
|
import shutil
|
|
import resource
|
|
|
|
ENV_IMAGE = 'pupy-python2-env'
|
|
ENV_CONTAINER = 'pupy-'
|
|
|
|
TEMPLATES = {
|
|
'linux32': 'sources-linux',
|
|
'linux64': 'sources-linux',
|
|
'linux-armhf': 'sources-linux',
|
|
'android': 'android_sources',
|
|
'windows': 'sources'
|
|
}
|
|
|
|
default_local_bin_location = os.path.expanduser('~/.local/bin/')
|
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
parser = argparse.ArgumentParser(prog="create-workspace.py")
|
|
parser.add_argument(
|
|
'-G', '--pupy-git-folder',
|
|
default=ROOT, help='Path to pupy git'
|
|
)
|
|
|
|
templates_args = parser.add_mutually_exclusive_group()
|
|
templates_args.add_argument(
|
|
'-NC', '--do-not-compile-templates',
|
|
action='store_true', default=False,
|
|
help='Do not compile payload templates'
|
|
)
|
|
|
|
templates_args.add_argument(
|
|
'-C', '--compile-templates',
|
|
default='linux32,linux64,windows',
|
|
help='Compile specified templates (default: linux32,linux64,windows)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-E', '--environment', choices=['virtualenv', 'docker', 'podman'],
|
|
default='virtualenv', help='The way to organize workspace bottle'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-N', '--network', default='host',
|
|
help='Network type for docker/podman. Default is host'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-P', '--persistent', default=False, action='store_true',
|
|
help='Do not remove docker/podman build image'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-S', '--squash', default=False, action='store_true',
|
|
help='Use --squash feature (podman/docker)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-R', '--images-repo', default='alxchk',
|
|
help='Use non-default toolchains repo (Use "local" to '
|
|
'build all the things on your PC'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-T', '--image-tag', default='latest', help='Image tag'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-B', '--bin-path', default=default_local_bin_location,
|
|
help='Store pupy launch wrapper to this folder (default={})'.format(
|
|
default_local_bin_location)
|
|
)
|
|
|
|
parser.add_argument('workdir', help='Location of workdir')
|
|
|
|
_REQUIRED_PROGRAMS = {
|
|
'podman': (
|
|
['podman', 'info'],
|
|
'Podman either is is not installed or not configured.\n'
|
|
'Installation: https://podman.io/getting-started/installation'
|
|
),
|
|
'docker': (
|
|
['docker', 'info'],
|
|
'Docker either is not installed or not configured.\n'
|
|
'Installation: https://docs.docker.com/install/'
|
|
),
|
|
'git': (
|
|
['git', '--help'],
|
|
'Install git (example: sudo apt-get install git)'
|
|
)
|
|
}
|
|
|
|
_ESCAPE = (
|
|
'"', '$', '`', '\\'
|
|
)
|
|
|
|
|
|
def shstr(string):
|
|
if not any(esc in string for esc in _ESCAPE):
|
|
return string
|
|
|
|
result = ['"']
|
|
|
|
for char in string:
|
|
if char in _ESCAPE:
|
|
result.append('\\')
|
|
result.append(char)
|
|
|
|
result.append('"')
|
|
return ''.join(result)
|
|
|
|
|
|
def shjoin(args):
|
|
return ' '.join(shstr(string) for string in args)
|
|
|
|
|
|
def get_place_digest(*args):
|
|
return hashlib.sha1(
|
|
b'\0'.join(
|
|
arg.encode('ascii') for arg in args
|
|
)
|
|
).hexdigest()[:4]
|
|
|
|
|
|
def check_programs(programs, available=False):
|
|
messages = []
|
|
ok = []
|
|
|
|
for program in programs:
|
|
args, message = _REQUIRED_PROGRAMS[program]
|
|
|
|
try:
|
|
with open(os.devnull, 'w') as devnull:
|
|
subprocess.check_call(args, stdout=devnull)
|
|
|
|
ok.append(program)
|
|
except (OSError, subprocess.CalledProcessError):
|
|
messages.append(message)
|
|
|
|
if available:
|
|
return ok
|
|
else:
|
|
return messages
|
|
|
|
|
|
def check_modules(modules):
|
|
messages = []
|
|
|
|
for module in modules:
|
|
try:
|
|
__import__(module)
|
|
except ImportError:
|
|
messages.append(
|
|
'Missing python module: {}'.format(module)
|
|
)
|
|
|
|
return messages
|
|
|
|
|
|
def get_repo_origin(git_folder):
|
|
return subprocess.check_output([
|
|
'git', 'remote', 'get-url', 'origin'
|
|
], cwd=git_folder)
|
|
|
|
|
|
def update_repo(git_folder):
|
|
return subprocess.check_output([
|
|
'git', 'submodule', 'update', '--init', '--recursive'
|
|
], cwd=git_folder)
|
|
|
|
|
|
def get_rev(git_folder):
|
|
return subprocess.check_output([
|
|
'git', 'rev-parse', 'HEAD'
|
|
], cwd=git_folder)
|
|
|
|
|
|
def get_changed_files(git_folder, prev_ref, current_ref='HEAD'):
|
|
return subprocess.check_output([
|
|
'git', 'diff', '--name-only', prev_ref, current_ref
|
|
], cwd=git_folder).split()
|
|
|
|
|
|
def build_templates(
|
|
git_folder, docker_repo, orchestrator, templates, tag, persistent):
|
|
print("[+] Compile templates: {}".format(templates))
|
|
|
|
if docker_repo.lower().strip() == 'local':
|
|
docker_repo = ''
|
|
|
|
repo = ''
|
|
|
|
if docker_repo:
|
|
repo = docker_repo + '/'
|
|
elif orchestrator == 'podman':
|
|
repo = 'localhost' + '/'
|
|
|
|
update_commands = []
|
|
|
|
for template in templates:
|
|
container_name = 'build-pupy-' + template + '-' + get_place_digest(
|
|
git_folder
|
|
)
|
|
|
|
create_template = False
|
|
|
|
try:
|
|
with open(os.devnull, 'w') as devnull:
|
|
subprocess.check_call([
|
|
orchestrator, 'inspect', container_name
|
|
], stdout=devnull, stderr=devnull)
|
|
except subprocess.CalledProcessError:
|
|
create_template = True
|
|
|
|
if create_template:
|
|
print("[+] Build {} using {} (create)".format(
|
|
template, container_name))
|
|
|
|
args = [
|
|
orchestrator, 'run'
|
|
]
|
|
|
|
if not persistent:
|
|
args.append('--rm')
|
|
|
|
args.extend([
|
|
'--name=' + container_name,
|
|
'--ulimit', 'nofile=65535:65535',
|
|
'--security-opt', 'label=disable',
|
|
'--mount', 'type=bind,src=' + git_folder +
|
|
',target=/build/workspace/project',
|
|
repo + 'tc-' + template + ':' + tag,
|
|
'client/' + TEMPLATES[template] + '/build-docker.sh'
|
|
])
|
|
|
|
try:
|
|
subprocess.check_call(args, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
if e.returncode == 139 and template == 'linux64':
|
|
print("[!] Likely you must to enable vsyscall=emulate")
|
|
|
|
raise
|
|
|
|
if persistent:
|
|
update_commands.append(
|
|
orchestrator + ' start -a ' + shstr(container_name)
|
|
)
|
|
else:
|
|
update_commands.append(shjoin(args))
|
|
|
|
else:
|
|
print("[+] Build {} using {} (existing)".format(
|
|
template, container_name))
|
|
|
|
try:
|
|
subprocess.check_call([
|
|
orchestrator, 'start', '-a', container_name
|
|
], stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
if e.returncode == 139 and template == 'linux64':
|
|
print("[!] Likely you must to enable vsyscall=emulate")
|
|
|
|
raise
|
|
|
|
update_commands.append(
|
|
orchestrator + ' start -a ' + shstr(container_name)
|
|
)
|
|
|
|
return update_commands
|
|
|
|
|
|
def make_pupysh_wrapper(workdir, git_folder, orchestrator):
|
|
pass
|
|
|
|
|
|
def makedirs_p(dirpath):
|
|
try:
|
|
os.makedirs(dirpath)
|
|
except OSError as e:
|
|
if e.errno == errno.EEXIST:
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
|
|
def initialize_workdir(workdir, gitdir):
|
|
for dirname in ('crypto', 'data', 'bin', 'config'):
|
|
makedirs_p(os.path.join(workdir, dirname))
|
|
|
|
shutil.copy(
|
|
os.path.join(
|
|
gitdir, 'pupy', 'conf', 'pupy.conf.docker'
|
|
),
|
|
os.path.join(
|
|
workdir, 'config', 'pupy.conf'
|
|
)
|
|
)
|
|
|
|
|
|
def create_virtualenv(workdir, git_path, orchestrator=None, templates=[]):
|
|
import virtualenv
|
|
|
|
if hasattr(virtualenv, 'create_environment'):
|
|
virtualenv.create_environment(workdir)
|
|
else:
|
|
virtualenv.cli_run([workdir])
|
|
|
|
print("[+] Update pip version ...")
|
|
subprocess.check_call([
|
|
os.path.join(workdir, 'bin', 'pip'),
|
|
'install',
|
|
'--upgrade', 'pip'
|
|
], cwd=workdir, stderr=subprocess.STDOUT)
|
|
|
|
print("[+] Install dependencies")
|
|
subprocess.check_call([
|
|
os.path.join(workdir, 'bin', 'pip'),
|
|
'install', '--no-use-pep517',
|
|
'-r', 'requirements.txt'
|
|
], cwd=os.path.join(git_path, 'pupy'), stderr=subprocess.STDOUT)
|
|
|
|
shell_commands = [
|
|
'exec {1}/bin/python -OB {0}/pupy/pupysh.py --workdir {1} "$@"'.format(
|
|
shstr(git_path), shstr(workdir)
|
|
)
|
|
]
|
|
|
|
update_commands = [
|
|
'cd {}'.format(git_path),
|
|
'prev_ref=`git rev-parse HEAD`',
|
|
'git pull --recurse-submodules=yes --autostash --rebase',
|
|
'if (git diff --name-only $prev_ref HEAD | grep client/ >/dev/null)'
|
|
'then',
|
|
]
|
|
|
|
if orchestrator and templates:
|
|
for target in templates:
|
|
update_commands.extend([
|
|
'echo "[+] Rebuilding templates for {}"'.format(target),
|
|
'{} start -a build-pupy-{}-{}'.format(
|
|
orchestrator, target,
|
|
get_place_digest(git_path)
|
|
)
|
|
])
|
|
else:
|
|
update_commands.extend([
|
|
'echo "[-] You must update templates manually"'
|
|
])
|
|
|
|
update_commands.extend([
|
|
'fi'
|
|
])
|
|
|
|
return shell_commands, update_commands
|
|
|
|
|
|
def create_container_env(
|
|
workdir, git_path, orchestrator, network, templates=[], squash=False):
|
|
|
|
print("[+] Build {} image ({})".format(orchestrator, ENV_IMAGE))
|
|
|
|
build_command = [
|
|
orchestrator, 'build'
|
|
]
|
|
|
|
if squash:
|
|
build_command.append('--squash')
|
|
|
|
build_command.extend([
|
|
'-t', ENV_IMAGE,
|
|
'-f', os.path.join(git_path, 'pupy', 'conf', 'Dockerfile.env'),
|
|
os.path.join(git_path, 'pupy')
|
|
])
|
|
|
|
try:
|
|
with open(os.devnull, 'w') as devnull:
|
|
subprocess.check_call([
|
|
orchestrator, 'inspect', ENV_IMAGE
|
|
], stdout=devnull, stderr=devnull)
|
|
except subprocess.CalledProcessError:
|
|
print("[+] Create pupysh environment image {}".format(
|
|
ENV_IMAGE
|
|
))
|
|
|
|
subprocess.check_call(build_command, stderr=subprocess.STDOUT)
|
|
|
|
container_name = ENV_CONTAINER + get_place_digest(
|
|
workdir, git_path
|
|
)
|
|
|
|
print("[+] Create podman container ({})".format(container_name))
|
|
|
|
create_command = [
|
|
orchestrator, 'create',
|
|
'--security-opt', 'label=disable',
|
|
'--hostname=pupy', '--network=' + network,
|
|
'--name='+container_name,
|
|
'--interactive', '--tty',
|
|
'--mount', 'type=bind,src=' + os.path.join(
|
|
git_path, 'pupy') + ',target=/pupy',
|
|
'--mount', 'type=bind,src=' + workdir + ',target=/project',
|
|
ENV_IMAGE
|
|
]
|
|
|
|
subprocess.check_call(create_command, stderr=subprocess.STDOUT)
|
|
|
|
shell_commands = [
|
|
'exec {} start -ai {}'.format(orchestrator, container_name)
|
|
]
|
|
|
|
update_commands = [
|
|
'cd {}'.format(git_path),
|
|
'prev_ref=`git rev-parse HEAD`',
|
|
'git pull --recurse-submodules=yes --autostash --rebase',
|
|
'echo "[+] Update {} environment"'.format(orchestrator),
|
|
shjoin(build_command),
|
|
orchestrator + ' kill ' + container_name + ' || true',
|
|
orchestrator + ' rm ' + container_name,
|
|
shjoin(create_command),
|
|
'if (git diff --name-only $prev_ref HEAD | grep client/ >/dev/null)',
|
|
'then',
|
|
]
|
|
|
|
if templates:
|
|
for target in templates:
|
|
update_commands.extend([
|
|
'echo "[+] Rebuilding templates for {}"'.format(target),
|
|
'{} start -a build-pupy-{}-{}'.format(
|
|
orchestrator, target, get_place_digest(git_path)
|
|
)
|
|
])
|
|
else:
|
|
update_commands.extend([
|
|
'echo "[-] You must update templates manually"'
|
|
])
|
|
|
|
update_commands.extend([
|
|
'fi'
|
|
])
|
|
|
|
return shell_commands, update_commands
|
|
|
|
|
|
def main():
|
|
args = parser.parse_args()
|
|
default_orchestrator = 'docker'
|
|
|
|
# Check some programs in advance
|
|
if args.environment == 'virtualenv' and not args.do_not_compile_templates:
|
|
available = check_programs([
|
|
'podman', 'docker'
|
|
], available=True)
|
|
|
|
for orchestrator in ('podman', 'docker'):
|
|
if orchestrator in available:
|
|
default_orchestrator = orchestrator
|
|
break
|
|
else:
|
|
if args.environment != "virtualenv":
|
|
default_orchestrator = args.environment
|
|
|
|
required_programs = {'git'}
|
|
required_modules = set()
|
|
required_abis = set()
|
|
|
|
if sys.version_info.major == 3 and args.environment == 'virtualenv':
|
|
sys.exit(
|
|
"Python 3 is not supported. If your can't or don't want"
|
|
" to install python 2 to the system, "
|
|
"use -E option to select podman or docker to build bottle.\n"
|
|
)
|
|
|
|
if not args.do_not_compile_templates and default_orchestrator == 'podman' \
|
|
and args.persistent:
|
|
print(
|
|
'Warning! You have chosen persistent images. '
|
|
'This known to have problems with podman + fuse-overlayfs'
|
|
)
|
|
|
|
if not args.do_not_compile_templates:
|
|
if args.environment == 'virtualenv':
|
|
required_programs.add(default_orchestrator)
|
|
else:
|
|
required_programs.add(args.environment)
|
|
|
|
required_abis.add('vsyscall32')
|
|
required_programs.add(default_orchestrator)
|
|
|
|
workdir = os.path.abspath(args.workdir)
|
|
|
|
if not os.path.isfile(
|
|
os.path.join(args.pupy_git_folder, 'create-workspace.py')):
|
|
sys.exit('{} is not pupy project folder'.format(
|
|
args.pupy_git_folder))
|
|
|
|
if os.path.isdir(workdir) and os.listdir(workdir):
|
|
sys.exit('{} is not empty'.format(workdir))
|
|
|
|
git_folder = os.path.abspath(args.pupy_git_folder)
|
|
|
|
print("[+] Git repo at {}".format(git_folder))
|
|
|
|
messages = []
|
|
|
|
messages.extend(
|
|
check_programs(required_programs)
|
|
)
|
|
|
|
messages.extend(
|
|
check_modules(required_modules)
|
|
)
|
|
|
|
if not args.do_not_compile_templates and default_orchestrator == 'podman':
|
|
_, nofile_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
if nofile_limit < 65535:
|
|
messages.append(
|
|
'To build templates using podman RLIMIT_NOFILE (ulimit -n) '
|
|
'must be >= 65535.\n'
|
|
'Read documentation for your linux distribution to find '
|
|
'how to change them.'
|
|
)
|
|
|
|
if messages:
|
|
sys.exit('\n'.join(messages))
|
|
|
|
update_repo(git_folder)
|
|
|
|
templates = []
|
|
|
|
update_commands = [
|
|
'set -xe'
|
|
]
|
|
|
|
if not args.do_not_compile_templates:
|
|
templates.extend(
|
|
set(
|
|
template.lower().strip() for template in
|
|
args.compile_templates.split(',')
|
|
)
|
|
)
|
|
|
|
update_commands.extend(
|
|
build_templates(
|
|
git_folder, args.images_repo,
|
|
default_orchestrator,
|
|
templates, args.image_tag, args.persistent
|
|
)
|
|
)
|
|
|
|
print("[+] Create workdir")
|
|
makedirs_p(workdir)
|
|
|
|
shell_cmds = []
|
|
|
|
if args.environment in ('podman', 'docker'):
|
|
shell_cmds, update_cmds = create_container_env(
|
|
workdir, git_folder, default_orchestrator,
|
|
args.network, templates, args.squash
|
|
)
|
|
|
|
update_commands.extend(update_cmds)
|
|
|
|
else:
|
|
shell_cmds, update_cmds = create_virtualenv(
|
|
workdir, git_folder, 'docker', templates
|
|
)
|
|
|
|
update_commands.extend(update_cmds)
|
|
|
|
print("[+] Initialize workdir")
|
|
initialize_workdir(workdir, git_folder)
|
|
|
|
wrappers = ("pupysh", "pupygen")
|
|
|
|
print("[+] Create {} wrappers".format(','.join(wrappers)))
|
|
|
|
pupysh_update_path = os.path.join(workdir, 'bin', 'pupysh-update')
|
|
pupysh_path = os.path.join(workdir, 'bin', 'pupysh')
|
|
|
|
with open(pupysh_path, 'w') as pupysh:
|
|
pupysh.write(
|
|
'\n'.join([
|
|
'#!/bin/sh',
|
|
] + shell_cmds) + '\n'
|
|
)
|
|
|
|
os.chmod(pupysh_path, 0o755)
|
|
|
|
with open(pupysh_update_path, 'w') as pupysh:
|
|
pupysh.write(
|
|
'\n'.join([
|
|
'#!/bin/sh',
|
|
] + update_commands) + '\n'
|
|
)
|
|
|
|
os.chmod(pupysh_update_path, 0o755)
|
|
|
|
if args.bin_path:
|
|
bin_path = os.path.abspath(args.bin_path)
|
|
print("[+] Store symlink to pupysh to {}".format(bin_path))
|
|
|
|
if not os.path.isdir(bin_path):
|
|
os.makedirs(bin_path)
|
|
|
|
for src, sympath in (
|
|
(
|
|
pupysh_path, 'pupysh'
|
|
), (
|
|
pupysh_update_path, 'pupysh-update'
|
|
)
|
|
):
|
|
sympath = os.path.join(bin_path, sympath)
|
|
|
|
if os.path.islink(sympath):
|
|
os.unlink(sympath)
|
|
|
|
elif os.path.exists(sympath):
|
|
sys.exit(
|
|
"[-] File at {} already exists and not symlink".format(
|
|
sympath))
|
|
|
|
os.symlink(src, sympath)
|
|
|
|
if bin_path not in os.environ['PATH']:
|
|
print("[-] {} is not in your PATH!".format(bin_path))
|
|
else:
|
|
print("[I] To execute pupysh:")
|
|
print("~ > pupysh")
|
|
print("[I] To update:")
|
|
print("~ > pupysh-update")
|
|
|
|
else:
|
|
print("[I] To execute pupysh:")
|
|
print("~ > {}".format(pupysh_path))
|
|
print("[I] To update:")
|
|
print("~ > {}".format(pupysh_update_path))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|