Background
Three weeks back, I received an odd project from a pentesting team, and it was to create a malware that would be used on a phishing engagement. The environment was unknown and that applied for their Endpoint Protection product as well. This was the beginning for my new internal tool dubbed the “AlexeySpecial” named after a team member who recently left.
AlexeySpecial since improved by leaps and bounds, leading it up from just two output, an .exe and .dll, into .hta, .html smuggling, .xlms/vba macros and more. All of which currently performing extremely well on both scan time and run time for a wide varieties of AVs/EPs.
As the development of AlexeySpecial continues, I see the need of using more stealthy approaches for getting code execution. This is where a twitter post from Malwrologist @DissectMalware really opened my eyes. This was the post. Further research into what the heck was going led me up to the keyword “ExecuteExcel4Macro” combined with an ex-colleague recommendation, I ended up here. This is going to be a post about how I got Excel to execute my malicious payload using 1992 technologies through the researches from the smart people at Outflank!
The Steps
First of all, we must know that any \x00 is going to give us a bad time. Excel does not like null bytes at all. If we have any null byte within our shellcode, we will end up with a broken shellcode and a sad face. Therefore, we will use msfvenom’s bad char ability to converts our shellcode into one without any bad char.
By echoing our bytes into a file and uses msfvenom with -b ‘\x00’, we will receive a new encoded shellcode without any specified bad char.
I will assume that you, the reader understand how we will execute our shellcode through winapi. However, here is a brief run down of the steps.
- The step start out with a VirtualAlloc call, giving us one to two pages of memory, each 4096 bytes long. We will make sure to VirtualAlloc the page with the last parameter being (dec )64 or (hex) 0x40, aka PAGE_EXECUTE_READWRITE.
- Second, we will perform a WPM or WriteProcessMemory, copying our shellcode from a cell/a place in memory, over to the VirtualAlloc-ed page(s).
- Third, we will CreateThread with the entry/starting address being our shellcode address/VirtualAlloc address.
Once we accomplish all of these steps, our code will be executed and we will be greeted with a shell.
To start with our shellcode writing, we must create a sheet as an Excel Macro 4.0 sheet, refer to Outflank’s blog post/Derbycon 2018 video for more information.
To store our shellcode in an excel’s cell, we must use the function CHAR(), with the decimal representation of our opcode inside, follows by ‘&’ for concatenation. However, we warned that for whatever reason, WPM can only write a maximum of 255 bytes per call. Therefore, if your shellcode is longer than 255 bytes, you will be required to make multiple calls with the correct offset.
And Voila~, we got our shellcode. Changing the first cell’s indicator to AUTO_OPEN will guarantee our shellcode is executed once enable macro is clicked.
And the last step would be to hide the Macro tab, save the excel sheet as an .xls file and we are done!
However, I am not going to just preach what Outflank already written about. We will take it a step further. Outflank mentioned that functions such as GET.WORKBOOK() can help us store our shellcode better, and in my opinion, it is a bit better.
But first, let’s look at what GET.WORKBOOK() does.
From that post, we can see that it takes in two arguments. First is type_num and second is name_text. I’ll save you the trouble and explain what those are. type_num identify what field/information you want the function to fetch and name_text is from which sheet you want the function to fetch it from. Leaving name_text blank will default back to the current sheet, which is what we want anyway.
But, what kind of fields can we pull information from? Let’s take a look at this nice chart.
The one thing that stuck out to me is that there are multiple different fields you can pull information from. However, comments would be the least noticeable place for us to hide our shellcode. Let’s see how we can achieve that.
Let’s insert “AAAA” into our comment field and see what it look like when stored (with my favorite hex editor, HxD).
We can see that our code is stored literally as what it is entered. This gave me an idea, and that is what if we add our own shellcode to the comment and then use WPM to copy it over instead of using CHAR() in the actual sheet?
I decided to see if I could append a new character to the comment, but it seem like there is a size check somewhere that cause the file to be corrupted due to the comment’s change.
Ok, so there is that. However, can we OVERWRITE instead of inserting new characters? That should satisfy the size check, right? Yep!
The letter changed from an A (0x41) to a B (0x42) and the sheet loads just fine! Now, we just need to make sure that there are enough room for our shellcode. Nothing a bit of copy-pasting can’t help!
Now that we have sufficient room in our comment for the shellcode, we can now overwrite the comment with actual shellcode using our hex editor.
Finally, the modified macro code. I use left() and mid() to splice the shellcode up to 4 chunks of 255 bytes. We will be copying a few extra ABs but since the code will be looping forever, establishing a reverse shell connection — we won’t have to worry about it ever hitting the ABs.
Let’s check if the shellcode was written correctly! (I know the address on the screenshots doesn’t match up, every time you execute the VirtualAlloc, it gives you a different page address and these two screenshots are from two different executions)
Finally, we receive a beacon from our target machine back to our cobalt strike server, with full command execution from inside Excel!
There it is, the conclusion and thank you for reading!