Compare commits

...

20 Commits

Author SHA1 Message Date
91b5ce8f28 Add NimScan 2023-04-24 00:50:58 +02:00
eefc1e7aa1 Update 2023-04-05 21:07:48 +02:00
6a5e28dc1c Add plink, allow mimikatz 2023-03-30 23:09:58 +02:00
b9790c7d9f Fix download method 2022-08-09 14:07:02 +02:00
89450cd2fe Update README 2022-08-02 19:11:57 +02:00
926676eb1b Add P0wnyShell 2022-06-08 14:28:49 +02:00
18eaa61dda Update README 2022-05-20 12:58:03 +02:00
5144bf6c79 Add: --update --help 2022-05-20 12:54:44 +02:00
bd5d7a2647 Fix: is_gz 2022-05-06 15:43:13 +02:00
e8eb9803d8 Switch for x86 chisel versions 2022-04-28 17:08:17 +02:00
3c92425126 Replace nc.exe for a better one 2022-04-28 16:23:08 +02:00
0e3d6c171e Fix tmp cleaning 2022-04-27 18:24:25 +02:00
9c090991c0 Cleaning tmp 2022-04-27 15:25:51 +02:00
46b68a17b8 Add files du config 2022-04-27 15:25:19 +02:00
e235f7a72b Support last_version and short_version paceholders in github releases 2022-04-26 12:53:44 +02:00
78bebf19d5 Support extract bin from gz. (github releases) 2022-04-26 10:22:36 +02:00
36c46920ec Make files executable 2022-04-24 03:08:34 +02:00
66b9959b68 Add ncat 2022-04-23 16:26:56 +02:00
a910f30889 Add SMB2 support 2022-04-21 21:08:07 +02:00
5c44f7c770 Default print PEAS files 2022-04-21 20:54:32 +02:00
3 changed files with 464 additions and 40 deletions

View File

