SquirrelWaffle (not exactly a waffle) Analysis (Part 1)

Ann Fam
7 min readSep 29, 2021

--

OALabs and Eli Salem inspired my first ever write-up on reverse engineering. Thank you for your contribution to the RE community and all your hard work!

You can check the reference materials at the end of this blog post.

SquirrelWaffle (sounds pretty yummy, no offense for squirrel lovers!) loader usually comes with Qakbot or Cobalt Strike flavors.

Today we will be analyzing the SquirrelWaffle loader with Cobalt Strike.

Let’s analyze the packed sample first.

The initial infection happens via Word document with embedded macro:

Malicious Word Doc containing Macro

Let’s do a quick analysis on the Word doc using olevba:

Macro Analysis using olevba

We have noticed some promising C2 domains and DLLs above. The malicious DLLs are then executed via rundll32.exe:

DLL execution via rundll32.exe

We also see the Sleep function (not even longer than my nap) before the execution:

Sleep Function

The DLLs are being downloaded using the PowerShell System.Net.WebClient method:

DLL download via PowerShell System.Net.WebClient

A large amount of random strings indicates that this is a packed loader:

Indication of the packed loader

How else can you detect that the sample is packed? Try running it in Detect-It-Easy (DiE), here is the comparison of the packed (on the left) and unpacked sample (on the right).

The packed sample has an entropy of 78% and the unpacked sample has an entropy of 46%:

Scanning the packed and unpacked samples using DiE

Another weird thing is that you will see interesting names in the exports section instead of ‘ldr’:

export section of the packed sample

To unpack this sample, you can either:

  1. Use https://www.unpac.me/ — an excellent resource for unpacking most malware samples, saves you a lot of time!
  2. Manually unpack it via x32dbg. Eli Salem did a thorough walkthrough of how to unpack it manually in the blog post I have mentioned in the references.

There is no preference, and I used both of the methods. If you are new to x32dbg or reversing world like me, I highly recommend practicing unpacking the sample manually.

The manual way of unpacking the sample:

We will need to put a breakpoint on VirtualAlloc, this is when we can see if the malware is allocating space in memory of a new process or creating new regions to stuff the code during the unpacking process

Setting a breakpoint on VirtualAlloc

I run until the first VirtualAlloc call, then I will run to the user code. This is where I am seeing another call to the VirtualAlloc:

Call to VirtualAlloc

The instruction rep movsb > transfers the shellcode to the allocated memory

rep movsb instruction

We can see the jump to EAX and if you follow the EAX dump, you should see the hex: E8 00 00 00 00 which means +0 bytes from the next instruction of the shellcode:

jump to EAX to get the shellcode instructions running

Click F9 (run) twice and then execute until return, then step over. You should see another call to VirtualAlloc [ebx+2113BC], click “Follow in Dump” in EAX. You won’t see anything at this point being written to the Dump section.

Call to VirtualAlloc (ebx+2113BC)

But it’s not the end of the world, let’s put the breakpoint for “Write” at the first row of the empty hex to observe the contents that will be written there and keep pressing “Run” (F9), until….

Setting the “Write” breakpoint

Until you see this…

Dump of the content

Let’s now put another breakpoint on ‘leave’ and run the debugger one more time:

Setting the breakpoint on ‘leave’

Doing some Googling on the M8Z header, I have stumbled across OALabs again :D According to OALabs: https://github.com/herrcore/aplib-ripper, MZ becomes M8Z after compressing the PE file using aPlib.

Let’s now see how aPlib will get decompressed and magically turn into MZ. Remove the breakpoints set before and put another breakpoint for “Access” on the first hex row:

Setting the “Access” breakpoint

Click “Run” or F9. This is how the aPlib decompression looks like:

aPlib decompression

Let’s follow the EDI registry in dump (this is where our unpacked payload will be decompressed) and set the breakpoint on the third return instruction.

return instruction

Click “Run” and you should see our MZ header.

You should be able to extract the MZ file at the end of the unpacking process by highlighting the rest of the MZ content, right-click > Binary > Save to a File.

Extracting the final payload

I didn’t have to tweak the Section Headers since the APIs are resolved correctly (Phew…):

Imports Section of the payload

The next step would be extracting the configuration from the payload.

We can do it in three ways:

  1. Debugging it via x32dbg
  2. Write a Python script
  3. Use CyberChef

If you choose the first method, you would need to get to the Entry Point of the payload and search for reference strings: APPDATA or TEMP, go to either of the functions and change the EIP register to point to the address of the entry point of APPDATA or TEMP and start the execution from there.

The EIP pointing to the entry point of APPDATA

While debugging the payload, you will notice that the IPs are populated after the highlighted “XOR key” string. How do we know it’s a XOR encryption, and how can we test it? Hang tight with me until the end of this blog post.

XOR key and IP addresses in x32dbg

Further running the payload, you will notice the function memcpy writing the C2 domains:

C2 Domains

So, let’s come back to our question of how we determined the XOR key and how we can quickly test the theory.

I will use the commercial version of IDA, but you can test it out with Ghidra or whichever floats your boat.

There is one call (sub_583B50) in the ‘ldr’ export function:

ldr export function call

We are seeing our familiar APPDATA and TEMP references:

References to APPDATA and TEMP in IDA

Let’s open the Pseudocode view with F5:

Pseudocode view of sub_583B50

We see the string that we mentioned above again.

Let’s check the sub_5819B0 function:

sub_5819B0 function

We will notice the for loop, and XOR decryption algorithm with a11 being the key length, v14 — pointer, v16 — key, p_Block is the data. The mod (%) and the key length is also a giveaway that this might be a XOR:

XOR decryption algorithm

Let’s test it out in CyberChef.

We can copy the cute strings as Hex by using an awesome script provided by OALabs: https://github.com/OALabs/hexcopy-ida/blob/main/hexcopy.py. For it to work 1000% — use this script with IDA running on Python 3.

Hex copy of the strings

And it works. Kudos to OALabs again!

Configuration extraction via CyberChef

You can also test out on your own with the other XOR keys by xref-ing the sub_5819B0 function.

Let’s get the domains out:

Extracted malicious domains via CyberChef

You can also be creative and practice your Python skills by writing a script to extract the configurations using the XOR decryption algorithm we have noticed above.

Python config extractor script

OALabs has a great video tutorial on how to write the configuration extractor in Python (check out the reference section).

Conclusion:

I hope you enjoyed my humble write-up. And if you have any recommendations, comments, or concerns, please don’t hesitate to reach out.

References:

  1. The Squirrel Strikes Back: Analysis of the newly emerged cobalt-strike loader “SquirrelWaffle” by Eli Salem: https://elis531989.medium.com/the-squirrel-strikes-back-analysis-of-the-newly-emerged-cobalt-strike-loader-squirrelwaffle-937b73dbd9f9
  2. Live Coding A Squirrelwaffle Malware Config Extractor by OALabs: https://www.youtube.com/watch?v=9X2P7aFKSw0
  3. My modified version of the SquirrelWaffle config extractor: https://github.com/RussianPanda95/SquirrelWaffle/blob/main/squirrelconfig.py
  4. SquirrelWaffle Loader with Cobalt Strike (Sample): https://www.malware-traffic-analysis.net/2021/09/17/index.html

--

--

Ann Fam

Threat Intelligence Researcher | Threat Hunter | Content Developer | The opinions expressed within the content published here are solely my own