SQUIRRELWAFFLE – Analysing The Main Loader | 0ffset By Chuong Dong Published: 2021-10-08 · Archived: 2026-04-05 16:47:25 UTC This is a follow up for my last post on unpacking SQUIRRELWAFFLE’s custom packer. In this post, we will take a look at the main loader for this malware family, which is typically used for downloading and launching Cobalt Strike. Since this is going to be a full analysis on this loader, we’ll be covering quite a lot. If you’re interested in following along, you can grab the sample from MalwareBazaar. SHA256: d6caf64597bd5e0803f7d0034e73195e83dae370450a2e890b82f77856830167 Step 1: Entry Point As a DLL, the SQUIRRELWAFFLE loader has three different export functions, which are DllEntryPoint, DllRegisterServer, and ldr. Since DllRegisterServer and ldr share the same address, they are basically the same function. For the sake of simplicity, I will refer to this function as ldr. A quick look in IDA Pro will show us that DllEntryPoint only calls the function DllMain, which is an empty function that moves the value 1 into eax and returns. On the other hand, ldr is a wrapper function that calls sub_10003B50, which seems to contain the main functionality of this loader, so we will treat this as the real entry point and begin our analysis. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 1 of 16 Step 2: String Wrapper Structure When analyzing sub_10003B50, we can quickly see that they rarely use raw strings directly. Instead, they have some stack variables that are potentially loaded with string data, such as content and length, using functions such as sub_10006AA0. In this example, we see the full path of %APPDATA% retrieved using getenv and passed in sub_10006AA0 with its length. Analyzing the small snippet of code at the end of this function shows that the first parameter is used as a structure due to values being set at an offset from this variable. We can use IDA’s “Create new struct type” functionality to create a structure with these default fields based on the offsets used from this variable. struct string_struct { void *pvoid0; _BYTE gap4[12]; _DWORD dword10; https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 2 of 16 _DWORD dword14; }; If we change the name of the main variable to string_structure and the type to the structure above, the same snippet of code becomes easier to understand. The input string is written to the pvoid0 field’s pointer, and its length is written to the dword10 field. As a result, we can rename these two fields to make things easier to analyze. Since the string_structure variable is returned, it is clear that the functionality of sub_10006AA0 is to populate this wrapper structure with the input string and its size. When needed, the malware can access the string’s data through this wrapper structure. Although this is excessive and makes analysis a bit more challenging, I don’t believe it is used as an anti-analysis mechanism. The malware author probably just wants a uniformed way to store and access strings. Typically, when a structure field’s name begins with “gap”, the field is not used anywhere in the local function, so in most cases, we can safely ignore this field and update it if we ever encounter code that accesses it later on. The last field we need to analyze is the dword14 field. In this part of the function, the field’s value is compared to the string’s size, and if it’s smaller, the malware will allocate a new buffer that is one byte bigger than the string’s size. This new size is set back to the dword14 field, and the newly allocated buffer is later set to the pvoid0 field and has the string input written to it. From this, we can assume the dword14 field contains the default buffer size for the structure’s string buffer. After renaming all fields, the string structure should look like this in IDA. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 3 of 16 struct string_struct { void *string_buffer; _BYTE gap4[12]; _DWORD string_size; _DWORD default_buffer_size; }; This structure will eventually be used throughout the malware, and SQUIRRELWAFFLE will have multiple functions that interact with it. There are functions to convert the structure to contain wide characters, combine two structures into one, append a string into the structure’s current string, etc. I won’t be discussing all of these functions and will simply refer to them by their functionality in the analysis. Step 3: Encryption/Decryption Routine Even though most of SQUIRRELWAFFLE’s strings are stored in plaintext in the .rdata section, the more important strings such as the list of C2 URLs are encoded and resolved by the malware dynamically. When encoding/decoding data, SQUIRRELWAFFLE loads the buffer and its length into a structure and loads the key and its length into another. Then, both structures’ fields are passed into function sub_100019B0 as parameters. Upon analyzing sub_100019B0, we can see that the algorithm boils down to a single for loop. First, SQUIRRELWAFFLE allocates an empty string structure to contain the result of the algorithm. Then, the malware uses a for loop to iterate through each character in the data, XOR-ing it with a character from the key. Since the same variable is used to index into both the data and the key, SQUIRRELWAFFLE mods its value by the key length when indexing into the key in order to reuse it when the length of the data is greater than the length of the key. The output character is written into a structure, which is later appended to the result structure. As a result, we can conclude that sub_100019B0 is a XOR cipher that is used for encoding/decoding data. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 4 of 16 Step 4: Block Sandbox IP Addresses From reading others’ analysis on SQUIRRELWAFFLE, I happen to know the first encoded string gets decoded into a list of IP addresses. In this particular sample however, instead of the normal encoded data, an empty buffer is passed into the data structure instead. Usually for encoded strings, it is simple to guess their functionalities based on their decoded contents. For strings that are replaced with an empty buffer because the malware authors decide to leave the functionality out, we must track and analyze how the decoded data is used in order to understand its functionality. After decoding this buffer, SQUIRRELWAFFLE calls sub_100011A0, which calls GetAdaptersInfo to retrieve the victim’s network adapter information. The malware then uses it to retrieve the local machine’s IPv4 address, writes the address into a structure, and returns it. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 5 of 16 After getting the machine’s IP address, SQUIRRELWAFFLE checks to see if the decoded data contains the address. If it does, the malware exits immediately. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 6 of 16 From this, we know that the encoded buffer contains IP addresses to blacklist, and the malware terminates if the machine’s address is blacklisted. This is typically used to check for IP addresses of sandboxes to prevent malware from running in these automated environments. Because the encoded data is an empty buffer, the feature is ignored for this particular sample. Step 5: Victim Information Extraction Prior to executing the main networking functionality, SQUIRRELWAFFLE calls the following WinAPI functions: GetComputerNameW to retrieve the local computer’s NetBIOS name, GetUserNameW to retrieve the local username, and NetWkstaGetInfo to retrieve the computer’s domain name. Using the results of these function calls, SQUIRRELWAFFLE builds up a structure that contains a string in the following format. \t\t\t\t\t\t Since this victim information string is later delivered to C2 servers, SQUIRRELWAFFLE encodes it using the XOR cipher with the key “KJKLO” and Base64 to avoid sending it in plaintext. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 7 of 16 Step 6: Decode C2 URLs In its main networking function, SQUIRRELWAFFLE first decodes its C2 URL list using the XOR cipher with the key “SgGPfGgbzrSEtPOTtuYkdqSujuBDgXlopIUKrDONXaACWZxGxWkWoIvf”. Below is the defanged version of the decoded content. pop[.]vicamtaynam[.]com/VtyiHAft|snsvidyapeeth[.]in/aXmo2Dr3|trinitytesttubebaby[.]com/QR2JvfE3Sv|iconskw[.]com We can see that the malware creates two C++ iterators. After splitting each URL by the separator “|”, SQUIRRELWAFFLE adds the domain to its domain array and the path to its path array and iterates them using the generated iterators. https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/ Page 8 of 16 Step 7: Build & Send POST Request When building the POST request to send to each C2 server, SQUIRRELWAFFLE first builds the URL endpoint path. It generates a random ASCII string with a random length between 1 and 28 characters, retrieves the local machine’s IP address, and appends them together. SQUIRRELWAFFLE generates an endpoint path by encrypting this string using the XOR cipher with the key “KJKLO” and encoding it with Base64. The final POST request is built in the format below. POST // HTTP/1.1\r\nHost: \r\nContent-Length: