{
	"id": "e46f41ac-a6ae-4273-a956-cbd8f8badc22",
	"created_at": "2026-04-06T00:13:35.478315Z",
	"updated_at": "2026-04-10T13:11:57.437193Z",
	"deleted_at": null,
	"sha1_hash": "7f29f48d8bbeeaf2766c3095989cd9e3884028c6",
	"title": "Reversing a NSIS dropper using quick and dirty shellcode emulation",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1632971,
	"plain_text": "Reversing a NSIS dropper using quick and dirty shellcode emulation\r\nBy Malcat EI\r\nArchived: 2026-04-05 22:58:56 UTC\r\nSample:\r\ne850f3849ea82980cf23844ad3caadf73856b2d5b0c4179847d82ce4016e80ee (Bazaar, VT)\r\nInfection chain:\r\nExcel stylesheet -\u003e Office equation -\u003e Shellcode (downloader) -\u003e NSIS installer -\u003e Shellcode (stage 1) -\u003e Shellcode\r\n(stage 2) -\u003e Lokibot\r\nTools used:\r\nMalcat, Speakeasy emulator\r\nDifficulty:\r\nEasy\r\nThe Excel document\r\nThe sample we are about to dissect today is an OpenXML Excel document which came as email attachment. The malicious\r\ndocument is very similar to the one we did analyze in our previous blog post: an encrypted OpenXML Excel document\r\nembedding an Equation object exploiting CVE-2018-0798. The same author is most likely behind this document as well,\r\nthey just updated the bait picture:\r\nFigure 1: Excel sheet baiting the user to deactivate safe mode\r\nWe won't go through the exploit shellcode extraction and decryption process again since the procedure is exactly the same\r\n(see here, shellcode offset is also 0x50 ). The exploit is again a downloader, downloading from the following url:\r\nhxxp://103.153.79.104/windows10/csrss.exe\r\nAt the time of the analysis, the file is still online. But this time, we don't get a DBatLoader instance, but a NSIS installer\r\ninstead. So let us fetch the file and have a look at the installer.\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 1 of 10\n\nNSIS installer\r\nThe file csrss.exe is a 418KB PE file of sha256\r\n291df8186e62df74b8fcf2c361c6913b9b73e3e864dde58eb63d5c3159a4c32d (Bazaar, VT). A NSIS installer is nothing more\r\nthan a NSIS archive appended to the NSIS PE installer. The file format of the archive, while not very documented, is\r\nrelatively simple as we will see.\r\nNSIS archive\r\nA NSIS archive is composed of a small NSIS header followed by the archive content. The header does not contain a lot of\r\ninformation:\r\nFirstHeader:\r\n Flags: // some installation flags\r\n Signature: 0xdeadbeef // NSIS archive start magic\r\n Magic: \"NullsoftInst\" // also magic\r\n InstallerSize: 0x6244 // unpacked size of the setup script\r\n ArchiveSize: 0x5e12e // size of the archive\r\nLike you can see, it does not tell us a lot. Directly following the headers come the \"files\". I say \"files\" because they don't\r\nreally have names, it is more like a list of data bytes or buffers. The files are compressed, and can be stored using two\r\nmodes:\r\nthe solid mode: archive content is a single compressed stream. The unpacked stream is a sequence of N buffers,\r\nwhere each buffer is prefixed by a DWORD telling the size of the buffer.\r\nthe non-solid mode: archive content is a sequence of N compressed streams, one for each file. Each compressed\r\nstream is prefixed by a DWORD telling the size of the stream.\r\nThere is sadly no flag in the header telling us which mode is used, this information is hardcoded inside the NSIS installer\r\nexecutable. The only solution there is trial and error: if the start of the archive starts with a DWORD which could be a size,\r\nthen it's most likely the non-solid mode. If it looks like a compression header, then it's most likely the solid mode. And\r\nregarding compression, NSIS supports three compression algorithms:\r\nLZMA (without headers)\r\nZlib\r\nA custom bzip2 compression algorithm\r\nMalcat supports NSIS files using both solid and non-solid mode for the Zlib and LZMA compression methods, but lacks\r\nsupport for bzip2, since the compression algorithm is custom. But since it's also the least used one, it's not really a big deal.\r\nThe NSIS archive we are looking at is a solid LZMA archive, so unpacking it is no issue. Like for most archive formats,\r\nMalcat lists found files in the upper left corner of the screen, under the Virtual File System tree. Double-clicking on a file\r\nopens it inside Malcat.\r\nFigure 2: Content of the archive\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 2 of 10\n\nThe first file is always the installer setup script, followed by user-provided files and/or installer plugins. As you can see,\r\nMalcat did give name to some of the files (all but the last one) which somehow contradicts what I said before. But these\r\nnames have been recovered by reversing the SETUP script, and there is no guarantee that it is the real name for these files.\r\nEven worse, a buffer in the archive can be extracted under different names on the local filesystem, so don't trust these names\r\n100%.\r\nThe SETUP script\r\nThe first thing to look at when reversing a NSIS installer is the setup script. NSIS scripts are a bunch of sections and\r\nassembly code written for the NSIS virtual machine. The NSIS VM architecture is relatively simple:\r\nEvery instruction is encoded on 7 DWORDs: first DWORD is for the opcode (about 70 different opcodes) and the\r\nother 6 DWORD encode arguments\r\nDepending on the opcode, arguments can be either:\r\na register (up to 31 registers): $0 .. $9 , $R0 .. $R9 or one of 11 specific registers like $EXEPATH or\r\n$CMDLINE (some are read-only, so more like constants)\r\na global variable: $var0 .. $varN\r\nan integer, signed or unsigned. It can also be an offset into the code section for jump-like opcodes\r\na string, more precisely an index into the Strings section of the setup script\r\nStrings themselves can be somewhat complex to parse/interpret:\r\nthere are 3 NSIS versions: ansi, park (a mix between ansi and unicode) and unicode. Each version encodes\r\nstrings differently. There is sadly no flag telling you which version is used.\r\nstrings can contains any of 4 special opcodes: skip , shell , var or lang\r\nstrings can include reference to system paths, variables or other strings, e.g. \"open\r\n{$INSTDIR}\\rampage\\goodie\\noticeably.tif\"\r\nLuckily for us, the full edition of Malcat features a NSIS disassembler / decompiler, so let us jump directly to the entry point\r\nof the script (Ctrl+E) and have a look at the OnInit method:\r\nFigure 3: NSIS setup script entry point\r\nWe can see that the script does the following:\r\nextract the first buffer (offset header+0 in archive) to a file named d54hdan9c9ebsx\r\nextract the second buffer (offset header+0x34f0f in archive) to a file named lognp\r\nextract the third buffer (offset header+0x36390 in archive) to ${PLUGINDIR}\\dwksh.dll , wherever that could be\r\ncall dwksh.dll's exported method sdvffryp without any argument\r\nThe rest of the method seems like junk code, judging by the strings which are either random letters or picked out of\r\ndictionary. Quickly inspecting the first two files tells us that both are encrypted and/or compressed, so no quick-win there.\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 3 of 10\n\nWe have to dig into the dll.\r\nStage 1: dwksh.dll\r\ndwksh.dll is a 294KB 32-bits DLL of sha256 be00a655cdf4e4706bf1bbf3659d698f967cad66acdf7cda0d12f16dc0cfda3e\r\n(VT). It contains several obfuscated methods. But we reversed the setup script and know what to look for: the method\r\nsdvffryp . This methods starts by reading a local file named lognp :\r\nFigure 4: lognp file is accessed\r\nIt then seems to decrypt it in memory into an executable buffer before jumping at the beginning of the buffer (see the call\r\neax below?). The file lognp is relatively small (5KB), it definitely looks like a shellcode.\r\nFigure 5: decryption loop in method sdvffryp\r\nThe decryption is pretty straightforward according to the sleigh decompiler. Every byte seem to be decrypted using the\r\nfollowing formula:\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 4 of 10\n\nbyte[i] = ((((byte[i] - 3) ^ 0xf2) + 0x11) ^ 0x28) - 1\r\nDecrypting the lognp file should be a piece of cake. Just open the file inside Malcat, select every byte (Ctrl+A) and open\r\nthe transform dialog (Ctrl+T). There you can chose the custom arithmetic transform which allows you to transform\r\nsequence of bytes/words/dwords using a custom python expression. Just paste the equation above, replacing byte[i] by\r\nvalue and voila, you've just decrypted the second stage.\r\nFigure 6: decrypting the shellcode\r\nFor the lazy readers, you can download the decrypted lognp file here (password: infected).\r\nStage2: obfuscated shellcode\r\nAnalyzing the shellcode\r\nThe lognp file, once decrypted, does not appear to be in any known file format. But the first byte is E9 , which is a jump\r\nin the x86 architecture and is very typical for shellcode prologs. So before starting the analysis, we will have to tell Malcat\r\ntwo things:\r\nthe architecture used: x86 in our case. This can be set using the dropdown menu in the status bar\r\nthe entry point of the shellcode, which is at address 0 in our case. We just have to define a new function start at this\r\naddress using the context menu in disassembly mode (F3)\r\nAfter this, Malcat is smart enough to recover most of the shellcode's CFG using its usual set of analyses. Following the\r\ncontrol flow, we quickly arrive in the function sub_7dd which contains interesting patterns:\r\nthe string d54hdan9c9ebsx (one of the NSIS archive's file names) is pushed on the stack at address 0x8eb\r\nsomething like a CreateFileA API call is performed at address 0x989 (the constant 0x80000000 is most likely for\r\nGENERIC_READ ). If we wanted to be sure, we would have to emulate the API lookup function at address 0x776 , but\r\nit looks like safe assumption.\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 5 of 10\n\nsoon after, the function sub_a01 gets called. Decompiling this functions reveals something similar to a decryption\r\nloop\r\nThe whole process can be retraced in the animated GIF below:\r\nFigure 7: locating the decryption function inside the shellcode\r\nThe code of the decryption function is given below. It is obviously obfuscated, and sadly it would not be immediate to\r\nreimplement it in python in Malcat. So we will have to find an alternative. Since the decryption function prototype is very\r\nsimple (it just needs a pointer to the buffer and the buffer size) and is without side effects, why not give emulation a go?\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\nBYTE* sub_a01(BYTE* buffer, uint4 size) {\r\n uint1 uVar1;\r\n char cVar2;\r\n uint1 uVar3;\r\n uint4 i;\r\n i = sub_0;\r\n while (i \u003c size) {\r\n uVar3 = i;\r\n uVar1 = -uVar3 - ((*(buffer + i) \u003e\u003e 1 | buffer[i] \u003c\u003c 7) - 0x40 ^ 0xf2);\r\n uVar1 = -uVar3 - (uVar1 \u003e\u003e 7 | uVar1 * '\\x02');\r\n uVar1 = -uVar3 - (((uVar1 \u003e\u003e 3 | uVar1 * ' ') ^ uVar3) - uVar3 ^ 0x9c) ^ 0xd6;\r\n cVar2 = ~((uVar1 \u003e\u003e 7 | uVar1 \u003c\u003c 1) + 0x34 ^ 0x87) - 0x10;\r\n uVar1 = ~(((-cVar2 \u003e\u003e sub_5 | cVar2 * -8) ^ 0x1d) + 0xac) ^ 0x5e;\r\n uVar1 = ~-(((0x99 - ((uVar1 \u003e\u003e 2 | uVar1 \u003c\u003c 6) + 0x49) ^ 0xa0) + 0x30 ^ 0x34) + uVar3);\r\n uVar1 = (-uVar1 \u003e\u003e 6 | uVar1 * -4) - uVar3 ^ uVar3;\r\n uVar1 = (-uVar1 \u003e\u003e 2 | uVar1 * -0x40) + 0x93;\r\n uVar1 = (-((((uVar1 \u003e\u003e sub_5 | uVar1 * '\\b') - 0x2e ^ 7) + 0xd ^ 0x96) + 0x31) ^ 0x73) + uVar3;\r\n uVar1 = -uVar3 - ((uVar1 \u003e\u003e 2 | uVar1 * '@') + 0x61) ^ uVar3;\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 6 of 10\n\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n uVar1 = ~((uVar1 \u003e\u003e 3 | uVar1 \u003c\u003c sub_5) ^ uVar3);\r\n uVar1 = (uVar1 \u003e\u003e 7 | uVar1 \u003c\u003c 1) + uVar3 ^ 0x2e;\r\n uVar1 = ~(~((uVar3 - (~(~(-(0xbc - ((uVar1 \u003e\u003e 6 | uVar1 \u003c\u003c 2) - uVar3) ^ 0x1e) ^ 0xc5) ^ 0x46) ^ 0xc1) ^ 0x4c)\r\n uVar3) ^ 0x4d) + 0x4c ^ uVar3;\r\n uVar3 = 0x2d - (-((uVar1 \u003e\u003e 3 | uVar1 \u003c\u003c sub_5) + uVar3) ^ 0x43);\r\n buffer[i] = (uVar3 \u003e\u003e 7 | uVar3 * '\\x02') + 0x15;\r\n i = i + 1;\r\n }\r\n return buffer;\r\n}\r\nEmulating the decryption function\r\nTo emulate shellcodes, Malcat comes bundled with a script named speakeay_shellcode.py which emulates shellcodes\r\nusing the Speakeasy emulator. Note that Speakeasy is not bundled with Malcat, you will have to install the python package\r\nyourself (and if you are running Malcat under Windows, be sure to check Use system python interpreter in the options).\r\nPatching lognp\r\nBefore emulating anything, we need to solve a problem: the data to decrypt ( d54hdan9c9ebsx ) is not embedded in the\r\nlognp shellcode, it is read from the filesystem using CreateFileA . So emulation is likely to fail. How are we going to\r\nsolve this issue?\r\nThere is the clean way: we could hook the CreateFileA/ReadFile APIs in speakeasy and intercept the call to give back the\r\ncontent of the file d54hdan9c9ebsx .\r\nBut there is also the dirty way: we could patch the decrypted lognp shellcode in order to embed the content of\r\nd54hdan9c9ebsx in the shellcode space and patch the shellcode entry point to perform a call to the decryption function with\r\nthe right parameters. Of course we will chose the dirty way. It is not only way faster, it is also more fun.\r\nHere is how to proceed:\r\n1. First open a copy of the decrypted lognp shellcode in Malcat with extra space at the end of the file (File \u003e Open\r\nCopy of File). The file d54hdan9c9ebsx is 216843 bytes big, we'll append 300KB just to be sure.\r\n2. Copy the content of the file d54hdan9c9ebsx in the clipboard: in a second Malcat instance, open d54hdan9c9ebsx\r\nand then hit Ctrl+A followed by Ctrl+C\r\n3. Paste the copied data after the shellcode in the first Malcat instance, let's say at address 0x2000 to make it easy to\r\nremember\r\n4. Enter disassembly view (F3) and go to the shellcode's entry point at address 0\r\nMalcat does not (yet) support assembling your own instruction, so we will need to manually edit the machine code. Click on\r\nany hexa byte in disasm mode and enter edit mode (Insert key). We need to assemble the following code:\r\n1. Push the second parameter which is the size of the buffer to decrypt (216843 = 0x34F0B). push uint32 is\r\nassembled using 0x68 + LSB-encoded uint32 in x86: 68 0B 4F 03 00\r\n2. Push the first parameter which is the address of the buffer to decrypt ( 0x2000 ): 68 00 20 00 00\r\n3. Call to the decryption function. The call opcode is 0xE8 + signed displacement starting from the end of the call\r\nopcode. The end of our call opcode is at address 0x000F , we want to jump to 0x0A01 , so 0x0A01 - 0x000F =\r\n0x09f2 . We need to assemble E8 F2 09 00 00 .\r\nYou can use Malcat's calculator to perform quick computation while analysing a binary, just hit Ctrl+Space.\r\nInternally, it uses the python interpreter, so use python syntax.\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 7 of 10\n\nAt the end, the patched shellcode should look like in the picture below. For the lazy readers, you can download the patched\r\nlognp file here (password: infected).\r\nFigure 8: patching the shellcode\r\nRunning speakeasy\r\nNow the only thing we have to do is to let speakeasy do its magic:\r\nlet us define the entry point: right-click at address 0 and chose Force function start in the context menu\r\nrun the script speakeasy_shellcode.py (Ctrl+U to run user scripts)\r\n... and voila, Malcat should open the result in a new file. A PE file has been detected by Malcat's file format parser at address\r\n0x2000, perfect! Just double-click the PE file under \"Carved Files\" to open it.\r\nFigure 9: decrypted d54hdan9c9ebsx\r\nStage 3: Lokibot and config extraction\r\nThe last (and final) stage we get is a PE file of sha 02dee91a81652e5234414e452622307fd61e7988e82bec43665f699f805c3151\r\n(VT). Judging by the low entropy and the visible strings, the file does not seem to be obfuscated, good news. So which kind\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 8 of 10\n\nof malware do we face? Malcat's Yara rules already spotted one of main malware intent: stealing credentials, as we can see\r\nin the screenshot below:\r\nFigure 10: file summary, displaying matching Yara rules\r\nIf we want to be more precise, we can use Malcat's online intelligence view (Ctrl+I, only for paid versions). Normally I\r\nwould avoid using Virustotal to identify a malware family (because of packer reuse among threat actors). But here we are\r\ndealing with the plain text final malware, so we should get at least some valid labels. In our case, it seems to be Lokibot, a\r\nsimple password stealer:\r\nFigure 11: querying online intelligence\r\nCan we go further? The last section of the PE file is weirdly named \".x\" . It contains a single method at address 0x4a0000\r\nand a few bytes of referenced data at address 0x4a0074 . Looking at the function, it seems to decode the data using a XOR\r\nopcode, with the key 0xDDDDFFFF . But actually, only the first byte of the key is used ( 0xFF ), so it is strictly equivalent to\r\nperforming a simple NOT on the data. Great, let us decrypt these few bytes using Malcat's transform:\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 9 of 10\n\nFigure 12: decrypting the data buffer in the .x section\r\nGreat, we got the address of the command and control server for this sample. This was a pretty easy catch ^^\r\nConclusion\r\nNSIS installers have been abused by malware authors for some years now. While the NSIS VM instruction set is relatively\r\nlimited, DLL plugins allow malicious actors to extend installer capabilities and obfuscate malware. In this example, two\r\nlayers of shellcodes were used by the NSIS installer in order to deliver its final payload: a LokiBot password stealer.\r\nInstead of running everything in a VM, we made great use of Malcat's NSIS disassembler, Malcat's transforms and\r\nspeakeasy emulator in order to quickly unpack these two layers statically.\r\nWe hope you enjoyed this new quick-and-dirty malware unpacking sessions. Future blog posts will be more focused toward\r\nbeginners as we will introduce a few of Malcat's features as in-depth tutorials.\r\nSource: https://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nhttps://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://malcat.fr/blog/reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation/"
	],
	"report_names": [
		"reversing-a-nsis-dropper-using-quick-and-dirty-shellcode-emulation"
	],
	"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
		},
		{
			"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": 1775434415,
	"ts_updated_at": 1775826717,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/7f29f48d8bbeeaf2766c3095989cd9e3884028c6.pdf",
		"text": "https://archive.orkl.eu/7f29f48d8bbeeaf2766c3095989cd9e3884028c6.txt",
		"img": "https://archive.orkl.eu/7f29f48d8bbeeaf2766c3095989cd9e3884028c6.jpg"
	}
}