It’s Not Just a Breakfast for Rodents

Note to reader: If you are following along, you will need a VPN connection that supports a large list of geographic endpoints. I have found South American and Eastern Bloc countries are good choices for endpoints.

Many downloaders have a double-edged goal in mind – make it easy for the common user to download and execute a malicious payload and hard for an analyst to automate. So today we are going to explore whether we can automate one of the more annoying and popular downloaders in the current threat landscape – SquirrelWaffle.

The traditional TTP is a spoofed reply chain email with an embedded link to a ZIP file. So, let’s start with a python script to spoof HTTP headers and try to download a remote ZIP file.

>>> url = 'https://accounts.kdcu.org/necessitatibusmolestias/deiaiosi-dusnimslmboauuii-glrtlbmudosetsit'
>>> headers = {
...     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
...     "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
...     "Accept-Language": "en-US,en;q=0.9"
... }
>>> import requests
>>> r = requests.get(url, headers=headers)
>>> r.headers['content-type']
'text/html; charset=UTF-8'
>>> r.content
b'<html><body><img src="/necessitatibusmolestias/logotype.jpg?1640113477" width="7px"><span id="cjuP" data-cjuP="/necessitatibusmolestias/AO_537777377.zip"></span><script>location.pathname = document.getElementById(\'cjuP\').getAttribute(\'data-cjuP\');</script></body></html>'

Looks like we ended up with a simple JavaScript redirect. And a simple regular expression extracts the ZIP files URI.

>>> import urllib.parse, re
>>> m = re.search(rb'"(.[^"]+\.zip)"', r.content)
>>> zip_file = m.group(1).decode()
>>> parts = urllib.parse.urlparse(url)
>>> new_url = '{}://{}{}'.format(parts.scheme, parts.netloc, zip_file)
>>> new_url
'https://accounts.kdcu.org/necessitatibusmolestias/M_1169734532.zip'
>>> r = requests.get(new_url, headers=headers)
>>> r.headers['content-type']
'application/octet-stream'

Perfect! So now we have a ZIP file that we need to unpack. Looks like this maldoc is an XLSB, a newer version of a Microsoft Excel document. Fortunately, DissectMalware has a python module, pyxlsb2, to parse these documents. If the user is unfamiliar with SquirrelWaffle maldocs or any common maldoc downloaders, then here is a quick cliff note:

  • XLM macros are the most common TTP
  • An Excel cell has a value and a formula property
  • Excel columns and rows are 1 offset and python modules are 0 offset
>>> import pyxlsb2
>>> book = pyxlsb2.open_workbook('DF-1140826109.xlsb')
>>> for ws in book.sheets:
...     sheet = book.get_sheet_by_name(ws.name)
...     for row in sheet.rows():
...             for cell in row:
...                     if cell.formula:
...                             print(pyxlsb2.formula.Formula.parse(cell.formula).stringify(book))
T(Sbbbbr1!O3&amp;Sbbbbr1!O3&amp;Sbbbbr1!L13&amp;Sbbbbr1!L13&amp;Sbbbbr1!F24&amp;B4&amp;H27)
T(Sbbbbr1!E2&amp;Sbbbbr1!F10&amp;Sbbbbr1!B11&amp;Sbbbbr1!S5&amp;Sbbbbr1!H28&amp;Sbbbbr1!R17&amp;Sbbbbr1!P15&amp;Sbbbbr1!E14)
T(Sbbbbr1!F26&amp;Sbbbbr1!O11&amp;Sbbbbr1!F26)
T(Sbbbbr1!E2&amp;Sbbbbr1!F10&amp;Sbbbbr1!B11&amp;Sbbbbr1!Q1&amp;Sbbbbr1!H28&amp;Sbbbbr1!R17&amp;Sbbbbr1!P15&amp;Sbbbbr1!E14)
T(Sbbbbr1!O3&amp;Sbbbbr1!O3&amp;Sbbbbr1!L13&amp;Sbbbbr1!L13&amp;Sbbbbr1!F24&amp;B4&amp;I30)
T(Sbbbbr1!E2&amp;Sbbbbr1!F10&amp;Sbbbbr1!B11&amp;Sbbbbr1!Q4&amp;Sbbbbr1!H28&amp;Sbbbbr1!R17&amp;Sbbbbr1!P15&amp;Sbbbbr1!E14)
T(Sbbbbr1!F24&amp;Sbbbbr1!N14&amp;Sbbbbr1!A4&amp;Sbbbbr1!E2&amp;Sbbbbr1!Q6&amp;Sbbbbr1!R17&amp;Sbbbbr1!B11&amp;Sbbbbr1!F24&amp;Sbbbbr1!F26&amp;Sbbbbr1!F24&amp;Sbbbbr1!L7)
T(Sbbbbr1!F26&amp;Sbbbbr1!O11&amp;Sbbbbr1!F26&amp;Sbbbbr1!O11&amp;Sbbbbr1!L31)
T(Sbbbbr1!F10&amp;Sbbbbr1!S2&amp;Sbbbbr1!F24&amp;Sbbbbr1!F26&amp;Sbbbbr1!F24&amp;Sbbbbr1!M4&amp;Sbbbbr1!M4)
T(Sbbbbr1!O3&amp;Sbbbbr1!O3&amp;Sbbbbr1!L13&amp;Sbbbbr1!L13&amp;Sbbbbr1!F24&amp;B4&amp;K27)
T(Sbbbbr1!K10&amp;Sbbbbr1!M16&amp;Sbbbbr1!Q11&amp;Sbbbbr1!R17&amp;Sbbbbr1!I3&amp;Sbbbbr1!B11&amp;Sbbbbr1!E2&amp;Sbbbbr1!R17&amp;Sbbbbr1!T9&amp;Sbbbbr1!M8&amp;Sbbbbr1!T4&amp;Sbbbbr1!R17&amp;Sbbbbr1!K2&amp;Sbbbbr1!S13&amp;Sbbbbr1!E2)
T(Sbbbbr1!F26&amp;Sbbbbr1!O11&amp;Sbbbbr1!F26&amp;Sbbbbr1!U3&amp;Sbbbbr1!L31)
T(Sbbbbr1!F24&amp;Sbbbbr1!M4&amp;Sbbbbr1!M4&amp;Sbbbbr1!O3&amp;Sbbbbr1!O3)
T(Sbbbbr1!F10&amp;Sbbbbr1!C16&amp;Sbbbbr1!O18&amp;Sbbbbr1!B3&amp;Sbbbbr1!A4&amp;Sbbbbr1!Q1&amp;Sbbbbr1!S5&amp;Sbbbbr1!F24&amp;Sbbbbr1!F26&amp;Sbbbbr1!F24)
T(Sbbbbr1!F24&amp;Sbbbbr1!H13&amp;Sbbbbr1!J5&amp;Sbbbbr1!F10&amp;Sbbbbr1!E2&amp;Sbbbbr1!E2&amp;Sbbbbr1!Q1&amp;Sbbbbr1!S5&amp;Sbbbbr1!F24&amp;Sbbbbr1!F26&amp;Sbbbbr1!F24&amp;Sbbbbr1!H13)
T(Sbbbbr1!O3&amp;Sbbbbr1!M4&amp;Sbbbbr1!M4&amp;Sbbbbr1!F24&amp;Sbbbr2!B4)

