{
	"id": "e7bc9d82-87d0-4083-88c6-81ebe2285129",
	"created_at": "2026-04-06T00:13:47.460243Z",
	"updated_at": "2026-04-10T13:11:30.730139Z",
	"deleted_at": null,
	"sha1_hash": "67a477749511c51894e6c40aa83018ad9a69a0dc",
	"title": "SQUIRRELWAFFLE – Analysing the Custom Packer | 0ffset",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 622816,
	"plain_text": "SQUIRRELWAFFLE – Analysing the Custom Packer | 0ffset\r\nBy Chuong Dong\r\nPublished: 2021-10-01 · Archived: 2026-04-05 16:11:34 UTC\r\nIn the last month, I have heard and seen a lot about SQUIRRELWAFFLE on Twitter, a new loader that has been\r\nused in email-based campaigns to download Cobalt Strike or Qakbot to the victim’s machine, so I figure it will be\r\nfun to take a look at this new actor!\r\nIn the initial stage of each campaign, a malicious Word document or Excel file containing malicious macros is\r\ndelivered to the victim through phishing malspam. The obfuscated macros drop a VBS file, which downloads the\r\nSQUIRRELWAFFLE loader on the victim’s machine and executes it.\r\nThe first stage of this loader comes in the form of a DLL packer. Despite being fairly simple, the packer utilizes\r\nsome interesting anti-analysis tricks, which makes it entertaining to patch and analyze statically!\r\nTo follow along, you can grab the sample on MalwareBazaar!\r\nSha256: 4545b601c6d8a636dce6597da6443dce45d11b48fcf668336bcdf12ffdc3e97e\r\nStep 1: Rebasing\r\nUpon opening the packer file in IDA Pro, I immediately spot an anti-analysis method used to hide WinAPI calls as\r\nwell as global variables’ access. Across the code, the malware accesses what seems to be addresses (e.g.\r\n0x4197EC and 0x41BA34) at an offset stored in ebx.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 1 of 9\n\nTypically, these addresses should get resolved by IDA if the executable’s image base is the standard address\r\n0x400000, but we can quickly check with PEBear to see that this is not the case. The image base is set to\r\n0x10000000 in the executable’s optional header, forcing IDA to load it at this particular address. To have it\r\nproperly loaded in IDA, we can try to map the base back to 0x400000 and see if those addresses actually make\r\nsense disregarding the value of ebx.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 2 of 9\n\nAfter patching the optional header’s image base value, the same part of code is resolved to meaningful API and\r\nvariable addresses.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 3 of 9\n\nAt this point, it’s safe to assume that sub_40211C writes the value of 0x10000000 – 0x400000 (or 0xfc00000)\r\ninto ebx and uses this value to rebase every address manually upon accessing them. This can quickly be checked\r\nusing dynamic analysis as the function code is fairly simple.\r\nAfter we have manually patched the image base to the correct value, this ebx offset is not needed to rebase the\r\naddresses in our IDB anymore. Therefore, we can simply insert the instruction “xor ebx, ebx” somewhere in the\r\nbeginning of every function to fully clean it up. After doing so, the IDB is turned back into looking like a normal\r\nexecutable for us to examine!\r\nStep 2: Anti-Analysis Through Binary Padding\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 4 of 9\n\nThe core of this executable is relatively short and simple to understand. However, the author of this packer has\r\nutilized binary padding to include junk functions and global variables to make static analysis a bit more complex.\r\nAs you can see from the images of the code base so far, there are some strange functions getting called such as\r\nCoGetCurrentProcess. If we examine the xrefs of the global variables that the result of these functions is being\r\nset to, we can see that these variables are not used anywhere else.\r\nThe list of junk functions used for padding is ImageList_DrawEx, OleInitialize, CoFreeUnusedLibraries,\r\nCoFileTimeNow, CoGetCurrentLogicalThreadId, OleUninitialize, CoGetCurrentProcess, CoCreateGuid,\r\nCoGetContextToken, CheckDlgButton, GetCaretBlinkTime, CheckRadioButton, GetCursorInfo,\r\nGetCapture, CheckMenuItem, CheckMenuRadioItem. The padding usually follows the form of checking if a\r\nglobal variable is initialized or not, and if it is not, the malware calls the padding function and writes its result to\r\nthis variable. The best way to get over this during static analysis is using the Collapse item functionality in IDA to\r\nhide away these if blocks.\r\nStep 3: Static Analysis\r\nThe first valuable WinAPI function that the packer calls is VirtualAlloc, which allocates a virtual buffer of\r\n0x401A000 bytes with read, write, and execute rights.\r\nNext, it uses the instructions “rep movsb” to copy the entire packer executable from the image base to this newly\r\nallocated buffer. The malware then manually calculates the offset of the function sub_402A1D to resolve its\r\nvirtual address in the allocated buffer. Finally, it transfers execution to that virtual address through a “jmp”\r\ninstruction.\r\nNOTE: Because of this execution flow, you should not put breakpoints in sub_402A1D in the main executable\r\nwhile analyzing dynamically in your debugger. The “int3” instruction (trap to debugger) will get copied into the\r\nvirtual buffer and break your execution with this interrupt since x32dbg or similar debuggers stops upon\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 5 of 9\n\nencountering any “int3” instruction that is not set by it. To smoothly use breakpoints, you should set it directly in\r\nthe memory addresses in the virtual buffer.\r\nIn the function sub_402A1D, the packer calls VirtualAlloc again to allocate for a buffer of size 0x12F10 bytes\r\nwith read and write access. Next, it calls VirtualProtect to change the current executable’s protection from read\r\nonly to execute, read, and write. At this point, we can make the assumption that the malware needs the execute and\r\nwrite accesses to write the next stage executable into memory and execute it. Finally, we see the virtual buffer and\r\nthe pointer off_419208 being passed into the function sub_401000 as parameters.\r\nBelow is a part of the buffer pointed to by off_419208, which seems to be some encrypted bytes. Here, another\r\nassumption can be made that the function sub_401000 might decrypt this buffer and write the content, which\r\nmight possibly be the executable for the next stage, into the allocated virtual buffer. With that assumption, let’s\r\nsave analyzing this function for dynamic analysis and moving on to see how the packer uses the virtual buffer\r\nafterward.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 6 of 9\n\nAfterward, the packer calls the function sub_4021A2 below. Assuming the decrypted stage 2 executable is written\r\ninto the virtual buffer, the malware extracts its entry point by querying the AddressOfEntryPoint field in its\r\noptional header structure. Next, it iterates through the LDR_DATA_TABLE_ENTRY structures from the PEB\r\nand compares each loaded library’s/executable’s entry point with its own entry point. This is to manually find the\r\nLDR_DATA_TABLE_ENTRY structures corresponding to its own executable. Once found, the EntryPoint field\r\nin this structure is set to the entry point of the stage 2 executable. This further confirms our previous assumption\r\nthat the decryption happens during the call to sub_401000.\r\nThe packer then calls the sub_402E14, which takes in the address of the virtual buffer as a parameter. This\r\nfunction extracts the stage 2 executable’s size of the headers and copies the headers to the current executable’s\r\nbase. It sets the newly written headers to have read only access using VirtualProtect.\r\nAt this point, it’s safe to say that our previous assumption is correct, and we can quickly extract the stage 2\r\nexecutable using dynamic analysis. The rest of this function iterates the stage 2 executable’s section table to map\r\nthe raw section to its virtual address in the current executable’s address space and transfer executions to it. Since\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 7 of 9\n\nwe already know where the next stage is decrypted already, static analysis can end here, and we can move to\r\ndynamic analysis to quickly unpack the next executable.\r\nStep 4: Unpacking Through Dynamic Analysis\r\nBecause we know that sub_401000 is the decrypting function, we can halt the execution right after this function\r\ngets called to unpack the next stage.\r\nFirst, we need to set a breakpoint at the “jmp” instruction at the end of DllEntryPoint to properly transfer\r\nexecution to the first virtual buffer and execute until we hit it.\r\nNext, to capture the address of the second virtual buffer that will eventually store the next stage, there are a few\r\nways. We can either set a breakpoint at the VirtualAlloc call and examine the result value or a breakpoint at the\r\ninstruction “call sub_401000” instruction and retrieve it from the stack. After we execute the decrypting function,\r\nwe see that a valid PE header is written at the beginning of the virtual buffer, so we can dump it directly from\r\nmemory to retrieve the executable for the second stage.\r\nNOTE: Since we are setting breakpoints in the virtual buffer, we have to manually map the executable’s address to\r\na virtual address based on the virtual buffer’s base. For example, the address 0x10002C2D of the instruction “call\r\nsub_401000” would become 0x3152C2D if the base is 0x3150000.\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 8 of 9\n\nFinally, we can check in PEBear to see that the executable is ready to be analyzed. Since all of the imports are\r\nresolved properly, we do not need to do further mapping of raw address to virtual address! \r\nAt this point, we have fully unpacked the next stage from this custom packer and can now analyze the main\r\nSQUIRRELWAFFLE executable! If you have any questions or issues while analyzing this sample, feel free to\r\nreach out via Twitter.\r\nSource: https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nhttps://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/\r\nPage 9 of 9\n\nNOTE: Since a virtual address we are setting breakpoints based on the in the virtual virtual buffer’s base. buffer, we For example, have to manually the address 0x10002C2D map the executable’s of the instruction address to “call\nsub_401000” would become 0x3152C2D if the base is 0x3150000.\n   Page 8 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.0ffset.net/reverse-engineering/malware-analysis/squirrelwaffle-custom-packer/"
	],
	"report_names": [
		"squirrelwaffle-custom-packer"
	],
	"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
		},
		{
			"id": "aa73cd6a-868c-4ae4-a5b2-7cb2c5ad1e9d",
			"created_at": "2022-10-25T16:07:24.139848Z",
			"updated_at": "2026-04-10T02:00:04.878798Z",
			"deleted_at": null,
			"main_name": "Safe",
			"aliases": [],
			"source_name": "ETDA:Safe",
			"tools": [
				"DebugView",
				"LZ77",
				"OpenDoc",
				"SafeDisk",
				"TypeConfig",
				"UPXShell",
				"UsbDoc",
				"UsbExe"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434427,
	"ts_updated_at": 1775826690,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/67a477749511c51894e6c40aa83018ad9a69a0dc.pdf",
		"text": "https://archive.orkl.eu/67a477749511c51894e6c40aa83018ad9a69a0dc.txt",
		"img": "https://archive.orkl.eu/67a477749511c51894e6c40aa83018ad9a69a0dc.jpg"
	}
}