pendora-box/pendora-box.py

521 lines
16 KiB
Python
Raw Permalink Normal View History

2022-08-02 19:11:57 +02:00
#!/usr/bin/env python3
2022-05-20 12:54:44 +02:00
import argparse
2022-04-16 18:48:42 +02:00
import json
2022-04-27 15:25:51 +02:00
from pathlib import Path
2022-04-16 18:48:42 +02:00
import hashlib
import requests
import sys
2022-08-02 19:11:57 +02:00
from os import geteuid, chdir
2022-04-16 18:48:42 +02:00
import subprocess
2022-04-23 16:26:56 +02:00
from io import BytesIO
import zipfile
import rpmfile
import gzip
2022-04-28 16:23:08 +02:00
from bs4 import BeautifulSoup
import re
2022-04-16 18:48:42 +02:00
def compute_file_hash(filepath):
with open(filepath, 'rb') as file_for_hash:
data = file_for_hash.read()
filesize = len(data)
# Github sha value is computed as such:
return hashlib.sha1(b"blob " + bytes(str(filesize), 'utf-8') + b"\0" + data).hexdigest()
def get_master_info(repo, filepath, credz):
2022-04-16 18:48:42 +02:00
url = f"https://api.github.com/repos/{repo}/contents/{filepath}"
r = requests.get(url, auth=credz)
2022-04-16 18:48:42 +02:00
sha = r.json()['sha']
2022-08-09 14:07:02 +02:00
dlurl = r.json()['download_url']
return sha, dlurl
2022-04-16 18:48:42 +02:00
def get_last_release_info(repo, credz):
url = f"https://api.github.com/repos/{repo}/releases"
r = requests.get(url, auth=credz)
for release in r.json():
if not release['draft'] and not release['prerelease']:
return release['tag_name']
2022-04-16 18:48:42 +02:00
2022-04-23 16:26:56 +02:00
def extract_bin(archtype, binpath, destpath, content):
ioobj = BytesIO(content) # Get a File object from bytes ;)
if archtype == 'zip':
with zipfile.ZipFile(ioobj, "r") as zf:
filenames = zf.namelist()
for fn in filenames:
if binpath in fn:
with open(destpath, 'wb') as f:
f.write(zf.read(fn))
break
elif archtype == 'rpm':
with rpmfile.RPMFile(fileobj=ioobj) as rpm:
# Extract a fileobject from the archive
fd = rpm.extractfile(binpath)
with open(destpath, 'wb') as f:
f.write(fd.read())
elif archtype == 'gz':
with open(destpath, 'wb') as f:
f.write(gzip.decompress(content))
2022-04-23 16:26:56 +02:00
ioobj.close()
def githubmastersync(reponame, filepaths, credz):
for filepath in filepaths:
2022-04-27 15:25:51 +02:00
localfile = Path('files').joinpath(Path(filepath).name)
print(f" * {localfile} ", end='')
2022-08-09 14:07:02 +02:00
lastsha, dlurl = get_master_info(reponame, filepath, credz)
if not localfile.exists():
2022-08-09 14:07:02 +02:00
r = requests.get(dlurl)
with open(localfile, 'wb') as f:
2022-08-09 14:07:02 +02:00
f.write(r.content)
print('-> Installed! ;)')
else:
sha = compute_file_hash(localfile)
if sha == lastsha:
print('-> Up-to-date.')
else:
2022-08-09 14:07:02 +02:00
r = requests.get(dlurl)
with open(localfile, 'wb') as f:
2022-08-09 14:07:02 +02:00
f.write(r.content)
print('-> Updated!')
def githubreleasesync(reponame, repoinfo, credz):
local_version = repoinfo['local_version']
last_version = get_last_release_info(reponame, credz)
2023-04-05 21:07:48 +02:00
short_version = last_version.replace('v', '')
2023-04-05 21:07:48 +02:00
nobeta_version = last_version.replace('p1-Beta', '') # See https://github.com/PowerShell/Win32-OpenSSH/releases
filenames = repoinfo['files']
2022-04-16 18:48:42 +02:00
for filename in filenames:
if isinstance(filename, dict):
2023-03-30 23:09:58 +02:00
inpath = filename['inpath']
outpath = filename['outpath']
filename = filename['filename']
2023-04-05 21:07:48 +02:00
filename = filename.replace('{last_version}', last_version).replace('{short_version}', short_version).replace('{nobeta_version}', nobeta_version)
2023-03-30 23:09:58 +02:00
localfile = Path('files').joinpath(outpath)
print(f" * {localfile} ", end='')
else:
2023-04-05 21:07:48 +02:00
filename = filename.replace('{last_version}', last_version).replace('{short_version}', short_version).replace('{nobeta_version}', nobeta_version)
2022-04-27 15:25:51 +02:00
localfile = Path('files').joinpath(Path(filename).name)
print(f" * {localfile} ", end='')
2022-05-06 15:43:13 +02:00
if filename.endswith('.gz'):
2023-03-30 23:09:58 +02:00
is_gz, is_zip = True, False
elif filename.endswith('.zip'):
is_gz, is_zip = False, True
2022-05-06 15:43:13 +02:00
else:
2023-03-30 23:09:58 +02:00
is_gz, is_zip = False, False
2022-05-06 15:43:13 +02:00
urldl = f'https://github.com/{reponame}/releases/download/{last_version}/{filename}'
2022-04-16 18:48:42 +02:00
if not localfile.exists():
content = requests.get(urldl, auth=credz).content
if is_gz:
2023-03-30 23:09:58 +02:00
extract_bin('gz', inpath, localfile, content)
elif is_zip:
extract_bin('zip', inpath, localfile, content)
else:
with open(localfile, 'wb') as f:
f.write(content)
2022-04-16 18:48:42 +02:00
print('-> Installed! ;)')
else:
if local_version == last_version:
print('-> Up-to-date.')
else:
content = requests.get(urldl, auth=credz).content
if is_gz:
2023-03-30 23:09:58 +02:00
extract_bin('gz', inpath, localfile, content)
elif is_zip:
extract_bin('zip', inpath, localfile, content)
else:
with open(localfile, 'wb') as f:
f.write(content)
2022-04-23 16:26:56 +02:00
print('-> Updated!')
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
data['githubreleasesync'][reponame]['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
def ncatsync(conf):
r = requests.get('https://nmap.org/dist/')
last_version = r.text.split('The latest Nmap release is version ')[1].split('.\n')[0]
local_version = conf['local_version']
for filename in conf['files']:
2022-04-27 15:25:51 +02:00
localfile = Path('files').joinpath(Path(filename).name)
2022-04-23 16:26:56 +02:00
print(f" * {localfile} ", end='')
if filename == "ncat.exe":
archtype = 'zip'
binpath = 'ncat.exe'
destpath = 'files/ncat.exe'
urldl = f'https://nmap.org/dist/nmap-{last_version}-win32.zip'
elif filename == "ncat":
archtype = 'rpm'
binpath = './usr/bin/ncat'
destpath = 'files/ncat'
urldl = f'https://nmap.org/dist/ncat-{last_version}-1.x86_64.rpm'
if not localfile.exists():
content = requests.get(urldl).content
extract_bin(archtype, binpath, destpath, content)
print('-> Installed! ;)')
else:
if local_version == last_version:
print('-> Up-to-date.')
else:
content = requests.get(urldl).content
extract_bin(archtype, binpath, destpath, content)
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
2022-04-23 16:26:56 +02:00
data['ncat']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
print('-> Updated!')
2022-04-23 16:26:56 +02:00
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
data['ncat']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
2022-04-28 16:23:08 +02:00
def netcatsync(conf):
# https://eternallybored.org/misc/netcat/ seems to be the most stable from all tested. (04/2022)
r = requests.get('https://eternallybored.org/misc/netcat/')
soup = BeautifulSoup(r.content, 'lxml')
versions = []
for link in soup.find_all('a', {'href': re.compile('netcat.+win.+')}):
version = link['href'].split('-')[-1].split('.zip')[0]
versions.append(version)
last_version = max(versions)
local_version = conf['local_version']
for filename in conf['files']:
# nc64.exe is also available in the archive feel free to add it.
localfile = Path('files').joinpath(Path(filename).name)
print(f" * {localfile} ", end='')
archtype = 'zip'
binpath = filename
destpath = f'files/{filename}'
urldl = f'https://eternallybored.org/misc/netcat/netcat-win32-{last_version}.zip'
if not localfile.exists():
content = requests.get(urldl).content
extract_bin(archtype, binpath, destpath, content)
print('-> Installed! ;)')
else:
if local_version == last_version:
print('-> Up-to-date.')
else:
content = requests.get(urldl).content
extract_bin(archtype, binpath, destpath, content)
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
data['netcat']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
print('-> Updated!')
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
data['netcat']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
2023-03-30 23:09:58 +02:00
def plinksync(conf):
r = requests.get('https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html')
last_version = r.text.split('PuTTY.\nCurrently this is ')[1].split(', ')[0]
local_version = conf['local_version']
for filename in conf['files']:
localfile = Path('files').joinpath(Path(filename).name)
print(f" * {localfile} ", end='')
urldl = 'https://the.earth.li/~sgtatham/putty/latest/w32/plink.exe'
if not localfile.exists():
content = requests.get(urldl).content
with open(localfile, 'wb') as f:
f.write(content)
print('-> Installed! ;)')
else:
if local_version == last_version:
print('-> Up-to-date.')
else:
content = requests.get(urldl).content
with open(localfile, 'wb') as f:
f.write(content)
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
data['plink']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
print('-> Updated!')
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
data['plink']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
def update(config):
print("Updating...")
with open("credz.json", "r") as jsonfile:
credz = json.load(jsonfile)
credz = (credz['username'], credz['token'])
for reponame, filepaths in config['githubmastersync'].items():
githubmastersync(reponame, filepaths, credz)
for reponame, repoinfo in config['githubreleasesync'].items():
githubreleasesync(reponame, repoinfo, credz)
2022-04-16 18:48:42 +02:00
2023-03-30 23:09:58 +02:00
# ncatsync(config['ncat'])
2022-04-28 16:23:08 +02:00
netcatsync(config['netcat'])
2023-03-30 23:09:58 +02:00
plinksync(config['plink'])
2022-04-24 03:08:34 +02:00
make_executable()
def make_executable():
2022-04-27 15:25:51 +02:00
path = Path('files')
2022-04-24 03:08:34 +02:00
for fpath in path.glob("*"):
if fpath.name != '.gitkeep':
fpath.chmod(0o777)
2022-04-23 16:26:56 +02:00
2022-04-16 18:48:42 +02:00
2022-04-27 15:25:51 +02:00
def yes_or_no(question, default="yes"):
"""Ask a yes/no question via input() and return their answer.
"question" is a string that is presented to the user.
"default" is the presumed answer if the user just hits <Enter>.
It must be "yes" (the default), "no" or None (meaning
an answer is required of the user).
The "answer" return value is True for "yes" or False for "no".
"""
valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
if default is None:
prompt = " [y/n] "
elif default == "yes":
prompt = " [Y/n] "
elif default == "no":
prompt = " [y/N] "
else:
raise ValueError("invalid default answer: '%s'" % default)
while True:
print(question + prompt)
choice = input().lower()
if default is not None and choice == "":
return valid[default]
elif choice in valid:
return valid[choice]
else:
print("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
def rmtree(root):
for p in root.iterdir():
if p.is_dir():
rmtree(p)
else:
p.unlink()
root.rmdir()
2022-04-16 18:48:42 +02:00
def print_menu(menu_options):
for key in menu_options.keys():
print(key, '->', menu_options[key])
def is_sudo():
return True if geteuid() == 0 else False
def ask_port(default_port):
while True:
try:
choice = input(f'What port would you like to listen on ? [{default_port}]: ')
if choice == '':
port = default_port
else:
port = int(choice)
break
except ValueError:
print('Wrong input. Please enter a number ...')
return port
def listen_http(files_dir):
port = ask_port(80)
ips = get_ips()
2022-04-21 20:54:32 +02:00
print('The HTTP server is about to start.\nA good start ;) :')
2022-04-16 18:48:42 +02:00
for iname in ips:
if port == 80:
2022-04-21 20:54:32 +02:00
print(f' -> {iname}: http://{ips[iname]}/linpeas.sh')
2022-04-16 18:48:42 +02:00
else:
2022-04-21 20:54:32 +02:00
print(f' -> {iname}: http://{ips[iname]}:{port}/linpeas.sh')
2022-04-16 18:48:42 +02:00
2022-04-21 21:08:07 +02:00
cmd = ['python', '-m', 'http.server', '-d', files_dir, str(port)]
2022-04-16 18:48:42 +02:00
if port < 1024 and not is_sudo():
print('Listening on any port under 1024 requires root permissions.')
2022-04-21 21:08:07 +02:00
cmd.insert(0, 'sudo')
2022-04-16 18:48:42 +02:00
subprocess.call(cmd)
2022-04-21 21:08:07 +02:00
def listen_smb(files_dir, version):
2022-04-16 18:48:42 +02:00
port = ask_port(445)
ips = get_ips()
2022-04-21 20:54:32 +02:00
print('The HTTP server is about to start.\nA good start ;) :')
2022-04-16 18:48:42 +02:00
for iname in ips:
if port == 445:
2022-04-21 20:54:32 +02:00
print(f' -> {iname}: \\\\{ips[iname]}\\share\\winPEASany.exe')
2022-04-16 18:48:42 +02:00
else:
2022-04-21 20:54:32 +02:00
print(f' -> {iname}: \\\\{ips[iname]}:{port}\\share\\winPEASany.exe # This syntax (:port) is not supported on Windows ?')
2022-04-23 16:26:56 +02:00
2022-04-21 21:08:07 +02:00
if version == 1:
cmd = ['smbserver.py', '-port', str(port), 'share', files_dir]
elif version == 2:
cmd = ['smbserver.py', '-smb2support', '-port', str(port), 'share', files_dir]
else:
sys.exit('Wrong SMB version')
2022-04-16 18:48:42 +02:00
if port < 1024 and not is_sudo():
print('Listening on any port under 1024 requires root permissions.')
2022-04-21 21:08:07 +02:00
cmd.insert(0, 'sudo')
2022-04-16 18:48:42 +02:00
subprocess.call(cmd)
def get_public_ip():
return requests.get('https://api.ipify.org').text
def get_ips():
ips = dict()
try:
ips['public'] = get_public_ip()
except requests.exceptions.ConnectionError:
pass
cmd = ['ip', 'a']
out = subprocess.run(cmd, capture_output=True).stdout.decode('utf-8')
lines = out.split('\n')
while("" in lines):
lines.remove("")
for line in lines:
if line[0].isdigit():
iname = line.split(' ')[1].split(':')[0]
if 'inet ' in line:
ip = line.split(' ')[5].split('/')[0]
if ip != '127.0.0.1':
ips[iname] = ip
return ips
def menu_choice(menu_options):
while(True):
print_menu(menu_options)
option = ''
try:
option = int(input('Enter your choice: '))
except ValueError:
print('Wrong input. Please enter a number ...')
2022-04-27 15:25:51 +02:00
files_dir = Path.cwd().joinpath('files')
2022-04-16 18:48:42 +02:00
if option == 1:
listen_http(files_dir)
elif option == 2:
2022-04-21 21:08:07 +02:00
listen_smb(files_dir, 1)
elif option == 3:
listen_smb(files_dir, 2)
2022-04-16 18:48:42 +02:00
elif option == 0:
2022-04-28 16:23:08 +02:00
print('Quitting')
sys.exit()
2022-04-16 18:48:42 +02:00
else:
print('Invalid option. Please enter a valid number.')
if __name__ == '__main__':
2022-05-20 12:54:44 +02:00
2022-08-02 19:11:57 +02:00
chdir(Path(__file__).resolve().parent) # Change current dir to source location
2022-05-20 12:54:44 +02:00
parser = argparse.ArgumentParser(description='Sync your files and starts a listener on HTTP, SMB or SMB2.')
parser.add_argument('-u', '--update', action='store_true', help='update your files (described in config.json)')
args = parser.parse_args()
with open("config.json", "r") as jsonfile:
config = json.load(jsonfile)
2022-05-20 12:54:44 +02:00
if args.update:
update(config)
2022-04-27 15:25:51 +02:00
tmp = Path('files/tmp')
is_empty = not any(tmp.iterdir())
if not is_empty:
if yes_or_no("The folder 'files/tmp' is not empty. Would you like to remove the content ?", default="no"):
2022-04-27 18:24:25 +02:00
for p in tmp.iterdir():
if p.is_dir():
rmtree(p)
else:
p.unlink()
2022-04-23 16:26:56 +02:00
print('Choose a service to start a listener:')
2022-04-16 18:48:42 +02:00
menu_options = {
1: 'HTTP',
2022-04-21 21:08:07 +02:00
2: 'SMB1',
3: 'SMB2',
2022-04-16 18:48:42 +02:00
0: 'Exit',
}
menu_choice(menu_options)