From d827c6a98c907c3ef068417daf62f685886d8a3d Mon Sep 17 00:00:00 2001 From: g0tmi1k Date: Mon, 25 Jan 2016 22:00:54 +0000 Subject: [PATCH] v1.3: Remove DEBUG.exe 64k limitation, Compression option & Bug fixes --- README.md | 85 ++++++++++++--------- exe2hex.py | 212 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 200 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 6fac303..873ec53 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,56 @@ # exe2hex -Inline file transfer method using `debug.exe` and/or PowerShell. +Inline file transfer using in-built Windows tools (`debug.exe` or PowerShell). - - - ### Overview +exe2hex encodes an executable binary file into ASCII text format. -Encodes a executable binary file into ASCII text format. +The result then can be transferred to the target machine (It is much easier to echo a ASCII file than binary data). -Restores using `DEBUG.exe` (BATch - x86) and/or PowerShell (PoSh - x86/x64). +Upon executing exe2hex's output file, the original program is restored by using `DEBUG.exe` or PowerShell (which are pre-installed by default). -```Binary EXE -> ASCII text -> Binary EXE``` +```Binary EXE -> ASCII Text -> *Transfer* -> Binary EXE``` -![](https://i.imgur.com/kMcqHNq.png) +![](https://i.imgur.com/UJjgq7q.png) - - - -### Quick usage +### Quick Guide - + Input with a file (`-x /path/to/binary.exe`) or STDIN (`-s`) - + Output to BAT (`-b /path/to/debug.bat`) and/or PoSH (`-p powershell.cmd`) + + Input using a file (`-x /path/to/binary-program.exe`) or STDIN (`-s`) + + Output to BATch (`-b file.bat`) and/or PoSH (`-p powershell.cmd`) #### Example Usage ```bash $ python3 exe2hex.py -x /usr/share/windows-binaries/sbd.exe -[*] exe2hex v1.2 +[*] exe2hex v1.3 [i] Outputting to /root/sbd.bat (BATch) and /root/sbd.cmd (PoSh) -[+] Successfully wrote (BAT): /root/sbd.bat -[+] Successfully wrote (PoSh): /root/sbd.cmd +[+] Successfully wrote (BATch) /root/sbd.bat +[+] Successfully wrote (PoSh) /root/sbd.cmd $ ``` ```bash -$ ./exe2hex.py -x /usr/share/windows-binaries/nc.exe -b /var/www/html/nc.txt -[*] exe2hex v1.2 -[+] Successfully wrote (BAT): /var/www/html/nc.txt +$ ./exe2hex.py -x /usr/share/windows-binaries/nc.exe -b /var/www/html/nc.txt -cc +[*] exe2hex v1.3 +[i] Attempting to clone and compress +[i] Creating temporary file /tmp/tmpkel8b4f0 +[+] Compression (strip) was successful! (0.0% saved) +[+] Compression (UPX) was successful! (50.9% saved) +[+] Successfully wrote (BATch) /var/www/html/nc.txt $ ``` ```bash -$ cat /usr/share/windows-binaries/whoami.exe | python3 exe2hex.py -s -b debug.bat -p ps.cmd -[*] exe2hex v1.2 +$ cat /usr/share/windows-binaries/whoami.exe | python exe2hex.py -s -b debug.bat -p ps.cmd +[*] exe2hex v1.3 [i] Reading from STDIN -[!] ERROR: Input is larger than 65536 bytes (BATch/DEBUG.exe limitation) -[i] Attempting to clone and compress -[i] Creating temporary file /tmp/tmpfypsf9if -[i] Running strip on /tmp/tmpfypsf9if -[+] Compression was successful! -[+] Successfully wrote (BAT): /root/debug.bat -[+] Successfully wrote (PoSh): /root/ps.cmd +[+] Successfully wrote (BATch) /root/debug.bat +[+] Successfully wrote (PoSh) /root/ps.cmd $ ``` @@ -58,7 +58,7 @@ $ ```bash $ python3 exe2hex.py -h -[*] exe2hex v1.2 +[*] exe2hex v1.3 Usage: exe2hex.py [options] Options: @@ -66,12 +66,14 @@ Options: -x EXE The EXE binary file to convert -s Read from STDIN -b BAT BAT output file (DEBUG.exe method - x86) - -p POSH PoSh output file (PowerShell method - x64/x86) + -p POSH PoSh output file (PowerShell method - x86/x64) -e URL encode the output -r TEXT pRefix - text to add before the command on each line -f TEXT suFfix - text to add after the command on each line - -l INT Maximum hex values per line + -l INT Maximum HEX values per line -v Enable verbose mode + -c Clones and compress the file before converting (-cc for higher + compression) $ ``` @@ -80,12 +82,29 @@ $ ### Methods/OS Support + **`DEBUG.exe` (BATch mode - `-b`)** - + Every version of Windows x86 (No x64 support). - + Useful for legacy versions of Windows (e.g. XP/2000). - + Has a limitation of 64k file size for binary files. + + Supports x86 OSs (No x64 support). + + Useful for legacy versions of Windows (e.g. Windows XP/Windows 2000). + + Pre-installed by default. Works out of the box. + + ~~Limitation of 64k file size for binary programs.~~ Creates multiple parts and joins with `copy /b` so this is not an issue any more! + **PowerShell (PoSh mode - `-p`)** - + Supports both Windows x86 & x64. + + Supports both x86 & x64 OSs. + Aimed at more "recent" versions of Windows. - + Powershell was first integrated into core OS with Windows 7/Windows Server 2008 R2. - + Windows XP SP2, Windows Server 2003 & Windows Vista requires PowerShell to be pre-installed. - + This is **not** a `.ps1` file (pure powershell). It only calls powershell at the end to convert. + + PowerShell was first integrated into core OS with Windows 7/Windows Server 2008 R2. + + Windows XP SP2, Windows Server 2003 & Windows Vista requires PowerShell to be pre-installed. + + This is **not** a `.ps1` file (pure PowerShell). It only calls PowerShell at the end to convert. + +- - - + +### Features + +**Primary purpose**: Convert a binary program into a ASCII HEX file which can be restored using in-built OS programs. + ++ Able to use a file or standard input ++ Work on old and new versions of Windows without any 3rd party programs. ++ Supports x86 & x64. ++ Includes a function to compress the file. ++ URL encode the output. ++ Option to add prefix and suffix text to each line. ++ Able to set a maximum HEX length. + +Note: This is nothing new. [The core idea has been around since 2003](https://www.blackhat.com/presentations/bh-asia-03/bh-asia-03-chong.pdf) _(if not before!)_. diff --git a/exe2hex.py b/exe2hex.py index 81bed37..c2d2497 100755 --- a/exe2hex.py +++ b/exe2hex.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -# Name: exe2hex v1.2 (2016-01-15) +# Name: exe2hex v1.3 (2016-01-25) # Author: g0tmilk ~ https://blog.g0tmi1k.com/ # Licence: MIT License ~ http://opensource.org/licenses/MIT # Credit to: exe2bat.exe & https://github.com/acjsec/exe2bam +# Notes: Could use certutil for base64... import os import shutil @@ -11,10 +12,11 @@ import subprocess import sys import tempfile -import urllib.parse from optparse import OptionParser -version = '1.2' +import urllib.parse + +version = '1.3' ################### @@ -100,7 +102,7 @@ def __init__(self, exe_file, bat_file, posh_file): if self.bat_file: # Get just the filename self.bat_filename = os.path.basename(self.bat_file) - verbose_msg("BAT filename: %s" % self.bat_filename) + verbose_msg("BATch filename: %s" % self.bat_filename) # Are we to make a posh file? if self.posh_file: @@ -118,21 +120,13 @@ def check_bat_size(self): verbose_msg('Binary file size: %s' % self.bin_size) if self.bin_size > 65536: - error_msg('Input is larger than 65536 bytes (BATch/DEBUG.exe limitation)') - self.compress_exe() - - if self.bin_size > 65536: - oversize = self.bin_size - 65536 - error_msg( - "Too large. For BATch output, the input file must be under 64k (%d/65536. %d bytes over) (DEBUG.exe limitation)" % ( - self.bin_size, oversize)) - return False - return True + verbose_msg('Input is larger than 65536 bytes') # Try and use strip and/or upx to compress (useful for bat) def compress_exe(self): notification_msg('Attempting to clone and compress') - tf = tempfile.NamedTemporaryFile() + + tf = tempfile.NamedTemporaryFile(delete=False) notification_msg('Creating temporary file %s' % tf.name) try: if (self.exe_file): @@ -143,41 +137,59 @@ def compress_exe(self): except: error_exit("A problem occurred while trying to clone into a temporary file") + # Compress the new temp file self.compress_exe_strip(tf) # Don't do it if its not needed. (AV may detect this) - if os.path.getsize(tf.name) > 65536: + if compress == 2: self.compress_exe_upx(tf) - else: - success_msg("Compression was successful!") + # Set the temp file as the main file self.exe_file = os.path.abspath(tf.name) - self.check_exe() - self.read_bin_file() # Use strip to compress (useful for bat) def compress_exe_strip(self, tf): if shutil.which("strip"): - notification_msg('Running strip on %s' % tf.name) + verbose_msg('Running strip on %s' % tf.name) + + # Get the size before compression + before_size = os.path.getsize(tf.name) + # Program to run to compress command = "strip -s %s" % tf.name process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) process.wait() - verbose_msg('Binary file size (after strip): %s' % os.path.getsize(tf.name)) + # Size after compression + after_size = os.path.getsize(tf.name) + diff_size = before_size - after_size + + # Feedback for the user + success_msg("Compression (strip) was successful! (%s saved)" % ("{:.1%}".format(diff_size / before_size))) + verbose_msg('Binary file size (after strip) %s' % os.path.getsize(tf.name)) else: error_msg("Cannot find strip. Skipping...") # Use upx to compress (useful for bat). Can be flag'd by AV def compress_exe_upx(self, tf): if shutil.which("upx"): - notification_msg('Running UPX on %s' % tf.name) + verbose_msg('Running UPX on %s' % tf.name) + + # Get the size before compression + before_size = os.path.getsize(tf.name) + # Program to run to compress command = "upx -9 -q -f %s" % tf.name process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) process.wait() - verbose_msg('Binary file size (after UPX): %s' % os.path.getsize(tf.name)) + # Size after compression + after_size = os.path.getsize(tf.name) + diff_size = before_size - after_size + + # Feedback for the user + success_msg("Compression (UPX) was successful! (%s saved)" % ("{:.1%}".format(diff_size / before_size))) + verbose_msg('Binary file size (after UPX) %s' % os.path.getsize(tf.name)) else: error_msg("Cannot find UPX. Skipping...") @@ -186,6 +198,9 @@ def read_bin_file(self): # Feedback for the user, to know where they are verbose_msg('Reading binary file') + # Start fresh. Empty the value + self.exe_bin = b'' + # Open the input file with open(self.exe_file, "rb") as f: # Loop forever @@ -223,7 +238,7 @@ def read_bin_stdin(self): # Did something go wrong? if stdin_bytes == 0: - error_exit('Zero bytes read from STDIN') % stdin_bytes + error_exit('Zero bytes read from STDIN') # Set the size from STDIN self.bin_size = stdin_bytes @@ -234,24 +249,50 @@ def read_bin_stdin(self): # Convert binary data to a bat file def bin_to_bat(self): # Feedback for the user, to know where they are - verbose_msg('Converting to BAT') + verbose_msg('Converting to BATch') - # Check size due to limitation of debug,exe (<= 64k) - if self.check_bat_size() == False: - return False + # Number of 64k+/max_size loops will be the number of parts made/ + x = -1 - # Loop through binary bytes - for i in range(0, len(self.exe_bin), hex_len): - self.bat_hex += '%secho e %s >>%s.hex%s\r\necho ' % ( - prefix, '{:04x}'.format(i + 256), self.short_file, suffix) - self.bat_hex += ' '.join('%02x' % i for i in self.exe_bin[i:i + hex_len]) - self.bat_hex += ' >>%s.hex%s\r\n' % (self.short_file, suffix) + # What is tha max size we can use for the loop + max_size = 65536 - (hex_len * 2) + + # Loop through binary bytes per 65536 (Debug.exe limitation) + for exeloop in range(0, len(self.exe_bin), max_size): + + # Increase the loop counter (incase the input file is 64k+) + x += 1 + + # Start fresh. Empty the value + self.byte_count = 0 + + # Loop through binary input file for this section + for i in range(exeloop, exeloop + max_size, hex_len): + + # Is there any more data? Are we at the end? + if not (self.exe_bin[i:i + hex_len]): + break - # Save byte counter (debug.exe needs it) - self.byte_count = len(self.exe_bin) + # Numbering for the hex position in this loop + hex_size = (i - (max_size * x)) + (hex_len * 2) - # Finished successfully - return True + # Convert to hex and debug.exe format + self.bat_hex += '%secho e %s>>%s.hex%s\r\necho ' % ( + prefix, '{:04x}'.format(hex_size), self.short_file, suffix) + self.bat_hex += ' '.join('%02x' % y for y in self.exe_bin[i:i + hex_len]) + self.bat_hex += '>>%s.hex%s\r\n' % (self.short_file, suffix) + + # Save the amount of data converted - aka byte counter (debug.exe needs it at the end) + self.byte_count += hex_len + + # Save the bat file + self.save_bat(x) + + # Start fresh. Empty the value + self.bat_hex = "" + + # Finish off the BATch file (incase there's multiple parts + self.finish_bat(x) # Convert binary data to a PoSh file def bin_to_posh(self): @@ -264,25 +305,51 @@ def bin_to_posh(self): self.posh_hex += ''.join('%02x' % i for i in self.exe_bin[i:i + hex_len]) self.posh_hex += '">%s.hex%s\r\n' % (self.short_file, suffix) - # Finished successfully - return True - # Write resulting bat file - def save_bat(self): + def save_bat(self, loop=0): # Create bat file! - output = '%secho n %s.dll >%s.hex%s\r\n' % (prefix, self.short_file, self.short_file, suffix) + output = '%secho n %s.%s>%s.hex%s\r\n' % (prefix, self.short_file, loop, self.short_file, suffix) output += self.bat_hex - output += '%secho r cx >>%s.hex%s\r\n' % (prefix, self.short_file, suffix) - output += '%secho %s >>%s.hex%s\r\n' % (prefix, '{:04x}'.format(self.byte_count), self.short_file, suffix) - output += '%secho w >>%s.hex%s\r\n' % (prefix, self.short_file, suffix) - output += '%secho q >>%s.hex%s\r\n' % (prefix, self.short_file, suffix) + output += '%secho r cx>>%s.hex%s\r\n' % (prefix, self.short_file, suffix) + output += '%secho %s>>%s.hex%s\r\n' % (prefix, '{:04x}'.format(self.byte_count), self.short_file, suffix) + output += '%secho w>>%s.hex%s\r\n' % (prefix, self.short_file, suffix) + output += '%secho q>>%s.hex%s\r\n' % (prefix, self.short_file, suffix) output += '%sdebug<%s.hex%s\r\n' % (prefix, self.short_file, suffix) - output += '%smove %s.dll %s%s\r\n' % (prefix, self.short_file, self.exe_filename, suffix) - output += '%sdel /F /Q %s.hex%s\r\n' % (prefix, self.short_file, suffix) + + # Write file out (Do we need need to overwrite?) + if loop > 0: + self.write_file(self.bat_file, output, "BATch", False) + else: + self.write_file(self.bat_file, output, "BATch", True) + + # Write resulting bat file + def finish_bat(self, loop=0): + # Is there more than one part? Going to be using this for the copy fu + if loop > 0: + # Loop them all, start with the first + parts = '%s.0' % self.short_file + for i in range(1, loop + 1, 1): + parts += '+%s.%s' % (self.short_file, i) + + # Command fu, to join all the parts together + output = '%scopy /b %s %s%s\r\n' % (prefix, parts, self.exe_filename, suffix) + else: + # Single file, just move it + output = '%smove %s.%s %s%s\r\n' % (prefix, self.short_file, loop, self.exe_filename, suffix) + + # Select every temp file used, so it can be deleted + parts = '%s.hex' % self.short_file + for i in range(0, loop + 1, 1): + parts += ' %s.%s' % (self.short_file, i) + + # Some times the del command will not remove it (still in use), so null it! + output += '%secho .>%s.hex%s\r\n' % (prefix, self.short_file, suffix) + + # The final few things + output += '%sdel /F /Q %s%s\r\n' % (prefix, parts, suffix) output += '%sstart /b %s%s\r\n' % (prefix, self.exe_filename, suffix) - # Write file out - self.write_file(self.bat_file, output, "BAT") + self.write_file(self.bat_file, output, "BATch", False) # Write resulting PoSh file def save_posh(self): @@ -300,20 +367,27 @@ def save_posh(self): output += "%sstart /b %s%s\r\n" % (prefix, self.exe_filename, suffix) # Write file out - self.write_file(self.posh_file, output, "PoSh") + self.write_file(self.posh_file, output, "PoSh", True) # Write output - def write_file(self, filepath, contents, type): + def write_file(self, filepath, contents, type, overwrite=True): # Do we need to HTML encode it? if encode: contents = urllib.parse.quote_plus(contents).replace("%0D%0A", "\r\n") + if os.path.isfile(filepath) and overwrite: + verbose_msg("File already exists. Overwriting %s" % filepath) + # Try and write the file out to disk try: - f = open(filepath, 'w') + if overwrite: + f = open(filepath, 'w') + else: + f = open(filepath, 'a') f.write(contents) f.close - success_msg("Successfully wrote (%s): %s" % (type, os.path.abspath(filepath))) + if overwrite: + success_msg("Successfully wrote (%s) %s" % (type, os.path.abspath(filepath))) except: error_msg("A problem occurred while writing (%s)" % filepath) @@ -323,20 +397,26 @@ def run(self): if self.exe_file != None: # If there is a EXE input, check its valid self.check_exe() + if compress: + self.compress_exe() self.read_bin_file() else: self.read_bin_stdin() + if compress: + self.compress_exe() + self.read_bin_file() # Make bat file if self.bat_file != None: - if self.bin_to_bat(): - self.save_bat() + self.check_bat_size() + self.bin_to_bat() # Make PoSh file if self.posh_file != None: self.bin_to_posh() self.save_posh() + ######################### # End BinaryInput class # ######################### @@ -344,7 +424,6 @@ def run(self): signal.signal(signal.SIGINT, signal_handler) - ################ # Main Program # ################ @@ -378,11 +457,15 @@ def run(self): help="suFfix - text to add after the command on each line", metavar="TEXT") parser.add_option("-l", dest="hex_len", default=128, - help="Maximum hex values per line", metavar="INT") + help="Maximum HEX values per line", metavar="INT") parser.add_option("-v", dest="verbose", default=False, help="Enable verbose mode", action="store_true", metavar="VERBOSE") + parser.add_option("-c", dest="compress", default=False, + help="Clones and compress the file before converting (-cc for higher compression)", + action="count", metavar="COMPRESS") + # Store command-line options and arguments in variables (options, args) = parser.parse_args() exe = options.exe @@ -397,6 +480,7 @@ def run(self): except: error_exit('Invalid length for -l %s' % options.hex_len) verbose = options.verbose + compress = options.compress # Being helpful if they haven't read -h... if len(sys.argv) == 2: @@ -407,15 +491,15 @@ def run(self): # Are there any arguments? elif len(sys.argv) <= 1: print('') - print("Encodes a executable binary file into ASCII text format") - print("Restores using DEBUG.exe (BATch - x86) and/or PowerShell (PoSh - x86/x64)") + print("Encodes an executable binary file into ASCII text format") + print("Restore using DEBUG.exe (BATch - x86) or PowerShell (PoSh - x86/x64)") print('') - print("Quick guide:") + print("Quick Guide:") print(" + Input binary file with -s or -x") print(" + Output with -b and/or -p") print("Example:") print(" $ %s -x /usr/share/windows-binaries/sbd.exe" % sys.argv[0]) - print(" $ %s -x /usr/share/windows-binaries/nc.exe -b /var/www/html/nc.txt" % sys.argv[0]) + print(" $ %s -x /usr/share/windows-binaries/nc.exe -b /var/www/html/nc.txt -cc" % sys.argv[0]) print(" $ cat /usr/share/windows-binaries/whoami.exe | %s -s -b debug.bat -p ps.cmd" % sys.argv[0]) print('') print('--- --- --- --- --- --- --- --- --- --- --- --- --- --- ---')