Added rdpFileUpload.py

This commit is contained in:
Mariusz B. / mgeeky 2020-05-14 12:09:50 +02:00
parent 3c5b7f3092
commit f072ae211a
2 changed files with 513 additions and 0 deletions

View File

@ -24,6 +24,68 @@
- **`pth-carpet.py`** - Pass-The-Hash Carpet Bombing utility - trying every provided hash against every specified machine. ([gist](https://gist.github.com/mgeeky/3018bf3643f80798bde75c17571a38a9)) - **`pth-carpet.py`** - Pass-The-Hash Carpet Bombing utility - trying every provided hash against every specified machine. ([gist](https://gist.github.com/mgeeky/3018bf3643f80798bde75c17571a38a9))
- **`rdpFileUpload.py`** - RDP file upload utility via Keyboard emulation. Uploads specified input file or directory, encodes it and retypes encoded contents by emulating keyboard keypresses into previously focused RDP session window. That will effectively transmit contents of the file onto the remote host without use of any sort of built-in file upload functionality. Remote desktop protocols such as RDP/VNC could be abused in this way by smuggling to the connected host implant files, etc. In case a directory was specified on input, will recursively add every file from that directory and create a Zip archive that will be later uploaded. Average transfer bandwidths largely depend on your connectivity performance and system utilization.
I've experienced following:
* transfer to the Citrix Receiver RDP session: `40-60 bytes/s`
* transfer to LAN RDP session RDP session: `400-800 bytes/s`
Sample usage:
```
PS> python3 rdpFileUpload.py -v -f certutil README.md
:: RDP file upload utility via Keyboard emulation.
Takes an input file/folder and retypes it into focused RDP session window.
That effectively uploads the file into remote host over a RDP channel.
Mariusz B. / mgeeky '20, (@mariuszbit)
<mb@binary-offensive.com>
[+] Will upload file's contents: "README.md"
[+] MD5 checksum of file to be uploaded: 442949e7bef67384161b511c2dd3e6bb
[+] MD5 checksum of encoded data to be retyped: 667fee7e6528bbd07075e2e54f7fee69
[.] Size of input file: 4993 - keys to retype: 6926
[*] Inter-key press interval: 5 miliseconds.
[*] Every chunk cooldown delay: 0.5 miliseconds.
[*]
================================================================
A) How to proceed now:
1) In your RDP session, spawn a text editor (notepad, vim)
2) Click inside of a text area as you were about to write something.
3) Leave your mouse cursor in that RDP session window (client) having that window focused
[.] Do not use your mouse/keyboard until file upload is completed!
[+] We're about to initiate upload process.
[.] Waiting 10 seconds before we begin...
[+] Starting file retype/upload...
[*] Mouse position of assumed RDP session window: Point(x=2422, y=1142)
100%|███████████████████████████████████████████████████████████████████| 6926/6926 [01:07<00:00, 45.52characters/s]
[+] FILE UPLOADED.
[*]
================================================================
B) After file was uploaded, next steps are:
*) Using your text editor: save the file in a remote system as "README.md.b64"
*) Verify MD5 sum of retyped file to base value 667fee7e6528bbd07075e2e54f7fee69:
$ md5sum README.md.b64
or
PS> Get-FileHash .\README.md.b64 -Algorithm MD5
*) Base64 decode file using certutil:
cmd> certutil -decode README.md.b64 README.md
*) Verify MD5 sum of final form of uploaded file to expected original value 442949e7bef67384161b511c2dd3e6bb:
$ md5sum README.md
or
PS> Get-FileHash .\README.md -Algorithm MD5
```
- **`revshell.c`** - Utterly simple reverse-shell, ready to be compiled by `mingw-w64` on Kali. No security features attached, completely not OPSEC-safe. - **`revshell.c`** - Utterly simple reverse-shell, ready to be compiled by `mingw-w64` on Kali. No security features attached, completely not OPSEC-safe.
- **`Simulate-DNSTunnel.ps1`** - Performs DNS Tunnelling simulation for purpose of triggering installed Network IPS and IDS systems, generating SIEM offenses and picking up Blue Teams. - **`Simulate-DNSTunnel.ps1`** - Performs DNS Tunnelling simulation for purpose of triggering installed Network IPS and IDS systems, generating SIEM offenses and picking up Blue Teams.

451
windows/rdpFileUpload.py Normal file
View File

@ -0,0 +1,451 @@
#
# RDP file upload utility via Keyboard emulation.
# Uploads specified input file or directory, encodes it and retypes encoded contents
# by emulating keyboard keypresses into previously focused RDP session window.
# That will effectively transmit contents of the file onto the remote host without use
# of any sort of built-in file upload functionality. Remote desktop protocols such as
# RDP/VNC could be abused in this way by smuggling to the connected host implant files, etc.
#
# In case a directory was specified on input, will recursively add every file from that directory
# and create a Zip archive that will be later uploaded.
#
# Mouse movements will suspend file upload process.
#
# Average transfer bandwidths largely depend on your connectivity performance and system utilization.
# I've experienced following:
# - transfer to the Citrix Receiver RDP session: 40-60 bytes/s
# - transfer to LAN RDP session RDP session: 400-800 bytes/s
#
# Requirements:
# - pyautogui
# - tqdm
#
# Author:
# Mariusz B. / mgeeky (@mariuszbit), '20
# <mb [at] binary-offensive.com>
#
import os
import sys
import time
import hashlib
import base64
import zipfile
import argparse
from io import BytesIO
try:
import pyautogui
except ImportError:
print('[!] Module "pyautogui" not found. Install it using: pip3 install pyautogui')
sys.exit(1)
try:
import tqdm
except ImportError:
print('[!] Module "tqdm" not found. Install it using: pip3 install tqdm')
sys.exit(1)
config = {
'debug': True,
'verbose': True,
'wait' : 10,
'interval' : 5,
'delay' : 0.5,
'base64' : 10,
'zip' : 10,
'chunk' : 256,
# Specifies what's the maximum mouse cursor position offset deviation (in pixels)
# that will be tolerated and won't interrupt file upload/retype loop.
# If mouse cursor will leave the N x N rectangle, we'll stop uploading/retyping.
'maxMouseDeviationInPixels' : 50,
'file' : '',
'format': '',
}
outputFormats = {
'raw',
'certutil',
}
fileWasEncoded = False
progressBar = None
class Logger:
@staticmethod
def _out(x):
sys.stdout.write(x + '\n')
@staticmethod
def verbose(x):
if config['verbose']:
Logger._out('[*] ' + x)
@staticmethod
def info(x):
Logger._out('[.] ' + x)
@staticmethod
def dbg(x):
if config['debug']:
Logger._out('[dbg] ' + x)
@staticmethod
def err(x):
sys.stdout.write('[!] ' + x + '\n')
@staticmethod
def fail(x):
Logger._out('[-] ' + x)
@staticmethod
def ok(x):
Logger._out('[+] ' + x)
class InMemoryZip(object):
# Source:
# - https://www.kompato.com/post/43805938842/in-memory-zip-in-python
# - https://stackoverflow.com/a/2463818
def __init__(self):
self.in_memory_zip = BytesIO()
def append(self, filename_in_zip, file_contents):
zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False)
zf.writestr(filename_in_zip, file_contents)
for zfile in zf.filelist:
zfile.create_system = 0
return self
def read(self):
self.in_memory_zip.seek(0)
return self.in_memory_zip.read()
def fetch_files(rootdir):
imz = InMemoryZip()
for folder, subs, files in os.walk(rootdir):
for filename in files:
real_path = os.path.join(folder, filename)
with open(real_path, 'rb') as src:
zip_path = real_path.replace(rootdir + '/', '')
imz.append(zip_path, src.read())
return imz.read()
def reformatOutput(contents):
out = contents
if config['format'] == 'certutil':
out = b'-----BEGIN CERTIFICATE-----\r\n'
for chunk in [contents[i:i + 64] for i in range(0, len(contents), 64)]:
out += chunk + b'\r\n'
out += b'-----END CERTIFICATE-----\r\n'
try:
Logger.dbg(f'''After reformatting output, data looks as follows:
----------------------------------------------------------------
{out}
----------------------------------------------------------------
''')
except: pass
return out
def checkChar(a):
if a >= 0x20 or a <= 0x7f:
return True
if a in [0x0a, 0x0d, 9]:
return True
return False
def encodeFile(filePath, contents, dontZip):
global fileWasEncoded
encoded = contents
if config['zip'] and not dontZip:
imz = InMemoryZip()
imz.append(os.path.basename(filePath), contents)
encoded = imz.read()
if config['base64'] or config['format'] == 'certutil':
encoded = base64.b64encode(encoded)
try:
Logger.dbg(f'''After encoding data:
----------------------------------------------------------------
{encoded}
----------------------------------------------------------------
''')
except: pass
out = reformatOutput(encoded)
for a in out:
if not checkChar(a):
Logger.err(f'After encoding file/directory contents we resulted with binary data ({a}). That\'s not supported.')
return None
fileWasEncoded = contents != out
return out.decode('utf-8', 'ignore')
def splitFile(contents, chunkSize):
for i in range(0, len(contents), chunkSize):
yield contents[i:i+chunkSize]
def flush():
time.sleep(2)
sys.stdout.flush()
sys.stderr.flush()
def progress(pos, total):
t = tqdm.tqdm(total=total, unit='characters')
if pos > 0: t.update(pos)
return t
def retypeFile(contents):
global progressBar
retypeMousePos = pyautogui.position()
pyautogui.click()
Logger.verbose(f'Mouse position of assumed RDP session window: {retypeMousePos}')
Logger._out('')
progressBar = progress(0, len(contents))
for chunk in splitFile(contents, config['chunk']):
prevPos = pyautogui.position()
if abs(prevPos.x - retypeMousePos.x) > config['maxMouseDeviationInPixels'] or \
abs(prevPos.y - retypeMousePos.y) > config['maxMouseDeviationInPixels']:
msg = "[?] Mouse cursor was moved away from initially focused position. File upload PAUSED.\n"
msg += " Press ENTER to resume upload or Ctrl-C to interrupt.\n"
progressBar.write(msg)
input('')
progressBar.clear()
progressBar.write(f"\n[?] Position your mouse cursor at the end of written text. Waiting {config['wait']} seconds to resume...\n")
if config['wait'] > 0: time.sleep(config['wait'])
retypeMousePos = pyautogui.position()
pyautogui.click()
progressBar.clear()
progressBar.unpause()
progressBar.refresh()
pyautogui.write(chunk, interval = (float(config['interval']) / 1000.0))
progressBar.update(len(chunk))
if config['delay'] > 0: time.sleep(config['delay'])
flush()
Logger._out('')
def printInstructions(hash1, hash2, filePath):
additional = ''
basename = os.path.basename(filePath)
inputFile = basename + '.txt' if fileWasEncoded else basename
output = basename
if config['zip']:
output = basename + '.zip'
if config['format'] == 'certutil':
inputFile = basename + '.b64'
additional = f'''
*) Base64 decode file using certutil:
cmd> certutil -decode {inputFile} {output}
'''
elif config['format'] != 'certutil' and config['base64']:
inputFile = basename + '.b64'
additional = f'''
*) Base64 decode file:
$ cat {inputFile} | base64 -d > {output}
or
cmd> powershell -c "[IO.File]::WriteAllBytes('{output}', [Convert]::FromBase64String([IO.File]::ReadAllText('{inputFile}')))"
'''
if config['zip']:
additional += f'''
*) Unzip resulting file:
$ unzip -d . {output}
or
PS> Expand-Archive -Path .\\{output} -Dest .
'''
if hash1 != hash2:
additional += f'''
*) Verify MD5 sum of final form of uploaded file to expected original value {hash1}:
$ md5sum {output}
or
PS> Get-FileHash .\\{output} -Algorithm MD5
'''
Logger.verbose(f'''
================================================================
B) After file was uploaded, next steps are:
*) Using your text editor: save the file in a remote system as "{inputFile}"
*) Verify MD5 sum of retyped file to base value {hash2}:
$ md5sum {inputFile}
or
PS> Get-FileHash .\\{inputFile} -Algorithm MD5
{additional}
''')
def parseOptions(argv):
global config
print('''
:: RDP file upload utility via Keyboard emulation.
Takes an input file/folder and retypes it into focused RDP session window.
That effectively uploads the file into remote host over a RDP channel.
Mariusz B. / mgeeky '20, (@mariuszbit)
<mb@binary-offensive.com>
''')
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] <inputFile>')
parser.add_argument('inputFile', help='Input file or directory to upload. In case of directory - all files will get zipped recursively and resulting zip file will be uploaded.')
parser.add_argument('-v', '--verbose', action='store_true', help='Displays verbose output containing field steps to follow.')
parser.add_argument('-D', '--debug', action='store_true', help='Display debug output.')
parser.add_argument('-f', '--format', choices=outputFormats, default='raw', help=f'Specifies into which format retype input file. Default: retype the file as is. Other formats: {outputFormats} . "certutil" format implies --base64 .')
timing = parser.add_argument_group('Timing & Performance', 'Adjusts settings impacting program\'s "upload" efficiency')
timing.add_argument('-w', '--wait', type=int, default=config['wait'], help=f'Hold on before we start retyping file contents for this long (in seconds). Default: {config["wait"]} seconds.')
timing.add_argument('-i', '--interval', type=int, default=config['interval'], help=f'Adjusts inter-key press interval (in milliseconds). Default: {config["interval"]} miliseconds.')
timing.add_argument('-d', '--delay', type=int, default=config['delay'], help=f'Every next chunk (of size {config["chunk"]} bytes) wait this amount of time. Default: {config["delay"]} miliseconds.')
encoding = parser.add_argument_group('Encoding', 'Controls how to encode the file before retyping it')
encoding.add_argument('-b', '--base64', action='store_true', help='Encode and then retype base64 encoded file contents.')
encoding.add_argument('-z', '--zip', action='store_true', help='Zip file contents before retyping them. If used with --base64 will retype results of base64(zip(file))')
args = parser.parse_args()
if not hasattr(args, 'inputFile') or args.inputFile is None:
parser.print_help()
return None
config['verbose'] = args.verbose
config['debug'] = args.debug
if args.debug: config['verbose'] = True
config['interval'] = args.interval
config['format'] = args.format
config['wait'] = args.wait
config['zip'] = args.zip
config['base64'] = args.base64
if args.interval < 5:
Logger.fail('WARNING: Setting too low inter-key press interval may result in keys being lost in the transit!')
Logger.fail(' Be sure to verify uploaded file\'s md5 checksum!\n')
return args
def main(argv):
opts = parseOptions(argv)
if not opts:
return False
contents = None
dontZip = False
t = 'file'
try:
if os.path.isfile(opts.inputFile):
with open(opts.inputFile, 'rb') as f:
contents = f.read()
elif os.path.isdir(opts.inputFile):
contents = fetch_files(rootdir)
t = 'directory'
dontZip = True
else:
Logger.err("Specified input file is neither a file nor directory (or it doesn't exist)!")
return False
except:
Logger.err(f'Could not open file for reading: "{opts.inputFile}"')
return False
if contents == None or len(contents) == 0:
Logger.fail("Specified file/directory was empty.")
return False
encoded = encodeFile(opts.inputFile, contents, dontZip)
if encoded == None or len(encoded) == 0:
Logger.fail("No encoded data to upload.")
return False
Logger.ok(f'Will upload {t}\'s contents: "{opts.inputFile}"\n')
hash1 = hashlib.md5(contents).hexdigest()
hash2 = hashlib.md5(encoded.encode()).hexdigest()
Logger.ok('MD5 checksum of file to be uploaded: ' + hash1)
Logger.ok('MD5 checksum of encoded data to be retyped: ' + hash2)
Logger.info(f'Size of input {t}: {len(contents)} - keys to retype: {len(encoded)}')
Logger.verbose(f'Inter-key press interval: {opts.interval} miliseconds.')
Logger.verbose(f'Every chunk cooldown delay: {opts.delay} miliseconds.')
del contents
Logger.verbose('''
================================================================
A) How to proceed now:
1) In your RDP session, spawn a text editor (notepad, vim)
2) Click inside of a text area as you were about to write something.
3) Leave your mouse cursor in that RDP session window (client) having that window focused
''')
Logger.info('Do not use your mouse/keyboard until file upload is completed!\n')
Logger.ok('We\'re about to initiate upload process.')
try:
Logger.info(f'Waiting {config["wait"]} seconds before we begin...\n')
time.sleep(opts.wait)
Logger.ok('Starting file retype/upload...')
retypeFile(encoded)
Logger._out('')
Logger.ok("FILE UPLOADED.")
printInstructions(hash1, hash2, opts.inputFile)
except KeyboardInterrupt:
progressBar.clear()
flush()
Logger._out('')
Logger.fail("FILE WAS NOT FULLY UPLOADED. User has interrupted file retype/upload process!\n")
return False
flush()
progressBar.clear()
return True
if __name__ == '__main__':
main(sys.argv)