{
	"id": "9fe055c8-24e7-462f-adb8-b082d91d1f7c",
	"created_at": "2026-04-06T00:16:55.187335Z",
	"updated_at": "2026-04-10T03:20:41.902271Z",
	"deleted_at": null,
	"sha1_hash": "011c9faf81aa9bc464b493efc26553717e8eba27",
	"title": "Statically unpacking a simple .NET dropper",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1737852,
	"plain_text": "Statically unpacking a simple .NET dropper\r\nBy Malcat EI\r\nArchived: 2026-04-05 19:01:02 UTC\r\nSample:\r\n15180ee9f6a8682b24a0d5cb0491bb4e09d457bfab5a24ec1fcb077dab59773b (Bazaar, VT)\r\nInfection chain:\r\n.NET dropper -\u003e .NET dropper + Reflective DLL -\u003e Loki\r\nDifficulty:\r\nEasy\r\nIntroduction\r\nToday we will try to unpack a simple 2-layers .NET dropper using static analysis only. The goal of most malware\r\npacker/obfuscator is not to be hard to crack: it is to circumvent AV detection for a while, and eventually get\r\nreplaced by a new one afterwards. And at the very end of the packer food chain are packers written in VB, .NET\r\nand AutoIT: they are particularly cheap and easy to crack. The sample we are about to analyse is no exception and\r\nwill make a good introduction to Malcat's decryption algorithms.\r\nA quick glance at the file metadata tells us immediately that the file is suspicious. A VB.NET application from\r\nMicrosoft with a 2013 copyright but freshly compiled... sure, those version informations are 100% not fake.\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 1 of 10\n\nFigure 1: Fake version information\r\nLet us cut the overview right there as we will directly focus on the packed payload.\r\nLocating the payload\r\nMost .NET packers embed one or more encrypted assemblies. .NET assemblies are not small, they have to be put\r\nsomewhere. They are usually put inside .NET resources (sometimes insides pictures), .NET static arrays or\r\nstrings. For this sample, Malcat has already spotted a 800KB+ hexadecimal string inside the program\r\n(HugeStringHexa), which is kind of unusual.\r\nFigure 2: A look at the anomalies\r\nThis is confirmed in the Strings view (shortcut: F6), which tells us than more than 90% of the file is made of\r\nstrings, and that our big hexadecimal string is by far the biggest one (the size 412768 is given in characters, so\r\nactual size for UTF16 is twice as much, about 824KB). Moreover, it has exactly one code reference, which is\r\nalways a good indicator for packed data:\r\nFigure 3: Big hexa string\r\nIf we follow the string reference in the Code view (right-click on the string, and then choose Cross-references sub-menu) we land on the code snippet presented below. By looking at the names of the method and package there, we\r\ncan infer that the application we are analyzing was most likely a clean .NET software that has been only slightly\r\nmodified to include a couple of malicious methods. This is a technique commonly used by obfuscators to evade\r\nAV heuristics.\r\nThe content of the method also tells us that we won't have to start our VM for now. In fact, the hexadecimal string\r\nseems to be decrypted using a simple XOR algorithm using the key \"wnhILKQcVU\" :\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 2 of 10\n\nFigure 4: String decryption\r\nDecrypting the first layer\r\nMalcat comes with several decryption algorithms which we will use on the string. First, right-click on the big hexa\r\nstring and chose the Transform... sub-menu. We will apply the following transformations (in order):\r\nchange text encoding from UTF-16le to UTF-8: we get an ascii hexadecimal string\r\nhex decode the hexadecimal string: we get the raw bytes\r\ndecrypt using the XOR algorithm and the key \"wnhILKQcVU\"\r\nAfter these three pass, we obtain ... a base64 string, so the job is still not finished. Using Malcat's transformations,\r\nwe can easily decode the base64 string. The result is identified by Malcat as a ... GZIP archive. Sure, after\r\nencoding your payload in hexa and base64, now you start to care about storage efficiency. But ok, Malcat can\r\nhandle GZIP archives just fine. Just double-click the content stream inside the files tab to finally obtain ... a\r\nnew PE file!\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 3 of 10\n\nFigure 5: Unpacking the first layer\r\nAt this point we can discard the rest of the application: the payload we just decrypted made for more than 90% of\r\nthe file and the packer authors cared enough to pack it several time. So it's pretty safe to assume that we got\r\neverything there was to see there.\r\nDecrypting the second layer\r\nThe second layer is also a .NET executable which also contains stolen VersionInformations (claims to be\r\nWallpaperChanger.dll). This time, there seem to be more than one packed content:\r\nwe see a high-entropy .net resource named Tesla of about 60Kb\r\none big base64 string of about 185Kb at offset 0x100131da\r\ntwo small hexadecimal strings of ~100 bytes\r\nThe rest of the application seem to be a clean app, with a few added malicious methods inside the class\r\nWallpaperChanger.QsJAksvOJQZGMrkQGUrJCZfDxJspOiApOTEDEDQQQBBEDh . So we will save us some time and not\r\nanalyze the code, and instead focus on the packed data: the big resource and the big base64 string. Let us start\r\nwith the resource.\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 4 of 10\n\nFigure 6: Second layer overview\r\nWhen adding a resource to a .NET program under VisualStudio, a standard resource getter name\r\nget_\u003cresource_name\u003e is often created. So we will go into the symbols list (shortcut: F5), hit Ctrl+F and look for\r\nTesla . There is exactly one method named WallpaperChanger.Properties.Resources.get_Tesla at offset\r\n0x1000278c . The getter has only one code reference at address 0x100026dc which looks promising:\r\nFigure 7: the method decrypting resource + strings\r\nWe see two different decryption methods called there:\r\nthe method AESDecrypt used to decrypt the .NET resource Tesla\r\nthe method RijndaelDecrypt used to decrypt the two small base64 strings we spotted earlier.\r\nThe big base64 string does not seem to be decrypted there. Since the small strings seem to be of little interest, let\r\nus focus on the method AESDecrypt first.\r\nDecrypting the Tesla resource\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 5 of 10\n\nIt looks like the authors of the packer were not satisfied with the security offered by XOR encryption and chose to\r\nstep up their game:\r\nFigure 8: the method AESDecrypt\r\nThe code is pretty straightforward: the string \"eرбF开ق艾A私اвдPءぎ迪\" is first encoded in utf16-BE and then\r\nhashed using the SHA256 algorithm. The result will be used as KEY for the AES algorithm. No IV is defined,\r\nsince the encryption mode is set to ECB. At the end, the resource content is decrypted using AES. We could easily\r\nrecover the decrypted content using a debugger there, but since the code is pretty straightforward, we can also do\r\neverything statically inside Malcat. First, we need to compute the AES key. We can simulate what the code is\r\ndoing using the following script:\r\nimport hashlib\r\nraw_bytes = \"eرбF开ق艾A私اвдPءぎ迪\".encode(\"utf-16-be\")\r\nprint(hashlib.sha256(raw_bytes).hexdigest())\r\n# -\u003e \"ab6edf45e299a7b2968a9d7cd013c1164efc6165508d691f085b7d9462ee945b\"\r\nHit F8 to enter the script editor, remove the example script, paste this content and you will see the result in the\r\noutput window. Copy the key in the clipboard and you are ready to decrypt the resource using Malcat's AES\r\ntransform:\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 6 of 10\n\nFigure 9: decrypting the Tesla resource\r\nWhat we get is a reflexive PE injector .NET DLL rightly named RunPE.dll . This is the kind of utility assembly\r\nwhich is used by dropper to inject their payload into a running process. Interesting, but it's definitely not our\r\npayload.\r\nDecrypting the base64 string\r\nOur next payload candidate is the big 185kb base64-encoded string located at address 0x100131da . There is\r\nagain only on code location referencing this string at address 0x1000208c . We can see that the string is decrypted\r\nusing the method RijndaelDecrypt this time using the key \"wnhILKQcVU\" . This is the same key which was used\r\nin the first layer for the XOR encryption.\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 7 of 10\n\nFigure 10: the RijndaelDecrypt method\r\nThis time the block cipher is used in CBC mode (the default in .NET) and the key generation is based on the\r\nRfc2898 (aka PBKDF2) algorithm. If we have a look at the offical documentation, we can see that the constructor\r\nof the class Rfc2898DeriveBytes takes two inputs:\r\na key, which in our case would be the string \"wnhILKQcVU\" (encoded in UTF-8 by default, since no\r\nencoding is specified)\r\na salt, which looks like a 8 bytes array initialized with the value of the field\r\nDD5783BCF1E9002BC00AD5B83A95ED6E4EBB4AD5\r\nThe class Rfc2898DeriveBytes is then used to generate a given number of bytes (32 and then 16 in this case)\r\nwhich are used as key and IV for the cipher. Regarding the Rijndael algorithm, we can see that in the .NET core\r\nimplementation, it defaults to AES256. This is good news for us, this means that the only thing we have to figure\r\nout is how to generate the key and IV. Again, we could debug the sample, but where is the fun in that? We will\r\nrewrite it in python instead.\r\nFirst thing first, we have to retrieve the salt value (an 8 bytes array) which is located in the field\r\nDD5783BCF1E9002BC00AD5B83A95ED6E4EBB4AD5 . By clicking on the field in the Code view, we can see its\r\ndefinition in the FieldTable structure. This field has three important flags set: HasRVA , Static and\r\nInitOnly which indicates that this is a static initialized variable. Also the HasRVA flag tells us that the field has\r\nan entry inside the .NET FieldRVA table.\r\nFigure 11: the field holding the salt value\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 8 of 10\n\nThe FieldRVA table has only one entry for field number 0x15 (aka 21) which is our field (the field\r\nDD5783BCF1E9002BC00AD5B83A95ED6E4EBB4AD5 is at index 20 aka 0x14 in the FieldTable , but Field references\r\nstart at 1 because 0 is reserved).\r\nFigure 12: the corresponding FieldRVA entry\r\nThe format of the data stored depends on the field type (and whether or not a ClassLayout exists). But we are\r\ndealing with a very simple 8 bytes array here, so reading the initial value is very simple: it is { 1, 2, 3, 4, 5,\r\n6, 7, 8 } , our salt.\r\nNext, we need to emulate the behavior of the class Rfc2898DeriveBytes . We will use the Cryptodome python\r\npackage which comes bundled with Malcat and its PBKDF2 algorithm. Go into the script editor (shortcut: F8) and\r\npaste the following code:\r\nfrom Cryptodome.Protocol.KDF import PBKDF2\r\npwd = \"wnhILKQcVU\".encode(\"utf8\")\r\nsalt = bytes(range(1, 9)) # content of DD5783BCF1E9002BC00AD5B83A95ED6E4EBB4AD5\r\ndata = PBKDF2(pwd, salt, 32 + 16)\r\nprint(data[:32].hex()) # the first 32 bytes are used for the key\r\n# -\u003e \"34ca280dd207ea1e1915f7ccdc5d59344c55c6863947e507e982a337bdc57742\"\r\nprint(data[32:].hex()) # the next 16 bytes are used for the IV\r\n# -\u003e \"be77bdd5564bbc0c4da984f89d88213d\"\r\nNow that we know both the key and the IV, we can decrypt the string at offset 0x100131da using the usual steps:\r\nRight-click on the string from the code view or the strings view and chose Transform..\r\nChange encoding from utf16 to utf8\r\nBase64 decode the result\r\nAES decrypt the result in CBC mode using the key and IV found above\r\nBase64 decode the result ...\r\nExtract the GZipped content ......\r\nWe get a new PE file!\r\nThe PE file looks like a native infostealer and is detected as Loki on VirusTotal. While a lot of its content is in\r\nplain text, some strings and configurations are still encrypted. The decryption process may be the subject of\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 9 of 10\n\nanother blog post.\r\nFigure 13: the final payload: Loki infostealer\r\nConclusion\r\nWe have seen how to navigate inside a .NET program, look for possible payload locations and how to use the\r\ndifferent decryption algorithms inside Malcat to extract the stages of the malware. We also introduced the python\r\nscript engine of Malcat, even if we just scratched the surface there (a scripting example which makes use of the\r\nbindings will be the subject of a future blog post).\r\nStatically unpacking a sample, while more complicated than debugging, offer many advantages:\r\nwe get better quality dumps\r\nwe don't care about anti-debugging and anti-sandboxing tricks\r\nthe scripts which were developed can be reused on other samples in the future\r\nit forces us to better understand the packing logic, and makes us less likely to miss something\r\nI hope you enjoyed this first tutorial, feel free to share with us your remarks or suggestions!\r\nSource: https://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nhttps://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://malcat.fr/blog/statically-unpacking-a-simple-net-dropper/"
	],
	"report_names": [
		"statically-unpacking-a-simple-net-dropper"
	],
	"threat_actors": [],
	"ts_created_at": 1775434615,
	"ts_updated_at": 1775791241,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/011c9faf81aa9bc464b493efc26553717e8eba27.pdf",
		"text": "https://archive.orkl.eu/011c9faf81aa9bc464b493efc26553717e8eba27.txt",
		"img": "https://archive.orkl.eu/011c9faf81aa9bc464b493efc26553717e8eba27.jpg"
	}
}