Welp, I was hoping the XLM macro was simple. Instead, we have some cell references. This shouldn’t stall us, let’s walk the cells and save their values to a mapper that uses a key of `Sheet!CRR` where C is the column letter and RR is the row number. I’m going to keep that mapper hidden for now, to save space. Let’s replace those cell references to their variables and parse out any URLs.

>>> for sheet in book.sheets:
...         worksheet = book.get_sheet_by_name(sheet.name)
...         for row in worksheet:
...             for cell in row:
...                 if cell.formula:
...                     value = pyxlsb2.formula.Formula.parse(cell.formula).stringify(book)
...                     value = re.sub(r'[&amp;(](\w+!\w+)',
...                                    lambda m: '&amp;"{}"'.format(sheet_map[m.group(1)]),
...                                    value)
...                     value = value.replace('"&amp;"', '')
...                     for m in re.finditer('"?(https?://[^"]+)[",]?', value):
...                         print(m.group(1))
...
https://esportsbrasfootleague.com/YFVkNcrS/u.png
https://toursbooking.mu/abYIRuBn/u.png
https://verdewall.com.br/ULKRNfV1a/u.png

I like where this is going. And can we download these DLL files; and yes they are not PNG image files.

>>> dlls = ['https://esportsbrasfootleague.com/YFVkNcrS/u.png',
...         'https://toursbooking.mu/abYIRuBn/u.png',
...         'https://verdewall.com.br/ULKRNfV1a/u.png']
>>> for i, dll in enumerate(dlls):
...         r = requests.get(dll, headers=headers)
...         with open('bad{}.dll'.format(i), 'wb') as f:
...             f.write(r.content)
...
678570
678579
678615
>>> quit()
% file bad*.dll
bad0.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
bad1.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
bad2.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows

And if we wrap all of this into a single script.

% python waffles.py 'https://accounts.kdcu.org/necessitatibusmolestias/deiaiosi-dusnimslmboauuii-glrtlbmudosetsit'
Downloaded M_1169734532.zip
Found next stage URL - https://esportsbrasfootleague.com/YFVkNcrS/u.png
Found next stage URL - https://toursbooking.mu/abYIRuBn/u.png
Found next stage URL - https://verdewall.com.br/ULKRNfV1a/u.png
Next stage URL - https://esportsbrasfootleague.com/YFVkNcrS/u.png
Saved bad0.dll from https://esportsbrasfootleague.com/YFVkNcrS/u.png
Next stage URL - https://toursbooking.mu/abYIRuBn/u.png
Saved bad1.dll from https://toursbooking.mu/abYIRuBn/u.png
Next stage URL - https://verdewall.com.br/ULKRNfV1a/u.png
Saved bad2.dll from https://verdewall.com.br/ULKRNfV1a/u.png

And the original email for this sample.

 

All third-party trademarks referenced by Cofense whether in logo form, name form or product form, or otherwise, remain the property of their respective holders, and use of these trademarks in no way indicates any relationship between Cofense and the holders of the trademarks.