Excel 4.0 Macro, Old but New!

Hoang Bui
6 min readJan 10, 2019

--

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” 1.0 in action

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.

Removing \x00 badchar

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.

Our Macro to execute shellcode

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.

Top left, change R1C1/A1 to AUTO_OPEN

And the last step would be to hide the Macro tab, save the excel sheet as an .xls file and we are done!

Ignores the difference in code, I will go further into that!

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.

GET.WORKBOOK: Returns information about a workbook.

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.

Oh, comments as well?

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).

Ignore the erased stuff, had my personal information metadata there…

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?

Again, ignore the erased stuff..

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.

Once recover, everything is lost

Ok, so there is that. However, can we OVERWRITE instead of inserting new characters? That should satisfy the size check, right? Yep!

Seem like I was correct!

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!

LOTS OF ABABABAB
The AB’s

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.

Shellcode replacing ABABs…
10/10 comments

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.

Final code, with useful ‘comments’

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)

Cheat Engine, best reversing tool ever
Cobalt Strike C2

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!

--

--