{
	"id": "8210ca5d-76e1-4718-a5e7-4285009563db",
	"created_at": "2026-04-06T00:10:19.334748Z",
	"updated_at": "2026-04-10T03:24:24.593988Z",
	"deleted_at": null,
	"sha1_hash": "6b78f7c254712e9de559d9b955b3358ae8800a2e",
	"title": "SQUIRRELWAFFLE – Analysing The Main Loader | 0ffset",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2774047,
	"plain_text": "SQUIRRELWAFFLE – Analysing The Main Loader | 0ffset\r\nBy Chuong Dong\r\nPublished: 2021-10-08 · Archived: 2026-04-05 16:47:25 UTC\r\nThis is a follow up for my last post on unpacking SQUIRRELWAFFLE’s custom packer. In this post, we will take\r\na look at the main loader for this malware family, which is typically used for downloading and launching Cobalt\r\nStrike.\r\nSince this is going to be a full analysis on this loader, we’ll be covering quite a lot. If you’re interested in\r\nfollowing along, you can grab the sample from MalwareBazaar.\r\nSHA256: d6caf64597bd5e0803f7d0034e73195e83dae370450a2e890b82f77856830167\r\nStep 1: Entry Point\r\nAs a DLL, the SQUIRRELWAFFLE loader has three different export functions, which are DllEntryPoint,\r\nDllRegisterServer, and ldr. Since DllRegisterServer and ldr share the same address, they are basically the same\r\nfunction. For the sake of simplicity, I will refer to this function as ldr.\r\nA quick look in IDA Pro will show us that DllEntryPoint only calls the function DllMain, which is an empty\r\nfunction that moves the value 1 into eax and returns. On the other hand, ldr is a wrapper function that calls\r\nsub_10003B50, which seems to contain the main functionality of this loader, so we will treat this as the real entry\r\npoint and begin our analysis.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 1 of 16\n\nStep 2: String Wrapper Structure\r\nWhen analyzing sub_10003B50, we can quickly see that they rarely use raw strings directly. Instead, they have\r\nsome stack variables that are potentially loaded with string data, such as content and length, using functions such\r\nas sub_10006AA0.\r\nIn this example, we see the full path of %APPDATA% retrieved using getenv and passed in sub_10006AA0 with\r\nits length.\r\nAnalyzing the small snippet of code at the end of this function shows that the first parameter is used as a structure\r\ndue to values being set at an offset from this variable.\r\nWe can use IDA’s “Create new struct type” functionality to create a structure with these default fields based on\r\nthe offsets used from this variable.\r\nstruct string_struct\r\n{\r\n void *pvoid0;\r\n _BYTE gap4[12];\r\n _DWORD dword10;\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 2 of 16\n\n_DWORD dword14;\r\n};\r\nIf we change the name of the main variable to string_structure and the type to the structure above, the same\r\nsnippet of code becomes easier to understand. The input string is written to the pvoid0 field’s pointer, and its\r\nlength is written to the dword10 field. As a result, we can rename these two fields to make things easier to\r\nanalyze.\r\nSince the string_structure variable is returned, it is clear that the functionality of sub_10006AA0 is to populate\r\nthis wrapper structure with the input string and its size. When needed, the malware can access the string’s data\r\nthrough this wrapper structure. Although this is excessive and makes analysis a bit more challenging, I don’t\r\nbelieve it is used as an anti-analysis mechanism. The malware author probably just wants a uniformed way to\r\nstore and access strings.\r\nTypically, when a structure field’s name begins with “gap”, the field is not used anywhere in the local function, so\r\nin most cases, we can safely ignore this field and update it if we ever encounter code that accesses it later on. The\r\nlast field we need to analyze is the dword14 field.\r\nIn this part of the function, the field’s value is compared to the string’s size, and if it’s smaller, the malware will\r\nallocate a new buffer that is one byte bigger than the string’s size. This new size is set back to the dword14 field,\r\nand the newly allocated buffer is later set to the pvoid0 field and has the string input written to it. From this, we\r\ncan assume the dword14 field contains the default buffer size for the structure’s string buffer.\r\nAfter renaming all fields, the string structure should look like this in IDA.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 3 of 16\n\nstruct string_struct\r\n{\r\n void *string_buffer;\r\n _BYTE gap4[12];\r\n _DWORD string_size;\r\n _DWORD default_buffer_size;\r\n};\r\nThis structure will eventually be used throughout the malware, and SQUIRRELWAFFLE will have multiple\r\nfunctions that interact with it. There are functions to convert the structure to contain wide characters, combine two\r\nstructures into one, append a string into the structure’s current string, etc. I won’t be discussing all of these\r\nfunctions and will simply refer to them by their functionality in the analysis.\r\nStep 3: Encryption/Decryption Routine\r\nEven though most of SQUIRRELWAFFLE’s strings are stored in plaintext in the .rdata section, the more\r\nimportant strings such as the list of C2 URLs are encoded and resolved by the malware dynamically.\r\nWhen encoding/decoding data, SQUIRRELWAFFLE loads the buffer and its length into a structure and loads the\r\nkey and its length into another. Then, both structures’ fields are passed into function sub_100019B0 as parameters.\r\nUpon analyzing sub_100019B0, we can see that the algorithm boils down to a single for loop. First,\r\nSQUIRRELWAFFLE allocates an empty string structure to contain the result of the algorithm. Then, the malware\r\nuses a for loop to iterate through each character in the data, XOR-ing it with a character from the key.\r\nSince the same variable is used to index into both the data and the key, SQUIRRELWAFFLE mods its value by\r\nthe key length when indexing into the key in order to reuse it when the length of the data is greater than the length\r\nof the key. The output character is written into a structure, which is later appended to the result structure. As a\r\nresult, we can conclude that sub_100019B0 is a XOR cipher that is used for encoding/decoding data.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 4 of 16\n\nStep 4: Block Sandbox IP Addresses\r\nFrom reading others’ analysis on SQUIRRELWAFFLE, I happen to know the first encoded string gets decoded\r\ninto a list of IP addresses. In this particular sample however, instead of the normal encoded data, an empty buffer\r\nis passed into the data structure instead.\r\nUsually for encoded strings, it is simple to guess their functionalities based on their decoded contents. For strings\r\nthat are replaced with an empty buffer because the malware authors decide to leave the functionality out, we must\r\ntrack and analyze how the decoded data is used in order to understand its functionality.\r\nAfter decoding this buffer, SQUIRRELWAFFLE calls sub_100011A0, which calls GetAdaptersInfo to retrieve\r\nthe victim’s network adapter information. The malware then uses it to retrieve the local machine’s IPv4 address,\r\nwrites the address into a structure, and returns it.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 5 of 16\n\nAfter getting the machine’s IP address, SQUIRRELWAFFLE checks to see if the decoded data contains the\r\naddress. If it does, the malware exits immediately.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 6 of 16\n\nFrom this, we know that the encoded buffer contains IP addresses to blacklist, and the malware terminates if the\r\nmachine’s address is blacklisted. This is typically used to check for IP addresses of sandboxes to prevent malware\r\nfrom running in these automated environments. Because the encoded data is an empty buffer, the feature is\r\nignored for this particular sample.\r\nStep 5: Victim Information Extraction\r\nPrior to executing the main networking functionality, SQUIRRELWAFFLE calls the following WinAPI functions:\r\nGetComputerNameW to retrieve the local computer’s NetBIOS name, GetUserNameW to retrieve the local\r\nusername, and NetWkstaGetInfo to retrieve the computer’s domain name.\r\nUsing the results of these function calls, SQUIRRELWAFFLE builds up a structure that contains a string in the\r\nfollowing format.\r\n\u003ccomputer name\u003e\\t\\t\u003cuser name\u003e\\t\\t\u003cAPPDATA path\u003e\\t\\t\u003ccomputer domain\u003e\r\nSince this victim information string is later delivered to C2 servers, SQUIRRELWAFFLE encodes it using the\r\nXOR cipher with the key “KJKLO” and Base64 to avoid sending it in plaintext.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 7 of 16\n\nStep 6: Decode C2 URLs\r\nIn its main networking function, SQUIRRELWAFFLE first decodes its C2 URL list using the XOR cipher with\r\nthe key “SgGPfGgbzrSEtPOTtuYkdqSujuBDgXlopIUKrDONXaACWZxGxWkWoIvf”.\r\nBelow is the defanged version of the decoded content.\r\npop[.]vicamtaynam[.]com/VtyiHAft|snsvidyapeeth[.]in/aXmo2Dr3|trinitytesttubebaby[.]com/QR2JvfE3Sv|iconskw[.]com\r\nWe can see that the malware creates two C++ iterators. After splitting each URL by the separator “|”,\r\nSQUIRRELWAFFLE adds the domain to its domain array and the path to its path array and iterates them using the\r\ngenerated iterators.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 8 of 16\n\nStep 7: Build \u0026 Send POST Request\r\nWhen building the POST request to send to each C2 server, SQUIRRELWAFFLE first builds the URL endpoint\r\npath. It generates a random ASCII string with a random length between 1 and 28 characters, retrieves the local\r\nmachine’s IP address, and appends them together.\r\nSQUIRRELWAFFLE generates an endpoint path by encrypting this string using the XOR cipher with the key\r\n“KJKLO” and encoding it with Base64. The final POST request is built in the format below.\r\nPOST /\u003cURL path\u003e/\u003cencoded endpoint path\u003e HTTP/1.1\\r\\nHost: \u003cURL\u003e\\r\\nContent-Length:\u003cencoded victim information\r\nFor some reason, I can not produce this full string in my debugger because the encoded endpoint path is not\r\nproperly resolved for some of the URLs. As an alternative, to confirm that the format from static analysis above is\r\ncorrect, I run the sample on ANY.RUN and check the captured PCAP file.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 9 of 16\n\nTo contact each C2 server, SQUIRRELWAFFLE calls WSAStartup to initiate use of the Winsock DLL and\r\ngetaddrinfo to retrieve a structure containing the server’s address information. Next, it calls socket and connect\r\nto create a socket and establish a connection to the remote server.\r\nFinally, the malware calls send to send the fully crafted POST request and recv to wait and receive a response\r\nfrom the server. Once received, the response string is written into a structure and returned.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 10 of 16\n\nStep 8: Analyze C2 Server’s Response\r\nUsing the PCAP we get from ANY.RUN, we can quickly extract and view the C2 server’s response. Below is one\r\nof the responses captured.\r\nf3p/QUVCQ0FBfn15eXV9f355eEJBQ0JGQnN/ZX5/fWR6e3pleHp1QkFDQkZCHhkOHmIbCUJFLi8nIiJGQglxEBo4Lzk/EyouJiUhFws7PAsqPio\r\nFrom static analysis, we can see that the response is decoded using Base64 and decrypted with the XOR-cipher\r\nusing the key “KJKLO”.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 11 of 16\n\nWith CyberChef, we can quickly decode this and examine the raw content of the C2’s response.\r\nSince the threat actor has the C2 server response with a 404 code and “NONE” for some of the response fields, we\r\ndon’t really get much out of the response except for its general layout. In order to know what each of the response\r\nfields does, we have to dive back into the sample with static analysis.\r\nIn IDA, we can see that SQUIRRELWAFFLE uses an iterator to iterate through all the response fields, and the\r\nfields are split by the separator “\\r\\n\\t\\t\\n\\r”. This gives us fifteen different fields in the response.\r\nIf the 8th field in the response is “true”, the malware immediately skips the server’s response and goes back to\r\ncontacting another server.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 12 of 16\n\nSimilarly, if the first field corresponding to the HTTP response code is anything but “200”, the malware also skips\r\nprocessing the server’s response.\r\nStep 9: Register Executable To Registry\r\nWhen the length of the 6th response field is greater than 100, this field contains the content of an executable to be\r\ndropped and registered in the registry. SQUIRRELWAFFLE first generates a random name with a random length\r\nbetween 1 and 11, appends “.txt” to it, and later uses it to drop the executable.\r\nThe malware then calls std::_Fiopen with the newly generated filename to get a file stream to write to. It extracts\r\nthe content of the executable from the response field and writes it to the file stream.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 13 of 16\n\nSQUIRRELWAFFLE then decodes the command “regsvr32.exe -s ”, appends the executable’s name to it, and\r\ncalls WinExec to register it as a command component in the registry.\r\nStep 10: Drop \u0026 Execute Executable\r\nWhen the length of the 7th response field is greater than 100, this field contains the content of the next stage\r\nexecutable to be dropped and executed.\r\nSQUIRRELWAFFLE also generates another random name for the executable, appends it after the machine’s\r\nTEMP path, and writes the executable’s content there.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 14 of 16\n\nFinally, it calls system to launch a start command and execute the dropped executable.\r\nStep 11: Shellcode Launching\r\nWhen the length of the 15th response field is greater than 10, this field contains a hex string that\r\nSQUIRRELWAFFLE parses and executes as shellcode.\r\nThe malware uses strtoul to convert the hex string into a buffer of bytes. It then calls CreateEventW to create an\r\nevent object, allocates virtual memory, and writes the buffer into the allocated memory.\r\nThe call to CreateThreadpoolWait registers the allocated buffer as a callback function to execute when the wait\r\nobject completes. Finally, the call SetThreadpoolWait sets the wait object for the event, which executes the\r\ncallback function and launches the shellcode.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 15 of 16\n\nStep 12: Drop \u0026 Execute Custom Executable\r\nThis part is similar to Step 10, except that the executable’s path is provided in the 13th response field instead of\r\nbeing randomly generated.\r\nSQUIRRELWAFFLE reads the file’s content from the 5th response field, writes it into the specified file path, and\r\nexecutes the command in the 14th response field to possibly launch the dropped executable.\r\nAt this point, we have fully dissected the entire SQUIRRELWAFFLE loader and understood how it can be used to\r\nlaunch executables and execute shellcode!\r\nIn the analysis, I skipped analyzing a lot of functions that can be automatically resolved using IDA’s Lumina\r\nserver and Capa. Since the malware reuses a lot of local variables for various functionalities, I have to rename\r\nthem in every image included in this post to avoid confusion.\r\nIf you have any trouble following the analysis, feel free to reach out to me via Twitter.\r\nSource: https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/\r\nPage 16 of 16",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-main-loader/"
	],
	"report_names": [
		"squirrelwaffle-main-loader"
	],
	"threat_actors": [
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434219,
	"ts_updated_at": 1775791464,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/6b78f7c254712e9de559d9b955b3358ae8800a2e.pdf",
		"text": "https://archive.orkl.eu/6b78f7c254712e9de559d9b955b3358ae8800a2e.txt",
		"img": "https://archive.orkl.eu/6b78f7c254712e9de559d9b955b3358ae8800a2e.jpg"
	}
}