Python 3 – Defeat your Email Filter by Encoding Files in base64

I’m sure everyone has came across this problem at least once; you try to send a colleague or a friend a file over email, only for it to be stripped by the filter. What’s most annoying is that not all email clients tell you that this has happened.

A while ago a colleague of mine wrote something like this in Perl, but rather than dig out or recreate that file I decided to do much the same thing in Python 3.

What this script does is read a file into memory and convert it to base 64. Then it stores this base 64 content as a string alongside the original file name. The output is a new Python script that can be used to retrieve the original file by simply running it. As it stands, this script requires Python 3 to be installed on both the source and destination, but it would be fairly straight forward to create a binary so Python is only required on the destination.

#!/usr/bin/python3
"""
SYNOPSIS

    encfile [-h, --help] [-v, --verbose] [-f <FILENAME>, --file <FILENAME>]

DESCRIPTION

    Requires: Python 3

    Many email filters block certain types of files, such as .exe. This
    is an important protection mechanism but can often cause issues when
    sending legitimate files over email. This script converts a file to
    base64 and embeds it in a new python script, so the original file can 
    be retrieved by simply having python 3 installed on the destination.

    Note: Testing with a 2.5mb .exe file it parsed this in less than 1 
    second. Testing with a 80mb zip file produces the base64 file in a 
    few seconds, but takes approximately 10 minutes to restore the 
    original .zip on a 7700K, utilising approx 200MB of RAM and 15% CPU.

    You can rename the output python script to anything you want. It will 
    restore the original file name when executed.

EXAMPLES

    Create a new python script <filename.zip.py> with the original 
    file embedded

    # Convert a file to base64
    > python encfile.py -f command.exe

    Output: command.exe.py

    You can now send command.exe.py, or optionally zip it first.

    # Recover the file
    > python command.exe.py

    Output: command.exe

AUTHOR

    Relaki <relaki@surmise.it>

LICENSE

    This script is in the public domain,
    free from copyrights or restrictions.

VERSION

    1.0
"""

import base64
import os
import sys
import traceback
import argparse
import time


contents = """#!/usr/bin/python3
import base64
import os
# start data
name = "{}"
data = "{}"
# end data
if __name__ == '__main__':
    cwd = os.path.dirname(os.path.realpath(__file__))
    org_path = os.path.join(cwd, name)
    fh = open(org_path, 'wb')
    fh.write(base64.b64decode(data[2:-1]))
    fh.flush()
    os.fsync(fh.fileno())
    fh.close()
"""


def read_file(path):
    if not os.path.isfile(path):
        print("File: {} not found.".format(path))
        return

    fh = open(path, 'rb')
    data = fh.read()
    fh.close()

    return base64.b64encode(data)


def write_file(path, data, mode):
    fh = open(path, mode)
    fh.write(data)
    fh.flush()
    os.fsync(fh.fileno())
    fh.close()


def main():
    global contents
    cwd = os.path.dirname(os.path.realpath(__file__))
    org_path = os.path.join(cwd, args.file[0])
    out_path = os.path.join(cwd, args.file[0] + ".py")
        
    b64data = read_file(org_path)
    data = contents.format(args.file[0], b64data)
    
    write_file(out_path, data.encode("utf-8"), "wb")


if __name__ == '__main__':
    try:
        start_time = time.time()

        parser = argparse.ArgumentParser()
        parser.add_argument("-v", "--verbose",
                            help="increase output verbosity",
                            action="store_true")
        parser.add_argument("-f", "--file", nargs=1, metavar='<FILENAME>',
                            help="The file to encode in base64", type=str,
                            required=True)
        args = parser.parse_args()

        if args.verbose:
            print("Start:" + time.asctime())
        main()
        if args.verbose:
            print("End:" + time.asctime())
        if args.verbose:
            print("Run(Min):" + str((time.time() - start_time) / 60.0))
        sys.exit(0)
    except KeyboardInterrupt as e:
        raise e
    except SystemExit as e:
        raise e
    except Exception as e:
        print("ERROR: UNEXPECTED EXCEPTION")
        print(str(e))
        traceback.print_exc()
        sys.exit(1)

Leave a Reply

Your email address will not be published. Required fields are marked *