@ -6,14 +6,20 @@ This too has several functions:
## To-Do
* Only update once per day, except if `-u` `--update`
* Add chisel x64
* Integrate msfvenom
* Add Webshells sync
* Ability to add comments or description
* Prettier code
* Adding more services to listen to ?
## Dependencies
## Pre-requisites
* requests python module
* rpmfile python module
* impacket smbserver.py must be in PATH
## Usage
[Create a Github personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), and create the file `credz.json`:
@ -25,9 +31,23 @@ This too has several functions:
}
```
`python pendora-box.py`
```
user@wow$ python pendora-box.py -h
usage: pendora-box.py [-h] [-u]
Sync your files and starts a listener on HTTP, SMB or SMB2.
options:
-h, --help show this help message and exit
-u, --update update your files (described in config.json)
```
## Adding a file to track
Simply add the informations to [config.json](./config.json), for a release set "local_version" to a random value and run the script.
The file is gonna be automatically downloaded.
## Share a file temporarly
You can add a file you wish to share once in `files/tmp`.
Every time you start the script, you will be asked if you want to clear the folder (if not empty).

View File

@ -14,17 +14,131 @@
"churrasco.exe"
],
"SecWiki/windows-kernel-exploits": [
"MS11-046/ms11-046.exe"
"MS11-046/ms11-046.exe",
"MS11-046/MS11_46_k8.exe",
"MS10-059/MS10-059.exe"
],
"flozz/p0wny-shell": [
"shell.php"
],
"int0x33/nc.exe": [
"nc64.exe"
],
"BloodHoundAD/BloodHound": [
"Collectors/SharpHound.exe"
],
"PowerShellMafia/PowerSploit": [
"Recon/PowerView.ps1"
],
"Flangvik/SharpCollection": [
"NetFramework_4.7_Any/Rubeus.exe"
],
"calebstewart/CVE-2021-1675": [
"CVE-2021-1675.ps1"
],
"besimorhino/powercat": [
"powercat.ps1"
],
"r3motecontrol/Ghostpack-CompiledBinaries": [
"Seatbelt.exe"
]
},
"githubreleasesync": {
"carlospolop/PEASS-ng": {
"local_version": "20220417",
"local_version": "20230419-b6aac9cb",
"files": [
"linpeas.sh",
"winPEAS.bat",
"winPEASany.exe"
"winPEASany.exe",
"winPEASany_ofs.exe"
]
},
"AlessandroZ/LaZagne": {
"local_version": "v2.4.5",
"files": [
"lazagne.exe"
]
},
"DominicBreuker/pspy": {
"local_version": "v1.2.1",
"files": [
"pspy32",
"pspy64"
]
},
"itm4n/PrintSpoofer": {
"local_version": "v1.0",
"files": [
"PrintSpoofer32.exe",
"PrintSpoofer64.exe"
]
},
"PowerShell/Win32-OpenSSH": {
"local_version": "v9.2.2.0p1-Beta",
"files": [
"OpenSSH-Win32-{nobeta_version}.msi",
"OpenSSH-Win64-{nobeta_version}.msi"
]
},
"elddy/NimScan": {
"local_version": "1.0.8",
"files": [
"NimScan.exe"
]
},
"jpillora/chisel": {
"local_version": "v1.8.1",
"files": [
{
"filename": "chisel_{short_version}_windows_386.gz",
"inpath": "chisel.exe",
"outpath": "chisel.exe"
},
{
"filename": "chisel_{short_version}_linux_386.gz",
"inpath": "chisel",
"outpath": "chisel"
},
{
"filename": "chisel_{short_version}_linux_amd64.gz",
"inpath": "chisel64",
"outpath": "chisel64"
}
]
},
"gentilkiwi/mimikatz": {
"local_version": "2.2.0-20220919",
"files": [
{
"filename": "mimikatz_trunk.zip",
"inpath": "Win32/mimikatz.exe",
"outpath": "mimikatz32.exe"
},
{
"filename": "mimikatz_trunk.zip",
"inpath": "x64/mimikatz.exe",
"outpath": "mimikatz64.exe"
}
]
}
},
"ncat": {
"local_version": "7.92",
"files": [
"ncat.exe",
"ncat"
]
},
"netcat": {
"local_version": "1.12",
"files": [
"nc.exe"
]
},
"plink": {
"local_version": "0.78",
"files": [
"w32/plink.exe"
]
}
}
}

350
pendora-box.py Normal file → Executable file
View File

@ -1,11 +1,19 @@
#!/usr/bin/env python3
import argparse
import json
import pathlib
from pathlib import Path
import hashlib
import requests
import base64
import sys
from os import geteuid
from os import geteuid, chdir
import subprocess
from io import BytesIO
import zipfile
import rpmfile
import gzip
from bs4 import BeautifulSoup
import re
def compute_file_hash(filepath):
@ -20,8 +28,8 @@ def get_master_info(repo, filepath, credz):
url = f"https://api.github.com/repos/{repo}/contents/{filepath}"
r = requests.get(url, auth=credz)
sha = r.json()['sha']
content = r.json()['content']
return sha, content
dlurl = r.json()['download_url']
return sha, dlurl
def get_last_release_info(repo, credz):
@ -32,16 +40,40 @@ def get_last_release_info(repo, credz):
return release['tag_name']
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))
ioobj.close()
def githubmastersync(reponame, filepaths, credz):
for filepath in filepaths:
localfile = pathlib.Path('files').joinpath(pathlib.Path(filepath).name)
localfile = Path('files').joinpath(Path(filepath).name)
print(f" * {localfile} ", end='')
lastsha, content = get_master_info(reponame, filepath, credz)
lastsha, dlurl = get_master_info(reponame, filepath, credz)
if not localfile.exists():
content = base64.b64decode(content)
r = requests.get(dlurl)
with open(localfile, 'wb') as f:
f.write(content)
f.write(r.content)
print('-> Installed! ;)')
else:
@ -50,9 +82,9 @@ def githubmastersync(reponame, filepaths, credz):
if sha == lastsha:
print('-> Up-to-date.')
else:
content = base64.b64decode(content)
r = requests.get(dlurl)
with open(localfile, 'wb') as f:
f.write(content)
f.write(r.content)
print('-> Updated!')
@ -60,15 +92,41 @@ def githubreleasesync(reponame, repoinfo, credz):
local_version = repoinfo['local_version']
last_version = get_last_release_info(reponame, credz)
short_version = last_version.replace('v', '')
nobeta_version = last_version.replace('p1-Beta', '') # See https://github.com/PowerShell/Win32-OpenSSH/releases
filenames = repoinfo['files']
for filename in filenames:
localfile = pathlib.Path('files').joinpath(pathlib.Path(filename).name)
urldl = f'https://github.com/{reponame}/releases/download/{last_version}/{filename}'
if isinstance(filename, dict):
inpath = filename['inpath']
outpath = filename['outpath']
filename = filename['filename']
filename = filename.replace('{last_version}', last_version).replace('{short_version}', short_version).replace('{nobeta_version}', nobeta_version)
localfile = Path('files').joinpath(outpath)
print(f" * {localfile} ", end='')
else:
filename = filename.replace('{last_version}', last_version).replace('{short_version}', short_version).replace('{nobeta_version}', nobeta_version)
localfile = Path('files').joinpath(Path(filename).name)
print(f" * {localfile} ", end='')
if filename.endswith('.gz'):
is_gz, is_zip = True, False
elif filename.endswith('.zip'):
is_gz, is_zip = False, True
else:
is_gz, is_zip = False, False
urldl = f'https://github.com/{reponame}/releases/download/{last_version}/{filename}'
if not localfile.exists():
content = requests.get(urldl, auth=credz).content
if is_gz:
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)
@ -79,8 +137,14 @@ def githubreleasesync(reponame, repoinfo, credz):
else:
content = requests.get(urldl, auth=credz).content
if is_gz:
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)
print('-> Updated!')
with open("config.json", "r") as jsonfile:
data = json.load(jsonfile)
@ -90,8 +154,155 @@ def githubreleasesync(reponame, repoinfo, credz):
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']:
localfile = Path('files').joinpath(Path(filename).name)
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)
data['ncat']['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['ncat']['local_version'] = last_version
with open("config.json", "w") as jsonfile:
json.dump(data, jsonfile, indent=4)
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)
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...")
@ -105,6 +316,58 @@ def update(config):
for reponame, repoinfo in config['githubreleasesync'].items():
githubreleasesync(reponame, repoinfo, credz)
# ncatsync(config['ncat'])
netcatsync(config['netcat'])
plinksync(config['plink'])
make_executable()
def make_executable():
path = Path('files')
for fpath in path.glob("*"):
if fpath.name != '.gitkeep':
fpath.chmod(0o777)
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()
def print_menu(menu_options):
for key in menu_options.keys():
@ -133,37 +396,42 @@ def listen_http(files_dir):
port = ask_port(80)
ips = get_ips()
print('The HTTP server is about to start.\nAvailable endpoints:')
print('The HTTP server is about to start.\nA good start ;) :')
for iname in ips:
if port == 80:
print(f' -> {iname}: http://{ips[iname]}/')
print(f' -> {iname}: http://{ips[iname]}/linpeas.sh')
else:
print(f' -> {iname}: http://{ips[iname]}:{port}/')
print(f' -> {iname}: http://{ips[iname]}:{port}/linpeas.sh')
cmd = ['python', '-m', 'http.server', '-d', files_dir, str(port)]
if port < 1024 and not is_sudo():
print('Listening on any port under 1024 requires root permissions.')
cmd = ['sudo', 'python', '-m', 'http.server', '-d', files_dir, str(port)]
else:
cmd = ['python', '-m', 'http.server', '-d', files_dir, str(port)]
cmd.insert(0, 'sudo')
subprocess.call(cmd)
def listen_smb(files_dir):
def listen_smb(files_dir, version):
port = ask_port(445)
ips = get_ips()
print('The HTTP server is about to start.\nAvailable endpoints:')
print('The HTTP server is about to start.\nA good start ;) :')
for iname in ips:
if port == 445:
print(f' -> {iname}: \\\\{ips[iname]}\\share\\')
print(f' -> {iname}: \\\\{ips[iname]}\\share\\winPEASany.exe')
else:
print(f' -> {iname}: \\\\{ips[iname]}:{port}\\share\\ # This syntax (:port) is not supported on Windows ?')
print(f' -> {iname}: \\\\{ips[iname]}:{port}\\share\\winPEASany.exe # This syntax (:port) is not supported on Windows ?')
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')
if port < 1024 and not is_sudo():
print('Listening on any port under 1024 requires root permissions.')
cmd = ['sudo', 'smbserver.py', '-port', str(port), 'share', files_dir]
else:
cmd = ['smbserver.py', '-port', str(port), 'share', files_dir]
cmd.insert(0, 'sudo')
subprocess.call(cmd)
@ -204,27 +472,49 @@ def menu_choice(menu_options):
except ValueError:
print('Wrong input. Please enter a number ...')
files_dir = pathlib.Path.cwd().joinpath('files')
files_dir = Path.cwd().joinpath('files')
if option == 1:
listen_http(files_dir)
elif option == 2:
listen_smb(files_dir)
listen_smb(files_dir, 1)
elif option == 3:
listen_smb(files_dir, 2)
elif option == 0:
sys.exit('Quitting')
print('Quitting')
sys.exit()
else:
print('Invalid option. Please enter a valid number.')
if __name__ == '__main__':
chdir(Path(__file__).resolve().parent) # Change current dir to source location
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)
if args.update:
update(config)
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"):
for p in tmp.iterdir():
if p.is_dir():
rmtree(p)
else:
p.unlink()
print('Choose a service to start a listener:')
menu_options = {
1: 'HTTP',
2: 'SMB',
2: 'SMB1',
3: 'SMB2',
0: 'Exit',
}
menu_choice(menu_options)